diff --git a/lib/modules/Crypto.pmod/SCRAM.pike b/lib/modules/Crypto.pmod/SCRAM.pike deleted file mode 100644 index 8fab19e716e6a822de03fd0ec820856a698b7c90..0000000000000000000000000000000000000000 --- a/lib/modules/Crypto.pmod/SCRAM.pike +++ /dev/null @@ -1,207 +0,0 @@ - -//! SCRAM, defined by @rfc{5802@}. -//! -//! This implements both the client- and the serverside. -//! You normally run either the server or the client, but if you would -//! run both (use a separate client and a separate server object!), -//! the sequence would be: -//! -//! @[client_1] -> @[server_1] -> @[server_2] -> @[client_2] -> -//! @[server_3] -> @[client_3] -//! -//! @note -//! This implementation does not pretend to support the full protocol. -//! Most notably optional extension arguments are not supported (yet). - -#pike __REAL_VERSION__ -#pragma strict_types -#require constant(Crypto.Hash) - -private .Hash H; // hash object - -private string(8bit) first, nonce; - -private string(7bit) encode64(string(8bit) raw) { - return MIME.encode_base64(raw, 1); -} - -private string(7bit) randomstring() { - return encode64(random_string(18)); -} - -private .MAC.State HMAC(string(8bit) key) { - return H->HMAC(key); -} - -private string(7bit) clientproof(string(8bit) salted_password) { - .MAC.State hmacsaltedpw = HMAC(salted_password); - salted_password = hmacsaltedpw("Client Key"); - // Returns ServerSignature through nonce - nonce = encode64(HMAC(hmacsaltedpw("Server Key"))(first)); - return encode64([string(8bit)] - (salted_password ^ HMAC(H->hash(salted_password))(first))); -} - -//! Step 0 in the SCRAM handshake, prior to creating the object, -//! you need to have agreed with your peer on the hashfunction to be used. -//! -//! @param h -//! The hash object on which the SCRAM object should base its -//! operations. Typical input is @[Crypto.SHA256]. -//! -//! @note -//! If you are a client, you must use the @ref{client_*@} methods; if you are -//! a server, you must use the @ref{server_*@} methods. -//! You cannot mix both client and server methods in a single object. -//! -//! @seealso -//! @[client_1], @[server_1] -protected void create(.Hash h) { - H = h; -} - -//! Client-side step 1 in the SCRAM handshake. -//! -//! @param username -//! The username to feed to the server. Some servers already received -//! the username through an alternate channel (usually during -//! the hash-function selection handshake), in which case it -//! should be omitted here. -//! -//! @returns -//! The client-first request to send to the server. -//! -//! @seealso -//! @[client_2] -string(7bit) client_1(void|string username) { - nonce = randomstring(); - return [string(7bit)](first = [string(8bit)]sprintf("n,,n=%s,r=%s", - username && username != "" ? Standards.IDNA.to_ascii(username, 1) : "", - nonce)); -} - -//! Server-side step 1 in the SCRAM handshake. -//! -//! @param line -//! The received client-first request from the client. -//! -//! @returns -//! The username specified by the client. Returns null -//! if the response could not be parsed. -//! -//! @seealso -//! @[server_2] -string server_1(string(8bit) line) { - constant format = "n,,n=%s,r=%s"; - string username, r; - catch { - first = [string(8bit)]line[3..]; - [username, r] = array_sscanf(line, format); - nonce = [string(8bit)]r; - r = Standards.IDNA.to_unicode(username); - }; - return r; -} - -//! Server-side step 2 in the SCRAM handshake. -//! -//! @param salt -//! The salt corresponding to the username that has been specified earlier. -//! -//! @param iters -//! The number of iterations the hashing algorithm should perform -//! to compute the authentication hash. -//! -//! @returns -//! The server-first challenge to send to the client. -//! -//! @seealso -//! @[server_3] -string(7bit) server_2(string(8bit) salt, int iters) { - string response = sprintf("r=%s,s=%s,i=%d", - nonce += randomstring(), encode64(salt), iters); - first += "," + response + ","; - return [string(7bit)]response; -} - -//! Client-side step 2 in the SCRAM handshake. -//! -//! @param line -//! The received server-first challenge from the server. -//! -//! @param pass -//! The password to feed to the server. -//! -//! @returns -//! The client-final response to send to the server. If the response is -//! null, the server sent something unacceptable or unparseable. -//! -//! @seealso -//! @[client_3] -string(7bit) client_2(string(8bit) line, string pass) { - constant format = "r=%s,s=%s,i=%d"; - string r, salt; - int iters; - if (!catch([r, salt, iters] = array_sscanf(line, format)) - && iters > 0 - && has_prefix(r, nonce)) { - line = [string(8bit)]sprintf("c=biws,r=%s", r); - first = [string(8bit)]sprintf("%s,r=%s,s=%s,i=%d,%s", - first[3..], r, salt, iters, line); - if (pass != "") - pass = Standards.IDNA.to_ascii(pass); - salt = MIME.decode_base64(salt); - nonce = [string(8bit)]sprintf("%s,%s,%d", pass, salt, iters); - if (!(r = .SCRAM_get_salted_password(H, nonce))) { - r = [string(8bit)]H->pbkdf2([string(8bit)]pass, [string(8bit)]salt, - iters, H->digest_size()); - .SCRAM_set_salted_password([string(8bit)]r, H, nonce); - } - salt = sprintf("%s,p=%s", line, clientproof([string(8bit)]r)); - first = 0; // Free memory - } else - salt = 0; - return [string(7bit)]salt; -} - -//! Final server-side step in the SCRAM handshake. -//! -//! @param line -//! The received client-final challenge and response from the client. -//! -//! @param salted_password -//! The salted (using the salt provided earlier) password belonging -//! to the specified username. -//! -//! @returns -//! The server-final response to send to the client. If the response -//! is null, the client did not supply the correct credentials or -//! the response was unparseable. -string(7bit) server_3(string(8bit) line, - string(8bit) salted_password) { - constant format = "c=biws,r=%s,p=%s"; - string r, p; - if (!catch([r, p] = array_sscanf(line, format)) - && r == nonce) { - first += sprintf("c=biws,r=%s", r); - p = p == clientproof(salted_password) && sprintf("v=%s", nonce); - } - return [string(7bit)]p; -} - -//! Final client-side step in the SCRAM handshake. If we get this far, the -//! server has already verified that we supplied the correct credentials. -//! If this step fails, it means the server does not have our -//! credentials at all and is an imposter. -//! -//! @param line -//! The received server-final verification response. -//! -//! @returns -//! True if the server is valid, false if the server is invalid. -int(0..1) client_3(string(8bit) line) { - constant format = "v=%s"; - string v; - return !catch([v] = array_sscanf(line, format)) - && v == nonce; -} diff --git a/lib/modules/Crypto.pmod/module.pmod b/lib/modules/Crypto.pmod/module.pmod index 31f6bce086a4be7d44ae54aac086aadacbe7b719..3a80b6a933d5e643ea548ad4a5b385d257a0d694 100644 --- a/lib/modules/Crypto.pmod/module.pmod +++ b/lib/modules/Crypto.pmod/module.pmod @@ -62,24 +62,6 @@ class Sign { protected State `()() { return State(); } } -// Salted password cache for SCRAM - -private mapping (Hash:mapping(string:string(8bit))) - SCRAM_salted_password_cache = ([]); - -final string(8bit) SCRAM_get_salted_password(Hash h, string key) { - mapping(string:string(8bit)) m = SCRAM_salted_password_cache[h]; - return m && m[key]; -} - -final void SCRAM_set_salted_password(string(8bit) SaltedPassword, - Hash h, string key) { - mapping(string:string(8bit)) m = SCRAM_salted_password_cache[h]; - if (!m || sizeof(m) > 16) - SCRAM_salted_password_cache[h] = m = ([]); - m[key] = SaltedPassword; -} - //! Hashes a @[password] together with a @[salt] with the //! crypt_md5 algorithm and returns the result. //! diff --git a/lib/modules/Crypto.pmod/testsuite.in b/lib/modules/Crypto.pmod/testsuite.in index 3f33683c469aec233963175e2cba1d8f3cd51a2f..35ad79cd24e032bb4f7d7077130b12060e5f9278 100644 --- a/lib/modules/Crypto.pmod/testsuite.in +++ b/lib/modules/Crypto.pmod/testsuite.in @@ -557,8 +557,8 @@ test_do( add_constant("RSA") ) define(test_SCRAM, [[ cond_resolv($1, [[ test_any([[ - Crypto.SCRAM client = Crypto.SCRAM($1); - Crypto.SCRAM server = Crypto.SCRAM($1); + Crypto.Hash.SCRAM client = $1.SCRAM(); + Crypto.Hash.SCRAM server = $1.SCRAM(); return $2 == server->server_1(client->client_1($2)) && client->client_3(server->server_3( client->client_2(server->server_2(MIME.decode_base64($5), $6), $4), diff --git a/lib/modules/Sql.pmod/pgsql_util.pmod b/lib/modules/Sql.pmod/pgsql_util.pmod index 815ca3e6595cb180d0e56ff6df42965a8f2a6a8c..5bd4b21c1bb2c8813513cf916a7c37e5553db86f 100644 --- a/lib/modules/Sql.pmod/pgsql_util.pmod +++ b/lib/modules/Sql.pmod/pgsql_util.pmod @@ -1432,7 +1432,7 @@ class proxy { final string host; final int(0..65535) port; private string database, user, pass; - private Crypto.SCRAM SASLcontext; + private Crypto.Hash.SCRAM SASLcontext; final Thread.Condition waitforauthready; final Thread.Mutex shortmux; final int readyforquerycount; @@ -1785,7 +1785,7 @@ class proxy { #endif } if (k) { - SASLcontext = Crypto.SCRAM(Crypto.SHA256); + SASLcontext = Crypto.SHA256.SCRAM(); word = SASLcontext.client_1(); authresponse(({ "SCRAM-SHA-256", 0, sprintf("%4c", sizeof(word)), word diff --git a/lib/modules/__builtin.pmod/Nettle.pmod/Hash.pike b/lib/modules/__builtin.pmod/Nettle.pmod/Hash.pike index c1218d9eda9101d5d8e0dd5c1715306e9b76c064..82e4bd59899d01553b11f4999e4b23db08e4ac43 100644 --- a/lib/modules/__builtin.pmod/Nettle.pmod/Hash.pike +++ b/lib/modules/__builtin.pmod/Nettle.pmod/Hash.pike @@ -537,3 +537,207 @@ string(8bit) P_hash(string(8bit) password, string(8bit) salt, return res[..(bytes-1)]; } +// Salted password cache for SCRAM +// FIXME: Consider mark as weak? +private mapping(string:string(8bit)) SCRAM_salted_password_cache = ([]); + +final string(8bit) SCRAM_get_salted_password(string key) { + mapping(string:string(8bit)) m = SCRAM_salted_password_cache; + return m && m[key]; +} + +final void SCRAM_set_salted_password(string(8bit) SaltedPassword, string key) { + mapping(string:string(8bit)) m = SCRAM_salted_password_cache; + if (!m || sizeof(m) > 16) + SCRAM_salted_password_cache = m = ([]); + m[key] = SaltedPassword; +} + +//! SCRAM, defined by @rfc{5802@}. +//! +//! This implements both the client- and the serverside. +//! You normally run either the server or the client, but if you would +//! run both (use a separate client and a separate server object!), +//! the sequence would be: +//! +//! @[client_1] -> @[server_1] -> @[server_2] -> @[client_2] -> +//! @[server_3] -> @[client_3] +//! +//! @note +//! If you are a client, you must use the @ref{client_*@} methods; if you are +//! a server, you must use the @ref{server_*@} methods. +//! You cannot mix both client and server methods in a single object. +//! +//! @note +//! This implementation does not pretend to support the full protocol. +//! Most notably optional extension arguments are not supported (yet). +//! +//! @seealso +//! @[client_1], @[server_1] +class SCRAM +{ + private string(8bit) first, nonce; + + private string(7bit) encode64(string(8bit) raw) { + return MIME.encode_base64(raw, 1); + } + + private string(7bit) randomstring() { + return encode64(random_string(18)); + } + + private string(7bit) clientproof(string(8bit) salted_password) { + _HMAC.State hmacsaltedpw = HMAC(salted_password); + salted_password = hmacsaltedpw("Client Key"); + // Returns ServerSignature through nonce + nonce = encode64(HMAC(hmacsaltedpw("Server Key"))(first)); + return encode64([string(8bit)] + (salted_password ^ HMAC(hash(salted_password))(first))); + } + + //! Client-side step 1 in the SCRAM handshake. + //! + //! @param username + //! The username to feed to the server. Some servers already received + //! the username through an alternate channel (usually during + //! the hash-function selection handshake), in which case it + //! should be omitted here. + //! + //! @returns + //! The client-first request to send to the server. + //! + //! @seealso + //! @[client_2] + string(7bit) client_1(void|string username) { + nonce = randomstring(); + return [string(7bit)](first = [string(8bit)]sprintf("n,,n=%s,r=%s", + username && username != "" ? Standards.IDNA.to_ascii(username, 1) : "", + nonce)); + } + + //! Server-side step 1 in the SCRAM handshake. + //! + //! @param line + //! The received client-first request from the client. + //! + //! @returns + //! The username specified by the client. Returns null + //! if the response could not be parsed. + //! + //! @seealso + //! @[server_2] + string server_1(string(8bit) line) { + constant format = "n,,n=%s,r=%s"; + string username, r; + catch { + first = [string(8bit)]line[3..]; + [username, r] = array_sscanf(line, format); + nonce = [string(8bit)]r; + r = Standards.IDNA.to_unicode(username); + }; + return r; + } + + //! Server-side step 2 in the SCRAM handshake. + //! + //! @param salt + //! The salt corresponding to the username that has been specified earlier. + //! + //! @param iters + //! The number of iterations the hashing algorithm should perform + //! to compute the authentication hash. + //! + //! @returns + //! The server-first challenge to send to the client. + //! + //! @seealso + //! @[server_3] + string(7bit) server_2(string(8bit) salt, int iters) { + string response = sprintf("r=%s,s=%s,i=%d", + nonce += randomstring(), encode64(salt), iters); + first += "," + response + ","; + return [string(7bit)]response; + } + + //! Client-side step 2 in the SCRAM handshake. + //! + //! @param line + //! The received server-first challenge from the server. + //! + //! @param pass + //! The password to feed to the server. + //! + //! @returns + //! The client-final response to send to the server. If the response is + //! null, the server sent something unacceptable or unparseable. + //! + //! @seealso + //! @[client_3] + string(7bit) client_2(string(8bit) line, string(8bit) pass) { + constant format = "r=%s,s=%s,i=%d"; + string(8bit) r, salt; + int iters; + if (!catch([r, salt, iters] = [array(string(8bit)|int)] + array_sscanf(line, format)) + && iters > 0 + && has_prefix(r, nonce)) { + line = [string(8bit)]sprintf("c=biws,r=%s", r); + first = [string(8bit)]sprintf("%s,r=%s,s=%s,i=%d,%s", + first[3..], r, salt, iters, line); + if (pass != "") + pass = [string(7bit)]Standards.IDNA.to_ascii(pass); + salt = MIME.decode_base64(salt); + nonce = [string(8bit)]sprintf("%s,%s,%d", pass, salt, iters); + if (!(r = SCRAM_get_salted_password(nonce))) { + r = pbkdf2(pass, salt, iters, digest_size()); + SCRAM_set_salted_password(r, nonce); + } + salt = sprintf("%s,p=%s", line, clientproof(r)); + first = 0; // Free memory + } else + salt = 0; + return [string(7bit)]salt; + } + + //! Final server-side step in the SCRAM handshake. + //! + //! @param line + //! The received client-final challenge and response from the client. + //! + //! @param salted_password + //! The salted (using the salt provided earlier) password belonging + //! to the specified username. + //! + //! @returns + //! The server-final response to send to the client. If the response + //! is null, the client did not supply the correct credentials or + //! the response was unparseable. + string(7bit) server_3(string(8bit) line, + string(8bit) salted_password) { + constant format = "c=biws,r=%s,p=%s"; + string r, p; + if (!catch([r, p] = array_sscanf(line, format)) + && r == nonce) { + first += sprintf("c=biws,r=%s", r); + p = p == clientproof(salted_password) && sprintf("v=%s", nonce); + } + return [string(7bit)]p; + } + + //! Final client-side step in the SCRAM handshake. If we get this far, the + //! server has already verified that we supplied the correct credentials. + //! If this step fails, it means the server does not have our + //! credentials at all and is an imposter. + //! + //! @param line + //! The received server-final verification response. + //! + //! @returns + //! True if the server is valid, false if the server is invalid. + int(0..1) client_3(string(8bit) line) { + constant format = "v=%s"; + string v; + return !catch([v] = array_sscanf(line, format)) + && v == nonce; + } +}