summaryrefslogtreecommitdiffstats
path: root/src/modules/rlm_wimax
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/rlm_wimax')
-rw-r--r--src/modules/rlm_wimax/README.md9
-rw-r--r--src/modules/rlm_wimax/all.mk9
-rw-r--r--src/modules/rlm_wimax/milenage.c642
-rw-r--r--src/modules/rlm_wimax/milenage.h128
-rw-r--r--src/modules/rlm_wimax/rlm_wimax.c842
5 files changed, 1630 insertions, 0 deletions
diff --git a/src/modules/rlm_wimax/README.md b/src/modules/rlm_wimax/README.md
new file mode 100644
index 0000000..6d1b342
--- /dev/null
+++ b/src/modules/rlm_wimax/README.md
@@ -0,0 +1,9 @@
+# rlm_wimax
+## Metadata
+<dl>
+ <dt>category</dt><dd>authentication</dd>
+</dl>
+
+## Summary
+
+Implements WiMAX authentication over RADIUS.
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
+ },
+};