1189251Ssam/* 2189251Ssam * EAP peer method: EAP-FAST PAC file processing 3189251Ssam * Copyright (c) 2004-2006, Jouni Malinen <j@w1.fi> 4189251Ssam * 5252726Srpaulo * This software may be distributed under the terms of the BSD license. 6252726Srpaulo * See README for more details. 7189251Ssam */ 8189251Ssam 9189251Ssam#include "includes.h" 10189251Ssam 11189251Ssam#include "common.h" 12189251Ssam#include "eap_config.h" 13189251Ssam#include "eap_i.h" 14189251Ssam#include "eap_fast_pac.h" 15189251Ssam 16189251Ssam/* TODO: encrypt PAC-Key in the PAC file */ 17189251Ssam 18189251Ssam 19189251Ssam/* Text data format */ 20189251Ssamstatic const char *pac_file_hdr = 21189251Ssam "wpa_supplicant EAP-FAST PAC file - version 1"; 22189251Ssam 23189251Ssam/* 24189251Ssam * Binary data format 25189251Ssam * 4-octet magic value: 6A E4 92 0C 26189251Ssam * 2-octet version (big endian) 27189251Ssam * <version specific data> 28189251Ssam * 29189251Ssam * version=0: 30189251Ssam * Sequence of PAC entries: 31189251Ssam * 2-octet PAC-Type (big endian) 32189251Ssam * 32-octet PAC-Key 33189251Ssam * 2-octet PAC-Opaque length (big endian) 34189251Ssam * <variable len> PAC-Opaque data (length bytes) 35189251Ssam * 2-octet PAC-Info length (big endian) 36189251Ssam * <variable len> PAC-Info data (length bytes) 37189251Ssam */ 38189251Ssam 39189251Ssam#define EAP_FAST_PAC_BINARY_MAGIC 0x6ae4920c 40189251Ssam#define EAP_FAST_PAC_BINARY_FORMAT_VERSION 0 41189251Ssam 42189251Ssam 43189251Ssam/** 44189251Ssam * eap_fast_free_pac - Free PAC data 45189251Ssam * @pac: Pointer to the PAC entry 46189251Ssam * 47189251Ssam * Note that the PAC entry must not be in a list since this function does not 48189251Ssam * remove the list links. 49189251Ssam */ 50189251Ssamvoid eap_fast_free_pac(struct eap_fast_pac *pac) 51189251Ssam{ 52189251Ssam os_free(pac->pac_opaque); 53189251Ssam os_free(pac->pac_info); 54189251Ssam os_free(pac->a_id); 55189251Ssam os_free(pac->i_id); 56189251Ssam os_free(pac->a_id_info); 57189251Ssam os_free(pac); 58189251Ssam} 59189251Ssam 60189251Ssam 61189251Ssam/** 62189251Ssam * eap_fast_get_pac - Get a PAC entry based on A-ID 63189251Ssam * @pac_root: Pointer to root of the PAC list 64189251Ssam * @a_id: A-ID to search for 65189251Ssam * @a_id_len: Length of A-ID 66189251Ssam * @pac_type: PAC-Type to search for 67189251Ssam * Returns: Pointer to the PAC entry, or %NULL if A-ID not found 68189251Ssam */ 69189251Ssamstruct eap_fast_pac * eap_fast_get_pac(struct eap_fast_pac *pac_root, 70189251Ssam const u8 *a_id, size_t a_id_len, 71189251Ssam u16 pac_type) 72189251Ssam{ 73189251Ssam struct eap_fast_pac *pac = pac_root; 74189251Ssam 75189251Ssam while (pac) { 76189251Ssam if (pac->pac_type == pac_type && pac->a_id_len == a_id_len && 77189251Ssam os_memcmp(pac->a_id, a_id, a_id_len) == 0) { 78189251Ssam return pac; 79189251Ssam } 80189251Ssam pac = pac->next; 81189251Ssam } 82189251Ssam return NULL; 83189251Ssam} 84189251Ssam 85189251Ssam 86189251Ssamstatic void eap_fast_remove_pac(struct eap_fast_pac **pac_root, 87189251Ssam struct eap_fast_pac **pac_current, 88189251Ssam const u8 *a_id, size_t a_id_len, u16 pac_type) 89189251Ssam{ 90189251Ssam struct eap_fast_pac *pac, *prev; 91189251Ssam 92189251Ssam pac = *pac_root; 93189251Ssam prev = NULL; 94189251Ssam 95189251Ssam while (pac) { 96189251Ssam if (pac->pac_type == pac_type && pac->a_id_len == a_id_len && 97189251Ssam os_memcmp(pac->a_id, a_id, a_id_len) == 0) { 98189251Ssam if (prev == NULL) 99189251Ssam *pac_root = pac->next; 100189251Ssam else 101189251Ssam prev->next = pac->next; 102189251Ssam if (*pac_current == pac) 103189251Ssam *pac_current = NULL; 104189251Ssam eap_fast_free_pac(pac); 105189251Ssam break; 106189251Ssam } 107189251Ssam prev = pac; 108189251Ssam pac = pac->next; 109189251Ssam } 110189251Ssam} 111189251Ssam 112189251Ssam 113189251Ssamstatic int eap_fast_copy_buf(u8 **dst, size_t *dst_len, 114189251Ssam const u8 *src, size_t src_len) 115189251Ssam{ 116189251Ssam if (src) { 117189251Ssam *dst = os_malloc(src_len); 118189251Ssam if (*dst == NULL) 119189251Ssam return -1; 120189251Ssam os_memcpy(*dst, src, src_len); 121189251Ssam *dst_len = src_len; 122189251Ssam } 123189251Ssam return 0; 124189251Ssam} 125189251Ssam 126189251Ssam 127189251Ssam/** 128189251Ssam * eap_fast_add_pac - Add a copy of a PAC entry to a list 129189251Ssam * @pac_root: Pointer to PAC list root pointer 130189251Ssam * @pac_current: Pointer to the current PAC pointer 131189251Ssam * @entry: New entry to clone and add to the list 132189251Ssam * Returns: 0 on success, -1 on failure 133189251Ssam * 134189251Ssam * This function makes a clone of the given PAC entry and adds this copied 135189251Ssam * entry to the list (pac_root). If an old entry for the same A-ID is found, 136189251Ssam * it will be removed from the PAC list and in this case, pac_current entry 137189251Ssam * is set to %NULL if it was the removed entry. 138189251Ssam */ 139189251Ssamint eap_fast_add_pac(struct eap_fast_pac **pac_root, 140189251Ssam struct eap_fast_pac **pac_current, 141189251Ssam struct eap_fast_pac *entry) 142189251Ssam{ 143189251Ssam struct eap_fast_pac *pac; 144189251Ssam 145189251Ssam if (entry == NULL || entry->a_id == NULL) 146189251Ssam return -1; 147189251Ssam 148189251Ssam /* Remove a possible old entry for the matching A-ID. */ 149189251Ssam eap_fast_remove_pac(pac_root, pac_current, 150189251Ssam entry->a_id, entry->a_id_len, entry->pac_type); 151189251Ssam 152189251Ssam /* Allocate a new entry and add it to the list of PACs. */ 153189251Ssam pac = os_zalloc(sizeof(*pac)); 154189251Ssam if (pac == NULL) 155189251Ssam return -1; 156189251Ssam 157189251Ssam pac->pac_type = entry->pac_type; 158189251Ssam os_memcpy(pac->pac_key, entry->pac_key, EAP_FAST_PAC_KEY_LEN); 159189251Ssam if (eap_fast_copy_buf(&pac->pac_opaque, &pac->pac_opaque_len, 160189251Ssam entry->pac_opaque, entry->pac_opaque_len) < 0 || 161189251Ssam eap_fast_copy_buf(&pac->pac_info, &pac->pac_info_len, 162189251Ssam entry->pac_info, entry->pac_info_len) < 0 || 163189251Ssam eap_fast_copy_buf(&pac->a_id, &pac->a_id_len, 164189251Ssam entry->a_id, entry->a_id_len) < 0 || 165189251Ssam eap_fast_copy_buf(&pac->i_id, &pac->i_id_len, 166189251Ssam entry->i_id, entry->i_id_len) < 0 || 167189251Ssam eap_fast_copy_buf(&pac->a_id_info, &pac->a_id_info_len, 168189251Ssam entry->a_id_info, entry->a_id_info_len) < 0) { 169189251Ssam eap_fast_free_pac(pac); 170189251Ssam return -1; 171189251Ssam } 172189251Ssam 173189251Ssam pac->next = *pac_root; 174189251Ssam *pac_root = pac; 175189251Ssam 176189251Ssam return 0; 177189251Ssam} 178189251Ssam 179189251Ssam 180189251Ssamstruct eap_fast_read_ctx { 181189251Ssam FILE *f; 182189251Ssam const char *pos; 183189251Ssam const char *end; 184189251Ssam int line; 185189251Ssam char *buf; 186189251Ssam size_t buf_len; 187189251Ssam}; 188189251Ssam 189189251Ssamstatic int eap_fast_read_line(struct eap_fast_read_ctx *rc, char **value) 190189251Ssam{ 191189251Ssam char *pos; 192189251Ssam 193189251Ssam rc->line++; 194189251Ssam if (rc->f) { 195189251Ssam if (fgets(rc->buf, rc->buf_len, rc->f) == NULL) 196189251Ssam return -1; 197189251Ssam } else { 198189251Ssam const char *l_end; 199189251Ssam size_t len; 200189251Ssam if (rc->pos >= rc->end) 201189251Ssam return -1; 202189251Ssam l_end = rc->pos; 203189251Ssam while (l_end < rc->end && *l_end != '\n') 204189251Ssam l_end++; 205189251Ssam len = l_end - rc->pos; 206189251Ssam if (len >= rc->buf_len) 207189251Ssam len = rc->buf_len - 1; 208189251Ssam os_memcpy(rc->buf, rc->pos, len); 209189251Ssam rc->buf[len] = '\0'; 210189251Ssam rc->pos = l_end + 1; 211189251Ssam } 212189251Ssam 213189251Ssam rc->buf[rc->buf_len - 1] = '\0'; 214189251Ssam pos = rc->buf; 215189251Ssam while (*pos != '\0') { 216189251Ssam if (*pos == '\n' || *pos == '\r') { 217189251Ssam *pos = '\0'; 218189251Ssam break; 219189251Ssam } 220189251Ssam pos++; 221189251Ssam } 222189251Ssam 223189251Ssam pos = os_strchr(rc->buf, '='); 224189251Ssam if (pos) 225189251Ssam *pos++ = '\0'; 226189251Ssam *value = pos; 227189251Ssam 228189251Ssam return 0; 229189251Ssam} 230189251Ssam 231189251Ssam 232189251Ssamstatic u8 * eap_fast_parse_hex(const char *value, size_t *len) 233189251Ssam{ 234189251Ssam int hlen; 235189251Ssam u8 *buf; 236189251Ssam 237189251Ssam if (value == NULL) 238189251Ssam return NULL; 239189251Ssam hlen = os_strlen(value); 240189251Ssam if (hlen & 1) 241189251Ssam return NULL; 242189251Ssam *len = hlen / 2; 243189251Ssam buf = os_malloc(*len); 244189251Ssam if (buf == NULL) 245189251Ssam return NULL; 246189251Ssam if (hexstr2bin(value, buf, *len)) { 247189251Ssam os_free(buf); 248189251Ssam return NULL; 249189251Ssam } 250189251Ssam return buf; 251189251Ssam} 252189251Ssam 253189251Ssam 254189251Ssamstatic int eap_fast_init_pac_data(struct eap_sm *sm, const char *pac_file, 255189251Ssam struct eap_fast_read_ctx *rc) 256189251Ssam{ 257189251Ssam os_memset(rc, 0, sizeof(*rc)); 258189251Ssam 259189251Ssam rc->buf_len = 2048; 260189251Ssam rc->buf = os_malloc(rc->buf_len); 261189251Ssam if (rc->buf == NULL) 262189251Ssam return -1; 263189251Ssam 264189251Ssam if (os_strncmp(pac_file, "blob://", 7) == 0) { 265189251Ssam const struct wpa_config_blob *blob; 266189251Ssam blob = eap_get_config_blob(sm, pac_file + 7); 267189251Ssam if (blob == NULL) { 268189251Ssam wpa_printf(MSG_INFO, "EAP-FAST: No PAC blob '%s' - " 269189251Ssam "assume no PAC entries have been " 270189251Ssam "provisioned", pac_file + 7); 271189251Ssam os_free(rc->buf); 272189251Ssam return -1; 273189251Ssam } 274189251Ssam rc->pos = (char *) blob->data; 275189251Ssam rc->end = (char *) blob->data + blob->len; 276189251Ssam } else { 277189251Ssam rc->f = fopen(pac_file, "rb"); 278189251Ssam if (rc->f == NULL) { 279189251Ssam wpa_printf(MSG_INFO, "EAP-FAST: No PAC file '%s' - " 280189251Ssam "assume no PAC entries have been " 281189251Ssam "provisioned", pac_file); 282189251Ssam os_free(rc->buf); 283189251Ssam return -1; 284189251Ssam } 285189251Ssam } 286189251Ssam 287189251Ssam return 0; 288189251Ssam} 289189251Ssam 290189251Ssam 291189251Ssamstatic void eap_fast_deinit_pac_data(struct eap_fast_read_ctx *rc) 292189251Ssam{ 293189251Ssam os_free(rc->buf); 294189251Ssam if (rc->f) 295189251Ssam fclose(rc->f); 296189251Ssam} 297189251Ssam 298189251Ssam 299189251Ssamstatic const char * eap_fast_parse_start(struct eap_fast_pac **pac) 300189251Ssam{ 301189251Ssam if (*pac) 302189251Ssam return "START line without END"; 303189251Ssam 304189251Ssam *pac = os_zalloc(sizeof(struct eap_fast_pac)); 305189251Ssam if (*pac == NULL) 306189251Ssam return "No memory for PAC entry"; 307189251Ssam (*pac)->pac_type = PAC_TYPE_TUNNEL_PAC; 308189251Ssam return NULL; 309189251Ssam} 310189251Ssam 311189251Ssam 312189251Ssamstatic const char * eap_fast_parse_end(struct eap_fast_pac **pac_root, 313189251Ssam struct eap_fast_pac **pac) 314189251Ssam{ 315189251Ssam if (*pac == NULL) 316189251Ssam return "END line without START"; 317189251Ssam if (*pac_root) { 318189251Ssam struct eap_fast_pac *end = *pac_root; 319189251Ssam while (end->next) 320189251Ssam end = end->next; 321189251Ssam end->next = *pac; 322189251Ssam } else 323189251Ssam *pac_root = *pac; 324189251Ssam 325189251Ssam *pac = NULL; 326189251Ssam return NULL; 327189251Ssam} 328189251Ssam 329189251Ssam 330189251Ssamstatic const char * eap_fast_parse_pac_type(struct eap_fast_pac *pac, 331189251Ssam char *pos) 332189251Ssam{ 333189251Ssam pac->pac_type = atoi(pos); 334189251Ssam if (pac->pac_type != PAC_TYPE_TUNNEL_PAC && 335189251Ssam pac->pac_type != PAC_TYPE_USER_AUTHORIZATION && 336189251Ssam pac->pac_type != PAC_TYPE_MACHINE_AUTHENTICATION) 337189251Ssam return "Unrecognized PAC-Type"; 338189251Ssam 339189251Ssam return NULL; 340189251Ssam} 341189251Ssam 342189251Ssam 343189251Ssamstatic const char * eap_fast_parse_pac_key(struct eap_fast_pac *pac, char *pos) 344189251Ssam{ 345189251Ssam u8 *key; 346189251Ssam size_t key_len; 347189251Ssam 348189251Ssam key = eap_fast_parse_hex(pos, &key_len); 349189251Ssam if (key == NULL || key_len != EAP_FAST_PAC_KEY_LEN) { 350189251Ssam os_free(key); 351189251Ssam return "Invalid PAC-Key"; 352189251Ssam } 353189251Ssam 354189251Ssam os_memcpy(pac->pac_key, key, EAP_FAST_PAC_KEY_LEN); 355189251Ssam os_free(key); 356189251Ssam 357189251Ssam return NULL; 358189251Ssam} 359189251Ssam 360189251Ssam 361189251Ssamstatic const char * eap_fast_parse_pac_opaque(struct eap_fast_pac *pac, 362189251Ssam char *pos) 363189251Ssam{ 364189251Ssam os_free(pac->pac_opaque); 365189251Ssam pac->pac_opaque = eap_fast_parse_hex(pos, &pac->pac_opaque_len); 366189251Ssam if (pac->pac_opaque == NULL) 367189251Ssam return "Invalid PAC-Opaque"; 368189251Ssam return NULL; 369189251Ssam} 370189251Ssam 371189251Ssam 372189251Ssamstatic const char * eap_fast_parse_a_id(struct eap_fast_pac *pac, char *pos) 373189251Ssam{ 374189251Ssam os_free(pac->a_id); 375189251Ssam pac->a_id = eap_fast_parse_hex(pos, &pac->a_id_len); 376189251Ssam if (pac->a_id == NULL) 377189251Ssam return "Invalid A-ID"; 378189251Ssam return NULL; 379189251Ssam} 380189251Ssam 381189251Ssam 382189251Ssamstatic const char * eap_fast_parse_i_id(struct eap_fast_pac *pac, char *pos) 383189251Ssam{ 384189251Ssam os_free(pac->i_id); 385189251Ssam pac->i_id = eap_fast_parse_hex(pos, &pac->i_id_len); 386189251Ssam if (pac->i_id == NULL) 387189251Ssam return "Invalid I-ID"; 388189251Ssam return NULL; 389189251Ssam} 390189251Ssam 391189251Ssam 392189251Ssamstatic const char * eap_fast_parse_a_id_info(struct eap_fast_pac *pac, 393189251Ssam char *pos) 394189251Ssam{ 395189251Ssam os_free(pac->a_id_info); 396189251Ssam pac->a_id_info = eap_fast_parse_hex(pos, &pac->a_id_info_len); 397189251Ssam if (pac->a_id_info == NULL) 398189251Ssam return "Invalid A-ID-Info"; 399189251Ssam return NULL; 400189251Ssam} 401189251Ssam 402189251Ssam 403189251Ssam/** 404189251Ssam * eap_fast_load_pac - Load PAC entries (text format) 405189251Ssam * @sm: Pointer to EAP state machine allocated with eap_peer_sm_init() 406189251Ssam * @pac_root: Pointer to root of the PAC list (to be filled) 407189251Ssam * @pac_file: Name of the PAC file/blob to load 408189251Ssam * Returns: 0 on success, -1 on failure 409189251Ssam */ 410189251Ssamint eap_fast_load_pac(struct eap_sm *sm, struct eap_fast_pac **pac_root, 411189251Ssam const char *pac_file) 412189251Ssam{ 413189251Ssam struct eap_fast_read_ctx rc; 414189251Ssam struct eap_fast_pac *pac = NULL; 415189251Ssam int count = 0; 416189251Ssam char *pos; 417189251Ssam const char *err = NULL; 418189251Ssam 419189251Ssam if (pac_file == NULL) 420189251Ssam return -1; 421189251Ssam 422189251Ssam if (eap_fast_init_pac_data(sm, pac_file, &rc) < 0) 423189251Ssam return 0; 424189251Ssam 425252726Srpaulo if (eap_fast_read_line(&rc, &pos) < 0) { 426252726Srpaulo /* empty file - assume it is fine to overwrite */ 427252726Srpaulo eap_fast_deinit_pac_data(&rc); 428252726Srpaulo return 0; 429252726Srpaulo } 430252726Srpaulo if (os_strcmp(pac_file_hdr, rc.buf) != 0) 431189251Ssam err = "Unrecognized header line"; 432189251Ssam 433189251Ssam while (!err && eap_fast_read_line(&rc, &pos) == 0) { 434189251Ssam if (os_strcmp(rc.buf, "START") == 0) 435189251Ssam err = eap_fast_parse_start(&pac); 436189251Ssam else if (os_strcmp(rc.buf, "END") == 0) { 437189251Ssam err = eap_fast_parse_end(pac_root, &pac); 438189251Ssam count++; 439189251Ssam } else if (!pac) 440189251Ssam err = "Unexpected line outside START/END block"; 441189251Ssam else if (os_strcmp(rc.buf, "PAC-Type") == 0) 442189251Ssam err = eap_fast_parse_pac_type(pac, pos); 443189251Ssam else if (os_strcmp(rc.buf, "PAC-Key") == 0) 444189251Ssam err = eap_fast_parse_pac_key(pac, pos); 445189251Ssam else if (os_strcmp(rc.buf, "PAC-Opaque") == 0) 446189251Ssam err = eap_fast_parse_pac_opaque(pac, pos); 447189251Ssam else if (os_strcmp(rc.buf, "A-ID") == 0) 448189251Ssam err = eap_fast_parse_a_id(pac, pos); 449189251Ssam else if (os_strcmp(rc.buf, "I-ID") == 0) 450189251Ssam err = eap_fast_parse_i_id(pac, pos); 451189251Ssam else if (os_strcmp(rc.buf, "A-ID-Info") == 0) 452189251Ssam err = eap_fast_parse_a_id_info(pac, pos); 453189251Ssam } 454189251Ssam 455189251Ssam if (pac) { 456189251Ssam err = "PAC block not terminated with END"; 457189251Ssam eap_fast_free_pac(pac); 458189251Ssam } 459189251Ssam 460189251Ssam eap_fast_deinit_pac_data(&rc); 461189251Ssam 462189251Ssam if (err) { 463189251Ssam wpa_printf(MSG_INFO, "EAP-FAST: %s in '%s:%d'", 464189251Ssam err, pac_file, rc.line); 465189251Ssam return -1; 466189251Ssam } 467189251Ssam 468189251Ssam wpa_printf(MSG_DEBUG, "EAP-FAST: Read %d PAC entries from '%s'", 469189251Ssam count, pac_file); 470189251Ssam 471189251Ssam return 0; 472189251Ssam} 473189251Ssam 474189251Ssam 475189251Ssamstatic void eap_fast_write(char **buf, char **pos, size_t *buf_len, 476189251Ssam const char *field, const u8 *data, 477189251Ssam size_t len, int txt) 478189251Ssam{ 479189251Ssam size_t i, need; 480189251Ssam int ret; 481214734Srpaulo char *end; 482189251Ssam 483214734Srpaulo if (data == NULL || buf == NULL || *buf == NULL || 484214734Srpaulo pos == NULL || *pos == NULL || *pos < *buf) 485189251Ssam return; 486189251Ssam 487189251Ssam need = os_strlen(field) + len * 2 + 30; 488189251Ssam if (txt) 489189251Ssam need += os_strlen(field) + len + 20; 490189251Ssam 491189251Ssam if (*pos - *buf + need > *buf_len) { 492189251Ssam char *nbuf = os_realloc(*buf, *buf_len + need); 493189251Ssam if (nbuf == NULL) { 494189251Ssam os_free(*buf); 495189251Ssam *buf = NULL; 496189251Ssam return; 497189251Ssam } 498252726Srpaulo *pos = nbuf + (*pos - *buf); 499189251Ssam *buf = nbuf; 500189251Ssam *buf_len += need; 501189251Ssam } 502214734Srpaulo end = *buf + *buf_len; 503189251Ssam 504214734Srpaulo ret = os_snprintf(*pos, end - *pos, "%s=", field); 505214734Srpaulo if (ret < 0 || ret >= end - *pos) 506189251Ssam return; 507189251Ssam *pos += ret; 508214734Srpaulo *pos += wpa_snprintf_hex(*pos, end - *pos, data, len); 509214734Srpaulo ret = os_snprintf(*pos, end - *pos, "\n"); 510214734Srpaulo if (ret < 0 || ret >= end - *pos) 511189251Ssam return; 512189251Ssam *pos += ret; 513189251Ssam 514189251Ssam if (txt) { 515214734Srpaulo ret = os_snprintf(*pos, end - *pos, "%s-txt=", field); 516214734Srpaulo if (ret < 0 || ret >= end - *pos) 517189251Ssam return; 518189251Ssam *pos += ret; 519189251Ssam for (i = 0; i < len; i++) { 520214734Srpaulo ret = os_snprintf(*pos, end - *pos, "%c", data[i]); 521214734Srpaulo if (ret < 0 || ret >= end - *pos) 522189251Ssam return; 523189251Ssam *pos += ret; 524189251Ssam } 525214734Srpaulo ret = os_snprintf(*pos, end - *pos, "\n"); 526214734Srpaulo if (ret < 0 || ret >= end - *pos) 527189251Ssam return; 528189251Ssam *pos += ret; 529189251Ssam } 530189251Ssam} 531189251Ssam 532189251Ssam 533189251Ssamstatic int eap_fast_write_pac(struct eap_sm *sm, const char *pac_file, 534189251Ssam char *buf, size_t len) 535189251Ssam{ 536189251Ssam if (os_strncmp(pac_file, "blob://", 7) == 0) { 537189251Ssam struct wpa_config_blob *blob; 538189251Ssam blob = os_zalloc(sizeof(*blob)); 539189251Ssam if (blob == NULL) 540189251Ssam return -1; 541189251Ssam blob->data = (u8 *) buf; 542189251Ssam blob->len = len; 543189251Ssam buf = NULL; 544189251Ssam blob->name = os_strdup(pac_file + 7); 545189251Ssam if (blob->name == NULL) { 546189251Ssam os_free(blob); 547189251Ssam return -1; 548189251Ssam } 549189251Ssam eap_set_config_blob(sm, blob); 550189251Ssam } else { 551189251Ssam FILE *f; 552189251Ssam f = fopen(pac_file, "wb"); 553189251Ssam if (f == NULL) { 554189251Ssam wpa_printf(MSG_INFO, "EAP-FAST: Failed to open PAC " 555189251Ssam "file '%s' for writing", pac_file); 556189251Ssam return -1; 557189251Ssam } 558189251Ssam if (fwrite(buf, 1, len, f) != len) { 559189251Ssam wpa_printf(MSG_INFO, "EAP-FAST: Failed to write all " 560189251Ssam "PACs into '%s'", pac_file); 561189251Ssam fclose(f); 562189251Ssam return -1; 563189251Ssam } 564189251Ssam os_free(buf); 565189251Ssam fclose(f); 566189251Ssam } 567189251Ssam 568189251Ssam return 0; 569189251Ssam} 570189251Ssam 571189251Ssam 572189251Ssamstatic int eap_fast_add_pac_data(struct eap_fast_pac *pac, char **buf, 573189251Ssam char **pos, size_t *buf_len) 574189251Ssam{ 575189251Ssam int ret; 576189251Ssam 577189251Ssam ret = os_snprintf(*pos, *buf + *buf_len - *pos, 578189251Ssam "START\nPAC-Type=%d\n", pac->pac_type); 579189251Ssam if (ret < 0 || ret >= *buf + *buf_len - *pos) 580189251Ssam return -1; 581189251Ssam 582189251Ssam *pos += ret; 583189251Ssam eap_fast_write(buf, pos, buf_len, "PAC-Key", 584189251Ssam pac->pac_key, EAP_FAST_PAC_KEY_LEN, 0); 585189251Ssam eap_fast_write(buf, pos, buf_len, "PAC-Opaque", 586189251Ssam pac->pac_opaque, pac->pac_opaque_len, 0); 587189251Ssam eap_fast_write(buf, pos, buf_len, "PAC-Info", 588189251Ssam pac->pac_info, pac->pac_info_len, 0); 589189251Ssam eap_fast_write(buf, pos, buf_len, "A-ID", 590189251Ssam pac->a_id, pac->a_id_len, 0); 591189251Ssam eap_fast_write(buf, pos, buf_len, "I-ID", 592189251Ssam pac->i_id, pac->i_id_len, 1); 593189251Ssam eap_fast_write(buf, pos, buf_len, "A-ID-Info", 594189251Ssam pac->a_id_info, pac->a_id_info_len, 1); 595189251Ssam if (*buf == NULL) { 596189251Ssam wpa_printf(MSG_DEBUG, "EAP-FAST: No memory for PAC " 597189251Ssam "data"); 598189251Ssam return -1; 599189251Ssam } 600189251Ssam ret = os_snprintf(*pos, *buf + *buf_len - *pos, "END\n"); 601189251Ssam if (ret < 0 || ret >= *buf + *buf_len - *pos) 602189251Ssam return -1; 603189251Ssam *pos += ret; 604189251Ssam 605189251Ssam return 0; 606189251Ssam} 607189251Ssam 608189251Ssam 609189251Ssam/** 610189251Ssam * eap_fast_save_pac - Save PAC entries (text format) 611189251Ssam * @sm: Pointer to EAP state machine allocated with eap_peer_sm_init() 612189251Ssam * @pac_root: Root of the PAC list 613189251Ssam * @pac_file: Name of the PAC file/blob 614189251Ssam * Returns: 0 on success, -1 on failure 615189251Ssam */ 616189251Ssamint eap_fast_save_pac(struct eap_sm *sm, struct eap_fast_pac *pac_root, 617189251Ssam const char *pac_file) 618189251Ssam{ 619189251Ssam struct eap_fast_pac *pac; 620189251Ssam int ret, count = 0; 621189251Ssam char *buf, *pos; 622189251Ssam size_t buf_len; 623189251Ssam 624189251Ssam if (pac_file == NULL) 625189251Ssam return -1; 626189251Ssam 627189251Ssam buf_len = 1024; 628189251Ssam pos = buf = os_malloc(buf_len); 629189251Ssam if (buf == NULL) 630189251Ssam return -1; 631189251Ssam 632189251Ssam ret = os_snprintf(pos, buf + buf_len - pos, "%s\n", pac_file_hdr); 633189251Ssam if (ret < 0 || ret >= buf + buf_len - pos) { 634189251Ssam os_free(buf); 635189251Ssam return -1; 636189251Ssam } 637189251Ssam pos += ret; 638189251Ssam 639189251Ssam pac = pac_root; 640189251Ssam while (pac) { 641189251Ssam if (eap_fast_add_pac_data(pac, &buf, &pos, &buf_len)) { 642189251Ssam os_free(buf); 643189251Ssam return -1; 644189251Ssam } 645189251Ssam count++; 646189251Ssam pac = pac->next; 647189251Ssam } 648189251Ssam 649189251Ssam if (eap_fast_write_pac(sm, pac_file, buf, pos - buf)) { 650189251Ssam os_free(buf); 651189251Ssam return -1; 652189251Ssam } 653189251Ssam 654189251Ssam wpa_printf(MSG_DEBUG, "EAP-FAST: Wrote %d PAC entries into '%s'", 655189251Ssam count, pac_file); 656189251Ssam 657189251Ssam return 0; 658189251Ssam} 659189251Ssam 660189251Ssam 661189251Ssam/** 662189251Ssam * eap_fast_pac_list_truncate - Truncate a PAC list to the given length 663189251Ssam * @pac_root: Root of the PAC list 664189251Ssam * @max_len: Maximum length of the list (>= 1) 665189251Ssam * Returns: Number of PAC entries removed 666189251Ssam */ 667189251Ssamsize_t eap_fast_pac_list_truncate(struct eap_fast_pac *pac_root, 668189251Ssam size_t max_len) 669189251Ssam{ 670189251Ssam struct eap_fast_pac *pac, *prev; 671189251Ssam size_t count; 672189251Ssam 673189251Ssam pac = pac_root; 674189251Ssam prev = NULL; 675189251Ssam count = 0; 676189251Ssam 677189251Ssam while (pac) { 678189251Ssam count++; 679189251Ssam if (count > max_len) 680189251Ssam break; 681189251Ssam prev = pac; 682189251Ssam pac = pac->next; 683189251Ssam } 684189251Ssam 685189251Ssam if (count <= max_len || prev == NULL) 686189251Ssam return 0; 687189251Ssam 688189251Ssam count = 0; 689189251Ssam prev->next = NULL; 690189251Ssam 691189251Ssam while (pac) { 692189251Ssam prev = pac; 693189251Ssam pac = pac->next; 694189251Ssam eap_fast_free_pac(prev); 695189251Ssam count++; 696189251Ssam } 697189251Ssam 698189251Ssam return count; 699189251Ssam} 700189251Ssam 701189251Ssam 702189251Ssamstatic void eap_fast_pac_get_a_id(struct eap_fast_pac *pac) 703189251Ssam{ 704189251Ssam u8 *pos, *end; 705189251Ssam u16 type, len; 706189251Ssam 707189251Ssam pos = pac->pac_info; 708189251Ssam end = pos + pac->pac_info_len; 709189251Ssam 710189251Ssam while (pos + 4 < end) { 711189251Ssam type = WPA_GET_BE16(pos); 712189251Ssam pos += 2; 713189251Ssam len = WPA_GET_BE16(pos); 714189251Ssam pos += 2; 715189251Ssam if (pos + len > end) 716189251Ssam break; 717189251Ssam 718189251Ssam if (type == PAC_TYPE_A_ID) { 719189251Ssam os_free(pac->a_id); 720189251Ssam pac->a_id = os_malloc(len); 721189251Ssam if (pac->a_id == NULL) 722189251Ssam break; 723189251Ssam os_memcpy(pac->a_id, pos, len); 724189251Ssam pac->a_id_len = len; 725189251Ssam } 726189251Ssam 727189251Ssam if (type == PAC_TYPE_A_ID_INFO) { 728189251Ssam os_free(pac->a_id_info); 729189251Ssam pac->a_id_info = os_malloc(len); 730189251Ssam if (pac->a_id_info == NULL) 731189251Ssam break; 732189251Ssam os_memcpy(pac->a_id_info, pos, len); 733189251Ssam pac->a_id_info_len = len; 734189251Ssam } 735189251Ssam 736189251Ssam pos += len; 737189251Ssam } 738189251Ssam} 739189251Ssam 740189251Ssam 741189251Ssam/** 742189251Ssam * eap_fast_load_pac_bin - Load PAC entries (binary format) 743189251Ssam * @sm: Pointer to EAP state machine allocated with eap_peer_sm_init() 744189251Ssam * @pac_root: Pointer to root of the PAC list (to be filled) 745189251Ssam * @pac_file: Name of the PAC file/blob to load 746189251Ssam * Returns: 0 on success, -1 on failure 747189251Ssam */ 748189251Ssamint eap_fast_load_pac_bin(struct eap_sm *sm, struct eap_fast_pac **pac_root, 749189251Ssam const char *pac_file) 750189251Ssam{ 751189251Ssam const struct wpa_config_blob *blob = NULL; 752189251Ssam u8 *buf, *end, *pos; 753189251Ssam size_t len, count = 0; 754189251Ssam struct eap_fast_pac *pac, *prev; 755189251Ssam 756189251Ssam *pac_root = NULL; 757189251Ssam 758189251Ssam if (pac_file == NULL) 759189251Ssam return -1; 760189251Ssam 761189251Ssam if (os_strncmp(pac_file, "blob://", 7) == 0) { 762189251Ssam blob = eap_get_config_blob(sm, pac_file + 7); 763189251Ssam if (blob == NULL) { 764189251Ssam wpa_printf(MSG_INFO, "EAP-FAST: No PAC blob '%s' - " 765189251Ssam "assume no PAC entries have been " 766189251Ssam "provisioned", pac_file + 7); 767189251Ssam return 0; 768189251Ssam } 769189251Ssam buf = blob->data; 770189251Ssam len = blob->len; 771189251Ssam } else { 772189251Ssam buf = (u8 *) os_readfile(pac_file, &len); 773189251Ssam if (buf == NULL) { 774189251Ssam wpa_printf(MSG_INFO, "EAP-FAST: No PAC file '%s' - " 775189251Ssam "assume no PAC entries have been " 776189251Ssam "provisioned", pac_file); 777189251Ssam return 0; 778189251Ssam } 779189251Ssam } 780189251Ssam 781189251Ssam if (len == 0) { 782189251Ssam if (blob == NULL) 783189251Ssam os_free(buf); 784189251Ssam return 0; 785189251Ssam } 786189251Ssam 787189251Ssam if (len < 6 || WPA_GET_BE32(buf) != EAP_FAST_PAC_BINARY_MAGIC || 788189251Ssam WPA_GET_BE16(buf + 4) != EAP_FAST_PAC_BINARY_FORMAT_VERSION) { 789189251Ssam wpa_printf(MSG_INFO, "EAP-FAST: Invalid PAC file '%s' (bin)", 790189251Ssam pac_file); 791189251Ssam if (blob == NULL) 792189251Ssam os_free(buf); 793189251Ssam return -1; 794189251Ssam } 795189251Ssam 796189251Ssam pac = prev = NULL; 797189251Ssam pos = buf + 6; 798189251Ssam end = buf + len; 799189251Ssam while (pos < end) { 800189251Ssam if (end - pos < 2 + 32 + 2 + 2) 801189251Ssam goto parse_fail; 802189251Ssam 803189251Ssam pac = os_zalloc(sizeof(*pac)); 804189251Ssam if (pac == NULL) 805189251Ssam goto parse_fail; 806189251Ssam 807189251Ssam pac->pac_type = WPA_GET_BE16(pos); 808189251Ssam pos += 2; 809189251Ssam os_memcpy(pac->pac_key, pos, EAP_FAST_PAC_KEY_LEN); 810189251Ssam pos += EAP_FAST_PAC_KEY_LEN; 811189251Ssam pac->pac_opaque_len = WPA_GET_BE16(pos); 812189251Ssam pos += 2; 813189251Ssam if (pos + pac->pac_opaque_len + 2 > end) 814189251Ssam goto parse_fail; 815189251Ssam pac->pac_opaque = os_malloc(pac->pac_opaque_len); 816189251Ssam if (pac->pac_opaque == NULL) 817189251Ssam goto parse_fail; 818189251Ssam os_memcpy(pac->pac_opaque, pos, pac->pac_opaque_len); 819189251Ssam pos += pac->pac_opaque_len; 820189251Ssam pac->pac_info_len = WPA_GET_BE16(pos); 821189251Ssam pos += 2; 822189251Ssam if (pos + pac->pac_info_len > end) 823189251Ssam goto parse_fail; 824189251Ssam pac->pac_info = os_malloc(pac->pac_info_len); 825189251Ssam if (pac->pac_info == NULL) 826189251Ssam goto parse_fail; 827189251Ssam os_memcpy(pac->pac_info, pos, pac->pac_info_len); 828189251Ssam pos += pac->pac_info_len; 829189251Ssam eap_fast_pac_get_a_id(pac); 830189251Ssam 831189251Ssam count++; 832189251Ssam if (prev) 833189251Ssam prev->next = pac; 834189251Ssam else 835189251Ssam *pac_root = pac; 836189251Ssam prev = pac; 837189251Ssam } 838189251Ssam 839189251Ssam if (blob == NULL) 840189251Ssam os_free(buf); 841189251Ssam 842189251Ssam wpa_printf(MSG_DEBUG, "EAP-FAST: Read %lu PAC entries from '%s' (bin)", 843189251Ssam (unsigned long) count, pac_file); 844189251Ssam 845189251Ssam return 0; 846189251Ssam 847189251Ssamparse_fail: 848189251Ssam wpa_printf(MSG_INFO, "EAP-FAST: Failed to parse PAC file '%s' (bin)", 849189251Ssam pac_file); 850189251Ssam if (blob == NULL) 851189251Ssam os_free(buf); 852189251Ssam if (pac) 853189251Ssam eap_fast_free_pac(pac); 854189251Ssam return -1; 855189251Ssam} 856189251Ssam 857189251Ssam 858189251Ssam/** 859189251Ssam * eap_fast_save_pac_bin - Save PAC entries (binary format) 860189251Ssam * @sm: Pointer to EAP state machine allocated with eap_peer_sm_init() 861189251Ssam * @pac_root: Root of the PAC list 862189251Ssam * @pac_file: Name of the PAC file/blob 863189251Ssam * Returns: 0 on success, -1 on failure 864189251Ssam */ 865189251Ssamint eap_fast_save_pac_bin(struct eap_sm *sm, struct eap_fast_pac *pac_root, 866189251Ssam const char *pac_file) 867189251Ssam{ 868189251Ssam size_t len, count = 0; 869189251Ssam struct eap_fast_pac *pac; 870189251Ssam u8 *buf, *pos; 871189251Ssam 872189251Ssam len = 6; 873189251Ssam pac = pac_root; 874189251Ssam while (pac) { 875189251Ssam if (pac->pac_opaque_len > 65535 || 876189251Ssam pac->pac_info_len > 65535) 877189251Ssam return -1; 878189251Ssam len += 2 + EAP_FAST_PAC_KEY_LEN + 2 + pac->pac_opaque_len + 879189251Ssam 2 + pac->pac_info_len; 880189251Ssam pac = pac->next; 881189251Ssam } 882189251Ssam 883189251Ssam buf = os_malloc(len); 884189251Ssam if (buf == NULL) 885189251Ssam return -1; 886189251Ssam 887189251Ssam pos = buf; 888189251Ssam WPA_PUT_BE32(pos, EAP_FAST_PAC_BINARY_MAGIC); 889189251Ssam pos += 4; 890189251Ssam WPA_PUT_BE16(pos, EAP_FAST_PAC_BINARY_FORMAT_VERSION); 891189251Ssam pos += 2; 892189251Ssam 893189251Ssam pac = pac_root; 894189251Ssam while (pac) { 895189251Ssam WPA_PUT_BE16(pos, pac->pac_type); 896189251Ssam pos += 2; 897189251Ssam os_memcpy(pos, pac->pac_key, EAP_FAST_PAC_KEY_LEN); 898189251Ssam pos += EAP_FAST_PAC_KEY_LEN; 899189251Ssam WPA_PUT_BE16(pos, pac->pac_opaque_len); 900189251Ssam pos += 2; 901189251Ssam os_memcpy(pos, pac->pac_opaque, pac->pac_opaque_len); 902189251Ssam pos += pac->pac_opaque_len; 903189251Ssam WPA_PUT_BE16(pos, pac->pac_info_len); 904189251Ssam pos += 2; 905189251Ssam os_memcpy(pos, pac->pac_info, pac->pac_info_len); 906189251Ssam pos += pac->pac_info_len; 907189251Ssam 908189251Ssam pac = pac->next; 909189251Ssam count++; 910189251Ssam } 911189251Ssam 912189251Ssam if (eap_fast_write_pac(sm, pac_file, (char *) buf, len)) { 913189251Ssam os_free(buf); 914189251Ssam return -1; 915189251Ssam } 916189251Ssam 917189251Ssam wpa_printf(MSG_DEBUG, "EAP-FAST: Wrote %lu PAC entries into '%s' " 918189251Ssam "(bin)", (unsigned long) count, pac_file); 919189251Ssam 920189251Ssam return 0; 921189251Ssam} 922