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 OFFSET_CODE 0 40 #define OFFSET_ID 1 41 #define OFFSET_LENGTH 2 42 #define OFFSET_AUTH 4 43 #define OFFSET_ATTR 20 44 #define AUTH_FIELD_SIZE (OFFSET_ATTR - OFFSET_AUTH) 45 46 #define offset(d, o) (&(d)->data[o]) 47 #define pkt_code_get(p) (*(krad_code *)offset(&(p)->pkt, OFFSET_CODE)) 48 #define pkt_code_set(p, v) (*(krad_code *)offset(&(p)->pkt, OFFSET_CODE)) = v 49 #define pkt_id_get(p) (*(uchar *)offset(&(p)->pkt, OFFSET_ID)) 50 #define pkt_id_set(p, v) (*(uchar *)offset(&(p)->pkt, OFFSET_ID)) = v 51 #define pkt_len_get(p) load_16_be(offset(&(p)->pkt, OFFSET_LENGTH)) 52 #define pkt_len_set(p, v) store_16_be(v, offset(&(p)->pkt, OFFSET_LENGTH)) 53 #define pkt_auth(p) ((uchar *)offset(&(p)->pkt, OFFSET_AUTH)) 54 #define pkt_attr(p) ((unsigned char *)offset(&(p)->pkt, OFFSET_ATTR)) 55 56 struct krad_packet_st { 57 char buffer[KRAD_PACKET_SIZE_MAX]; 58 krad_attrset *attrset; 59 krb5_data pkt; 60 }; 61 62 typedef struct { 63 uchar x[(UCHAR_MAX + 1) / 8]; 64 } idmap; 65 66 /* Ensure the map is empty. */ 67 static inline void 68 idmap_init(idmap *map) 69 { 70 memset(map, 0, sizeof(*map)); 71 } 72 73 /* Set an id as already allocated. */ 74 static inline void 75 idmap_set(idmap *map, uchar id) 76 { 77 map->x[id / 8] |= 1 << (id % 8); 78 } 79 80 /* Determine whether or not an id is used. */ 81 static inline krb5_boolean 82 idmap_isset(const idmap *map, uchar id) 83 { 84 return (map->x[id / 8] & (1 << (id % 8))) != 0; 85 } 86 87 /* Find an unused id starting the search at the value specified in id. 88 * NOTE: For optimal security, the initial value of id should be random. */ 89 static inline krb5_error_code 90 idmap_find(const idmap *map, uchar *id) 91 { 92 krb5_int16 i; 93 94 for (i = *id; i >= 0 && i <= UCHAR_MAX; (*id % 2 == 0) ? i++ : i--) { 95 if (!idmap_isset(map, i)) 96 goto success; 97 } 98 99 for (i = *id; i >= 0 && i <= UCHAR_MAX; (*id % 2 == 1) ? i++ : i--) { 100 if (!idmap_isset(map, i)) 101 goto success; 102 } 103 104 return ERANGE; 105 106 success: 107 *id = i; 108 return 0; 109 } 110 111 /* Generate size bytes of random data into the buffer. */ 112 static inline krb5_error_code 113 randomize(krb5_context ctx, void *buffer, unsigned int size) 114 { 115 krb5_data rdata = make_data(buffer, size); 116 return krb5_c_random_make_octets(ctx, &rdata); 117 } 118 119 /* Generate a radius packet id. */ 120 static krb5_error_code 121 id_generate(krb5_context ctx, krad_packet_iter_cb cb, void *data, uchar *id) 122 { 123 krb5_error_code retval; 124 const krad_packet *tmp; 125 idmap used; 126 uchar i; 127 128 retval = randomize(ctx, &i, sizeof(i)); 129 if (retval != 0) { 130 if (cb != NULL) 131 (*cb)(data, TRUE); 132 return retval; 133 } 134 135 if (cb != NULL) { 136 idmap_init(&used); 137 for (tmp = (*cb)(data, FALSE); tmp != NULL; tmp = (*cb)(data, FALSE)) 138 idmap_set(&used, tmp->pkt.data[1]); 139 140 retval = idmap_find(&used, &i); 141 if (retval != 0) 142 return retval; 143 } 144 145 *id = i; 146 return 0; 147 } 148 149 /* Generate a random authenticator field. */ 150 static krb5_error_code 151 auth_generate_random(krb5_context ctx, uchar *rauth) 152 { 153 krb5_ui_4 trunctime; 154 time_t currtime; 155 156 /* Get the least-significant four bytes of the current time. */ 157 currtime = time(NULL); 158 if (currtime == (time_t)-1) 159 return errno; 160 trunctime = (krb5_ui_4)currtime; 161 memcpy(rauth, &trunctime, sizeof(trunctime)); 162 163 /* Randomize the rest of the buffer. */ 164 return randomize(ctx, rauth + sizeof(trunctime), 165 AUTH_FIELD_SIZE - sizeof(trunctime)); 166 } 167 168 /* Generate a response authenticator field. */ 169 static krb5_error_code 170 auth_generate_response(krb5_context ctx, const char *secret, 171 const krad_packet *response, const uchar *auth, 172 uchar *rauth) 173 { 174 krb5_error_code retval; 175 krb5_checksum hash; 176 krb5_data data; 177 178 /* Allocate the temporary buffer. */ 179 retval = alloc_data(&data, response->pkt.length + strlen(secret)); 180 if (retval != 0) 181 return retval; 182 183 /* Encoded RADIUS packet with the request's 184 * authenticator and the secret at the end. */ 185 memcpy(data.data, response->pkt.data, response->pkt.length); 186 memcpy(data.data + OFFSET_AUTH, auth, AUTH_FIELD_SIZE); 187 memcpy(data.data + response->pkt.length, secret, strlen(secret)); 188 189 /* Hash it. */ 190 retval = krb5_c_make_checksum(ctx, CKSUMTYPE_RSA_MD5, NULL, 0, &data, 191 &hash); 192 free(data.data); 193 if (retval != 0) 194 return retval; 195 196 memcpy(rauth, hash.contents, AUTH_FIELD_SIZE); 197 krb5_free_checksum_contents(ctx, &hash); 198 return 0; 199 } 200 201 /* Create a new packet. */ 202 static krad_packet * 203 packet_new() 204 { 205 krad_packet *pkt; 206 207 pkt = calloc(1, sizeof(krad_packet)); 208 if (pkt == NULL) 209 return NULL; 210 pkt->pkt = make_data(pkt->buffer, sizeof(pkt->buffer)); 211 212 return pkt; 213 } 214 215 /* Set the attrset object by decoding the packet. */ 216 static krb5_error_code 217 packet_set_attrset(krb5_context ctx, const char *secret, krad_packet *pkt) 218 { 219 krb5_data tmp; 220 221 tmp = make_data(pkt_attr(pkt), pkt->pkt.length - OFFSET_ATTR); 222 return kr_attrset_decode(ctx, &tmp, secret, pkt_auth(pkt), &pkt->attrset); 223 } 224 225 ssize_t 226 krad_packet_bytes_needed(const krb5_data *buffer) 227 { 228 size_t len; 229 230 if (buffer->length < OFFSET_AUTH) 231 return OFFSET_AUTH - buffer->length; 232 233 len = load_16_be(offset(buffer, OFFSET_LENGTH)); 234 if (len > KRAD_PACKET_SIZE_MAX) 235 return -1; 236 237 return (buffer->length > len) ? 0 : len - buffer->length; 238 } 239 240 void 241 krad_packet_free(krad_packet *pkt) 242 { 243 if (pkt) 244 krad_attrset_free(pkt->attrset); 245 free(pkt); 246 } 247 248 /* Create a new request packet. */ 249 krb5_error_code 250 krad_packet_new_request(krb5_context ctx, const char *secret, krad_code code, 251 const krad_attrset *set, krad_packet_iter_cb cb, 252 void *data, krad_packet **request) 253 { 254 krb5_error_code retval; 255 krad_packet *pkt; 256 uchar id; 257 size_t attrset_len; 258 259 pkt = packet_new(); 260 if (pkt == NULL) { 261 if (cb != NULL) 262 (*cb)(data, TRUE); 263 return ENOMEM; 264 } 265 266 /* Generate the ID. */ 267 retval = id_generate(ctx, cb, data, &id); 268 if (retval != 0) 269 goto error; 270 pkt_id_set(pkt, id); 271 272 /* Generate the authenticator. */ 273 retval = auth_generate_random(ctx, pkt_auth(pkt)); 274 if (retval != 0) 275 goto error; 276 277 /* Encode the attributes. */ 278 retval = kr_attrset_encode(set, secret, pkt_auth(pkt), pkt_attr(pkt), 279 &attrset_len); 280 if (retval != 0) 281 goto error; 282 283 /* Set the code, ID and length. */ 284 pkt->pkt.length = attrset_len + OFFSET_ATTR; 285 pkt_code_set(pkt, code); 286 pkt_len_set(pkt, pkt->pkt.length); 287 288 /* Copy the attrset for future use. */ 289 retval = packet_set_attrset(ctx, secret, pkt); 290 if (retval != 0) 291 goto error; 292 293 *request = pkt; 294 return 0; 295 296 error: 297 free(pkt); 298 return retval; 299 } 300 301 /* Create a new request packet. */ 302 krb5_error_code 303 krad_packet_new_response(krb5_context ctx, const char *secret, krad_code code, 304 const krad_attrset *set, const krad_packet *request, 305 krad_packet **response) 306 { 307 krb5_error_code retval; 308 krad_packet *pkt; 309 size_t attrset_len; 310 311 pkt = packet_new(); 312 if (pkt == NULL) 313 return ENOMEM; 314 315 /* Encode the attributes. */ 316 retval = kr_attrset_encode(set, secret, pkt_auth(request), pkt_attr(pkt), 317 &attrset_len); 318 if (retval != 0) 319 goto error; 320 321 /* Set the code, ID and length. */ 322 pkt->pkt.length = attrset_len + OFFSET_ATTR; 323 pkt_code_set(pkt, code); 324 pkt_id_set(pkt, pkt_id_get(request)); 325 pkt_len_set(pkt, pkt->pkt.length); 326 327 /* Generate the authenticator. */ 328 retval = auth_generate_response(ctx, secret, pkt, pkt_auth(request), 329 pkt_auth(pkt)); 330 if (retval != 0) 331 goto error; 332 333 /* Copy the attrset for future use. */ 334 retval = packet_set_attrset(ctx, secret, pkt); 335 if (retval != 0) 336 goto error; 337 338 *response = pkt; 339 return 0; 340 341 error: 342 free(pkt); 343 return retval; 344 } 345 346 /* Decode a packet. */ 347 static krb5_error_code 348 decode_packet(krb5_context ctx, const char *secret, const krb5_data *buffer, 349 krad_packet **pkt) 350 { 351 krb5_error_code retval; 352 krad_packet *tmp; 353 krb5_ui_2 len; 354 355 tmp = packet_new(); 356 if (tmp == NULL) { 357 retval = ENOMEM; 358 goto error; 359 } 360 361 /* Ensure a proper message length. */ 362 retval = (buffer->length < OFFSET_ATTR) ? EMSGSIZE : 0; 363 if (retval != 0) 364 goto error; 365 len = load_16_be(offset(buffer, OFFSET_LENGTH)); 366 retval = (len < OFFSET_ATTR) ? EBADMSG : 0; 367 if (retval != 0) 368 goto error; 369 retval = (len > buffer->length || len > tmp->pkt.length) ? EBADMSG : 0; 370 if (retval != 0) 371 goto error; 372 373 /* Copy over the buffer. */ 374 tmp->pkt.length = len; 375 memcpy(tmp->pkt.data, buffer->data, len); 376 377 /* Parse the packet to ensure it is well-formed. */ 378 retval = packet_set_attrset(ctx, secret, tmp); 379 if (retval != 0) 380 goto error; 381 382 *pkt = tmp; 383 return 0; 384 385 error: 386 krad_packet_free(tmp); 387 return retval; 388 } 389 390 krb5_error_code 391 krad_packet_decode_request(krb5_context ctx, const char *secret, 392 const krb5_data *buffer, krad_packet_iter_cb cb, 393 void *data, const krad_packet **duppkt, 394 krad_packet **reqpkt) 395 { 396 const krad_packet *tmp = NULL; 397 krb5_error_code retval; 398 399 retval = decode_packet(ctx, secret, buffer, reqpkt); 400 if (cb != NULL && retval == 0) { 401 for (tmp = (*cb)(data, FALSE); tmp != NULL; tmp = (*cb)(data, FALSE)) { 402 if (pkt_id_get(*reqpkt) == pkt_id_get(tmp)) 403 break; 404 } 405 } 406 407 if (cb != NULL && (retval != 0 || tmp != NULL)) 408 (*cb)(data, TRUE); 409 410 *duppkt = tmp; 411 return retval; 412 } 413 414 krb5_error_code 415 krad_packet_decode_response(krb5_context ctx, const char *secret, 416 const krb5_data *buffer, krad_packet_iter_cb cb, 417 void *data, const krad_packet **reqpkt, 418 krad_packet **rsppkt) 419 { 420 uchar auth[AUTH_FIELD_SIZE]; 421 const krad_packet *tmp = NULL; 422 krb5_error_code retval; 423 424 retval = decode_packet(ctx, secret, buffer, rsppkt); 425 if (cb != NULL && retval == 0) { 426 for (tmp = (*cb)(data, FALSE); tmp != NULL; tmp = (*cb)(data, FALSE)) { 427 if (pkt_id_get(*rsppkt) != pkt_id_get(tmp)) 428 continue; 429 430 /* Response */ 431 retval = auth_generate_response(ctx, secret, *rsppkt, 432 pkt_auth(tmp), auth); 433 if (retval != 0) { 434 krad_packet_free(*rsppkt); 435 break; 436 } 437 438 /* If the authenticator matches, then the response is valid. */ 439 if (memcmp(pkt_auth(*rsppkt), auth, sizeof(auth)) == 0) 440 break; 441 } 442 } 443 444 if (cb != NULL && (retval != 0 || tmp != NULL)) 445 (*cb)(data, TRUE); 446 447 *reqpkt = tmp; 448 return retval; 449 } 450 451 const krb5_data * 452 krad_packet_encode(const krad_packet *pkt) 453 { 454 return &pkt->pkt; 455 } 456 457 krad_code 458 krad_packet_get_code(const krad_packet *pkt) 459 { 460 if (pkt == NULL) 461 return 0; 462 463 return pkt_code_get(pkt); 464 } 465 466 const krb5_data * 467 krad_packet_get_attr(const krad_packet *pkt, krad_attr type, size_t indx) 468 { 469 return krad_attrset_get(pkt->attrset, type, indx); 470 } 471