本文分析 Free5gc UE 安全鉴权流程
1. AMF 向 AUSF 发起 Nausf_UE_Authentication 请求
Authentication
--> AuthenticationProcedure
--> SendUEAuthenticationAuthenticateRequest
func SendUEAuthenticationAuthenticateRequest(ue *amf_context.AmfUe,
resynchronizationInfo *models.ResynchronizationInfo) (*models.UeAuthenticationCtx, *models.ProblemDetails, error) {
configuration := Nausf_UEAuthentication.NewConfiguration()
configuration.SetBasePath(ue.AusfUri)
client := Nausf_UEAuthentication.NewAPIClient(configuration)
amfSelf := amf_context.AMF_Self()
servedGuami := amfSelf.ServedGuamiList[0]
var authInfo models.AuthenticationInfo
authInfo.SupiOrSuci = ue.Suci
if mnc, err := strconv.Atoi(servedGuami.PlmnId.Mnc); err != nil {
return nil, nil, err
} else {
authInfo.ServingNetworkName = fmt.Sprintf("5G:mnc%03d.mcc%s.3gppnetwork.org", mnc, servedGuami.PlmnId.Mcc)
}
if resynchronizationInfo != nil {
authInfo.ResynchronizationInfo = resynchronizationInfo
}
ueAuthenticationCtx, httpResponse, err := client.DefaultApi.UeAuthenticationsPost(context.Background(), authInfo)
if err == nil {
return &ueAuthenticationCtx, nil, nil
} else if httpResponse != nil {
if httpResponse.Status != err.Error() {
return nil, nil, err
}
problem := err.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails)
return nil, &problem, nil
} else {
return nil, nil, openapi.ReportError("server no response")
}
}
AMF 收到注册请求,如果 AMF 不存在 UE 安全上下文,则发起验证流程,AMF 向 AUSF 发起 Nausf_UEAuthentications 流程,包括 SUCI/SUPI 或者服务网络名
Resource URI: {apiRoot}/nausf-auth/v1/ue-authentications
2. AUSF 处理 Authentication 请求
HTTPUeAuthenticationsPost
--> HandleUeAuthPostRequest
--> UeAuthPostRequestProcedure
func UeAuthPostRequestProcedure(updateAuthenticationInfo models.AuthenticationInfo) (*models.UeAuthenticationCtx,
string, *models.ProblemDetails) {
var responseBody models.UeAuthenticationCtx
var authInfoReq models.AuthenticationInfoRequest
supiOrSuci := updateAuthenticationInfo.SupiOrSuci
snName := updateAuthenticationInfo.ServingNetworkName
2.1 根据服务网络名,AUSF 决定是否 AMF 有权发送此消息
如果服务网络未被授权,则 AUSF 应使用 SERVING_NETWORK_NOT_AUTHORIZED “原因”。 但是这里比较简单,只是正则表达式匹配 5G:mnc[0-9]{3}[.]mcc[0-9]{3}[.]3gppnetwork[.]org
snName := updateAuthenticationInfo.ServingNetworkName
servingNetworkAuthorized := ausf_context.IsServingNetworkAuthorized(snName)
if !servingNetworkAuthorized {
var problemDetails models.ProblemDetails
problemDetails.Cause = "SERVING_NETWORK_NOT_AUTHORIZED"
problemDetails.Status = http.StatusForbidden
logger.UeAuthPostLog.Infoln("403 forbidden: serving network NOT AUTHORIZED")
return nil, "", &problemDetails
}
logger.UeAuthPostLog.Infoln("Serving network authorized")
2.2 如果请求 body 包含 ResynchronizationInfo
if updateAuthenticationInfo.ResynchronizationInfo != nil {
logger.UeAuthPostLog.Warningln("Auts: ", updateAuthenticationInfo.ResynchronizationInfo.Auts)
ausfCurrentSupi := ausf_context.GetSupiFromSuciSupiMap(supiOrSuci)
logger.UeAuthPostLog.Warningln(ausfCurrentSupi)
ausfCurrentContext := ausf_context.GetAusfUeContext(ausfCurrentSupi)
logger.UeAuthPostLog.Warningln(ausfCurrentContext.Rand)
updateAuthenticationInfo.ResynchronizationInfo.Rand = ausfCurrentContext.Rand
logger.UeAuthPostLog.Warningln("Rand: ", updateAuthenticationInfo.ResynchronizationInfo.Rand)
authInfoReq.ResynchronizationInfo = updateAuthenticationInfo.ResynchronizationInfo
}
实例化 var authInfoReq models.AuthenticationInfoRequest,并赋值,猜测应该是向 UDM 请求的 body
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"`
}
2.3 AUSF 向 UDM 请求SUPI / SUCI 的身份验证信息数据
NF 服务使用者(AUSF)从 UDM 请求 SUPI / SUCI 的身份验证信息数据。 如果提供了 SUCI,则 UDM 根据 SUCI 计算 SUPI(请参阅3GPP TS 33.501 [6])。 如果选择了 5G AKA 或 EAP-AKA',则 UDM 会考虑从 NF 服务使用者(AUSF)接收到的信息以及该资源的当前表示来计算认证向量。 有关详细信息,请参见 3GPP TS 33.501 [6]。
GenerateAuthData 向 UDM 发送请求,/nudm-ueau/v1/{udId}/security-information/generate-auth-data
udmUrl := getUdmUrl(self.NrfUri)
client := createClientToUdmUeau(udmUrl)
authInfoResult, _, err := client.GenerateAuthDataApi.GenerateAuthData(context.Background(), supiOrSuci, authInfoReq)
if err != nil {
logger.UeAuthPostLog.Infoln(err.Error())
var problemDetails models.ProblemDetails
if authInfoResult.AuthenticationVector == nil {
problemDetails.Cause = "AV_GENERATION_PROBLEM"
} else {
problemDetails.Cause = "UPSTREAM_SERVER_ERROR"
}
problemDetails.Status = http.StatusInternalServerError
return nil, "", &problemDetails
}
3. UDM 处理 AUSF 的身份验证信息数据请求
/:supiOrSuci/security-information/generate-auth-data
--> HttpGenerateAuthData
--> HandleGenerateAuthDataRequest
--> GenerateAuthDataProcedure
如果提供了 SUCI,则 UDM 根据 SUCI 计算 SUPI(请参阅3GPP TS 33.501 [6])。 如果选择了 5G AKA 或 EAP-AKA',则 UDM 会考虑从 NF 服务使用者(AUSF)接收到的信息以及该资源的当前表示来计算认证向量。 有关详细信息,请参见 3GPP TS 33.501 [6]。
func GenerateAuthDataProcedure(authInfoRequest models.AuthenticationInfoRequest, supiOrSuci string) (
response *models.AuthenticationInfoResult, problemDetails *models.ProblemDetails) {
logger.UeauLog.Traceln("In GenerateAuthDataProcedure")
response = &models.AuthenticationInfoResult{}
rand.Seed(time.Now().UnixNano())
supi, err := suci.ToSupi(supiOrSuci)
3.1 UDM 向 UDR 查询鉴权数据
/subscription-data/{ueId}/authentication-data/authentication-subscription, 包括 OP OPC K SQN
client := createUDMClientToUDR(supi, false)
authSubs, _, err := client.AuthenticationDataDocumentApi.QueryAuthSubsData(context.Background(), supi, nil)
if err != nil {
problemDetails = &models.ProblemDetails{
Status: http.StatusForbidden,
Cause: authenticationRejected,
Detail: err.Error(),
}
logger.UeauLog.Errorln("Return from UDR QueryAuthSubsData error")
return nil, problemDetails
}
{ "_id" : ObjectId("5fb4baa6ea01aa3a37fd5d96"), "sequenceNumber" : "000000000022", "authenticationManagementField" : "8000", "milenage" : { "op" : { "opValue" : "c9e8763286b5b9ffbdf56e1297d0887b", "encryptionAlgorithm" : 0, "encryptionKey" : 0 } }, "opc" : { "opcValue" : "981d464c7c52eb6e5036234984ad0bcf", "encryptionAlgorithm" : 0, "encryptionKey" : 0 }, "ueId" : "imsi-2089300007487", "authenticationMethod" : "5G_AKA", "permanentKey" : { "encryptionAlgorithm" : 0, "encryptionKey" : 0, "permanentKeyValue" : "5122250214c33e723a5dd523fc145fc0" } }
3.2 AMF 硬编码 8000(Authentication Management Field)
AMF, err := hex.DecodeString("8000")
if err != nil {
problemDetails = &models.ProblemDetails{
Status: http.StatusForbidden,
Cause: authenticationRejected,
Detail: err.Error(),
}
logger.UeauLog.Errorln("err", err)
return nil, problemDetails
}
3.3 需要重新同步的情况
// re-synchroniztion
if authInfoRequest.ResynchronizationInfo != nil {
Auts, deCodeErr := hex.DecodeString(authInfoRequest.ResynchronizationInfo.Auts)
if deCodeErr != nil {
problemDetails = &models.ProblemDetails{
Status: http.StatusForbidden,
Cause: authenticationRejected,
Detail: deCodeErr.Error(),
}
logger.UeauLog.Errorln("err", deCodeErr)
return nil, problemDetails
}
3.4 对 SQN 加一处理,并重新存储
// increment sqn
bigSQN := big.NewInt(0)
sqn, err = hex.DecodeString(sqnStr)
if err != nil {
problemDetails = &models.ProblemDetails{
Status: http.StatusForbidden,
Cause: authenticationRejected,
Detail: err.Error(),
}
logger.UeauLog.Errorln("err", err)
return nil, problemDetails
}
bigSQN.SetString(sqnStr, 16)
bigInc := big.NewInt(1)
bigSQN = bigInc.Add(bigSQN, bigInc)
SQNheStr := fmt.Sprintf("%x", bigSQN)
SQNheStr = strictHex(SQNheStr, 12)
patchItemArray := []models.PatchItem{
{
Op: models.PatchOperation_REPLACE,
Path: "/sequenceNumber",
Value: SQNheStr,
},
}
- MAC = f1K(SQN || RAND || AMF)
- XRES = f2K (RAND)
- CK = f3K (RAND)
- IK = f4K (RAND)
- AK = f5K (RAND)
- AUTN = SQN Å AK || AMF || MAC
// Generate macA, macS
err = milenage.F1(opc, k, RAND, sqn, AMF, macA, macS)
if err != nil {
logger.UeauLog.Errorln("milenage F1 err ", err)
}
f1 函数生成 macA 和 macS
* milenage_f1 - Milenage f1 and f1* algorithms * @opc: OPc = 128-bit value derived from OP and K * @k: K = 128-bit subscriber key * @_rand: RAND = 128-bit random challenge * @sqn: SQN = 48-bit sequence number * @amf: AMF = 16-bit authentication management field * @mac_a: Buffer for MAC-A = 64-bit network authentication code, or %NULL * @mac_s: Buffer for MAC-S = 64-bit resync authentication code, or %NULL
3.5 生成 AUTN
// Generate RES, CK, IK, AK, AKstar
// RES == XRES (expected RES) for server
err = milenage.F2345(opc, k, RAND, RES, CK, IK, AK, AKstar)
if err != nil {
logger.UeauLog.Errorln("milenage F2345 err ", err)
}
// fmt.Printf("milenage RES = %s\n", hex.EncodeToString(RES))
// Generate AUTN
// fmt.Printf("SQN=%x\nAK =%x\n", SQN, AK)
// fmt.Printf("AMF=%x, macA=%x\n", AMF, macA)
SQNxorAK := make([]byte, 6)
for i := 0; i < len(sqn); i++ {
SQNxorAK[i] = sqn[i] ^ AK[i]
}
// fmt.Printf("SQN xor AK = %x\n", SQNxorAK)
AUTN := append(append(SQNxorAK, AMF...), macA...)
fmt.Printf("AUTN = %x\n", AUTN)
4. AUSF 向 AMF response ue-authentications
4.1 AUSF 根据 XRES* 推导出 HXRES*
locationURI := self.Url + "/nausf-auth/v1/ue-authentications/" + supiOrSuci
putLink := locationURI
if authInfoResult.AuthType == models.AuthType__5_G_AKA {
logger.UeAuthPostLog.Infoln("Use 5G AKA auth method")
putLink += "/5g-aka-confirmation"
// Derive HXRES* from XRES*
concat := authInfoResult.AuthenticationVector.Rand + authInfoResult.AuthenticationVector.XresStar
var hxresStarBytes []byte
if bytes, err := hex.DecodeString(concat); err != nil {
logger.Auth5gAkaComfirmLog.Warnf("decode error: %+v", err)
} else {
hxresStarBytes = bytes
}
hxresStarAll := sha256.Sum256(hxresStarBytes)
hxresStar := hex.EncodeToString(hxresStarAll[16:]) // last 128 bits
logger.Auth5gAkaComfirmLog.Infof("XresStar = %x\n", authInfoResult.AuthenticationVector.XresStar)
4.2 AUSF 根据 Kausf 推导出 Kseaf
// Derive Kseaf from Kausf
Kausf := authInfoResult.AuthenticationVector.Kausf
var KausfDecode []byte
if ausfDecode, err := hex.DecodeString(Kausf); err != nil {
logger.Auth5gAkaComfirmLog.Warnf("AUSF decode failed: %+v", err)
} else {
KausfDecode = ausfDecode
}
P0 := []byte(snName)
Kseaf := UeauCommon.GetKDFValue(KausfDecode, UeauCommon.FC_FOR_KSEAF_DERIVATION, P0, UeauCommon.KDFLen(P0))
ausfUeContext.XresStar = authInfoResult.AuthenticationVector.XresStar
ausfUeContext.Kausf = Kausf
ausfUeContext.Kseaf = hex.EncodeToString(Kseaf)
ausfUeContext.Rand = authInfoResult.AuthenticationVector.Rand
AUSF 存储上下文,只给 AMF 返回 Rand Autn HxresStar
5. AMF 向 UE 发送 Authentication Request
Gmm 消息类型为 MsgTypeAuthenticationRequest,包裹的 NGAP 消息为 DownlinkNASTransport
6. UE 向 AMF 发送 Authentication response
- 接收到 RAND 和 AUTN, USIM 首先计算匿名密钥 AK = f5K (RAND),以及检索 SQN = SQN = (SQN Å AK) Å AK
- USIM 计算 f1K (SQN || RAND || AMF),比较 MAC 和 在 AUTN 的 MAC,如果不相同,UE 将验证失败消息发送回 VLR / SGSN,并说明原因,然后用户放弃该过程。
- 接下来,USIM 验证接收到的序列号 SQN 在正确范围内。
- 如果 USIM 认为序列号不在正确的范围内,则会将同步失败(包括适当的参数)发送回VLR / SGSN,并放弃该过程。
6.1 UE 向 AMF 发送 Authentication response 同步失败
6.1.1 AMF 处理 Authentication request Synch failure
func HandleAuthenticationFailure(ue *context.AmfUe, anType models.AccessType,
authenticationFailure *nasMessage.AuthenticationFailure) error {
logger.GmmLog.Info("[AMF] Handle Authentication Failure")
util.StopT3560(ue)
可以看到处理的失败原因包括:
- Cause5GMMMACFailure
- Cause5GMMNon5GAuthenticationUnacceptable
- Cause5GMMngKSIAlreadyInUse
- Cause5GMMSynchFailure
对于 Synch failure 的情况处理,超过两次则 reject 处理
case nasMessage.Cause5GMMSynchFailure: // TS 24.501 5.4.1.3.7 case f
logger.GmmLog.Warn("Authentication Failure 5GMM Cause: Synch Failure")
ue.AuthFailureCauseSynchFailureTimes++
if ue.AuthFailureCauseSynchFailureTimes >= 2 {
logger.GmmLog.Warnf("2 consecutive Synch Failure, terminate authentication procedure")
gmm_message.SendAuthenticationReject(ue.RanUe[anType], "")
return GmmFSM.SendEvent(ue.State[anType], AuthFailEvent, fsm.ArgsType{ArgAmfUe: ue, ArgAccessType: anType})
}
6.1.1.1 SendUEAuthenticationAuthenticateRequest 重新向 AUSF 发送鉴权请求
则进入第 2 章节进行处理, 这次包含 resynchronizationInfo 的处理