1189251Ssam/*
2209158Srpaulo * EAP peer method: EAP-GPSK (RFC 5433)
3189251Ssam * Copyright (c) 2006-2008, 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"
12252726Srpaulo#include "crypto/random.h"
13189251Ssam#include "eap_peer/eap_i.h"
14189251Ssam#include "eap_common/eap_gpsk_common.h"
15189251Ssam
16189251Ssamstruct eap_gpsk_data {
17189251Ssam	enum { GPSK_1, GPSK_3, SUCCESS, FAILURE } state;
18189251Ssam	u8 rand_server[EAP_GPSK_RAND_LEN];
19189251Ssam	u8 rand_peer[EAP_GPSK_RAND_LEN];
20189251Ssam	u8 msk[EAP_MSK_LEN];
21189251Ssam	u8 emsk[EAP_EMSK_LEN];
22189251Ssam	u8 sk[EAP_GPSK_MAX_SK_LEN];
23189251Ssam	size_t sk_len;
24189251Ssam	u8 pk[EAP_GPSK_MAX_PK_LEN];
25189251Ssam	size_t pk_len;
26189251Ssam	u8 session_id;
27189251Ssam	int session_id_set;
28189251Ssam	u8 *id_peer;
29189251Ssam	size_t id_peer_len;
30189251Ssam	u8 *id_server;
31189251Ssam	size_t id_server_len;
32189251Ssam	int vendor; /* CSuite/Specifier */
33189251Ssam	int specifier; /* CSuite/Specifier */
34189251Ssam	u8 *psk;
35189251Ssam	size_t psk_len;
36189251Ssam};
37189251Ssam
38189251Ssam
39189251Ssamstatic struct wpabuf * eap_gpsk_send_gpsk_2(struct eap_gpsk_data *data,
40189251Ssam					    u8 identifier,
41189251Ssam					    const u8 *csuite_list,
42189251Ssam					    size_t csuite_list_len);
43189251Ssamstatic struct wpabuf * eap_gpsk_send_gpsk_4(struct eap_gpsk_data *data,
44189251Ssam					    u8 identifier);
45189251Ssam
46189251Ssam
47189251Ssam#ifndef CONFIG_NO_STDOUT_DEBUG
48189251Ssamstatic const char * eap_gpsk_state_txt(int state)
49189251Ssam{
50189251Ssam	switch (state) {
51189251Ssam	case GPSK_1:
52189251Ssam		return "GPSK-1";
53189251Ssam	case GPSK_3:
54189251Ssam		return "GPSK-3";
55189251Ssam	case SUCCESS:
56189251Ssam		return "SUCCESS";
57189251Ssam	case FAILURE:
58189251Ssam		return "FAILURE";
59189251Ssam	default:
60189251Ssam		return "?";
61189251Ssam	}
62189251Ssam}
63189251Ssam#endif /* CONFIG_NO_STDOUT_DEBUG */
64189251Ssam
65189251Ssam
66189251Ssamstatic void eap_gpsk_state(struct eap_gpsk_data *data, int state)
67189251Ssam{
68189251Ssam	wpa_printf(MSG_DEBUG, "EAP-GPSK: %s -> %s",
69189251Ssam		   eap_gpsk_state_txt(data->state),
70189251Ssam		   eap_gpsk_state_txt(state));
71189251Ssam	data->state = state;
72189251Ssam}
73189251Ssam
74189251Ssam
75189251Ssamstatic void eap_gpsk_deinit(struct eap_sm *sm, void *priv);
76189251Ssam
77189251Ssam
78189251Ssamstatic void * eap_gpsk_init(struct eap_sm *sm)
79189251Ssam{
80189251Ssam	struct eap_gpsk_data *data;
81189251Ssam	const u8 *identity, *password;
82189251Ssam	size_t identity_len, password_len;
83189251Ssam
84189251Ssam	password = eap_get_config_password(sm, &password_len);
85189251Ssam	if (password == NULL) {
86189251Ssam		wpa_printf(MSG_INFO, "EAP-GPSK: No key (password) configured");
87189251Ssam		return NULL;
88189251Ssam	}
89189251Ssam
90189251Ssam	data = os_zalloc(sizeof(*data));
91189251Ssam	if (data == NULL)
92189251Ssam		return NULL;
93189251Ssam	data->state = GPSK_1;
94189251Ssam
95189251Ssam	identity = eap_get_config_identity(sm, &identity_len);
96189251Ssam	if (identity) {
97189251Ssam		data->id_peer = os_malloc(identity_len);
98189251Ssam		if (data->id_peer == NULL) {
99189251Ssam			eap_gpsk_deinit(sm, data);
100189251Ssam			return NULL;
101189251Ssam		}
102189251Ssam		os_memcpy(data->id_peer, identity, identity_len);
103189251Ssam		data->id_peer_len = identity_len;
104189251Ssam	}
105189251Ssam
106189251Ssam	data->psk = os_malloc(password_len);
107189251Ssam	if (data->psk == NULL) {
108189251Ssam		eap_gpsk_deinit(sm, data);
109189251Ssam		return NULL;
110189251Ssam	}
111189251Ssam	os_memcpy(data->psk, password, password_len);
112189251Ssam	data->psk_len = password_len;
113189251Ssam
114189251Ssam	return data;
115189251Ssam}
116189251Ssam
117189251Ssam
118189251Ssamstatic void eap_gpsk_deinit(struct eap_sm *sm, void *priv)
119189251Ssam{
120189251Ssam	struct eap_gpsk_data *data = priv;
121189251Ssam	os_free(data->id_server);
122189251Ssam	os_free(data->id_peer);
123189251Ssam	os_free(data->psk);
124189251Ssam	os_free(data);
125189251Ssam}
126189251Ssam
127189251Ssam
128189251Ssamstatic const u8 * eap_gpsk_process_id_server(struct eap_gpsk_data *data,
129189251Ssam					     const u8 *pos, const u8 *end)
130189251Ssam{
131189251Ssam	u16 alen;
132189251Ssam
133189251Ssam	if (end - pos < 2) {
134189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Too short GPSK-1 packet");
135189251Ssam		return NULL;
136189251Ssam	}
137189251Ssam	alen = WPA_GET_BE16(pos);
138189251Ssam	pos += 2;
139189251Ssam	if (end - pos < alen) {
140189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: ID_Server overflow");
141189251Ssam		return NULL;
142189251Ssam	}
143189251Ssam	os_free(data->id_server);
144189251Ssam	data->id_server = os_malloc(alen);
145189251Ssam	if (data->id_server == NULL) {
146189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: No memory for ID_Server");
147189251Ssam		return NULL;
148189251Ssam	}
149189251Ssam	os_memcpy(data->id_server, pos, alen);
150189251Ssam	data->id_server_len = alen;
151189251Ssam	wpa_hexdump_ascii(MSG_DEBUG, "EAP-GPSK: ID_Server",
152189251Ssam			  data->id_server, data->id_server_len);
153189251Ssam	pos += alen;
154189251Ssam
155189251Ssam	return pos;
156189251Ssam}
157189251Ssam
158189251Ssam
159189251Ssamstatic const u8 * eap_gpsk_process_rand_server(struct eap_gpsk_data *data,
160189251Ssam					       const u8 *pos, const u8 *end)
161189251Ssam{
162189251Ssam	if (pos == NULL)
163189251Ssam		return NULL;
164189251Ssam
165189251Ssam	if (end - pos < EAP_GPSK_RAND_LEN) {
166189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: RAND_Server overflow");
167189251Ssam		return NULL;
168189251Ssam	}
169189251Ssam	os_memcpy(data->rand_server, pos, EAP_GPSK_RAND_LEN);
170189251Ssam	wpa_hexdump(MSG_DEBUG, "EAP-GPSK: RAND_Server",
171189251Ssam		    data->rand_server, EAP_GPSK_RAND_LEN);
172189251Ssam	pos += EAP_GPSK_RAND_LEN;
173189251Ssam
174189251Ssam	return pos;
175189251Ssam}
176189251Ssam
177189251Ssam
178189251Ssamstatic int eap_gpsk_select_csuite(struct eap_sm *sm,
179189251Ssam				  struct eap_gpsk_data *data,
180189251Ssam				  const u8 *csuite_list,
181189251Ssam				  size_t csuite_list_len)
182189251Ssam{
183189251Ssam	struct eap_gpsk_csuite *csuite;
184189251Ssam	int i, count;
185189251Ssam
186189251Ssam	count = csuite_list_len / sizeof(struct eap_gpsk_csuite);
187189251Ssam	data->vendor = EAP_GPSK_VENDOR_IETF;
188189251Ssam	data->specifier = EAP_GPSK_CIPHER_RESERVED;
189189251Ssam	csuite = (struct eap_gpsk_csuite *) csuite_list;
190189251Ssam	for (i = 0; i < count; i++) {
191189251Ssam		int vendor, specifier;
192189251Ssam		vendor = WPA_GET_BE32(csuite->vendor);
193189251Ssam		specifier = WPA_GET_BE16(csuite->specifier);
194189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: CSuite[%d]: %d:%d",
195189251Ssam			   i, vendor, specifier);
196189251Ssam		if (data->vendor == EAP_GPSK_VENDOR_IETF &&
197189251Ssam		    data->specifier == EAP_GPSK_CIPHER_RESERVED &&
198189251Ssam		    eap_gpsk_supported_ciphersuite(vendor, specifier)) {
199189251Ssam			data->vendor = vendor;
200189251Ssam			data->specifier = specifier;
201189251Ssam		}
202189251Ssam		csuite++;
203189251Ssam	}
204189251Ssam	if (data->vendor == EAP_GPSK_VENDOR_IETF &&
205189251Ssam	    data->specifier == EAP_GPSK_CIPHER_RESERVED) {
206189251Ssam		wpa_msg(sm->msg_ctx, MSG_INFO, "EAP-GPSK: No supported "
207189251Ssam			"ciphersuite found");
208189251Ssam		return -1;
209189251Ssam	}
210189251Ssam	wpa_printf(MSG_DEBUG, "EAP-GPSK: Selected ciphersuite %d:%d",
211189251Ssam		   data->vendor, data->specifier);
212189251Ssam
213189251Ssam	return 0;
214189251Ssam}
215189251Ssam
216189251Ssam
217189251Ssamstatic const u8 * eap_gpsk_process_csuite_list(struct eap_sm *sm,
218189251Ssam					       struct eap_gpsk_data *data,
219189251Ssam					       const u8 **list,
220189251Ssam					       size_t *list_len,
221189251Ssam					       const u8 *pos, const u8 *end)
222189251Ssam{
223189251Ssam	if (pos == NULL)
224189251Ssam		return NULL;
225189251Ssam
226189251Ssam	if (end - pos < 2) {
227189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Too short GPSK-1 packet");
228189251Ssam		return NULL;
229189251Ssam	}
230189251Ssam	*list_len = WPA_GET_BE16(pos);
231189251Ssam	pos += 2;
232189251Ssam	if (end - pos < (int) *list_len) {
233189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: CSuite_List overflow");
234189251Ssam		return NULL;
235189251Ssam	}
236189251Ssam	if (*list_len == 0 || (*list_len % sizeof(struct eap_gpsk_csuite))) {
237189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Invalid CSuite_List len %lu",
238189251Ssam			   (unsigned long) *list_len);
239189251Ssam		return NULL;
240189251Ssam	}
241189251Ssam	*list = pos;
242189251Ssam	pos += *list_len;
243189251Ssam
244189251Ssam	if (eap_gpsk_select_csuite(sm, data, *list, *list_len) < 0)
245189251Ssam		return NULL;
246189251Ssam
247189251Ssam	return pos;
248189251Ssam}
249189251Ssam
250189251Ssam
251189251Ssamstatic struct wpabuf * eap_gpsk_process_gpsk_1(struct eap_sm *sm,
252189251Ssam					       struct eap_gpsk_data *data,
253189251Ssam					       struct eap_method_ret *ret,
254189251Ssam					       const struct wpabuf *reqData,
255189251Ssam					       const u8 *payload,
256189251Ssam					       size_t payload_len)
257189251Ssam{
258189251Ssam	size_t csuite_list_len;
259189251Ssam	const u8 *csuite_list, *pos, *end;
260189251Ssam	struct wpabuf *resp;
261189251Ssam
262189251Ssam	if (data->state != GPSK_1) {
263189251Ssam		ret->ignore = TRUE;
264189251Ssam		return NULL;
265189251Ssam	}
266189251Ssam
267189251Ssam	wpa_printf(MSG_DEBUG, "EAP-GPSK: Received Request/GPSK-1");
268189251Ssam
269189251Ssam	end = payload + payload_len;
270189251Ssam
271189251Ssam	pos = eap_gpsk_process_id_server(data, payload, end);
272189251Ssam	pos = eap_gpsk_process_rand_server(data, pos, end);
273189251Ssam	pos = eap_gpsk_process_csuite_list(sm, data, &csuite_list,
274189251Ssam					   &csuite_list_len, pos, end);
275189251Ssam	if (pos == NULL) {
276189251Ssam		eap_gpsk_state(data, FAILURE);
277189251Ssam		return NULL;
278189251Ssam	}
279189251Ssam
280189251Ssam	resp = eap_gpsk_send_gpsk_2(data, eap_get_id(reqData),
281189251Ssam				    csuite_list, csuite_list_len);
282189251Ssam	if (resp == NULL)
283189251Ssam		return NULL;
284189251Ssam
285189251Ssam	eap_gpsk_state(data, GPSK_3);
286189251Ssam
287189251Ssam	return resp;
288189251Ssam}
289189251Ssam
290189251Ssam
291189251Ssamstatic struct wpabuf * eap_gpsk_send_gpsk_2(struct eap_gpsk_data *data,
292189251Ssam					    u8 identifier,
293189251Ssam					    const u8 *csuite_list,
294189251Ssam					    size_t csuite_list_len)
295189251Ssam{
296189251Ssam	struct wpabuf *resp;
297189251Ssam	size_t len, miclen;
298189251Ssam	u8 *rpos, *start;
299189251Ssam	struct eap_gpsk_csuite *csuite;
300189251Ssam
301189251Ssam	wpa_printf(MSG_DEBUG, "EAP-GPSK: Sending Response/GPSK-2");
302189251Ssam
303189251Ssam	miclen = eap_gpsk_mic_len(data->vendor, data->specifier);
304189251Ssam	len = 1 + 2 + data->id_peer_len + 2 + data->id_server_len +
305189251Ssam		2 * EAP_GPSK_RAND_LEN + 2 + csuite_list_len +
306189251Ssam		sizeof(struct eap_gpsk_csuite) + 2 + miclen;
307189251Ssam
308189251Ssam	resp = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_GPSK, len,
309189251Ssam			     EAP_CODE_RESPONSE, identifier);
310189251Ssam	if (resp == NULL)
311189251Ssam		return NULL;
312189251Ssam
313189251Ssam	wpabuf_put_u8(resp, EAP_GPSK_OPCODE_GPSK_2);
314189251Ssam	start = wpabuf_put(resp, 0);
315189251Ssam
316189251Ssam	wpa_hexdump_ascii(MSG_DEBUG, "EAP-GPSK: ID_Peer",
317189251Ssam			  data->id_peer, data->id_peer_len);
318189251Ssam	wpabuf_put_be16(resp, data->id_peer_len);
319189251Ssam	wpabuf_put_data(resp, data->id_peer, data->id_peer_len);
320189251Ssam
321189251Ssam	wpabuf_put_be16(resp, data->id_server_len);
322189251Ssam	wpabuf_put_data(resp, data->id_server, data->id_server_len);
323189251Ssam
324252726Srpaulo	if (random_get_bytes(data->rand_peer, EAP_GPSK_RAND_LEN)) {
325189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Failed to get random data "
326189251Ssam			   "for RAND_Peer");
327189251Ssam		eap_gpsk_state(data, FAILURE);
328189251Ssam		wpabuf_free(resp);
329189251Ssam		return NULL;
330189251Ssam	}
331189251Ssam	wpa_hexdump(MSG_DEBUG, "EAP-GPSK: RAND_Peer",
332189251Ssam		    data->rand_peer, EAP_GPSK_RAND_LEN);
333189251Ssam	wpabuf_put_data(resp, data->rand_peer, EAP_GPSK_RAND_LEN);
334189251Ssam	wpabuf_put_data(resp, data->rand_server, EAP_GPSK_RAND_LEN);
335189251Ssam
336189251Ssam	wpabuf_put_be16(resp, csuite_list_len);
337189251Ssam	wpabuf_put_data(resp, csuite_list, csuite_list_len);
338189251Ssam
339189251Ssam	csuite = wpabuf_put(resp, sizeof(*csuite));
340189251Ssam	WPA_PUT_BE32(csuite->vendor, data->vendor);
341189251Ssam	WPA_PUT_BE16(csuite->specifier, data->specifier);
342189251Ssam
343189251Ssam	if (eap_gpsk_derive_keys(data->psk, data->psk_len,
344189251Ssam				 data->vendor, data->specifier,
345189251Ssam				 data->rand_peer, data->rand_server,
346189251Ssam				 data->id_peer, data->id_peer_len,
347189251Ssam				 data->id_server, data->id_server_len,
348189251Ssam				 data->msk, data->emsk,
349189251Ssam				 data->sk, &data->sk_len,
350189251Ssam				 data->pk, &data->pk_len) < 0) {
351189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Failed to derive keys");
352189251Ssam		eap_gpsk_state(data, FAILURE);
353189251Ssam		wpabuf_free(resp);
354189251Ssam		return NULL;
355189251Ssam	}
356189251Ssam
357189251Ssam	/* No PD_Payload_1 */
358189251Ssam	wpabuf_put_be16(resp, 0);
359189251Ssam
360189251Ssam	rpos = wpabuf_put(resp, miclen);
361189251Ssam	if (eap_gpsk_compute_mic(data->sk, data->sk_len, data->vendor,
362189251Ssam				 data->specifier, start, rpos - start, rpos) <
363189251Ssam	    0) {
364189251Ssam		eap_gpsk_state(data, FAILURE);
365189251Ssam		wpabuf_free(resp);
366189251Ssam		return NULL;
367189251Ssam	}
368189251Ssam
369189251Ssam	return resp;
370189251Ssam}
371189251Ssam
372189251Ssam
373189251Ssamstatic const u8 * eap_gpsk_validate_rand(struct eap_gpsk_data *data,
374189251Ssam					 const u8 *pos, const u8 *end)
375189251Ssam{
376189251Ssam	if (end - pos < EAP_GPSK_RAND_LEN) {
377189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Message too short for "
378189251Ssam			   "RAND_Peer");
379189251Ssam		return NULL;
380189251Ssam	}
381189251Ssam	if (os_memcmp(pos, data->rand_peer, EAP_GPSK_RAND_LEN) != 0) {
382189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: RAND_Peer in GPSK-2 and "
383189251Ssam			   "GPSK-3 did not match");
384189251Ssam		wpa_hexdump(MSG_DEBUG, "EAP-GPSK: RAND_Peer in GPSK-2",
385189251Ssam			    data->rand_peer, EAP_GPSK_RAND_LEN);
386189251Ssam		wpa_hexdump(MSG_DEBUG, "EAP-GPSK: RAND_Peer in GPSK-3",
387189251Ssam			    pos, EAP_GPSK_RAND_LEN);
388189251Ssam		return NULL;
389189251Ssam	}
390189251Ssam	pos += EAP_GPSK_RAND_LEN;
391189251Ssam
392189251Ssam	if (end - pos < EAP_GPSK_RAND_LEN) {
393189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Message too short for "
394189251Ssam			   "RAND_Server");
395189251Ssam		return NULL;
396189251Ssam	}
397189251Ssam	if (os_memcmp(pos, data->rand_server, EAP_GPSK_RAND_LEN) != 0) {
398189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: RAND_Server in GPSK-1 and "
399189251Ssam			   "GPSK-3 did not match");
400189251Ssam		wpa_hexdump(MSG_DEBUG, "EAP-GPSK: RAND_Server in GPSK-1",
401189251Ssam			    data->rand_server, EAP_GPSK_RAND_LEN);
402189251Ssam		wpa_hexdump(MSG_DEBUG, "EAP-GPSK: RAND_Server in GPSK-3",
403189251Ssam			    pos, EAP_GPSK_RAND_LEN);
404189251Ssam		return NULL;
405189251Ssam	}
406189251Ssam	pos += EAP_GPSK_RAND_LEN;
407189251Ssam
408189251Ssam	return pos;
409189251Ssam}
410189251Ssam
411189251Ssam
412189251Ssamstatic const u8 * eap_gpsk_validate_id_server(struct eap_gpsk_data *data,
413189251Ssam					      const u8 *pos, const u8 *end)
414189251Ssam{
415189251Ssam	size_t len;
416189251Ssam
417189251Ssam	if (pos == NULL)
418189251Ssam		return NULL;
419189251Ssam
420189251Ssam	if (end - pos < (int) 2) {
421189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Message too short for "
422189251Ssam			   "length(ID_Server)");
423189251Ssam		return NULL;
424189251Ssam	}
425189251Ssam
426189251Ssam	len = WPA_GET_BE16(pos);
427189251Ssam	pos += 2;
428189251Ssam
429189251Ssam	if (end - pos < (int) len) {
430189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Message too short for "
431189251Ssam			   "ID_Server");
432189251Ssam		return NULL;
433189251Ssam	}
434189251Ssam
435189251Ssam	if (len != data->id_server_len ||
436189251Ssam	    os_memcmp(pos, data->id_server, len) != 0) {
437189251Ssam		wpa_printf(MSG_INFO, "EAP-GPSK: ID_Server did not match with "
438189251Ssam			   "the one used in GPSK-1");
439189251Ssam		wpa_hexdump_ascii(MSG_DEBUG, "EAP-GPSK: ID_Server in GPSK-1",
440189251Ssam				  data->id_server, data->id_server_len);
441189251Ssam		wpa_hexdump_ascii(MSG_DEBUG, "EAP-GPSK: ID_Server in GPSK-3",
442189251Ssam				  pos, len);
443189251Ssam		return NULL;
444189251Ssam	}
445189251Ssam
446189251Ssam	pos += len;
447189251Ssam
448189251Ssam	return pos;
449189251Ssam}
450189251Ssam
451189251Ssam
452189251Ssamstatic const u8 * eap_gpsk_validate_csuite(struct eap_gpsk_data *data,
453189251Ssam					   const u8 *pos, const u8 *end)
454189251Ssam{
455189251Ssam	int vendor, specifier;
456189251Ssam	const struct eap_gpsk_csuite *csuite;
457189251Ssam
458189251Ssam	if (pos == NULL)
459189251Ssam		return NULL;
460189251Ssam
461189251Ssam	if (end - pos < (int) sizeof(*csuite)) {
462189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Message too short for "
463189251Ssam			   "CSuite_Sel");
464189251Ssam		return NULL;
465189251Ssam	}
466189251Ssam	csuite = (const struct eap_gpsk_csuite *) pos;
467189251Ssam	vendor = WPA_GET_BE32(csuite->vendor);
468189251Ssam	specifier = WPA_GET_BE16(csuite->specifier);
469189251Ssam	pos += sizeof(*csuite);
470189251Ssam	if (vendor != data->vendor || specifier != data->specifier) {
471189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: CSuite_Sel (%d:%d) does not "
472189251Ssam			   "match with the one sent in GPSK-2 (%d:%d)",
473189251Ssam			   vendor, specifier, data->vendor, data->specifier);
474189251Ssam		return NULL;
475189251Ssam	}
476189251Ssam
477189251Ssam	return pos;
478189251Ssam}
479189251Ssam
480189251Ssam
481189251Ssamstatic const u8 * eap_gpsk_validate_pd_payload_2(struct eap_gpsk_data *data,
482189251Ssam						 const u8 *pos, const u8 *end)
483189251Ssam{
484189251Ssam	u16 alen;
485189251Ssam
486189251Ssam	if (pos == NULL)
487189251Ssam		return NULL;
488189251Ssam
489189251Ssam	if (end - pos < 2) {
490189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Message too short for "
491189251Ssam			   "PD_Payload_2 length");
492189251Ssam		return NULL;
493189251Ssam	}
494189251Ssam	alen = WPA_GET_BE16(pos);
495189251Ssam	pos += 2;
496189251Ssam	if (end - pos < alen) {
497189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Message too short for "
498189251Ssam			   "%d-octet PD_Payload_2", alen);
499189251Ssam		return NULL;
500189251Ssam	}
501189251Ssam	wpa_hexdump(MSG_DEBUG, "EAP-GPSK: PD_Payload_2", pos, alen);
502189251Ssam	pos += alen;
503189251Ssam
504189251Ssam	return pos;
505189251Ssam}
506189251Ssam
507189251Ssam
508189251Ssamstatic const u8 * eap_gpsk_validate_gpsk_3_mic(struct eap_gpsk_data *data,
509189251Ssam					       const u8 *payload,
510189251Ssam					       const u8 *pos, const u8 *end)
511189251Ssam{
512189251Ssam	size_t miclen;
513189251Ssam	u8 mic[EAP_GPSK_MAX_MIC_LEN];
514189251Ssam
515189251Ssam	if (pos == NULL)
516189251Ssam		return NULL;
517189251Ssam
518189251Ssam	miclen = eap_gpsk_mic_len(data->vendor, data->specifier);
519189251Ssam	if (end - pos < (int) miclen) {
520189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Message too short for MIC "
521189251Ssam			   "(left=%lu miclen=%lu)",
522189251Ssam			   (unsigned long) (end - pos),
523189251Ssam			   (unsigned long) miclen);
524189251Ssam		return NULL;
525189251Ssam	}
526189251Ssam	if (eap_gpsk_compute_mic(data->sk, data->sk_len, data->vendor,
527189251Ssam				 data->specifier, payload, pos - payload, mic)
528189251Ssam	    < 0) {
529189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Failed to compute MIC");
530189251Ssam		return NULL;
531189251Ssam	}
532189251Ssam	if (os_memcmp(mic, pos, miclen) != 0) {
533189251Ssam		wpa_printf(MSG_INFO, "EAP-GPSK: Incorrect MIC in GPSK-3");
534189251Ssam		wpa_hexdump(MSG_DEBUG, "EAP-GPSK: Received MIC", pos, miclen);
535189251Ssam		wpa_hexdump(MSG_DEBUG, "EAP-GPSK: Computed MIC", mic, miclen);
536189251Ssam		return NULL;
537189251Ssam	}
538189251Ssam	pos += miclen;
539189251Ssam
540189251Ssam	return pos;
541189251Ssam}
542189251Ssam
543189251Ssam
544189251Ssamstatic struct wpabuf * eap_gpsk_process_gpsk_3(struct eap_sm *sm,
545189251Ssam					       struct eap_gpsk_data *data,
546189251Ssam					       struct eap_method_ret *ret,
547189251Ssam					       const struct wpabuf *reqData,
548189251Ssam					       const u8 *payload,
549189251Ssam					       size_t payload_len)
550189251Ssam{
551189251Ssam	struct wpabuf *resp;
552189251Ssam	const u8 *pos, *end;
553189251Ssam
554189251Ssam	if (data->state != GPSK_3) {
555189251Ssam		ret->ignore = TRUE;
556189251Ssam		return NULL;
557189251Ssam	}
558189251Ssam
559189251Ssam	wpa_printf(MSG_DEBUG, "EAP-GPSK: Received Request/GPSK-3");
560189251Ssam
561189251Ssam	end = payload + payload_len;
562189251Ssam
563189251Ssam	pos = eap_gpsk_validate_rand(data, payload, end);
564189251Ssam	pos = eap_gpsk_validate_id_server(data, pos, end);
565189251Ssam	pos = eap_gpsk_validate_csuite(data, pos, end);
566189251Ssam	pos = eap_gpsk_validate_pd_payload_2(data, pos, end);
567189251Ssam	pos = eap_gpsk_validate_gpsk_3_mic(data, payload, pos, end);
568189251Ssam
569189251Ssam	if (pos == NULL) {
570189251Ssam		eap_gpsk_state(data, FAILURE);
571189251Ssam		return NULL;
572189251Ssam	}
573189251Ssam	if (pos != end) {
574189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Ignored %lu bytes of extra "
575189251Ssam			   "data in the end of GPSK-2",
576189251Ssam			   (unsigned long) (end - pos));
577189251Ssam	}
578189251Ssam
579189251Ssam	resp = eap_gpsk_send_gpsk_4(data, eap_get_id(reqData));
580189251Ssam	if (resp == NULL)
581189251Ssam		return NULL;
582189251Ssam
583189251Ssam	eap_gpsk_state(data, SUCCESS);
584189251Ssam	ret->methodState = METHOD_DONE;
585189251Ssam	ret->decision = DECISION_UNCOND_SUCC;
586189251Ssam
587189251Ssam	return resp;
588189251Ssam}
589189251Ssam
590189251Ssam
591189251Ssamstatic struct wpabuf * eap_gpsk_send_gpsk_4(struct eap_gpsk_data *data,
592189251Ssam					    u8 identifier)
593189251Ssam{
594189251Ssam	struct wpabuf *resp;
595189251Ssam	u8 *rpos, *start;
596189251Ssam	size_t mlen;
597189251Ssam
598189251Ssam	wpa_printf(MSG_DEBUG, "EAP-GPSK: Sending Response/GPSK-4");
599189251Ssam
600189251Ssam	mlen = eap_gpsk_mic_len(data->vendor, data->specifier);
601189251Ssam
602189251Ssam	resp = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_GPSK, 1 + 2 + mlen,
603189251Ssam			     EAP_CODE_RESPONSE, identifier);
604189251Ssam	if (resp == NULL)
605189251Ssam		return NULL;
606189251Ssam
607189251Ssam	wpabuf_put_u8(resp, EAP_GPSK_OPCODE_GPSK_4);
608189251Ssam	start = wpabuf_put(resp, 0);
609189251Ssam
610189251Ssam	/* No PD_Payload_3 */
611189251Ssam	wpabuf_put_be16(resp, 0);
612189251Ssam
613189251Ssam	rpos = wpabuf_put(resp, mlen);
614189251Ssam	if (eap_gpsk_compute_mic(data->sk, data->sk_len, data->vendor,
615189251Ssam				 data->specifier, start, rpos - start, rpos) <
616189251Ssam	    0) {
617189251Ssam		eap_gpsk_state(data, FAILURE);
618189251Ssam		wpabuf_free(resp);
619189251Ssam		return NULL;
620189251Ssam	}
621189251Ssam
622189251Ssam	return resp;
623189251Ssam}
624189251Ssam
625189251Ssam
626189251Ssamstatic struct wpabuf * eap_gpsk_process(struct eap_sm *sm, void *priv,
627189251Ssam					struct eap_method_ret *ret,
628189251Ssam					const struct wpabuf *reqData)
629189251Ssam{
630189251Ssam	struct eap_gpsk_data *data = priv;
631189251Ssam	struct wpabuf *resp;
632189251Ssam	const u8 *pos;
633189251Ssam	size_t len;
634189251Ssam
635189251Ssam	pos = eap_hdr_validate(EAP_VENDOR_IETF, EAP_TYPE_GPSK, reqData, &len);
636189251Ssam	if (pos == NULL || len < 1) {
637189251Ssam		ret->ignore = TRUE;
638189251Ssam		return NULL;
639189251Ssam	}
640189251Ssam
641189251Ssam	wpa_printf(MSG_DEBUG, "EAP-GPSK: Received frame: opcode %d", *pos);
642189251Ssam
643189251Ssam	ret->ignore = FALSE;
644189251Ssam	ret->methodState = METHOD_MAY_CONT;
645189251Ssam	ret->decision = DECISION_FAIL;
646189251Ssam	ret->allowNotifications = FALSE;
647189251Ssam
648189251Ssam	switch (*pos) {
649189251Ssam	case EAP_GPSK_OPCODE_GPSK_1:
650189251Ssam		resp = eap_gpsk_process_gpsk_1(sm, data, ret, reqData,
651189251Ssam					       pos + 1, len - 1);
652189251Ssam		break;
653189251Ssam	case EAP_GPSK_OPCODE_GPSK_3:
654189251Ssam		resp = eap_gpsk_process_gpsk_3(sm, data, ret, reqData,
655189251Ssam					       pos + 1, len - 1);
656189251Ssam		break;
657189251Ssam	default:
658189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Ignoring message with "
659189251Ssam			   "unknown opcode %d", *pos);
660189251Ssam		ret->ignore = TRUE;
661189251Ssam		return NULL;
662189251Ssam	}
663189251Ssam
664189251Ssam	return resp;
665189251Ssam}
666189251Ssam
667189251Ssam
668189251Ssamstatic Boolean eap_gpsk_isKeyAvailable(struct eap_sm *sm, void *priv)
669189251Ssam{
670189251Ssam	struct eap_gpsk_data *data = priv;
671189251Ssam	return data->state == SUCCESS;
672189251Ssam}
673189251Ssam
674189251Ssam
675189251Ssamstatic u8 * eap_gpsk_getKey(struct eap_sm *sm, void *priv, size_t *len)
676189251Ssam{
677189251Ssam	struct eap_gpsk_data *data = priv;
678189251Ssam	u8 *key;
679189251Ssam
680189251Ssam	if (data->state != SUCCESS)
681189251Ssam		return NULL;
682189251Ssam
683189251Ssam	key = os_malloc(EAP_MSK_LEN);
684189251Ssam	if (key == NULL)
685189251Ssam		return NULL;
686189251Ssam	os_memcpy(key, data->msk, EAP_MSK_LEN);
687189251Ssam	*len = EAP_MSK_LEN;
688189251Ssam
689189251Ssam	return key;
690189251Ssam}
691189251Ssam
692189251Ssam
693189251Ssamstatic u8 * eap_gpsk_get_emsk(struct eap_sm *sm, void *priv, size_t *len)
694189251Ssam{
695189251Ssam	struct eap_gpsk_data *data = priv;
696189251Ssam	u8 *key;
697189251Ssam
698189251Ssam	if (data->state != SUCCESS)
699189251Ssam		return NULL;
700189251Ssam
701189251Ssam	key = os_malloc(EAP_EMSK_LEN);
702189251Ssam	if (key == NULL)
703189251Ssam		return NULL;
704189251Ssam	os_memcpy(key, data->emsk, EAP_EMSK_LEN);
705189251Ssam	*len = EAP_EMSK_LEN;
706189251Ssam
707189251Ssam	return key;
708189251Ssam}
709189251Ssam
710189251Ssam
711189251Ssamint eap_peer_gpsk_register(void)
712189251Ssam{
713189251Ssam	struct eap_method *eap;
714189251Ssam	int ret;
715189251Ssam
716189251Ssam	eap = eap_peer_method_alloc(EAP_PEER_METHOD_INTERFACE_VERSION,
717189251Ssam				    EAP_VENDOR_IETF, EAP_TYPE_GPSK, "GPSK");
718189251Ssam	if (eap == NULL)
719189251Ssam		return -1;
720189251Ssam
721189251Ssam	eap->init = eap_gpsk_init;
722189251Ssam	eap->deinit = eap_gpsk_deinit;
723189251Ssam	eap->process = eap_gpsk_process;
724189251Ssam	eap->isKeyAvailable = eap_gpsk_isKeyAvailable;
725189251Ssam	eap->getKey = eap_gpsk_getKey;
726189251Ssam	eap->get_emsk = eap_gpsk_get_emsk;
727189251Ssam
728189251Ssam	ret = eap_peer_method_register(eap);
729189251Ssam	if (ret)
730189251Ssam		eap_peer_method_free(eap);
731189251Ssam	return ret;
732189251Ssam}
733