]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Shamelessly copy the milenage code from wpa_supplicant
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Fri, 8 Dec 2017 11:47:54 +0000 (11:47 +0000)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Fri, 8 Dec 2017 11:50:31 +0000 (11:50 +0000)
This lets us simulate an AS, and hopefully have eapol_test simulate a usim, meaning we can add sim,aka,aka' to CIT in future.

src/modules/rlm_eap/lib/sim/all.mk
src/modules/rlm_eap/lib/sim/milenage.c [new file with mode: 0644]
src/modules/rlm_eap/lib/sim/milenage.h [new file with mode: 0644]

index 290fbc748443da0938857ba192016aa63434d9fc..2aaa1113ca6bc03677a1b770181cb10d8a4073db 100644 (file)
@@ -10,6 +10,7 @@ SOURCES       := \
        encode.c \
        fips186prf.c \
        id.c \
+       milenage.c \
        vector.c \
        xlat.c
 
diff --git a/src/modules/rlm_eap/lib/sim/milenage.c b/src/modules/rlm_eap/lib/sim/milenage.c
new file mode 100644 (file)
index 0000000..250ca65
--- /dev/null
@@ -0,0 +1,375 @@
+/**
+ * @file rlm_eap/lib/sim/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/tls_log.h>
+#include <freeradius-devel/proto.h>
+#include <openssl/evp.h>
+#include "milenage.h"
+
+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)) {
+               tls_strerror_printf(true, "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)) {
+               tls_strerror_printf(true, "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[8], uint8_t mac_s[8],
+                      uint8_t const opc[16], uint8_t const k[16], uint8_t const rand[16],
+                      uint8_t const sqn[6], uint8_t const amf[2])
+{
+       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(true, "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, NULL, 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] akstar  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[8], uint8_t ck[16], uint8_t ik[16], uint8_t ak[6], uint8_t akstar[6],
+                         uint8_t const opc[16], uint8_t const k[16], uint8_t const rand[16])
+{
+       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) {
+               tls_strerror_printf(true, "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, NULL, 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, NULL, 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, NULL, tmp1, ik) < 0) goto error;
+
+               for (i = 0; i < 16; i++) ik[i] ^= opc[i];
+       }
+
+       /* f5* */
+       if (akstar) {
+               /* 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++) akstar[i] = tmp1[i] ^ opc[i];
+       }
+       EVP_CIPHER_CTX_free(evp_ctx);
+
+       return 0;
+}
+
+/** milenage_generate - 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] res     Buffer for RES = 64-bit signed response (f2), or NULL.
+ * @param[in] res_len  Max length for res; set to used length or 0 on failure.
+ * @param[in] opc      128-bit operator variant algorithm configuration field (encr.).
+ * @param[in] amf      16-bit authentication management field.
+ * @param[in] k                128-bit subscriber key.
+ * @param[in] sqn      48-bit sequence number.
+ * @param[in] rand     128-bit random challenge.
+ */
+int milenage_umts_generate(uint8_t autn[16], uint8_t ik[16], uint8_t ck[16], uint8_t *res, size_t *res_len,
+                          uint8_t const opc[16], uint8_t const amf[2], uint8_t const k[16],
+                          uint8_t const sqn[6], uint8_t const rand[16])
+{
+       int     i;
+       uint8_t mac_a[8], ak[6];
+
+       *res_len = 0;
+
+       if (*res_len < 8) return - 1;
+       if ((milenage_f1(mac_a, NULL, opc, k, rand, sqn, amf) < 0) ||
+           (milenage_f2345(res, ck, ik, ak, NULL, opc, k, rand) < 0)) return -1;
+       *res_len = 8;
+
+       /* AUTN = (SQN ^ AK) || AMF || MAC */
+       for (i = 0; i < 6; i++) autn[i] = sqn[i] ^ ak[i];
+       memcpy(autn + 6, amf, 2);
+       memcpy(autn + 8, mac_a, 8);
+
+       return 0;
+}
+
+/** milenage_auts - Milenage AUTS validation
+ *
+ * @param[out] sqn     Buffer for SQN = 48-bit sequence number.
+ * @param[in] opc      128-bit operator variant algorithm configuration field (encr.).
+ * @param[in] k                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(uint8_t sqn[6],
+                 uint8_t const opc[16], uint8_t const k[16], uint8_t const rand[16], uint8_t const auts[14])
+{
+       uint8_t amf[2] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */
+       uint8_t ak[6], mac_s[8];
+       int i;
+
+       if (milenage_f2345(NULL, NULL, NULL, NULL, ak, opc, k, rand)) return -1;
+       for (i = 0; i < 6; i++) sqn[i] = auts[i] ^ ak[i];
+
+       if (milenage_f1(NULL, mac_s, opc, k, rand, sqn, amf) || CRYPTO_memcmp(mac_s, auts + 6, 8) != 0) return -1;
+       return 0;
+}
+
+/** gsm_milenage - 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] k                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[4], uint8_t kc[8],
+                         uint8_t const opc[16], uint8_t const k[16], uint8_t const rand[16])
+{
+       uint8_t res[8], ck[16], ik[16];
+       int i;
+
+       if (milenage_f2345(res, ck, ik, NULL, NULL, opc, k, rand)) return -1;
+
+       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 */
+
+       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] res_len  Variable that will be set to RES length.
+ * @param[in] auts     112-bit buffer for AUTS.
+ * @param[in] opc      128-bit operator variant algorithm configuration field (encr.).
+ * @param[in] k                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[16], uint8_t ck[16], uint8_t *res, size_t *res_len, uint8_t *auts,
+                  uint8_t const opc[16], uint8_t const k[16], uint8_t const sqn[6],
+                  uint8_t const rand[16], uint8_t const autn[16])
+{
+       int i;
+       uint8_t mac_a[8], ak[6], rx_sqn[6];
+       const uint8_t *amf;
+
+       FR_PROTO_HEX_DUMP("AUTN", autn, 16);
+       FR_PROTO_HEX_DUMP("RAND", rand, 16);
+
+       if (milenage_f2345(res, ck, ik, ak, NULL, opc, k, rand))
+               return -1;
+
+       *res_len = 8;
+       FR_PROTO_HEX_DUMP("RES", res, *res_len);
+       FR_PROTO_HEX_DUMP("CK", ck, 16);
+       FR_PROTO_HEX_DUMP("IK", ik, 16);
+       FR_PROTO_HEX_DUMP("AK", ak, 6);
+
+       /* AUTN = (SQN ^ AK) || AMF || MAC */
+       for (i = 0; i < 6; i++)
+               rx_sqn[i] = autn[i] ^ ak[i];
+       FR_PROTO_HEX_DUMP("SQN", rx_sqn, 6);
+
+       if (memcmp(rx_sqn, sqn, 6) <= 0) {
+               uint8_t auts_amf[2] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */
+
+               if (milenage_f2345(NULL, NULL, NULL, NULL, ak, opc, k, rand)) return -1;
+
+               FR_PROTO_HEX_DUMP("AK*", ak, 6);
+               for (i = 0; i < 6; i++) auts[i] = sqn[i] ^ ak[i];
+
+               if (milenage_f1(NULL, auts + 6, opc, k, rand, sqn, auts_amf) < 0) return -1;
+               FR_PROTO_HEX_DUMP("AUTS", auts, 14);
+               return -2;
+       }
+
+       amf = autn + 6;
+       FR_PROTO_HEX_DUMP("AMF", amf, 2);
+       if (milenage_f1(mac_a, NULL, opc, k, rand, rx_sqn, amf) < 0) return -1;
+
+       FR_PROTO_HEX_DUMP("MAC_A", mac_a, 8);
+
+       if (CRYPTO_memcmp(mac_a, autn + 8, 8) != 0) {
+               fr_strerror_printf("MAC mismatch");
+               FR_PROTO_HEX_DUMP("Received MAC_A", autn + 8, 8);
+               return -1;
+       }
+
+       return 0;
+}
diff --git a/src/modules/rlm_eap/lib/sim/milenage.h b/src/modules/rlm_eap/lib/sim/milenage.h
new file mode 100644 (file)
index 0000000..d3cacc1
--- /dev/null
@@ -0,0 +1,34 @@
+/**
+ * @file rlm_eap/lib/sim/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>
+
+int    milenage_umts_generate(uint8_t autn[16], uint8_t ik[16], uint8_t ck[16], uint8_t *res, size_t *res_len,
+                              uint8_t const opc[16], uint8_t const amf[2], uint8_t const k[16],
+                              uint8_t const sqn[6], uint8_t const rand[16]);
+
+int    milenage_auts(uint8_t sqn[6],
+                     uint8_t const opc[16], uint8_t const k[16], uint8_t const rand[16], uint8_t const auts[14]);
+
+int    milenage_gsm_generate(uint8_t sres[4], uint8_t kc[8],
+                             uint8_t const opc[16], uint8_t const k[16], uint8_t const rand[16]);
+
+int    milenage_check(uint8_t ik[16], uint8_t ck[16], uint8_t *res, size_t *res_len, uint8_t *auts,
+                      uint8_t const opc[16], uint8_t const k[16], uint8_t const sqn[6],
+                      uint8_t const rand[16], uint8_t const autn[16]);