//******************************************************************************************************// RadiusClient.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.// 12/13/2012 - Starlynn Danyelle Gilliam// Modified Header.// 04/24/2014 - J. Ritchie Carroll// Code clean-up.////****************************************************************************************************** using System;using System.Collections.Generic;using System.Threading;using GSF.Parsing; namespace GSF.Communication.Radius{ /// <summary> /// Represents a RADIUS communication client. /// </summary> public class RadiusClient : IDisposable { #region [ Members ] // Constants /// <summary> /// Default port of the RADIUS server. /// </summary> public const int DefaultServerPort = 1812; /// <summary> /// Default text for comparing with the text of ReplyMessage attribute in an AccessChallenge /// server response to determine whether or not Step 1 (ensuring that the user account is in /// the "New Pin" mode) of creating a new pin was successful. /// </summary> public const string DefaultNewPinModeMessage1 = "Enter a new PIN"; /// <summary> /// Default text for comparing with the text of ReplyMessage attribute in an AccessChallenge /// server response to determine whether or not Step 2 (new pin is accepted in attempt #1) /// of creating a new pin was successful. /// </summary> public const string DefaultNewPinModeMessage2 = "Please re-enter new PIN"; /// <summary> /// Default text for comparing with the text of ReplyMessage attribute in an AccessChallenge /// server response to determine whether or not Step 3 (new pin is accepted in attempts #2) /// of creating a new pin was successful. /// </summary> public const string DefaultNewPinModeMessage3 = "PIN Accepted"; /// <summary> /// Default text for comparing with the text of ReplyMessage attribute in an AccessChallenge /// server response to determine whether or not a user account is in the "Next Token" mode. /// </summary> public const string DefaultNextTokenModeMessage = "Wait for token to change"; // Fields private short m_requestAttempts; private int m_reponseTimeout; private string m_sharedSecret; private string m_newPinModeMessage1; private string m_newPinModeMessage2; private string m_newPinModeMessage3; private string m_nextTokenModeMessage; private bool m_disposed; private byte[] m_responseBytes; private readonly UdpClient m_udpClient; #endregion #region [ Constructors ] /// <summary> /// Creates an instance of RADIUS client for sending request to a RADIUS server. /// </summary> /// <param name="serverName">Name or address of the RADIUS server.</param> /// <param name="sharedSecret">Shared secret used for encryption and authentication.</param> public RadiusClient(string serverName, string sharedSecret) : this(serverName, DefaultServerPort, sharedSecret) { } /// <summary> /// Creates an instance of RADIUS client for sending request to a RADIUS server. /// </summary> /// <param name="serverName">Name or address of the RADIUS server.</param> /// <param name="serverPort">Port number of the RADIUS server.</param> /// <param name="sharedSecret">Shared secret used for encryption and authentication.</param> /// <remarks></remarks> public RadiusClient(string serverName, int serverPort, string sharedSecret) { SharedSecret = sharedSecret; RequestAttempts = 1; ReponseTimeout = 15000; NewPinModeMessage1 = DefaultNewPinModeMessage1; NewPinModeMessage2 = DefaultNewPinModeMessage2; NewPinModeMessage3 = DefaultNewPinModeMessage3; NextTokenModeMessage = DefaultNextTokenModeMessage; m_udpClient = new UdpClient($"Server={serverName}; RemotePort={serverPort}; LocalPort=0"); m_udpClient.ReceiveDataComplete += m_udpClient_ReceivedData; m_udpClient.Connect(); // Start the connection cycle. } #endregion #region [ Properties ] /// <summary> /// Gets or sets the name or address of the RADIUS server. /// </summary> /// <value></value> /// <returns>Name or address of RADIUS server.</returns> public string ServerName { get => m_udpClient.ConnectionString.ParseKeyValuePairs()["server"]; set { CheckDisposed(); if (!string.IsNullOrEmpty(value)) { Dictionary<string, string> parts = m_udpClient.ConnectionString.ParseKeyValuePairs(); parts["server"] = value; m_udpClient.ConnectionString = parts.JoinKeyValuePairs(); } else { throw new ArgumentNullException(nameof(value)); } } } /// <summary> /// Gets or sets the port number of the RADIUS server. /// </summary> /// <value></value> /// <returns>Port number of the RADIUS server.</returns> public int ServerPort { get => Convert.ToInt32(m_udpClient.ConnectionString.ParseKeyValuePairs()["remotePort"]); set { CheckDisposed(); if (value >= 0 && value <= 65535) { Dictionary<string, string> parts = m_udpClient.ConnectionString.ParseKeyValuePairs(); parts["remotePort"] = value.ToString(); m_udpClient.ConnectionString = parts.JoinKeyValuePairs(); } else { throw new ArgumentOutOfRangeException(nameof(value), "Value must be between 0 and 65535."); } } } /// <summary> /// Gets or sets the number of time a request is to sent to the server until a valid response is received. /// </summary> /// <value></value> /// <returns>Number of time a request is to sent to the server until a valid response is received.</returns> public short RequestAttempts { get => m_requestAttempts; set { CheckDisposed(); if (value >= 1 && value <= 10) m_requestAttempts = value; else throw new ArgumentOutOfRangeException(nameof(value), "Value must be between 1 and 10."); } } /// <summary> /// Gets or sets the time (in milliseconds) to wait for a response from server after sending a request. /// </summary> /// <value></value> /// <returns>Time (in milliseconds) to wait for a response from server after sending a request.</returns> public int ReponseTimeout { get => m_reponseTimeout; set { CheckDisposed(); if (value >= 1000 && value <= 60000) m_reponseTimeout = value; else throw new ArgumentOutOfRangeException(nameof(value), "Value must be between 1000 and 60000."); } } /// <summary> /// Gets or sets the shared secret used between the client and server for encryption and authentication. /// </summary> /// <value></value> /// <returns>Shared secret used between the client and server for encryption and authentication.</returns> public string SharedSecret { get => m_sharedSecret; set { CheckDisposed(); if (!string.IsNullOrEmpty(value)) m_sharedSecret = value; else throw new ArgumentNullException(nameof(value)); } } /// <summary> /// Gets or sets the text for comparing with the text of ReplyMessage attribute in an AccessChallenge /// server response to determine whether or not Step 1 (ensuring that the user account is in the /// "New Pin" mode) of creating a new pin was successful. /// </summary> /// <value></value> /// <returns>Text for "New Pin" mode's first message.</returns> public string NewPinModeMessage1 { get => m_newPinModeMessage1; set { CheckDisposed(); if (!string.IsNullOrEmpty(value)) m_newPinModeMessage1 = value; else throw new ArgumentNullException(nameof(value)); } } /// <summary> /// Gets or sets the text for comparing with the text of ReplyMessage attribute in an AccessChallenge /// server response to determine whether or not Step 2 (new pin is accepted in attempt #1) of creating /// a new pin was successful. /// </summary> /// <value></value> /// <returns>Text for "New Pin" mode's second message.</returns> public string NewPinModeMessage2 { get => m_newPinModeMessage2; set { CheckDisposed(); if (!string.IsNullOrEmpty(value)) m_newPinModeMessage2 = value; else throw new ArgumentNullException(nameof(value)); } } /// <summary> /// Gets or sets the text for comparing with the text of ReplyMessage attribute in an AccessChallenge /// server response to determine whether or not Step 3 (new pin is accepted in attempts #2) of creating /// a new pin was successful. /// </summary> /// <value></value> /// <returns>Text for "New Pin" mode's third message.</returns> public string NewPinModeMessage3 { get => m_newPinModeMessage3; set { CheckDisposed(); if (!string.IsNullOrEmpty(value)) m_newPinModeMessage3 = value; else throw new ArgumentNullException(nameof(value)); } } /// <summary> /// Gets or sets the text for comparing with the text of ReplyMessage attribute in an AccessChallenge /// server response to determine whether or not a user account is in the "Next Token" mode. /// </summary> /// <value></value> /// <returns>Text for "Next Token" mode.</returns> public string NextTokenModeMessage { get => m_nextTokenModeMessage; set { CheckDisposed(); if (!string.IsNullOrEmpty(value)) m_nextTokenModeMessage = value; else throw new ArgumentNullException(nameof(value)); } } #endregion #region [ Methods ] /// <summary> /// Send a request to the server and waits for a response back. /// </summary> /// <param name="request">Request to be sent to the server.</param> /// <returns>Response packet if a valid response is received from the server; otherwise Nothing.</returns> public RadiusPacket ProcessRequest(RadiusPacket request) { CheckDisposed(); // We wait indefinitely for the connection to establish. But since this is UDP, the connection // will always be successful (locally we're binding to any available UDP port). if (m_udpClient.CurrentState != ClientState.Connected) return null; RadiusPacket response = null; // We have a UDP socket we can use for exchanging packets. for (int i = 1; i <= m_requestAttempts; i++) { m_responseBytes = null; m_udpClient.Send(request.BinaryImage()); DateTime stopTime = DateTime.UtcNow.AddMilliseconds(m_reponseTimeout); while (true) { Thread.Sleep(1); // Stay in the loop until: // 1) We receive a response OR // 2) We exceed the response timeout duration if (m_responseBytes != null || DateTime.UtcNow > stopTime) break; } if (m_responseBytes != null) { // The server sent a response. response = new RadiusPacket(m_responseBytes, 0, m_responseBytes.Length); if (response.Identifier == request.Identifier && response.Authenticator.CompareTo(RadiusPacket.CreateResponseAuthenticator(m_sharedSecret, request, response)) == 0) break; // The response failed the verification, so we'll silently discard it. response = null; } } return response; } /// <summary> /// Create a new pin for the user. /// </summary> /// <param name="username">Name of the user.</param> /// <param name="token">Current token of the user.</param> /// <param name="pin">New pin of the user.</param> /// <returns>True if a new pin is created for the user successfully; otherwise False.</returns> /// <remarks>NOTE: This method is specific to RSA RADIUS implementation.</remarks> public bool CreateNewPin(string username, string token, string pin) { CheckDisposed(); if (string.IsNullOrEmpty(pin)) throw new ArgumentNullException(nameof(pin)); // Step 1: Send username and token for password, receive a challenge response with reply // message worded "Enter a new PIN". [Verification] // Step 2: Send username and new ping for password, receive a challenge response with reply // message worded "Please re-enter. [Attempt #1] // Step 3: Send username and new ping for password, receive a challenge response with reply // message worded "PIN Accepted". [Attempt #2] RadiusPacket response = Authenticate(username, token); if (!IsUserInNewPinMode(response)) return false; // User account is really in "New Pin" mode. response = Authenticate(username, pin, response.GetAttributeValue(AttributeType.State)); byte[] reply = response.GetAttributeValue(AttributeType.ReplyMessage); if (!RadiusPacket.Encoding.GetString(reply, 0, reply.Length).ToLower().Contains(m_newPinModeMessage2.ToLower())) return false; // New pin not accepted in attempt #1. response = Authenticate(username, pin, response.GetAttributeValue(AttributeType.State)); reply = response.GetAttributeValue(AttributeType.ReplyMessage); return RadiusPacket.Encoding.GetString(reply, 0, reply.Length).ToLower().Contains(m_newPinModeMessage3.ToLower()); } /// <summary> /// Authenticates the username and password against the RADIUS server. /// </summary> /// <param name="username">Username to be authenticated.</param> /// <param name="password">Password to be authenticated.</param> /// <returns>Response packet received from the server for the authentication request.</returns> /// <remarks> /// <para> /// The type of response packet (if any) will be one of the following: /// <list> /// <item>AccessAccept: If the authentication is successful.</item> /// <item>AccessReject: If the authentication is not successful.</item> /// <item>AccessChallenge: If the server need more information from the user.</item> /// </list> /// </para> /// <para> /// When an AccessChallenge response packet is received from the server, it contains a State attribute /// that must be included in the AccessRequest packet that is being sent in response to the AccessChallenge /// response. So if this method returns an AccessChallenge packet, then this method is to be called again /// with the requested information (from ReplyMessage attribute) in the password field and the value State /// attribute. /// </para> /// </remarks> public RadiusPacket Authenticate(string username, string password) => Authenticate(username, password, null); /// <summary> /// Authenticates the username and password against the RADIUS server. /// </summary> /// <param name="username">Username to be authenticated.</param> /// <param name="password">Password to be authenticated.</param> /// <param name="state">State value from a previous challenge response.</param> /// <returns>Response packet received from the server for the authentication request.</returns> /// <remarks> /// <para> /// The type of response packet (if any) will be one of the following: /// <list> /// <item>AccessAccept: If the authentication is successful.</item> /// <item>AccessReject: If the authentication is not successful.</item> /// <item>AccessChallenge: If the server need more information from the user.</item> /// </list> /// </para> /// <para> /// When an AccessChallenge response packet is received from the server, it contains a State attribute /// that must be included in the AccessRequest packet that is being sent in response to the AccessChallenge /// response. So if this method returns an AccessChallenge packet, then this method is to be called again /// with the requested information (from ReplyMessage attribute) in the password field and the value State /// attribute. /// </para> /// </remarks> public RadiusPacket Authenticate(string username, string password, byte[] state) { CheckDisposed(); if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) throw new ArgumentException("Username and Password cannot be null."); RadiusPacket request = new RadiusPacket(PacketType.AccessRequest); byte[] authenticator = RadiusPacket.CreateRequestAuthenticator(m_sharedSecret); request.Authenticator = authenticator; request.Attributes.Add(new RadiusPacketAttribute(AttributeType.UserName, username)); request.Attributes.Add(new RadiusPacketAttribute(AttributeType.UserPassword, RadiusPacket.EncryptPassword(password, m_sharedSecret, authenticator))); // State attribute is used when responding to a AccessChallenge response. if (state != null) request.Attributes.Add(new RadiusPacketAttribute(AttributeType.State, state)); return ProcessRequest(request); } /// <summary> /// Determines whether or not the response indicates that the user account is in "New Pin" mode. /// </summary> /// <param name="response">Response packet sent by the server.</param> /// <returns>True if the user account is in "New Pin" mode; otherwise False.</returns> /// <remarks> /// <para>A user's account can be in the "New Pin" mode when set on the server.</para> /// <para>NOTE: This method is specific to RSA RADIUS implementation.</para> /// </remarks> public bool IsUserInNewPinMode(RadiusPacket response) { CheckDisposed(); if (response == null) throw new ArgumentNullException(nameof(response)); byte[] messageBytes = response.GetAttributeValue(AttributeType.ReplyMessage); if (messageBytes == null) throw new ArgumentException("ReplyMessage attribute is not present", nameof(response));Lines 1–500 of 583. Load more
//****************************************************************************************************** // RadiusClient.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. // 12/13/2012 - Starlynn Danyelle Gilliam // Modified Header. // 04/24/2014 - J. Ritchie Carroll // Code clean-up. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.Threading; using GSF.Parsing; namespace GSF.Communication.Radius { /// <summary> /// Represents a RADIUS communication client. /// </summary> public class RadiusClient : IDisposable { #region [ Members ] // Constants /// <summary> /// Default port of the RADIUS server. /// </summary> public const int DefaultServerPort = 1812; /// <summary> /// Default text for comparing with the text of ReplyMessage attribute in an AccessChallenge /// server response to determine whether or not Step 1 (ensuring that the user account is in /// the "New Pin" mode) of creating a new pin was successful. /// </summary> public const string DefaultNewPinModeMessage1 = "Enter a new PIN"; /// <summary> /// Default text for comparing with the text of ReplyMessage attribute in an AccessChallenge /// server response to determine whether or not Step 2 (new pin is accepted in attempt #1) /// of creating a new pin was successful. /// </summary> public const string DefaultNewPinModeMessage2 = "Please re-enter new PIN"; /// <summary> /// Default text for comparing with the text of ReplyMessage attribute in an AccessChallenge /// server response to determine whether or not Step 3 (new pin is accepted in attempts #2) /// of creating a new pin was successful. /// </summary> public const string DefaultNewPinModeMessage3 = "PIN Accepted"; /// <summary> /// Default text for comparing with the text of ReplyMessage attribute in an AccessChallenge /// server response to determine whether or not a user account is in the "Next Token" mode. /// </summary> public const string DefaultNextTokenModeMessage = "Wait for token to change"; // Fields private short m_requestAttempts; private int m_reponseTimeout; private string m_sharedSecret; private string m_newPinModeMessage1; private string m_newPinModeMessage2; private string m_newPinModeMessage3; private string m_nextTokenModeMessage; private bool m_disposed; private byte[] m_responseBytes; private readonly UdpClient m_udpClient; #endregion #region [ Constructors ] /// <summary> /// Creates an instance of RADIUS client for sending request to a RADIUS server. /// </summary> /// <param name="serverName">Name or address of the RADIUS server.</param> /// <param name="sharedSecret">Shared secret used for encryption and authentication.</param> public RadiusClient(string serverName, string sharedSecret) : this(serverName, DefaultServerPort, sharedSecret) { } /// <summary> /// Creates an instance of RADIUS client for sending request to a RADIUS server. /// </summary> /// <param name="serverName">Name or address of the RADIUS server.</param> /// <param name="serverPort">Port number of the RADIUS server.</param> /// <param name="sharedSecret">Shared secret used for encryption and authentication.</param> /// <remarks></remarks> public RadiusClient(string serverName, int serverPort, string sharedSecret) { SharedSecret = sharedSecret; RequestAttempts = 1; ReponseTimeout = 15000; NewPinModeMessage1 = DefaultNewPinModeMessage1; NewPinModeMessage2 = DefaultNewPinModeMessage2; NewPinModeMessage3 = DefaultNewPinModeMessage3; NextTokenModeMessage = DefaultNextTokenModeMessage; m_udpClient = new UdpClient($"Server={serverName}; RemotePort={serverPort}; LocalPort=0"); m_udpClient.ReceiveDataComplete += m_udpClient_ReceivedData; m_udpClient.Connect(); // Start the connection cycle. } #endregion #region [ Properties ] /// <summary> /// Gets or sets the name or address of the RADIUS server. /// </summary> /// <value></value> /// <returns>Name or address of RADIUS server.</returns> public string ServerName { get => m_udpClient.ConnectionString.ParseKeyValuePairs()["server"]; set { CheckDisposed(); if (!string.IsNullOrEmpty(value)) { Dictionary<string, string> parts = m_udpClient.ConnectionString.ParseKeyValuePairs(); parts["server"] = value; m_udpClient.ConnectionString = parts.JoinKeyValuePairs(); } else { throw new ArgumentNullException(nameof(value)); } } } /// <summary> /// Gets or sets the port number of the RADIUS server. /// </summary> /// <value></value> /// <returns>Port number of the RADIUS server.</returns> public int ServerPort { get => Convert.ToInt32(m_udpClient.ConnectionString.ParseKeyValuePairs()["remotePort"]); set { CheckDisposed(); if (value >= 0 && value <= 65535) { Dictionary<string, string> parts = m_udpClient.ConnectionString.ParseKeyValuePairs(); parts["remotePort"] = value.ToString(); m_udpClient.ConnectionString = parts.JoinKeyValuePairs(); } else { throw new ArgumentOutOfRangeException(nameof(value), "Value must be between 0 and 65535."); } } } /// <summary> /// Gets or sets the number of time a request is to sent to the server until a valid response is received. /// </summary> /// <value></value> /// <returns>Number of time a request is to sent to the server until a valid response is received.</returns> public short RequestAttempts { get => m_requestAttempts; set { CheckDisposed(); if (value >= 1 && value <= 10) m_requestAttempts = value; else throw new ArgumentOutOfRangeException(nameof(value), "Value must be between 1 and 10."); } } /// <summary> /// Gets or sets the time (in milliseconds) to wait for a response from server after sending a request. /// </summary> /// <value></value> /// <returns>Time (in milliseconds) to wait for a response from server after sending a request.</returns> public int ReponseTimeout { get => m_reponseTimeout; set { CheckDisposed(); if (value >= 1000 && value <= 60000) m_reponseTimeout = value; else throw new ArgumentOutOfRangeException(nameof(value), "Value must be between 1000 and 60000."); } } /// <summary> /// Gets or sets the shared secret used between the client and server for encryption and authentication. /// </summary> /// <value></value> /// <returns>Shared secret used between the client and server for encryption and authentication.</returns> public string SharedSecret { get => m_sharedSecret; set { CheckDisposed(); if (!string.IsNullOrEmpty(value)) m_sharedSecret = value; else throw new ArgumentNullException(nameof(value)); } } /// <summary> /// Gets or sets the text for comparing with the text of ReplyMessage attribute in an AccessChallenge /// server response to determine whether or not Step 1 (ensuring that the user account is in the /// "New Pin" mode) of creating a new pin was successful. /// </summary> /// <value></value> /// <returns>Text for "New Pin" mode's first message.</returns> public string NewPinModeMessage1 { get => m_newPinModeMessage1; set { CheckDisposed(); if (!string.IsNullOrEmpty(value)) m_newPinModeMessage1 = value; else throw new ArgumentNullException(nameof(value)); } } /// <summary> /// Gets or sets the text for comparing with the text of ReplyMessage attribute in an AccessChallenge /// server response to determine whether or not Step 2 (new pin is accepted in attempt #1) of creating /// a new pin was successful. /// </summary> /// <value></value> /// <returns>Text for "New Pin" mode's second message.</returns> public string NewPinModeMessage2 { get => m_newPinModeMessage2; set { CheckDisposed(); if (!string.IsNullOrEmpty(value)) m_newPinModeMessage2 = value; else throw new ArgumentNullException(nameof(value)); } } /// <summary> /// Gets or sets the text for comparing with the text of ReplyMessage attribute in an AccessChallenge /// server response to determine whether or not Step 3 (new pin is accepted in attempts #2) of creating /// a new pin was successful. /// </summary> /// <value></value> /// <returns>Text for "New Pin" mode's third message.</returns> public string NewPinModeMessage3 { get => m_newPinModeMessage3; set { CheckDisposed(); if (!string.IsNullOrEmpty(value)) m_newPinModeMessage3 = value; else throw new ArgumentNullException(nameof(value)); } } /// <summary> /// Gets or sets the text for comparing with the text of ReplyMessage attribute in an AccessChallenge /// server response to determine whether or not a user account is in the "Next Token" mode. /// </summary> /// <value></value> /// <returns>Text for "Next Token" mode.</returns> public string NextTokenModeMessage { get => m_nextTokenModeMessage; set { CheckDisposed(); if (!string.IsNullOrEmpty(value)) m_nextTokenModeMessage = value; else throw new ArgumentNullException(nameof(value)); } } #endregion #region [ Methods ] /// <summary> /// Send a request to the server and waits for a response back. /// </summary> /// <param name="request">Request to be sent to the server.</param> /// <returns>Response packet if a valid response is received from the server; otherwise Nothing.</returns> public RadiusPacket ProcessRequest(RadiusPacket request) { CheckDisposed(); // We wait indefinitely for the connection to establish. But since this is UDP, the connection // will always be successful (locally we're binding to any available UDP port). if (m_udpClient.CurrentState != ClientState.Connected) return null; RadiusPacket response = null; // We have a UDP socket we can use for exchanging packets. for (int i = 1; i <= m_requestAttempts; i++) { m_responseBytes = null; m_udpClient.Send(request.BinaryImage()); DateTime stopTime = DateTime.UtcNow.AddMilliseconds(m_reponseTimeout); while (true) { Thread.Sleep(1); // Stay in the loop until: // 1) We receive a response OR // 2) We exceed the response timeout duration if (m_responseBytes != null || DateTime.UtcNow > stopTime) break; } if (m_responseBytes != null) { // The server sent a response. response = new RadiusPacket(m_responseBytes, 0, m_responseBytes.Length); if (response.Identifier == request.Identifier && response.Authenticator.CompareTo(RadiusPacket.CreateResponseAuthenticator(m_sharedSecret, request, response)) == 0) break; // The response failed the verification, so we'll silently discard it. response = null; } } return response; } /// <summary> /// Create a new pin for the user. /// </summary> /// <param name="username">Name of the user.</param> /// <param name="token">Current token of the user.</param> /// <param name="pin">New pin of the user.</param> /// <returns>True if a new pin is created for the user successfully; otherwise False.</returns> /// <remarks>NOTE: This method is specific to RSA RADIUS implementation.</remarks> public bool CreateNewPin(string username, string token, string pin) { CheckDisposed(); if (string.IsNullOrEmpty(pin)) throw new ArgumentNullException(nameof(pin)); // Step 1: Send username and token for password, receive a challenge response with reply // message worded "Enter a new PIN". [Verification] // Step 2: Send username and new ping for password, receive a challenge response with reply // message worded "Please re-enter. [Attempt #1] // Step 3: Send username and new ping for password, receive a challenge response with reply // message worded "PIN Accepted". [Attempt #2] RadiusPacket response = Authenticate(username, token); if (!IsUserInNewPinMode(response)) return false; // User account is really in "New Pin" mode. response = Authenticate(username, pin, response.GetAttributeValue(AttributeType.State)); byte[] reply = response.GetAttributeValue(AttributeType.ReplyMessage); if (!RadiusPacket.Encoding.GetString(reply, 0, reply.Length).ToLower().Contains(m_newPinModeMessage2.ToLower())) return false; // New pin not accepted in attempt #1. response = Authenticate(username, pin, response.GetAttributeValue(AttributeType.State)); reply = response.GetAttributeValue(AttributeType.ReplyMessage); return RadiusPacket.Encoding.GetString(reply, 0, reply.Length).ToLower().Contains(m_newPinModeMessage3.ToLower()); } /// <summary> /// Authenticates the username and password against the RADIUS server. /// </summary> /// <param name="username">Username to be authenticated.</param> /// <param name="password">Password to be authenticated.</param> /// <returns>Response packet received from the server for the authentication request.</returns> /// <remarks> /// <para> /// The type of response packet (if any) will be one of the following: /// <list> /// <item>AccessAccept: If the authentication is successful.</item> /// <item>AccessReject: If the authentication is not successful.</item> /// <item>AccessChallenge: If the server need more information from the user.</item> /// </list> /// </para> /// <para> /// When an AccessChallenge response packet is received from the server, it contains a State attribute /// that must be included in the AccessRequest packet that is being sent in response to the AccessChallenge /// response. So if this method returns an AccessChallenge packet, then this method is to be called again /// with the requested information (from ReplyMessage attribute) in the password field and the value State /// attribute. /// </para> /// </remarks> public RadiusPacket Authenticate(string username, string password) => Authenticate(username, password, null); /// <summary> /// Authenticates the username and password against the RADIUS server. /// </summary> /// <param name="username">Username to be authenticated.</param> /// <param name="password">Password to be authenticated.</param> /// <param name="state">State value from a previous challenge response.</param> /// <returns>Response packet received from the server for the authentication request.</returns> /// <remarks> /// <para> /// The type of response packet (if any) will be one of the following: /// <list> /// <item>AccessAccept: If the authentication is successful.</item> /// <item>AccessReject: If the authentication is not successful.</item> /// <item>AccessChallenge: If the server need more information from the user.</item> /// </list> /// </para> /// <para> /// When an AccessChallenge response packet is received from the server, it contains a State attribute /// that must be included in the AccessRequest packet that is being sent in response to the AccessChallenge /// response. So if this method returns an AccessChallenge packet, then this method is to be called again /// with the requested information (from ReplyMessage attribute) in the password field and the value State /// attribute. /// </para> /// </remarks> public RadiusPacket Authenticate(string username, string password, byte[] state) { CheckDisposed(); if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) throw new ArgumentException("Username and Password cannot be null."); RadiusPacket request = new RadiusPacket(PacketType.AccessRequest); byte[] authenticator = RadiusPacket.CreateRequestAuthenticator(m_sharedSecret); request.Authenticator = authenticator; request.Attributes.Add(new RadiusPacketAttribute(AttributeType.UserName, username)); request.Attributes.Add(new RadiusPacketAttribute(AttributeType.UserPassword, RadiusPacket.EncryptPassword(password, m_sharedSecret, authenticator))); // State attribute is used when responding to a AccessChallenge response. if (state != null) request.Attributes.Add(new RadiusPacketAttribute(AttributeType.State, state)); return ProcessRequest(request); } /// <summary> /// Determines whether or not the response indicates that the user account is in "New Pin" mode. /// </summary> /// <param name="response">Response packet sent by the server.</param> /// <returns>True if the user account is in "New Pin" mode; otherwise False.</returns> /// <remarks> /// <para>A user's account can be in the "New Pin" mode when set on the server.</para> /// <para>NOTE: This method is specific to RSA RADIUS implementation.</para> /// </remarks> public bool IsUserInNewPinMode(RadiusPacket response) { CheckDisposed(); if (response == null) throw new ArgumentNullException(nameof(response)); byte[] messageBytes = response.GetAttributeValue(AttributeType.ReplyMessage); if (messageBytes == null) throw new ArgumentException("ReplyMessage attribute is not present", nameof(response));