1189251Ssam/*
2189251Ssam * EAP server/peer: EAP-SAKE 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"
12189251Ssam#include "wpabuf.h"
13214734Srpaulo#include "crypto/sha1.h"
14189251Ssam#include "eap_defs.h"
15189251Ssam#include "eap_sake_common.h"
16189251Ssam
17189251Ssam
18189251Ssamstatic int eap_sake_parse_add_attr(struct eap_sake_parse_attr *attr,
19189251Ssam				   const u8 *pos)
20189251Ssam{
21189251Ssam	size_t i;
22189251Ssam
23189251Ssam	switch (pos[0]) {
24189251Ssam	case EAP_SAKE_AT_RAND_S:
25189251Ssam		wpa_printf(MSG_DEBUG, "EAP-SAKE: Parse: AT_RAND_S");
26189251Ssam		if (pos[1] != 2 + EAP_SAKE_RAND_LEN) {
27189251Ssam			wpa_printf(MSG_DEBUG, "EAP-SAKE: AT_RAND_S with "
28189251Ssam				   "invalid length %d", pos[1]);
29189251Ssam			return -1;
30189251Ssam		}
31189251Ssam		attr->rand_s = pos + 2;
32189251Ssam		break;
33189251Ssam	case EAP_SAKE_AT_RAND_P:
34189251Ssam		wpa_printf(MSG_DEBUG, "EAP-SAKE: Parse: AT_RAND_P");
35189251Ssam		if (pos[1] != 2 + EAP_SAKE_RAND_LEN) {
36189251Ssam			wpa_printf(MSG_DEBUG, "EAP-SAKE: AT_RAND_P with "
37189251Ssam				   "invalid length %d", pos[1]);
38189251Ssam			return -1;
39189251Ssam		}
40189251Ssam		attr->rand_p = pos + 2;
41189251Ssam		break;
42189251Ssam	case EAP_SAKE_AT_MIC_S:
43189251Ssam		wpa_printf(MSG_DEBUG, "EAP-SAKE: Parse: AT_MIC_S");
44189251Ssam		if (pos[1] != 2 + EAP_SAKE_MIC_LEN) {
45189251Ssam			wpa_printf(MSG_DEBUG, "EAP-SAKE: AT_MIC_S with "
46189251Ssam				   "invalid length %d", pos[1]);
47189251Ssam			return -1;
48189251Ssam		}
49189251Ssam		attr->mic_s = pos + 2;
50189251Ssam		break;
51189251Ssam	case EAP_SAKE_AT_MIC_P:
52189251Ssam		wpa_printf(MSG_DEBUG, "EAP-SAKE: Parse: AT_MIC_P");
53189251Ssam		if (pos[1] != 2 + EAP_SAKE_MIC_LEN) {
54189251Ssam			wpa_printf(MSG_DEBUG, "EAP-SAKE: AT_MIC_P with "
55189251Ssam				   "invalid length %d", pos[1]);
56189251Ssam			return -1;
57189251Ssam		}
58189251Ssam		attr->mic_p = pos + 2;
59189251Ssam		break;
60189251Ssam	case EAP_SAKE_AT_SERVERID:
61189251Ssam		wpa_printf(MSG_DEBUG, "EAP-SAKE: Parse: AT_SERVERID");
62189251Ssam		attr->serverid = pos + 2;
63189251Ssam		attr->serverid_len = pos[1] - 2;
64189251Ssam		break;
65189251Ssam	case EAP_SAKE_AT_PEERID:
66189251Ssam		wpa_printf(MSG_DEBUG, "EAP-SAKE: Parse: AT_PEERID");
67189251Ssam		attr->peerid = pos + 2;
68189251Ssam		attr->peerid_len = pos[1] - 2;
69189251Ssam		break;
70189251Ssam	case EAP_SAKE_AT_SPI_S:
71189251Ssam		wpa_printf(MSG_DEBUG, "EAP-SAKE: Parse: AT_SPI_S");
72189251Ssam		attr->spi_s = pos + 2;
73189251Ssam		attr->spi_s_len = pos[1] - 2;
74189251Ssam		break;
75189251Ssam	case EAP_SAKE_AT_SPI_P:
76189251Ssam		wpa_printf(MSG_DEBUG, "EAP-SAKE: Parse: AT_SPI_P");
77189251Ssam		attr->spi_p = pos + 2;
78189251Ssam		attr->spi_p_len = pos[1] - 2;
79189251Ssam		break;
80189251Ssam	case EAP_SAKE_AT_ANY_ID_REQ:
81189251Ssam		wpa_printf(MSG_DEBUG, "EAP-SAKE: Parse: AT_ANY_ID_REQ");
82189251Ssam		if (pos[1] != 4) {
83189251Ssam			wpa_printf(MSG_DEBUG, "EAP-SAKE: Invalid AT_ANY_ID_REQ"
84189251Ssam				   " length %d", pos[1]);
85189251Ssam			return -1;
86189251Ssam		}
87189251Ssam		attr->any_id_req = pos + 2;
88189251Ssam		break;
89189251Ssam	case EAP_SAKE_AT_PERM_ID_REQ:
90189251Ssam		wpa_printf(MSG_DEBUG, "EAP-SAKE: Parse: AT_PERM_ID_REQ");
91189251Ssam		if (pos[1] != 4) {
92189251Ssam			wpa_printf(MSG_DEBUG, "EAP-SAKE: Invalid "
93189251Ssam				   "AT_PERM_ID_REQ length %d", pos[1]);
94189251Ssam			return -1;
95189251Ssam		}
96189251Ssam		attr->perm_id_req = pos + 2;
97189251Ssam		break;
98189251Ssam	case EAP_SAKE_AT_ENCR_DATA:
99189251Ssam		wpa_printf(MSG_DEBUG, "EAP-SAKE: Parse: AT_ENCR_DATA");
100189251Ssam		attr->encr_data = pos + 2;
101189251Ssam		attr->encr_data_len = pos[1] - 2;
102189251Ssam		break;
103189251Ssam	case EAP_SAKE_AT_IV:
104189251Ssam		wpa_printf(MSG_DEBUG, "EAP-SAKE: Parse: AT_IV");
105189251Ssam		attr->iv = pos + 2;
106189251Ssam		attr->iv_len = pos[1] - 2;
107189251Ssam		break;
108189251Ssam	case EAP_SAKE_AT_PADDING:
109189251Ssam		wpa_printf(MSG_DEBUG, "EAP-SAKE: Parse: AT_PADDING");
110189251Ssam		for (i = 2; i < pos[1]; i++) {
111189251Ssam			if (pos[i]) {
112189251Ssam				wpa_printf(MSG_DEBUG, "EAP-SAKE: AT_PADDING "
113189251Ssam					   "with non-zero pad byte");
114189251Ssam				return -1;
115189251Ssam			}
116189251Ssam		}
117189251Ssam		break;
118189251Ssam	case EAP_SAKE_AT_NEXT_TMPID:
119189251Ssam		wpa_printf(MSG_DEBUG, "EAP-SAKE: Parse: AT_NEXT_TMPID");
120189251Ssam		attr->next_tmpid = pos + 2;
121189251Ssam		attr->next_tmpid_len = pos[1] - 2;
122189251Ssam		break;
123189251Ssam	case EAP_SAKE_AT_MSK_LIFE:
124189251Ssam		wpa_printf(MSG_DEBUG, "EAP-SAKE: Parse: AT_IV");
125189251Ssam		if (pos[1] != 6) {
126189251Ssam			wpa_printf(MSG_DEBUG, "EAP-SAKE: Invalid "
127189251Ssam				   "AT_MSK_LIFE length %d", pos[1]);
128189251Ssam			return -1;
129189251Ssam		}
130189251Ssam		attr->msk_life = pos + 2;
131189251Ssam		break;
132189251Ssam	default:
133189251Ssam		if (pos[0] < 128) {
134189251Ssam			wpa_printf(MSG_DEBUG, "EAP-SAKE: Unknown non-skippable"
135189251Ssam				   " attribute %d", pos[0]);
136189251Ssam			return -1;
137189251Ssam		}
138189251Ssam		wpa_printf(MSG_DEBUG, "EAP-SAKE: Ignoring unknown skippable "
139189251Ssam			   "attribute %d", pos[0]);
140189251Ssam		break;
141189251Ssam	}
142189251Ssam
143189251Ssam	if (attr->iv && !attr->encr_data) {
144189251Ssam		wpa_printf(MSG_DEBUG, "EAP-SAKE: AT_IV included without "
145189251Ssam			   "AT_ENCR_DATA");
146189251Ssam		return -1;
147189251Ssam	}
148189251Ssam
149189251Ssam	return 0;
150189251Ssam}
151189251Ssam
152189251Ssam
153189251Ssam/**
154189251Ssam * eap_sake_parse_attributes - Parse EAP-SAKE attributes
155189251Ssam * @buf: Packet payload (starting with the first attribute)
156189251Ssam * @len: Payload length
157189251Ssam * @attr: Structure to be filled with found attributes
158189251Ssam * Returns: 0 on success or -1 on failure
159189251Ssam */
160189251Ssamint eap_sake_parse_attributes(const u8 *buf, size_t len,
161189251Ssam			      struct eap_sake_parse_attr *attr)
162189251Ssam{
163189251Ssam	const u8 *pos = buf, *end = buf + len;
164189251Ssam
165189251Ssam	os_memset(attr, 0, sizeof(*attr));
166189251Ssam	while (pos < end) {
167189251Ssam		if (end - pos < 2) {
168189251Ssam			wpa_printf(MSG_DEBUG, "EAP-SAKE: Too short attribute");
169189251Ssam			return -1;
170189251Ssam		}
171189251Ssam
172189251Ssam		if (pos[1] < 2) {
173189251Ssam			wpa_printf(MSG_DEBUG, "EAP-SAKE: Invalid attribute "
174189251Ssam				   "length (%d)", pos[1]);
175189251Ssam			return -1;
176189251Ssam		}
177189251Ssam
178189251Ssam		if (pos + pos[1] > end) {
179189251Ssam			wpa_printf(MSG_DEBUG, "EAP-SAKE: Attribute underflow");
180189251Ssam			return -1;
181189251Ssam		}
182189251Ssam
183189251Ssam		if (eap_sake_parse_add_attr(attr, pos))
184189251Ssam			return -1;
185189251Ssam
186189251Ssam		pos += pos[1];
187189251Ssam	}
188189251Ssam
189189251Ssam	return 0;
190189251Ssam}
191189251Ssam
192189251Ssam
193189251Ssam/**
194189251Ssam * eap_sake_kdf - EAP-SAKE Key Derivation Function (KDF)
195189251Ssam * @key: Key for KDF
196189251Ssam * @key_len: Length of the key in bytes
197189251Ssam * @label: A unique label for each purpose of the KDF
198189251Ssam * @data: Extra data (start) to bind into the key
199189251Ssam * @data_len: Length of the data
200189251Ssam * @data2: Extra data (end) to bind into the key
201189251Ssam * @data2_len: Length of the data2
202189251Ssam * @buf: Buffer for the generated pseudo-random key
203189251Ssam * @buf_len: Number of bytes of key to generate
204189251Ssam *
205189251Ssam * This function is used to derive new, cryptographically separate keys from a
206189251Ssam * given key (e.g., SMS). This is identical to the PRF used in IEEE 802.11i.
207189251Ssam */
208189251Ssamstatic void eap_sake_kdf(const u8 *key, size_t key_len, const char *label,
209189251Ssam			 const u8 *data, size_t data_len,
210189251Ssam			 const u8 *data2, size_t data2_len,
211189251Ssam			 u8 *buf, size_t buf_len)
212189251Ssam{
213189251Ssam	u8 counter = 0;
214189251Ssam	size_t pos, plen;
215189251Ssam	u8 hash[SHA1_MAC_LEN];
216189251Ssam	size_t label_len = os_strlen(label) + 1;
217189251Ssam	const unsigned char *addr[4];
218189251Ssam	size_t len[4];
219189251Ssam
220189251Ssam	addr[0] = (u8 *) label; /* Label | Y */
221189251Ssam	len[0] = label_len;
222189251Ssam	addr[1] = data; /* Msg[start] */
223189251Ssam	len[1] = data_len;
224189251Ssam	addr[2] = data2; /* Msg[end] */
225189251Ssam	len[2] = data2_len;
226189251Ssam	addr[3] = &counter; /* Length */
227189251Ssam	len[3] = 1;
228189251Ssam
229189251Ssam	pos = 0;
230189251Ssam	while (pos < buf_len) {
231189251Ssam		plen = buf_len - pos;
232189251Ssam		if (plen >= SHA1_MAC_LEN) {
233189251Ssam			hmac_sha1_vector(key, key_len, 4, addr, len,
234189251Ssam					 &buf[pos]);
235189251Ssam			pos += SHA1_MAC_LEN;
236189251Ssam		} else {
237189251Ssam			hmac_sha1_vector(key, key_len, 4, addr, len,
238189251Ssam					 hash);
239189251Ssam			os_memcpy(&buf[pos], hash, plen);
240189251Ssam			break;
241189251Ssam		}
242189251Ssam		counter++;
243189251Ssam	}
244189251Ssam}
245189251Ssam
246189251Ssam
247189251Ssam/**
248189251Ssam * eap_sake_derive_keys - Derive EAP-SAKE keys
249189251Ssam * @root_secret_a: 16-byte Root-Secret-A
250189251Ssam * @root_secret_b: 16-byte Root-Secret-B
251189251Ssam * @rand_s: 16-byte RAND_S
252189251Ssam * @rand_p: 16-byte RAND_P
253189251Ssam * @tek: Buffer for Temporary EAK Keys (TEK-Auth[16] | TEK-Cipher[16])
254189251Ssam * @msk: Buffer for 64-byte MSK
255189251Ssam * @emsk: Buffer for 64-byte EMSK
256189251Ssam *
257189251Ssam * This function derives EAP-SAKE keys as defined in RFC 4763, section 3.2.6.
258189251Ssam */
259189251Ssamvoid eap_sake_derive_keys(const u8 *root_secret_a, const u8 *root_secret_b,
260189251Ssam			  const u8 *rand_s, const u8 *rand_p, u8 *tek, u8 *msk,
261189251Ssam			  u8 *emsk)
262189251Ssam{
263189251Ssam	u8 sms_a[EAP_SAKE_SMS_LEN];
264189251Ssam	u8 sms_b[EAP_SAKE_SMS_LEN];
265189251Ssam	u8 key_buf[EAP_MSK_LEN + EAP_EMSK_LEN];
266189251Ssam
267189251Ssam	wpa_printf(MSG_DEBUG, "EAP-SAKE: Deriving keys");
268189251Ssam
269189251Ssam	wpa_hexdump_key(MSG_DEBUG, "EAP-SAKE: Root-Secret-A",
270189251Ssam			root_secret_a, EAP_SAKE_ROOT_SECRET_LEN);
271189251Ssam	eap_sake_kdf(root_secret_a, EAP_SAKE_ROOT_SECRET_LEN,
272189251Ssam		     "SAKE Master Secret A",
273189251Ssam		     rand_p, EAP_SAKE_RAND_LEN, rand_s, EAP_SAKE_RAND_LEN,
274189251Ssam		     sms_a, EAP_SAKE_SMS_LEN);
275189251Ssam	wpa_hexdump_key(MSG_DEBUG, "EAP-SAKE: SMS-A", sms_a, EAP_SAKE_SMS_LEN);
276189251Ssam	eap_sake_kdf(sms_a, EAP_SAKE_SMS_LEN, "Transient EAP Key",
277189251Ssam		     rand_s, EAP_SAKE_RAND_LEN, rand_p, EAP_SAKE_RAND_LEN,
278189251Ssam		     tek, EAP_SAKE_TEK_LEN);
279189251Ssam	wpa_hexdump_key(MSG_DEBUG, "EAP-SAKE: TEK-Auth",
280189251Ssam			tek, EAP_SAKE_TEK_AUTH_LEN);
281189251Ssam	wpa_hexdump_key(MSG_DEBUG, "EAP-SAKE: TEK-Cipher",
282189251Ssam			tek + EAP_SAKE_TEK_AUTH_LEN, EAP_SAKE_TEK_CIPHER_LEN);
283189251Ssam
284189251Ssam	wpa_hexdump_key(MSG_DEBUG, "EAP-SAKE: Root-Secret-B",
285189251Ssam			root_secret_b, EAP_SAKE_ROOT_SECRET_LEN);
286189251Ssam	eap_sake_kdf(root_secret_b, EAP_SAKE_ROOT_SECRET_LEN,
287189251Ssam		     "SAKE Master Secret B",
288189251Ssam		     rand_p, EAP_SAKE_RAND_LEN, rand_s, EAP_SAKE_RAND_LEN,
289189251Ssam		     sms_b, EAP_SAKE_SMS_LEN);
290189251Ssam	wpa_hexdump_key(MSG_DEBUG, "EAP-SAKE: SMS-B", sms_b, EAP_SAKE_SMS_LEN);
291189251Ssam	eap_sake_kdf(sms_b, EAP_SAKE_SMS_LEN, "Master Session Key",
292189251Ssam		     rand_s, EAP_SAKE_RAND_LEN, rand_p, EAP_SAKE_RAND_LEN,
293189251Ssam		     key_buf, sizeof(key_buf));
294189251Ssam	os_memcpy(msk, key_buf, EAP_MSK_LEN);
295189251Ssam	os_memcpy(emsk, key_buf + EAP_MSK_LEN, EAP_EMSK_LEN);
296189251Ssam	wpa_hexdump_key(MSG_DEBUG, "EAP-SAKE: MSK", msk, EAP_MSK_LEN);
297189251Ssam	wpa_hexdump_key(MSG_DEBUG, "EAP-SAKE: EMSK", emsk, EAP_EMSK_LEN);
298189251Ssam}
299189251Ssam
300189251Ssam
301189251Ssam/**
302189251Ssam * eap_sake_compute_mic - Compute EAP-SAKE MIC for an EAP packet
303189251Ssam * @tek_auth: 16-byte TEK-Auth
304189251Ssam * @rand_s: 16-byte RAND_S
305189251Ssam * @rand_p: 16-byte RAND_P
306189251Ssam * @serverid: SERVERID
307189251Ssam * @serverid_len: SERVERID length
308189251Ssam * @peerid: PEERID
309189251Ssam * @peerid_len: PEERID length
310189251Ssam * @peer: MIC calculation for 0 = Server, 1 = Peer message
311189251Ssam * @eap: EAP packet
312189251Ssam * @eap_len: EAP packet length
313189251Ssam * @mic_pos: MIC position in the EAP packet (must be [eap .. eap + eap_len])
314189251Ssam * @mic: Buffer for the computed 16-byte MIC
315189251Ssam */
316189251Ssamint eap_sake_compute_mic(const u8 *tek_auth,
317189251Ssam			 const u8 *rand_s, const u8 *rand_p,
318189251Ssam			 const u8 *serverid, size_t serverid_len,
319189251Ssam			 const u8 *peerid, size_t peerid_len,
320189251Ssam			 int peer, const u8 *eap, size_t eap_len,
321189251Ssam			 const u8 *mic_pos, u8 *mic)
322189251Ssam{
323189251Ssam	u8 _rand[2 * EAP_SAKE_RAND_LEN];
324189251Ssam	u8 *tmp, *pos;
325189251Ssam	size_t tmplen;
326189251Ssam
327189251Ssam	tmplen = serverid_len + 1 + peerid_len + 1 + eap_len;
328189251Ssam	tmp = os_malloc(tmplen);
329189251Ssam	if (tmp == NULL)
330189251Ssam		return -1;
331189251Ssam	pos = tmp;
332189251Ssam	if (peer) {
333189251Ssam		if (peerid) {
334189251Ssam			os_memcpy(pos, peerid, peerid_len);
335189251Ssam			pos += peerid_len;
336189251Ssam		}
337189251Ssam		*pos++ = 0x00;
338189251Ssam		if (serverid) {
339189251Ssam			os_memcpy(pos, serverid, serverid_len);
340189251Ssam			pos += serverid_len;
341189251Ssam		}
342189251Ssam		*pos++ = 0x00;
343189251Ssam
344189251Ssam		os_memcpy(_rand, rand_s, EAP_SAKE_RAND_LEN);
345189251Ssam		os_memcpy(_rand + EAP_SAKE_RAND_LEN, rand_p,
346189251Ssam			  EAP_SAKE_RAND_LEN);
347189251Ssam	} else {
348189251Ssam		if (serverid) {
349189251Ssam			os_memcpy(pos, serverid, serverid_len);
350189251Ssam			pos += serverid_len;
351189251Ssam		}
352189251Ssam		*pos++ = 0x00;
353189251Ssam		if (peerid) {
354189251Ssam			os_memcpy(pos, peerid, peerid_len);
355189251Ssam			pos += peerid_len;
356189251Ssam		}
357189251Ssam		*pos++ = 0x00;
358189251Ssam
359189251Ssam		os_memcpy(_rand, rand_p, EAP_SAKE_RAND_LEN);
360189251Ssam		os_memcpy(_rand + EAP_SAKE_RAND_LEN, rand_s,
361189251Ssam			  EAP_SAKE_RAND_LEN);
362189251Ssam	}
363189251Ssam
364189251Ssam	os_memcpy(pos, eap, eap_len);
365189251Ssam	os_memset(pos + (mic_pos - eap), 0, EAP_SAKE_MIC_LEN);
366189251Ssam
367189251Ssam	eap_sake_kdf(tek_auth, EAP_SAKE_TEK_AUTH_LEN,
368189251Ssam		     peer ? "Peer MIC" : "Server MIC",
369189251Ssam		     _rand, 2 * EAP_SAKE_RAND_LEN, tmp, tmplen,
370189251Ssam		     mic, EAP_SAKE_MIC_LEN);
371189251Ssam
372189251Ssam	os_free(tmp);
373189251Ssam
374189251Ssam	return 0;
375189251Ssam}
376189251Ssam
377189251Ssam
378189251Ssamvoid eap_sake_add_attr(struct wpabuf *buf, u8 type, const u8 *data,
379189251Ssam		       size_t len)
380189251Ssam{
381189251Ssam	wpabuf_put_u8(buf, type);
382189251Ssam	wpabuf_put_u8(buf, 2 + len); /* Length; including attr header */
383189251Ssam	if (data)
384189251Ssam		wpabuf_put_data(buf, data, len);
385189251Ssam	else
386189251Ssam		os_memset(wpabuf_put(buf, len), 0, len);
387189251Ssam}
388