eap_gpsk.c revision 281806
1189251Ssam/*
2209158Srpaulo * EAP peer method: EAP-GPSK (RFC 5433)
3281806Srpaulo * Copyright (c) 2006-2014, 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;
26281806Srpaulo	u8 session_id[128];
27281806Srpaulo	size_t id_len;
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;
36281806Srpaulo	u16 forced_cipher; /* force cipher or 0 to allow all supported */
37189251Ssam};
38189251Ssam
39189251Ssam
40189251Ssamstatic struct wpabuf * eap_gpsk_send_gpsk_2(struct eap_gpsk_data *data,
41189251Ssam					    u8 identifier,
42189251Ssam					    const u8 *csuite_list,
43189251Ssam					    size_t csuite_list_len);
44189251Ssamstatic struct wpabuf * eap_gpsk_send_gpsk_4(struct eap_gpsk_data *data,
45189251Ssam					    u8 identifier);
46189251Ssam
47189251Ssam
48189251Ssam#ifndef CONFIG_NO_STDOUT_DEBUG
49189251Ssamstatic const char * eap_gpsk_state_txt(int state)
50189251Ssam{
51189251Ssam	switch (state) {
52189251Ssam	case GPSK_1:
53189251Ssam		return "GPSK-1";
54189251Ssam	case GPSK_3:
55189251Ssam		return "GPSK-3";
56189251Ssam	case SUCCESS:
57189251Ssam		return "SUCCESS";
58189251Ssam	case FAILURE:
59189251Ssam		return "FAILURE";
60189251Ssam	default:
61189251Ssam		return "?";
62189251Ssam	}
63189251Ssam}
64189251Ssam#endif /* CONFIG_NO_STDOUT_DEBUG */
65189251Ssam
66189251Ssam
67189251Ssamstatic void eap_gpsk_state(struct eap_gpsk_data *data, int state)
68189251Ssam{
69189251Ssam	wpa_printf(MSG_DEBUG, "EAP-GPSK: %s -> %s",
70189251Ssam		   eap_gpsk_state_txt(data->state),
71189251Ssam		   eap_gpsk_state_txt(state));
72189251Ssam	data->state = state;
73189251Ssam}
74189251Ssam
75189251Ssam
76189251Ssamstatic void eap_gpsk_deinit(struct eap_sm *sm, void *priv);
77189251Ssam
78189251Ssam
79189251Ssamstatic void * eap_gpsk_init(struct eap_sm *sm)
80189251Ssam{
81189251Ssam	struct eap_gpsk_data *data;
82189251Ssam	const u8 *identity, *password;
83189251Ssam	size_t identity_len, password_len;
84281806Srpaulo	const char *phase1;
85189251Ssam
86189251Ssam	password = eap_get_config_password(sm, &password_len);
87189251Ssam	if (password == NULL) {
88189251Ssam		wpa_printf(MSG_INFO, "EAP-GPSK: No key (password) configured");
89189251Ssam		return NULL;
90189251Ssam	}
91189251Ssam
92189251Ssam	data = os_zalloc(sizeof(*data));
93189251Ssam	if (data == NULL)
94189251Ssam		return NULL;
95189251Ssam	data->state = GPSK_1;
96189251Ssam
97189251Ssam	identity = eap_get_config_identity(sm, &identity_len);
98189251Ssam	if (identity) {
99189251Ssam		data->id_peer = os_malloc(identity_len);
100189251Ssam		if (data->id_peer == NULL) {
101189251Ssam			eap_gpsk_deinit(sm, data);
102189251Ssam			return NULL;
103189251Ssam		}
104189251Ssam		os_memcpy(data->id_peer, identity, identity_len);
105189251Ssam		data->id_peer_len = identity_len;
106189251Ssam	}
107189251Ssam
108281806Srpaulo	phase1 = eap_get_config_phase1(sm);
109281806Srpaulo	if (phase1) {
110281806Srpaulo		const char *pos;
111281806Srpaulo
112281806Srpaulo		pos = os_strstr(phase1, "cipher=");
113281806Srpaulo		if (pos) {
114281806Srpaulo			data->forced_cipher = atoi(pos + 7);
115281806Srpaulo			wpa_printf(MSG_DEBUG, "EAP-GPSK: Forced cipher %u",
116281806Srpaulo				   data->forced_cipher);
117281806Srpaulo		}
118281806Srpaulo	}
119281806Srpaulo
120189251Ssam	data->psk = os_malloc(password_len);
121189251Ssam	if (data->psk == NULL) {
122189251Ssam		eap_gpsk_deinit(sm, data);
123189251Ssam		return NULL;
124189251Ssam	}
125189251Ssam	os_memcpy(data->psk, password, password_len);
126189251Ssam	data->psk_len = password_len;
127189251Ssam
128189251Ssam	return data;
129189251Ssam}
130189251Ssam
131189251Ssam
132189251Ssamstatic void eap_gpsk_deinit(struct eap_sm *sm, void *priv)
133189251Ssam{
134189251Ssam	struct eap_gpsk_data *data = priv;
135189251Ssam	os_free(data->id_server);
136189251Ssam	os_free(data->id_peer);
137281806Srpaulo	if (data->psk) {
138281806Srpaulo		os_memset(data->psk, 0, data->psk_len);
139281806Srpaulo		os_free(data->psk);
140281806Srpaulo	}
141281806Srpaulo	bin_clear_free(data, sizeof(*data));
142189251Ssam}
143189251Ssam
144189251Ssam
145189251Ssamstatic const u8 * eap_gpsk_process_id_server(struct eap_gpsk_data *data,
146189251Ssam					     const u8 *pos, const u8 *end)
147189251Ssam{
148189251Ssam	u16 alen;
149189251Ssam
150189251Ssam	if (end - pos < 2) {
151189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Too short GPSK-1 packet");
152189251Ssam		return NULL;
153189251Ssam	}
154189251Ssam	alen = WPA_GET_BE16(pos);
155189251Ssam	pos += 2;
156189251Ssam	if (end - pos < alen) {
157189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: ID_Server overflow");
158189251Ssam		return NULL;
159189251Ssam	}
160189251Ssam	os_free(data->id_server);
161189251Ssam	data->id_server = os_malloc(alen);
162189251Ssam	if (data->id_server == NULL) {
163189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: No memory for ID_Server");
164189251Ssam		return NULL;
165189251Ssam	}
166189251Ssam	os_memcpy(data->id_server, pos, alen);
167189251Ssam	data->id_server_len = alen;
168189251Ssam	wpa_hexdump_ascii(MSG_DEBUG, "EAP-GPSK: ID_Server",
169189251Ssam			  data->id_server, data->id_server_len);
170189251Ssam	pos += alen;
171189251Ssam
172189251Ssam	return pos;
173189251Ssam}
174189251Ssam
175189251Ssam
176189251Ssamstatic const u8 * eap_gpsk_process_rand_server(struct eap_gpsk_data *data,
177189251Ssam					       const u8 *pos, const u8 *end)
178189251Ssam{
179189251Ssam	if (pos == NULL)
180189251Ssam		return NULL;
181189251Ssam
182189251Ssam	if (end - pos < EAP_GPSK_RAND_LEN) {
183189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: RAND_Server overflow");
184189251Ssam		return NULL;
185189251Ssam	}
186189251Ssam	os_memcpy(data->rand_server, pos, EAP_GPSK_RAND_LEN);
187189251Ssam	wpa_hexdump(MSG_DEBUG, "EAP-GPSK: RAND_Server",
188189251Ssam		    data->rand_server, EAP_GPSK_RAND_LEN);
189189251Ssam	pos += EAP_GPSK_RAND_LEN;
190189251Ssam
191189251Ssam	return pos;
192189251Ssam}
193189251Ssam
194189251Ssam
195189251Ssamstatic int eap_gpsk_select_csuite(struct eap_sm *sm,
196189251Ssam				  struct eap_gpsk_data *data,
197189251Ssam				  const u8 *csuite_list,
198189251Ssam				  size_t csuite_list_len)
199189251Ssam{
200189251Ssam	struct eap_gpsk_csuite *csuite;
201189251Ssam	int i, count;
202189251Ssam
203189251Ssam	count = csuite_list_len / sizeof(struct eap_gpsk_csuite);
204189251Ssam	data->vendor = EAP_GPSK_VENDOR_IETF;
205189251Ssam	data->specifier = EAP_GPSK_CIPHER_RESERVED;
206189251Ssam	csuite = (struct eap_gpsk_csuite *) csuite_list;
207189251Ssam	for (i = 0; i < count; i++) {
208189251Ssam		int vendor, specifier;
209189251Ssam		vendor = WPA_GET_BE32(csuite->vendor);
210189251Ssam		specifier = WPA_GET_BE16(csuite->specifier);
211189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: CSuite[%d]: %d:%d",
212189251Ssam			   i, vendor, specifier);
213189251Ssam		if (data->vendor == EAP_GPSK_VENDOR_IETF &&
214189251Ssam		    data->specifier == EAP_GPSK_CIPHER_RESERVED &&
215281806Srpaulo		    eap_gpsk_supported_ciphersuite(vendor, specifier) &&
216281806Srpaulo		    (!data->forced_cipher || data->forced_cipher == specifier))
217281806Srpaulo		{
218189251Ssam			data->vendor = vendor;
219189251Ssam			data->specifier = specifier;
220189251Ssam		}
221189251Ssam		csuite++;
222189251Ssam	}
223189251Ssam	if (data->vendor == EAP_GPSK_VENDOR_IETF &&
224189251Ssam	    data->specifier == EAP_GPSK_CIPHER_RESERVED) {
225189251Ssam		wpa_msg(sm->msg_ctx, MSG_INFO, "EAP-GPSK: No supported "
226189251Ssam			"ciphersuite found");
227189251Ssam		return -1;
228189251Ssam	}
229189251Ssam	wpa_printf(MSG_DEBUG, "EAP-GPSK: Selected ciphersuite %d:%d",
230189251Ssam		   data->vendor, data->specifier);
231189251Ssam
232189251Ssam	return 0;
233189251Ssam}
234189251Ssam
235189251Ssam
236189251Ssamstatic const u8 * eap_gpsk_process_csuite_list(struct eap_sm *sm,
237189251Ssam					       struct eap_gpsk_data *data,
238189251Ssam					       const u8 **list,
239189251Ssam					       size_t *list_len,
240189251Ssam					       const u8 *pos, const u8 *end)
241189251Ssam{
242281806Srpaulo	size_t len;
243281806Srpaulo
244189251Ssam	if (pos == NULL)
245189251Ssam		return NULL;
246189251Ssam
247189251Ssam	if (end - pos < 2) {
248189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Too short GPSK-1 packet");
249189251Ssam		return NULL;
250189251Ssam	}
251281806Srpaulo	len = WPA_GET_BE16(pos);
252189251Ssam	pos += 2;
253281806Srpaulo	if (len > (size_t) (end - pos)) {
254189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: CSuite_List overflow");
255189251Ssam		return NULL;
256189251Ssam	}
257281806Srpaulo	if (len == 0 || (len % sizeof(struct eap_gpsk_csuite))) {
258189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Invalid CSuite_List len %lu",
259281806Srpaulo			   (unsigned long) len);
260189251Ssam		return NULL;
261189251Ssam	}
262189251Ssam
263281806Srpaulo	if (eap_gpsk_select_csuite(sm, data, pos, len) < 0)
264189251Ssam		return NULL;
265189251Ssam
266281806Srpaulo	*list = pos;
267281806Srpaulo	*list_len = len;
268281806Srpaulo	pos += len;
269281806Srpaulo
270189251Ssam	return pos;
271189251Ssam}
272189251Ssam
273189251Ssam
274189251Ssamstatic struct wpabuf * eap_gpsk_process_gpsk_1(struct eap_sm *sm,
275189251Ssam					       struct eap_gpsk_data *data,
276189251Ssam					       struct eap_method_ret *ret,
277189251Ssam					       const struct wpabuf *reqData,
278189251Ssam					       const u8 *payload,
279189251Ssam					       size_t payload_len)
280189251Ssam{
281189251Ssam	size_t csuite_list_len;
282189251Ssam	const u8 *csuite_list, *pos, *end;
283189251Ssam	struct wpabuf *resp;
284189251Ssam
285189251Ssam	if (data->state != GPSK_1) {
286189251Ssam		ret->ignore = TRUE;
287189251Ssam		return NULL;
288189251Ssam	}
289189251Ssam
290189251Ssam	wpa_printf(MSG_DEBUG, "EAP-GPSK: Received Request/GPSK-1");
291189251Ssam
292189251Ssam	end = payload + payload_len;
293189251Ssam
294189251Ssam	pos = eap_gpsk_process_id_server(data, payload, end);
295189251Ssam	pos = eap_gpsk_process_rand_server(data, pos, end);
296189251Ssam	pos = eap_gpsk_process_csuite_list(sm, data, &csuite_list,
297189251Ssam					   &csuite_list_len, pos, end);
298189251Ssam	if (pos == NULL) {
299281806Srpaulo		ret->methodState = METHOD_DONE;
300189251Ssam		eap_gpsk_state(data, FAILURE);
301189251Ssam		return NULL;
302189251Ssam	}
303189251Ssam
304189251Ssam	resp = eap_gpsk_send_gpsk_2(data, eap_get_id(reqData),
305189251Ssam				    csuite_list, csuite_list_len);
306189251Ssam	if (resp == NULL)
307189251Ssam		return NULL;
308189251Ssam
309189251Ssam	eap_gpsk_state(data, GPSK_3);
310189251Ssam
311189251Ssam	return resp;
312189251Ssam}
313189251Ssam
314189251Ssam
315189251Ssamstatic struct wpabuf * eap_gpsk_send_gpsk_2(struct eap_gpsk_data *data,
316189251Ssam					    u8 identifier,
317189251Ssam					    const u8 *csuite_list,
318189251Ssam					    size_t csuite_list_len)
319189251Ssam{
320189251Ssam	struct wpabuf *resp;
321189251Ssam	size_t len, miclen;
322189251Ssam	u8 *rpos, *start;
323189251Ssam	struct eap_gpsk_csuite *csuite;
324189251Ssam
325189251Ssam	wpa_printf(MSG_DEBUG, "EAP-GPSK: Sending Response/GPSK-2");
326189251Ssam
327189251Ssam	miclen = eap_gpsk_mic_len(data->vendor, data->specifier);
328189251Ssam	len = 1 + 2 + data->id_peer_len + 2 + data->id_server_len +
329189251Ssam		2 * EAP_GPSK_RAND_LEN + 2 + csuite_list_len +
330189251Ssam		sizeof(struct eap_gpsk_csuite) + 2 + miclen;
331189251Ssam
332189251Ssam	resp = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_GPSK, len,
333189251Ssam			     EAP_CODE_RESPONSE, identifier);
334189251Ssam	if (resp == NULL)
335189251Ssam		return NULL;
336189251Ssam
337189251Ssam	wpabuf_put_u8(resp, EAP_GPSK_OPCODE_GPSK_2);
338189251Ssam	start = wpabuf_put(resp, 0);
339189251Ssam
340189251Ssam	wpa_hexdump_ascii(MSG_DEBUG, "EAP-GPSK: ID_Peer",
341189251Ssam			  data->id_peer, data->id_peer_len);
342189251Ssam	wpabuf_put_be16(resp, data->id_peer_len);
343189251Ssam	wpabuf_put_data(resp, data->id_peer, data->id_peer_len);
344189251Ssam
345189251Ssam	wpabuf_put_be16(resp, data->id_server_len);
346189251Ssam	wpabuf_put_data(resp, data->id_server, data->id_server_len);
347189251Ssam
348252726Srpaulo	if (random_get_bytes(data->rand_peer, EAP_GPSK_RAND_LEN)) {
349189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Failed to get random data "
350189251Ssam			   "for RAND_Peer");
351189251Ssam		eap_gpsk_state(data, FAILURE);
352189251Ssam		wpabuf_free(resp);
353189251Ssam		return NULL;
354189251Ssam	}
355189251Ssam	wpa_hexdump(MSG_DEBUG, "EAP-GPSK: RAND_Peer",
356189251Ssam		    data->rand_peer, EAP_GPSK_RAND_LEN);
357189251Ssam	wpabuf_put_data(resp, data->rand_peer, EAP_GPSK_RAND_LEN);
358189251Ssam	wpabuf_put_data(resp, data->rand_server, EAP_GPSK_RAND_LEN);
359189251Ssam
360189251Ssam	wpabuf_put_be16(resp, csuite_list_len);
361189251Ssam	wpabuf_put_data(resp, csuite_list, csuite_list_len);
362189251Ssam
363189251Ssam	csuite = wpabuf_put(resp, sizeof(*csuite));
364189251Ssam	WPA_PUT_BE32(csuite->vendor, data->vendor);
365189251Ssam	WPA_PUT_BE16(csuite->specifier, data->specifier);
366189251Ssam
367189251Ssam	if (eap_gpsk_derive_keys(data->psk, data->psk_len,
368189251Ssam				 data->vendor, data->specifier,
369189251Ssam				 data->rand_peer, data->rand_server,
370189251Ssam				 data->id_peer, data->id_peer_len,
371189251Ssam				 data->id_server, data->id_server_len,
372189251Ssam				 data->msk, data->emsk,
373189251Ssam				 data->sk, &data->sk_len,
374189251Ssam				 data->pk, &data->pk_len) < 0) {
375189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Failed to derive keys");
376189251Ssam		eap_gpsk_state(data, FAILURE);
377189251Ssam		wpabuf_free(resp);
378189251Ssam		return NULL;
379189251Ssam	}
380189251Ssam
381281806Srpaulo	if (eap_gpsk_derive_session_id(data->psk, data->psk_len,
382281806Srpaulo				       data->vendor, data->specifier,
383281806Srpaulo				       data->rand_peer, data->rand_server,
384281806Srpaulo				       data->id_peer, data->id_peer_len,
385281806Srpaulo				       data->id_server, data->id_server_len,
386281806Srpaulo				       EAP_TYPE_GPSK,
387281806Srpaulo				       data->session_id, &data->id_len) < 0) {
388281806Srpaulo		wpa_printf(MSG_DEBUG, "EAP-GPSK: Failed to derive Session-Id");
389281806Srpaulo		eap_gpsk_state(data, FAILURE);
390281806Srpaulo		wpabuf_free(resp);
391281806Srpaulo		return NULL;
392281806Srpaulo	}
393281806Srpaulo	wpa_hexdump(MSG_DEBUG, "EAP-GPSK: Derived Session-Id",
394281806Srpaulo		    data->session_id, data->id_len);
395281806Srpaulo
396189251Ssam	/* No PD_Payload_1 */
397189251Ssam	wpabuf_put_be16(resp, 0);
398189251Ssam
399189251Ssam	rpos = wpabuf_put(resp, miclen);
400189251Ssam	if (eap_gpsk_compute_mic(data->sk, data->sk_len, data->vendor,
401189251Ssam				 data->specifier, start, rpos - start, rpos) <
402189251Ssam	    0) {
403189251Ssam		eap_gpsk_state(data, FAILURE);
404189251Ssam		wpabuf_free(resp);
405189251Ssam		return NULL;
406189251Ssam	}
407189251Ssam
408189251Ssam	return resp;
409189251Ssam}
410189251Ssam
411189251Ssam
412189251Ssamstatic const u8 * eap_gpsk_validate_rand(struct eap_gpsk_data *data,
413189251Ssam					 const u8 *pos, const u8 *end)
414189251Ssam{
415189251Ssam	if (end - pos < EAP_GPSK_RAND_LEN) {
416189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Message too short for "
417189251Ssam			   "RAND_Peer");
418189251Ssam		return NULL;
419189251Ssam	}
420189251Ssam	if (os_memcmp(pos, data->rand_peer, EAP_GPSK_RAND_LEN) != 0) {
421189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: RAND_Peer in GPSK-2 and "
422189251Ssam			   "GPSK-3 did not match");
423189251Ssam		wpa_hexdump(MSG_DEBUG, "EAP-GPSK: RAND_Peer in GPSK-2",
424189251Ssam			    data->rand_peer, EAP_GPSK_RAND_LEN);
425189251Ssam		wpa_hexdump(MSG_DEBUG, "EAP-GPSK: RAND_Peer in GPSK-3",
426189251Ssam			    pos, EAP_GPSK_RAND_LEN);
427189251Ssam		return NULL;
428189251Ssam	}
429189251Ssam	pos += EAP_GPSK_RAND_LEN;
430189251Ssam
431189251Ssam	if (end - pos < EAP_GPSK_RAND_LEN) {
432189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Message too short for "
433189251Ssam			   "RAND_Server");
434189251Ssam		return NULL;
435189251Ssam	}
436189251Ssam	if (os_memcmp(pos, data->rand_server, EAP_GPSK_RAND_LEN) != 0) {
437189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: RAND_Server in GPSK-1 and "
438189251Ssam			   "GPSK-3 did not match");
439189251Ssam		wpa_hexdump(MSG_DEBUG, "EAP-GPSK: RAND_Server in GPSK-1",
440189251Ssam			    data->rand_server, EAP_GPSK_RAND_LEN);
441189251Ssam		wpa_hexdump(MSG_DEBUG, "EAP-GPSK: RAND_Server in GPSK-3",
442189251Ssam			    pos, EAP_GPSK_RAND_LEN);
443189251Ssam		return NULL;
444189251Ssam	}
445189251Ssam	pos += EAP_GPSK_RAND_LEN;
446189251Ssam
447189251Ssam	return pos;
448189251Ssam}
449189251Ssam
450189251Ssam
451189251Ssamstatic const u8 * eap_gpsk_validate_id_server(struct eap_gpsk_data *data,
452189251Ssam					      const u8 *pos, const u8 *end)
453189251Ssam{
454189251Ssam	size_t len;
455189251Ssam
456189251Ssam	if (pos == NULL)
457189251Ssam		return NULL;
458189251Ssam
459189251Ssam	if (end - pos < (int) 2) {
460189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Message too short for "
461189251Ssam			   "length(ID_Server)");
462189251Ssam		return NULL;
463189251Ssam	}
464189251Ssam
465189251Ssam	len = WPA_GET_BE16(pos);
466189251Ssam	pos += 2;
467189251Ssam
468189251Ssam	if (end - pos < (int) len) {
469189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Message too short for "
470189251Ssam			   "ID_Server");
471189251Ssam		return NULL;
472189251Ssam	}
473189251Ssam
474189251Ssam	if (len != data->id_server_len ||
475189251Ssam	    os_memcmp(pos, data->id_server, len) != 0) {
476189251Ssam		wpa_printf(MSG_INFO, "EAP-GPSK: ID_Server did not match with "
477189251Ssam			   "the one used in GPSK-1");
478189251Ssam		wpa_hexdump_ascii(MSG_DEBUG, "EAP-GPSK: ID_Server in GPSK-1",
479189251Ssam				  data->id_server, data->id_server_len);
480189251Ssam		wpa_hexdump_ascii(MSG_DEBUG, "EAP-GPSK: ID_Server in GPSK-3",
481189251Ssam				  pos, len);
482189251Ssam		return NULL;
483189251Ssam	}
484189251Ssam
485189251Ssam	pos += len;
486189251Ssam
487189251Ssam	return pos;
488189251Ssam}
489189251Ssam
490189251Ssam
491189251Ssamstatic const u8 * eap_gpsk_validate_csuite(struct eap_gpsk_data *data,
492189251Ssam					   const u8 *pos, const u8 *end)
493189251Ssam{
494189251Ssam	int vendor, specifier;
495189251Ssam	const struct eap_gpsk_csuite *csuite;
496189251Ssam
497189251Ssam	if (pos == NULL)
498189251Ssam		return NULL;
499189251Ssam
500189251Ssam	if (end - pos < (int) sizeof(*csuite)) {
501189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Message too short for "
502189251Ssam			   "CSuite_Sel");
503189251Ssam		return NULL;
504189251Ssam	}
505189251Ssam	csuite = (const struct eap_gpsk_csuite *) pos;
506189251Ssam	vendor = WPA_GET_BE32(csuite->vendor);
507189251Ssam	specifier = WPA_GET_BE16(csuite->specifier);
508189251Ssam	pos += sizeof(*csuite);
509189251Ssam	if (vendor != data->vendor || specifier != data->specifier) {
510189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: CSuite_Sel (%d:%d) does not "
511189251Ssam			   "match with the one sent in GPSK-2 (%d:%d)",
512189251Ssam			   vendor, specifier, data->vendor, data->specifier);
513189251Ssam		return NULL;
514189251Ssam	}
515189251Ssam
516189251Ssam	return pos;
517189251Ssam}
518189251Ssam
519189251Ssam
520189251Ssamstatic const u8 * eap_gpsk_validate_pd_payload_2(struct eap_gpsk_data *data,
521189251Ssam						 const u8 *pos, const u8 *end)
522189251Ssam{
523189251Ssam	u16 alen;
524189251Ssam
525189251Ssam	if (pos == NULL)
526189251Ssam		return NULL;
527189251Ssam
528189251Ssam	if (end - pos < 2) {
529189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Message too short for "
530189251Ssam			   "PD_Payload_2 length");
531189251Ssam		return NULL;
532189251Ssam	}
533189251Ssam	alen = WPA_GET_BE16(pos);
534189251Ssam	pos += 2;
535189251Ssam	if (end - pos < alen) {
536189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Message too short for "
537189251Ssam			   "%d-octet PD_Payload_2", alen);
538189251Ssam		return NULL;
539189251Ssam	}
540189251Ssam	wpa_hexdump(MSG_DEBUG, "EAP-GPSK: PD_Payload_2", pos, alen);
541189251Ssam	pos += alen;
542189251Ssam
543189251Ssam	return pos;
544189251Ssam}
545189251Ssam
546189251Ssam
547189251Ssamstatic const u8 * eap_gpsk_validate_gpsk_3_mic(struct eap_gpsk_data *data,
548189251Ssam					       const u8 *payload,
549189251Ssam					       const u8 *pos, const u8 *end)
550189251Ssam{
551189251Ssam	size_t miclen;
552189251Ssam	u8 mic[EAP_GPSK_MAX_MIC_LEN];
553189251Ssam
554189251Ssam	if (pos == NULL)
555189251Ssam		return NULL;
556189251Ssam
557189251Ssam	miclen = eap_gpsk_mic_len(data->vendor, data->specifier);
558189251Ssam	if (end - pos < (int) miclen) {
559189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Message too short for MIC "
560189251Ssam			   "(left=%lu miclen=%lu)",
561189251Ssam			   (unsigned long) (end - pos),
562189251Ssam			   (unsigned long) miclen);
563189251Ssam		return NULL;
564189251Ssam	}
565189251Ssam	if (eap_gpsk_compute_mic(data->sk, data->sk_len, data->vendor,
566189251Ssam				 data->specifier, payload, pos - payload, mic)
567189251Ssam	    < 0) {
568189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Failed to compute MIC");
569189251Ssam		return NULL;
570189251Ssam	}
571281806Srpaulo	if (os_memcmp_const(mic, pos, miclen) != 0) {
572189251Ssam		wpa_printf(MSG_INFO, "EAP-GPSK: Incorrect MIC in GPSK-3");
573189251Ssam		wpa_hexdump(MSG_DEBUG, "EAP-GPSK: Received MIC", pos, miclen);
574189251Ssam		wpa_hexdump(MSG_DEBUG, "EAP-GPSK: Computed MIC", mic, miclen);
575189251Ssam		return NULL;
576189251Ssam	}
577189251Ssam	pos += miclen;
578189251Ssam
579189251Ssam	return pos;
580189251Ssam}
581189251Ssam
582189251Ssam
583189251Ssamstatic struct wpabuf * eap_gpsk_process_gpsk_3(struct eap_sm *sm,
584189251Ssam					       struct eap_gpsk_data *data,
585189251Ssam					       struct eap_method_ret *ret,
586189251Ssam					       const struct wpabuf *reqData,
587189251Ssam					       const u8 *payload,
588189251Ssam					       size_t payload_len)
589189251Ssam{
590189251Ssam	struct wpabuf *resp;
591189251Ssam	const u8 *pos, *end;
592189251Ssam
593189251Ssam	if (data->state != GPSK_3) {
594189251Ssam		ret->ignore = TRUE;
595189251Ssam		return NULL;
596189251Ssam	}
597189251Ssam
598189251Ssam	wpa_printf(MSG_DEBUG, "EAP-GPSK: Received Request/GPSK-3");
599189251Ssam
600189251Ssam	end = payload + payload_len;
601189251Ssam
602189251Ssam	pos = eap_gpsk_validate_rand(data, payload, end);
603189251Ssam	pos = eap_gpsk_validate_id_server(data, pos, end);
604189251Ssam	pos = eap_gpsk_validate_csuite(data, pos, end);
605189251Ssam	pos = eap_gpsk_validate_pd_payload_2(data, pos, end);
606189251Ssam	pos = eap_gpsk_validate_gpsk_3_mic(data, payload, pos, end);
607189251Ssam
608189251Ssam	if (pos == NULL) {
609189251Ssam		eap_gpsk_state(data, FAILURE);
610189251Ssam		return NULL;
611189251Ssam	}
612189251Ssam	if (pos != end) {
613189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Ignored %lu bytes of extra "
614189251Ssam			   "data in the end of GPSK-2",
615189251Ssam			   (unsigned long) (end - pos));
616189251Ssam	}
617189251Ssam
618189251Ssam	resp = eap_gpsk_send_gpsk_4(data, eap_get_id(reqData));
619189251Ssam	if (resp == NULL)
620189251Ssam		return NULL;
621189251Ssam
622189251Ssam	eap_gpsk_state(data, SUCCESS);
623189251Ssam	ret->methodState = METHOD_DONE;
624189251Ssam	ret->decision = DECISION_UNCOND_SUCC;
625189251Ssam
626189251Ssam	return resp;
627189251Ssam}
628189251Ssam
629189251Ssam
630189251Ssamstatic struct wpabuf * eap_gpsk_send_gpsk_4(struct eap_gpsk_data *data,
631189251Ssam					    u8 identifier)
632189251Ssam{
633189251Ssam	struct wpabuf *resp;
634189251Ssam	u8 *rpos, *start;
635189251Ssam	size_t mlen;
636189251Ssam
637189251Ssam	wpa_printf(MSG_DEBUG, "EAP-GPSK: Sending Response/GPSK-4");
638189251Ssam
639189251Ssam	mlen = eap_gpsk_mic_len(data->vendor, data->specifier);
640189251Ssam
641189251Ssam	resp = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_GPSK, 1 + 2 + mlen,
642189251Ssam			     EAP_CODE_RESPONSE, identifier);
643189251Ssam	if (resp == NULL)
644189251Ssam		return NULL;
645189251Ssam
646189251Ssam	wpabuf_put_u8(resp, EAP_GPSK_OPCODE_GPSK_4);
647189251Ssam	start = wpabuf_put(resp, 0);
648189251Ssam
649189251Ssam	/* No PD_Payload_3 */
650189251Ssam	wpabuf_put_be16(resp, 0);
651189251Ssam
652189251Ssam	rpos = wpabuf_put(resp, mlen);
653189251Ssam	if (eap_gpsk_compute_mic(data->sk, data->sk_len, data->vendor,
654189251Ssam				 data->specifier, start, rpos - start, rpos) <
655189251Ssam	    0) {
656189251Ssam		eap_gpsk_state(data, FAILURE);
657189251Ssam		wpabuf_free(resp);
658189251Ssam		return NULL;
659189251Ssam	}
660189251Ssam
661189251Ssam	return resp;
662189251Ssam}
663189251Ssam
664189251Ssam
665189251Ssamstatic struct wpabuf * eap_gpsk_process(struct eap_sm *sm, void *priv,
666189251Ssam					struct eap_method_ret *ret,
667189251Ssam					const struct wpabuf *reqData)
668189251Ssam{
669189251Ssam	struct eap_gpsk_data *data = priv;
670189251Ssam	struct wpabuf *resp;
671189251Ssam	const u8 *pos;
672189251Ssam	size_t len;
673189251Ssam
674189251Ssam	pos = eap_hdr_validate(EAP_VENDOR_IETF, EAP_TYPE_GPSK, reqData, &len);
675189251Ssam	if (pos == NULL || len < 1) {
676189251Ssam		ret->ignore = TRUE;
677189251Ssam		return NULL;
678189251Ssam	}
679189251Ssam
680189251Ssam	wpa_printf(MSG_DEBUG, "EAP-GPSK: Received frame: opcode %d", *pos);
681189251Ssam
682189251Ssam	ret->ignore = FALSE;
683189251Ssam	ret->methodState = METHOD_MAY_CONT;
684189251Ssam	ret->decision = DECISION_FAIL;
685189251Ssam	ret->allowNotifications = FALSE;
686189251Ssam
687189251Ssam	switch (*pos) {
688189251Ssam	case EAP_GPSK_OPCODE_GPSK_1:
689189251Ssam		resp = eap_gpsk_process_gpsk_1(sm, data, ret, reqData,
690189251Ssam					       pos + 1, len - 1);
691189251Ssam		break;
692189251Ssam	case EAP_GPSK_OPCODE_GPSK_3:
693189251Ssam		resp = eap_gpsk_process_gpsk_3(sm, data, ret, reqData,
694189251Ssam					       pos + 1, len - 1);
695189251Ssam		break;
696189251Ssam	default:
697189251Ssam		wpa_printf(MSG_DEBUG, "EAP-GPSK: Ignoring message with "
698189251Ssam			   "unknown opcode %d", *pos);
699189251Ssam		ret->ignore = TRUE;
700189251Ssam		return NULL;
701189251Ssam	}
702189251Ssam
703189251Ssam	return resp;
704189251Ssam}
705189251Ssam
706189251Ssam
707189251Ssamstatic Boolean eap_gpsk_isKeyAvailable(struct eap_sm *sm, void *priv)
708189251Ssam{
709189251Ssam	struct eap_gpsk_data *data = priv;
710189251Ssam	return data->state == SUCCESS;
711189251Ssam}
712189251Ssam
713189251Ssam
714189251Ssamstatic u8 * eap_gpsk_getKey(struct eap_sm *sm, void *priv, size_t *len)
715189251Ssam{
716189251Ssam	struct eap_gpsk_data *data = priv;
717189251Ssam	u8 *key;
718189251Ssam
719189251Ssam	if (data->state != SUCCESS)
720189251Ssam		return NULL;
721189251Ssam
722189251Ssam	key = os_malloc(EAP_MSK_LEN);
723189251Ssam	if (key == NULL)
724189251Ssam		return NULL;
725189251Ssam	os_memcpy(key, data->msk, EAP_MSK_LEN);
726189251Ssam	*len = EAP_MSK_LEN;
727189251Ssam
728189251Ssam	return key;
729189251Ssam}
730189251Ssam
731189251Ssam
732189251Ssamstatic u8 * eap_gpsk_get_emsk(struct eap_sm *sm, void *priv, size_t *len)
733189251Ssam{
734189251Ssam	struct eap_gpsk_data *data = priv;
735189251Ssam	u8 *key;
736189251Ssam
737189251Ssam	if (data->state != SUCCESS)
738189251Ssam		return NULL;
739189251Ssam
740189251Ssam	key = os_malloc(EAP_EMSK_LEN);
741189251Ssam	if (key == NULL)
742189251Ssam		return NULL;
743189251Ssam	os_memcpy(key, data->emsk, EAP_EMSK_LEN);
744189251Ssam	*len = EAP_EMSK_LEN;
745189251Ssam
746189251Ssam	return key;
747189251Ssam}
748189251Ssam
749189251Ssam
750281806Srpaulostatic u8 * eap_gpsk_get_session_id(struct eap_sm *sm, void *priv, size_t *len)
751281806Srpaulo{
752281806Srpaulo	struct eap_gpsk_data *data = priv;
753281806Srpaulo	u8 *sid;
754281806Srpaulo
755281806Srpaulo	if (data->state != SUCCESS)
756281806Srpaulo		return NULL;
757281806Srpaulo
758281806Srpaulo	sid = os_malloc(data->id_len);
759281806Srpaulo	if (sid == NULL)
760281806Srpaulo		return NULL;
761281806Srpaulo	os_memcpy(sid, data->session_id, data->id_len);
762281806Srpaulo	*len = data->id_len;
763281806Srpaulo
764281806Srpaulo	return sid;
765281806Srpaulo}
766281806Srpaulo
767281806Srpaulo
768189251Ssamint eap_peer_gpsk_register(void)
769189251Ssam{
770189251Ssam	struct eap_method *eap;
771189251Ssam	int ret;
772189251Ssam
773189251Ssam	eap = eap_peer_method_alloc(EAP_PEER_METHOD_INTERFACE_VERSION,
774189251Ssam				    EAP_VENDOR_IETF, EAP_TYPE_GPSK, "GPSK");
775189251Ssam	if (eap == NULL)
776189251Ssam		return -1;
777189251Ssam
778189251Ssam	eap->init = eap_gpsk_init;
779189251Ssam	eap->deinit = eap_gpsk_deinit;
780189251Ssam	eap->process = eap_gpsk_process;
781189251Ssam	eap->isKeyAvailable = eap_gpsk_isKeyAvailable;
782189251Ssam	eap->getKey = eap_gpsk_getKey;
783189251Ssam	eap->get_emsk = eap_gpsk_get_emsk;
784281806Srpaulo	eap->getSessionId = eap_gpsk_get_session_id;
785189251Ssam
786189251Ssam	ret = eap_peer_method_register(eap);
787189251Ssam	if (ret)
788189251Ssam		eap_peer_method_free(eap);
789189251Ssam	return ret;
790189251Ssam}
791