428 lines · 18.9 KB · C#
RawDownload
//******************************************************************************************************//  RadiusPacket.cs - Gbtc////  Copyright © 2012, Grid Protection Alliance.  All Rights Reserved.////  Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See//  the NOTICE file distributed with this work for additional information regarding copyright ownership.//  The GPA licenses this file to you under the MIT License (MIT), the "License"; you may//  not use this file except in compliance with the License. You may obtain a copy of the License at:////      http://www.opensource.org/licenses/MIT////  Unless agreed to in writing, the subject software distributed under the License is distributed on an//  "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the//  License for the specific language governing permissions and limitations.////  Code Modification History://  ----------------------------------------------------------------------------------------------------//  11/26/2010 - Pinal C. Patel//       Generated original version of source code.//  11/23/2011 - J. Ritchie Carroll//       Modified to support buffer optimized ISupportBinaryImage.//  12/13/2012 - Starlynn Danyelle Gilliam//       Modified Header.////****************************************************************************************************** using System;using System.Collections.Generic;using System.Linq;using System.Security.Cryptography;using System.Text;using GSF.Parsing;using Random = GSF.Security.Cryptography.Random; #pragma warning disable SCS0006 // Weak hashing function namespace GSF.Communication.Radius{    #region [ Enumerations ]     /// <summary>    /// Specifies the type of the <see cref="RadiusPacket"/>.    /// </summary>    public enum PacketType    {        /// <summary>        /// Packet sent to a RADIUS server for verification of credentials.        /// </summary>        AccessRequest = 1,        /// <summary>        /// Packet sent by a RADIUS server when credential verification is successful.        /// </summary>        AccessAccept = 2,        /// <summary>        /// Packet sent by a RADIUS server when credential verification is unsuccessful.        /// </summary>        AccessReject = 3,        /// <summary>        /// Not used. No description available. [RFC 2882]        /// </summary>        AccountingRequest = 4,        /// <summary>        /// Not used. No description available. [RFC 2882]        /// </summary>        AccountingResponse = 5,        /// <summary>        /// Not used. No description available. [RFC 2882]        /// </summary>        AccountingStatus = 6,        /// <summary>        /// Not used. No description available. [RFC 2882]        /// </summary>        PasswordRequest = 7,        /// <summary>        /// Not used. No description available. [RFC 2882]        /// </summary>        PasswordAccept = 8,        /// <summary>        /// Not used. No description available. [RFC 2882]        /// </summary>        PasswordReject = 9,        /// <summary>        /// Not used. No description available. [RFC 2882]        /// </summary>        AccountingMessage = 10,        /// <summary>        /// Packet sent by a RADIUS server when further information is needed for credential verification.        /// </summary>        AccessChallenge = 11,        /// <summary>        /// Not used. No description available. [RFC 2882]        /// </summary>        StatuServer = 12,        /// <summary>        /// Not used. No description available. [RFC 2882]        /// </summary>        StatusClient = 13    }     #endregion     /// <summary>    /// Represents a data packet transferred between RADIUS client and server.    /// </summary>    /// <seealso cref="RadiusPacketAttribute"/>    /// <seealso cref="RadiusClient"/>    public class RadiusPacket : ISupportBinaryImage    {        // 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        //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+        //|     Code      |  Identifier   |            Length             |        //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+        //|                                                               |        //|                         Authenticator                         |        //|                                                               |        //|                                                               |        //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+        //|  Attributes ...        //+-+-+-+-+-+-+-+-+-+-+-+-+-         #region [ Members ]         // Fields        private byte[] m_authenticator;         #endregion         #region [ Constructors ]         /// <summary>        /// Initializes a new instance of the <see cref="RadiusPacket"/> class.        /// </summary>        public RadiusPacket()        {            Identifier = (byte)Random.Between(0, 255);            m_authenticator = new byte[16];            Attributes = new List<RadiusPacketAttribute>();        }         /// <summary>        /// Initializes a new instance of the <see cref="RadiusPacket"/> class.        /// </summary>        /// <param name="type">Type of the <see cref="RadiusPacket"/>.</param>        public RadiusPacket(PacketType type) : this() => Type = type;         /// <summary>        /// Initializes a new instance of the <see cref="RadiusPacket"/> class.        /// </summary>        /// <param name="binaryImage">Binary image to be used for initializing <see cref="RadiusPacket"/>.</param>        /// <param name="startIndex">0-based starting index of initialization data in the <paramref name="binaryImage"/>.</param>        /// <param name="length">Valid number of bytes in <paramref name="binaryImage"/> from <paramref name="startIndex"/>.</param>        public RadiusPacket(byte[] binaryImage, int startIndex, int length) : this() => ParseBinaryImage(binaryImage, startIndex, length);         #endregion         #region [ Properties ]         /// <summary>        /// Gets or sets the type of the <see cref="RadiusPacket"/>.        /// </summary>        public PacketType Type { get; set; }         /// <summary>        /// Gets or sets the <see cref="RadiusPacket"/> identifier.        /// </summary>        public byte Identifier { get; set; }         /// <summary>        /// Gets or sets the <see cref="RadiusPacket"/> authenticator.        /// </summary>        /// <exception cref="ArgumentNullException">The value being assigned is null.</exception>        /// <exception cref="ArgumentException">The value being assigned is not 16-bytes in length.</exception>        public byte[] Authenticator        {            get => m_authenticator;            set            {                if (value == null)                    throw new ArgumentNullException(nameof(value));                 if (value.Length != 16)                    throw new ArgumentException("Value must 16-byte long.");                 m_authenticator = value;            }        }         /// <summary>        /// Gets a list of <see cref="RadiusPacketAttribute"/>s.        /// </summary>        public List<RadiusPacketAttribute> Attributes { get; }         /// <summary>        /// Gets the length of the <see cref="RadiusPacket"/>.        /// </summary>        // 20 bytes are fixed + length of all attributes combined        public int BinaryLength => 20 + Attributes.Where(attribute => attribute != null).Sum(attribute => attribute.BinaryLength);         #endregion         #region [ Methods ]         /// <summary>        /// Parses <see cref="RadiusPacket"/> object by parsing the specified <paramref name="buffer"/> containing a binary image.        /// </summary>        /// <param name="buffer">Buffer containing binary image to parse.</param>        /// <param name="startIndex">0-based starting index in the <paramref name="buffer"/> to start parsing.</param>        /// <param name="length">Valid number of bytes within <paramref name="buffer"/> from <paramref name="startIndex"/>.</param>        /// <returns>The number of bytes used for initialization in the <paramref name="buffer"/> (i.e., the number of bytes parsed), or 0 if not enough data.</returns>        /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is null.</exception>        public int ParseBinaryImage(byte[] buffer, int startIndex, int length)        {            if (buffer == null)                throw new ArgumentNullException(nameof(buffer));             int imageLength = BinaryLength;             // Binary image does not have sufficient data.            if (length < imageLength)                return 0;             // Binary image has sufficient data.            Type = (PacketType)buffer[startIndex];            Identifier = buffer[startIndex + 1];            ushort size = EndianOrder.ToUInt16(buffer, startIndex + 2);            Buffer.BlockCopy(buffer, startIndex + 4, m_authenticator, 0, m_authenticator.Length);             // Parse all attributes in the packet.            int cursor = 20;             while (cursor < size)            {                RadiusPacketAttribute attribute = new RadiusPacketAttribute(buffer, startIndex + cursor, length);                Attributes.Add(attribute);                cursor += attribute.BinaryLength;            }             return imageLength;        }         /// <summary>        /// Generates a binary representation of this <see cref="RadiusPacket"/> object and copies it into the given buffer.        /// </summary>        /// <param name="buffer">Buffer used to hold generated binary image of the source object.</param>        /// <param name="startIndex">0-based starting index in the <paramref name="buffer"/> to start writing.</param>        /// <returns>The number of bytes written to the <paramref name="buffer"/>.</returns>        /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is null.</exception>        /// <exception cref="ArgumentOutOfRangeException">        /// <paramref name="startIndex"/> or <see cref="ISupportBinaryImage.BinaryLength"/> is less than 0 -or-         /// <paramref name="startIndex"/> and <see cref="ISupportBinaryImage.BinaryLength"/> will exceed <paramref name="buffer"/> length.        /// </exception>        public int GenerateBinaryImage(byte[] buffer, int startIndex)        {            int length = BinaryLength;             buffer.ValidateParameters(startIndex, length);             // Populate the buffer            buffer[startIndex] = Convert.ToByte(Type);            buffer[startIndex + 1] = Identifier;            Buffer.BlockCopy(EndianOrder.GetBytes((ushort)BinaryLength), 0, buffer, startIndex + 2, 2);            Buffer.BlockCopy(m_authenticator, 0, buffer, startIndex + 4, m_authenticator.Length);            startIndex += 20;             foreach (RadiusPacketAttribute attribute in Attributes)            {                if (attribute != null)                    startIndex += attribute.GenerateBinaryImage(buffer, startIndex);            }             return length;        }         /// <summary>        /// Gets the value of the specified <paramref name="attributeType"/> if it is present in the <see cref="RadiusPacket"/>.        /// </summary>        /// <param name="attributeType"><see cref="RadiusPacketAttribute.Type"/> of the <see cref="RadiusPacketAttribute"/> whose value is to be retrieved.</param>        /// <returns><see cref="RadiusPacketAttribute"/>.<see cref="RadiusPacketAttribute.Value"/> if <see cref="RadiusPacketAttribute"/> is present; otherwise null.</returns>        public byte[] GetAttributeValue(AttributeType attributeType)        {            RadiusPacketAttribute match = Attributes.Find(attribute => attribute.Type == attributeType);            return match?.Value;        }         #endregion         #region [ Static ]         // Static Fields         /// <summary>        /// <see cref="Encoding "/> format for encoding text.        /// </summary>        public static Encoding Encoding = Encoding.UTF8;         /// <summary>        /// <see cref="EndianOrder"/> to use for byte conversion.        /// </summary>        public static EndianOrder EndianOrder = EndianOrder.BigEndian;         // Static Methods         /// <summary>        /// Generates an "Authenticator" value used in a RADIUS request packet sent by the client to server.        /// </summary>        /// <param name="sharedSecret">The shared secret to be used in generating the output.</param>        /// <returns>A byte array.</returns>        public static byte[] CreateRequestAuthenticator(string sharedSecret)        {            // We create a input buffer that'll be used to create a 16-byte value using the RSA MD5 algorithm.            // Since the output value (The Authenticator) has to be unique over the life of the shared secret,            // we prepend a randomly generated "salt" text to ensure the uniqueness of the output value.            byte[] randomBuffer = new byte[16];            byte[] secretBuffer = Encoding.GetBytes(sharedSecret);            Random.GetBytes(randomBuffer);             using (MD5CryptoServiceProvider md5Provider = new MD5CryptoServiceProvider())                return md5Provider.ComputeHash(randomBuffer.Combine(secretBuffer));        }         /// <summary>        /// Generates an "Authenticator" value used in a RADIUS response packet sent by the server to client.        /// </summary>        /// <param name="sharedSecret">The shared secret key.</param>        /// <param name="requestPacket">RADIUS packet sent from client to server.</param>        /// <param name="responsePacket">RADIUS packet sent from server to client.</param>        /// <returns>A byte array.</returns>        public static byte[] CreateResponseAuthenticator(string sharedSecret, RadiusPacket requestPacket, RadiusPacket responsePacket)        {            // Response authenticator is generated as follows:            // MD5(Code + Identifier + Length + Request Authenticator + Attributes + Shared Secret)            //   where:            //   Code, Identifier, Length & Attributes are from the response RADIUS packet            //   Request Authenticator is from the request RADIUS packet            //   Shared Secret is the shared secret key            int length = responsePacket.BinaryLength;            byte[] sharedSecretBytes = Encoding.GetBytes(sharedSecret);            byte[] buffer = new byte[length + sharedSecretBytes.Length];             responsePacket.GenerateBinaryImage(buffer, 0);            Buffer.BlockCopy(requestPacket.BinaryImage(), 4, buffer, 4, 16);            Buffer.BlockCopy(sharedSecretBytes, 0, buffer, length, sharedSecretBytes.Length);             using (MD5CryptoServiceProvider md5Provider = new MD5CryptoServiceProvider())                return md5Provider.ComputeHash(buffer);        }         /// <summary>        /// Generates an encrypted password using the RADIUS protocol specification (RFC 2285).        /// </summary>        /// <param name="password">User's password.</param>        /// <param name="sharedSecret">Shared secret key.</param>        /// <param name="requestAuthenticator">Request authenticator byte array.</param>        /// <returns>A byte array.</returns>        public static byte[] EncryptPassword(string password, string sharedSecret, byte[] requestAuthenticator)        {            // Avoiding Null Dereferences            if (string.IsNullOrEmpty(sharedSecret))                throw new ArgumentException("Shared secret cannot be null or empty.");             if (string.IsNullOrEmpty(password))                throw new ArgumentException("Password cannot be null or empty.");             if (requestAuthenticator == null)                throw new ArgumentException("Request authenticator cannot be null.");             // Max length of the password can be 130 according to RFC 2865. Since 128 is the closest multiple            // of 16 (password segment length), we allow the password to be no longer than 128 characters.            if (password.Length > 128)                throw new ArgumentException("Password can be a maximum of 128 characters in length.");             byte[] result;            byte[] xorBytes = null;            byte[] passwordBytes = Encoding.GetBytes(password);            byte[] sharedSecretBytes = Encoding.GetBytes(sharedSecret);            byte[] md5HashInputBytes = new byte[sharedSecretBytes.Length + 16];             using (MD5CryptoServiceProvider md5Provider = new MD5CryptoServiceProvider())            {                if (passwordBytes.Length % 16 == 0)                {                    // Length of password is a multiple of 16.                    result = new byte[passwordBytes.Length];                }                else                {                    // Length of password is not a multiple of 16, so we'll take the multiple of 16 that's next                    // closest to the password's length and leave the empty space at the end as padding.                    result = new byte[passwordBytes.Length / 16 * 16 + 16];                }                 // Copy the password to the result buffer where it'll be XORed.                Buffer.BlockCopy(passwordBytes, 0, result, 0, passwordBytes.Length);                 // For the first 16-byte segment of the password, password characters are to be XORed with the                // MD5 hash value that's computed as follows:                //   MD5(Shared secret key + Request authenticator)                Buffer.BlockCopy(sharedSecretBytes, 0, md5HashInputBytes, 0, sharedSecretBytes.Length);                Buffer.BlockCopy(requestAuthenticator, 0, md5HashInputBytes, sharedSecretBytes.Length, requestAuthenticator.Length);                 for (int i = 0; i <= result.Length - 1; i += 16)                {                    // Perform XOR-based encryption of the password in 16-byte segments.                    if (i > 0 && xorBytes != null)                    {                        // For passwords that are more than 16 characters in length, each consecutive 16-byte                        // segment of the password is XORed with MD5 hash value that's computed as follows:                        //   MD5(Shared secret key + XOR bytes used in the previous segment)                        // ReSharper disable once PossibleNullReferenceException                        Buffer.BlockCopy(xorBytes, 0, md5HashInputBytes, sharedSecretBytes.Length, xorBytes.Length);                    }                     xorBytes = md5Provider.ComputeHash(md5HashInputBytes);                     // XOR the password bytes in the current segment with the XOR bytes.                    for (int j = i; j <= i + 16 - 1; j++)                        result[j] = (byte)(result[j] ^ xorBytes[j]);                }            }             return result;        }         #endregion    }}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t