1189251Ssam/*
2189251Ssam * EAP server/peer: EAP-GPSK shared routines
3189251Ssam * Copyright (c) 2006-2007, Jouni Malinen <j@w1.fi>
4189251Ssam *
5252726Srpaulo * This software may be distributed under the terms of the BSD license.
6252726Srpaulo * See README for more details.
7189251Ssam */
8189251Ssam
9189251Ssam#include "includes.h"
10189251Ssam
11189251Ssam#include "common.h"
12214734Srpaulo#include "crypto/aes_wrap.h"
13214734Srpaulo#include "crypto/sha256.h"
14189251Ssam#include "eap_defs.h"
15189251Ssam#include "eap_gpsk_common.h"
16189251Ssam
17189251Ssam
18189251Ssam/**
19189251Ssam * eap_gpsk_supported_ciphersuite - Check whether ciphersuite is supported
20189251Ssam * @vendor: CSuite/Vendor
21189251Ssam * @specifier: CSuite/Specifier
22189251Ssam * Returns: 1 if ciphersuite is support, or 0 if not
23189251Ssam */
24189251Ssamint eap_gpsk_supported_ciphersuite(int vendor, int specifier)
25189251Ssam{
26189251Ssam	if (vendor == EAP_GPSK_VENDOR_IETF &&
27189251Ssam	    specifier == EAP_GPSK_CIPHER_AES)
28189251Ssam		return 1;
29189251Ssam#ifdef EAP_GPSK_SHA256
30189251Ssam	if (vendor == EAP_GPSK_VENDOR_IETF &&
31189251Ssam	    specifier == EAP_GPSK_CIPHER_SHA256)
32189251Ssam		return 1;
33189251Ssam#endif /* EAP_GPSK_SHA256 */
34189251Ssam	return 0;
35189251Ssam}
36189251Ssam
37189251Ssam
38189251Ssamstatic int eap_gpsk_gkdf_cmac(const u8 *psk /* Y */,
39189251Ssam			      const u8 *data /* Z */, size_t data_len,
40189251Ssam			      u8 *buf, size_t len /* X */)
41189251Ssam{
42189251Ssam	u8 *opos;
43189251Ssam	size_t i, n, hashlen, left, clen;
44189251Ssam	u8 ibuf[2], hash[16];
45189251Ssam	const u8 *addr[2];
46189251Ssam	size_t vlen[2];
47189251Ssam
48189251Ssam	hashlen = sizeof(hash);
49189251Ssam	/* M_i = MAC_Y (i || Z); (MAC = AES-CMAC-128) */
50189251Ssam	addr[0] = ibuf;
51189251Ssam	vlen[0] = sizeof(ibuf);
52189251Ssam	addr[1] = data;
53189251Ssam	vlen[1] = data_len;
54189251Ssam
55189251Ssam	opos = buf;
56189251Ssam	left = len;
57189251Ssam	n = (len + hashlen - 1) / hashlen;
58189251Ssam	for (i = 1; i <= n; i++) {
59189251Ssam		WPA_PUT_BE16(ibuf, i);
60189251Ssam		if (omac1_aes_128_vector(psk, 2, addr, vlen, hash))
61189251Ssam			return -1;
62189251Ssam		clen = left > hashlen ? hashlen : left;
63189251Ssam		os_memcpy(opos, hash, clen);
64189251Ssam		opos += clen;
65189251Ssam		left -= clen;
66189251Ssam	}
67189251Ssam
68189251Ssam	return 0;
69189251Ssam}
70189251Ssam
71189251Ssam
72189251Ssam#ifdef EAP_GPSK_SHA256
73189251Ssamstatic int eap_gpsk_gkdf_sha256(const u8 *psk /* Y */,
74189251Ssam				const u8 *data /* Z */, size_t data_len,
75189251Ssam				u8 *buf, size_t len /* X */)
76189251Ssam{
77189251Ssam	u8 *opos;
78189251Ssam	size_t i, n, hashlen, left, clen;
79189251Ssam	u8 ibuf[2], hash[SHA256_MAC_LEN];
80189251Ssam	const u8 *addr[2];
81189251Ssam	size_t vlen[2];
82189251Ssam
83189251Ssam	hashlen = SHA256_MAC_LEN;
84189251Ssam	/* M_i = MAC_Y (i || Z); (MAC = HMAC-SHA256) */
85189251Ssam	addr[0] = ibuf;
86189251Ssam	vlen[0] = sizeof(ibuf);
87189251Ssam	addr[1] = data;
88189251Ssam	vlen[1] = data_len;
89189251Ssam
90189251Ssam	opos = buf;
91189251Ssam	left = len;
92189251Ssam	n = (len + hashlen - 1) / hashlen;
93189251Ssam	for (i = 1; i <= n; i++) {
94189251Ssam		WPA_PUT_BE16(ibuf, i);
95189251Ssam		hmac_sha256_vector(psk, 32, 2, addr, vlen, hash);
96189251Ssam		clen = left > hashlen ? hashlen : left;
97189251Ssam		os_memcpy(opos, hash, clen);
98189251Ssam		opos += clen;
99189251Ssam		left -= clen;
100189251Ssam	}
101189251Ssam
102189251Ssam	return 0;
103189251Ssam}
104189251Ssam#endif /* EAP_GPSK_SHA256 */
105189251Ssam
106189251Ssam
107189251Ssamstatic int eap_gpsk_derive_keys_helper(u32 csuite_specifier,
108189251Ssam				       u8 *kdf_out, size_t kdf_out_len,
109189251Ssam				       const u8 *psk, size_t psk_len,
110189251Ssam				       const u8 *seed, size_t seed_len,
111189251Ssam				       u8 *msk, u8 *emsk,
112189251Ssam				       u8 *sk, size_t sk_len,
113189251Ssam				       u8 *pk, size_t pk_len)
114189251Ssam{
115189251Ssam	u8 mk[32], *pos, *data;
116189251Ssam	size_t data_len, mk_len;
117189251Ssam	int (*gkdf)(const u8 *_psk, const u8 *_data, size_t _data_len,
118189251Ssam		    u8 *buf, size_t len);
119189251Ssam
120189251Ssam	gkdf = NULL;
121189251Ssam	switch (csuite_specifier) {
122189251Ssam	case EAP_GPSK_CIPHER_AES:
123189251Ssam		gkdf = eap_gpsk_gkdf_cmac;
124189251Ssam		mk_len = 16;
125189251Ssam		break;
126189251Ssam#ifdef EAP_GPSK_SHA256
127189251Ssam	case EAP_GPSK_CIPHER_SHA256:
128189251Ssam		gkdf = eap_gpsk_gkdf_sha256;
129189251Ssam		mk_len = SHA256_MAC_LEN;
130189251Ssam		break;
131189251Ssam#endif /* EAP_GPSK_SHA256 */
132189251Ssam	default:
133189251Ssam		return -1;
134189251Ssam	}
135189251Ssam
136189251Ssam	if (psk_len < mk_len)
137189251Ssam		return -1;
138189251Ssam
139189251Ssam	data_len = 2 + psk_len + 6 + seed_len;
140189251Ssam	data = os_malloc(data_len);
141189251Ssam	if (data == NULL)
142189251Ssam		return -1;
143189251Ssam	pos = data;
144189251Ssam	WPA_PUT_BE16(pos, psk_len);
145189251Ssam	pos += 2;
146189251Ssam	os_memcpy(pos, psk, psk_len);
147189251Ssam	pos += psk_len;
148189251Ssam	WPA_PUT_BE32(pos, EAP_GPSK_VENDOR_IETF); /* CSuite/Vendor = IETF */
149189251Ssam	pos += 4;
150189251Ssam	WPA_PUT_BE16(pos, csuite_specifier); /* CSuite/Specifier */
151189251Ssam	pos += 2;
152189251Ssam	os_memcpy(pos, seed, seed_len); /* inputString */
153189251Ssam	wpa_hexdump_key(MSG_DEBUG, "EAP-GPSK: Data to MK derivation",
154189251Ssam			data, data_len);
155189251Ssam
156189251Ssam	if (gkdf(psk, data, data_len, mk, mk_len) < 0) {
157189251Ssam		os_free(data);
158189251Ssam		return -1;
159189251Ssam	}
160189251Ssam	os_free(data);
161189251Ssam	wpa_hexdump_key(MSG_DEBUG, "EAP-GPSK: MK", mk, mk_len);
162189251Ssam
163189251Ssam	if (gkdf(mk, seed, seed_len, kdf_out, kdf_out_len) < 0)
164189251Ssam		return -1;
165189251Ssam
166189251Ssam	pos = kdf_out;
167189251Ssam	wpa_hexdump_key(MSG_DEBUG, "EAP-GPSK: MSK", pos, EAP_MSK_LEN);
168189251Ssam	os_memcpy(msk, pos, EAP_MSK_LEN);
169189251Ssam	pos += EAP_MSK_LEN;
170189251Ssam
171189251Ssam	wpa_hexdump_key(MSG_DEBUG, "EAP-GPSK: EMSK", pos, EAP_EMSK_LEN);
172189251Ssam	os_memcpy(emsk, pos, EAP_EMSK_LEN);
173189251Ssam	pos += EAP_EMSK_LEN;
174189251Ssam
175189251Ssam	wpa_hexdump_key(MSG_DEBUG, "EAP-GPSK: SK", pos, sk_len);
176189251Ssam	os_memcpy(sk, pos, sk_len);
177189251Ssam	pos += sk_len;
178189251Ssam
179189251Ssam	if (pk) {
180189251Ssam		wpa_hexdump_key(MSG_DEBUG, "EAP-GPSK: PK", pos, pk_len);
181189251Ssam		os_memcpy(pk, pos, pk_len);
182189251Ssam	}
183189251Ssam
184189251Ssam	return 0;
185189251Ssam}
186189251Ssam
187189251Ssam
188189251Ssamstatic int eap_gpsk_derive_keys_aes(const u8 *psk, size_t psk_len,
189189251Ssam				    const u8 *seed, size_t seed_len,
190189251Ssam				    u8 *msk, u8 *emsk, u8 *sk, size_t *sk_len,
191189251Ssam				    u8 *pk, size_t *pk_len)
192189251Ssam{
193189251Ssam#define EAP_GPSK_SK_LEN_AES 16
194189251Ssam#define EAP_GPSK_PK_LEN_AES 16
195189251Ssam	u8 kdf_out[EAP_MSK_LEN + EAP_EMSK_LEN + EAP_GPSK_SK_LEN_AES +
196189251Ssam		   EAP_GPSK_PK_LEN_AES];
197189251Ssam
198189251Ssam	/*
199189251Ssam	 * inputString = RAND_Peer || ID_Peer || RAND_Server || ID_Server
200189251Ssam	 *            (= seed)
201189251Ssam	 * KS = 16, PL = psk_len, CSuite_Sel = 0x00000000 0x0001
202189251Ssam	 * MK = GKDF-16 (PSK[0..15], PL || PSK || CSuite_Sel || inputString)
203189251Ssam	 * MSK = GKDF-160 (MK, inputString)[0..63]
204189251Ssam	 * EMSK = GKDF-160 (MK, inputString)[64..127]
205189251Ssam	 * SK = GKDF-160 (MK, inputString)[128..143]
206189251Ssam	 * PK = GKDF-160 (MK, inputString)[144..159]
207189251Ssam	 * zero = 0x00 || 0x00 || ... || 0x00 (16 times)
208189251Ssam	 * Method-ID = GKDF-16 (zero, "Method ID" || EAP_Method_Type ||
209189251Ssam	 *                      CSuite_Sel || inputString)
210189251Ssam	 */
211189251Ssam
212189251Ssam	*sk_len = EAP_GPSK_SK_LEN_AES;
213189251Ssam	*pk_len = EAP_GPSK_PK_LEN_AES;
214189251Ssam
215189251Ssam	return eap_gpsk_derive_keys_helper(EAP_GPSK_CIPHER_AES,
216189251Ssam					   kdf_out, sizeof(kdf_out),
217189251Ssam					   psk, psk_len, seed, seed_len,
218189251Ssam					   msk, emsk, sk, *sk_len,
219189251Ssam					   pk, *pk_len);
220189251Ssam}
221189251Ssam
222189251Ssam
223189251Ssam#ifdef EAP_GPSK_SHA256
224189251Ssamstatic int eap_gpsk_derive_keys_sha256(const u8 *psk, size_t psk_len,
225189251Ssam				       const u8 *seed, size_t seed_len,
226189251Ssam				       u8 *msk, u8 *emsk,
227189251Ssam				       u8 *sk, size_t *sk_len)
228189251Ssam{
229189251Ssam#define EAP_GPSK_SK_LEN_SHA256 SHA256_MAC_LEN
230189251Ssam#define EAP_GPSK_PK_LEN_SHA256 SHA256_MAC_LEN
231189251Ssam	u8 kdf_out[EAP_MSK_LEN + EAP_EMSK_LEN + EAP_GPSK_SK_LEN_SHA256 +
232189251Ssam		   EAP_GPSK_PK_LEN_SHA256];
233189251Ssam
234189251Ssam	/*
235189251Ssam	 * inputString = RAND_Peer || ID_Peer || RAND_Server || ID_Server
236189251Ssam	 *            (= seed)
237189251Ssam	 * KS = 32, PL = psk_len, CSuite_Sel = 0x00000000 0x0002
238189251Ssam	 * MK = GKDF-32 (PSK[0..31], PL || PSK || CSuite_Sel || inputString)
239189251Ssam	 * MSK = GKDF-160 (MK, inputString)[0..63]
240189251Ssam	 * EMSK = GKDF-160 (MK, inputString)[64..127]
241189251Ssam	 * SK = GKDF-160 (MK, inputString)[128..159]
242189251Ssam	 * zero = 0x00 || 0x00 || ... || 0x00 (32 times)
243189251Ssam	 * Method-ID = GKDF-16 (zero, "Method ID" || EAP_Method_Type ||
244189251Ssam	 *                      CSuite_Sel || inputString)
245189251Ssam	 */
246189251Ssam
247189251Ssam	*sk_len = EAP_GPSK_SK_LEN_SHA256;
248189251Ssam
249189251Ssam	return eap_gpsk_derive_keys_helper(EAP_GPSK_CIPHER_SHA256,
250189251Ssam					   kdf_out, sizeof(kdf_out),
251189251Ssam					   psk, psk_len, seed, seed_len,
252189251Ssam					   msk, emsk, sk, *sk_len,
253189251Ssam					   NULL, 0);
254189251Ssam}
255189251Ssam#endif /* EAP_GPSK_SHA256 */
256189251Ssam
257189251Ssam
258189251Ssam/**
259189251Ssam * eap_gpsk_derive_keys - Derive EAP-GPSK keys
260189251Ssam * @psk: Pre-shared key
261189251Ssam * @psk_len: Length of psk in bytes
262189251Ssam * @vendor: CSuite/Vendor
263189251Ssam * @specifier: CSuite/Specifier
264189251Ssam * @rand_peer: 32-byte RAND_Peer
265189251Ssam * @rand_server: 32-byte RAND_Server
266189251Ssam * @id_peer: ID_Peer
267189251Ssam * @id_peer_len: Length of ID_Peer
268189251Ssam * @id_server: ID_Server
269189251Ssam * @id_server_len: Length of ID_Server
270189251Ssam * @msk: Buffer for 64-byte MSK
271189251Ssam * @emsk: Buffer for 64-byte EMSK
272189251Ssam * @sk: Buffer for SK (at least EAP_GPSK_MAX_SK_LEN bytes)
273189251Ssam * @sk_len: Buffer for returning length of SK
274189251Ssam * @pk: Buffer for PK (at least EAP_GPSK_MAX_PK_LEN bytes)
275189251Ssam * @pk_len: Buffer for returning length of PK
276189251Ssam * Returns: 0 on success, -1 on failure
277189251Ssam */
278189251Ssamint eap_gpsk_derive_keys(const u8 *psk, size_t psk_len, int vendor,
279189251Ssam			 int specifier,
280189251Ssam			 const u8 *rand_peer, const u8 *rand_server,
281189251Ssam			 const u8 *id_peer, size_t id_peer_len,
282189251Ssam			 const u8 *id_server, size_t id_server_len,
283189251Ssam			 u8 *msk, u8 *emsk, u8 *sk, size_t *sk_len,
284189251Ssam			 u8 *pk, size_t *pk_len)
285189251Ssam{
286189251Ssam	u8 *seed, *pos;
287189251Ssam	size_t seed_len;
288189251Ssam	int ret;
289189251Ssam
290189251Ssam	wpa_printf(MSG_DEBUG, "EAP-GPSK: Deriving keys (%d:%d)",
291189251Ssam		   vendor, specifier);
292189251Ssam
293189251Ssam	if (vendor != EAP_GPSK_VENDOR_IETF)
294189251Ssam		return -1;
295189251Ssam
296189251Ssam	wpa_hexdump_key(MSG_DEBUG, "EAP-GPSK: PSK", psk, psk_len);
297189251Ssam
298189251Ssam	/* Seed = RAND_Peer || ID_Peer || RAND_Server || ID_Server */
299189251Ssam	seed_len = 2 * EAP_GPSK_RAND_LEN + id_server_len + id_peer_len;
300189251Ssam	seed = os_malloc(seed_len);
301189251Ssam	if (seed == NULL) {
302189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Failed to allocate memory "
303189251Ssam			   "for key derivation");
304189251Ssam		return -1;
305189251Ssam	}
306189251Ssam
307189251Ssam	pos = seed;
308189251Ssam	os_memcpy(pos, rand_peer, EAP_GPSK_RAND_LEN);
309189251Ssam	pos += EAP_GPSK_RAND_LEN;
310189251Ssam	os_memcpy(pos, id_peer, id_peer_len);
311189251Ssam	pos += id_peer_len;
312189251Ssam	os_memcpy(pos, rand_server, EAP_GPSK_RAND_LEN);
313189251Ssam	pos += EAP_GPSK_RAND_LEN;
314189251Ssam	os_memcpy(pos, id_server, id_server_len);
315189251Ssam	pos += id_server_len;
316189251Ssam	wpa_hexdump(MSG_DEBUG, "EAP-GPSK: Seed", seed, seed_len);
317189251Ssam
318189251Ssam	switch (specifier) {
319189251Ssam	case EAP_GPSK_CIPHER_AES:
320189251Ssam		ret = eap_gpsk_derive_keys_aes(psk, psk_len, seed, seed_len,
321189251Ssam					       msk, emsk, sk, sk_len,
322189251Ssam					       pk, pk_len);
323189251Ssam		break;
324189251Ssam#ifdef EAP_GPSK_SHA256
325189251Ssam	case EAP_GPSK_CIPHER_SHA256:
326189251Ssam		ret = eap_gpsk_derive_keys_sha256(psk, psk_len, seed, seed_len,
327189251Ssam						  msk, emsk, sk, sk_len);
328189251Ssam		break;
329189251Ssam#endif /* EAP_GPSK_SHA256 */
330189251Ssam	default:
331189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Unknown cipher %d:%d used in "
332189251Ssam			   "key derivation", vendor, specifier);
333189251Ssam		ret = -1;
334189251Ssam		break;
335189251Ssam	}
336189251Ssam
337189251Ssam	os_free(seed);
338189251Ssam
339189251Ssam	return ret;
340189251Ssam}
341189251Ssam
342189251Ssam
343189251Ssam/**
344189251Ssam * eap_gpsk_mic_len - Get the length of the MIC
345189251Ssam * @vendor: CSuite/Vendor
346189251Ssam * @specifier: CSuite/Specifier
347189251Ssam * Returns: MIC length in bytes
348189251Ssam */
349189251Ssamsize_t eap_gpsk_mic_len(int vendor, int specifier)
350189251Ssam{
351189251Ssam	if (vendor != EAP_GPSK_VENDOR_IETF)
352189251Ssam		return 0;
353189251Ssam
354189251Ssam	switch (specifier) {
355189251Ssam	case EAP_GPSK_CIPHER_AES:
356189251Ssam		return 16;
357189251Ssam#ifdef EAP_GPSK_SHA256
358189251Ssam	case EAP_GPSK_CIPHER_SHA256:
359189251Ssam		return 32;
360189251Ssam#endif /* EAP_GPSK_SHA256 */
361189251Ssam	default:
362189251Ssam		return 0;
363189251Ssam	}
364189251Ssam}
365189251Ssam
366189251Ssam
367189251Ssamstatic int eap_gpsk_compute_mic_aes(const u8 *sk, size_t sk_len,
368189251Ssam				    const u8 *data, size_t len, u8 *mic)
369189251Ssam{
370189251Ssam	if (sk_len != 16) {
371189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Invalid SK length %lu for "
372189251Ssam			   "AES-CMAC MIC", (unsigned long) sk_len);
373189251Ssam		return -1;
374189251Ssam	}
375189251Ssam
376189251Ssam	return omac1_aes_128(sk, data, len, mic);
377189251Ssam}
378189251Ssam
379189251Ssam
380189251Ssam/**
381189251Ssam * eap_gpsk_compute_mic - Compute EAP-GPSK MIC for an EAP packet
382189251Ssam * @sk: Session key SK from eap_gpsk_derive_keys()
383189251Ssam * @sk_len: SK length in bytes from eap_gpsk_derive_keys()
384189251Ssam * @vendor: CSuite/Vendor
385189251Ssam * @specifier: CSuite/Specifier
386189251Ssam * @data: Input data to MIC
387189251Ssam * @len: Input data length in bytes
388189251Ssam * @mic: Buffer for the computed MIC, eap_gpsk_mic_len(cipher) bytes
389189251Ssam * Returns: 0 on success, -1 on failure
390189251Ssam */
391189251Ssamint eap_gpsk_compute_mic(const u8 *sk, size_t sk_len, int vendor,
392189251Ssam			 int specifier, const u8 *data, size_t len, u8 *mic)
393189251Ssam{
394189251Ssam	int ret;
395189251Ssam
396189251Ssam	if (vendor != EAP_GPSK_VENDOR_IETF)
397189251Ssam		return -1;
398189251Ssam
399189251Ssam	switch (specifier) {
400189251Ssam	case EAP_GPSK_CIPHER_AES:
401189251Ssam		ret = eap_gpsk_compute_mic_aes(sk, sk_len, data, len, mic);
402189251Ssam		break;
403189251Ssam#ifdef EAP_GPSK_SHA256
404189251Ssam	case EAP_GPSK_CIPHER_SHA256:
405189251Ssam		hmac_sha256(sk, sk_len, data, len, mic);
406189251Ssam		ret = 0;
407189251Ssam		break;
408189251Ssam#endif /* EAP_GPSK_SHA256 */
409189251Ssam	default:
410189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Unknown cipher %d:%d used in "
411189251Ssam			   "MIC computation", vendor, specifier);
412189251Ssam		ret = -1;
413189251Ssam		break;
414189251Ssam	}
415189251Ssam
416189251Ssam	return ret;
417189251Ssam}
418