2 * EAP peer method: EAP-FAST PAC file processing
3 * Copyright (c) 2004-2006, Jouni Malinen <j@w1.fi>
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.
9 * Alternatively, this software may be distributed under the terms of BSD
12 * See README and COPYING for more details.
18 #include "eap_config.h"
20 #include "eap_fast_pac.h"
22 /* TODO: encrypt PAC-Key in the PAC file */
25 /* Text data format */
26 static const char *pac_file_hdr
=
27 "wpa_supplicant EAP-FAST PAC file - version 1";
31 * 4-octet magic value: 6A E4 92 0C
32 * 2-octet version (big endian)
33 * <version specific data>
36 * Sequence of PAC entries:
37 * 2-octet PAC-Type (big endian)
39 * 2-octet PAC-Opaque length (big endian)
40 * <variable len> PAC-Opaque data (length bytes)
41 * 2-octet PAC-Info length (big endian)
42 * <variable len> PAC-Info data (length bytes)
45 #define EAP_FAST_PAC_BINARY_MAGIC 0x6ae4920c
46 #define EAP_FAST_PAC_BINARY_FORMAT_VERSION 0
50 * eap_fast_free_pac - Free PAC data
51 * @pac: Pointer to the PAC entry
53 * Note that the PAC entry must not be in a list since this function does not
54 * remove the list links.
56 void eap_fast_free_pac(struct eap_fast_pac
*pac
)
58 os_free(pac
->pac_opaque
);
59 os_free(pac
->pac_info
);
62 os_free(pac
->a_id_info
);
68 * eap_fast_get_pac - Get a PAC entry based on A-ID
69 * @pac_root: Pointer to root of the PAC list
70 * @a_id: A-ID to search for
71 * @a_id_len: Length of A-ID
72 * @pac_type: PAC-Type to search for
73 * Returns: Pointer to the PAC entry, or %NULL if A-ID not found
75 struct eap_fast_pac
* eap_fast_get_pac(struct eap_fast_pac
*pac_root
,
76 const u8
*a_id
, size_t a_id_len
,
79 struct eap_fast_pac
*pac
= pac_root
;
82 if (pac
->pac_type
== pac_type
&& pac
->a_id_len
== a_id_len
&&
83 os_memcmp(pac
->a_id
, a_id
, a_id_len
) == 0) {
92 static void eap_fast_remove_pac(struct eap_fast_pac
**pac_root
,
93 struct eap_fast_pac
**pac_current
,
94 const u8
*a_id
, size_t a_id_len
, u16 pac_type
)
96 struct eap_fast_pac
*pac
, *prev
;
102 if (pac
->pac_type
== pac_type
&& pac
->a_id_len
== a_id_len
&&
103 os_memcmp(pac
->a_id
, a_id
, a_id_len
) == 0) {
105 *pac_root
= pac
->next
;
107 prev
->next
= pac
->next
;
108 if (*pac_current
== pac
)
110 eap_fast_free_pac(pac
);
119 static int eap_fast_copy_buf(u8
**dst
, size_t *dst_len
,
120 const u8
*src
, size_t src_len
)
123 *dst
= os_malloc(src_len
);
126 os_memcpy(*dst
, src
, src_len
);
134 * eap_fast_add_pac - Add a copy of a PAC entry to a list
135 * @pac_root: Pointer to PAC list root pointer
136 * @pac_current: Pointer to the current PAC pointer
137 * @entry: New entry to clone and add to the list
138 * Returns: 0 on success, -1 on failure
140 * This function makes a clone of the given PAC entry and adds this copied
141 * entry to the list (pac_root). If an old entry for the same A-ID is found,
142 * it will be removed from the PAC list and in this case, pac_current entry
143 * is set to %NULL if it was the removed entry.
145 int eap_fast_add_pac(struct eap_fast_pac
**pac_root
,
146 struct eap_fast_pac
**pac_current
,
147 struct eap_fast_pac
*entry
)
149 struct eap_fast_pac
*pac
;
151 if (entry
== NULL
|| entry
->a_id
== NULL
)
154 /* Remove a possible old entry for the matching A-ID. */
155 eap_fast_remove_pac(pac_root
, pac_current
,
156 entry
->a_id
, entry
->a_id_len
, entry
->pac_type
);
158 /* Allocate a new entry and add it to the list of PACs. */
159 pac
= os_zalloc(sizeof(*pac
));
163 pac
->pac_type
= entry
->pac_type
;
164 os_memcpy(pac
->pac_key
, entry
->pac_key
, EAP_FAST_PAC_KEY_LEN
);
165 if (eap_fast_copy_buf(&pac
->pac_opaque
, &pac
->pac_opaque_len
,
166 entry
->pac_opaque
, entry
->pac_opaque_len
) < 0 ||
167 eap_fast_copy_buf(&pac
->pac_info
, &pac
->pac_info_len
,
168 entry
->pac_info
, entry
->pac_info_len
) < 0 ||
169 eap_fast_copy_buf(&pac
->a_id
, &pac
->a_id_len
,
170 entry
->a_id
, entry
->a_id_len
) < 0 ||
171 eap_fast_copy_buf(&pac
->i_id
, &pac
->i_id_len
,
172 entry
->i_id
, entry
->i_id_len
) < 0 ||
173 eap_fast_copy_buf(&pac
->a_id_info
, &pac
->a_id_info_len
,
174 entry
->a_id_info
, entry
->a_id_info_len
) < 0) {
175 eap_fast_free_pac(pac
);
179 pac
->next
= *pac_root
;
186 struct eap_fast_read_ctx
{
195 static int eap_fast_read_line(struct eap_fast_read_ctx
*rc
, char **value
)
201 if (fgets(rc
->buf
, rc
->buf_len
, rc
->f
) == NULL
)
206 if (rc
->pos
>= rc
->end
)
209 while (l_end
< rc
->end
&& *l_end
!= '\n')
211 len
= l_end
- rc
->pos
;
212 if (len
>= rc
->buf_len
)
213 len
= rc
->buf_len
- 1;
214 os_memcpy(rc
->buf
, rc
->pos
, len
);
219 rc
->buf
[rc
->buf_len
- 1] = '\0';
221 while (*pos
!= '\0') {
222 if (*pos
== '\n' || *pos
== '\r') {
229 pos
= os_strchr(rc
->buf
, '=');
238 static u8
* eap_fast_parse_hex(const char *value
, size_t *len
)
245 hlen
= os_strlen(value
);
249 buf
= os_malloc(*len
);
252 if (hexstr2bin(value
, buf
, *len
)) {
260 static int eap_fast_init_pac_data(struct eap_sm
*sm
, const char *pac_file
,
261 struct eap_fast_read_ctx
*rc
)
263 os_memset(rc
, 0, sizeof(*rc
));
266 rc
->buf
= os_malloc(rc
->buf_len
);
270 if (os_strncmp(pac_file
, "blob://", 7) == 0) {
271 const struct wpa_config_blob
*blob
;
272 blob
= eap_get_config_blob(sm
, pac_file
+ 7);
274 wpa_printf(MSG_INFO
, "EAP-FAST: No PAC blob '%s' - "
275 "assume no PAC entries have been "
276 "provisioned", pac_file
+ 7);
280 rc
->pos
= (char *) blob
->data
;
281 rc
->end
= (char *) blob
->data
+ blob
->len
;
283 rc
->f
= fopen(pac_file
, "rb");
285 wpa_printf(MSG_INFO
, "EAP-FAST: No PAC file '%s' - "
286 "assume no PAC entries have been "
287 "provisioned", pac_file
);
297 static void eap_fast_deinit_pac_data(struct eap_fast_read_ctx
*rc
)
305 static const char * eap_fast_parse_start(struct eap_fast_pac
**pac
)
308 return "START line without END";
310 *pac
= os_zalloc(sizeof(struct eap_fast_pac
));
312 return "No memory for PAC entry";
313 (*pac
)->pac_type
= PAC_TYPE_TUNNEL_PAC
;
318 static const char * eap_fast_parse_end(struct eap_fast_pac
**pac_root
,
319 struct eap_fast_pac
**pac
)
322 return "END line without START";
324 struct eap_fast_pac
*end
= *pac_root
;
336 static const char * eap_fast_parse_pac_type(struct eap_fast_pac
*pac
,
339 pac
->pac_type
= atoi(pos
);
340 if (pac
->pac_type
!= PAC_TYPE_TUNNEL_PAC
&&
341 pac
->pac_type
!= PAC_TYPE_USER_AUTHORIZATION
&&
342 pac
->pac_type
!= PAC_TYPE_MACHINE_AUTHENTICATION
)
343 return "Unrecognized PAC-Type";
349 static const char * eap_fast_parse_pac_key(struct eap_fast_pac
*pac
, char *pos
)
354 key
= eap_fast_parse_hex(pos
, &key_len
);
355 if (key
== NULL
|| key_len
!= EAP_FAST_PAC_KEY_LEN
) {
357 return "Invalid PAC-Key";
360 os_memcpy(pac
->pac_key
, key
, EAP_FAST_PAC_KEY_LEN
);
367 static const char * eap_fast_parse_pac_opaque(struct eap_fast_pac
*pac
,
370 os_free(pac
->pac_opaque
);
371 pac
->pac_opaque
= eap_fast_parse_hex(pos
, &pac
->pac_opaque_len
);
372 if (pac
->pac_opaque
== NULL
)
373 return "Invalid PAC-Opaque";
378 static const char * eap_fast_parse_a_id(struct eap_fast_pac
*pac
, char *pos
)
381 pac
->a_id
= eap_fast_parse_hex(pos
, &pac
->a_id_len
);
382 if (pac
->a_id
== NULL
)
383 return "Invalid A-ID";
388 static const char * eap_fast_parse_i_id(struct eap_fast_pac
*pac
, char *pos
)
391 pac
->i_id
= eap_fast_parse_hex(pos
, &pac
->i_id_len
);
392 if (pac
->i_id
== NULL
)
393 return "Invalid I-ID";
398 static const char * eap_fast_parse_a_id_info(struct eap_fast_pac
*pac
,
401 os_free(pac
->a_id_info
);
402 pac
->a_id_info
= eap_fast_parse_hex(pos
, &pac
->a_id_info_len
);
403 if (pac
->a_id_info
== NULL
)
404 return "Invalid A-ID-Info";
410 * eap_fast_load_pac - Load PAC entries (text format)
411 * @sm: Pointer to EAP state machine allocated with eap_peer_sm_init()
412 * @pac_root: Pointer to root of the PAC list (to be filled)
413 * @pac_file: Name of the PAC file/blob to load
414 * Returns: 0 on success, -1 on failure
416 int eap_fast_load_pac(struct eap_sm
*sm
, struct eap_fast_pac
**pac_root
,
417 const char *pac_file
)
419 struct eap_fast_read_ctx rc
;
420 struct eap_fast_pac
*pac
= NULL
;
423 const char *err
= NULL
;
425 if (pac_file
== NULL
)
428 if (eap_fast_init_pac_data(sm
, pac_file
, &rc
) < 0)
431 if (eap_fast_read_line(&rc
, &pos
) < 0 ||
432 os_strcmp(pac_file_hdr
, rc
.buf
) != 0)
433 err
= "Unrecognized header line";
435 while (!err
&& eap_fast_read_line(&rc
, &pos
) == 0) {
436 if (os_strcmp(rc
.buf
, "START") == 0)
437 err
= eap_fast_parse_start(&pac
);
438 else if (os_strcmp(rc
.buf
, "END") == 0) {
439 err
= eap_fast_parse_end(pac_root
, &pac
);
442 err
= "Unexpected line outside START/END block";
443 else if (os_strcmp(rc
.buf
, "PAC-Type") == 0)
444 err
= eap_fast_parse_pac_type(pac
, pos
);
445 else if (os_strcmp(rc
.buf
, "PAC-Key") == 0)
446 err
= eap_fast_parse_pac_key(pac
, pos
);
447 else if (os_strcmp(rc
.buf
, "PAC-Opaque") == 0)
448 err
= eap_fast_parse_pac_opaque(pac
, pos
);
449 else if (os_strcmp(rc
.buf
, "A-ID") == 0)
450 err
= eap_fast_parse_a_id(pac
, pos
);
451 else if (os_strcmp(rc
.buf
, "I-ID") == 0)
452 err
= eap_fast_parse_i_id(pac
, pos
);
453 else if (os_strcmp(rc
.buf
, "A-ID-Info") == 0)
454 err
= eap_fast_parse_a_id_info(pac
, pos
);
458 err
= "PAC block not terminated with END";
459 eap_fast_free_pac(pac
);
462 eap_fast_deinit_pac_data(&rc
);
465 wpa_printf(MSG_INFO
, "EAP-FAST: %s in '%s:%d'",
466 err
, pac_file
, rc
.line
);
470 wpa_printf(MSG_DEBUG
, "EAP-FAST: Read %d PAC entries from '%s'",
477 static void eap_fast_write(char **buf
, char **pos
, size_t *buf_len
,
478 const char *field
, const u8
*data
,
484 if (data
== NULL
|| *buf
== NULL
)
487 need
= os_strlen(field
) + len
* 2 + 30;
489 need
+= os_strlen(field
) + len
+ 20;
491 if (*pos
- *buf
+ need
> *buf_len
) {
492 char *nbuf
= os_realloc(*buf
, *buf_len
+ need
);
502 ret
= os_snprintf(*pos
, *buf
+ *buf_len
- *pos
, "%s=", field
);
503 if (ret
< 0 || ret
>= *buf
+ *buf_len
- *pos
)
506 *pos
+= wpa_snprintf_hex(*pos
, *buf
+ *buf_len
- *pos
, data
, len
);
507 ret
= os_snprintf(*pos
, *buf
+ *buf_len
- *pos
, "\n");
508 if (ret
< 0 || ret
>= *buf
+ *buf_len
- *pos
)
513 ret
= os_snprintf(*pos
, *buf
+ *buf_len
- *pos
,
515 if (ret
< 0 || ret
>= *buf
+ *buf_len
- *pos
)
518 for (i
= 0; i
< len
; i
++) {
519 ret
= os_snprintf(*pos
, *buf
+ *buf_len
- *pos
,
521 if (ret
< 0 || ret
>= *buf
+ *buf_len
- *pos
)
525 ret
= os_snprintf(*pos
, *buf
+ *buf_len
- *pos
, "\n");
526 if (ret
< 0 || ret
>= *buf
+ *buf_len
- *pos
)
533 static int eap_fast_write_pac(struct eap_sm
*sm
, const char *pac_file
,
534 char *buf
, size_t len
)
536 if (os_strncmp(pac_file
, "blob://", 7) == 0) {
537 struct wpa_config_blob
*blob
;
538 blob
= os_zalloc(sizeof(*blob
));
541 blob
->data
= (u8
*) buf
;
544 blob
->name
= os_strdup(pac_file
+ 7);
545 if (blob
->name
== NULL
) {
549 eap_set_config_blob(sm
, blob
);
552 f
= fopen(pac_file
, "wb");
554 wpa_printf(MSG_INFO
, "EAP-FAST: Failed to open PAC "
555 "file '%s' for writing", pac_file
);
558 if (fwrite(buf
, 1, len
, f
) != len
) {
559 wpa_printf(MSG_INFO
, "EAP-FAST: Failed to write all "
560 "PACs into '%s'", pac_file
);
572 static int eap_fast_add_pac_data(struct eap_fast_pac
*pac
, char **buf
,
573 char **pos
, size_t *buf_len
)
577 ret
= os_snprintf(*pos
, *buf
+ *buf_len
- *pos
,
578 "START\nPAC-Type=%d\n", pac
->pac_type
);
579 if (ret
< 0 || ret
>= *buf
+ *buf_len
- *pos
)
583 eap_fast_write(buf
, pos
, buf_len
, "PAC-Key",
584 pac
->pac_key
, EAP_FAST_PAC_KEY_LEN
, 0);
585 eap_fast_write(buf
, pos
, buf_len
, "PAC-Opaque",
586 pac
->pac_opaque
, pac
->pac_opaque_len
, 0);
587 eap_fast_write(buf
, pos
, buf_len
, "PAC-Info",
588 pac
->pac_info
, pac
->pac_info_len
, 0);
589 eap_fast_write(buf
, pos
, buf_len
, "A-ID",
590 pac
->a_id
, pac
->a_id_len
, 0);
591 eap_fast_write(buf
, pos
, buf_len
, "I-ID",
592 pac
->i_id
, pac
->i_id_len
, 1);
593 eap_fast_write(buf
, pos
, buf_len
, "A-ID-Info",
594 pac
->a_id_info
, pac
->a_id_info_len
, 1);
596 wpa_printf(MSG_DEBUG
, "EAP-FAST: No memory for PAC "
600 ret
= os_snprintf(*pos
, *buf
+ *buf_len
- *pos
, "END\n");
601 if (ret
< 0 || ret
>= *buf
+ *buf_len
- *pos
)
610 * eap_fast_save_pac - Save PAC entries (text format)
611 * @sm: Pointer to EAP state machine allocated with eap_peer_sm_init()
612 * @pac_root: Root of the PAC list
613 * @pac_file: Name of the PAC file/blob
614 * Returns: 0 on success, -1 on failure
616 int eap_fast_save_pac(struct eap_sm
*sm
, struct eap_fast_pac
*pac_root
,
617 const char *pac_file
)
619 struct eap_fast_pac
*pac
;
624 if (pac_file
== NULL
)
628 pos
= buf
= os_malloc(buf_len
);
632 ret
= os_snprintf(pos
, buf
+ buf_len
- pos
, "%s\n", pac_file_hdr
);
633 if (ret
< 0 || ret
>= buf
+ buf_len
- pos
) {
641 if (eap_fast_add_pac_data(pac
, &buf
, &pos
, &buf_len
)) {
649 if (eap_fast_write_pac(sm
, pac_file
, buf
, pos
- buf
)) {
654 wpa_printf(MSG_DEBUG
, "EAP-FAST: Wrote %d PAC entries into '%s'",
662 * eap_fast_pac_list_truncate - Truncate a PAC list to the given length
663 * @pac_root: Root of the PAC list
664 * @max_len: Maximum length of the list (>= 1)
665 * Returns: Number of PAC entries removed
667 size_t eap_fast_pac_list_truncate(struct eap_fast_pac
*pac_root
,
670 struct eap_fast_pac
*pac
, *prev
;
685 if (count
<= max_len
|| prev
== NULL
)
694 eap_fast_free_pac(prev
);
702 static void eap_fast_pac_get_a_id(struct eap_fast_pac
*pac
)
708 end
= pos
+ pac
->pac_info_len
;
710 while (pos
+ 4 < end
) {
711 type
= WPA_GET_BE16(pos
);
713 len
= WPA_GET_BE16(pos
);
718 if (type
== PAC_TYPE_A_ID
) {
720 pac
->a_id
= os_malloc(len
);
721 if (pac
->a_id
== NULL
)
723 os_memcpy(pac
->a_id
, pos
, len
);
727 if (type
== PAC_TYPE_A_ID_INFO
) {
728 os_free(pac
->a_id_info
);
729 pac
->a_id_info
= os_malloc(len
);
730 if (pac
->a_id_info
== NULL
)
732 os_memcpy(pac
->a_id_info
, pos
, len
);
733 pac
->a_id_info_len
= len
;
742 * eap_fast_load_pac_bin - Load PAC entries (binary format)
743 * @sm: Pointer to EAP state machine allocated with eap_peer_sm_init()
744 * @pac_root: Pointer to root of the PAC list (to be filled)
745 * @pac_file: Name of the PAC file/blob to load
746 * Returns: 0 on success, -1 on failure
748 int eap_fast_load_pac_bin(struct eap_sm
*sm
, struct eap_fast_pac
**pac_root
,
749 const char *pac_file
)
751 const struct wpa_config_blob
*blob
= NULL
;
753 size_t len
, count
= 0;
754 struct eap_fast_pac
*pac
, *prev
;
758 if (pac_file
== NULL
)
761 if (os_strncmp(pac_file
, "blob://", 7) == 0) {
762 blob
= eap_get_config_blob(sm
, pac_file
+ 7);
764 wpa_printf(MSG_INFO
, "EAP-FAST: No PAC blob '%s' - "
765 "assume no PAC entries have been "
766 "provisioned", pac_file
+ 7);
772 buf
= (u8
*) os_readfile(pac_file
, &len
);
774 wpa_printf(MSG_INFO
, "EAP-FAST: No PAC file '%s' - "
775 "assume no PAC entries have been "
776 "provisioned", pac_file
);
787 if (len
< 6 || WPA_GET_BE32(buf
) != EAP_FAST_PAC_BINARY_MAGIC
||
788 WPA_GET_BE16(buf
+ 4) != EAP_FAST_PAC_BINARY_FORMAT_VERSION
) {
789 wpa_printf(MSG_INFO
, "EAP-FAST: Invalid PAC file '%s' (bin)",
800 if (end
- pos
< 2 + 32 + 2 + 2)
803 pac
= os_zalloc(sizeof(*pac
));
807 pac
->pac_type
= WPA_GET_BE16(pos
);
809 os_memcpy(pac
->pac_key
, pos
, EAP_FAST_PAC_KEY_LEN
);
810 pos
+= EAP_FAST_PAC_KEY_LEN
;
811 pac
->pac_opaque_len
= WPA_GET_BE16(pos
);
813 if (pos
+ pac
->pac_opaque_len
+ 2 > end
)
815 pac
->pac_opaque
= os_malloc(pac
->pac_opaque_len
);
816 if (pac
->pac_opaque
== NULL
)
818 os_memcpy(pac
->pac_opaque
, pos
, pac
->pac_opaque_len
);
819 pos
+= pac
->pac_opaque_len
;
820 pac
->pac_info_len
= WPA_GET_BE16(pos
);
822 if (pos
+ pac
->pac_info_len
> end
)
824 pac
->pac_info
= os_malloc(pac
->pac_info_len
);
825 if (pac
->pac_info
== NULL
)
827 os_memcpy(pac
->pac_info
, pos
, pac
->pac_info_len
);
828 pos
+= pac
->pac_info_len
;
829 eap_fast_pac_get_a_id(pac
);
842 wpa_printf(MSG_DEBUG
, "EAP-FAST: Read %lu PAC entries from '%s' (bin)",
843 (unsigned long) count
, pac_file
);
848 wpa_printf(MSG_INFO
, "EAP-FAST: Failed to parse PAC file '%s' (bin)",
853 eap_fast_free_pac(pac
);
859 * eap_fast_save_pac_bin - Save PAC entries (binary format)
860 * @sm: Pointer to EAP state machine allocated with eap_peer_sm_init()
861 * @pac_root: Root of the PAC list
862 * @pac_file: Name of the PAC file/blob
863 * Returns: 0 on success, -1 on failure
865 int eap_fast_save_pac_bin(struct eap_sm
*sm
, struct eap_fast_pac
*pac_root
,
866 const char *pac_file
)
868 size_t len
, count
= 0;
869 struct eap_fast_pac
*pac
;
875 if (pac
->pac_opaque_len
> 65535 ||
876 pac
->pac_info_len
> 65535)
878 len
+= 2 + EAP_FAST_PAC_KEY_LEN
+ 2 + pac
->pac_opaque_len
+
879 2 + pac
->pac_info_len
;
883 buf
= os_malloc(len
);
888 WPA_PUT_BE32(pos
, EAP_FAST_PAC_BINARY_MAGIC
);
890 WPA_PUT_BE16(pos
, EAP_FAST_PAC_BINARY_FORMAT_VERSION
);
895 WPA_PUT_BE16(pos
, pac
->pac_type
);
897 os_memcpy(pos
, pac
->pac_key
, EAP_FAST_PAC_KEY_LEN
);
898 pos
+= EAP_FAST_PAC_KEY_LEN
;
899 WPA_PUT_BE16(pos
, pac
->pac_opaque_len
);
901 os_memcpy(pos
, pac
->pac_opaque
, pac
->pac_opaque_len
);
902 pos
+= pac
->pac_opaque_len
;
903 WPA_PUT_BE16(pos
, pac
->pac_info_len
);
905 os_memcpy(pos
, pac
->pac_info
, pac
->pac_info_len
);
906 pos
+= pac
->pac_info_len
;
912 if (eap_fast_write_pac(sm
, pac_file
, (char *) buf
, len
)) {
917 wpa_printf(MSG_DEBUG
, "EAP-FAST: Wrote %lu PAC entries into '%s' "
918 "(bin)", (unsigned long) count
, pac_file
);