The FreeRADIUS server $Id: a4e7f8c94e8038b7627bcf52356b7ed15955c1d7 $
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: 6f6f817448e7e4eb9abf6cd11de0e1fd3b799108 $
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 * TEST_ASSERT() aborts through acutest_check_(), which the static
43 * analyser does not inline, so TEST_ASSERT_OR_RETURN() tests the
44 * condition in the test function and returns when the condition is false.
45 */
46#define TEST_ASSERT_OR_RETURN(_cond) \
47do { \
48 bool _ok = (_cond); \
49 TEST_CHECK_(_ok, "%s", #_cond); \
50 if (!_ok) return; \
51} while (0)
52
53/*
54 * Fixed inputs for the hostap cross-check vectors. If any of these
55 * change, the expected values below must be regenerated.
56 */
58 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
59 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
60};
62 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
63 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f
64};
66 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
67 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f
68};
69static char const test_id_s[] = "FreeRADIUS";
70static char const test_id_p[] = "bob@example.com";
71
72/*
73 * Expected values, computed by hostap (see the file header).
74 */
76 0x18, 0xb6, 0x2d, 0x2c, 0x84, 0xc5, 0xe4, 0x57,
77 0x1a, 0xfc, 0x41, 0xa2, 0x9d, 0xb7, 0x1f, 0x4d
78};
80 0x97, 0xb7, 0x04, 0x35, 0x00, 0x85, 0x02, 0x83,
81 0x63, 0x92, 0x46, 0x12, 0x56, 0x5b, 0x9b, 0x0d
82};
84 0x96, 0xa0, 0x0b, 0x88, 0x8b, 0x1f, 0x6b, 0x2f,
85 0x8e, 0x20, 0x1e, 0xe9, 0x6a, 0x22, 0x77, 0xf6
86};
88 0xe2, 0x82, 0xc7, 0xc3, 0x2f, 0x4a, 0x3b, 0x28,
89 0xf1, 0x8d, 0x75, 0x86, 0x0f, 0xa2, 0x67, 0x1c,
90 0x46, 0xd0, 0x71, 0xe2, 0xf7, 0xe5, 0x09, 0xce,
91 0x2e, 0x98, 0xae, 0x9a, 0xe3, 0x34, 0x99, 0x77,
92 0x05, 0xf9, 0x27, 0xd1, 0x84, 0xea, 0xd2, 0x71,
93 0xf9, 0xbc, 0xb1, 0xb7, 0xa1, 0xf9, 0x22, 0x30,
94 0xbf, 0x0d, 0xc9, 0x0d, 0x94, 0x61, 0x17, 0xaa,
95 0xe0, 0xd7, 0x84, 0xb8, 0x35, 0x59, 0x57, 0xd5
96};
98 0xd2, 0x0b, 0x6b, 0x75, 0x56, 0x33, 0x92, 0x00,
99 0xf0, 0x4e, 0x65, 0x30, 0xeb, 0x7d, 0x3c, 0x61,
100 0xa7, 0x81, 0x0b, 0x5c, 0xa9, 0x3f, 0x4c, 0x0b,
101 0xa8, 0x9e, 0xa5, 0xee, 0xdf, 0x63, 0x4a, 0x69,
102 0x79, 0x29, 0x20, 0xa6, 0xca, 0xea, 0x9d, 0x8e,
103 0x7b, 0x11, 0x2d, 0x63, 0x83, 0xdd, 0x85, 0x88,
104 0xb6, 0x8b, 0x74, 0xf6, 0xe7, 0xc2, 0xf8, 0xd5,
105 0x1a, 0xce, 0x97, 0x06, 0x71, 0x17, 0xac, 0x92
106};
108 0x97, 0xb0, 0xed, 0xe6, 0x89, 0xa3, 0xe4, 0x55,
109 0x3d, 0x8b, 0x30, 0x22, 0x58, 0xd1, 0x95, 0x12
110};
112 0x25, 0xa1, 0xd6, 0x46, 0x87, 0x9e, 0xef, 0x18,
113 0x7d, 0x1c, 0xc1, 0xff, 0x17, 0x7e, 0x26, 0x6d
114};
115
116/*
117 * Third/fourth message EAX header H (22 bytes), and the expected
118 * ciphertext/tag for the single protected byte 0x80 (R=DONE_SUCCESS).
119 * Message 3: Code=Request(1), Id=2, Length=59, Type=47, Flags=T2.
120 * Message 4: Code=Response(2), Id=2, Length=43, Type=47, Flags=T3.
121 */
122static uint8_t const expect_msg3_cipher = 0x8f;
124 0x47, 0xd6, 0x44, 0x26, 0x5c, 0xae, 0x5a, 0x68,
125 0x9a, 0x40, 0xa4, 0x7a, 0xc5, 0x4d, 0xdf, 0x80
126};
127static uint8_t const expect_msg4_cipher = 0xcc;
129 0x7a, 0x7a, 0x71, 0x52, 0x7b, 0x1f, 0x04, 0x7a,
130 0x77, 0xfb, 0xf0, 0xef, 0xa6, 0x73, 0xf7, 0x54
131};
132
133static void test_header(uint8_t header[EAP_PSK_HEADER_LEN], uint8_t code, uint16_t eap_len, uint8_t flags)
134{
135 header[0] = code;
136 header[1] = 0x02;
137 header[2] = (uint8_t) (eap_len >> 8);
138 header[3] = (uint8_t) (eap_len & 0xff);
139 header[4] = 0x2f; /* EAP Type 47, EAP-PSK */
140 header[5] = flags;
141 memcpy(header + 6, test_rand_s, EAP_PSK_RAND_LEN);
142}
143
144/** AES-CMAC vectors from RFC 4493 Section 4
145 *
146 */
147static void test_cmac_rfc4493(void)
148{
149 static uint8_t const key[16] = {
150 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
151 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c
152 };
153 static uint8_t const msg[64] = {
154 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96,
155 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
156 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c,
157 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
158 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11,
159 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef,
160 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17,
161 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10
162 };
163 static uint8_t const expect_m0[16] = {
164 0xbb, 0x1d, 0x69, 0x29, 0xe9, 0x59, 0x37, 0x28,
165 0x7f, 0xa3, 0x7d, 0x12, 0x9b, 0x75, 0x67, 0x46
166 };
167 static uint8_t const expect_m16[16] = {
168 0x07, 0x0a, 0x16, 0xb4, 0x6b, 0x4d, 0x41, 0x44,
169 0xf7, 0x9b, 0xdd, 0x9d, 0xd0, 0x4a, 0x28, 0x7c
170 };
171 static uint8_t const expect_m40[16] = {
172 0xdf, 0xa6, 0x67, 0x47, 0xde, 0x9a, 0xe6, 0x30,
173 0x30, 0xca, 0x32, 0x61, 0x14, 0x97, 0xc8, 0x27
174 };
175 static uint8_t const expect_m64[16] = {
176 0x51, 0xf0, 0xbe, 0xbf, 0x7e, 0x3b, 0x9d, 0x92,
177 0xfc, 0x49, 0x74, 0x17, 0x79, 0x36, 0x3c, 0xfe
178 };
179 uint8_t mac[16];
180
181 /* Example 1 - empty message */
182 TEST_ASSERT_OR_RETURN(eap_psk_cmac(mac, key, NULL, 0, NULL, 0, NULL, 0, NULL, 0) == 0);
183 TEST_CHECK(memcmp(mac, expect_m0, sizeof(mac)) == 0);
184
185 /* Example 2 - 16 bytes */
186 TEST_ASSERT_OR_RETURN(eap_psk_cmac(mac, key, msg, 16, NULL, 0, NULL, 0, NULL, 0) == 0);
187 TEST_CHECK(memcmp(mac, expect_m16, sizeof(mac)) == 0);
188
189 /* Example 3 - 40 bytes */
190 TEST_ASSERT_OR_RETURN(eap_psk_cmac(mac, key, msg, 40, NULL, 0, NULL, 0, NULL, 0) == 0);
191 TEST_CHECK(memcmp(mac, expect_m40, sizeof(mac)) == 0);
192
193 /* Example 3 again, split across segments - must match the one-shot value */
194 TEST_ASSERT_OR_RETURN(eap_psk_cmac(mac, key, msg, 16, msg + 16, 16, msg + 32, 8, NULL, 0) == 0);
195 TEST_CHECK(memcmp(mac, expect_m40, sizeof(mac)) == 0);
196
197 /* Example 4 - 64 bytes */
198 TEST_ASSERT_OR_RETURN(eap_psk_cmac(mac, key, msg, 64, NULL, 0, NULL, 0, NULL, 0) == 0);
199 TEST_CHECK(memcmp(mac, expect_m64, sizeof(mac)) == 0);
200}
201
202/** AK/KDK derivation matches hostap's implementation
203 *
204 */
205static void test_derive_ak_kdk(void)
206{
208
210 TEST_CHECK(memcmp(ak, expect_ak, sizeof(ak)) == 0);
211 TEST_CHECK(memcmp(kdk, expect_kdk, sizeof(kdk)) == 0);
212}
213
214/** TEK/MSK/EMSK derivation matches hostap's implementation
215 *
216 */
217static void test_derive_keys(void)
218{
220
222 TEST_CHECK(memcmp(tek, expect_tek, sizeof(tek)) == 0);
223 TEST_CHECK(memcmp(msk, expect_msk, sizeof(msk)) == 0);
224 TEST_CHECK(memcmp(emsk, expect_emsk, sizeof(emsk)) == 0);
225}
226
227/** MAC_P and MAC_S match hostap's implementation
228 *
229 */
230static void test_macs(void)
231{
233
235 (uint8_t const *) test_id_p, strlen(test_id_p),
236 (uint8_t const *) test_id_s, strlen(test_id_s),
237 test_rand_s, test_rand_p) == 0);
238 TEST_CHECK(memcmp(mac_p, expect_mac_p, sizeof(mac_p)) == 0);
239
241 (uint8_t const *) test_id_s, strlen(test_id_s),
242 test_rand_p) == 0);
243 TEST_CHECK(memcmp(mac_s, expect_mac_s, sizeof(mac_s)) == 0);
244}
245
246/** Protected channel encryption matches hostap, for both message directions
247 *
248 */
249static void test_pchannel_encrypt(void)
250{
252 uint8_t plain = 0x80; /* R = DONE_SUCCESS, E = 0 */
253 uint8_t cipher = 0;
255
256 test_header(header, 0x01, 59, 0x80);
258 header, sizeof(header), &plain, 1) == 0);
260 TEST_CHECK(memcmp(tag, expect_msg3_tag, sizeof(tag)) == 0);
261
262 test_header(header, 0x02, 43, 0xc0);
264 header, sizeof(header), &plain, 1) == 0);
266 TEST_CHECK(memcmp(tag, expect_msg4_tag, sizeof(tag)) == 0);
267}
268
269/** Protected channel decryption round-trips, and rejects tampering
270 *
271 */
272static void test_pchannel_decrypt(void)
273{
275 uint8_t plain = 0;
276 uint8_t bad_tag[EAP_PSK_TAG_LEN];
277 uint8_t bad_cipher;
278
279 test_header(header, 0x02, 43, 0xc0);
280
281 /* The hostap-computed message 4 ciphertext decrypts to R = DONE_SUCCESS */
283 header, sizeof(header),
285 TEST_CHECK(plain == 0x80);
286
287 /* A tampered tag must be rejected */
288 memcpy(bad_tag, expect_msg4_tag, sizeof(bad_tag));
289 bad_tag[0] ^= 0x01;
291 header, sizeof(header),
292 &expect_msg4_cipher, 1, bad_tag) < 0);
293
294 /* A tampered ciphertext must be rejected */
295 bad_cipher = expect_msg4_cipher ^ 0x01;
297 header, sizeof(header),
298 &bad_cipher, 1, expect_msg4_tag) < 0);
299
300 /* The wrong nonce must be rejected */
302 header, sizeof(header),
304
305 /* A tampered header must be rejected */
306 header[1] ^= 0x01;
308 header, sizeof(header),
310}
311
313 { "cmac_rfc4493", test_cmac_rfc4493 },
314 { "derive_ak_kdk", test_derive_ak_kdk },
315 { "derive_keys", test_derive_keys },
316 { "macs", test_macs },
317 { "pchannel_encrypt", test_pchannel_encrypt },
318 { "pchannel_decrypt", test_pchannel_decrypt },
319 { NULL, NULL }
320};
#define TEST_CHECK(cond)
Definition acutest.h:101
log_entry msg
Definition acutest.h:947
#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]
#define TEST_ASSERT_OR_RETURN(_cond)
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