]>
Commit | Line | Data |
---|---|---|
ad08c363 JM |
1 | /* |
2 | * EAP-WSC peer for Wi-Fi Protected Setup | |
52eb293d | 3 | * Copyright (c) 2007-2009, Jouni Malinen <j@w1.fi> |
ad08c363 JM |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License version 2 as | |
7 | * published by the Free Software Foundation. | |
8 | * | |
9 | * Alternatively, this software may be distributed under the terms of BSD | |
10 | * license. | |
11 | * | |
12 | * See README and COPYING for more details. | |
13 | */ | |
14 | ||
15 | #include "includes.h" | |
16 | ||
17 | #include "common.h" | |
18 | #include "uuid.h" | |
19 | #include "eap_i.h" | |
20 | #include "eap_common/eap_wsc_common.h" | |
21 | #include "wps/wps.h" | |
22 | #include "wps/wps_defs.h" | |
23 | ||
24 | ||
25 | struct eap_wsc_data { | |
47ce4bfc | 26 | enum { WAIT_START, MESG, FRAG_ACK, WAIT_FRAG_ACK, DONE, FAIL } state; |
ad08c363 JM |
27 | int registrar; |
28 | struct wpabuf *in_buf; | |
29 | struct wpabuf *out_buf; | |
f90c86d4 | 30 | enum wsc_op_code in_op_code, out_op_code; |
ad08c363 JM |
31 | size_t out_used; |
32 | size_t fragment_size; | |
33 | struct wps_data *wps; | |
34 | struct wps_context *wps_ctx; | |
35 | }; | |
36 | ||
37 | ||
38 | static const char * eap_wsc_state_txt(int state) | |
39 | { | |
40 | switch (state) { | |
41 | case WAIT_START: | |
42 | return "WAIT_START"; | |
47ce4bfc JM |
43 | case MESG: |
44 | return "MESG"; | |
ad08c363 JM |
45 | case FRAG_ACK: |
46 | return "FRAG_ACK"; | |
47 | case WAIT_FRAG_ACK: | |
48 | return "WAIT_FRAG_ACK"; | |
49 | case DONE: | |
50 | return "DONE"; | |
51 | case FAIL: | |
52 | return "FAIL"; | |
53 | default: | |
54 | return "?"; | |
55 | } | |
56 | } | |
57 | ||
58 | ||
59 | static void eap_wsc_state(struct eap_wsc_data *data, int state) | |
60 | { | |
61 | wpa_printf(MSG_DEBUG, "EAP-WSC: %s -> %s", | |
62 | eap_wsc_state_txt(data->state), | |
63 | eap_wsc_state_txt(state)); | |
64 | data->state = state; | |
65 | } | |
66 | ||
67 | ||
52eb293d JM |
68 | static int eap_wsc_new_ap_settings(struct wps_credential *cred, |
69 | const char *params) | |
70 | { | |
71 | const char *pos, *end; | |
72 | size_t len; | |
73 | ||
74 | os_memset(cred, 0, sizeof(*cred)); | |
75 | ||
76 | pos = os_strstr(params, "new_ssid="); | |
77 | if (pos == NULL) | |
78 | return 0; | |
79 | pos += 9; | |
80 | end = os_strchr(pos, ' '); | |
81 | if (end == NULL) | |
82 | len = os_strlen(pos); | |
83 | else | |
84 | len = end - pos; | |
85 | if ((len & 1) || len > 2 * sizeof(cred->ssid) || | |
86 | hexstr2bin(pos, cred->ssid, len / 2)) | |
87 | return -1; | |
88 | cred->ssid_len = len / 2; | |
89 | ||
90 | pos = os_strstr(params, "new_auth="); | |
91 | if (pos == NULL) | |
92 | return -1; | |
93 | if (os_strncmp(pos + 9, "OPEN", 4) == 0) | |
94 | cred->auth_type = WPS_AUTH_OPEN; | |
95 | else if (os_strncmp(pos + 9, "WPAPSK", 6) == 0) | |
96 | cred->auth_type = WPS_AUTH_WPAPSK; | |
97 | else if (os_strncmp(pos + 9, "WPA2PSK", 7) == 0) | |
98 | cred->auth_type = WPS_AUTH_WPA2PSK; | |
99 | else | |
100 | return -1; | |
101 | ||
102 | pos = os_strstr(params, "new_encr="); | |
103 | if (pos == NULL) | |
104 | return -1; | |
105 | if (os_strncmp(pos + 9, "NONE", 4) == 0) | |
106 | cred->encr_type = WPS_ENCR_NONE; | |
107 | else if (os_strncmp(pos + 9, "WEP", 3) == 0) | |
108 | cred->encr_type = WPS_ENCR_WEP; | |
109 | else if (os_strncmp(pos + 9, "TKIP", 4) == 0) | |
110 | cred->encr_type = WPS_ENCR_TKIP; | |
111 | else if (os_strncmp(pos + 9, "CCMP", 4) == 0) | |
112 | cred->encr_type = WPS_ENCR_AES; | |
113 | else | |
114 | return -1; | |
115 | ||
116 | pos = os_strstr(params, "new_key="); | |
117 | if (pos == NULL) | |
118 | return 0; | |
119 | pos += 8; | |
120 | end = os_strchr(pos, ' '); | |
121 | if (end == NULL) | |
122 | len = os_strlen(pos); | |
123 | else | |
124 | len = end - pos; | |
125 | if ((len & 1) || len > 2 * sizeof(cred->key) || | |
126 | hexstr2bin(pos, cred->key, len / 2)) | |
127 | return -1; | |
128 | cred->key_len = len / 2; | |
129 | ||
130 | return 1; | |
131 | } | |
132 | ||
133 | ||
ad08c363 JM |
134 | static void * eap_wsc_init(struct eap_sm *sm) |
135 | { | |
136 | struct eap_wsc_data *data; | |
137 | const u8 *identity; | |
138 | size_t identity_len; | |
139 | int registrar; | |
140 | struct wps_config cfg; | |
ad08c363 JM |
141 | const char *pos; |
142 | const char *phase1; | |
116654ce | 143 | struct wps_context *wps; |
52eb293d JM |
144 | struct wps_credential new_ap_settings; |
145 | int res; | |
116654ce JM |
146 | |
147 | wps = sm->wps; | |
148 | if (wps == NULL) { | |
149 | wpa_printf(MSG_ERROR, "EAP-WSC: WPS context not available"); | |
150 | return NULL; | |
151 | } | |
ad08c363 JM |
152 | |
153 | identity = eap_get_config_identity(sm, &identity_len); | |
154 | ||
155 | if (identity && identity_len == WSC_ID_REGISTRAR_LEN && | |
156 | os_memcmp(identity, WSC_ID_REGISTRAR, WSC_ID_REGISTRAR_LEN) == 0) | |
157 | registrar = 1; /* Supplicant is Registrar */ | |
158 | else if (identity && identity_len == WSC_ID_ENROLLEE_LEN && | |
159 | os_memcmp(identity, WSC_ID_ENROLLEE, WSC_ID_ENROLLEE_LEN) == 0) | |
160 | registrar = 0; /* Supplicant is Enrollee */ | |
161 | else { | |
162 | wpa_hexdump_ascii(MSG_INFO, "EAP-WSC: Unexpected identity", | |
163 | identity, identity_len); | |
164 | return NULL; | |
165 | } | |
166 | ||
167 | data = os_zalloc(sizeof(*data)); | |
168 | if (data == NULL) | |
169 | return NULL; | |
47ce4bfc | 170 | data->state = registrar ? MESG : WAIT_START; |
ad08c363 | 171 | data->registrar = registrar; |
ff434cbd | 172 | data->wps_ctx = wps; |
ad08c363 | 173 | |
ad08c363 | 174 | os_memset(&cfg, 0, sizeof(cfg)); |
ad08c363 | 175 | cfg.wps = wps; |
41c00105 | 176 | cfg.registrar = registrar; |
ad08c363 JM |
177 | |
178 | phase1 = eap_get_config_phase1(sm); | |
179 | if (phase1 == NULL) { | |
180 | wpa_printf(MSG_INFO, "EAP-WSC: phase1 configuration data not " | |
181 | "set"); | |
182 | os_free(data); | |
183 | return NULL; | |
184 | } | |
185 | ||
186 | pos = os_strstr(phase1, "pin="); | |
187 | if (pos) { | |
188 | pos += 4; | |
189 | cfg.pin = (const u8 *) pos; | |
190 | while (*pos != '\0' && *pos != ' ') | |
191 | pos++; | |
192 | cfg.pin_len = pos - (const char *) cfg.pin; | |
193 | } else { | |
194 | pos = os_strstr(phase1, "pbc=1"); | |
195 | if (pos) | |
196 | cfg.pbc = 1; | |
197 | } | |
198 | ||
199 | if (cfg.pin == NULL && !cfg.pbc) { | |
200 | wpa_printf(MSG_INFO, "EAP-WSC: PIN or PBC not set in phase1 " | |
201 | "configuration data"); | |
202 | os_free(data); | |
203 | return NULL; | |
204 | } | |
205 | ||
52eb293d JM |
206 | res = eap_wsc_new_ap_settings(&new_ap_settings, phase1); |
207 | if (res < 0) { | |
208 | os_free(data); | |
209 | return NULL; | |
210 | } | |
211 | if (res == 1) { | |
212 | wpa_printf(MSG_DEBUG, "EAP-WSC: Provide new AP settings for " | |
213 | "WPS"); | |
214 | cfg.new_ap_settings = &new_ap_settings; | |
215 | } | |
216 | ||
ad08c363 JM |
217 | data->wps = wps_init(&cfg); |
218 | if (data->wps == NULL) { | |
219 | os_free(data); | |
220 | return NULL; | |
221 | } | |
f3a3e698 JM |
222 | res = eap_get_config_fragment_size(sm); |
223 | if (res > 0) | |
224 | data->fragment_size = res; | |
225 | else | |
226 | data->fragment_size = WSC_FRAGMENT_SIZE; | |
ad08c363 | 227 | |
d6e0ce9a | 228 | if (registrar && cfg.pin) { |
31fcea93 | 229 | wps_registrar_add_pin(data->wps_ctx->registrar, NULL, NULL, |
077a781f | 230 | cfg.pin, cfg.pin_len, 0); |
ad08c363 JM |
231 | } |
232 | ||
9301f651 JM |
233 | /* Use reduced client timeout for WPS to avoid long wait */ |
234 | if (sm->ClientTimeout > 30) | |
235 | sm->ClientTimeout = 30; | |
236 | ||
ad08c363 JM |
237 | return data; |
238 | } | |
239 | ||
240 | ||
241 | static void eap_wsc_deinit(struct eap_sm *sm, void *priv) | |
242 | { | |
243 | struct eap_wsc_data *data = priv; | |
244 | wpabuf_free(data->in_buf); | |
245 | wpabuf_free(data->out_buf); | |
246 | wps_deinit(data->wps); | |
116654ce JM |
247 | os_free(data->wps_ctx->network_key); |
248 | data->wps_ctx->network_key = NULL; | |
ad08c363 JM |
249 | os_free(data); |
250 | } | |
251 | ||
252 | ||
253 | static struct wpabuf * eap_wsc_build_msg(struct eap_wsc_data *data, | |
254 | struct eap_method_ret *ret, u8 id) | |
255 | { | |
256 | struct wpabuf *resp; | |
257 | u8 flags; | |
258 | size_t send_len, plen; | |
259 | ||
260 | ret->ignore = FALSE; | |
261 | wpa_printf(MSG_DEBUG, "EAP-WSC: Generating Response"); | |
262 | ret->allowNotifications = TRUE; | |
263 | ||
264 | flags = 0; | |
265 | send_len = wpabuf_len(data->out_buf) - data->out_used; | |
266 | if (2 + send_len > data->fragment_size) { | |
267 | send_len = data->fragment_size - 2; | |
268 | flags |= WSC_FLAGS_MF; | |
269 | if (data->out_used == 0) { | |
270 | flags |= WSC_FLAGS_LF; | |
271 | send_len -= 2; | |
272 | } | |
273 | } | |
274 | plen = 2 + send_len; | |
275 | if (flags & WSC_FLAGS_LF) | |
276 | plen += 2; | |
277 | resp = eap_msg_alloc(EAP_VENDOR_WFA, EAP_VENDOR_TYPE_WSC, plen, | |
278 | EAP_CODE_RESPONSE, id); | |
279 | if (resp == NULL) | |
280 | return NULL; | |
281 | ||
282 | wpabuf_put_u8(resp, data->out_op_code); /* Op-Code */ | |
283 | wpabuf_put_u8(resp, flags); /* Flags */ | |
284 | if (flags & WSC_FLAGS_LF) | |
285 | wpabuf_put_be16(resp, wpabuf_len(data->out_buf)); | |
286 | ||
287 | wpabuf_put_data(resp, wpabuf_head_u8(data->out_buf) + data->out_used, | |
288 | send_len); | |
289 | data->out_used += send_len; | |
290 | ||
291 | ret->methodState = METHOD_MAY_CONT; | |
292 | ret->decision = DECISION_FAIL; | |
293 | ||
294 | if (data->out_used == wpabuf_len(data->out_buf)) { | |
295 | wpa_printf(MSG_DEBUG, "EAP-WSC: Sending out %lu bytes " | |
296 | "(message sent completely)", | |
297 | (unsigned long) send_len); | |
298 | wpabuf_free(data->out_buf); | |
299 | data->out_buf = NULL; | |
300 | data->out_used = 0; | |
301 | if ((data->state == FAIL && data->out_op_code == WSC_ACK) || | |
302 | data->out_op_code == WSC_NACK || | |
303 | data->out_op_code == WSC_Done) { | |
304 | eap_wsc_state(data, FAIL); | |
305 | ret->methodState = METHOD_DONE; | |
306 | } else | |
47ce4bfc | 307 | eap_wsc_state(data, MESG); |
ad08c363 JM |
308 | } else { |
309 | wpa_printf(MSG_DEBUG, "EAP-WSC: Sending out %lu bytes " | |
310 | "(%lu more to send)", (unsigned long) send_len, | |
311 | (unsigned long) wpabuf_len(data->out_buf) - | |
312 | data->out_used); | |
313 | eap_wsc_state(data, WAIT_FRAG_ACK); | |
314 | } | |
315 | ||
316 | return resp; | |
317 | } | |
318 | ||
319 | ||
320 | static int eap_wsc_process_cont(struct eap_wsc_data *data, | |
321 | const u8 *buf, size_t len, u8 op_code) | |
322 | { | |
323 | /* Process continuation of a pending message */ | |
324 | if (op_code != data->in_op_code) { | |
325 | wpa_printf(MSG_DEBUG, "EAP-WSC: Unexpected Op-Code %d in " | |
326 | "fragment (expected %d)", | |
327 | op_code, data->in_op_code); | |
328 | return -1; | |
329 | } | |
330 | ||
331 | if (len > wpabuf_tailroom(data->in_buf)) { | |
332 | wpa_printf(MSG_DEBUG, "EAP-WSC: Fragment overflow"); | |
333 | eap_wsc_state(data, FAIL); | |
334 | return -1; | |
335 | } | |
336 | ||
337 | wpabuf_put_data(data->in_buf, buf, len); | |
338 | wpa_printf(MSG_DEBUG, "EAP-WSC: Received %lu bytes, waiting " | |
339 | "for %lu bytes more", (unsigned long) len, | |
340 | (unsigned long) wpabuf_tailroom(data->in_buf)); | |
341 | ||
342 | return 0; | |
343 | } | |
344 | ||
345 | ||
346 | static struct wpabuf * eap_wsc_process_fragment(struct eap_wsc_data *data, | |
347 | struct eap_method_ret *ret, | |
348 | u8 id, u8 flags, u8 op_code, | |
349 | u16 message_length, | |
350 | const u8 *buf, size_t len) | |
351 | { | |
352 | /* Process a fragment that is not the last one of the message */ | |
353 | if (data->in_buf == NULL && !(flags & WSC_FLAGS_LF)) { | |
354 | wpa_printf(MSG_DEBUG, "EAP-WSC: No Message Length field in a " | |
355 | "fragmented packet"); | |
356 | ret->ignore = TRUE; | |
357 | return NULL; | |
358 | } | |
359 | ||
360 | if (data->in_buf == NULL) { | |
361 | /* First fragment of the message */ | |
362 | data->in_buf = wpabuf_alloc(message_length); | |
363 | if (data->in_buf == NULL) { | |
364 | wpa_printf(MSG_DEBUG, "EAP-WSC: No memory for " | |
365 | "message"); | |
366 | ret->ignore = TRUE; | |
367 | return NULL; | |
368 | } | |
369 | data->in_op_code = op_code; | |
370 | wpabuf_put_data(data->in_buf, buf, len); | |
371 | wpa_printf(MSG_DEBUG, "EAP-WSC: Received %lu bytes in first " | |
372 | "fragment, waiting for %lu bytes more", | |
373 | (unsigned long) len, | |
374 | (unsigned long) wpabuf_tailroom(data->in_buf)); | |
375 | } | |
376 | ||
377 | return eap_wsc_build_frag_ack(id, EAP_CODE_RESPONSE); | |
378 | } | |
379 | ||
380 | ||
381 | static struct wpabuf * eap_wsc_process(struct eap_sm *sm, void *priv, | |
382 | struct eap_method_ret *ret, | |
383 | const struct wpabuf *reqData) | |
384 | { | |
385 | struct eap_wsc_data *data = priv; | |
386 | const u8 *start, *pos, *end; | |
387 | size_t len; | |
388 | u8 op_code, flags, id; | |
389 | u16 message_length = 0; | |
390 | enum wps_process_res res; | |
391 | struct wpabuf tmpbuf; | |
9301f651 | 392 | struct wpabuf *r; |
ad08c363 JM |
393 | |
394 | pos = eap_hdr_validate(EAP_VENDOR_WFA, EAP_VENDOR_TYPE_WSC, reqData, | |
395 | &len); | |
396 | if (pos == NULL || len < 2) { | |
397 | ret->ignore = TRUE; | |
398 | return NULL; | |
399 | } | |
400 | ||
401 | id = eap_get_id(reqData); | |
402 | ||
403 | start = pos; | |
404 | end = start + len; | |
405 | ||
406 | op_code = *pos++; | |
407 | flags = *pos++; | |
408 | if (flags & WSC_FLAGS_LF) { | |
409 | if (end - pos < 2) { | |
410 | wpa_printf(MSG_DEBUG, "EAP-WSC: Message underflow"); | |
411 | ret->ignore = TRUE; | |
412 | return NULL; | |
413 | } | |
414 | message_length = WPA_GET_BE16(pos); | |
415 | pos += 2; | |
416 | ||
417 | if (message_length < end - pos) { | |
418 | wpa_printf(MSG_DEBUG, "EAP-WSC: Invalid Message " | |
419 | "Length"); | |
420 | ret->ignore = TRUE; | |
421 | return NULL; | |
422 | } | |
423 | } | |
424 | ||
425 | wpa_printf(MSG_DEBUG, "EAP-WSC: Received packet: Op-Code %d " | |
426 | "Flags 0x%x Message Length %d", | |
427 | op_code, flags, message_length); | |
428 | ||
429 | if (data->state == WAIT_FRAG_ACK) { | |
430 | if (op_code != WSC_FRAG_ACK) { | |
431 | wpa_printf(MSG_DEBUG, "EAP-WSC: Unexpected Op-Code %d " | |
432 | "in WAIT_FRAG_ACK state", op_code); | |
433 | ret->ignore = TRUE; | |
434 | return NULL; | |
435 | } | |
436 | wpa_printf(MSG_DEBUG, "EAP-WSC: Fragment acknowledged"); | |
47ce4bfc | 437 | eap_wsc_state(data, MESG); |
ad08c363 JM |
438 | return eap_wsc_build_msg(data, ret, id); |
439 | } | |
440 | ||
441 | if (op_code != WSC_ACK && op_code != WSC_NACK && op_code != WSC_MSG && | |
442 | op_code != WSC_Done && op_code != WSC_Start) { | |
443 | wpa_printf(MSG_DEBUG, "EAP-WSC: Unexpected Op-Code %d", | |
444 | op_code); | |
445 | ret->ignore = TRUE; | |
446 | return NULL; | |
447 | } | |
448 | ||
449 | if (data->state == WAIT_START) { | |
450 | if (op_code != WSC_Start) { | |
451 | wpa_printf(MSG_DEBUG, "EAP-WSC: Unexpected Op-Code %d " | |
452 | "in WAIT_START state", op_code); | |
453 | ret->ignore = TRUE; | |
454 | return NULL; | |
455 | } | |
456 | wpa_printf(MSG_DEBUG, "EAP-WSC: Received start"); | |
47ce4bfc | 457 | eap_wsc_state(data, MESG); |
ad08c363 JM |
458 | /* Start message has empty payload, skip processing */ |
459 | goto send_msg; | |
460 | } else if (op_code == WSC_Start) { | |
461 | wpa_printf(MSG_DEBUG, "EAP-WSC: Unexpected Op-Code %d", | |
462 | op_code); | |
463 | ret->ignore = TRUE; | |
464 | return NULL; | |
465 | } | |
466 | ||
467 | if (data->in_buf && | |
468 | eap_wsc_process_cont(data, pos, end - pos, op_code) < 0) { | |
469 | ret->ignore = TRUE; | |
470 | return NULL; | |
471 | } | |
472 | ||
473 | if (flags & WSC_FLAGS_MF) { | |
474 | return eap_wsc_process_fragment(data, ret, id, flags, op_code, | |
475 | message_length, pos, | |
476 | end - pos); | |
477 | } | |
478 | ||
479 | if (data->in_buf == NULL) { | |
480 | /* Wrap unfragmented messages as wpabuf without extra copy */ | |
481 | wpabuf_set(&tmpbuf, pos, end - pos); | |
482 | data->in_buf = &tmpbuf; | |
483 | } | |
484 | ||
485 | res = wps_process_msg(data->wps, op_code, data->in_buf); | |
486 | switch (res) { | |
487 | case WPS_DONE: | |
488 | wpa_printf(MSG_DEBUG, "EAP-WSC: WPS processing completed " | |
489 | "successfully - wait for EAP failure"); | |
490 | eap_wsc_state(data, FAIL); | |
491 | break; | |
492 | case WPS_CONTINUE: | |
47ce4bfc | 493 | eap_wsc_state(data, MESG); |
ad08c363 JM |
494 | break; |
495 | case WPS_FAILURE: | |
f620268f | 496 | case WPS_PENDING: |
ad08c363 JM |
497 | wpa_printf(MSG_DEBUG, "EAP-WSC: WPS processing failed"); |
498 | eap_wsc_state(data, FAIL); | |
499 | break; | |
ad08c363 JM |
500 | } |
501 | ||
502 | if (data->in_buf != &tmpbuf) | |
503 | wpabuf_free(data->in_buf); | |
504 | data->in_buf = NULL; | |
505 | ||
506 | send_msg: | |
507 | if (data->out_buf == NULL) { | |
508 | data->out_buf = wps_get_msg(data->wps, &data->out_op_code); | |
509 | if (data->out_buf == NULL) { | |
510 | wpa_printf(MSG_DEBUG, "EAP-WSC: Failed to receive " | |
511 | "message from WPS"); | |
512 | return NULL; | |
513 | } | |
514 | data->out_used = 0; | |
515 | } | |
516 | ||
47ce4bfc | 517 | eap_wsc_state(data, MESG); |
9301f651 JM |
518 | r = eap_wsc_build_msg(data, ret, id); |
519 | if (data->state == FAIL && ret->methodState == METHOD_DONE) { | |
520 | /* Use reduced client timeout for WPS to avoid long wait */ | |
521 | if (sm->ClientTimeout > 2) | |
522 | sm->ClientTimeout = 2; | |
523 | } | |
524 | return r; | |
ad08c363 JM |
525 | } |
526 | ||
527 | ||
528 | int eap_peer_wsc_register(void) | |
529 | { | |
530 | struct eap_method *eap; | |
531 | int ret; | |
532 | ||
533 | eap = eap_peer_method_alloc(EAP_PEER_METHOD_INTERFACE_VERSION, | |
534 | EAP_VENDOR_WFA, EAP_VENDOR_TYPE_WSC, | |
535 | "WSC"); | |
536 | if (eap == NULL) | |
537 | return -1; | |
538 | ||
539 | eap->init = eap_wsc_init; | |
540 | eap->deinit = eap_wsc_deinit; | |
541 | eap->process = eap_wsc_process; | |
542 | ||
543 | ret = eap_peer_method_register(eap); | |
544 | if (ret) | |
545 | eap_peer_method_free(eap); | |
546 | return ret; | |
547 | } |