本文研究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/context/
里面定义的数据结构,因为我们在前文已经知道,Context类型都是NF整个证明周期里最核心的数据存储中心。
// https://github.com/free5gc/ausf/blob/v1.2.3/internal/context/context.gotype AUSFContext struct {// other fields ......suciSupiMap sync.MapUePool sync.MapUdmUeauUrl stringEapAkaSupiImsiPrefix bool
}type AusfUeContext struct {Supi stringKausf stringKseaf stringServingNetworkName stringAuthStatus models.AuthResultUdmUeauUrl string// for 5G AKAXresStar string// for EAP-AKA'K_aut stringXRES stringRand stringEapID uint8Resynced 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.SupiOrSuciresult, err, pd := p.Consumer().GenerateAuthDataApi(udmUrl, supiOrSuci, authInfoReq)authInfoResult := *resultueid := authInfoResult.SupiausfUeContext := 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.AuthTypec.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.ConfirmationDataResponseausfCurrentContext := ausf_context.GetAusfUeContext(currentSupi)// 对比收到的 RES* 和之前存起来的 XRES*if strings.EqualFold(updateConfirmationData.ResStar, ausfCurrentContext.XresStar) {// 鉴权成功,生成密钥 KSeaf ausfCurrentContext.AuthStatus = models.AuthResult_SUCCESSconfirmDataRsp.AuthResult = models.AuthResult_SUCCESSsuccess = trueconfirmDataRsp.Kseaf = ausfCurrentContext.Kseaf} else {// 鉴权失败ausfCurrentContext.AuthStatus = models.AuthResult_FAILUREconfirmDataRsp.AuthResult = models.AuthResult_FAILUREp.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.EapSessionausfCurrentContext := ausf_context.GetAusfUeContext(currentSupi)eapOK := true// 如果当前认证状态已经是失败,则返回401错误if ausfCurrentContext.AuthStatus == models.AuthResult_FAILURE {eapSession.AuthResult = models.AuthResult_FAILUREc.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].ValueXRES := ausfCurrentContext.XRESRES := 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.KseafeapSession.Supi = currentSupieapSession.AuthResult = models.AuthResult_SUCCESSp.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_FAILUREeapSession.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 packetEapPayload 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 uint8Length uint8Value []byte
}// https://github.com/free5gc/ausf/blob/v1.2.3/internal/context/context.go#L63
type EapAkaPrimePkt struct {Subtype uint8Attributes map[uint8]EapAkaPrimeAttributeMACInput []byte
}// https://github.com/google/gopacket/blob/v1.1.19/layers/eap.go#L36
type EAP struct {BaseLayerCode EAPCodeId uint8Length uint16Type EAPTypeTypeData []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网络中设备的认证鉴权过程,还深入研究了其原地阿玛实现。