/* * 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 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 */ u_int8_t code; /** message identifier */ u_int8_t identifier; /** length of Code, Identifier, Length, Authenticator and Attributes */ u_int16_t length; /** message authenticator, MD5 hash */ u_int8_t authenticator[HASH_SIZE_MD5]; /** variable list of packed attributes */ u_int8_t attributes[]; } __attribute__((packed)); /** * RADIUS message attribute. */ struct rattr_t { /** attribute type, radius_attribute_type_t */ u_int8_t type; /** length of the attriubte, including the Type, Length and Value fields */ u_int8_t length; /** variable length attribute value */ u_int8_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; }; 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_END(radius_message_code_names, RMC_ACCESS_CHALLENGE); ENUM(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"); /** * Attribute enumerator implementation */ typedef struct { /** implements enumerator interface */ enumerator_t public; /** currently pointing attribute */ rattr_t *next; /** bytes left */ int left; } attribute_enumerator_t; /** * Implementation of attribute_enumerator_t.enumerate */ static bool attribute_enumerate(attribute_enumerator_t *this, int *type, chunk_t *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; } /** * Implementation of radius_message_t.create_enumerator */ static enumerator_t* create_enumerator(private_radius_message_t *this) { attribute_enumerator_t *e; if (ntohs(this->msg->length) < sizeof(rmsg_t) + sizeof(rattr_t)) { return enumerator_create_empty(); } e = malloc_thing(attribute_enumerator_t); e->public.enumerate = (void*)attribute_enumerate; e->public.destroy = (void*)free; e->next = (rattr_t*)this->msg->attributes; e->left = ntohs(this->msg->length) - sizeof(rmsg_t); return &e->public; } /** * Implementation of radius_message_t.add */ static void add(private_radius_message_t *this, radius_attribute_type_t type, chunk_t data) { rattr_t *attribute; data.len = min(data.len, 253); 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); } /** * Implementation of radius_message_t.sign */ static void sign(private_radius_message_t *this, rng_t *rng, signer_t *signer) { char buf[HASH_SIZE_MD5]; /* build Request-Authenticator */ rng->get_bytes(rng, HASH_SIZE_MD5, this->msg->authenticator); /* build Message-Authenticator attribute, using 16 null bytes */ memset(buf, 0, sizeof(buf)); add(this, RAT_MESSAGE_AUTHENTICATOR, chunk_create(buf, sizeof(buf))); 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); } /** * Implementation of radius_message_t.verify */ static bool verify(private_radius_message_t *this, u_int8_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; /* replace Response by Request Authenticator for verification */ memcpy(res_auth, this->msg->authenticator, HASH_SIZE_MD5); memcpy(this->msg->authenticator, req_auth, HASH_SIZE_MD5); msg = chunk_create((u_char*)this->msg, ntohs(this->msg->length)); /* verify Response-Authenticator */ hasher->get_hash(hasher, msg, NULL); hasher->get_hash(hasher, secret, buf); if (!memeq(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); /* 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; } /** * Implementation of radius_message_t.get_code */ static radius_message_code_t get_code(private_radius_message_t *this) { return this->msg->code; } /** * Implementation of radius_message_t.get_identifier */ static u_int8_t get_identifier(private_radius_message_t *this) { return this->msg->identifier; } /** * Implementation of radius_message_t.set_identifier */ static void set_identifier(private_radius_message_t *this, u_int8_t identifier) { this->msg->identifier = identifier; } /** * Implementation of radius_message_t.get_authenticator */ static u_int8_t* get_authenticator(private_radius_message_t *this) { return this->msg->authenticator; } /** * Implementation of radius_message_t.get_encoding */ static chunk_t get_encoding(private_radius_message_t *this) { return chunk_create((u_char*)this->msg, ntohs(this->msg->length)); } /** * Implementation of radius_message_t.destroy. */ static void destroy(private_radius_message_t *this) { free(this->msg); free(this); } /** * Generic constructor */ static private_radius_message_t *radius_message_create() { private_radius_message_t *this = malloc_thing(private_radius_message_t); this->public.create_enumerator = (enumerator_t*(*)(radius_message_t*))create_enumerator; this->public.add = (void(*)(radius_message_t*, radius_attribute_type_t,chunk_t))add; this->public.get_code = (radius_message_code_t(*)(radius_message_t*))get_code; this->public.get_identifier = (u_int8_t(*)(radius_message_t*))get_identifier; this->public.set_identifier = (void(*)(radius_message_t*, u_int8_t identifier))set_identifier; this->public.get_authenticator = (u_int8_t*(*)(radius_message_t*))get_authenticator; this->public.get_encoding = (chunk_t(*)(radius_message_t*))get_encoding; this->public.sign = (void(*)(radius_message_t*, rng_t *rng, signer_t *signer))sign; this->public.verify = (bool(*)(radius_message_t*, u_int8_t *req_auth, chunk_t secret, hasher_t *hasher, signer_t *signer))verify; this->public.destroy = (void(*)(radius_message_t*))destroy; return this; } /** * See header */ radius_message_t *radius_message_create_request() { private_radius_message_t *this = radius_message_create(); this->msg = malloc_thing(rmsg_t); this->msg->code = RMC_ACCESS_REQUEST; this->msg->identifier = 0; this->msg->length = htons(sizeof(rmsg_t)); return &this->public; } /** * See header */ radius_message_t *radius_message_parse_response(chunk_t data) { private_radius_message_t *this = radius_message_create(); 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; }