Free5GC源码研究(4) - AUSF研究
本文研究AUthentication Server Function (AUSF) 主要实现的功能
AUSF的概念
在开始研究其源代码之前,我们需要先对AUSF有一些概念上的认识。AUSF的主要技术文档是TS29.509,规定了AUSF提供哪些服务,其对应的API接口有哪些。总的来说,AUSF就是帮助其他NF,比如AMF,来认证一个接入设备。然而整个鉴权过程比较复杂,AUSF在其中起到主导作用,但也会需要其他NF,比如UDM的协助。
我们熟悉的用户鉴权过程都是像提供一个用户密码、提供一个手机验证码,或者一个实物证件这样的,来证明我是我自己。而在通信网络中鉴权过程对我们来说则略显陌生,用的是一种叫AKA(Authentication and Key Agreement)的方式:它要求接入设备计算一个aka-challenge,而只有真正的设备才能准确算出这个aka-challenge,如此一来就完成了鉴权过程。这个过程本质上还是用密钥加密解密的过程。整个鉴权过程包含了而至少两次相互通信,第一次通信是设备向网络提供自己的身份信息(就像输入用户名),网络端在数据库里找到设备的相关数据后返回一个aka-challenge;第二次通信是设备向网络发送自己对aka-challenge解出来的RES,网络端判断这个RES是否正确,如果正确则返回鉴权成功信息,否则返回鉴权失败信息。
能完成以上鉴权过程的具体协议至少有两种,一种是EAP-AKA协议,目前最新版的文档是RFC9048;一种是5G-AKA协议,定义在TS33.401文档里。EAP-AKA是一个较为通用的认证协议,而5G-AKA则是专门为5G网络设计的协议,提供了更强的保护和更优的体验。考虑到了用户和设备的多样性。一些老旧设备可能只支持EAP-AKA,而新设备则可能支持5G-AKA,5G标准要求同时支持这两种协议,以确保网络的顺利迭代、提供不同级别的安全保护、和满足不同用户及设备的需求。TS29.509定义了AUSF应当提供的服务:
可以看到,除了基本的设备鉴权服务(ue-authentications)以外,AUSF还应该提供SOR和UPU的保护,也就是提供消息认证和完整性保护机制,这些服务确保了5G网络在漫游和用户面参数更新等关键操作中的安全性和可靠性,对于维护5G网络的稳定运行和保护用户隐私至关重要。然而目前的v3.4.3版本中free5gc/ausf@v
1.2.3尚未实现这些功能。
// https://github.com/free5gc/ausf/blob/v1.2.3/internal/sbi/api_sorprotection.go func (s *Server) SupiUeSorPost(c *gin.Context) { c.JSON(http.StatusNotImplemented, gin.H{}) }
// https://github.com/free5gc/ausf/blob/v1.2.3/internal/sbi/api_upuprotection.go func (s *Server) SupiUeUpuPost(c *gin.Context) { c.JSON(http.StatusNotImplemented, gin.H{}) }
因此,我们只需聚焦于研究设备鉴权服务即可。
AUSF的实现
AUSF的设计文档描绘了为其设计的软件架构(函数调用关系图):
整个图里面我们需要重点关注的函数是红框中的三个函数,它们才是实际解决鉴权问题的函数,其他都是简单处理或者套壳调用,而这三个函数都定义在internal/sbi/processor/ue_authentication.go这一个文件里。但在深入研究这三个函数以前,有必要先瞅一眼internal/context/
里面定义的数据结构,因为我们在前文已经知道,Context类型都是NF整个证明周期里最核心的数据存储中心。
// https://github.com/free5gc/ausf/blob/v1.2.3/internal/context/context.go type AUSFContext struct { // other fields ...... suciSupiMap sync.Map UePool sync.Map UdmUeauUrl string EapAkaSupiImsiPrefix bool } type AusfUeContext struct { Supi string Kausf string Kseaf string ServingNetworkName string AuthStatus models.AuthResult UdmUeauUrl string // for 5G AKA XresStar string // for EAP-AKA' K_aut string XRES string Rand string EapID uint8 Resynced bool }
context.go里定义了各种类型,其中最值得留意的自然是AUSFContext
和AusfUeContext
,前者存储整个AUSF的重要数据,后者存储一次设备鉴权过程需要用到的数据。每一个设备都和一个AusfUeContext
对应,存储在AUSFContext.UePool
中。而AusfUeContext
中的设备ID都是SUPI(Subscription Permanent Identifier),然而为了保护用户隐私,5G网络允许设备在提供自身身份信息时发送SUCI(Subscription Concealed Identifier)。SUCI是一种隐藏或加密了的用户订阅标识符,网络端在收到SUCI后要还原成SUPI才能进行其他操作,而这SCUI及其还原后的SUPI则存储在AUSFContext.suciSupiMap
结构里,方便后续使用。
UeAuthPostRequestProcedure
简单了解过这两个Context类型后,我们开始聚焦UeAuthPostRequestProcedure
函数,也就是设备在鉴权过程第一步,访问/ue-authentications
时网络端的处理过程。下面是其大幅简化后的代码:
// https://github.com/free5gc/ausf/blob/v1.2.3/internal/sbi/processor/ue_authentication.go#L216 func (p *Processor) UeAuthPostRequestProcedure(c *gin.Context, updateAuthenticationInfo models.AuthenticationInfo) { supiOrSuci := updateAuthenticationInfo.SupiOrSuci result, err, pd := p.Consumer().GenerateAuthDataApi(udmUrl, supiOrSuci, authInfoReq) authInfoResult := *result ueid := authInfoResult.Supi ausfUeContext := ausf_context.NewAusfUeContext(ueid) ausf_context.AddAusfUeContextToPool(ausfUeContext) ausf_context.AddSuciSupiPairToMap(supiOrSuci, ueid) if authInfoResult.AuthType == models.AuthType__5_G_AKA { ausfUeContext.XresStar = authInfoResult.AuthenticationVector.XresStar // av5gAka := encode5gAka(authInfoResult.AuthenticationVector) responseBody.Var5gAuthData = av5gAka } else if authInfoResult.AuthType == models.AuthType_EAP_AKA_PRIME { ausfUeContext.XRES = authInfoResult.AuthenticationVector.Xres // encodedPktAfterMAC := encodePacketArray(authInfoResult.AuthenticationVector) responseBody.Var5gAuthData = base64.StdEncoding.EncodeToString(encodedPktAfterMAC) } responseBody.AuthType = authInfoResult.AuthType c.JSON(http.StatusCreated, responseBody) // 返回设备一个aka-challenge }
整个函数所做的事情就是生成一个aka-challenge (responseBody.Var5gAuthData)和对应的Xres/XresStar,把Xres存储在相应的ausfUeContext
里,然后把生成的aka-challenge返回给用户设备让它计算出一个解来。这里面生成aka-challenge的主要工作是用函数GenerateAuthDataApi
调用UDM来实际完成的。其具体的做法,还要等我们以后研究UDM在看。另外,整个鉴权过程的第一步还用到了很多在free5gc/openapi定义的类型,了解这些类型的细节能进一步提高我们的理解水平。
点击查看更多openapi里的类型
/* https://github.com/free5gc/openapi/blob/v1.0.8/models/model_authentication_info_request.go */ type AuthenticationInfo struct { SupiOrSuci string `json:"supiOrSuci" yaml:"supiOrSuci" bson:"supiOrSuci"` ServingNetworkName string `json:"servingNetworkName" yaml:"servingNetworkName" bson:"servingNetworkName"` ResynchronizationInfo *ResynchronizationInfo `json:"resynchronizationInfo,omitempty" yaml:"resynchronizationInfo" bson:"resynchronizationInfo"` TraceData *TraceData `json:"traceData,omitempty" yaml:"traceData" bson:"traceData"` } /* https://github.com/free5gc/openapi/blob/v1.0.8/models/model_ue_authentication_ctx.go */ type UeAuthenticationCtx struct { AuthType AuthType `json:"authType" yaml:"authType" bson:"authType"` Var5gAuthData interface{} `json:"5gAuthData" yaml:"5gAuthData" bson:"5gAuthData"` Links map[string]LinksValueSchema `json:"_links" yaml:"_links" bson:"_links"` ServingNetworkName string `json:"servingNetworkName,omitempty" yaml:"servingNetworkName" bson:"servingNetworkName"` } /* https://github.com/free5gc/openapi/blob/v1.0.8/models/model_authentication_info_request.go */ type AuthenticationInfoRequest struct { SupportedFeatures string `json:"supportedFeatures,omitempty" yaml:"supportedFeatures" bson:"supportedFeatures" mapstructure:"SupportedFeatures"` ServingNetworkName string `json:"servingNetworkName" yaml:"servingNetworkName" bson:"servingNetworkName" mapstructure:"ServingNetworkName"` ResynchronizationInfo *ResynchronizationInfo `json:"resynchronizationInfo,omitempty" yaml:"resynchronizationInfo" bson:"resynchronizationInfo" mapstructure:"ResynchronizationInfo"` AusfInstanceId string `json:"ausfInstanceId" yaml:"ausfInstanceId" bson:"ausfInstanceId" mapstructure:"AusfInstanceId"` } /* https://github.com/free5gc/openapi/blob/v1.0.8/models/model_authentication_info_result.go */ type AuthenticationInfoResult struct { AuthType AuthType `json:"authType" yaml:"authType" bson:"authType" mapstructure:"AuthType"` SupportedFeatures string `json:"supportedFeatures,omitempty" yaml:"supportedFeatures" bson:"supportedFeatures" mapstructure:"SupportedFeatures"` AuthenticationVector *AuthenticationVector `json:"authenticationVector,omitempty" yaml:"authenticationVector" bson:"authenticationVector" mapstructure:"AuthenticationVector"` Supi string `json:"supi,omitempty" yaml:"supi" bson:"supi" mapstructure:"Supi"` } /* https://github.com/free5gc/openapi/blob/v1.0.8/models/model_authentication_vector.go */ type AuthenticationVector struct { AvType AvType `json:"avType" yaml:"avType" bson:"avType" mapstructure:"AvType"` Rand string `json:"rand" yaml:"rand" bson:"rand" mapstructure:"Rand"` Xres string `json:"xres" yaml:"xres" bson:"xres" mapstructure:"Xres"` Autn string `json:"autn" yaml:"autn" bson:"autn" mapstructure:"Autn"` CkPrime string `json:"ckPrime" yaml:"ckPrime" bson:"ckPrime" mapstructure:"CkPrime"` IkPrime string `json:"ikPrime" yaml:"ikPrime" bson:"ikPrime" mapstructure:"IkPrime"` XresStar string `json:"xresStar" yaml:"xresStar" bson:"xresStar" mapstructure:"XresStar"` Kausf string `json:"kausf" yaml:"kausf" bson:"kausf" mapstructure:"Kausf"` }
Auth5gAkaComfirmRequestProcedure
当用户收到aka-challenge,计算出相应的Res后,就开始第二步向网络端确认自己的计算结果。网络端将对比用户计算的Res和之前生成的XRes,确认是否鉴权成功。如果设备使用的协议是5G-AKA协议,那么相应的处理函数就是Auth5gAkaComfirmRequestProcedure
,其简化版代码如下:
// https://github.com/free5gc/ausf/blob/v1.2.3/internal/sbi/processor/ue_authentication.go#L456 func (p *Processor) Auth5gAkaComfirmRequestProcedure(c *gin.Context, updateConfirmationData models.ConfirmationData, ConfirmationDataResponseID string, ) { var confirmDataRsp models.ConfirmationDataResponse ausfCurrentContext := ausf_context.GetAusfUeContext(currentSupi) // 对比收到的 RES* 和之前存起来的 XRES* if strings.EqualFold(updateConfirmationData.ResStar, ausfCurrentContext.XresStar) { // 鉴权成功,生成密钥 KSeaf ausfCurrentContext.AuthStatus = models.AuthResult_SUCCESS confirmDataRsp.AuthResult = models.AuthResult_SUCCESS success = true confirmDataRsp.Kseaf = ausfCurrentContext.Kseaf } else { // 鉴权失败 ausfCurrentContext.AuthStatus = models.AuthResult_FAILURE confirmDataRsp.AuthResult = models.AuthResult_FAILURE p.logConfirmFailureAndInformUDM(ConfirmationDataResponseID, models.AuthType__5_G_AKA, servingNetworkName, "5G AKA confirmation failed", ausfCurrentContext.UdmUeauUrl) } p.Consumer().SendAuthResultToUDM(currentSupi, models.AuthType__5_G_AKA, success, servingNetworkName, ausfCurrentContext.UdmUeauUrl) c.JSON(http.StatusOK, confirmDataRsp) }
点击查看更多openapi里的类型
/* https://github.com/free5gc/openapi/blob/v1.0.8/models/model_confirmation_data.go */ type ConfirmationData struct { ResStar string `json:"resStar" yaml:"resStar" bson:"resStar"` } /* https://github.com/free5gc/openapi/blob/v1.0.8/models/model_confirmation_data_response.go */ type ConfirmationDataResponse struct { AuthResult AuthResult `json:"authResult" yaml:"authResult" bson:"authResult"` Supi string `json:"supi,omitempty" yaml:"supi" bson:"supi"` Kseaf string `json:"kseaf,omitempty" yaml:"kseaf" bson:"kseaf"` }
EapAuthComfirmRequestProcedure
可见这个函数的处理逻辑还是比较简洁的,实际上未经简化的完整函数也只有60行代码。而如果设备使用的是EAP-AKA协议,那么对应的EapAuthComfirmRequestProcedure
函数就会相对来说更复杂点,其完整代码有173行,下面是简化版代码。
https://github.com/free5gc/ausf/blob/v1.2.3/internal/sbi/processor/ue_authentication.go#L36 func (p *Processor) EapAuthComfirmRequestProcedure(c *gin.Context, updateEapSession models.EapSession, eapSessionID string) { var eapSession models.EapSession ausfCurrentContext := ausf_context.GetAusfUeContext(currentSupi) eapOK := true // 如果当前认证状态已经是失败,则返回401错误 if ausfCurrentContext.AuthStatus == models.AuthResult_FAILURE { eapSession.AuthResult = models.AuthResult_FAILURE c.JSON(http.StatusUnauthorized, eapSession) return } // decodeEapAkaPrimePkt := 对 `updateEapSession.EapPayload` 的各种处理 switch decodeEapAkaPrimePkt.Subtype { case ausf_context.AKA_CHALLENGE_SUBTYPE: XMAC := CalculateAtMAC(K_aut, decodeEapAkaPrimePkt.MACInput) MAC := decodeEapAkaPrimePkt.Attributes[ausf_context.AT_MAC_ATTRIBUTE].Value XRES := ausfCurrentContext.XRES RES := hex.EncodeToString(decodeEapAkaPrimePkt.Attributes[ausf_context.AT_RES_ATTRIBUTE].Value) if !bytes.Equal(MAC, XMAC) { eapOK = false } else if XRES == RES { // 鉴权成功,生成密钥 KSeaf eapSession.KSeaf = ausfCurrentContext.Kseaf eapSession.Supi = currentSupi eapSession.AuthResult = models.AuthResult_SUCCESS p.Consumer().SendAuthResultToUDM(eapSessionID, models.AuthType_EAP_AKA_PRIME,true, servingNetworkName, ausfCurrentContext.UdmUeauUrl) ausfCurrentContext.AuthStatus = models.AuthResult_SUCCESS } else { eapOK = false } default: ausfCurrentContext.AuthStatus = models.AuthResult_FAILURE } } if !eapOK { logger.AuthELog.Warnf("EAP-AKA' failure: %s", eapErrStr) p.Consumer().SendAuthResultToUDM(eapSessionID, models.AuthType_EAP_AKA_PRIME, false, servingNetworkName, ausfCurrentContext.UdmUeauUrl) ausfCurrentContext.AuthStatus = models.AuthResult_FAILURE eapSession.AuthResult = models.AuthResult_ONGOING } else if ausfCurrentContext.AuthStatus == models.AuthResult_FAILURE { p.Consumer().SendAuthResultToUDM(eapSessionID, models.AuthType_EAP_AKA_PRIME, false, servingNetworkName, ausfCurrentContext.UdmUeauUrl) eapSession.AuthResult = models.AuthResult_FAILURE } c.JSON(http.StatusOK, eapSession) }
可以看到EAP协议中设备发送的请求和网络返回的回复都是eapSession
。网络拿到一个eapSession
后要首先检查一下ausfCurrentContext.AuthStatus
是否已经被设置为failed,如果是的话直接返回鉴权失败,因为这意味着设备尝试对一个aka-challenge进行多次求解。此后,将eapSession.EapPayload
中的内容提取出来,分别会很对其MAC与XMAC,RES与XRES,如果都成功,则鉴权通过。
EAP-AKA之所以看起来比5G-AKA复杂,是因为它需要适应更广泛的应用场景和网络环境,同时提供更多的安全和隐私保护选项。而5G-AKA作为专为5G设计的协议,虽然在安全性上进行了增强,但其设计目标更为集中,因此在实现上可能显得更为精简和高效。
点击查看更多与EAP-AKA相关的类型
/* https://github.com/free5gc/openapi/blob/v1.0.8/models/model_eap_session.go */ type EapSession struct { // contains an EAP packet EapPayload string `json:"eapPayload" yaml:"eapPayload" bson:"eapPayload"` KSeaf string `json:"kSeaf,omitempty" yaml:"kSeaf" bson:"kSeaf"` Links map[string]LinksValueSchema `json:"_links,omitempty" yaml:"_links" bson:"_links"` AuthResult AuthResult `json:"authResult,omitempty" yaml:"authResult" bson:"authResult"` Supi string `json:"supi,omitempty" yaml:"supi" bson:"supi"` } // https://github.com/free5gc/ausf/blob/v1.2.3/internal/context/context.go#L57 type EapAkaPrimeAttribute struct { Type uint8 Length uint8 Value []byte } // https://github.com/free5gc/ausf/blob/v1.2.3/internal/context/context.go#L63 type EapAkaPrimePkt struct { Subtype uint8 Attributes map[uint8]EapAkaPrimeAttribute MACInput []byte } // https://github.com/google/gopacket/blob/v1.1.19/layers/eap.go#L36 type EAP struct { BaseLayer Code EAPCode Id uint8 Length uint16 Type EAPType TypeData []byte } // https://github.com/google/gopacket/blob/v1.1.19/layers/base.go#L15 type BaseLayer struct { // Contents is the set of bytes that make up this layer. IE: for an // Ethernet packet, this would be the set of bytes making up the // Ethernet frame. Contents []byte // Payload is the set of bytes contained by (but not part of) this // Layer. Again, to take Ethernet as an example, this would be the // set of bytes encapsulated by the Ethernet protocol. Payload []byte }
在本文中我们粗略了解了5G网络中设备的认证鉴权过程,还深入研究了其源代码实现。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!