1189251Ssam/* 2214734Srpaulo * RADIUS message processing 3214734Srpaulo * Copyright (c) 2002-2009, Jouni Malinen <j@w1.fi> 4189251Ssam * 5189251Ssam * This program is free software; you can redistribute it and/or modify 6189251Ssam * it under the terms of the GNU General Public License version 2 as 7189251Ssam * published by the Free Software Foundation. 8189251Ssam * 9189251Ssam * Alternatively, this software may be distributed under the terms of BSD 10189251Ssam * license. 11189251Ssam * 12189251Ssam * See README and COPYING for more details. 13189251Ssam */ 14189251Ssam 15214734Srpaulo#include "utils/includes.h" 16189251Ssam 17214734Srpaulo#include "utils/common.h" 18214734Srpaulo#include "utils/wpabuf.h" 19214734Srpaulo#include "crypto/md5.h" 20214734Srpaulo#include "crypto/crypto.h" 21189251Ssam#include "radius.h" 22189251Ssam 23189251Ssam 24214734Srpaulo/** 25214734Srpaulo * struct radius_msg - RADIUS message structure for new and parsed messages 26214734Srpaulo */ 27214734Srpaulostruct radius_msg { 28214734Srpaulo /** 29214734Srpaulo * buf - Allocated buffer for RADIUS message 30214734Srpaulo */ 31214734Srpaulo struct wpabuf *buf; 32214734Srpaulo 33214734Srpaulo /** 34214734Srpaulo * hdr - Pointer to the RADIUS header in buf 35214734Srpaulo */ 36214734Srpaulo struct radius_hdr *hdr; 37214734Srpaulo 38214734Srpaulo /** 39214734Srpaulo * attr_pos - Array of indexes to attributes 40214734Srpaulo * 41214734Srpaulo * The values are number of bytes from buf to the beginning of 42214734Srpaulo * struct radius_attr_hdr. 43214734Srpaulo */ 44214734Srpaulo size_t *attr_pos; 45214734Srpaulo 46214734Srpaulo /** 47214734Srpaulo * attr_size - Total size of the attribute pointer array 48214734Srpaulo */ 49214734Srpaulo size_t attr_size; 50214734Srpaulo 51214734Srpaulo /** 52214734Srpaulo * attr_used - Total number of attributes in the array 53214734Srpaulo */ 54214734Srpaulo size_t attr_used; 55214734Srpaulo}; 56214734Srpaulo 57214734Srpaulo 58214734Srpaulostruct radius_hdr * radius_msg_get_hdr(struct radius_msg *msg) 59189251Ssam{ 60214734Srpaulo return msg->hdr; 61189251Ssam} 62189251Ssam 63189251Ssam 64214734Srpaulostruct wpabuf * radius_msg_get_buf(struct radius_msg *msg) 65189251Ssam{ 66214734Srpaulo return msg->buf; 67214734Srpaulo} 68189251Ssam 69189251Ssam 70214734Srpaulostatic struct radius_attr_hdr * 71214734Srpauloradius_get_attr_hdr(struct radius_msg *msg, int idx) 72214734Srpaulo{ 73214734Srpaulo return (struct radius_attr_hdr *) 74214734Srpaulo (wpabuf_mhead_u8(msg->buf) + msg->attr_pos[idx]); 75214734Srpaulo} 76189251Ssam 77189251Ssam 78214734Srpaulostatic void radius_msg_set_hdr(struct radius_msg *msg, u8 code, u8 identifier) 79214734Srpaulo{ 80214734Srpaulo msg->hdr->code = code; 81214734Srpaulo msg->hdr->identifier = identifier; 82189251Ssam} 83189251Ssam 84189251Ssam 85214734Srpaulostatic int radius_msg_initialize(struct radius_msg *msg) 86189251Ssam{ 87189251Ssam msg->attr_pos = 88189251Ssam os_zalloc(RADIUS_DEFAULT_ATTR_COUNT * sizeof(*msg->attr_pos)); 89214734Srpaulo if (msg->attr_pos == NULL) 90189251Ssam return -1; 91189251Ssam 92189251Ssam msg->attr_size = RADIUS_DEFAULT_ATTR_COUNT; 93189251Ssam msg->attr_used = 0; 94189251Ssam 95189251Ssam return 0; 96189251Ssam} 97189251Ssam 98189251Ssam 99214734Srpaulo/** 100214734Srpaulo * radius_msg_new - Create a new RADIUS message 101214734Srpaulo * @code: Code for RADIUS header 102214734Srpaulo * @identifier: Identifier for RADIUS header 103214734Srpaulo * Returns: Context for RADIUS message or %NULL on failure 104214734Srpaulo * 105214734Srpaulo * The caller is responsible for freeing the returned data with 106214734Srpaulo * radius_msg_free(). 107214734Srpaulo */ 108214734Srpaulostruct radius_msg * radius_msg_new(u8 code, u8 identifier) 109189251Ssam{ 110214734Srpaulo struct radius_msg *msg; 111214734Srpaulo 112214734Srpaulo msg = os_zalloc(sizeof(*msg)); 113214734Srpaulo if (msg == NULL) 114214734Srpaulo return NULL; 115214734Srpaulo 116214734Srpaulo msg->buf = wpabuf_alloc(RADIUS_DEFAULT_MSG_SIZE); 117214734Srpaulo if (msg->buf == NULL || radius_msg_initialize(msg)) { 118214734Srpaulo radius_msg_free(msg); 119214734Srpaulo return NULL; 120214734Srpaulo } 121214734Srpaulo msg->hdr = wpabuf_put(msg->buf, sizeof(struct radius_hdr)); 122214734Srpaulo 123214734Srpaulo radius_msg_set_hdr(msg, code, identifier); 124214734Srpaulo 125214734Srpaulo return msg; 126189251Ssam} 127189251Ssam 128189251Ssam 129214734Srpaulo/** 130214734Srpaulo * radius_msg_free - Free a RADIUS message 131214734Srpaulo * @msg: RADIUS message from radius_msg_new() or radius_msg_parse() 132214734Srpaulo */ 133189251Ssamvoid radius_msg_free(struct radius_msg *msg) 134189251Ssam{ 135214734Srpaulo if (msg == NULL) 136214734Srpaulo return; 137189251Ssam 138214734Srpaulo wpabuf_free(msg->buf); 139189251Ssam os_free(msg->attr_pos); 140214734Srpaulo os_free(msg); 141189251Ssam} 142189251Ssam 143189251Ssam 144189251Ssamstatic const char *radius_code_string(u8 code) 145189251Ssam{ 146189251Ssam switch (code) { 147189251Ssam case RADIUS_CODE_ACCESS_REQUEST: return "Access-Request"; 148189251Ssam case RADIUS_CODE_ACCESS_ACCEPT: return "Access-Accept"; 149189251Ssam case RADIUS_CODE_ACCESS_REJECT: return "Access-Reject"; 150189251Ssam case RADIUS_CODE_ACCOUNTING_REQUEST: return "Accounting-Request"; 151189251Ssam case RADIUS_CODE_ACCOUNTING_RESPONSE: return "Accounting-Response"; 152189251Ssam case RADIUS_CODE_ACCESS_CHALLENGE: return "Access-Challenge"; 153189251Ssam case RADIUS_CODE_STATUS_SERVER: return "Status-Server"; 154189251Ssam case RADIUS_CODE_STATUS_CLIENT: return "Status-Client"; 155189251Ssam case RADIUS_CODE_RESERVED: return "Reserved"; 156189251Ssam default: return "?Unknown?"; 157189251Ssam } 158189251Ssam} 159189251Ssam 160189251Ssam 161189251Ssamstruct radius_attr_type { 162189251Ssam u8 type; 163189251Ssam char *name; 164189251Ssam enum { 165189251Ssam RADIUS_ATTR_UNDIST, RADIUS_ATTR_TEXT, RADIUS_ATTR_IP, 166189251Ssam RADIUS_ATTR_HEXDUMP, RADIUS_ATTR_INT32, RADIUS_ATTR_IPV6 167189251Ssam } data_type; 168189251Ssam}; 169189251Ssam 170189251Ssamstatic struct radius_attr_type radius_attrs[] = 171189251Ssam{ 172189251Ssam { RADIUS_ATTR_USER_NAME, "User-Name", RADIUS_ATTR_TEXT }, 173189251Ssam { RADIUS_ATTR_USER_PASSWORD, "User-Password", RADIUS_ATTR_UNDIST }, 174189251Ssam { RADIUS_ATTR_NAS_IP_ADDRESS, "NAS-IP-Address", RADIUS_ATTR_IP }, 175189251Ssam { RADIUS_ATTR_NAS_PORT, "NAS-Port", RADIUS_ATTR_INT32 }, 176189251Ssam { RADIUS_ATTR_FRAMED_MTU, "Framed-MTU", RADIUS_ATTR_INT32 }, 177189251Ssam { RADIUS_ATTR_REPLY_MESSAGE, "Reply-Message", RADIUS_ATTR_TEXT }, 178189251Ssam { RADIUS_ATTR_STATE, "State", RADIUS_ATTR_UNDIST }, 179189251Ssam { RADIUS_ATTR_CLASS, "Class", RADIUS_ATTR_UNDIST }, 180189251Ssam { RADIUS_ATTR_VENDOR_SPECIFIC, "Vendor-Specific", RADIUS_ATTR_UNDIST }, 181189251Ssam { RADIUS_ATTR_SESSION_TIMEOUT, "Session-Timeout", RADIUS_ATTR_INT32 }, 182189251Ssam { RADIUS_ATTR_IDLE_TIMEOUT, "Idle-Timeout", RADIUS_ATTR_INT32 }, 183189251Ssam { RADIUS_ATTR_TERMINATION_ACTION, "Termination-Action", 184189251Ssam RADIUS_ATTR_INT32 }, 185189251Ssam { RADIUS_ATTR_CALLED_STATION_ID, "Called-Station-Id", 186189251Ssam RADIUS_ATTR_TEXT }, 187189251Ssam { RADIUS_ATTR_CALLING_STATION_ID, "Calling-Station-Id", 188189251Ssam RADIUS_ATTR_TEXT }, 189189251Ssam { RADIUS_ATTR_NAS_IDENTIFIER, "NAS-Identifier", RADIUS_ATTR_TEXT }, 190189251Ssam { RADIUS_ATTR_PROXY_STATE, "Proxy-State", RADIUS_ATTR_UNDIST }, 191189251Ssam { RADIUS_ATTR_ACCT_STATUS_TYPE, "Acct-Status-Type", 192189251Ssam RADIUS_ATTR_INT32 }, 193189251Ssam { RADIUS_ATTR_ACCT_DELAY_TIME, "Acct-Delay-Time", RADIUS_ATTR_INT32 }, 194189251Ssam { RADIUS_ATTR_ACCT_INPUT_OCTETS, "Acct-Input-Octets", 195189251Ssam RADIUS_ATTR_INT32 }, 196189251Ssam { RADIUS_ATTR_ACCT_OUTPUT_OCTETS, "Acct-Output-Octets", 197189251Ssam RADIUS_ATTR_INT32 }, 198189251Ssam { RADIUS_ATTR_ACCT_SESSION_ID, "Acct-Session-Id", RADIUS_ATTR_TEXT }, 199189251Ssam { RADIUS_ATTR_ACCT_AUTHENTIC, "Acct-Authentic", RADIUS_ATTR_INT32 }, 200189251Ssam { RADIUS_ATTR_ACCT_SESSION_TIME, "Acct-Session-Time", 201189251Ssam RADIUS_ATTR_INT32 }, 202189251Ssam { RADIUS_ATTR_ACCT_INPUT_PACKETS, "Acct-Input-Packets", 203189251Ssam RADIUS_ATTR_INT32 }, 204189251Ssam { RADIUS_ATTR_ACCT_OUTPUT_PACKETS, "Acct-Output-Packets", 205189251Ssam RADIUS_ATTR_INT32 }, 206189251Ssam { RADIUS_ATTR_ACCT_TERMINATE_CAUSE, "Acct-Terminate-Cause", 207189251Ssam RADIUS_ATTR_INT32 }, 208189251Ssam { RADIUS_ATTR_ACCT_MULTI_SESSION_ID, "Acct-Multi-Session-Id", 209189251Ssam RADIUS_ATTR_TEXT }, 210189251Ssam { RADIUS_ATTR_ACCT_LINK_COUNT, "Acct-Link-Count", RADIUS_ATTR_INT32 }, 211189251Ssam { RADIUS_ATTR_ACCT_INPUT_GIGAWORDS, "Acct-Input-Gigawords", 212189251Ssam RADIUS_ATTR_INT32 }, 213189251Ssam { RADIUS_ATTR_ACCT_OUTPUT_GIGAWORDS, "Acct-Output-Gigawords", 214189251Ssam RADIUS_ATTR_INT32 }, 215189251Ssam { RADIUS_ATTR_EVENT_TIMESTAMP, "Event-Timestamp", 216189251Ssam RADIUS_ATTR_INT32 }, 217189251Ssam { RADIUS_ATTR_NAS_PORT_TYPE, "NAS-Port-Type", RADIUS_ATTR_INT32 }, 218189251Ssam { RADIUS_ATTR_TUNNEL_TYPE, "Tunnel-Type", RADIUS_ATTR_HEXDUMP }, 219189251Ssam { RADIUS_ATTR_TUNNEL_MEDIUM_TYPE, "Tunnel-Medium-Type", 220189251Ssam RADIUS_ATTR_HEXDUMP }, 221189251Ssam { RADIUS_ATTR_CONNECT_INFO, "Connect-Info", RADIUS_ATTR_TEXT }, 222189251Ssam { RADIUS_ATTR_EAP_MESSAGE, "EAP-Message", RADIUS_ATTR_UNDIST }, 223189251Ssam { RADIUS_ATTR_MESSAGE_AUTHENTICATOR, "Message-Authenticator", 224189251Ssam RADIUS_ATTR_UNDIST }, 225189251Ssam { RADIUS_ATTR_TUNNEL_PRIVATE_GROUP_ID, "Tunnel-Private-Group-Id", 226189251Ssam RADIUS_ATTR_HEXDUMP }, 227189251Ssam { RADIUS_ATTR_ACCT_INTERIM_INTERVAL, "Acct-Interim-Interval", 228189251Ssam RADIUS_ATTR_INT32 }, 229189251Ssam { RADIUS_ATTR_CHARGEABLE_USER_IDENTITY, "Chargable-User-Identity", 230189251Ssam RADIUS_ATTR_TEXT }, 231189251Ssam { RADIUS_ATTR_NAS_IPV6_ADDRESS, "NAS-IPv6-Address", RADIUS_ATTR_IPV6 }, 232189251Ssam}; 233189251Ssam#define RADIUS_ATTRS (sizeof(radius_attrs) / sizeof(radius_attrs[0])) 234189251Ssam 235189251Ssam 236189251Ssamstatic struct radius_attr_type *radius_get_attr_type(u8 type) 237189251Ssam{ 238189251Ssam size_t i; 239189251Ssam 240189251Ssam for (i = 0; i < RADIUS_ATTRS; i++) { 241189251Ssam if (type == radius_attrs[i].type) 242189251Ssam return &radius_attrs[i]; 243189251Ssam } 244189251Ssam 245189251Ssam return NULL; 246189251Ssam} 247189251Ssam 248189251Ssam 249189251Ssamstatic void print_char(char c) 250189251Ssam{ 251189251Ssam if (c >= 32 && c < 127) 252189251Ssam printf("%c", c); 253189251Ssam else 254189251Ssam printf("<%02x>", c); 255189251Ssam} 256189251Ssam 257189251Ssam 258189251Ssamstatic void radius_msg_dump_attr(struct radius_attr_hdr *hdr) 259189251Ssam{ 260189251Ssam struct radius_attr_type *attr; 261189251Ssam int i, len; 262189251Ssam unsigned char *pos; 263189251Ssam 264189251Ssam attr = radius_get_attr_type(hdr->type); 265189251Ssam 266189251Ssam printf(" Attribute %d (%s) length=%d\n", 267189251Ssam hdr->type, attr ? attr->name : "?Unknown?", hdr->length); 268189251Ssam 269189251Ssam if (attr == NULL) 270189251Ssam return; 271189251Ssam 272189251Ssam len = hdr->length - sizeof(struct radius_attr_hdr); 273189251Ssam pos = (unsigned char *) (hdr + 1); 274189251Ssam 275189251Ssam switch (attr->data_type) { 276189251Ssam case RADIUS_ATTR_TEXT: 277189251Ssam printf(" Value: '"); 278189251Ssam for (i = 0; i < len; i++) 279189251Ssam print_char(pos[i]); 280189251Ssam printf("'\n"); 281189251Ssam break; 282189251Ssam 283189251Ssam case RADIUS_ATTR_IP: 284189251Ssam if (len == 4) { 285189251Ssam struct in_addr addr; 286189251Ssam os_memcpy(&addr, pos, 4); 287189251Ssam printf(" Value: %s\n", inet_ntoa(addr)); 288189251Ssam } else 289189251Ssam printf(" Invalid IP address length %d\n", len); 290189251Ssam break; 291189251Ssam 292189251Ssam#ifdef CONFIG_IPV6 293189251Ssam case RADIUS_ATTR_IPV6: 294189251Ssam if (len == 16) { 295189251Ssam char buf[128]; 296189251Ssam const char *atxt; 297189251Ssam struct in6_addr *addr = (struct in6_addr *) pos; 298189251Ssam atxt = inet_ntop(AF_INET6, addr, buf, sizeof(buf)); 299189251Ssam printf(" Value: %s\n", atxt ? atxt : "?"); 300189251Ssam } else 301189251Ssam printf(" Invalid IPv6 address length %d\n", len); 302189251Ssam break; 303189251Ssam#endif /* CONFIG_IPV6 */ 304189251Ssam 305189251Ssam case RADIUS_ATTR_HEXDUMP: 306189251Ssam case RADIUS_ATTR_UNDIST: 307189251Ssam printf(" Value:"); 308189251Ssam for (i = 0; i < len; i++) 309189251Ssam printf(" %02x", pos[i]); 310189251Ssam printf("\n"); 311189251Ssam break; 312189251Ssam 313189251Ssam case RADIUS_ATTR_INT32: 314189251Ssam if (len == 4) 315189251Ssam printf(" Value: %u\n", WPA_GET_BE32(pos)); 316189251Ssam else 317189251Ssam printf(" Invalid INT32 length %d\n", len); 318189251Ssam break; 319189251Ssam 320189251Ssam default: 321189251Ssam break; 322189251Ssam } 323189251Ssam} 324189251Ssam 325189251Ssam 326189251Ssamvoid radius_msg_dump(struct radius_msg *msg) 327189251Ssam{ 328189251Ssam size_t i; 329189251Ssam 330189251Ssam printf("RADIUS message: code=%d (%s) identifier=%d length=%d\n", 331189251Ssam msg->hdr->code, radius_code_string(msg->hdr->code), 332189251Ssam msg->hdr->identifier, ntohs(msg->hdr->length)); 333189251Ssam 334189251Ssam for (i = 0; i < msg->attr_used; i++) { 335189251Ssam struct radius_attr_hdr *attr = radius_get_attr_hdr(msg, i); 336189251Ssam radius_msg_dump_attr(attr); 337189251Ssam } 338189251Ssam} 339189251Ssam 340189251Ssam 341189251Ssamint radius_msg_finish(struct radius_msg *msg, const u8 *secret, 342189251Ssam size_t secret_len) 343189251Ssam{ 344189251Ssam if (secret) { 345189251Ssam u8 auth[MD5_MAC_LEN]; 346189251Ssam struct radius_attr_hdr *attr; 347189251Ssam 348189251Ssam os_memset(auth, 0, MD5_MAC_LEN); 349189251Ssam attr = radius_msg_add_attr(msg, 350189251Ssam RADIUS_ATTR_MESSAGE_AUTHENTICATOR, 351189251Ssam auth, MD5_MAC_LEN); 352189251Ssam if (attr == NULL) { 353214734Srpaulo wpa_printf(MSG_WARNING, "RADIUS: Could not add " 354214734Srpaulo "Message-Authenticator"); 355189251Ssam return -1; 356189251Ssam } 357214734Srpaulo msg->hdr->length = htons(wpabuf_len(msg->buf)); 358214734Srpaulo hmac_md5(secret, secret_len, wpabuf_head(msg->buf), 359214734Srpaulo wpabuf_len(msg->buf), (u8 *) (attr + 1)); 360189251Ssam } else 361214734Srpaulo msg->hdr->length = htons(wpabuf_len(msg->buf)); 362189251Ssam 363214734Srpaulo if (wpabuf_len(msg->buf) > 0xffff) { 364214734Srpaulo wpa_printf(MSG_WARNING, "RADIUS: Too long message (%lu)", 365214734Srpaulo (unsigned long) wpabuf_len(msg->buf)); 366189251Ssam return -1; 367189251Ssam } 368189251Ssam return 0; 369189251Ssam} 370189251Ssam 371189251Ssam 372189251Ssamint radius_msg_finish_srv(struct radius_msg *msg, const u8 *secret, 373189251Ssam size_t secret_len, const u8 *req_authenticator) 374189251Ssam{ 375189251Ssam u8 auth[MD5_MAC_LEN]; 376189251Ssam struct radius_attr_hdr *attr; 377189251Ssam const u8 *addr[4]; 378189251Ssam size_t len[4]; 379189251Ssam 380189251Ssam os_memset(auth, 0, MD5_MAC_LEN); 381189251Ssam attr = radius_msg_add_attr(msg, RADIUS_ATTR_MESSAGE_AUTHENTICATOR, 382189251Ssam auth, MD5_MAC_LEN); 383189251Ssam if (attr == NULL) { 384189251Ssam printf("WARNING: Could not add Message-Authenticator\n"); 385189251Ssam return -1; 386189251Ssam } 387214734Srpaulo msg->hdr->length = htons(wpabuf_len(msg->buf)); 388189251Ssam os_memcpy(msg->hdr->authenticator, req_authenticator, 389189251Ssam sizeof(msg->hdr->authenticator)); 390214734Srpaulo hmac_md5(secret, secret_len, wpabuf_head(msg->buf), 391214734Srpaulo wpabuf_len(msg->buf), (u8 *) (attr + 1)); 392189251Ssam 393189251Ssam /* ResponseAuth = MD5(Code+ID+Length+RequestAuth+Attributes+Secret) */ 394189251Ssam addr[0] = (u8 *) msg->hdr; 395189251Ssam len[0] = 1 + 1 + 2; 396189251Ssam addr[1] = req_authenticator; 397189251Ssam len[1] = MD5_MAC_LEN; 398214734Srpaulo addr[2] = wpabuf_head_u8(msg->buf) + sizeof(struct radius_hdr); 399214734Srpaulo len[2] = wpabuf_len(msg->buf) - sizeof(struct radius_hdr); 400189251Ssam addr[3] = secret; 401189251Ssam len[3] = secret_len; 402189251Ssam md5_vector(4, addr, len, msg->hdr->authenticator); 403189251Ssam 404214734Srpaulo if (wpabuf_len(msg->buf) > 0xffff) { 405214734Srpaulo wpa_printf(MSG_WARNING, "RADIUS: Too long message (%lu)", 406214734Srpaulo (unsigned long) wpabuf_len(msg->buf)); 407189251Ssam return -1; 408189251Ssam } 409189251Ssam return 0; 410189251Ssam} 411189251Ssam 412189251Ssam 413189251Ssamvoid radius_msg_finish_acct(struct radius_msg *msg, const u8 *secret, 414189251Ssam size_t secret_len) 415189251Ssam{ 416189251Ssam const u8 *addr[2]; 417189251Ssam size_t len[2]; 418189251Ssam 419214734Srpaulo msg->hdr->length = htons(wpabuf_len(msg->buf)); 420189251Ssam os_memset(msg->hdr->authenticator, 0, MD5_MAC_LEN); 421214734Srpaulo addr[0] = wpabuf_head(msg->buf); 422214734Srpaulo len[0] = wpabuf_len(msg->buf); 423189251Ssam addr[1] = secret; 424189251Ssam len[1] = secret_len; 425189251Ssam md5_vector(2, addr, len, msg->hdr->authenticator); 426189251Ssam 427214734Srpaulo if (wpabuf_len(msg->buf) > 0xffff) { 428214734Srpaulo wpa_printf(MSG_WARNING, "RADIUS: Too long messages (%lu)", 429214734Srpaulo (unsigned long) wpabuf_len(msg->buf)); 430189251Ssam } 431189251Ssam} 432189251Ssam 433189251Ssam 434189251Ssamstatic int radius_msg_add_attr_to_array(struct radius_msg *msg, 435189251Ssam struct radius_attr_hdr *attr) 436189251Ssam{ 437189251Ssam if (msg->attr_used >= msg->attr_size) { 438189251Ssam size_t *nattr_pos; 439189251Ssam int nlen = msg->attr_size * 2; 440189251Ssam 441189251Ssam nattr_pos = os_realloc(msg->attr_pos, 442189251Ssam nlen * sizeof(*msg->attr_pos)); 443189251Ssam if (nattr_pos == NULL) 444189251Ssam return -1; 445189251Ssam 446189251Ssam msg->attr_pos = nattr_pos; 447189251Ssam msg->attr_size = nlen; 448189251Ssam } 449189251Ssam 450214734Srpaulo msg->attr_pos[msg->attr_used++] = 451214734Srpaulo (unsigned char *) attr - wpabuf_head_u8(msg->buf); 452189251Ssam 453189251Ssam return 0; 454189251Ssam} 455189251Ssam 456189251Ssam 457189251Ssamstruct radius_attr_hdr *radius_msg_add_attr(struct radius_msg *msg, u8 type, 458189251Ssam const u8 *data, size_t data_len) 459189251Ssam{ 460189251Ssam size_t buf_needed; 461189251Ssam struct radius_attr_hdr *attr; 462189251Ssam 463189251Ssam if (data_len > RADIUS_MAX_ATTR_LEN) { 464189251Ssam printf("radius_msg_add_attr: too long attribute (%lu bytes)\n", 465189251Ssam (unsigned long) data_len); 466189251Ssam return NULL; 467189251Ssam } 468189251Ssam 469214734Srpaulo buf_needed = sizeof(*attr) + data_len; 470189251Ssam 471214734Srpaulo if (wpabuf_tailroom(msg->buf) < buf_needed) { 472189251Ssam /* allocate more space for message buffer */ 473214734Srpaulo if (wpabuf_resize(&msg->buf, buf_needed) < 0) 474189251Ssam return NULL; 475214734Srpaulo msg->hdr = wpabuf_mhead(msg->buf); 476189251Ssam } 477189251Ssam 478214734Srpaulo attr = wpabuf_put(msg->buf, sizeof(struct radius_attr_hdr)); 479189251Ssam attr->type = type; 480189251Ssam attr->length = sizeof(*attr) + data_len; 481214734Srpaulo wpabuf_put_data(msg->buf, data, data_len); 482189251Ssam 483189251Ssam if (radius_msg_add_attr_to_array(msg, attr)) 484189251Ssam return NULL; 485189251Ssam 486189251Ssam return attr; 487189251Ssam} 488189251Ssam 489189251Ssam 490214734Srpaulo/** 491214734Srpaulo * radius_msg_parse - Parse a RADIUS message 492214734Srpaulo * @data: RADIUS message to be parsed 493214734Srpaulo * @len: Length of data buffer in octets 494214734Srpaulo * Returns: Parsed RADIUS message or %NULL on failure 495214734Srpaulo * 496214734Srpaulo * This parses a RADIUS message and makes a copy of its data. The caller is 497214734Srpaulo * responsible for freeing the returned data with radius_msg_free(). 498214734Srpaulo */ 499214734Srpaulostruct radius_msg * radius_msg_parse(const u8 *data, size_t len) 500189251Ssam{ 501189251Ssam struct radius_msg *msg; 502189251Ssam struct radius_hdr *hdr; 503189251Ssam struct radius_attr_hdr *attr; 504189251Ssam size_t msg_len; 505189251Ssam unsigned char *pos, *end; 506189251Ssam 507189251Ssam if (data == NULL || len < sizeof(*hdr)) 508189251Ssam return NULL; 509189251Ssam 510189251Ssam hdr = (struct radius_hdr *) data; 511189251Ssam 512189251Ssam msg_len = ntohs(hdr->length); 513189251Ssam if (msg_len < sizeof(*hdr) || msg_len > len) { 514214734Srpaulo wpa_printf(MSG_INFO, "RADIUS: Invalid message length"); 515189251Ssam return NULL; 516189251Ssam } 517189251Ssam 518189251Ssam if (msg_len < len) { 519214734Srpaulo wpa_printf(MSG_DEBUG, "RADIUS: Ignored %lu extra bytes after " 520214734Srpaulo "RADIUS message", (unsigned long) len - msg_len); 521189251Ssam } 522189251Ssam 523214734Srpaulo msg = os_zalloc(sizeof(*msg)); 524189251Ssam if (msg == NULL) 525189251Ssam return NULL; 526189251Ssam 527214734Srpaulo msg->buf = wpabuf_alloc_copy(data, msg_len); 528214734Srpaulo if (msg->buf == NULL || radius_msg_initialize(msg)) { 529214734Srpaulo radius_msg_free(msg); 530189251Ssam return NULL; 531189251Ssam } 532214734Srpaulo msg->hdr = wpabuf_mhead(msg->buf); 533189251Ssam 534189251Ssam /* parse attributes */ 535214734Srpaulo pos = wpabuf_mhead_u8(msg->buf) + sizeof(struct radius_hdr); 536214734Srpaulo end = wpabuf_mhead_u8(msg->buf) + wpabuf_len(msg->buf); 537189251Ssam while (pos < end) { 538189251Ssam if ((size_t) (end - pos) < sizeof(*attr)) 539189251Ssam goto fail; 540189251Ssam 541189251Ssam attr = (struct radius_attr_hdr *) pos; 542189251Ssam 543189251Ssam if (pos + attr->length > end || attr->length < sizeof(*attr)) 544189251Ssam goto fail; 545189251Ssam 546189251Ssam /* TODO: check that attr->length is suitable for attr->type */ 547189251Ssam 548189251Ssam if (radius_msg_add_attr_to_array(msg, attr)) 549189251Ssam goto fail; 550189251Ssam 551189251Ssam pos += attr->length; 552189251Ssam } 553189251Ssam 554189251Ssam return msg; 555189251Ssam 556189251Ssam fail: 557189251Ssam radius_msg_free(msg); 558189251Ssam return NULL; 559189251Ssam} 560189251Ssam 561189251Ssam 562189251Ssamint radius_msg_add_eap(struct radius_msg *msg, const u8 *data, size_t data_len) 563189251Ssam{ 564189251Ssam const u8 *pos = data; 565189251Ssam size_t left = data_len; 566189251Ssam 567189251Ssam while (left > 0) { 568189251Ssam int len; 569189251Ssam if (left > RADIUS_MAX_ATTR_LEN) 570189251Ssam len = RADIUS_MAX_ATTR_LEN; 571189251Ssam else 572189251Ssam len = left; 573189251Ssam 574189251Ssam if (!radius_msg_add_attr(msg, RADIUS_ATTR_EAP_MESSAGE, 575189251Ssam pos, len)) 576189251Ssam return 0; 577189251Ssam 578189251Ssam pos += len; 579189251Ssam left -= len; 580189251Ssam } 581189251Ssam 582189251Ssam return 1; 583189251Ssam} 584189251Ssam 585189251Ssam 586189251Ssamu8 *radius_msg_get_eap(struct radius_msg *msg, size_t *eap_len) 587189251Ssam{ 588189251Ssam u8 *eap, *pos; 589189251Ssam size_t len, i; 590189251Ssam struct radius_attr_hdr *attr; 591189251Ssam 592189251Ssam if (msg == NULL) 593189251Ssam return NULL; 594189251Ssam 595189251Ssam len = 0; 596189251Ssam for (i = 0; i < msg->attr_used; i++) { 597189251Ssam attr = radius_get_attr_hdr(msg, i); 598189251Ssam if (attr->type == RADIUS_ATTR_EAP_MESSAGE) 599189251Ssam len += attr->length - sizeof(struct radius_attr_hdr); 600189251Ssam } 601189251Ssam 602189251Ssam if (len == 0) 603189251Ssam return NULL; 604189251Ssam 605189251Ssam eap = os_malloc(len); 606189251Ssam if (eap == NULL) 607189251Ssam return NULL; 608189251Ssam 609189251Ssam pos = eap; 610189251Ssam for (i = 0; i < msg->attr_used; i++) { 611189251Ssam attr = radius_get_attr_hdr(msg, i); 612189251Ssam if (attr->type == RADIUS_ATTR_EAP_MESSAGE) { 613189251Ssam int flen = attr->length - sizeof(*attr); 614189251Ssam os_memcpy(pos, attr + 1, flen); 615189251Ssam pos += flen; 616189251Ssam } 617189251Ssam } 618189251Ssam 619189251Ssam if (eap_len) 620189251Ssam *eap_len = len; 621189251Ssam 622189251Ssam return eap; 623189251Ssam} 624189251Ssam 625189251Ssam 626189251Ssamint radius_msg_verify_msg_auth(struct radius_msg *msg, const u8 *secret, 627189251Ssam size_t secret_len, const u8 *req_auth) 628189251Ssam{ 629189251Ssam u8 auth[MD5_MAC_LEN], orig[MD5_MAC_LEN]; 630189251Ssam u8 orig_authenticator[16]; 631189251Ssam struct radius_attr_hdr *attr = NULL, *tmp; 632189251Ssam size_t i; 633189251Ssam 634189251Ssam for (i = 0; i < msg->attr_used; i++) { 635189251Ssam tmp = radius_get_attr_hdr(msg, i); 636189251Ssam if (tmp->type == RADIUS_ATTR_MESSAGE_AUTHENTICATOR) { 637189251Ssam if (attr != NULL) { 638189251Ssam printf("Multiple Message-Authenticator " 639189251Ssam "attributes in RADIUS message\n"); 640189251Ssam return 1; 641189251Ssam } 642189251Ssam attr = tmp; 643189251Ssam } 644189251Ssam } 645189251Ssam 646189251Ssam if (attr == NULL) { 647189251Ssam printf("No Message-Authenticator attribute found\n"); 648189251Ssam return 1; 649189251Ssam } 650189251Ssam 651189251Ssam os_memcpy(orig, attr + 1, MD5_MAC_LEN); 652189251Ssam os_memset(attr + 1, 0, MD5_MAC_LEN); 653189251Ssam if (req_auth) { 654189251Ssam os_memcpy(orig_authenticator, msg->hdr->authenticator, 655189251Ssam sizeof(orig_authenticator)); 656189251Ssam os_memcpy(msg->hdr->authenticator, req_auth, 657189251Ssam sizeof(msg->hdr->authenticator)); 658189251Ssam } 659214734Srpaulo hmac_md5(secret, secret_len, wpabuf_head(msg->buf), 660214734Srpaulo wpabuf_len(msg->buf), auth); 661189251Ssam os_memcpy(attr + 1, orig, MD5_MAC_LEN); 662189251Ssam if (req_auth) { 663189251Ssam os_memcpy(msg->hdr->authenticator, orig_authenticator, 664189251Ssam sizeof(orig_authenticator)); 665189251Ssam } 666189251Ssam 667189251Ssam if (os_memcmp(orig, auth, MD5_MAC_LEN) != 0) { 668189251Ssam printf("Invalid Message-Authenticator!\n"); 669189251Ssam return 1; 670189251Ssam } 671189251Ssam 672189251Ssam return 0; 673189251Ssam} 674189251Ssam 675189251Ssam 676189251Ssamint radius_msg_verify(struct radius_msg *msg, const u8 *secret, 677189251Ssam size_t secret_len, struct radius_msg *sent_msg, int auth) 678189251Ssam{ 679189251Ssam const u8 *addr[4]; 680189251Ssam size_t len[4]; 681189251Ssam u8 hash[MD5_MAC_LEN]; 682189251Ssam 683189251Ssam if (sent_msg == NULL) { 684189251Ssam printf("No matching Access-Request message found\n"); 685189251Ssam return 1; 686189251Ssam } 687189251Ssam 688189251Ssam if (auth && 689189251Ssam radius_msg_verify_msg_auth(msg, secret, secret_len, 690189251Ssam sent_msg->hdr->authenticator)) { 691189251Ssam return 1; 692189251Ssam } 693189251Ssam 694189251Ssam /* ResponseAuth = MD5(Code+ID+Length+RequestAuth+Attributes+Secret) */ 695189251Ssam addr[0] = (u8 *) msg->hdr; 696189251Ssam len[0] = 1 + 1 + 2; 697189251Ssam addr[1] = sent_msg->hdr->authenticator; 698189251Ssam len[1] = MD5_MAC_LEN; 699214734Srpaulo addr[2] = wpabuf_head_u8(msg->buf) + sizeof(struct radius_hdr); 700214734Srpaulo len[2] = wpabuf_len(msg->buf) - sizeof(struct radius_hdr); 701189251Ssam addr[3] = secret; 702189251Ssam len[3] = secret_len; 703189251Ssam md5_vector(4, addr, len, hash); 704189251Ssam if (os_memcmp(hash, msg->hdr->authenticator, MD5_MAC_LEN) != 0) { 705189251Ssam printf("Response Authenticator invalid!\n"); 706189251Ssam return 1; 707189251Ssam } 708189251Ssam 709189251Ssam return 0; 710189251Ssam} 711189251Ssam 712189251Ssam 713189251Ssamint radius_msg_copy_attr(struct radius_msg *dst, struct radius_msg *src, 714189251Ssam u8 type) 715189251Ssam{ 716189251Ssam struct radius_attr_hdr *attr; 717189251Ssam size_t i; 718189251Ssam int count = 0; 719189251Ssam 720189251Ssam for (i = 0; i < src->attr_used; i++) { 721189251Ssam attr = radius_get_attr_hdr(src, i); 722189251Ssam if (attr->type == type) { 723189251Ssam if (!radius_msg_add_attr(dst, type, (u8 *) (attr + 1), 724189251Ssam attr->length - sizeof(*attr))) 725189251Ssam return -1; 726189251Ssam count++; 727189251Ssam } 728189251Ssam } 729189251Ssam 730189251Ssam return count; 731189251Ssam} 732189251Ssam 733189251Ssam 734189251Ssam/* Create Request Authenticator. The value should be unique over the lifetime 735189251Ssam * of the shared secret between authenticator and authentication server. 736189251Ssam * Use one-way MD5 hash calculated from current timestamp and some data given 737189251Ssam * by the caller. */ 738189251Ssamvoid radius_msg_make_authenticator(struct radius_msg *msg, 739189251Ssam const u8 *data, size_t len) 740189251Ssam{ 741189251Ssam struct os_time tv; 742189251Ssam long int l; 743189251Ssam const u8 *addr[3]; 744189251Ssam size_t elen[3]; 745189251Ssam 746189251Ssam os_get_time(&tv); 747189251Ssam l = os_random(); 748189251Ssam addr[0] = (u8 *) &tv; 749189251Ssam elen[0] = sizeof(tv); 750189251Ssam addr[1] = data; 751189251Ssam elen[1] = len; 752189251Ssam addr[2] = (u8 *) &l; 753189251Ssam elen[2] = sizeof(l); 754189251Ssam md5_vector(3, addr, elen, msg->hdr->authenticator); 755189251Ssam} 756189251Ssam 757189251Ssam 758189251Ssam/* Get Vendor-specific RADIUS Attribute from a parsed RADIUS message. 759189251Ssam * Returns the Attribute payload and sets alen to indicate the length of the 760189251Ssam * payload if a vendor attribute with subtype is found, otherwise returns NULL. 761189251Ssam * The returned payload is allocated with os_malloc() and caller must free it 762189251Ssam * by calling os_free(). 763189251Ssam */ 764189251Ssamstatic u8 *radius_msg_get_vendor_attr(struct radius_msg *msg, u32 vendor, 765189251Ssam u8 subtype, size_t *alen) 766189251Ssam{ 767189251Ssam u8 *data, *pos; 768189251Ssam size_t i, len; 769189251Ssam 770189251Ssam if (msg == NULL) 771189251Ssam return NULL; 772189251Ssam 773189251Ssam for (i = 0; i < msg->attr_used; i++) { 774189251Ssam struct radius_attr_hdr *attr = radius_get_attr_hdr(msg, i); 775189251Ssam size_t left; 776189251Ssam u32 vendor_id; 777189251Ssam struct radius_attr_vendor *vhdr; 778189251Ssam 779189251Ssam if (attr->type != RADIUS_ATTR_VENDOR_SPECIFIC) 780189251Ssam continue; 781189251Ssam 782189251Ssam left = attr->length - sizeof(*attr); 783189251Ssam if (left < 4) 784189251Ssam continue; 785189251Ssam 786189251Ssam pos = (u8 *) (attr + 1); 787189251Ssam 788189251Ssam os_memcpy(&vendor_id, pos, 4); 789189251Ssam pos += 4; 790189251Ssam left -= 4; 791189251Ssam 792189251Ssam if (ntohl(vendor_id) != vendor) 793189251Ssam continue; 794189251Ssam 795189251Ssam while (left >= sizeof(*vhdr)) { 796189251Ssam vhdr = (struct radius_attr_vendor *) pos; 797189251Ssam if (vhdr->vendor_length > left || 798189251Ssam vhdr->vendor_length < sizeof(*vhdr)) { 799189251Ssam left = 0; 800189251Ssam break; 801189251Ssam } 802189251Ssam if (vhdr->vendor_type != subtype) { 803189251Ssam pos += vhdr->vendor_length; 804189251Ssam left -= vhdr->vendor_length; 805189251Ssam continue; 806189251Ssam } 807189251Ssam 808189251Ssam len = vhdr->vendor_length - sizeof(*vhdr); 809189251Ssam data = os_malloc(len); 810189251Ssam if (data == NULL) 811189251Ssam return NULL; 812189251Ssam os_memcpy(data, pos + sizeof(*vhdr), len); 813189251Ssam if (alen) 814189251Ssam *alen = len; 815189251Ssam return data; 816189251Ssam } 817189251Ssam } 818189251Ssam 819189251Ssam return NULL; 820189251Ssam} 821189251Ssam 822189251Ssam 823189251Ssamstatic u8 * decrypt_ms_key(const u8 *key, size_t len, 824189251Ssam const u8 *req_authenticator, 825189251Ssam const u8 *secret, size_t secret_len, size_t *reslen) 826189251Ssam{ 827189251Ssam u8 *plain, *ppos, *res; 828189251Ssam const u8 *pos; 829189251Ssam size_t left, plen; 830189251Ssam u8 hash[MD5_MAC_LEN]; 831189251Ssam int i, first = 1; 832189251Ssam const u8 *addr[3]; 833189251Ssam size_t elen[3]; 834189251Ssam 835189251Ssam /* key: 16-bit salt followed by encrypted key info */ 836189251Ssam 837189251Ssam if (len < 2 + 16) 838189251Ssam return NULL; 839189251Ssam 840189251Ssam pos = key + 2; 841189251Ssam left = len - 2; 842189251Ssam if (left % 16) { 843189251Ssam printf("Invalid ms key len %lu\n", (unsigned long) left); 844189251Ssam return NULL; 845189251Ssam } 846189251Ssam 847189251Ssam plen = left; 848189251Ssam ppos = plain = os_malloc(plen); 849189251Ssam if (plain == NULL) 850189251Ssam return NULL; 851189251Ssam plain[0] = 0; 852189251Ssam 853189251Ssam while (left > 0) { 854189251Ssam /* b(1) = MD5(Secret + Request-Authenticator + Salt) 855189251Ssam * b(i) = MD5(Secret + c(i - 1)) for i > 1 */ 856189251Ssam 857189251Ssam addr[0] = secret; 858189251Ssam elen[0] = secret_len; 859189251Ssam if (first) { 860189251Ssam addr[1] = req_authenticator; 861189251Ssam elen[1] = MD5_MAC_LEN; 862189251Ssam addr[2] = key; 863189251Ssam elen[2] = 2; /* Salt */ 864189251Ssam } else { 865189251Ssam addr[1] = pos - MD5_MAC_LEN; 866189251Ssam elen[1] = MD5_MAC_LEN; 867189251Ssam } 868189251Ssam md5_vector(first ? 3 : 2, addr, elen, hash); 869189251Ssam first = 0; 870189251Ssam 871189251Ssam for (i = 0; i < MD5_MAC_LEN; i++) 872189251Ssam *ppos++ = *pos++ ^ hash[i]; 873189251Ssam left -= MD5_MAC_LEN; 874189251Ssam } 875189251Ssam 876189251Ssam if (plain[0] == 0 || plain[0] > plen - 1) { 877189251Ssam printf("Failed to decrypt MPPE key\n"); 878189251Ssam os_free(plain); 879189251Ssam return NULL; 880189251Ssam } 881189251Ssam 882189251Ssam res = os_malloc(plain[0]); 883189251Ssam if (res == NULL) { 884189251Ssam os_free(plain); 885189251Ssam return NULL; 886189251Ssam } 887189251Ssam os_memcpy(res, plain + 1, plain[0]); 888189251Ssam if (reslen) 889189251Ssam *reslen = plain[0]; 890189251Ssam os_free(plain); 891189251Ssam return res; 892189251Ssam} 893189251Ssam 894189251Ssam 895189251Ssamstatic void encrypt_ms_key(const u8 *key, size_t key_len, u16 salt, 896189251Ssam const u8 *req_authenticator, 897189251Ssam const u8 *secret, size_t secret_len, 898189251Ssam u8 *ebuf, size_t *elen) 899189251Ssam{ 900189251Ssam int i, len, first = 1; 901189251Ssam u8 hash[MD5_MAC_LEN], saltbuf[2], *pos; 902189251Ssam const u8 *addr[3]; 903189251Ssam size_t _len[3]; 904189251Ssam 905189251Ssam WPA_PUT_BE16(saltbuf, salt); 906189251Ssam 907189251Ssam len = 1 + key_len; 908189251Ssam if (len & 0x0f) { 909189251Ssam len = (len & 0xf0) + 16; 910189251Ssam } 911189251Ssam os_memset(ebuf, 0, len); 912189251Ssam ebuf[0] = key_len; 913189251Ssam os_memcpy(ebuf + 1, key, key_len); 914189251Ssam 915189251Ssam *elen = len; 916189251Ssam 917189251Ssam pos = ebuf; 918189251Ssam while (len > 0) { 919189251Ssam /* b(1) = MD5(Secret + Request-Authenticator + Salt) 920189251Ssam * b(i) = MD5(Secret + c(i - 1)) for i > 1 */ 921189251Ssam addr[0] = secret; 922189251Ssam _len[0] = secret_len; 923189251Ssam if (first) { 924189251Ssam addr[1] = req_authenticator; 925189251Ssam _len[1] = MD5_MAC_LEN; 926189251Ssam addr[2] = saltbuf; 927189251Ssam _len[2] = sizeof(saltbuf); 928189251Ssam } else { 929189251Ssam addr[1] = pos - MD5_MAC_LEN; 930189251Ssam _len[1] = MD5_MAC_LEN; 931189251Ssam } 932189251Ssam md5_vector(first ? 3 : 2, addr, _len, hash); 933189251Ssam first = 0; 934189251Ssam 935189251Ssam for (i = 0; i < MD5_MAC_LEN; i++) 936189251Ssam *pos++ ^= hash[i]; 937189251Ssam 938189251Ssam len -= MD5_MAC_LEN; 939189251Ssam } 940189251Ssam} 941189251Ssam 942189251Ssam 943189251Ssamstruct radius_ms_mppe_keys * 944189251Ssamradius_msg_get_ms_keys(struct radius_msg *msg, struct radius_msg *sent_msg, 945189251Ssam const u8 *secret, size_t secret_len) 946189251Ssam{ 947189251Ssam u8 *key; 948189251Ssam size_t keylen; 949189251Ssam struct radius_ms_mppe_keys *keys; 950189251Ssam 951189251Ssam if (msg == NULL || sent_msg == NULL) 952189251Ssam return NULL; 953189251Ssam 954189251Ssam keys = os_zalloc(sizeof(*keys)); 955189251Ssam if (keys == NULL) 956189251Ssam return NULL; 957189251Ssam 958189251Ssam key = radius_msg_get_vendor_attr(msg, RADIUS_VENDOR_ID_MICROSOFT, 959189251Ssam RADIUS_VENDOR_ATTR_MS_MPPE_SEND_KEY, 960189251Ssam &keylen); 961189251Ssam if (key) { 962189251Ssam keys->send = decrypt_ms_key(key, keylen, 963189251Ssam sent_msg->hdr->authenticator, 964189251Ssam secret, secret_len, 965189251Ssam &keys->send_len); 966189251Ssam os_free(key); 967189251Ssam } 968189251Ssam 969189251Ssam key = radius_msg_get_vendor_attr(msg, RADIUS_VENDOR_ID_MICROSOFT, 970189251Ssam RADIUS_VENDOR_ATTR_MS_MPPE_RECV_KEY, 971189251Ssam &keylen); 972189251Ssam if (key) { 973189251Ssam keys->recv = decrypt_ms_key(key, keylen, 974189251Ssam sent_msg->hdr->authenticator, 975189251Ssam secret, secret_len, 976189251Ssam &keys->recv_len); 977189251Ssam os_free(key); 978189251Ssam } 979189251Ssam 980189251Ssam return keys; 981189251Ssam} 982189251Ssam 983189251Ssam 984189251Ssamstruct radius_ms_mppe_keys * 985189251Ssamradius_msg_get_cisco_keys(struct radius_msg *msg, struct radius_msg *sent_msg, 986189251Ssam const u8 *secret, size_t secret_len) 987189251Ssam{ 988189251Ssam u8 *key; 989189251Ssam size_t keylen; 990189251Ssam struct radius_ms_mppe_keys *keys; 991189251Ssam 992189251Ssam if (msg == NULL || sent_msg == NULL) 993189251Ssam return NULL; 994189251Ssam 995189251Ssam keys = os_zalloc(sizeof(*keys)); 996189251Ssam if (keys == NULL) 997189251Ssam return NULL; 998189251Ssam 999189251Ssam key = radius_msg_get_vendor_attr(msg, RADIUS_VENDOR_ID_CISCO, 1000189251Ssam RADIUS_CISCO_AV_PAIR, &keylen); 1001189251Ssam if (key && keylen == 51 && 1002189251Ssam os_memcmp(key, "leap:session-key=", 17) == 0) { 1003189251Ssam keys->recv = decrypt_ms_key(key + 17, keylen - 17, 1004189251Ssam sent_msg->hdr->authenticator, 1005189251Ssam secret, secret_len, 1006189251Ssam &keys->recv_len); 1007189251Ssam } 1008189251Ssam os_free(key); 1009189251Ssam 1010189251Ssam return keys; 1011189251Ssam} 1012189251Ssam 1013189251Ssam 1014189251Ssamint radius_msg_add_mppe_keys(struct radius_msg *msg, 1015189251Ssam const u8 *req_authenticator, 1016189251Ssam const u8 *secret, size_t secret_len, 1017189251Ssam const u8 *send_key, size_t send_key_len, 1018189251Ssam const u8 *recv_key, size_t recv_key_len) 1019189251Ssam{ 1020189251Ssam struct radius_attr_hdr *attr; 1021189251Ssam u32 vendor_id = htonl(RADIUS_VENDOR_ID_MICROSOFT); 1022189251Ssam u8 *buf; 1023189251Ssam struct radius_attr_vendor *vhdr; 1024189251Ssam u8 *pos; 1025189251Ssam size_t elen; 1026189251Ssam int hlen; 1027189251Ssam u16 salt; 1028189251Ssam 1029189251Ssam hlen = sizeof(vendor_id) + sizeof(*vhdr) + 2; 1030189251Ssam 1031189251Ssam /* MS-MPPE-Send-Key */ 1032189251Ssam buf = os_malloc(hlen + send_key_len + 16); 1033189251Ssam if (buf == NULL) { 1034189251Ssam return 0; 1035189251Ssam } 1036189251Ssam pos = buf; 1037189251Ssam os_memcpy(pos, &vendor_id, sizeof(vendor_id)); 1038189251Ssam pos += sizeof(vendor_id); 1039189251Ssam vhdr = (struct radius_attr_vendor *) pos; 1040189251Ssam vhdr->vendor_type = RADIUS_VENDOR_ATTR_MS_MPPE_SEND_KEY; 1041189251Ssam pos = (u8 *) (vhdr + 1); 1042189251Ssam salt = os_random() | 0x8000; 1043189251Ssam WPA_PUT_BE16(pos, salt); 1044189251Ssam pos += 2; 1045189251Ssam encrypt_ms_key(send_key, send_key_len, salt, req_authenticator, secret, 1046189251Ssam secret_len, pos, &elen); 1047189251Ssam vhdr->vendor_length = hlen + elen - sizeof(vendor_id); 1048189251Ssam 1049189251Ssam attr = radius_msg_add_attr(msg, RADIUS_ATTR_VENDOR_SPECIFIC, 1050189251Ssam buf, hlen + elen); 1051189251Ssam os_free(buf); 1052189251Ssam if (attr == NULL) { 1053189251Ssam return 0; 1054189251Ssam } 1055189251Ssam 1056189251Ssam /* MS-MPPE-Recv-Key */ 1057189251Ssam buf = os_malloc(hlen + send_key_len + 16); 1058189251Ssam if (buf == NULL) { 1059189251Ssam return 0; 1060189251Ssam } 1061189251Ssam pos = buf; 1062189251Ssam os_memcpy(pos, &vendor_id, sizeof(vendor_id)); 1063189251Ssam pos += sizeof(vendor_id); 1064189251Ssam vhdr = (struct radius_attr_vendor *) pos; 1065189251Ssam vhdr->vendor_type = RADIUS_VENDOR_ATTR_MS_MPPE_RECV_KEY; 1066189251Ssam pos = (u8 *) (vhdr + 1); 1067189251Ssam salt ^= 1; 1068189251Ssam WPA_PUT_BE16(pos, salt); 1069189251Ssam pos += 2; 1070189251Ssam encrypt_ms_key(recv_key, recv_key_len, salt, req_authenticator, secret, 1071189251Ssam secret_len, pos, &elen); 1072189251Ssam vhdr->vendor_length = hlen + elen - sizeof(vendor_id); 1073189251Ssam 1074189251Ssam attr = radius_msg_add_attr(msg, RADIUS_ATTR_VENDOR_SPECIFIC, 1075189251Ssam buf, hlen + elen); 1076189251Ssam os_free(buf); 1077189251Ssam if (attr == NULL) { 1078189251Ssam return 0; 1079189251Ssam } 1080189251Ssam 1081189251Ssam return 1; 1082189251Ssam} 1083189251Ssam 1084189251Ssam 1085189251Ssam/* Add User-Password attribute to a RADIUS message and encrypt it as specified 1086189251Ssam * in RFC 2865, Chap. 5.2 */ 1087189251Ssamstruct radius_attr_hdr * 1088189251Ssamradius_msg_add_attr_user_password(struct radius_msg *msg, 1089189251Ssam const u8 *data, size_t data_len, 1090189251Ssam const u8 *secret, size_t secret_len) 1091189251Ssam{ 1092189251Ssam u8 buf[128]; 1093189251Ssam int padlen, i; 1094189251Ssam size_t buf_len, pos; 1095189251Ssam const u8 *addr[2]; 1096189251Ssam size_t len[2]; 1097189251Ssam u8 hash[16]; 1098189251Ssam 1099189251Ssam if (data_len > 128) 1100189251Ssam return NULL; 1101189251Ssam 1102189251Ssam os_memcpy(buf, data, data_len); 1103189251Ssam buf_len = data_len; 1104189251Ssam 1105189251Ssam padlen = data_len % 16; 1106189251Ssam if (padlen) { 1107189251Ssam padlen = 16 - padlen; 1108189251Ssam os_memset(buf + data_len, 0, padlen); 1109189251Ssam buf_len += padlen; 1110189251Ssam } 1111189251Ssam 1112189251Ssam addr[0] = secret; 1113189251Ssam len[0] = secret_len; 1114189251Ssam addr[1] = msg->hdr->authenticator; 1115189251Ssam len[1] = 16; 1116189251Ssam md5_vector(2, addr, len, hash); 1117189251Ssam 1118189251Ssam for (i = 0; i < 16; i++) 1119189251Ssam buf[i] ^= hash[i]; 1120189251Ssam pos = 16; 1121189251Ssam 1122189251Ssam while (pos < buf_len) { 1123189251Ssam addr[0] = secret; 1124189251Ssam len[0] = secret_len; 1125189251Ssam addr[1] = &buf[pos - 16]; 1126189251Ssam len[1] = 16; 1127189251Ssam md5_vector(2, addr, len, hash); 1128189251Ssam 1129189251Ssam for (i = 0; i < 16; i++) 1130189251Ssam buf[pos + i] ^= hash[i]; 1131189251Ssam 1132189251Ssam pos += 16; 1133189251Ssam } 1134189251Ssam 1135189251Ssam return radius_msg_add_attr(msg, RADIUS_ATTR_USER_PASSWORD, 1136189251Ssam buf, buf_len); 1137189251Ssam} 1138189251Ssam 1139189251Ssam 1140189251Ssamint radius_msg_get_attr(struct radius_msg *msg, u8 type, u8 *buf, size_t len) 1141189251Ssam{ 1142189251Ssam struct radius_attr_hdr *attr = NULL, *tmp; 1143189251Ssam size_t i, dlen; 1144189251Ssam 1145189251Ssam for (i = 0; i < msg->attr_used; i++) { 1146189251Ssam tmp = radius_get_attr_hdr(msg, i); 1147189251Ssam if (tmp->type == type) { 1148189251Ssam attr = tmp; 1149189251Ssam break; 1150189251Ssam } 1151189251Ssam } 1152189251Ssam 1153189251Ssam if (!attr) 1154189251Ssam return -1; 1155189251Ssam 1156189251Ssam dlen = attr->length - sizeof(*attr); 1157189251Ssam if (buf) 1158189251Ssam os_memcpy(buf, (attr + 1), dlen > len ? len : dlen); 1159189251Ssam return dlen; 1160189251Ssam} 1161189251Ssam 1162189251Ssam 1163189251Ssamint radius_msg_get_attr_ptr(struct radius_msg *msg, u8 type, u8 **buf, 1164189251Ssam size_t *len, const u8 *start) 1165189251Ssam{ 1166189251Ssam size_t i; 1167189251Ssam struct radius_attr_hdr *attr = NULL, *tmp; 1168189251Ssam 1169189251Ssam for (i = 0; i < msg->attr_used; i++) { 1170189251Ssam tmp = radius_get_attr_hdr(msg, i); 1171189251Ssam if (tmp->type == type && 1172189251Ssam (start == NULL || (u8 *) tmp > start)) { 1173189251Ssam attr = tmp; 1174189251Ssam break; 1175189251Ssam } 1176189251Ssam } 1177189251Ssam 1178189251Ssam if (!attr) 1179189251Ssam return -1; 1180189251Ssam 1181189251Ssam *buf = (u8 *) (attr + 1); 1182189251Ssam *len = attr->length - sizeof(*attr); 1183189251Ssam return 0; 1184189251Ssam} 1185189251Ssam 1186189251Ssam 1187189251Ssamint radius_msg_count_attr(struct radius_msg *msg, u8 type, int min_len) 1188189251Ssam{ 1189189251Ssam size_t i; 1190189251Ssam int count; 1191189251Ssam 1192189251Ssam for (count = 0, i = 0; i < msg->attr_used; i++) { 1193189251Ssam struct radius_attr_hdr *attr = radius_get_attr_hdr(msg, i); 1194189251Ssam if (attr->type == type && 1195189251Ssam attr->length >= sizeof(struct radius_attr_hdr) + min_len) 1196189251Ssam count++; 1197189251Ssam } 1198189251Ssam 1199189251Ssam return count; 1200189251Ssam} 1201189251Ssam 1202189251Ssam 1203189251Ssamstruct radius_tunnel_attrs { 1204189251Ssam int tag_used; 1205189251Ssam int type; /* Tunnel-Type */ 1206189251Ssam int medium_type; /* Tunnel-Medium-Type */ 1207189251Ssam int vlanid; 1208189251Ssam}; 1209189251Ssam 1210189251Ssam 1211189251Ssam/** 1212189251Ssam * radius_msg_get_vlanid - Parse RADIUS attributes for VLAN tunnel information 1213189251Ssam * @msg: RADIUS message 1214189251Ssam * Returns: VLAN ID for the first tunnel configuration of -1 if none is found 1215189251Ssam */ 1216189251Ssamint radius_msg_get_vlanid(struct radius_msg *msg) 1217189251Ssam{ 1218189251Ssam struct radius_tunnel_attrs tunnel[RADIUS_TUNNEL_TAGS], *tun; 1219189251Ssam size_t i; 1220189251Ssam struct radius_attr_hdr *attr = NULL; 1221189251Ssam const u8 *data; 1222189251Ssam char buf[10]; 1223189251Ssam size_t dlen; 1224189251Ssam 1225189251Ssam os_memset(&tunnel, 0, sizeof(tunnel)); 1226189251Ssam 1227189251Ssam for (i = 0; i < msg->attr_used; i++) { 1228189251Ssam attr = radius_get_attr_hdr(msg, i); 1229189251Ssam data = (const u8 *) (attr + 1); 1230189251Ssam dlen = attr->length - sizeof(*attr); 1231189251Ssam if (attr->length < 3) 1232189251Ssam continue; 1233189251Ssam if (data[0] >= RADIUS_TUNNEL_TAGS) 1234189251Ssam tun = &tunnel[0]; 1235189251Ssam else 1236189251Ssam tun = &tunnel[data[0]]; 1237189251Ssam 1238189251Ssam switch (attr->type) { 1239189251Ssam case RADIUS_ATTR_TUNNEL_TYPE: 1240189251Ssam if (attr->length != 6) 1241189251Ssam break; 1242189251Ssam tun->tag_used++; 1243189251Ssam tun->type = WPA_GET_BE24(data + 1); 1244189251Ssam break; 1245189251Ssam case RADIUS_ATTR_TUNNEL_MEDIUM_TYPE: 1246189251Ssam if (attr->length != 6) 1247189251Ssam break; 1248189251Ssam tun->tag_used++; 1249189251Ssam tun->medium_type = WPA_GET_BE24(data + 1); 1250189251Ssam break; 1251189251Ssam case RADIUS_ATTR_TUNNEL_PRIVATE_GROUP_ID: 1252189251Ssam if (data[0] < RADIUS_TUNNEL_TAGS) { 1253189251Ssam data++; 1254189251Ssam dlen--; 1255189251Ssam } 1256189251Ssam if (dlen >= sizeof(buf)) 1257189251Ssam break; 1258189251Ssam os_memcpy(buf, data, dlen); 1259189251Ssam buf[dlen] = '\0'; 1260189251Ssam tun->tag_used++; 1261189251Ssam tun->vlanid = atoi(buf); 1262189251Ssam break; 1263189251Ssam } 1264189251Ssam } 1265189251Ssam 1266189251Ssam for (i = 0; i < RADIUS_TUNNEL_TAGS; i++) { 1267189251Ssam tun = &tunnel[i]; 1268189251Ssam if (tun->tag_used && 1269189251Ssam tun->type == RADIUS_TUNNEL_TYPE_VLAN && 1270189251Ssam tun->medium_type == RADIUS_TUNNEL_MEDIUM_TYPE_802 && 1271189251Ssam tun->vlanid > 0) 1272189251Ssam return tun->vlanid; 1273189251Ssam } 1274189251Ssam 1275189251Ssam return -1; 1276189251Ssam} 1277214734Srpaulo 1278214734Srpaulo 1279214734Srpaulovoid radius_free_class(struct radius_class_data *c) 1280214734Srpaulo{ 1281214734Srpaulo size_t i; 1282214734Srpaulo if (c == NULL) 1283214734Srpaulo return; 1284214734Srpaulo for (i = 0; i < c->count; i++) 1285214734Srpaulo os_free(c->attr[i].data); 1286214734Srpaulo os_free(c->attr); 1287214734Srpaulo c->attr = NULL; 1288214734Srpaulo c->count = 0; 1289214734Srpaulo} 1290214734Srpaulo 1291214734Srpaulo 1292214734Srpauloint radius_copy_class(struct radius_class_data *dst, 1293214734Srpaulo const struct radius_class_data *src) 1294214734Srpaulo{ 1295214734Srpaulo size_t i; 1296214734Srpaulo 1297214734Srpaulo if (src->attr == NULL) 1298214734Srpaulo return 0; 1299214734Srpaulo 1300214734Srpaulo dst->attr = os_zalloc(src->count * sizeof(struct radius_attr_data)); 1301214734Srpaulo if (dst->attr == NULL) 1302214734Srpaulo return -1; 1303214734Srpaulo 1304214734Srpaulo dst->count = 0; 1305214734Srpaulo 1306214734Srpaulo for (i = 0; i < src->count; i++) { 1307214734Srpaulo dst->attr[i].data = os_malloc(src->attr[i].len); 1308214734Srpaulo if (dst->attr[i].data == NULL) 1309214734Srpaulo break; 1310214734Srpaulo dst->count++; 1311214734Srpaulo os_memcpy(dst->attr[i].data, src->attr[i].data, 1312214734Srpaulo src->attr[i].len); 1313214734Srpaulo dst->attr[i].len = src->attr[i].len; 1314214734Srpaulo } 1315214734Srpaulo 1316214734Srpaulo return 0; 1317214734Srpaulo} 1318