135dd82017-11-12Stephen R. van den Berg  //! SCRAM, defined by @rfc{5802@}.
9c26612017-11-13Stephen R. van den Berg //!
fbb7452017-11-13Stephen R. van den Berg //! This implements both the client- and the serverside.
9c26612017-11-13Stephen R. van den Berg //! 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]
606c9e2017-11-13Stephen R. van den Berg //! //! @note //! This implementation does not pretend to support the full protocol. //! Most notably optional extension arguments are not supported (yet).
135dd82017-11-12Stephen R. van den Berg  #pike __REAL_VERSION__ #pragma strict_types #require constant(Crypto.Hash) private .Hash H; // hash object
5f824f2017-11-13Martin Nilsson private string(8bit) first, nonce;
135dd82017-11-12Stephen R. van den Berg 
9c26612017-11-13Stephen R. van den Berg constant ClientKey = "Client Key"; constant ServerKey = "Server Key";
606c9e2017-11-13Stephen R. van den Berg private string(7bit) encode64(string(8bit) raw) { return MIME.encode_base64(raw, 1); }
38f2c72017-11-14Stephen R. van den Berg private string(7bit) randomstring() { return encode64(random_string(18)); } private .MAC.State HMAC(string(8bit) key) { return H->HMAC(key); } private array proofsignature(string(8bit) salted_password) { .MAC.State hmacsaltedpw = HMAC(salted_password); salted_password = hmacsaltedpw([string(8bit)]ClientKey); return ({ salted_password ^ HMAC(H->hash(salted_password))(first), HMAC(hmacsaltedpw([string(8bit)]ServerKey))(first) }); }
9c26612017-11-13Stephen R. van den Berg //! 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. //!
fbb7452017-11-13Stephen R. van den Berg //! @param h //! The hash object on which the SCRAM object should base its //! operations. Typical input is @[Crypto.SHA256]. //!
27fc0f2017-11-13Stephen R. van den Berg //! @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 //! //! @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; }
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
fbb7452017-11-13Stephen R. van den Berg //! The client-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) {
38f2c72017-11-14Stephen R. van den Berg  nonce = randomstring();
135dd82017-11-12Stephen R. van den Berg  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) : "",
5f824f2017-11-13Martin Nilsson  nonce));
9c26612017-11-13Stephen R. van den Berg } //! Server-side step 1 in the SCRAM handshake. //!
fbb7452017-11-13Stephen R. van den Berg //! @param line //! The received client-first request from the client. //!
9c26612017-11-13Stephen R. van den Berg //! @returns
606c9e2017-11-13Stephen R. van den Berg //! The username specified by the client. Returns null //! if the response could not be parsed.
9c26612017-11-13Stephen R. van den Berg //! //! @seealso //! @[server_2] string server_1(Stdio.Buffer|string(8bit) line) { constant format = "n,,n=%s,r=%s"; string username, r;
606c9e2017-11-13Stephen R. van den Berg  catch {
ac4ff32017-11-13Stephen R. van den Berg  first = [string(8bit)]line[3..];
606c9e2017-11-13Stephen R. van den Berg  [username, r] = stringp(line) ? array_sscanf([string]line, format) : [array(string)](line->sscanf(format));
5f824f2017-11-13Martin Nilsson  nonce = [string(8bit)]r;
606c9e2017-11-13Stephen R. van den Berg  r = Standards.IDNA.to_unicode(username); }; return r;
9c26612017-11-13Stephen R. van den Berg } //! 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
fbb7452017-11-13Stephen R. van den Berg //! The server-first challenge 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",
38f2c72017-11-14Stephen R. van den Berg  nonce += randomstring(), encode64(salt), iters);
9c26612017-11-13Stephen R. van den Berg  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 //!
fbb7452017-11-13Stephen R. van den Berg //! @param line //! The received server-first challenge from the server. //!
135dd82017-11-12Stephen R. van den Berg //! @param pass //! The password to feed to the server. //! //! @returns
fbb7452017-11-13Stephen R. van den Berg //! The client-final response to send to the server. If the response is
606c9e2017-11-13Stephen R. van den Berg //! null, the server sent something unacceptable or unparseable.
135dd82017-11-12Stephen R. van den Berg //! //! @seealso
9c26612017-11-13Stephen R. van den Berg //! @[client_3]
fbb7452017-11-13Stephen R. van den Berg string(7bit) client_2(Stdio.Buffer|string(8bit) line, string pass) {
135dd82017-11-12Stephen R. van den Berg  constant format = "r=%s,s=%s,i=%d"; string r, salt; int iters;
606c9e2017-11-13Stephen R. van den Berg  if (!catch([r, salt, iters] = stringp(line) ? array_sscanf([string]line, format) : [array(string)](line->sscanf(format))) && iters > 0
5f824f2017-11-13Martin Nilsson  && has_prefix(r, nonce)) {
135dd82017-11-12Stephen R. van den Berg  line = [string(8bit)]sprintf("c=biws,r=%s", r);
38f2c72017-11-14Stephen R. van den Berg  first = sprintf("%s,r=%s,s=%s,i=%d,%s", first[3..], r, salt, iters, line);
135dd82017-11-12Stephen R. van den Berg  if (pass != "") pass = Standards.IDNA.to_ascii(pass); salt = MIME.decode_base64(salt);
ac4ff32017-11-13Stephen R. van den Berg  nonce = [string(8bit)]sprintf("%s,%s,%d", pass, salt, iters);
38f2c72017-11-14Stephen R. van den Berg  if (!(r = .SCRAM_get_salted_password(H, nonce))) { r = [string(8bit)]H->pbkdf2(pass, salt, iters, H->digest_size()); .SCRAM_set_salted_password(r, H, nonce);
135dd82017-11-12Stephen R. van den Berg  }
38f2c72017-11-14Stephen R. van den Berg  [salt, nonce] = proofsignature(r);
135dd82017-11-12Stephen R. van den Berg  first = 0; // Free memory
38f2c72017-11-14Stephen R. van den Berg  salt = sprintf("%s,p=%s", line, encode64(salt));
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 //!
fbb7452017-11-13Stephen R. van den Berg //! @param line //! The received client-final challenge and response from the client. //!
9c26612017-11-13Stephen R. van den Berg //! @param salted_password //! The salted (using the salt provided earlier) password belonging //! to the specified username. //! //! @returns
fbb7452017-11-13Stephen R. van den Berg //! The server-final response to send to the client. If the response
606c9e2017-11-13Stephen R. van den Berg //! is null, the client did not supply the correct credentials or //! the response was unparseable.
fbb7452017-11-13Stephen R. van den Berg string(7bit) server_3(Stdio.Buffer|string(8bit) line, string(8bit) salted_password) {
9c26612017-11-13Stephen R. van den Berg  constant format = "c=biws,r=%s,p=%s";
ac4ff32017-11-13Stephen R. van den Berg  string r, p;
606c9e2017-11-13Stephen R. van den Berg  if (!catch([r, p] = stringp(line) ? array_sscanf([string]line, format) : [array(string)](line->sscanf(format)))
5f824f2017-11-13Martin Nilsson  && r == nonce) {
9c26612017-11-13Stephen R. van den Berg  first += sprintf("c=biws,r=%s", r);
38f2c72017-11-14Stephen R. van den Berg  [r, nonce] = proofsignature(salted_password); p = MIME.decode_base64(p) == r && sprintf("v=%s", encode64(nonce));
9c26612017-11-13Stephen R. van den Berg  }
38f2c72017-11-14Stephen R. van den Berg  return [string(7bit)]p;
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
fbb7452017-11-13Stephen R. van den Berg //! The received server-final verification response.
135dd82017-11-12Stephen R. van den Berg //! //! @returns //! True if the server is valid, false if the server is invalid.
fbb7452017-11-13Stephen R. van den Berg int(0..1) client_3(Stdio.Buffer|string(8bit) line) {
135dd82017-11-12Stephen R. van den Berg  constant format = "v=%s";
ac4ff32017-11-13Stephen R. van den Berg  string v;
606c9e2017-11-13Stephen R. van den Berg  return !catch([v] = stringp(line)
ac4ff32017-11-13Stephen R. van den Berg  ? array_sscanf([string]line, format)
ef99342017-11-13Stephen R. van den Berg  : [array(string)](line->sscanf(format)))
5f824f2017-11-13Martin Nilsson  && MIME.decode_base64(v) == nonce;
135dd82017-11-12Stephen R. van den Berg }