1: <?php
2:
3: /**
4: * Authenticator
5: * @package framework
6: * @subpackage crypt
7: */
8:
9: class ScramAuthenticator {
10:
11: private $hashes = array(
12: 'sha-1' => 'sha1',
13: 'sha1' => 'sha1',
14: 'sha-224' => 'sha224',
15: 'sha224' => 'sha224',
16: 'sha-256' => 'sha256',
17: 'sha256' => 'sha256',
18: 'sha-384' => 'sha384',
19: 'sha384' => 'sha384',
20: 'sha-512' => 'sha512',
21: 'sha512' => 'sha512'
22: );
23:
24: private function getHashAlgorithm($scramAlgorithm) {
25: $parts = explode('-', mb_strtolower($scramAlgorithm));
26: return $this->hashes[$parts[1]] ?? 'sha1'; // Default to sha1 if the algorithm is not found
27: }
28: private function log($message) {
29: // Use Hm_Debug to add the debug message
30: Hm_Debug::add(sprintf($message));
31: }
32: public function generateClientProof($username, $password, $salt, $clientNonce, $serverNonce, $algorithm) {
33: $iterations = 4096;
34: $keyLength = strlen(hash($algorithm, '', true)); // Dynamically determine key length based on algorithm
35:
36: $passwordBytes = hash($algorithm, $password, true);
37: $saltedPassword = hash_pbkdf2($algorithm, $passwordBytes, $salt, $iterations, $keyLength, true);
38: $clientKey = hash_hmac($algorithm, "Client Key", $saltedPassword, true);
39: $storedKey = hash($algorithm, $clientKey, true);
40: $authMessage = 'n=' . $username . ',r=' . $clientNonce . ',s=' . base64_encode($salt) . ',r=' . $serverNonce;
41: $clientSignature = hash_hmac($algorithm, $authMessage, $storedKey, true);
42: $clientProof = base64_encode($clientKey ^ $clientSignature);
43: $this->log("Client proof generated successfully");
44: return $clientProof;
45: }
46:
47: public function authenticateScram($scramAlgorithm, $username, $password, $getServerResponse, $sendCommand) {
48: $algorithm = $this->getHashAlgorithm($scramAlgorithm);
49:
50: // Send initial SCRAM command
51: $scramCommand = 'AUTHENTICATE ' . $scramAlgorithm . "\r\n";
52: $sendCommand($scramCommand);
53: $response = $getServerResponse();
54: if (!empty($response) && mb_substr($response[0], 0, 2) == '+ ') {
55: $this->log("Received server challenge: " . $response[0]);
56: // Extract salt and server nonce from the server's challenge
57: $serverChallenge = base64_decode(mb_substr($response[0], 2));
58: $parts = explode(',', $serverChallenge);
59: $serverNonce = base64_decode(substr($parts[0], strpos($parts[0], "=") + 1));
60: $salt = base64_decode(substr($parts[1], strpos($parts[1], "=") + 1));
61:
62: // Generate client nonce
63: $clientNonce = base64_encode(random_bytes(32));
64: $this->log("Generated client nonce: " . $clientNonce);
65:
66: // Calculate client proof
67: $clientProof = $this->generateClientProof($username, $password, $salt, $clientNonce, $serverNonce, $algorithm);
68:
69: // Construct client final message
70: $channelBindingData = (mb_stripos($scramAlgorithm, 'plus') !== false) ? 'c=' . base64_encode('tls-unique') . ',' : 'c=biws,';
71: $clientFinalMessage = $channelBindingData . 'r=' . $serverNonce . $clientNonce . ',p=' . $clientProof;
72: $clientFinalMessageEncoded = base64_encode($clientFinalMessage);
73: $this->log("Sending client final message: " . $clientFinalMessageEncoded);
74: // Send client final message to server
75: $sendCommand($clientFinalMessageEncoded . "\r\n");
76:
77: // Verify server's response
78: $response = $getServerResponse();
79: if (!empty($response) && mb_substr($response[0], 0, 2) == '+ ') {
80: $serverFinalMessage = base64_decode(mb_substr($response[0], 2));
81: $parts = explode(',', $serverFinalMessage);
82: $serverProof = substr($parts[0], strpos($parts[0], "=") + 1);
83:
84: // Generate server key
85: $passwordBytes = hash($algorithm, $password, true);
86: $saltedPassword = hash_pbkdf2($algorithm, $passwordBytes, $salt, 4096, strlen(hash($algorithm, '', true)), true);
87: $serverKey = hash_hmac($algorithm, "Server Key", $saltedPassword, true);
88:
89: // Calculate server signature
90: $authMessage = 'n=' . $username . ',r=' . $clientNonce . ',s=' . base64_encode($salt) . ',r=' . $serverNonce;
91: $serverSignature = base64_encode(hash_hmac($algorithm, $authMessage, $serverKey, true));
92:
93: // Compare server signature with server proof
94: if ($serverSignature === $serverProof) {
95: $this->log("SCRAM authentication successful");
96: return true; // Authentication successful if they match
97: } else {
98: $this->log("SCRAM authentication failed: Server signature mismatch");
99: }
100: } else {
101: $this->log("SCRAM authentication failed: Invalid server final response");
102: }
103: } else {
104: $this->log("SCRAM authentication failed: Invalid server challenge");
105: }
106: return false; // Authentication failed
107: }
108: }