Securing WordPress with Radius Server login.
This project was created in mind to help secure WordPress but allow users an easier way to securely login without having to have a really long password to remember.
While I do not go into creating the Radius server you can go to http://www.radiusdesk.com/getting_started/install_ubuntu_nginx to get help setting one up.
Order of setup:
- Have a working Radius server with IP and secret code available.
- Have WordPress installed with a username configured to match what is in the Radius server
- Have SNMP installed on the UNIX box
- Create the additional MySQL table using the .SQL file
- Run the CreateUserCredentials.php file in UNIX terminal (php CreateUserCredentials.php dkittell)
- Direct the user to http://(your url)/wp-radius/ to login
sudo apt-get install snmp
The code for this post is also posted in GitHub – WP-Radius, look for updates to happen there, this post is for initial documentation.
This process works by using a new table in the database of WordPress to store the username and AES 256-bit encrypted password. We need the password stored in this format so we can have as small of an impact on WordPress.
DROP TABLE IF EXISTS `wp_sso`; CREATE TABLE `wp_sso` ( `user_login` VARCHAR(60) NOT NULL ,`user_pass` VARCHAR(250) NOT NULL ) ENGINE = InnoDB DEFAULT CHARSET = latin1;
Now we setup two configuration files
<?PHP $sRadiusServer = '127.0.0.1'; $sRadiusServerKey = 'SomethingSecret'; $sEncryptionKey = '9970197D67354DB93FEDBC8D331EFC3F6B441CD0A2CDB70810971531C23791E9'; // Create a new password (UNIX terminal): openssl enc -aes-256-cbc -k MySuperSecretPassPhrase -P -md sha1 $sDBServer = 'localhost'; $sDBUser = 'DBUser'; $sDBPass = 'DBPass'; $sDBName = 'DBName'; ?>
<?PHP define('AES_METHOD', 'AES-256-CBC'); class AES256 { public function Encrypt($password, $plainText) { if(empty($password) || empty($plainText)) { return FALSE; } //generate a random salt $Salt = openssl_random_pseudo_bytes(8); if($Salt === FALSE){ return FALSE; } //generate a random initialization vector $IV = openssl_random_pseudo_bytes( openssl_cipher_iv_length( AES_METHOD)); if($IV === FALSE){ return FALSE; } //generate aes key $pwd = substr(hash('sha256', $password), 0, 32); $Key = openssl_pbkdf2($pwd, $Salt, 32, 5); if($Key === FALSE){ return FALSE; } //encrypt message $cipherText = openssl_encrypt($plainText, AES_METHOD, $Key, true, $IV); //check if encryption failed if($cipherText === FALSE){ return FALSE; } //create something safer than the following code //this is just a demonstration $IV64 = base64_encode($IV); $Salt64 = base64_encode($Salt); $Cipher64 = base64_encode($cipherText); if($IV64 === FALSE || $Salt64 === FALSE || $Cipher64 === FALSE) { return FALSE; } return base64_encode($IV64.'^^'.$Cipher64.'**'.$Salt64); } public function Decrypt($password, $cipherText) { if(empty($password) || empty($cipherText)) { return FALSE; } $decoded = base64_decode($cipherText); if($decoded === FALSE){ return FALSE; } //locate iv value $IV = base64_decode(substr($decoded, 0, strpos($decoded, '^^'))); if($IV === FALSE){ return FALSE; } //locate salt value $encodedSalt = substr($decoded, strpos($decoded, '**') + 2, strlen($decoded)); $Salt = base64_decode($encodedSalt); if($Salt === FALSE){ return FALSE; } //locate cipher text $ciphertext = base64_decode(substr($decoded, strpos($decoded, '^^') + 2, -(strlen($encodedSalt)+2))); if($ciphertext === FALSE){ return FALSE; } //generate aes key $pwd = substr(hash('sha256', $password), 0, 32); $Key = openssl_pbkdf2($pwd, $Salt, 32, 5); if($Key === FALSE){ return FALSE; } return openssl_decrypt($ciphertext, AES_METHOD, $Key, true, $IV); } } /********************************************************************* * * Radius * Pure PHP radius class * * Creation 2008-01-04 * Update 2009-01-05 * @package radius * @version v.1.2.2 * @author SysCo/al * *********************************************************************/ class Radius { var $_ip_radius_server; // Radius server IP address var $_shared_secret; // Shared secret with the radius server var $_radius_suffix; // Radius suffix (default is ''); var $_udp_timeout; // Timeout of the UDP connection in seconds (default value is 5) var $_authentication_port; // Authentication port (default value is 1812) var $_accounting_port; // Accouting port (default value is 1813) var $_nas_ip_address; // NAS IP address var $_nas_port; // NAS port var $_encrypted_password; // Encrypted password, as described in the RFC 2865 var $_user_ip_address; // Remote IP address of the user var $_request_authenticator; // Request-Authenticator, 16 octets random number var $_response_authenticator; // Request-Authenticator, 16 octets random number var $_username; // Username to sent to the Radius server var $_password; // Password to sent to the Radius server (clear password, must be encrypted) var $_identifier_to_send; // Identifier field for the packet to be sent var $_identifier_received; // Identifier field for the received packet var $_radius_packet_to_send; // Radius packet code (1=Access-Request, 2=Access-Accept, 3=Access-Reject, 4=Accounting-Request, 5=Accounting-Response, 11=Access-Challenge, 12=Status-Server (experimental), 13=Status-Client (experimental), 255=Reserved var $_radius_packet_received; // Radius packet code (1=Access-Request, 2=Access-Accept, 3=Access-Reject, 4=Accounting-Request, 5=Accounting-Response, 11=Access-Challenge, 12=Status-Server (experimental), 13=Status-Client (experimental), 255=Reserved var $_attributes_to_send; // Radius attributes to send var $_attributes_received; // Radius attributes received var $_socket_to_server; // Socket connection var $_debug_mode; // Debug mode flag var $_attributes_info; // Attributes info array var $_radius_packet_info; // Radius packet codes info array var $_last_error_code; // Last error code var $_last_error_message; // Last error message /********************************************************************* * * Name: Radius * short description: Radius class constructor * * Creation 2008-01-04 * Update 2009-01-05 * @version v.1.2.2 * @author SysCo/al * @param string ip address of the radius server * @param string shared secret with the radius server * @param string radius domain name suffix (default is empty) * @param integer UDP timeout (default is 5) * @param integer authentication port * @param integer accounting port * @return NULL *********************************************************************/ public function Radius($ip_radius_server = '127.0.0.1', $shared_secret = '', $radius_suffix = '', $udp_timeout = 5, $authentication_port = 1812, $accounting_port = 1813) { $this->_radius_packet_info[1] = 'Access-Request'; $this->_radius_packet_info[2] = 'Access-Accept'; $this->_radius_packet_info[3] = 'Access-Reject'; $this->_radius_packet_info[4] = 'Accounting-Request'; $this->_radius_packet_info[5] = 'Accounting-Response'; $this->_radius_packet_info[11] = 'Access-Challenge'; $this->_radius_packet_info[12] = 'Status-Server (experimental)'; $this->_radius_packet_info[13] = 'Status-Client (experimental)'; $this->_radius_packet_info[255] = 'Reserved'; $this->_attributes_info[1] = array('User-Name', 'S'); $this->_attributes_info[2] = array('User-Password', 'S'); $this->_attributes_info[3] = array('CHAP-Password', 'S'); // Type (1) / Length (1) / CHAP Ident (1) / String $this->_attributes_info[4] = array('NAS-IP-Address', 'A'); $this->_attributes_info[5] = array('NAS-Port', 'I'); $this->_attributes_info[6] = array('Service-Type', 'I'); $this->_attributes_info[7] = array('Framed-Protocol', 'I'); $this->_attributes_info[8] = array('Framed-IP-Address', 'A'); $this->_attributes_info[9] = array('Framed-IP-Netmask', 'A'); $this->_attributes_info[10] = array('Framed-Routing', 'I'); $this->_attributes_info[11] = array('Filter-Id', 'T'); $this->_attributes_info[12] = array('Framed-MTU', 'I'); $this->_attributes_info[13] = array('Framed-Compression', 'I'); $this->_attributes_info[14] = array( 'Login-IP-Host', 'A'); $this->_attributes_info[15] = array('Login-service', 'I'); $this->_attributes_info[16] = array('Login-TCP-Port', 'I'); $this->_attributes_info[17] = array('(unassigned)', ''); $this->_attributes_info[18] = array('Reply-Message', 'T'); $this->_attributes_info[19] = array('Callback-Number', 'S'); $this->_attributes_info[20] = array('Callback-Id', 'S'); $this->_attributes_info[21] = array('(unassigned)', ''); $this->_attributes_info[22] = array('Framed-Route', 'T'); $this->_attributes_info[23] = array('Framed-IPX-Network', 'I'); $this->_attributes_info[24] = array('State', 'S'); $this->_attributes_info[25] = array('Class', 'S'); $this->_attributes_info[26] = array('Vendor-Specific', 'S'); // Type (1) / Length (1) / Vendor-Id (4) / Vendor type (1) / Vendor length (1) / Attribute-Specific... $this->_attributes_info[27] = array('Session-Timeout', 'I'); $this->_attributes_info[28] = array('Idle-Timeout', 'I'); $this->_attributes_info[29] = array('Termination-Action', 'I'); $this->_attributes_info[30] = array('Called-Station-Id', 'S'); $this->_attributes_info[31] = array('Calling-Station-Id', 'S'); $this->_attributes_info[32] = array('NAS-Identifier', 'S'); $this->_attributes_info[33] = array('Proxy-State', 'S'); $this->_attributes_info[34] = array('Login-LAT-Service', 'S'); $this->_attributes_info[35] = array('Login-LAT-Node', 'S'); $this->_attributes_info[36] = array('Login-LAT-Group', 'S'); $this->_attributes_info[37] = array('Framed-AppleTalk-Link', 'I'); $this->_attributes_info[38] = array('Framed-AppleTalk-Network', 'I'); $this->_attributes_info[39] = array('Framed-AppleTalk-Zone', 'S'); $this->_attributes_info[60] = array('CHAP-Challenge', 'S'); $this->_attributes_info[61] = array('NAS-Port-Type', 'I'); $this->_attributes_info[62] = array('Port-Limit', 'I'); $this->_attributes_info[63] = array('Login-LAT-Port', 'S'); $this->_attributes_info[76] = array('Prompt', 'I'); $this->_identifier_to_send = 0; $this->_user_ip_address = (isset($_SERVER['REMOTE_ADDR'])?$_SERVER['REMOTE_ADDR']:'0.0.0.0'); $this->GenerateRequestAuthenticator(); $this->SetIpRadiusServer($ip_radius_server); $this->SetSharedSecret($shared_secret); $this->SetAuthenticationPort($authentication_port); $this->SetAccountingPort($accounting_port); $this->SetRadiusSuffix($radius_suffix); $this->SetUdpTimeout($udp_timeout); $this->SetUsername(); $this->SetPassword(); $this->SetNasIpAddress(); $this->SetNasPort(); $this->ClearLastError(); $this->ClearDataToSend(); $this->ClearDataReceived(); } function GetNextIdentifier() { $this->_identifier_to_send = (($this->_identifier_to_send + 1) % 256); return $this->_identifier_to_send; } function GenerateRequestAuthenticator() { $this->_request_authenticator = ''; for ($ra_loop = 0; $ra_loop <= 15; $ra_loop++) { $this->_request_authenticator .= chr(rand(1, 255)); } } function GetRequestAuthenticator() { return $this->_request_authenticator; } function GetLastError() { if (0 < $this->_last_error_code) { return $this->_last_error_message.' ('.$this->_last_error_code.')'; } else { return ''; } } function ClearDataToSend() { $this->_radius_packet_to_send = 0; $this->_attributes_to_send = NULL; } function ClearDataReceived() { $this->_radius_packet_received = 0; $this->_attributes_received = NULL; } function SetPacketCodeToSend($packet_code) { $this->_radius_packet_to_send = $packet_code; } function SetDebugMode($debug_mode) { $this->_debug_mode = (TRUE === $debug_mode); } function SetIpRadiusServer($ip_radius_server) { $this->_ip_radius_server = gethostbyname($ip_radius_server); } function SetSharedSecret($shared_secret) { $this->_shared_secret = $shared_secret; } function SetRadiusSuffix($radius_suffix) { $this->_radius_suffix = $radius_suffix; } function SetUsername($username = '') { $temp_username = $username; if (false === strpos($temp_username, '@')) { $temp_username .= $this->_radius_suffix; } $this->_username = $temp_username; $this->SetAttribute(1, $this->_username); } function SetPassword($password = '') { $this->_password = $password; $encrypted_password = ''; $padded_password = $password; if (0 != (strlen($password)%16)) { $padded_password .= str_repeat(chr(0),(16-strlen($password)%16)); } $previous_result = $this->_request_authenticator; for ($full_loop = 0; $full_loop < (strlen($padded_password)/16); $full_loop++) { $xor_value = md5($this->_shared_secret.$previous_result); $previous_result = ''; for ($xor_loop = 0; $xor_loop <= 15; $xor_loop++) { $value1 = ord(substr($padded_password, ($full_loop * 16) + $xor_loop, 1)); $value2 = hexdec(substr($xor_value, 2*$xor_loop, 2)); $xor_result = $value1 ^ $value2; $previous_result .= chr($xor_result); } $encrypted_password .= $previous_result; } $this->_encrypted_password = $encrypted_password; $this->SetAttribute(2, $this->_encrypted_password); } function SetNasIPAddress($nas_ip_address = '') { if (0 < strlen($nas_ip_address)) { $this->_nas_ip_address = gethostbyname($nas_ip_address); } else { $this->_nas_ip_address = gethostbyname(isset($_SERVER['SERVER_ADDR'])?$_SERVER['SERVER_ADDR']:'0.0.0.0'); } $this->SetAttribute(4, $this->_nas_ip_address); } function SetNasPort($nas_port = 0) { $this->_nas_port = intval($nas_port); $this->SetAttribute(5, $this->_nas_port); } function SetUdpTimeout($udp_timeout = 5) { if (intval($udp_timeout) > 0) { $this->_udp_timeout = intval($udp_timeout); } } function ClearLastError() { $this->_last_error_code = 0; $this->_last_error_message = ''; } function SetAuthenticationPort($authentication_port) { if ((intval($authentication_port) > 0) && (intval($authentication_port) < 65536)) { $this->_authentication_port = intval($authentication_port); } } function SetAccountingPort($accounting_port) { if ((intval($accounting_port) > 0) && (intval($accounting_port) < 65536)) { $this->_accounting_port = intval($accounting_port); } } function GetReceivedPacket() { return $this->_radius_packet_received; } function GetReceivedAttributes() { return $this->_attributes_received; } function GetReadableReceivedAttributes() { $readable_attributes = ''; if (isset($this->_attributes_received)) { foreach($this->_attributes_received as $one_received_attribute) { $attributes_info = $this->GetAttributesInfo($one_received_attribute[0]); $readable_attributes .= $attributes_info[0].": "; if (26 == $one_received_attribute[0]) { $vendor_array = $this->DecodeVendorSpecificContent($one_received_attribute[1]); foreach($vendor_array as $vendor_one) { $readable_attributes .= 'Vendor-Id: '.$vendor_one[0].", Vendor-type: ".$vendor_one[1].", Attribute-specific: ".$vendor_one[2]; } } else { $readable_attributes .= $one_received_attribute[1]; } $readable_attributes .= "<br />\n"; } } return $readable_attributes; } function GetAttribute($attribute_type) { $attribute_value = NULL; foreach($this->_attributes_received as $one_received_attribute) { if (intval($attribute_type) == $one_received_attribute[0]) { $attribute_value = $one_received_attribute[1]; break; } } return $attribute_value; } function GetRadiusPacketInfo($info_index) { if (isset($this->_radius_packet_info[intval($info_index)])) { return $this->_radius_packet_info[intval($info_index)]; } else { return ''; } } function GetAttributesInfo($info_index) { if (isset($this->_attributes_info[intval($info_index)])) { return $this->_attributes_info[intval($info_index)]; } else { return array('',''); } } function DebugInfo($debug_info) { if ($this->_debug_mode) { echo date('Y-m-d H:i:s').' DEBUG: <div style="margin-left:15px;">' . $debug_info . '</div>'; flush(); } } function SetAttribute($type, $value) { $attribute_index = -1; for ($attributes_loop = 0; $attributes_loop < count($this->_attributes_to_send); $attributes_loop++) { if ($type == ord(substr($this->_attributes_to_send[$attributes_loop], 0, 1))) { $attribute_index = $attributes_loop; break; } } $temp_attribute = NULL; if (isset($this->_attributes_info[$type])) { switch ($this->_attributes_info[$type][1]) { case 'T': // Text, 1-253 octets containing UTF-8 encoded ISO 10646 characters (RFC 2279). $temp_attribute = chr($type).chr(2+strlen($value)).$value; break; case 'S': // String, 1-253 octets containing binary data (values 0 through 255 decimal, inclusive). $temp_attribute = chr($type).chr(2+strlen($value)).$value; break; case 'A': // Address, 32 bit value, most significant octet first. $ip_array = explode(".", $value); $temp_attribute = chr($type).chr(6).chr($ip_array[0]).chr($ip_array[1]).chr($ip_array[2]).chr($ip_array[3]); break; case 'I': // Integer, 32 bit unsigned value, most significant octet first. $temp_attribute = chr($type).chr(6).chr(($value/(256*256*256))%256).chr(($value/(256*256))%256).chr(($value/(256))%256).chr($value%256); break; case 'D': // Time, 32 bit unsigned value, most significant octet first -- seconds since 00:00:00 UTC, January 1, 1970. (not used in this RFC) $temp_attribute = NULL; break; default: $temp_attribute = NULL; } } if ($attribute_index > -1) { $this->_attributes_to_send[$attribute_index] = $temp_attribute; $additional_debug = 'Modified'; } else { $this->_attributes_to_send[] = $temp_attribute; $additional_debug = 'Added'; } $attribute_info = $this->GetAttributesInfo($type); $this->DebugInfo($additional_debug.' Attribute '.$type.' ('.$attribute_info[0].'), format '.$attribute_info[1].', value <em>'.$value.'</em>'); } function DecodeAttribute($attribute_raw_value, $attribute_format) { $attribute_value = NULL; if (isset($this->_attributes_info[$attribute_format])) { switch ($this->_attributes_info[$attribute_format][1]) { case 'T': // Text, 1-253 octets containing UTF-8 encoded ISO 10646 characters (RFC 2279). $attribute_value = $attribute_raw_value; break; case 'S': // String, 1-253 octets containing binary data (values 0 through 255 decimal, inclusive). $attribute_value = $attribute_raw_value; break; case 'A': // Address, 32 bit value, most significant octet first. $attribute_value = ord(substr($attribute_raw_value, 0, 1)).'.'.ord(substr($attribute_raw_value, 1, 1)).'.'.ord(substr($attribute_raw_value, 2, 1)).'.'.ord(substr($attribute_raw_value, 3, 1)); break; case 'I': // Integer, 32 bit unsigned value, most significant octet first. $attribute_value = (ord(substr($attribute_raw_value, 0, 1))*256*256*256)+(ord(substr($attribute_raw_value, 1, 1))*256*256)+(ord(substr($attribute_raw_value, 2, 1))*256)+ord(substr($attribute_raw_value, 3, 1)); break; case 'D': // Time, 32 bit unsigned value, most significant octet first -- seconds since 00:00:00 UTC, January 1, 1970. (not used in this RFC) $attribute_value = NULL; break; default: $attribute_value = NULL; } } return $attribute_value; } /********************************************************************* * Array returned: array(array(Vendor-Id1, Vendor type1, Attribute-Specific1), ..., array(Vendor-IdN, Vendor typeN, Attribute-SpecificN) *********************************************************************/ function DecodeVendorSpecificContent($vendor_specific_raw_value) { $result = array(); $offset_in_raw = 0; $vendor_id = (ord(substr($vendor_specific_raw_value, 0, 1))*256*256*256)+(ord(substr($vendor_specific_raw_value, 1, 1))*256*256)+(ord(substr($vendor_specific_raw_value, 2, 1))*256)+ord(substr($vendor_specific_raw_value, 3, 1)); $offset_in_raw += 4; while ($offset_in_raw < strlen($vendor_specific_raw_value)) { $vendor_type = (ord(substr($vendor_specific_raw_value, 0+$offset_in_raw, 1))); $vendor_length = (ord(substr($vendor_specific_raw_value, 1+$offset_in_raw, 1))); $attribute_specific = substr($vendor_specific_raw_value, 2+$offset_in_raw, $vendor_length); $result[] = array($vendor_id, $vendor_type, $attribute_specific); $offset_in_raw += ($vendor_length); } return $result; } /* * Function : AccessRequest * * Return TRUE if Access-Request is accepted, FALSE otherwise */ function AccessRequest($username = '', $password = '', $udp_timeout = 0, $state = NULL) { $this->ClearDataReceived(); $this->ClearLastError(); $this->SetPacketCodeToSend(1); // Access-Request if (0 < strlen($username)) { $this->SetUsername($username); } if (0 < strlen($password)) { $this->SetPassword($password); } if ($state!==NULL) { $this->SetAttribute(24, $state); } else { $this->SetAttribute(6, 1); // 1=Login } if (intval($udp_timeout) > 0) { $this->SetUdpTimeout($udp_timeout); } $attributes_content = ''; for ($attributes_loop = 0; $attributes_loop < count($this->_attributes_to_send); $attributes_loop++) { $attributes_content .= $this->_attributes_to_send[$attributes_loop]; } $packet_length = 4; // Radius packet code + Identifier + Length high + Length low $packet_length += strlen($this->_request_authenticator); // Request-Authenticator $packet_length += strlen($attributes_content); // Attributes $packet_data = chr($this->_radius_packet_to_send); $packet_data .= chr($this->GetNextIdentifier()); $packet_data .= chr(intval($packet_length/256)); $packet_data .= chr(intval($packet_length%256)); $packet_data .= $this->_request_authenticator; $packet_data .= $attributes_content; $_socket_to_server = socket_create(AF_INET, SOCK_DGRAM, 17); // UDP packet = 17 if ($_socket_to_server === FALSE) { $this->_last_error_code = socket_last_error(); $this->_last_error_message = socket_strerror($this->_last_error_code); } elseif (FALSE === socket_connect($_socket_to_server, $this->_ip_radius_server, $this->_authentication_port)) { $this->_last_error_code = socket_last_error(); $this->_last_error_message = socket_strerror($this->_last_error_code); } elseif (FALSE === socket_write($_socket_to_server, $packet_data, $packet_length)) { $this->_last_error_code = socket_last_error(); $this->_last_error_message = socket_strerror($this->_last_error_code); } else { $this->DebugInfo('<b>Packet type '.$this->_radius_packet_to_send.' ('.$this->GetRadiusPacketInfo($this->_radius_packet_to_send).')'.' sent</b>'); if ($this->_debug_mode) { $readable_attributes = ''; foreach($this->_attributes_to_send as $one_attribute_to_send) { $attribute_info = $this->GetAttributesInfo(ord(substr($one_attribute_to_send,0,1))); $this->DebugInfo('Attribute '.ord(substr($one_attribute_to_send,0,1)).' ('.$attribute_info[0].'), length '.(ord(substr($one_attribute_to_send,1,1))-2).', format '.$attribute_info[1].', value <em>'.$this->DecodeAttribute(substr($one_attribute_to_send,2), ord(substr($one_attribute_to_send,0,1))).'</em>'); } } $read_socket_array = array($_socket_to_server); $write_socket_array = NULL; $except_socket_array = NULL; $received_packet = chr(0); if (!(FALSE === socket_select($read_socket_array, $write_socket_array, $except_socket_array, $this->_udp_timeout))) { if (in_array($_socket_to_server, $read_socket_array)) { if (FALSE === ($received_packet = @socket_read($_socket_to_server, 1024))) // @ used, than no error is displayed if the connection is closed by the remote host { $received_packet = chr(0); $this->_last_error_code = socket_last_error(); $this->_last_error_message = socket_strerror($this->_last_error_code); } else { socket_close($_socket_to_server); } } } else { socket_close($_socket_to_server); } } $this->_radius_packet_received = intval(ord(substr($received_packet, 0, 1))); $this->DebugInfo('<b>Packet type '.$this->_radius_packet_received.' ('.$this->GetRadiusPacketInfo($this->_radius_packet_received).')'.' received</b>'); if ($this->_radius_packet_received > 0) { $this->_identifier_received = intval(ord(substr($received_packet, 1, 1))); $packet_length = (intval(ord(substr($received_packet, 2, 1))) * 256) + (intval(ord(substr($received_packet, 3, 1)))); $this->_response_authenticator = substr($received_packet, 4, 16); $attributes_content = substr($received_packet, 20, ($packet_length - 4 - 16)); while (strlen($attributes_content) > 2) { $attribute_type = intval(ord(substr($attributes_content,0,1))); $attribute_length = intval(ord(substr($attributes_content,1,1))); $attribute_raw_value = substr($attributes_content,2,$attribute_length-2); $attributes_content = substr($attributes_content, $attribute_length); $attribute_value = $this->DecodeAttribute($attribute_raw_value, $attribute_type); $attribute_info = $this->GetAttributesInfo($attribute_type); if (26 == $attribute_type) { $vendor_array = $this->DecodeVendorSpecificContent($attribute_value); foreach($vendor_array as $vendor_one) { $this->DebugInfo('Attribute '.$attribute_type.' ('.$attribute_info[0].'), length '.($attribute_length-2).', format '.$attribute_info[1].', Vendor-Id: '.$vendor_one[0].", Vendor-type: ".$vendor_one[1].", Attribute-specific: ".$vendor_one[2]); } } else { $this->DebugInfo('Attribute '.$attribute_type.' ('.$attribute_info[0].'), length '.($attribute_length-2).', format '.$attribute_info[1].', value <em>'.$attribute_value.'</em>'); } $this->_attributes_received[] = array($attribute_type, $attribute_value); } } return (2 == ($this->_radius_packet_received)); } } ?>
Now to create the unique password for the connection to work. First take notice of the first lines of this script as it restricts the access to UNIX command line (yes technically can be done on Windows as well).
NOTE: This script does not create the user on the Radius or in WordPress servers but creates a link between the two. This script will change the password in WordPress to match the automatically generated password. Should the user lose their Radius app and still need in an admin can run this script and give the user the generated password to get in.
<?php // Restrict access to this file - start if (php_sapi_name() != 'cli'){ exit; } // Restrict access to this file - stop // Require CLI Parameter - Start // Require a username to get passed via CLI, if no username is received we will display an error at the end if (isset($argv[1]) && ('' != trim($argv[1]))) { $username = $argv[1]; require_once('functions.php'); require_once('config.php'); // OpenSSL Password Generation - Start // usage: $newpassword = generatePassword(12); // for a 12-char password, upper/lower/numbers. // functions that use rand() or mt_rand() are not secure according to the PHP manual. // https://gist.github.com/zyphlar/7217f566fc83a9633959 function getRandomBytes($nbBytes = 32) { $bytes = openssl_random_pseudo_bytes($nbBytes, $strong); if (false !== $bytes && true === $strong) { return $bytes; } else { throw new \Exception("Unable to generate secure token from OpenSSL."); } } function generatePassword($length){ return substr(preg_replace("/[^a-zA-Z0-9]/", "", base64_encode(getRandomBytes($length+1))),0,$length); } $sNewPassword = generatePassword(24); echo " \nUser Password: \n" .$sNewPassword . "\n"; $sNewPasswordWP = md5($sNewPassword); // Encrypt Password - Start // Set timezone date_default_timezone_set("UTC"); define('sEncryptionKey', $sEncryptionKey); $encryptor = new AES256(); $cipher = $encryptor->Encrypt(sEncryptionKey, $username . '|' . date("Y-m-d H:i:s") .'|'.$sNewPassword); if($cipher) { echo "Cipher: \n". $cipher . "\n \n"; // Record the new password // database connection $conn = new PDO("mysql:host=$sDBServer;dbname=$sDBName",$sDBUser,$sDBPass); // new data $user_login = $username; $user_pass = $cipher; // Remove user reference if exists $sql = "delete from wp_sso WHERE user_login = :user_login"; $q = $conn->prepare($sql); $q->execute(array(':user_login'=>$user_login)); // Insert token into wp_sso $sql = "INSERT INTO wp_sso (user_login,user_pass) VALUES (:user_login,:user_pass)"; $q = $conn->prepare($sql); $q->execute(array(':user_pass'=>$user_pass,':user_login'=>$user_login)); // Insert password into wp_users $sql = "update wp_users set user_pass = :user_pass WHERE user_login = :user_login"; $q = $conn->prepare($sql); $q->execute(array(':user_pass'=>$sNewPasswordWP,':user_login'=>$user_login)); } // Encrypt Password - Stop } else { echo "Parameter is missing\n"; } // Require CLI Parameter - Stop ?>
Now for the login page, this can be dressed up in many ways.
<?php require_once('functions.php'); require_once('config.php'); require($_SERVER['DOCUMENT_ROOT'] . "/wp-load.php"); if ((isset($_POST['username'])) && ('' != trim($_POST['username'])) && (isset($_POST['password'])) && ('' != trim($_POST['password']))) { $radius = new Radius($sRadiusServer, $sRadiusServerKey); $radius->SetNasIpAddress($sRadiusServer); // Needed for some devices, and not auto_detected if PHP not runned through a web server // Enable Debug Mode for the demonstration //$radius->SetDebugMode(TRUE); if ($radius->AccessRequest($_POST['username'], $_POST['password'])) { //echo "<strong>Authentication accepted.</strong>"; $username = $_POST['username']; // WordPress Integration - Start // User has properly authenticated using the RADIUS server credentials, now we will create the WordPress session // Set timezone date_default_timezone_set("UTC"); // database connection $conn = new PDO("mysql:host=$sDBServer;dbname=$sDBName",$sDBUser,$sDBPass); $sql = "select * from wp_sso WHERE user_login = ?"; $q = $conn->prepare($sql); $q->execute(array($username)); $q->setFetchMode(PDO::FETCH_ASSOC); while ($r = $q->fetch()) { $sToken = $r['user_pass']; //echo '<p>'.$sToken . '</p>'; define('sEncryptionKey', $sEncryptionKey); $encryptor = new AES256(); $decrypted = $encryptor->Decrypt(sEncryptionKey, $sToken); $saDecrypted = explode("|", $decrypted); //echo "Login: " . $saDecrypted[0]; // Auto Login - Start // ACCOUNT USERNAME TO LOGIN TO $creds['user_login'] = $saDecrypted[0]; // ACCOUNT PASSWORD TO USE $creds['user_password'] = $saDecrypted[2]; $creds['remember'] = true; $autologin_user = wp_signon( $creds, false ); if ( !is_wp_error($autologin_user) ) { header('Location: /wp-admin'); // LOCATION TO REDIRECT TO } // Auto Login - Stop } // WordPress Integration - Stop } else { echo "<strong>Authentication rejected.</strong>"; echo "<br />"; echo "<a href=\"".$_SERVER['PHP_SELF']."\">Reload authentication form</a>"; } //echo "<br /><strong>Get Readable Received Attributes</strong><br />"; //echo $radius->GetReadableReceivedAttributes(); } else { ?> <form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>"> <p><label for="username"> Username:<br/> <input name="username" type="text" value="" /> </label></p> <p><label for="password"> Password:<br/> <input name="password" type="password" class="input" value="" /> </label></p> <p class="submit"> <input name="submit" type="submit" value="Check authentication" class="button button-primary button-large" /> </p> </form> <?php } ?>
Last Updated on November 19, 2015