/* ==================================================================== * The Vovida Software License, Version 1.0 * * Copyright (c) 2001 Vovida Networks, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The names "VOCAL", "Vovida Open Communication Application Library", * and "Vovida Open Communication Application Library (VOCAL)" must * not be used to endorse or promote products derived from this * software without prior written permission. For written * permission, please contact vocal@vovida.org. * * 4. Products derived from this software may not be called "VOCAL", nor * may "VOCAL" appear in their name, without prior written * permission of Vovida Networks, Inc. * * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL VOVIDA * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * * ==================================================================== * * This software consists of voluntary contributions made by Vovida * Networks, Inc. and many individuals on behalf of Vovida Networks, * Inc. For more information on Vovida Networks, Inc., please see * . * */ static const char* const RadiusMessage_cxx_Version = "$Id: RadiusMessage.cxx,v 1.11 2002/12/12 02:34:48 bko Exp $"; #ifdef __FreeBSD__ #include #endif #include #include #include "global.h" #include "cpLog.h" #include "vmd5.h" #include "support.hxx" #include "RadiusMessage.hxx" /** Create a RADIUS Message with type, no Attriubte yet Useful for composing a request */ RadiusMessage::RadiusMessage( const RadiusPacketType code ) { cpLog( LOG_DEBUG_STACK, "Create packet with code %d", code ); // These are the requests that we handle now assert( code == RP_ACCOUNTING_REQUEST || code == RP_ACCESS_REQUEST ); memset( &myData, 0, sizeof( struct RadiusHdr ) ); myData.msgHdr.code = code; myData.msgHdr.length = htons( sizeof( struct RadiusHdr ) ); memset( &myRequestAuthenticator, 0, RadiusAuthenticatorLength ); // Attributes are added later // Identifier is set and Authenticator is calculated when message is sent } /** Create a RADIUS Message with type, identifier and an Authenticator No Attriubte yet Use for composing a response */ RadiusMessage::RadiusMessage( const RadiusPacketType code, const u_int8_t requestId, const u_int8_t *requestAuth ) { cpLog( LOG_DEBUG_STACK, "Create packet with code %d", code ); assert( requestAuth != 0 ); // These are the responses that we handle now assert( code == RP_ACCOUNTING_RESPONSE || code == RP_ACCESS_REJECT || code == RP_ACCESS_CHALLENGE || code == RP_ACCESS_ACCEPT ); memset( &myData, 0, sizeof( struct RadiusHdr ) ); myData.msgHdr.code = code; myData.msgHdr.identifier = requestId; myData.msgHdr.length = htons( sizeof( struct RadiusHdr ) ); // save original request Authenticator for later use memcpy( &myRequestAuthenticator, requestAuth, RadiusAuthenticatorLength ); // Attributes are added later // Authenticator is calculated when message is sent } /** Create a RADIUS Message from data Use when a message is received */ RadiusMessage::RadiusMessage( const RadiusData rawMsg, const char* secret ) throw( VRadiusException& ) { cpLog( LOG_DEBUG_STACK, "Create packet with raw data" ); u_int16_t msgLen = rawMsg.length(); if( msgLen < RadiusMinPacketSize || msgLen > RadiusMaxPacketSize ) { throw VRadiusException( "Invalid packet size", __FILE__, __LINE__ ); } // Save raw packet memcpy( myData.buffer, rawMsg.data(), msgLen ); if( msgLen != ntohs(myData.msgHdr.length) ) { throw VRadiusException( "Packet size and length field don't match", __FILE__, __LINE__ ); } try { decodeAttributes( secret ); } catch( VRadiusException &e ) { throw e; } if( myData.msgHdr.code == RP_ACCOUNTING_REQUEST ) { // Verify Accounting Request Authenticator if( verifyAccountingRequestAuthenticator( secret ) == false ) { throw VRadiusException( "Accounting Request Authentication failed", __FILE__, __LINE__ ); } } // Access Request Authenticator is not verified // Response Authenticator should be verified with the original // request Authenticator later } /** Decode Attributes If an Attribute is received in an Access-Request but an exception is thrown, an Access-Reject SHOULD be transmitted. If an Attribute is received in an Access-Accept, Access-Reject or Access-Challenge packet and an exception is thrown, the packet must either be treated as an Access-Reject or else silently discarded. TODO: Enforce attribute quantity guide Section 5.44 of RFC 2865 */ void RadiusMessage::decodeAttributes( const char* secret ) throw( VRadiusException& ) { u_int16_t totalLen = ntohs( myData.msgHdr.length ); cpLog( LOG_DEBUG_STACK, "Received code=%d id=%d length=%d", myData.msgHdr.code, myData.msgHdr.identifier, totalLen); // Move to the beginning of Attributes u_int32_t parsed = sizeof( RadiusHdr ); const u_int8_t *msgPtr = myData.buffer + parsed; const u_int8_t *attrPtr; RadiusAttributeType attrType; u_int8_t attrLen; u_int32_t vendorId; // For VSA // Build Attribute list while( parsed < totalLen ) { attrPtr = msgPtr; attrType = *attrPtr++; attrLen = *attrPtr++; msgPtr += attrLen; parsed += attrLen; if( (attrLen < 2) || (parsed > totalLen) ) { throw VRadiusException( "Invalid Attribute length", __FILE__, __LINE__ ); } if( attrType == RA_VENDOR_SPECIFIC ) // VSA { // Vendor-Specific Attribute format // // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Type | Length | Vendor-Id // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // Vendor-Id (cont) | String... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- if( attrLen > 8 ) { // Extract Vendor-Id vendorId = ntohl( *(reinterpret_cast< const u_int32_t * >(attrPtr)) ); attrPtr += 4; // Move pointer to Vendor type // Exclude Type, Length and Vendor-Id RadiusData rdat( attrPtr, attrLen - 6 ); RadiusAttribute attr( attrType, rdat, vendorId ); myAttributes.push_back( attr ); // Multiple subattributes may be encoded within a single // Vendor-Specific attribute. They should further be decoded // by a vendor-specific application. } else { throw VRadiusException( "VSA has invalid length", __FILE__, __LINE__ ); } } else { // Attribute format // // 0 1 2 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- // | Type | Length | Value ... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- if( attrType == RA_USER_PASSWORD ) { if( myData.msgHdr.code != RP_ACCESS_REQUEST ) { throw VRadiusException( "User-Password Attribute in Access-Request", __FILE__, __LINE__ ); } if( ((attrLen-2)%16) != 0 ) { throw VRadiusException( "Invalid User-Password Attribute Length", __FILE__, __LINE__ ); } // Decode password RadiusData hiddenPassword( attrPtr, attrLen - 2 ); u_int8_t decodedPassword[ 256 ]; decodeUserPassword( decodedPassword, secret, hiddenPassword ); // Store decoded password in attribute list RadiusData password( decodedPassword, attrLen - 2 ); RadiusAttribute attr( attrType, password ); myAttributes.push_back( attr ); } else { // Exclude Type and Length RadiusData rdat( attrPtr, attrLen - 2 ); RadiusAttribute attr( attrType, rdat ); myAttributes.push_back( attr ); } } } } bool RadiusMessage::verifyAccountingRequestAuthenticator( const char* secret ) { cpLog( LOG_DEBUG_STACK, "Verify Accounting Request Authenticator" ); assert( myData.msgHdr.code == RP_ACCOUNTING_REQUEST ); // Copy of Authenticator u_int8_t auth[ RadiusAuthenticatorLength ]; memcpy( auth, myData.msgHdr.authenticator, RadiusAuthenticatorLength ); // Re-calculate calcAccountingRequestAuthenticator( secret ); // Compare return( 0 == memcmp( auth, myData.msgHdr.authenticator, RadiusAuthenticatorLength ) ); } bool RadiusMessage::verifyResponseAuthenticator( const u_int8_t *reqAuth, const char* secret ) { cpLog( LOG_DEBUG_STACK, "Verify Response Authenticator" ); assert( myData.msgHdr.code == RP_ACCESS_ACCEPT || myData.msgHdr.code == RP_ACCESS_REJECT || myData.msgHdr.code == RP_ACCOUNTING_RESPONSE ); // Copy Request Authenticator memcpy( myRequestAuthenticator, reqAuth, RadiusAuthenticatorLength ); // Copy Response Authenticator u_int8_t respAuth[ RadiusAuthenticatorLength ]; memcpy( respAuth, myData.msgHdr.authenticator, RadiusAuthenticatorLength ); // Re-calculate calcResponseAuthenticator( secret ); return( 0 == memcmp( respAuth, myData.msgHdr.authenticator, RadiusAuthenticatorLength ) ); } bool RadiusMessage::add( const RadiusAttribute& attr ) { cpLog( LOG_DEBUG_STACK, "Add Attribute of type %d", attr.type() ); u_int16_t attrLen = attr.length(); u_int16_t currLen = ntohs( myData.msgHdr.length ); if( (currLen + attrLen) > RadiusMaxPacketSize ) { cpLog( LOG_ERR, "Cannot add %d octets, current length is %d", attrLen, myData.msgHdr.length ); return false; } memcpy( myData.buffer + currLen, attr.encode().data() , attrLen ); myData.msgHdr.length = htons( currLen + attrLen ); myAttributes.push_back( attr ); return true; } /** Encode User-Password in Access-Request according to RFC 2865 Section 5.2 Call the shared secret S and the pseudo-random 128-bit Request Authenticator RA. Break the password into 16-octet chunks p1, p2, etc. with the last one padded at the end with nulls to a 16-octet boundary. Call the ciphertext blocks c(1), c(2), etc. We'll need intermediate values b1, b2, etc. b1 = MD5(S + RA) c(1) = p1 xor b1 b2 = MD5(S + c(1)) c(2) = p2 xor b2 . . . . . . bi = MD5(S + c(i-1)) c(i) = pi xor bi The String will contain c(1)+c(2)+...+c(i) where + denotes concatenation. */ void RadiusMessage::encodeUserPassword( u_int8_t* newPassword, const char* secret, const RadiusData& oldPassword ) { cpLog( LOG_DEBUG_STACK, "Encode User-Password" ); int secretLen = strlen(secret); assert( secretLen < 256 ); int hashLen = strlen(secret) + RadiusAuthenticatorLength; u_int8_t hashInput[ 256 + RadiusAuthenticatorLength ]; // Secret + Request Authenticator memcpy( hashInput, secret, secretLen ); memcpy( hashInput + secretLen, myData.msgHdr.authenticator, RadiusAuthenticatorLength ); assert( (oldPassword.length() % 16) == 0 ); const u_int8_t* p = oldPassword.data(); u_int8_t b[ 16 ]; unsigned int j; int i; for( i = 0; i < oldPassword.length(); i += sizeof(b) ) { if( i ) { memcpy( hashInput + secretLen, newPassword + (i - sizeof(b)), sizeof(b) ); } calcMD5( b, hashInput, hashLen ); for( j = 0; j < sizeof(b); ++j ) { newPassword[i+j] = *(p+i+j) ^ b[j]; } } } /** Decode User-Password in Access-Request Reversal of encodeUserPassword above */ void RadiusMessage::decodeUserPassword( u_int8_t* newPassword, const char* secret, const RadiusData& oldPassword ) { cpLog( LOG_DEBUG_STACK, "Decode User-Password" ); int secretLen = strlen(secret); assert( secretLen < 256 ); int hashLen = strlen(secret) + RadiusAuthenticatorLength; u_int8_t hashInput[ 256 + RadiusAuthenticatorLength ]; memcpy( hashInput, secret, secretLen ); assert( (oldPassword.length() % 16) == 0 ); const u_int8_t* p = oldPassword.data(); u_int8_t b[ 16 ]; unsigned int j; int i; for( i = oldPassword.length() - sizeof(b); i >= 0; i -= sizeof(b) ) { if( i ) { memcpy( hashInput + secretLen, p + (i - sizeof(b)), sizeof(b) ); } else { memcpy( hashInput + secretLen, myData.msgHdr.authenticator, RadiusAuthenticatorLength ); } calcMD5( b, hashInput, hashLen ); for( j = 0; j < sizeof(b); ++j ) { newPassword[i+j] = *(p+i+j) ^ b[j]; } } } /** Hide User-Password in Access-Request */ void RadiusMessage::hideUserPassword( const char* secret ) { cpLog( LOG_DEBUG_STACK, "Hide User-Password" ); assert( myData.msgHdr.code == RP_ACCESS_REQUEST ); RadiusData password; try { password = get( RA_USER_PASSWORD ).value(); } catch( VRadiusException& e ) { cpLog( LOG_WARNING, "%s", e.getDescription().c_str() ); return; } u_int8_t hiddenPassword[ 256 ]; encodeUserPassword( hiddenPassword, secret, password ); // Replace password in raw message with hidden password int offset = RadiusPacketHeaderSize; for ( RadiusAttrIter itr = myAttributes.begin(); itr != myAttributes.end(); itr++ ) { if( itr->type() == RA_USER_PASSWORD ) { memcpy( myData.buffer + offset + 2, hiddenPassword, password.length() ); } else { offset += itr->length(); } } } /** Calculate and set the Authenticator Must be called right before the packet is sent */ void RadiusMessage::calcAuthenticator( const char* secret ) { switch( myData.msgHdr.code ) { case RP_ACCESS_REQUEST: { calcAccessRequestAuthenticator(); hideUserPassword( secret ); break; } case RP_ACCESS_ACCEPT: case RP_ACCESS_REJECT: case RP_ACCOUNTING_RESPONSE: case RP_ACCESS_CHALLENGE: { calcResponseAuthenticator( secret ); break; } case RP_ACCOUNTING_REQUEST: { calcAccountingRequestAuthenticator( secret ); break; } default: { cpLog( LOG_ERR, "Cannot calculate Authenticator for code %d", myData.msgHdr.code ); assert( 0 ); } } } /** Calculate and set the myRequestAuthenticator for Access-Request packets */ void RadiusMessage::calcAccessRequestAuthenticator() { // Generate a global and temporal unique Authenticator // Request Authenticator = MD5( time + ip + id ) try { u_int8_t hashInput[ 256 ]; unsigned int t = time( NULL ); memcpy( hashInput, &t, sizeof(t) ); int hashLen = sizeof(t); memcpy( hashInput+hashLen, get( RA_NAS_IP_ADDRESS ).value().data(), get( RA_NAS_IP_ADDRESS ).value().length() ); hashLen += get( RA_NAS_IP_ADDRESS ).value().length(); memcpy( hashInput+hashLen, &myData.msgHdr.identifier, sizeof(myData.msgHdr.identifier) ); hashLen += sizeof(myData.msgHdr.identifier); // Calculate calcMD5( myRequestAuthenticator, hashInput, hashLen ); // Set Authenticator in Access Request raw message memcpy( myData.msgHdr.authenticator, myRequestAuthenticator, RadiusAuthenticatorLength ); } catch( VRadiusException &e ) { cpLog( LOG_ERR, "Calculate Access Request Authenticator: %s", e.getDescription().c_str() ); } } /** Calculate and set the Authenticator for Response (Access-Accept, Access-Reject, Access-Challenge or Accounting Response) packets */ void RadiusMessage::calcResponseAuthenticator( const char* secret ) { // See RFC 2865 Section 3 and RFC 2866 Section 3 // MD5( Code + ID + Length + myRequestAuthenticator + Attributes + Secret ) u_int16_t msgLen = ntohs( myData.msgHdr.length ); // TODO: make buffer bigger to accommodate secret assert( (msgLen + strlen(secret)) <= RadiusMaxPacketSize ); // Load original request's Authenticator memcpy( myData.msgHdr.authenticator, myRequestAuthenticator, RadiusAuthenticatorLength ); // Load secret behind message memcpy( myData.buffer + msgLen, secret, strlen(secret) ); u_int8_t auth[ RadiusAuthenticatorLength ]; // Calculate calcMD5( auth, myData.buffer, msgLen + strlen(secret) ); // Set Authenticator in response packet memcpy( myData.msgHdr.authenticator, auth, RadiusAuthenticatorLength ); } /** Calculate and set the Authenticator for Accounting request packets */ void RadiusMessage::calcAccountingRequestAuthenticator( const char* secret ) { // See RFC 2866 Section 3 // MD5( Code + ID + Length + 16 zero octets + Attributes + Secret ) cpLog( LOG_DEBUG_STACK, "Calculate Accounting Request Authenticator" ); u_int32_t msgLen = ntohs( myData.msgHdr.length ); assert( (msgLen + strlen(secret)) <= RadiusMaxPacketSize ); memset( myData.msgHdr.authenticator, 0, RadiusAuthenticatorLength ); // Load secret behind message memcpy( myData.buffer + msgLen, secret, strlen(secret) ); // Calculate calcMD5( myRequestAuthenticator, myData.buffer, msgLen + strlen(secret) ); // Set Authenticator in request packet memcpy( myData.msgHdr.authenticator, myRequestAuthenticator, RadiusAuthenticatorLength ); } void RadiusMessage::calcMD5( u_int8_t *digest, const u_int8_t *buf, const u_int32_t bufLen ) { cpLog( LOG_DEBUG_STACK, "Calculate MD5" ); MD5Context context; MD5Init( &context ); MD5Update( &context, buf, bufLen ); MD5Final( digest, &context ); } RadiusData RadiusMessage::encodeAttributes() const { RadiusData msg; for( RadiusAttrIter itr = myAttributes.begin(); itr != myAttributes.end(); itr++ ) { msg += (*itr).encode(); } return msg; } const RadiusAttribute& RadiusMessage::get( const RadiusAttributeType t ) const throw( VRadiusException& ) { for ( RadiusAttrIter itr = myAttributes.begin(); itr != myAttributes.end(); itr++ ) { if( itr->type() == t ) { // Found one return *itr; } } string e( "No attributes of type " + itos( t ) + " found in Attribute list" ); // Not found throw VRadiusException( e, __FILE__, __LINE__ ); } list< RadiusAttribute > RadiusMessage::getAll( const RadiusAttributeType t ) const { list< RadiusAttribute > attrList; for ( RadiusAttrIter itr = myAttributes.begin(); itr != myAttributes.end(); itr++ ) { if( itr->type() == t ) { attrList.push_back( *itr ); } } return attrList; } // Get a human readable representation of the message string RadiusMessage::verbose() const { return string( hexDump() + headerDump() + attributesVerbose() ); } // Raw message in hex for debugging string RadiusMessage::hexDump() const { string s; // Complete output (all lines) string a; // "Ascii" portion of a line u_int8_t temp; // One octet u_int8_t hi; // High hex digit of an octet u_int8_t low; // Low hex digit of an octet char buf[4]; // Output buffer for one octet char cntBuf[6]; // Output buffer for counter at the beginning of a line int msgLen = ntohs(myData.msgHdr.length); int i; for( i = 0; i < msgLen; i++ ) { if( 0 == (i % 16) ) // 16 in a line { snprintf( cntBuf, 6, "%04x ", i ); s += a + "\n" + cntBuf; a = " "; } temp = myData.buffer[i]; hi = (temp & 0xf0) / 16; low = (temp & 0xf); snprintf( buf, 4, " %x%x", hi, low ); if( temp < 0x20 || temp > 0x7E) { a += "."; } else { a += reinterpret_cast(temp); } s += buf; } // Last line only: // Pad with ' ' to make the "ascii" interpreted part line up with // the previous line int j = i % 16; string b; // Blanks if( j ) { b = string( (16-j)*3, ' ' ); } s += b + a + "\n"; return s; } // Get a human readable representation of the message header string RadiusMessage::headerDump() const { string codeStr; switch( myData.msgHdr.code ) { case RP_ACCESS_REQUEST: { codeStr = "Access-Request (1)"; break; } case RP_ACCESS_ACCEPT: { codeStr = "Access-Accept (2)"; break; } case RP_ACCESS_REJECT: { codeStr = "Access-Reject (3)"; break; } case RP_ACCOUNTING_REQUEST: { codeStr = "Accounting-Request (4)"; break; } case RP_ACCOUNTING_RESPONSE: { codeStr = "Accounting-Response (5)"; break; } case RP_ACCESS_CHALLENGE: { codeStr = "Access-Challenge (11)"; break; } default: { codeStr = "Unknown (" + itos( myData.msgHdr.code ) + ")"; } } return string( "\n 1 Code = " + codeStr + "\n 1 ID = " + itos( myData.msgHdr.identifier ) + "\n 2 Length = " + itos( ntohs(myData.msgHdr.length) ) + "\n 16 Authenticator\n" ); } string RadiusMessage::attributesVerbose() const { string s = "\nAttributes:\n"; for ( RadiusAttrIter itr = myAttributes.begin(); itr != myAttributes.end(); itr++ ) { s += itr->verbose(); } return s; } // Local Variables: // mode:c++ // c-file-style:"bsd" // c-basic-offset:4 // c-file-offsets:((inclass . ++)) // indent-tabs-mode:nil // End: