From: Arran Cudbard-Bell Date: Tue, 28 Nov 2017 19:04:02 +0000 (+0000) Subject: Add xlats to encrypt/decrypt 3gpp pseudonyms X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=521711bd77f826fa024ec2209d220ca7302b33b4;p=thirdparty%2Ffreeradius-server.git Add xlats to encrypt/decrypt 3gpp pseudonyms --- diff --git a/share/dictionary.freeradius.internal b/share/dictionary.freeradius.internal index 755fe568830..12f5c33614e 100644 --- a/share/dictionary.freeradius.internal +++ b/share/dictionary.freeradius.internal @@ -380,6 +380,19 @@ ATTRIBUTE SIM-IMSI 1201 string # The algorithm the SIM card uses (Milenage for UMTS, COMP128 for others) ATTRIBUTE SIM-Algo-Version 1202 integer +ATTRIBUTE SIM-Method-Hint 1203 integer + +VALUE SIM-Method-Hint Unknown 0 +VALUE SIM-Method-Hint SIM 1 +VALUE SIM-Method-Hint AKA 2 + +ATTRIBUTE SIM-Identity-Type 1204 integer + +VALUE SIM-Identity-Type Unknown 0 +VALUE SIM-Identity-Type Permanent 1 +VALUE SIM-Identity-Type Pseudonym 2 +VALUE SIM-Identity-Type 3GPP-Pseudonym 3 +VALUE SIM-Identity-Type Fastauth 4 # # Range: 1210-1219 diff --git a/src/modules/rlm_eap/lib/sim/all.mk b/src/modules/rlm_eap/lib/sim/all.mk index 88672b5e663..a056db88cee 100644 --- a/src/modules/rlm_eap/lib/sim/all.mk +++ b/src/modules/rlm_eap/lib/sim/all.mk @@ -6,7 +6,9 @@ SOURCES := \ comp128.c \ crypto.c \ fips186prf.c \ + id.c \ sim_proto.c \ - vector.c + vector.c \ + xlat.c SRC_INCDIRS := . ${top_srcdir}/src/modules/rlm_eap/lib/base ${top_srcdir}/src/modules/rlm_eap/ diff --git a/src/modules/rlm_eap/lib/sim/id.c b/src/modules/rlm_eap/lib/sim/id.c index d049de6d6a1..778f6969c9a 100644 --- a/src/modules/rlm_eap/lib/sim/id.c +++ b/src/modules/rlm_eap/lib/sim/id.c @@ -24,6 +24,7 @@ #include #include #include "sim_proto.h" +#include "id.h" #define us(x) (uint8_t) x @@ -35,14 +36,14 @@ * @return * - How long the identity portion of the NAI is. */ -size_t fr_sim_id_len(char const *nai, size_t nai_len) +size_t fr_sim_id_user_len(char const *nai, size_t nai_len) { char const *p; p = (char *)memchr((uint8_t const *)nai, '@', nai_len); if (!p) return nai_len; - return nai - p; + return p - nai; } /** Find where in the NAI string the domain starts @@ -72,49 +73,85 @@ char const *fr_sim_domain(char const *nai, size_t nai_len) * @param[in] domain to parse. * @param[in] domain_len Length of the domain component. * @return - * - 0 on success. - * - -1 on failure. + * - number of bytes parsed. + * - <= 0 on error - The negative offset of where parsing failed. */ -int fr_sim_3gpp_root_nai_domain_mcc_mnc(uint16_t *mnc, uint16_t *mcc, - char const *domain, size_t domain_len) +ssize_t fr_sim_3gpp_root_nai_domain_mcc_mnc(uint16_t *mnc, uint16_t *mcc, + char const *domain, size_t domain_len) { - uint8_t const *p = domain, *end = p + domain_len; + char const *p = domain, *end = p + domain_len; + char *q; unsigned long num; if (((p + 8) < end) || (memcmp(p, "wlan.mnc", 8) != 0)) return -1; p += 8; - if (((p + 3) < end) return -1; - num = strtoul( + if (((p + 3) < end)) { + fr_strerror_printf("Missing MNC component"); + return (domain - p); + } + num = strtoul(p, &q, 10); + if (*q != '.') { + fr_strerror_printf("Invalid MCN component"); + return (domain - q); + } + *mnc = (uint16_t)num; + p = q + 1; + + if (((p + 3) < end) || (memcmp(p, "mcc", 3) != 0)) { + fr_strerror_printf("Missing MCC component"); + return (domain - p); + } + num = strtoul(p, &q, 10); + if (*q != '.') { + fr_strerror_printf("Invalid MCC component"); + return (domain - q); + } + *mcc = (uint16_t)num; + + p = q + 1; + if (((p + 15) < end) || (memcmp(p, "3gppnetwork.org", 15) != 0)) { + fr_strerror_printf("Missing 3gppnetwork.org suffix"); + return (domain - p); + } + p += 15; + + if (p != end) { + fr_strerror_printf("Trailing garbage"); + return (domain - p); + } + + return p - domain; } /** Determine what type of ID was provided in the initial identity response * * @param[out] hint Whether this is a hint to do EAP-SIM or EAP-AKA[']: - * - SIM_HINT_AKA this ID was generated during an EAP-AKA exchange - * or the supplicant hints it wants to perform EAP-AKA. - * - SIM_HINT_SIM this IS was generated during an EAP-SIM exchange - * or the supplicant hints it wants to perform EAP-SIM. - * - SIM_HINT_UNKNOWN we don't know what type of authentication generated - * this ID or which one to start. + * - SIM_METHOD_HINT_AKA this ID was generated during an EAP-AKA exchange + * or the supplicant hints it wants to perform EAP-AKA. + * - SIM_METHOD_HINT_SIM this IS was generated during an EAP-SIM exchange + * or the supplicant hints it wants to perform EAP-SIM. + * - SIM_METHOD_HINT_UNKNOWN we don't know what type of authentication generated + * this ID or which one to start. * @param[out] type What type of identity this is: - * - SIM_ID_PERMANENT if the ID is an IMSI. - * - SIM_ID_3GPP_PSEUDONYM if the ID is a 3GPP pseudonym (not validated). - * - SIM_ID_PSEUDONYM if the ID is a freeform pseudonym. - * - SIM_ID_FASTAUTH if the ID is a fastauth identity. - * - SIM_ID_INVALID if we can't determine what sort of ID this is. - * @param[in] id provided. - * @param[in] id_len the length of the ID. + * - SIM_ID_TYPE_PERMANENT if the ID is an IMSI. + * - SIM_ID_TYPE_3GPP_PSEUDONYM if the ID is a 3GPP pseudonym (not validated). + * - SIM_ID_TYPE_PSEUDONYM if the ID is a freeform pseudonym. + * - SIM_ID_TYPE_FASTAUTH if the ID is a fastauth identity. + * - SIM_ID_TYPE_UNKNOWN if we can't determine what sort of ID this is. + * @param[in] id the NAI string provided. + * @param[in] id_len the length of the user portion of the NAI string. + * See #fr_sim_id_user_len. * @return Length of the ID written to out. */ -int fr_sim_id_type(fr_sim_identity_type_t *type, fr_sim_method_hint_t *hint, - uint8_t const *id, size_t id_len) +int fr_sim_id_type(fr_sim_id_type_t *type, fr_sim_method_hint_t *hint, + char const *id, size_t id_len) { size_t i; if (id_len < 1) { - *hint = SIM_HINT_UNKNOWN; - *type = SIM_ID_INVALID; + *hint = SIM_METHOD_HINT_UNKNOWN; + *type = SIM_ID_TYPE_UNKNOWN; return -1; } @@ -126,14 +163,14 @@ int fr_sim_id_type(fr_sim_identity_type_t *type, fr_sim_method_hint_t *hint, if (i == id_len) { switch (id[0]) { - case '0': - *hint = SIM_HINT_AKA; - *type = SIM_ID_PERMANENT; /* All digits */ + case SIM_ID_TAG_PERMANENT_AKA: + *hint = SIM_METHOD_HINT_AKA; + *type = SIM_ID_TYPE_PERMANENT; /* All digits */ return 0; - case '1': - *hint = SIM_HINT_SIM; - *type = SIM_ID_PERMANENT; /* All digits */ + case SIM_ID_TAG_PERMANENT_SIM: + *hint = SIM_METHOD_HINT_SIM; + *type = SIM_ID_TYPE_PERMANENT; /* All digits */ return 0; default: @@ -151,14 +188,14 @@ int fr_sim_id_type(fr_sim_identity_type_t *type, fr_sim_method_hint_t *hint, if (i == id_len) { switch (id[0]) { - case '6': - *hint = SIM_HINT_AKA; - *type = SIM_ID_3GPP_PSEUDONYM; + case SIM_ID_TAG_3GPP_PSEUDONYM_AKA: + *hint = SIM_METHOD_HINT_AKA; + *type = SIM_ID_TYPE_3GPP_PSEUDONYM; return 0; - case '7': - *hint = SIM_HINT_SIM; - *type = SIM_ID_3GPP_PSEUDONYM; + case SIM_ID_TAG_3GPP_PSEUDONYM_SIM: + *hint = SIM_METHOD_HINT_SIM; + *type = SIM_ID_TYPE_3GPP_PSEUDONYM; return 0; default: @@ -171,32 +208,32 @@ int fr_sim_id_type(fr_sim_identity_type_t *type, fr_sim_method_hint_t *hint, * User assigned pseudonym */ switch (id[0]) { - case '2': - *hint = SIM_HINT_AKA; - *type = SIM_ID_PSEUDONYM; + case SIM_ID_TAG_PSEUDONYM_AKA: + *hint = SIM_METHOD_HINT_AKA; + *type = SIM_ID_TYPE_PSEUDONYM; return 0; - case '3': - *hint = SIM_HINT_SIM; - *type = SIM_ID_PSEUDONYM; + case SIM_ID_TAG_PSEUDONYM_SIM: + *hint = SIM_METHOD_HINT_SIM; + *type = SIM_ID_TYPE_PSEUDONYM; return 0; /* * Fast reauth identity */ - case '4': - *hint = SIM_HINT_AKA; - *type = SIM_ID_FASTAUTH; + case SIM_ID_TAG_FASTAUTH_AKA: + *hint = SIM_METHOD_HINT_AKA; + *type = SIM_ID_TYPE_FASTAUTH; return 0; - case '5': - *hint = SIM_HINT_SIM; - *type = SIM_ID_FASTAUTH; + case SIM_ID_TAG_FASTAUTH_SIM: + *hint = SIM_METHOD_HINT_SIM; + *type = SIM_ID_TYPE_FASTAUTH; return 0; default: - *hint = SIM_HINT_UNKNOWN; - *type = SIM_ID_INVALID; + *hint = SIM_METHOD_HINT_UNKNOWN; + *type = SIM_ID_TYPE_UNKNOWN; return -1; } } @@ -204,24 +241,24 @@ int fr_sim_id_type(fr_sim_identity_type_t *type, fr_sim_method_hint_t *hint, /** Create a 3gpp pseudonym from a permanent ID * * @param[out] out Where to write the resulting pseudonym, must be a buffer of - * exactly SIM_3GPP_PSEUDONYM_LEN bytes. - * @param[in] id Permanent ID to derive pseudonym from. Note: If the IMSI is less than + * exactly SIM_3GPP_PSEUDONYM_LEN + 1 bytes. + * @param[in] imsi Permanent ID to derive pseudonym from. Note: If the IMSI is less than * 15 digits it will be rpadded with zeros. - * @param[in] id_len Length of that ID. Must be between 1-15. - * @param[in] tag Tag value to prepend to the pseudonym. This field is 6 bits wide + * @param[in] imsi_len Length of the IMSI. Must be between 1-15. + * @param[in] tag Tag value to prepend to the pseudonym. This field is 6 bits wimsie * (0-63). * @param[in] key_ind Key indicator (or key index), the key number used to produce * the encr ID. There may be up to 16 keys in use at any one - * time. This field is 4 bits wide (0-15). - * @param[in] kpseu as described by the 'Security aspects of non-3GPP accesses' document. + * time. This field is 4 bits wimsie (0-15). + * @param[in] key as described by the 'Security aspects of non-3GPP accesses' document. * Must be 128 bits (8 bytes). * @return * - 0 on success. - * - -1 if any of the parameters were invalid. + * - -1 if any of the parameters were invalimsi. */ -int fr_sim_id_3gpp_pseudonym_encrypt(uint8_t out[SIM_3GPP_PSEUDONYM_LEN], - uint8_t const *id, size_t id_len, - uint8_t tag, uint8_t key_ind, uint8_t const *kpseu[16]) +int fr_sim_id_3gpp_pseudonym_encrypt(char out[SIM_3GPP_PSEUDONYM_LEN + 1], + char const *imsi, size_t imsi_len, + uint8_t tag, uint8_t key_ind, uint8_t const key[8]) { uint8_t padded[16]; /* Random (8 bytes) + Compressed (8 bytes) */ uint8_t encr[16]; /* aes_ecb(padded) */ @@ -229,30 +266,31 @@ int fr_sim_id_3gpp_pseudonym_encrypt(uint8_t out[SIM_3GPP_PSEUDONYM_LEN], char *out_p = out; - uint8_t const *p = id, *end = p + id_len; + char const *p = imsi, *end = p + imsi_len; + uint8_t *u_p, *u_end; uint32_t rand[2]; uint8_t *compressed = padded + sizeof(rand); /* Part of padded which contains the compressed IMSI */ EVP_CIPHER_CTX *cctx; - if (unlikely(key_ind > 15)) { /* 4 bits wide */ + if (unlikely(key_ind > 15)) { /* 4 bits wimsie */ fr_strerror_printf("Invalid key indicator value, expected value between 0-15, got %u", key_ind); return -1; } - if (unlikely(tag > 63)) { /* 6 bits wide */ + if (unlikely(tag > 63)) { /* 6 bits wimsie */ fr_strerror_printf("Invalid tag value, expected value between 0-63, got %u", tag); return -1; } - if (unlikely(id_len != 15)) { - fr_strerror_printf("Invalid ID len, expected length of 15, got %zu", id_len); + if (unlikely(imsi_len != 15)) { + fr_strerror_printf("Invalid ID len, expected length of 15, got %zu", imsi_len); return -1; } - if (unlikely(!kpseu[key_ind])) { + if (unlikely(!key)) { fr_strerror_printf("Provided key was NULL"); return -1; } - memset(padded, 0, sizeof(padded)); /* So we don't output garbage if id_len < 15 */ + memset(padded, 0, sizeof(padded)); /* So we don't output garbage if imsi_len < 15 */ /* * ID is an odd length (15). @@ -266,7 +304,7 @@ int fr_sim_id_3gpp_pseudonym_encrypt(uint8_t out[SIM_3GPP_PSEUDONYM_LEN], */ while (p < end) { if (unlikely(!isdigit((char)p[0]) || !isdigit((char)p[1]))) { - fr_strerror_printf("IMSI contains invalid character"); + fr_strerror_printf("IMSI contains invalimsi character"); return -1; } @@ -293,7 +331,7 @@ int fr_sim_id_3gpp_pseudonym_encrypt(uint8_t out[SIM_3GPP_PSEUDONYM_LEN], return -1; } - if (unlikely(EVP_EncryptInit_ex(cctx, EVP_aes_128_ecb(), NULL, kpseu[key_ind], NULL) != 1)) { + if (unlikely(EVP_EncryptInit_ex(cctx, EVP_aes_128_ecb(), NULL, key, NULL) != 1)) { tls_strerror_printf(true, "Failed initialising AES-128-ECB context"); error: EVP_CIPHER_CTX_free(cctx); @@ -336,56 +374,75 @@ int fr_sim_id_3gpp_pseudonym_encrypt(uint8_t out[SIM_3GPP_PSEUDONYM_LEN], /* * Now encode the entire output as base64. */ - p = encr; - end = p + encr_len; + u_p = encr; + u_end = u_p + encr_len; /* * Consume tag (6 bits) + key_ind (4 bits) + encr[0] (8 bits) = 18 bits (or 3 bytes of b64) */ *out_p++ = fr_base64_str[tag & 0x3f]; /* 6 bits tag */ - *out_p++ = fr_base64_str[((key_ind & 0x0f) << 2) | ((p[0] & 0xc0) >> 6)]; /* 4 bits key_ind + 2 high bits encr[0] */ - *out_p++ = fr_base64_str[p[0] & 0x3f]; /* 6 low bits of encr[0] */ - p++; + *out_p++ = fr_base64_str[((key_ind & 0x0f) << 2) | ((u_p[0] & 0xc0) >> 6)]; /* 4 bits key_ind + 2 high bits encr[0] */ + *out_p++ = fr_base64_str[u_p[0] & 0x3f]; /* 6 low bits of encr[0] */ + u_p++; /* * Consume 3 bytes of input for 4 bytes of b64 (5 iterations) */ - while (p < end) { - *out_p++ = fr_base64_str[(p[0] & 0xfc) >> 2]; /* 6 high bits of p[0] */ - *out_p++ = fr_base64_str[((p[0] & 0x03) << 4) | ((p[1] & 0xf0) >> 4)]; /* 2 low bits of p[0] + 4 high bits of p[1] */ - *out_p++ = fr_base64_str[((p[1] & 0x0f) << 2) | ((p[2] & 0xc0) >> 6)]; /* 4 low bits of p[1] + 2 high bits of p[2] */ - *out_p++ = fr_base64_str[p[2] & 0x3f]; /* 6 low bits of p[2] */ - p += 3; + while (u_p < u_end) { + *out_p++ = fr_base64_str[(u_p[0] & 0xfc) >> 2]; /* 6 high bits of p[0] */ + *out_p++ = fr_base64_str[((u_p[0] & 0x03) << 4) | ((u_p[1] & 0xf0) >> 4)];/* 2 low bits of p[0] + 4 high bits of p[1] */ + *out_p++ = fr_base64_str[((u_p[1] & 0x0f) << 2) | ((u_p[2] & 0xc0) >> 6)];/* 4 low bits of p[1] + 2 high bits of p[2] */ + *out_p++ = fr_base64_str[u_p[2] & 0x3f]; /* 6 low bits of p[2] */ + u_p += 3; } if ((out_p - out) != SIM_3GPP_PSEUDONYM_LEN) { - fr_strerror_printf("Base64 output length invalid, expected %i bytes, got %zu bytes", + fr_strerror_printf("Base64 output length invalimsi, expected %i bytes, got %zu bytes", SIM_3GPP_PSEUDONYM_LEN, out_p - out); return -1; } + out[SIM_3GPP_PSEUDONYM_LEN] = '\0'; + return 0; } +/** Return the tag from a 3gpp pseudonym + * + * @param[in] encr_id The 3gpp pseudonym. + * + * @return the tag associated with the pseudonym. + */ +uint8_t fr_sim_id_3gpp_pseudonym_tag(char const encr_id[SIM_3GPP_PSEUDONYM_LEN]) +{ + return fr_base64_sextet[us(encr_id[0])]; +} + +/** Return the key index from a 3gpp pseudonym + * + * @param[in] encr_id The 3gpp pseudonym. + * + * @return the key index associated with the pseudonym. + */ +uint8_t fr_sim_id_3gpp_pseudonym_key_index(char const encr_id[SIM_3GPP_PSEUDONYM_LEN]) +{ + return ((fr_base64_sextet[us(encr_id[1])] & 0x3c) >> 2); +} + /** Decrypt the 3GPP pseudonym * * @param[out] out Where to write the decypted, uncompressed IMSI. - * @param[out] tag_out Tag retrieved from pseudonym. - * @param[out] key_ind_out Key indicator retrieved from pseudonym. - * @param[in] encr_id to decypt. - * @param[in] kpseu array of 8 byte keys. - * @param[in] kpseu_count the number of keys. + * @param[in] encr_id to decypt. Will read exactly 23 bytes from the buffer. + * @param[in] key to use to decrypt the encrypted, compressed IMSI. * @return * - 0 on success. * - -1 if any of the parameters were invalid. */ -int fr_sim_id_3gpp_pseudonym_decypt(uint8_t out[SIM_IMSI_MAX_LEN], uint8_t *tag_out, uint8_t *key_ind_out, - char const encr_id[SIM_3GPP_PSEUDONYM_LEN], - uint8_t const *kpseu[16], size_t kpseu_count) +int fr_sim_id_3gpp_pseudonym_decrypt(char out[SIM_IMSI_MAX_LEN + 1], + char const encr_id[SIM_3GPP_PSEUDONYM_LEN], uint8_t const key[8]) { EVP_CIPHER_CTX *cctx; - uint8_t tag, key_ind; - uint8_t *out_p = out; + char *out_p = out; uint8_t dec[16]; uint8_t *dec_p = dec; @@ -399,11 +456,6 @@ int fr_sim_id_3gpp_pseudonym_decypt(uint8_t out[SIM_IMSI_MAX_LEN], uint8_t *tag_ size_t len = 0; int i; - if (unlikely(kpseu_count < 1) || unlikely(kpseu_count > 16)) { - tls_strerror_printf(true, "Invalid number of keys provided, need between 1-16 keys"); - return -1; - } - for (i = 0; i < SIM_3GPP_PSEUDONYM_LEN; i++) { if (!fr_is_base64(encr_id[i])) { fr_strerror_printf("Encrypted IMSI contains non-base64 char"); @@ -411,11 +463,6 @@ int fr_sim_id_3gpp_pseudonym_decypt(uint8_t out[SIM_IMSI_MAX_LEN], uint8_t *tag_ } } - /* - * Decode tag (6 bit) + key_ind (4 bit) + encrypted[0] - */ - tag = fr_base64_sextet[us(p[0])]; - key_ind = ((fr_base64_sextet[us(p[1])] & 0x3c) >> 2); *dec_p++ = (((fr_base64_sextet[us(p[1])] & 0x03) << 6) | fr_base64_sextet[us(p[2])]); p += 3; @@ -427,18 +474,13 @@ int fr_sim_id_3gpp_pseudonym_decypt(uint8_t out[SIM_IMSI_MAX_LEN], uint8_t *tag_ p += 4; /* 32bit input -> 24bit output */ } - if (key_ind >= kpseu_count) { - fr_strerror_printf("key_ind specified key[%u], but we only have %zu keys", key_ind, kpseu_count); - return -1; - } - cctx = EVP_CIPHER_CTX_new(); if (!cctx) { tls_strerror_printf(true, "Failed allocating EVP context"); return -1; } - if (unlikely(EVP_DecryptInit_ex(cctx, EVP_aes_128_ecb(), NULL, kpseu[key_ind], NULL) != 1)) { + if (unlikely(EVP_DecryptInit_ex(cctx, EVP_aes_128_ecb(), NULL, key, NULL) != 1)) { tls_strerror_printf(true, "Failed initialising AES-128-ECB context"); error: EVP_CIPHER_CTX_free(cctx); @@ -483,21 +525,21 @@ int fr_sim_id_3gpp_pseudonym_decypt(uint8_t out[SIM_IMSI_MAX_LEN], uint8_t *tag_ * we ignore. */ *out_p++ = (compressed[0] & 0x0f) + '0'; - for (i = 1; i < SIM_IMSI_MAX_LEN; i++) { + for (i = 1; i < 8; i++) { *out_p++ = ((compressed[i] & 0xf0) >> 4) + '0'; *out_p++ = (compressed[i] & 0x0f) + '0'; } - if (tag_out) *tag_out = tag; - if (key_ind_out) *key_ind_out = key_ind; EVP_CIPHER_CTX_free(cctx); + out[SIM_IMSI_MAX_LEN] = '\0'; + return 0; } #ifdef TESTING_SIM_ID /* - * cc id.c -g3 -Wall -DHAVE_DLFCN_H -DTESTING_SIM_ID -DWITH_TLS -I../../../../ -I../../../ -I ../base/ -I /usr/local/opt/openssl/include/ -include ../include/build.h -L /usr/local/opt/openssl/lib/ -l ssl -l crypto -l talloc -L ../../../../../build/lib/.libs/ -lfreeradius-server -lfreeradius-util -o test_sim_id && ./test_sim_id + * cc id.c -g3 -Wall -DHAVE_DLFCN_H -DTESTING_SIM_ID -DWITH_TLS -I../../../../ -I../../../ -I ../base/ -I /usr/local/opt/openssl/include/ -include ../include/build.h -L /usr/local/opt/openssl/lib/ -l ssl -l crypto -l talloc -L ../../../../../build/lib/local/.libs/ -lfreeradius-server -lfreeradius-tls -lfreeradius-util -o test_sim_id && ./test_sim_id */ #include #include @@ -509,54 +551,53 @@ void test_encrypt_decypt_key0(void) { char const id[] = "001234554321001"; char const key[] = "1234567812345678"; - char const *keys[] = { key }; uint8_t tag; uint8_t key_ind; char const *log; - char encrypted_id[SIM_3GPP_PSEUDONYM_LEN]; - uint8_t decrypted_id[sizeof(id)]; + char encrypted_id[SIM_3GPP_PSEUDONYM_LEN + 1]; + char decrypted_id[sizeof(id)]; fr_log_fp = stdout; - TEST_CHECK(fr_sim_id_3gpp_pseudonym_encrypt(encrypted_id, (uint8_t const *)id, sizeof(id) - 1, - 11, 0, (uint8_t const **)keys) == 0); + TEST_CHECK(fr_sim_id_3gpp_pseudonym_encrypt(encrypted_id, id, sizeof(id) - 1, 6, 0, (uint8_t const *)key) == 0); while ((log = fr_strerror_pop())) printf("%s\n", log); - TEST_CHECK(fr_sim_id_3gpp_pseudonym_decypt(decrypted_id, &tag, &key_ind, - encrypted_id, (uint8_t const **)keys, 1) == 0); + tag = fr_sim_id_3gpp_pseudonym_tag(encrypted_id); + TEST_CHECK(tag == 6); + key_ind = fr_sim_id_3gpp_pseudonym_key_index(encrypted_id); + TEST_CHECK(key_ind == 0); + + TEST_CHECK(fr_sim_id_3gpp_pseudonym_decrypt(decrypted_id, encrypted_id, (uint8_t const *)key) == 0); while ((log = fr_strerror_pop())) printf("%s\n", log); - TEST_CHECK(tag == 11); - TEST_CHECK(key_ind == 0); TEST_CHECK(memcmp(id, decrypted_id, 15) == 0); } void test_encrypt_decypt_key1(void) { char const id[] = "001234554321001"; - char const key0[] = "1234567812345678"; - char const key1[] = "2222222288888888"; - char const *keys[] = { key0, key1 }; + char const key[] = "1234567812345678"; uint8_t tag; uint8_t key_ind; char const *log; - char encrypted_id[SIM_3GPP_PSEUDONYM_LEN]; - uint8_t decrypted_id[sizeof(id)]; + char encrypted_id[SIM_3GPP_PSEUDONYM_LEN + 1]; + char decrypted_id[sizeof(id)]; fr_log_fp = stdout; - TEST_CHECK(fr_sim_id_3gpp_pseudonym_encrypt(encrypted_id, (uint8_t const *)id, sizeof(id) - 1, - 11, 1, (uint8_t const **)keys) == 0); - while ((log = fr_strerror_pop())) printf("%s\n", log); - - TEST_CHECK(fr_sim_id_3gpp_pseudonym_decypt(decrypted_id, &tag, &key_ind, - encrypted_id, (uint8_t const **)keys, 2) == 0); + TEST_CHECK(fr_sim_id_3gpp_pseudonym_encrypt(encrypted_id, id, sizeof(id) - 1, 11, 1, (uint8_t const *)key) == 0); while ((log = fr_strerror_pop())) printf("%s\n", log); + tag = fr_sim_id_3gpp_pseudonym_tag(encrypted_id); TEST_CHECK(tag == 11); + key_ind = fr_sim_id_3gpp_pseudonym_key_index(encrypted_id); TEST_CHECK(key_ind == 1); + + TEST_CHECK(fr_sim_id_3gpp_pseudonym_decrypt(decrypted_id, encrypted_id, (uint8_t const *)key) == 0); + while ((log = fr_strerror_pop())) printf("%s\n", log); + TEST_CHECK(memcmp(id, decrypted_id, 15) == 0); } @@ -586,21 +627,23 @@ void test_encrypt_decypt_key16(void) uint8_t key_ind; char const *log; - char encrypted_id[SIM_3GPP_PSEUDONYM_LEN]; - uint8_t decrypted_id[sizeof(id)]; + char encrypted_id[SIM_3GPP_PSEUDONYM_LEN + 1]; + char decrypted_id[sizeof(id)]; fr_log_fp = stdout; - TEST_CHECK(fr_sim_id_3gpp_pseudonym_encrypt(encrypted_id, (uint8_t const *)id, sizeof(id) - 1, - 9, 15, (uint8_t const **)keys) == 0); - while ((log = fr_strerror_pop())) printf("%s\n", log); - - TEST_CHECK(fr_sim_id_3gpp_pseudonym_decypt(decrypted_id, &tag, &key_ind, - encrypted_id, (uint8_t const **)keys, 16) == 0); + TEST_CHECK(fr_sim_id_3gpp_pseudonym_encrypt(encrypted_id, id, sizeof(id) - 1, + 9, 15, (uint8_t const *)keys[15]) == 0); while ((log = fr_strerror_pop())) printf("%s\n", log); + tag = fr_sim_id_3gpp_pseudonym_tag(encrypted_id); TEST_CHECK(tag == 9); + key_ind = fr_sim_id_3gpp_pseudonym_key_index(encrypted_id); TEST_CHECK(key_ind == 15); + + TEST_CHECK(fr_sim_id_3gpp_pseudonym_decrypt(decrypted_id, encrypted_id, (uint8_t const *)keys[key_ind]) == 0); + while ((log = fr_strerror_pop())) printf("%s\n", log); + TEST_CHECK(memcmp(id, decrypted_id, 15) == 0); } diff --git a/src/modules/rlm_eap/lib/sim/id.h b/src/modules/rlm_eap/lib/sim/id.h new file mode 100644 index 00000000000..36779e32a89 --- /dev/null +++ b/src/modules/rlm_eap/lib/sim/id.h @@ -0,0 +1,84 @@ +/* + * This program is is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * @file rlm_eap/lib/sim/id.h + * @brief EAP-SIM/EAP-AKA identity detection, creation, and decyption. + * + * @copyright 2017 The FreeRADIUS server project + */ +#ifndef _EAP_SIM_ID_TYPE_H +#define _EAP_SIM_ID_TYPE_H + +#define SIM_3GPP_PSEUDONYM_LEN 23 //!< Length of a base64 encoded 3gpp pseudonym. +#define SIM_IMSI_MAX_LEN 15 //!< Length of an IMSI number in ASCII. + +/** SIM/AKA method hints + * + * Derived from processing the provided identity. + */ +typedef enum { + SIM_METHOD_HINT_UNKNOWN = 0, //!< We don't know what method the identity hints at. + SIM_METHOD_HINT_SIM = 1, //!< The identity hints the supplicant wants to use + ///< EAP-SIM. + SIM_METHOD_HINT_AKA = 2 //!< The identity hints the supplicant wants to use + ///< EAP-AKA. +} fr_sim_method_hint_t; + +/** SIM/AKA identity type hints + * + * Derived from the processing the provided identity. + */ +typedef enum { + SIM_ID_TYPE_UNKNOWN = 0, //!< We don't know what type of identity this is. + SIM_ID_TYPE_PERMANENT = 1, //!< This is a permanent identity (the IMSI of the SIM). + SIM_ID_TYPE_PSEUDONYM = 2, //!< This is a custom pseudonym. + SIM_ID_TYPE_3GPP_PSEUDONYM = 3, //!< This is a reversibly encrypted 3gpp pseudonym. + SIM_ID_TYPE_FASTAUTH = 4 //!< This is a fastauth (session-resumption) id. +} fr_sim_id_type_t; + +typedef enum { + SIM_ID_TAG_PERMANENT_AKA = '0', + SIM_ID_TAG_PERMANENT_SIM = '1', + SIM_ID_TAG_PSEUDONYM_AKA = '2', + SIM_ID_TAG_PSEUDONYM_SIM = '3', + SIM_ID_TAG_3GPP_PSEUDONYM_AKA = '6', + SIM_ID_TAG_3GPP_PSEUDONYM_SIM = '7', + SIM_ID_TAG_FASTAUTH_AKA = '4', + SIM_ID_TAG_FASTAUTH_SIM = '5' +} fr_sim_id_tag_t; + +size_t fr_sim_id_user_len(char const *nai, size_t nai_len); + +char const *fr_sim_domain(char const *nai, size_t nai_len); + +ssize_t fr_sim_3gpp_root_nai_domain_mcc_mnc(uint16_t *mnc, uint16_t *mcc, + char const *domain, size_t domain_len); + +int fr_sim_id_type(fr_sim_id_type_t *type, fr_sim_method_hint_t *hint, + char const *id, size_t id_len); + +int fr_sim_id_3gpp_pseudonym_encrypt(char out[SIM_3GPP_PSEUDONYM_LEN + 1], + char const *imsi, size_t imsi_len, + uint8_t tag, uint8_t key_ind, uint8_t const key[8]); + +uint8_t fr_sim_id_3gpp_pseudonym_tag(char const encr_id[SIM_3GPP_PSEUDONYM_LEN]); + +uint8_t fr_sim_id_3gpp_pseudonym_key_index(char const encr_id[SIM_3GPP_PSEUDONYM_LEN]); + +int fr_sim_id_3gpp_pseudonym_decrypt(char out[SIM_IMSI_MAX_LEN], + char const encr_id[SIM_3GPP_PSEUDONYM_LEN], uint8_t const key[8]); +#endif /* _EAP_SIM_ID_TYPE_H */ diff --git a/src/modules/rlm_eap/lib/sim/sim_proto.h b/src/modules/rlm_eap/lib/sim/sim_proto.h index 7f32350b045..f092e6b062d 100644 --- a/src/modules/rlm_eap/lib/sim/sim_proto.h +++ b/src/modules/rlm_eap/lib/sim/sim_proto.h @@ -32,6 +32,7 @@ RCSIDH(sim_h, "$Id$") #include #include "dict.h" +#include "id.h" #include "eap_types.h" #include "eap_sim_common.h" #include "eap_aka_common.h" @@ -210,4 +211,10 @@ int fr_sim_vector_umts_from_attrs(eap_session_t *eap_session, VALUE_PAIR *vps, void fr_sim_fips186_2prf(uint8_t out[160], uint8_t mk[20]) CC_BOUNDED(__size__, 2, 160, 160) CC_BOUNDED(__size__, 1, 20, 20); + +/* + * xlat.c + */ +void sim_xlat_register(void); +void sim_xlat_unregister(void); #endif /* _SIM_PROTO_H */ diff --git a/src/modules/rlm_eap/lib/sim/xlat.c b/src/modules/rlm_eap/lib/sim/xlat.c new file mode 100644 index 00000000000..814dd250fe8 --- /dev/null +++ b/src/modules/rlm_eap/lib/sim/xlat.c @@ -0,0 +1,470 @@ +/* + * This program is is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * @file rlm_eap/lib/sim/id_xlat.c + * @brief EAP-SIM/EAP-AKA identity detection, creation, and decyption. + * + * @copyright 2017 The FreeRADIUS server project + */ + +#include +#include "sim_proto.h" + +static int sim_xlat_refs = 0; + + +/** Returns the SIM method EAP-SIM or EAP-AKA hinted at by the user identifier + * + * %{sim_id_method:&id_attr} + */ +static ssize_t sim_xlat_id_method(TALLOC_CTX *ctx, char **out, UNUSED size_t outlen, + UNUSED void const *mod_inst, UNUSED void const *xlat_inst, + REQUEST *request, char const *fmt) +{ + vp_tmpl_t *vpt; + TALLOC_CTX *our_ctx = talloc_init("sim_xlat"); + ssize_t slen, len, id_len; + char const *p = fmt, *id, *method; + fr_sim_id_type_t type_hint; + fr_sim_method_hint_t method_hint; + fr_dict_attr_t const *da; + + /* + * Trim whitespace + */ + while (isspace(*p) && p++); + + slen = tmpl_afrom_attr_substr(our_ctx, &vpt, p, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false); + if (slen <= 0) { + RPEDEBUG("Invalid attribute reference"); + error: + talloc_free(our_ctx); + return -1; + } + + if (tmpl_aexpand(our_ctx, &id, request, vpt, NULL, NULL) < 0) { + RPEDEBUG2("Failing expanding ID attribute"); + goto error; + } + + id_len = talloc_array_length(id) - 1; + len = fr_sim_id_user_len(id, id_len); + if (len == id_len ) { + RPEDEBUG2("SIM ID \"%pS\" is not an NAI", id); + goto error; + } + + if (fr_sim_id_type(&type_hint, &method_hint, id, len) < 0) { + RPEDEBUG2("SIM ID \"%pS\" has unrecognised format", id); + goto error; + } + + da = fr_dict_attr_by_num(NULL, 0, FR_SIM_METHOD_HINT); + if (!da) { + REDEBUG("Missing Sim-Method-Hint attribute"); + goto error; + } + + method = fr_dict_enum_alias_by_value(NULL, da, fr_box_uint32(method_hint)); + if (!method) { + REDEBUG("Missing Sim-Method-Hint value"); + goto error; + } + *out = talloc_typed_strdup(ctx, method); + talloc_free(our_ctx); + + return talloc_array_length(*out) - 1; +} + +/** Returns the type of identity used + * + * %{sim_id_type:&id_attr} + */ +static ssize_t sim_xlat_id_type(TALLOC_CTX *ctx, char **out, UNUSED size_t outlen, + UNUSED void const *mod_inst, UNUSED void const *xlat_inst, + REQUEST *request, char const *fmt) +{ + vp_tmpl_t *vpt; + TALLOC_CTX *our_ctx = talloc_init("sim_xlat"); + ssize_t slen, user_len, id_len; + char const *p = fmt, *id, *method; + fr_sim_id_type_t type_hint; + fr_sim_method_hint_t method_hint; + fr_dict_attr_t const *da; + + /* + * Trim whitespace + */ + while (isspace(*p) && p++); + + slen = tmpl_afrom_attr_substr(our_ctx, &vpt, p, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false); + if (slen <= 0) { + RPEDEBUG("Invalid attribute reference"); + error: + talloc_free(our_ctx); + return -1; + } + + if (tmpl_aexpand(our_ctx, &id, request, vpt, NULL, NULL) < 0) { + RPEDEBUG2("Failing expanding ID attribute"); + goto error; + } + + id_len = talloc_array_length(id) - 1; + user_len = fr_sim_id_user_len(id, id_len); + if (user_len == id_len ) { + RPEDEBUG2("SIM ID \"%pS\" is not an NAI", id); + goto error; + } + + if (fr_sim_id_type(&type_hint, &method_hint, id, user_len) < 0) { + RPEDEBUG2("SIM ID \"%pS\" has unrecognised format", id); + goto error; + } + + da = fr_dict_attr_by_num(NULL, 0, FR_SIM_IDENTITY_TYPE); + if (!da) { + REDEBUG("Missing Sim-Method-Hint attribute"); + goto error; + } + + method = fr_dict_enum_alias_by_value(NULL, da, fr_box_uint32(type_hint)); + if (!method) { + REDEBUG("Missing Sim-Method-Hint value"); + goto error; + } + *out = talloc_typed_strdup(ctx, method); + talloc_free(our_ctx); + + return talloc_array_length(*out) - 1; +} + +/** Returns the key index from a 3gpp pseudonym + * + * %{sim_id_3gpp_pseudonym_key_index:&id_attr} + * + */ +static ssize_t sim_xlat_3gpp_pseudonym_key_index(TALLOC_CTX *ctx, char **out, UNUSED size_t outlen, + UNUSED void const *mod_inst, UNUSED void const *xlat_inst, + REQUEST *request, char const *fmt) +{ + vp_tmpl_t *vpt; + TALLOC_CTX *our_ctx = talloc_init("sim_xlat"); + ssize_t slen, user_len, id_len; + char const *p = fmt, *id; + + /* + * Trim whitespace + */ + while (isspace(*p) && p++); + + slen = tmpl_afrom_attr_substr(our_ctx, &vpt, p, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false); + if (slen <= 0) { + RPEDEBUG("Invalid attribute reference"); + error: + talloc_free(our_ctx); + return -1; + } + + if (tmpl_aexpand(our_ctx, &id, request, vpt, NULL, NULL) < 0) { + RPEDEBUG2("Failing expanding ID attribute"); + goto error; + } + + id_len = talloc_array_length(id) - 1; + user_len = fr_sim_id_user_len(id, id_len); + if (user_len != SIM_3GPP_PSEUDONYM_LEN) { + REDEBUG2("3gpp pseudonym incorrect length, expected %i bytes, got %zu bytes", + SIM_3GPP_PSEUDONYM_LEN, user_len); + goto error; + } + + MEM(*out = talloc_typed_asprintf(ctx, "%i", fr_sim_id_3gpp_pseudonym_tag(id))); + talloc_free(our_ctx); + + return talloc_array_length(*out) - 1; +} + +/** Decrypts a 3gpp pseudonym + * + * %{sim_id_3gpp_pseudonym_decrypt_nai:&id_attr &key_attr} + * + */ +static ssize_t sim_xlat_3gpp_pseudonym_decrypt_nai(TALLOC_CTX *ctx, char **out, UNUSED size_t outlen, + UNUSED void const *mod_inst, UNUSED void const *xlat_inst, + REQUEST *request, char const *fmt) +{ + vp_tmpl_t *id_vpt, *key_vpt; + TALLOC_CTX *our_ctx = talloc_init("sim_xlat"); + ssize_t slen, user_len, id_len, key_len; + uint8_t tag; + char out_tag; + uint8_t *key; + char decrypted[SIM_IMSI_MAX_LEN + 1]; + char const *p = fmt, *id; + + /* + * Trim whitespace + */ + while (isspace(*p) && p++); + + slen = tmpl_afrom_attr_substr(our_ctx, &id_vpt, p, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false); + if (slen <= 0) { + RPEDEBUG("Invalid ID attribute reference"); + error: + talloc_free(our_ctx); + return -1; + } + + p += slen; + if (*p != ' ') { + REDEBUG2("Missing key argument"); + goto error; + } + p++; + + slen = tmpl_afrom_attr_substr(our_ctx, &key_vpt, p, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false); + if (slen <= 0) { + RPEDEBUG("Invalid key attribute reference"); + goto error; + } + + if (tmpl_aexpand(our_ctx, &id, request, id_vpt, NULL, NULL) < 0) { + RPEDEBUG2("Failing expanding ID attribute"); + goto error; + } + + + if (tmpl_aexpand(our_ctx, &key, request, key_vpt, NULL, NULL) < 0) { + RPEDEBUG2("Failing expanding Key attribute"); + goto error; + } + + id_len = talloc_array_length(id); + user_len = fr_sim_id_user_len(id, id_len); + if (user_len != SIM_3GPP_PSEUDONYM_LEN) { + REDEBUG2("3gpp pseudonym incorrect length, expected %i bytes, got %zu bytes", + SIM_3GPP_PSEUDONYM_LEN, user_len); + return -1; + } + + key_len = talloc_array_length(key); + if (key_len != 8) { + REDEBUG2("Decryption key incorrect length, expected %i bytes, got %zu bytes", 8, key_len); + return -1; + } + + tag = fr_sim_id_3gpp_pseudonym_tag(id); + switch (tag) { + case 59: /* 7 in the base64 alphabet (SIM) */ + out_tag = SIM_ID_TAG_PERMANENT_SIM; + break; + + case 58: /* 6 in the base64 alphabet (AKA) */ + out_tag = SIM_ID_TAG_PERMANENT_AKA; + break; + + default: + REDEBUG2("Unexpected tag value (%u) in SIM ID \"%pS\"", tag, id); + return -1; + } + + RDEBUG2("Decrypting \"%.*s\"", (int)user_len, id); + if (fr_sim_id_3gpp_pseudonym_decrypt(decrypted, id, key) < 0) { + RPEDEBUG2("SIM ID \"%pS\" is not a 3gpp pseudonym", id); + return -1; + } + + /* + * Recombine unencrypted IMSI with @domain + */ + MEM(*out = talloc_typed_asprintf(ctx, "%c%s%s", out_tag, decrypted, id + user_len)); + talloc_free(our_ctx); + + return talloc_array_length(*out) - 1; +} + +/** Decrypts a 3gpp pseudonym + * + * %{sim_id_3gpp_pseudonym_encrypt:&id_attr &key_attr } + * + */ +static ssize_t sim_xlat_3gpp_pseudonym_encrypt_nai(TALLOC_CTX *ctx, char **out, UNUSED size_t outlen, + UNUSED void const *mod_inst, UNUSED void const *xlat_inst, + REQUEST *request, char const *fmt) +{ + vp_tmpl_t *id_vpt, *key_vpt; + TALLOC_CTX *our_ctx = talloc_init("sim_xlat"); + ssize_t slen, user_len, id_len, key_len; + uint8_t *key, tag; + unsigned long key_index; + char encrypted[SIM_3GPP_PSEUDONYM_LEN + 1]; + char const *p = fmt, *id; + fr_sim_id_type_t type_hint; + fr_sim_method_hint_t method_hint; + + /* + * Trim whitespace + */ + while (isspace(*p) && p++); + + slen = tmpl_afrom_attr_substr(our_ctx, &id_vpt, p, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false); + if (slen <= 0) { + RPEDEBUG("Invalid ID attribute reference"); + error: + talloc_free(our_ctx); + return -1; + } + + p += slen; + if (*p != ' ') { + REDEBUG2("Missing key argument"); + goto error; + } + p++; + + slen = tmpl_afrom_attr_substr(our_ctx, &key_vpt, p, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false); + if (slen <= 0) { + RPEDEBUG("Invalid key attribute reference"); + goto error; + } + p += slen; + + if (*p != ' ') { + REDEBUG2("Missing key index"); + goto error; + } + p++; + + /* + * Get the key index + */ + key_index = strtoul(p, NULL, 10); + if (key_index > 15) { + REDEBUG2("Key index must be between 0-15"); + goto error; + } + + /* + * Get the ID + */ + if (tmpl_aexpand(our_ctx, &id, request, id_vpt, NULL, NULL) < 0) { + RPEDEBUG2("Failing expanding ID attribute"); + goto error; + } + + id_len = talloc_array_length(id) - 1; + user_len = fr_sim_id_user_len(id, id_len); + if (user_len > (SIM_IMSI_MAX_LEN + 1)) { /* +1 for tag */ + REDEBUG2("3gpp pseudonym incorrect length, expected less than %i bytes, got %zu bytes", + SIM_IMSI_MAX_LEN + 1, user_len); + return -1; + } + + /* + * Get the key + */ + if (tmpl_aexpand(our_ctx, &key, request, key_vpt, NULL, NULL) < 0) { + RPEDEBUG2("Failing expanding Key attribute"); + goto error; + } + + key_len = talloc_array_length(key); + if (key_len != 8) { + REDEBUG2("Encryption key incorrect length, expected %i bytes, got %zu bytes", 8, key_len); + return -1; + } + + /* + * Determine what type/method hints are in + * the current ID. + */ + if (fr_sim_id_type(&type_hint, &method_hint, id, user_len) < 0) { + RPEDEBUG2("SIM ID \"%pS\" has unrecognised format", id); + goto error; + } + + if (type_hint != SIM_ID_TYPE_PERMANENT) { + REDEBUG2("SIM ID \"%pS\" is not a permanent identity (IMSI)", id); + goto error; + } + + switch (method_hint) { + case SIM_METHOD_HINT_SIM: + tag = 59; /* 7 in the base64 alphabet */ + break; + + case SIM_METHOD_HINT_AKA: + tag = 58; /* 6 in the base64 alphabet */ + break; + + case SIM_METHOD_HINT_UNKNOWN: + REDEBUG2("SIM ID \"%pS\" does not contain a method hint", id); + goto error; + } + + /* + * Encrypt the IMSI + * + * Strip existing tag from the permanent id + */ + if (fr_sim_id_3gpp_pseudonym_encrypt(encrypted, id + 1, user_len - 1, tag, (uint8_t)key_index, key) < 0) { + RPEDEBUG2("Failed encrypting SIM ID \"%pS\"", id); + return -1; + } + + /* + * Recombine encrypted IMSI with @domain + */ + MEM(*out = talloc_typed_asprintf(ctx, "%s%s", encrypted, id + user_len)); + talloc_free(our_ctx); + + return talloc_array_length(*out) - 1; +} + +void sim_xlat_register(void) +{ + if (sim_xlat_refs) { + sim_xlat_refs++; + return; + } + + xlat_register(NULL, "sim_id_method", sim_xlat_id_method, NULL, NULL, 0, 0, true); + xlat_register(NULL, "sim_id_type", sim_xlat_id_type, NULL, NULL, 0, 0, true); + xlat_register(NULL, "3gpp_pseudonym_key_index", + sim_xlat_3gpp_pseudonym_key_index, NULL, NULL, 0, 0, true); + xlat_register(NULL, "3gpp_pseudonym_decrypt_nai", + sim_xlat_3gpp_pseudonym_decrypt_nai, NULL, NULL, 0, 0, true); + xlat_register(NULL, "3gpp_pseudonym_encrypt_nai", + sim_xlat_3gpp_pseudonym_encrypt_nai, NULL, NULL, 0, 0, true); + sim_xlat_refs = 1; +} + +void sim_xlat_unregister(void) +{ + if (sim_xlat_refs > 1) { + sim_xlat_refs--; + return; + } + + xlat_unregister("sim_id_method"); + xlat_unregister("sim_id_type"); + xlat_unregister("3gpp_pseudonym_key_index"); + xlat_unregister("3gpp_pseudonym_decrypt_nai"); + xlat_unregister("3gpp_pseudonym_encrypt_nai"); + sim_xlat_refs = 0; +} diff --git a/src/modules/rlm_eap/types/rlm_eap_aka/rlm_eap_aka.c b/src/modules/rlm_eap/types/rlm_eap_aka/rlm_eap_aka.c index 7ee488feb21..56647a72c62 100644 --- a/src/modules/rlm_eap/types/rlm_eap_aka/rlm_eap_aka.c +++ b/src/modules/rlm_eap/types/rlm_eap_aka/rlm_eap_aka.c @@ -407,9 +407,16 @@ static int mod_load(void) return -1; } if (fr_sim_global_init() < 0) return -1; + sim_xlat_register(); + return 0; } +static void mod_unload(void) +{ + sim_xlat_unregister(); +} + /* * The module name should be the only globally exported symbol. * That is, everything else should be 'static'. @@ -419,6 +426,7 @@ rlm_eap_submodule_t rlm_eap_aka = { .name = "eap_aka", .magic = RLM_MODULE_INIT, .load = mod_load, + .unload = mod_unload, .session_init = mod_session_init, /* Initialise a new EAP session */ .process = mod_process, /* Process next round of EAP method */ }; diff --git a/src/modules/rlm_eap/types/rlm_eap_sim/rlm_eap_sim.c b/src/modules/rlm_eap/types/rlm_eap_sim/rlm_eap_sim.c index 82e15f8f348..6eb7ca2dffe 100644 --- a/src/modules/rlm_eap/types/rlm_eap_sim/rlm_eap_sim.c +++ b/src/modules/rlm_eap/types/rlm_eap_sim/rlm_eap_sim.c @@ -589,9 +589,16 @@ static int mod_load(void) return -1; } if (fr_sim_global_init() < 0) return -1; + sim_xlat_register(); + return 0; } +static void mod_unload(void) +{ + sim_xlat_unregister(); +} + /* * The module name should be the only globally exported symbol. * That is, everything else should be 'static'. @@ -601,6 +608,7 @@ rlm_eap_submodule_t rlm_eap_sim = { .name = "eap_sim", .magic = RLM_MODULE_INIT, .load = mod_load, + .unload = mod_unload, .instantiate = mod_instantiate, /* Create new submodule instance */ .session_init = mod_session_init, /* Initialise a new EAP session */ .process = mod_process, /* Process next round of EAP method */ diff --git a/src/tests/modules/eap_sim/.gitignore b/src/tests/modules/eap_sim/.gitignore new file mode 100644 index 00000000000..405551a9439 --- /dev/null +++ b/src/tests/modules/eap_sim/.gitignore @@ -0,0 +1 @@ +rlm_sql_sqlite.db diff --git a/src/tests/modules/eap_sim/all.mk b/src/tests/modules/eap_sim/all.mk new file mode 100644 index 00000000000..ec75fd4f960 --- /dev/null +++ b/src/tests/modules/eap_sim/all.mk @@ -0,0 +1,3 @@ +# +# Test the eap_sim module +# diff --git a/src/tests/modules/eap_sim/module.conf b/src/tests/modules/eap_sim/module.conf new file mode 100644 index 00000000000..f6b1e689de4 --- /dev/null +++ b/src/tests/modules/eap_sim/module.conf @@ -0,0 +1,6 @@ +eap { + type = sim + sim { + + } +} diff --git a/src/tests/modules/eap_sim/sim_xlat_id_aka.attrs b/src/tests/modules/eap_sim/sim_xlat_id_aka.attrs new file mode 100644 index 00000000000..7723ccbcced --- /dev/null +++ b/src/tests/modules/eap_sim/sim_xlat_id_aka.attrs @@ -0,0 +1,9 @@ +# +# Input packet +# +User-Name = "0420032219455258@wlan.mnc003.mcc420.3gppnetwork.org" + +# +# Expected answer +# +Response-Packet-Type == Access-Accept diff --git a/src/tests/modules/eap_sim/sim_xlat_id_aka.unlang b/src/tests/modules/eap_sim/sim_xlat_id_aka.unlang new file mode 100644 index 00000000000..ce74db4e667 --- /dev/null +++ b/src/tests/modules/eap_sim/sim_xlat_id_aka.unlang @@ -0,0 +1,61 @@ +if ("%{sim_id_method:&User-Name}" != 'AKA') { + test_fail +} else { + test_pass +} + +if ("%{sim_id_type:&User-Name}" != 'Permanent') { + test_fail +} else { + test_pass +} + +# +# Encrypt the permanent ID +# +update control { + Tmp-String-0 := '12345678' +} +update control { + User-Name := "%{3gpp_pseudonym_encrypt_nai:&User-Name &control:Tmp-String-0 6}" +} + +# +# Can we get the EAP method from the encrypted blob correctly? +# +if ("%{sim_id_method:&control:User-Name}" != 'AKA') { + test_fail +} else { + test_pass +} + +# +# Can we identify the encrypted blob correctly? +# +if ("%{sim_id_type:&control:User-Name}" != '3GPP-Pseudonym') { + test_fail +} else { + test_pass +} + +# +# We should refuse to re-encrypt an encrypted NAI +# +if ("%{3gpp_pseudonym_encrypt_nai:&control:User-Name &control:Tmp-String-0 6}" != '') { + test_fail +} else { + test_pass +} + +# +# Get the original IMSI back again +# +update control { + Tmp-String-1 := "%{3gpp_pseudonym_decrypt_nai:&control:User-Name &control:Tmp-String-0}" +} + +if (&control:Tmp-String-1 != &User-Name) { + test_fail +} else { + test_pass +} diff --git a/src/tests/modules/eap_sim/sim_xlat_id_error.attrs b/src/tests/modules/eap_sim/sim_xlat_id_error.attrs new file mode 100644 index 00000000000..354fe91b16a --- /dev/null +++ b/src/tests/modules/eap_sim/sim_xlat_id_error.attrs @@ -0,0 +1,9 @@ +# +# Input packet +# +User-Name = "foo" + +# +# Expected answer +# +Response-Packet-Type == Access-Accept diff --git a/src/tests/modules/eap_sim/sim_xlat_id_error.unlang b/src/tests/modules/eap_sim/sim_xlat_id_error.unlang new file mode 100644 index 00000000000..0da78e62207 --- /dev/null +++ b/src/tests/modules/eap_sim/sim_xlat_id_error.unlang @@ -0,0 +1,25 @@ +# +# No domain separator +# +update request { + User-Name := '1420032219455258wlan.mnc003.mcc420.3gppnetwork.org' +} + +if ("%{sim_id_method:&User-Name}" != '') { + test_fail +} else { + test_pass +} + +# +# Zero length ID +# +update request { + User-Name := '' +} + +if ("%{sim_id_method:&User-Name}" != '') { + test_fail +} else { + test_pass +} diff --git a/src/tests/modules/eap_sim/sim_xlat_id_sim.attrs b/src/tests/modules/eap_sim/sim_xlat_id_sim.attrs new file mode 100644 index 00000000000..272590d8e12 --- /dev/null +++ b/src/tests/modules/eap_sim/sim_xlat_id_sim.attrs @@ -0,0 +1,9 @@ +# +# Input packet +# +User-Name = "1420032219455258@wlan.mnc003.mcc420.3gppnetwork.org" + +# +# Expected answer +# +Response-Packet-Type == Access-Accept diff --git a/src/tests/modules/eap_sim/sim_xlat_id_sim.unlang b/src/tests/modules/eap_sim/sim_xlat_id_sim.unlang new file mode 100644 index 00000000000..12df061fcca --- /dev/null +++ b/src/tests/modules/eap_sim/sim_xlat_id_sim.unlang @@ -0,0 +1,48 @@ +if ("%{sim_id_method:&User-Name}" != 'SIM') { + test_fail +} else { + test_pass +} + +if ("%{sim_id_type:&User-Name}" != 'Permanent') { + test_fail +} else { + test_pass +} + +# +# Encrypt the permanent ID +# +update control { + Tmp-String-0 := '12345678' +} +update control { + User-Name := "%{3gpp_pseudonym_encrypt_nai:&User-Name &control:Tmp-String-0 6}" +} + +# +# Can we get the EAP method from the encrypted blob correctly? +# +if ("%{sim_id_method:&control:User-Name}" != 'SIM') { + test_fail +} else { + test_pass +} + +# +# Can we identify the encrypted blob correctly? +# +if ("%{sim_id_type:&control:User-Name}" != '3GPP-Pseudonym') { + test_fail +} else { + test_pass +} + +# +# We should refuse to re-encrypt an encrypted NAI +# +if ("%{3gpp_pseudonym_encrypt_nai:&control:User-Name &control:Tmp-String-0 6}" != '') { + test_fail +} else { + test_pass +}