diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:49:46 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:49:46 +0000 |
commit | 50b37d4a27d3295a29afca2286f1a5a086142cec (patch) | |
tree | 9212f763934ee090ef72d823f559f52ce387f268 /src/modules/rlm_wimax | |
parent | Initial commit. (diff) | |
download | freeradius-upstream.tar.xz freeradius-upstream.zip |
Adding upstream version 3.2.1+dfsg.upstream/3.2.1+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/modules/rlm_wimax')
-rw-r--r-- | src/modules/rlm_wimax/all.mk | 9 | ||||
-rw-r--r-- | src/modules/rlm_wimax/milenage.c | 642 | ||||
-rw-r--r-- | src/modules/rlm_wimax/milenage.h | 128 | ||||
-rw-r--r-- | src/modules/rlm_wimax/rlm_wimax.c | 842 |
4 files changed, 1621 insertions, 0 deletions
diff --git a/src/modules/rlm_wimax/all.mk b/src/modules/rlm_wimax/all.mk new file mode 100644 index 0000000..89e759b --- /dev/null +++ b/src/modules/rlm_wimax/all.mk @@ -0,0 +1,9 @@ +TARGETNAME := rlm_wimax + +ifneq "$(OPENSSL_LIBS)" "" +TARGET := $(TARGETNAME).a +endif + +SOURCES := $(TARGETNAME).c milenage.c + +TGT_LDLIBS := $(OPENSSL_LIBS) diff --git a/src/modules/rlm_wimax/milenage.c b/src/modules/rlm_wimax/milenage.c new file mode 100644 index 0000000..e14086e --- /dev/null +++ b/src/modules/rlm_wimax/milenage.c @@ -0,0 +1,642 @@ +/** + * @file src/modules/rlm_wimax/milenage.c + * @brief 3GPP AKA - Milenage algorithm (3GPP TS 35.205, .206, .207, .208) + * + * This file implements an example authentication algorithm defined for 3GPP + * AKA. This can be used to implement a simple HLR/AuC into hlr_auc_gw to allow + * EAP-AKA to be tested properly with real USIM cards. + * + * This implementations assumes that the r1..r5 and c1..c5 constants defined in + * TS 35.206 are used, i.e., r1=64, r2=0, r3=32, r4=64, r5=96, c1=00..00, + * c2=00..01, c3=00..02, c4=00..04, c5=00..08. The block cipher is assumed to + * be AES (Rijndael). + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + * + * @copyright 2017 The FreeRADIUS server project + * @copyright 2006-2007 (j@w1.fi) + */ +#include <stddef.h> +#include <string.h> + +#include <freeradius-devel/radiusd.h> +#include <freeradius-devel/modules.h> +#include <openssl/evp.h> +#include "milenage.h" + +#define MILENAGE_MAC_A_SIZE 8 +#define MILENAGE_MAC_S_SIZE 8 + +static inline int aes_128_encrypt_block(EVP_CIPHER_CTX *evp_ctx, + uint8_t const key[16], uint8_t const in[16], uint8_t out[16]) +{ + size_t len; + + if (unlikely(EVP_EncryptInit_ex(evp_ctx, EVP_aes_128_ecb(), NULL, key, NULL) != 1)) { + fr_strerror_printf("Failed initialising AES-128-ECB context"); + return -1; + } + + /* + * By default OpenSSL will try and pad out a 16 byte + * plaintext to 32 bytes so that it's detectable that + * there was padding. + * + * In this case we know the length of the plaintext + * we're trying to recover, so we explicitly tell + * OpenSSL not to pad here, and not to expected padding + * when decrypting. + */ + EVP_CIPHER_CTX_set_padding(evp_ctx, 0); + if (unlikely(EVP_EncryptUpdate(evp_ctx, out, (int *)&len, in, 16) != 1) || + unlikely(EVP_EncryptFinal_ex(evp_ctx, out + len, (int *)&len) != 1)) { + fr_strerror_printf("Failed encrypting data"); + return -1; + } + + return 0; +} + +/** milenage_f1 - Milenage f1 and f1* algorithms + * + * @param[in] opc 128-bit value derived from OP and K. + * @param[in] k 128-bit subscriber key. + * @param[in] rand 128-bit random challenge. + * @param[in] sqn 48-bit sequence number. + * @param[in] amf 16-bit authentication management field. + * @param[out] mac_a Buffer for MAC-A = 64-bit network authentication code, or NULL + * @param[out] mac_s Buffer for MAC-S = 64-bit resync authentication code, or NULL + * @return + * - 0 on success. + * - -1 on failure. + */ +static int milenage_f1(uint8_t mac_a[MILENAGE_MAC_A_SIZE], + uint8_t mac_s[MILENAGE_MAC_S_SIZE], + uint8_t const opc[MILENAGE_OPC_SIZE], + uint8_t const k[MILENAGE_KI_SIZE], + uint8_t const rand[MILENAGE_RAND_SIZE], + uint8_t const sqn[MILENAGE_SQN_SIZE], + uint8_t const amf[MILENAGE_AMF_SIZE]) +{ + uint8_t tmp1[16], tmp2[16], tmp3[16]; + int i; + EVP_CIPHER_CTX *evp_ctx; + + /* tmp1 = TEMP = E_K(RAND XOR OP_C) */ + for (i = 0; i < 16; i++) tmp1[i] = rand[i] ^ opc[i]; + + evp_ctx = EVP_CIPHER_CTX_new(); + if (!evp_ctx) { + //tls_strerror_printf("Failed allocating EVP context"); + return -1; + } + + if (aes_128_encrypt_block(evp_ctx, k, tmp1, tmp1) < 0) { + error: + EVP_CIPHER_CTX_free(evp_ctx); + return -1; + } + + /* tmp2 = IN1 = SQN || AMF || SQN || AMF */ + memcpy(tmp2, sqn, 6); + memcpy(tmp2 + 6, amf, 2); + memcpy(tmp2 + 8, tmp2, 8); + + /* OUT1 = E_K(TEMP XOR rot(IN1 XOR OP_C, r1) XOR c1) XOR OP_C */ + + /* + * rotate (tmp2 XOR OP_C) by r1 (= 0x40 = 8 bytes) + */ + for (i = 0; i < 16; i++) tmp3[(i + 8) % 16] = tmp2[i] ^ opc[i]; + + /* + * XOR with TEMP = E_K(RAND XOR OP_C) + */ + for (i = 0; i < 16; i++) tmp3[i] ^= tmp1[i]; + /* XOR with c1 (= ..00, i.e., NOP) */ + + /* + * f1 || f1* = E_K(tmp3) XOR OP_c + */ + if (aes_128_encrypt_block(evp_ctx, k, tmp3, tmp1) < 0) goto error; /* Reuses existing key */ + + for (i = 0; i < 16; i++) tmp1[i] ^= opc[i]; + + if (mac_a) memcpy(mac_a, tmp1, 8); /* f1 */ + if (mac_s) memcpy(mac_s, tmp1 + 8, 8); /* f1* */ + + EVP_CIPHER_CTX_free(evp_ctx); + + return 0; +} + +/** milenage_f2345 - Milenage f2, f3, f4, f5, f5* algorithms + * + * @param[out] res Buffer for RES = 64-bit signed response (f2), or NULL + * @param[out] ck Buffer for CK = 128-bit confidentiality key (f3), or NULL + * @param[out] ik Buffer for IK = 128-bit integrity key (f4), or NULL + * @param[out] ak Buffer for AK = 48-bit anonymity key (f5), or NULL + * @param[out] ak_resync Buffer for AK = 48-bit anonymity key (f5*), or NULL + * @param[in] opc 128-bit value derived from OP and K. + * @param[in] k 128-bit subscriber key + * @param[in] rand 128-bit random challenge + * @return + * - 0 on success. + * - -1 on failure. + */ +static int milenage_f2345(uint8_t res[MILENAGE_RES_SIZE], + uint8_t ik[MILENAGE_IK_SIZE], + uint8_t ck[MILENAGE_CK_SIZE], + uint8_t ak[MILENAGE_AK_SIZE], + uint8_t ak_resync[MILENAGE_AK_SIZE], + uint8_t const opc[MILENAGE_OPC_SIZE], + uint8_t const k[MILENAGE_KI_SIZE], + uint8_t const rand[MILENAGE_RAND_SIZE]) +{ + uint8_t tmp1[16], tmp2[16], tmp3[16]; + int i; + EVP_CIPHER_CTX *evp_ctx; + + /* tmp2 = TEMP = E_K(RAND XOR OP_C) */ + for (i = 0; i < 16; i++) tmp1[i] = rand[i] ^ opc[i]; + + evp_ctx = EVP_CIPHER_CTX_new(); + if (!evp_ctx) { + fr_strerror_printf("Failed allocating EVP context"); + return -1; + } + + if (aes_128_encrypt_block(evp_ctx, k, tmp1, tmp2) < 0) { + error: + EVP_CIPHER_CTX_free(evp_ctx); + return -1; + } + + /* OUT2 = E_K(rot(TEMP XOR OP_C, r2) XOR c2) XOR OP_C */ + /* OUT3 = E_K(rot(TEMP XOR OP_C, r3) XOR c3) XOR OP_C */ + /* OUT4 = E_K(rot(TEMP XOR OP_C, r4) XOR c4) XOR OP_C */ + /* OUT5 = E_K(rot(TEMP XOR OP_C, r5) XOR c5) XOR OP_C */ + + /* f2 and f5 */ + /* rotate by r2 (= 0, i.e., NOP) */ + for (i = 0; i < 16; i++) tmp1[i] = tmp2[i] ^ opc[i]; + tmp1[15] ^= 1; /* XOR c2 (= ..01) */ + /* f5 || f2 = E_K(tmp1) XOR OP_c */ + + if (aes_128_encrypt_block(evp_ctx, k, tmp1, tmp3) < 0) goto error; + + for (i = 0; i < 16; i++) tmp3[i] ^= opc[i]; + if (res) memcpy(res, tmp3 + 8, 8); /* f2 */ + if (ak) memcpy(ak, tmp3, 6); /* f5 */ + + /* f3 */ + if (ck) { + /* rotate by r3 = 0x20 = 4 bytes */ + for (i = 0; i < 16; i++) tmp1[(i + 12) % 16] = tmp2[i] ^ opc[i]; + tmp1[15] ^= 2; /* XOR c3 (= ..02) */ + + if (aes_128_encrypt_block(evp_ctx, k, tmp1, ck) < 0) goto error; + + for (i = 0; i < 16; i++) ck[i] ^= opc[i]; + } + + /* f4 */ + if (ik) { + /* rotate by r4 = 0x40 = 8 bytes */ + for (i = 0; i < 16; i++) tmp1[(i + 8) % 16] = tmp2[i] ^ opc[i]; + tmp1[15] ^= 4; /* XOR c4 (= ..04) */ + + if (aes_128_encrypt_block(evp_ctx, k, tmp1, ik) < 0) goto error; + + for (i = 0; i < 16; i++) ik[i] ^= opc[i]; + } + + /* f5* */ + if (ak_resync) { + /* rotate by r5 = 0x60 = 12 bytes */ + for (i = 0; i < 16; i++) tmp1[(i + 4) % 16] = tmp2[i] ^ opc[i]; + tmp1[15] ^= 8; /* XOR c5 (= ..08) */ + + if (aes_128_encrypt_block(evp_ctx, k, tmp1, tmp1) < 0) goto error; + + for (i = 0; i < 6; i++) ak_resync[i] = tmp1[i] ^ opc[i]; + } + EVP_CIPHER_CTX_free(evp_ctx); + + return 0; +} + +/** Derive OPc from OP and Ki + * + * @param[out] opc The derived Operator Code used as an input to other Milenage + * functions. + * @param[in] op Operator Code. + * @param[in] ki Subscriber key. + * @return + * - 0 on success. + * - -1 on failure. + */ +int milenage_opc_generate(uint8_t opc[MILENAGE_OPC_SIZE], + uint8_t const op[MILENAGE_OP_SIZE], + uint8_t const ki[MILENAGE_KI_SIZE]) +{ + int ret; + uint8_t tmp[MILENAGE_OPC_SIZE]; + EVP_CIPHER_CTX *evp_ctx; + size_t i; + + evp_ctx = EVP_CIPHER_CTX_new(); + if (!evp_ctx) { + fr_strerror_printf("Failed allocating EVP context"); + return -1; + } + ret = aes_128_encrypt_block(evp_ctx, ki, op, tmp); + EVP_CIPHER_CTX_free(evp_ctx); + if (ret < 0) return ret; + + for (i = 0; i < sizeof(tmp); i++) opc[i] = op[i] ^ tmp[i]; + + return 0; +} + +/** Generate AKA AUTN, IK, CK, RES + * + * @param[out] autn Buffer for AUTN = 128-bit authentication token. + * @param[out] ik Buffer for IK = 128-bit integrity key (f4), or NULL. + * @param[out] ck Buffer for CK = 128-bit confidentiality key (f3), or NULL. + * @param[out] ak Buffer for AK = 48-bit anonymity key (f5), or NULL + * @param[out] res Buffer for RES = 64-bit signed response (f2), or NULL. + * @param[in] opc 128-bit operator variant algorithm configuration field (encr.). + * @param[in] amf 16-bit authentication management field. + * @param[in] ki 128-bit subscriber key. + * @param[in] sqn 48-bit sequence number (host byte order). + * @param[in] rand 128-bit random challenge. + * @return + * - 0 on success. + * - -1 on failure. + */ +int milenage_umts_generate(uint8_t autn[MILENAGE_AUTN_SIZE], + uint8_t ik[MILENAGE_IK_SIZE], + uint8_t ck[MILENAGE_CK_SIZE], + uint8_t ak[MILENAGE_AK_SIZE], + uint8_t res[MILENAGE_RES_SIZE], + uint8_t const opc[MILENAGE_OPC_SIZE], + uint8_t const amf[MILENAGE_AMF_SIZE], + uint8_t const ki[MILENAGE_KI_SIZE], + uint64_t sqn, + uint8_t const rand[MILENAGE_RAND_SIZE]) +{ + uint8_t mac_a[8], ak_buff[MILENAGE_AK_SIZE]; + uint8_t sqn_buff[MILENAGE_SQN_SIZE]; + uint8_t *p = autn; + size_t i; + + if ((milenage_f1(mac_a, NULL, opc, ki, rand, + uint48_to_buff(sqn_buff, sqn), amf) < 0) || + (milenage_f2345(res, ik, ck, ak_buff, NULL, opc, ki, rand) < 0)) return -1; + + /* + * AUTN = (SQN ^ AK) || AMF || MAC_A + */ + for (i = 0; i < sizeof(sqn_buff); i++) *p++ = sqn_buff[i] ^ ak_buff[i]; + memcpy(p, amf, MILENAGE_AMF_SIZE); + p += MILENAGE_AMF_SIZE; + memcpy(p, mac_a, sizeof(mac_a)); + + /* + * Output the anonymity key if required + */ + if (ak) memcpy(ak, ak_buff, sizeof(ak_buff)); + + return 0; +} + +/** Milenage AUTS validation + * + * @param[out] sqn SQN = 48-bit sequence number (host byte order). + * @param[in] opc 128-bit operator variant algorithm configuration field (encr.). + * @param[in] ki 128-bit subscriber key. + * @param[in] rand 128-bit random challenge. + * @param[in] auts 112-bit authentication token from client. + * @return + * - 0 on success with sqn filled. + * - -1 on failure. + */ +int milenage_auts(uint64_t *sqn, + uint8_t const opc[MILENAGE_OPC_SIZE], + uint8_t const ki[MILENAGE_KI_SIZE], + uint8_t const rand[MILENAGE_RAND_SIZE], + uint8_t const auts[MILENAGE_AUTS_SIZE]) +{ + uint8_t amf[MILENAGE_AMF_SIZE] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */ + uint8_t ak[MILENAGE_AK_SIZE], mac_s[MILENAGE_MAC_S_SIZE]; + uint8_t sqn_buff[MILENAGE_SQN_SIZE]; + size_t i; + + if (milenage_f2345(NULL, NULL, NULL, NULL, ak, opc, ki, rand)) return -1; + for (i = 0; i < sizeof(sqn_buff); i++) sqn_buff[i] = auts[i] ^ ak[i]; + + if (milenage_f1(NULL, mac_s, opc, ki, rand, sqn_buff, amf) || CRYPTO_memcmp(mac_s, auts + 6, 8) != 0) return -1; + + *sqn = uint48_from_buff(sqn_buff); + + return 0; +} + +/** Generate GSM-Milenage (3GPP TS 55.205) authentication triplet from a quintuplet + * + * @param[out] sres Buffer for SRES = 32-bit SRES. + * @param[out] kc 64-bit Kc. + * @param[in] ik 128-bit integrity. + * @param[in] ck Confidentiality key. + * @param[in] res 64-bit signed response. + */ +void milenage_gsm_from_umts(uint8_t sres[MILENAGE_SRES_SIZE], + uint8_t kc[MILENAGE_KC_SIZE], + uint8_t const ik[MILENAGE_IK_SIZE], + uint8_t const ck[MILENAGE_CK_SIZE], + uint8_t const res[MILENAGE_RES_SIZE]) +{ + int i; + + for (i = 0; i < 8; i++) kc[i] = ck[i] ^ ck[i + 8] ^ ik[i] ^ ik[i + 8]; + +#ifdef GSM_MILENAGE_ALT_SRES + memcpy(sres, res, 4); +#else /* GSM_MILENAGE_ALT_SRES */ + for (i = 0; i < 4; i++) sres[i] = res[i] ^ res[i + 4]; +#endif /* GSM_MILENAGE_ALT_SRES */ +} + +/** Generate GSM-Milenage (3GPP TS 55.205) authentication triplet + * + * @param[out] sres Buffer for SRES = 32-bit SRES. + * @param[out] kc 64-bit Kc. + * @param[in] opc 128-bit operator variant algorithm configuration field (encr.). + * @param[in] ki 128-bit subscriber key. + * @param[in] rand 128-bit random challenge. + * @return + * - 0 on success. + * - -1 on failure. + */ +int milenage_gsm_generate(uint8_t sres[MILENAGE_SRES_SIZE], + uint8_t kc[MILENAGE_KC_SIZE], + uint8_t const opc[MILENAGE_OPC_SIZE], + uint8_t const ki[MILENAGE_KI_SIZE], + uint8_t const rand[MILENAGE_RAND_SIZE]) +{ + uint8_t res[MILENAGE_RES_SIZE], ck[MILENAGE_CK_SIZE], ik[MILENAGE_IK_SIZE]; + + if (milenage_f2345(res, ik, ck, NULL, NULL, opc, ki, rand)) return -1; + + milenage_gsm_from_umts(sres, kc, ik, ck, res); + + return 0; +} + +/** Milenage check + * + * @param[out] ik Buffer for IK = 128-bit integrity key (f4), or NULL. + * @param[out] ck Buffer for CK = 128-bit confidentiality key (f3), or NULL. + * @param[out] res Buffer for RES = 64-bit signed response (f2), or NULL. + * @param[in] auts 112-bit buffer for AUTS. + * @param[in] opc 128-bit operator variant algorithm configuration field (encr.). + * @param[in] ki 128-bit subscriber key. + * @param[in] sqn 48-bit sequence number. + * @param[in] rand 128-bit random challenge. + * @param[in] autn 128-bit authentication token. + * @return + * - 0 on success. + * - -1 on failure. + * - -2 on synchronization failure + */ +int milenage_check(uint8_t ik[MILENAGE_IK_SIZE], + uint8_t ck[MILENAGE_CK_SIZE], + uint8_t res[MILENAGE_RES_SIZE], + uint8_t auts[MILENAGE_AUTS_SIZE], + uint8_t const opc[MILENAGE_OPC_SIZE], + uint8_t const ki[MILENAGE_KI_SIZE], + uint64_t sqn, + uint8_t const rand[MILENAGE_RAND_SIZE], + uint8_t const autn[MILENAGE_AUTN_SIZE]) +{ + + uint8_t mac_a[MILENAGE_MAC_A_SIZE], ak[MILENAGE_AK_SIZE], rx_sqn[MILENAGE_SQN_SIZE]; + uint8_t sqn_buff[MILENAGE_SQN_SIZE]; + const uint8_t *amf; + size_t i; + + uint48_to_buff(sqn_buff, sqn); + + //FR_PROTO_HEX_DUMP(autn, MILENAGE_AUTN_SIZE, "AUTN"); + //FR_PROTO_HEX_DUMP(rand, MILENAGE_RAND_SIZE, "RAND"); + + if (milenage_f2345(res, ck, ik, ak, NULL, opc, ki, rand)) return -1; + + //FR_PROTO_HEX_DUMP(res, MILENAGE_RES_SIZE, "RES"); + //FR_PROTO_HEX_DUMP(ck, MILENAGE_CK_SIZE, "CK"); + //FR_PROTO_HEX_DUMP(ik, MILENAGE_IK_SIZE, "IK"); + //FR_PROTO_HEX_DUMP(ak, MILENAGE_AK_SIZE, "AK"); + + /* AUTN = (SQN ^ AK) || AMF || MAC */ + for (i = 0; i < 6; i++) rx_sqn[i] = autn[i] ^ ak[i]; + //FR_PROTO_HEX_DUMP(rx_sqn, MILENAGE_SQN_SIZE, "SQN"); + + if (CRYPTO_memcmp(rx_sqn, sqn_buff, sizeof(rx_sqn)) <= 0) { + uint8_t auts_amf[MILENAGE_AMF_SIZE] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */ + + if (milenage_f2345(NULL, NULL, NULL, NULL, ak, opc, ki, rand)) return -1; + + //FR_PROTO_HEX_DUMP(ak, sizeof(ak), "AK*"); + for (i = 0; i < 6; i++) auts[i] = sqn_buff[i] ^ ak[i]; + + if (milenage_f1(NULL, auts + 6, opc, ki, rand, sqn_buff, auts_amf) < 0) return -1; + //FR_PROTO_HEX_DUMP(auts, 14, "AUTS"); + return -2; + } + + amf = autn + 6; + //FR_PROTO_HEX_DUMP(amf, MILENAGE_AMF_SIZE, "AMF"); + if (milenage_f1(mac_a, NULL, opc, ki, rand, rx_sqn, amf) < 0) return -1; + + //FR_PROTO_HEX_DUMP(mac_a, MILENAGE_MAC_A_SIZE, "MAC_A"); + + if (CRYPTO_memcmp(mac_a, autn + 8, 8) != 0) { + //FR_PROTO_HEX_DUMP(autn + 8, 8, "Received MAC_A"); + fr_strerror_printf("MAC mismatch"); + return -1; + } + + return 0; +} + +#ifdef TESTING_MILENAGE +/* + * cc milenage.c -g3 -Wall -DHAVE_DLFCN_H -DTESTING_MILENAGE -DWITH_TLS -I../../../../ -I../../../ -I ../base/ -I /usr/local/opt/openssl/include/ -include ../include/build.h -L /usr/local/opt/openssl/lib/ -l ssl -l crypto -l talloc -L ../../../../../build/lib/local/.libs/ -lfreeradius-server -lfreeradius-tls -lfreeradius-util -o test_milenage && ./test_milenage + */ +#include <freeradius-devel/util/acutest.h> + +void test_set_1(void) +{ + /* + * Inputs + */ + uint8_t ki[] = { 0x46, 0x5b, 0x5c, 0xe8, 0xb1, 0x99, 0xb4, 0x9f, + 0xaa, 0x5f, 0x0a, 0x2e, 0xe2, 0x38, 0xa6, 0xbc }; + uint8_t rand[] = { 0x23, 0x55, 0x3c, 0xbe, 0x96, 0x37, 0xa8, 0x9d, + 0x21, 0x8a, 0xe6, 0x4d, 0xae, 0x47, 0xbf, 0x35 }; + uint8_t sqn[] = { 0xff, 0x9b, 0xb4, 0xd0, 0xb6, 0x07 }; + uint8_t amf[] = { 0xb9, 0xb9 }; + uint8_t op[] = { 0xcd, 0xc2, 0x02, 0xd5, 0x12, 0x3e, 0x20, 0xf6, + 0x2b, 0x6d, 0x67, 0x6a, 0xc7, 0x2c, 0xb3, 0x18 }; + uint8_t opc[] = { 0xcd, 0x63, 0xcb, 0x71, 0x95, 0x4a, 0x9f, 0x4e, + 0x48, 0xa5, 0x99, 0x4e, 0x37, 0xa0, 0x2b, 0xaf }; + + /* + * Outputs + */ + uint8_t opc_out[MILENAGE_OPC_SIZE]; + uint8_t mac_a_out[MILENAGE_MAC_A_SIZE]; + uint8_t mac_s_out[MILENAGE_MAC_S_SIZE]; + uint8_t res_out[MILENAGE_RES_SIZE]; + uint8_t ck_out[MILENAGE_CK_SIZE]; + uint8_t ik_out[MILENAGE_IK_SIZE]; + uint8_t ak_out[MILENAGE_AK_SIZE]; + uint8_t ak_resync_out[MILENAGE_AK_SIZE]; + + /* function 1 */ + uint8_t mac_a[] = { 0x4a, 0x9f, 0xfa, 0xc3, 0x54, 0xdf, 0xaf, 0xb3 }; + /* function 1* */ + uint8_t mac_s[] = { 0x01, 0xcf, 0xaf, 0x9e, 0xc4, 0xe8, 0x71, 0xe9 }; + /* function 2 */ + uint8_t res[] = { 0xa5, 0x42, 0x11, 0xd5, 0xe3, 0xba, 0x50, 0xbf }; + /* function 3 */ + uint8_t ck[] = { 0xb4, 0x0b, 0xa9, 0xa3, 0xc5, 0x8b, 0x2a, 0x05, + 0xbb, 0xf0, 0xd9, 0x87, 0xb2, 0x1b, 0xf8, 0xcb }; + /* function 4 */ + uint8_t ik[] = { 0xf7, 0x69, 0xbc, 0xd7, 0x51, 0x04, 0x46, 0x04, + 0x12, 0x76, 0x72, 0x71, 0x1c, 0x6d, 0x34, 0x41 }; + /* function 5 */ + uint8_t ak[] = { 0xaa, 0x68, 0x9c, 0x64, 0x83, 0x70 }; + /* function 5* */ + uint8_t ak_resync[] = { 0x45, 0x1e, 0x8b, 0xec, 0xa4, 0x3b }; + + int ret = 0; + +/* + fr_debug_lvl = 4; +*/ + ret = milenage_opc_generate(opc_out, op, ki); + TEST_CHECK(ret == 0); + + //FR_PROTO_HEX_DUMP(opc_out, sizeof(opc_out), "opc"); + + TEST_CHECK(memcmp(opc_out, opc, sizeof(opc_out)) == 0); + + if ((milenage_f1(mac_a_out, mac_s_out, opc, ki, rand, sqn, amf) < 0) || + (milenage_f2345(res_out, ik_out, ck_out, ak_out, ak_resync_out, opc, ki, rand) < 0)) ret = -1; + + //FR_PROTO_HEX_DUMP(mac_a, sizeof(mac_a_out), "mac_a"); + //FR_PROTO_HEX_DUMP(mac_s, sizeof(mac_s_out), "mac_s"); + //FR_PROTO_HEX_DUMP(ik_out, sizeof(ik_out), "ik"); + //FR_PROTO_HEX_DUMP(ck_out, sizeof(ck_out), "ck"); + //FR_PROTO_HEX_DUMP(res_out, sizeof(res_out), "res"); + //FR_PROTO_HEX_DUMP(ak_out, sizeof(ak_out), "ak"); + //FR_PROTO_HEX_DUMP(ak_resync_out, sizeof(ak_resync_out), "ak_resync"); + + TEST_CHECK(ret == 0); + TEST_CHECK(memcmp(mac_a_out, mac_a, sizeof(mac_a_out)) == 0); + TEST_CHECK(memcmp(mac_s_out, mac_s, sizeof(mac_s_out)) == 0); + TEST_CHECK(memcmp(res_out, res, sizeof(res_out)) == 0); + TEST_CHECK(memcmp(ck_out, ck, sizeof(ck_out)) == 0); + TEST_CHECK(memcmp(ik_out, ik, sizeof(ik_out)) == 0); + TEST_CHECK(memcmp(ak_out, ak, sizeof(ak_out)) == 0); + TEST_CHECK(memcmp(ak_resync, ak_resync, sizeof(ak_resync_out)) == 0); +} + +void test_set_19(void) +{ + /* + * Inputs + */ + uint8_t ki[] = { 0x51, 0x22, 0x25, 0x02, 0x14, 0xc3, 0x3e, 0x72, + 0x3a, 0x5d, 0xd5, 0x23, 0xfc, 0x14, 0x5f, 0xc0 }; + uint8_t rand[] = { 0x81, 0xe9, 0x2b, 0x6c, 0x0e, 0xe0, 0xe1, 0x2e, + 0xbc, 0xeb, 0xa8, 0xd9, 0x2a, 0x99, 0xdf, 0xa5 }; + uint8_t sqn[] = { 0x16, 0xf3, 0xb3, 0xf7, 0x0f, 0xc2 }; + uint8_t amf[] = { 0xc3, 0xab }; + uint8_t op[] = { 0xc9, 0xe8, 0x76, 0x32, 0x86, 0xb5, 0xb9, 0xff, + 0xbd, 0xf5, 0x6e, 0x12, 0x97, 0xd0, 0x88, 0x7b }; + uint8_t opc[] = { 0x98, 0x1d, 0x46, 0x4c, 0x7c, 0x52, 0xeb, 0x6e, + 0x50, 0x36, 0x23, 0x49, 0x84, 0xad, 0x0b, 0xcf }; + + /* + * Outputs + */ + uint8_t opc_out[MILENAGE_OPC_SIZE]; + uint8_t mac_a_out[MILENAGE_MAC_A_SIZE]; + uint8_t mac_s_out[MILENAGE_MAC_S_SIZE]; + uint8_t res_out[MILENAGE_RES_SIZE]; + uint8_t ck_out[MILENAGE_CK_SIZE]; + uint8_t ik_out[MILENAGE_IK_SIZE]; + uint8_t ak_out[MILENAGE_AK_SIZE]; + uint8_t ak_resync_out[MILENAGE_AK_SIZE]; + + /* function 1 */ + uint8_t mac_a[] = { 0x2a, 0x5c, 0x23, 0xd1, 0x5e, 0xe3, 0x51, 0xd5 }; + /* function 1* */ + uint8_t mac_s[] = { 0x62, 0xda, 0xe3, 0x85, 0x3f, 0x3a, 0xf9, 0xd2 }; + /* function 2 */ + uint8_t res[] = { 0x28, 0xd7, 0xb0, 0xf2, 0xa2, 0xec, 0x3d, 0xe5 }; + /* function 3 */ + uint8_t ck[] = { 0x53, 0x49, 0xfb, 0xe0, 0x98, 0x64, 0x9f, 0x94, + 0x8f, 0x5d, 0x2e, 0x97, 0x3a, 0x81, 0xc0, 0x0f }; + /* function 4 */ + uint8_t ik[] = { 0x97, 0x44, 0x87, 0x1a, 0xd3, 0x2b, 0xf9, 0xbb, + 0xd1, 0xdd, 0x5c, 0xe5, 0x4e, 0x3e, 0x2e, 0x5a }; + /* function 5 */ + uint8_t ak[] = { 0xad, 0xa1, 0x5a, 0xeb, 0x7b, 0xb8 }; + /* function 5* */ + uint8_t ak_resync[] = { 0xd4, 0x61, 0xbc, 0x15, 0x47, 0x5d }; + + int ret = 0; + +/* + fr_debug_lvl = 4; +*/ + + ret = milenage_opc_generate(opc_out, op, ki); + TEST_CHECK(ret == 0); + + //FR_PROTO_HEX_DUMP(opc_out, sizeof(opc_out), "opc"); + + TEST_CHECK(memcmp(opc_out, opc, sizeof(opc_out)) == 0); + + if ((milenage_f1(mac_a_out, mac_s_out, opc, ki, rand, sqn, amf) < 0) || + (milenage_f2345(res_out, ik_out, ck_out, ak_out, ak_resync_out, opc, ki, rand) < 0)) ret = -1; + + //FR_PROTO_HEX_DUMP(mac_a, sizeof(mac_a_out), "mac_a"); + //FR_PROTO_HEX_DUMP(mac_s, sizeof(mac_s_out), "mac_s"); + //FR_PROTO_HEX_DUMP(ik_out, sizeof(ik_out), "ik"); + //FR_PROTO_HEX_DUMP(ck_out, sizeof(ck_out), "ck"); + //FR_PROTO_HEX_DUMP(res_out, sizeof(res_out), "res"); + //FR_PROTO_HEX_DUMP(ak_out, sizeof(ak_out), "ak"); + //FR_PROTO_HEX_DUMP(ak_resync_out, sizeof(ak_resync_out), "ak_resync"); + + TEST_CHECK(ret == 0); + TEST_CHECK(memcmp(mac_a_out, mac_a, sizeof(mac_a_out)) == 0); + TEST_CHECK(memcmp(mac_s_out, mac_s, sizeof(mac_s_out)) == 0); + TEST_CHECK(memcmp(res_out, res, sizeof(res_out)) == 0); + TEST_CHECK(memcmp(ck_out, ck, sizeof(ck_out)) == 0); + TEST_CHECK(memcmp(ik_out, ik, sizeof(ik_out)) == 0); + TEST_CHECK(memcmp(ak_out, ak, sizeof(ak_out)) == 0); + TEST_CHECK(memcmp(ak_resync, ak_resync, sizeof(ak_resync_out)) == 0); +} + +TEST_LIST = { + { "test_set_1", test_set_1 }, + { "test_set_19", test_set_19 }, + { NULL } +}; +#endif diff --git a/src/modules/rlm_wimax/milenage.h b/src/modules/rlm_wimax/milenage.h new file mode 100644 index 0000000..758383a --- /dev/null +++ b/src/modules/rlm_wimax/milenage.h @@ -0,0 +1,128 @@ +#pragma once +/** + * @file src/modules/rlm_wimax/milenage.h + * @brief 3GPP AKA - Milenage algorithm (3GPP TS 35.205, .206, .207, .208) + * + * This file implements an example authentication algorithm defined for 3GPP + * AKA. This can be used to implement a simple HLR/AuC into hlr_auc_gw to allow + * EAP-AKA to be tested properly with real USIM cards. + * + * This implementations assumes that the r1..r5 and c1..c5 constants defined in + * TS 35.206 are used, i.e., r1=64, r2=0, r3=32, r4=64, r5=96, c1=00..00, + * c2=00..01, c3=00..02, c4=00..04, c5=00..08. The block cipher is assumed to + * be AES (Rijndael). + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + * + * @copyright 2017 The FreeRADIUS server project + * @copyright 2006-2007 (j@w1.fi) + */ +#include <stddef.h> + +/* + * Inputs + */ +#define MILENAGE_KI_SIZE 16 //!< Subscriber key. +#define MILENAGE_OP_SIZE 16 //!< Operator code (unique to the operator) +#define MILENAGE_OPC_SIZE 16 //!< Derived operator code (unique to the operator and subscriber). +#define MILENAGE_AMF_SIZE 2 //!< Authentication management field. +#define MILENAGE_SQN_SIZE 6 //!< Sequence number. +#define MILENAGE_RAND_SIZE 16 //!< Random challenge. + +/* + * UMTS Outputs + */ +#define MILENAGE_AK_SIZE 6 //!< Anonymisation key. +#define MILENAGE_AUTN_SIZE 16 //!< Network authentication key. +#define MILENAGE_IK_SIZE 16 //!< Integrity key. +#define MILENAGE_CK_SIZE 16 //!< Ciphering key. +#define MILENAGE_RES_SIZE 8 +#define MILENAGE_AUTS_SIZE 14 + +/* + * GSM (COMP128-4) outputs + */ +#define MILENAGE_SRES_SIZE 4 +#define MILENAGE_KC_SIZE 8 + +/** Copy a 48bit value from a 64bit integer into a uint8_t buff in big endian byte order + * + * There may be fast ways of doing this, but this is the *correct* + * way, and does not make assumptions about how integers are laid + * out in memory. + * + * @param[out] out 6 byte butter to store value. + * @param[in] i integer value. + * @return pointer to out. + */ +static inline uint8_t *uint48_to_buff(uint8_t out[6], uint64_t i) +{ + out[0] = (i & 0xff0000000000) >> 40; + out[1] = (i & 0x00ff00000000) >> 32; + out[2] = (i & 0x0000ff000000) >> 24; + out[3] = (i & 0x000000ff0000) >> 16; + out[4] = (i & 0x00000000ff00) >> 8; + out[5] = (i & 0x0000000000ff); + + return out; +} + +/** Convert a 48bit big endian value into a unsigned 64bit integer + * + */ +static inline uint64_t uint48_from_buff(uint8_t const in[6]) +{ + uint64_t i = 0; + + i |= ((uint64_t)in[0]) << 40; + i |= ((uint64_t)in[1]) << 32; + i |= ((uint32_t)in[2]) << 24; + i |= ((uint32_t)in[3]) << 16; + i |= ((uint16_t)in[4]) << 8; + i |= in[5]; + + return i; +} + +int milenage_opc_generate(uint8_t opc[MILENAGE_OPC_SIZE], + uint8_t const op[MILENAGE_OP_SIZE], + uint8_t const ki[MILENAGE_KI_SIZE]); + +int milenage_umts_generate(uint8_t autn[MILENAGE_AUTN_SIZE], + uint8_t ik[MILENAGE_IK_SIZE], + uint8_t ck[MILENAGE_CK_SIZE], + uint8_t ak[MILENAGE_AK_SIZE], + uint8_t res[MILENAGE_RES_SIZE], + uint8_t const opc[MILENAGE_OPC_SIZE], + uint8_t const amf[MILENAGE_AMF_SIZE], + uint8_t const ki[MILENAGE_KI_SIZE], + uint64_t sqn, + uint8_t const rand[MILENAGE_RAND_SIZE]); + +int milenage_auts(uint64_t *sqn, + uint8_t const opc[MILENAGE_OPC_SIZE], + uint8_t const ki[MILENAGE_KI_SIZE], + uint8_t const rand[MILENAGE_RAND_SIZE], + uint8_t const auts[MILENAGE_AUTS_SIZE]); + +void milenage_gsm_from_umts(uint8_t sres[MILENAGE_SRES_SIZE], + uint8_t kc[MILENAGE_KC_SIZE], + uint8_t const ik[MILENAGE_IK_SIZE], + uint8_t const ck[MILENAGE_CK_SIZE], + uint8_t const res[MILENAGE_RES_SIZE]); + +int milenage_gsm_generate(uint8_t sres[MILENAGE_SRES_SIZE], uint8_t kc[MILENAGE_KC_SIZE], + uint8_t const opc[MILENAGE_OPC_SIZE], + uint8_t const ki[MILENAGE_KI_SIZE], + uint8_t const rand[MILENAGE_RAND_SIZE]); + +int milenage_check(uint8_t ik[MILENAGE_IK_SIZE], + uint8_t ck[MILENAGE_CK_SIZE], + uint8_t res[MILENAGE_RES_SIZE], + uint8_t auts[MILENAGE_AUTS_SIZE], + uint8_t const opc[MILENAGE_OPC_SIZE], + uint8_t const ki[MILENAGE_KI_SIZE], + uint64_t sqn, + uint8_t const rand[MILENAGE_RAND_SIZE], + uint8_t const autn[MILENAGE_AUTN_SIZE]); diff --git a/src/modules/rlm_wimax/rlm_wimax.c b/src/modules/rlm_wimax/rlm_wimax.c new file mode 100644 index 0000000..d2125eb --- /dev/null +++ b/src/modules/rlm_wimax/rlm_wimax.c @@ -0,0 +1,842 @@ +/* + * 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 rlm_wimax.c + * @brief Supports various WiMax functionality. + * + * @copyright 2008 Alan DeKok <aland@networkradius.com> + */ +RCSID("$Id$") +USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ + +#include <freeradius-devel/radiusd.h> +#include <freeradius-devel/modules.h> +#include "milenage.h" + +#ifdef HAVE_OPENSSL_HMAC_H +#include <openssl/hmac.h> +#endif + +#include <freeradius-devel/openssl3.h> + +#define WIMAX_EPSAKA_RAND_SIZE 16 +#define WIMAX_EPSAKA_KI_SIZE 16 +#define WIMAX_EPSAKA_OPC_SIZE 16 +#define WIMAX_EPSAKA_AMF_SIZE 2 +#define WIMAX_EPSAKA_SQN_SIZE 6 +#define WIMAX_EPSAKA_MAC_A_SIZE 8 +#define WIMAX_EPSAKA_MAC_S_SIZE 8 +#define WIMAX_EPSAKA_XRES_SIZE 8 +#define WIMAX_EPSAKA_CK_SIZE 16 +#define WIMAX_EPSAKA_IK_SIZE 16 +#define WIMAX_EPSAKA_AK_SIZE 6 +#define WIMAX_EPSAKA_AK_RESYNC_SIZE 6 +#define WIMAX_EPSAKA_KK_SIZE 32 +#define WIMAX_EPSAKA_KS_SIZE 14 +#define WIMAX_EPSAKA_PLMN_SIZE 3 +#define WIMAX_EPSAKA_KASME_SIZE 32 +#define WIMAX_EPSAKA_AUTN_SIZE 16 +#define WIMAX_EPSAKA_AUTS_SIZE 14 + +/* + * FIXME: Fix the build system to create definitions from names. + */ +typedef struct rlm_wimax_t { + bool delete_mppe_keys; + + DICT_ATTR const *resync_info; + DICT_ATTR const *xres; + DICT_ATTR const *autn; + DICT_ATTR const *kasme; +} rlm_wimax_t; + +/* + * A mapping of configuration file names to internal variables. + * + * Note that the string is dynamically allocated, so it MUST + * be freed. When the configuration file parse re-reads the string, + * it free's the old one, and strdup's the new one, placing the pointer + * to the strdup'd string into 'config.string'. This gets around + * buffer over-flows. + */ +static const CONF_PARSER module_config[] = { + { "delete_mppe_keys", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_wimax_t, delete_mppe_keys), "no" }, + CONF_PARSER_TERMINATOR +}; + +/* + * Print hex values in a readable format for debugging + * Example: + * FOO: 00 11 AA 22 00 FF + */ +static void rdebug_hex(REQUEST *request, char const *prefix, uint8_t const *data, int len) +{ + int i; + char buffer[256]; /* large enough for largest len */ + + /* + * Leave a trailing space, we don't really care about that. + */ + for (i = 0; i < len; i++) { + snprintf(buffer + i * 3, sizeof(buffer) - i * 3, "%02x ", data[i]); + } + + RDEBUG("%s %s", prefix, buffer); +} +#define RDEBUG_HEX if (rad_debug_lvl) rdebug_hex + +/* + * Find the named user in this modules database. Create the set + * of attribute-value pairs to check and reply with for this user + * from the database. The authentication code only needs to check + * the password, the rest is done here. + */ +static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request) +{ + VALUE_PAIR *vp; + rlm_wimax_t *inst = instance; + + /* + * Fix Calling-Station-Id. Damn you, WiMAX! + */ + vp = fr_pair_find_by_num(request->packet->vps, PW_CALLING_STATION_ID, 0, TAG_ANY); + if (vp && (vp->vp_length == 6)) { + int i; + char *p; + uint8_t buffer[6]; + + memcpy(buffer, vp->vp_strvalue, 6); + vp->vp_length = (5*3)+2; + vp->vp_strvalue = p = talloc_array(vp, char, vp->vp_length + 1); + vp->type = VT_DATA; + + /* + * RFC 3580 Section 3.20 says this is the preferred + * format. Everyone *SANE* is using this format, + * so we fix it here. + */ + for (i = 0; i < 6; i++) { + fr_bin2hex(&p[i * 3], &buffer[i], 1); + p[(i * 3) + 2] = '-'; + } + + p[(5*3)+2] = '\0'; + + DEBUG2("rlm_wimax: Fixing WiMAX binary Calling-Station-Id to %s", + vp->vp_strvalue); + return RLM_MODULE_OK; + } + + /* + * Check for attr WiMAX-Re-synchronization-Info + * which contains the concatenation of RAND and AUTS + * + * If it is present then we proceed to verify the SIM and + * extract the new value of SQN + */ + VALUE_PAIR *resync_info, *ki, *opc, *sqn, *rand; + int m_ret; + + /* Look for the Re-synchronization-Info attribute in the request */ + resync_info = fr_pair_find_by_da(request->packet->vps, inst->resync_info, TAG_ANY); + if (resync_info && (resync_info->vp_length < (WIMAX_EPSAKA_RAND_SIZE + WIMAX_EPSAKA_AUTS_SIZE))) { + RWDEBUG("Found request:WiMAX-Re-synchronization-Info with incorrect length: Ignoring it"); + resync_info = NULL; + } + + /* + * These are the private keys which should be added to the control + * list after looking them up in a database by IMSI + * + * We grab them from the control list here + */ + ki = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_KI, 0, TAG_ANY); + if (ki && (ki->vp_length < MILENAGE_CK_SIZE)) { + RWDEBUG("Found config:WiMAX-SIM-Ki with incorrect length: Ignoring it"); + ki = NULL; + } + + opc = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_OPC, 0, TAG_ANY); + if (opc && (opc->vp_length < MILENAGE_IK_SIZE)) { + RWDEBUG("Found config:WiMAX-SIM-OPC with incorrect length: Ignoring it"); + opc = NULL; + } + + /* If we have resync info (RAND and AUTS), Ki and OPc then we can proceed */ + if (resync_info && ki && opc) { + uint64_t sqn_bin; + uint8_t rand_bin[WIMAX_EPSAKA_RAND_SIZE]; + uint8_t auts_bin[WIMAX_EPSAKA_AUTS_SIZE]; + + RDEBUG("Found WiMAX-Re-synchronization-Info. Proceeding with SQN resync"); + + /* Split Re-synchronization-Info into seperate RAND and AUTS */ + + memcpy(rand_bin, &resync_info->vp_octets[0], WIMAX_EPSAKA_RAND_SIZE); + memcpy(auts_bin, &resync_info->vp_octets[WIMAX_EPSAKA_RAND_SIZE], WIMAX_EPSAKA_AUTS_SIZE); + + RDEBUG_HEX(request, "RAND ", rand_bin, WIMAX_EPSAKA_RAND_SIZE); + RDEBUG_HEX(request, "AUTS ", auts_bin, WIMAX_EPSAKA_AUTS_SIZE); + + /* + * This procedure uses the secret keys Ki and OPc to authenticate + * the SIM and extract the SQN + */ + m_ret = milenage_auts(&sqn_bin, opc->vp_octets, ki->vp_octets, rand_bin, auts_bin); + + /* + * If the SIM verification fails then we can't go any further as + * we don't have the keys. And that probably means something bad + * is happening so we bail out now + */ + if (m_ret < 0) { + RDEBUG("SIM verification failed"); + return RLM_MODULE_REJECT; + } + + /* + * If we got this far it means have got a new SQN and RAND + * so we store them in: + * control:WiMAX-SIM-SQN + * control:WiMAX-SIM-RAND + * + * From there they can be grabbed by unlang and used later + */ + + /* SQN is six bytes so we extract what we need from the 64 bit variable */ + uint8_t sqn_bin_arr[WIMAX_EPSAKA_SQN_SIZE] = { + (sqn_bin & 0x0000FF0000000000ull) >> 40, + (sqn_bin & 0x000000FF00000000ull) >> 32, + (sqn_bin & 0x00000000FF000000ull) >> 24, + (sqn_bin & 0x0000000000FF0000ull) >> 16, + (sqn_bin & 0x000000000000FF00ull) >> 8, + (sqn_bin & 0x00000000000000FFull) >> 0 + }; + + /* Add SQN to control:WiMAX-SIM-SQN */ + sqn = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_SQN, 0, TAG_ANY); + if (sqn && (sqn->vp_length < WIMAX_EPSAKA_SQN_SIZE)) { + RWDEBUG("Found config:WiMAX-SIM-SQN with incorrect length: Ignoring it"); + sqn = NULL; + } + + if (!sqn) { + MEM(sqn = pair_make_config("WiMAX-SIM-SQN", NULL, T_OP_SET)); + fr_pair_value_memcpy(sqn, sqn_bin_arr, WIMAX_EPSAKA_SQN_SIZE); + } + RDEBUG_HEX(request, "SQN ", sqn->vp_octets, WIMAX_EPSAKA_SQN_SIZE); + + /* Add RAND to control:WiMAX-SIM-RAND */ + rand = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_RAND, 0, TAG_ANY); + if (rand && (rand->vp_length < WIMAX_EPSAKA_RAND_SIZE)) { + RWDEBUG("Found config:WiMAX-SIM-RAND with incorrect length: Ignoring it"); + rand = NULL; + } + + if (!rand) { + MEM(rand = pair_make_config("WiMAX-SIM-RAND", NULL, T_OP_SET)); + fr_pair_value_memcpy(rand, rand_bin, WIMAX_EPSAKA_RAND_SIZE); + } + RDEBUG_HEX(request, "RAND ", rand->vp_octets, WIMAX_EPSAKA_RAND_SIZE); + + return RLM_MODULE_UPDATED; + } + + return RLM_MODULE_NOOP; +} + +/* + * Massage the request before recording it or proxying it + */ +static rlm_rcode_t CC_HINT(nonnull) mod_preacct(void *instance, REQUEST *request) +{ + return mod_authorize(instance, request); +} + + +/* + * This function generates the keys for old style WiMAX (v1 to v2.0) + */ +static int mip_keys_generate(void *instance, REQUEST *request, VALUE_PAIR *msk, VALUE_PAIR *emsk) +{ + rlm_wimax_t *inst = instance; + VALUE_PAIR *vp; + VALUE_PAIR *mn_nai, *ip, *fa_rk; + HMAC_CTX *hmac; + unsigned int rk1_len, rk2_len, rk_len; + uint32_t mip_spi; + uint8_t usage_data[24]; + uint8_t mip_rk_1[EVP_MAX_MD_SIZE], mip_rk_2[EVP_MAX_MD_SIZE]; + uint8_t mip_rk[2 * EVP_MAX_MD_SIZE]; + + /* + * If we delete the MS-MPPE-*-Key attributes, then add in + * the WiMAX-MSK so that the client has a key available. + */ + if (inst->delete_mppe_keys) { + fr_pair_delete_by_num(&request->reply->vps, 16, VENDORPEC_MICROSOFT, TAG_ANY); + fr_pair_delete_by_num(&request->reply->vps, 17, VENDORPEC_MICROSOFT, TAG_ANY); + + MEM(vp = pair_make_reply("WiMAX-MSK", NULL, T_OP_EQ)); + fr_pair_value_memcpy(vp, msk->vp_octets, msk->vp_length); + } + + /* + * Initialize usage data. + */ + memcpy(usage_data, "miprk@wimaxforum.org", 21); /* with trailing \0 */ + usage_data[21] = 0x02; + usage_data[22] = 0x00; + usage_data[23] = 0x01; + + /* + * MIP-RK-1 = HMAC-SSHA256(EMSK, usage-data | 0x01) + */ + hmac = HMAC_CTX_new(); + HMAC_Init_ex(hmac, emsk->vp_octets, emsk->vp_length, EVP_sha256(), NULL); + rk1_len = SHA256_DIGEST_LENGTH; + + HMAC_Update(hmac, &usage_data[0], sizeof(usage_data)); + HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); + + /* + * MIP-RK-2 = HMAC-SSHA256(EMSK, MIP-RK-1 | usage-data | 0x01) + */ + HMAC_Init_ex(hmac, emsk->vp_octets, emsk->vp_length, EVP_sha256(), NULL); + + HMAC_Update(hmac, (uint8_t const *) &mip_rk_1, rk1_len); + HMAC_Update(hmac, &usage_data[0], sizeof(usage_data)); + rk2_len = SHA256_DIGEST_LENGTH; + HMAC_Final(hmac, &mip_rk_2[0], &rk2_len); + + memcpy(mip_rk, mip_rk_1, rk1_len); + memcpy(mip_rk + rk1_len, mip_rk_2, rk2_len); + rk_len = rk1_len + rk2_len; + + /* + * MIP-SPI = HMAC-SSHA256(MIP-RK, "SPI CMIP PMIP"); + */ + HMAC_Init_ex(hmac, mip_rk, rk_len, EVP_sha256(), NULL); + + HMAC_Update(hmac, (uint8_t const *) "SPI CMIP PMIP", 12); + rk1_len = SHA256_DIGEST_LENGTH; + HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); + + /* + * Take the 4 most significant octets. + * If less than 256, add 256. + */ + mip_spi = ((mip_rk_1[0] << 24) | (mip_rk_1[1] << 16) | + (mip_rk_1[2] << 8) | mip_rk_1[3]); + if (mip_spi < 256) mip_spi += 256; + + RDEBUG_HEX(request, "MIP-RK ", mip_rk, rk_len); + RDEBUG("MIP-SPI = %08x", ntohl(mip_spi)); + + /* + * FIXME: Perform SPI collision prevention + */ + + /* + * Calculate mobility keys + */ + mn_nai = fr_pair_find_by_num(request->packet->vps, PW_WIMAX_MN_NAI, 0, TAG_ANY); + if (!mn_nai) mn_nai = fr_pair_find_by_num(request->reply->vps, PW_WIMAX_MN_NAI, 0, TAG_ANY); + if (!mn_nai) { + RWDEBUG("WiMAX-MN-NAI was not found in the request or in the reply"); + RWDEBUG("We cannot calculate MN-HA keys"); + } + + /* + * WiMAX-IP-Technology + */ + vp = NULL; + if (mn_nai) vp = fr_pair_find_by_num(request->reply->vps, 23, VENDORPEC_WIMAX, TAG_ANY); + if (!vp) { + RWDEBUG("WiMAX-IP-Technology not found in reply"); + RWDEBUG("Not calculating MN-HA keys"); + } + + if (vp) switch (vp->vp_integer) { + case 2: /* PMIP4 */ + /* + * Look for WiMAX-hHA-IP-MIP4 + */ + ip = fr_pair_find_by_num(request->reply->vps, 6, VENDORPEC_WIMAX, TAG_ANY); + if (!ip) { + RWDEBUG("WiMAX-hHA-IP-MIP4 not found. Cannot calculate MN-HA-PMIP4 key"); + break; + } + + /* + * MN-HA-PMIP4 = + * H(MIP-RK, "PMIP4 MN HA" | HA-IPv4 | MN-NAI); + */ + HMAC_Init_ex(hmac, mip_rk, rk_len, EVP_sha1(), NULL); + + HMAC_Update(hmac, (uint8_t const *) "PMIP4 MN HA", 11); + HMAC_Update(hmac, (uint8_t const *) &ip->vp_ipaddr, 4); + HMAC_Update(hmac, (uint8_t const *) &mn_nai->vp_strvalue, mn_nai->vp_length); + rk1_len = SHA1_DIGEST_LENGTH; + HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); + + /* + * Put MN-HA-PMIP4 into WiMAX-MN-hHA-MIP4-Key + */ + vp = fr_pair_find_by_num(request->reply->vps, 10, VENDORPEC_WIMAX, TAG_ANY); + if (!vp) { + vp = radius_pair_create(request->reply, &request->reply->vps, + 10, VENDORPEC_WIMAX); + } + if (!vp) { + RWDEBUG("Failed creating WiMAX-MN-hHA-MIP4-Key"); + break; + } + fr_pair_value_memcpy(vp, &mip_rk_1[0], rk1_len); + + /* + * Put MN-HA-PMIP4-SPI into WiMAX-MN-hHA-MIP4-SPI + */ + vp = fr_pair_find_by_num(request->reply->vps, 11, VENDORPEC_WIMAX, TAG_ANY); + if (!vp) { + vp = radius_pair_create(request->reply, &request->reply->vps, + 11, VENDORPEC_WIMAX); + } + if (!vp) { + RWDEBUG("Failed creating WiMAX-MN-hHA-MIP4-SPI"); + break; + } + vp->vp_integer = mip_spi + 1; + break; + + case 3: /* CMIP4 */ + /* + * Look for WiMAX-hHA-IP-MIP4 + */ + ip = fr_pair_find_by_num(request->reply->vps, 6, VENDORPEC_WIMAX, TAG_ANY); + if (!ip) { + RWDEBUG("WiMAX-hHA-IP-MIP4 not found. Cannot calculate MN-HA-CMIP4 key"); + break; + } + + /* + * MN-HA-CMIP4 = + * H(MIP-RK, "CMIP4 MN HA" | HA-IPv4 | MN-NAI); + */ + HMAC_Init_ex(hmac, mip_rk, rk_len, EVP_sha1(), NULL); + + HMAC_Update(hmac, (uint8_t const *) "CMIP4 MN HA", 11); + HMAC_Update(hmac, (uint8_t const *) &ip->vp_ipaddr, 4); + HMAC_Update(hmac, (uint8_t const *) &mn_nai->vp_strvalue, mn_nai->vp_length); + rk1_len = SHA1_DIGEST_LENGTH; + HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); + + /* + * Put MN-HA-CMIP4 into WiMAX-MN-hHA-MIP4-Key + */ + vp = fr_pair_find_by_num(request->reply->vps, 10, VENDORPEC_WIMAX, TAG_ANY); + if (!vp) { + vp = radius_pair_create(request->reply, &request->reply->vps, + 10, VENDORPEC_WIMAX); + } + if (!vp) { + RWDEBUG("Failed creating WiMAX-MN-hHA-MIP4-Key"); + break; + } + fr_pair_value_memcpy(vp, &mip_rk_1[0], rk1_len); + + /* + * Put MN-HA-CMIP4-SPI into WiMAX-MN-hHA-MIP4-SPI + */ + vp = fr_pair_find_by_num(request->reply->vps, 11, VENDORPEC_WIMAX, TAG_ANY); + if (!vp) { + vp = radius_pair_create(request->reply, &request->reply->vps, + 11, VENDORPEC_WIMAX); + } + if (!vp) { + RWDEBUG("Failed creating WiMAX-MN-hHA-MIP4-SPI"); + break; + } + vp->vp_integer = mip_spi; + break; + + case 4: /* CMIP6 */ + /* + * Look for WiMAX-hHA-IP-MIP6 + */ + ip = fr_pair_find_by_num(request->reply->vps, 7, VENDORPEC_WIMAX, TAG_ANY); + if (!ip) { + RWDEBUG("WiMAX-hHA-IP-MIP6 not found. Cannot calculate MN-HA-CMIP6 key"); + break; + } + + /* + * MN-HA-CMIP6 = + * H(MIP-RK, "CMIP6 MN HA" | HA-IPv6 | MN-NAI); + */ + HMAC_Init_ex(hmac, mip_rk, rk_len, EVP_sha1(), NULL); + + HMAC_Update(hmac, (uint8_t const *) "CMIP6 MN HA", 11); + HMAC_Update(hmac, (uint8_t const *) &ip->vp_ipv6addr, 16); + HMAC_Update(hmac, (uint8_t const *) &mn_nai->vp_strvalue, mn_nai->vp_length); + rk1_len = SHA1_DIGEST_LENGTH; + HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); + + /* + * Put MN-HA-CMIP6 into WiMAX-MN-hHA-MIP6-Key + */ + vp = fr_pair_find_by_num(request->reply->vps, 12, VENDORPEC_WIMAX, TAG_ANY); + if (!vp) { + vp = radius_pair_create(request->reply, &request->reply->vps, + 12, VENDORPEC_WIMAX); + } + if (!vp) { + RWDEBUG("Failed creating WiMAX-MN-hHA-MIP6-Key"); + break; + } + fr_pair_value_memcpy(vp, &mip_rk_1[0], rk1_len); + + /* + * Put MN-HA-CMIP6-SPI into WiMAX-MN-hHA-MIP6-SPI + */ + vp = fr_pair_find_by_num(request->reply->vps, 13, VENDORPEC_WIMAX, TAG_ANY); + if (!vp) { + vp = radius_pair_create(request->reply, &request->reply->vps, + 13, VENDORPEC_WIMAX); + } + if (!vp) { + RWDEBUG("Failed creating WiMAX-MN-hHA-MIP6-SPI"); + break; + } + vp->vp_integer = mip_spi + 2; + break; + + default: + break; /* do nothing */ + } + + /* + * Generate FA-RK, if requested. + * + * FA-RK= H(MIP-RK, "FA-RK") + */ + fa_rk = fr_pair_find_by_num(request->reply->vps, 14, VENDORPEC_WIMAX, TAG_ANY); + if (fa_rk && (fa_rk->vp_length <= 1)) { + HMAC_Init_ex(hmac, mip_rk, rk_len, EVP_sha1(), NULL); + + HMAC_Update(hmac, (uint8_t const *) "FA-RK", 5); + + rk1_len = SHA1_DIGEST_LENGTH; + HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); + + fr_pair_value_memcpy(fa_rk, &mip_rk_1[0], rk1_len); + } + + /* + * Create FA-RK-SPI, which is really SPI-CMIP4, which is + * really MIP-SPI. Clear? Of course. This is WiMAX. + */ + if (fa_rk) { + vp = fr_pair_find_by_num(request->reply->vps, 61, VENDORPEC_WIMAX, TAG_ANY); + if (!vp) { + vp = radius_pair_create(request->reply, &request->reply->vps, + 61, VENDORPEC_WIMAX); + } + if (!vp) { + RWDEBUG("Failed creating WiMAX-FA-RK-SPI"); + } else { + vp->vp_integer = mip_spi; + } + } + + /* + * Give additional information about requests && responses + * + * WiMAX-RRQ-MN-HA-SPI + */ + vp = fr_pair_find_by_num(request->packet->vps, 20, VENDORPEC_WIMAX, TAG_ANY); + if (vp) { + RDEBUG("Client requested MN-HA key: Should use SPI to look up key from storage"); + if (!mn_nai) { + RWDEBUG("MN-NAI was not found!"); + } + + /* + * WiMAX-RRQ-HA-IP + */ + if (!fr_pair_find_by_num(request->packet->vps, 18, VENDORPEC_WIMAX, TAG_ANY)) { + RWDEBUG("HA-IP was not found!"); + } + + + /* + * WiMAX-HA-RK-Key-Requested + */ + vp = fr_pair_find_by_num(request->packet->vps, 58, VENDORPEC_WIMAX, TAG_ANY); + if (vp && (vp->vp_integer == 1)) { + RDEBUG("Client requested HA-RK: Should use IP to look it up from storage"); + } + } + + /* + * Wipe the context of all sensitive information. + */ + HMAC_CTX_free(hmac); + + return RLM_MODULE_UPDATED; +} + +/* + * Generate the EPS-AKA authentication vector + * + * These are the keys needed for new style WiMAX (LTE / 3gpp authentication), + for WiMAX v2.1 + */ +static rlm_rcode_t aka_keys_generate(REQUEST *request, rlm_wimax_t const *inst, VALUE_PAIR *ki, VALUE_PAIR *opc, + VALUE_PAIR *amf, VALUE_PAIR *sqn, VALUE_PAIR *plmn) +{ + size_t i; + VALUE_PAIR *rand_previous, *rand, *xres, *autn, *kasme; + + /* + * For most authentication requests we need to generate a fresh RAND + * + * The exception is after SQN re-syncronisation - in this case we + * get RAND in the request, and this module if called in authorize should + * have put it in control:WiMAX-SIM-RAND so we can grab it from there) + */ + rand_previous = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_RAND, 0, TAG_ANY); + if (rand_previous && (rand_previous->vp_length < WIMAX_EPSAKA_RAND_SIZE)) { + RWDEBUG("Found config:WiMAX-SIM-Rand with incorrect size. Ignoring it."); + rand_previous = NULL; + } + + MEM(rand = pair_make_reply("WiMAX-E-UTRAN-Vector-RAND", NULL, T_OP_SET)); + if (!rand_previous) { + uint32_t lvalue; + uint8_t buffer[WIMAX_EPSAKA_RAND_SIZE]; + + for (i = 0; i < (WIMAX_EPSAKA_RAND_SIZE / 4); i++) { + lvalue = fr_rand(); + memcpy(buffer + i * 4, &lvalue, sizeof(lvalue)); + } + + fr_pair_value_memcpy(rand, buffer, WIMAX_EPSAKA_RAND_SIZE); + + } else { + fr_pair_value_memcpy(rand, rand_previous->vp_octets, WIMAX_EPSAKA_RAND_SIZE); + } + + /* + * Feed AMF, Ki, SQN and RAND into the Milenage algorithm (f1, f2, f3, f4, f5) + * which returns AUTN, AK, CK, IK, XRES. + */ + uint8_t xres_bin[WIMAX_EPSAKA_XRES_SIZE]; + uint8_t ck_bin[WIMAX_EPSAKA_CK_SIZE]; + uint8_t ik_bin[WIMAX_EPSAKA_IK_SIZE]; + uint8_t ak_bin[WIMAX_EPSAKA_AK_SIZE]; + uint8_t autn_bin[WIMAX_EPSAKA_AUTN_SIZE]; + + /* But first convert uint8 SQN to uint64 */ + uint64_t sqn_bin = 0x0000000000000000; + for (i = 0; i < sqn->vp_length; ++i) sqn_bin = (sqn_bin << 8) | sqn->vp_octets[i]; + + if (!opc || (opc->vp_length < MILENAGE_OPC_SIZE)) { + RWDEBUG("Found config:WiMAX-SIM-OPC with incorrect size. Ignoring it"); + return RLM_MODULE_NOOP; + } + if (!amf || (amf->vp_length < MILENAGE_AMF_SIZE)) { + RWDEBUG("Found config:WiMAX-SIM-AMF with incorrect size. Ignoring it"); + return RLM_MODULE_NOOP; + } + if (!ki || (ki->vp_length < MILENAGE_KI_SIZE)) { + RWDEBUG("Found config:WiMAX-SIM-KI with incorrect size. Ignoring it"); + return RLM_MODULE_NOOP; + } + + /* Call milenage */ + milenage_umts_generate(autn_bin, ik_bin, ck_bin, ak_bin, xres_bin, opc->vp_octets, + amf->vp_octets, ki->vp_octets, sqn_bin, rand->vp_octets); + + /* + * Now we genertate KASME + * + * Officially described in 33401-g30.doc section A.2 + * But an easier to read explanation can be found at: + * https://medium.com/uw-ictd/lte-authentication-2d0810a061ec + * + */ + + /* k = CK || IK */ + uint8_t kk_bin[WIMAX_EPSAKA_KK_SIZE]; + memcpy(kk_bin, ck_bin, sizeof(ck_bin)); + memcpy(kk_bin + sizeof(ck_bin), ik_bin, sizeof(ik_bin)); + + /* Initialize a 14 byte buffer s */ + uint8_t ks_bin[WIMAX_EPSAKA_KS_SIZE]; + + /* Assign the first byte of s as 0x10 */ + ks_bin[0] = 0x10; + + /* Copy the 3 bytes of PLMN into s */ + memcpy(ks_bin + 1, plmn->vp_octets, 3); + + /* Assign 5th and 6th byte as 0x00 and 0x03 */ + ks_bin[4] = 0x00; + ks_bin[5] = 0x03; + + /* Assign the next 6 bytes as SQN XOR AK */ + for (i = 0; i < 6; i++) { + ks_bin[i+6] = sqn->vp_octets[i] ^ ak_bin[i]; + } + + /* Assign the last two bytes as 0x00 and 0x06 */ + ks_bin[12] = 0x00; + ks_bin[13] = 0x06; + + /* Perform an HMAC-SHA256 using Key k from step 1 and s as the message. */ + uint8_t kasme_bin[WIMAX_EPSAKA_KASME_SIZE]; + HMAC_CTX *hmac; + unsigned int kasme_len = sizeof(kasme_bin); + + hmac = HMAC_CTX_new(); + HMAC_Init_ex(hmac, kk_bin, sizeof(kk_bin), EVP_sha256(), NULL); + HMAC_Update(hmac, ks_bin, sizeof(ks_bin)); + kasme_len = SHA256_DIGEST_LENGTH; + HMAC_Final(hmac, &kasme_bin[0], &kasme_len); + HMAC_CTX_free(hmac); + + /* + * Add reply attributes XRES, AUTN and KASME (RAND we added earlier) + * + * Note that we can't call fr_pair_find_by_num(), as + * these attributes are buried deep inside of the WiMAX + * hierarchy. + */ + xres = fr_pair_find_by_da(request->reply->vps, inst->xres, TAG_ANY); + if (!xres) { + MEM(xres = pair_make_reply("WiMAX-E-UTRAN-Vector-XRES", NULL, T_OP_SET)); + fr_pair_value_memcpy(xres, xres_bin, WIMAX_EPSAKA_XRES_SIZE); + } + + autn = fr_pair_find_by_da(request->reply->vps, inst->autn, TAG_ANY); + if (!autn) { + MEM(autn = pair_make_reply("WiMAX-E-UTRAN-Vector-AUTN", NULL, T_OP_SET)); + fr_pair_value_memcpy(autn, autn_bin, WIMAX_EPSAKA_AUTN_SIZE); + } + + kasme = fr_pair_find_by_da(request->reply->vps, inst->kasme, TAG_ANY); + if (!kasme) { + MEM(kasme = pair_make_reply("WiMAX-E-UTRAN-Vector-KASME", NULL, T_OP_SET)); + fr_pair_value_memcpy(kasme, kasme_bin, WIMAX_EPSAKA_KASME_SIZE); + } + + /* Print keys to log for debugging */ + if (rad_debug_lvl) { + RDEBUG("-------- Milenage in --------"); + RDEBUG_HEX(request, "OPc ", opc->vp_octets, opc->vp_length); + RDEBUG_HEX(request, "Ki ", ki->vp_octets, ki->vp_length); + RDEBUG_HEX(request, "RAND ", rand->vp_octets, rand->vp_length); + RDEBUG_HEX(request, "SQN ", sqn->vp_octets, sqn->vp_length); + RDEBUG_HEX(request, "AMF ", amf->vp_octets, amf->vp_length); + RDEBUG("-------- Milenage out -------"); + RDEBUG_HEX(request, "XRES ", xres->vp_octets, xres->vp_length); + RDEBUG_HEX(request, "Ck ", ck_bin, sizeof(ck_bin)); + RDEBUG_HEX(request, "Ik ", ik_bin, sizeof(ik_bin)); + RDEBUG_HEX(request, "Ak ", ak_bin, sizeof(ak_bin)); + RDEBUG_HEX(request, "AUTN ", autn->vp_octets, autn->vp_length); + RDEBUG("-----------------------------"); + RDEBUG_HEX(request, "Kk ", kk_bin, sizeof(kk_bin)); + RDEBUG_HEX(request, "Ks ", ks_bin, sizeof(ks_bin)); + RDEBUG_HEX(request, "KASME ", kasme->vp_octets, kasme->vp_length); + } + + return RLM_MODULE_UPDATED; +} + +/* + * Generate the keys after the user has been authenticated. + */ +static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request) +{ + VALUE_PAIR *msk, *emsk, *ki, *opc, *amf, *sqn, *plmn; + + /* + * If we have MSK and EMSK then assume we want MIP keys + * Else if we have the SIM keys then we want the EPS-AKA vector + */ + + msk = fr_pair_find_by_num(request->reply->vps, PW_EAP_MSK, 0, TAG_ANY); + emsk = fr_pair_find_by_num(request->reply->vps, PW_EAP_EMSK, 0, TAG_ANY); + + if (msk && emsk) { + RDEBUG("MSK and EMSK found. Generating MIP keys"); + return mip_keys_generate(instance, request, msk, emsk); + } + + ki = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_KI, 0, TAG_ANY); + opc = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_OPC, 0, TAG_ANY); + amf = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_AMF, 0, TAG_ANY); + sqn = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_SQN, 0, TAG_ANY); + plmn = fr_pair_find_by_num(request->packet->vps, 146, VENDORPEC_WIMAX, TAG_ANY); + + if (ki && opc && amf && sqn && plmn) { + RDEBUG("AKA attributes found. Generating AKA keys."); + return aka_keys_generate(request, instance, ki, opc, amf, sqn, plmn); + } + + RDEBUG("Input keys not found. Cannot create WiMAX keys"); + return RLM_MODULE_NOOP; +} + + +static int mod_instantiate(UNUSED CONF_SECTION *conf, void *instance) +{ + rlm_wimax_t *inst = instance; + + inst->resync_info = dict_attrbyname("WiMAX-Re-synchronization-Info"); + inst->xres = dict_attrbyname("WiMAX-E-UTRAN-Vector-XRES"); + inst->autn = dict_attrbyname("WiMAX-E-UTRAN-Vector-AUTN"); + inst->kasme = dict_attrbyname("WiMAX-E-UTRAN-Vector-KASME"); + + return 0; +} + +/* + * The module name should be the only globally exported symbol. + * That is, everything else should be 'static'. + * + * If the module needs to temporarily modify it's instantiation + * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE. + * The server will then take care of ensuring that the module + * is single-threaded. + */ +extern module_t rlm_wimax; +module_t rlm_wimax = { + .magic = RLM_MODULE_INIT, + .name = "wimax", + .type = RLM_TYPE_THREAD_SAFE, + .inst_size = sizeof(rlm_wimax_t), + .config = module_config, + .instantiate = mod_instantiate, + .methods = { + [MOD_AUTHORIZE] = mod_authorize, + [MOD_PREACCT] = mod_preacct, + [MOD_POST_AUTH] = mod_post_auth + }, +}; |