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应当提供的服务:

img

可以看到,除了基本的设备鉴权服务(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的设计文档描绘了为其设计的软件架构(函数调用关系图):

img

整个图里面我们需要重点关注的函数是红框中的三个函数,它们才是实际解决鉴权问题的函数,其他都是简单处理或者套壳调用,而这三个函数都定义在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里定义了各种类型,其中最值得留意的自然是AUSFContextAusfUeContext,前者存储整个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网络中设备的认证鉴权过程,还深入研究了其源代码实现。

posted @ 2024-10-01 18:07  zrq96  阅读(60)  评论(0编辑  收藏  举报