pespin has submitted this change. ( https://gerrit.osmocom.org/c/osmo-ttcn3-hacks/+/40413?usp=email )
Change subject: 5gc: Implement AKA authentication ...................................................................... 5gc: Implement AKA authentication deps/nas.git already has some Auth related functionaltities to calculate XRES*, but I couldn't get them to generate proper output. Furthermore, using them directly would include a lot of churn we don't want: * ttcn: Otherwise ttcn/Lib_NG_NAS/LIB_NG_NAS_Functions.ttcn brings in tons of dependencies about Emulation stuff which is not interesting for us. * .cc: We want to implement our own low level functions using our eclipse Titan API, as well as our own C lib dependencies (because some code for those dependencies is not really available in the repo). Hence, instead use our own milenage.c implementation imported from libosmocore (actually from a slightly modified self-contained copy we already ported to some development branch of asterisk VoLTE project). With this patch it is already possible to detect SQN Out-of-sync and answer with an Auth Failure, then get a new Auth Req from network and properly answer with an Auth Resp which the network likes and hence answers with a Security Mode Command. Change-Id: I11527f47e4310863124f3f02148e3f71da7d911e --- M 5gc/C5G_Tests.cfg M 5gc/C5G_Tests.ttcn M 5gc/gen_links.sh M 5gc/regen_makefile.sh M deps/Makefile M library/General_Types.ttcn A library/NG_CryptoFunctionDefs.cc A library/NG_CryptoFunctions.ttcn M library/NG_NAS_Osmo_Templates.ttcn A library/milenage/Milenage_FunctionDefs.cc A library/milenage/Milenage_Functions.ttcn A library/milenage/milenage.c A library/milenage/milenage.h A library/ng_crypto/key_derivation.c A library/ng_crypto/key_derivation.h 15 files changed, 837 insertions(+), 17 deletions(-) Approvals: jolly: Looks good to me, but someone else must approve Jenkins Builder: Verified laforge: Looks good to me, but someone else must approve pespin: Looks good to me, approved diff --git a/5gc/C5G_Tests.cfg b/5gc/C5G_Tests.cfg index 85ad9a3..d45b963 100644 --- a/5gc/C5G_Tests.cfg +++ b/5gc/C5G_Tests.cfg @@ -16,6 +16,9 @@ mp_5gc_ngap_port := 38412; mp_local_ngap_ip := "127.0.0.202"; mp_local_ngap_port := 50000; +mp_imsi := '999700000000000'H; +mp_usim_key := '762a2206fe0b4151ace403c86a11e479'O; +mp_usim_opc := '3c6e0b8a9c15224a8228b9a98ca1531d'O; [MAIN_CONTROLLER] diff --git a/5gc/C5G_Tests.ttcn b/5gc/C5G_Tests.ttcn index 9b45298..9cf5ec3 100644 --- a/5gc/C5G_Tests.ttcn +++ b/5gc/C5G_Tests.ttcn @@ -19,6 +19,8 @@ import from Osmocom_Types all; import from GSM_Types all; +import from Milenage_Functions all; + import from NGAP_PDU_Descriptions all; import from NGAP_IEs all; import from NGAP_PDU_Contents all; @@ -31,14 +33,14 @@ import from NGAP_Emulation all; import from NAS_CommonTypeDefs all; - +import from NAS_CommonTemplates all; import from NG_NAS_Common all; import from NG_NAS_MsgContainers all; -import from NAS_CommonTemplates all; import from NG_NAS_Templates all; import from NG_NAS_Osmo_Templates all; import from NG_NAS_Functions all; +import from NG_CryptoFunctions all; /* (maximum) number of emulated eNBs */ const integer NUM_NGRAN := 1; @@ -56,6 +58,8 @@ GsmMcc mp_mcc := '999'H; GsmMnc mp_mnc := '70'H; hexstring mp_imsi := '999700000000000'H; + octetstring mp_usim_key := '762a2206fe0b4151ace403c86a11e479'O; + octetstring mp_usim_opc := '3c6e0b8a9c15224a8228b9a98ca1531d'O; uint24_t mp_tac := 1; } @@ -69,6 +73,8 @@ /* parameters of emulated UE */ type record UeParams { hexstring imsi, + octetstring usim_key, + octetstring usim_opc, charstring ue_ip } @@ -145,6 +151,8 @@ uep := { imsi := f_concat_pad(lengthof(mp_imsi), substr(mp_imsi, 0, lengthof(mp_imsi) - 6), imsi_suffix), + usim_key := mp_usim_key, + usim_opc := mp_usim_opc, ue_ip := "192.168.123.50" } } @@ -211,12 +219,18 @@ iE_Extensions := omit } -private function f_SUCI_IMSI() runs on ConnHdlr return octetstring { +private function f_imsi_plmn_id() runs on ConnHdlr return PLMNIdentity { var hexstring imsi := g_pars.ue_pars.imsi; var GsmMcc mcc := substr(imsi, 0, 3); var GsmMnc mnc := substr(imsi, 3, 2); - var octetstring imsi_suffix := imsi_hex2oct(substr(imsi, lengthof(imsi)-10, 10)); - return f_enc_mcc_mnc(mcc, mnc) & '21430001'O & imsi_suffix; + return f_enc_mcc_mnc(mcc, mnc); +} + +private function f_SUCI_IMSI() runs on ConnHdlr return octetstring { + var hexstring imsi := g_pars.ue_pars.imsi; + var PLMNIdentity plmn_id := f_imsi_plmn_id(); + var octetstring imsi_suffix := imsi_hex2oct(substr(imsi, lengthof(imsi) - 10, 10)); + return plmn_id & '21430001'O & imsi_suffix; } friend function f_ngap_setup(integer idx := 0, template (omit) NGAP_IEs.Cause cause := omit) runs on MTC_CT { @@ -270,17 +284,60 @@ } } -private altstep as_ngap_handle_auth() runs on ConnHdlr { +/* 3GPP TS 24.501 5.4.1.3.2, 3GPP TS 33.501 6.1.3.2 */ +private altstep as_ngap_handle_auth(boolean allow_resync := true) runs on ConnHdlr { var NG_NAS_DL_Message_Type rx_nas; var template (present) NAS_KeySetIdentifier kset_id := f_tr_ConnHdlr_kset_id(); [] NGAP.receive(cr_NG_AUTHENTICATION_REQUEST) -> value rx_nas { log("Rx NAS message: ", rx_nas); + var integer ret; + var integer my_sqn := 0; /* TODO: move to a ConnHdlr state attribute? */ + var OCT16 rand := bit2oct(rx_nas.authentication_Request.rand.randValue); + var OCT16 autn := bit2oct(rx_nas.authentication_Request.autn.aUTN); + var OCT16 ik; + var OCT16 ck; + var OCT8 res; + var OCT14 auts; g_pars.kset_id := rx_nas.authentication_Request.ngNasKeySetId; - /* TODO: generate proper RES, 3GPP TS 33.501 A.4 */ - const OCT8 res := '6a91970e838fd079'O; - NGAP.send(cs_NG_AUTHENTICATION_RESPONSE(cs_AuthenticationResponseParameter(oct2bit(res)))); + ret := f_milenage_check(g_pars.ue_pars.usim_key, + g_pars.ue_pars.usim_opc, + int2oct(my_sqn, 8), + rand, autn, ik, ck, res, auts); + if (ret == -1) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str("fx_milenage_check() failed! -1")); + } + if (ret == -2) { + if (not allow_resync) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str("fx_milenage_check() unexpected Resync!")); + } + log("Milenage: RX-SQN differs from SIM SQN: Re-Sync! AUTS=", auts); + /* 3GPP TS 24.501 5.4.1.3.6 d) */ + NGAP.send(cs_NG_AUTHENTICATION_FAILURE(cs_GMM_GSM_Cause(omit, int2bit(21, 8)), + cs_AuthFailParam(oct2bit(auts)))); + as_ngap_handle_auth(allow_resync := false); + return; + } + + /* Derive (X)RES* from (X)RES, 3GPP TS 33.501 A.4 */ + var octetstring ssn := f_NG_NAS_ServingNetworkName_OCT(f_imsi_plmn_id(), omit); + var OCT16 res_star := f_kdf_xres_star(ssn, ck, ik, rand, res); + log("Auth Response: RES=", res, ", SSN=", ssn, " / ", oct2char(ssn), ", RES*=", res_star); + + NGAP.send(cs_NG_AUTHENTICATION_RESPONSE(cs_AuthenticationResponseParameter(oct2bit(res_star)))); + } +} + +private altstep as_ngap_handle_sec_mode() runs on ConnHdlr { + var NG_NAS_DL_Message_Type rx_nas; + + [] NGAP.receive(cr_NG_SECURITY_PROTECTED_NAS_MESSAGE) -> value rx_nas { + var NG_NAS_DL_Message_Type in_nas := dec_NG_NAS_DL_Message_Type(rx_nas.security_Protected_Nas_Message.plainNASMessage); + log("PESPIN: Rx inner NAS: ", in_nas); + /* TODO: apply below integrity and ciphering based on Security Mode Command */ } } @@ -320,6 +377,7 @@ } as_ngap_handle_auth(); + as_ngap_handle_sec_mode(); /* TODO: handle Auth, SecurityModeCommand, InitialContextSetup, PDUSessionresource, */ f_sleep(5.0); diff --git a/5gc/gen_links.sh b/5gc/gen_links.sh index 295ca24..9f67736 100755 --- a/5gc/gen_links.sh +++ b/5gc/gen_links.sh @@ -61,12 +61,21 @@ FILES+="NGAP_EncDec.cc NGAP_Types.ttcn NGAP_Pixits.ttcn NGAP_Templates.ttcn " gen_links $DIR $FILES +DIR=../library/milenage +FILES="milenage.c milenage.h Milenage_FunctionDefs.cc Milenage_Functions.ttcn " +gen_links $DIR $FILES + +DIR=../library/ng_crypto +FILES="key_derivation.c key_derivation.h " +gen_links $DIR $FILES + DIR=../library FILES="Misc_Helpers.ttcn General_Types.ttcn GSM_Types.ttcn Osmocom_Types.ttcn Native_Functions.ttcn Native_FunctionDefs.cc IPCP_Types.ttcn IPCP_Templates.ttcn " FILES+="SCTP_Templates.ttcn " FILES+="DNS_Helpers.ttcn " FILES+="NGAP_CodecPort.ttcn NGAP_CodecPort_CtrlFunctDef.cc NGAP_CodecPort_CtrlFunct.ttcn NGAP_Functions.ttcn NGAP_Emulation.ttcn " FILES+="NG_NAS_Osmo_Templates.ttcn NG_NAS_Functions.ttcn " +FILES+="NG_CryptoFunctionDefs.cc NG_CryptoFunctions.ttcn " gen_links $DIR $FILES gen_links_finish diff --git a/5gc/regen_makefile.sh b/5gc/regen_makefile.sh index 44f7a56..0d53583 100755 --- a/5gc/regen_makefile.sh +++ b/5gc/regen_makefile.sh @@ -4,13 +4,16 @@ FILES=" *.asn + *.c *.ttcn IPL4asp_PT.cc IPL4asp_discovery.cc Native_FunctionDefs.cc common_ext.cc + Milenage_FunctionDefs.cc NGAP_CodecPort_CtrlFunctDef.cc NGAP_EncDec.cc + NG_CryptoFunctionDefs.cc TCCConversion.cc TCCEncoding.cc TCCInterface.cc diff --git a/deps/Makefile b/deps/Makefile index aa2b36c..b7e06cb 100644 --- a/deps/Makefile +++ b/deps/Makefile @@ -85,7 +85,7 @@ # Do not put references to branches here, except for local testing: this breaks the caching # logic of docker containers, which only invalidate their cached ttcn3 dependencies if this # file changed. -nas_commit= 4371752494b7c85cd5a3bc32bc6f498171c7aa13 +nas_commit= f7921c4d88e204e09067f0975957d075be82e800 titan.Libraries.TCCUsefulFunctions_commit= R.35.B-6-gb3687da titan.ProtocolEmulations.M3UA_commit= b58f92046e48a7b1ed531e243a2319ebca53bf4c titan.ProtocolEmulations.SCCP_commit= 750a3e836831e58eae59d4757ef5d0c759f9ca5d diff --git a/library/General_Types.ttcn b/library/General_Types.ttcn index 2daa593..163ec47 100644 --- a/library/General_Types.ttcn +++ b/library/General_Types.ttcn @@ -84,7 +84,11 @@ type bitstring BIT30 length(30) with { variant "FIELDLENGTH(30)" }; type bitstring BIT31 length(31) with { variant "FIELDLENGTH(31)" }; type bitstring BIT32 length(32) with { variant "FIELDLENGTH(32)" }; + type bitstring BIT48 length(48) with { variant "FIELDLENGTH(48)" }; type bitstring BIT56 length(56) with { variant "FIELDLENGTH(56)" }; + type bitstring BIT64 length(64) with { variant "FIELDLENGTH(64)" }; + type bitstring BIT128 length(128) with { variant "FIELDLENGTH(128)" }; + type bitstring BIT256 length(256) with { variant "FIELDLENGTH(256)" }; //**************************************************** // Octetstrings diff --git a/library/NG_CryptoFunctionDefs.cc b/library/NG_CryptoFunctionDefs.cc new file mode 100644 index 0000000..e45e519 --- /dev/null +++ b/library/NG_CryptoFunctionDefs.cc @@ -0,0 +1,40 @@ +/* AKA USIM Utility functions */ + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <stdint.h> +#include <gnutls/crypto.h> + +#include <Addfunc.hh> +#include <Encdec.hh> +#include <Boolean.hh> +#include <Integer.hh> +#include <Octetstring.hh> +#include <Bitstring.hh> + +#include "key_derivation.h" + +namespace NG__CryptoFunctions { + +/* 3GPP TS 33.501 A.4 RES* and XRES* derivation function */ +OCTETSTRING f__kdf__xres__star(const OCTETSTRING &ssn, const OCTETSTRING& ck, const OCTETSTRING& ik, + const OCTETSTRING& rand, const OCTETSTRING& xres) +{ + TTCN_Buffer ttcn_buf_ssn(ssn); + TTCN_Buffer ttcn_buf_ck(ck); + TTCN_Buffer ttcn_buf_ik(ik); + TTCN_Buffer ttcn_buf_rand(rand); + TTCN_Buffer ttcn_buf_xres(xres); + uint8_t xres_star[16]; + + kdf_xres_star(ttcn_buf_ssn.get_data(), ttcn_buf_ssn.get_len(), + ttcn_buf_ck.get_data(), + ttcn_buf_ik.get_data(), + ttcn_buf_rand.get_data(), + ttcn_buf_xres.get_data(), ttcn_buf_xres.get_len(), + xres_star); + return OCTETSTRING(sizeof(xres_star), xres_star); +} + +} diff --git a/library/NG_CryptoFunctions.ttcn b/library/NG_CryptoFunctions.ttcn new file mode 100644 index 0000000..fbbe57e --- /dev/null +++ b/library/NG_CryptoFunctions.ttcn @@ -0,0 +1,78 @@ +/* Utility crypto functions for NG NAS (5G) + * + * (C) 2025 by sysmocom - s.f.m.c. GmbH <i...@sysmocom.de> + * Author: Pau Espin Pedrol <pes...@sysmocom.de + * All rights reserved. + * + * Released under the terms of GNU General Public License, Version 2 or + * (at your option) any later version. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +module NG_CryptoFunctions { + +import from General_Types all; +import from Misc_Helpers all; + +import from NAS_CommonTypeDefs all; + +/********************************************************************************* + * low-level API (external C/C++ code) + *********************************************************************************/ + +/* 3GPP TS 33.102 Figure 9, 3GPP TS 35.206 Annex 3 */ +external function f_kdf_xres_star(octetstring ssn, OCT16 ck, OCT16 ik, OCT16 rand, + octetstring xres) return OCT16; + +/********************************************************************************* + * mid-level API + *********************************************************************************/ + +function f_NG_NAS_SNN_MNC(NAS_PlmnId plmn_id) return charstring +{ + var charstring mnc := ""; + var hexstring plmn_hex := oct2hex(plmn_id); + + if (plmn_hex[2] == 'F'H) { + mnc := "0"; + } + mnc := mnc & hex2str(plmn_hex[5]) & hex2str(plmn_hex[4]); + if (plmn_hex[2] != 'F'H) { + /* add last digit if not F */ + mnc := mnc & hex2str(plmn_hex[2]); + } + return mnc; +} + +function f_NG_NAS_SNN_MCC(NAS_PlmnId plmn_id) return charstring +{ + var charstring mcc; + var hexstring plmn_hex := oct2hex(plmn_id); + + mcc := hex2str(plmn_hex[1]) & + hex2str(plmn_hex[0]) & + hex2str(plmn_hex[3]); + + return mcc; +} + +function f_NG_NAS_ServingNetworkName(NAS_PlmnId plmn_id, template (omit) hexstring NID := omit) return charstring +{ + var charstring ssn := "5G:mnc" & f_NG_NAS_SNN_MNC(plmn_id) & + ".mcc" & f_NG_NAS_SNN_MCC(plmn_id) & + ".3gppnetwork.org"; + + if (isvalue(NID)) { + ssn := ssn & ":" & hex2str (valueof(NID)); + } + + return ssn; +} + +function f_NG_NAS_ServingNetworkName_OCT(NAS_PlmnId plmn_id, template (omit) hexstring NID := omit) return octetstring +{ + return char2oct(f_NG_NAS_ServingNetworkName(plmn_id, NID)); +} + +} diff --git a/library/NG_NAS_Osmo_Templates.ttcn b/library/NG_NAS_Osmo_Templates.ttcn index 1108947..5887fa6 100644 --- a/library/NG_NAS_Osmo_Templates.ttcn +++ b/library/NG_NAS_Osmo_Templates.ttcn @@ -54,6 +54,13 @@ res := p_ExpectedRES } + template (value) AuthenticationFailureParameter cs_AuthFailParam(template (value) B112_Type auts) := + { + iei := '30'O, // version 110, and value used in GMM + iel := '0E'O, + auts := auts + }; + /* 24.501 cl. 8.2.1 */ template (present) NG_NAS_DL_Message_Type @@ -63,7 +70,7 @@ template AUTN p_AUTN := *, template EAP_Message p_EAP := * ) := -{ /* 24.501 cl. 8.2.1 */ +{ authentication_Request := { protocolDiscriminator := tsc_EPD_GMM, /* cl. 9.2 M V 1 */ spareHalfOctet := tsc_SpareHalfOctet, /* cl. 9.3 M V 1/2 */ @@ -84,13 +91,47 @@ template (omit) EAP_Message p_EAP := omit) := { authentication_Response := { - protocolDiscriminator := tsc_EPD_GMM, /* cl. 9.2 M V 1 */ - spareHalfOctet := tsc_SpareHalfOctet, /* cl. 9.3 M V 1/2 */ - securityHeaderType := tsc_SHT_NoSecurityProtection, - messageType := tsc_MT_NG_AuthenticationResponse, /* cl. 9.7 M V 1 */ - authResponseParam := p_Res, /* cl. 9.11.3.17 O TLV 18 IEI=2D */ - eapMessage := p_EAP /* cl. 9.11.2.2 O TLV-E 7-1503 IEI=78 */ + protocolDiscriminator := tsc_EPD_GMM, /* cl. 9.2 M V 1 */ + spareHalfOctet := tsc_SpareHalfOctet, /* cl. 9.3 M V 1/2 */ + securityHeaderType := tsc_SHT_NoSecurityProtection, + messageType := tsc_MT_NG_AuthenticationResponse, /* cl. 9.7 M V 1 */ + authResponseParam := p_Res, /* cl. 9.11.3.17 O TLV 18 IEI=2D */ + eapMessage := p_EAP /* cl. 9.11.2.2 O TLV-E 7-1503 IEI=78 */ + } } + +/* 24.501 cl. 8.2.4 */ +template (value) NG_NAS_UL_Message_Type cs_NG_AUTHENTICATION_FAILURE(template (value) GMM_GSM_Cause p_Cause, + template (omit) AuthenticationFailureParameter p_AuthFailParam := omit) := +{ + authentication_Failure := { + protocolDiscriminator := tsc_EPD_GMM, /* cl. 9.2 M V 1 */ + spareHalfOctet := tsc_SpareHalfOctet, /* cl. 9.3 M V 1/2 */ + securityHeaderType := tsc_SHT_NoSecurityProtection, + messageType := tsc_MT_NG_AuthenticationFailure, /* cl. 9.7 M V 1 */ + gmmCause := p_Cause, /* cl. 9.11.3.2 M V 1 */ + authFailureParam := p_AuthFailParam /* cl. 9.11.3.12 O TLV 16 IEI=30 */ + } +} + + +/* 24.501 cl. 8.2.28 */ +template (present) NG_NAS_DL_Message_Type +cr_NG_SECURITY_PROTECTED_NAS_MESSAGE(template (present) ExtdProtocolDiscriminator p_protocolDiscriminator := ?, + template (present) SpareHalfOctet p_spareHalfOctet := ?, + template (present) SecurityHeaderType p_securityHeaderType := ?, + template (present) MessageAuthenticationCode p_messageAuthenticationCode := ?, + template (present) NAS_SequenceNumber p_sequenceNumber := ?, + template (present) NG_NAS_Message p_plainNASMessage := ?) := +{ + security_Protected_Nas_Message := { + protocolDiscriminator := p_protocolDiscriminator, /* cl. 9.2 M V 1 */ + spareHalfOctet := p_spareHalfOctet, /* cl. 9.5 M V 1/2 */ + securityHeaderType := p_securityHeaderType, /* cl. 9.3 M V 1/2 */ + messageAuthenticationCode := p_messageAuthenticationCode, /* cl. 9.8 M V 4 */ + sequenceNumber := p_sequenceNumber, /* cl. 9.10 M V 1 */ + plainNASMessage := p_plainNASMessage /* cl. 9.9 M V 3-n */ + } } } diff --git a/library/milenage/Milenage_FunctionDefs.cc b/library/milenage/Milenage_FunctionDefs.cc new file mode 100644 index 0000000..ddd399f --- /dev/null +++ b/library/milenage/Milenage_FunctionDefs.cc @@ -0,0 +1,53 @@ +/* AKA USIM Utility functions */ + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <stdint.h> + +#include <Addfunc.hh> +#include <Encdec.hh> +#include <Boolean.hh> +#include <Integer.hh> +#include <Octetstring.hh> +#include <Bitstring.hh> + +#include "milenage.h" + +namespace Milenage__Functions { + +/* 3GPP TS 33.102 Figure 9, 3GPP TS 35.206 Annex 3 */ +INTEGER f__milenage__check(const OCTETSTRING& opc, const OCTETSTRING& k, + const OCTETSTRING& sqn, const OCTETSTRING& _rand, const OCTETSTRING& autn, + OCTETSTRING& ik, OCTETSTRING& ck, OCTETSTRING& res, OCTETSTRING& auts) +{ + TTCN_Buffer ttcn_buf_opc(opc); + TTCN_Buffer ttcn_buf_k(k); + TTCN_Buffer ttcn_buf_sqn(sqn); + TTCN_Buffer ttcn_buf_rand(_rand); + TTCN_Buffer ttcn_buf_autn(autn); + uint8_t buf_ik[16]; + uint8_t buf_ck[16]; + uint8_t buf_res[8]; + uint8_t buf_auts[14]; + size_t res_len = 0; + int rc; + + rc = milenage_check(ttcn_buf_opc.get_data(), + ttcn_buf_k.get_data(), + ttcn_buf_sqn.get_data(), + ttcn_buf_rand.get_data(), + ttcn_buf_autn.get_data(), + buf_ik, buf_ck, + buf_res, &res_len, + buf_auts); + + ik = OCTETSTRING(16, static_cast<const unsigned char*>(&buf_ik[0])); + ck = OCTETSTRING(16, static_cast<const unsigned char*>(&buf_ck[0])); + res = OCTETSTRING(res_len, static_cast<const unsigned char*>(&buf_res[0])); + auts = OCTETSTRING(14, static_cast<const unsigned char*>(&buf_auts[0])); + + return INTEGER(rc); +} + +} diff --git a/library/milenage/Milenage_Functions.ttcn b/library/milenage/Milenage_Functions.ttcn new file mode 100644 index 0000000..8fb9b04 --- /dev/null +++ b/library/milenage/Milenage_Functions.ttcn @@ -0,0 +1,24 @@ +/* Milenage crypto functions (USIM AKA) + * + * (C) 2025 by sysmocom - s.f.m.c. GmbH <i...@sysmocom.de> + * Author: Pau Espin Pedrol <pes...@sysmocom.de + * All rights reserved. + * + * Released under the terms of GNU General Public License, Version 2 or + * (at your option) any later version. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +module Milenage_Functions { + +import from General_Types all; + +/* 3GPP TS 33.102 Figure 9, 3GPP TS 35.206 Annex 3 */ +external function f_milenage_check(OCT16 opc,OCT16 k, + OCT8 sqn, OCT16 rand, OCT16 autn, + out OCT16 ik, out OCT16 ck, + out OCT8 res, out OCT14 auts) return integer; + + +} diff --git a/library/milenage/milenage.c b/library/milenage/milenage.c new file mode 100644 index 0000000..d079a3d --- /dev/null +++ b/library/milenage/milenage.c @@ -0,0 +1,424 @@ +/* + * 3GPP AKA - Milenage algorithm (3GPP TS 35.205, .206, .207, .208) + * Copyright (c) 2006-2007 <j...@w1.fi> + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + * + * This file implements an example authentication algorithm defined for 3GPP + * AKA. This can be used to implement a simple HLR/AuC into hlr_auc_gw to allow + * EAP-AKA to be tested properly with real USIM cards. + * + * This implementations assumes that the r1..r5 and c1..c5 constants defined in + * TS 35.206 are used, i.e., r1=64, r2=0, r3=32, r4=64, r5=96, c1=00..00, + * c2=00..01, c3=00..02, c4=00..04, c5=00..08. The block cipher is assumed to + * be AES (Rijndael). + */ + + +#include "milenage.h" + +#include <openssl/err.h> /* for ERR_print_errors_fp */ +#include <openssl/ssl.h> /* for NID_sha1, RSA */ +#include <openssl/evp.h> /* for EVP_PKEY, EVP_sha1(), ... */ +#include <openssl/md5.h> /* for MD5_DIGEST_LENGTH */ +#include <openssl/sha.h> /* for SHA_DIGEST_LENGTH */ + +#define AST_CRYPTO_RSA_KEY_BITS 1024 +#define AST_CRYPTO_AES_BLOCKSIZE 128 + +struct ast_aes { + unsigned char raw[AST_CRYPTO_AES_BLOCKSIZE / 8]; +}; + +int ast_aes_set_encrypt_key(const unsigned char *key, struct ast_aes *ctx) +{ + if (key == NULL || ctx == NULL) + return -1; + + memcpy(ctx->raw, key, AST_CRYPTO_AES_BLOCKSIZE / 8); + return 0; +} + +static int evp_cipher_aes_encrypt(const unsigned char *in, unsigned char *out, unsigned inlen, const struct ast_aes *key) +{ + EVP_CIPHER_CTX *ctx = NULL; + int res, outlen, finallen; + unsigned char final[AST_CRYPTO_AES_BLOCKSIZE / 8]; + + if ((ctx = EVP_CIPHER_CTX_new()) == NULL) + return -1; + + do { + if ((res = EVP_CipherInit(ctx, EVP_aes_128_ecb(), key->raw, NULL, 1)) <= 0) + break; + EVP_CIPHER_CTX_set_padding(ctx, 0); + if ((res = EVP_CipherUpdate(ctx, out, &outlen, in, inlen)) <= 0) + break; + /* for ECB, this is a no-op */ + if ((res = EVP_CipherFinal(ctx, final, &finallen)) <= 0) + break; + res = outlen; + } while (0); + + EVP_CIPHER_CTX_free(ctx); + + return res; +} + +int ast_aes_encrypt(const unsigned char *in, unsigned char *out, const struct ast_aes *key) +{ + int res; + + if ((res = evp_cipher_aes_encrypt(in, out, AST_CRYPTO_AES_BLOCKSIZE / 8, key)) <= 0) { + printf("AES encryption failed\n"); + exit(0); + } + return res; +} + +static int aes_128_encrypt_block(const u8 *key, const u8 *plain, u8 *encr) +{ + struct ast_aes aes_key; + + ast_aes_set_encrypt_key(key, &aes_key); + if (ast_aes_encrypt(plain, encr, &aes_key) <= 0) { + printf("Failed to ecrypt AES 128."); + return -1; + } + + return 0; +} + +#define DEBUG +#ifdef DEBUG +void hexdump(const char *text, const uint8_t *data, int len) +{ + char s[3 * len + 2], *p; + int f; + + for (p = s, f = 0; f < len; f++, p += 3) + sprintf(p, "%02hhX ", (unsigned char)data[f]); + + printf("%s: %s\n", text, s); +} +#endif + + +/** + * 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 + * Returns: 0 on success, -1 on failure + */ +int milenage_f1(const u8 *opc, const u8 *k, const u8 *_rand, + const u8 *sqn, const u8 *amf, u8 *mac_a, u8 *mac_s) +{ + u8 tmp1[16], tmp2[16], tmp3[16]; + int i; + + /* tmp1 = TEMP = E_K(RAND XOR OP_C) */ + for (i = 0; i < 16; i++) + tmp1[i] = _rand[i] ^ opc[i]; + if (aes_128_encrypt_block(k, tmp1, tmp1)) + return -1; + + /* tmp2 = IN1 = SQN || AMF || SQN || AMF */ + memcpy(tmp2, sqn, 6); + memcpy(tmp2 + 6, amf, 2); + memcpy(tmp2 + 8, tmp2, 8); + + /* OUT1 = E_K(TEMP XOR rot(IN1 XOR OP_C, r1) XOR c1) XOR OP_C */ + + /* rotate (tmp2 XOR OP_C) by r1 (= 0x40 = 8 bytes) */ + for (i = 0; i < 16; i++) + tmp3[(i + 8) % 16] = tmp2[i] ^ opc[i]; + /* XOR with TEMP = E_K(RAND XOR OP_C) */ + for (i = 0; i < 16; i++) + tmp3[i] ^= tmp1[i]; + /* XOR with c1 (= ..00, i.e., NOP) */ + + /* f1 || f1* = E_K(tmp3) XOR OP_c */ + if (aes_128_encrypt_block(k, tmp3, tmp1)) + return -1; + for (i = 0; i < 16; i++) + tmp1[i] ^= opc[i]; + if (mac_a) + memcpy(mac_a, tmp1, 8); /* f1 */ + if (mac_s) + memcpy(mac_s, tmp1 + 8, 8); /* f1* */ + return 0; +} + + +/** + * milenage_f2345 - Milenage f2, f3, f4, f5, f5* algorithms + * @opc: OPc = 128-bit value derived from OP and K + * @k: K = 128-bit subscriber key + * @_rand: RAND = 128-bit random challenge + * @res: Buffer for RES = 64-bit signed response (f2), or %NULL + * @ck: Buffer for CK = 128-bit confidentiality key (f3), or %NULL + * @ik: Buffer for IK = 128-bit integrity key (f4), or %NULL + * @ak: Buffer for AK = 48-bit anonymity key (f5), or %NULL + * @akstar: Buffer for AK = 48-bit anonymity key (f5*), or %NULL + * Returns: 0 on success, -1 on failure + */ +int milenage_f2345(const u8 *opc, const u8 *k, const u8 *_rand, + u8 *res, u8 *ck, u8 *ik, u8 *ak, u8 *akstar) +{ + u8 tmp1[16], tmp2[16], tmp3[16]; + int i; + + /* tmp2 = TEMP = E_K(RAND XOR OP_C) */ + for (i = 0; i < 16; i++) + tmp1[i] = _rand[i] ^ opc[i]; + if (aes_128_encrypt_block(k, tmp1, tmp2)) + return -1; + + /* OUT2 = E_K(rot(TEMP XOR OP_C, r2) XOR c2) XOR OP_C */ + /* OUT3 = E_K(rot(TEMP XOR OP_C, r3) XOR c3) XOR OP_C */ + /* OUT4 = E_K(rot(TEMP XOR OP_C, r4) XOR c4) XOR OP_C */ + /* OUT5 = E_K(rot(TEMP XOR OP_C, r5) XOR c5) XOR OP_C */ + + /* f2 and f5 */ + /* rotate by r2 (= 0, i.e., NOP) */ + for (i = 0; i < 16; i++) + tmp1[i] = tmp2[i] ^ opc[i]; + tmp1[15] ^= 1; /* XOR c2 (= ..01) */ + /* f5 || f2 = E_K(tmp1) XOR OP_c */ + if (aes_128_encrypt_block(k, tmp1, tmp3)) + return -1; + for (i = 0; i < 16; i++) + tmp3[i] ^= opc[i]; + if (res) + memcpy(res, tmp3 + 8, 8); /* f2 */ + if (ak) + memcpy(ak, tmp3, 6); /* f5 */ + + /* f3 */ + if (ck) { + /* rotate by r3 = 0x20 = 4 bytes */ + for (i = 0; i < 16; i++) + tmp1[(i + 12) % 16] = tmp2[i] ^ opc[i]; + tmp1[15] ^= 2; /* XOR c3 (= ..02) */ + if (aes_128_encrypt_block(k, tmp1, ck)) + return -1; + for (i = 0; i < 16; i++) + ck[i] ^= opc[i]; + } + + /* f4 */ + if (ik) { + /* rotate by r4 = 0x40 = 8 bytes */ + for (i = 0; i < 16; i++) + tmp1[(i + 8) % 16] = tmp2[i] ^ opc[i]; + tmp1[15] ^= 4; /* XOR c4 (= ..04) */ + if (aes_128_encrypt_block(k, tmp1, ik)) + return -1; + for (i = 0; i < 16; i++) + ik[i] ^= opc[i]; + } + + /* f5* */ + if (akstar) { + /* rotate by r5 = 0x60 = 12 bytes */ + for (i = 0; i < 16; i++) + tmp1[(i + 4) % 16] = tmp2[i] ^ opc[i]; + tmp1[15] ^= 8; /* XOR c5 (= ..08) */ + if (aes_128_encrypt_block(k, tmp1, tmp1)) + return -1; + for (i = 0; i < 6; i++) + akstar[i] = tmp1[i] ^ opc[i]; + } + + return 0; +} + + +/** + * milenage_generate - Generate AKA AUTN,IK,CK,RES + * @opc: OPc = 128-bit operator variant algorithm configuration field (encr.) + * @amf: AMF = 16-bit authentication management field + * @k: K = 128-bit subscriber key + * @sqn: SQN = 48-bit sequence number + * @_rand: RAND = 128-bit random challenge + * @autn: Buffer for AUTN = 128-bit authentication token + * @ik: Buffer for IK = 128-bit integrity key (f4), or %NULL + * @ck: Buffer for CK = 128-bit confidentiality key (f3), or %NULL + * @res: Buffer for RES = 64-bit signed response (f2), or %NULL + * @res_len: Max length for res; set to used length or 0 on failure + */ +void milenage_generate(const u8 *opc, const u8 *amf, const u8 *k, + const u8 *sqn, const u8 *_rand, u8 *autn, u8 *ik, + u8 *ck, u8 *res, size_t *res_len) +{ + int i; + u8 mac_a[8], ak[6]; + + if (*res_len < 8) { + *res_len = 0; + return; + } + if (milenage_f1(opc, k, _rand, sqn, amf, mac_a, NULL) || + milenage_f2345(opc, k, _rand, res, ck, ik, ak, NULL)) { + *res_len = 0; + return; + } + *res_len = 8; + + /* AUTN = (SQN ^ AK) || AMF || MAC */ + for (i = 0; i < 6; i++) + autn[i] = sqn[i] ^ ak[i]; + memcpy(autn + 6, amf, 2); + memcpy(autn + 8, mac_a, 8); +} + + +/** + * milenage_auts - Milenage AUTS validation + * @opc: OPc = 128-bit operator variant algorithm configuration field (encr.) + * @k: K = 128-bit subscriber key + * @_rand: RAND = 128-bit random challenge + * @auts: AUTS = 112-bit authentication token from client + * @sqn: Buffer for SQN = 48-bit sequence number + * Returns: 0 = success (sqn filled), -1 on failure + */ +int milenage_auts(const u8 *opc, const u8 *k, const u8 *_rand, const u8 *auts, + u8 *sqn) +{ + u8 amf[2] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */ + u8 ak[6], mac_s[8]; + int i; + + if (milenage_f2345(opc, k, _rand, NULL, NULL, NULL, NULL, ak)) + return -1; + for (i = 0; i < 6; i++) + sqn[i] = auts[i] ^ ak[i]; + if (milenage_f1(opc, k, _rand, sqn, amf, NULL, mac_s) || + memcmp(mac_s, auts + 6, 8) != 0) + return -1; + return 0; +} + + +/** + * gsm_milenage - Generate GSM-Milenage (3GPP TS 55.205) authentication triplet + * @opc: OPc = 128-bit operator variant algorithm configuration field (encr.) + * @k: K = 128-bit subscriber key + * @_rand: RAND = 128-bit random challenge + * @sres: Buffer for SRES = 32-bit SRES + * @kc: Buffer for Kc = 64-bit Kc + * Returns: 0 on success, -1 on failure + */ +int gsm_milenage(const u8 *opc, const u8 *k, const u8 *_rand, u8 *sres, u8 *kc) +{ + u8 res[8], ck[16], ik[16]; + int i; + + if (milenage_f2345(opc, k, _rand, res, ck, ik, NULL, NULL)) + return -1; + + for (i = 0; i < 8; i++) + kc[i] = ck[i] ^ ck[i + 8] ^ ik[i] ^ ik[i + 8]; + +#ifdef GSM_MILENAGE_ALT_SRES + memcpy(sres, res, 4); +#else /* GSM_MILENAGE_ALT_SRES */ + for (i = 0; i < 4; i++) + sres[i] = res[i] ^ res[i + 4]; +#endif /* GSM_MILENAGE_ALT_SRES */ + return 0; +} + + +/** + * milenage_generate - Generate AKA AUTN,IK,CK,RES + * @opc: OPc = 128-bit operator variant algorithm configuration field (encr.) + * @k: K = 128-bit subscriber key + * @sqn: SQN = 48-bit sequence number + * @_rand: RAND = 128-bit random challenge + * @autn: AUTN = 128-bit authentication token + * @ik: Buffer for IK = 128-bit integrity key (f4), or %NULL + * @ck: Buffer for CK = 128-bit confidentiality key (f3), or %NULL + * @res: Buffer for RES = 64-bit signed response (f2), or %NULL + * @res_len: Variable that will be set to RES length + * @auts: 112-bit buffer for AUTS + * Returns: 0 on success, -1 on failure, or -2 on synchronization failure + */ +int milenage_check(const u8 *opc, const u8 *k, const u8 *sqn, const u8 *_rand, + const u8 *autn, u8 *ik, u8 *ck, u8 *res, size_t *res_len, + u8 *auts) +{ + int i; + u8 mac_a[8], ak[6], rx_sqn[6]; + const u8 *amf; + +#ifdef DEBUG + hexdump("Milenage: OPC", opc, 16); + hexdump("Milenage: K", k, 16); + hexdump("Milenage: AUTN", autn, 16); + hexdump("Milenage: RAND", _rand, 16); +#endif + + if (milenage_f2345(opc, k, _rand, res, ck, ik, ak, NULL)) + return -1; + + *res_len = 8; +#ifdef DEBUG + hexdump("Milenage: RES", res, *res_len); + hexdump("Milenage: CK", ck, 16); + hexdump("Milenage: IK", ik, 16); + hexdump("Milenage: AK", ak, 6); +#endif + + /* AUTN = (SQN ^ AK) || AMF || MAC */ + for (i = 0; i < 6; i++) + rx_sqn[i] = autn[i] ^ ak[i]; +#ifdef DEBUG + hexdump("Milenage: RX SQN", rx_sqn, 6); + hexdump("Milenage: SQN", sqn, 6); +#endif + + if (memcmp(rx_sqn, sqn, 6) <= 0) { + u8 auts_amf[2] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */ + if (milenage_f2345(opc, k, _rand, NULL, NULL, NULL, NULL, ak)) + return -1; +#ifdef DEBUG + hexdump("Milenage: AK*", ak, 6); +#endif + for (i = 0; i < 6; i++) + auts[i] = sqn[i] ^ ak[i]; + if (milenage_f1(opc, k, _rand, sqn, auts_amf, NULL, auts + 6)) + return -1; +#ifdef DEBUG + hexdump("Milenage: AUTS", auts, 14); +#endif + return -2; + } + + amf = autn + 6; +#ifdef DEBUG + hexdump("Milenage: AMF", amf, 2); +#endif + if (milenage_f1(opc, k, _rand, rx_sqn, amf, mac_a, NULL)) + return -1; + +#ifdef DEBUG + hexdump("Milenage: MAC_A", mac_a, 8); +#endif + + if (memcmp(mac_a, autn + 8, 8) != 0) { +#ifdef DEBUG + hexdump("Milenage: MAC mismatch, Received MAC_A", + autn + 8, 8); +#endif + return -1; + } + + return 0; +} diff --git a/library/milenage/milenage.h b/library/milenage/milenage.h new file mode 100644 index 0000000..238df62 --- /dev/null +++ b/library/milenage/milenage.h @@ -0,0 +1,22 @@ + +#include <stdint.h> +#include <stddef.h> +#include <string.h> + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; + +int milenage_f1(const u8 *opc, const u8 *k, const u8 *_rand, + const u8 *sqn, const u8 *amf, u8 *mac_a, u8 *mac_s); +int milenage_f2345(const u8 *opc, const u8 *k, const u8 *_rand, + u8 *res, u8 *ck, u8 *ik, u8 *ak, u8 *akstar); +void milenage_generate(const u8 *opc, const u8 *amf, const u8 *k, + const u8 *sqn, const u8 *_rand, u8 *autn, u8 *ik, + u8 *ck, u8 *res, size_t *res_len); +int milenage_auts(const u8 *opc, const u8 *k, const u8 *_rand, const u8 *auts, + u8 *sqn); +int gsm_milenage(const u8 *opc, const u8 *k, const u8 *_rand, u8 *sres, u8 *kc); +int milenage_check(const u8 *opc, const u8 *k, const u8 *sqn, const u8 *_rand, + const u8 *autn, u8 *ik, u8 *ck, u8 *res, size_t *res_len, + u8 *auts); diff --git a/library/ng_crypto/key_derivation.c b/library/ng_crypto/key_derivation.c new file mode 100644 index 0000000..29e0849 --- /dev/null +++ b/library/ng_crypto/key_derivation.c @@ -0,0 +1,49 @@ +#include <stdint.h> +#include <string.h> +#include <arpa/inet.h> +#include <gnutls/crypto.h> + +#include "key_derivation.h" + +/* 3GPP TS 33.501 A.4 RES* and XRES* derivation function */ +void kdf_xres_star(const uint8_t *serving_network_name, + uint16_t serving_network_name_len, + const uint8_t *ck, + const uint8_t *ik, + const uint8_t *rand, + const uint8_t *xres, size_t xres_len, + uint8_t *out) +{ + uint8_t s[1024]; + uint8_t key[16*2]; + uint8_t tmp_out[32]; + size_t pos = 0; + uint16_t lenbe; + + memcpy(key, ck, 16); + memcpy(key+16, ik, 16); + + s[pos++] = 0x6B; /* FC Value */ + + memcpy(&s[pos], serving_network_name, serving_network_name_len); + pos += serving_network_name_len; + lenbe = htons(serving_network_name_len); + memcpy(&s[pos], &lenbe, 2); + pos += 2; + + memcpy(&s[pos], rand, 16); + pos += 16; + lenbe = htons(16); + memcpy(&s[pos], &lenbe, 2); + pos += 2; + + memcpy(&s[pos], xres, xres_len); + pos += xres_len; + lenbe = htons(xres_len); + memcpy(&s[pos], &lenbe, 2); + pos += 2; + + gnutls_hmac_fast(GNUTLS_MAC_SHA256, key, sizeof(key), s, pos, tmp_out); + + memcpy(out, tmp_out+16, 16); +} diff --git a/library/ng_crypto/key_derivation.h b/library/ng_crypto/key_derivation.h new file mode 100644 index 0000000..f78d746 --- /dev/null +++ b/library/ng_crypto/key_derivation.h @@ -0,0 +1,12 @@ +#pragma once + +#include <stdint.h> +#include <unistd.h> + +void kdf_xres_star(const uint8_t *serving_network_name, + uint16_t serving_network_name_len, + const uint8_t *ck, + const uint8_t *ik, + const uint8_t *rand, + const uint8_t *xres, size_t xres_len, + uint8_t *out); -- To view, visit https://gerrit.osmocom.org/c/osmo-ttcn3-hacks/+/40413?usp=email To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email Gerrit-MessageType: merged Gerrit-Project: osmo-ttcn3-hacks Gerrit-Branch: master Gerrit-Change-Id: I11527f47e4310863124f3f02148e3f71da7d911e Gerrit-Change-Number: 40413 Gerrit-PatchSet: 9 Gerrit-Owner: pespin <pes...@sysmocom.de> Gerrit-Reviewer: Jenkins Builder Gerrit-Reviewer: daniel <dwillm...@sysmocom.de> Gerrit-Reviewer: fixeria <vyanits...@sysmocom.de> Gerrit-Reviewer: jolly <andr...@eversberg.eu> Gerrit-Reviewer: laforge <lafo...@osmocom.org> Gerrit-Reviewer: pespin <pes...@sysmocom.de>