The FreeRADIUS server $Id: f3670dba8951ca10eb4948feb3dc3db9423a334f $
Loading...
Searching...
No Matches
eap_psk_tests.c
Go to the documentation of this file.
1/*
2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/**
18 * $Id: f520d45e479c01d88127446790469c8bc568739d $
19 *
20 * @file eap_psk_tests.c
21 * @brief Known-answer tests for the EAP-PSK (RFC 4764) cryptographic primitives
22 *
23 * RFC 4764 publishes no test vectors, so verification is done in two layers:
24 *
25 * 1. The CMAC core is checked against the official AES-CMAC vectors from
26 * RFC 4493 Section 4.
27 *
28 * 2. The EAP-PSK derivations (AK/KDK, TEK/MSK/EMSK, MAC_P, MAC_S, and the
29 * EAX protected channel) are checked against known-answer values
30 * computed with hostap's INDEPENDENT implementation
31 * (src/eap_common/eap_psk_common.c, src/crypto/aes-omac1.c and
32 * aes-eax.c, hostap commit f541de4), for the fixed inputs documented
33 * below.
34 *
35 * @copyright 2026 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
36 */
37#include <freeradius-devel/util/test/acutest.h>
38
39#include "crypto.c"
40
41/*
42 * Fixed inputs for the hostap cross-check vectors. If any of these
43 * change, the expected values below must be regenerated.
44 */
46 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
47 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
48};
50 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
51 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f
52};
54 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
55 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f
56};
57static char const test_id_s[] = "FreeRADIUS";
58static char const test_id_p[] = "bob@example.com";
59
60/*
61 * Expected values, computed by hostap (see the file header).
62 */
64 0x18, 0xb6, 0x2d, 0x2c, 0x84, 0xc5, 0xe4, 0x57,
65 0x1a, 0xfc, 0x41, 0xa2, 0x9d, 0xb7, 0x1f, 0x4d
66};
68 0x97, 0xb7, 0x04, 0x35, 0x00, 0x85, 0x02, 0x83,
69 0x63, 0x92, 0x46, 0x12, 0x56, 0x5b, 0x9b, 0x0d
70};
72 0x96, 0xa0, 0x0b, 0x88, 0x8b, 0x1f, 0x6b, 0x2f,
73 0x8e, 0x20, 0x1e, 0xe9, 0x6a, 0x22, 0x77, 0xf6
74};
76 0xe2, 0x82, 0xc7, 0xc3, 0x2f, 0x4a, 0x3b, 0x28,
77 0xf1, 0x8d, 0x75, 0x86, 0x0f, 0xa2, 0x67, 0x1c,
78 0x46, 0xd0, 0x71, 0xe2, 0xf7, 0xe5, 0x09, 0xce,
79 0x2e, 0x98, 0xae, 0x9a, 0xe3, 0x34, 0x99, 0x77,
80 0x05, 0xf9, 0x27, 0xd1, 0x84, 0xea, 0xd2, 0x71,
81 0xf9, 0xbc, 0xb1, 0xb7, 0xa1, 0xf9, 0x22, 0x30,
82 0xbf, 0x0d, 0xc9, 0x0d, 0x94, 0x61, 0x17, 0xaa,
83 0xe0, 0xd7, 0x84, 0xb8, 0x35, 0x59, 0x57, 0xd5
84};
86 0xd2, 0x0b, 0x6b, 0x75, 0x56, 0x33, 0x92, 0x00,
87 0xf0, 0x4e, 0x65, 0x30, 0xeb, 0x7d, 0x3c, 0x61,
88 0xa7, 0x81, 0x0b, 0x5c, 0xa9, 0x3f, 0x4c, 0x0b,
89 0xa8, 0x9e, 0xa5, 0xee, 0xdf, 0x63, 0x4a, 0x69,
90 0x79, 0x29, 0x20, 0xa6, 0xca, 0xea, 0x9d, 0x8e,
91 0x7b, 0x11, 0x2d, 0x63, 0x83, 0xdd, 0x85, 0x88,
92 0xb6, 0x8b, 0x74, 0xf6, 0xe7, 0xc2, 0xf8, 0xd5,
93 0x1a, 0xce, 0x97, 0x06, 0x71, 0x17, 0xac, 0x92
94};
96 0x97, 0xb0, 0xed, 0xe6, 0x89, 0xa3, 0xe4, 0x55,
97 0x3d, 0x8b, 0x30, 0x22, 0x58, 0xd1, 0x95, 0x12
98};
100 0x25, 0xa1, 0xd6, 0x46, 0x87, 0x9e, 0xef, 0x18,
101 0x7d, 0x1c, 0xc1, 0xff, 0x17, 0x7e, 0x26, 0x6d
102};
103
104/*
105 * Third/fourth message EAX header H (22 bytes), and the expected
106 * ciphertext/tag for the single protected byte 0x80 (R=DONE_SUCCESS).
107 * Message 3: Code=Request(1), Id=2, Length=59, Type=47, Flags=T2.
108 * Message 4: Code=Response(2), Id=2, Length=43, Type=47, Flags=T3.
109 */
110static uint8_t const expect_msg3_cipher = 0x8f;
112 0x47, 0xd6, 0x44, 0x26, 0x5c, 0xae, 0x5a, 0x68,
113 0x9a, 0x40, 0xa4, 0x7a, 0xc5, 0x4d, 0xdf, 0x80
114};
115static uint8_t const expect_msg4_cipher = 0xcc;
117 0x7a, 0x7a, 0x71, 0x52, 0x7b, 0x1f, 0x04, 0x7a,
118 0x77, 0xfb, 0xf0, 0xef, 0xa6, 0x73, 0xf7, 0x54
119};
120
121static void test_header(uint8_t header[EAP_PSK_HEADER_LEN], uint8_t code, uint16_t eap_len, uint8_t flags)
122{
123 header[0] = code;
124 header[1] = 0x02;
125 header[2] = (uint8_t) (eap_len >> 8);
126 header[3] = (uint8_t) (eap_len & 0xff);
127 header[4] = 0x2f; /* EAP Type 47, EAP-PSK */
128 header[5] = flags;
129 memcpy(header + 6, test_rand_s, EAP_PSK_RAND_LEN);
130}
131
132/** AES-CMAC vectors from RFC 4493 Section 4
133 *
134 */
135static void test_cmac_rfc4493(void)
136{
137 static uint8_t const key[16] = {
138 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
139 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c
140 };
141 static uint8_t const msg[64] = {
142 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96,
143 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
144 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c,
145 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
146 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11,
147 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef,
148 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17,
149 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10
150 };
151 static uint8_t const expect_m0[16] = {
152 0xbb, 0x1d, 0x69, 0x29, 0xe9, 0x59, 0x37, 0x28,
153 0x7f, 0xa3, 0x7d, 0x12, 0x9b, 0x75, 0x67, 0x46
154 };
155 static uint8_t const expect_m16[16] = {
156 0x07, 0x0a, 0x16, 0xb4, 0x6b, 0x4d, 0x41, 0x44,
157 0xf7, 0x9b, 0xdd, 0x9d, 0xd0, 0x4a, 0x28, 0x7c
158 };
159 static uint8_t const expect_m40[16] = {
160 0xdf, 0xa6, 0x67, 0x47, 0xde, 0x9a, 0xe6, 0x30,
161 0x30, 0xca, 0x32, 0x61, 0x14, 0x97, 0xc8, 0x27
162 };
163 static uint8_t const expect_m64[16] = {
164 0x51, 0xf0, 0xbe, 0xbf, 0x7e, 0x3b, 0x9d, 0x92,
165 0xfc, 0x49, 0x74, 0x17, 0x79, 0x36, 0x3c, 0xfe
166 };
167 uint8_t mac[16];
168
169 /* Example 1 - empty message */
170 TEST_ASSERT(eap_psk_cmac(mac, key, NULL, 0, NULL, 0, NULL, 0, NULL, 0) == 0);
171 TEST_CHECK(memcmp(mac, expect_m0, sizeof(mac)) == 0);
172
173 /* Example 2 - 16 bytes */
174 TEST_ASSERT(eap_psk_cmac(mac, key, msg, 16, NULL, 0, NULL, 0, NULL, 0) == 0);
175 TEST_CHECK(memcmp(mac, expect_m16, sizeof(mac)) == 0);
176
177 /* Example 3 - 40 bytes */
178 TEST_ASSERT(eap_psk_cmac(mac, key, msg, 40, NULL, 0, NULL, 0, NULL, 0) == 0);
179 TEST_CHECK(memcmp(mac, expect_m40, sizeof(mac)) == 0);
180
181 /* Example 3 again, split across segments - must match the one-shot value */
182 TEST_ASSERT(eap_psk_cmac(mac, key, msg, 16, msg + 16, 16, msg + 32, 8, NULL, 0) == 0);
183 TEST_CHECK(memcmp(mac, expect_m40, sizeof(mac)) == 0);
184
185 /* Example 4 - 64 bytes */
186 TEST_ASSERT(eap_psk_cmac(mac, key, msg, 64, NULL, 0, NULL, 0, NULL, 0) == 0);
187 TEST_CHECK(memcmp(mac, expect_m64, sizeof(mac)) == 0);
188}
189
190/** AK/KDK derivation matches hostap's implementation
191 *
192 */
193static void test_derive_ak_kdk(void)
194{
196
198 TEST_CHECK(memcmp(ak, expect_ak, sizeof(ak)) == 0);
199 TEST_CHECK(memcmp(kdk, expect_kdk, sizeof(kdk)) == 0);
200}
201
202/** TEK/MSK/EMSK derivation matches hostap's implementation
203 *
204 */
205static void test_derive_keys(void)
206{
208
210 TEST_CHECK(memcmp(tek, expect_tek, sizeof(tek)) == 0);
211 TEST_CHECK(memcmp(msk, expect_msk, sizeof(msk)) == 0);
212 TEST_CHECK(memcmp(emsk, expect_emsk, sizeof(emsk)) == 0);
213}
214
215/** MAC_P and MAC_S match hostap's implementation
216 *
217 */
218static void test_macs(void)
219{
221
223 (uint8_t const *) test_id_p, strlen(test_id_p),
224 (uint8_t const *) test_id_s, strlen(test_id_s),
225 test_rand_s, test_rand_p) == 0);
226 TEST_CHECK(memcmp(mac_p, expect_mac_p, sizeof(mac_p)) == 0);
227
229 (uint8_t const *) test_id_s, strlen(test_id_s),
230 test_rand_p) == 0);
231 TEST_CHECK(memcmp(mac_s, expect_mac_s, sizeof(mac_s)) == 0);
232}
233
234/** Protected channel encryption matches hostap, for both message directions
235 *
236 */
237static void test_pchannel_encrypt(void)
238{
240 uint8_t plain = 0x80; /* R = DONE_SUCCESS, E = 0 */
241 uint8_t cipher = 0;
243
244 test_header(header, 0x01, 59, 0x80);
246 header, sizeof(header), &plain, 1) == 0);
248 TEST_CHECK(memcmp(tag, expect_msg3_tag, sizeof(tag)) == 0);
249
250 test_header(header, 0x02, 43, 0xc0);
252 header, sizeof(header), &plain, 1) == 0);
254 TEST_CHECK(memcmp(tag, expect_msg4_tag, sizeof(tag)) == 0);
255}
256
257/** Protected channel decryption round-trips, and rejects tampering
258 *
259 */
260static void test_pchannel_decrypt(void)
261{
263 uint8_t plain = 0;
264 uint8_t bad_tag[EAP_PSK_TAG_LEN];
265 uint8_t bad_cipher;
266
267 test_header(header, 0x02, 43, 0xc0);
268
269 /* The hostap-computed message 4 ciphertext decrypts to R = DONE_SUCCESS */
271 header, sizeof(header),
273 TEST_CHECK(plain == 0x80);
274
275 /* A tampered tag must be rejected */
276 memcpy(bad_tag, expect_msg4_tag, sizeof(bad_tag));
277 bad_tag[0] ^= 0x01;
279 header, sizeof(header),
280 &expect_msg4_cipher, 1, bad_tag) < 0);
281
282 /* A tampered ciphertext must be rejected */
283 bad_cipher = expect_msg4_cipher ^ 0x01;
285 header, sizeof(header),
286 &bad_cipher, 1, expect_msg4_tag) < 0);
287
288 /* The wrong nonce must be rejected */
290 header, sizeof(header),
292
293 /* A tampered header must be rejected */
294 header[1] ^= 0x01;
296 header, sizeof(header),
298}
299
301 { "cmac_rfc4493", test_cmac_rfc4493 },
302 { "derive_ak_kdk", test_derive_ak_kdk },
303 { "derive_keys", test_derive_keys },
304 { "macs", test_macs },
305 { "pchannel_encrypt", test_pchannel_encrypt },
306 { "pchannel_decrypt", test_pchannel_decrypt },
307 { NULL, NULL }
308};
#define TEST_CHECK(cond)
Definition acutest.h:87
#define TEST_ASSERT(cond)
Definition acutest.h:110
log_entry msg
Definition acutest.h:794
#define EAP_PSK_TAG_LEN
Definition crypto.h:43
#define EAP_PSK_MAC_LEN
Definition crypto.h:35
#define EAP_PSK_TEK_LEN
Definition crypto.h:39
#define EAP_PSK_PSK_LEN
Definition crypto.h:36
#define EAP_PSK_HEADER_LEN
Definition crypto.h:78
#define EAP_PSK_AK_LEN
Definition crypto.h:37
#define EAP_PSK_EMSK_LEN
Definition crypto.h:41
#define EAP_PSK_MSK_LEN
Definition crypto.h:40
#define EAP_PSK_RAND_LEN
Definition crypto.h:34
#define EAP_PSK_KDK_LEN
Definition crypto.h:38
static uint8_t const test_rand_s[EAP_PSK_RAND_LEN]
TEST_LIST
static uint8_t const expect_mac_p[EAP_PSK_MAC_LEN]
static char const test_id_p[]
static uint8_t const expect_msg4_cipher
static uint8_t const expect_msg3_cipher
static uint8_t const expect_tek[EAP_PSK_TEK_LEN]
static uint8_t const test_psk[EAP_PSK_PSK_LEN]
static uint8_t const expect_msg3_tag[EAP_PSK_TAG_LEN]
static uint8_t const expect_msk[EAP_PSK_MSK_LEN]
static uint8_t const expect_mac_s[EAP_PSK_MAC_LEN]
static void test_macs(void)
MAC_P and MAC_S match hostap's implementation.
static void test_header(uint8_t header[EAP_PSK_HEADER_LEN], uint8_t code, uint16_t eap_len, uint8_t flags)
static uint8_t const test_rand_p[EAP_PSK_RAND_LEN]
static void test_derive_ak_kdk(void)
AK/KDK derivation matches hostap's implementation.
static void test_cmac_rfc4493(void)
AES-CMAC vectors from RFC 4493 Section 4.
static uint8_t const expect_ak[EAP_PSK_AK_LEN]
static char const test_id_s[]
static void test_pchannel_decrypt(void)
Protected channel decryption round-trips, and rejects tampering.
static uint8_t const expect_msg4_tag[EAP_PSK_TAG_LEN]
static void test_pchannel_encrypt(void)
Protected channel encryption matches hostap, for both message directions.
static uint8_t const expect_kdk[EAP_PSK_KDK_LEN]
static uint8_t const expect_emsk[EAP_PSK_EMSK_LEN]
static void test_derive_keys(void)
TEK/MSK/EMSK derivation matches hostap's implementation.
unsigned short uint16_t
unsigned char uint8_t
The cryptographic primitives of EAP-PSK (RFC 4764).
int eap_psk_derive_keys(uint8_t tek[static EAP_PSK_TEK_LEN], uint8_t msk[static EAP_PSK_MSK_LEN], uint8_t emsk[static EAP_PSK_EMSK_LEN], uint8_t const kdk[static EAP_PSK_KDK_LEN], uint8_t const rand_p[static EAP_PSK_RAND_LEN])
Session-key derivation: expand RAND_P under KDK into nine output blocks.
Definition crypto.c:194
int eap_psk_mac_s(uint8_t mac_s[static EAP_PSK_MAC_LEN], uint8_t const ak[static EAP_PSK_AK_LEN], uint8_t const *id_s, size_t id_s_len, uint8_t const rand_p[static EAP_PSK_RAND_LEN])
Compute MAC_S = CMAC-AES-128(AK, ID_S || RAND_P)
Definition crypto.c:311
int eap_psk_pchannel_encrypt(uint8_t *cipher, uint8_t tag[static EAP_PSK_TAG_LEN], uint8_t const tek[static EAP_PSK_TEK_LEN], uint32_t nonce, uint8_t const *header, size_t header_len, uint8_t const *plain, size_t plain_len)
EAX encrypt for the protected channel (RFC 4764 Section 3.3)
Definition crypto.c:395
int eap_psk_mac_p(uint8_t mac_p[static EAP_PSK_MAC_LEN], uint8_t const ak[static EAP_PSK_AK_LEN], uint8_t const *id_p, size_t id_p_len, uint8_t const *id_s, size_t id_s_len, uint8_t const rand_s[static EAP_PSK_RAND_LEN], uint8_t const rand_p[static EAP_PSK_RAND_LEN])
Compute MAC_P = CMAC-AES-128(AK, ID_P || ID_S || RAND_S || RAND_P)
Definition crypto.c:283
int eap_psk_derive_ak_kdk(uint8_t ak[static EAP_PSK_AK_LEN], uint8_t kdk[static EAP_PSK_KDK_LEN], uint8_t const psk[static EAP_PSK_PSK_LEN])
Key setup: derive AK (counter 1) and KDK (counter 2) from the PSK.
Definition crypto.c:164
static int eap_psk_cmac(uint8_t mac[static 16], uint8_t const key[static 16], uint8_t const *seg1, size_t len1, uint8_t const *seg2, size_t len2, uint8_t const *seg3, size_t len3, uint8_t const *seg4, size_t len4)
AES-128 CMAC over one or more concatenated buffers.
Definition crypto.c:235
int eap_psk_pchannel_decrypt(uint8_t *plain, uint8_t const tek[static EAP_PSK_TEK_LEN], uint32_t nonce, uint8_t const *header, size_t header_len, uint8_t const *cipher, size_t cipher_len, uint8_t const tag[static EAP_PSK_TAG_LEN])
EAX decrypt-and-verify for the protected channel (RFC 4764 Section 3.3)
Definition crypto.c:439