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 
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); }
cee74f2017-11-14Stephen R. van den Berg private string(7bit) clientproof(string(8bit) salted_password) {
38f2c72017-11-14Stephen R. van den Berg  .MAC.State hmacsaltedpw = HMAC(salted_password);
25a6eb2017-11-20Stephen R. van den Berg  salted_password = hmacsaltedpw("Client Key");
cee74f2017-11-14Stephen R. van den Berg  // Returns ServerSignature through nonce
25a6eb2017-11-20Stephen R. van den Berg  nonce = encode64(HMAC(hmacsaltedpw("Server Key"))(first));
39ee8a2017-11-15Stephen R. van den Berg  return encode64([string(8bit)] (salted_password ^ HMAC(H->hash(salted_password))(first)));
38f2c72017-11-14Stephen R. van den Berg }
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]
b7cdcf2017-11-15Martin Nilsson string server_1(string(8bit) line) {
9c26612017-11-13Stephen R. van den Berg  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..];
b7cdcf2017-11-15Martin Nilsson  [username, r] = array_sscanf(line, 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]
b7cdcf2017-11-15Martin Nilsson string(7bit) client_2(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;
b7cdcf2017-11-15Martin Nilsson  if (!catch([r, salt, iters] = array_sscanf(line, format))
606c9e2017-11-13Stephen R. van den Berg  && 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);
39ee8a2017-11-15Stephen R. van den Berg  first = [string(8bit)]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))) {
dc5cd62017-11-30Stephen R. van den Berg  r = [string(8bit)]H->pbkdf2([string(8bit)]pass, [string(8bit)]salt, iters, H->digest_size());
39ee8a2017-11-15Stephen R. van den Berg  .SCRAM_set_salted_password([string(8bit)]r, H, nonce);
135dd82017-11-12Stephen R. van den Berg  }
39ee8a2017-11-15Stephen R. van den Berg  salt = sprintf("%s,p=%s", line, clientproof([string(8bit)]r));
135dd82017-11-12Stephen R. van den Berg  first = 0; // Free memory } 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.
b7cdcf2017-11-15Martin Nilsson string(7bit) server_3(string(8bit) line,
fbb7452017-11-13Stephen R. van den Berg  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;
b7cdcf2017-11-15Martin Nilsson  if (!catch([r, p] = array_sscanf(line, format))
5f824f2017-11-13Martin Nilsson  && r == nonce) {
9c26612017-11-13Stephen R. van den Berg  first += sprintf("c=biws,r=%s", r);
cee74f2017-11-14Stephen R. van den Berg  p = p == clientproof(salted_password) && sprintf("v=%s", 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.
b7cdcf2017-11-15Martin Nilsson int(0..1) client_3(string(8bit) line) {
135dd82017-11-12Stephen R. van den Berg  constant format = "v=%s";
ac4ff32017-11-13Stephen R. van den Berg  string v;
b7cdcf2017-11-15Martin Nilsson  return !catch([v] = array_sscanf(line, format))
cee74f2017-11-14Stephen R. van den Berg  && v == nonce;
135dd82017-11-12Stephen R. van den Berg }