diff options
Diffstat (limited to 'mme')
-rw-r--r-- | mme/LTE_CryptoFunctionDefs.cc | 189 | ||||
-rw-r--r-- | mme/LTE_CryptoFunctions.ttcn | 263 | ||||
-rw-r--r-- | mme/key_derivation.c | 82 | ||||
-rw-r--r-- | mme/key_derivation.h | 21 | ||||
-rwxr-xr-x | mme/regen_makefile.sh | 4 |
5 files changed, 557 insertions, 2 deletions
diff --git a/mme/LTE_CryptoFunctionDefs.cc b/mme/LTE_CryptoFunctionDefs.cc new file mode 100644 index 00000000..da2d521d --- /dev/null +++ b/mme/LTE_CryptoFunctionDefs.cc @@ -0,0 +1,189 @@ +/* Utility functions from ogslib imported to TTCN-3 + * + * (C) 2019 Harald Welte <laforge@gnumonks.org> + * 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 + */ + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <stdint.h> + +#include <Boolean.hh> +#include <Integer.hh> +#include <Octetstring.hh> +#include <Bitstring.hh> + +#include "snow-3g.h" +#include "key_derivation.h" + +//#define DEBUG + +#ifdef DEBUG +static __thread char hexd_buff[4096]; +static const char hex_chars[] = "0123456789abcdef"; + +static const char *_osmo_hexdump_buf(char *out_buf, size_t out_buf_size, const unsigned char *buf, int len, const char *delim, + bool delim_after_last) +{ + int i; + char *cur = out_buf; + size_t delim_len; + + if (!out_buf || !out_buf_size) + return ""; + + delim = delim ? : ""; + delim_len = strlen(delim); + + for (i = 0; i < len; i++) { + const char *delimp = delim; + int len_remain = out_buf_size - (cur - out_buf) - 1; + if (len_remain < (2 + delim_len) + && !(!delim_after_last && i == (len - 1) && len_remain >= 2)) + break; + + *cur++ = hex_chars[buf[i] >> 4]; + *cur++ = hex_chars[buf[i] & 0xf]; + + if (i == (len - 1) && !delim_after_last) + break; + + while (len_remain > 1 && *delimp) { + *cur++ = *delimp++; + len_remain--; + } + } + *cur = '\0'; + return out_buf; +} + +static char *_osmo_hexdump(const unsigned char *buf, int len) +{ + _osmo_hexdump_buf(hexd_buff, sizeof(hexd_buff), buf, len, "", true); + return hexd_buff; +} +#endif + +namespace LTE__CryptoFunctions { + + +/* f8. +* Input key: 128 bit Confidentiality Key as OCT16. +* Input count:32-bit Count, Frame dependent input as INTEGER. +* Input bearer: 5-bit Bearer identity (in the LSB side) as BIT5. +* Input is_dlwnlink: Direction of transmission. +* Input data: length number of bits, input bit stream as OCTETSTRING. +* Output data: Output bit stream. Assumes data is suitably memory +* allocated. +* Encrypts/decrypts blocks of data between 1 and 2^32 bits in length as +* defined in Section 3. +*/ +OCTETSTRING f__snow__3g__f8(const OCTETSTRING& key, const INTEGER& count, const INTEGER & bearer, + const BOOLEAN& is_downlink, const OCTETSTRING& data) +{ + TTCN_Buffer ttcn_buf_data(data); + TTCN_Buffer ttcn_buf_key(key); + uint32_t direction = (uint32_t)is_downlink; + + snow_3g_f8((u8 *)ttcn_buf_key.get_data(), (u32) count, (u32)bearer, direction, + (u8 *)ttcn_buf_data.get_data(), ttcn_buf_data.get_len()); + + return OCTETSTRING(ttcn_buf_data.get_len(), ttcn_buf_data.get_data()); +} + +/* f9. +* Input key: 128 bit Integrity Key as OCT16. +* Input count:32-bit Count, Frame dependent input as UINT32. +* Input fresh: 32-bit Random number as UINT32. +* Input is_downlink:1 Direction of transmission. +* Input data: input bit stream. +* Output : 32 bit block used as MAC +* Generates 32-bit MAC using UIA2 algorithm as defined in Section 4. +*/ + +OCTETSTRING f__snow__3g__f9(const OCTETSTRING& key, const INTEGER& count, const INTEGER& fresh, + const BOOLEAN& is_downlink, const OCTETSTRING& data) +{ + TTCN_Buffer ttcn_buf_data(data); + TTCN_Buffer ttcn_buf_key(key); + uint32_t direction = (uint32_t)is_downlink; + uint8_t tmp[4]; + TTCN_Buffer ttcn_buf_mac; + +#ifdef DEBUG + printf("F9: key=%s, count=%u, fresh=%u, direction=%u, ", + _osmo_hexdump((u8 *)ttcn_buf_key.get_data(), ttcn_buf_key.get_len()), (u32) count, + (u32) fresh, direction); + printf("data=%s -> ", _osmo_hexdump(ttcn_buf_data.get_data(), ttcn_buf_data.get_len())); +#endif + snow_3g_f9((u8 *)ttcn_buf_key.get_data(), (u32) count, (u32) fresh, direction, + (u8 *)ttcn_buf_data.get_data(), ttcn_buf_data.get_len()*8, tmp); +#ifdef DEBUG + printf("%s\n", _osmo_hexdump(tmp, sizeof(tmp))); +#endif + + return OCTETSTRING(4, tmp); +} + +OCTETSTRING f__kdf__kasme(const OCTETSTRING& ck, const OCTETSTRING& ik, const OCTETSTRING& plmn_id, + const OCTETSTRING& sqn, const OCTETSTRING& ak) +{ + TTCN_Buffer ttcn_buf_ck(ck); + TTCN_Buffer ttcn_buf_ik(ik); + TTCN_Buffer ttcn_buf_plmn_id(plmn_id); + TTCN_Buffer ttcn_buf_sqn(sqn); + TTCN_Buffer ttcn_buf_ak(ak); + uint8_t kasme[32]; + + hss_auc_kasme(ttcn_buf_ck.get_data(), ttcn_buf_ik.get_data(), ttcn_buf_plmn_id.get_data(), + ttcn_buf_sqn.get_data(), ttcn_buf_ak.get_data(), kasme); + return OCTETSTRING(sizeof(kasme), kasme); +} + +OCTETSTRING f__kdf__nas__int(const INTEGER& alg_id, const OCTETSTRING &kasme) +{ + TTCN_Buffer ttcn_buf_kasme(kasme); + uint8_t knas[16]; + + mme_kdf_nas(MME_KDF_NAS_INT_ALG, (int)alg_id, (const u8*) ttcn_buf_kasme.get_data(), knas); + return OCTETSTRING(sizeof(knas), knas); +} + +OCTETSTRING f__kdf__nas__enc(const INTEGER& alg_id, const OCTETSTRING &kasme) +{ + TTCN_Buffer ttcn_buf_kasme(kasme); + uint8_t knas[16]; + + mme_kdf_nas(MME_KDF_NAS_ENC_ALG, (int)alg_id, (const u8*) ttcn_buf_kasme.get_data(), knas); + return OCTETSTRING(sizeof(knas), knas); +} + + +OCTETSTRING f__kdf__enb(const OCTETSTRING &kasme, const INTEGER &ul_count) +{ + TTCN_Buffer ttcn_buf_kasme(kasme); + uint8_t kenb[32]; + + mme_kdf_enb(ttcn_buf_kasme.get_data(), (int)ul_count, kenb); + return OCTETSTRING(sizeof(kenb), kenb); +} + +OCTETSTRING f__kdf__nh(const OCTETSTRING &kasme, const OCTETSTRING &sync_inp) +{ + TTCN_Buffer ttcn_buf_kasme(kasme); + TTCN_Buffer ttcn_buf_sync_inp(sync_inp); + uint8_t kenb[32]; + + mme_kdf_nh(ttcn_buf_kasme.get_data(), ttcn_buf_sync_inp.get_data(), kenb); + return OCTETSTRING(sizeof(kenb), kenb); +} + + + +} // namespace diff --git a/mme/LTE_CryptoFunctions.ttcn b/mme/LTE_CryptoFunctions.ttcn new file mode 100644 index 00000000..687caabe --- /dev/null +++ b/mme/LTE_CryptoFunctions.ttcn @@ -0,0 +1,263 @@ +/* Utility functions from ogslib imported to TTCN-3 + * + * (C) 2019 Harald Welte <laforge@gnumonks.org> + * 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 LTE_CryptoFunctions { + +import from General_Types all; + +import from S1AP_Types all; +import from S1AP_PDU_Descriptions all; + +import from NAS_EPS_Types all; +import from NAS_Templates all; + +/********************************************************************************* + * low-level API (external C/C++ code) + *********************************************************************************/ + +external function f_snow_3g_f8(in OCT16 key, in integer count, in integer bearer, + in boolean is_downlink, in octetstring data) return octetstring; + +external function f_snow_3g_f9(in OCT16 key, in integer count, in integer fresh, + in boolean is_downlink, in octetstring data) return OCT4; + +external function f_kdf_kasme(in OCT16 ck, in OCT16 ik, in OCT3 plmn_id, + in OCT6 sqn, in OCT6 ak) return OCT32; + +external function f_kdf_nas_int(in integer alg_id, in OCT32 kasme) return OCT32; +external function f_kdf_nas_enc(in integer alg_id, in OCT32 kasme) return OCT32; + +external function f_kdf_enb(in OCT16 kasme, in integer ul_count) return OCT32; + +external function f_kdf_nh(in OCT16 kasme, in OCT32 sync_inp) return OCT32; + +/********************************************************************************* + * mid-level API + *********************************************************************************/ + +function f_nas_mac_calc(NAS_ALG_INT alg, octetstring k_nas_int, integer seq_nr, + integer bearer, boolean is_downlink, octetstring data) return OCT4 { + select (alg) { + case (NAS_ALG_IP_EIA0) { + return '00000000'O; + } + case (NAS_ALG_IP_EIA1) { + return f_snow_3g_f9(k_nas_int, seq_nr, bearer, is_downlink, data); + } + case else { + setverdict(fail, "Unsupported EIA: ", alg); + mtc.stop; + } + } +} + +function f_nas_encrypt(NAS_ALG_ENC alg, octetstring k_nas_enc, integer count, + integer bearer, boolean is_downlink, inout octetstring data) { + select (alg) { + case (NAS_ALG_ENC_EEA0) { } + case (NAS_ALG_ENC_EEA1) { + f_snow_3g_f8(k_nas_enc, count, bearer, is_downlink, data); + } + case else { + setverdict(fail, "Unsupported EEA: ", alg); + mtc.stop; + } + } +} + + +/********************************************************************************* + * high-level API (full NAS encapsulation/decapsulation) + *********************************************************************************/ + +type record NAS_UE_State { + NAS_Role role, /* ATS implements UE or MME role? */ + + NAS_ALG_INT alg_int, /* NAS Integrity Protection Algorithm */ + octetstring k_nas_int, /* NAS Integrity Protection Key */ + NAS_ALG_ENC alg_enc, /* NAS Encryption Algorithm */ + octetstring k_nas_enc, /* NAS Encryption Key */ + integer rx_count, /* frame counter (ATS rx side) */ + integer tx_count /* frame counter (ATS tx side) */ +}; + +template (value) NAS_UE_State t_NAS_UE_State(NAS_Role role) := { + role := role, + alg_int := NAS_ALG_IP_EIA0, + k_nas_int := ''O, + alg_enc := NAS_ALG_ENC_EEA0, + k_nas_enc := ''O, + rx_count := 0, + tx_count := 0 +}; + +type enumerated NAS_Role { + NAS_ROLE_UE, /* ATS implements/emulates UE */ + NAS_ROLE_MME /* ATS implements/emulates MME */ +}; +type enumerated NAS_ALG_INT { + NAS_ALG_IP_EIA0, /* no integrity protection */ + NAS_ALG_IP_EIA1, /* SNOW-3G F9 based */ + NAS_ALG_IP_EIA2, /* AES based */ + NAS_ALG_IP_EIA3 /* ZUC */ +}; +type enumerated NAS_ALG_ENC { + NAS_ALG_ENC_EEA0, /* no encryption */ + NAS_ALG_ENC_EEA1, /* SNOW-3G F8 based */ + NAS_ALG_ENC_EEA2, /* AES based */ + NAS_ALG_ENC_EEA3 /* ZUC */ +}; + +/* port between individual per-connection components and this translator */ +type port S1AP_NAS_Conn_PT message { + inout S1AP_PDU, PDU_NAS_EPS; +} with { extension "internal" }; + +/* determine if a received (from the IUT) message is downlink or not */ +private function f_rx_is_downlink(in NAS_UE_State nus) return boolean +{ + if (nus.role == NAS_ROLE_UE) { + return true; + } else { + return false; + } +} + +/* determine if a message transmitted to the IUT message is downlink or not */ +private function f_tx_is_downlink(in NAS_UE_State nus) return boolean +{ + return not f_rx_is_downlink(nus); +} + +private function f_nas_check_ip(inout NAS_UE_State nus, + in PDU_NAS_EPS_SecurityProtectedNASMessage secp_nas) return boolean +{ + var octetstring data_with_seq := int2oct(secp_nas.sequenceNumber, 1) & secp_nas.nAS_Message; + var OCT4 exp_mac := f_nas_mac_calc(nus.alg_int, nus.k_nas_int, nus.rx_count, 0, + f_rx_is_downlink(nus), data_with_seq); + if (exp_mac != secp_nas.messageAuthenticationCode) { + setverdict(fail, "Received NAS MAC ", secp_nas.messageAuthenticationCode, + " doesn't match expected MAC ", exp_mac, ": ", secp_nas); + return false; + } + return true; +} + +/* try to decapsulate (MAC verify, decrypt) NAS message */ +function f_nas_try_decaps(inout NAS_UE_State nus, PDU_NAS_EPS nas) return PDU_NAS_EPS +{ + var PDU_NAS_EPS_SecurityProtectedNASMessage secp_nas; + + /* transparently pass through any non-protected NAS */ + if (not match(nas, tr_NAS_EMM_SecurityProtected)) { + return nas; + } + + /* process any security-protected NAS */ + secp_nas := nas.ePS_messages.ePS_MobilityManagement.pDU_NAS_EPS_SecurityProtectedNASMessage; + select (secp_nas.securityHeaderType) { + case ('0011'B) { /* IP with new EPS security context */ + nus.rx_count := 0; + nus.alg_int := NAS_ALG_IP_EIA1; /* FIXME: from decoded inner message! */ + if (not f_nas_check_ip(nus, secp_nas)) { + mtc.stop; + } + return dec_PDU_NAS_EPS(secp_nas.nAS_Message); + } + case ('0001'B) { /* IP only */ + if (not f_nas_check_ip(nus, secp_nas)) { + mtc.stop; + } + return dec_PDU_NAS_EPS(secp_nas.nAS_Message); + } + case ('0010'B) { /* IP + ciphered */ + if (not f_nas_check_ip(nus, secp_nas)) { + mtc.stop; + } + f_nas_encrypt(nus.alg_enc, nus.k_nas_enc, nus.rx_count, 0, + f_rx_is_downlink(nus), secp_nas.nAS_Message); + return dec_PDU_NAS_EPS(secp_nas.nAS_Message); + } + case ('0100'B) { /* IP + ciphered; new EPS security context */ + nus.rx_count := 0; + if (not f_nas_check_ip(nus, secp_nas)) { + mtc.stop; + } + f_nas_encrypt(nus.alg_enc, nus.k_nas_enc, nus.rx_count, 0, + f_rx_is_downlink(nus), secp_nas.nAS_Message); + return dec_PDU_NAS_EPS(secp_nas.nAS_Message); + } + //case ('0101'B) { /* IP + partially ciphered */ } + //case ('1100'B) { /* Service Request Message */ } + case else { + setverdict(fail, "Implement SecHdrType for ", secp_nas); + mtc.stop; + } + } +} + +private function f_nas_determine_sec_hdr_t(boolean encrypt, boolean authenticate, boolean new_ctx) +return BIT4 +{ + if (encrypt == false and authenticate == false and new_ctx == false) { + return '0000'B; + } else if (encrypt == false and authenticate == true and new_ctx == false) { + return '0001'B; + } else if (encrypt == false and authenticate == true and new_ctx == true) { + return '0011'B; + } else if (encrypt == true and authenticate == true and new_ctx == true) { + return '0100'B; + } else if (encrypt == true and authenticate == true and new_ctx == false) { + return '0010'B; + } else { + setverdict(fail, "invalid sec_hdr conditions"); + mtc.stop; + } +} + +/* encapsulate a NAS message (encrypt, MAC) */ +function f_nas_encaps(inout NAS_UE_State nus, PDU_NAS_EPS nas_in, boolean new_ctx := false) +return PDU_NAS_EPS +{ + var boolean encrypt := false; + var boolean authenticate := false; + if (nus.alg_int != NAS_ALG_IP_EIA0) { + authenticate := true; + } + if (nus.alg_enc != NAS_ALG_ENC_EEA0) { + encrypt := true; + } + + if (encrypt == false and authenticate == false) { + return nas_in; + } + + if (new_ctx) { + nus.tx_count := 0; + } + + var BIT4 sec_hdr_t := f_nas_determine_sec_hdr_t(encrypt, authenticate, new_ctx); + var octetstring nas_enc := enc_PDU_NAS_EPS(nas_in); + if (encrypt) { + f_nas_encrypt(nus.alg_enc, nus.k_nas_enc, nus.tx_count, 0, + f_tx_is_downlink(nus), nas_enc); + } + var PDU_NAS_EPS nas_out; + nas_out := valueof(ts_NAS_EMM_SecurityProtected(sec_hdr_t, nus.tx_count, nas_enc)); + if (authenticate) { + var OCT4 mac := f_nas_mac_calc(nus.alg_int, nus.k_nas_int, nus.tx_count, 0, + f_tx_is_downlink(nus), '00'O & nas_enc); + nas_out.ePS_messages.ePS_MobilityManagement.pDU_NAS_EPS_SecurityProtectedNASMessage.messageAuthenticationCode := mac; + } + return nas_out; +} + +} // namespace diff --git a/mme/key_derivation.c b/mme/key_derivation.c new file mode 100644 index 00000000..36e4c915 --- /dev/null +++ b/mme/key_derivation.c @@ -0,0 +1,82 @@ +#include <stdint.h> +#include <string.h> +#include <arpa/inet.h> +#include <gnutls/crypto.h> + +/* From nextepc/src/mme/mme-kdf.c under AGPLv3+ */ + +void mme_kdf_nas(uint8_t algorithm_type_distinguishers, + uint8_t algorithm_identity, const uint8_t *kasme, uint8_t *knas) +{ + uint8_t s[7]; + uint8_t out[32]; + + s[0] = 0x15; /* FC Value */ + + s[1] = algorithm_type_distinguishers; + s[2] = 0x00; + s[3] = 0x01; + + s[4] = algorithm_identity; + s[5] = 0x00; + s[6] = 0x01; + + gnutls_hmac_fast(GNUTLS_MAC_SHA256, kasme, 32, s, 7, out); + memcpy(knas, out+16, 16); +} + +void mme_kdf_enb(const uint8_t *kasme, uint32_t ul_count, uint8_t *kenb) +{ + uint8_t s[7]; + + s[0] = 0x11; /* FC Value */ + + ul_count = htonl(ul_count); + memcpy(s+1, &ul_count, 4); + + s[5] = 0x00; + s[6] = 0x04; + + gnutls_hmac_fast(GNUTLS_MAC_SHA256, kasme, 32, s, 7, kenb); +} + +void mme_kdf_nh(const uint8_t *kasme, const uint8_t *sync_input, uint8_t *kenb) +{ + uint8_t s[35]; + + s[0] = 0x12; /* FC Value */ + + memcpy(s+1, sync_input, 32); + + s[33] = 0x00; + s[34] = 0x20; + + gnutls_hmac_fast(GNUTLS_MAC_SHA256, kasme, 32, s, 35, kenb); +} + +/* From nextepc/src/hss/hss-auc.c under AGPLv3+ */ + +#define FC_VALUE 0x10 + +void hss_auc_kasme(const uint8_t *ck, const uint8_t *ik, const uint8_t plmn_id[3], + const uint8_t *sqn, const uint8_t *ak, uint8_t *kasme) +{ + uint8_t s[14]; + uint8_t k[32]; + int i; + + memcpy(&k[0], ck, 16); + memcpy(&k[16], ik, 16); + + s[0] = FC_VALUE; + memcpy(&s[1], plmn_id, 3); + s[4] = 0x00; + s[5] = 0x03; + + for (i = 0; i < 6; i++) + s[6+i] = sqn[i] ^ ak[i]; + s[12] = 0x00; + s[13] = 0x06; + + gnutls_hmac_fast(GNUTLS_MAC_SHA256, k, 32, s, 14, kasme); +} diff --git a/mme/key_derivation.h b/mme/key_derivation.h new file mode 100644 index 00000000..496f52bc --- /dev/null +++ b/mme/key_derivation.h @@ -0,0 +1,21 @@ +#pragma once + +#include <stdint.h> + +#define HSS_SQN_LEN 6 +#define HSS_AK_LEN 6 + +void hss_auc_kasme(const uint8_t *ck, const uint8_t *ik, + const uint8_t plmn_id[3], const uint8_t *sqn, const uint8_t *ak, + uint8_t *kasme); + +/* Algorithm Type Distinguishers */ +#define MME_KDF_NAS_ENC_ALG 0x01 +#define MME_KDF_NAS_INT_ALG 0x02 + +void mme_kdf_nas(uint8_t algorithm_type_distinguishers, + uint8_t algorithm_identity, const uint8_t *kasme, uint8_t *knas); + +void mme_kdf_enb(const uint8_t *kasme, uint32_t ul_count, uint8_t *kenb); + +void mme_kdf_nh(const uint8_t *kasme, const uint8_t *sync_input, uint8_t *kenb); diff --git a/mme/regen_makefile.sh b/mme/regen_makefile.sh index ee736580..47523021 100755 --- a/mme/regen_makefile.sh +++ b/mme/regen_makefile.sh @@ -1,9 +1,9 @@ #!/bin/sh -FILES="*.ttcn *.asn IPL4asp_PT.cc IPL4asp_discovery.cc Native_FunctionDefs.cc SGsAP_CodecPort_CtrlFunctDef.cc TCCConversion.cc TCCEncoding.cc TCCInterface.cc TELNETasp_PT.cc S1AP_EncDec.cc " +FILES="*.ttcn *.asn *.c IPL4asp_PT.cc IPL4asp_discovery.cc Native_FunctionDefs.cc SGsAP_CodecPort_CtrlFunctDef.cc TCCConversion.cc TCCEncoding.cc TCCInterface.cc TELNETasp_PT.cc S1AP_EncDec.cc LTE_CryptoFunctionDefs.cc " export CPPFLAGS_TTCN3="" ../regen-makefile.sh MME_Tests.ttcn $FILES -sed -i -e 's/^LINUX_LIBS = -lxml2/LINUX_LIBS = -lxml2 -lfftranscode/' Makefile +sed -i -e 's/^LINUX_LIBS = -lxml2/LINUX_LIBS = -lxml2 -lfftranscode -lgnutls/' Makefile |