Path: blob/main/crypto/krb5/src/lib/krad/packet.c
107521 views
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */1/* lib/krad/packet.c - Packet functions for libkrad */2/*3* Copyright 2013 Red Hat, Inc. All rights reserved.4*5* Redistribution and use in source and binary forms, with or without6* modification, are permitted provided that the following conditions are met:7*8* 1. Redistributions of source code must retain the above copyright9* notice, this list of conditions and the following disclaimer.10*11* 2. Redistributions in binary form must reproduce the above copyright12* notice, this list of conditions and the following disclaimer in13* the documentation and/or other materials provided with the14* distribution.15*16* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS17* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED18* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A19* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER20* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,21* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,22* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR23* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF24* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING25* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS26* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.27*/2829#include "internal.h"3031#include <string.h>3233#include <arpa/inet.h>3435typedef unsigned char uchar;3637/* RFC 2865 */38#define MSGAUTH_SIZE (2 + MD5_DIGEST_SIZE)39#define OFFSET_CODE 040#define OFFSET_ID 141#define OFFSET_LENGTH 242#define OFFSET_AUTH 443#define OFFSET_ATTR 2044#define AUTH_FIELD_SIZE (OFFSET_ATTR - OFFSET_AUTH)4546#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)) = v49#define pkt_id_get(p) (*(uchar *)offset(&(p)->pkt, OFFSET_ID))50#define pkt_id_set(p, v) (*(uchar *)offset(&(p)->pkt, OFFSET_ID)) = v51#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))5556struct krad_packet_st {57char buffer[KRAD_PACKET_SIZE_MAX];58krad_attrset *attrset;59krb5_data pkt;60};6162typedef struct {63uchar x[(UCHAR_MAX + 1) / 8];64} idmap;6566/* Ensure the map is empty. */67static inline void68idmap_init(idmap *map)69{70memset(map, 0, sizeof(*map));71}7273/* Set an id as already allocated. */74static inline void75idmap_set(idmap *map, uchar id)76{77map->x[id / 8] |= 1 << (id % 8);78}7980/* Determine whether or not an id is used. */81static inline krb5_boolean82idmap_isset(const idmap *map, uchar id)83{84return (map->x[id / 8] & (1 << (id % 8))) != 0;85}8687/* 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. */89static inline krb5_error_code90idmap_find(const idmap *map, uchar *id)91{92krb5_int16 i;9394for (i = *id; i >= 0 && i <= UCHAR_MAX; (*id % 2 == 0) ? i++ : i--) {95if (!idmap_isset(map, i))96goto success;97}9899for (i = *id; i >= 0 && i <= UCHAR_MAX; (*id % 2 == 1) ? i++ : i--) {100if (!idmap_isset(map, i))101goto success;102}103104return ERANGE;105106success:107*id = i;108return 0;109}110111/* Generate size bytes of random data into the buffer. */112static inline krb5_error_code113randomize(krb5_context ctx, void *buffer, unsigned int size)114{115krb5_data rdata = make_data(buffer, size);116return krb5_c_random_make_octets(ctx, &rdata);117}118119/* Generate a radius packet id. */120static krb5_error_code121id_generate(krb5_context ctx, krad_packet_iter_cb cb, void *data, uchar *id)122{123krb5_error_code retval;124const krad_packet *tmp;125idmap used;126uchar i;127128retval = randomize(ctx, &i, sizeof(i));129if (retval != 0) {130if (cb != NULL)131(*cb)(data, TRUE);132return retval;133}134135if (cb != NULL) {136idmap_init(&used);137for (tmp = (*cb)(data, FALSE); tmp != NULL; tmp = (*cb)(data, FALSE))138idmap_set(&used, tmp->pkt.data[1]);139140retval = idmap_find(&used, &i);141if (retval != 0)142return retval;143}144145*id = i;146return 0;147}148149/* Generate a random authenticator field. */150static krb5_error_code151auth_generate_random(krb5_context ctx, uchar *rauth)152{153krb5_ui_4 trunctime;154time_t currtime;155156/* Get the least-significant four bytes of the current time. */157currtime = time(NULL);158if (currtime == (time_t)-1)159return errno;160trunctime = (krb5_ui_4)currtime;161memcpy(rauth, &trunctime, sizeof(trunctime));162163/* Randomize the rest of the buffer. */164return randomize(ctx, rauth + sizeof(trunctime),165AUTH_FIELD_SIZE - sizeof(trunctime));166}167168/* Generate a response authenticator field. */169static krb5_error_code170auth_generate_response(krb5_context ctx, const char *secret,171const krad_packet *response, const uchar *auth,172uchar *rauth)173{174krb5_error_code retval;175krb5_checksum hash;176krb5_data data;177178/* Allocate the temporary buffer. */179retval = alloc_data(&data, response->pkt.length + strlen(secret));180if (retval != 0)181return retval;182183/* Encoded RADIUS packet with the request's184* authenticator and the secret at the end. */185memcpy(data.data, response->pkt.data, response->pkt.length);186memcpy(data.data + OFFSET_AUTH, auth, AUTH_FIELD_SIZE);187memcpy(data.data + response->pkt.length, secret, strlen(secret));188189/* Hash it. */190retval = krb5_c_make_checksum(ctx, CKSUMTYPE_RSA_MD5, NULL, 0, &data,191&hash);192free(data.data);193if (retval != 0)194return retval;195196memcpy(rauth, hash.contents, AUTH_FIELD_SIZE);197krb5_free_checksum_contents(ctx, &hash);198return 0;199}200201/* Create a new packet. */202static krad_packet *203packet_new(void)204{205krad_packet *pkt;206207pkt = calloc(1, sizeof(krad_packet));208if (pkt == NULL)209return NULL;210pkt->pkt = make_data(pkt->buffer, sizeof(pkt->buffer));211212return pkt;213}214215/* Set the attrset object by decoding the packet. */216static krb5_error_code217packet_set_attrset(krb5_context ctx, const char *secret, krad_packet *pkt)218{219krb5_data tmp;220221tmp = make_data(pkt_attr(pkt), pkt->pkt.length - OFFSET_ATTR);222return kr_attrset_decode(ctx, &tmp, secret, pkt_auth(pkt), &pkt->attrset);223}224225/* Determine if a packet requires a Message-Authenticator attribute. */226static inline krb5_boolean227requires_msgauth(const char *secret, krad_code code)228{229/* If no secret is provided, assume that the transport is a UNIX socket.230* Message-Authenticator is required only on UDP and TCP connections. */231if (*secret == '\0')232return FALSE;233234/*235* Per draft-ietf-radext-deprecating-radius-03 sections 5.2.1 and 5.2.4,236* Message-Authenticator is required in Access-Request packets and all237* potential responses when UDP or TCP transport is used.238*/239return code == KRAD_CODE_ACCESS_REQUEST ||240code == KRAD_CODE_ACCESS_ACCEPT || code == KRAD_CODE_ACCESS_REJECT ||241code == KRAD_CODE_ACCESS_CHALLENGE;242}243244/* Check if the packet has a Message-Authenticator attribute. */245static inline krb5_boolean246has_pkt_msgauth(const krad_packet *pkt)247{248return krad_attrset_get(pkt->attrset, KRAD_ATTR_MESSAGE_AUTHENTICATOR,2490) != NULL;250}251252/* Return the beginning of the Message-Authenticator attribute in pkt, or NULL253* if no such attribute is present. */254static const uint8_t *255lookup_msgauth_addr(const krad_packet *pkt)256{257size_t i;258uint8_t *p;259260i = OFFSET_ATTR;261while (i + 2 < pkt->pkt.length) {262p = (uint8_t *)offset(&pkt->pkt, i);263if (*p == KRAD_ATTR_MESSAGE_AUTHENTICATOR)264return p;265i += p[1];266}267268return NULL;269}270271/*272* Calculate the message authenticator MAC for pkt as specified in RFC 2869273* section 5.14, placing the result in mac_out. Use the provided authenticator274* auth, which may be from pkt or from a corresponding request.275*/276static krb5_error_code277calculate_mac(const char *secret, const krad_packet *pkt,278const uint8_t auth[AUTH_FIELD_SIZE],279uint8_t mac_out[MD5_DIGEST_SIZE])280{281const uint8_t *msgauth_attr, *msgauth_end, *pkt_end;282krb5_crypto_iov input[5];283krb5_data ksecr, mac;284static const uint8_t zeroed_msgauth[MSGAUTH_SIZE] = {285KRAD_ATTR_MESSAGE_AUTHENTICATOR, MSGAUTH_SIZE286};287288msgauth_attr = lookup_msgauth_addr(pkt);289if (msgauth_attr == NULL)290return EINVAL;291msgauth_end = msgauth_attr + MSGAUTH_SIZE;292pkt_end = (const uint8_t *)pkt->pkt.data + pkt->pkt.length;293294/* Read code, id, and length from the packet. */295input[0].flags = KRB5_CRYPTO_TYPE_DATA;296input[0].data = make_data(pkt->pkt.data, OFFSET_AUTH);297298/* Read the provided authenticator. */299input[1].flags = KRB5_CRYPTO_TYPE_DATA;300input[1].data = make_data((uint8_t *)auth, AUTH_FIELD_SIZE);301302/* Read any attributes before Message-Authenticator. */303input[2].flags = KRB5_CRYPTO_TYPE_DATA;304input[2].data = make_data(pkt_attr(pkt), msgauth_attr - pkt_attr(pkt));305306/* Read Message-Authenticator with the data bytes all set to zero, per RFC307* 2869 section 5.14. */308input[3].flags = KRB5_CRYPTO_TYPE_DATA;309input[3].data = make_data((uint8_t *)zeroed_msgauth, MSGAUTH_SIZE);310311/* Read any attributes after Message-Authenticator. */312input[4].flags = KRB5_CRYPTO_TYPE_DATA;313input[4].data = make_data((uint8_t *)msgauth_end, pkt_end - msgauth_end);314315mac = make_data(mac_out, MD5_DIGEST_SIZE);316ksecr = string2data((char *)secret);317return k5_hmac_md5(&ksecr, input, 5, &mac);318}319320ssize_t321krad_packet_bytes_needed(const krb5_data *buffer)322{323size_t len;324325if (buffer->length < OFFSET_AUTH)326return OFFSET_AUTH - buffer->length;327328len = load_16_be(offset(buffer, OFFSET_LENGTH));329if (len > KRAD_PACKET_SIZE_MAX)330return -1;331332return (buffer->length > len) ? 0 : len - buffer->length;333}334335void336krad_packet_free(krad_packet *pkt)337{338if (pkt)339krad_attrset_free(pkt->attrset);340free(pkt);341}342343/* Create a new request packet. */344krb5_error_code345krad_packet_new_request(krb5_context ctx, const char *secret, krad_code code,346const krad_attrset *set, krad_packet_iter_cb cb,347void *data, krad_packet **request)348{349krb5_error_code retval;350krad_packet *pkt;351uchar id;352size_t attrset_len;353krb5_boolean msgauth_required;354355pkt = packet_new();356if (pkt == NULL) {357if (cb != NULL)358(*cb)(data, TRUE);359return ENOMEM;360}361362/* Generate the ID. */363retval = id_generate(ctx, cb, data, &id);364if (retval != 0)365goto error;366pkt_id_set(pkt, id);367368/* Generate the authenticator. */369retval = auth_generate_random(ctx, pkt_auth(pkt));370if (retval != 0)371goto error;372373/* Determine if Message-Authenticator is required. */374msgauth_required = (*secret != '\0' && code == KRAD_CODE_ACCESS_REQUEST);375376/* Encode the attributes. */377retval = kr_attrset_encode(set, secret, pkt_auth(pkt), msgauth_required,378pkt_attr(pkt), &attrset_len);379if (retval != 0)380goto error;381382/* Set the code, ID and length. */383pkt->pkt.length = attrset_len + OFFSET_ATTR;384pkt_code_set(pkt, code);385pkt_len_set(pkt, pkt->pkt.length);386387if (msgauth_required) {388/* Calculate and set the Message-Authenticator MAC. */389retval = calculate_mac(secret, pkt, pkt_auth(pkt), pkt_attr(pkt) + 2);390if (retval != 0)391goto error;392}393394/* Copy the attrset for future use. */395retval = packet_set_attrset(ctx, secret, pkt);396if (retval != 0)397goto error;398399*request = pkt;400return 0;401402error:403free(pkt);404return retval;405}406407/* Create a new request packet. */408krb5_error_code409krad_packet_new_response(krb5_context ctx, const char *secret, krad_code code,410const krad_attrset *set, const krad_packet *request,411krad_packet **response)412{413krb5_error_code retval;414krad_packet *pkt;415size_t attrset_len;416krb5_boolean msgauth_required;417418pkt = packet_new();419if (pkt == NULL)420return ENOMEM;421422/* Determine if Message-Authenticator is required. */423msgauth_required = requires_msgauth(secret, code);424425/* Encode the attributes. */426retval = kr_attrset_encode(set, secret, pkt_auth(request),427msgauth_required, pkt_attr(pkt), &attrset_len);428if (retval != 0)429goto error;430431/* Set the code, ID and length. */432pkt->pkt.length = attrset_len + OFFSET_ATTR;433pkt_code_set(pkt, code);434pkt_id_set(pkt, pkt_id_get(request));435pkt_len_set(pkt, pkt->pkt.length);436437/* Generate the authenticator. */438retval = auth_generate_response(ctx, secret, pkt, pkt_auth(request),439pkt_auth(pkt));440if (retval != 0)441goto error;442443if (msgauth_required) {444/*445* Calculate and replace the Message-Authenticator MAC. Per RFC 2869446* section 5.14, use the authenticator from the request, not from the447* response.448*/449retval = calculate_mac(secret, pkt, pkt_auth(request),450pkt_attr(pkt) + 2);451if (retval != 0)452goto error;453}454455/* Copy the attrset for future use. */456retval = packet_set_attrset(ctx, secret, pkt);457if (retval != 0)458goto error;459460*response = pkt;461return 0;462463error:464free(pkt);465return retval;466}467468/* Verify the Message-Authenticator value in pkt, using the provided469* authenticator (which may be from pkt or from a corresponding request). */470static krb5_error_code471verify_msgauth(const char *secret, const krad_packet *pkt,472const uint8_t auth[AUTH_FIELD_SIZE])473{474uint8_t mac[MD5_DIGEST_SIZE];475const krb5_data *msgauth;476krb5_error_code retval;477478msgauth = krad_packet_get_attr(pkt, KRAD_ATTR_MESSAGE_AUTHENTICATOR, 0);479/* XXX ENODATA does not exist in FreeBSD. The closest thing we have to */480/* XXX ENODATA is ENOATTR. We use that instead. */481#define ENODATA ENOATTR482if (msgauth == NULL)483return ENODATA;484485retval = calculate_mac(secret, pkt, auth, mac);486if (retval)487return retval;488489if (msgauth->length != MD5_DIGEST_SIZE)490return EMSGSIZE;491492if (k5_bcmp(mac, msgauth->data, MD5_DIGEST_SIZE) != 0)493return EBADMSG;494495return 0;496}497498/* Decode a packet. */499static krb5_error_code500decode_packet(krb5_context ctx, const char *secret, const krb5_data *buffer,501krad_packet **pkt)502{503krb5_error_code retval;504krad_packet *tmp;505krb5_ui_2 len;506507tmp = packet_new();508if (tmp == NULL) {509retval = ENOMEM;510goto error;511}512513/* Ensure a proper message length. */514retval = (buffer->length < OFFSET_ATTR) ? EMSGSIZE : 0;515if (retval != 0)516goto error;517len = load_16_be(offset(buffer, OFFSET_LENGTH));518retval = (len < OFFSET_ATTR) ? EBADMSG : 0;519if (retval != 0)520goto error;521retval = (len > buffer->length || len > tmp->pkt.length) ? EBADMSG : 0;522if (retval != 0)523goto error;524525/* Copy over the buffer. */526tmp->pkt.length = len;527memcpy(tmp->pkt.data, buffer->data, len);528529/* Parse the packet to ensure it is well-formed. */530retval = packet_set_attrset(ctx, secret, tmp);531if (retval != 0)532goto error;533534*pkt = tmp;535return 0;536537error:538krad_packet_free(tmp);539return retval;540}541542krb5_error_code543krad_packet_decode_request(krb5_context ctx, const char *secret,544const krb5_data *buffer, krad_packet_iter_cb cb,545void *data, const krad_packet **duppkt,546krad_packet **reqpkt)547{548const krad_packet *tmp = NULL;549krad_packet *req;550krb5_error_code retval;551552retval = decode_packet(ctx, secret, buffer, &req);553if (retval)554return retval;555556/* Verify Message-Authenticator if present. */557if (has_pkt_msgauth(req)) {558retval = verify_msgauth(secret, req, pkt_auth(req));559if (retval) {560krad_packet_free(req);561return retval;562}563}564565if (cb != NULL) {566for (tmp = (*cb)(data, FALSE); tmp != NULL; tmp = (*cb)(data, FALSE)) {567if (pkt_id_get(*reqpkt) == pkt_id_get(tmp))568break;569}570571if (tmp != NULL)572(*cb)(data, TRUE);573}574575*reqpkt = req;576*duppkt = tmp;577return 0;578}579580krb5_error_code581krad_packet_decode_response(krb5_context ctx, const char *secret,582const krb5_data *buffer, krad_packet_iter_cb cb,583void *data, const krad_packet **reqpkt,584krad_packet **rsppkt)585{586uchar auth[AUTH_FIELD_SIZE];587const krad_packet *tmp = NULL;588krb5_error_code retval;589590retval = decode_packet(ctx, secret, buffer, rsppkt);591if (cb != NULL && retval == 0) {592for (tmp = (*cb)(data, FALSE); tmp != NULL; tmp = (*cb)(data, FALSE)) {593if (pkt_id_get(*rsppkt) != pkt_id_get(tmp))594continue;595596/* Response */597retval = auth_generate_response(ctx, secret, *rsppkt,598pkt_auth(tmp), auth);599if (retval != 0) {600krad_packet_free(*rsppkt);601break;602}603604/* Verify the response authenticator. */605if (k5_bcmp(pkt_auth(*rsppkt), auth, sizeof(auth)) != 0)606continue;607608/* Verify Message-Authenticator if present. */609if (has_pkt_msgauth(*rsppkt)) {610if (verify_msgauth(secret, *rsppkt, pkt_auth(tmp)) != 0)611continue;612}613614break;615}616}617618if (cb != NULL && (retval != 0 || tmp != NULL))619(*cb)(data, TRUE);620621*reqpkt = tmp;622return retval;623}624625const krb5_data *626krad_packet_encode(const krad_packet *pkt)627{628return &pkt->pkt;629}630631krad_code632krad_packet_get_code(const krad_packet *pkt)633{634if (pkt == NULL)635return 0;636637return pkt_code_get(pkt);638}639640const krb5_data *641krad_packet_get_attr(const krad_packet *pkt, krad_attr type, size_t indx)642{643return krad_attrset_get(pkt->attrset, type, indx);644}645646647