From fdfe321925f7f322ebd5d03549516bed6948fd14 Mon Sep 17 00:00:00 2001 From: Ghislain Bourgeois Date: Wed, 29 May 2024 18:58:38 -0400 Subject: [PATCH] chore: Copy http2_util, logger_conf and logger_util to util project (#21) * chore: Copy http2_util, logger_conf and logger_util to util project * Standardize on logger instead of logger_util * Copy util_3gpp to the util project --- go.mod | 4 +- go.sum | 4 + http2_util/server.go | 50 +++++ http2_util/server_debug.go | 42 ++++ logger/logger_config.go | 26 ++- logger_conf/conf.go | 65 +++++++ util_3gpp/3gpp_type.go | 21 ++ util_3gpp/logger/logger.go | 59 ++++++ util_3gpp/str_conv.go | 18 ++ util_3gpp/suci/toSupi.go | 381 +++++++++++++++++++++++++++++++++++++ 10 files changed, 664 insertions(+), 6 deletions(-) create mode 100644 http2_util/server.go create mode 100644 http2_util/server_debug.go create mode 100644 logger_conf/conf.go create mode 100644 util_3gpp/3gpp_type.go create mode 100644 util_3gpp/logger/logger.go create mode 100644 util_3gpp/str_conv.go create mode 100644 util_3gpp/suci/toSupi.go diff --git a/go.mod b/go.mod index 3972d7f..fe73f1c 100644 --- a/go.mod +++ b/go.mod @@ -8,12 +8,14 @@ require ( github.com/evanphx/json-patch v4.11.0+incompatible github.com/gin-gonic/gin v1.7.3 github.com/mitchellh/mapstructure v1.4.1 + github.com/omec-project/openapi v1.2.0 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.0 github.com/smartystreets/goconvey v1.6.4 github.com/stretchr/testify v1.8.0 github.com/thakurajayL/go-ipam v0.0.5-dev go.mongodb.org/mongo-driver v1.10.1 + golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b ) @@ -30,6 +32,7 @@ require ( github.com/go-playground/validator/v10 v10.4.1 // indirect github.com/go-redis/redis/v8 v8.11.5 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt v3.2.1+incompatible // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect @@ -58,7 +61,6 @@ require ( go.uber.org/zap v1.23.0 // indirect go4.org/intern v0.0.0-20220617035311-6925f38cc365 // indirect go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect - golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect golang.org/x/text v0.3.7 // indirect diff --git a/go.sum b/go.sum index 22af254..f306d9a 100644 --- a/go.sum +++ b/go.sum @@ -99,6 +99,8 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= +github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -199,6 +201,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/omec-project/openapi v1.2.0 h1:7Wvi0HLvhvxMyQtqGcqtMCPC/0QCGAFP5htrXCfWxRc= +github.com/omec-project/openapi v1.2.0/go.mod h1:hjU13MB1m9MHTko87JfsUNCdeD6/m6VkNZDD8Vq5U9M= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= diff --git a/http2_util/server.go b/http2_util/server.go new file mode 100644 index 0000000..f180ab1 --- /dev/null +++ b/http2_util/server.go @@ -0,0 +1,50 @@ +// Copyright 2019 Communication Service/Software Laboratory, National Chiao Tung University (free5gc.org) +// +// SPDX-License-Identifier: Apache-2.0 + +//go:build !debug +// +build !debug + +package http2_util + +import ( + "crypto/tls" + "fmt" + "net/http" + "os" + "time" + + "github.com/pkg/errors" + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" +) + +// NewServer returns a server instance with HTTP/2.0 and HTTP/2.0 cleartext support +// If this function cannot open or create the secret log file, +// **it still returns server instance** but without the secret log and error indication +func NewServer(bindAddr string, preMasterSecretLogPath string, handler http.Handler) (server *http.Server, err error) { + if handler == nil { + return nil, errors.New("server needs handler to handle request") + } + + h2Server := &http2.Server{ + // TODO: extends the idle time after re-use openapi client + IdleTimeout: 1 * time.Millisecond, + } + server = &http.Server{ + Addr: bindAddr, + Handler: h2c.NewHandler(handler, h2Server), + } + + if preMasterSecretLogPath != "" { + preMasterSecretFile, err := os.OpenFile(preMasterSecretLogPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return server, fmt.Errorf("create pre-master-secret log [%s] fail: %s", preMasterSecretLogPath, err) + } + server.TLSConfig = &tls.Config{ + KeyLogWriter: preMasterSecretFile, + } + } + + return +} diff --git a/http2_util/server_debug.go b/http2_util/server_debug.go new file mode 100644 index 0000000..272364b --- /dev/null +++ b/http2_util/server_debug.go @@ -0,0 +1,42 @@ +// Copyright 2019 Communication Service/Software Laboratory, National Chiao Tung University (free5gc.org) +// +// SPDX-License-Identifier: Apache-2.0 + +//+build debug + +package http2_util + +import ( + "crypto/tls" + "github.com/pkg/errors" + "net/http" + "os" +) + +type ZeroSource struct{} + +func (ZeroSource) Read(b []byte) (n int, err error) { + for i := range b { + b[i] = 0 + } + return len(b), nil +} + +func NewServer(bindAddr string, tlskeylog string, handler http.Handler) (server *http.Server, err error) { + keylogFile, err := os.OpenFile(tlskeylog, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return nil, err + } + if handler == nil { + return nil, errors.New("server need handler") + } + server = &http.Server{ + Addr: bindAddr, + TLSConfig: &tls.Config{ + KeyLogWriter: keylogFile, + Rand: ZeroSource{}, + }, + Handler: handler, + } + return +} diff --git a/logger/logger_config.go b/logger/logger_config.go index 5e20147..406ae42 100644 --- a/logger/logger_config.go +++ b/logger/logger_config.go @@ -28,11 +28,27 @@ type Logger struct { NWDAF *LogSetting `yaml:"NWDAF" valid:"optional"` WEBUI *LogSetting `yaml:"WEBUI" valid:"optional"` - Aper *LogSetting `yaml:"Aper" valid:"optional"` - FSM *LogSetting `yaml:"FSM" valid:"optional"` - NAS *LogSetting `yaml:"NAS" valid:"optional"` - NGAP *LogSetting `yaml:"NGAP" valid:"optional"` - PFCP *LogSetting `yaml:"PFCP" valid:"optional"` + Aper *LogSetting `yaml:"Aper" valid:"optional"` + CommonConsumerTest *LogSetting `yaml:"CommonConsumerTest" valid:"optional"` + FSM *LogSetting `yaml:"FSM" valid:"optional"` + MongoDBLibrary *LogSetting `yaml:"MongoDBLibrary" valid:"optional"` + NAS *LogSetting `yaml:"NAS" valid:"optional"` + NGAP *LogSetting `yaml:"NGAP" valid:"optional"` + OpenApi *LogSetting `yaml:"OpenApi" valid:"optional"` + NamfCommunication *LogSetting `yaml:"NamfCommunication" valid:"optional"` + NamfEventExposure *LogSetting `yaml:"NamfEventExposure" valid:"optional"` + NnssfNSSAIAvailability *LogSetting `yaml:"NnssfNSSAIAvailability" valid:"optional"` + NnssfNSSelection *LogSetting `yaml:"NnssfNSSelection" valid:"optional"` + NsmfEventExposure *LogSetting `yaml:"NsmfEventExposure" valid:"optional"` + NsmfPDUSession *LogSetting `yaml:"NsmfPDUSession" valid:"optional"` + NudmEventExposure *LogSetting `yaml:"NudmEventExposure" valid:"optional"` + NudmParameterProvision *LogSetting `yaml:"NudmParameterProvision" valid:"optional"` + NudmSubscriberDataManagement *LogSetting `yaml:"NudmSubscriberDataManagement" valid:"optional"` + NudmUEAuthentication *LogSetting `yaml:"NudmUEAuthentication" valid:"optional"` + NudmUEContextManagement *LogSetting `yaml:"NudmUEContextManagement" valid:"optional"` + NudrDataRepository *LogSetting `yaml:"NudrDataRepository" valid:"optional"` + PathUtil *LogSetting `yaml:"PathUtil" valid:"optional"` + PFCP *LogSetting `yaml:"PFCP" valid:"optional"` } func (l *Logger) Validate() (bool, error) { diff --git a/logger_conf/conf.go b/logger_conf/conf.go new file mode 100644 index 0000000..364c0f4 --- /dev/null +++ b/logger_conf/conf.go @@ -0,0 +1,65 @@ +// Copyright 2019 Communication Service/Software Laboratory, National Chiao Tung University (free5gc.org) +// +// SPDX-License-Identifier: Apache-2.0 + +package logger_conf + +import ( + "log" + "os" + "strconv" + + "github.com/omec-project/util/path_util" +) + +var Free5gcLogDir string = path_util.Free5gcPath("free5gc/log") + "/" +var LibLogDir string = Free5gcLogDir + "lib/" +var NfLogDir string = Free5gcLogDir + "nf/" + +var Free5gcLogFile string = Free5gcLogDir + "free5gc.log" + +func init() { + if err := os.MkdirAll(LibLogDir, 0775); err != nil { + log.Printf("Mkdir %s failed: %+v", LibLogDir, err) + } + if err := os.MkdirAll(NfLogDir, 0775); err != nil { + log.Printf("Mkdir %s failed: %+v", NfLogDir, err) + } + + // Create log file or if it already exist, check if user can access it + f, fileOpenErr := os.OpenFile(Free5gcLogFile, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666) + if fileOpenErr != nil { + // user cannot access it. + log.Printf("Cannot Open %s\n", Free5gcLogFile) + } else { + // user can access it + if err := f.Close(); err != nil { + log.Printf("File %s cannot been closed\n", Free5gcLogFile) + } + } + + sudoUID, errUID := strconv.Atoi(os.Getenv("SUDO_UID")) + sudoGID, errGID := strconv.Atoi(os.Getenv("SUDO_GID")) + + if errUID == nil && errGID == nil { + // if using sudo to run the program, errUID will be nil and sudoUID will get the uid who run sudo + // else errUID will not be nil and sudoUID will be nil + // If user using sudo to run the program and create log file, log will own by root, + // here we change own to user so user can view and reuse the file + if err := os.Chown(Free5gcLogDir, sudoUID, sudoGID); err != nil { + log.Printf("Dir %s chown to %d:%d error: %v\n", Free5gcLogDir, sudoUID, sudoGID, err) + } + if err := os.Chown(LibLogDir, sudoUID, sudoGID); err != nil { + log.Printf("Dir %s chown to %d:%d error: %v\n", LibLogDir, sudoUID, sudoGID, err) + } + if err := os.Chown(NfLogDir, sudoUID, sudoGID); err != nil { + log.Printf("Dir %s chown to %d:%d error: %v\n", NfLogDir, sudoUID, sudoGID, err) + } + + if fileOpenErr == nil { + if err := os.Chown(Free5gcLogFile, sudoUID, sudoGID); err != nil { + log.Printf("File %s chown to %d:%d error: %v\n", Free5gcLogFile, sudoUID, sudoGID, err) + } + } + } +} diff --git a/util_3gpp/3gpp_type.go b/util_3gpp/3gpp_type.go new file mode 100644 index 0000000..10b6492 --- /dev/null +++ b/util_3gpp/3gpp_type.go @@ -0,0 +1,21 @@ +// Copyright 2019 Communication Service/Software Laboratory, National Chiao Tung University (free5gc.org) +// +// SPDX-License-Identifier: Apache-2.0 + +package util_3gpp + +type Dnn []uint8 + +func (d *Dnn) MarshalBinary() (data []byte, err error) { + + data = append(data, uint8(len(*d))) + data = append(data, (*d)...) + + return data, nil +} + +func (d *Dnn) UnmarshalBinary(data []byte) error { + + (*d) = data[1:] + return nil +} diff --git a/util_3gpp/logger/logger.go b/util_3gpp/logger/logger.go new file mode 100644 index 0000000..85e19b8 --- /dev/null +++ b/util_3gpp/logger/logger.go @@ -0,0 +1,59 @@ +// Copyright 2019 Communication Service/Software Laboratory, National Chiao Tung University (free5gc.org) +// +// SPDX-License-Identifier: Apache-2.0 + +package logger + +import ( + "os" + "time" + + formatter "github.com/antonfisher/nested-logrus-formatter" + "github.com/sirupsen/logrus" + + "github.com/omec-project/util/logger_conf" + "github.com/omec-project/util/logger" +) + +var log *logrus.Logger + +// Util3GPPLog : Log entry of util_3gpp +var Util3GPPLog *logrus.Entry + +func init() { + log = logrus.New() + log.SetReportCaller(false) + + log.Formatter = &formatter.Formatter{ + TimestampFormat: time.RFC3339, + TrimMessages: true, + NoFieldsSpace: true, + HideKeys: true, + FieldsOrder: []string{"component", "category"}, + } + + free5gcLogHook, err := logger.NewFileHook(logger_conf.Free5gcLogFile, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666) + if err == nil { + log.Hooks.Add(free5gcLogHook) + } + + selfLogHook, err := logger.NewFileHook(logger_conf.LibLogDir+"util_3gpp.log", + os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666) + if err == nil { + log.Hooks.Add(selfLogHook) + } + + Util3GPPLog = log.WithFields(logrus.Fields{"component": "LIB", "category": "3GPP"}) +} + +// SetLogLevel : set the log level (panic|fatal|error|warn|info|debug|trace) +func SetLogLevel(level logrus.Level) { + Util3GPPLog.Infoln("set log level :", level) + log.SetLevel(level) +} + +// SetReportCaller : Set whether shows the filePath and functionName on loggers +func SetReportCaller(bool bool) { + Util3GPPLog.Infoln("set report call :", bool) + log.SetReportCaller(bool) +} diff --git a/util_3gpp/str_conv.go b/util_3gpp/str_conv.go new file mode 100644 index 0000000..392e76e --- /dev/null +++ b/util_3gpp/str_conv.go @@ -0,0 +1,18 @@ +// Copyright 2019 Communication Service/Software Laboratory, National Chiao Tung University (free5gc.org) +// +// SPDX-License-Identifier: Apache-2.0 + +package util_3gpp + +import ( + "fmt" + + "github.com/omec-project/openapi/models" +) + +func SNssaiToString(snssai *models.Snssai) (str string) { + if snssai.Sd == "" { + return fmt.Sprintf("%d-%s", snssai.Sst, snssai.Sd) + } + return fmt.Sprintf("%d", snssai.Sst) +} diff --git a/util_3gpp/suci/toSupi.go b/util_3gpp/suci/toSupi.go new file mode 100644 index 0000000..5bf5186 --- /dev/null +++ b/util_3gpp/suci/toSupi.go @@ -0,0 +1,381 @@ +// Copyright 2019 Communication Service/Software Laboratory, National Chiao Tung University (free5gc.org) +// +// SPDX-License-Identifier: Apache-2.0 + +package suci + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/elliptic" + "crypto/hmac" + "crypto/sha256" + "encoding/binary" + "encoding/hex" + "fmt" + "log" + "math" + "math/big" + "math/bits" + "strconv" + "strings" + + "golang.org/x/crypto/curve25519" + + "github.com/omec-project/util/util_3gpp/logger" +) + +type SuciProfile struct { + ProtectionScheme string `yaml:"ProtectionScheme,omitempty"` + PrivateKey string `yaml:"PrivateKey,omitempty"` + PublicKey string `yaml:"PublicKey,omitempty"` +} + +// profile A. +const ProfileAMacKeyLen = 32 // octets +const ProfileAEncKeyLen = 16 // octets +const ProfileAIcbLen = 16 // octets +const ProfileAMacLen = 8 // octets +const ProfileAHashLen = 32 // octets + +// profile B. +const ProfileBMacKeyLen = 32 // octets +const ProfileBEncKeyLen = 16 // octets +const ProfileBIcbLen = 16 // octets +const ProfileBMacLen = 8 // octets +const ProfileBHashLen = 32 // octets + +func CompressKey(uncompressed []byte, y *big.Int) []byte { + compressed := uncompressed[0:33] + if y.Bit(0) == 1 { // 0x03 + compressed[0] = 0x03 + } else { // 0x02 + compressed[0] = 0x02 + } + // fmt.Printf("compressed: %x\n", compressed) + return compressed +} + +// modified from https://stackoverflow.com/questions/46283760/ +// how-to-uncompress-a-single-x9-62-compressed-point-on-an-ecdh-p256-curve-in-go. +func uncompressKey(compressedBytes []byte, priv []byte) (*big.Int, *big.Int) { + // Split the sign byte from the rest + signByte := uint(compressedBytes[0]) + xBytes := compressedBytes[1:] + + x := new(big.Int).SetBytes(xBytes) + three := big.NewInt(3) + + // The params for P256 + c := elliptic.P256().Params() + + // The equation is y^2 = x^3 - 3x + b + // x^3, mod P + xCubed := new(big.Int).Exp(x, three, c.P) + + // 3x, mod P + threeX := new(big.Int).Mul(x, three) + threeX.Mod(threeX, c.P) + + // x^3 - 3x + b mod P + ySquared := new(big.Int).Sub(xCubed, threeX) + ySquared.Add(ySquared, c.B) + ySquared.Mod(ySquared, c.P) + + // find the square root mod P + y := new(big.Int).ModSqrt(ySquared, c.P) + if y == nil { + // If this happens then you're dealing with an invalid point. + logger.Util3GPPLog.Errorln("Uncompressed key with invalid point") + return nil, nil + } + + // Finally, check if you have the correct root. If not you want -y mod P + if y.Bit(0) != signByte&1 { + y.Neg(y) + y.Mod(y, c.P) + } + // fmt.Printf("xUncom: %x\nyUncon: %x\n", x, y) + return x, y +} + +func HmacSha256(input, macKey []byte, macLen int) []byte { + h := hmac.New(sha256.New, macKey) + if _, err := h.Write(input); err != nil { + log.Printf("HMAC SHA256 error %+v", err) + } + macVal := h.Sum(nil) + macTag := macVal[:macLen] + // fmt.Printf("macVal: %x\nmacTag: %x\n", macVal, macTag) + return macTag +} + +func Aes128ctr(input, encKey, icb []byte) []byte { + output := make([]byte, len(input)) + block, err := aes.NewCipher(encKey) + if err != nil { + log.Printf("AES128 CTR error %+v", err) + } + stream := cipher.NewCTR(block, icb) + stream.XORKeyStream(output, input) + // fmt.Printf("aes input: %x %x %x\naes output: %x\n", input, encKey, icb, output) + return output +} + +func AnsiX963KDF(sharedKey, publicKey []byte, profileEncKeyLen, profileMacKeyLen, profileHashLen int) []byte { + var counter uint32 = 0x00000001 + var kdfKey []byte + kdfRounds := int(math.Ceil(float64(profileEncKeyLen+profileMacKeyLen) / float64(profileHashLen))) + for i := 1; i <= kdfRounds; i++ { + counterBytes := make([]byte, 4) + binary.BigEndian.PutUint32(counterBytes, counter) + // fmt.Printf("counterBytes: %x\n", counterBytes) + tmpK := sha256.Sum256(append(append(sharedKey, counterBytes...), publicKey...)) + sliceK := tmpK[:] + kdfKey = append(kdfKey, sliceK...) + // fmt.Printf("kdfKey in round %d: %x\n", i, kdfKey) + counter++ + } + return kdfKey +} + +func swapNibbles(input []byte) []byte { + output := make([]byte, len(input)) + for i, b := range input { + output[i] = bits.RotateLeft8(b, 4) + } + return output +} + +func calcSchemeResult(decryptPlainText []byte, supiType string) string { + var schemeResult string + if supiType == typeIMSI { + schemeResult = hex.EncodeToString(swapNibbles(decryptPlainText)) + if schemeResult[len(schemeResult)-1] == 'f' { + schemeResult = schemeResult[:len(schemeResult)-1] + } + } else { + schemeResult = hex.EncodeToString(decryptPlainText) + } + return schemeResult +} + +func profileA(input, supiType, privateKey string) (string, error) { + logger.Util3GPPLog.Infoln("SuciToSupi Profile A") + s, hexDecodeErr := hex.DecodeString(input) + if hexDecodeErr != nil { + logger.Util3GPPLog.Errorln("hex DecodeString error") + return "", hexDecodeErr + } + + // for X25519(profile A), q (The number of elements in the field Fq) = 2^255 - 19 + // len(pubkey) is therefore ceil((log2q)/8+1) = 32octets + ProfileAPubKeyLen := 32 + if len(s) < ProfileAPubKeyLen+ProfileAMacLen { + logger.Util3GPPLog.Errorln("len of input data is too short!") + return "", fmt.Errorf("suci input too short\n") + } + + decryptMac := s[len(s)-ProfileAMacLen:] + decryptPublicKey := s[:ProfileAPubKeyLen] + decryptCipherText := s[ProfileAPubKeyLen : len(s)-ProfileAMacLen] + // fmt.Printf("dePub: %x\ndeCiph: %x\ndeMac: %x\n", decryptPublicKey, decryptCipherText, decryptMac) + + // test data from TS33.501 Annex C.4 + // aHNPriv, _ := hex.DecodeString("c53c2208b61860b06c62e5406a7b330c2b577aa5558981510d128247d38bd1d") + var aHNPriv []byte + if aHNPrivTmp, err := hex.DecodeString(privateKey); err != nil { + log.Printf("Decode error: %+v", err) + } else { + aHNPriv = aHNPrivTmp + } + var decryptSharedKey []byte + if decryptSharedKeyTmp, err := curve25519.X25519(aHNPriv, []byte(decryptPublicKey)); err != nil { + log.Printf("X25519 error: %+v", err) + } else { + decryptSharedKey = decryptSharedKeyTmp + } + // fmt.Printf("deShared: %x\n", decryptSharedKey) + + kdfKey := AnsiX963KDF(decryptSharedKey, decryptPublicKey, ProfileAEncKeyLen, ProfileAMacKeyLen, ProfileAHashLen) + decryptEncKey := kdfKey[:ProfileAEncKeyLen] + decryptIcb := kdfKey[ProfileAEncKeyLen : ProfileAEncKeyLen+ProfileAIcbLen] + decryptMacKey := kdfKey[len(kdfKey)-ProfileAMacKeyLen:] + // fmt.Printf("\ndeEncKey(size%d): %x\ndeMacKey: %x\ndeIcb: %x\n", len(decryptEncKey), decryptEncKey, decryptMacKey, + // decryptIcb) + + decryptMacTag := HmacSha256(decryptCipherText, decryptMacKey, ProfileAMacLen) + if bytes.Equal(decryptMacTag, decryptMac) { + logger.Util3GPPLog.Infoln("decryption MAC match") + } else { + logger.Util3GPPLog.Errorln("decryption MAC failed") + return "", fmt.Errorf("decryption MAC failed\n") + } + + decryptPlainText := Aes128ctr(decryptCipherText, decryptEncKey, decryptIcb) + + return calcSchemeResult(decryptPlainText, supiType), nil +} + +func profileB(input, supiType, privateKey string) (string, error) { + logger.Util3GPPLog.Infoln("SuciToSupi Profile B") + s, hexDecodeErr := hex.DecodeString(input) + if hexDecodeErr != nil { + logger.Util3GPPLog.Errorln("hex DecodeString error") + return "", hexDecodeErr + } + + var ProfileBPubKeyLen int // p256, module q = 2^256 - 2^224 + 2^192 + 2^96 - 1 + var uncompressed bool + if s[0] == 0x02 || s[0] == 0x03 { + ProfileBPubKeyLen = 33 // ceil(log(2, q)/8) + 1 = 33 + uncompressed = false + } else if s[0] == 0x04 { + ProfileBPubKeyLen = 65 // 2*ceil(log(2, q)/8) + 1 = 65 + uncompressed = true + } else { + logger.Util3GPPLog.Errorln("input error") + return "", fmt.Errorf("suci input error\n") + } + + // fmt.Printf("len:%d %d\n", len(s), ProfileBPubKeyLen + ProfileBMacLen) + if len(s) < ProfileBPubKeyLen+ProfileBMacLen { + logger.Util3GPPLog.Errorln("len of input data is too short!") + return "", fmt.Errorf("suci input too short\n") + } + decryptPublicKey := s[:ProfileBPubKeyLen] + decryptMac := s[len(s)-ProfileBMacLen:] + decryptCipherText := s[ProfileBPubKeyLen : len(s)-ProfileBMacLen] + // fmt.Printf("dePub: %x\ndeCiph: %x\ndeMac: %x\n", decryptPublicKey, decryptCipherText, decryptMac) + + // test data from TS33.501 Annex C.4 + // bHNPriv, _ := hex.DecodeString("F1AB1074477EBCC7F554EA1C5FC368B1616730155E0041AC447D6301975FECDA") + var bHNPriv []byte + if bHNPrivTmp, err := hex.DecodeString(privateKey); err != nil { + log.Printf("Decode error: %+v", err) + } else { + bHNPriv = bHNPrivTmp + } + + var xUncompressed, yUncompressed *big.Int + if uncompressed { + xUncompressed = new(big.Int).SetBytes(decryptPublicKey[1:(ProfileBPubKeyLen/2 + 1)]) + yUncompressed = new(big.Int).SetBytes(decryptPublicKey[(ProfileBPubKeyLen/2 + 1):]) + } else { + xUncompressed, yUncompressed = uncompressKey(decryptPublicKey, bHNPriv) + if xUncompressed == nil || yUncompressed == nil { + logger.Util3GPPLog.Errorln("Uncompressed key has invalid point") + return "", fmt.Errorf("Key uncompression error\n") + } + } + // fmt.Printf("xUncom: %x\nyUncom: %x\n", xUncompressed, yUncompressed) + + // x-coordinate is the shared key + decryptSharedKey, _ := elliptic.P256().ScalarMult(xUncompressed, yUncompressed, bHNPriv) + // fmt.Printf("deShared: %x\n", decryptSharedKey.Bytes()) + + decryptPublicKeyForKDF := decryptPublicKey + if uncompressed { + decryptPublicKeyForKDF = CompressKey(decryptPublicKey, yUncompressed) + } + + kdfKey := AnsiX963KDF(decryptSharedKey.Bytes(), decryptPublicKeyForKDF, ProfileBEncKeyLen, ProfileBMacKeyLen, + ProfileBHashLen) + // fmt.Printf("kdfKey: %x\n", kdfKey) + decryptEncKey := kdfKey[:ProfileBEncKeyLen] + decryptIcb := kdfKey[ProfileBEncKeyLen : ProfileBEncKeyLen+ProfileBIcbLen] + decryptMacKey := kdfKey[len(kdfKey)-ProfileBMacKeyLen:] + // fmt.Printf("\ndeEncKey(size%d): %x\ndeMacKey: %x\ndeIcb: %x\n", len(decryptEncKey), decryptEncKey, decryptMacKey, + // decryptIcb) + + decryptMacTag := HmacSha256(decryptCipherText, decryptMacKey, ProfileBMacLen) + if bytes.Equal(decryptMacTag, decryptMac) { + logger.Util3GPPLog.Infoln("decryption MAC match") + } else { + logger.Util3GPPLog.Errorln("decryption MAC failed") + return "", fmt.Errorf("decryption MAC failed\n") + } + + decryptPlainText := Aes128ctr(decryptCipherText, decryptEncKey, decryptIcb) + + return calcSchemeResult(decryptPlainText, supiType), nil +} + +// suci-0(SUPI type)-mcc-mnc-routingIndentifier-protectionScheme-homeNetworkPublicKeyIdentifier-schemeOutput. +const supiTypePlace = 1 +const mccPlace = 2 +const mncPlace = 3 +const schemePlace = 5 +const HNPublicKeyIDPlace = 6 + +const typeIMSI = "0" +const imsiPrefix = "imsi-" +const nullScheme = "0" +const profileAScheme = "1" +const profileBScheme = "2" + +func ToSupi(suci string, suciProfiles []SuciProfile) (string, error) { + suciPart := strings.Split(suci, "-") + logger.Util3GPPLog.Infof("suciPart: %+v", suciPart) + + suciPrefix := suciPart[0] + if suciPrefix == "imsi" || suciPrefix == "nai" { + logger.Util3GPPLog.Infof("Got supi\n") + return suci, nil + + } else if suciPrefix == "suci" { + if len(suciPart) < 6 { + return "", fmt.Errorf("Suci with wrong format\n") + } + + } else { + return "", fmt.Errorf("Unknown suciPrefix [%s]", suciPrefix) + } + + logger.Util3GPPLog.Infof("scheme %s\n", suciPart[schemePlace]) + scheme := suciPart[schemePlace] + mccMnc := suciPart[mccPlace] + suciPart[mncPlace] + + supiPrefix := imsiPrefix + if suciPrefix == "suci" && suciPart[supiTypePlace] == typeIMSI { + supiPrefix = imsiPrefix + logger.Util3GPPLog.Infof("SUPI type is IMSI\n") + } + + if scheme == nullScheme { // NULL scheme + return supiPrefix + mccMnc + suciPart[len(suciPart)-1], nil + } + + // (HNPublicKeyID-1) is the index of "suciProfiles" slices + keyIndex, err := strconv.Atoi(suciPart[HNPublicKeyIDPlace]) + if err != nil { + return "", fmt.Errorf("Parse HNPublicKeyID error: %+v", err) + } + if keyIndex > len(suciProfiles) { + return "", fmt.Errorf("keyIndex(%d) out of range(%d)", keyIndex, len(suciProfiles)) + } + + protectScheme := suciProfiles[keyIndex-1].ProtectionScheme + privateKey := suciProfiles[keyIndex-1].PrivateKey + + if scheme != protectScheme { + return "", fmt.Errorf("Protect Scheme mismatch [%s:%s]", scheme, protectScheme) + } + + if scheme == profileAScheme { + if profileAResult, err := profileA(suciPart[len(suciPart)-1], suciPart[supiTypePlace], privateKey); err != nil { + return "", err + } else { + return supiPrefix + mccMnc + profileAResult, nil + } + } else if scheme == profileBScheme { + if profileBResult, err := profileB(suciPart[len(suciPart)-1], suciPart[supiTypePlace], privateKey); err != nil { + return "", err + } else { + return supiPrefix + mccMnc + profileBResult, nil + } + } else { + return "", fmt.Errorf("Protect Scheme (%s) is not supported", scheme) + } +}