本文研究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.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網路中裝置的認證鑑權過程,還深入研究了其原地阿瑪實現。