From: Arran Cudbard-Bell Date: Mon, 29 Mar 2021 16:16:10 +0000 (+0100) Subject: Move the majority of the SIM/AKA/AKA' logic into process modules X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=ca7e5cfb4baf26fae48fbc59fbe69d251ee12f7a;p=thirdparty%2Ffreeradius-server.git Move the majority of the SIM/AKA/AKA' logic into process modules --- diff --git a/share/dictionary/eap/aka-sim/dictionary.freeradius b/share/dictionary/eap/aka-sim/dictionary.freeradius index 41c6cba5151..d91af9cee4f 100644 --- a/share/dictionary/eap/aka-sim/dictionary.freeradius +++ b/share/dictionary/eap/aka-sim/dictionary.freeradius @@ -10,7 +10,7 @@ # Internal (Non-Protocol) attributes # FLAGS internal -ATTRIBUTE Subtype 256 integer +ATTRIBUTE Subtype 260 integer VALUE Subtype AKA-Challenge 1 VALUE Subtype AKA-Authentication-Reject 2 @@ -22,46 +22,52 @@ VALUE Subtype AKA-SIM-Notification 12 VALUE Subtype AKA-SIM-Reauthentication 13 VALUE Subtype AKA-SIM-Client-Error 14 -ATTRIBUTE HMAC 257 string -ATTRIBUTE Key 258 octets -ATTRIBUTE Extra 259 octets +# +# Keying material exported by the state machine +# to allow the encoder to function. +# +ATTRIBUTE HMAC-Extra-Request 261 octets +ATTRIBUTE HMAC-Extra-Response 262 octets +ATTRIBUTE K-Encr 263 octets +ATTRIBUTE K-Aut 264 octets + +# +# Keys passed to the session resumption sections +# for storage and retrieval. +# +ATTRIBUTE MK 270 octets # Master key +ATTRIBUTE AK 271 octets +ATTRIBUTE K-Re 272 octets # AKA' KDF 1 reauthentication key. # # UMTS vector # -ATTRIBUTE XRES 260 octets -ATTRIBUTE CK 261 octets -ATTRIBUTE IK 262 octets -ATTRIBUTE AUTHN 263 octets +ATTRIBUTE XRES 280 octets +ATTRIBUTE CK 281 octets +ATTRIBUTE IK 282 octets +ATTRIBUTE AUTHN 283 octets # # SIM vector # -ATTRIBUTE SRES 270 octets -ATTRIBUTE KC 271 octets +ATTRIBUTE SRES 290 octets +ATTRIBUTE KC 291 octets # # Identity negotiation # -ATTRIBUTE Method-Hint 280 integer +ATTRIBUTE Method-Hint 300 integer VALUE Method-Hint AKA-Prime 0 VALUE Method-Hint AKA 1 VALUE Method-Hint SIM 2 -ATTRIBUTE Identity-Type 281 integer +ATTRIBUTE Identity-Type 310 integer VALUE Identity-Type Permanent 0 VALUE Identity-Type Pseudonym 1 VALUE Identity-Type Fastauth 2 -ATTRIBUTE Permanent-Identity 282 string -ATTRIBUTE KDF-Identity 283 string # Allow override of KDF Identity input - -# -# Reauthentication -# -ATTRIBUTE MK 290 octets # Master key -ATTRIBUTE AK 291 octets -ATTRIBUTE K-Re 292 octets # AKA' KDF 1 reauthentication key. +ATTRIBUTE Permanent-Identity 311 string +ATTRIBUTE KDF-Identity 312 string # Allow override of KDF Identity input diff --git a/src/lib/eap/base.h b/src/lib/eap/base.h index c53da011b45..ddc6f08875b 100644 --- a/src/lib/eap/base.h +++ b/src/lib/eap/base.h @@ -45,15 +45,6 @@ RCSIDH(lib_eap_base_h, "$Id$") #define REQUEST_DATA_EAP_TUNNEL_CALLBACK FR_EAP_MESSAGE #define REQUEST_DATA_EAP_MSCHAP_TUNNEL_CALLBACK ((FR_EAP_MESSAGE << 16) | FR_EAP_METHOD_MSCHAPV2) - -#define EAP_SECTION_DEFINE(_field, _verb, _name) \ - { \ - .name = _verb, \ - .name2 = _name, \ - .component = MOD_AUTHORIZE, \ - .offset = offsetof(eap_aka_sim_actions_t, _field), \ - } - /* * This is for tunneled callbacks */ diff --git a/src/lib/eap_aka_sim/all.mk b/src/lib/eap_aka_sim/all.mk index 35c59b429e3..757139411b9 100644 --- a/src/lib/eap_aka_sim/all.mk +++ b/src/lib/eap_aka_sim/all.mk @@ -4,12 +4,13 @@ endif SOURCES := \ base.c \ - state_machine.c \ crypto.c \ decode.c \ encode.c \ fips186prf.c \ id.c \ + module.c \ + state_machine.c \ vector.c \ xlat.c diff --git a/src/lib/eap_aka_sim/attrs.h b/src/lib/eap_aka_sim/attrs.h index 66d0932ad68..6c700859b1a 100644 --- a/src/lib/eap_aka_sim/attrs.h +++ b/src/lib/eap_aka_sim/attrs.h @@ -38,19 +38,21 @@ extern fr_dict_attr_t const *attr_eap_aka_sim_bidding; extern fr_dict_attr_t const *attr_eap_aka_sim_checkcode; extern fr_dict_attr_t const *attr_eap_aka_sim_ck; extern fr_dict_attr_t const *attr_eap_aka_sim_client_error_code; -extern fr_dict_attr_t const *attr_eap_aka_sim_counter_too_small; extern fr_dict_attr_t const *attr_eap_aka_sim_counter; +extern fr_dict_attr_t const *attr_eap_aka_sim_counter_too_small; extern fr_dict_attr_t const *attr_eap_aka_sim_encr_data; extern fr_dict_attr_t const *attr_eap_aka_sim_fullauth_id_req; -extern fr_dict_attr_t const *attr_eap_aka_sim_identity_type; extern fr_dict_attr_t const *attr_eap_aka_sim_identity; +extern fr_dict_attr_t const *attr_eap_aka_sim_identity_type; extern fr_dict_attr_t const *attr_eap_aka_sim_ik; extern fr_dict_attr_t const *attr_eap_aka_sim_iv; +extern fr_dict_attr_t const *attr_eap_aka_sim_k_aut; +extern fr_dict_attr_t const *attr_eap_aka_sim_k_encr; extern fr_dict_attr_t const *attr_eap_aka_sim_k_re; extern fr_dict_attr_t const *attr_eap_aka_sim_kc; +extern fr_dict_attr_t const *attr_eap_aka_sim_kdf; extern fr_dict_attr_t const *attr_eap_aka_sim_kdf_identity; extern fr_dict_attr_t const *attr_eap_aka_sim_kdf_input; -extern fr_dict_attr_t const *attr_eap_aka_sim_kdf; extern fr_dict_attr_t const *attr_eap_aka_sim_mac; extern fr_dict_attr_t const *attr_eap_aka_sim_method_hint; extern fr_dict_attr_t const *attr_eap_aka_sim_mk; @@ -63,11 +65,13 @@ extern fr_dict_attr_t const *attr_eap_aka_sim_padding; extern fr_dict_attr_t const *attr_eap_aka_sim_permanent_id_req; extern fr_dict_attr_t const *attr_eap_aka_sim_permanent_identity; extern fr_dict_attr_t const *attr_eap_aka_sim_rand; +extern fr_dict_attr_t const *attr_eap_aka_sim_hmac_extra_request; extern fr_dict_attr_t const *attr_eap_aka_sim_res; +extern fr_dict_attr_t const *attr_eap_aka_sim_hmac_extra_response; extern fr_dict_attr_t const *attr_eap_aka_sim_result_ind; +extern fr_dict_attr_t const *attr_eap_aka_sim_selected_version; extern fr_dict_attr_t const *attr_eap_aka_sim_sres; extern fr_dict_attr_t const *attr_eap_aka_sim_subtype; -extern fr_dict_attr_t const *attr_eap_aka_sim_selected_version; extern fr_dict_attr_t const *attr_eap_aka_sim_version_list; extern fr_dict_attr_t const *attr_eap_aka_sim_xres; diff --git a/src/lib/eap_aka_sim/base.c b/src/lib/eap_aka_sim/base.c index 0008f02aa42..41a16489146 100644 --- a/src/lib/eap_aka_sim/base.c +++ b/src/lib/eap_aka_sim/base.c @@ -63,19 +63,21 @@ fr_dict_attr_t const *attr_eap_aka_sim_bidding; fr_dict_attr_t const *attr_eap_aka_sim_checkcode; fr_dict_attr_t const *attr_eap_aka_sim_ck; fr_dict_attr_t const *attr_eap_aka_sim_client_error_code; -fr_dict_attr_t const *attr_eap_aka_sim_counter_too_small; fr_dict_attr_t const *attr_eap_aka_sim_counter; +fr_dict_attr_t const *attr_eap_aka_sim_counter_too_small; fr_dict_attr_t const *attr_eap_aka_sim_encr_data; fr_dict_attr_t const *attr_eap_aka_sim_fullauth_id_req; -fr_dict_attr_t const *attr_eap_aka_sim_identity_type; fr_dict_attr_t const *attr_eap_aka_sim_identity; +fr_dict_attr_t const *attr_eap_aka_sim_identity_type; fr_dict_attr_t const *attr_eap_aka_sim_ik; fr_dict_attr_t const *attr_eap_aka_sim_iv; +fr_dict_attr_t const *attr_eap_aka_sim_k_aut; +fr_dict_attr_t const *attr_eap_aka_sim_k_encr; fr_dict_attr_t const *attr_eap_aka_sim_k_re; fr_dict_attr_t const *attr_eap_aka_sim_kc; +fr_dict_attr_t const *attr_eap_aka_sim_kdf; fr_dict_attr_t const *attr_eap_aka_sim_kdf_identity; fr_dict_attr_t const *attr_eap_aka_sim_kdf_input; -fr_dict_attr_t const *attr_eap_aka_sim_kdf; fr_dict_attr_t const *attr_eap_aka_sim_mac; fr_dict_attr_t const *attr_eap_aka_sim_method_hint; fr_dict_attr_t const *attr_eap_aka_sim_mk; @@ -88,10 +90,12 @@ fr_dict_attr_t const *attr_eap_aka_sim_padding; fr_dict_attr_t const *attr_eap_aka_sim_permanent_id_req; fr_dict_attr_t const *attr_eap_aka_sim_permanent_identity; fr_dict_attr_t const *attr_eap_aka_sim_rand; +fr_dict_attr_t const *attr_eap_aka_sim_hmac_extra_request; fr_dict_attr_t const *attr_eap_aka_sim_res; +fr_dict_attr_t const *attr_eap_aka_sim_hmac_extra_response; fr_dict_attr_t const *attr_eap_aka_sim_result_ind; -fr_dict_attr_t const *attr_eap_aka_sim_sres; fr_dict_attr_t const *attr_eap_aka_sim_selected_version; +fr_dict_attr_t const *attr_eap_aka_sim_sres; fr_dict_attr_t const *attr_eap_aka_sim_subtype; fr_dict_attr_t const *attr_eap_aka_sim_version_list; fr_dict_attr_t const *attr_eap_aka_sim_xres; @@ -120,19 +124,21 @@ fr_dict_attr_autoload_t libfreeradius_aka_sim_dict_attr[] = { { .out = &attr_eap_aka_sim_checkcode, .name = "Checkcode", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim }, { .out = &attr_eap_aka_sim_ck, .name = "CK", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim }, { .out = &attr_eap_aka_sim_client_error_code, .name = "Client-Error-Code", .type = FR_TYPE_UINT16, .dict = &dict_eap_aka_sim }, - { .out = &attr_eap_aka_sim_counter_too_small, .name = "Encr-Data.Counter-Too-Small", .type = FR_TYPE_BOOL, .dict = &dict_eap_aka_sim }, { .out = &attr_eap_aka_sim_counter, .name = "Encr-Data.Counter", .type = FR_TYPE_UINT16, .dict = &dict_eap_aka_sim }, + { .out = &attr_eap_aka_sim_counter_too_small, .name = "Encr-Data.Counter-Too-Small", .type = FR_TYPE_BOOL, .dict = &dict_eap_aka_sim }, { .out = &attr_eap_aka_sim_encr_data, .name = "Encr-Data", .type = FR_TYPE_TLV, .dict = &dict_eap_aka_sim }, { .out = &attr_eap_aka_sim_fullauth_id_req, .name = "Fullauth-ID-Req", .type = FR_TYPE_BOOL, .dict = &dict_eap_aka_sim }, - { .out = &attr_eap_aka_sim_identity_type, .name = "Identity-Type", .type = FR_TYPE_UINT32, .dict = &dict_eap_aka_sim }, { .out = &attr_eap_aka_sim_identity, .name = "Identity", .type = FR_TYPE_STRING, .dict = &dict_eap_aka_sim }, + { .out = &attr_eap_aka_sim_identity_type, .name = "Identity-Type", .type = FR_TYPE_UINT32, .dict = &dict_eap_aka_sim }, { .out = &attr_eap_aka_sim_ik, .name = "IK", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim }, { .out = &attr_eap_aka_sim_iv, .name = "IV", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim }, + { .out = &attr_eap_aka_sim_k_aut, .name = "K-Aut", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim }, + { .out = &attr_eap_aka_sim_k_encr, .name = "K-Encr", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim }, { .out = &attr_eap_aka_sim_k_re, .name = "K-Re", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim }, { .out = &attr_eap_aka_sim_kc, .name = "KC", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim }, + { .out = &attr_eap_aka_sim_kdf, .name = "KDF", .type = FR_TYPE_UINT16, .dict = &dict_eap_aka_sim }, { .out = &attr_eap_aka_sim_kdf_identity, .name = "KDF-Identity", .type = FR_TYPE_STRING, .dict = &dict_eap_aka_sim }, { .out = &attr_eap_aka_sim_kdf_input, .name = "KDF-Input", .type = FR_TYPE_STRING, .dict = &dict_eap_aka_sim }, - { .out = &attr_eap_aka_sim_kdf, .name = "KDF", .type = FR_TYPE_UINT16, .dict = &dict_eap_aka_sim }, { .out = &attr_eap_aka_sim_mac, .name = "MAC", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim }, { .out = &attr_eap_aka_sim_method_hint, .name = "Method-Hint", .type = FR_TYPE_UINT32, .dict = &dict_eap_aka_sim }, { .out = &attr_eap_aka_sim_mk, .name = "MK", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim }, @@ -145,10 +151,12 @@ fr_dict_attr_autoload_t libfreeradius_aka_sim_dict_attr[] = { { .out = &attr_eap_aka_sim_permanent_id_req, .name = "Permanent-Id-Req", .type = FR_TYPE_BOOL, .dict = &dict_eap_aka_sim }, { .out = &attr_eap_aka_sim_permanent_identity, .name = "Permanent-Identity", .type = FR_TYPE_STRING, .dict = &dict_eap_aka_sim }, { .out = &attr_eap_aka_sim_rand, .name = "RAND", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim }, + { .out = &attr_eap_aka_sim_hmac_extra_request, .name = "HMAC-Extra-Request", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim }, { .out = &attr_eap_aka_sim_res, .name = "RES", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim }, + { .out = &attr_eap_aka_sim_hmac_extra_response, .name = "HMAC-Extra-Response", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim }, { .out = &attr_eap_aka_sim_result_ind, .name = "Result-Ind", .type = FR_TYPE_BOOL, .dict = &dict_eap_aka_sim }, - { .out = &attr_eap_aka_sim_sres, .name = "RES", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim }, { .out = &attr_eap_aka_sim_selected_version, .name = "Selected-Version", .type = FR_TYPE_UINT16, .dict = &dict_eap_aka_sim }, + { .out = &attr_eap_aka_sim_sres, .name = "SRES", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim }, { .out = &attr_eap_aka_sim_subtype, .name = "Subtype", .type = FR_TYPE_UINT32, .dict = &dict_eap_aka_sim }, { .out = &attr_eap_aka_sim_version_list, .name = "Version-List", .type = FR_TYPE_UINT16, .dict = &dict_eap_aka_sim }, { .out = &attr_eap_aka_sim_xres, .name = "XRES", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim }, diff --git a/src/lib/eap_aka_sim/base.h b/src/lib/eap_aka_sim/base.h index 04ae1a1e91b..e8381a643c0 100644 --- a/src/lib/eap_aka_sim/base.h +++ b/src/lib/eap_aka_sim/base.h @@ -218,27 +218,27 @@ typedef struct { uint8_t emsk[64]; //!< Derived extended master session key. } fr_aka_sim_keys_t; +/** Encoder/decoder ctx + * + */ typedef struct { - fr_aka_sim_keys_t const *keys; //!< From the EAP session. - uint8_t iv[AKA_SIM_IV_SIZE]; //!< From the current packet. - bool have_iv; //!< Whether we found the IV already. -} fr_aka_sim_decode_ctx_t; + uint8_t iv[AKA_SIM_IV_SIZE]; //!< From the current packet. + bool have_iv; //!< Whether we found the IV already. -typedef struct { - fr_dict_attr_t const *root; //!< Root attribute of the dictionary. - fr_aka_sim_keys_t const *keys; //!< From the EAP session. - uint8_t iv[AKA_SIM_IV_SIZE]; //!< Generated by us using our PRNG. - bool iv_included; //!< Whether we've already added an IV to this packet. + eap_packet_t *eap_packet; //!< Needed for validating AT_MAC. - /* - * Additional HMAC inputs - */ - EVP_MD const *hmac_md; //!< HMAC digest algorithm, usually EVP_sha1(). - eap_packet_t *eap_packet; //!< Needed for HMAC generation so we can construct - ///< the EAP packet header. - uint8_t const *hmac_extra; //!< Extra data for the HMAC function. - size_t hmac_extra_len; //!< The length of the HMAC data. -} fr_aka_sim_encode_ctx_t; + EVP_MD const *hmac_md; //!< HMAC digest algorithm, usually EVP_sha1(). + EVP_MD const *checkcode_md; //!< HMAC we use for calculating the checkcode. + + uint8_t const *hmac_extra; //!< Extra data for the HMAC function. + size_t hmac_extra_len; + + uint8_t const *k_encr; //!< The encryption key used for encrypting. + ///< always 16 bytes. + + uint8_t const *k_aut; //!< The authentication key used for signing. + size_t k_aut_len; +} fr_aka_sim_ctx_t; extern size_t const fr_aka_sim_attr_sizes[FR_TYPE_MAX + 1][2]; @@ -246,10 +246,10 @@ extern size_t const fr_aka_sim_attr_sizes[FR_TYPE_MAX + 1][2]; * decode.c */ ssize_t fr_aka_sim_decode_pair(TALLOC_CTX *ctx, fr_dcursor_t *cursor, fr_dict_t const *dict, - uint8_t const *data, size_t data_len, void *decode_ctx); + uint8_t const *data, size_t data_len, void *decode_ctx); int fr_aka_sim_decode(request_t *request, fr_dcursor_t *decoded, fr_dict_t const *dict, - uint8_t const *data, size_t data_len, fr_aka_sim_decode_ctx_t *ctx); + uint8_t const *data, size_t data_len, fr_aka_sim_ctx_t *ctx); /* * encode.c @@ -280,9 +280,9 @@ int fr_aka_sim_crypto_update_checkcode(fr_aka_sim_checkcode_t *checkcode, eap_p ssize_t fr_aka_sim_crypto_finalise_checkcode(uint8_t *out, fr_aka_sim_checkcode_t **checkcode); ssize_t fr_aka_sim_crypto_sign_packet(uint8_t out[static AKA_SIM_MAC_DIGEST_SIZE], - eap_packet_t *eap_packet, bool zero_mac, - EVP_MD const *md, uint8_t const *key, size_t const key_len, - uint8_t const *hmac_extra, size_t const hmac_extra_len); + eap_packet_t *eap_packet, bool zero_mac, + EVP_MD const *md, uint8_t const *key, size_t const key_len, + uint8_t const *hmac_extra, size_t const hmac_extra_len); int fr_aka_sim_crypto_gsm_kdf_0(fr_aka_sim_keys_t *keys); diff --git a/src/lib/eap_aka_sim/decode.c b/src/lib/eap_aka_sim/decode.c index 55c8b5dfe67..d2a618d1ca0 100644 --- a/src/lib/eap_aka_sim/decode.c +++ b/src/lib/eap_aka_sim/decode.c @@ -116,7 +116,7 @@ static ssize_t sim_value_decrypt(TALLOC_CTX *ctx, uint8_t **out, uint8_t const *data, size_t const attr_len, size_t const data_len, void *decode_ctx) { - fr_aka_sim_decode_ctx_t *packet_ctx = decode_ctx; + fr_aka_sim_ctx_t *packet_ctx = decode_ctx; EVP_CIPHER_CTX *evp_ctx; EVP_CIPHER const *evp_cipher = EVP_aes_128_cbc(); size_t block_size = EVP_CIPHER_block_size(evp_cipher); @@ -532,7 +532,7 @@ static ssize_t sim_decode_pair_value(TALLOC_CTX *ctx, fr_dcursor_t *cursor, fr_d size_t prefix = 0; fr_dict_attr_t *unknown; - fr_aka_sim_decode_ctx_t *packet_ctx = decode_ctx; + fr_aka_sim_ctx_t *packet_ctx = decode_ctx; if (!fr_cond_assert(attr_len <= data_len)) return -1; if (!fr_cond_assert(parent)) return -1; @@ -960,7 +960,7 @@ ssize_t fr_aka_sim_decode_pair(TALLOC_CTX *ctx, fr_dcursor_t *cursor, fr_dict_t * - -1 on failure. */ int fr_aka_sim_decode(request_t *request, fr_dcursor_t *decoded, fr_dict_t const *dict, - uint8_t const *data, size_t data_len, fr_aka_sim_decode_ctx_t *decode_ctx) + uint8_t const *data, size_t data_len, fr_aka_sim_ctx_t *decode_ctx) { ssize_t ret; uint8_t const *p = data; @@ -1025,23 +1025,21 @@ int fr_aka_sim_decode(request_t *request, fr_dcursor_t *decoded, fr_dict_t const return 0; } -static int _test_ctx_free(UNUSED fr_aka_sim_decode_ctx_t *ctx) +static int _test_ctx_free(UNUSED fr_aka_sim_ctx_t *ctx) { fr_aka_sim_free(); return 0; } -static fr_aka_sim_decode_ctx_t *test_ctx_init(TALLOC_CTX *ctx, uint8_t const *k_encr, size_t k_encr_len) +static fr_aka_sim_ctx_t *test_ctx_init(TALLOC_CTX *ctx, uint8_t const *k_encr, size_t k_encr_len) { - fr_aka_sim_decode_ctx_t *test_ctx; - fr_aka_sim_keys_t *keys; + fr_aka_sim_ctx_t *test_ctx; if (fr_aka_sim_init() < 0) return NULL; - test_ctx = talloc_zero(ctx, fr_aka_sim_decode_ctx_t); - test_ctx->keys = keys = talloc_zero(test_ctx, fr_aka_sim_keys_t); - memcpy(keys->k_encr, k_encr, k_encr_len); + test_ctx = talloc_zero(ctx, fr_aka_sim_ctx_t); + test_ctx->k_encr = talloc_memdup(test_ctx, k_encr, k_encr_len); talloc_set_destructor(test_ctx, _test_ctx_free); return test_ctx; @@ -1051,7 +1049,7 @@ static fr_aka_sim_decode_ctx_t *test_ctx_init(TALLOC_CTX *ctx, uint8_t const *k_ */ static int decode_test_ctx_sim(void **out, TALLOC_CTX *ctx) { - fr_aka_sim_decode_ctx_t *test_ctx; + fr_aka_sim_ctx_t *test_ctx; static uint8_t k_encr[] = { 0x00, 0x01, 0x02, 0x03, 0x04 ,0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; @@ -1068,7 +1066,7 @@ static int decode_test_ctx_sim(void **out, TALLOC_CTX *ctx) static int decode_test_ctx_aka(void **out, TALLOC_CTX *ctx) { - fr_aka_sim_decode_ctx_t *test_ctx; + fr_aka_sim_ctx_t *test_ctx; static uint8_t k_encr[] = { 0x00, 0x01, 0x02, 0x03, 0x04 ,0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; @@ -1084,7 +1082,7 @@ static int decode_test_ctx_aka(void **out, TALLOC_CTX *ctx) static int decode_test_ctx_sim_rfc4186(void **out, TALLOC_CTX *ctx) { - fr_aka_sim_decode_ctx_t *test_ctx; + fr_aka_sim_ctx_t *test_ctx; static uint8_t k_encr[] = { 0x53, 0x6e, 0x5e, 0xbc, 0x44 ,0x65, 0x58, 0x2a, 0xa6, 0xa8, 0xec, 0x99, 0x86, 0xeb, 0xb6, 0x20 }; diff --git a/src/lib/eap_aka_sim/encode.c b/src/lib/eap_aka_sim/encode.c index 8953cffdb29..e7b68eb4246 100644 --- a/src/lib/eap_aka_sim/encode.c +++ b/src/lib/eap_aka_sim/encode.c @@ -67,10 +67,9 @@ static ssize_t encode_tlv_hdr(fr_dbuff_t *dbuff, * * @return true if the underlying fr_pair_t is EAP_AKA encodable, false otherwise */ -static bool is_eap_aka_encodable(void const *item, void const *uctx) +static bool is_eap_aka_encodable(void const *item, UNUSED void const *uctx) { fr_pair_t const *vp = item; - fr_aka_sim_encode_ctx_t const *packet_ctx = uctx; if (!vp) return false; if (vp->da->flags.internal) return false; @@ -79,7 +78,7 @@ static bool is_eap_aka_encodable(void const *item, void const *uctx) * and absence is 'false' */ if ((vp->da->type == FR_TYPE_BOOL) && (vp->vp_bool == false)) return false; - if (!fr_dict_attr_common_parent(packet_ctx->root, vp->da, true)) return false; + if (!fr_dict_attr_common_parent(fr_dict_root(dict_eap_aka_sim), vp->da, true)) return false; return true; } @@ -101,14 +100,14 @@ static bool is_eap_aka_encodable(void const *item, void const *uctx) */ static ssize_t encode_iv(fr_dbuff_t *dbuff, void *encode_ctx) { - fr_aka_sim_encode_ctx_t *packet_ctx = encode_ctx; + fr_aka_sim_ctx_t *packet_ctx = encode_ctx; uint32_t iv[4]; fr_dbuff_t work_dbuff = FR_DBUFF_NO_ADVANCE(dbuff); /* * One IV per packet */ - if (packet_ctx->iv_included) return 0; + if (packet_ctx->have_iv) return 0; /* * Generate IV @@ -125,7 +124,7 @@ static ssize_t encode_iv(fr_dbuff_t *dbuff, void *encode_ctx) FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), fr_dbuff_used(&work_dbuff), "Initialisation vector"); - packet_ctx->iv_included = true; + packet_ctx->have_iv = true; return fr_dbuff_set(dbuff, &work_dbuff); } @@ -156,10 +155,11 @@ static ssize_t encode_encrypted_value(fr_dbuff_t *dbuff, size_t total_len, pad_len, encr_len, len = 0; fr_dbuff_t work_dbuff = FR_DBUFF_NO_ADVANCE(dbuff); uint8_t *encr = NULL; - fr_aka_sim_encode_ctx_t *packet_ctx = encode_ctx; + fr_aka_sim_ctx_t *packet_ctx = encode_ctx; EVP_CIPHER_CTX *evp_ctx; EVP_CIPHER const *evp_cipher = EVP_aes_128_cbc(); size_t block_size = EVP_CIPHER_block_size(evp_cipher); + ssize_t slen; /* * Needs to be a multiple of 4 else we can't * pad with AT_PADDING correctly as its @@ -273,7 +273,7 @@ static ssize_t encode_value(fr_dbuff_t *dbuff, fr_dbuff_t work_dbuff = FR_DBUFF_NO_ADVANCE(dbuff); fr_pair_t const *vp = fr_dcursor_current(cursor); fr_dict_attr_t const *da = da_stack->da[depth]; - fr_aka_sim_encode_ctx_t *packet_ctx = encode_ctx; + fr_aka_sim_ctx_t *packet_ctx = encode_ctx; VP_VERIFY(vp); FR_PROTO_STACK_PRINT(da_stack, depth); @@ -322,7 +322,7 @@ static ssize_t encode_value(fr_dbuff_t *dbuff, return PAIR_ENCODE_FATAL_ERROR; } memcpy(packet_ctx->iv, vp->vp_octets, sizeof(packet_ctx->iv)); - packet_ctx->iv_included = true; + packet_ctx->have_iv = true; break; /* Encode IV */ /* @@ -662,7 +662,7 @@ static inline ssize_t encode_tlv_internal(fr_dbuff_t *dbuff, value_dbuff = FR_DBUFF_NO_ADVANCE(&work_dbuff); fr_dbuff_marker(&value_start, &value_dbuff); - for(;;) { + for (;;) { FR_PROTO_STACK_PRINT(da_stack, depth); /* @@ -679,19 +679,28 @@ static inline ssize_t encode_tlv_internal(fr_dbuff_t *dbuff, slen = encode_rfc_hdr(&work_dbuff, da_stack, depth + 1, cursor, encode_ctx); } - if (slen <= 0) return slen; + if (slen <= 0) { + DEBUG("OUT OF BUFFER"); + return slen; + } /* * If nothing updated the attribute, stop */ - if (!fr_dcursor_current(cursor) || (vp == fr_dcursor_current(cursor))) break; + if (!fr_dcursor_current(cursor) || (vp == fr_dcursor_current(cursor))) { + DEBUG("NO ADVNACE"); + break; + } /* * We can encode multiple sub TLVs, if after * rebuilding the TLV Stack, the attribute * at this depth is the same. */ - if ((da != da_stack->da[depth]) || (da_stack->depth < da->depth)) break; + if ((da != da_stack->da[depth]) || (da_stack->depth < da->depth)) { + DEBUG("WRONG DEPTH"); + break; + } vp = fr_dcursor_current(cursor); } @@ -780,7 +789,6 @@ ssize_t fr_aka_sim_encode_pair(fr_dbuff_t *dbuff, fr_dcursor_t *cursor, void *en fr_da_stack_t da_stack; fr_dict_attr_t const *da = NULL; - fr_aka_sim_encode_ctx_t *packet_ctx = encode_ctx; fr_dbuff_marker(&m, &work_dbuff); if (!cursor) return PAIR_ENCODE_FATAL_ERROR; @@ -809,7 +817,7 @@ ssize_t fr_aka_sim_encode_pair(fr_dbuff_t *dbuff, fr_dcursor_t *cursor, void *en /* * Fast path for the common case. */ - if ((vp->da->parent == packet_ctx->root) && (vp->vp_type != FR_TYPE_TLV)) { + if ((vp->da->parent == fr_dict_root(dict_eap_aka_sim)) && (vp->vp_type != FR_TYPE_TLV)) { da_stack.da[0] = vp->da; da_stack.da[1] = NULL; da_stack.depth = 1; @@ -868,7 +876,7 @@ ssize_t fr_aka_sim_encode(request_t *request, fr_pair_list_t *to_encode, void *e unsigned char subtype; fr_dcursor_t cursor; - fr_aka_sim_encode_ctx_t *packet_ctx = encode_ctx; + fr_aka_sim_ctx_t *packet_ctx = encode_ctx; eap_packet_t *eap_packet = packet_ctx->eap_packet; /* @@ -876,7 +884,7 @@ ssize_t fr_aka_sim_encode(request_t *request, fr_pair_list_t *to_encode, void *e * It might be too big for putting into an * EAP packet. */ - vp = fr_pair_find_by_child_num(to_encode, packet_ctx->root, FR_SUBTYPE); + vp = fr_pair_find_by_child_num(to_encode, fr_dict_root(dict_eap_aka_sim), FR_SUBTYPE); if (!vp) { REDEBUG("Missing subtype attribute"); return PAIR_ENCODE_FATAL_ERROR; @@ -892,7 +900,7 @@ ssize_t fr_aka_sim_encode(request_t *request, fr_pair_list_t *to_encode, void *e /* * Will we need to generate a HMAC? */ - if (fr_pair_find_by_child_num(to_encode, packet_ctx->root, FR_MAC)) do_hmac = true; + if (fr_pair_find_by_child_num(to_encode, fr_dict_root(dict_eap_aka_sim), FR_MAC)) do_hmac = true; /* * Fast path, we just need to encode a subtype @@ -948,9 +956,9 @@ ssize_t fr_aka_sim_encode(request_t *request, fr_pair_list_t *to_encode, void *e */ if (do_hmac) { slen = fr_aka_sim_crypto_sign_packet(fr_dbuff_current(&hmac), eap_packet, false, - packet_ctx->hmac_md, - packet_ctx->keys->k_aut, packet_ctx->keys->k_aut_len, - packet_ctx->hmac_extra, packet_ctx->hmac_extra_len); + packet_ctx->hmac_md, + packet_ctx->k_aut, packet_ctx->k_aut_len, + packet_ctx->hmac_extra, packet_ctx->hmac_extra_len); if (slen < 0) goto error; FR_PROTO_HEX_DUMP(fr_dbuff_current(&hmac) - 4, AKA_SIM_MAC_SIZE, "hmac attribute"); } @@ -974,21 +982,22 @@ ssize_t fr_aka_sim_encode(request_t *request, fr_pair_list_t *to_encode, void *e /* * Test ctx data */ -static int _test_ctx_free(UNUSED fr_aka_sim_encode_ctx_t *ctx) +static int _test_ctx_free(UNUSED fr_aka_sim_ctx_t *ctx) { fr_aka_sim_free(); return 0; } -static fr_aka_sim_encode_ctx_t *test_ctx_init(TALLOC_CTX *ctx, uint8_t const *k_encr, size_t k_encr_len) +static fr_aka_sim_ctx_t *test_ctx_init(TALLOC_CTX *ctx, uint8_t const *k_encr, size_t k_encr_len) { - fr_aka_sim_encode_ctx_t *test_ctx; - fr_aka_sim_keys_t *keys; + fr_aka_sim_ctx_t *test_ctx; + + test_ctx = talloc_zero(ctx, fr_aka_sim_ctx_t); + test_ctx->k_encr = talloc_memdup(test_ctx, k_encr, k_encr_len); + test_ctx->k_aut = talloc_zero_array(test_ctx, uint8_t, 16); + test_ctx->k_aut_len = 16; - test_ctx = talloc_zero(ctx, fr_aka_sim_encode_ctx_t); - test_ctx->keys = keys = talloc_zero(test_ctx, fr_aka_sim_keys_t); - memcpy(keys->k_encr, k_encr, k_encr_len); talloc_set_destructor(test_ctx, _test_ctx_free); if (fr_aka_sim_init() < 0) { @@ -1004,15 +1013,14 @@ static fr_aka_sim_encode_ctx_t *test_ctx_init(TALLOC_CTX *ctx, uint8_t const *k_ */ static int encode_test_ctx_sim(void **out, TALLOC_CTX *ctx) { - fr_aka_sim_encode_ctx_t *test_ctx; + fr_aka_sim_ctx_t *test_ctx; static uint8_t k_encr[] = { 0x00, 0x01, 0x02, 0x03, 0x04 ,0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; test_ctx = test_ctx_init(ctx, k_encr, sizeof(k_encr)); if (!test_ctx) return -1; - test_ctx->root = fr_dict_root(dict_eap_aka_sim); - test_ctx->iv_included = true; /* Ensures IV is all zeros */ + test_ctx->have_iv = true; /* Ensures IV is all zeros */ *out = test_ctx; @@ -1021,15 +1029,14 @@ static int encode_test_ctx_sim(void **out, TALLOC_CTX *ctx) static int encode_test_ctx_aka(void **out, TALLOC_CTX *ctx) { - fr_aka_sim_encode_ctx_t *test_ctx; + fr_aka_sim_ctx_t *test_ctx; static uint8_t k_encr[] = { 0x00, 0x01, 0x02, 0x03, 0x04 ,0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; test_ctx = test_ctx_init(ctx, k_encr, sizeof(k_encr)); if (!test_ctx) return -1; - test_ctx->root = fr_dict_root(dict_eap_aka_sim); - test_ctx->iv_included = true; /* Ensures IV is all zeros */ + test_ctx->have_iv = true; /* Ensures IV is all zeros */ *out = test_ctx; @@ -1038,15 +1045,13 @@ static int encode_test_ctx_aka(void **out, TALLOC_CTX *ctx) static int encode_test_ctx_sim_rfc4186(void **out, TALLOC_CTX *ctx) { - fr_aka_sim_encode_ctx_t *test_ctx; + fr_aka_sim_ctx_t *test_ctx; static uint8_t k_encr[] = { 0x53, 0x6e, 0x5e, 0xbc, 0x44 ,0x65, 0x58, 0x2a, 0xa6, 0xa8, 0xec, 0x99, 0x86, 0xeb, 0xb6, 0x20 }; test_ctx = test_ctx_init(ctx, k_encr, sizeof(k_encr)); if (!test_ctx) return -1; - test_ctx->root = fr_dict_root(dict_eap_aka_sim); - *out = test_ctx; return 0; diff --git a/src/lib/eap_aka_sim/module.c b/src/lib/eap_aka_sim/module.c new file mode 100644 index 00000000000..091e376414e --- /dev/null +++ b/src/lib/eap_aka_sim/module.c @@ -0,0 +1,337 @@ +/* + * 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 src/lib/eap_aka_sim/module.c + * @brief Common encode/decode functions for EAP subtype modules + * + * @author Arran Cudbard-Bell (a.cudbardb@freeradius.org) + * + * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org) + */ +RCSID("$Id$") + +#include +#include +#include +#include +#include +#include +#include + +#include "attrs.h" +#include "base.h" +#include "module.h" + +/** Encode EAP session data from attributes + * + */ +static unlang_action_t mod_encode(rlm_rcode_t *p_result, module_ctx_t const *mctx, + request_t *request, UNUSED void *rctx) +{ + eap_aka_sim_module_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_module_conf_t); + eap_session_t *eap_session = eap_session_get(request->parent); + eap_aka_sim_mod_session_t *mod_session = talloc_get_type_abort(eap_session->opaque, + eap_aka_sim_mod_session_t); + fr_pair_t *subtype_vp; + + static eap_code_t rcode_to_eap_code[RLM_MODULE_NUMCODES] = { + [RLM_MODULE_REJECT] = FR_EAP_CODE_FAILURE, + [RLM_MODULE_FAIL] = FR_EAP_CODE_FAILURE, + [RLM_MODULE_OK] = FR_EAP_CODE_SUCCESS, + [RLM_MODULE_HANDLED] = FR_EAP_CODE_REQUEST, + [RLM_MODULE_INVALID] = FR_EAP_CODE_FAILURE, + [RLM_MODULE_DISALLOW] = FR_EAP_CODE_FAILURE, + [RLM_MODULE_NOTFOUND] = FR_EAP_CODE_FAILURE, + [RLM_MODULE_NOOP] = FR_EAP_CODE_FAILURE, + [RLM_MODULE_UPDATED] = FR_EAP_CODE_FAILURE + }; + eap_code_t code; + rlm_rcode_t rcode = unlang_interpret_stack_result(request); + fr_aka_sim_ctx_t encode_ctx; + uint8_t const *request_hmac_extra = NULL; + size_t request_hmac_extra_len = 0; + int ret; + + /* + * If there's no subtype vp, we look at the rcode + * from the virtual server to determine what kind + * of EAP response to send. + */ + subtype_vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_subtype); + if (!subtype_vp) { + eap_session->this_round->request->code = (rcode == RLM_MODULE_OK) ? + FR_EAP_CODE_SUCCESS : FR_EAP_CODE_FAILURE; + /* + * RFC 3748 requires the request and response + * IDs to be identical for EAP-SUCCESS and + * EAP-FAILURE. + * + * The EAP common code will do the right thing + * here if we just tell it we haven't se the + * request ID. + */ + eap_session->this_round->set_request_id = false; + eap_session->finished = true; + TALLOC_FREE(eap_session->opaque); + + return UNLANG_ACTION_CALCULATE_RESULT; + } + + /* + * If there is a subtype vp, verify the return + * code allows us send EAP-SIM/AKA/AKA' data back. + */ + code = rcode_to_eap_code[rcode]; + if (code != FR_EAP_CODE_REQUEST) { + eap_session->this_round->request->code = code; + eap_session->this_round->set_request_id = false; + eap_session->finished = true; + TALLOC_FREE(eap_session->opaque); + + return UNLANG_ACTION_CALCULATE_RESULT; + } + + RDEBUG2("Encoding attributes"); + log_request_pair_list(L_DBG_LVL_2, request, NULL, &request->reply_pairs, NULL); + + /* + * It's not an EAP-Success or an EAP-Failure + * it's a real EAP-SIM/AKA/AKA' response. + */ + eap_session->this_round->request->type.num = inst->type; + eap_session->this_round->request->code = code; + eap_session->this_round->set_request_id = true; + + /* + * RFC 3748 says this ID need only be different to + * the previous ID. + * + * We need to set the type, code, id here as the + * HMAC operates on the complete packet we're + * returning including the EAP headers, so the packet + * fields must be filled in before we call encode. + */ + eap_session->this_round->response->id++; + + /* + * Perform different actions depending on the type + * of request we're sending. + */ + switch (subtype_vp->vp_uint16) { + case FR_SUBTYPE_VALUE_SIM_START: + case FR_SUBTYPE_VALUE_AKA_IDENTITY: + { + fr_pair_t *id_vp; + + if (RDEBUG_ENABLED2) break; + + /* + * Figure out if the state machine is + * requesting an ID. + */ + if ((id_vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_any_id_req)) || + (id_vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_fullauth_id_req)) || + (id_vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_permanent_id_req))) { + RDEBUG2("Sending EAP-Request/%pV (%s)", &subtype_vp->data, id_vp->da->name); + } else { + RDEBUG2("Sending EAP-Request/%pV", &subtype_vp->data); + } + } + break; + + /* + * Deal with sending bidding VP + * + * This can either come from policy or be set by the default + * virtual server. + * + * We send AT_BIDDING in our EAP-Request/AKA-Challenge message + * to tell the supplicant that if it has AKA' available/enabled + * it should have used that. + */ + case FR_SUBTYPE_VALUE_AKA_CHALLENGE: + { + fr_pair_t *bidding_vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_bidding); + + /* + * Explicit NO + */ + if (inst->aka.send_at_bidding_prefer_prime_is_set && + !inst->aka.send_at_bidding_prefer_prime) { + if (bidding_vp) pair_delete_reply(attr_eap_aka_sim_bidding); + /* + * Implicit or explicit YES + */ + } else if (inst->aka.send_at_bidding_prefer_prime) { + MEM(pair_append_reply(&bidding_vp, attr_eap_aka_sim_bidding) >= 0); + bidding_vp->vp_uint16 = FR_BIDDING_VALUE_PREFER_AKA_PRIME; + } + } + FALL_THROUGH; + + case FR_SUBTYPE_VALUE_SIM_CHALLENGE: + case FR_SUBTYPE_VALUE_AKA_SIM_REAUTHENTICATION: + { + fr_pair_t *vp; + + /* + * Extra data to append to the packet when signing. + */ + vp = fr_pair_find_by_da(&request->control_pairs, attr_eap_aka_sim_hmac_extra_request); + if (vp) { + request_hmac_extra = vp->vp_octets; + request_hmac_extra_len = vp->vp_length; + } + + /* + * Extra data to append to the response packet when + * validating the signature. + */ + vp = fr_pair_find_by_da(&request->control_pairs, attr_eap_aka_sim_hmac_extra_response); + if (vp) { + fr_assert(!mod_session->response_hmac_extra); + MEM(mod_session->response_hmac_extra = talloc_memdup(mod_session, + vp->vp_octets, vp->vp_length)); + mod_session->response_hmac_extra_len = vp->vp_length; + } + /* + * Key we use for encrypting and decrypting attributes. + */ + vp = fr_pair_find_by_da(&request->control_pairs, attr_eap_aka_sim_k_encr); + if (vp) { + fr_assert(!mod_session->ctx.k_encr); + MEM(mod_session->ctx.k_encr = talloc_memdup(mod_session, vp->vp_octets, vp->vp_length)); + } + + /* + * Key we use for signing and validating mac values. + */ + vp = fr_pair_find_by_da(&request->control_pairs, attr_eap_aka_sim_k_aut); + if (vp) { + fr_assert(!mod_session->ctx.k_aut); + MEM(mod_session->ctx.k_aut = talloc_memdup(mod_session, vp->vp_octets, vp->vp_length)); + mod_session->ctx.k_aut_len = vp->vp_length; + } + + fr_assert(mod_session->ctx.k_encr && mod_session->ctx.k_aut); + } + FALL_THROUGH; + + default: + RDEBUG2("Sending EAP-Request/%pV", &subtype_vp->data); + break; + } + + encode_ctx = mod_session->ctx; + encode_ctx.eap_packet = eap_session->this_round->request; + encode_ctx.hmac_extra = request_hmac_extra; + encode_ctx.hmac_extra_len = request_hmac_extra_len; + + ret = fr_aka_sim_encode(request, &request->reply_pairs, &encode_ctx); + if (ret <= 0) { /* No valid packets have length 0 */ + RPEDEBUG("Failed encoding response"); + RETURN_MODULE_FAIL; + } + + return UNLANG_ACTION_CALCULATE_RESULT; /* rcode is already correct */ +} + +/** Decode EAP session data into attribute + * + */ +unlang_action_t eap_aka_sim_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request) +{ + eap_aka_sim_module_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_module_conf_t); + eap_session_t *eap_session = eap_session_get(request->parent); + eap_aka_sim_mod_session_t *mod_session = talloc_get_type_abort(eap_session->opaque, + eap_aka_sim_mod_session_t); + fr_pair_t *subtype_vp; + fr_dcursor_t cursor; + int ret; + fr_aka_sim_ctx_t decode_ctx; + + switch (eap_session->this_round->response->type.num) { + case FR_EAP_METHOD_IDENTITY: + break; + + /* + * Only decode data for EAP-SIM/AKA/AKA' responses + */ + default: + fr_dcursor_init(&cursor, &request->request_pairs); + + decode_ctx = mod_session->ctx; + decode_ctx.hmac_extra = mod_session->response_hmac_extra; + decode_ctx.hmac_extra_len = mod_session->response_hmac_extra_len; + decode_ctx.eap_packet = eap_session->this_round->response; + + ret = fr_aka_sim_decode(request, + &cursor, + dict_eap_aka_sim, + eap_session->this_round->response->type.data, + eap_session->this_round->response->type.length, + &decode_ctx); + + /* + * Only good for one response packet + */ + TALLOC_FREE(mod_session->response_hmac_extra); + mod_session->response_hmac_extra_len = 0; + + /* + * RFC 4187 says we *MUST* notify, not just send + * an EAP-Failure in this case where we cannot + * decode an EAP-AKA packet. + * + * We instead call the state machine and allow it + * to fail when it can't find the necessary + * attributes. + */ + if (ret < 0) { + RPEDEBUG2("Failed decoding attributes"); + goto done; + } + + if (!fr_pair_list_empty(&request->request_pairs) && RDEBUG_ENABLED2) { + RDEBUG2("Decoded attributes"); + log_request_pair_list(L_DBG_LVL_2, request, NULL, &request->request_pairs, NULL); + } + + subtype_vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_subtype); + if (subtype_vp) { + RDEBUG2("Received EAP-Response/%pV", &(subtype_vp)->data); + } else { + REDEBUG2("Missing Sub-Type"); + } + break; + } + +done: + /* + * Setup our encode function as the resumption + * frame when the state machine finishes with + * this round. + */ + (void)unlang_module_yield(request, mod_encode, NULL, NULL); + + if (virtual_server_push(request, inst->virtual_server, UNLANG_SUB_FRAME) < 0) { + unlang_interpet_frame_discard(request); + RETURN_MODULE_FAIL; + } + + return UNLANG_ACTION_PUSHED_CHILD; +} diff --git a/src/lib/eap_aka_sim/module.h b/src/lib/eap_aka_sim/module.h new file mode 100644 index 00000000000..0e990614309 --- /dev/null +++ b/src/lib/eap_aka_sim/module.h @@ -0,0 +1,83 @@ +/* + * 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 + */ + +/** + * $Id$ + * @file lib/eap_aka_sim/module.h + * @brief Declarations for the common module functions used by EAP-SIM/AKA submodules + * + * @author Arran Cudbard-Bell (a.cudbardb@freeradius.org) + * + * @copyright 2016-2019 The FreeRADIUS server project + * @copyright 2016-2019 Network RADIUS SARL + */ +RCSIDH(lib_eap_aka_sim_module_h, "$Id$") + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + CONF_SECTION *virtual_server; //!< Virtual server. + + /** Whether we should include a bid-down prevention attribute by default + * + * Only used for EAP-AKA, as a signal that EAP-AKA' was available/enabled + * on the server, and if the client supports EAP-AKA', it should continue + * with that EAP-Method instead. + */ + struct { + bool send_at_bidding_prefer_prime; //!< Include the AT bidding attribute in + ///< challenge requests. + bool send_at_bidding_prefer_prime_is_set; //!< Whether the user specified + ///< a value. + } aka; + + eap_type_t type; +} eap_aka_sim_module_conf_t; + +/** Structure used to track session state at the module level + * + * The process module has a similar structure (eap_aka_sim_module_t) which tracks + * all of the cryptographic parameters for the session. + * + * The structure here stores copies of the cryptographic parameters used for + * validating incoming packets, and signing outgoing packets, from control attributes + * provided by the state machine. + * + * This separation is to allow the process module to be executed without the + * submodule, so that the state machine can be tested independently of the + * encode/decode/crypto code. + */ +typedef struct { + uint8_t id; //!< Last ID used, monotonically increments. + + uint8_t *response_hmac_extra; //!< Data to concatenate to response packet + ///< before validating. + size_t response_hmac_extra_len; + + fr_aka_sim_ctx_t ctx; +} eap_aka_sim_mod_session_t; + +unlang_action_t eap_aka_sim_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request); + +#ifdef __cplusplus +} +#endif diff --git a/src/lib/eap_aka_sim/state_machine.c b/src/lib/eap_aka_sim/state_machine.c index 9932a2b0387..b816d0d557c 100644 --- a/src/lib/eap_aka_sim/state_machine.c +++ b/src/lib/eap_aka_sim/state_machine.c @@ -18,15 +18,18 @@ * @file src/lib/eap_aka_sim/state_machine.c * @brief Implement a common state machine for EAP-SIM, EAP-AKA, EAP-AKA'. * - * @author Arran Cudbard-Bell \ + * @author Arran Cudbard-Bell (a.cudbardb@freeradius.org) * + * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org) * @copyright 2019 The FreeRADIUS server project - * @copyright 2019 Network RADIUS \ + * @copyright 2019 Network RADIUS (legal@networkradius.com) */ RCSID("$Id$") #include #include +#include #include +#include #include "base.h" #include "state_machine.h" @@ -36,54 +39,75 @@ RCSID("$Id$") # define EAP_TLS_MPPE_KEY_LEN 32 #endif -/** A state transition function - * - * This is passed to sub-state machines that perform other actions, before - * fully transitioning to a new state. - * - * Examples of these are the sub-state machines that deal with clearing - * pseudonyms and reauthentication data. - */ -typedef unlang_action_t(*aka_sim_state_enter_t)(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session); - -static unlang_action_t common_eap_failure(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request); -static unlang_action_t common_failure_notification(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request); -static unlang_action_t common_eap_success(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request); -static unlang_action_t common_success_notification(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request); -static unlang_action_t common_reauthentication(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request); -static unlang_action_t aka_challenge(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request); -static unlang_action_t sim_challenge(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request); -static unlang_action_t aka_identity(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request); -static unlang_action_t sim_start(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request); - -static unlang_action_t common_failure_notification_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session); -static unlang_action_t aka_challenge_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session); -static unlang_action_t sim_challenge_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session); -static unlang_action_t common_challenge_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session); -static unlang_action_t aka_identity_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request, eap_session_t *eap_session); -static unlang_action_t sim_start_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request, eap_session_t *eap_session); -static unlang_action_t common_identity_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session); - -static module_state_func_table_t const aka_sim_stable_table[] = { - { "FAILURE-NOTIFICATION", common_failure_notification }, - { "EAP-FAILURE", common_eap_failure }, - { "SUCCESS-NOTIFICATION", common_success_notification }, - { "EAP-SUCCESS", common_eap_success }, - { "REAUTHENTICATION", common_reauthentication }, - { "AKA-CHALLENGE", aka_challenge }, - { "SIM-CHALLENGE", sim_challenge }, - { "IDENTITY", aka_identity }, - { "SIM-START", sim_start }, - { "EAP-IDENTITY", aka_sim_state_machine_start }, - - { NULL } +#define STATE(_x) static inline unlang_action_t state_ ## _x(rlm_rcode_t *p_result, \ + module_ctx_t const *mctx, \ + request_t *request,\ + eap_aka_sim_session_t *eap_aka_sim_session) +#define STATE_GUARD(_x) static unlang_action_t guard_ ## _x(rlm_rcode_t *p_result, \ + module_ctx_t const *mctx, \ + request_t *request, \ + eap_aka_sim_session_t *eap_aka_sim_session) + +#define RESUME(_x) static inline unlang_action_t resume_ ## _x(rlm_rcode_t *p_result, \ + module_ctx_t const *mctx, \ + request_t *request, \ + void *rctx) + +#define STATE_TRANSITION(_x) guard_ ## _x(p_result, mctx, request, eap_aka_sim_session); + +#define CALL_SECTION(_x) unlang_module_yield_to_section(p_result, \ + request, \ + inst->actions._x, \ + RLM_MODULE_NOOP, \ + resume_ ## _x, \ + mod_signal, \ + eap_aka_sim_session) + +/* + * Declare all state and guard functions to + * avoid ordering issues. + */ +STATE(eap_failure); +STATE_GUARD(eap_failure); +STATE(common_failure_notification); +STATE_GUARD(common_failure_notification); +STATE(eap_success); +STATE_GUARD(eap_success); +STATE(common_success_notification); +STATE_GUARD(common_success_notification); +STATE(common_reauthentication); +STATE_GUARD(common_reauthentication); +STATE(aka_challenge); +STATE_GUARD(aka_challenge); +STATE(sim_challenge); +STATE_GUARD(sim_challenge); +STATE_GUARD(common_challenge); +STATE(aka_identity); +STATE_GUARD(aka_identity); +STATE(sim_start); +STATE_GUARD(sim_start); +STATE_GUARD(common_identity); +STATE(init); + +static fr_table_ptr_ordered_t const aka_sim_state_table[] = { + { L("INIT"), NULL }, + + { L("EAP-IDENTITY"), (void *)state_init }, + { L("SIM-START"), (void *)state_sim_start }, + { L("AKA-IDENTITY"), (void *)state_aka_identity }, + + { L("SIM-CHALLENGE"), (void *)state_sim_challenge }, + { L("AKA-CHALLENGE"), (void *)state_aka_challenge }, + + { L("SUCCESS-NOTIFICATION"), (void *)state_common_success_notification }, + { L("FAILURE-NOTIFICATION"), (void *)state_common_failure_notification }, + + { L("REAUTHENTICATION"), (void *)state_common_reauthentication }, + + { L("EAP-SUCCESS"), (void *)state_eap_success }, + { L("EAP-FAILURE"), (void *)state_eap_failure } }; +static size_t aka_sim_state_table_len = NUM_ELEMENTS(aka_sim_state_table); /** Cancel a call to a submodule * @@ -92,65 +116,113 @@ static module_state_func_table_t const aka_sim_stable_table[] = { * @param[in] rctx the eap_session_t * @param[in] action to perform. */ -static void mod_signal(UNUSED module_ctx_t const *mctx, request_t *request, UNUSED void *rctx, +static void mod_signal(UNUSED module_ctx_t const *mctx, request_t *request, void *rctx, fr_state_signal_t action) { - eap_session_t *eap_session = eap_session_get(request->parent); - if (action != FR_SIGNAL_CANCEL) return; RDEBUG2("Request cancelled - Destroying session"); - TALLOC_FREE(eap_session->opaque); + /* + * Remove data from the request to + * avoid double free. + */ + if (!fr_cond_assert(request_data_get(request, (void *)eap_aka_sim_state_machine_process, 0) == rctx)) return; + + TALLOC_FREE(rctx); } /** Warn the user that the rcode they provided is being ignored in this section * */ -static inline void section_rcode_ignored(request_t *request) -{ - switch (request->rcode) { - case RLM_MODULE_USER_SECTION_REJECT: - RWDEBUG("Ignoring rcode (%s)", - fr_table_str_by_value(rcode_table, request->rcode, "")); - break; - - default: - break; - } -} +#define SECTION_RCODE_IGNORED \ +do { \ + switch (request->rcode) { \ + case RLM_MODULE_USER_SECTION_REJECT: \ + RWDEBUG("Ignoring rcode (%s)", \ + fr_table_str_by_value(rcode_table, request->rcode, "")); \ + break; \ + default: \ + break; \ + } \ +} while(0) /** Trigger a state transition to FAILURE-NOTIFICATION if the section returned a failure code * */ -#define section_rcode_process(_p_result, _inst, _request, _eap_session, _eap_aka_sim_session) \ -{ \ - if (after_authentication(_eap_aka_sim_session)) { \ - switch ((_request)->rcode) { \ +#define SECTION_RCODE_PROCESS \ +do { \ + if (after_authentication(eap_aka_sim_session)) { \ + switch (unlang_interpret_stack_result(request)) { \ case RLM_MODULE_REJECT: \ case RLM_MODULE_DISALLOW: \ eap_aka_sim_session->failure_type = FR_NOTIFICATION_VALUE_TEMPORARILY_DENIED; \ - return common_failure_notification_enter(_p_result, _inst, _request, _eap_session); \ + return STATE_TRANSITION(common_failure_notification); \ case RLM_MODULE_NOTFOUND: \ eap_aka_sim_session->failure_type = FR_NOTIFICATION_VALUE_NOT_SUBSCRIBED; \ - return common_failure_notification_enter(_p_result, _inst, _request, _eap_session); \ + return STATE_TRANSITION(common_failure_notification); \ case RLM_MODULE_INVALID: \ case RLM_MODULE_FAIL: \ eap_aka_sim_session->failure_type = FR_NOTIFICATION_VALUE_GENERAL_FAILURE_AFTER_AUTHENTICATION;\ - return common_failure_notification_enter(_p_result, _inst, _request, _eap_session); \ + return STATE_TRANSITION(common_failure_notification); \ default: \ break; \ } \ } else { \ - switch ((_request)->rcode) { \ + switch (unlang_interpret_stack_result(request)) { \ case RLM_MODULE_USER_SECTION_REJECT: \ REDEBUG("Section rcode (%s) indicates we should reject the user", \ fr_table_str_by_value(rcode_table, request->rcode, "")); \ - return common_failure_notification_enter(_p_result, _inst, _request, _eap_session); \ + return STATE_TRANSITION(common_failure_notification); \ default: \ break; \ } \ } \ +} while(0) + +/** Print debugging information, and write new state to eap_aka_sim_session->state + * + */ +static inline CC_HINT(always_inline) void state_set(request_t *request, + eap_aka_sim_session_t *eap_aka_sim_session, + eap_aka_sim_state_t new_state) +{ + eap_aka_sim_state_t old_state = eap_aka_sim_session->state; + + if (new_state != old_state) { + RDEBUG2("Changed state %s -> %s", + fr_table_str_by_value(aka_sim_state_table, (void *)old_state, ""), + fr_table_str_by_value(aka_sim_state_table, (void *)new_state, "")); + } else { + RDEBUG2("Reentering state %s", + fr_table_str_by_value(aka_sim_state_table, (void *)old_state, "")); + } + + eap_aka_sim_session->state = new_state; +} +#define STATE_SET(_new_state) state_set(request, eap_aka_sim_session, state_ ## _new_state) + +/** Determine if we're after authentication + * + */ +static inline CC_HINT(always_inline) bool after_authentication(eap_aka_sim_session_t *eap_aka_sim_session) +{ + return eap_aka_sim_session->challenge_success || eap_aka_sim_session->reauthentication_success; +} + +/** Print out the error the client returned + * + */ +static inline CC_HINT(always_inline) void client_error_debug(request_t *request) +{ + fr_pair_t *vp; + + vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_client_error_code); + if (!vp) { + REDEBUG("Peer has not supplied a AT_ERROR_CODE"); + } else { + REDEBUG("Peer rejected request with error: %i (%pV)", vp->vp_uint16, &vp->data); + } } /** Sync up what identity we're requesting with attributes in the reply @@ -267,21 +339,6 @@ static void identity_hint_pairs_add(fr_aka_sim_id_type_t *type_p, fr_aka_sim_met if (method_p) *method_p = method; } -/** Print out the error the client returned - * - */ -static inline void client_error_debug(request_t *request, fr_pair_list_t *from_peer) -{ - fr_pair_t *vp; - - vp = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_client_error_code); - if (!vp) { - REDEBUG("Peer has not supplied a AT_ERROR_CODE"); - } else { - REDEBUG("Peer rejected request with error: %i (%pV)", vp->vp_uint16, &vp->data); - } -} - /** Add an Identity Request attribute to the reply * * Verify the progression of identity requests is valid. @@ -447,8 +504,8 @@ static int identity_to_permanent_identity(request_t *request, fr_pair_t *in, eap /** Set the crypto identity from a received identity * */ -static void identity_to_crypto_identity(request_t *request, eap_aka_sim_session_t *eap_aka_sim_session, - uint8_t const *identity, size_t len) +static void crypto_identity_set(request_t *request, eap_aka_sim_session_t *eap_aka_sim_session, + uint8_t const *identity, size_t len) { RDEBUG3("Setting cryptographic identity to \"%pV\"", fr_box_strvalue_len((char const *)identity, len)); @@ -458,21 +515,12 @@ static void identity_to_crypto_identity(request_t *request, eap_aka_sim_session_ } -/** Determine if we're after authentication - * - */ -static inline bool after_authentication(eap_aka_sim_session_t *eap_aka_sim_session) -{ - return eap_aka_sim_session->challenge_success || eap_aka_sim_session->reauthentication_success; -} - /** Resume after 'store session { ... }' * */ -static unlang_action_t session_store_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, void *rctx) +RESUME(store_session) { - aka_sim_state_enter_t state_enter = (aka_sim_state_enter_t)rctx; + eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t); switch (request->rcode) { /* @@ -488,20 +536,17 @@ static unlang_action_t session_store_resume(rlm_rcode_t *p_result, module_ctx_t pair_delete_request(attr_eap_aka_sim_next_reauth_id); - return state_enter(p_result, mctx, request, eap_session_get(request->parent)); + return eap_aka_sim_session->next(p_result, mctx, request, eap_aka_sim_session); } /** Resume after 'store pseudonym { ... }' * + * Stores session data if required. */ -static unlang_action_t pseudonym_store_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, void *rctx) +RESUME(store_pseudonym) { - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t); - eap_session_t *eap_session = eap_session_get(request->parent); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - aka_sim_state_enter_t state_enter = (aka_sim_state_enter_t)rctx; + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); + eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t); fr_pair_t *vp; fr_pair_t *new; @@ -521,6 +566,11 @@ static unlang_action_t pseudonym_store_resume(rlm_rcode_t *p_result, module_ctx_ pair_delete_request(attr_eap_aka_sim_next_pseudonym); + /* + * Generate fast-reauth data if we + * find a next_reauth_id pair in the + * reply list. + */ vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_next_reauth_id); if (vp) { /* @@ -608,17 +658,14 @@ static unlang_action_t pseudonym_store_resume(rlm_rcode_t *p_result, module_ctx_ vp->vp_uint16 = 0; } - return unlang_module_yield_to_section(p_result, - request, - inst->actions.store_session, - RLM_MODULE_NOOP, - session_store_resume, - mod_signal, - rctx); + return CALL_SECTION(store_session); } + /* + * We didn't store any fast-reauth data + */ done: - return state_enter(p_result, mctx, request, eap_session_get(request->parent)); + return eap_aka_sim_session->next(p_result, mctx, request, eap_aka_sim_session); } /** Implements a set of states for storing pseudonym and fastauth identities @@ -628,23 +675,24 @@ done: * * Call the appropriate sections to persist those values. * - * @param[out] p_result Result of calling the module. - * @param[in] mctx Module calling ctx. - * @param[in] request the current request. - * @param[in] eap_session the EAP session - * @param[in] state_enter state entry function for the - * state to transition to *after* the current - * state. + * @param[out] p_result Result of calling the module. + * @param[in] mctx Module calling ctx. + * @param[in] request the current request. + * @param[in] eap_aka_sim_session the EAP session + * @param[in] next function to call after storing sessions and pseudonyms. */ static unlang_action_t session_and_pseudonym_store(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session, - aka_sim_state_enter_t state_enter) + request_t *request, + eap_aka_sim_session_t *eap_aka_sim_session, + eap_aka_sim_next_t next) { - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - fr_pair_t *vp; - fr_pair_t *new; + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); + fr_pair_t *vp; + fr_pair_t *new; + + if (!fr_cond_assert(next)) return STATE_TRANSITION(common_failure_notification); + + eap_aka_sim_session->next = next; request->rcode = RLM_MODULE_NOOP; /* Needed because we may call resume functions directly */ @@ -660,7 +708,7 @@ static unlang_action_t session_and_pseudonym_store(rlm_rcode_t *p_result, module RWDEBUG("Found empty Pseudonym-Id, and told not to generate one. " "Skipping store pseudonym { ... } section"); - return pseudonym_store_resume(p_result, mctx, request, (void *)state_enter); + return resume_store_pseudonym(p_result, mctx, request, eap_aka_sim_session); } MEM(identity = talloc_array(vp, char, inst->ephemeral_id_length + 2)); @@ -689,48 +737,36 @@ static unlang_action_t session_and_pseudonym_store(rlm_rcode_t *p_result, module MEM(eap_aka_sim_session->pseudonym_sent = talloc_bstrndup(eap_aka_sim_session, vp->vp_strvalue, vp->vp_length)); - - return unlang_module_yield_to_section(p_result, - request, - inst->actions.store_pseudonym, - RLM_MODULE_NOOP, - pseudonym_store_resume, - mod_signal, - (void *)state_enter); + return CALL_SECTION(store_pseudonym); } - return pseudonym_store_resume(p_result, mctx, request, (void *)state_enter); + return resume_store_pseudonym(p_result, mctx, request, eap_aka_sim_session); } /** Resume after 'clear session { ... }' * */ -static unlang_action_t session_clear_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, void *rctx) +RESUME(clear_session) { - aka_sim_state_enter_t state_enter = (aka_sim_state_enter_t)rctx; + eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t); pair_delete_request(attr_session_id); - return state_enter(p_result, mctx, request, eap_session_get(request->parent)); + return eap_aka_sim_session->next(p_result, mctx, request, eap_aka_sim_session); } /** Resume after 'clear pseudonym { ... }' * */ -static unlang_action_t pseudonym_clear_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, void *rctx) +RESUME(clear_pseudonym) { - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t); - eap_session_t *eap_session = eap_session_get(request->parent); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - aka_sim_state_enter_t state_enter = (aka_sim_state_enter_t)rctx; + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); + eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t); pair_delete_request(attr_eap_aka_sim_next_pseudonym); /* - * Clear session + * Clear session (if one was stored) */ if (eap_aka_sim_session->fastauth_sent) { fr_pair_t *vp; @@ -743,16 +779,10 @@ static unlang_action_t pseudonym_clear_resume(rlm_rcode_t *p_result, module_ctx_ talloc_array_length(eap_aka_sim_session->fastauth_sent) - 1, true); TALLOC_FREE(eap_aka_sim_session->fastauth_sent); - return unlang_module_yield_to_section(p_result, - request, - inst->actions.clear_session, - RLM_MODULE_NOOP, - session_clear_resume, - mod_signal, - rctx); + return CALL_SECTION(clear_session); } - return state_enter(p_result, mctx, request, eap_session_get(request->parent)); + return eap_aka_sim_session->next(p_result, mctx, request, eap_aka_sim_session); } /** Implements a set of states for clearing out pseudonym and fastauth identities @@ -761,24 +791,27 @@ static unlang_action_t pseudonym_clear_resume(rlm_rcode_t *p_result, module_ctx_ * any identities that were provided during those rounds, as the supplicant * will have discarded them. * - * @param[out] p_result Result of calling the module. - * @param[in] mctx module calling ctx. - * @param[in] request the current request. - * @param[in] eap_session the current EAP session - * @param[in] state_enter state entry function for the - * state to transition to *after* the current - * state. + * @param[out] p_result Result of calling the module. + * @param[in] mctx module calling ctx. + * @param[in] request the current request. + * @param[in] eap_aka_sim_session the current EAP session + * @param[in] next function to call after clearing sessions and pseudonyms. */ static unlang_action_t session_and_pseudonym_clear(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session, - aka_sim_state_enter_t state_enter) + request_t *request, + eap_aka_sim_session_t *eap_aka_sim_session, + eap_aka_sim_next_t next) { - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); + + if (!fr_cond_assert(next)) return STATE_TRANSITION(common_failure_notification); + + eap_aka_sim_session->next = next; + + request->rcode = RLM_MODULE_NOOP; /* Needed because we may call resume functions directly */ /* - * Clear out pseudonyms + * Clear out pseudonyms (if we sent one) */ if (eap_aka_sim_session->pseudonym_sent) { fr_pair_t *vp; @@ -787,756 +820,908 @@ static unlang_action_t session_and_pseudonym_clear(rlm_rcode_t *p_result, module fr_value_box_bstrdup_buffer(vp, &vp->data, NULL, eap_aka_sim_session->pseudonym_sent, true); TALLOC_FREE(eap_aka_sim_session->pseudonym_sent); - return unlang_module_yield_to_section(p_result, - request, - inst->actions.clear_pseudonym, - RLM_MODULE_NOOP, - session_clear_resume, - mod_signal, - (void *)state_enter); + return CALL_SECTION(clear_pseudonym); } - return pseudonym_clear_resume(p_result, mctx, request, (void *)state_enter); + return resume_clear_pseudonym(p_result, mctx, request, eap_aka_sim_session); } -/** Encode EAP-SIM/AKA['] attributes +/** Export EAP-SIM/AKA['] attributes * + * Makes any internal data available as attributes in the response. + * This allows test frameworks and the encoder to access any data they need without + * needing to look at the eap_aka_session_t. */ -static int common_encode(request_t *request, eap_session_t *eap_session, uint16_t subtype, - uint8_t const *hmac_extra, size_t hmac_extra_len) +static void common_reply(request_t *request, eap_aka_sim_session_t *eap_aka_sim_session, uint16_t subtype) { - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - fr_dcursor_t cursor; - fr_dcursor_t to_encode; - fr_pair_list_t head; - fr_pair_t *vp, *subtype_vp; - ssize_t ret; - fr_aka_sim_encode_ctx_t encode_ctx = { - .root = fr_dict_root(dict_eap_aka_sim), - .keys = &eap_aka_sim_session->keys, - - .iv = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, - .iv_included = false, - - .hmac_md = eap_aka_sim_session->mac_md, - .eap_packet = eap_session->this_round->request, - .hmac_extra = hmac_extra, - .hmac_extra_len = hmac_extra_len - }; - - fr_pair_list_init(&head); + fr_pair_t *vp = NULL, *subtype_vp; + /* * Set the subtype to identity request */ MEM(pair_update_reply(&subtype_vp, attr_eap_aka_sim_subtype) >= 0); subtype_vp->vp_uint16 = subtype; - /* - * State what kind of request we're sending - */ - if (RDEBUG_ENABLED2) { - switch (subtype) { - case FR_SUBTYPE_VALUE_SIM_START: - case FR_SUBTYPE_VALUE_AKA_IDENTITY: - RDEBUG2("Sending EAP-Request/%pV (%s)", &subtype_vp->data, - fr_table_str_by_value(fr_aka_sim_id_request_table, - eap_aka_sim_session->id_req, "")); - break; - - default: - RDEBUG2("Sending EAP-Request/%pV", &subtype_vp->data); - break; + if (!eap_aka_sim_session->allow_encrypted) { + while ((vp = fr_pair_list_next(&request->reply_pairs, vp))) { + if (fr_dict_attr_common_parent(attr_eap_aka_sim_encr_data, vp->da, true)) { + RWDEBUG("Silently discarding &reply.%pP: Encrypted attributes not " + "allowed in this round", vp); + vp = fr_pair_list_free_item(&request->reply_pairs, vp); + continue; + } } } +} - fr_dcursor_init(&cursor, &request->reply_pairs); - fr_dcursor_init(&to_encode, &head); +static void CC_HINT(nonnull(1,2)) +common_crypto_export(request_t *request, eap_aka_sim_session_t *eap_aka_sim_session, + uint8_t const *hmac_extra_request, size_t hmac_extra_request_len, + uint8_t const *hmac_extra_response, size_t hmac_extra_response_len) +{ + fr_pair_t *vp; /* - * We have to be *SUPER* careful here not to reorder - * attributes, because for EAP-SIM the RAND values - * must be inserted into the packet in exactly the - * same order as they appear in fr_sim_keys_t - * otherwise the KDF will produce a different - * result. + * Export keying material necessary for + * the encoder to encrypt and sign + * packets. */ - while ((vp = fr_dcursor_current(&cursor))) { - if (!fr_dict_attr_common_parent(encode_ctx.root, vp->da, true)) { - fr_dcursor_next(&cursor); - continue; - } - vp = fr_dcursor_remove(&cursor); - - /* - * Silently discard encrypted attributes until - * the peer should have k_encr. These can be - * added by policy, and seem to cause - * wpa_supplicant to fail if sent before the challenge. - */ - if (!eap_aka_sim_session->allow_encrypted && - fr_dict_attr_common_parent(attr_eap_aka_sim_encr_data, vp->da, true)) { - RWDEBUG("Silently discarding &reply.%s: Encrypted attributes not allowed in this round", - vp->da->name); - talloc_free(vp); - continue; - } - - fr_dcursor_prepend(&to_encode, vp); + if (hmac_extra_request && hmac_extra_request_len) { + MEM(pair_update_control(&vp, attr_eap_aka_sim_hmac_extra_request) >= 0); + MEM(fr_pair_value_memdup(vp, hmac_extra_request, hmac_extra_request_len, true) == 0); } + if (hmac_extra_response && hmac_extra_response_len) { + MEM(pair_update_control(&vp, attr_eap_aka_sim_hmac_extra_response) >= 0); + MEM(fr_pair_value_memdup(vp, hmac_extra_response, hmac_extra_response_len, true) == 0); + } - RDEBUG2("Encoding attributes"); - log_request_pair_list(L_DBG_LVL_2, request, NULL, &head, NULL); - - eap_session->this_round->request->type.num = eap_aka_sim_session->type; - eap_session->this_round->request->id = eap_aka_sim_session->id++ & 0xff; - eap_session->this_round->set_request_id = true; - - ret = fr_aka_sim_encode(request, &head, &encode_ctx); - fr_dcursor_head(&to_encode); - fr_dcursor_free_list(&to_encode); + MEM(pair_update_control(&vp, attr_eap_aka_sim_k_aut) >= 0); + MEM(fr_pair_value_memdup(vp, + eap_aka_sim_session->keys.k_aut, + eap_aka_sim_session->keys.k_aut_len, + true) == 0); - if (ret < 0) { - RPEDEBUG("Failed encoding attributes"); - return -1; - } - return 0; + MEM(pair_update_control(&vp, attr_eap_aka_sim_k_encr) >= 0); + MEM(fr_pair_value_memdup(vp, + eap_aka_sim_session->keys.k_encr, + sizeof(eap_aka_sim_session->keys.k_encr), + true) == 0); } -/** Send a EAP-Failure message to the supplicant +/** Called after 'store session { ... }' and 'store pseudonym { ... }' * */ -static unlang_action_t common_eap_failure_send(rlm_rcode_t *p_result, request_t *request, eap_session_t *eap_session) +static unlang_action_t common_reauthentication_request_send(rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx, + request_t *request, + eap_aka_sim_session_t *eap_aka_sim_session) { - RDEBUG2("Sending EAP-Failure"); + /* + * Return reply attributes - AT_IV is handled automatically by the encoder + */ + common_reply(request, eap_aka_sim_session, FR_SUBTYPE_VALUE_AKA_SIM_REAUTHENTICATION); - eap_session->this_round->request->code = FR_EAP_CODE_FAILURE; - eap_session->finished = true; + /* + * 9.5. EAP-Request/SIM/Re-authentication + * + * AT_MAC MUST be included. No message-specific data is included in the + * MAC calculation. See Section 10.14. + * + * 9.6. EAP-Response/SIM/Re-authentication + * + * The AT_MAC attribute MUST be included. For + * EAP-Response/SIM/Re-authentication, the MAC code is calculated over + * the following data: EAP packet| NONCE_S + * + * 9.7. EAP-Request/AKA-Reauthentication + * + * The AT_MAC attribute MUST be included. No message-specific data is + * included in the MAC calculation, see Section 10.15. + * + * 9.8. EAP-Response/AKA-Reauthentication + * + * The AT_MAC attribute MUST be included. For + * EAP-Response/AKA-Reauthentication, the MAC code is calculated over + * the following data: EAP packet| NONCE_S. + */ + common_crypto_export(request, eap_aka_sim_session, + NULL, 0, + eap_aka_sim_session->keys.reauth.nonce_s, + sizeof(eap_aka_sim_session->keys.reauth.nonce_s)); - RETURN_MODULE_REJECT; + RETURN_MODULE_HANDLED; } -/** Send a EAP-Request/(AKA|SIM)-Notification (Failure) message to the supplicant +/** Called after 'store session { ... }' and 'store pseudonym { ... }' * */ -static unlang_action_t common_failure_notification_send(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session) +static unlang_action_t aka_challenge_request_send(rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx, + request_t *request, eap_aka_sim_session_t *eap_aka_sim_session) { - fr_pair_t *vp, *notification_vp; - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - /* - * Allow the user to specify specific failure notification - * types. We assume the user knows what they're doing and - * only toggle success and phase bits. - * - * This allows custom notification schemes to be implemented. - * - * If this is prior to authentication, valid values are: - * - FR_NOTIFICATION_VALUE_GENERAL_FAILURE - * - * If this is after authentication, valid values are: - * - FR_NOTIFICATION_VALUE_GENERAL_FAILURE_AFTER_AUTHENTICATION - * - FR_NOTIFICATION_VALUE_TEMPORARILY_DENIED - User has been - * temporarily denied access to the requested service. - * - FR_NOTIFICATION_VALUE_NOT_SUBSCRIBED - * User has not subscribed to the requested service. + * Encode the packet - AT_IV is handled automatically + * by the encoder. */ - notification_vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_notification); + common_reply(request, eap_aka_sim_session, FR_SUBTYPE_VALUE_AKA_CHALLENGE); /* - * Change the failure notification depending where - * we are in the eap_aka_state machine. + * 9.3. EAP-Request/AKA-Challenge + * + * AT_MAC MUST be included. In EAP-Request/AKA-Challenge, there is no + * message-specific data covered by the MAC, see Section 10.15. + * + * 9.4. EAP-Response/AKA-Challenge + * + * The AT_MAC attribute MUST be included. In + * EAP-Response/AKA-Challenge, there is no message-specific data covered + * by the MAC, see Section 10.15. */ - if (after_authentication(eap_aka_sim_session)) { - if (!notification_vp) { - MEM(pair_append_reply(¬ification_vp, attr_eap_aka_sim_notification) >= 0); - notification_vp->vp_uint16 = eap_aka_sim_session->failure_type; /* Default will be zero */ - } + common_crypto_export(request, eap_aka_sim_session, NULL, 0, NULL, 0); - notification_vp->vp_uint16 &= ~0x4000; /* Unset phase bit */ + RETURN_MODULE_HANDLED; +} - /* - * Include the counter attribute if we're failing - * after a reauthentication success. - * - * RFC 4187 Section #9.10 - * - * If EAP-Request/AKA-Notification is used on - * a fast re-authentication exchange, and if - * the P bit in AT_NOTIFICATION is set to zero, - * then AT_COUNTER is used for replay protection. - * In this case, the AT_ENCR_DATA and AT_IV - * attributes MUST be included, and the - * encapsulated plaintext attributes MUST include - * the AT_COUNTER attribute. The counter value - * included in AT_COUNTER MUST be the same - * as in the EAP-Request/AKA-Reauthentication - * packet on the same fast re-authentication - * exchange. - * - * If the counter is used it should never be zero, - * as it's incremented on first reauthentication - * request. - */ - if (eap_aka_sim_session->reauthentication_success) { - MEM(pair_update_reply(¬ification_vp, attr_eap_aka_sim_counter) >= 0); - notification_vp->vp_uint16 = eap_aka_sim_session->keys.reauth.counter; - } +/** Called after 'store session { ... }' and 'store pseudonym { ... }' + * + */ +static unlang_action_t sim_challenge_request_send(rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx, + request_t *request, eap_aka_sim_session_t *eap_aka_sim_session) +{ + uint8_t sres_cat[AKA_SIM_VECTOR_GSM_SRES_SIZE * 3]; + uint8_t *p = sres_cat; - /* - * If we're after the challenge phase - * then we need to include a MAC to - * protect notifications. - */ - MEM(pair_update_reply(&vp, attr_eap_aka_sim_mac) >= 0); - fr_pair_value_memdup(vp, NULL, 0, false); - } else { - /* - * Only valid code is general failure - */ - if (!notification_vp) { - MEM(pair_append_reply(¬ification_vp, attr_eap_aka_sim_notification) >= 0); - notification_vp->vp_uint16 = FR_NOTIFICATION_VALUE_GENERAL_FAILURE; - /* - * User supplied failure code - */ - } else { - notification_vp->vp_uint16 |= 0x4000; /* Set phase bit */ - } - } - notification_vp->vp_uint16 &= ~0x8000; /* In both cases success bit should be low */ + /* + * Encode the packet - AT_IV is handled automatically + * by the encoder. + */ + common_reply(request, eap_aka_sim_session, FR_SUBTYPE_VALUE_SIM_CHALLENGE); - eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; + memcpy(p, eap_aka_sim_session->keys.gsm.vector[0].sres, AKA_SIM_VECTOR_GSM_SRES_SIZE); + p += AKA_SIM_VECTOR_GSM_SRES_SIZE; + memcpy(p, eap_aka_sim_session->keys.gsm.vector[1].sres, AKA_SIM_VECTOR_GSM_SRES_SIZE); + p += AKA_SIM_VECTOR_GSM_SRES_SIZE; + memcpy(p, eap_aka_sim_session->keys.gsm.vector[2].sres, AKA_SIM_VECTOR_GSM_SRES_SIZE); /* - * Encode the packet + * 9.3. EAP-Request/SIM/Challenge + * + * The AT_MAC attribute MUST be included. For + * EAP-Request/SIM/Challenge, the MAC code is calculated over the + * following data: EAP packet| NONCE_MT + * + * 9.4. EAP-Response/SIM/Challenge + * + * The AT_MAC attribute MUST be included. For EAP- + * Response/SIM/Challenge, the MAC code is calculated over the following + * data: EAP packet| n*SRES */ - if (common_encode(request, eap_session, FR_SUBTYPE_VALUE_AKA_SIM_NOTIFICATION, NULL, 0) < 0) { - return common_failure_notification_enter(p_result, mctx, request, eap_session); - } + common_crypto_export(request, eap_aka_sim_session, + eap_aka_sim_session->keys.gsm.nonce_mt, sizeof(eap_aka_sim_session->keys.gsm.nonce_mt), + sres_cat, sizeof(sres_cat)); RETURN_MODULE_HANDLED; } -/** Add MPPE keys to the request being sent to the supplicant +/** Helper function to check for the presence and length of AT_SELECTED_VERSION and copy its value into the keys structure * - * The only work to be done is the add the appropriate SEND/RECV - * attributes derived from the MSK. + * Also checks the version matches one of the ones we advertised in our version list, + * which is a bit redundant seeing as there's only one version of EAP-SIM. */ -static unlang_action_t common_eap_success_send(rlm_rcode_t *p_result, request_t *request, eap_session_t *eap_session) +static int sim_start_selected_version_check(request_t *request, eap_aka_sim_session_t *eap_aka_sim_session) { - uint8_t *p; - eap_aka_sim_session_t *eap_aka_sim_session; - - RDEBUG2("Sending EAP-Success"); - - eap_session->this_round->request->code = FR_EAP_CODE_SUCCESS; - eap_session->finished = true; + fr_pair_t *selected_version_vp; - eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, eap_aka_sim_session_t); + /* + * Check that we got an AT_SELECTED_VERSION + */ + selected_version_vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_selected_version); + if (!selected_version_vp) { + REDEBUG("EAP-Response/SIM/Start does not contain AT_SELECTED_VERSION"); + return -1; + } - RDEBUG2("Adding attributes for MSK"); - p = eap_aka_sim_session->keys.msk; - eap_add_reply(request->parent, attr_ms_mppe_recv_key, p, EAP_TLS_MPPE_KEY_LEN); - p += EAP_TLS_MPPE_KEY_LEN; - eap_add_reply(request->parent, attr_ms_mppe_send_key, p, EAP_TLS_MPPE_KEY_LEN); + /* + * See if the selected version was in our list + */ + { + uint8_t selected_version[2]; + uint8_t *p, *end; + bool found = false; - RETURN_MODULE_OK; + p = eap_aka_sim_session->keys.gsm.version_list; + end = p + eap_aka_sim_session->keys.gsm.version_list_len; + + selected_version[0] = (selected_version_vp->vp_uint16 & 0xff00) >> 8; + selected_version[1] = (selected_version_vp->vp_uint16 & 0x00ff); + + while (p < end) { + if ((p[0] == selected_version[0]) && (p[1] == selected_version[1])) { + found = true; + /* + * Update our keying material + */ + eap_aka_sim_session->keys.gsm.version_select[0] = selected_version[0]; + eap_aka_sim_session->keys.gsm.version_select[1] = selected_version[1]; + break; + } + } + + if (!found) { + REDEBUG("AT_SELECTED_VERSION (%u) does not match a value in our version list", + selected_version_vp->vp_uint16); + return -1; + } + } + + return 0; } -/** Send a EAP-Request/(AKA|SIM)-Notification (Success) message to the supplicant +/** Helper function to check for the presence and length of AT_NONCE_MT and copy its value into the keys structure * + * Does not actually perform cryptographic validation of AT_NONCE_MT, this is done later. */ -static unlang_action_t common_success_notification_send(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session) +static int sim_start_nonce_mt_check(request_t *request, eap_aka_sim_session_t *eap_aka_sim_session) { - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, eap_aka_sim_session_t); - fr_pair_t *vp; - - eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; - - if (!fr_cond_assert(after_authentication(eap_aka_sim_session))) { - RETURN_MODULE_FAIL; - } + fr_pair_t *nonce_mt_vp; /* - * If we're in this state success bit is - * high phase bit is low. + * Copy nonce_mt to the keying material */ - MEM(pair_update_reply(&vp, attr_eap_aka_sim_notification) >= 0); - vp->vp_uint16 = FR_NOTIFICATION_VALUE_SUCCESS; + nonce_mt_vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_nonce_mt); + if (!nonce_mt_vp) { + REDEBUG("EAP-Response/SIM/Start does not contain AT_NONCE_MT"); + return -1; + } - /* - * RFC 4187 section #9.10 - * - * If EAP-Request/AKA-Notification is used on - * a fast re-authentication exchange, and if - * the P bit in AT_NOTIFICATION is set to zero, - * then AT_COUNTER is used for replay protection. - * In this case, the AT_ENCR_DATA and AT_IV - * attributes MUST be included, and the - * encapsulated plaintext attributes MUST include - * the AT_COUNTER attribute. The counter value - * included in AT_COUNTER MUST be the same - * as in the EAP-Request/AKA-Reauthentication - * packet on the same fast re-authentication - * exchange. - * - * If the counter is used it should never be zero, - * as it's incremented on first reauthentication - * request. - */ - if (eap_aka_sim_session->keys.reauth.counter > 0) { - MEM(pair_update_reply(&vp, attr_eap_aka_sim_counter) >= 0); - vp->vp_uint16 = eap_aka_sim_session->keys.reauth.counter; + if (nonce_mt_vp->vp_length != sizeof(eap_aka_sim_session->keys.gsm.nonce_mt)) { + REDEBUG("AT_NONCE_MT must be exactly %zu bytes, not %zu bytes", + sizeof(eap_aka_sim_session->keys.gsm.nonce_mt), nonce_mt_vp->vp_length); + return -1; } + memcpy(eap_aka_sim_session->keys.gsm.nonce_mt, nonce_mt_vp->vp_octets, + sizeof(eap_aka_sim_session->keys.gsm.nonce_mt)); - /* - * Need to include an AT_MAC attribute so that - * it will get calculated. - */ - MEM(pair_update_reply(&vp, attr_eap_aka_sim_mac) >= 0); - fr_pair_value_memdup(vp, NULL, 0, false); + return 0; +} - /* - * Encode the packet - */ - if (common_encode(request, eap_session, FR_SUBTYPE_VALUE_AKA_SIM_NOTIFICATION, NULL, 0) < 0) { - return common_failure_notification_enter(p_result, mctx, request, eap_session); - } +/** FAILURE state - State machine exit point after sending EAP-Failure + * + * Should never actually be called. Is just a placeholder function to represent the FAILURE + * termination state. Could equally be a NULL pointer, but then on a logic error + * we'd get a SEGV instead of a more friendly assert/failure rcode. + */ +STATE(eap_failure) +{ + if (!fr_cond_assert(request && mctx && eap_aka_sim_session)) RETURN_MODULE_FAIL; /* unused args */ - RETURN_MODULE_HANDLED; + fr_assert(0); /* Should never actually be called */ + + RETURN_MODULE_FAIL; } -/** Called after 'store session { ... }' and 'store pseudonym { ... }' +/** Resume after 'send EAP-Failure { ... }' * */ -static unlang_action_t common_reauthentication_request_send(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session) +RESUME(send_eap_failure) { - /* - * Encode the packet - AT_IV is handled automatically - * by the encoder. - */ - if (common_encode(request, eap_session, FR_SUBTYPE_VALUE_AKA_SIM_REAUTHENTICATION, NULL, 0) < 0) { - return common_failure_notification_enter(p_result, mctx, request, eap_session); - } + if (!fr_cond_assert(mctx && rctx)) RETURN_MODULE_FAIL; /* unused args */ - RETURN_MODULE_HANDLED; + SECTION_RCODE_IGNORED; + + RDEBUG2("Sending EAP-Failure"); + + RETURN_MODULE_REJECT; } -/** Send a EAP-Request/(AKA|SIM)-Reauthenticate message to the supplicant +/** Enter EAP-FAILURE state * */ -static unlang_action_t common_reauthentication_request_compose(rlm_rcode_t *p_result, - module_ctx_t const *mctx, - request_t *request, - eap_session_t *eap_session) +STATE_GUARD(eap_failure) { - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - fr_pair_list_t *to_peer = &request->reply_pairs; - fr_pair_t *vp; + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); - fr_pair_t *kdf_id; + /* + * Free anything we were going to send out... + */ + fr_pair_list_free(&request->reply_pairs); /* - * Allow override of KDF Identity - * - * Because certain handset manufacturers don't - * implement RFC 4187 correctly and use the - * wrong identity as input the the PRF/KDF. - * - * Not seen any doing this for re-authentication - * but you never know... + * If we're failing, then any identities + * we sent are now invalid. */ - kdf_id = fr_pair_find_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity); - if (kdf_id) { - identity_to_crypto_identity(request, eap_aka_sim_session, - (uint8_t const *)kdf_id->vp_strvalue, kdf_id->vp_length); - fr_pair_delete_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity); + if (eap_aka_sim_session->pseudonym_sent || eap_aka_sim_session->fastauth_sent) { + return session_and_pseudonym_clear(p_result, mctx, + request, eap_aka_sim_session, guard_eap_failure); + /* come back when we're done */ } - RDEBUG2("Generating new session keys"); + STATE_SET(eap_failure); + + return CALL_SECTION(send_eap_failure); +} + +/** Resume after 'recv Failure-Notification-Ack { ... }' + * + * - Enter the EAP-FAILURE state. + */ +RESUME(recv_common_failure_notification_ack) +{ + eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t); + + SECTION_RCODE_IGNORED; - switch (eap_aka_sim_session->type) { /* - * The GSM and UMTS KDF_0 mutate their keys using - * and identical algorithm. + * Case 2 where we're allowed to send an EAP-Failure */ - case FR_EAP_METHOD_SIM: - case FR_EAP_METHOD_AKA: - if (fr_aka_sim_vector_gsm_umts_kdf_0_reauth_from_attrs(request, &request->session_state_pairs, - &eap_aka_sim_session->keys) != 0) { - request_new_id: - switch (eap_aka_sim_session->last_id_req) { - /* - * Got here processing EAP-Identity-Response - * If this is the *true* reauth ID, then - * there's no point in setting AKA_SIM_ANY_ID_REQ. - */ - case AKA_SIM_NO_ID_REQ: - case AKA_SIM_ANY_ID_REQ: - RDEBUG2("Composing EAP-Request/Reauthentication failed. Clearing reply attributes and " - "requesting additional Identity"); - fr_pair_list_free(&request->reply_pairs); - eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ; - return common_identity_enter(p_result, mctx, request, eap_session); + return STATE_TRANSITION(eap_failure); +} - case AKA_SIM_FULLAUTH_ID_REQ: - case AKA_SIM_PERMANENT_ID_REQ: - REDEBUG("Last requested fullauth or permanent ID, " - "but received, or were told we received (by policy), " - "a fastauth ID. Cannot continue"); - failure: - return common_failure_notification_enter(p_result, mctx, request, eap_session); - } - } - if (fr_aka_sim_crypto_kdf_0_reauth(&eap_aka_sim_session->keys) < 0) goto request_new_id; - break; +/** FAILURE-NOTIFICATION state - Continue the state machine after receiving a response to our EAP-Request/(AKA|SIM)-Notification + * + * - Continue based on received AT_SUBTYPE value: + * - EAP-Response/SIM-Client-Error - Call 'recv Failure-Notification-Ack { ... }' + * - Anything else, enter the FAILURE-NOTIFICATION state. + */ +STATE(common_failure_notification) +{ + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); + fr_pair_t *subtype_vp = NULL; - case FR_EAP_METHOD_AKA_PRIME: - switch (eap_aka_sim_session->kdf) { - case FR_KDF_VALUE_PRIME_WITH_CK_PRIME_IK_PRIME: - if (fr_aka_sim_vector_umts_kdf_1_reauth_from_attrs(request, &request->session_state_pairs, - &eap_aka_sim_session->keys) != 0) { - goto request_new_id; - } - if (fr_aka_sim_crypto_umts_kdf_1_reauth(&eap_aka_sim_session->keys) < 0) goto request_new_id; - break; + subtype_vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_subtype); + if (!subtype_vp) goto fail; - default: - fr_assert(0); - break; - } - break; + switch (subtype_vp->vp_uint16) { + case FR_SUBTYPE_VALUE_AKA_SIM_NOTIFICATION: + RDEBUG2("Failure-Notification ACKed, sending EAP-Failure"); + return CALL_SECTION(recv_common_failure_notification_ack); default: - fr_assert(0); - break; + fail: + RWDEBUG("Failure-Notification not ACKed correctly, sending EAP-Failure anyway"); + return STATE_TRANSITION(eap_failure) } +} - if (RDEBUG_ENABLED3) fr_aka_sim_crypto_keys_log(request, &eap_aka_sim_session->keys); +/** Resume after 'send Failure-Notification { ... }' + * + * Ignores return code from send Failure-Notification { ... } section. + */ +RESUME(send_common_failure_notification) +{ + fr_pair_t *vp, *notification_vp; + eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, + eap_aka_sim_session_t); - /* - * Add AT_IV, AT_COUNTER, AT_NONCE_S, and AT_MAC to to reply - * The user may have added AT_NEXT_REAUTH_ID, in which case - * we'll have sent that too. - */ - eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; + if (!fr_cond_assert(mctx)) RETURN_MODULE_FAIL; /* quiet unused warning */ - /* - * Indicate we'd like to use protected success messages - * with AT_RESULT_IND - * - * Use our default, but allow user override too. - */ - vp = fr_pair_find_by_da(to_peer, attr_eap_aka_sim_result_ind); - if (vp) eap_aka_sim_session->send_result_ind = vp->vp_bool; + SECTION_RCODE_IGNORED; /* - * RFC 5448 says AT_BIDDING is only sent in the challenge - * not in reauthentication, so don't add that here. + * Free anything we were going to send out... */ - - /* - * Add AT_NONCE_S - */ - MEM(pair_update_reply(&vp, attr_eap_aka_sim_nonce_s) >= 0); - fr_pair_value_memdup(vp, eap_aka_sim_session->keys.reauth.nonce_s, - sizeof(eap_aka_sim_session->keys.reauth.nonce_s), false); + fr_pair_list_free(&request->reply_pairs); /* - * Add AT_COUNTER - */ - MEM(pair_update_reply(&vp, attr_eap_aka_sim_counter) >= 0); - vp->vp_uint16 = eap_aka_sim_session->keys.reauth.counter; - - /* - * need to include an empty AT_MAC attribute so that - * the mac will get calculated. + * Allow the user to specify specific failure notification + * types. We assume the user knows what they're doing and + * only toggle success and phase bits. + * + * This allows custom notification schemes to be implemented. + * + * If this is prior to authentication, valid values are: + * - FR_NOTIFICATION_VALUE_GENERAL_FAILURE + * + * If this is after authentication, valid values are: + * - FR_NOTIFICATION_VALUE_GENERAL_FAILURE_AFTER_AUTHENTICATION + * - FR_NOTIFICATION_VALUE_TEMPORARILY_DENIED - User has been + * temporarily denied access to the requested service. + * - FR_NOTIFICATION_VALUE_NOT_SUBSCRIBED + * User has not subscribed to the requested service. */ - MEM(pair_update_reply(&vp, attr_eap_aka_sim_mac) >= 0); - fr_pair_value_memdup(vp, NULL, 0, false); + notification_vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_notification); /* - * If there's no checkcode_md we're not doing - * checkcodes. + * Change the failure notification depending where + * we are in the eap_aka_state machine. */ - if (eap_aka_sim_session->checkcode_md) { + if (after_authentication(eap_aka_sim_session)) { + if (!notification_vp) { + MEM(pair_append_reply(¬ification_vp, attr_eap_aka_sim_notification) >= 0); + notification_vp->vp_uint16 = eap_aka_sim_session->failure_type; /* Default will be zero */ + } + + notification_vp->vp_uint16 &= ~0x4000; /* Unset phase bit */ + /* - * If we have checkcode data, send that to the peer - * in AT_CHECKCODE for validation. + * Include the counter attribute if we're failing + * after a reauthentication success. + * + * RFC 4187 Section #9.10 + * + * If EAP-Request/AKA-Notification is used on + * a fast re-authentication exchange, and if + * the P bit in AT_NOTIFICATION is set to zero, + * then AT_COUNTER is used for replay protection. + * In this case, the AT_ENCR_DATA and AT_IV + * attributes MUST be included, and the + * encapsulated plaintext attributes MUST include + * the AT_COUNTER attribute. The counter value + * included in AT_COUNTER MUST be the same + * as in the EAP-Request/AKA-Reauthentication + * packet on the same fast re-authentication + * exchange. + * + * If the counter is used it should never be zero, + * as it's incremented on first reauthentication + * request. */ - if (eap_aka_sim_session->checkcode_state) { - ssize_t slen; - - slen = fr_aka_sim_crypto_finalise_checkcode(eap_aka_sim_session->checkcode, - &eap_aka_sim_session->checkcode_state); - if (slen < 0) { - RPEDEBUG("Failed calculating checkcode"); - goto failure; - } - eap_aka_sim_session->checkcode_len = slen; + if (eap_aka_sim_session->reauthentication_success) { + MEM(pair_update_reply(¬ification_vp, attr_eap_aka_sim_counter) >= 0); + notification_vp->vp_uint16 = eap_aka_sim_session->keys.reauth.counter; + } - MEM(pair_update_reply(&vp, attr_eap_aka_sim_checkcode) >= 0); - fr_pair_value_memdup(vp, eap_aka_sim_session->checkcode, slen, false); /* - * If we don't have checkcode data, then we exchanged - * no identity packets, so checkcode is zero. + * If we're after the challenge phase + * then we need to include a MAC to + * protect notifications. + */ + MEM(pair_update_reply(&vp, attr_eap_aka_sim_mac) >= 0); + fr_pair_value_memdup(vp, NULL, 0, false); + } else { + /* + * Only valid code is general failure + */ + if (!notification_vp) { + MEM(pair_append_reply(¬ification_vp, attr_eap_aka_sim_notification) >= 0); + notification_vp->vp_uint16 = FR_NOTIFICATION_VALUE_GENERAL_FAILURE; + /* + * User supplied failure code */ } else { - MEM(pair_update_reply(&vp, attr_eap_aka_sim_checkcode) >= 0); - fr_pair_value_memdup(vp, NULL, 0, false); - eap_aka_sim_session->checkcode_len = 0; + notification_vp->vp_uint16 |= 0x4000; /* Set phase bit */ } } + notification_vp->vp_uint16 &= ~0x8000; /* In both cases success bit should be low */ /* - * We've sent the challenge so the peer should now be able - * to accept encrypted attributes. + * Send a response */ - eap_aka_sim_session->allow_encrypted = true; + common_reply(request, eap_aka_sim_session, FR_SUBTYPE_VALUE_AKA_SIM_NOTIFICATION); - return session_and_pseudonym_store(p_result, mctx,request, eap_session, common_reauthentication_request_send); + RETURN_MODULE_HANDLED; } -/** Called after 'store session { ... }' and 'store pseudonym { ... }' +/** Enter the FAILURE-NOTIFICATION state * */ -static unlang_action_t aka_challenge_request_send(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session) +STATE_GUARD(common_failure_notification) { + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); + /* - * Encode the packet - AT_IV is handled automatically - * by the encoder. + * If we're failing, then any identities + * we sent are now invalid. */ - if (common_encode(request, eap_session, FR_SUBTYPE_VALUE_AKA_CHALLENGE, NULL, 0) < 0) { - return common_failure_notification_enter(p_result, mctx, request, eap_session); + if (eap_aka_sim_session->pseudonym_sent || eap_aka_sim_session->fastauth_sent) { + return session_and_pseudonym_clear(p_result, mctx, request, eap_aka_sim_session, + guard_common_failure_notification); /* come back when we're done */ } - RETURN_MODULE_HANDLED; + /* + * We've already sent a failure notification + * Now we just fail as it means something + * went wrong processing the ACK or we got + * garbage from the supplicant. + */ + if (eap_aka_sim_session->state == state_common_failure_notification) { + return STATE_TRANSITION(eap_failure); + } + + /* + * Otherwise just transition as normal... + */ + STATE_SET(common_failure_notification); + + return CALL_SECTION(send_common_failure_notification); } -/** Send a EAP-Request/AKA-Challenge message to the supplicant +/** SUCCESS state - State machine exit point after sending EAP-Success * + * Should never actually be called. Is just a placeholder function to represent the SUCCESS + * termination state. Could equally be a NULL pointer, but then on a logic error + * we'd get a SEGV instead of a more friendly assert/failure rcode. */ -static unlang_action_t aka_challenge_request_compose(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session) +STATE(eap_success) { - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, eap_aka_sim_session_t); - fr_pair_list_t *to_peer = &request->reply_pairs; - fr_pair_t *vp; - fr_aka_sim_vector_src_t src = AKA_SIM_VECTOR_SRC_AUTO; + if (!fr_cond_assert(request && mctx && eap_aka_sim_session)) RETURN_MODULE_FAIL; /* unused args */ - fr_pair_t *kdf_id; + fr_assert(0); /* Should never actually be called */ + + RETURN_MODULE_FAIL; +} + +/** Resume after 'send EAP-Success { ... }' + * + * Add MPPE keys to the request being sent to the supplicant + * + * The only work to be done is the add the appropriate SEND/RECV + * attributes derived from the MSK. + */ +RESUME(send_eap_success) +{ + eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t); + uint8_t *p; + + RDEBUG2("Sending EAP-Success"); /* - * Allow override of KDF Identity + * If this is true we're entering this state + * after sending a AKA-Success-Notification * - * Because certain handset manufacturers don't - * implement RFC 4187 correctly and use the - * wrong identity as input the the PRF/KDF. + * Is seems like a really bad idea to allow the + * user to send a protected success to the + * supplicant and then force a failure using + * the send EAP-Success { ... } section. */ - kdf_id = fr_pair_find_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity); - if (kdf_id) { - identity_to_crypto_identity(request, eap_aka_sim_session, - (uint8_t const *)kdf_id->vp_strvalue, kdf_id->vp_length); - fr_pair_delete_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity); - } - - RDEBUG2("Acquiring UMTS vector(s)"); + if (eap_aka_sim_session->send_result_ind) { + switch (request->rcode) { + case RLM_MODULE_USER_SECTION_REJECT: + RWDEBUG("Ignoring rcode (%s) from send EAP-Success { ... } " + "as we already sent a Success-Notification", + fr_table_str_by_value(rcode_table, request->rcode, "")); + RWDEBUG("If you need to force a failure, return an error code from " + "send Success-Notification { ... }"); + break; - if (eap_aka_sim_session->type == FR_EAP_METHOD_AKA_PRIME) { - /* - * Copy the network name the user specified for - * key derivation purposes. - */ - vp = fr_pair_find_by_da(to_peer, attr_eap_aka_sim_kdf_input); - if (vp) { - talloc_free(eap_aka_sim_session->keys.network); - eap_aka_sim_session->keys.network = talloc_memdup(eap_aka_sim_session, - (uint8_t const *)vp->vp_strvalue, - vp->vp_length); - eap_aka_sim_session->keys.network_len = vp->vp_length; - } else { - REDEBUG("No network name available, can't set AT_KDF_INPUT"); - failure: - return common_failure_notification_enter(p_result, mctx, request, eap_session); + default: + break; } - /* - * We don't allow the user to specify - * the KDF currently. - */ - MEM(pair_update_reply(&vp, attr_eap_aka_sim_kdf) >= 0); - vp->vp_uint16 = eap_aka_sim_session->kdf; - } - /* - * Get vectors from attribute or generate - * them using COMP128-* or Milenage. + * But... if we're not working with protected + * success indication, this is the only + * opportunity the user has to force a failure at + * the end of authentication. */ - if (fr_aka_sim_vector_umts_from_attrs(request, &request->control_pairs, &eap_aka_sim_session->keys, &src) != 0) { - REDEBUG("Failed retrieving UMTS vectors"); - goto failure; + } else { + SECTION_RCODE_PROCESS; } - /* - * Don't leave the AMF hanging around - */ - if (eap_aka_sim_session->type == FR_EAP_METHOD_AKA_PRIME) pair_delete_control(attr_sim_amf); + RDEBUG2("Adding attributes for MSK"); + p = eap_aka_sim_session->keys.msk; + eap_add_reply(request->parent, attr_ms_mppe_recv_key, p, EAP_TLS_MPPE_KEY_LEN); + p += EAP_TLS_MPPE_KEY_LEN; + eap_add_reply(request->parent, attr_ms_mppe_send_key, p, EAP_TLS_MPPE_KEY_LEN); - /* - * All set, calculate keys! - */ - switch (eap_aka_sim_session->type) { - default: - case FR_EAP_METHOD_SIM: - fr_assert(0); /* EAP-SIM has its own Challenge state */ - break; + RETURN_MODULE_OK; +} - case FR_EAP_METHOD_AKA: - fr_aka_sim_crypto_umts_kdf_0(&eap_aka_sim_session->keys); - break; +/** Enter EAP-SUCCESS state + * + */ +STATE_GUARD(eap_success) +{ + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); - case FR_EAP_METHOD_AKA_PRIME: - switch (eap_aka_sim_session->kdf) { - case FR_KDF_VALUE_PRIME_WITH_CK_PRIME_IK_PRIME: - fr_aka_sim_crypto_umts_kdf_1(&eap_aka_sim_session->keys); - break; + STATE_SET(eap_success); - default: - fr_assert(0); - break; - } - } - if (RDEBUG_ENABLED3) fr_aka_sim_crypto_keys_log(request, &eap_aka_sim_session->keys); + return CALL_SECTION(send_eap_success); +} - eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; +/** Resume after 'recv Success-Notification-Ack { ... }' + * + * - Enter the EAP-SUCCESS state. + */ +RESUME(recv_common_success_notification_ack) +{ + eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t); - /* - * Indicate we'd like to use protected success messages - * with AT_RESULT_IND - * - * Use our default, but allow user override too. - */ - vp = fr_pair_find_by_da(to_peer, attr_eap_aka_sim_result_ind); - if (vp) eap_aka_sim_session->send_result_ind = vp->vp_bool; + SECTION_RCODE_IGNORED; /* - * See if we're indicating we want EAP-AKA' - * If so include AT_BIDDING with the correct - * value. + * RFC 4187 says we ignore the contents of the + * next packet after we send our success notification + * and always send a success. */ - vp = fr_pair_find_by_da(to_peer, attr_eap_aka_sim_bidding); - if (vp) { - eap_aka_sim_session->send_at_bidding_prefer_prime = - (vp->vp_uint16 == FR_BIDDING_VALUE_PREFER_AKA_PRIME); - } + return STATE_TRANSITION(eap_success); +} + +/** SUCCESS-NOTIFICATION state - Continue the state machine after receiving a response to our EAP-Request/(AKA|SIM)-Notification + * + * - Call 'recv Success-Notification-Ack { ... }' + */ +STATE(common_success_notification) +{ + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); /* - * These attributes are only allowed with - * EAP-AKA', protect users from themselves. - */ - if (eap_aka_sim_session->type == FR_EAP_METHOD_AKA) { - pair_delete_reply(attr_eap_aka_sim_kdf_input); - pair_delete_reply(attr_eap_aka_sim_kdf); - } + * Because the server uses the AT_NOTIFICATION code "Success" (32768) to + * indicate that the EAP exchange has completed successfully, the EAP + * exchange cannot fail when the server processes the EAP-AKA response + * to this notification. Hence, the server MUST ignore the contents of + * the EAP-AKA response it receives to the EAP-Request/AKA-Notification + * with this code. Regardless of the contents of the EAP-AKA response, + * the server MUST send EAP-Success as the next packet. + */ + return CALL_SECTION(recv_common_success_notification_ack); +} + +/** Resume after 'send Success-Notification { ... }' + * + */ +RESUME(send_common_success_notification) +{ + eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t); + fr_pair_t *vp; + + SECTION_RCODE_PROCESS; + + if (!fr_cond_assert(after_authentication(eap_aka_sim_session))) RETURN_MODULE_FAIL; /* - * Okay, we got the challenge! Put it into an attribute. + * If we're in this state success bit is + * high phase bit is low. */ - MEM(pair_update_reply(&vp, attr_eap_aka_sim_rand) >= 0); - fr_pair_value_memdup(vp, eap_aka_sim_session->keys.umts.vector.rand, AKA_SIM_VECTOR_UMTS_RAND_SIZE, false); + MEM(pair_update_reply(&vp, attr_eap_aka_sim_notification) >= 0); + vp->vp_uint16 = FR_NOTIFICATION_VALUE_SUCCESS; /* - * Send the AUTN value to the client, so it can authenticate - * whoever has knowledge of the Ki. + * RFC 4187 section #9.10 + * + * If EAP-Request/AKA-Notification is used on + * a fast re-authentication exchange, and if + * the P bit in AT_NOTIFICATION is set to zero, + * then AT_COUNTER is used for replay protection. + * In this case, the AT_ENCR_DATA and AT_IV + * attributes MUST be included, and the + * encapsulated plaintext attributes MUST include + * the AT_COUNTER attribute. The counter value + * included in AT_COUNTER MUST be the same + * as in the EAP-Request/AKA-Reauthentication + * packet on the same fast re-authentication + * exchange. + * + * If the counter is used it should never be zero, + * as it's incremented on first reauthentication + * request. */ - MEM(pair_update_reply(&vp, attr_eap_aka_sim_autn) >= 0); - fr_pair_value_memdup(vp, eap_aka_sim_session->keys.umts.vector.autn, AKA_SIM_VECTOR_UMTS_AUTN_SIZE, false); + if (eap_aka_sim_session->keys.reauth.counter > 0) { + MEM(pair_update_reply(&vp, attr_eap_aka_sim_counter) >= 0); + vp->vp_uint16 = eap_aka_sim_session->keys.reauth.counter; + } /* - * need to include an AT_MAC attribute so that it will get - * calculated. + * Need to include an AT_MAC attribute so that + * it will get calculated. */ MEM(pair_update_reply(&vp, attr_eap_aka_sim_mac) >= 0); fr_pair_value_memdup(vp, NULL, 0, false); /* - * If we have checkcode data, send that to the peer - * in AT_CHECKCODE for validation. + * Return reply attributes */ - if (eap_aka_sim_session->checkcode_state) { - ssize_t slen; + common_reply(request, eap_aka_sim_session, FR_SUBTYPE_VALUE_AKA_SIM_NOTIFICATION); - slen = fr_aka_sim_crypto_finalise_checkcode(eap_aka_sim_session->checkcode, - &eap_aka_sim_session->checkcode_state); - if (slen < 0) { - RPEDEBUG("Failed calculating checkcode"); + RETURN_MODULE_HANDLED; +} + +/** Enter the SUCCESS-NOTIFICATION state + * + */ +STATE_GUARD(common_success_notification) +{ + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); + + STATE_SET(common_success_notification); + + return CALL_SECTION(send_common_success_notification); +} + +/** Resume after 'recv Client-Error { ... }' + * + * - Enter the EAP-FAILURE state. + */ +RESUME(recv_common_client_error) +{ + eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t); + + SECTION_RCODE_IGNORED; + + return STATE_TRANSITION(eap_failure); +} + +/** Resume after 'recv Reauthentication-Response { ... }' + * + * - If 'recv Reauthentication-Response { ... }' returned a failure + * rcode, enter the FAILURE-NOTIFICATION state. + * - ...or call the EAP-Request/Reauthentication-Response function to act on the + * contents of the response. + */ +RESUME(recv_common_reauthentication_response) +{ + eap_session_t *eap_session = eap_session_get(request->parent); + eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t); + uint8_t calc_mac[AKA_SIM_MAC_DIGEST_SIZE]; + ssize_t slen; + fr_pair_t *mac; + +#if 0 + *checkcode; +#endif + + SECTION_RCODE_PROCESS; + + mac = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_mac); + if (!mac) { + REDEBUG("Missing AT_MAC attribute"); + failure: + return STATE_TRANSITION(common_failure_notification); + } + if (mac->vp_length != AKA_SIM_MAC_DIGEST_SIZE) { + REDEBUG("MAC has incorrect length, expected %u bytes got %zu bytes", + AKA_SIM_MAC_DIGEST_SIZE, mac->vp_length); + goto failure; + } + + // TODO: MAC validation should happen in the decoder not the module + slen = fr_aka_sim_crypto_sign_packet(calc_mac, eap_session->this_round->response, true, + eap_aka_sim_session->mac_md, + eap_aka_sim_session->keys.k_aut, eap_aka_sim_session->keys.k_aut_len, + eap_aka_sim_session->keys.reauth.nonce_s, + sizeof(eap_aka_sim_session->keys.reauth.nonce_s)); + if (slen < 0) { + RPEDEBUG("Failed calculating MAC"); + goto failure; + } else if (slen == 0) { + REDEBUG("Zero length AT_MAC attribute"); + goto failure; + } + + if (memcmp(mac->vp_octets, calc_mac, sizeof(calc_mac)) == 0) { + RDEBUG2("Received MAC matches calculated MAC"); + } else { + REDEBUG("Received MAC does not match calculated MAC"); + RHEXDUMP_INLINE2(mac->vp_octets, AKA_SIM_MAC_DIGEST_SIZE, "Received"); + RHEXDUMP_INLINE2(calc_mac, AKA_SIM_MAC_DIGEST_SIZE, "Expected"); + goto failure; + } + +#if 0 + /* + * If the peer doesn't include a checkcode then that + * means they don't support it, and we can't validate + * their view of the identity packets. + */ + checkcode = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_checkcode); + if (checkcode) { + if (checkcode->vp_length != eap_aka_sim_session->checkcode_len) { + REDEBUG("Received checkcode's length (%zu) does not match calculated checkcode's length (%zu)", + checkcode->vp_length, eap_aka_sim_session->checkcode_len); goto failure; } - eap_aka_sim_session->checkcode_len = slen; - MEM(pair_update_reply(&vp, attr_eap_aka_sim_checkcode) >= 0); - fr_pair_value_memdup(vp, eap_aka_sim_session->checkcode, slen, false); + if (memcmp(checkcode->vp_octets, eap_aka_sim_session->checkcode, + eap_aka_sim_session->checkcode_len) == 0) { + RDEBUG2("Received checkcode matches calculated checkcode"); + } else { + REDEBUG("Received checkcode does not match calculated checkcode"); + RHEXDUMP_INLINE2(checkcode->vp_octets, checkcode->vp_length, "Received"); + RHEXDUMP_INLINE2(eap_aka_sim_session->checkcode, + eap_aka_sim_session->checkcode_len, "Expected"); + goto failure; + } /* - * If we don't have checkcode data, then we exchanged - * no identity packets, so AT_CHECKCODE is zero. + * Only print something if we calculated a checkcode */ - } else { - MEM(pair_update_reply(&vp, attr_eap_aka_sim_checkcode) >= 0); - fr_pair_value_memdup(vp, NULL, 0, false); - eap_aka_sim_session->checkcode_len = 0; + } else if (eap_aka_sim_session->checkcode_len > 0){ + RDEBUG2("Peer didn't include AT_CHECKCODE, skipping checkcode validation"); } +#endif /* - * We've sent the challenge so the peer should now be able - * to accept encrypted attributes. + * Check to see if the supplicant sent + * AT_COUNTER_TOO_SMALL, if they did then we + * clear out reauth information and enter the + * challenge state. */ - eap_aka_sim_session->allow_encrypted = true; + if (fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_counter_too_small)) { + RWDEBUG("Peer sent AT_COUNTER_TOO_SMALL (indicating our AT_COUNTER value (%u) wasn't fresh)", + eap_aka_sim_session->keys.reauth.counter); + + fr_aka_sim_vector_umts_reauth_clear(&eap_aka_sim_session->keys); + eap_aka_sim_session->allow_encrypted = false; + + return STATE_TRANSITION(aka_challenge); + } + + /* + * If the peer wants a Success notification, and + * we included AT_RESULT_IND then send a success + * notification, otherwise send a normal EAP-Success. + * + * RFC 4187 Section #6.2. Result Indications + */ + if (eap_aka_sim_session->send_result_ind) { + if (!fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_result_ind)) { + RDEBUG("We wanted to use protected result indications, but peer does not"); + eap_aka_sim_session->send_result_ind = false; + } else { + return STATE_TRANSITION(common_success_notification); + } + } else if (fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_result_ind)) { + RDEBUG("Peer wanted to use protected result indications, but we do not"); + } + + eap_aka_sim_session->reauthentication_success = true; - return session_and_pseudonym_store(p_result, mctx,request, eap_session, aka_challenge_request_send); + return STATE_TRANSITION(eap_success); } -/** Called after 'store session { ... }' and 'store pseudonym { ... }' +/** REAUTHENTICATION state - Continue the state machine after receiving a response to our EAP-Request/SIM-Start * + * - Continue based on received AT_SUBTYPE value: + * - EAP-Response/(SIM|AKA)-Reauthentication - call 'recv Reauthentication-Response { ... }' + * - EAP-Response/(SIM|AKA)-Client-Error - call 'recv Client-Error { ... }' and after that + * send a EAP-Request/(SIM|AKA)-Notification indicating a General Failure. + * - Anything else, enter the FAILURE-NOTIFICATION state. */ -static unlang_action_t sim_challenge_request_send(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session) +STATE(common_reauthentication) { - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); + fr_pair_t *subtype_vp = NULL; + + subtype_vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_subtype); + if (!subtype_vp) { + REDEBUG("Missing AT_SUBTYPE"); + goto fail; + } /* - * Encode the packet - AT_IV is handled automatically - * by the encoder. + * These aren't allowed in Reauthentication responses as they don't apply: + * + * EAP_AKA_AUTHENTICATION_REJECT - We didn't provide an AUTN value + * EAP_AKA_SYNCHRONIZATION_FAILURE - We didn't use new vectors. */ - if (common_encode(request, eap_session, FR_SUBTYPE_VALUE_SIM_CHALLENGE, - eap_aka_sim_session->keys.gsm.nonce_mt, sizeof(eap_aka_sim_session->keys.gsm.nonce_mt)) < 0) { - return common_failure_notification_enter(p_result, mctx, request, eap_session); - } + switch (subtype_vp->vp_uint16) { + case FR_SUBTYPE_VALUE_AKA_SIM_REAUTHENTICATION: + /* + * AT_COUNTER_TOO_SMALL is handled + * in common_reauthentication_response_process. + */ + return CALL_SECTION(recv_common_reauthentication_response); - RETURN_MODULE_HANDLED; + /* + * Case 1 where we're allowed to send an EAP-Failure + */ + case FR_SUBTYPE_VALUE_AKA_SIM_CLIENT_ERROR: + client_error_debug(request); + + eap_aka_sim_session->allow_encrypted = false; + + return CALL_SECTION(recv_common_client_error); + /* + * RFC 4187 says we *MUST* notify, not just + * send an EAP-Failure in this case. + */ + default: + REDEBUG("Unexpected subtype %pV", &subtype_vp->data); + fail: + eap_aka_sim_session->allow_encrypted = false; + + return STATE_TRANSITION(common_failure_notification); + } } -/** Send a EAP-Request/SIM-Challenge message to the supplicant + +/** Send a EAP-Request/(AKA|SIM)-Reauthenticate message to the supplicant * */ -static unlang_action_t sim_challenge_request_compose(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session) +static unlang_action_t common_reauthentication_request_compose(rlm_rcode_t *p_result, + module_ctx_t const *mctx, + request_t *request, + eap_aka_sim_session_t *eap_aka_sim_session) { - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - fr_pair_list_t *to_peer = &request->reply_pairs; fr_pair_t *vp; - fr_aka_sim_vector_src_t src = AKA_SIM_VECTOR_SRC_AUTO; - fr_pair_t *kdf_id; /* @@ -1545,30 +1730,76 @@ static unlang_action_t sim_challenge_request_compose(rlm_rcode_t *p_result, modu * Because certain handset manufacturers don't * implement RFC 4187 correctly and use the * wrong identity as input the the PRF/KDF. + * + * Not seen any doing this for re-authentication + * but you never know... */ kdf_id = fr_pair_find_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity); if (kdf_id) { - identity_to_crypto_identity(request, eap_aka_sim_session, - (uint8_t const *)kdf_id->vp_strvalue, kdf_id->vp_length); + crypto_identity_set(request, eap_aka_sim_session, + (uint8_t const *)kdf_id->vp_strvalue, kdf_id->vp_length); fr_pair_delete_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity); } - RDEBUG2("Acquiring GSM vector(s)"); - if ((fr_aka_sim_vector_gsm_from_attrs(request, &request->control_pairs, 0, - &eap_aka_sim_session->keys, &src) != 0) || - (fr_aka_sim_vector_gsm_from_attrs(request, &request->control_pairs, 1, - &eap_aka_sim_session->keys, &src) != 0) || - (fr_aka_sim_vector_gsm_from_attrs(request, &request->control_pairs, 2, - &eap_aka_sim_session->keys, &src) != 0)) { - REDEBUG("Failed retrieving SIM vectors"); - RETURN_MODULE_FAIL; - } + RDEBUG2("Generating new session keys"); - fr_aka_sim_crypto_gsm_kdf_0(&eap_aka_sim_session->keys); + switch (eap_aka_sim_session->type) { + /* + * The GSM and UMTS KDF_0 mutate their keys using + * and identical algorithm. + */ + case FR_EAP_METHOD_SIM: + case FR_EAP_METHOD_AKA: + if (fr_aka_sim_vector_gsm_umts_kdf_0_reauth_from_attrs(request, &request->session_state_pairs, + &eap_aka_sim_session->keys) != 0) { + request_new_id: + switch (eap_aka_sim_session->last_id_req) { + /* + * Got here processing EAP-Identity-Response + * If this is the *true* reauth ID, then + * there's no point in setting AKA_SIM_ANY_ID_REQ. + */ + case AKA_SIM_NO_ID_REQ: + case AKA_SIM_ANY_ID_REQ: + RDEBUG2("Composing EAP-Request/Reauthentication failed. Clearing reply attributes and " + "requesting additional Identity"); + fr_pair_list_free(&request->reply_pairs); + eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ; + return STATE_TRANSITION(common_identity); - if (RDEBUG_ENABLED3) fr_aka_sim_crypto_keys_log(request, &eap_aka_sim_session->keys); + case AKA_SIM_FULLAUTH_ID_REQ: + case AKA_SIM_PERMANENT_ID_REQ: + REDEBUG("Last requested fullauth or permanent ID, " + "but received, or were told we received (by policy), " + "a fastauth ID. Cannot continue"); + return STATE_TRANSITION(common_failure_notification); + } + } + if (fr_aka_sim_crypto_kdf_0_reauth(&eap_aka_sim_session->keys) < 0) goto request_new_id; + break; + + case FR_EAP_METHOD_AKA_PRIME: + switch (eap_aka_sim_session->kdf) { + case FR_KDF_VALUE_PRIME_WITH_CK_PRIME_IK_PRIME: + if (fr_aka_sim_vector_umts_kdf_1_reauth_from_attrs(request, &request->session_state_pairs, + &eap_aka_sim_session->keys) != 0) { + goto request_new_id; + } + if (fr_aka_sim_crypto_umts_kdf_1_reauth(&eap_aka_sim_session->keys) < 0) goto request_new_id; + break; + + default: + fr_assert(0); + break; + } + break; + + default: + fr_assert(0); + break; + } - eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; + if (RDEBUG_ENABLED3) fr_aka_sim_crypto_keys_log(request, &eap_aka_sim_session->keys); /* * Indicate we'd like to use protected success messages @@ -1576,486 +1807,178 @@ static unlang_action_t sim_challenge_request_compose(rlm_rcode_t *p_result, modu * * Use our default, but allow user override too. */ - vp = fr_pair_find_by_da(to_peer, attr_eap_aka_sim_result_ind); + vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_result_ind); if (vp) eap_aka_sim_session->send_result_ind = vp->vp_bool; /* - * Okay, we got the challenges! Put them into attributes. + * RFC 5448 says AT_BIDDING is only sent in the challenge + * not in reauthentication, so don't add that here. */ - MEM(pair_append_reply(&vp, attr_eap_aka_sim_rand) >= 0); - fr_pair_value_memdup(vp, eap_aka_sim_session->keys.gsm.vector[0].rand, AKA_SIM_VECTOR_GSM_RAND_SIZE, false); - MEM(pair_append_reply(&vp, attr_eap_aka_sim_rand) >= 0); - fr_pair_value_memdup(vp, eap_aka_sim_session->keys.gsm.vector[1].rand, AKA_SIM_VECTOR_GSM_RAND_SIZE, false); + /* + * Add AT_NONCE_S + */ + MEM(pair_update_reply(&vp, attr_eap_aka_sim_nonce_s) >= 0); + fr_pair_value_memdup(vp, eap_aka_sim_session->keys.reauth.nonce_s, + sizeof(eap_aka_sim_session->keys.reauth.nonce_s), false); - MEM(pair_append_reply(&vp, attr_eap_aka_sim_rand) >= 0); - fr_pair_value_memdup(vp, eap_aka_sim_session->keys.gsm.vector[2].rand, AKA_SIM_VECTOR_GSM_RAND_SIZE, false); + /* + * Add AT_COUNTER + */ + MEM(pair_update_reply(&vp, attr_eap_aka_sim_counter) >= 0); + vp->vp_uint16 = eap_aka_sim_session->keys.reauth.counter; /* - * need to include an AT_MAC attribute so that it will get - * calculated. + * need to include an empty AT_MAC attribute so that + * the mac will get calculated. */ MEM(pair_update_reply(&vp, attr_eap_aka_sim_mac) >= 0); fr_pair_value_memdup(vp, NULL, 0, false); +#if 0 + /* + * If there's no checkcode_md we're not doing + * checkcodes. + */ + if (eap_aka_sim_session->checkcode_md) { + /* + * If we have checkcode data, send that to the peer + * in AT_CHECKCODE for validation. + */ + if (eap_aka_sim_session->checkcode_state) { + ssize_t slen; + + slen = fr_aka_sim_crypto_finalise_checkcode(eap_aka_sim_session->checkcode, + &eap_aka_sim_session->checkcode_state); + if (slen < 0) { + RPEDEBUG("Failed calculating checkcode"); + goto failure; + } + eap_aka_sim_session->checkcode_len = slen; + + MEM(pair_update_reply(&vp, attr_eap_aka_sim_checkcode) >= 0); + fr_pair_value_memdup(vp, eap_aka_sim_session->checkcode, slen, false); + /* + * If we don't have checkcode data, then we exchanged + * no identity packets, so checkcode is zero. + */ + } else { + MEM(pair_update_reply(&vp, attr_eap_aka_sim_checkcode) >= 0); + fr_pair_value_memdup(vp, NULL, 0, false); + eap_aka_sim_session->checkcode_len = 0; + } + } +#endif + /* * We've sent the challenge so the peer should now be able * to accept encrypted attributes. */ eap_aka_sim_session->allow_encrypted = true; - return session_and_pseudonym_store(p_result, mctx,request, eap_session, sim_challenge_request_send); + return session_and_pseudonym_store(p_result, mctx, request, eap_aka_sim_session, + common_reauthentication_request_send); } -/** Send a EAP-Request/AKA-Identity message to the supplicant - * - * There are three types of user identities that can be implemented - * - Permanent identities such as 0123456789098765@myoperator.com - * Permanent identities can be identified by the leading zero followed by - * by 15 digits (the IMSI number). - * - Ephemeral identities (pseudonyms). These are identities assigned for - * identity privacy so the user can't be tracked. These can identities - * can either be generated as per the 3GPP 'Security aspects of non-3GPP accesses' - * document section 14, where a set of up to 16 encryption keys are used - * to reversibly encrypt the IMSI. Alternatively the pseudonym can be completely - * randomised and stored in a datastore. - * - A fast resumption ID which resolves to data used for fast resumption. - * - * In order to perform full authentication the original IMSI is required for - * forwarding to the HLR. In the case where we can't match/decrypt the pseudonym, - * or can't perform fast resumption, we need to request the full identity from - * the supplicant. +/** Resume after 'send Reauthentication-Request { ... }' * - * @param[out] p_result Result of calling the module, one of: - * - RLM_MODULE_HANDLED on success. - * - anything else on failure. - * @param[in] mctx module calling ctx. - * @param[in] request The current subrequest. - * @param[in] eap_session to continue. */ -static unlang_action_t aka_identity_request_send(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session) +RESUME(send_common_reauthentication_request) { - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; + eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t); + switch (request->rcode) { /* - * Update eap_aka_sim_session->id_req in case the the - * user set attributes in `send Identity-Request { ... }` - * Also removes all existing id_req attributes - * from the reply. + * Failed getting the values we need for resumption + * Request a different identity. */ - identity_req_set_by_user(request, eap_aka_sim_session); + default: + switch (eap_aka_sim_session->last_id_req) { + /* + * Got here processing EAP-Identity-Response + * If this is the *true* reauth ID, then + * there's no point in setting AKA_SIM_ANY_ID_REQ. + */ + case AKA_SIM_NO_ID_REQ: + case AKA_SIM_ANY_ID_REQ: + RDEBUG2("Previous section returned (%s), clearing reply attributes and " + "requesting additional identity", + fr_table_str_by_value(rcode_table, request->rcode, "")); + fr_pair_list_free(&request->reply_pairs); + eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ; - /* - * Select the right type of identity request attribute - * - * Implement checks on identity request order described - * by RFC4187 section #4.1.5. - * - * The internal state machine should always handle this - * correctly, but the user may have other ideas... - */ - if (identity_req_pairs_add(request, eap_aka_sim_session) < 0) { + return STATE_TRANSITION(common_identity); + + case AKA_SIM_FULLAUTH_ID_REQ: + case AKA_SIM_PERMANENT_ID_REQ: + default: + break; + } + REDEBUG("Last requested Full-Auth-Id or Permanent-Identity, " + "but received a Fast-Auth-Id. Cannot continue"); failure: - return common_failure_notification_enter(p_result, mctx, request, eap_session); - } - eap_aka_sim_session->last_id_req = eap_aka_sim_session->id_req; /* Record what we last requested */ + return STATE_TRANSITION(common_failure_notification); /* - * Encode the packet + * Policy rejected the user */ - if (common_encode(request, eap_session, FR_SUBTYPE_VALUE_AKA_IDENTITY, NULL, 0) < 0) goto failure; + case RLM_MODULE_REJECT: + case RLM_MODULE_DISALLOW: + goto failure; /* - * Digest the packet contents, updating our checkcode. + * Everything looks ok, send the EAP-Request/reauthentication message + * After storing any new pseudonyms or session information. */ - if (eap_aka_sim_session->checkcode_md) { - if (!eap_aka_sim_session->checkcode_state && - fr_aka_sim_crypto_init_checkcode(eap_aka_sim_session, &eap_aka_sim_session->checkcode_state, - eap_aka_sim_session->checkcode_md) < 0) { - RPEDEBUG("Failed initialising checkcode"); - goto failure; - } - if (fr_aka_sim_crypto_update_checkcode(eap_aka_sim_session->checkcode_state, - eap_session->this_round->request) < 0) { - RPEDEBUG("Failed updating checkcode"); - goto failure; - } + case RLM_MODULE_NOOP: + case RLM_MODULE_OK: + case RLM_MODULE_UPDATED: + return common_reauthentication_request_compose(p_result, mctx, request, eap_aka_sim_session); } - - RETURN_MODULE_HANDLED; } -/** Send a EAP-Request/SIM-Start message to the supplicant - * - * There are three types of user identities that can be implemented - * - Permanent identities such as 0123456789098765@myoperator.com - * Permanent identities can be identified by the leading zero followed by - * by 15 digits (the IMSI number). - * - Ephemeral identities (pseudonyms). These are identities assigned for - * identity privacy so the user can't be tracked. These can identities - * can either be generated as per the 3GPP 'Security aspects of non-3GPP accesses' - * document section 14, where a set of up to 16 encryption keys are used - * to reversibly encrypt the IMSI. Alternatively the pseudonym can be completely - * randomised and stored in a datastore. - * - A fast resumption ID which resolves to data used for fast resumption. - * - * In order to perform full authentication the original IMSI is required for - * forwarding to the HLR. In the case where we can't match/decrypt the pseudonym, - * or can't perform fast resumption, we need to request the full identity from - * the supplicant. +/** Resume after 'load pseudonym { ... }' * - * @param[out] p_result Result of calling the module, one of: - * - RLM_MODULE_HANDLED on success. - * - anything else on failure. - * @param[in] mctx module calling context. - * @param[in] request The current subrequest. - * @param[in] eap_session to continue. */ -static unlang_action_t sim_start_request_send(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session) +RESUME(load_pseudonym) { - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - fr_pair_t *vp; - uint8_t *p, *end; - - eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); + eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t); - p = eap_aka_sim_session->keys.gsm.version_list; - end = p + sizeof(eap_aka_sim_session->keys.gsm.version_list); - eap_aka_sim_session->keys.gsm.version_list_len = 0; + pair_delete_request(attr_eap_aka_sim_next_reauth_id); /* - * If the user provided no versions, then - * just add the default (1). + * Control attributes required could have been specified + * in another section. */ - if (!(fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_version_list))) { - MEM(pair_append_reply(&vp, attr_eap_aka_sim_version_list) >= 0); - vp->vp_uint16 = EAP_SIM_VERSION; + if (!inst->actions.load_pseudonym) { + next_state: + return eap_aka_sim_session->next(p_result, mctx, request, eap_aka_sim_session); } + switch (request->rcode) { /* - * Iterate over the the versions adding them - * to the version list we use for keying. - */ - for (vp = fr_pair_list_head(&request->reply_pairs); vp; vp = fr_pair_list_next(&request->reply_pairs, vp)) { - if (vp->da != attr_eap_aka_sim_version_list) continue; - - if ((end - p) < 2) break; - - /* - * Store as big endian - */ - *p++ = (vp->vp_uint16 & 0xff00) >> 8; - *p++ = (vp->vp_uint16 & 0x00ff); - eap_aka_sim_session->keys.gsm.version_list_len += sizeof(uint16_t); - } - - /* - * Update eap_aka_sim_session->id_req in case the the - * user set attributes in `send Identity-Request { ... }` - * Also removes all existing id_req attributes - * from the reply. - */ - identity_req_set_by_user(request, eap_aka_sim_session); - - /* - * Select the right type of identity request attribute - * - * Implement checks on identity request order described - * by RFC4186 section #4.2.5. - * - * The internal state machine should always handle this - * correctly, but the user may have other ideas... - */ - if (identity_req_pairs_add(request, eap_aka_sim_session) < 0) { - failure: - return common_failure_notification_enter(p_result, mctx, request, eap_session); - } - eap_aka_sim_session->last_id_req = eap_aka_sim_session->id_req; /* Record what we last requested */ - - /* - * Encode the packet - */ - if (common_encode(request, eap_session, FR_SUBTYPE_VALUE_SIM_START, NULL, 0) < 0) goto failure; - - RETURN_MODULE_HANDLED; -} - -/** Print debugging information, and write new state to eap_session->process - * - */ -static inline void state_transition(request_t *request, eap_session_t *eap_session, module_method_t new_state) -{ - module_method_t old_state = eap_session->process; - - if (new_state != old_state) { - RDEBUG2("Changed state %s -> %s", - module_state_method_to_str(aka_sim_stable_table, old_state, ""), - module_state_method_to_str(aka_sim_stable_table, new_state, "")); - } else { - RDEBUG2("Reentering state %s", - module_state_method_to_str(aka_sim_stable_table, old_state, "")); - } - - eap_session->process = new_state; -} - -/** Resume after 'send EAP-Failure { ... }' - * - */ -static unlang_action_t common_eap_failure_enter_resume(rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx, - request_t *request, UNUSED void *rctx) -{ - eap_session_t *eap_session = eap_session_get(request->parent); - - section_rcode_ignored(request); - - return common_eap_failure_send(p_result, request, eap_session); -} - -/** Enter EAP-FAILURE state - * - */ -static unlang_action_t common_eap_failure_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session) -{ - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, eap_aka_sim_session_t); - - /* - * Free anything we were going to send out... - */ - fr_pair_list_free(&request->reply_pairs); - - /* - * If we're failing, then any identities - * we sent are now invalid. - */ - if (eap_aka_sim_session->pseudonym_sent || eap_aka_sim_session->fastauth_sent) { - return session_and_pseudonym_clear(p_result, mctx, - request, eap_session, common_eap_failure_enter); - /* come back when we're done */ - } - - state_transition(request, eap_session, common_eap_failure); - - return unlang_module_yield_to_section(p_result, - request, - inst->actions.send_eap_failure, - RLM_MODULE_NOOP, - common_eap_failure_enter_resume, - mod_signal, - NULL); -} - -/** Resume after 'send Failure-Notification { ... }' - * - * Ignores return code from send Failure-Notification { ... } section. - */ -static unlang_action_t common_failure_notification_enter_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, UNUSED void *rctx) -{ - eap_session_t *eap_session = eap_session_get(request->parent); - - section_rcode_ignored(request); - - /* - * Free anything we were going to send out... - */ - fr_pair_list_free(&request->reply_pairs); - - /* - * If there's an issue composing the failure - * message just send an EAP-Failure instead. - */ - return common_failure_notification_send(p_result, mctx,request, eap_session); -} - -/** Enter the FAILURE-NOTIFICATION state - * - */ -static unlang_action_t common_failure_notification_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session) -{ - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, eap_aka_sim_session_t); - - /* - * If we're failing, then any identities - * we sent are now invalid. - */ - if (eap_aka_sim_session->pseudonym_sent || eap_aka_sim_session->fastauth_sent) { - return session_and_pseudonym_clear(p_result, mctx,request, eap_session, - common_failure_notification_enter); /* come back when we're done */ - } - - /* - * We've already sent a failure notification - * Now we just fail as it means something - * went wrong processing the ACK or we got - * garbage from the supplicant. - */ - if (eap_session->process == common_failure_notification) { - return common_eap_failure_enter(p_result, mctx, request, eap_session); - } - - /* - * Otherwise just transition as normal... - */ - state_transition(request, eap_session, common_failure_notification); - - return unlang_module_yield_to_section(p_result, - request, - inst->actions.send_failure_notification, - RLM_MODULE_NOOP, - common_failure_notification_enter_resume, - mod_signal, - NULL); -} - -/** Resume after 'send EAP-Success { ... }' - * - */ -static unlang_action_t common_eap_success_enter_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, UNUSED void *rctx) -{ - eap_session_t *eap_session = eap_session_get(request->parent); - - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - - /* - * If this is true we're entering this state - * after sending a AKA-Success-Notification - * - * Is seems like a really bad idea to allow the - * user to send a protected success to the - * supplicant and then force a failure using - * the send EAP-Success { ... } section. - */ - if (eap_aka_sim_session->send_result_ind) { - switch (request->rcode) { - case RLM_MODULE_USER_SECTION_REJECT: - RWDEBUG("Ignoring rcode (%s) from send EAP-Success { ... } " - "as we already sent a Success-Notification", - fr_table_str_by_value(rcode_table, request->rcode, "")); - RWDEBUG("If you need to force a failure, return an error code from " - "send Success-Notification { ... }"); - break; - - default: - break; - } - - /* - * But... if we're not working with protected - * success indication, this is the only - * opportunity the user has to force a failure at - * the end of authentication. - */ - } else { - section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session); - } - - return common_eap_success_send(p_result, request, eap_session); -} - -/** Enter EAP-SUCCESS state - * - */ -static unlang_action_t common_eap_success_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request, - eap_session_t *eap_session) -{ - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t); - state_transition(request, eap_session, common_eap_success); - - return unlang_module_yield_to_section(p_result, - request, - inst->actions.send_eap_success, - RLM_MODULE_NOOP, - common_eap_success_enter_resume, - mod_signal, - NULL); -} - -/** Resume after 'send Success-Notification { ... }' - * - */ -static unlang_action_t common_success_notification_enter_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, UNUSED void *rctx) -{ - eap_session_t *eap_session = eap_session_get(request->parent); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - - section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session); - - return common_success_notification_send(p_result, mctx, request, eap_session); -} - -/** Enter the SUCCESS-NOTIFICATION state - * - */ -static unlang_action_t common_success_notification_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session) -{ - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t); - state_transition(request, eap_session, common_success_notification); - - return unlang_module_yield_to_section(p_result, - request, - inst->actions.send_success_notification, - RLM_MODULE_NOOP, - common_success_notification_enter_resume, - mod_signal, - NULL); -} - -/** Resume after 'send Reauthentication-Request { ... }' - * - */ -static unlang_action_t common_reauthentication_send_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, UNUSED void *rctx) -{ - eap_session_t *eap_session = eap_session_get(request->parent); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - - switch (request->rcode) { - /* - * Failed getting the values we need for resumption - * Request a different identity. + * Failed resolving the pseudonym + * request a different identity. */ default: switch (eap_aka_sim_session->last_id_req) { - /* - * Got here processing EAP-Identity-Response - * If this is the *true* reauth ID, then - * there's no point in setting AKA_SIM_ANY_ID_REQ. - */ case AKA_SIM_NO_ID_REQ: case AKA_SIM_ANY_ID_REQ: + case AKA_SIM_FULLAUTH_ID_REQ: RDEBUG2("Previous section returned (%s), clearing reply attributes and " "requesting additional identity", fr_table_str_by_value(rcode_table, request->rcode, "")); fr_pair_list_free(&request->reply_pairs); eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ; + return STATE_TRANSITION(common_identity); - return common_identity_enter(p_result, mctx, request, eap_session); - - case AKA_SIM_FULLAUTH_ID_REQ: case AKA_SIM_PERMANENT_ID_REQ: - default: - break; + REDEBUG("Last requested a Permanent-Identity, but received a Pseudonym. Cannot continue"); + failure: + return STATE_TRANSITION(common_failure_notification); } - REDEBUG("Last requested Full-Auth-Id or Permanent-Identity, " - "but received a Fast-Auth-Id. Cannot continue"); - failure: - return common_failure_notification_enter(p_result, mctx, request, eap_session); + break; /* * Policy rejected the user @@ -2065,26 +1988,23 @@ static unlang_action_t common_reauthentication_send_resume(rlm_rcode_t *p_result goto failure; /* - * Everything looks ok, send the EAP-Request/reauthentication message - * After storing any new pseudonyms or session information. + * Everything OK */ - case RLM_MODULE_NOOP: case RLM_MODULE_OK: case RLM_MODULE_UPDATED: - return common_reauthentication_request_compose(p_result, mctx, request, eap_session); + goto next_state; } + + goto failure; } /** Resume after 'load session { ... }' * */ -static unlang_action_t session_load_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, UNUSED void *rctx) +RESUME(load_session) { - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t); - eap_session_t *eap_session = eap_session_get(request->parent); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); + eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t); pair_delete_request(attr_session_id); @@ -2113,14 +2033,13 @@ static unlang_action_t session_load_resume(rlm_rcode_t *p_result, module_ctx_t c fr_table_str_by_value(rcode_table, request->rcode, "")); fr_pair_list_free(&request->reply_pairs); eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ; - return common_identity_enter(p_result, mctx, request, eap_session); + return STATE_TRANSITION(common_identity); case AKA_SIM_FULLAUTH_ID_REQ: case AKA_SIM_PERMANENT_ID_REQ: REDEBUG("Last requested Full-Auth-Id or Permanent-Identity, " "but received a Fast-Auth-Id. Cannot continue"); - return common_failure_notification_enter(p_result, mctx, request, eap_session); - + return STATE_TRANSITION(common_failure_notification); } break; @@ -2130,7 +2049,7 @@ static unlang_action_t session_load_resume(rlm_rcode_t *p_result, module_ctx_t c case RLM_MODULE_REJECT: case RLM_MODULE_DISALLOW: reject: - return common_failure_notification_enter(p_result, mctx, request, eap_session); + return STATE_TRANSITION(common_failure_notification); /* * Everything OK @@ -2138,1639 +2057,930 @@ static unlang_action_t session_load_resume(rlm_rcode_t *p_result, module_ctx_t c case RLM_MODULE_OK: case RLM_MODULE_UPDATED: reauthenticate: - return unlang_module_yield_to_section(p_result, - request, - inst->actions.send_reauthentication_request, - RLM_MODULE_NOOP, - common_reauthentication_send_resume, - mod_signal, - NULL); + return CALL_SECTION(send_common_reauthentication_request); } goto reject; } -/** Resume after 'load pseudonym { ... }' +/** Enter the REAUTHENTICATION state * */ -static unlang_action_t pseudonym_load_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, void *rctx) +STATE_GUARD(common_reauthentication) { - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t); - eap_session_t *eap_session = eap_session_get(request->parent); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - aka_sim_state_enter_t state_enter = (aka_sim_state_enter_t)rctx; + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); + fr_pair_t *vp = NULL; - pair_delete_request(attr_eap_aka_sim_next_reauth_id); + STATE_SET(common_reauthentication); /* - * Control attributes required could have been specified - * in another section. + * Add the current identity as session_id + * to make it easier to load/store things from + * the cache module. */ - if (!inst->actions.load_pseudonym) { - next_state: - return state_enter(p_result, mctx, request, eap_session); - } + MEM(pair_update_request(&vp, attr_session_id) >= 0); + fr_pair_value_memdup(vp, eap_aka_sim_session->keys.identity, eap_aka_sim_session->keys.identity_len, true); - switch (request->rcode) { - /* - * Failed resolving the pseudonym - * request a different identity. - */ - default: - switch (eap_aka_sim_session->last_id_req) { - case AKA_SIM_NO_ID_REQ: - case AKA_SIM_ANY_ID_REQ: - case AKA_SIM_FULLAUTH_ID_REQ: - RDEBUG2("Previous section returned (%s), clearing reply attributes and " - "requesting additional identity", - fr_table_str_by_value(rcode_table, request->rcode, "")); - fr_pair_list_free(&request->reply_pairs); - eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ; - return common_identity_enter(p_result, mctx, request, eap_session); + return CALL_SECTION(load_session); +} - case AKA_SIM_PERMANENT_ID_REQ: - REDEBUG("Last requested a Permanent-Identity, but received a Pseudonym. Cannot continue"); - failure: - return common_failure_notification_enter(p_result, mctx, request, eap_session); - } - break; +/** Resume after 'recv Synchronization-Failure { ... }' + * + * - If 'recv Synchronization-Failure { ... }' returned a failure + * rcode, enter the FAILURE-NOTIFICATION state. + * - ...or if no 'recv Syncronization-Failure { ... }' section was + * defined, then enter the FAILURE-NOTIFICATION state. + * - ...or if the user didn't provide a new SQN value in &control.SQN + * then enter the FAILURE-NOTIFICATION state. + * - ...or enter the AKA-CHALLENGE state. + */ +RESUME(recv_aka_syncronization_failure) +{ + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); + eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t); + fr_pair_t *vp; + + SECTION_RCODE_PROCESS; /* - * Policy rejected the user + * If there's no section to handle this, then no resynchronisation + * can't have occurred and we just send a reject. + * + * Similarly, if we've already received one synchronisation failure + * then it's highly likely whatever user configured action was + * configured was unsuccessful, and we should just give up. */ - case RLM_MODULE_REJECT: - case RLM_MODULE_DISALLOW: - goto failure; + if (!inst->actions.recv_aka_syncronization_failure || eap_aka_sim_session->prev_recv_sync_failure) { + failure: + return STATE_TRANSITION(common_failure_notification); + } /* - * Everything OK + * We couldn't generate an SQN and the user didn't provide one, + * so we need to fail. */ - case RLM_MODULE_OK: - case RLM_MODULE_UPDATED: - goto next_state; + vp = fr_pair_find_by_da(&request->control_pairs, attr_sim_sqn); + if (!vp) { + REDEBUG("No &control.SQN value provided after resynchronisation, cannot continue"); + goto failure; } - goto failure; + /* + * RFC 4187 Section #6.3.1 + * + * "if the peer detects that the + * sequence number in AUTN is not correct, the peer responds with + * EAP-Response/AKA-Synchronization-Failure (Section 9.6), and the + * server proceeds with a new EAP-Request/AKA-Challenge." + */ + return STATE_TRANSITION(aka_challenge); } -/** Enter the REAUTHENTICATION state +/** Resume after 'recv Authentication-Reject { ... }' * + * - Enter the FAILURE-NOTIFICATION state. */ -static unlang_action_t common_reauthentication_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session) +RESUME(recv_aka_authentication_reject) { - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t); - fr_pair_t *vp = NULL; - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); + eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t); - state_transition(request, eap_session, common_reauthentication); + SECTION_RCODE_IGNORED; /* - * Add the current identity as session_id - * to make it easier to load/store things from - * the cache module. + * Case 2 where we're allowed to send an EAP-Failure */ - MEM(pair_update_request(&vp, attr_session_id) >= 0); - fr_pair_value_memdup(vp, eap_aka_sim_session->keys.identity, eap_aka_sim_session->keys.identity_len, true); - - return unlang_module_yield_to_section(p_result, - request, - inst->actions.load_session, - RLM_MODULE_NOOP, - session_load_resume, - mod_signal, - NULL); + return STATE_TRANSITION(eap_failure); } -/** Resume after 'send Challenge-Request { ... }' +/** Resume after 'recv Challenge-Response { ... }' + * + * - If the previous section returned a failure rcode, enter the FAILURE-NOTIFICATION state. + * - ...or call a function to process the contents of the AKA-Challenge message. * + * Verify that MAC, and RES match what we expect. */ -static unlang_action_t aka_challenge_enter_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, UNUSED void *rctx) +RESUME(recv_aka_challenge_response) { - eap_session_t *eap_session = eap_session_get(request->parent); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - - section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session); + eap_session_t *eap_session = eap_session_get(request->parent); + eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t); + uint8_t calc_mac[AKA_SIM_MAC_DIGEST_SIZE]; + ssize_t slen; + fr_pair_t *vp = NULL, *mac; +#if 0 + *checkcode; +#endif + SECTION_RCODE_PROCESS; - return aka_challenge_request_compose(p_result, mctx, request, eap_session); -} -/** Enter the AKA-CHALLENGE state - * - */ -static unlang_action_t aka_challenge_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session) -{ - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, eap_aka_sim_session_t); - fr_pair_t *vp; + mac = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_mac); + if (!mac) { + REDEBUG("Missing AT_MAC attribute"); + failure: + return STATE_TRANSITION(common_failure_notification); + } + if (mac->vp_length != AKA_SIM_MAC_DIGEST_SIZE) { + REDEBUG("MAC has incorrect length, expected %u bytes got %zu bytes", + AKA_SIM_MAC_DIGEST_SIZE, mac->vp_length); + goto failure; + } - /* - * If we've sent either of these identities it - * means we've come here form a Reauthentication-Request - * that failed. - */ - if (eap_aka_sim_session->pseudonym_sent || eap_aka_sim_session->fastauth_sent) { - return session_and_pseudonym_clear(p_result, mctx,request, eap_session, aka_challenge_enter); /* come back when we're done */ + // TODO: MAC validation should happen in the decoder not the state machine + slen = fr_aka_sim_crypto_sign_packet(calc_mac, eap_session->this_round->response, true, + eap_aka_sim_session->mac_md, + eap_aka_sim_session->keys.k_aut, eap_aka_sim_session->keys.k_aut_len, + NULL, 0); + if (slen < 0) { + RPEDEBUG("Failed calculating MAC"); + goto failure; + } else if (slen == 0) { + REDEBUG("Zero length AT_MAC attribute"); + goto failure; } - state_transition(request, eap_session, aka_challenge); + if (memcmp(mac->vp_octets, calc_mac, sizeof(calc_mac)) == 0) { + RDEBUG2("Received MAC matches calculated MAC"); + } else { + REDEBUG("Received MAC does not match calculated MAC"); + RHEXDUMP_INLINE2(mac->vp_octets, AKA_SIM_MAC_DIGEST_SIZE, "Received"); + RHEXDUMP_INLINE2(calc_mac, AKA_SIM_MAC_DIGEST_SIZE, "Expected"); + goto failure; + } /* - * Set some default attributes, giving the user a - * chance to modify them. + * If the peer doesn't include a checkcode then that + * means they don't support it, and we can't validate + * their view of the identity packets. */ - switch (eap_session->type) { - case FR_EAP_METHOD_AKA_PRIME: - { - uint8_t amf_buff[2] = { 0x80, 0x00 }; /* Set the AMF separation bit high */ +#if 0 + if (eap_aka_sim_session->checkcode_md) { + checkcode = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_checkcode); + if (checkcode) { + if (checkcode->vp_length != eap_aka_sim_session->checkcode_len) { + REDEBUG("Received checkcode's length (%zu) does not match " + "calculated checkcode's length (%zu)", + checkcode->vp_length, eap_aka_sim_session->checkcode_len); + goto failure; + } + if (memcmp(checkcode->vp_octets, + eap_aka_sim_session->checkcode, eap_aka_sim_session->checkcode_len) == 0) { + RDEBUG2("Received checkcode matches calculated checkcode"); + } else { + REDEBUG("Received checkcode does not match calculated checkcode"); + RHEXDUMP_INLINE2(checkcode->vp_octets, checkcode->vp_length, "Received"); + RHEXDUMP_INLINE2(eap_aka_sim_session->checkcode, + eap_aka_sim_session->checkcode_len, "Expected"); + goto failure; + } /* - * Toggle the AMF high bit to indicate we're doing AKA' + * Only print something if we calculated a checkcode */ - MEM(pair_update_control(&vp, attr_sim_amf) >= 0); - fr_pair_value_memdup(vp, amf_buff, sizeof(amf_buff), false); - - /* - * Use the default network name we have configured - * and send it to the peer. - */ - if (inst->network_name && - !fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_kdf_input)) { - MEM(pair_append_reply(&vp, attr_eap_aka_sim_kdf_input) >= 0); - fr_pair_value_bstrdup_buffer(vp, inst->network_name, false); + } else if (eap_aka_sim_session->checkcode_len > 0){ + RDEBUG2("Peer didn't include AT_CHECKCODE, skipping checkcode validation"); } } - break; +#endif - default: - /* - * Use the default bidding value we have configured - */ - if (eap_aka_sim_session->send_at_bidding_prefer_prime && - !fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_bidding)) { - MEM(pair_append_reply(&vp, attr_eap_aka_sim_bidding) >= 0); - vp->vp_uint16 = FR_BIDDING_VALUE_PREFER_AKA_PRIME; - } - break; + vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_res); + if (!vp) { + REDEBUG("AT_RES missing from challenge response"); + goto failure; + } + if (vp->vp_length != eap_aka_sim_session->keys.umts.vector.xres_len) { + REDEBUG("Received RES' length (%zu) does not match calculated XRES' length (%zu)", + vp->vp_length, eap_aka_sim_session->keys.umts.vector.xres_len); + goto failure; } - /* - * Set the defaults for protected result indicator - */ - if (eap_aka_sim_session->send_result_ind && - !fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_result_ind)) { - MEM(pair_append_reply(&vp, attr_eap_aka_sim_result_ind) >= 0); - vp->vp_bool = true; + if (memcmp(vp->vp_octets, eap_aka_sim_session->keys.umts.vector.xres, vp->vp_length)) { + REDEBUG("Received RES does not match calculated XRES"); + RHEXDUMP_INLINE2(vp->vp_octets, vp->vp_length, "RES :"); + RHEXDUMP_INLINE2(eap_aka_sim_session->keys.umts.vector.xres, + eap_aka_sim_session->keys.umts.vector.xres_len, "XRES :"); + goto failure; } - return unlang_module_yield_to_section(p_result, - request, - inst->actions.send_challenge_request, - RLM_MODULE_NOOP, - aka_challenge_enter_resume, - mod_signal, - NULL); -} + RDEBUG2("Received RES matches calculated XRES"); -/** Resume after 'send Challenge-Request { ... }' - * - */ -static unlang_action_t sim_challenge_enter_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, UNUSED void *rctx) -{ - eap_session_t *eap_session = eap_session_get(request->parent); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); + eap_aka_sim_session->challenge_success = true; - section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session); + /* + * If the peer wants a Success notification, and + * we included AT_RESULT_IND then send a success + * notification, otherwise send a normal EAP-Success. + * + * RFC 4187 Section #6.2. Result Indications + */ + if (eap_aka_sim_session->send_result_ind) { + if (!fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_result_ind)) { + RDEBUG("We wanted to use protected result indications, but peer does not"); + eap_aka_sim_session->send_result_ind = false; + } else { + return STATE_TRANSITION(common_success_notification); + } + } else if (fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_result_ind)) { + RDEBUG("Peer wanted to use protected result indications, but we do not"); + } - return sim_challenge_request_compose(p_result, mctx, request, eap_session); + return STATE_TRANSITION(eap_success); } -/** Enter the SIM-CHALLENGE state +/** AKA-CHALLENGE state - Continue the state machine after receiving a response to our EAP-Request/SIM-Challenge * + * - Continue based on received AT_SUBTYPE value: + * - EAP-Response/AKA-Challenge - call 'recv Challenge-Response { ... }'. + * - EAP-Response/AKA-Authentication-Reject - call 'recv Authentication-Reject { ... }' and after that + * send a EAP-Request/SIM-Notification indicating a General Failure. + * - EAP-Response/AKA-Syncronization-Failure - call 'recv Syncronization-Failure { ... }'. + * - EAP-Response/AKA-Client-Error - call 'recv Client-Error { ... }' and after that + * send a EAP-Request/AKA-Notification indicating a General Failure. + * - Anything else, enter the FAILURE-NOTIFICATION state. */ -static unlang_action_t sim_challenge_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session) +STATE(aka_challenge) { - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); + fr_pair_t *subtype_vp = NULL; fr_pair_t *vp; - /* - * If we've sent either of these identities it - * means we've come here form a Reauthentication-Request - * that failed. - */ - if (eap_aka_sim_session->pseudonym_sent || eap_aka_sim_session->fastauth_sent) { - return session_and_pseudonym_clear(p_result, mctx,request, eap_session, sim_challenge_enter); /* come back when we're done */ + subtype_vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_subtype); + if (!subtype_vp) { + REDEBUG("Missing AT_SUBTYPE"); + goto fail; } - state_transition(request, eap_session, sim_challenge); + switch (subtype_vp->vp_uint16) { + case FR_SUBTYPE_VALUE_AKA_CHALLENGE: + return CALL_SECTION(recv_aka_challenge_response); /* - * Set the defaults for protected result indicator + * Case 2 where we're allowed to send an EAP-Failure */ - if (eap_aka_sim_session->send_result_ind && - !fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_result_ind)) { - MEM(pair_append_reply(&vp, attr_eap_aka_sim_result_ind) >= 0); - vp->vp_bool = true; - } + case FR_SUBTYPE_VALUE_AKA_AUTHENTICATION_REJECT: + eap_aka_sim_session->allow_encrypted = false; + return CALL_SECTION(recv_aka_authentication_reject); - return unlang_module_yield_to_section(p_result, - request, - inst->actions.send_challenge_request, - RLM_MODULE_NOOP, - sim_challenge_enter_resume, - mod_signal, - NULL); -} - -/** Enter the SIM-CHALLENGE or AKA-CHALLENGE state - * - * Called by functions which are common to both the EAP-SIM and EAP-AKA state machines - * to enter the correct challenge state. - */ -static unlang_action_t common_challenge_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session) -{ - switch (eap_session->type) { - case FR_EAP_METHOD_SIM: - return sim_challenge_enter(p_result, mctx, request, eap_session); - - case FR_EAP_METHOD_AKA: - case FR_EAP_METHOD_AKA_PRIME: - return aka_challenge_enter(p_result, mctx, request, eap_session); - - default: - break; - } - - fr_assert(0); - RETURN_MODULE_FAIL; -} - -/** Resume after 'send Identity-Request { ... }' - * - */ -static unlang_action_t aka_identity_enter_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, UNUSED void *rctx) -{ - eap_session_t *eap_session = eap_session_get(request->parent); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - - section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session); - - return aka_identity_request_send(p_result, mctx,request, eap_session); -} - -/** Enter the AKA-IDENTITY state - * - */ -static unlang_action_t aka_identity_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session) -{ - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t); - - state_transition(request, eap_session, aka_identity); - - /* - * If we have an send_aka_identity_request section - * then run that, otherwise just run the normal - * identity request section. - */ - return unlang_module_yield_to_section(p_result, - request, - inst->actions.aka.send_aka_identity_request ? - inst->actions.aka.send_aka_identity_request: - inst->actions.send_identity_request, - RLM_MODULE_NOOP, - aka_identity_enter_resume, - mod_signal, - NULL); -} - -/** Resume after 'send Start { ... }' - * - */ -static unlang_action_t sim_start_enter_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, UNUSED void *rctx) -{ - eap_session_t *eap_session = eap_session_get(request->parent); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - - section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session); - - return sim_start_request_send(p_result, mctx,request, eap_session); -} - -/** Enter the SIM-START state - * - */ -static unlang_action_t sim_start_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session) -{ - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t); - - state_transition(request, eap_session, sim_start); - - return unlang_module_yield_to_section(p_result, - request, - inst->actions.sim.send_sim_start_request ? - inst->actions.sim.send_sim_start_request: - inst->actions.send_identity_request, - RLM_MODULE_NOOP, - sim_start_enter_resume, - mod_signal, - NULL); -} - -/** Enter the SIM-START or AKA-IDENTITY state - * - * Called by functions which are common to both the EAP-SIM and EAP-AKA state machines - * to enter the correct Identity-Request state. - */ -static unlang_action_t common_identity_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session) -{ - switch (eap_session->type) { - case FR_EAP_METHOD_SIM: - return sim_start_enter(p_result, mctx, request, eap_session); - - case FR_EAP_METHOD_AKA: - case FR_EAP_METHOD_AKA_PRIME: - return aka_identity_enter(p_result, mctx, request, eap_session); - - default: - break; - } - - fr_assert(0); - RETURN_MODULE_FAIL; -} - -/** Process a EAP-Response/(AKA|SIM)-Reauthentication message - The response to our EAP-Request/(AKA|SIM)-Reauthentication message - * - */ -static unlang_action_t common_reauthentication_response_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session) -{ - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - - uint8_t calc_mac[AKA_SIM_MAC_DIGEST_SIZE]; - ssize_t slen; - fr_pair_t *mac, *checkcode; - fr_pair_list_t *from_peer = &request->request_pairs; - - mac = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_mac); - if (!mac) { - REDEBUG("Missing AT_MAC attribute"); - failure: - return common_failure_notification_enter(p_result, mctx, request, eap_session); - } - if (mac->vp_length != AKA_SIM_MAC_DIGEST_SIZE) { - REDEBUG("MAC has incorrect length, expected %u bytes got %zu bytes", - AKA_SIM_MAC_DIGEST_SIZE, mac->vp_length); - goto failure; - } - - slen = fr_aka_sim_crypto_sign_packet(calc_mac, eap_session->this_round->response, true, - eap_aka_sim_session->mac_md, - eap_aka_sim_session->keys.k_aut, eap_aka_sim_session->keys.k_aut_len, - eap_aka_sim_session->keys.reauth.nonce_s, - sizeof(eap_aka_sim_session->keys.reauth.nonce_s)); - if (slen < 0) { - RPEDEBUG("Failed calculating MAC"); - goto failure; - } else if (slen == 0) { - REDEBUG("Zero length AT_MAC attribute"); - goto failure; - } - - if (memcmp(mac->vp_octets, calc_mac, sizeof(calc_mac)) == 0) { - RDEBUG2("Received MAC matches calculated MAC"); - } else { - REDEBUG("Received MAC does not match calculated MAC"); - RHEXDUMP_INLINE2(mac->vp_octets, AKA_SIM_MAC_DIGEST_SIZE, "Received"); - RHEXDUMP_INLINE2(calc_mac, AKA_SIM_MAC_DIGEST_SIZE, "Expected"); - goto failure; - } - - /* - * If the peer doesn't include a checkcode then that - * means they don't support it, and we can't validate - * their view of the identity packets. - */ - checkcode = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_checkcode); - if (checkcode) { - if (checkcode->vp_length != eap_aka_sim_session->checkcode_len) { - REDEBUG("Received checkcode's length (%zu) does not match calculated checkcode's length (%zu)", - checkcode->vp_length, eap_aka_sim_session->checkcode_len); - goto failure; - } - - if (memcmp(checkcode->vp_octets, eap_aka_sim_session->checkcode, - eap_aka_sim_session->checkcode_len) == 0) { - RDEBUG2("Received checkcode matches calculated checkcode"); - } else { - REDEBUG("Received checkcode does not match calculated checkcode"); - RHEXDUMP_INLINE2(checkcode->vp_octets, checkcode->vp_length, "Received"); - RHEXDUMP_INLINE2(eap_aka_sim_session->checkcode, - eap_aka_sim_session->checkcode_len, "Expected"); - goto failure; - } - /* - * Only print something if we calculated a checkcode - */ - } else if (eap_aka_sim_session->checkcode_len > 0){ - RDEBUG2("Peer didn't include AT_CHECKCODE, skipping checkcode validation"); - } - - /* - * Check to see if the supplicant sent - * AT_COUNTER_TOO_SMALL, if they did then we - * clear out reauth information and enter the - * challenge state. - */ - if (fr_pair_find_by_da(from_peer, attr_eap_aka_sim_counter_too_small)) { - RWDEBUG("Peer sent AT_COUNTER_TOO_SMALL (indicating our AT_COUNTER value (%u) wasn't fresh)", - eap_aka_sim_session->keys.reauth.counter); - - fr_aka_sim_vector_umts_reauth_clear(&eap_aka_sim_session->keys); - eap_aka_sim_session->allow_encrypted = false; - - return aka_challenge_enter(p_result, mctx, request, eap_session); - } - - /* - * If the peer wants a Success notification, and - * we included AT_RESULT_IND then send a success - * notification, otherwise send a normal EAP-Success. - * - * RFC 4187 Section #6.2. Result Indications - */ - if (eap_aka_sim_session->send_result_ind) { - if (!fr_pair_find_by_da(from_peer, attr_eap_aka_sim_result_ind)) { - RDEBUG("We wanted to use protected result indications, but peer does not"); - eap_aka_sim_session->send_result_ind = false; - } else { - return common_success_notification_enter(p_result, mctx, request, eap_session); - } - } else if (fr_pair_find_by_da(from_peer, attr_eap_aka_sim_result_ind)) { - RDEBUG("Peer wanted to use protected result indications, but we do not"); - } - - eap_aka_sim_session->reauthentication_success = true; - - return common_eap_success_enter(p_result, mctx, request, eap_session); -} - -/** Process a EAP-Response/AKA-Challenge message - The response to our EAP-Request/AKA-Challenge message - * - * Verify that MAC, and RES match what we expect. - */ -static unlang_action_t aka_challenge_response_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session) -{ - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - - uint8_t calc_mac[AKA_SIM_MAC_DIGEST_SIZE]; - ssize_t slen; - fr_pair_t *vp = NULL, *mac, *checkcode; - fr_pair_list_t *from_peer = &request->request_pairs; - - mac = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_mac); - if (!mac) { - REDEBUG("Missing AT_MAC attribute"); - failure: - return common_failure_notification_enter(p_result, mctx, request, eap_session); - } - if (mac->vp_length != AKA_SIM_MAC_DIGEST_SIZE) { - REDEBUG("MAC has incorrect length, expected %u bytes got %zu bytes", - AKA_SIM_MAC_DIGEST_SIZE, mac->vp_length); - goto failure; - } - - slen = fr_aka_sim_crypto_sign_packet(calc_mac, eap_session->this_round->response, true, - eap_aka_sim_session->mac_md, - eap_aka_sim_session->keys.k_aut, eap_aka_sim_session->keys.k_aut_len, - NULL, 0); - if (slen < 0) { - RPEDEBUG("Failed calculating MAC"); - goto failure; - } else if (slen == 0) { - REDEBUG("Zero length AT_MAC attribute"); - goto failure; - } - - if (memcmp(mac->vp_octets, calc_mac, sizeof(calc_mac)) == 0) { - RDEBUG2("Received MAC matches calculated MAC"); - } else { - REDEBUG("Received MAC does not match calculated MAC"); - RHEXDUMP_INLINE2(mac->vp_octets, AKA_SIM_MAC_DIGEST_SIZE, "Received"); - RHEXDUMP_INLINE2(calc_mac, AKA_SIM_MAC_DIGEST_SIZE, "Expected"); - goto failure; - } - - /* - * If the peer doesn't include a checkcode then that - * means they don't support it, and we can't validate - * their view of the identity packets. - */ - if (eap_aka_sim_session->checkcode_md) { - checkcode = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_checkcode); - if (checkcode) { - if (checkcode->vp_length != eap_aka_sim_session->checkcode_len) { - REDEBUG("Received checkcode's length (%zu) does not match " - "calculated checkcode's length (%zu)", - checkcode->vp_length, eap_aka_sim_session->checkcode_len); - goto failure; - } - - if (memcmp(checkcode->vp_octets, - eap_aka_sim_session->checkcode, eap_aka_sim_session->checkcode_len) == 0) { - RDEBUG2("Received checkcode matches calculated checkcode"); - } else { - REDEBUG("Received checkcode does not match calculated checkcode"); - RHEXDUMP_INLINE2(checkcode->vp_octets, checkcode->vp_length, "Received"); - RHEXDUMP_INLINE2(eap_aka_sim_session->checkcode, - eap_aka_sim_session->checkcode_len, "Expected"); - goto failure; - } - /* - * Only print something if we calculated a checkcode - */ - } else if (eap_aka_sim_session->checkcode_len > 0){ - RDEBUG2("Peer didn't include AT_CHECKCODE, skipping checkcode validation"); - } - } - - vp = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_res); - if (!vp) { - REDEBUG("AT_RES missing from challenge response"); - goto failure; - } - - if (vp->vp_length != eap_aka_sim_session->keys.umts.vector.xres_len) { - REDEBUG("Received RES' length (%zu) does not match calculated XRES' length (%zu)", - vp->vp_length, eap_aka_sim_session->keys.umts.vector.xres_len); - goto failure; - } - - if (memcmp(vp->vp_octets, eap_aka_sim_session->keys.umts.vector.xres, vp->vp_length)) { - REDEBUG("Received RES does not match calculated XRES"); - RHEXDUMP_INLINE2(vp->vp_octets, vp->vp_length, "RES :"); - RHEXDUMP_INLINE2(eap_aka_sim_session->keys.umts.vector.xres, - eap_aka_sim_session->keys.umts.vector.xres_len, "XRES :"); - goto failure; - } - - RDEBUG2("Received RES matches calculated XRES"); - - eap_aka_sim_session->challenge_success = true; - - /* - * If the peer wants a Success notification, and - * we included AT_RESULT_IND then send a success - * notification, otherwise send a normal EAP-Success. - * - * RFC 4187 Section #6.2. Result Indications - */ - if (eap_aka_sim_session->send_result_ind) { - if (!fr_pair_find_by_da(from_peer, attr_eap_aka_sim_result_ind)) { - RDEBUG("We wanted to use protected result indications, but peer does not"); - eap_aka_sim_session->send_result_ind = false; - } else { - return common_success_notification_enter(p_result, mctx, request, eap_session); - } - } else if (fr_pair_find_by_da(from_peer, attr_eap_aka_sim_result_ind)) { - RDEBUG("Peer wanted to use protected result indications, but we do not"); - } - - return common_eap_success_enter(p_result, mctx, request, eap_session); -} - -/** Process a EAP-Response/SIM-Challenge message - The response to our EAP-Request/SIM-Challenge message - * - * Verify that MAC, and RES match what we expect. - */ -static unlang_action_t sim_challenge_response_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session) -{ - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - - uint8_t sres_cat[AKA_SIM_VECTOR_GSM_SRES_SIZE * 3]; - uint8_t *p = sres_cat; - - uint8_t calc_mac[AKA_SIM_MAC_DIGEST_SIZE]; - ssize_t slen; - fr_pair_t *mac; - fr_pair_list_t *from_peer = &request->request_pairs; - - memcpy(p, eap_aka_sim_session->keys.gsm.vector[0].sres, AKA_SIM_VECTOR_GSM_SRES_SIZE); - p += AKA_SIM_VECTOR_GSM_SRES_SIZE; - memcpy(p, eap_aka_sim_session->keys.gsm.vector[1].sres, AKA_SIM_VECTOR_GSM_SRES_SIZE); - p += AKA_SIM_VECTOR_GSM_SRES_SIZE; - memcpy(p, eap_aka_sim_session->keys.gsm.vector[2].sres, AKA_SIM_VECTOR_GSM_SRES_SIZE); - - mac = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_mac); - if (!mac) { - REDEBUG("Missing AT_MAC attribute"); - failure: - return common_failure_notification_enter(p_result, mctx, request, eap_session); - } - if (mac->vp_length != AKA_SIM_MAC_DIGEST_SIZE) { - REDEBUG("MAC has incorrect length, expected %u bytes got %zu bytes", - AKA_SIM_MAC_DIGEST_SIZE, mac->vp_length); - goto failure; - } - - slen = fr_aka_sim_crypto_sign_packet(calc_mac, eap_session->this_round->response, true, - eap_aka_sim_session->mac_md, - eap_aka_sim_session->keys.k_aut, eap_aka_sim_session->keys.k_aut_len, - sres_cat, sizeof(sres_cat)); - if (slen < 0) { - RPEDEBUG("Failed calculating MAC"); - goto failure; - } else if (slen == 0) { - REDEBUG("Zero length AT_MAC attribute"); - goto failure; - } - - if (memcmp(mac->vp_octets, calc_mac, sizeof(calc_mac)) == 0) { - RDEBUG2("Received MAC matches calculated MAC"); - } else { - REDEBUG("Received MAC does not match calculated MAC"); - RHEXDUMP_INLINE2(mac->vp_octets, AKA_SIM_MAC_DIGEST_SIZE, "Received"); - RHEXDUMP_INLINE2(calc_mac, AKA_SIM_MAC_DIGEST_SIZE, "Expected"); - goto failure; - } - - eap_aka_sim_session->challenge_success = true; - - /* - * If the peer wants a Success notification, and - * we included AT_RESULT_IND then send a success - * notification, otherwise send a normal EAP-Success. - */ - if (eap_aka_sim_session->send_result_ind) { - if (!fr_pair_find_by_da(from_peer, attr_eap_aka_sim_result_ind)) { - RDEBUG("We wanted to use protected result indications, but peer does not"); - eap_aka_sim_session->send_result_ind = false; - } else { - return common_success_notification_enter(p_result, mctx, request, eap_session); - } - } else if (fr_pair_find_by_da(from_peer, attr_eap_aka_sim_result_ind)) { - RDEBUG("Peer wanted to use protected result indications, but we do not"); - } - - return common_eap_success_enter(p_result, mctx, request, eap_session); -} - -/** Process a EAP-Response/AKA-Identity message i.e. an EAP-AKA Identity-Response - * - * - If the message does not contain AT_IDENTITY, then enter the FAILURE-NOTIFICATION state. - * - If the user requested another identity, re-enter the AKA-Identity sate. - * - ...or continue based on the value of &Identity-Type which was added by #aka_identity, - * and possibly modified by the user. - * - Fastauth - Enter the REAUTHENTICATION state. - * - Pseudonym - Call 'load pseudonym { ... }' - * - Permanent - Enter the CHALLENGE state. - */ -static unlang_action_t aka_identity_response_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session) -{ - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - bool user_set_id_req; - fr_pair_t *identity_type; - fr_pair_list_t *from_peer = &request->request_pairs; - /* - * Digest the identity response - */ - if (eap_aka_sim_session->checkcode_md) { - if (fr_aka_sim_crypto_update_checkcode(eap_aka_sim_session->checkcode_state, - eap_session->this_round->response) < 0) { - RPEDEBUG("Failed updating checkcode"); - failure: - return common_failure_notification_enter(p_result, mctx, request, eap_session); - } - } - - /* - * See if the user wants us to request another - * identity. - * - * If they set one themselves don't override - * what they set. - */ - user_set_id_req = identity_req_set_by_user(request, eap_aka_sim_session); - if ((request->rcode == RLM_MODULE_NOTFOUND) || user_set_id_req) { - if (!user_set_id_req) { - switch (eap_aka_sim_session->last_id_req) { - case AKA_SIM_ANY_ID_REQ: - eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ; - break; - - case AKA_SIM_FULLAUTH_ID_REQ: - eap_aka_sim_session->id_req = AKA_SIM_PERMANENT_ID_REQ; - break; - - case AKA_SIM_NO_ID_REQ: /* Should not happen */ - fr_assert(0); - FALL_THROUGH; - - case AKA_SIM_PERMANENT_ID_REQ: - REDEBUG("Peer sent no usable identities"); - goto failure; - - } - RDEBUG2("Previous section returned (%s), requesting next most permissive identity (%s)", - fr_table_str_by_value(rcode_table, request->rcode, ""), - fr_table_str_by_value(fr_aka_sim_id_request_table, - eap_aka_sim_session->id_req, "")); - } - return aka_identity_enter(p_result, mctx, request, eap_session); - } - - /* - * If the identity looks like a fast re-auth id - * run fast re-auth, otherwise do fullauth. - */ - identity_type = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_identity_type); - if (identity_type) switch (identity_type->vp_uint32) { - case FR_IDENTITY_TYPE_VALUE_FASTAUTH: - return common_reauthentication_enter(p_result, mctx, request, eap_session); - - /* - * It's a pseudonym, which now needs resolving. - * The resume function here calls aka_challenge_enter - * if pseudonym resolution went ok. - */ - case FR_IDENTITY_TYPE_VALUE_PSEUDONYM: - return unlang_module_yield_to_section(p_result, - request, - inst->actions.load_pseudonym, - RLM_MODULE_NOOP, - pseudonym_load_resume, - mod_signal, - (void *)aka_challenge_enter); - default: - break; - } - - return aka_challenge_enter(p_result, mctx, request, eap_session); -} - -/** Helper function to check for the presence and length of AT_SELECTED_VERSION and copy its value into the keys structure - * - * Also checks the version matches one of the ones we advertised in our version list, - * which is a bit redundant seeing as there's only one version of EAP-SIM. - */ -static int sim_start_selected_version_check(request_t *request, fr_pair_list_t *from_peer, - eap_aka_sim_session_t *eap_aka_sim_session) -{ - fr_pair_t *selected_version_vp; - - /* - * Check that we got an AT_SELECTED_VERSION - */ - selected_version_vp = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_selected_version); - if (!selected_version_vp) { - REDEBUG("EAP-Response/SIM/Start does not contain AT_SELECTED_VERSION"); - return -1; - } - - /* - * See if the selected version was in our list - */ - { - uint8_t selected_version[2]; - uint8_t *p, *end; - bool found = false; - - p = eap_aka_sim_session->keys.gsm.version_list; - end = p + eap_aka_sim_session->keys.gsm.version_list_len; - - selected_version[0] = (selected_version_vp->vp_uint16 & 0xff00) >> 8; - selected_version[1] = (selected_version_vp->vp_uint16 & 0x00ff); - - while (p < end) { - if ((p[0] == selected_version[0]) && (p[1] == selected_version[1])) { - found = true; - /* - * Update our keying material - */ - eap_aka_sim_session->keys.gsm.version_select[0] = selected_version[0]; - eap_aka_sim_session->keys.gsm.version_select[1] = selected_version[1]; - break; - } - } - - if (!found) { - REDEBUG("AT_SELECTED_VERSION (%u) does not match a value in our version list", - selected_version_vp->vp_uint16); - return -1; - } - } - - return 0; -} - -/** Helper function to check for the presence and length of AT_NONCE_MT and copy its value into the keys structure - * - * Does not actually perform cryptographic validation of AT_NONCE_MT, this is done later. - */ -static int sim_start_nonce_mt_check(request_t *request, fr_pair_list_t *from_peer, - eap_aka_sim_session_t *eap_aka_sim_session) -{ - fr_pair_t *nonce_mt_vp; - - /* - * Copy nonce_mt to the keying material - */ - nonce_mt_vp = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_nonce_mt); - if (!nonce_mt_vp) { - REDEBUG("EAP-Response/SIM/Start does not contain AT_NONCE_MT"); - return -1; - } - - if (nonce_mt_vp->vp_length != sizeof(eap_aka_sim_session->keys.gsm.nonce_mt)) { - REDEBUG("AT_NONCE_MT must be exactly %zu bytes, not %zu bytes", - sizeof(eap_aka_sim_session->keys.gsm.nonce_mt), nonce_mt_vp->vp_length); - return -1; - } - memcpy(eap_aka_sim_session->keys.gsm.nonce_mt, nonce_mt_vp->vp_octets, - sizeof(eap_aka_sim_session->keys.gsm.nonce_mt)); + case FR_SUBTYPE_VALUE_AKA_SYNCHRONIZATION_FAILURE: + { + uint64_t new_sqn; - return 0; -} + eap_aka_sim_session->allow_encrypted = false; -/** Process a EAP-Response/SIM-Start message i.e. an EAP-SIM Identity-Response - * - * - If the message does not contain AT_IDENTITY, then enter the FAILURE-NOTIFICATION state. - * - If the user requested another identity, re-enter the SIM-START sate. - * - ...or continue based on the value of &Identity-Type which was added by #sim_start, - * and possibly modified by the user. - * - Fastauth - * - If AT_NONCE_MT or AT_SELECTED_VERSION are present, enter the FAILURE-NOTIFICATION state. - * - ...or enter the REAUTHENTICATION state. - * - Pseudonym - Verify selected version and AT_NONCE_MT, then call 'load pseudonym { ... }' - * - Permanent - Verify selected version and AT_NONCE_MT, then enter the CHALLENGE state. - */ -static unlang_action_t sim_start_response_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, eap_session_t *eap_session) -{ - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - bool user_set_id_req; - fr_pair_t *identity_type; + vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_auts); + if (!vp) { + REDEBUG("EAP-Response/AKA-Synchronisation-Failure missing AT_AUTS"); + failure: + return STATE_TRANSITION(common_failure_notification); + } - fr_pair_list_t *from_peer = &request->request_pairs; + switch (fr_aka_sim_umts_resync_from_attrs(&new_sqn, + request, vp, &eap_aka_sim_session->keys)) { + /* + * Add everything back that we'll need in the + * next challenge round. + */ + case 0: + MEM(pair_append_control(&vp, attr_sim_sqn) >= 0); + vp->vp_uint64 = new_sqn; - /* - * See if the user wants us to request another - * identity. - * - * If they set one themselves don't override - * what they set. - */ - user_set_id_req = identity_req_set_by_user(request, eap_aka_sim_session); - if ((request->rcode == RLM_MODULE_NOTFOUND) || user_set_id_req) { - if (!user_set_id_req) { - switch (eap_aka_sim_session->last_id_req) { - case AKA_SIM_ANY_ID_REQ: - eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ; - break; + MEM(pair_append_control(&vp, attr_sim_ki) >= 0); + fr_pair_value_memdup(vp, eap_aka_sim_session->keys.auc.ki, + sizeof(eap_aka_sim_session->keys.auc.ki), false); - case AKA_SIM_FULLAUTH_ID_REQ: - eap_aka_sim_session->id_req = AKA_SIM_PERMANENT_ID_REQ; - break; + MEM(pair_append_control(&vp, attr_sim_opc) >= 0); + fr_pair_value_memdup(vp, eap_aka_sim_session->keys.auc.opc, + sizeof(eap_aka_sim_session->keys.auc.opc), false); + break; - case AKA_SIM_NO_ID_REQ: /* Should not happen */ - fr_assert(0); - FALL_THROUGH; + case 1: /* Don't have Ki or OPc so something else will need to deal with this */ + break; - case AKA_SIM_PERMANENT_ID_REQ: - REDEBUG("Peer sent no usable identities"); - failure: - return common_failure_notification_enter(p_result, mctx, request, eap_session); - } - RDEBUG2("Previous section returned (%s), requesting next most permissive identity (%s)", - fr_table_str_by_value(rcode_table, request->rcode, ""), - fr_table_str_by_value(fr_aka_sim_id_request_table, - eap_aka_sim_session->id_req, "")); + default: + case -1: + goto failure; } - return sim_start_enter(p_result, mctx, request, eap_session); + + return CALL_SECTION(recv_aka_syncronization_failure); } /* - * If the identity looks like a fast re-auth id - * run fast re-auth, otherwise do fullauth. + * Case 1 where we're allowed to send an EAP-Failure */ - identity_type = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_identity_type); - if (identity_type) switch (identity_type->vp_uint32) { - case FR_IDENTITY_TYPE_VALUE_FASTAUTH: - /* - * RFC 4186 Section #9.2 - * - * The AT_NONCE_MT attribute MUST NOT be included if the AT_IDENTITY - * with a fast re-authentication identity is present for fast - * re-authentication - */ - if (fr_pair_find_by_da(from_peer, attr_eap_aka_sim_nonce_mt)) { - REDEBUG("AT_NONCE_MT is not allowed in EAP-Response/SIM-Reauthentication messages"); - return common_failure_notification_enter(p_result, mctx, request, eap_session); - } - - /* - * RFC 4186 Section #9.2 - * - * The AT_SELECTED_VERSION attribute MUST NOT be included if the - * AT_IDENTITY attribute with a fast re-authentication identity is - * present for fast re-authentication. - */ - if (fr_pair_find_by_da(from_peer, attr_eap_aka_sim_selected_version)) { - REDEBUG("AT_SELECTED_VERSION is not allowed in EAP-Response/SIM-Reauthentication messages"); - return common_failure_notification_enter(p_result, mctx, request, eap_session); - } - - return common_reauthentication_enter(p_result, mctx, request, eap_session); + case FR_SUBTYPE_VALUE_AKA_SIM_CLIENT_ERROR: + client_error_debug(request); - /* - * It's a pseudonym, which now needs resolving. - * The resume function here calls aka_challenge_enter - * if pseudonym resolution went ok. - */ - case FR_IDENTITY_TYPE_VALUE_PSEUDONYM: - if (sim_start_selected_version_check(request, from_peer, eap_aka_sim_session) < 0) goto failure; - if (sim_start_nonce_mt_check(request, from_peer, eap_aka_sim_session) < 0) goto failure; + eap_aka_sim_session->allow_encrypted = false; - return unlang_module_yield_to_section(p_result, - request, - inst->actions.load_pseudonym, - RLM_MODULE_NOOP, - pseudonym_load_resume, - mod_signal, - (void *)sim_challenge_enter); + return CALL_SECTION(recv_common_client_error); /* - * If it's a permanent ID, copy it over to - * the session state list for use in the - * store pseudonym/store session sections - * later. + * RFC 4187 says we *MUST* notify, not just + * send an EAP-Failure in this case. */ - case FR_IDENTITY_TYPE_VALUE_PERMANENT: - if (sim_start_selected_version_check(request, from_peer, eap_aka_sim_session) < 0) goto failure; - if (sim_start_nonce_mt_check(request, from_peer, eap_aka_sim_session) < 0) goto failure; - - FALL_THROUGH; default: - break; + REDEBUG("Unexpected subtype %pV", &subtype_vp->data); + fail: + eap_aka_sim_session->allow_encrypted = false; + goto failure; } - - return sim_challenge_enter(p_result, mctx, request, eap_session); } -/** Resume after 'recv Failure-Notification-Ack { ... }' +/** Resume after 'send Challenge-Request { ... }' * - * - Enter the EAP-FAILURE state. */ -static unlang_action_t common_failure_notification_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, UNUSED void *rctx) +RESUME(send_aka_challenge_request) { - eap_session_t *eap_session = eap_session_get(request->parent); - - section_rcode_ignored(request); - - /* - * Case 2 where we're allowed to send an EAP-Failure - */ - return common_eap_failure_enter(p_result, mctx, request, eap_session); -} + eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t); + fr_pair_t *vp; + fr_aka_sim_vector_src_t src = AKA_SIM_VECTOR_SRC_AUTO; -/** Resume after 'recv Success-Notification-Ack { ... }' - * - * - Enter the EAP-SUCCESS state. - */ -static unlang_action_t common_success_notification_ack_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, UNUSED void *rctx) -{ - eap_session_t *eap_session = eap_session_get(request->parent); + fr_pair_t *kdf_id; - section_rcode_ignored(request); + SECTION_RCODE_PROCESS; /* - * RFC 4187 says we ignore the contents of the - * next packet after we send our success notification - * and always send a success. + * Allow override of KDF Identity + * + * Because certain handset manufacturers don't + * implement RFC 4187 correctly and use the + * wrong identity as input the the PRF/KDF. */ - return common_eap_success_enter(p_result, mctx, request, eap_session); -} - -/** Resume after 'recv Challenge-Response { ... }' - * - * - If the previous section returned a failure rcode, enter the FAILURE-NOTIFICATION state. - * - ...or call a function to process the contents of the AKA-Challenge message. - */ -static unlang_action_t aka_challenge_response_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, UNUSED void *rctx) -{ - eap_session_t *eap_session = eap_session_get(request->parent); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - - section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session); - - return aka_challenge_response_process(p_result, mctx,request, eap_session); -} - -/** Resume after 'recv Challenge-Response { ... }' - * - * - If the previous section returned a failure rcode, enter the FAILURE-NOTIFICATION state. - * - ...or call a function to process the contents of the SIM-Challenge message. - */ -static unlang_action_t sim_challenge_response_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, UNUSED void *rctx) -{ - eap_session_t *eap_session = eap_session_get(request->parent); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - - section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session); - - return sim_challenge_response_process(p_result, mctx,request, eap_session); -} - -/** Resume after 'recv Identity-Response { ... }' or 'recv AKA-Identity { ... }' - * - * - If the previous section returned a failure rcode, enter the FAILURE-NOTIFICATION state. - * - ...or call a function to process the contents of the AKA-Identity message, mainly the AT_IDENTITY value. - */ -static unlang_action_t aka_identity_response_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, UNUSED void *rctx) -{ - eap_session_t *eap_session = eap_session_get(request->parent); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - - section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session); - - return aka_identity_response_process(p_result, mctx,request, eap_session); -} - -/** Resume after 'recv Identity-Response { ... }' or 'recv SIM-Start { ... }' - * - * - If the previous section returned a failure rcode, enter the FAILURE-NOTIFICATION state. - * - ...or call a function to process the contents of the SIM-Start message, mainly the AT_IDENTITY value. - */ -static unlang_action_t sim_start_response_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, UNUSED void *rctx) -{ - eap_session_t *eap_session = eap_session_get(request->parent); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); + kdf_id = fr_pair_find_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity); + if (kdf_id) { + crypto_identity_set(request, eap_aka_sim_session, + (uint8_t const *)kdf_id->vp_strvalue, kdf_id->vp_length); + fr_pair_delete_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity); + } - section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session); + RDEBUG2("Acquiring UMTS vector(s)"); - return sim_start_response_process(p_result, mctx,request, eap_session); -} + if (eap_aka_sim_session->type == FR_EAP_METHOD_AKA_PRIME) { + /* + * Copy the network name the user specified for + * key derivation purposes. + */ + vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_kdf_input); + if (vp) { + talloc_free(eap_aka_sim_session->keys.network); + eap_aka_sim_session->keys.network = talloc_memdup(eap_aka_sim_session, + (uint8_t const *)vp->vp_strvalue, + vp->vp_length); + eap_aka_sim_session->keys.network_len = vp->vp_length; + } else { + REDEBUG("No network name available, can't set AT_KDF_INPUT"); + failure: + return STATE_TRANSITION(common_failure_notification); + } -/** Resume after 'recv Authentication-Reject { ... }' - * - * - Enter the FAILURE-NOTIFICATION state. - */ -static unlang_action_t aka_authentication_reject_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, UNUSED void *rctx) -{ - eap_session_t *eap_session = eap_session_get(request->parent); + /* + * We don't allow the user to specify + * the KDF currently. + */ + MEM(pair_update_reply(&vp, attr_eap_aka_sim_kdf) >= 0); + vp->vp_uint16 = eap_aka_sim_session->kdf; + } - section_rcode_ignored(request); + /* + * Get vectors from attribute or generate + * them using COMP128-* or Milenage. + */ + if (fr_aka_sim_vector_umts_from_attrs(request, &request->control_pairs, + &eap_aka_sim_session->keys, &src) != 0) { + REDEBUG("Failed retrieving UMTS vectors"); + goto failure; + } /* - * Case 2 where we're allowed to send an EAP-Failure + * Don't leave the AMF hanging around */ - return common_eap_failure_enter(p_result, mctx, request, eap_session); -} + if (eap_aka_sim_session->type == FR_EAP_METHOD_AKA_PRIME) pair_delete_control(attr_sim_amf); -/** Resume after 'recv Synchronization-Failure { ... }' - * - * - If 'recv Synchronization-Failure { ... }' returned a failure - * rcode, enter the FAILURE-NOTIFICATION state. - * - ...or if no 'recv Syncronization-Failure { ... }' section was - * defined, then enter the FAILURE-NOTIFICATION state. - * - ...or if the user didn't provide a new SQN value in &control.SQN - * then enter the FAILURE-NOTIFICATION state. - * - ...or enter the AKA-CHALLENGE state. - */ -static unlang_action_t aka_synchronization_failure_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, UNUSED void *rctx) -{ - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t); - eap_session_t *eap_session = eap_session_get(request->parent); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - fr_pair_t *vp; + /* + * All set, calculate keys! + */ + switch (eap_aka_sim_session->type) { + default: + case FR_EAP_METHOD_SIM: + fr_assert(0); /* EAP-SIM has its own Challenge state */ + break; + + case FR_EAP_METHOD_AKA: + fr_aka_sim_crypto_umts_kdf_0(&eap_aka_sim_session->keys); + break; + + case FR_EAP_METHOD_AKA_PRIME: + switch (eap_aka_sim_session->kdf) { + case FR_KDF_VALUE_PRIME_WITH_CK_PRIME_IK_PRIME: + fr_aka_sim_crypto_umts_kdf_1(&eap_aka_sim_session->keys); + break; - section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session); + default: + fr_assert(0); + break; + } + } + if (RDEBUG_ENABLED3) fr_aka_sim_crypto_keys_log(request, &eap_aka_sim_session->keys); /* - * If there's no section to handle this, then no resynchronisation - * can't have occurred and we just send a reject. + * Indicate we'd like to use protected success messages + * with AT_RESULT_IND * - * Similarly, if we've already received one synchronisation failure - * then it's highly likely whatever user configured action was - * configured was unsuccessful, and we should just give up. + * Use our default, but allow user override too. */ - if (!inst->actions.aka.recv_syncronization_failure || eap_aka_sim_session->prev_recv_sync_failure) { - failure: - return common_failure_notification_enter(p_result, mctx, request, eap_session); - } + vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_result_ind); + if (vp) eap_aka_sim_session->send_result_ind = vp->vp_bool; /* - * We couldn't generate an SQN and the user didn't provide one, - * so we need to fail. + * These attributes are only allowed with + * EAP-AKA', protect users from themselves. */ - vp = fr_pair_find_by_da(&request->control_pairs, attr_sim_sqn); - if (!vp) { - REDEBUG("No &control.SQN value provided after resynchronisation, cannot continue"); - goto failure; + if (eap_aka_sim_session->type == FR_EAP_METHOD_AKA) { + pair_delete_reply(attr_eap_aka_sim_kdf_input); + pair_delete_reply(attr_eap_aka_sim_kdf); } /* - * RFC 4187 Section #6.3.1 - * - * "if the peer detects that the - * sequence number in AUTN is not correct, the peer responds with - * EAP-Response/AKA-Synchronization-Failure (Section 9.6), and the - * server proceeds with a new EAP-Request/AKA-Challenge." + * Okay, we got the challenge! Put it into an attribute. */ - return aka_challenge_enter(p_result, mctx, request, eap_session); -} + MEM(pair_update_reply(&vp, attr_eap_aka_sim_rand) >= 0); + fr_pair_value_memdup(vp, eap_aka_sim_session->keys.umts.vector.rand, AKA_SIM_VECTOR_UMTS_RAND_SIZE, false); -/** Resume after 'recv Client-Error { ... }' - * - * - Enter the EAP-FAILURE state. - */ -static unlang_action_t common_client_error_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, UNUSED void *rctx) -{ - eap_session_t *eap_session = eap_session_get(request->parent); + /* + * Send the AUTN value to the client, so it can authenticate + * whoever has knowledge of the Ki. + */ + MEM(pair_update_reply(&vp, attr_eap_aka_sim_autn) >= 0); + fr_pair_value_memdup(vp, eap_aka_sim_session->keys.umts.vector.autn, AKA_SIM_VECTOR_UMTS_AUTN_SIZE, false); + + /* + * need to include an AT_MAC attribute so that it will get + * calculated. + */ + MEM(pair_update_reply(&vp, attr_eap_aka_sim_mac) >= 0); + fr_pair_value_memdup(vp, NULL, 0, false); - section_rcode_ignored(request); +#if 0 + /* + * If we have checkcode data, send that to the peer + * in AT_CHECKCODE for validation. + */ + if (eap_aka_sim_session->checkcode_state) { + ssize_t slen; - return common_eap_failure_enter(p_result, mctx, request, eap_session); -} + slen = fr_aka_sim_crypto_finalise_checkcode(eap_aka_sim_session->checkcode, + &eap_aka_sim_session->checkcode_state); + if (slen < 0) { + RPEDEBUG("Failed calculating checkcode"); + goto failure; + } + eap_aka_sim_session->checkcode_len = slen; -/** Resume after 'recv Reauthentication-Response { ... }' - * - * - If 'recv Reauthentication-Response { ... }' returned a failure - * rcode, enter the FAILURE-NOTIFICATION state. - * - ...or call the EAP-Request/Reauthentication-Response function to act on the - * contents of the response. - */ -static unlang_action_t common_reauthentication_response_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, UNUSED void *rctx) -{ - eap_session_t *eap_session = eap_session_get(request->parent); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session); + MEM(pair_update_reply(&vp, attr_eap_aka_sim_checkcode) >= 0); + fr_pair_value_memdup(vp, eap_aka_sim_session->checkcode, slen, false); + /* + * If we don't have checkcode data, then we exchanged + * no identity packets, so AT_CHECKCODE is zero. + */ + } else { + MEM(pair_update_reply(&vp, attr_eap_aka_sim_checkcode) >= 0); + fr_pair_value_memdup(vp, NULL, 0, false); + eap_aka_sim_session->checkcode_len = 0; + } +#endif + + /* + * We've sent the challenge so the peer should now be able + * to accept encrypted attributes. + */ + eap_aka_sim_session->allow_encrypted = true; - return common_reauthentication_response_process(p_result, mctx,request, eap_session); + return session_and_pseudonym_store(p_result, mctx, request, eap_aka_sim_session, aka_challenge_request_send); } -/** Decode the peer's response +/** Enter the AKA-CHALLENGE state * - * This is called by the state_* functions to decode the peer's response. */ -static unlang_action_t common_decode(fr_pair_t **subtype_vp, fr_pair_list_t *vps, - rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request) +STATE_GUARD(aka_challenge) { - eap_session_t *eap_session = eap_session_get(request->parent); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - - fr_aka_sim_decode_ctx_t ctx = { - .keys = &eap_aka_sim_session->keys, - }; - fr_pair_t *aka_vp; - fr_pair_list_t aka_vps; - fr_dcursor_t cursor; + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); + fr_pair_t *vp; - int ret; + /* + * If we've sent either of these identities it + * means we've come here form a Reauthentication-Request + * that failed. + */ + if (eap_aka_sim_session->pseudonym_sent || eap_aka_sim_session->fastauth_sent) { + return session_and_pseudonym_clear(p_result, mctx, request, + eap_aka_sim_session, guard_aka_challenge); + /* come back when we're done */ + } - fr_pair_list_init(&aka_vps); - fr_dcursor_init(&cursor, &request->request_pairs); - fr_dcursor_tail(&cursor); + STATE_SET(aka_challenge); - ret = fr_aka_sim_decode(request, - &cursor, - dict_eap_aka_sim, - eap_session->this_round->response->type.data, - eap_session->this_round->response->type.length, - &ctx); /* - * RFC 4187 says we *MUST* notify, not just - * send an EAP-Failure in this case where - * we cannot decode an EAP-AKA packet. + * Set some default attributes, giving the user a + * chance to modify them. */ - if (ret < 0) { - RPEDEBUG2("Failed decoding attributes"); - failure: - return common_failure_notification_enter(p_result, mctx, request, eap_session); - } - /* vps is the data from the client */ - aka_vp = fr_dcursor_next(&cursor); - fr_pair_sublist_copy(request, &aka_vps, &request->request_pairs, aka_vp); - if (!fr_pair_list_empty(&aka_vps) && RDEBUG_ENABLED2) { - RDEBUG2("Decoded attributes"); - log_request_pair_list(L_DBG_LVL_2, request, NULL, &aka_vps, NULL); + switch (eap_aka_sim_session->type) { + case FR_EAP_METHOD_AKA_PRIME: + { + uint8_t amf_buff[2] = { 0x80, 0x00 }; /* Set the AMF separation bit high */ + + /* + * Toggle the AMF high bit to indicate we're doing AKA' + */ + MEM(pair_update_control(&vp, attr_sim_amf) >= 0); + fr_pair_value_memdup(vp, amf_buff, sizeof(amf_buff), false); + + /* + * Use the default network name we have configured + * and send it to the peer. + */ + if (inst->network_name && + !fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_kdf_input)) { + MEM(pair_append_reply(&vp, attr_eap_aka_sim_kdf_input) >= 0); + fr_pair_value_bstrdup_buffer(vp, inst->network_name, false); + } } + break; + + default: + break; - *subtype_vp = fr_pair_find_by_da(&aka_vps, attr_eap_aka_sim_subtype); - if (!*subtype_vp) { - REDEBUG("Missing AT_SUBTYPE"); - goto failure; } - fr_tmp_pair_list_move(vps, &aka_vps); - RDEBUG2("Received EAP-Response/%pV", &(*subtype_vp)->data); + /* + * Set the defaults for protected result indicator + */ + if (eap_aka_sim_session->send_result_ind && + !fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_result_ind)) { + MEM(pair_append_reply(&vp, attr_eap_aka_sim_result_ind) >= 0); + vp->vp_bool = true; + } - RETURN_MODULE_OK; + return CALL_SECTION(send_aka_challenge_request); } -/** FAILURE state - State machine exit point after sending EAP-Failure +/** Resume after 'recv Challenge-Response { ... }' * - * Should never actually be called. Is just a placeholder function to represent the FAILURE - * termination state. Could equally be a NULL pointer, but then on a logic error - * we'd get a SEGV instead of a more friendly assert/failure rcode. - */ -static unlang_action_t common_eap_failure(rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx, - UNUSED request_t *request) -{ - fr_assert(0); /* Should never actually be called */ - RETURN_MODULE_FAIL; -} - -/** FAILURE-NOTIFICATION state - Continue the state machine after receiving a response to our EAP-Request/(AKA|SIM)-Notification + * - If the previous section returned a failure rcode, enter the FAILURE-NOTIFICATION state. + * - ...or call a function to process the contents of the SIM-Challenge message. * - * - Continue based on received AT_SUBTYPE value: - * - EAP-Response/SIM-Client-Error - Call 'recv Failure-Notification-Ack { ... }' - * - Anything else, enter the FAILURE-NOTIFICATION state. + * Verify that MAC, and RES match what we expect. */ -static unlang_action_t common_failure_notification(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request) +RESUME(recv_sim_challenge_response) { - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t); - eap_session_t *eap_session = eap_session_get(request->parent); + eap_session_t *eap_session = eap_session_get(request->parent); + eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t); + uint8_t sres_cat[AKA_SIM_VECTOR_GSM_SRES_SIZE * 3]; + uint8_t *p = sres_cat; - fr_pair_t *subtype_vp = NULL; - fr_pair_list_t vps; + uint8_t calc_mac[AKA_SIM_MAC_DIGEST_SIZE]; + ssize_t slen; + fr_pair_t *mac; - fr_pair_list_init(&vps); - common_decode(&subtype_vp, &vps, p_result, mctx, request); - if (*p_result != RLM_MODULE_OK) return UNLANG_ACTION_CALCULATE_RESULT; + SECTION_RCODE_PROCESS; -#ifdef __clang_analyzer__ - if (!subtype_vp) { - RETURN_MODULE_FAIL; + memcpy(p, eap_aka_sim_session->keys.gsm.vector[0].sres, AKA_SIM_VECTOR_GSM_SRES_SIZE); + p += AKA_SIM_VECTOR_GSM_SRES_SIZE; + memcpy(p, eap_aka_sim_session->keys.gsm.vector[1].sres, AKA_SIM_VECTOR_GSM_SRES_SIZE); + p += AKA_SIM_VECTOR_GSM_SRES_SIZE; + memcpy(p, eap_aka_sim_session->keys.gsm.vector[2].sres, AKA_SIM_VECTOR_GSM_SRES_SIZE); + + mac = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_mac); + if (!mac) { + REDEBUG("Missing AT_MAC attribute"); + failure: + return STATE_TRANSITION(common_failure_notification); + } + if (mac->vp_length != AKA_SIM_MAC_DIGEST_SIZE) { + REDEBUG("MAC has incorrect length, expected %u bytes got %zu bytes", + AKA_SIM_MAC_DIGEST_SIZE, mac->vp_length); + goto failure; } -#endif - switch (subtype_vp->vp_uint16) { - case FR_SUBTYPE_VALUE_AKA_SIM_NOTIFICATION: - RDEBUG2("Failure-Notification ACKed, sending EAP-Failure"); - return unlang_module_yield_to_section(p_result, - request, - inst->actions.recv_failure_notification_ack, - RLM_MODULE_NOOP, - common_failure_notification_recv_resume, - mod_signal, - NULL); + // TODO: MAC validation should be in the decoder + slen = fr_aka_sim_crypto_sign_packet(calc_mac, eap_session->this_round->response, true, + eap_aka_sim_session->mac_md, + eap_aka_sim_session->keys.k_aut, eap_aka_sim_session->keys.k_aut_len, + sres_cat, sizeof(sres_cat)); + if (slen < 0) { + RPEDEBUG("Failed calculating MAC"); + goto failure; + } else if (slen == 0) { + REDEBUG("Zero length AT_MAC attribute"); + goto failure; + } - default: - RWDEBUG("Failure-Notification not ACKed correctly, sending EAP-Failure anyway"); - return common_eap_failure_enter(p_result, mctx, request, eap_session); + if (memcmp(mac->vp_octets, calc_mac, sizeof(calc_mac)) == 0) { + RDEBUG2("Received MAC matches calculated MAC"); + } else { + REDEBUG("Received MAC does not match calculated MAC"); + RHEXDUMP_INLINE2(mac->vp_octets, AKA_SIM_MAC_DIGEST_SIZE, "Received"); + RHEXDUMP_INLINE2(calc_mac, AKA_SIM_MAC_DIGEST_SIZE, "Expected"); + goto failure; } -} -/** SUCCESS state - State machine exit point after sending EAP-Success - * - * Should never actually be called. Is just a placeholder function to represent the FAILURE - * termination state. Could equally be a NULL pointer, but then on a logic error - * we'd get a SEGV instead of a more friendly assert/failure rcode. - */ -static unlang_action_t common_eap_success(rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx, - UNUSED request_t *request) -{ - fr_assert(0); - RETURN_MODULE_FAIL; -} + eap_aka_sim_session->challenge_success = true; -/** SUCCESS-NOTIFICATION state - Continue the state machine after receiving a response to our EAP-Request/(AKA|SIM)-Notification - * - * - Call 'recv Success-Notification-Ack { ... }' - */ -static unlang_action_t common_success_notification(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request) -{ - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t); + /* + * If the peer wants a Success notification, and + * we included AT_RESULT_IND then send a success + * notification, otherwise send a normal EAP-Success. + */ + if (eap_aka_sim_session->send_result_ind) { + if (!fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_result_ind)) { + RDEBUG("We wanted to use protected result indications, but peer does not"); + eap_aka_sim_session->send_result_ind = false; + } else { + return STATE_TRANSITION(common_success_notification); + } + } else if (fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_result_ind)) { + RDEBUG("Peer wanted to use protected result indications, but we do not"); + } - return unlang_module_yield_to_section(p_result, - request, - inst->actions.recv_success_notification_ack, - RLM_MODULE_NOOP, - common_success_notification_ack_recv_resume, - mod_signal, - NULL); + return STATE_TRANSITION(eap_success); } - -/** REAUTHENTICATION state - Continue the state machine after receiving a response to our EAP-Request/SIM-Start +/** SIM-CHALLENGE state - Continue the state machine after receiving a response to our EAP-Request/SIM-Challenge * * - Continue based on received AT_SUBTYPE value: - * - EAP-Response/(SIM|AKA)-Reauthentication - call 'recv Reauthentication-Response { ... }' - * - EAP-Response/(SIM|AKA)-Client-Error - call 'recv Client-Error { ... }' and after that - * send a EAP-Request/(SIM|AKA)-Notification indicating a General Failure. + * - EAP-Response/SIM-Challenge - call 'recv Challenge-Response { ... }'. + * - EAP-Response/SIM-Client-Error - call 'recv Client-Error { ... }' and after that + * send a EAP-Request/SIM-Notification indicating a General Failure. * - Anything else, enter the FAILURE-NOTIFICATION state. */ -static unlang_action_t common_reauthentication(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request) +STATE(sim_challenge) { - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t); - eap_session_t *eap_session = eap_session_get(request->parent); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - - fr_pair_t *subtype_vp = NULL; - fr_pair_list_t from_peer; + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); + fr_pair_t *subtype_vp = NULL; - fr_pair_list_init(&from_peer); - common_decode(&subtype_vp, &from_peer, p_result, mctx, request); - if (*p_result != RLM_MODULE_OK) return UNLANG_ACTION_CALCULATE_RESULT; - -#ifdef __clang_analyzer__ + subtype_vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_subtype); if (!subtype_vp) { - RETURN_MODULE_FAIL; + REDEBUG("Missing AT_SUBTYPE"); + goto fail; } -#endif - /* - * These aren't allowed in Reauthentication responses as they don't apply: - * - * EAP_AKA_AUTHENTICATION_REJECT - We didn't provide an AUTN value - * EAP_AKA_SYNCHRONIZATION_FAILURE - We didn't use new vectors. - */ + switch (subtype_vp->vp_uint16) { - case FR_SUBTYPE_VALUE_AKA_SIM_REAUTHENTICATION: - /* - * AT_COUNTER_TOO_SMALL is handled - * in common_reauthentication_response_process. - */ - return unlang_module_yield_to_section(p_result, - request, - inst->actions.recv_reauthentication_response, - RLM_MODULE_NOOP, - common_reauthentication_response_recv_resume, - mod_signal, - NULL); + case FR_SUBTYPE_VALUE_SIM_CHALLENGE: + return CALL_SECTION(recv_sim_challenge_response); /* * Case 1 where we're allowed to send an EAP-Failure */ case FR_SUBTYPE_VALUE_AKA_SIM_CLIENT_ERROR: - client_error_debug(request, &from_peer); + client_error_debug(request); eap_aka_sim_session->allow_encrypted = false; - return unlang_module_yield_to_section(p_result, - request, - inst->actions.recv_client_error, - RLM_MODULE_NOOP, - common_client_error_recv_resume, - mod_signal, - NULL); + return CALL_SECTION(recv_common_client_error); /* - * RFC 4187 says we *MUST* notify, not just + * RFC 4186 says we *MUST* notify, not just * send an EAP-Failure in this case. */ default: REDEBUG("Unexpected subtype %pV", &subtype_vp->data); + fail: eap_aka_sim_session->allow_encrypted = false; - return common_failure_notification_enter(p_result, mctx, request, eap_session); + + return STATE_TRANSITION(common_failure_notification); } } -/** AKA-CHALLENGE state - Continue the state machine after receiving a response to our EAP-Request/SIM-Challenge +/** Resume after 'send Challenge-Request { ... }' * - * - Continue based on received AT_SUBTYPE value: - * - EAP-Response/AKA-Challenge - call 'recv Challenge-Response { ... }'. - * - EAP-Response/AKA-Authentication-Reject - call 'recv Authentication-Reject { ... }' and after that - * send a EAP-Request/SIM-Notification indicating a General Failure. - * - EAP-Response/AKA-Syncronization-Failure - call 'recv Syncronization-Failure { ... }'. - * - EAP-Response/AKA-Client-Error - call 'recv Client-Error { ... }' and after that - * send a EAP-Request/AKA-Notification indicating a General Failure. - * - Anything else, enter the FAILURE-NOTIFICATION state. */ -static unlang_action_t aka_challenge(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request) +RESUME(send_sim_challenge_request) { - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t); - eap_session_t *eap_session = eap_session_get(request->parent); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); + eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t); - fr_pair_t *subtype_vp = NULL; fr_pair_t *vp; - fr_pair_list_t from_peer; + fr_aka_sim_vector_src_t src = AKA_SIM_VECTOR_SRC_AUTO; + + fr_pair_t *kdf_id; - fr_pair_list_init(&from_peer); - common_decode(&subtype_vp, &from_peer, p_result, mctx, request); - if (*p_result != RLM_MODULE_OK) return UNLANG_ACTION_CALCULATE_RESULT; + SECTION_RCODE_PROCESS; -#ifdef __clang_analyzer__ - if (!subtype_vp) { + /* + * Allow override of KDF Identity + * + * Because certain handset manufacturers don't + * implement RFC 4187 correctly and use the + * wrong identity as input the the PRF/KDF. + */ + kdf_id = fr_pair_find_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity); + if (kdf_id) { + crypto_identity_set(request, eap_aka_sim_session, + (uint8_t const *)kdf_id->vp_strvalue, kdf_id->vp_length); + fr_pair_delete_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity); + } + + RDEBUG2("Acquiring GSM vector(s)"); + if ((fr_aka_sim_vector_gsm_from_attrs(request, &request->control_pairs, 0, + &eap_aka_sim_session->keys, &src) != 0) || + (fr_aka_sim_vector_gsm_from_attrs(request, &request->control_pairs, 1, + &eap_aka_sim_session->keys, &src) != 0) || + (fr_aka_sim_vector_gsm_from_attrs(request, &request->control_pairs, 2, + &eap_aka_sim_session->keys, &src) != 0)) { + REDEBUG("Failed retrieving SIM vectors"); RETURN_MODULE_FAIL; } -#endif - switch (subtype_vp->vp_uint16) { - case FR_SUBTYPE_VALUE_AKA_CHALLENGE: - return unlang_module_yield_to_section(p_result, - request, - inst->actions.recv_challenge_response, - RLM_MODULE_NOOP, - aka_challenge_response_recv_resume, - mod_signal, - NULL); + fr_aka_sim_crypto_gsm_kdf_0(&eap_aka_sim_session->keys); + + if (RDEBUG_ENABLED3) fr_aka_sim_crypto_keys_log(request, &eap_aka_sim_session->keys); /* - * Case 2 where we're allowed to send an EAP-Failure + * Indicate we'd like to use protected success messages + * with AT_RESULT_IND + * + * Use our default, but allow user override too. */ - case FR_SUBTYPE_VALUE_AKA_AUTHENTICATION_REJECT: - eap_aka_sim_session->allow_encrypted = false; - - return unlang_module_yield_to_section(p_result, - request, - inst->actions.aka.recv_authentication_reject, - RLM_MODULE_NOOP, - aka_authentication_reject_recv_resume, - mod_signal, - NULL); - - case FR_SUBTYPE_VALUE_AKA_SYNCHRONIZATION_FAILURE: - { - uint64_t new_sqn; + vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_result_ind); + if (vp) eap_aka_sim_session->send_result_ind = vp->vp_bool; - eap_aka_sim_session->allow_encrypted = false; + /* + * Okay, we got the challenges! Put them into attributes. + */ + MEM(pair_append_reply(&vp, attr_eap_aka_sim_rand) >= 0); + fr_pair_value_memdup(vp, eap_aka_sim_session->keys.gsm.vector[0].rand, AKA_SIM_VECTOR_GSM_RAND_SIZE, false); - vp = fr_pair_find_by_da(&from_peer, attr_eap_aka_sim_auts); - if (!vp) { - REDEBUG("EAP-Response/AKA-Synchronisation-Failure missing AT_AUTS"); - failure: - return common_failure_notification_enter(p_result, mctx, request, eap_session); - } + MEM(pair_append_reply(&vp, attr_eap_aka_sim_rand) >= 0); + fr_pair_value_memdup(vp, eap_aka_sim_session->keys.gsm.vector[1].rand, AKA_SIM_VECTOR_GSM_RAND_SIZE, false); - switch (fr_aka_sim_umts_resync_from_attrs(&new_sqn, - request, vp, &eap_aka_sim_session->keys)) { - /* - * Add everything back that we'll need in the - * next challenge round. - */ - case 0: - MEM(pair_append_control(&vp, attr_sim_sqn) >= 0); - vp->vp_uint64 = new_sqn; + MEM(pair_append_reply(&vp, attr_eap_aka_sim_rand) >= 0); + fr_pair_value_memdup(vp, eap_aka_sim_session->keys.gsm.vector[2].rand, AKA_SIM_VECTOR_GSM_RAND_SIZE, false); - MEM(pair_append_control(&vp, attr_sim_ki) >= 0); - fr_pair_value_memdup(vp, eap_aka_sim_session->keys.auc.ki, - sizeof(eap_aka_sim_session->keys.auc.ki), false); + /* + * need to include an AT_MAC attribute so that it will get + * calculated. + */ + MEM(pair_update_reply(&vp, attr_eap_aka_sim_mac) >= 0); + fr_pair_value_memdup(vp, NULL, 0, false); - MEM(pair_append_control(&vp, attr_sim_opc) >= 0); - fr_pair_value_memdup(vp, eap_aka_sim_session->keys.auc.opc, - sizeof(eap_aka_sim_session->keys.auc.opc), false); - break; + /* + * We've sent the challenge so the peer should now be able + * to accept encrypted attributes. + */ + eap_aka_sim_session->allow_encrypted = true; - case 1: /* Don't have Ki or OPc so something else will need to deal with this */ - break; + return session_and_pseudonym_store(p_result, mctx,request, eap_aka_sim_session, sim_challenge_request_send); +} - default: - case -1: - goto failure; - } +/** Enter the SIM-CHALLENGE state + * + */ +STATE_GUARD(sim_challenge) +{ + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); + fr_pair_t *vp; - return unlang_module_yield_to_section(p_result, - request, - inst->actions.aka.recv_syncronization_failure, - RLM_MODULE_NOOP, - aka_synchronization_failure_recv_resume, - mod_signal, - NULL); + /* + * If we've sent either of these identities it + * means we've come here form a Reauthentication-Request + * that failed. + */ + if (eap_aka_sim_session->pseudonym_sent || eap_aka_sim_session->fastauth_sent) { + return session_and_pseudonym_clear(p_result, mctx, request, + eap_aka_sim_session, guard_sim_challenge); + /* come back when we're done */ } + STATE_SET(sim_challenge); + /* - * Case 1 where we're allowed to send an EAP-Failure + * Set the defaults for protected result indicator */ - case FR_SUBTYPE_VALUE_AKA_SIM_CLIENT_ERROR: - client_error_debug(request, &from_peer); + if (eap_aka_sim_session->send_result_ind && + !fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_result_ind)) { + MEM(pair_append_reply(&vp, attr_eap_aka_sim_result_ind) >= 0); + vp->vp_bool = true; + } - eap_aka_sim_session->allow_encrypted = false; + return CALL_SECTION(send_sim_challenge_request); +} - return unlang_module_yield_to_section(p_result, - request, - inst->actions.recv_client_error, - RLM_MODULE_NOOP, - common_client_error_recv_resume, - mod_signal, - NULL); +/** Enter the SIM-CHALLENGE or AKA-CHALLENGE state + * + * Called by functions which are common to both the EAP-SIM and EAP-AKA state machines + * to enter the correct challenge state. + */ +STATE_GUARD(common_challenge) +{ + switch (eap_aka_sim_session->type) { + case FR_EAP_METHOD_SIM: + return STATE_TRANSITION(sim_challenge); + + case FR_EAP_METHOD_AKA: + case FR_EAP_METHOD_AKA_PRIME: + return STATE_TRANSITION(aka_challenge); - /* - * RFC 4187 says we *MUST* notify, not just - * send an EAP-Failure in this case. - */ default: - REDEBUG("Unexpected subtype %pV", &subtype_vp->data); - eap_aka_sim_session->allow_encrypted = false; - goto failure; + break; } + + fr_assert(0); + RETURN_MODULE_FAIL; } -/** SIM-CHALLENGE state - Continue the state machine after receiving a response to our EAP-Request/SIM-Challenge +/** Resume after 'recv Identity-Response { ... }' or 'recv AKA-Identity { ... }' * - * - Continue based on received AT_SUBTYPE value: - * - EAP-Response/SIM-Challenge - call 'recv Challenge-Response { ... }'. - * - EAP-Response/SIM-Client-Error - call 'recv Client-Error { ... }' and after that - * send a EAP-Request/SIM-Notification indicating a General Failure. - * - Anything else, enter the FAILURE-NOTIFICATION state. + * - If the previous section returned a failure rcode, enter the FAILURE-NOTIFICATION state. + * - ...or call a function to process the contents of the AKA-Identity message, mainly the AT_IDENTITY value. + * - If the message does not contain AT_IDENTITY, then enter the FAILURE-NOTIFICATION state. + * - If the user requested another identity, re-enter the AKA-Identity sate. + * - ...or continue based on the value of &Identity-Type which was added by #aka_identity, + * and possibly modified by the user. + * - Fastauth - Enter the REAUTHENTICATION state. + * - Pseudonym - Call 'load pseudonym { ... }' + * - Permanent - Enter the CHALLENGE state. */ -static unlang_action_t sim_challenge(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request) +RESUME(recv_aka_identity_response) { - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t); - eap_session_t *eap_session = eap_session_get(request->parent); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); + eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t); - fr_pair_t *subtype_vp = NULL; - fr_pair_list_t from_peer; + bool user_set_id_req; + fr_pair_t *identity_type; - fr_pair_list_init(&from_peer); - common_decode(&subtype_vp, &from_peer, p_result, mctx, request); - if (*p_result != RLM_MODULE_OK) return UNLANG_ACTION_CALCULATE_RESULT; + SECTION_RCODE_PROCESS; -#ifdef __clang_analyzer__ - if (!subtype_vp) { - RETURN_MODULE_FAIL; +#if 0 + /* + * Digest the identity response + */ + if (eap_aka_sim_session->checkcode_md) { + // TODO: Need another way of doing this + if (fr_aka_sim_crypto_update_checkcode(eap_aka_sim_session->checkcode_state, + eap_session->this_round->response) < 0) { + RPEDEBUG("Failed updating checkcode"); + failure: + return STATE_TRANSITION(common_failure_notification); + } } #endif - switch (subtype_vp->vp_uint16) { - case FR_SUBTYPE_VALUE_SIM_CHALLENGE: - return unlang_module_yield_to_section(p_result, - request, - inst->actions.recv_challenge_response, - RLM_MODULE_NOOP, - sim_challenge_response_recv_resume, - mod_signal, - NULL); - /* - * Case 1 where we're allowed to send an EAP-Failure + * See if the user wants us to request another + * identity. + * + * If they set one themselves don't override + * what they set. */ - case FR_SUBTYPE_VALUE_AKA_SIM_CLIENT_ERROR: - client_error_debug(request, &from_peer); + user_set_id_req = identity_req_set_by_user(request, eap_aka_sim_session); + if ((request->rcode == RLM_MODULE_NOTFOUND) || user_set_id_req) { + if (!user_set_id_req) { + switch (eap_aka_sim_session->last_id_req) { + case AKA_SIM_ANY_ID_REQ: + eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ; + break; - eap_aka_sim_session->allow_encrypted = false; + case AKA_SIM_FULLAUTH_ID_REQ: + eap_aka_sim_session->id_req = AKA_SIM_PERMANENT_ID_REQ; + break; - return unlang_module_yield_to_section(p_result, - request, - inst->actions.recv_client_error, - RLM_MODULE_NOOP, - common_client_error_recv_resume, - mod_signal, - NULL); + case AKA_SIM_NO_ID_REQ: /* Should not happen */ + fr_assert(0); + FALL_THROUGH; + + case AKA_SIM_PERMANENT_ID_REQ: + REDEBUG("Peer sent no usable identities"); + return STATE_TRANSITION(common_failure_notification); + + } + RDEBUG2("Previous section returned (%s), requesting next most permissive identity (%s)", + fr_table_str_by_value(rcode_table, request->rcode, ""), + fr_table_str_by_value(fr_aka_sim_id_request_table, + eap_aka_sim_session->id_req, "")); + } + return STATE_TRANSITION(aka_identity); + } /* - * RFC 4186 says we *MUST* notify, not just - * send an EAP-Failure in this case. + * If the identity looks like a fast re-auth id + * run fast re-auth, otherwise do fullauth. */ - default: - REDEBUG("Unexpected subtype %pV", &subtype_vp->data); + identity_type = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_identity_type); + if (identity_type) switch (identity_type->vp_uint32) { + case FR_IDENTITY_TYPE_VALUE_FASTAUTH: + return STATE_TRANSITION(common_reauthentication); - eap_aka_sim_session->allow_encrypted = false; + /* + * It's a pseudonym, which now needs resolving. + * The resume function here calls aka_challenge_enter + * if pseudonym resolution went ok. + */ + case FR_IDENTITY_TYPE_VALUE_PSEUDONYM: + eap_aka_sim_session->next = guard_aka_challenge; + return CALL_SECTION(load_pseudonym); - return common_failure_notification_enter(p_result, mctx, request, eap_session); + default: + break; } + + return STATE_TRANSITION(aka_challenge); } /** AKA-IDENTITY state - Continue the state machine after receiving a response to our EAP-Request/AKA-Identity @@ -3786,24 +2996,16 @@ static unlang_action_t sim_challenge(rlm_rcode_t *p_result, module_ctx_t const * * send a EAP-Request/SIM-Notification indicating a General Failure. * - Anything else, enter the FAILURE-NOTIFICATION state. */ -static unlang_action_t aka_identity(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request) +STATE(aka_identity) { - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t); - eap_session_t *eap_session = eap_session_get(request->parent); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); fr_pair_t *subtype_vp = NULL; - fr_pair_list_t from_peer; - - fr_pair_list_init(&from_peer); - common_decode(&subtype_vp, &from_peer, p_result, mctx, request); - if (*p_result != RLM_MODULE_OK) return UNLANG_ACTION_CALCULATE_RESULT; -#ifdef __clang_analyzer__ + subtype_vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_subtype); if (!subtype_vp) { - RETURN_MODULE_FAIL; + REDEBUG("Missing AT_SUBTYPE"); + goto fail; } -#endif switch (subtype_vp->vp_uint16) { /* @@ -3814,7 +3016,7 @@ static unlang_action_t aka_identity(rlm_rcode_t *p_result, module_ctx_t const *m fr_pair_t *id; fr_aka_sim_id_type_t type; - id = fr_pair_find_by_da(&from_peer, attr_eap_aka_sim_identity); + id = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_identity); if (!id) { /* * 9.2. EAP-Response/Identity @@ -3825,63 +3027,281 @@ static unlang_action_t aka_identity(rlm_rcode_t *p_result, module_ctx_t const *m * AT_IDENTITY is defined in Section 4.1. */ REDEBUG("EAP-Response/Identity does not contain AT_IDENTITY"); - return common_failure_notification_enter(p_result, mctx, request, eap_session); + return STATE_TRANSITION(common_failure_notification); + } + + /* + * Add ID hint attributes to the request to help + * the user make policy decisions. + */ + identity_hint_pairs_add(&type, NULL, request, id->vp_strvalue); + if (type == AKA_SIM_ID_TYPE_PERMANENT) { + identity_to_permanent_identity(request, id, + eap_aka_sim_session->type, + inst->strip_permanent_identity_hint); + } + + /* + * Update cryptographic identity + */ + crypto_identity_set(request, eap_aka_sim_session, + (uint8_t const *)id->vp_strvalue, id->vp_length); + + return unlang_module_yield_to_section(p_result, + request, + inst->actions.recv_aka_identity_response ? + inst->actions.recv_aka_identity_response: + inst->actions.recv_common_identity_response, + RLM_MODULE_NOOP, + resume_recv_aka_identity_response, + mod_signal, + eap_aka_sim_session); + } + + /* + * Case 1 where we're allowed to send an EAP-Failure + * + * This can happen in the case of a conservative + * peer, where it refuses to provide the permanent + * identity. + */ + case FR_SUBTYPE_VALUE_AKA_SIM_CLIENT_ERROR: + client_error_debug(request); + + return CALL_SECTION(recv_common_client_error); + + default: + /* + * RFC 4187 says we *MUST* notify, not just + * send an EAP-Failure in this case. + */ + REDEBUG("Unexpected subtype %pV", &subtype_vp->data); + fail: + return STATE_TRANSITION(common_failure_notification); + } +} + +/** Resume after 'send Identity-Request { ... }' + * + * There are three types of user identities that can be implemented + * - Permanent identities such as 0123456789098765@myoperator.com + * Permanent identities can be identified by the leading zero followed by + * by 15 digits (the IMSI number). + * - Ephemeral identities (pseudonyms). These are identities assigned for + * identity privacy so the user can't be tracked. These can identities + * can either be generated as per the 3GPP 'Security aspects of non-3GPP accesses' + * document section 14, where a set of up to 16 encryption keys are used + * to reversibly encrypt the IMSI. Alternatively the pseudonym can be completely + * randomised and stored in a datastore. + * - A fast resumption ID which resolves to data used for fast resumption. + * + * In order to perform full authentication the original IMSI is required for + * forwarding to the HLR. In the case where we can't match/decrypt the pseudonym, + * or can't perform fast resumption, we need to request the full identity from + * the supplicant. + * + */ +RESUME(send_aka_identity_request) +{ + eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t); + + SECTION_RCODE_PROCESS; + + /* + * Update eap_aka_sim_session->id_req in case the the + * user set attributes in `send Identity-Request { ... }` + * Also removes all existing id_req attributes + * from the reply. + */ + identity_req_set_by_user(request, eap_aka_sim_session); + + /* + * Select the right type of identity request attribute + * + * Implement checks on identity request order described + * by RFC4187 section #4.1.5. + * + * The internal state machine should always handle this + * correctly, but the user may have other ideas... + */ + if (identity_req_pairs_add(request, eap_aka_sim_session) < 0) { + return STATE_TRANSITION(common_failure_notification); + } + eap_aka_sim_session->last_id_req = eap_aka_sim_session->id_req; /* Record what we last requested */ + + /* + * Encode the packet + */ + common_reply(request, eap_aka_sim_session, FR_SUBTYPE_VALUE_AKA_IDENTITY); + +#if 0 + /* + * Digest the packet contents, updating our checkcode. + */ + if (eap_aka_sim_session->checkcode_md) { + if (!eap_aka_sim_session->checkcode_state && + fr_aka_sim_crypto_init_checkcode(eap_aka_sim_session, &eap_aka_sim_session->checkcode_state, + eap_aka_sim_session->checkcode_md) < 0) { + RPEDEBUG("Failed initialising checkcode"); + goto failure; + } + // TODO: Need another way of doing this + if (fr_aka_sim_crypto_update_checkcode(eap_aka_sim_session->checkcode_state, + eap_session->this_round->request) < 0) { + RPEDEBUG("Failed updating checkcode"); + goto failure; + } + } +#endif + + RETURN_MODULE_HANDLED; +} + +/** Enter the AKA-IDENTITY state + * + */ +STATE_GUARD(aka_identity) +{ + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); + + STATE_SET(aka_identity); + + /* + * If we have an send_aka_identity_request section + * then run that, otherwise just run the normal + * identity request section. + */ + return unlang_module_yield_to_section(p_result, + request, + inst->actions.send_aka_identity_request ? + inst->actions.send_aka_identity_request: + inst->actions.send_common_identity_request, + RLM_MODULE_NOOP, + resume_send_aka_identity_request, + mod_signal, + eap_aka_sim_session); +} + +/** Resume after 'recv Identity-Response { ... }' or 'recv SIM-Start { ... }' + * + * - If the previous section returned a failure rcode, enter the FAILURE-NOTIFICATION state. + * - ...or call a function to process the contents of the SIM-Start message, mainly the AT_IDENTITY value. + * - If the message does not contain AT_IDENTITY, then enter the FAILURE-NOTIFICATION state. + * - If the user requested another identity, re-enter the SIM-START sate. + * - ...or continue based on the value of &Identity-Type which was added by #sim_start, + * and possibly modified by the user. + * - Fastauth + * - If AT_NONCE_MT or AT_SELECTED_VERSION are present, enter the FAILURE-NOTIFICATION state. + * - ...or enter the REAUTHENTICATION state. + * - Pseudonym - Verify selected version and AT_NONCE_MT, then call 'load pseudonym { ... }' + * - Permanent - Verify selected version and AT_NONCE_MT, then enter the CHALLENGE state. + */ +RESUME(recv_sim_start_response) +{ + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); + eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t); + bool user_set_id_req; + fr_pair_t *identity_type; + + SECTION_RCODE_PROCESS; + + /* + * See if the user wants us to request another + * identity. + * + * If they set one themselves don't override + * what they set. + */ + user_set_id_req = identity_req_set_by_user(request, eap_aka_sim_session); + if ((request->rcode == RLM_MODULE_NOTFOUND) || user_set_id_req) { + if (!user_set_id_req) { + switch (eap_aka_sim_session->last_id_req) { + case AKA_SIM_ANY_ID_REQ: + eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ; + break; + + case AKA_SIM_FULLAUTH_ID_REQ: + eap_aka_sim_session->id_req = AKA_SIM_PERMANENT_ID_REQ; + break; + + case AKA_SIM_NO_ID_REQ: /* Should not happen */ + fr_assert(0); + FALL_THROUGH; + + case AKA_SIM_PERMANENT_ID_REQ: + REDEBUG("Peer sent no usable identities"); + failure: + return STATE_TRANSITION(common_failure_notification); + } + RDEBUG2("Previous section returned (%s), requesting next most permissive identity (%s)", + fr_table_str_by_value(rcode_table, request->rcode, ""), + fr_table_str_by_value(fr_aka_sim_id_request_table, + eap_aka_sim_session->id_req, "")); } + return STATE_TRANSITION(sim_start); + } + /* + * If the identity looks like a fast re-auth id + * run fast re-auth, otherwise do fullauth. + */ + identity_type = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_identity_type); + if (identity_type) switch (identity_type->vp_uint32) { + case FR_IDENTITY_TYPE_VALUE_FASTAUTH: /* - * Add ID hint attributes to the request to help - * the user make policy decisions. + * RFC 4186 Section #9.2 + * + * The AT_NONCE_MT attribute MUST NOT be included if the AT_IDENTITY + * with a fast re-authentication identity is present for fast + * re-authentication */ - identity_hint_pairs_add(&type, NULL, request, id->vp_strvalue); - if (type == AKA_SIM_ID_TYPE_PERMANENT) { - identity_to_permanent_identity(request, id, - eap_aka_sim_session->type, - inst->strip_permanent_identity_hint); + if (fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_nonce_mt)) { + REDEBUG("AT_NONCE_MT is not allowed in EAP-Response/SIM-Reauthentication messages"); + return STATE_TRANSITION(common_failure_notification); } /* - * Update cryptographic identity + * RFC 4186 Section #9.2 + * + * The AT_SELECTED_VERSION attribute MUST NOT be included if the + * AT_IDENTITY attribute with a fast re-authentication identity is + * present for fast re-authentication. */ - identity_to_crypto_identity(request, eap_aka_sim_session, - (uint8_t const *)id->vp_strvalue, id->vp_length); + if (fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_selected_version)) { + REDEBUG("AT_SELECTED_VERSION is not allowed in EAP-Response/SIM-Reauthentication messages"); + return STATE_TRANSITION(common_failure_notification); + } - return unlang_module_yield_to_section(p_result, - request, - inst->actions.aka.recv_aka_identity_response? - inst->actions.aka.recv_aka_identity_response: - inst->actions.recv_identity_response, - RLM_MODULE_NOOP, - aka_identity_response_recv_resume, - mod_signal, - NULL); - } + return STATE_TRANSITION(common_reauthentication); /* - * Case 1 where we're allowed to send an EAP-Failure - * - * This can happen in the case of a conservative - * peer, where it refuses to provide the permanent - * identity. + * It's a pseudonym, which now needs resolving. + * The resume function here calls aka_challenge_enter + * if pseudonym resolution went ok. */ - case FR_SUBTYPE_VALUE_AKA_SIM_CLIENT_ERROR: - client_error_debug(request, &from_peer); + case FR_IDENTITY_TYPE_VALUE_PSEUDONYM: + if (sim_start_selected_version_check(request, eap_aka_sim_session) < 0) goto failure; + if (sim_start_nonce_mt_check(request, eap_aka_sim_session) < 0) goto failure; - return unlang_module_yield_to_section(p_result, - request, - inst->actions.recv_client_error, - RLM_MODULE_NOOP, - common_client_error_recv_resume, - mod_signal, - NULL); + eap_aka_sim_session->next = guard_sim_challenge; + return CALL_SECTION(load_pseudonym); + + /* + * If it's a permanent ID, copy it over to + * the session state list for use in the + * store pseudonym/store session sections + * later. + */ + case FR_IDENTITY_TYPE_VALUE_PERMANENT: + if (sim_start_selected_version_check(request, eap_aka_sim_session) < 0) goto failure; + if (sim_start_nonce_mt_check(request, eap_aka_sim_session) < 0) goto failure; + FALL_THROUGH; default: - /* - * RFC 4187 says we *MUST* notify, not just - * send an EAP-Failure in this case. - */ - REDEBUG("Unexpected subtype %pV", &subtype_vp->data); - return common_failure_notification_enter(p_result, mctx, request, eap_session); + break; } + + return STATE_TRANSITION(sim_challenge); } /** SIM-START state - Continue the state machine after receiving a response to our EAP-Request/SIM-Start @@ -3897,33 +3317,23 @@ static unlang_action_t aka_identity(rlm_rcode_t *p_result, module_ctx_t const *m * send a EAP-Request/SIM-Notification indicating a General Failure. * - Anything else, enter the FAILURE-NOTIFICATION state. */ -static unlang_action_t sim_start(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request) +STATE(sim_start) { - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t); - eap_session_t *eap_session = eap_session_get(request->parent); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); - + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); fr_pair_t *subtype_vp = NULL; - fr_pair_list_t from_peer; - fr_pair_list_init(&from_peer); - common_decode(&subtype_vp, &from_peer, p_result, mctx, request); - if (*p_result != RLM_MODULE_OK) return UNLANG_ACTION_CALCULATE_RESULT; - -#ifdef __clang_analyzer__ + subtype_vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_subtype); if (!subtype_vp) { - RETURN_MODULE_FAIL; + REDEBUG("Missing AT_SUBTYPE"); + goto fail; } -#endif - switch (subtype_vp->vp_uint16) { case FR_SUBTYPE_VALUE_SIM_START: { fr_pair_t *id; fr_aka_sim_id_type_t type; - id = fr_pair_find_by_da(&from_peer, attr_eap_aka_sim_identity); + id = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_identity); if (!id) { /* * RFC 4186 Section #9.2 @@ -3934,7 +3344,7 @@ static unlang_action_t sim_start(rlm_rcode_t *p_result, module_ctx_t const *mctx * AT_IDENTITY is defined in Section 4.1. */ REDEBUG("EAP-Response/SIM/Start does not contain AT_IDENTITY"); - return common_failure_notification_enter(p_result, mctx, request, eap_session); + return STATE_TRANSITION(common_failure_notification); } /* @@ -3951,18 +3361,18 @@ static unlang_action_t sim_start(rlm_rcode_t *p_result, module_ctx_t const *mctx /* * Update cryptographic identity */ - identity_to_crypto_identity(request, eap_aka_sim_session, - (uint8_t const *)id->vp_strvalue, id->vp_length); + crypto_identity_set(request, eap_aka_sim_session, + (uint8_t const *)id->vp_strvalue, id->vp_length); return unlang_module_yield_to_section(p_result, request, - inst->actions.sim.recv_sim_start_response? - inst->actions.sim.recv_sim_start_response: - inst->actions.recv_identity_response, + inst->actions.recv_sim_start_response? + inst->actions.recv_sim_start_response: + inst->actions.recv_common_identity_response, RLM_MODULE_NOOP, - sim_start_response_recv_resume, + resume_recv_sim_start_response, mod_signal, - NULL); + eap_aka_sim_session); } /* @@ -3973,15 +3383,9 @@ static unlang_action_t sim_start(rlm_rcode_t *p_result, module_ctx_t const *mctx * identity. */ case FR_SUBTYPE_VALUE_AKA_SIM_CLIENT_ERROR: - client_error_debug(request, &from_peer); + client_error_debug(request); - return unlang_module_yield_to_section(p_result, - request, - inst->actions.recv_client_error, - RLM_MODULE_NOOP, - common_client_error_recv_resume, - mod_signal, - NULL); + return CALL_SECTION(recv_common_client_error); default: /* @@ -3989,8 +3393,140 @@ static unlang_action_t sim_start(rlm_rcode_t *p_result, module_ctx_t const *mctx * send an EAP-Failure in this case. */ REDEBUG("Unexpected subtype %pV", &subtype_vp->data); - return common_failure_notification_enter(p_result, mctx, request, eap_session); + fail: + return STATE_TRANSITION(common_failure_notification); + } +} + +/** Resume after 'send Start { ... }' + * + * Send a EAP-Request/SIM-Start message to the supplicant + * + * There are three types of user identities that can be implemented + * - Permanent identities such as 0123456789098765@myoperator.com + * Permanent identities can be identified by the leading zero followed by + * by 15 digits (the IMSI number). + * - Ephemeral identities (pseudonyms). These are identities assigned for + * identity privacy so the user can't be tracked. These can identities + * can either be generated as per the 3GPP 'Security aspects of non-3GPP accesses' + * document section 14, where a set of up to 16 encryption keys are used + * to reversibly encrypt the IMSI. Alternatively the pseudonym can be completely + * randomised and stored in a datastore. + * - A fast resumption ID which resolves to data used for fast resumption. + * + * In order to perform full authentication the original IMSI is required for + * forwarding to the HLR. In the case where we can't match/decrypt the pseudonym, + * or can't perform fast resumption, we need to request the full identity from + * the supplicant. + */ +RESUME(send_sim_start) +{ + eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t); + fr_pair_t *vp; + uint8_t *p, *end; + + SECTION_RCODE_PROCESS; + + p = eap_aka_sim_session->keys.gsm.version_list; + end = p + sizeof(eap_aka_sim_session->keys.gsm.version_list); + eap_aka_sim_session->keys.gsm.version_list_len = 0; + + /* + * If the user provided no versions, then + * just add the default (1). + */ + if (!(fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_version_list))) { + MEM(pair_append_reply(&vp, attr_eap_aka_sim_version_list) >= 0); + vp->vp_uint16 = EAP_SIM_VERSION; + } + + /* + * Iterate over the the versions adding them + * to the version list we use for keying. + */ + for (vp = fr_pair_list_head(&request->reply_pairs); + vp; + vp = fr_pair_list_next(&request->reply_pairs, vp)) { + if (vp->da != attr_eap_aka_sim_version_list) continue; + + if ((end - p) < 2) break; + + /* + * Store as big endian + */ + *p++ = (vp->vp_uint16 & 0xff00) >> 8; + *p++ = (vp->vp_uint16 & 0x00ff); + eap_aka_sim_session->keys.gsm.version_list_len += sizeof(uint16_t); + } + + /* + * Update eap_aka_sim_session->id_req in case the the + * user set attributes in `send Identity-Request { ... }` + * Also removes all existing id_req attributes + * from the reply. + */ + identity_req_set_by_user(request, eap_aka_sim_session); + + /* + * Select the right type of identity request attribute + * + * Implement checks on identity request order described + * by RFC4186 section #4.2.5. + * + * The internal state machine should always handle this + * correctly, but the user may have other ideas... + */ + if (identity_req_pairs_add(request, eap_aka_sim_session) < 0) { + return STATE_TRANSITION(common_failure_notification); + } + eap_aka_sim_session->last_id_req = eap_aka_sim_session->id_req; /* Record what we last requested */ + + common_reply(request, eap_aka_sim_session, FR_SUBTYPE_VALUE_SIM_START); + + RETURN_MODULE_HANDLED; +} + +/** Enter the SIM-START state + * + */ +STATE_GUARD(sim_start) +{ + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); + + STATE_SET(sim_start); + + return unlang_module_yield_to_section(p_result, + request, + inst->actions.send_sim_start_request ? + inst->actions.send_sim_start_request: + inst->actions.send_common_identity_request, + RLM_MODULE_NOOP, + resume_send_sim_start, + mod_signal, + eap_aka_sim_session); +} + +/** Enter the SIM-START or AKA-IDENTITY state + * + * Called by functions which are common to both the EAP-SIM and EAP-AKA state machines + * to enter the correct Identity-Request state. + */ +STATE_GUARD(common_identity) +{ + switch (eap_aka_sim_session->type) { + case FR_EAP_METHOD_SIM: + return STATE_TRANSITION(sim_start); + + case FR_EAP_METHOD_AKA: + case FR_EAP_METHOD_AKA_PRIME: + return STATE_TRANSITION(aka_identity); + + default: + break; } + + fr_assert(0); + RETURN_MODULE_FAIL; } /** Resume after 'recv Identity-Response { ... }' @@ -4002,18 +3538,14 @@ static unlang_action_t sim_start(rlm_rcode_t *p_result, module_ctx_t const *mctx * - If identity is a fastauth identity, enter the REAUTHENTICATE state. * - If identity is a permanent identity, enter the CHALLENGE state. */ -static unlang_action_t common_eap_identity_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, - request_t *request, UNUSED void *rctx) +RESUME(recv_common_identity_response) { - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t); - eap_session_t *eap_session = eap_session_get(request->parent); - eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, - eap_aka_sim_session_t); + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); + eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t); fr_pair_t *eap_type, *method, *identity_type; fr_aka_sim_method_hint_t running, hinted; - fr_pair_list_t *from_peer = &request->request_pairs; - section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session); + SECTION_RCODE_PROCESS; /* * Ignore attempts to change the EAP-Type @@ -4023,7 +3555,7 @@ static unlang_action_t common_eap_identity_resume(rlm_rcode_t *p_result, module_ eap_type = fr_pair_find_by_da(&request->control_pairs, attr_eap_type); if (eap_type) RWDEBUG("Ignoring &control.EAP-Type, this must be set *before* the EAP module is called"); - method = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_method_hint); + method = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_method_hint); /* * Set default configuration, we may allow these @@ -4037,7 +3569,7 @@ static unlang_action_t common_eap_identity_resume(rlm_rcode_t *p_result, module_ * use it... It has stronger keying, and * binds authentication to the network. */ - switch (eap_session->type) { + switch (eap_aka_sim_session->type) { case FR_EAP_METHOD_SIM: RDEBUG2("New EAP-SIM session"); @@ -4045,13 +3577,6 @@ static unlang_action_t common_eap_identity_resume(rlm_rcode_t *p_result, module_ eap_aka_sim_session->type = FR_EAP_METHOD_SIM; eap_aka_sim_session->mac_md = EVP_sha1(); /* no checkcode support, so no checkcode_md */ - - /* - * RFC 5448 makes no mention of being - * able to use this with EAP-SIM, so it's - * permanently disabled for that EAP method. - */ - eap_aka_sim_session->send_at_bidding_prefer_prime = false; break; case FR_EAP_METHOD_AKA: @@ -4061,7 +3586,6 @@ static unlang_action_t common_eap_identity_resume(rlm_rcode_t *p_result, module_ eap_aka_sim_session->type = FR_EAP_METHOD_AKA; eap_aka_sim_session->checkcode_md = eap_aka_sim_session->mac_md = EVP_sha1(); - eap_aka_sim_session->send_at_bidding_prefer_prime = inst->send_at_bidding_prefer_prime; break; case FR_EAP_METHOD_AKA_PRIME: @@ -4135,18 +3659,16 @@ static unlang_action_t common_eap_identity_resume(rlm_rcode_t *p_result, module_ * can 'decorate' the identity in the identity * response. */ - if (eap_aka_sim_session->id_req != AKA_SIM_NO_ID_REQ) { - return common_identity_enter(p_result, mctx, request, eap_session); - } + if (eap_aka_sim_session->id_req != AKA_SIM_NO_ID_REQ) return STATE_TRANSITION(common_identity); /* * If the identity looks like a fast re-auth id * run fast re-auth, otherwise do a fullauth. */ - identity_type = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_identity_type); + identity_type = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_identity_type); if (identity_type) switch (identity_type->vp_uint32) { case FR_IDENTITY_TYPE_VALUE_FASTAUTH: - return common_reauthentication_enter(p_result, mctx, request, eap_session); + return STATE_TRANSITION(common_reauthentication); /* * It's a pseudonym, which now needs resolving. @@ -4154,58 +3676,29 @@ static unlang_action_t common_eap_identity_resume(rlm_rcode_t *p_result, module_ * if pseudonym resolution went ok. */ case FR_IDENTITY_TYPE_VALUE_PSEUDONYM: - return unlang_module_yield_to_section(p_result, - request, - inst->actions.load_pseudonym, - RLM_MODULE_NOOP, - pseudonym_load_resume, - mod_signal, - (void *)common_challenge_enter); + eap_aka_sim_session->next = guard_common_challenge; + return CALL_SECTION(load_pseudonym); case FR_IDENTITY_TYPE_VALUE_PERMANENT: default: break; } - return common_challenge_enter(p_result, mctx, request, eap_session); -} - -/** Zero out the eap_aka_sim_session when we free it to clear knowledge of secret keys - * - * @param[in] eap_aka_sim_session to free. - * @return 0 - */ -static int _eap_aka_sim_session_free(eap_aka_sim_session_t *eap_aka_sim_session) -{ - memset(eap_aka_sim_session, 0, sizeof(*eap_aka_sim_session)); - return 0; + return STATE_TRANSITION(common_challenge); } -/** Enter the EAP-IDENTITY state - State machine entry point +/** Enter the EAP-IDENTITY state * * - Process the incoming EAP-Identity-Response * - Start EAP-SIM/EAP-AKA/EAP-AKA' state machine optionally calling 'recv Identity-Response { ... }' */ -unlang_action_t aka_sim_state_machine_start(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request) +STATE(init) { - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t); eap_session_t *eap_session = eap_session_get(request->parent); - eap_aka_sim_session_t *eap_aka_sim_session; + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); fr_pair_t *vp; fr_aka_sim_id_type_t type; - - MEM(eap_aka_sim_session = talloc_zero(eap_session, eap_aka_sim_session_t)); - talloc_set_destructor(eap_aka_sim_session, _eap_aka_sim_session_free); - - eap_session->opaque = eap_aka_sim_session; - - /* - * This value doesn't have be strong, but it is - * good if it is different now and then. - */ - eap_aka_sim_session->id = (fr_rand() & 0xff); - /* * Verify we received an EAP-Response/Identity * message before the supplicant started sending @@ -4213,16 +3706,11 @@ unlang_action_t aka_sim_state_machine_start(rlm_rcode_t *p_result, module_ctx_t */ if (!eap_session->identity) { REDEBUG("All SIM or AKA exchanges must begin with a EAP-Response/Identity message"); - return common_failure_notification_enter(p_result, mctx, request, eap_session); + return STATE_TRANSITION(common_failure_notification); } /* - * Add ID hint attributes to the request to help - * the user make policy decisions. - */ - - /* - * Copy the EAP-Identity into and Identity + * Copy the EAP-Identity into our Identity * attribute to make policies easier. */ MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >= 0); @@ -4238,19 +3726,68 @@ unlang_action_t aka_sim_state_machine_start(rlm_rcode_t *p_result, module_ctx_t inst->strip_permanent_identity_hint); } - identity_to_crypto_identity(request, eap_aka_sim_session, - (uint8_t const *)eap_session->identity, - talloc_array_length(eap_session->identity) - 1); + /* + * Set the initial crypto identity from + * the EAP-Identity-Response + */ + crypto_identity_set(request, eap_aka_sim_session, + (uint8_t const *)eap_session->identity, + talloc_array_length(eap_session->identity) - 1); + + return CALL_SECTION(recv_common_identity_response); +} + +/** Zero out the eap_aka_sim_session when we free it to clear knowledge of secret keys + * + * @param[in] eap_aka_sim_session to free. + * @return 0 + */ +static int _eap_aka_sim_session_free(eap_aka_sim_session_t *eap_aka_sim_session) +{ + memset(eap_aka_sim_session, 0, sizeof(*eap_aka_sim_session)); + return 0; +} +/** Resumes the state machine when receiving a new response packet + * + */ +unlang_action_t eap_aka_sim_state_machine_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request) +{ + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t); + eap_aka_sim_session_t *eap_aka_sim_session = request_data_reference(request, + (void *)eap_aka_sim_state_machine_process, + 0); /* - * Running the same section as Identity-Response - * makes policies significantly easier. + * A new EAP-SIM/AKA/AKA' session! */ - return unlang_module_yield_to_section(p_result, - request, - inst->actions.recv_identity_response, - RLM_MODULE_NOOP, - common_eap_identity_resume, - mod_signal, - NULL); + if (!eap_aka_sim_session) { + /* + * Must be allocated in the NULL ctx as this will + * need to persist over multiple rounds of EAP. + */ + MEM(eap_aka_sim_session = talloc_zero(NULL, eap_aka_sim_session_t)); + talloc_set_destructor(eap_aka_sim_session, _eap_aka_sim_session_free); + + /* + * Add new session data to the request + * We only ever need to do this once as it's restored + * during the next round of EAP automatically. + * + * It will also be freed automatically if the request + * is freed and persistable data hasn't been moved + * into the parent. + */ + if (unlikely(request_data_add(request, (void *)eap_aka_sim_state_machine_process, 0, + eap_aka_sim_session, true, true, true) < 0)) { + RPEDEBUG("Failed creating new EAP-SIM/AKA/AKA' session"); + RETURN_MODULE_FAIL; + } + eap_aka_sim_session->type = inst->type; + + return state_init(p_result, mctx, request, eap_aka_sim_session); + } + + if (!fr_cond_assert(eap_aka_sim_session->state)) RETURN_MODULE_FAIL; + + return eap_aka_sim_session->state(p_result, mctx, request, eap_aka_sim_session); } diff --git a/src/lib/eap_aka_sim/state_machine.h b/src/lib/eap_aka_sim/state_machine.h index 6f474af9c46..cd5a4602525 100644 --- a/src/lib/eap_aka_sim/state_machine.h +++ b/src/lib/eap_aka_sim/state_machine.h @@ -28,76 +28,36 @@ RCSIDH(lib_eap_aka_sim_state_machine_h, "$Id$") #include -/** Cache sections to call on various protocol events - * - */ -typedef struct { - union { - struct { - CONF_SECTION *send_aka_identity_request; //!< Called when we're about to request a - ///< different identity. - CONF_SECTION *recv_aka_identity_response; //!< Called when we receive a new identity. - - CONF_SECTION *recv_authentication_reject; //!< Called if the supplicant rejects the - ///< authentication attempt. - CONF_SECTION *recv_syncronization_failure; //!< Called if the supplicant determines - ///< the AUTN value is invalid. - ///< Usually used for resyncing with the HLR. - } aka; - - struct { - CONF_SECTION *send_sim_start_request; //!< Called when we're about to request a - ///< different identity. - CONF_SECTION *recv_sim_start_response; //!< Called when we receive a new identity. - - } sim; - }; +#ifdef __cplusplus +extern "C" { +#endif - CONF_SECTION *send_identity_request; //!< Called when we're about to request a - ///< different identity. - CONF_SECTION *recv_identity_response; //!< Called when we receive a new identity. +typedef struct eap_aka_sim_session_s eap_aka_sim_session_t; - CONF_SECTION *send_challenge_request; //!< Called when we're about to send a - ///< a challenge. - CONF_SECTION *recv_challenge_response; //!< Called when we receive a response - ///< to a previous challenge. - - CONF_SECTION *send_fast_reauth_request; //!< Called when we're about to send a - ///< Fast-Reauth-Request. - CONF_SECTION *recv_fast_reauth_response; //!< Called when we receive a response - ///< to a previous Fast-Reauth-Request. - - CONF_SECTION *recv_client_error; //!< Called if the supplicant experiences - ///< an error of some kind. - - CONF_SECTION *send_reauthentication_request; //!< Challenge the supplicant with an MK - ///< from an existing session. - - CONF_SECTION *recv_reauthentication_response; //!< Process the reauthentication response - ///< from the supplicant. - - CONF_SECTION *send_failure_notification; //!< Called when we're about to send a - ///< failure notification. - CONF_SECTION *send_success_notification; //!< Called when we're about to send a - ///< success notification. - CONF_SECTION *recv_failure_notification_ack; //!< Called when the supplicant ACKs our - ///< failure notification. - CONF_SECTION *recv_success_notification_ack; //!< Called when the supplicant ACKs our - ///< success notification. +/** The state function which should receive any incoming packets + * + */ +typedef unlang_action_t (*eap_aka_sim_state_t)(rlm_rcode_t *p_result, module_ctx_t const *mctx, + request_t *request, eap_aka_sim_session_t *eap_aka_sim_session); - CONF_SECTION *send_eap_success; //!< Called when we send an EAP-Success message. - CONF_SECTION *send_eap_failure; //!< Called when we send an EAP-Failure message. +/** The function to execute after common code has completed + * + * Mostly used to set functions to run after pseudonym and session + * loading/clearing/storing is complete. + */ +typedef unlang_action_t (*eap_aka_sim_next_t)(rlm_rcode_t *p_result, module_ctx_t const *mctx, + request_t *request, eap_aka_sim_session_t *eap_aka_sim_session); - CONF_SECTION *load_pseudonym; //!< Resolve a pseudonym to a permanent ID. - CONF_SECTION *store_pseudonym; //!< Store a permanent ID to pseudonym mapping. - CONF_SECTION *clear_pseudonym; //!< Clear pseudonym to permanent ID mapping. +struct eap_aka_sim_session_s { + /* + * State machine management + */ + eap_aka_sim_state_t state; //!< The process function to run when we + ///< receive the next round of EAP-SIM/AKA/AKA'. - CONF_SECTION *load_session; //!< Load cached authentication vectors. - CONF_SECTION *store_session; //!< Store authentication vectors. - CONF_SECTION *clear_session; //!< Clear authentication vectors. -} eap_aka_sim_actions_t; + eap_aka_sim_next_t next; //!< Resumption function to call after + ///< executing common code. -typedef struct { eap_type_t type; //!< Either FR_TYPE_AKA, or FR_TYPE_AKA_PRIME. bool challenge_success; //!< Whether we received the correct @@ -130,8 +90,6 @@ typedef struct { bool send_result_ind; //!< Say that we would like to use protected ///< result indications ///< (AKA-Notification-Success). - bool send_at_bidding_prefer_prime; //!< Indicate that we prefer EAP-AKA' and - ///< include an AT_BIDDING attribute. bool prev_recv_sync_failure; //!< We only allow one sync failure per ///< session for sanity. @@ -153,30 +111,129 @@ typedef struct { EVP_MD const *mac_md; //!< HMAC-MD we use to generate the MAC. ///< EVP_sha1() for EAP-AKA, EVP_sha256() ///< for EAP-AKA'. +}; - int id; //!< Packet ID. (replay protection). -} eap_aka_sim_session_t; +/** Cache sections to call on various protocol events + * + */ +typedef struct { + union { + /** @name EAP-AKA specific sections + * + * @{ + */ + struct { + CONF_SECTION *send_aka_identity_request; //!< Called when we're about to request a + ///< different identity. + CONF_SECTION *recv_aka_identity_response; //!< Called when we receive a new identity. + + CONF_SECTION *recv_aka_authentication_reject;//!< Called if the supplicant rejects the + ///< authentication attempt. + CONF_SECTION *recv_aka_syncronization_failure;//!< Called if the supplicant determines + ///< the AUTN value is invalid. + ///< Usually used for resyncing with the HLR. + + CONF_SECTION *send_aka_challenge_request; //!< Called when we're about to send a + ///< a challenge. + CONF_SECTION *recv_aka_challenge_response; //!< Called when we receive a response + ///< to a previous challenge. + }; + /** @} */ + + /** @name EAP-SIM specific sections + * + * @{ + */ + struct { + CONF_SECTION *send_sim_challenge_request; //!< Called when we're about to send a + ///< a challenge. + CONF_SECTION *recv_sim_challenge_response; //!< Called when we receive a response + ///< to a previous challenge. + + CONF_SECTION *send_sim_start_request; //!< Called when we're about to request a + ///< different identity. + CONF_SECTION *recv_sim_start_response; //!< Called when we receive a new identity. + }; + /** @} */ + }; + + /** @name Common protocol sections for all methods + * + * @{ + */ + CONF_SECTION *send_common_identity_request; //!< Called when we're about to request a + ///< different identity. + CONF_SECTION *recv_common_identity_response; //!< Called when we receive a new identity. + + CONF_SECTION *recv_common_client_error; //!< Called if the supplicant experiences + ///< an error of some kind. + + CONF_SECTION *send_common_reauthentication_request; //!< Challenge the supplicant with an MK + ///< from an existing session. + + CONF_SECTION *recv_common_reauthentication_response; //!< Process the reauthentication response + ///< from the supplicant. + + CONF_SECTION *recv_common_failure_notification_ack; //!< Called when the supplicant ACKs our + ///< failure notification. + + CONF_SECTION *send_common_failure_notification; //!< Called when we're about to send a + ///< failure notification. + + CONF_SECTION *recv_common_success_notification_ack; //!< Called when the supplicant ACKs our + ///< success notification. + + CONF_SECTION *send_common_success_notification; //!< Called when we're about to send a + ///< success notification. + + + CONF_SECTION *send_eap_success; //!< Called when we send an EAP-Success message. + CONF_SECTION *send_eap_failure; //!< Called when we send an EAP-Failure message. + /** @} */ + + /** @name Internal sections for caching + * + * @{ + */ + CONF_SECTION *load_pseudonym; //!< Resolve a pseudonym to a permanent ID. + CONF_SECTION *store_pseudonym; //!< Store a permanent ID to pseudonym mapping. + CONF_SECTION *clear_pseudonym; //!< Clear pseudonym to permanent ID mapping. + + CONF_SECTION *load_session; //!< Load cached authentication vectors. + CONF_SECTION *store_session; //!< Store authentication vectors. + CONF_SECTION *clear_session; //!< Clear authentication vectors. + /** @} */ +} eap_aka_sim_actions_t; typedef struct { + eap_type_t type; //!< The preferred EAP-Type of this instance + ///< of the EAP-SIM/AKA/AKA' state machine. + char const *network_name; //!< Network ID as described by RFC 5448. fr_aka_sim_id_req_type_t request_identity; //!< Whether we always request the identity of ///< the subscriber. size_t ephemeral_id_length; //!< The length of any identities we're ///< generating. - CONF_SECTION *virtual_server; //!< Virtual server. + bool protected_success; //!< Send a success notification as well as ///< and EAP-Success packet. - bool send_at_bidding_prefer_prime; //!< Include the AT bidding attribute in - ///< challenge requests. - bool send_at_bidding_prefer_prime_is_set; //!< Whether the user specified a value. bool strip_permanent_identity_hint; //!< Control whether the hint byte is stripped ///< when populating Permanent-Identity. + EVP_MD const *hmac_md; //!< The hmac used for validating packets. + ///< EVP_sha1() for EAP-AKA, EVP_sha256() + ///< for EAP-AKA'. + + EVP_MD const *checkcode_md; //!< The hmac used for validating packets + ///< checkcodes. + eap_aka_sim_actions_t actions; //!< Pre-compiled virtual server sections. -} eap_aka_sim_common_conf_t; +} eap_aka_sim_process_conf_t; + +unlang_action_t eap_aka_sim_state_machine_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request); + +#ifdef __cplusplus +} +#endif -/* - * The main entry point - */ -unlang_action_t aka_sim_state_machine_start(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request); 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 1ee756d7e73..62676e47bda 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 @@ -21,6 +21,7 @@ * * @author Arran Cudbard-Bell (a.cudbardb@freeradius.org) * + * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org) * @copyright 2016 The FreeRADIUS server project * @copyright 2016 Network RADIUS SARL (sales@networkradius.com) */ @@ -28,134 +29,39 @@ RCSID("$Id$") #include #include -#include -#include +#include +#include #include #include static CONF_PARSER submodule_config[] = { - { FR_CONF_OFFSET("request_identity", FR_TYPE_UINT32, eap_aka_sim_common_conf_t, request_identity ), - .func = cf_table_parse_uint32, .uctx = &(cf_table_parse_ctx_t){ .table = fr_aka_sim_id_request_table, .len = &fr_aka_sim_id_request_table_len }}, - { FR_CONF_OFFSET("strip_permanent_identity_hint", FR_TYPE_BOOL, eap_aka_sim_common_conf_t, - strip_permanent_identity_hint ), .dflt = "yes" }, - { FR_CONF_OFFSET("ephemeral_id_length", FR_TYPE_SIZE, eap_aka_sim_common_conf_t, ephemeral_id_length ), .dflt = "14" }, /* 14 for compatibility */ - { FR_CONF_OFFSET("protected_success", FR_TYPE_BOOL, eap_aka_sim_common_conf_t, protected_success ), .dflt = "no" }, - { FR_CONF_OFFSET_IS_SET("prefer_aka_prime", FR_TYPE_BOOL, eap_aka_sim_common_conf_t, send_at_bidding_prefer_prime ), .dflt = "no" }, - { FR_CONF_OFFSET("virtual_server", FR_TYPE_VOID, eap_aka_sim_common_conf_t, virtual_server), .func = virtual_server_cf_parse }, - CONF_PARSER_TERMINATOR -}; - -static virtual_server_compile_t compile_list[] = { - /* - * Identity negotiation - */ - EAP_SECTION_DEFINE(send_identity_request, "send", "Identity-Request"), - EAP_SECTION_DEFINE(recv_identity_response, "recv", "Identity-Response"), - - /* - * Optional override sections if the user *really* - * wants to apply special policies for subsequent - * request/response rounds. - */ - EAP_SECTION_DEFINE(aka.send_aka_identity_request, "send", "AKA-Identity-Request"), - EAP_SECTION_DEFINE(aka.recv_aka_identity_response, "recv", "AKA-Identity-Response"), - - /* - * Full-Authentication - */ - EAP_SECTION_DEFINE(send_challenge_request, "send", "Challenge-Request"), - EAP_SECTION_DEFINE(recv_challenge_response, "recv", "Challenge-Response"), - - /* - * Fast-Reauthentication - */ - EAP_SECTION_DEFINE(send_reauthentication_request, "send", "Reauthentication-Request"), - EAP_SECTION_DEFINE(recv_reauthentication_response, "recv", "Reauthentication-Response"), + { FR_CONF_OFFSET("virtual_server", FR_TYPE_VOID, eap_aka_sim_module_conf_t, virtual_server), .func = virtual_server_cf_parse }, + { FR_CONF_OFFSET_IS_SET("prefer_aka_prime", FR_TYPE_BOOL, eap_aka_sim_module_conf_t, aka.send_at_bidding_prefer_prime ), .dflt = "no" }, - /* - * Failures originating from the supplicant - */ - EAP_SECTION_DEFINE(recv_client_error, "recv", "Client-Error"), - EAP_SECTION_DEFINE(aka.recv_authentication_reject, "recv", "Authentication-Reject"), - EAP_SECTION_DEFINE(aka.recv_syncronization_failure, "recv", "Syncronization-Failure"), - - /* - * Failure originating from the server - */ - EAP_SECTION_DEFINE(send_failure_notification, "send", "Failure-Notification"), - EAP_SECTION_DEFINE(recv_failure_notification_ack, "recv", "Failure-Notification-ACK"), - - /* - * Protected success indication - */ - EAP_SECTION_DEFINE(send_success_notification, "send", "Success-Notification"), - EAP_SECTION_DEFINE(recv_success_notification_ack, "recv", "Success-Notification-ACK"), - - /* - * Final EAP-Success and EAP-Failure messages - */ - EAP_SECTION_DEFINE(send_eap_success, "send", "EAP-Success"), - EAP_SECTION_DEFINE(send_eap_failure, "send", "EAP-Failure"), - - /* - * Fast-Reauth vectors - */ - EAP_SECTION_DEFINE(store_session, "store", "session"), - EAP_SECTION_DEFINE(load_session, "load", "session"), - EAP_SECTION_DEFINE(clear_session, "clear", "session"), - - /* - * Pseudonym processing - */ - EAP_SECTION_DEFINE(store_pseudonym, "store", "pseudonym"), - EAP_SECTION_DEFINE(load_pseudonym, "load", "pseudonym"), - EAP_SECTION_DEFINE(clear_pseudonym, "clear", "pseudonym"), - - COMPILE_TERMINATOR + CONF_PARSER_TERMINATOR }; +extern rlm_eap_submodule_t rlm_eap_aka; -/** Compile virtual server sections - * - * Called twice, once when a server with an eap-aka namespace is found, and once - * when an EAP-AKA module is instantiated. - * - * The first time is with actions == NULL and is to compile the sections and - * perform validation. - * The second time is to write out pointers to the compiled sections which the - * EAP-AKA module will use to execute unlang code. - * - */ -static int mod_section_compile(eap_aka_sim_actions_t *actions, CONF_SECTION *server_cs) +static unlang_action_t mod_session_init(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request) { - int found; - tmpl_rules_t parse_rules; - - if (!fr_cond_assert(server_cs)) return -1; - - memset(&parse_rules, 0, sizeof(parse_rules)); - parse_rules.dict_def = dict_eap_aka_sim; + eap_session_t *eap_session = eap_session_get(request->parent); + eap_aka_sim_mod_session_t *mod_session; - found = virtual_server_compile_sections(server_cs, compile_list, &parse_rules, actions); - if (found < 0) return -1; + MEM(mod_session = talloc_zero(eap_session, eap_aka_sim_mod_session_t)); + mod_session->id = (uint8_t)(fr_rand() & 0xff); + mod_session->ctx.hmac_md = mod_session->ctx.checkcode_md = EVP_sha1(); + eap_session->opaque = mod_session; + eap_session->process = eap_aka_sim_process; - /* - * Warn if we couldn't find any actions. - */ - if (!found) { - cf_log_warn(server_cs, "No \"eap-aka\" actions found in virtual server \"%s\"", - cf_section_name2(server_cs)); - } - - return 0; + return eap_session->process(p_result, mctx, request); } - static int mod_instantiate(void *instance, CONF_SECTION *conf) { - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(instance, eap_aka_sim_common_conf_t); + eap_aka_sim_module_conf_t *inst = talloc_get_type_abort(instance, eap_aka_sim_module_conf_t); - if (mod_section_compile(&inst->actions, inst->virtual_server) < 0) return -1; + inst->type = rlm_eap_aka.provides[0]; /* * If the user didn't specify a bidding value @@ -163,14 +69,15 @@ static int mod_instantiate(void *instance, CONF_SECTION *conf) * attribute, by whether the EAP module has * has the AKA-Prime module enabled. */ - if (!inst->send_at_bidding_prefer_prime_is_set) { + if (!inst->aka.send_at_bidding_prefer_prime_is_set) { CONF_SECTION *parent = cf_item_to_section(cf_parent(conf)); CONF_PAIR *cp = NULL; while ((cp = cf_pair_find_next(parent, cp, "type"))) { if (strcmp(cf_pair_value(cp), "aka-prime") == 0) { cf_log_debug(conf, "Setting 'prefer_aka_prime = yes', as EAP-AKA-Prime is enabled"); - inst->send_at_bidding_prefer_prime = true; + inst->aka.send_at_bidding_prefer_prime = true; + inst->aka.send_at_bidding_prefer_prime_is_set = true; break; } } @@ -179,20 +86,10 @@ static int mod_instantiate(void *instance, CONF_SECTION *conf) return 0; } -/** Compile any virtual servers with the "eap-aka" namespace - * - */ -static int mod_namespace_load(CONF_SECTION *server_cs) -{ - return mod_section_compile(NULL, server_cs); -} - static int mod_load(void) { if (fr_aka_sim_init() < 0) return -1; - if (virtual_namespace_register("eap-aka", dict_eap_aka_sim, mod_namespace_load) < 0) return -1; - fr_aka_sim_xlat_register(); return 0; @@ -209,15 +106,14 @@ static void mod_unload(void) * The module name should be the only globally exported symbol. * That is, everything else should be 'static'. */ -extern rlm_eap_submodule_t rlm_eap_aka; rlm_eap_submodule_t rlm_eap_aka = { .name = "eap_aka", .magic = RLM_MODULE_INIT, .provides = { FR_EAP_METHOD_AKA }, - .inst_size = sizeof(eap_aka_sim_common_conf_t), - .inst_type = "eap_aka_sim_common_conf_t", + .inst_size = sizeof(eap_aka_sim_module_conf_t), + .inst_type = "eap_aka_sim_module_conf_t", .config = submodule_config, .onload = mod_load, @@ -225,6 +121,7 @@ rlm_eap_submodule_t rlm_eap_aka = { .instantiate = mod_instantiate, - .session_init = aka_sim_state_machine_start, /* Initialise a new EAP session */ + .session_init = mod_session_init, + .entry_point = eap_aka_sim_process, .namespace = &dict_eap_aka_sim }; diff --git a/src/modules/rlm_eap/types/rlm_eap_aka_prime/rlm_eap_aka_prime.c b/src/modules/rlm_eap/types/rlm_eap_aka_prime/rlm_eap_aka_prime.c index c914b1e3a38..32e9a5def49 100644 --- a/src/modules/rlm_eap/types/rlm_eap_aka_prime/rlm_eap_aka_prime.c +++ b/src/modules/rlm_eap/types/rlm_eap_aka_prime/rlm_eap_aka_prime.c @@ -21,6 +21,7 @@ * * @author Arran Cudbard-Bell * + * @copyright 2021 Arran Cudbard-Bell * @copyright 2019 The FreeRADIUS server project * @copyright 2019 Network RADIUS SARL */ @@ -28,151 +29,46 @@ RCSID("$Id$") #include #include -#include -#include +#include +#include #include #include static CONF_PARSER submodule_config[] = { - { FR_CONF_OFFSET("network_name", FR_TYPE_STRING, eap_aka_sim_common_conf_t, network_name ) }, - { FR_CONF_OFFSET("request_identity", FR_TYPE_UINT32, eap_aka_sim_common_conf_t, request_identity ), - .func = cf_table_parse_uint32, .uctx = &(cf_table_parse_ctx_t){ .table = fr_aka_sim_id_request_table, .len = &fr_aka_sim_id_request_table_len }}, - { FR_CONF_OFFSET("strip_permanent_identity_hint", FR_TYPE_BOOL, eap_aka_sim_common_conf_t, - strip_permanent_identity_hint ), .dflt = "yes" }, - { FR_CONF_OFFSET("ephemeral_id_length", FR_TYPE_SIZE, eap_aka_sim_common_conf_t, ephemeral_id_length ), .dflt = "14" }, /* 14 for compatibility */ - { FR_CONF_OFFSET("protected_success", FR_TYPE_BOOL, eap_aka_sim_common_conf_t, protected_success ), .dflt = "no" }, - { FR_CONF_OFFSET("virtual_server", FR_TYPE_VOID, eap_aka_sim_common_conf_t, virtual_server), .func = virtual_server_cf_parse }, - CONF_PARSER_TERMINATOR -}; + { FR_CONF_OFFSET("virtual_server", FR_TYPE_VOID, eap_aka_sim_module_conf_t, virtual_server), .func = virtual_server_cf_parse }, -static virtual_server_compile_t compile_list[] = { - /* - * Identity negotiation - */ - EAP_SECTION_DEFINE(send_identity_request, "send", "Identity-Request"), - EAP_SECTION_DEFINE(recv_identity_response, "recv", "Identity-Response"), - - /* - * Optional override sections if the user *really* - * wants to apply special policies for subsequent - * request/response rounds. - */ - EAP_SECTION_DEFINE(aka.send_aka_identity_request, "send", "AKA-Identity-Request"), - EAP_SECTION_DEFINE(aka.recv_aka_identity_response, "recv", "AKA-Identity-Response"), - - /* - * Full-Authentication - */ - EAP_SECTION_DEFINE(send_challenge_request, "send", "Challenge-Request"), - EAP_SECTION_DEFINE(recv_challenge_response, "recv", "Challenge-Response"), - - /* - * Fast-Re-Authentication - */ - EAP_SECTION_DEFINE(send_reauthentication_request, "send", "Reauthentication-Request"), - EAP_SECTION_DEFINE(recv_reauthentication_response, "recv", "Reauthentication-Response"), - - /* - * Failures originating from the supplicant - */ - EAP_SECTION_DEFINE(recv_client_error, "recv", "Client-Error"), - EAP_SECTION_DEFINE(aka.recv_authentication_reject, "recv", "Authentication-Reject"), - EAP_SECTION_DEFINE(aka.recv_syncronization_failure, "recv", "Syncronization-Failure"), - - /* - * Failure originating from the server - */ - EAP_SECTION_DEFINE(send_failure_notification, "send", "Failure-Notification"), - EAP_SECTION_DEFINE(recv_failure_notification_ack, "recv", "Failure-Notification-ACK"), - - /* - * Protected success indication - */ - EAP_SECTION_DEFINE(send_success_notification, "send", "Success-Notification"), - EAP_SECTION_DEFINE(recv_success_notification_ack, "recv", "Success-Notification-ACK"), - - /* - * Final EAP-Success and EAP-Failure messages - */ - EAP_SECTION_DEFINE(send_eap_success, "send", "EAP-Success"), - EAP_SECTION_DEFINE(send_eap_failure, "send", "EAP-Failure"), - - /* - * Fast-Reauth vectors - */ - EAP_SECTION_DEFINE(store_session, "store", "session"), - EAP_SECTION_DEFINE(load_session, "load", "session"), - EAP_SECTION_DEFINE(clear_session, "clear", "session"), - - /* - * Pseudonym processing - */ - EAP_SECTION_DEFINE(store_pseudonym, "store", "pseudonym"), - EAP_SECTION_DEFINE(load_pseudonym, "load", "pseudonym"), - EAP_SECTION_DEFINE(clear_pseudonym, "clear", "pseudonym"), - - COMPILE_TERMINATOR + CONF_PARSER_TERMINATOR }; +extern rlm_eap_submodule_t rlm_eap_aka_prime; -/** Compile virtual server sections - * - * Called twice, once when a server with an eap-aka namespace is found, and once - * when an EAP-AKA module is instantiated. - * - * The first time is with actions == NULL and is to compile the sections and - * perform validation. - * The second time is to write out pointers to the compiled sections which the - * EAP-AKA module will use to execute unlang code. - * - */ -static int mod_section_compile(eap_aka_sim_actions_t *actions, CONF_SECTION *server_cs) +static unlang_action_t mod_session_init(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request) { - int found; - tmpl_rules_t parse_rules; - - if (!fr_cond_assert(server_cs)) return -1; - - memset(&parse_rules, 0, sizeof(parse_rules)); - parse_rules.dict_def = dict_eap_aka_sim; + eap_session_t *eap_session = eap_session_get(request->parent); + eap_aka_sim_mod_session_t *mod_session; - found = virtual_server_compile_sections(server_cs, compile_list, &parse_rules, actions); - if (found < 0) return -1; + MEM(mod_session = talloc_zero(eap_session, eap_aka_sim_mod_session_t)); + mod_session->id = (uint8_t)(fr_rand() & 0xff); + mod_session->ctx.hmac_md = mod_session->ctx.checkcode_md = EVP_sha256(); + eap_session->opaque = mod_session; + eap_session->process = eap_aka_sim_process; - /* - * Warn if we couldn't find any actions. - */ - if (!found) { - cf_log_warn(server_cs, "No \"eap-aka-prime\" actions found in virtual server \"%s\"", - cf_section_name2(server_cs)); - } - - return 0; + return eap_session->process(p_result, mctx, request); } static int mod_instantiate(void *instance, UNUSED CONF_SECTION *conf) { - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(instance, eap_aka_sim_common_conf_t); + eap_aka_sim_module_conf_t *inst = talloc_get_type_abort(instance, eap_aka_sim_module_conf_t); - if (mod_section_compile(&inst->actions, inst->virtual_server) < 0) return -1; + inst->type = rlm_eap_aka_prime.provides[0]; return 0; } -/** Compile any virtual servers with the "eap-aka" namespace - * - */ -static int mod_namespace_load(CONF_SECTION *server_cs) -{ - return mod_section_compile(NULL, server_cs); -} - static int mod_load(void) { if (fr_aka_sim_init() < 0) return -1; - if (virtual_namespace_register("eap-aka-prime", dict_eap_aka_sim, mod_namespace_load) < 0) return -1; - fr_aka_sim_xlat_register(); return 0; @@ -189,15 +85,14 @@ static void mod_unload(void) * The module name should be the only globally exported symbol. * That is, everything else should be 'static'. */ -extern rlm_eap_submodule_t rlm_eap_aka_prime; rlm_eap_submodule_t rlm_eap_aka_prime = { .name = "eap_aka_prime", .magic = RLM_MODULE_INIT, .provides = { FR_EAP_METHOD_AKA_PRIME }, - .inst_size = sizeof(eap_aka_sim_common_conf_t), - .inst_type = "eap_aka_sim_common_conf_t", + .inst_size = sizeof(eap_aka_sim_module_conf_t), + .inst_type = "eap_aka_sim_module_conf_t", .config = submodule_config, .onload = mod_load, @@ -205,6 +100,7 @@ rlm_eap_submodule_t rlm_eap_aka_prime = { .instantiate = mod_instantiate, - .session_init = aka_sim_state_machine_start, /* Initialise a new EAP session */ + .session_init = mod_session_init, + .entry_point = eap_aka_sim_process, /* Initialise a new EAP session */ .namespace = &dict_eap_aka_sim }; 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 b9efc963fa0..b16b53d5006 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 @@ -21,154 +21,52 @@ * * @author Arran Cudbard-Bell * + * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org) * @copyright 2016 The FreeRADIUS server project - * @copyright 2016 Network RADIUS SARL + * @copyright 2016 Network RADIUS SARL (sales@networkradius.com) */ RCSID("$Id$") #include #include -#include -#include +#include +#include #include #include static CONF_PARSER submodule_config[] = { - { FR_CONF_OFFSET("request_identity", FR_TYPE_UINT32, eap_aka_sim_common_conf_t, request_identity ), - .func = cf_table_parse_uint32, .uctx = &(cf_table_parse_ctx_t){ .table = fr_aka_sim_id_request_table, .len = &fr_aka_sim_id_request_table_len }}, - { FR_CONF_OFFSET("strip_permanent_identity_hint", FR_TYPE_BOOL, eap_aka_sim_common_conf_t, - strip_permanent_identity_hint ), .dflt = "yes" }, - { FR_CONF_OFFSET("ephemeral_id_length", FR_TYPE_SIZE, eap_aka_sim_common_conf_t, ephemeral_id_length ), .dflt = "14" }, /* 14 for compatibility */ - { FR_CONF_OFFSET("protected_success", FR_TYPE_BOOL, eap_aka_sim_common_conf_t, protected_success ), .dflt = "no" }, - { FR_CONF_OFFSET("virtual_server", FR_TYPE_VOID | FR_TYPE_REQUIRED, eap_aka_sim_common_conf_t, virtual_server), .func = virtual_server_cf_parse }, + { FR_CONF_OFFSET("virtual_server", FR_TYPE_VOID | FR_TYPE_REQUIRED, eap_aka_sim_module_conf_t, virtual_server), .func = virtual_server_cf_parse }, CONF_PARSER_TERMINATOR }; -static virtual_server_compile_t compile_list[] = { - /* - * Identity negotiation - */ - EAP_SECTION_DEFINE(send_identity_request, "send", "Identity-Request"), - EAP_SECTION_DEFINE(recv_identity_response, "recv", "Identity-Response"), - - /* - * Optional override sections if the user *really* - * wants to apply special policies for subsequent - * request/response rounds. - */ - EAP_SECTION_DEFINE(sim.send_sim_start_request, "send", "Start"), - EAP_SECTION_DEFINE(sim.recv_sim_start_response, "recv", "Start"), - - /* - * Full-Authentication - */ - EAP_SECTION_DEFINE(send_challenge_request, "send", "Challenge-Request"), - EAP_SECTION_DEFINE(recv_challenge_response, "recv", "Challenge-Response"), - - /* - * Fast-Re-Authentication - */ - EAP_SECTION_DEFINE(send_reauthentication_request, "send", "Reauthentication-Request"), - EAP_SECTION_DEFINE(recv_reauthentication_response, "recv", "Reauthentication-Response"), - - /* - * Failures originating from the supplicant - */ - EAP_SECTION_DEFINE(recv_client_error, "recv", "Client-Error"), - - /* - * Failure originating from the server - */ - EAP_SECTION_DEFINE(send_failure_notification, "send", "Failure-Notification"), - EAP_SECTION_DEFINE(recv_failure_notification_ack, "recv", "Failure-Notification-ACK"), - - /* - * Protected success indication - */ - EAP_SECTION_DEFINE(send_success_notification, "send", "Success-Notification"), - EAP_SECTION_DEFINE(recv_success_notification_ack, "recv", "Success-Notification-ACK"), - - /* - * Final EAP-Success and EAP-Failure messages - */ - EAP_SECTION_DEFINE(send_eap_success, "send", "EAP-Success"), - EAP_SECTION_DEFINE(send_eap_failure, "send", "EAP-Failure"), - - /* - * Fast-Reauth vectors - */ - EAP_SECTION_DEFINE(store_session, "store", "session"), - EAP_SECTION_DEFINE(load_session, "load", "session"), - EAP_SECTION_DEFINE(clear_session, "clear", "session"), - - /* - * Pseudonym processing - */ - EAP_SECTION_DEFINE(store_pseudonym, "store", "pseudonym"), - EAP_SECTION_DEFINE(load_pseudonym, "load", "pseudonym"), - EAP_SECTION_DEFINE(clear_pseudonym, "clear", "pseudonym"), - - COMPILE_TERMINATOR -}; +extern rlm_eap_submodule_t rlm_eap_sim; -/** Compile virtual server sections - * - * Called twice, once when a server with an eap-aka namespace is found, and once - * when an EAP-AKA module is instantiated. - * - * The first time is with actions == NULL and is to compile the sections and - * perform validation. - * The second time is to write out pointers to the compiled sections which the - * EAP-AKA module will use to execute unlang code. - * - */ -static int mod_section_compile(eap_aka_sim_actions_t *actions, CONF_SECTION *server_cs) +static unlang_action_t mod_session_init(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request) { - int found; - tmpl_rules_t parse_rules; - - if (!fr_cond_assert(server_cs)) return -1; - - memset(&parse_rules, 0, sizeof(parse_rules)); - parse_rules.dict_def = dict_eap_aka_sim; - - found = virtual_server_compile_sections(server_cs, compile_list, &parse_rules, actions); - if (found < 0) return -1; + eap_session_t *eap_session = eap_session_get(request->parent); + eap_aka_sim_mod_session_t *mod_session; - /* - * Warn if we couldn't find any actions. - */ - if (!found) { - cf_log_warn(server_cs, "No \"eap-sim\" actions found in virtual server \"%s\"", - cf_section_name2(server_cs)); - } + MEM(mod_session = talloc_zero(eap_session, eap_aka_sim_mod_session_t)); + mod_session->id = (uint8_t)(fr_rand() & 0xff); + mod_session->ctx.hmac_md = mod_session->ctx.checkcode_md = EVP_sha1(); + eap_session->opaque = mod_session; + eap_session->process = eap_aka_sim_process; - return 0; + return eap_session->process(p_result, mctx, request); } static int mod_instantiate(void *instance, UNUSED CONF_SECTION *conf) { - eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(instance, eap_aka_sim_common_conf_t); + eap_aka_sim_module_conf_t *inst = talloc_get_type_abort(instance, eap_aka_sim_module_conf_t); - if (mod_section_compile(&inst->actions, inst->virtual_server) < 0) return -1; + inst->type = rlm_eap_sim.provides[0]; return 0; } - -/** Compile any virtual servers with the "eap-aka" namespace - * - */ -static int mod_namespace_load(CONF_SECTION *server_cs) -{ - return mod_section_compile(NULL, server_cs); -} - static int mod_load(void) { if (fr_aka_sim_init() < 0) return -1; - if (virtual_namespace_register("eap-sim", dict_eap_aka_sim, mod_namespace_load) < 0) return -1; - fr_aka_sim_xlat_register(); return 0; @@ -185,15 +83,14 @@ static void mod_unload(void) * The module name should be the only globally exported symbol. * That is, everything else should be 'static'. */ -extern rlm_eap_submodule_t rlm_eap_sim; rlm_eap_submodule_t rlm_eap_sim = { .name = "eap_sim", .magic = RLM_MODULE_INIT, .provides = { FR_EAP_METHOD_SIM }, - .inst_size = sizeof(eap_aka_sim_common_conf_t), - .inst_type = "eap_aka_sim_common_conf_t", + .inst_size = sizeof(eap_aka_sim_module_conf_t), + .inst_type = "eap_aka_sim_module_conf_t", .config = submodule_config, .onload = mod_load, @@ -201,6 +98,7 @@ rlm_eap_submodule_t rlm_eap_sim = { .instantiate = mod_instantiate, - .session_init = aka_sim_state_machine_start, /* Initialise a new EAP session */ + .session_init = mod_session_init, /* Initialise a new EAP session */ + .entry_point = eap_aka_sim_process, .namespace = &dict_eap_aka_sim }; diff --git a/src/process/eap_aka/all.mk b/src/process/eap_aka/all.mk new file mode 100644 index 00000000000..efed1c1af5e --- /dev/null +++ b/src/process/eap_aka/all.mk @@ -0,0 +1,10 @@ +TARGETNAME := process_eap_aka + +ifneq "$(OPENSSL_LIBS)" "" +TARGET := $(TARGETNAME).a +endif + +SOURCES := base.c + +TGT_PREREQS := libfreeradius-util.a libfreeradius-eap-aka-sim.a + diff --git a/src/process/eap_aka/base.c b/src/process/eap_aka/base.c new file mode 100644 index 00000000000..62c8ac96a89 --- /dev/null +++ b/src/process/eap_aka/base.c @@ -0,0 +1,274 @@ +/* + * This program 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 + */ + +/** + * $Id$ + * @file src/process/eap_aka/base.c + * @brief EAP-AKA process module + * + * The state machine for EAP-SIM, EAP-AKA and EAP-AKA' is common to all methods + * and is in src/lib/eap_aka_sim/state_machine.c + * + * The process modules for the different EAP methods just define the sections + * for that EAP method, and parse different config items. + * + * @copyright 2021 Arran Cudbard-Bell + */ + +#include +#include +#include +#include +#include + +static CONF_PARSER submodule_config[] = { + { FR_CONF_OFFSET("request_identity", FR_TYPE_UINT32, eap_aka_sim_process_conf_t, request_identity ), + .func = cf_table_parse_uint32, .uctx = &(cf_table_parse_ctx_t){ .table = fr_aka_sim_id_request_table, .len = &fr_aka_sim_id_request_table_len }}, + { FR_CONF_OFFSET("strip_permanent_identity_hint", FR_TYPE_BOOL, eap_aka_sim_process_conf_t, + strip_permanent_identity_hint ), .dflt = "yes" }, + { FR_CONF_OFFSET("ephemeral_id_length", FR_TYPE_SIZE, eap_aka_sim_process_conf_t, ephemeral_id_length ), .dflt = "14" }, /* 14 for compatibility */ + { FR_CONF_OFFSET("protected_success", FR_TYPE_BOOL, eap_aka_sim_process_conf_t, protected_success ), .dflt = "no" }, + + CONF_PARSER_TERMINATOR +}; + +static virtual_server_compile_t const compile_list[] = { + /* + * Identity negotiation + * The initial identity here is the EAP-Identity. + * We can then choose to request additional + * identities. + */ + { + .name = "recv", + .name2 = "Identity-Response", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_identity_response) + }, + { + .name = "send", + .name2 = "Identity-Request", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_common_identity_request) + }, + + /* + * Optional override sections if the user *really* + * wants to apply special policies for subsequent + * request/response rounds. + */ + { + .name = "send", + .name2 = "AKA-Identity-Request", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_aka_identity_request) + }, + { + .name = "recv", + .name2 = "AKA-Identity-Response", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_aka_identity_response) + }, + + /* + * Full-Authentication + */ + { + .name = "send", + .name2 = "Challenge-Request", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_aka_challenge_request) + }, + { + .name = "recv", + .name2 = "Challenge-Response", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_aka_challenge_response) + }, + + /* + * Fast-Re-Authentication + */ + { + .name = "send", + .name2 = "Reauthentication-Request", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_common_reauthentication_request) + }, + { + .name = "recv", + .name2 = "Reauthentication-Response", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_reauthentication_response) + }, + + /* + * Failures originating from the supplicant + */ + { + .name = "recv", + .name2 = "Client-Error", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_client_error) + }, + { + .name = "recv", + .name2 = "Authentication-Reject", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_aka_authentication_reject) + }, + { + .name = "recv", + .name2 = "Syncronization-Failure", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_aka_syncronization_failure) + }, + + /* + * Failure originating from the server + */ + { + .name = "send", + .name2 = "Failure-Notification", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_common_failure_notification) + }, + { + .name = "recv", + .name2 = "Failure-Notification-ACK", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_failure_notification_ack) + }, + + /* + * Protected success indication + */ + { + .name = "send", + .name2 = "Success-Notification", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_common_success_notification) + }, + { + .name = "recv", + .name2 = "Success-Notification-ACK", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_success_notification_ack) + }, + + /* + * Final EAP-Success and EAP-Failure messages + */ + { + .name = "send", + .name2 = "EAP-Success", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_eap_success) + }, + { + .name = "send", + .name2 = "EAP-Failure", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_eap_failure) + }, + + /* + * Fast-Reauth vectors + */ + { + .name = "store", + .name2 = "session", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.store_session) + }, + { + .name = "load", + .name2 = "session", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.load_session) + }, + { + .name = "clear", + .name2 = "session", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.clear_session) + }, + + /* + * Pseudonym processing + */ + { + .name = "store", + .name2 = "pseudonym", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.store_pseudonym) + }, + { + .name = "load", + .name2 = "pseudonym", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.load_pseudonym) + }, + { + .name = "clear", + .name2 = "pseudonym", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.clear_pseudonym) + }, + + COMPILE_TERMINATOR +}; + +static int mod_instantiate(void *instance, UNUSED CONF_SECTION *conf) +{ + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(instance, eap_aka_sim_process_conf_t); + + inst->type = FR_EAP_METHOD_AKA; + + return 0; +} + +static int mod_load(void) +{ + if (unlikely(fr_aka_sim_init() < 0)) return -1; + + fr_aka_sim_xlat_register(); + + return 0; +} + +static void mod_unload(void) +{ + fr_aka_sim_xlat_unregister(); + + fr_aka_sim_free(); +} + +extern fr_process_module_t process_eap_aka; +fr_process_module_t process_eap_aka = { + .magic = RLM_MODULE_INIT, + .name = "process_eap_aka", + .onload = mod_load, + .unload = mod_unload, + .config = submodule_config, + .instantiate = mod_instantiate, + .inst_size = sizeof(eap_aka_sim_process_conf_t), + .inst_type = "eap_aka_sim_process_conf_t", + + .process = eap_aka_sim_state_machine_process, + .compile_list = compile_list, + .dict = &dict_eap_aka_sim, +}; diff --git a/src/process/eap_aka_prime/all.mk b/src/process/eap_aka_prime/all.mk new file mode 100644 index 00000000000..08b8bd7be4a --- /dev/null +++ b/src/process/eap_aka_prime/all.mk @@ -0,0 +1,10 @@ +TARGETNAME := process_eap_aka_prime + +ifneq "$(OPENSSL_LIBS)" "" +TARGET := $(TARGETNAME).a +endif + +SOURCES := base.c + +TGT_PREREQS := libfreeradius-util.a libfreeradius-eap-aka-sim.a + diff --git a/src/process/eap_aka_prime/base.c b/src/process/eap_aka_prime/base.c new file mode 100644 index 00000000000..b426dfa1091 --- /dev/null +++ b/src/process/eap_aka_prime/base.c @@ -0,0 +1,275 @@ +/* + * This program 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 + */ + +/** + * $Id$ + * @file src/process/eap_aka_prime/base.c + * @brief EAP-AKA' process module + * + * The state machine for EAP-SIM, EAP-AKA and EAP-AKA' is common to all methods + * and is in src/lib/eap_aka_sim/state_machine.c + * + * The process modules for the different EAP methods just define the sections + * for that EAP method, and parse different config items. + * + * @copyright 2021 Arran Cudbard-Bell + */ + +#include +#include +#include +#include +#include + +static CONF_PARSER submodule_config[] = { + { FR_CONF_OFFSET("network_name", FR_TYPE_STRING, eap_aka_sim_process_conf_t, network_name ) }, + { FR_CONF_OFFSET("request_identity", FR_TYPE_UINT32, eap_aka_sim_process_conf_t, request_identity ), + .func = cf_table_parse_uint32, .uctx = &(cf_table_parse_ctx_t){ .table = fr_aka_sim_id_request_table, .len = &fr_aka_sim_id_request_table_len }}, + { FR_CONF_OFFSET("strip_permanent_identity_hint", FR_TYPE_BOOL, eap_aka_sim_process_conf_t, + strip_permanent_identity_hint ), .dflt = "yes" }, + { FR_CONF_OFFSET("ephemeral_id_length", FR_TYPE_SIZE, eap_aka_sim_process_conf_t, ephemeral_id_length ), .dflt = "14" }, /* 14 for compatibility */ + { FR_CONF_OFFSET("protected_success", FR_TYPE_BOOL, eap_aka_sim_process_conf_t, protected_success ), .dflt = "no" }, + + CONF_PARSER_TERMINATOR +}; + +static virtual_server_compile_t const compile_list[] = { + /* + * Identity negotiation + * The initial identity here is the EAP-Identity. + * We can then choose to request additional + * identities. + */ + { + .name = "recv", + .name2 = "Identity-Response", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_identity_response) + }, + { + .name = "send", + .name2 = "Identity-Request", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_common_identity_request) + }, + + /* + * Optional override sections if the user *really* + * wants to apply special policies for subsequent + * request/response rounds. + */ + { + .name = "send", + .name2 = "AKA-Identity-Request", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_aka_identity_request) + }, + { + .name = "recv", + .name2 = "AKA-Identity-Response", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_aka_identity_response) + }, + + /* + * Full-Authentication + */ + { + .name = "send", + .name2 = "Challenge-Request", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_aka_challenge_request) + }, + { + .name = "recv", + .name2 = "Challenge-Response", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_aka_challenge_response) + }, + + /* + * Fast-Re-Authentication + */ + { + .name = "send", + .name2 = "Reauthentication-Request", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_common_reauthentication_request) + }, + { + .name = "recv", + .name2 = "Reauthentication-Response", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_reauthentication_response) + }, + + /* + * Failures originating from the supplicant + */ + { + .name = "recv", + .name2 = "Client-Error", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_client_error) + }, + { + .name = "recv", + .name2 = "Authentication-Reject", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_aka_authentication_reject) + }, + { + .name = "recv", + .name2 = "Syncronization-Failure", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_aka_syncronization_failure) + }, + + /* + * Failure originating from the server + */ + { + .name = "send", + .name2 = "Failure-Notification", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_common_failure_notification) + }, + { + .name = "recv", + .name2 = "Failure-Notification-ACK", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_failure_notification_ack) + }, + + /* + * Protected success indication + */ + { + .name = "send", + .name2 = "Success-Notification", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_common_success_notification) + }, + { + .name = "recv", + .name2 = "Success-Notification-ACK", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_success_notification_ack) + }, + + /* + * Final EAP-Success and EAP-Failure messages + */ + { + .name = "send", + .name2 = "EAP-Success", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_eap_success) + }, + { + .name = "send", + .name2 = "EAP-Failure", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_eap_failure) + }, + + /* + * Fast-Reauth vectors + */ + { + .name = "store", + .name2 = "session", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.store_session) + }, + { + .name = "load", + .name2 = "session", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.load_session) + }, + { + .name = "clear", + .name2 = "session", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.clear_session) + }, + + /* + * Pseudonym processing + */ + { + .name = "store", + .name2 = "pseudonym", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.store_pseudonym) + }, + { + .name = "load", + .name2 = "pseudonym", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.load_pseudonym) + }, + { + .name = "clear", + .name2 = "pseudonym", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.clear_pseudonym) + }, + + COMPILE_TERMINATOR +}; + +static int mod_instantiate(void *instance, UNUSED CONF_SECTION *conf) +{ + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(instance, eap_aka_sim_process_conf_t); + + inst->type = FR_EAP_METHOD_AKA_PRIME; + + return 0; +} + +static int mod_load(void) +{ + if (unlikely(fr_aka_sim_init() < 0)) return -1; + + fr_aka_sim_xlat_register(); + + return 0; +} + +static void mod_unload(void) +{ + fr_aka_sim_xlat_unregister(); + + fr_aka_sim_free(); +} + +extern fr_process_module_t process_eap_aka_prime; +fr_process_module_t process_eap_aka_prime = { + .magic = RLM_MODULE_INIT, + .name = "process_eap_aka_prime", + .onload = mod_load, + .unload = mod_unload, + .config = submodule_config, + .instantiate = mod_instantiate, + .inst_size = sizeof(eap_aka_sim_process_conf_t), + .inst_type = "eap_aka_sim_process_conf_t", + + .process = eap_aka_sim_state_machine_process, + .compile_list = compile_list, + .dict = &dict_eap_aka_sim, +}; diff --git a/src/process/eap_sim/all.mk b/src/process/eap_sim/all.mk new file mode 100644 index 00000000000..eac5be85c66 --- /dev/null +++ b/src/process/eap_sim/all.mk @@ -0,0 +1,10 @@ +TARGETNAME := process_eap_sim + +ifneq "$(OPENSSL_LIBS)" "" +TARGET := $(TARGETNAME).a +endif + +SOURCES := base.c + +TGT_PREREQS := libfreeradius-util.a libfreeradius-eap-aka-sim.a + diff --git a/src/process/eap_sim/base.c b/src/process/eap_sim/base.c new file mode 100644 index 00000000000..563fa2ed5b8 --- /dev/null +++ b/src/process/eap_sim/base.c @@ -0,0 +1,262 @@ +/* + * This program 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 + */ + +/** + * $Id$ + * @file src/process/eap_sim/base.c + * @brief EAP-SIM process module + * + * The state machine for EAP-SIM, EAP-AKA and EAP-AKA' is common to all methods + * and is in src/lib/eap_aka_sim/state_machine.c + * + * The process modules for the different EAP methods just define the sections + * for that EAP method, and parse different config items. + * + * @copyright 2021 Arran Cudbard-Bell + */ + +#include +#include +#include +#include +#include + +static CONF_PARSER submodule_config[] = { + { FR_CONF_OFFSET("request_identity", FR_TYPE_UINT32, eap_aka_sim_process_conf_t, request_identity ), + .func = cf_table_parse_uint32, .uctx = &(cf_table_parse_ctx_t){ .table = fr_aka_sim_id_request_table, .len = &fr_aka_sim_id_request_table_len }}, + { FR_CONF_OFFSET("strip_permanent_identity_hint", FR_TYPE_BOOL, eap_aka_sim_process_conf_t, + strip_permanent_identity_hint ), .dflt = "yes" }, + { FR_CONF_OFFSET("ephemeral_id_length", FR_TYPE_SIZE, eap_aka_sim_process_conf_t, ephemeral_id_length ), .dflt = "14" }, /* 14 for compatibility */ + { FR_CONF_OFFSET("protected_success", FR_TYPE_BOOL, eap_aka_sim_process_conf_t, protected_success ), .dflt = "no" }, + + CONF_PARSER_TERMINATOR +}; + +static virtual_server_compile_t compile_list[] = { + /* + * Identity negotiation + * The initial identity here is the EAP-Identity. + * We can then choose to request additional + * identities. + */ + { + .name = "recv", + .name2 = "Identity-Response", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_identity_response) + }, + { + .name = "send", + .name2 = "Identity-Request", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_common_identity_request) + }, + + /* + * Optional override sections if the user *really* + * wants to apply special policies for subsequent + * request/response rounds. + */ + { + .name = "send", + .name2 = "Start", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_sim_start_request) + }, + { + .name = "recv", + .name2 = "Start", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_sim_start_response) + }, + + /* + * Full-Authentication + */ + { + .name = "send", + .name2 = "Challenge-Request", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_sim_challenge_request) + }, + { + .name = "recv", + .name2 = "Challenge-Response", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_sim_challenge_response) + }, + + /* + * Fast-Re-Authentication + */ + { + .name = "send", + .name2 = "Reauthentication-Request", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_common_reauthentication_request) + }, + { + .name = "recv", + .name2 = "Reauthentication-Response", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_reauthentication_response) + }, + + /* + * Failures originating from the supplicant + */ + { + .name = "recv", + .name2 = "Client-Error", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_client_error) + }, + + /* + * Failure originating from the server + */ + { + .name = "send", + .name2 = "Failure-Notification", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_common_failure_notification) + }, + { + .name = "recv", + .name2 = "Failure-Notification-ACK", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_failure_notification_ack) + }, + + /* + * Protected success indication + */ + { + .name = "send", + .name2 = "Success-Notification", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_common_success_notification) + }, + { + .name = "recv", + .name2 = "Success-Notification-ACK", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_success_notification_ack) + }, + + /* + * Final EAP-Success and EAP-Failure messages + */ + { + .name = "send", + .name2 = "EAP-Success", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_eap_success) + }, + { + .name = "send", + .name2 = "EAP-Failure", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_eap_failure) + }, + + /* + * Fast-Reauth vectors + */ + { + .name = "store", + .name2 = "session", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.store_session) + }, + { + .name = "load", + .name2 = "session", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.load_session) + }, + { + .name = "clear", + .name2 = "session", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.clear_session) + }, + + /* + * Pseudonym processing + */ + { + .name = "store", + .name2 = "pseudonym", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.store_pseudonym) + }, + { + .name = "load", + .name2 = "pseudonym", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.load_pseudonym) + }, + { + .name = "clear", + .name2 = "pseudonym", + .component = MOD_AUTHORIZE, + .offset = offsetof(eap_aka_sim_process_conf_t, actions.clear_pseudonym) + }, + + COMPILE_TERMINATOR +}; + +static int mod_instantiate(void *instance, UNUSED CONF_SECTION *conf) +{ + eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(instance, eap_aka_sim_process_conf_t); + + inst->type = FR_EAP_METHOD_SIM; + + return 0; +} + +static int mod_load(void) +{ + if (unlikely(fr_aka_sim_init() < 0)) return -1; + + fr_aka_sim_xlat_register(); + + return 0; +} + +static void mod_unload(void) +{ + fr_aka_sim_xlat_unregister(); + + fr_aka_sim_free(); +} + +extern fr_process_module_t process_eap_sim; +fr_process_module_t process_eap_sim = { + .magic = RLM_MODULE_INIT, + .name = "process_eap_sim", + .onload = mod_load, + .unload = mod_unload, + .config = submodule_config, + .instantiate = mod_instantiate, + .inst_size = sizeof(eap_aka_sim_process_conf_t), + .inst_type = "eap_aka_sim_process_conf_t", + + .process = eap_aka_sim_state_machine_process, + .compile_list = compile_list, + .dict = &dict_eap_aka_sim, +}; diff --git a/src/tests/eapol_test/config/aka-prime/methods-enabled/aka-prime b/src/tests/eapol_test/config/aka-prime/methods-enabled/aka-prime index 47df1bf44b6..f751c59d38a 100644 --- a/src/tests/eapol_test/config/aka-prime/methods-enabled/aka-prime +++ b/src/tests/eapol_test/config/aka-prime/methods-enabled/aka-prime @@ -2,6 +2,5 @@ type = aka_prime default_eap_type = aka-prime aka_prime { - network_name = "testing123" virtual_server = "eap-aka-prime" } diff --git a/src/tests/eapol_test/config/aka-prime/sites-enabled/aka-prime b/src/tests/eapol_test/config/aka-prime/sites-enabled/aka-prime index 87609764853..3135d3bf345 100644 --- a/src/tests/eapol_test/config/aka-prime/sites-enabled/aka-prime +++ b/src/tests/eapol_test/config/aka-prime/sites-enabled/aka-prime @@ -1,6 +1,10 @@ server eap-aka-prime { namespace = eap-aka-prime + eap-aka-prime { + network_name = "testing123" + } + recv Identity-Response { "%(debug_attr:&session-state.)" if (!&session-state.Tmp-String-0) { diff --git a/src/tests/eapol_test/config/aka/methods-enabled/aka b/src/tests/eapol_test/config/aka/methods-enabled/aka index da4fd3ac8ea..8b184ac36d1 100644 --- a/src/tests/eapol_test/config/aka/methods-enabled/aka +++ b/src/tests/eapol_test/config/aka/methods-enabled/aka @@ -2,6 +2,5 @@ type = aka default_eap_type = aka aka { - network_name = "testing123" virtual_server = "eap-aka" } diff --git a/src/tests/eapol_test/config/aka/sites-enabled/aka b/src/tests/eapol_test/config/aka/sites-enabled/aka index a3e422ac630..01fc66a9de7 100644 --- a/src/tests/eapol_test/config/aka/sites-enabled/aka +++ b/src/tests/eapol_test/config/aka/sites-enabled/aka @@ -2,6 +2,10 @@ server eap-aka { namespace = eap-aka + eap-aka { + network_name = "testing123" + } + recv Identity-Response { "%(debug_attr:&session-state.)" if (!&session-state.Tmp-String-0) {