135dd82017-11-12Stephen R. van den Berg  //! SCRAM, defined by @rfc{5802@}.
9c26612017-11-13Stephen R. van den Berg //! //! This implements both the client and the serverside. //! You normally run either the server or the client, but if you would
27fc0f2017-11-13Stephen R. van den Berg //! run both (use a separate client and a separate server object!), //! the sequence would be:
9c26612017-11-13Stephen R. van den Berg //! //! @[client_1] -> @[server_1] -> @[server_2] -> @[client_2] ->
27fc0f2017-11-13Stephen R. van den Berg //! @[server_3] -> @[client_3]
135dd82017-11-12Stephen R. van den Berg  #pike __REAL_VERSION__ #pragma strict_types #require constant(Crypto.Hash) private .Hash H; // hash object private string(8bit) first, cnonce;
9c26612017-11-13Stephen R. van den Berg constant ClientKey = "Client Key"; constant ServerKey = "Server Key"; //! Step 0 in the SCRAM handshake, prior to creating the object,
27fc0f2017-11-13Stephen R. van den Berg //! you need to have agreed with your peer on the hashfunction to be used. //! //! @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.
135dd82017-11-12Stephen R. van den Berg //! //! @param h //! The hash object on which the SCRAM object should base its //! operations. Typical input is @[Crypto.SHA256]. //! //! @seealso
27fc0f2017-11-13Stephen R. van den Berg //! @[client_1], @[server_1]
135dd82017-11-12Stephen R. van den Berg protected void create(.Hash h) { H = h; } private Crypto.MAC.State HMAC(string(8bit) key) {
a1d4ac2017-11-13Stephen R. van den Berg  return H->HMAC(key);
135dd82017-11-12Stephen R. van den Berg }
9c26612017-11-13Stephen R. van den Berg //! Client-side step 1 in the SCRAM handshake.
135dd82017-11-12Stephen R. van den Berg //! //! @param username
9c26612017-11-13Stephen R. van den Berg //! 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.
135dd82017-11-12Stephen R. van den Berg //! //! @returns
9c26612017-11-13Stephen R. van den Berg //! The first request to send to the server.
135dd82017-11-12Stephen R. van den Berg //! //! @seealso
9c26612017-11-13Stephen R. van den Berg //! @[client_2] string(7bit) client_1(void|string username) {
135dd82017-11-12Stephen R. van den Berg  cnonce = MIME.encode_base64(random_string(18)); return [string(7bit)](first = [string(8bit)]sprintf("n,,n=%s,r=%s",
9c26612017-11-13Stephen R. van den Berg  username && username != "" ? Standards.IDNA.to_ascii(username, 1) : "", cnonce)); } //! Server-side step 1 in the SCRAM handshake. //! //! @returns //! The username specified by the client. //! //! @seealso //! @[server_2] string server_1(Stdio.Buffer|string(8bit) line) { constant format = "n,,n=%s,r=%s"; string username, r; first = [string(8bit)]line[3..]; [username, r] = stringp(line) ? array_sscanf([string]line, format) : [array(string)](line->sscanf(format)); cnonce = [string(8bit)]r; return Standards.IDNA.to_unicode(username); } //! 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
27fc0f2017-11-13Stephen R. van den Berg //! The first response to send to the client.
9c26612017-11-13Stephen R. van den Berg //! //! @seealso //! @[server_3] string(7bit) server_2(string(8bit) salt, int iters) { string response = sprintf("r=%s,s=%s,i=%d", cnonce += MIME.encode_base64(random_string(18)), MIME.encode_base64(salt), iters); first += "," + response + ","; return [string(7bit)]response;
135dd82017-11-12Stephen R. van den Berg }
9c26612017-11-13Stephen R. van den Berg //! Client-side step 2 in the SCRAM handshake.
135dd82017-11-12Stephen R. van den Berg //! //! @param pass //! The password to feed to the server. //! //! @param line //! The challenge received from the server to our @[client_first]. //! //! @returns
27fc0f2017-11-13Stephen R. van den Berg //! The final response to send to the server. If the response is //! null, the server messed up the handshake.
135dd82017-11-12Stephen R. van den Berg //! //! @seealso
9c26612017-11-13Stephen R. van den Berg //! @[client_3] string(7bit) client_2(string pass, Stdio.Buffer|string(8bit) line) {
135dd82017-11-12Stephen R. van den Berg  constant format = "r=%s,s=%s,i=%d"; string r, salt; int iters; [r, salt, iters] = stringp(line) ? array_sscanf([string]line, format)
9c26612017-11-13Stephen R. van den Berg  : [array(string)](line->sscanf(format)); if (iters > 0 && has_prefix(r, cnonce)) {
135dd82017-11-12Stephen R. van den Berg  line = [string(8bit)]sprintf("c=biws,r=%s", r); r = 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); if (!(first = .SCRAM_get_salted_password(H, pass, salt, iters))) { first = [string(8bit)]H->pbkdf2(pass, salt, iters, H->digest_size()); .SCRAM_set_salted_password(first, H, pass, salt, iters); } Crypto.MAC.State hmacfirst = HMAC(first); first = 0; // Free memory
9c26612017-11-13Stephen R. van den Berg  salt = hmacfirst([string(8bit)]ClientKey);
135dd82017-11-12Stephen R. van den Berg  salt = sprintf("%s,p=%s", line,
9c26612017-11-13Stephen R. van den Berg  MIME.encode_base64(salt ^ HMAC(H->hash([string(8bit)]salt))([string(8bit)]r))); cnonce = HMAC(hmacfirst([string(8bit)]ServerKey))([string(8bit)]r);
135dd82017-11-12Stephen R. van den Berg  } else salt = 0;
9c26612017-11-13Stephen R. van den Berg  return [string(7bit)]salt; }
27fc0f2017-11-13Stephen R. van den Berg //! Final server-side step in the SCRAM handshake.
9c26612017-11-13Stephen R. van den Berg //! //! @param salted_password //! The salted (using the salt provided earlier) password belonging //! to the specified username. //! //! @returns
27fc0f2017-11-13Stephen R. van den Berg //! The final response to send to the client. If the response //! is null, the client did not supply the correct credentials.
9c26612017-11-13Stephen R. van den Berg string(7bit) server_3(string(8bit) salted_password, Stdio.Buffer|string(8bit) line) { constant format = "c=biws,r=%s,p=%s"; string r, p, response; [r, p] = stringp(line) ? array_sscanf([string]line, format) : [array(string)](line->sscanf(format)); if (r == cnonce) { first += sprintf("c=biws,r=%s", r); Crypto.MAC.State hmacfirst = HMAC(salted_password); r = hmacfirst([string(8bit)]ClientKey); if (MIME.decode_base64(p) == [string(8bit)](r ^ HMAC(H->hash([string(8bit)]r))(first))) response = sprintf("v=%s", MIME.encode_base64(HMAC( hmacfirst([string(8bit)]ServerKey))(first))); } return response;
135dd82017-11-12Stephen R. van den Berg }
27fc0f2017-11-13Stephen R. van den Berg //! Final client-side step in the SCRAM handshake. If we get this far, the
135dd82017-11-12Stephen R. van den Berg //! server has already verified that we supplied the correct credentials. //! If this step fails, it means the server does not have our
27fc0f2017-11-13Stephen R. van den Berg //! credentials at all and is an imposter.
135dd82017-11-12Stephen R. van den Berg //! //! @param line //! The verification received from the server to our @[client_final]. //! //! @returns //! True if the server is valid, false if the server is invalid.
9c26612017-11-13Stephen R. van den Berg int(0..1) client_3(Stdio.Buffer|string line) {
135dd82017-11-12Stephen R. van den Berg  constant format = "v=%s"; string(8bit) v; [v] = stringp(line) ? array_sscanf(line, format) : line->sscanf(format); return MIME.decode_base64(v) == cnonce; }