/* * Copyright (C) 2009 Martin Willi * Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See . * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ #include "radius_message.h" #include #include #include typedef struct private_radius_message_t private_radius_message_t; typedef struct rmsg_t rmsg_t; typedef struct rattr_t rattr_t; /** * RADIUS message header */ struct rmsg_t { /** message code, radius_message_code_t */ uint8_t code; /** message identifier */ uint8_t identifier; /** length of Code, Identifier, Length, Authenticator and Attributes */ uint16_t length; /** message authenticator, MD5 hash */ uint8_t authenticator[HASH_SIZE_MD5]; /** variable list of packed attributes */ uint8_t attributes[]; } __attribute__((packed)); /** * RADIUS message attribute. */ struct rattr_t { /** attribute type, radius_attribute_type_t */ uint8_t type; /** length of the attriubte, including the Type, Length and Value fields */ uint8_t length; /** variable length attribute value */ uint8_t value[]; } __attribute__((packed)); /** * Private data of an radius_message_t object. */ struct private_radius_message_t { /** * Public radius_message_t interface. */ radius_message_t public; /** * message data, allocated */ rmsg_t *msg; /** * User-Password to encrypt and encode, if any */ chunk_t password; }; /** * Described in header. */ void libradius_init(void) { /* empty */ } ENUM_BEGIN(radius_message_code_names, RMC_ACCESS_REQUEST, RMC_ACCOUNTING_RESPONSE, "Access-Request", "Access-Accept", "Access-Reject", "Accounting-Request", "Accounting-Response"); ENUM_NEXT(radius_message_code_names, RMC_ACCESS_CHALLENGE, RMC_ACCESS_CHALLENGE, RMC_ACCOUNTING_RESPONSE, "Access-Challenge"); ENUM_NEXT(radius_message_code_names, RMC_DISCONNECT_REQUEST, RMC_COA_NAK, RMC_ACCESS_CHALLENGE, "Disconnect-Request", "Disconnect-ACK", "Disconnect-NAK", "CoA-Request", "CoA-ACK", "CoA-NAK"); ENUM_END(radius_message_code_names, RMC_COA_NAK); ENUM_BEGIN(radius_attribute_type_names, RAT_USER_NAME, RAT_MIP6_HOME_LINK_PREFIX, "User-Name", "User-Password", "CHAP-Password", "NAS-IP-Address", "NAS-Port", "Service-Type", "Framed-Protocol", "Framed-IP-Address", "Framed-IP-Netmask", "Framed-Routing", "Filter-Id", "Framed-MTU", "Framed-Compression", "Login-IP-Host", "Login-Service", "Login-TCP-Port", "Unassigned", "Reply-Message", "Callback-Number", "Callback-Id", "Unassigned", "Framed-Route", "Framed-IPX-Network", "State", "Class", "Vendor-Specific", "Session-Timeout", "Idle-Timeout", "Termination-Action", "Called-Station-Id", "Calling-Station-Id", "NAS-Identifier", "Proxy-State", "Login-LAT-Service", "Login-LAT-Node", "Login-LAT-Group", "Framed-AppleTalk-Link", "Framed-AppleTalk-Network", "Framed-AppleTalk-Zone", "Acct-Status-Type", "Acct-Delay-Time", "Acct-Input-Octets", "Acct-Output-Octets", "Acct-Session-Id", "Acct-Authentic", "Acct-Session-Time", "Acct-Input-Packets", "Acct-Output-Packets", "Acct-Terminate-Cause", "Acct-Multi-Session-Id", "Acct-Link-Count", "Acct-Input-Gigawords", "Acct-Output-Gigawords", "Unassigned", "Event-Timestamp", "Egress-VLANID", "Ingress-Filters", "Egress-VLAN-Name", "User-Priority-Table", "CHAP-Challenge", "NAS-Port-Type", "Port-Limit", "Login-LAT-Port", "Tunnel-Type", "Tunnel-Medium-Type", "Tunnel-Client-Endpoint", "Tunnel-Server-Endpoint", "Acct-Tunnel-Connection", "Tunnel-Password", "ARAP-Password", "ARAP-Features", "ARAP-Zone-Access", "ARAP-Security", "ARAP-Security-Data", "Password-Retry", "Prompt", "Connect-Info", "Configuration-Token", "EAP-Message", "Message-Authenticator", "Tunnel-Private-Group-ID", "Tunnel-Assignment-ID", "Tunnel-Preference", "ARAP-Challenge-Response", "Acct-Interim-Interval", "Acct-Tunnel-Packets-Lost", "NAS-Port-Id", "Framed-Pool", "CUI", "Tunnel-Client-Auth-ID", "Tunnel-Server-Auth-ID", "NAS-Filter-Rule", "Unassigned", "Originating-Line-Info", "NAS-IPv6-Address", "Framed-Interface-Id", "Framed-IPv6-Prefix", "Login-IPv6-Host", "Framed-IPv6-Route", "Framed-IPv6-Pool", "Error-Cause", "EAP-Key-Name", "Digest-Response", "Digest-Realm", "Digest-Nonce", "Digest-Response-Auth", "Digest-Nextnonce", "Digest-Method", "Digest-URI", "Digest-Qop", "Digest-Algorithm", "Digest-Entity-Body-Hash", "Digest-CNonce", "Digest-Nonce-Count", "Digest-Username", "Digest-Opaque", "Digest-Auth-Param", "Digest-AKA-Auts", "Digest-Domain", "Digest-Stale", "Digest-HA1", "SIP-AOR", "Delegated-IPv6-Prefix", "MIP6-Feature-Vector", "MIP6-Home-Link-Prefix"); ENUM_NEXT(radius_attribute_type_names, RAT_FRAMED_IPV6_ADDRESS, RAT_STATEFUL_IPV6_ADDRESS_POOL, RAT_MIP6_HOME_LINK_PREFIX, "Framed-IPv6-Address", "DNS-Server-IPv6-Address", "Route-IPv6-Information", "Delegated-IPv6-Prefix-Pool", "Stateful-IPv6-Address-Pool"); ENUM_END(radius_attribute_type_names, RAT_STATEFUL_IPV6_ADDRESS_POOL); /** * Attribute enumerator implementation */ typedef struct { /** implements enumerator interface */ enumerator_t public; /** currently pointing attribute */ rattr_t *next; /** bytes left */ int left; } attribute_enumerator_t; METHOD(enumerator_t, attribute_enumerate, bool, attribute_enumerator_t *this, va_list args) { chunk_t *data; int *type; VA_ARGS_VGET(args, type, data); if (this->left == 0) { return FALSE; } if (this->left < sizeof(rattr_t) || this->left < this->next->length) { DBG1(DBG_IKE, "RADIUS message truncated"); return FALSE; } *type = this->next->type; data->ptr = this->next->value; data->len = this->next->length - sizeof(rattr_t); this->left -= this->next->length; this->next = ((void*)this->next) + this->next->length; return TRUE; } METHOD(radius_message_t, create_enumerator, enumerator_t*, private_radius_message_t *this) { attribute_enumerator_t *e; if (ntohs(this->msg->length) < sizeof(rmsg_t) + sizeof(rattr_t)) { return enumerator_create_empty(); } INIT(e, .public = { .enumerate = enumerator_enumerate_default, .venumerate = _attribute_enumerate, .destroy = (void*)free, }, .next = (rattr_t*)this->msg->attributes, .left = ntohs(this->msg->length) - sizeof(rmsg_t), ); return &e->public; } /** * Vendor attribute enumerator implementation */ typedef struct { /** implements enumerator interface */ enumerator_t public; /** inner attribute enumerator */ enumerator_t *inner; /** current vendor ID */ uint32_t vendor; /** reader for current vendor ID */ bio_reader_t *reader; } vendor_enumerator_t; METHOD(enumerator_t, vendor_enumerate, bool, vendor_enumerator_t *this, va_list args) { chunk_t inner_data, *data; int inner_type, *vendor, *type; uint8_t type8, len; VA_ARGS_VGET(args, vendor, type, data); while (TRUE) { if (this->reader) { if (this->reader->remaining(this->reader) >= 2 && this->reader->read_uint8(this->reader, &type8) && this->reader->read_uint8(this->reader, &len) && len >= 2 && this->reader->read_data(this->reader, len - 2, data)) { *vendor = this->vendor; *type = type8; return TRUE; } this->reader->destroy(this->reader); this->reader = NULL; } if (this->inner->enumerate(this->inner, &inner_type, &inner_data)) { if (inner_type == RAT_VENDOR_SPECIFIC) { this->reader = bio_reader_create(inner_data); if (!this->reader->read_uint32(this->reader, &this->vendor)) { this->reader->destroy(this->reader); this->reader = NULL; } } } else { return FALSE; } } } METHOD(enumerator_t, vendor_destroy, void, vendor_enumerator_t *this) { DESTROY_IF(this->reader); this->inner->destroy(this->inner); free(this); } METHOD(radius_message_t, create_vendor_enumerator, enumerator_t*, private_radius_message_t *this) { vendor_enumerator_t *e; INIT(e, .public = { .enumerate = enumerator_enumerate_default, .venumerate = _vendor_enumerate, .destroy = _vendor_destroy, }, .inner = create_enumerator(this), ); return &e->public; } METHOD(radius_message_t, add, void, private_radius_message_t *this, radius_attribute_type_t type, chunk_t data) { rattr_t *attribute; if (type == RAT_USER_PASSWORD && !this->password.len) { /* store a null-padded password */ this->password = chunk_alloc(round_up(data.len, HASH_SIZE_MD5)); memset(this->password.ptr + data.len, 0, this->password.len - data.len); memcpy(this->password.ptr, data.ptr, data.len); return; } data.len = min(data.len, MAX_RADIUS_ATTRIBUTE_SIZE); this->msg = realloc(this->msg, ntohs(this->msg->length) + sizeof(rattr_t) + data.len); attribute = ((void*)this->msg) + ntohs(this->msg->length); attribute->type = type; attribute->length = data.len + sizeof(rattr_t); memcpy(attribute->value, data.ptr, data.len); this->msg->length = htons(ntohs(this->msg->length) + attribute->length); } METHOD(radius_message_t, crypt, bool, private_radius_message_t *this, chunk_t salt, chunk_t in, chunk_t out, chunk_t secret, hasher_t *hasher) { char b[HASH_SIZE_MD5]; /** * From RFC2548 (encryption): * b(1) = MD5(S + R + A) c(1) = p(1) xor b(1) C = c(1) * b(2) = MD5(S + c(1)) c(2) = p(2) xor b(2) C = C + c(2) * . . . * b(i) = MD5(S + c(i-1)) c(i) = p(i) xor b(i) C = C + c(i) * * P/C = Plain/Crypted => in/out * S = secret * R = authenticator * A = salt */ if (in.len != out.len) { return FALSE; } if (in.len % HASH_SIZE_MD5 || in.len < HASH_SIZE_MD5) { return FALSE; } if (out.ptr != in.ptr) { memcpy(out.ptr, in.ptr, in.len); } /* Preparse seed for first round: * b(1) = MD5(S + R + A) */ if (!hasher->get_hash(hasher, secret, NULL) || !hasher->get_hash(hasher, chunk_from_thing(this->msg->authenticator), NULL) || !hasher->get_hash(hasher, salt, b)) { return FALSE; } while (in.len) { /* p(i) = b(i) xor c(1) */ memxor(out.ptr, b, HASH_SIZE_MD5); out = chunk_skip(out, HASH_SIZE_MD5); if (out.len) { /* Prepare seed for next round:: * b(i) = MD5(S + c(i-1)) */ if (!hasher->get_hash(hasher, secret, NULL) || !hasher->get_hash(hasher, chunk_create(in.ptr, HASH_SIZE_MD5), b)) { return FALSE; } } in = chunk_skip(in, HASH_SIZE_MD5); } return TRUE; } METHOD(radius_message_t, sign, bool, private_radius_message_t *this, uint8_t *req_auth, chunk_t secret, hasher_t *hasher, signer_t *signer, rng_t *rng, bool msg_auth) { if (rng) { /* build Request-Authenticator */ if (!rng->get_bytes(rng, HASH_SIZE_MD5, this->msg->authenticator)) { return FALSE; } } else { /* prepare build of Response-Authenticator */ if (req_auth) { memcpy(this->msg->authenticator, req_auth, HASH_SIZE_MD5); } else { memset(this->msg->authenticator, 0, sizeof(this->msg->authenticator)); } } if (this->password.len) { /* encrypt password inline */ if (!crypt(this, chunk_empty, this->password, this->password, secret, hasher)) { return FALSE; } add(this, RAT_USER_PASSWORD, this->password); chunk_clear(&this->password); } if (msg_auth) { char buf[HASH_SIZE_MD5]; /* build Message-Authenticator attribute, using 16 null bytes */ memset(buf, 0, sizeof(buf)); add(this, RAT_MESSAGE_AUTHENTICATOR, chunk_create(buf, sizeof(buf))); if (!signer->get_signature(signer, chunk_create((u_char*)this->msg, ntohs(this->msg->length)), ((u_char*)this->msg) + ntohs(this->msg->length) - HASH_SIZE_MD5)) { return FALSE; } } if (!rng) { chunk_t msg; /* build Response-Authenticator */ msg = chunk_create((u_char*)this->msg, ntohs(this->msg->length)); if (!hasher->get_hash(hasher, msg, NULL) || !hasher->get_hash(hasher, secret, this->msg->authenticator)) { return FALSE; } } return TRUE; } METHOD(radius_message_t, verify, bool, private_radius_message_t *this, uint8_t *req_auth, chunk_t secret, hasher_t *hasher, signer_t *signer) { char buf[HASH_SIZE_MD5], res_auth[HASH_SIZE_MD5]; enumerator_t *enumerator; int type; chunk_t data, msg; bool has_eap = FALSE, has_auth = FALSE; msg = chunk_create((u_char*)this->msg, ntohs(this->msg->length)); if (this->msg->code != RMC_ACCESS_REQUEST) { /* replace Response by Request Authenticator for verification */ memcpy(res_auth, this->msg->authenticator, HASH_SIZE_MD5); if (req_auth) { memcpy(this->msg->authenticator, req_auth, HASH_SIZE_MD5); } else { memset(this->msg->authenticator, 0, HASH_SIZE_MD5); } /* verify Response-Authenticator */ if (!hasher->get_hash(hasher, msg, NULL) || !hasher->get_hash(hasher, secret, buf) || !memeq_const(buf, res_auth, HASH_SIZE_MD5)) { DBG1(DBG_CFG, "RADIUS Response-Authenticator verification failed"); return FALSE; } } /* verify Message-Authenticator attribute */ enumerator = create_enumerator(this); while (enumerator->enumerate(enumerator, &type, &data)) { if (type == RAT_MESSAGE_AUTHENTICATOR) { if (data.len != HASH_SIZE_MD5) { DBG1(DBG_CFG, "RADIUS Message-Authenticator invalid length"); enumerator->destroy(enumerator); return FALSE; } memcpy(buf, data.ptr, data.len); memset(data.ptr, 0, data.len); if (signer->verify_signature(signer, msg, chunk_create(buf, sizeof(buf)))) { /* restore Message-Authenticator */ memcpy(data.ptr, buf, data.len); has_auth = TRUE; break; } else { DBG1(DBG_CFG, "RADIUS Message-Authenticator verification failed"); enumerator->destroy(enumerator); return FALSE; } } else if (type == RAT_EAP_MESSAGE) { has_eap = TRUE; } } enumerator->destroy(enumerator); if (this->msg->code != RMC_ACCESS_REQUEST) { /* restore Response-Authenticator */ memcpy(this->msg->authenticator, res_auth, HASH_SIZE_MD5); } if (has_eap && !has_auth) { /* Message-Authenticator is required if we have an EAP-Message */ DBG1(DBG_CFG, "RADIUS Message-Authenticator attribute missing"); return FALSE; } return TRUE; } METHOD(radius_message_t, get_code, radius_message_code_t, private_radius_message_t *this) { return this->msg->code; } METHOD(radius_message_t, get_identifier, uint8_t, private_radius_message_t *this) { return this->msg->identifier; } METHOD(radius_message_t, set_identifier, void, private_radius_message_t *this, uint8_t identifier) { this->msg->identifier = identifier; } METHOD(radius_message_t, get_authenticator, uint8_t*, private_radius_message_t *this) { return this->msg->authenticator; } METHOD(radius_message_t, get_encoding, chunk_t, private_radius_message_t *this) { return chunk_create((u_char*)this->msg, ntohs(this->msg->length)); } METHOD(radius_message_t, destroy, void, private_radius_message_t *this) { chunk_clear(&this->password); free(this->msg); free(this); } /** * Generic constructor */ static private_radius_message_t *radius_message_create_empty() { private_radius_message_t *this; INIT(this, .public = { .create_enumerator = _create_enumerator, .create_vendor_enumerator = _create_vendor_enumerator, .add = _add, .get_code = _get_code, .get_identifier = _get_identifier, .set_identifier = _set_identifier, .get_authenticator = _get_authenticator, .get_encoding = _get_encoding, .sign = _sign, .verify = _verify, .crypt = _crypt, .destroy = _destroy, }, ); return this; } /** * See header */ radius_message_t *radius_message_create(radius_message_code_t code) { private_radius_message_t *this = radius_message_create_empty(); INIT(this->msg, .code = code, .identifier = 0, .length = htons(sizeof(rmsg_t)), ); return &this->public; } /** * See header */ radius_message_t *radius_message_parse(chunk_t data) { private_radius_message_t *this = radius_message_create_empty(); this->msg = malloc(data.len); memcpy(this->msg, data.ptr, data.len); if (data.len < sizeof(rmsg_t) || ntohs(this->msg->length) != data.len) { DBG1(DBG_IKE, "RADIUS message has invalid length"); destroy(this); return NULL; } return &this->public; }