Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/integrity check #109

Merged
merged 12 commits into from
Oct 1, 2023
3 changes: 3 additions & 0 deletions internal/context/amf_ue.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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 != "" {
Expand Down
11 changes: 10 additions & 1 deletion internal/gmm/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down
1 change: 1 addition & 0 deletions internal/nas/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion internal/nas/nas_security/fuzz_test.go
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}
Expand Down
9 changes: 6 additions & 3 deletions internal/nas/nas_security/security.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package nas_security

import (
"encoding/binary"
"encoding/hex"
"fmt"
"reflect"
Expand Down Expand Up @@ -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
Expand All @@ -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:]

Expand Down Expand Up @@ -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()
Expand All @@ -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()
}
Expand Down
8 changes: 1 addition & 7 deletions internal/sbi/consumer/communication.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package consumer

import (
"bytes"
"context"
"fmt"

Expand Down Expand Up @@ -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}
Expand Down
191 changes: 131 additions & 60 deletions internal/sbi/producer/ue_context.go
Original file line number Diff line number Diff line change
@@ -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"
)
Expand Down Expand Up @@ -191,6 +195,20 @@ func ReleaseUEContextProcedure(ueContextID string, ueContextRelease models.UeCon
return nil
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix lint error


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")
Expand Down Expand Up @@ -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)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the procedure keep going if err is nil? (Should function return here when there's an error?)

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{
Expand All @@ -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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix lint error

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)
}
Expand Down