1
  
2
  
3
  
4
  
5
  
6
  
7
  
8
  
9
  
10
  
11
  
12
  
13
  
14
  
15
  
16
  
17
  
18
  
19
  
20
  
21
  
22
  
23
  
24
  
25
  
26
  
27
  
28
  
29
  
30
  
31
  
32
  
33
  
34
  
35
  
36
  
37
  
38
  
39
  
40
  
41
  
42
  
43
  
44
  
45
  
46
  
47
  
48
  
49
  
50
  
51
  
52
  
53
  
54
  
55
  
56
  
57
  
58
  
59
  
60
  
61
  
62
  
63
  
64
  
65
  
66
  
67
  
68
  
69
  
70
  
71
  
72
  
73
  
74
  
75
  
76
  
77
  
78
  
79
  
80
  
81
  
82
  
83
  
84
  
85
  
86
  
87
  
88
  
89
  
90
  
91
  
92
  
93
  
94
  
95
  
96
  
97
  
98
  
99
  
100
  
101
  
102
  
103
  
104
  
 
//! SCRAM, defined by @rfc{5802@}. 
 
#pike __REAL_VERSION__ 
#pragma strict_types 
#require constant(Crypto.Hash) 
 
private .Hash H;  // hash object 
 
private string(8bit) first, cnonce; 
 
//! Step 0 in the SCRAM handshake, you need to agree 
//! with the server 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]. 
//! 
//! @seealso 
//!   @[client_first] 
protected void create(.Hash h) { 
  H = h; 
} 
 
private Crypto.MAC.State HMAC(string(8bit) key) { 
  return H->HMAC(key, H->digest_size()); 
} 
 
//! Step 1 in the SCRAM handshake. 
//! 
//! @param username 
//!   The username to feed to the server. 
//! 
//! @returns 
//!   The request to send to the server. 
//! 
//! @seealso 
//!   @[client_final] 
string(7bit) client_first(string username) { 
  cnonce = MIME.encode_base64(random_string(18)); 
  return [string(7bit)](first = [string(8bit)]sprintf("n,,n=%s,r=%s", 
    username == "" ? "" : Standards.IDNA.to_ascii(username, 1), cnonce)); 
} 
 
//! Step 2 in the SCRAM handshake. 
//! 
//! @param pass 
//!   The password to feed to the server. 
//! 
//! @param line 
//!   The challenge received from the server to our @[client_first]. 
//! 
//! @returns 
//!   The response to send to the server. 
//! 
//! @seealso 
//!   @[server_final] 
string(7bit) client_final(string pass, Stdio.Buffer|string(8bit) line) { 
  constant format = "r=%s,s=%s,i=%d"; 
  string r, salt; 
  int iters; 
  [r, salt, iters] = stringp(line) 
    ? array_sscanf([string]line, format) 
    : [array(string|int)](line->sscanf(format)); 
  if (iters >0 && has_prefix(r, cnonce)) { 
    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 
    salt = hmacfirst("Client Key"); 
    salt = sprintf("%s,p=%s", line, 
      MIME.encode_base64(salt ^ HMAC(H->hash(salt))(r))); 
    cnonce = HMAC(hmacfirst("Server Key"))(r); 
  } else 
    salt = 0; 
  return salt; 
} 
 
//! Final step 3 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. 
//! 
//! @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. 
int(0..1) server_final(Stdio.Buffer|string line) { 
  constant format = "v=%s"; 
  string(8bit) v; 
  [v] = stringp(line) 
    ? array_sscanf(line, format) : line->sscanf(format); 
  return MIME.decode_base64(v) == cnonce; 
}