1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ 2 /* lib/krad/packet.c - Packet functions for libkrad */ 3 /* 4 * Copyright 2013 Red Hat, Inc. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in 14 * the documentation and/or other materials provided with the 15 * distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 18 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 20 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 21 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 #include "internal.h" 31 32 #include <string.h> 33 34 #include <arpa/inet.h> 35 36 typedef unsigned char uchar; 37 38 /* RFC 2865 */ 39 #define MSGAUTH_SIZE (2 + MD5_DIGEST_SIZE) 40 #define OFFSET_CODE 0 41 #define OFFSET_ID 1 42 #define OFFSET_LENGTH 2 43 #define OFFSET_AUTH 4 44 #define OFFSET_ATTR 20 45 #define AUTH_FIELD_SIZE (OFFSET_ATTR - OFFSET_AUTH) 46 47 #define offset(d, o) (&(d)->data[o]) 48 #define pkt_code_get(p) (*(krad_code *)offset(&(p)->pkt, OFFSET_CODE)) 49 #define pkt_code_set(p, v) (*(krad_code *)offset(&(p)->pkt, OFFSET_CODE)) = v 50 #define pkt_id_get(p) (*(uchar *)offset(&(p)->pkt, OFFSET_ID)) 51 #define pkt_id_set(p, v) (*(uchar *)offset(&(p)->pkt, OFFSET_ID)) = v 52 #define pkt_len_get(p) load_16_be(offset(&(p)->pkt, OFFSET_LENGTH)) 53 #define pkt_len_set(p, v) store_16_be(v, offset(&(p)->pkt, OFFSET_LENGTH)) 54 #define pkt_auth(p) ((uchar *)offset(&(p)->pkt, OFFSET_AUTH)) 55 #define pkt_attr(p) ((unsigned char *)offset(&(p)->pkt, OFFSET_ATTR)) 56 57 struct krad_packet_st { 58 char buffer[KRAD_PACKET_SIZE_MAX]; 59 krad_attrset *attrset; 60 krb5_data pkt; 61 }; 62 63 typedef struct { 64 uchar x[(UCHAR_MAX + 1) / 8]; 65 } idmap; 66 67 /* Ensure the map is empty. */ 68 static inline void 69 idmap_init(idmap *map) 70 { 71 memset(map, 0, sizeof(*map)); 72 } 73 74 /* Set an id as already allocated. */ 75 static inline void 76 idmap_set(idmap *map, uchar id) 77 { 78 map->x[id / 8] |= 1 << (id % 8); 79 } 80 81 /* Determine whether or not an id is used. */ 82 static inline krb5_boolean 83 idmap_isset(const idmap *map, uchar id) 84 { 85 return (map->x[id / 8] & (1 << (id % 8))) != 0; 86 } 87 88 /* Find an unused id starting the search at the value specified in id. 89 * NOTE: For optimal security, the initial value of id should be random. */ 90 static inline krb5_error_code 91 idmap_find(const idmap *map, uchar *id) 92 { 93 krb5_int16 i; 94 95 for (i = *id; i >= 0 && i <= UCHAR_MAX; (*id % 2 == 0) ? i++ : i--) { 96 if (!idmap_isset(map, i)) 97 goto success; 98 } 99 100 for (i = *id; i >= 0 && i <= UCHAR_MAX; (*id % 2 == 1) ? i++ : i--) { 101 if (!idmap_isset(map, i)) 102 goto success; 103 } 104 105 return ERANGE; 106 107 success: 108 *id = i; 109 return 0; 110 } 111 112 /* Generate size bytes of random data into the buffer. */ 113 static inline krb5_error_code 114 randomize(krb5_context ctx, void *buffer, unsigned int size) 115 { 116 krb5_data rdata = make_data(buffer, size); 117 return krb5_c_random_make_octets(ctx, &rdata); 118 } 119 120 /* Generate a radius packet id. */ 121 static krb5_error_code 122 id_generate(krb5_context ctx, krad_packet_iter_cb cb, void *data, uchar *id) 123 { 124 krb5_error_code retval; 125 const krad_packet *tmp; 126 idmap used; 127 uchar i; 128 129 retval = randomize(ctx, &i, sizeof(i)); 130 if (retval != 0) { 131 if (cb != NULL) 132 (*cb)(data, TRUE); 133 return retval; 134 } 135 136 if (cb != NULL) { 137 idmap_init(&used); 138 for (tmp = (*cb)(data, FALSE); tmp != NULL; tmp = (*cb)(data, FALSE)) 139 idmap_set(&used, tmp->pkt.data[1]); 140 141 retval = idmap_find(&used, &i); 142 if (retval != 0) 143 return retval; 144 } 145 146 *id = i; 147 return 0; 148 } 149 150 /* Generate a random authenticator field. */ 151 static krb5_error_code 152 auth_generate_random(krb5_context ctx, uchar *rauth) 153 { 154 krb5_ui_4 trunctime; 155 time_t currtime; 156 157 /* Get the least-significant four bytes of the current time. */ 158 currtime = time(NULL); 159 if (currtime == (time_t)-1) 160 return errno; 161 trunctime = (krb5_ui_4)currtime; 162 memcpy(rauth, &trunctime, sizeof(trunctime)); 163 164 /* Randomize the rest of the buffer. */ 165 return randomize(ctx, rauth + sizeof(trunctime), 166 AUTH_FIELD_SIZE - sizeof(trunctime)); 167 } 168 169 /* Generate a response authenticator field. */ 170 static krb5_error_code 171 auth_generate_response(krb5_context ctx, const char *secret, 172 const krad_packet *response, const uchar *auth, 173 uchar *rauth) 174 { 175 krb5_error_code retval; 176 krb5_checksum hash; 177 krb5_data data; 178 179 /* Allocate the temporary buffer. */ 180 retval = alloc_data(&data, response->pkt.length + strlen(secret)); 181 if (retval != 0) 182 return retval; 183 184 /* Encoded RADIUS packet with the request's 185 * authenticator and the secret at the end. */ 186 memcpy(data.data, response->pkt.data, response->pkt.length); 187 memcpy(data.data + OFFSET_AUTH, auth, AUTH_FIELD_SIZE); 188 memcpy(data.data + response->pkt.length, secret, strlen(secret)); 189 190 /* Hash it. */ 191 retval = krb5_c_make_checksum(ctx, CKSUMTYPE_RSA_MD5, NULL, 0, &data, 192 &hash); 193 free(data.data); 194 if (retval != 0) 195 return retval; 196 197 memcpy(rauth, hash.contents, AUTH_FIELD_SIZE); 198 krb5_free_checksum_contents(ctx, &hash); 199 return 0; 200 } 201 202 /* Create a new packet. */ 203 static krad_packet * 204 packet_new(void) 205 { 206 krad_packet *pkt; 207 208 pkt = calloc(1, sizeof(krad_packet)); 209 if (pkt == NULL) 210 return NULL; 211 pkt->pkt = make_data(pkt->buffer, sizeof(pkt->buffer)); 212 213 return pkt; 214 } 215 216 /* Set the attrset object by decoding the packet. */ 217 static krb5_error_code 218 packet_set_attrset(krb5_context ctx, const char *secret, krad_packet *pkt) 219 { 220 krb5_data tmp; 221 222 tmp = make_data(pkt_attr(pkt), pkt->pkt.length - OFFSET_ATTR); 223 return kr_attrset_decode(ctx, &tmp, secret, pkt_auth(pkt), &pkt->attrset); 224 } 225 226 /* Determine if a packet requires a Message-Authenticator attribute. */ 227 static inline krb5_boolean 228 requires_msgauth(const char *secret, krad_code code) 229 { 230 /* If no secret is provided, assume that the transport is a UNIX socket. 231 * Message-Authenticator is required only on UDP and TCP connections. */ 232 if (*secret == '\0') 233 return FALSE; 234 235 /* 236 * Per draft-ietf-radext-deprecating-radius-03 sections 5.2.1 and 5.2.4, 237 * Message-Authenticator is required in Access-Request packets and all 238 * potential responses when UDP or TCP transport is used. 239 */ 240 return code == KRAD_CODE_ACCESS_REQUEST || 241 code == KRAD_CODE_ACCESS_ACCEPT || code == KRAD_CODE_ACCESS_REJECT || 242 code == KRAD_CODE_ACCESS_CHALLENGE; 243 } 244 245 /* Check if the packet has a Message-Authenticator attribute. */ 246 static inline krb5_boolean 247 has_pkt_msgauth(const krad_packet *pkt) 248 { 249 return krad_attrset_get(pkt->attrset, KRAD_ATTR_MESSAGE_AUTHENTICATOR, 250 0) != NULL; 251 } 252 253 /* Return the beginning of the Message-Authenticator attribute in pkt, or NULL 254 * if no such attribute is present. */ 255 static const uint8_t * 256 lookup_msgauth_addr(const krad_packet *pkt) 257 { 258 size_t i; 259 uint8_t *p; 260 261 i = OFFSET_ATTR; 262 while (i + 2 < pkt->pkt.length) { 263 p = (uint8_t *)offset(&pkt->pkt, i); 264 if (*p == KRAD_ATTR_MESSAGE_AUTHENTICATOR) 265 return p; 266 i += p[1]; 267 } 268 269 return NULL; 270 } 271 272 /* 273 * Calculate the message authenticator MAC for pkt as specified in RFC 2869 274 * section 5.14, placing the result in mac_out. Use the provided authenticator 275 * auth, which may be from pkt or from a corresponding request. 276 */ 277 static krb5_error_code 278 calculate_mac(const char *secret, const krad_packet *pkt, 279 const uint8_t auth[AUTH_FIELD_SIZE], 280 uint8_t mac_out[MD5_DIGEST_SIZE]) 281 { 282 const uint8_t *msgauth_attr, *msgauth_end, *pkt_end; 283 krb5_crypto_iov input[5]; 284 krb5_data ksecr, mac; 285 static const uint8_t zeroed_msgauth[MSGAUTH_SIZE] = { 286 KRAD_ATTR_MESSAGE_AUTHENTICATOR, MSGAUTH_SIZE 287 }; 288 289 msgauth_attr = lookup_msgauth_addr(pkt); 290 if (msgauth_attr == NULL) 291 return EINVAL; 292 msgauth_end = msgauth_attr + MSGAUTH_SIZE; 293 pkt_end = (const uint8_t *)pkt->pkt.data + pkt->pkt.length; 294 295 /* Read code, id, and length from the packet. */ 296 input[0].flags = KRB5_CRYPTO_TYPE_DATA; 297 input[0].data = make_data(pkt->pkt.data, OFFSET_AUTH); 298 299 /* Read the provided authenticator. */ 300 input[1].flags = KRB5_CRYPTO_TYPE_DATA; 301 input[1].data = make_data((uint8_t *)auth, AUTH_FIELD_SIZE); 302 303 /* Read any attributes before Message-Authenticator. */ 304 input[2].flags = KRB5_CRYPTO_TYPE_DATA; 305 input[2].data = make_data(pkt_attr(pkt), msgauth_attr - pkt_attr(pkt)); 306 307 /* Read Message-Authenticator with the data bytes all set to zero, per RFC 308 * 2869 section 5.14. */ 309 input[3].flags = KRB5_CRYPTO_TYPE_DATA; 310 input[3].data = make_data((uint8_t *)zeroed_msgauth, MSGAUTH_SIZE); 311 312 /* Read any attributes after Message-Authenticator. */ 313 input[4].flags = KRB5_CRYPTO_TYPE_DATA; 314 input[4].data = make_data((uint8_t *)msgauth_end, pkt_end - msgauth_end); 315 316 mac = make_data(mac_out, MD5_DIGEST_SIZE); 317 ksecr = string2data((char *)secret); 318 return k5_hmac_md5(&ksecr, input, 5, &mac); 319 } 320 321 ssize_t 322 krad_packet_bytes_needed(const krb5_data *buffer) 323 { 324 size_t len; 325 326 if (buffer->length < OFFSET_AUTH) 327 return OFFSET_AUTH - buffer->length; 328 329 len = load_16_be(offset(buffer, OFFSET_LENGTH)); 330 if (len > KRAD_PACKET_SIZE_MAX) 331 return -1; 332 333 return (buffer->length > len) ? 0 : len - buffer->length; 334 } 335 336 void 337 krad_packet_free(krad_packet *pkt) 338 { 339 if (pkt) 340 krad_attrset_free(pkt->attrset); 341 free(pkt); 342 } 343 344 /* Create a new request packet. */ 345 krb5_error_code 346 krad_packet_new_request(krb5_context ctx, const char *secret, krad_code code, 347 const krad_attrset *set, krad_packet_iter_cb cb, 348 void *data, krad_packet **request) 349 { 350 krb5_error_code retval; 351 krad_packet *pkt; 352 uchar id; 353 size_t attrset_len; 354 krb5_boolean msgauth_required; 355 356 pkt = packet_new(); 357 if (pkt == NULL) { 358 if (cb != NULL) 359 (*cb)(data, TRUE); 360 return ENOMEM; 361 } 362 363 /* Generate the ID. */ 364 retval = id_generate(ctx, cb, data, &id); 365 if (retval != 0) 366 goto error; 367 pkt_id_set(pkt, id); 368 369 /* Generate the authenticator. */ 370 retval = auth_generate_random(ctx, pkt_auth(pkt)); 371 if (retval != 0) 372 goto error; 373 374 /* Determine if Message-Authenticator is required. */ 375 msgauth_required = (*secret != '\0' && code == KRAD_CODE_ACCESS_REQUEST); 376 377 /* Encode the attributes. */ 378 retval = kr_attrset_encode(set, secret, pkt_auth(pkt), msgauth_required, 379 pkt_attr(pkt), &attrset_len); 380 if (retval != 0) 381 goto error; 382 383 /* Set the code, ID and length. */ 384 pkt->pkt.length = attrset_len + OFFSET_ATTR; 385 pkt_code_set(pkt, code); 386 pkt_len_set(pkt, pkt->pkt.length); 387 388 if (msgauth_required) { 389 /* Calculate and set the Message-Authenticator MAC. */ 390 retval = calculate_mac(secret, pkt, pkt_auth(pkt), pkt_attr(pkt) + 2); 391 if (retval != 0) 392 goto error; 393 } 394 395 /* Copy the attrset for future use. */ 396 retval = packet_set_attrset(ctx, secret, pkt); 397 if (retval != 0) 398 goto error; 399 400 *request = pkt; 401 return 0; 402 403 error: 404 free(pkt); 405 return retval; 406 } 407 408 /* Create a new request packet. */ 409 krb5_error_code 410 krad_packet_new_response(krb5_context ctx, const char *secret, krad_code code, 411 const krad_attrset *set, const krad_packet *request, 412 krad_packet **response) 413 { 414 krb5_error_code retval; 415 krad_packet *pkt; 416 size_t attrset_len; 417 krb5_boolean msgauth_required; 418 419 pkt = packet_new(); 420 if (pkt == NULL) 421 return ENOMEM; 422 423 /* Determine if Message-Authenticator is required. */ 424 msgauth_required = requires_msgauth(secret, code); 425 426 /* Encode the attributes. */ 427 retval = kr_attrset_encode(set, secret, pkt_auth(request), 428 msgauth_required, pkt_attr(pkt), &attrset_len); 429 if (retval != 0) 430 goto error; 431 432 /* Set the code, ID and length. */ 433 pkt->pkt.length = attrset_len + OFFSET_ATTR; 434 pkt_code_set(pkt, code); 435 pkt_id_set(pkt, pkt_id_get(request)); 436 pkt_len_set(pkt, pkt->pkt.length); 437 438 /* Generate the authenticator. */ 439 retval = auth_generate_response(ctx, secret, pkt, pkt_auth(request), 440 pkt_auth(pkt)); 441 if (retval != 0) 442 goto error; 443 444 if (msgauth_required) { 445 /* 446 * Calculate and replace the Message-Authenticator MAC. Per RFC 2869 447 * section 5.14, use the authenticator from the request, not from the 448 * response. 449 */ 450 retval = calculate_mac(secret, pkt, pkt_auth(request), 451 pkt_attr(pkt) + 2); 452 if (retval != 0) 453 goto error; 454 } 455 456 /* Copy the attrset for future use. */ 457 retval = packet_set_attrset(ctx, secret, pkt); 458 if (retval != 0) 459 goto error; 460 461 *response = pkt; 462 return 0; 463 464 error: 465 free(pkt); 466 return retval; 467 } 468 469 /* Verify the Message-Authenticator value in pkt, using the provided 470 * authenticator (which may be from pkt or from a corresponding request). */ 471 static krb5_error_code 472 verify_msgauth(const char *secret, const krad_packet *pkt, 473 const uint8_t auth[AUTH_FIELD_SIZE]) 474 { 475 uint8_t mac[MD5_DIGEST_SIZE]; 476 const krb5_data *msgauth; 477 krb5_error_code retval; 478 479 msgauth = krad_packet_get_attr(pkt, KRAD_ATTR_MESSAGE_AUTHENTICATOR, 0); 480 /* XXX ENODATA does not exist in FreeBSD. The closest thing we have to */ 481 /* XXX ENODATA is ENOATTR. We use that instead. */ 482 #define ENODATA ENOATTR 483 if (msgauth == NULL) 484 return ENODATA; 485 486 retval = calculate_mac(secret, pkt, auth, mac); 487 if (retval) 488 return retval; 489 490 if (msgauth->length != MD5_DIGEST_SIZE) 491 return EMSGSIZE; 492 493 if (k5_bcmp(mac, msgauth->data, MD5_DIGEST_SIZE) != 0) 494 return EBADMSG; 495 496 return 0; 497 } 498 499 /* Decode a packet. */ 500 static krb5_error_code 501 decode_packet(krb5_context ctx, const char *secret, const krb5_data *buffer, 502 krad_packet **pkt) 503 { 504 krb5_error_code retval; 505 krad_packet *tmp; 506 krb5_ui_2 len; 507 508 tmp = packet_new(); 509 if (tmp == NULL) { 510 retval = ENOMEM; 511 goto error; 512 } 513 514 /* Ensure a proper message length. */ 515 retval = (buffer->length < OFFSET_ATTR) ? EMSGSIZE : 0; 516 if (retval != 0) 517 goto error; 518 len = load_16_be(offset(buffer, OFFSET_LENGTH)); 519 retval = (len < OFFSET_ATTR) ? EBADMSG : 0; 520 if (retval != 0) 521 goto error; 522 retval = (len > buffer->length || len > tmp->pkt.length) ? EBADMSG : 0; 523 if (retval != 0) 524 goto error; 525 526 /* Copy over the buffer. */ 527 tmp->pkt.length = len; 528 memcpy(tmp->pkt.data, buffer->data, len); 529 530 /* Parse the packet to ensure it is well-formed. */ 531 retval = packet_set_attrset(ctx, secret, tmp); 532 if (retval != 0) 533 goto error; 534 535 *pkt = tmp; 536 return 0; 537 538 error: 539 krad_packet_free(tmp); 540 return retval; 541 } 542 543 krb5_error_code 544 krad_packet_decode_request(krb5_context ctx, const char *secret, 545 const krb5_data *buffer, krad_packet_iter_cb cb, 546 void *data, const krad_packet **duppkt, 547 krad_packet **reqpkt) 548 { 549 const krad_packet *tmp = NULL; 550 krad_packet *req; 551 krb5_error_code retval; 552 553 retval = decode_packet(ctx, secret, buffer, &req); 554 if (retval) 555 return retval; 556 557 /* Verify Message-Authenticator if present. */ 558 if (has_pkt_msgauth(req)) { 559 retval = verify_msgauth(secret, req, pkt_auth(req)); 560 if (retval) { 561 krad_packet_free(req); 562 return retval; 563 } 564 } 565 566 if (cb != NULL) { 567 for (tmp = (*cb)(data, FALSE); tmp != NULL; tmp = (*cb)(data, FALSE)) { 568 if (pkt_id_get(*reqpkt) == pkt_id_get(tmp)) 569 break; 570 } 571 572 if (tmp != NULL) 573 (*cb)(data, TRUE); 574 } 575 576 *reqpkt = req; 577 *duppkt = tmp; 578 return 0; 579 } 580 581 krb5_error_code 582 krad_packet_decode_response(krb5_context ctx, const char *secret, 583 const krb5_data *buffer, krad_packet_iter_cb cb, 584 void *data, const krad_packet **reqpkt, 585 krad_packet **rsppkt) 586 { 587 uchar auth[AUTH_FIELD_SIZE]; 588 const krad_packet *tmp = NULL; 589 krb5_error_code retval; 590 591 retval = decode_packet(ctx, secret, buffer, rsppkt); 592 if (cb != NULL && retval == 0) { 593 for (tmp = (*cb)(data, FALSE); tmp != NULL; tmp = (*cb)(data, FALSE)) { 594 if (pkt_id_get(*rsppkt) != pkt_id_get(tmp)) 595 continue; 596 597 /* Response */ 598 retval = auth_generate_response(ctx, secret, *rsppkt, 599 pkt_auth(tmp), auth); 600 if (retval != 0) { 601 krad_packet_free(*rsppkt); 602 break; 603 } 604 605 /* Verify the response authenticator. */ 606 if (k5_bcmp(pkt_auth(*rsppkt), auth, sizeof(auth)) != 0) 607 continue; 608 609 /* Verify Message-Authenticator if present. */ 610 if (has_pkt_msgauth(*rsppkt)) { 611 if (verify_msgauth(secret, *rsppkt, pkt_auth(tmp)) != 0) 612 continue; 613 } 614 615 break; 616 } 617 } 618 619 if (cb != NULL && (retval != 0 || tmp != NULL)) 620 (*cb)(data, TRUE); 621 622 *reqpkt = tmp; 623 return retval; 624 } 625 626 const krb5_data * 627 krad_packet_encode(const krad_packet *pkt) 628 { 629 return &pkt->pkt; 630 } 631 632 krad_code 633 krad_packet_get_code(const krad_packet *pkt) 634 { 635 if (pkt == NULL) 636 return 0; 637 638 return pkt_code_get(pkt); 639 } 640 641 const krb5_data * 642 krad_packet_get_attr(const krad_packet *pkt, krad_attr type, size_t indx) 643 { 644 return krad_attrset_get(pkt->attrset, type, indx); 645 } 646