diff --git a/internal/context/amf_ue.go b/internal/context/amf_ue.go index a387ffae..f293ddfc 100644 --- a/internal/context/amf_ue.go +++ b/internal/context/amf_ue.go @@ -66,6 +66,7 @@ type AmfUe struct { ServingAmfChanged bool DeregistrationTargetAccessType uint8 // only used when deregistration procedure is initialized by the network RegistrationAcceptForNon3GPPAccess []byte + NasPduValue []byte RetransmissionOfInitialNASMsg bool RequestIdentityType uint8 /* Used for AMF relocation */ @@ -735,6 +736,8 @@ func (ue *AmfUe) CopyDataFromUeContextModel(ueContext models.UeContext) { ue.NH = nh } ue.NCC = uint8(seafData.Ncc) + } else { + ue.SecurityContextAvailable = false } if ueContext.PcfId != "" { diff --git a/internal/gmm/handler.go b/internal/gmm/handler.go index 9cbf2489..6be75112 100644 --- a/internal/gmm/handler.go +++ b/internal/gmm/handler.go @@ -482,6 +482,7 @@ func HandleRegistrationRequest(ue *context.AmfUe, anType models.AccessType, proc return fmt.Errorf("decode GUTI failed: %w", err) } guamiFromUeGuti = guamiFromUeGutiTmp + ue.PlmnId = *guamiFromUeGuti.PlmnId ue.GmmLog.Infof("MobileIdentity5GS: GUTI[%s]", guti) // TODO: support multiple ServedGuami @@ -564,7 +565,6 @@ func HandleRegistrationRequest(ue *context.AmfUe, anType models.AccessType, proc // if failed, give up to retrieve the old context and start a new authentication procedure. ue.ServingAmfChanged = false context.GetSelf().AllocateGutiToUe(ue) // refresh 5G-GUTI - ue.SecurityContextAvailable = false // need to start authentication procedure later } } return nil @@ -594,14 +594,23 @@ func contextTransferFromOldAmf(ue *context.AmfUe, anType models.AccessType, oldA ueContextTransferRspData, problemDetails, err := consumer.UEContextTransferRequest(ue, anType, transferReason) if problemDetails != nil { if problemDetails.Cause == "INTEGRITY_CHECK_FAIL" || problemDetails.Cause == "CONTEXT_NOT_FOUND" { + // TODO 9a. After successful authentication in new AMF, which is triggered by the integrity check failure + // in old AMF at step 5, the new AMF invokes step 4 above again and indicates that the UE is validated + //(i.e. through the reason parameter as specified in clause 5.2.2.2.2). return fmt.Errorf("Can not retrieve UE Context from old AMF[Cause: %s]", problemDetails.Cause) } return fmt.Errorf("UE Context Transfer Request Failed Problem[%+v]", problemDetails) } else if err != nil { return fmt.Errorf("UE Context Transfer Request Error[%+v]", err) + } else { + ue.SecurityContextAvailable = true + ue.MacFailed = false } ue.CopyDataFromUeContextModel(*ueContextTransferRspData.UeContext) + if ue.SecurityContextAvailable { + ue.DerivateAlgKey() + } return nil } diff --git a/internal/nas/handler.go b/internal/nas/handler.go index 69e82f48..dee9a22b 100644 --- a/internal/nas/handler.go +++ b/internal/nas/handler.go @@ -33,6 +33,7 @@ func HandleNAS(ue *amf_context.RanUe, procedureCode int64, nasPdu []byte, initia ue.AmfUe.NASLog.Errorln(err) return } + ue.AmfUe.NasPduValue = nasPdu ue.AmfUe.MacFailed = !integrityProtected if err := Dispatch(ue.AmfUe, ue.Ran.AnType, procedureCode, msg); err != nil { diff --git a/internal/nas/nas_security/fuzz_test.go b/internal/nas/nas_security/fuzz_test.go old mode 100644 new mode 100755 index 4daf188f..e9098785 --- a/internal/nas/nas_security/fuzz_test.go +++ b/internal/nas/nas_security/fuzz_test.go @@ -50,7 +50,7 @@ func FuzzNASSecurity(f *testing.F) { if !integrityProtected2 { panic("integrityProtected mismatch") } - if !reflect.DeepEqual(msg0, msg2) { + if !reflect.DeepEqual(msg0.GmmMessage, msg2.GmmMessage) { panic("msg mismatch") } } diff --git a/internal/nas/nas_security/security.go b/internal/nas/nas_security/security.go index 22d89634..d5ebe30d 100644 --- a/internal/nas/nas_security/security.go +++ b/internal/nas/nas_security/security.go @@ -1,6 +1,7 @@ package nas_security import ( + "encoding/binary" "encoding/hex" "fmt" "reflect" @@ -123,6 +124,7 @@ func Decode(ue *context.AmfUe, accessType models.AccessType, payload []byte, ulCountNew := ue.ULCount msg = new(nas.Message) + msg.ProtocolDiscriminator = payload[0] msg.SecurityHeaderType = nas.GetSecurityHeaderType(payload) & 0x0f ue.NASLog.Traceln("securityHeaderType is ", msg.SecurityHeaderType) if msg.SecurityHeaderType != nas.SecurityHeaderTypePlainNas { // Security protected NAS message @@ -139,8 +141,9 @@ func Decode(ue *context.AmfUe, accessType models.AccessType, payload []byte, ue.NASLog.Traceln("securityHeader is ", securityHeader) sequenceNumber := payload[6] ue.NASLog.Traceln("sequenceNumber", sequenceNumber) - + msg.SequenceNumber = sequenceNumber receivedMac32 := securityHeader[2:] + msg.MessageAuthenticationCode = binary.BigEndian.Uint32(receivedMac32) // remove security Header except for sequece Number payload = payload[6:] @@ -278,7 +281,7 @@ func Decode(ue *context.AmfUe, accessType models.AccessType, payload []byte, mobileIdentityContents := msg.IdentityResponse.MobileIdentity.GetMobileIdentityContents() if len(mobileIdentityContents) >= 1 && nasConvert.GetTypeOfIdentity(mobileIdentityContents[0]) == nasMessage.MobileIdentity5GSTypeSuci { - // Identity is SUSI + // Identity is SUCI if ue.SecurityContextAvailable { if msg.SecurityHeaderType != nas.SecurityHeaderTypeIntegrityProtectedAndCiphered { return nil, false, errWrongSecurityHeader() @@ -288,7 +291,7 @@ func Decode(ue *context.AmfUe, accessType models.AccessType, payload []byte, } } } else { - // Identity is not SUSI + // Identity is not SUCI if !ue.SecurityContextAvailable { return nil, false, errNoSecurityContext() } diff --git a/internal/sbi/consumer/communication.go b/internal/sbi/consumer/communication.go index 59a6a6f8..8f545afb 100644 --- a/internal/sbi/consumer/communication.go +++ b/internal/sbi/consumer/communication.go @@ -1,7 +1,6 @@ package consumer import ( - "bytes" "context" "fmt" @@ -214,18 +213,13 @@ func UEContextTransferRequest( JsonData: &ueContextTransferReqData, } if transferReason == models.TransferReason_INIT_REG || transferReason == models.TransferReason_MOBI_REG { - var buf bytes.Buffer - err = ue.RegistrationRequest.EncodeRegistrationRequest(&buf) - if err != nil { - return nil, nil, fmt.Errorf("re-encoding registration request message is failed: %w", err) - } ueContextTransferReqData.RegRequest = &models.N1MessageContainer{ N1MessageClass: models.N1MessageClass__5_GMM, N1MessageContent: &models.RefToBinaryData{ ContentId: "n1Msg", }, } - req.BinaryDataN1Message = buf.Bytes() + req.BinaryDataN1Message = ue.NasPduValue } // guti format is defined at TS 29.518 Table 6.1.3.2.2-1 5g-guti-[0-9]{5,6}[0-9a-fA-F]{14} diff --git a/internal/sbi/producer/ue_context.go b/internal/sbi/producer/ue_context.go index 09fc9da3..b0b1638c 100644 --- a/internal/sbi/producer/ue_context.go +++ b/internal/sbi/producer/ue_context.go @@ -1,13 +1,17 @@ package producer import ( + "encoding/base64" + "encoding/hex" "net/http" "strings" "github.com/free5gc/amf/internal/context" gmm_common "github.com/free5gc/amf/internal/gmm/common" "github.com/free5gc/amf/internal/logger" + "github.com/free5gc/amf/internal/nas/nas_security" "github.com/free5gc/amf/internal/sbi/consumer" + "github.com/free5gc/nas/security" "github.com/free5gc/openapi/models" "github.com/free5gc/util/httpwrapper" ) @@ -191,6 +195,20 @@ func ReleaseUEContextProcedure(ueContextID string, ueContextRelease models.UeCon return nil } +func HandleMobiRegUe(ue *context.AmfUe, ueContextTransferRspData *models.UeContextTransferRspData, + ueContextTransferResponse *models.UeContextTransferResponse, +) { + ueContextTransferRspData.UeRadioCapability = &models.N2InfoContent{ + NgapMessageType: 0, + NgapIeType: models.NgapIeType_UE_RADIO_CAPABILITY, + NgapData: &models.RefToBinaryData{ + ContentId: "n2Info", + }, + } + b := []byte(ue.UeRadioCapability) + copy(ueContextTransferResponse.BinaryDataN2Information, b) +} + // TS 29.518 5.2.2.2.1 func HandleUEContextTransferRequest(request *httpwrapper.Request) *httpwrapper.Response { logger.CommLog.Info("Handle UE Context Transfer Request") @@ -256,70 +274,52 @@ func UEContextTransferProcedure(ueContextID string, ueContextTransferRequest mod switch UeContextTransferReqData.Reason { case models.TransferReason_INIT_REG: - // TODO: check integrity of the registration request included in ueContextTransferRequest + _, integrityProtected, err := nas_security.Decode(ue, UeContextTransferReqData.AccessType, + ueContextTransferRequest.BinaryDataN1Message, true) + if err != nil { + problemDetails := &models.ProblemDetails{ + Status: http.StatusForbidden, + Cause: "INTEGRITY_CHECK_FAIL", + } + ue.NASLog.Errorln(err) + return nil, problemDetails + } + if integrityProtected { + ueContextTransferRspData.UeContext = buildUEContextModel(ue, UeContextTransferReqData.Reason) + } else { + problemDetails := &models.ProblemDetails{ + Status: http.StatusForbidden, + Cause: "INTEGRITY_CHECK_FAIL", + } + return nil, problemDetails + } // TODO: handle condition of TS 29.518 5.2.2.2.1.1 step 2a case b - ueContextTransferRspData.UeContext = buildUEContextModel(ue) case models.TransferReason_MOBI_REG: - // TODO: check integrity of the registration request included in ueContextTransferRequest - ueContextTransferRspData.UeContext = buildUEContextModel(ue) - - sessionContextList := &ueContextTransferRspData.UeContext.SessionContextList - ue.SmContextList.Range(func(key, value interface{}) bool { - smContext := value.(*context.SmContext) - snssai := smContext.Snssai() - pduSessionContext := models.PduSessionContext{ - PduSessionId: smContext.PduSessionID(), - SmContextRef: smContext.SmContextRef(), - SNssai: &snssai, - Dnn: smContext.Dnn(), - AccessType: smContext.AccessType(), - HsmfId: smContext.HSmfID(), - VsmfId: smContext.VSmfID(), - NsInstance: smContext.NsInstance(), + _, integrityProtected, err := nas_security.Decode(ue, UeContextTransferReqData.AccessType, + ueContextTransferRequest.BinaryDataN1Message, false) + if err != nil { + problemDetails := &models.ProblemDetails{ + Status: http.StatusForbidden, + Cause: "INTEGRITY_CHECK_FAIL", } - *sessionContextList = append(*sessionContextList, pduSessionContext) - return true - }) - - ueContextTransferRspData.UeRadioCapability = &models.N2InfoContent{ - NgapMessageType: 0, - NgapIeType: models.NgapIeType_UE_RADIO_CAPABILITY, - NgapData: &models.RefToBinaryData{ - ContentId: "n2Info", - }, + ue.NASLog.Errorln(err) + return nil, problemDetails } - b := []byte(ue.UeRadioCapability) - copy(ueContextTransferResponse.BinaryDataN2Information, b) - case models.TransferReason_MOBI_REG_UE_VALIDATED: - ueContextTransferRspData.UeContext = buildUEContextModel(ue) - - sessionContextList := &ueContextTransferRspData.UeContext.SessionContextList - ue.SmContextList.Range(func(key, value interface{}) bool { - smContext := value.(*context.SmContext) - snssai := smContext.Snssai() - pduSessionContext := models.PduSessionContext{ - PduSessionId: smContext.PduSessionID(), - SmContextRef: smContext.SmContextRef(), - SNssai: &snssai, - Dnn: smContext.Dnn(), - AccessType: smContext.AccessType(), - HsmfId: smContext.HSmfID(), - VsmfId: smContext.VSmfID(), - NsInstance: smContext.NsInstance(), + if integrityProtected { + ueContextTransferRspData.UeContext = buildUEContextModel(ue, UeContextTransferReqData.Reason) + } else { + problemDetails := &models.ProblemDetails{ + Status: http.StatusForbidden, + Cause: "INTEGRITY_CHECK_FAIL", } - *sessionContextList = append(*sessionContextList, pduSessionContext) - return true - }) - - ueContextTransferRspData.UeRadioCapability = &models.N2InfoContent{ - NgapMessageType: 0, - NgapIeType: models.NgapIeType_UE_RADIO_CAPABILITY, - NgapData: &models.RefToBinaryData{ - ContentId: "n2Info", - }, + return nil, problemDetails } - b := []byte(ue.UeRadioCapability) - copy(ueContextTransferResponse.BinaryDataN2Information, b) + HandleMobiRegUe(ue, ueContextTransferRspData, ueContextTransferResponse) + + case models.TransferReason_MOBI_REG_UE_VALIDATED: + ueContextTransferRspData.UeContext = buildUEContextModel(ue, UeContextTransferReqData.Reason) + HandleMobiRegUe(ue, ueContextTransferRspData, ueContextTransferResponse) + default: logger.ProducerLog.Warnf("Invalid Transfer Reason: %+v", UeContextTransferReqData.Reason) problemDetails := &models.ProblemDetails{ @@ -336,11 +336,82 @@ func UEContextTransferProcedure(ueContextID string, ueContextTransferRequest mod return ueContextTransferResponse, nil } -func buildUEContextModel(ue *context.AmfUe) *models.UeContext { +func buildUEContextModel(ue *context.AmfUe, Reason models.TransferReason) *models.UeContext { ueContext := new(models.UeContext) ueContext.Supi = ue.Supi ueContext.SupiUnauthInd = ue.UnauthenticatedSupi - + if Reason == models.TransferReason_INIT_REG || Reason == models.TransferReason_MOBI_REG { + var mmContext models.MmContext + mmContext.AccessType = models.AccessType__3_GPP_ACCESS + NasSecurityMode := new(models.NasSecurityMode) + switch ue.IntegrityAlg { + case security.AlgIntegrity128NIA0: + NasSecurityMode.IntegrityAlgorithm = models.IntegrityAlgorithm_NIA0 + case security.AlgIntegrity128NIA1: + NasSecurityMode.IntegrityAlgorithm = models.IntegrityAlgorithm_NIA1 + case security.AlgIntegrity128NIA2: + NasSecurityMode.IntegrityAlgorithm = models.IntegrityAlgorithm_NIA2 + case security.AlgIntegrity128NIA3: + NasSecurityMode.IntegrityAlgorithm = models.IntegrityAlgorithm_NIA3 + } + switch ue.CipheringAlg { + case security.AlgCiphering128NEA0: + NasSecurityMode.CipheringAlgorithm = models.CipheringAlgorithm_NEA0 + case security.AlgCiphering128NEA1: + NasSecurityMode.CipheringAlgorithm = models.CipheringAlgorithm_NEA1 + case security.AlgCiphering128NEA2: + NasSecurityMode.CipheringAlgorithm = models.CipheringAlgorithm_NEA2 + case security.AlgCiphering128NEA3: + NasSecurityMode.CipheringAlgorithm = models.CipheringAlgorithm_NEA3 + } + NgKsi := new(models.NgKsi) + NgKsi.Ksi = ue.NgKsi.Ksi + NgKsi.Tsc = ue.NgKsi.Tsc + KeyAmf := new(models.KeyAmf) + KeyAmf.KeyType = models.KeyAmfType_KAMF + KeyAmf.KeyVal = ue.Kamf + SeafData := new(models.SeafData) + SeafData.NgKsi = NgKsi + SeafData.KeyAmf = KeyAmf + if ue.NH != nil { + SeafData.Nh = hex.EncodeToString(ue.NH) + } + SeafData.Ncc = int32(ue.NCC) + SeafData.KeyAmfChangeInd = false + SeafData.KeyAmfHDerivationInd = false + ueContext.SeafData = SeafData + mmContext.NasSecurityMode = NasSecurityMode + if ue.UESecurityCapability.Buffer != nil { + mmContext.UeSecurityCapability = base64.StdEncoding.EncodeToString(ue.UESecurityCapability.Buffer) + } + mmContext.NasDownlinkCount = int32(ue.DLCount.Get()) + mmContext.NasUplinkCount = int32(ue.ULCount.Get()) + if ue.AllowedNssai[models.AccessType__3_GPP_ACCESS] != nil { + for _, allowedSnssai := range ue.AllowedNssai[models.AccessType__3_GPP_ACCESS] { + mmContext.AllowedNssai = append(mmContext.AllowedNssai, *(allowedSnssai.AllowedSnssai)) + } + } + ueContext.MmContextList = append(ueContext.MmContextList, mmContext) + } + if Reason == models.TransferReason_MOBI_REG_UE_VALIDATED || Reason == models.TransferReason_MOBI_REG { + sessionContextList := &ueContext.SessionContextList + ue.SmContextList.Range(func(key, value interface{}) bool { + smContext := value.(*context.SmContext) + snssai := smContext.Snssai() + pduSessionContext := models.PduSessionContext{ + PduSessionId: smContext.PduSessionID(), + SmContextRef: smContext.SmContextRef(), + SNssai: &snssai, + Dnn: smContext.Dnn(), + AccessType: smContext.AccessType(), + HsmfId: smContext.HSmfID(), + VsmfId: smContext.VSmfID(), + NsInstance: smContext.NsInstance(), + } + *sessionContextList = append(*sessionContextList, pduSessionContext) + return true + }) + } if ue.Gpsi != "" { ueContext.GpsiList = append(ueContext.GpsiList, ue.Gpsi) }