#include <freeradius-devel/eap.sim.h>
#include <openssl/evp.h>
+/** 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.
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];
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)) {
* 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;
* 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);
* 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;
}
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.
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.
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;
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
*/
/*
* 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 */
/*
* 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");
*/
(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);
*/
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);
}
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
*
*/
/*
* 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
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;
*/
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
/*
* 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);
* 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;
///< 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;
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
};
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);
}
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);
fr_cursor_t cursor;
RDEBUG2("Sending AKA-Identity (%s)", fr_int2str(sim_id_request_table, eap_aka_session->id_req, "<INVALID>"));
+ eap_session->this_round->request->code = FR_EAP_CODE_REQUEST;
packet = request->reply;
fr_cursor_init(&cursor, &packet->vps);
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;
}
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;
}
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
*/
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!
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;
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);
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;
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
*/
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;
}
*/
case EAP_AKA_SERVER_IDENTITY:
eap_aka_send_identity_request(eap_session);
- eap_aka_compose(eap_session);
break;
/*
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;
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.
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;
}
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) {
}
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");
}
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");
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;
}
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:
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 {
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 */
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);
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);
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;
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
/*
* 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));
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;
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) {
}
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;