From: Arran Cudbard-Bell Date: Sun, 10 Dec 2017 18:33:44 +0000 (+0000) Subject: Add support for checkcodes X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=9e2ed641ac7e4f4ea9f06e3918c3e9ede6150b72;p=thirdparty%2Ffreeradius-server.git Add support for checkcodes Add support for result_ind Make signing digests flexible in preparation for EAP-AKA' Add success notification state for result_ind --- diff --git a/src/modules/rlm_eap/lib/sim/crypto.c b/src/modules/rlm_eap/lib/sim/crypto.c index bb6b68520c7..994e44b2ddc 100644 --- a/src/modules/rlm_eap/lib/sim/crypto.c +++ b/src/modules/rlm_eap/lib/sim/crypto.c @@ -35,6 +35,120 @@ RCSID("$Id$") #include #include +/** Free OpenSSL memory associated with our checkcode ctx + * + * @param[in] checkcode to free. + * @return 0 + */ +static int _fr_sim_crypto_free_checkcode(fr_sim_checkcode_t *checkcode) +{ + if (checkcode->md_ctx) EVP_MD_CTX_destroy(checkcode->md_ctx); + return 0; +} + +/** Initialise checkcode message digest + * + * @param[in] ctx to allocate checkcode structure in. + * @param[out] checkcode a new checkcode structure. + * @param[in] md to use when calculating the checkcode, + * either EVP_sha1(), or EVP_sha256(). + * @return + * - 0 on success. + * - -1 on failure. + */ +int fr_sim_crypto_init_checkcode(TALLOC_CTX *ctx, fr_sim_checkcode_t **checkcode, EVP_MD const *md) +{ + *checkcode = talloc_zero(ctx, fr_sim_checkcode_t); + if (!*checkcode) { + fr_strerror_printf("Out of memory"); + return -1; + } + + (*checkcode)->md_ctx = EVP_MD_CTX_create(); + if (!(*checkcode)->md_ctx) { + tls_strerror_printf(true, "Failed creating MD ctx"); + error: + TALLOC_FREE(*checkcode); + return -1; + } + if (EVP_DigestInit_ex((*checkcode)->md_ctx, md, NULL) != 1) { + tls_strerror_printf(true, "Failed intialising MD ctx"); + goto error; + } + + talloc_set_destructor(*checkcode, _fr_sim_crypto_free_checkcode); + + return 0; +} + +/** Digest a packet, updating the checkcode + * + * Call #fr_sim_crypto_finalise_checkcode to obtain the final checkcode value. + * + * @param[in,out] checkcode if *checkcode is NULL, a new checkcode structure + * will be allocated and the message digest context + * will be initialised before the provided + * #eap_packet is fed into the digest. + * @param[in] eap_packet to digest. + * @return + * - 0 on success. + * - -1 on failure. + */ +int fr_sim_crypto_update_checkcode(fr_sim_checkcode_t *checkcode, eap_packet_t *eap_packet) +{ + uint16_t packet_len; + eap_packet_raw_t eap_hdr; + + eap_hdr.code = eap_packet->code; + eap_hdr.id = eap_packet->id; + packet_len = htons((sizeof(eap_hdr) + eap_packet->type.length) & UINT16_MAX); /* EAP Header + Method + SIM data */ + memcpy(&eap_hdr.length, &packet_len, sizeof(packet_len)); + eap_hdr.data[0] = eap_packet->type.num; + + /* + * Digest the header + */ + if (EVP_DigestUpdate(checkcode->md_ctx, &eap_hdr, sizeof(eap_hdr)) != 1) { + tls_strerror_printf(true, "Failed digesting EAP header"); + return -1; + } + + /* + * Digest the packet + */ + if (EVP_DigestUpdate(checkcode->md_ctx, eap_packet->type.data, eap_packet->type.length) != 1) { + tls_strerror_printf(true, "Failed digesting packet data"); + return -1; + } + + return 0; +} + +/** Write out the final checkcode value + * + * @param[out] out Where to write the checkcode value. Must be at least 20 + * bytes if MD was SHA1, or 32 bytes if MD was SHA256. + * @param[in] outlen Length of the output buffer. + * @param[in,out] checkcode structure to get final digest from and to tree. + * @return + * - <= 0 on failure. + * - > 0 the number of bytes written to out. + */ +ssize_t fr_sim_crypto_finalise_checkcode(uint8_t *out, fr_sim_checkcode_t **checkcode) +{ + unsigned int len; + + if (EVP_DigestFinal_ex((*checkcode)->md_ctx, out, &len) != 1) { + tls_strerror_printf(true, "Failed finalising checkcode digest"); + TALLOC_FREE(*checkcode); + return -1; + } + + TALLOC_FREE(*checkcode); + + return len; +} + /** Locate the start of the AT_MAC value in the buffer * * @param[in,out] data to search for the AT_MAC in. @@ -77,29 +191,35 @@ static int fr_sim_find_mac(uint8_t const **out, uint8_t *data, size_t data_len) return 1; } -/** Append AT_MAC to the end a packet. +/** Calculate the digest value for a packet + * + * Run a digest over a fake EAP header, the entire SIM packet and any extra HMAC data, + * writing a truncated (16 byte) digest value to out. * - * Run SHA1 digest over a fake EAP header, the entire SIM packet and any extra HMAC data, - * writing out the complete AT_HMAC and digest to out. + * @note The 16 byte digest field in the packet must have either been zeroed out before + * this function is called (as it is when encoding data), or #zero_mac must be set + * to true. * * @param[out] out Where to write the digest. * @param[in] eap_packet to extract header values from. + * @param[in] zero_mac Assume the mac field is not zeroed (i.e. received packet) + * and skip it during mac calculation feeding in 16 zeroed + * bytes in its place. * @param[in] key to use to sign the packet. * @param[in] key_len Length of the key. * @param[in] hmac_extra data to concatenate with the packet when calculating the HMAC * (may be NULL). - * @param[in] hmac_extra_len Length of hmac_extra. + * @param[in] hmac_extra_len Length of hmac_extra (may be zero). * @return * - < 0 on failure. * - 0 if there's no MAC attribute to verify. * - > 0 the number of bytes written to out. */ ssize_t fr_sim_crypto_sign_packet(uint8_t out[16], eap_packet_t *eap_packet, bool zero_mac, - uint8_t const *key, size_t const key_len, + EVP_MD const *md, uint8_t const *key, size_t const key_len, uint8_t const *hmac_extra, size_t const hmac_extra_len) { EVP_MD_CTX *md_ctx = NULL; - EVP_MD const *md = EVP_sha1(); EVP_PKEY *pkey; uint8_t digest[SHA1_DIGEST_LENGTH]; @@ -143,13 +263,13 @@ ssize_t fr_sim_crypto_sign_packet(uint8_t out[16], eap_packet_t *eap_packet, boo FR_PROTO_HEX_DUMP("hmac input eap_hdr", (uint8_t *)&eap_hdr, sizeof(eap_hdr)); if (EVP_DigestSignUpdate(md_ctx, &eap_hdr, sizeof(eap_hdr)) != 1) { - tls_strerror_printf(true, "Failed digesting EAP header"); + tls_strerror_printf(true, "Failed digesting EAP data"); goto error; } /* * Digest the packet up to the AT_MAC, value, then - * ingest 16 bytes of zero. + * digest 16 bytes of zero. */ if (zero_mac) { switch (fr_sim_find_mac(&mac, p, end - p)) { @@ -163,7 +283,7 @@ ssize_t fr_sim_crypto_sign_packet(uint8_t out[16], eap_packet_t *eap_packet, boo * AT_MAC header and reserved bytes. */ if (EVP_DigestSignUpdate(md_ctx, p, mac - p) != 1) { - tls_strerror_printf(true, "Failed digesting header"); + tls_strerror_printf(true, "Failed digesting packet data (before MAC)"); goto error; } p += mac - p; @@ -173,7 +293,7 @@ ssize_t fr_sim_crypto_sign_packet(uint8_t out[16], eap_packet_t *eap_packet, boo * simulated the zeroed out Mac. */ if (EVP_DigestSignUpdate(md_ctx, zero, sizeof(zero)) != 1) { - tls_strerror_printf(true, "Failed zeroes mac"); + tls_strerror_printf(true, "Failed digesting zeroed MAC"); goto error; } p += sizeof(zero); @@ -193,7 +313,7 @@ ssize_t fr_sim_crypto_sign_packet(uint8_t out[16], eap_packet_t *eap_packet, boo * Digest the rest of the packet. */ if (EVP_DigestSignUpdate(md_ctx, p, end - p) != 1) { - tls_strerror_printf(true, "Failed digesting body"); + tls_strerror_printf(true, "Failed digesting packet data"); goto error; } @@ -382,45 +502,6 @@ int fr_sim_crypto_kdf_0_umts(fr_sim_keys_t *keys) return 0; } -static int fr_sim_crypto_aka_prime_prf(uint8_t *out, size_t outlen, - uint8_t const *key, size_t key_len, uint8_t const *in, size_t in_len) -{ - uint8_t *p = out, *end = p + outlen; - uint8_t c = 0; - uint8_t digest[SHA256_DIGEST_LENGTH]; - HMAC_CTX *hmac; - - MEM(hmac = HMAC_CTX_new()); - if (HMAC_Init_ex(hmac, key, key_len, EVP_sha256(), NULL) != 1) { - error: - tls_strerror_printf(true, "HMAC failure"); - HMAC_CTX_free(hmac); - return -1; - } - - while (p < end) { - unsigned int len = sizeof(digest); - size_t copy; - - c++; - - if (HMAC_Init_ex(hmac, NULL, 0, EVP_sha256(), NULL) != 1) goto error; - if ((p != out) && HMAC_Update(hmac, digest, sizeof(digest)) != 1) goto error; /* Ingest last round */ - if (HMAC_Update(hmac, in, in_len) != 1) goto error; /* Ingest s */ - if (HMAC_Update(hmac, &c, sizeof(c)) != 1) goto error; /* Ingest round number */ - if (HMAC_Final(hmac, digest, &len) != 1) goto error; /* Output T(i) */ - - copy = p - end; - if (copy > SHA256_DIGEST_LENGTH) copy = SHA256_DIGEST_LENGTH; - - memcpy(p, digest, copy); - p += copy; - } - HMAC_CTX_free(hmac); - - return 0; -} - /** EAP-AKA Prime CK Prime IK Prime derivation function * * @note expects keys to contain a SIM_VECTOR_UMTS. @@ -507,6 +588,45 @@ int fr_sim_crypto_derive_ck_ik_prime(fr_sim_keys_t *keys) return 0; } +static int fr_sim_crypto_aka_prime_prf(uint8_t *out, size_t outlen, + uint8_t const *key, size_t key_len, uint8_t const *in, size_t in_len) +{ + uint8_t *p = out, *end = p + outlen; + uint8_t c = 0; + uint8_t digest[SHA256_DIGEST_LENGTH]; + HMAC_CTX *hmac; + + MEM(hmac = HMAC_CTX_new()); + if (HMAC_Init_ex(hmac, key, key_len, EVP_sha256(), NULL) != 1) { + error: + tls_strerror_printf(true, "HMAC failure"); + HMAC_CTX_free(hmac); + return -1; + } + + while (p < end) { + unsigned int len = sizeof(digest); + size_t copy; + + c++; + + if (HMAC_Init_ex(hmac, NULL, 0, EVP_sha256(), NULL) != 1) goto error; + if ((p != out) && HMAC_Update(hmac, digest, sizeof(digest)) != 1) goto error; /* Ingest last round */ + if (HMAC_Update(hmac, in, in_len) != 1) goto error; /* Ingest s */ + if (HMAC_Update(hmac, &c, sizeof(c)) != 1) goto error; /* Ingest round number */ + if (HMAC_Final(hmac, digest, &len) != 1) goto error; /* Output T(i) */ + + copy = p - end; + if (copy > SHA256_DIGEST_LENGTH) copy = SHA256_DIGEST_LENGTH; + + memcpy(p, digest, copy); + p += copy; + } + HMAC_CTX_free(hmac); + + return 0; +} + /** EAP-AKA Prime Key derivation function * * @note expects keys to contain a SIM_VECTOR_UMTS. diff --git a/src/modules/rlm_eap/lib/sim/encode.c b/src/modules/rlm_eap/lib/sim/encode.c index 739418c10c8..d094729c4d2 100644 --- a/src/modules/rlm_eap/lib/sim/encode.c +++ b/src/modules/rlm_eap/lib/sim/encode.c @@ -965,13 +965,10 @@ ssize_t fr_sim_encode_pair(uint8_t *out, size_t outlen, vp_cursor_t *cursor, voi return ret; } -ssize_t fr_sim_encode(REQUEST *request, fr_dict_attr_t const *parent, uint8_t type, - VALUE_PAIR *to_encode, eap_packet_t *eap_packet, fr_sim_keys_t const *keys) +ssize_t fr_sim_encode(REQUEST *request, VALUE_PAIR *to_encode, void *encode_ctx) { VALUE_PAIR *vp; - unsigned int id, eap_code; - uint8_t *buff, *p, *end, *hmac = NULL; size_t len = 0; ssize_t slen; @@ -980,35 +977,21 @@ ssize_t fr_sim_encode(REQUEST *request, fr_dict_attr_t const *parent, uint8_t ty unsigned char subtype; vp_cursor_t cursor; - fr_sim_encode_ctx_t packet_ctx = { - .root = parent, - .keys = keys, - .iv_included = false - }; + fr_sim_encode_ctx_t *packet_ctx = encode_ctx; + eap_packet_t *eap_packet = packet_ctx->eap_packet; /* * Encoded_msg is now an EAP-SIM message. * It might be too big for putting into an * EAP packet. */ - vp = fr_pair_find_by_child_num(to_encode, parent, FR_SIM_SUBTYPE, TAG_ANY); + vp = fr_pair_find_by_child_num(to_encode, packet_ctx->root, FR_SIM_SUBTYPE, TAG_ANY); if (!vp) { REDEBUG("Missing subtype attribute"); return -1; } subtype = vp->vp_uint16; - vp = fr_pair_find_by_child_num(to_encode, parent, FR_EAP_CODE, TAG_ANY); - eap_code = vp ? vp->vp_uint32 : FR_EAP_CODE_REQUEST; - - /* - * Fill in some bits in the EAP packet - * - * These are needed even if we're sending an almost empty packet. - */ - if (eap_packet->code != FR_EAP_CODE_SUCCESS) eap_packet->code = eap_code; - eap_packet->type.num = type; - /* * Group attributes with similar lineages together */ @@ -1018,7 +1001,7 @@ ssize_t fr_sim_encode(REQUEST *request, fr_dict_attr_t const *parent, uint8_t ty /* * Fast path... */ - if (!next_encodable(&cursor, &packet_ctx)) { + if (!next_encodable(&cursor, packet_ctx)) { MEM(buff = talloc_array(eap_packet, uint8_t, 3)); buff[0] = subtype; /* SIM or AKA subtype */ @@ -1042,7 +1025,7 @@ ssize_t fr_sim_encode(REQUEST *request, fr_dict_attr_t const *parent, uint8_t ty /* * Add space in the packet for AT_MAC */ - vp = fr_pair_find_by_child_num(to_encode, parent, FR_EAP_SIM_MAC, TAG_ANY); + vp = fr_pair_find_by_child_num(to_encode, packet_ctx->root, FR_EAP_SIM_MAC, TAG_ANY); if (vp) { if ((end - p) < SIM_MAC_SIZE) { fr_strerror_printf("Insufficient space to store AT_MAC"); @@ -1065,7 +1048,7 @@ ssize_t fr_sim_encode(REQUEST *request, fr_dict_attr_t const *parent, uint8_t ty */ (void)fr_pair_cursor_first(&cursor); while ((vp = fr_pair_cursor_current(&cursor))) { - slen = fr_sim_encode_pair(p, end - p, &cursor, &packet_ctx); + slen = fr_sim_encode_pair(p, end - p, &cursor, packet_ctx); if (slen < 0) { error: talloc_free(buff); @@ -1083,9 +1066,9 @@ ssize_t fr_sim_encode(REQUEST *request, fr_dict_attr_t const *parent, uint8_t ty */ if (do_hmac) { slen = fr_sim_crypto_sign_packet(hmac, eap_packet, false, - keys->k_aut, keys->k_aut_len, - keys->vector_type == SIM_VECTOR_GSM ? keys->gsm.nonce_mt : NULL, - keys->vector_type == SIM_VECTOR_GSM ? sizeof(keys->gsm.nonce_mt) : 0); + packet_ctx->hmac_md, + packet_ctx->keys->k_aut, packet_ctx->keys->k_aut_len, + packet_ctx->hmac_extra, packet_ctx->hmac_extra_len); if (slen < 0) goto error; FR_PROTO_HEX_DUMP("hmac attribute", hmac - 4, SIM_MAC_SIZE); } diff --git a/src/modules/rlm_eap/lib/sim/sim_proto.h b/src/modules/rlm_eap/lib/sim/sim_proto.h index 8b3099e6ccd..73cc14881d1 100644 --- a/src/modules/rlm_eap/lib/sim/sim_proto.h +++ b/src/modules/rlm_eap/lib/sim/sim_proto.h @@ -109,6 +109,23 @@ typedef struct { size_t xres_len; //!< Length of res (it's variable). } fr_sim_vector_umts_t; +/** Stores our checkcode state + * + * The checkcode is a hash of all identity packets exchanged + * up until the challenge is sent. + * + * It allows both parties to verify that they've seen the same + * sequence of packets. + */ +typedef struct { + EVP_MD const *md; //!< Type of message digest, either EVP_Sha1() AKA + ///< or EVP_Sha256() AKA'. + EVP_MD_CTX *md_ctx; //!< Context to hold state of digest as we + ///< consume packets. + uint8_t checkcode[32]; //!< Final digest value 20 bytes for SHA1, + ///< 32 bytes for SHA-256 +} fr_sim_checkcode_t; + /** Master key state struct for all SIMlike EAP protocols * */ @@ -154,8 +171,8 @@ typedef struct { /* * Intermediates */ - uint8_t ck_prime[SIM_VECTOR_UMTS_CK_SIZE]; - uint8_t ik_prime[SIM_VECTOR_UMTS_IK_SIZE]; + uint8_t ck_prime[SIM_VECTOR_UMTS_CK_SIZE]; //!< Derived from CK, for AKA'. + uint8_t ik_prime[SIM_VECTOR_UMTS_IK_SIZE]; //!< Derived from IK, for AKA'. /* * Outputs @@ -183,6 +200,15 @@ typedef struct { fr_sim_keys_t const *keys; //!< From the EAP session. uint8_t iv[SIM_IV_SIZE]; //!< Generated by us using our PRNG. bool iv_included; //!< Whether we've already added an IV to this packet. + + /* + * 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_sim_encode_ctx_t; typedef struct _eap_session eap_session_t; @@ -205,8 +231,7 @@ int fr_sim_decode(REQUEST *request, vp_cursor_t *decoded, */ ssize_t fr_sim_encode_pair(uint8_t *out, size_t outlen, vp_cursor_t *cursor, void *encoder_ctx); -ssize_t fr_sim_encode(REQUEST *request, fr_dict_attr_t const *parent, uint8_t type, - VALUE_PAIR *to_encode, eap_packet_t *eap_packet, fr_sim_keys_t const *keys); +ssize_t fr_sim_encode(REQUEST *request, VALUE_PAIR *to_encode, void *encode_ctx); /* * base.c @@ -222,8 +247,14 @@ int fr_sim_global_init(void); /* * crypto.c */ +int fr_sim_crypto_init_checkcode(TALLOC_CTX *ctx, fr_sim_checkcode_t **checkcode, EVP_MD const *md); + +int fr_sim_crypto_update_checkcode(fr_sim_checkcode_t *checkcode, eap_packet_t *eap_packet); + +ssize_t fr_sim_crypto_finalise_checkcode(uint8_t *out, fr_sim_checkcode_t **checkcode); + ssize_t fr_sim_crypto_sign_packet(uint8_t out[16], eap_packet_t *eap_packet, bool zero_mac, - uint8_t const *key, size_t const key_len, + 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_sim_crypto_kdf_0_gsm(fr_sim_keys_t *keys); diff --git a/src/modules/rlm_eap/types/rlm_eap_aka/eap_aka.h b/src/modules/rlm_eap/types/rlm_eap_aka/eap_aka.h index face29eeec5..265e1fa00b1 100644 --- a/src/modules/rlm_eap/types/rlm_eap_aka/eap_aka.h +++ b/src/modules/rlm_eap/types/rlm_eap_aka/eap_aka.h @@ -33,12 +33,13 @@ RCSIDH(rlm_eap_aka_eap_aka_h, "$Id$") * In server_start, we send a EAP-AKA Start message. */ typedef enum { - EAP_AKA_SERVER_START = 0, //!< Initial state. - EAP_AKA_SERVER_IDENTITY = 5, //!< Attempting to discover permanent + EAP_AKA_SERVER_START = 0, //!< Initial state. + EAP_AKA_SERVER_IDENTITY, //!< Attempting to discover permanent ///< identity of the supplicant. - EAP_AKA_SERVER_CHALLENGE = 1, //!< We've challenged the supplicant. - EAP_AKA_SERVER_SUCCESS = 2, //!< Authentication completed successfully. - EAP_AKA_SERVER_GENERAL_FAILURE, + EAP_AKA_SERVER_CHALLENGE, //!< We've challenged the supplicant. + EAP_AKA_SERVER_SUCCESS_NOTIFICATION, //!< Send success notification. + EAP_AKA_SERVER_SUCCESS, //!< Authentication completed successfully. + EAP_AKA_SERVER_GENERAL_FAILURE_NOTIFICATION, //!< Send failure notification. EAP_AKA_SERVER_MAX_STATES } eap_aka_server_state_t; @@ -47,6 +48,11 @@ typedef struct { ///< or previously requested. eap_aka_server_state_t state; //!< Current session state. fr_sim_keys_t keys; //!< Various EAP-AKA keys. + + fr_sim_checkcode_t *checkcode_state; //!< Digest of all identity packets we've seen. + uint8_t checkcode[32]; //!< Checkcode we calculated. + size_t checkcode_len; //!< 0, 20 or 32 bytes. + int aka_id; //!< Packet ID. (replay protection). } eap_aka_session_t; 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 a70c08404b8..8f4b760442b 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 @@ -50,7 +50,7 @@ FR_NAME_NUMBER const aka_state_table[] = { static rlm_rcode_t mod_process(UNUSED void *arg, eap_session_t *eap_session); static CONF_PARSER submodule_config[] = { - { FR_CONF_OFFSET("request_identity", FR_TYPE_BOOL, rlm_eap_aka_t, request_identity ) }, + { FR_CONF_OFFSET("request_identity", FR_TYPE_BOOL, rlm_eap_aka_t, request_identity ), .dflt = "yes" }, { FR_CONF_OFFSET("virtual_server", FR_TYPE_STRING, rlm_eap_aka_t, virtual_server) }, CONF_PARSER_TERMINATOR }; @@ -63,11 +63,24 @@ static int eap_aka_compose(eap_session_t *eap_session) VALUE_PAIR *head = NULL, *vp; REQUEST *request = eap_session->request; ssize_t ret; + fr_sim_encode_ctx_t encoder_ctx = { + .root = dict_aka_root, + .keys = &eap_aka_session->keys, + + .iv = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + .iv_included = false, + + .hmac_md = EVP_sha1(), + .eap_packet = eap_session->this_round->request, + .hmac_extra = NULL, + .hmac_extra_len = 0 + }; fr_pair_cursor_init(&cursor, &eap_session->request->reply->vps); fr_pair_cursor_init(&to_encode, &head); - while ((fr_pair_cursor_next_by_ancestor(&cursor, dict_sim_root, TAG_ANY))) { + while ((fr_pair_cursor_next_by_ancestor(&cursor, dict_aka_root, TAG_ANY))) { vp = fr_pair_cursor_remove(&cursor); fr_pair_cursor_append(&to_encode, vp); } @@ -75,12 +88,11 @@ static int eap_aka_compose(eap_session_t *eap_session) RDEBUG2("Encoding EAP-AKA attributes"); rdebug_pair_list(L_DBG_LVL_2, request, head, NULL); + eap_session->this_round->request->type.num = FR_EAP_AKA; eap_session->this_round->request->id = eap_aka_session->aka_id++ & 0xff; eap_session->this_round->set_request_id = true; - ret = fr_sim_encode(eap_session->request, dict_aka_root, FR_EAP_AKA, - head, eap_session->this_round->request, - &eap_aka_session->keys); + ret = fr_sim_encode(eap_session->request, head, &encoder_ctx); fr_pair_cursor_first(&to_encode); fr_pair_cursor_free(&to_encode); @@ -124,6 +136,7 @@ static int eap_aka_send_identity_request(eap_session_t *eap_session) fr_cursor_t cursor; RDEBUG2("Sending AKA-Identity (%s)", fr_int2str(sim_id_request_table, eap_aka_session->id_req, "")); + eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; packet = request->reply; fr_cursor_init(&cursor, &packet->vps); @@ -157,6 +170,24 @@ static int eap_aka_send_identity_request(eap_session_t *eap_session) vp->vp_bool = true; fr_cursor_append(&cursor, vp); + /* + * Encode the packet + */ + if (eap_aka_compose(eap_session) < 0) return -1; + + /* + * Digest the packet contents, updating our checkcode. + */ + if (!eap_aka_session->checkcode_state && + fr_sim_crypto_init_checkcode(eap_aka_session, &eap_aka_session->checkcode_state, EVP_sha1()) < 0) { + RPEDEBUG("Failed initialising checkcode"); + return -1; + } + if (fr_sim_crypto_update_checkcode(eap_aka_session->checkcode_state, eap_session->this_round->request) < 0) { + RPEDEBUG("Failed updating checkcode"); + return -1; + } + return 0; } @@ -182,7 +213,7 @@ static int eap_aka_send_challenge(eap_session_t *eap_session) REQUEST *request = eap_session->request; eap_aka_session_t *eap_aka_session = talloc_get_type_abort(eap_session->opaque, eap_aka_session_t); - VALUE_PAIR **to_client, *vp; + VALUE_PAIR **to_peer, *vp; RADIUS_PACKET *packet; fr_sim_vector_src_t src = SIM_VECTOR_SRC_AUTO; @@ -196,26 +227,34 @@ static int eap_aka_send_challenge(eap_session_t *eap_session) } RDEBUG2("Sending AKA-Challenge"); + eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; /* - * to_client is the data to the client + * to_peer is the data to the client */ packet = eap_session->request->reply; - to_client = &packet->vps; + to_peer = &packet->vps; /* * Set the subtype to challenge */ - vp = fr_pair_afrom_child_num(packet, dict_aka_root, FR_EAP_AKA_SUBTYPE); + MEM(vp = fr_pair_afrom_child_num(packet, dict_aka_root, FR_EAP_AKA_SUBTYPE)); vp->vp_uint32 = FR_EAP_AKA_SUBTYPE_VALUE_AKA_CHALLENGE; - fr_pair_replace(to_client, vp); + fr_pair_replace(to_peer, vp); + + /* + * Indicate we'd like to use protected success messages + */ + MEM(vp = fr_pair_afrom_child_num(packet, dict_aka_root, FR_EAP_AKA_RESULT_IND)); + vp->vp_bool = true; + fr_pair_replace(to_peer, vp); /* * Okay, we got the challenge! Put it into an attribute. */ MEM(vp = fr_pair_afrom_child_num(packet, dict_aka_root, FR_EAP_AKA_RAND)); fr_pair_value_memcpy(vp, eap_aka_session->keys.umts.vector.rand, SIM_VECTOR_UMTS_RAND_SIZE); - fr_pair_add(to_client, vp); + fr_pair_replace(to_peer, vp); /* * Send the AUTN value to the client, so it can authenticate @@ -223,7 +262,41 @@ static int eap_aka_send_challenge(eap_session_t *eap_session) */ MEM(vp = fr_pair_afrom_child_num(packet, dict_aka_root, FR_EAP_AKA_AUTN)); fr_pair_value_memcpy(vp, eap_aka_session->keys.umts.vector.autn, SIM_VECTOR_UMTS_AUTN_SIZE); - fr_pair_add(to_client, vp); + fr_pair_replace(to_peer, vp); + + /* + * need to include an AT_MAC attribute so that it will get + * calculated. + */ + MEM(vp = fr_pair_afrom_child_num(packet, dict_aka_root, FR_EAP_AKA_MAC)); + fr_pair_value_memcpy(vp, hmac_zero, sizeof(hmac_zero)); + fr_pair_replace(to_peer, vp); + + /* + * If we have checkcode data, send that to the peer + * for validation. + */ + if (eap_aka_session->checkcode_state) { + ssize_t slen; + + slen = fr_sim_crypto_finalise_checkcode(eap_aka_session->checkcode, &eap_aka_session->checkcode_state); + if (slen < 0) { + RPEDEBUG("Failed calculating checkcode"); + return -1; + } + eap_aka_session->checkcode_len = slen; + + MEM(vp = fr_pair_afrom_child_num(packet, dict_aka_root, FR_EAP_AKA_CHECKCODE)); + fr_pair_value_memcpy(vp, eap_aka_session->checkcode, slen); + /* + * If we don't have checkcode data, then we exchanged + * no identity packets, so checkcode is zero. + */ + } else { + MEM(vp = fr_pair_afrom_child_num(packet, dict_aka_root, FR_EAP_AKA_CHECKCODE)); + eap_aka_session->checkcode_len = 0; + } + fr_pair_replace(to_peer, vp); /* * All set, calculate keys! @@ -231,23 +304,42 @@ static int eap_aka_send_challenge(eap_session_t *eap_session) fr_sim_crypto_kdf_0_umts(&eap_aka_session->keys); if (RDEBUG_ENABLED3) fr_sim_crypto_keys_log(request, &eap_aka_session->keys); + return 1; +} + +/** Send a success notification + * + */ +static void eap_aka_send_eap_success_notification(eap_session_t *eap_session) +{ + REQUEST *request = eap_session->request; + RADIUS_PACKET *packet = eap_session->request->reply; + fr_cursor_t cursor; + VALUE_PAIR *vp; + + RDEBUG2("Sending AKA-Notification (Success)"); + eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; + + fr_cursor_init(&cursor, &packet->vps); + /* - * need to include an AT_MAC attribute so that it will get - * calculated. + * Set the subtype to notification */ - vp = fr_pair_afrom_child_num(packet, dict_aka_root, FR_EAP_AKA_MAC); - fr_pair_value_memcpy(vp, hmac_zero, sizeof(hmac_zero)); - fr_pair_replace(to_client, vp); + vp = fr_pair_afrom_child_num(packet, dict_aka_root, FR_EAP_AKA_SUBTYPE); + vp->vp_uint32 = FR_EAP_AKA_SUBTYPE_VALUE_AKA_NOTIFICATION; + fr_cursor_append(&cursor, vp); - return 1; + vp = fr_pair_afrom_child_num(packet, dict_aka_root, FR_EAP_AKA_NOTIFICATION); + vp->vp_uint32 = FR_EAP_AKA_NOTIFICATION_VALUE_SUCCESS; + fr_cursor_append(&cursor, vp); } -/** Send a success message +/** Send a success message with MPPE-keys * * The only work to be done is the add the appropriate SEND/RECV * attributes derived from the MSK. */ -static void eap_aka_send_success(eap_session_t *eap_session) +static void eap_aka_send_eap_success(eap_session_t *eap_session) { REQUEST *request = eap_session->request; uint8_t *p; @@ -259,7 +351,7 @@ static void eap_aka_send_success(eap_session_t *eap_session) eap_session->this_round->request->code = FR_EAP_CODE_SUCCESS; eap_session->finished = true; - /* to_client is the data to the client. */ + /* to_peer is the data to the client. */ packet = eap_session->request->reply; eap_aka_session = talloc_get_type_abort(eap_session->opaque, eap_aka_session_t); @@ -269,10 +361,10 @@ static void eap_aka_send_success(eap_session_t *eap_session) eap_add_reply(eap_session->request, "MS-MPPE-Send-Key", p, EAP_TLS_MPPE_KEY_LEN); } -/** Send a success message +/** Send a failure message * */ -static void eap_aka_send_general_failure(eap_session_t *eap_session) +static void eap_aka_send_eap_failure_notification(eap_session_t *eap_session) { REQUEST *request = eap_session->request; RADIUS_PACKET *packet = eap_session->request->reply; @@ -280,11 +372,10 @@ static void eap_aka_send_general_failure(eap_session_t *eap_session) VALUE_PAIR *vp; RDEBUG2("Sending AKA-Notification (General-Failure)"); + eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; fr_cursor_init(&cursor, &packet->vps); - eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; - /* * Set the subtype to notification */ @@ -297,7 +388,7 @@ static void eap_aka_send_general_failure(eap_session_t *eap_session) fr_cursor_append(&cursor, vp); } -static void eap_aka_send_failure(eap_session_t *eap_session) +static void eap_aka_send_eap_failure(eap_session_t *eap_session) { eap_session->this_round->request->code = FR_EAP_CODE_FAILURE; } @@ -327,7 +418,6 @@ static void eap_aka_state_enter(eap_session_t *eap_session, */ case EAP_AKA_SERVER_IDENTITY: eap_aka_send_identity_request(eap_session); - eap_aka_compose(eap_session); break; /* @@ -338,18 +428,26 @@ static void eap_aka_state_enter(eap_session_t *eap_session, eap_aka_compose(eap_session); break; + /* + * Sent a protected success notification + */ + case EAP_AKA_SERVER_SUCCESS_NOTIFICATION: + eap_aka_send_eap_success_notification(eap_session); + eap_aka_compose(eap_session); + break; + /* * Send the EAP Success message */ case EAP_AKA_SERVER_SUCCESS: - eap_aka_send_success(eap_session); + eap_aka_send_eap_success(eap_session); return; /* * Send a general failure notification */ - case EAP_AKA_SERVER_GENERAL_FAILURE: - eap_aka_send_general_failure(eap_session); + case EAP_AKA_SERVER_GENERAL_FAILURE_NOTIFICATION: + eap_aka_send_eap_failure_notification(eap_session); eap_aka_compose(eap_session); return; @@ -370,15 +468,27 @@ static int process_eap_aka_identity(eap_session_t *eap_session, VALUE_PAIR *vps) fr_sim_id_type_t type = SIM_ID_TYPE_UNKNOWN; fr_sim_method_hint_t method = SIM_METHOD_HINT_UNKNOWN; + /* + * Digest the identity response + */ + if (fr_sim_crypto_update_checkcode(eap_aka_session->checkcode_state, eap_session->this_round->response) < 0) { + RPEDEBUG("Failed updating checkcode"); + return -1; + } + /* * See if we got an AT_IDENTITY */ id = fr_pair_find_by_child_num(vps, dict_aka_root, FR_EAP_AKA_IDENTITY, TAG_ANY); if (id && fr_sim_id_type(&type, &method, - eap_session->identity, talloc_array_length(eap_session->identity) - 1) < 0) { + eap_session->identity, talloc_array_length(eap_session->identity) - 1) == 0) { RDEBUG2("Failed parsing identity: %s", fr_strerror()); } + talloc_const_free(eap_aka_session->keys.identity); + eap_aka_session->keys.identity_len = id->vp_length; + MEM(eap_aka_session->keys.identity = talloc_memdup(eap_aka_session, id->vp_strvalue, id->vp_length)); + /* * Negotiate the next permissive form * if identity, or fail. @@ -395,8 +505,9 @@ static int process_eap_aka_identity(eap_session_t *eap_session, VALUE_PAIR *vps) break; case SIM_PERMANENT_ID_REQ: + eap_aka_state_enter(eap_session, eap_aka_session, EAP_AKA_SERVER_CHALLENGE); REDEBUG2("Failed to negotiate a usable identity"); - eap_aka_state_enter(eap_session, eap_aka_session, EAP_AKA_SERVER_GENERAL_FAILURE); +// eap_aka_state_enter(eap_session, eap_aka_session, EAP_AKA_SERVER_GENERAL_FAILURE_NOTIFICATION); break; } @@ -414,7 +525,7 @@ static int process_eap_aka_challenge(eap_session_t *eap_session, VALUE_PAIR *vps uint8_t calc_mac[SIM_MAC_HASH_SIZE]; ssize_t slen; - VALUE_PAIR *vp = NULL, *mac; + VALUE_PAIR *vp = NULL, *mac, *checkcode; mac = fr_pair_find_by_child_num(vps, dict_aka_root, FR_EAP_AKA_MAC, TAG_ANY); if (!mac) { @@ -428,7 +539,7 @@ static int process_eap_aka_challenge(eap_session_t *eap_session, VALUE_PAIR *vps } slen = fr_sim_crypto_sign_packet(calc_mac, eap_session->this_round->response, true, - eap_aka_session->keys.k_aut, sizeof(eap_aka_session->keys.k_aut), + EVP_sha1(), eap_aka_session->keys.k_aut, sizeof(eap_aka_session->keys.k_aut), NULL, 0); if (slen < 0) { RPEDEBUG("Failed calculating MAC"); @@ -436,19 +547,48 @@ static int process_eap_aka_challenge(eap_session_t *eap_session, VALUE_PAIR *vps } if (slen == 0) { - REDEBUG("Missing AT_MAC attribute in packet buffer"); + REDEBUG("Missing EAP-AKA-MAC attribute in packet buffer"); return -1; } if (memcmp(mac->vp_octets, calc_mac, sizeof(calc_mac)) == 0) { - RDEBUG2("MAC check succeed"); + RDEBUG2("EAP-AKA-MAC matches calculated MAC"); } else { - REDEBUG("MAC checked failed"); + REDEBUG("EAP-AKA-MAC does not match calculated MAC"); RHEXDUMP_INLINE(L_DBG_LVL_2, mac->vp_octets, SIM_MAC_HASH_SIZE, "Received"); RHEXDUMP_INLINE(L_DBG_LVL_2, calc_mac, SIM_MAC_HASH_SIZE, "Expected"); return -1; } + /* + * 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_child_num(vps, dict_aka_root, FR_EAP_AKA_CHECKCODE, TAG_ANY); + if (checkcode) { + if (checkcode->vp_length != eap_aka_session->checkcode_len) { + REDEBUG("Checkcode length (%zu) does not match calculated checkcode length (%zu)", + checkcode->vp_length, eap_aka_session->checkcode_len); + return -1; + } + + if (memcmp(checkcode->vp_octets, eap_aka_session->checkcode, eap_aka_session->checkcode_len) == 0) { + RDEBUG("EAP-AKA-Checkcode matches calculated checkcode"); + } else { + REDEBUG("EAP-AKA-Checkcode does not match calculated checkcode"); + RHEXDUMP_INLINE(L_DBG_LVL_2, checkcode->vp_octets, checkcode->vp_length, "Received"); + RHEXDUMP_INLINE(L_DBG_LVL_2, eap_aka_session->checkcode, + eap_aka_session->checkcode_len, "Expected"); + return -1; + } + /* + * Only print something if we calculated a checkcode + */ + } else if (eap_aka_session->checkcode_len > 0){ + RDEBUG2("Peer didn't include EAP-AKA-Checkcode, skipping checkcode validation"); + } + vp = fr_pair_find_by_child_num(vps, dict_aka_root, FR_EAP_AKA_RES, TAG_ANY); if (!vp) { REDEBUG("Missing EAP-AKA-RES from challenge response"); @@ -471,8 +611,16 @@ static int process_eap_aka_challenge(eap_session_t *eap_session, VALUE_PAIR *vps RDEBUG2("EAP-AKA-RES matches XRES"); - /* everything looks good, change states */ - eap_aka_state_enter(eap_session, eap_aka_session, EAP_AKA_SERVER_SUCCESS); + /* + * If the peer wants a Success notification, then + * send a success notification, otherwise send a + * normal EAP-Success. + */ + if (fr_pair_find_by_child_num(vps, dict_aka_root, FR_EAP_AKA_RESULT_IND, TAG_ANY)) { + eap_aka_state_enter(eap_session, eap_aka_session, EAP_AKA_SERVER_SUCCESS_NOTIFICATION); + } else { + eap_aka_state_enter(eap_session, eap_aka_session, EAP_AKA_SERVER_SUCCESS); + } return 0; } @@ -540,7 +688,7 @@ static rlm_rcode_t mod_process(UNUSED void *arg, eap_session_t *eap_session) case EAP_AKA_SYNCHRONIZATION_FAILURE: REDEBUG("EAP-AKA Peer synchronization failure"); failure: - eap_aka_send_failure(eap_session); + eap_aka_send_eap_failure(eap_session); return RLM_MODULE_REJECT; case EAP_AKA_AUTHENTICATION_REJECT: @@ -566,7 +714,15 @@ static rlm_rcode_t mod_process(UNUSED void *arg, eap_session_t *eap_session) return process_eap_aka_challenge(eap_session, vps) < 0 ? RLM_MODULE_FAIL : RLM_MODULE_HANDLED; } - case EAP_AKA_SERVER_GENERAL_FAILURE: + /* + * RFC says we ignore the ACK from the peer + * and always send a success. + */ + case EAP_AKA_SERVER_SUCCESS_NOTIFICATION: + eap_aka_state_enter(eap_session, eap_aka_session, EAP_AKA_SERVER_SUCCESS); + break; + + case EAP_AKA_SERVER_GENERAL_FAILURE_NOTIFICATION: if (subtype == EAP_AKA_NOTIFICATION) { RDEBUG2("AKA-Notification ACKed, sending EAP-Failure"); } else { 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 4ac0b661978..e1a61454b48 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 @@ -49,6 +49,20 @@ static int eap_sim_compose(eap_session_t *eap_session) vp_cursor_t to_encode; VALUE_PAIR *head = NULL, *vp; REQUEST *request = eap_session->request; + fr_sim_encode_ctx_t encoder_ctx = { + .root = dict_sim_root, + .keys = &eap_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 = EVP_sha1(), + .eap_packet = eap_session->this_round->request, + .hmac_extra = eap_sim_session->keys.gsm.nonce_mt, + .hmac_extra_len = sizeof(eap_sim_session->keys.gsm.nonce_mt) + }; + ssize_t ret; /* we will set the ID on requests, since we have to HMAC it */ @@ -65,9 +79,11 @@ static int eap_sim_compose(eap_session_t *eap_session) RDEBUG2("Encoding EAP-SIM attributes"); rdebug_pair_list(L_DBG_LVL_2, request, head, NULL); - ret = fr_sim_encode(eap_session->request, dict_sim_root, FR_EAP_SIM, - head, eap_session->this_round->request, - &eap_sim_session->keys); + eap_session->this_round->request->type.num = FR_EAP_SIM; + eap_session->this_round->request->id = eap_sim_session->sim_id++ & 0xff; + eap_session->this_round->set_request_id = true; + + ret = fr_sim_encode(eap_session->request, head, &encoder_ctx); fr_pair_cursor_first(&to_encode); fr_pair_cursor_free(&to_encode); @@ -80,6 +96,7 @@ static int eap_sim_compose(eap_session_t *eap_session) static int eap_sim_send_state(eap_session_t *eap_session) { + REQUEST *request = eap_session->request; VALUE_PAIR **vps, *vp; uint16_t version; eap_sim_session_t *eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t); @@ -88,6 +105,9 @@ static int eap_sim_send_state(eap_session_t *eap_session) rad_assert(eap_session->request != NULL); rad_assert(eap_session->request->reply); + RDEBUG2("Sending SIM-State"); + eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; + /* these are the outgoing attributes */ packet = eap_session->request->reply; vps = &packet->vps; @@ -145,18 +165,21 @@ static int eap_sim_send_challenge(eap_session_t *eap_session) REQUEST *request = eap_session->request; eap_sim_session_t *eap_sim_session; - VALUE_PAIR **from_client, **to_client, *vp; + VALUE_PAIR **from_peer, **to_client, *vp; RADIUS_PACKET *packet; eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t); rad_assert(eap_session->request != NULL); rad_assert(eap_session->request->reply); + RDEBUG2("Sending SIM-Challenge"); + eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; + /* - * from_client is the data from the client but this is for non-protocol data here. + * from_peer is the data from the client but this is for non-protocol data here. * We should already have consumed any client originated data. */ - from_client = &eap_session->request->packet->vps; + from_peer = &eap_session->request->packet->vps; /* * to_client is the data to the client @@ -189,7 +212,7 @@ static int eap_sim_send_challenge(eap_session_t *eap_session) /* * Use the SIM identity, if available */ - vp = fr_pair_find_by_child_num(*from_client, dict_sim_root, FR_EAP_SIM_IDENTITY, TAG_ANY); + vp = fr_pair_find_by_child_num(*from_peer, dict_sim_root, FR_EAP_SIM_IDENTITY, TAG_ANY); if (vp) { MEM(eap_sim_session->keys.identity = (uint8_t *)talloc_bstrndup(eap_sim_session, vp->vp_strvalue, vp->vp_length)); @@ -238,10 +261,13 @@ static int eap_sim_send_challenge(eap_session_t *eap_session) static int eap_sim_send_success(eap_session_t *eap_session) { uint8_t *p; + + REQUEST *request = eap_session->request; eap_sim_session_t *eap_sim_session; VALUE_PAIR *vp; RADIUS_PACKET *packet; + RDEBUG2("Sending SIM-Success"); eap_session->this_round->request->code = FR_EAP_CODE_SUCCESS; eap_session->finished = true; @@ -393,7 +419,7 @@ static int process_eap_sim_challenge(eap_session_t *eap_session, VALUE_PAIR *vps return -1; } - slen = fr_sim_crypto_sign_packet(calc_mac, eap_session->this_round->response, true, + slen = fr_sim_crypto_sign_packet(calc_mac, eap_session->this_round->response, true, EVP_sha1(), eap_sim_session->keys.k_aut, sizeof(eap_sim_session->keys.k_aut), NULL, 0); if (slen < 0) { @@ -402,14 +428,14 @@ static int process_eap_sim_challenge(eap_session_t *eap_session, VALUE_PAIR *vps } if (slen == 0) { - REDEBUG("Missing AT_MAC attribute in packet buffer"); + REDEBUG("Missing EAP-SIM-MAC attribute in packet buffer"); return -1; } if (memcmp(mac->vp_octets, calc_mac, sizeof(calc_mac)) == 0) { - RDEBUG2("MAC check succeed"); + RDEBUG2("EAP-SIM-MAC matches calculated MAC"); } else { - REDEBUG("MAC checked failed"); + REDEBUG("EAP-SIM-MAC does not match calculated MAC"); RHEXDUMP_INLINE(L_DBG_LVL_2, mac->vp_octets, SIM_MAC_SIZE, "Received"); RHEXDUMP_INLINE(L_DBG_LVL_2, calc_mac, SIM_MAC_SIZE, "Expected"); return -1;