From 6aaa917ddedeca772e32ac13a81cf7ce8024c350 Mon Sep 17 00:00:00 2001 From: James Prestwood Date: Mon, 21 Aug 2017 14:09:06 -0700 Subject: [PATCH] aka: EAP-AKA protocol implementation --- Makefile.am | 2 +- src/eap-aka.c | 561 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/eap.h | 1 + src/simutil.c | 127 ++++++++++++ src/simutil.h | 29 +++ 5 files changed, 719 insertions(+), 1 deletion(-) create mode 100644 src/eap-aka.c diff --git a/Makefile.am b/Makefile.am index 0f8d142d..55f6db39 100644 --- a/Makefile.am +++ b/Makefile.am @@ -86,7 +86,7 @@ src_iwd_SOURCES = src/main.c linux/nl80211.h \ src/rfkill.h src/rfkill.c \ src/watchlist.h src/watchlist.c \ src/ftutil.h src/ftutil.c \ - src/iwd.h src/eap-sim.c \ + src/iwd.h src/eap-sim.c src/eap-aka.c \ src/simutil.h src/simutil.c src_iwd_LDADD = ell/libell-internal.la -ldl diff --git a/src/eap-aka.c b/src/eap-aka.c new file mode 100644 index 00000000..b620272e --- /dev/null +++ b/src/eap-aka.c @@ -0,0 +1,561 @@ +/* + * + * Wireless daemon for Linux + * + * Copyright (C) 2017 Intel Corporation. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include "crypto.h" +#include "simutil.h" + +/* + * EAP-AKA specific values + */ +#define EAP_AKA_KI_LEN 16 +#define EAP_AKA_OPC_LEN 16 +#define EAP_AKA_AMF_LEN 2 +#define EAP_AKA_SQN_LEN 6 +#define EAP_AKA_IK_LEN 16 +#define EAP_AKA_CK_LEN 16 +#define EAP_AKA_AUTN_LEN 16 +#define EAP_AKA_RES_LEN 8 +#define EAP_AKA_K_RE_LEN 32 + +#define EAP_AKA_ST_CHALLENGE 0x01 +#define EAP_AKA_ST_AUTH_REJECT 0x02 +#define EAP_AKA_ST_SYNC_FAILURE 0x04 +#define EAP_AKA_ST_IDENTITY 0x05 +#define EAP_AKA_ST_NOTIFICATION 0x0c +#define EAP_AKA_ST_CLIENT_ERROR 0x0e + +/* + * Internal client state, tracked to ensure that we are receiving the right + * messages at the right time. + */ +enum eap_aka_state { + EAP_AKA_STATE_UNCONNECTED = 0, + EAP_AKA_STATE_IDENTITY, + EAP_AKA_STATE_CHALLENGE, + EAP_AKA_STATE_SUCCESS, + EAP_AKA_STATE_ERROR +}; + +struct eap_aka_handle { + enum eap_aka_state state; + /* Identity from SIM */ + char *identity; + + /* Derived master key */ + uint8_t mk[EAP_SIM_MK_LEN]; + + /* Derived K_encr key from PRNG */ + uint8_t k_encr[EAP_SIM_K_ENCR_LEN]; + + /* Derived K_aut key from PRNG */ + uint8_t k_aut[EAP_SIM_K_AUT_LEN]; + + /* Derived MSK from PRNG */ + uint8_t msk[EAP_SIM_MSK_LEN]; + + /* Derived EMSK from PRNG */ + uint8_t emsk[EAP_SIM_EMSK_LEN]; + + /* Flag set if AT_ANY_ID_REQ was present */ + bool any_id_req : 1; + + /* Flag to indicate protected status indications */ + bool protected : 1; + + /* Subscriber key */ + uint8_t ki[EAP_AKA_KI_LEN]; + + /* Key derived from OP and ki */ + uint8_t opc[EAP_AKA_OPC_LEN]; + + /* Authentication management field */ + uint8_t amf[EAP_AKA_AMF_LEN]; + + /* Sequence number */ + uint8_t sqn[EAP_AKA_SQN_LEN]; + + /* Integrity key */ + uint8_t ik[EAP_AKA_IK_LEN]; + + /* Signed response */ + uint8_t res[EAP_AKA_RES_LEN]; + + /* Confidentiality key */ + uint8_t ck[EAP_AKA_CK_LEN]; + + /* Authentication value from AuC */ + uint8_t autn[EAP_AKA_AUTN_LEN]; +}; + +static int eap_aka_probe(struct eap_state *eap, const char *name) +{ + struct eap_aka_handle *aka; + + if (strcasecmp(name, "AKA")) + return -ENOTSUP; + + aka = l_new(struct eap_aka_handle, 1); + + eap_set_data(eap, aka); + + return 0; +} + +static void eap_aka_remove(struct eap_state *eap) +{ + struct eap_aka_handle *aka = eap_get_data(eap); + + l_free(aka->identity); + l_free(aka); + + eap_set_data(eap, NULL); +} + +static bool derive_aka_mk(const char *identity, uint8_t *ik, uint8_t *ck, + uint8_t *mk) +{ + int ret; + struct iovec iov[5]; + struct l_checksum *checksum = l_checksum_new(L_CHECKSUM_SHA1); + + if (!checksum) { + l_error("could not create SHA1 checksum"); + return false; + } + + iov[0].iov_base = (void *)identity; + iov[0].iov_len = strlen(identity); + iov[1].iov_base = (void *)ik; + iov[1].iov_len = EAP_AKA_IK_LEN; + iov[2].iov_base = (void *)ck; + iov[2].iov_len = EAP_AKA_CK_LEN; + + if (!l_checksum_updatev(checksum, iov, 3)) + goto mk_error; + + ret = l_checksum_get_digest(checksum, mk, EAP_SIM_MK_LEN); + l_checksum_free(checksum); + + return (ret == EAP_SIM_MK_LEN); + +mk_error: + l_checksum_free(checksum); + l_error("error deriving master key"); + return false; +} + +/* + * Handles EAP-AKA Challenge subtype + */ +static void handle_challenge(struct eap_state *eap, const uint8_t *pkt, + size_t len) +{ + struct eap_aka_handle *aka = eap_get_data(eap); + struct eap_sim_tlv_iter iter; + uint8_t prng_buf[160]; + size_t resp_len = 40; + uint8_t response[resp_len + 4]; + uint8_t *pos = response; + const uint8_t *rand = NULL; + const uint8_t *autn = NULL; + + if (len < 3) { + l_error("packet is too small"); + goto chal_error; + } + + if (aka->state != EAP_AKA_STATE_IDENTITY) { + l_error("invalid packet for EAP-AKA state"); + goto chal_error; + } + + eap_sim_tlv_iter_init(&iter, pkt + 3, len - 3); + + while (eap_sim_tlv_iter_next(&iter)) { + const uint8_t *contents = eap_sim_tlv_iter_get_data(&iter); + uint16_t length = eap_sim_tlv_iter_get_length(&iter); + + switch (eap_sim_tlv_iter_get_type(&iter)) { + case EAP_SIM_AT_AUTN: + if (length < EAP_AKA_AUTN_LEN + 2) { + l_error("malformed AT_AUTN"); + goto chal_error; + } + + autn = contents + 2; + + break; + + case EAP_SIM_AT_RAND: + if (length < EAP_SIM_RAND_LEN + 2) { + l_error("malformed AT_RAND"); + goto chal_error; + } + + rand = contents + 2; + + break; + + case EAP_SIM_AT_RESULT_IND: + if (length < 2) { + l_error("malformed AT_RESULT_IND"); + goto chal_error; + } + + aka->protected = 1; + resp_len += 4; + + break; + + case EAP_SIM_AT_NEXT_PSEUDONYM: + case EAP_SIM_AT_NEXT_REAUTH_ID: + case EAP_SIM_AT_IV: + case EAP_SIM_AT_ENCR_DATA: + case EAP_SIM_AT_PADDING: + case EAP_SIM_AT_CHECKCODE: + case EAP_SIM_AT_MAC: + /* + * AT_BIDDING is defined in RFC 5448 (AKA'). It is used to + * communicate support for AKA', if supported. + */ + case EAP_SIM_AT_BIDDING: + /* RFC 4187, Section 10.1 */ + break; + + default: + l_error("attribute %u was found in Challenge", + eap_sim_tlv_iter_get_type(&iter)); + goto chal_error; + } + } + + eap_aka_get_milenage(aka->opc, aka->ki, rand, aka->sqn, aka->amf, + aka->autn, aka->ck, aka->ik, aka->res); + + if (memcmp(autn, aka->autn, EAP_AKA_AUTN_LEN)) { + l_error("EAP_SIM_AT_AUTN is not valid"); + goto chal_error; + } + + if (!derive_aka_mk(aka->identity, aka->ik, aka->ck, aka->mk)) { + l_error("error deriving MK"); + goto chal_fatal; + } + + eap_sim_fips_prf(aka->mk, 20, prng_buf, 160); + + if (!eap_sim_get_encryption_keys(prng_buf, aka->k_encr, aka->k_aut, + aka->msk, aka->emsk)) { + l_error("could not derive encryption keys"); + goto chal_fatal; + } + + if (!eap_sim_verify_mac(eap, EAP_TYPE_AKA, pkt, len, aka->k_aut, NULL, + 0)) { + l_error("MAC was not valid"); + goto chal_error; + } + + aka->state = EAP_AKA_STATE_CHALLENGE; + + pos += eap_sim_build_header(eap, EAP_TYPE_AKA, EAP_AKA_ST_CHALLENGE, + pos, resp_len); + pos += eap_sim_add_attribute(pos, EAP_SIM_AT_RES, + EAP_SIM_PAD_LENGTH_BITS, aka->res, EAP_AKA_RES_LEN); + + if (aka->protected) + pos += eap_sim_add_attribute(pos, EAP_SIM_AT_RESULT_IND, + EAP_SIM_PAD_NONE, NULL, 2); + + pos += eap_sim_add_attribute(pos, EAP_SIM_AT_MAC, EAP_SIM_PAD_NONE, + NULL, EAP_SIM_MAC_LEN); + + if (!eap_sim_derive_mac(response, resp_len, aka->k_aut, + pos - EAP_SIM_MAC_LEN)) { + l_error("error deriving MAC"); + goto chal_fatal; + } + + eap_send_response(eap, EAP_TYPE_AKA, response, resp_len); + + if (!aka->protected) { + eap_method_success(eap); + eap_set_key_material(eap, aka->msk, 32, NULL, 0, NULL, 0); + + aka->state = EAP_AKA_STATE_SUCCESS; + } + + return; + +chal_fatal: + eap_method_error(eap); + aka->state = EAP_AKA_STATE_ERROR; + return; + +chal_error: + eap_sim_client_error(eap, EAP_TYPE_AKA, EAP_SIM_ERROR_PROCESS); +} + +/* + * Handles Notification subtype + */ +static void handle_notification(struct eap_state *eap, const uint8_t *pkt, + size_t len) +{ + struct eap_aka_handle *aka = eap_get_data(eap); + struct eap_sim_tlv_iter iter; + int32_t value = -1; + + if (len < 3) { + l_error("packet is too small"); + goto notif_error; + } + + eap_sim_tlv_iter_init(&iter, pkt + 3, len - 3); + + while (eap_sim_tlv_iter_next(&iter)) { + const uint8_t *contents = eap_sim_tlv_iter_get_data(&iter); + uint16_t length = eap_sim_tlv_iter_get_length(&iter); + + switch (eap_sim_tlv_iter_get_type(&iter)) { + case EAP_SIM_AT_NOTIFICATION: + if (length < 2) { + l_error("malformed AT_NOTIFICATION"); + goto notif_error; + } + + value = l_get_be16(contents); + break; + + case EAP_SIM_AT_IV: + case EAP_SIM_AT_ENCR_DATA: + case EAP_SIM_AT_PADDING: + case EAP_SIM_AT_MAC: + /* RFC 4186, Section 10.1 */ + break; + + default: + l_error("attribute type %u not allowed in Notification", + eap_sim_tlv_iter_get_type(&iter)); + goto notif_error; + } + } + + if (value == EAP_SIM_SUCCESS && aka->protected && + aka->state == EAP_AKA_STATE_CHALLENGE) { + /* header + MAC + MAC header */ + uint8_t response[8 + EAP_SIM_MAC_LEN + 4]; + uint8_t *pos = response; + + /* + * Server sent successful result indication + */ + eap_method_success(eap); + eap_set_key_material(eap, aka->msk, 32, NULL, 0, NULL, 0); + + /* + * Build response packet + */ + pos += eap_sim_build_header(eap, EAP_TYPE_AKA, + EAP_AKA_ST_NOTIFICATION, pos, 20); + pos += eap_sim_add_attribute(pos, EAP_SIM_AT_MAC, + EAP_SIM_PAD_NONE, NULL, EAP_SIM_MAC_LEN); + + if (!eap_sim_derive_mac(response, pos - response, aka->k_aut, + response + 12)) { + l_error("could not derive MAC"); + eap_method_error(eap); + aka->state = EAP_AKA_STATE_ERROR; + return; + } + + eap_send_response(eap, EAP_TYPE_AKA, response, pos - response); + + aka->state = EAP_AKA_STATE_SUCCESS; + + return; + } else if (value == EAP_SIM_SUCCESS) { + /* + * Unexpected success notification, what should + * be done here? + */ + l_error("Unexpected success notification"); + } else { + /* + * All other values are error conditions. + * Nothing unique can be done for any error so + * print the code and signal EAP failure. + */ + l_error("Error authenticating: code=%u", value); + } + +notif_error: + eap_sim_client_error(eap, EAP_TYPE_AKA, EAP_SIM_ERROR_PROCESS); +} + +static void handle_identity(struct eap_state *eap, const uint8_t *pkt, + size_t len) +{ + struct eap_aka_handle *aka = eap_get_data(eap); + uint8_t response[8 + strlen(aka->identity) + 4]; + uint8_t *pos = response; + + if (aka->state != EAP_AKA_STATE_UNCONNECTED) { + l_error("invalid packet for EAP-AKA state"); + eap_sim_client_error(eap, EAP_TYPE_AKA, EAP_SIM_ERROR_PROCESS); + return; + } + + aka->state = EAP_AKA_STATE_IDENTITY; + /* + * Build response packet + */ + pos += eap_sim_build_header(eap, EAP_TYPE_AKA, EAP_AKA_ST_IDENTITY, pos, + 20); + pos += eap_sim_add_attribute(pos, EAP_SIM_AT_IDENTITY, + EAP_SIM_PAD_LENGTH, (uint8_t *)aka->identity, + strlen(aka->identity)); + + eap_send_response(eap, EAP_TYPE_AKA, response, pos - response); +} + +static void eap_aka_handle_request(struct eap_state *eap, + const uint8_t *pkt, size_t len) +{ + if (len < 1) { + l_error("packet is too small"); + goto req_error; + } + + switch (pkt[0]) { + case EAP_AKA_ST_IDENTITY: + handle_identity(eap, pkt, len); + break; + + case EAP_AKA_ST_CHALLENGE: + handle_challenge(eap, pkt, len); + break; + + case EAP_AKA_ST_NOTIFICATION: + handle_notification(eap, pkt, len); + break; + + default: + l_error("unknown EAP-SIM subtype: %u", pkt[0]); + goto req_error; + } + + return; + +req_error: + eap_sim_client_error(eap, EAP_TYPE_AKA, EAP_SIM_ERROR_PROCESS); +} + +static bool eap_aka_load_settings(struct eap_state *eap, + struct l_settings *settings, + const char *prefix) +{ + struct eap_aka_handle *aka = eap_get_data(eap); + char setting[64]; + const char *imsi; + const char *ki; + const char *opc; + const char *amf; + const char *sqn; + size_t len; + + snprintf(setting, sizeof(setting), "%sAKA-IMSI", prefix); + imsi = l_settings_get_value(settings, "Security", setting); + if (imsi) + aka->identity = l_strdup(imsi); + + snprintf(setting, sizeof(setting), "%sAKA-KI", prefix); + ki = l_settings_get_value(settings, "Security", setting); + if (ki) { + uint8_t *val = l_util_from_hexstring(ki, &len); + + memcpy(aka->ki, val, len); + l_free(val); + } + + snprintf(setting, sizeof(setting), "%sAKA-OPC", prefix); + opc = l_settings_get_value(settings, "Security", setting); + if (opc) { + uint8_t *val = l_util_from_hexstring(opc, &len); + + memcpy(aka->opc, val, len); + l_free(val); + } + + snprintf(setting, sizeof(setting), "%sAKA-AMF", prefix); + amf = l_settings_get_value(settings, "Security", setting); + if (amf) { + uint8_t *val = l_util_from_hexstring(amf, &len); + + memcpy(aka->amf, val, len); + l_free(val); + } + + snprintf(setting, sizeof(setting), "%sAKA-SQN", prefix); + sqn = l_settings_get_value(settings, "Security", setting); + if (sqn) { + uint8_t *val = l_util_from_hexstring(sqn, &len); + + memcpy(aka->sqn, val, len); + l_free(val); + } + + return true; +} + +static struct eap_method eap_aka = { + .request_type = EAP_TYPE_AKA, + .exports_msk = true, + .name = "AKA", + .probe = eap_aka_probe, + .remove = eap_aka_remove, + .handle_request = eap_aka_handle_request, + .load_settings = eap_aka_load_settings, +}; + +static int eap_aka_init(void) +{ + l_debug(""); + return eap_register_method(&eap_aka); +} + +static void eap_aka_exit(void) +{ + l_debug(""); + eap_unregister_method(&eap_aka); +} + +EAP_METHOD_BUILTIN(eap_aka, eap_aka_init, eap_aka_exit) diff --git a/src/eap.h b/src/eap.h index f321e964..de147834 100644 --- a/src/eap.h +++ b/src/eap.h @@ -73,6 +73,7 @@ enum eap_type { EAP_TYPE_TLS_EAP = 13, EAP_TYPE_SIM = 18, EAP_TYPE_TTLS = 21, + EAP_TYPE_AKA = 23, EAP_TYPE_MSCHAPV2 = 26, EAP_TYPE_EXPANDED = 254, }; diff --git a/src/simutil.c b/src/simutil.c index 180a1132..1c496db0 100644 --- a/src/simutil.c +++ b/src/simutil.c @@ -143,6 +143,133 @@ static void G(uint32_t *out, uint8_t *block) memcpy(out, H, sizeof(H)); } +/* + * Helper to XOR an array + * to - result of XOR array + * a - array 1 + * b - array 2 + * len - size of aray + */ +#define XOR(to, a, b, len) \ + for (i = 0; i < len; i++) { \ + to[i] = a[i] ^ b[i]; \ + } + +bool eap_aka_get_milenage(const uint8_t *opc, const uint8_t *k, + const uint8_t *rand, const uint8_t *sqn, const uint8_t *amf, + uint8_t *autn, uint8_t *ck, uint8_t *ik, uint8_t *res) +{ + /* algorithm variables: TEMP, IN1, OUT1, OUT2, OUT5 (OUT3/4 == IK/CK) */ + uint8_t temp[16]; + uint8_t in1[16]; + uint8_t out1[16], out2[16], out5[16]; + /* other variables */ + struct l_cipher *aes; + int i; + uint8_t tmp1[16]; + uint8_t tmp2[16]; + + aes = l_cipher_new(L_CIPHER_AES, k, 16); + + /* temp = TEMP = E[RAND ^ OPc]k */ + XOR(tmp1, rand, opc, 16); + l_cipher_encrypt(aes, tmp1, temp, 16); + + /* IN1[0-47] = SQN[0-47] */ + memcpy(in1, sqn, 6); + /* IN1[48-63] = AMF[0-15] */ + memcpy(in1 + 6, amf, 2); + /* IN1[64-111] = SQN[0-47] */ + memcpy(in1 + 8, sqn, 6); + /* IN1[112-127] = AMF[0-15] */ + memcpy(in1 + 14, amf, 2); + + /* + * f1 and f1* output OUT1 + */ + /* + * tmp1 = rot(IN1 ^ OPc)r1 + * r1 = 64 bits = 8 bytes + */ + for (i = 0; i < 16; i++) + tmp1[(i + 8) % 16] = in1[i] ^ opc[i]; + + /* tmp2 = TEMP ^ tmp1 */ + XOR(tmp2, temp, tmp1, 16); + /* tmp2 = E[tmp2]k */ + l_cipher_encrypt(aes, tmp2, tmp1, 16); + /* out1 = OUT1 = tmp1 ^ opc */ + XOR(out1, tmp1, opc, 16); + + /* + * f2 outputs OUT2 (RES | AK) + * + * r2 = 0 == no rotation + */ + /* tmp1 = rot(TEMP ^ OPc)r2 */ + XOR(tmp1, temp, opc, 16); + /* tmp1 ^ c2. c2 at bit 127 == 1 */ + tmp1[15] ^= 1; + l_cipher_encrypt(aes, tmp1, out2, 16); + + /* get RES from OUT2 */ + XOR(out2, out2, opc, 16); + memcpy(res, out2 + 8, 8); + + /* AUTN = (SQN ^ AK) | AMF | MAC_A */ + XOR(autn, sqn, out2, 6); + memcpy(autn + 6, amf, 2); + memcpy(autn + 8, out1, 8); + + /* + * f3 outputs CK (OUT3) + * + * tmp1 = rot(TEMP ^ OPc)r3 + * + * r3 = 32 bits = 4 bytes + */ + for (i = 0; i < 16; i++) + tmp1[(i + 12) % 16] = temp[i] ^ opc[i]; + + /* tmp1 ^ c3. c3 at bit 126 == 1 */ + tmp1[15] ^= 1 << 1; + l_cipher_encrypt(aes, tmp1, ck, 16); + /* ck ^ opc */ + XOR(ck, ck, opc, 16); + + /* + * f4 outputs IK (OUT4) + * + * tmp1 = rot(TEMP ^ OPc)r4 + * + * r4 = 64 bits = 8 bytes + */ + for (i = 0; i < 16; i++) + tmp1[(i + 8) % 16] = temp[i] ^ opc[i]; + + /* tmp1 ^ c4. c4 at bit 125 == 1 */ + tmp1[15] ^= 1 << 2; + l_cipher_encrypt(aes, tmp1, ik, 16); + /* ik ^ opc */ + XOR(ik, ik, opc, 16); + + /* + * f5* outputs AK' (OUT5) + */ + for (i = 0; i < 16; i++) + tmp1[(i + 4) % 16] = temp[i] ^ opc[i]; + + /* tmp1 ^ c5. c5 at bit 124 == 1 */ + tmp1[15] ^= 1 << 3; + l_cipher_encrypt(aes, tmp1, out5, 16); + /* out5 ^ opc */ + XOR(out5, out5, opc, 16); + + l_cipher_free(aes); + + return true; +} + void eap_sim_fips_prf(const void *seed, size_t slen, uint8_t *out, size_t olen) { uint8_t xkey[64]; diff --git a/src/simutil.h b/src/simutil.h index 020c0cf1..bc1a0667 100644 --- a/src/simutil.h +++ b/src/simutil.h @@ -122,6 +122,35 @@ enum eap_sim_fail { */ void eap_sim_fips_prf(const void *seed, size_t slen, uint8_t *out, size_t olen); +/* + * 3GPP TS 35.206 + * + * Algorithm for generating milenage parameters. + * + * opc - Input: OPc value + * k - Input: K key value + * rand - Input: Rand from server + * sqn - Input: Sequence number + * amf - Input: AMF value + * + * autn - Output: AUTN computed + * ck - Output: CK computed + * ik - Output: IK computed + * res - Output: RES computed + * + * Internal Functions used: + * f1 and f1* output MAC-A and MAC-S. MAC-A along with SQN/AK/AMF make AUTN + * f2 outputs RES where RES == OUT2[8-16] + * f3 outputs CK where CK == OUT3[0-16] + * f4 outputs IK where IK == OUT4[0-16] + * f5 outputs AK where AK == OUT2[0-6] + * + * f5* outputs AK', not used with EAP-AKA + */ +bool eap_aka_get_milenage(const uint8_t *opc, const uint8_t *k, + const uint8_t *rand, const uint8_t *sqn, const uint8_t *amf, + uint8_t *autn, uint8_t *ck, uint8_t *ik, uint8_t *res); + /* * Separate PRNG data into encryption keys. k_encr and k_aut may be NULL in the * case of fast re-authentication.