///|
fn sha256(data : Bytes) -> Bytes {
Bytes::from_array(@crypto.sha256(data)[:])
}
///|
fn hmac(key : Bytes, data : Bytes) -> Bytes {
Bytes::from_array(@crypto.hmac(@crypto.SHA256::new(), key, data)[:])
}
///|
fn scram_response(
password : String,
nonce : String,
first : String,
) -> (String, String) raise {
let attrs : Map[String, String] = Map([])
for part in first.split(",") {
if part.length() < 3 || part[1] != '=' {
raise Authentication("invalid SCRAM attribute")
}
let key = part[0:1].to_owned()
if attrs.contains(key) {
raise Authentication("duplicate SCRAM attribute")
}
attrs[key] = part[2:].to_owned()
}
guard attrs.get("r") is Some(server_nonce) &&
attrs.get("s") is Some(salt_text) &&
attrs.get("i") is Some(iter_text) else {
raise Authentication("missing SCRAM attributes")
}
if attrs.contains("m") ||
!server_nonce.has_prefix(nonce) ||
server_nonce.length() <= nonce.length() {
raise Authentication("invalid SCRAM nonce or mandatory extension")
}
let iterations = @string.parse_int(iter_text)
if iterations < 4096 || iterations > 1000000 {
raise Authentication("SCRAM iteration limit")
}
// PostgreSQL uses SASLprep; this first release accepts printable ASCII passwords only.
for c in password {
if c.to_int() < 32 || c.to_int() > 126 {
raise Authentication("password must be printable ASCII")
}
}
let salt = @base64.decode(salt_text)
let key = @utf8.encode(password)
let mut u = hmac(key, salt + b"\x00\x00\x00\x01")
let acc = u.to_fixedarray()
for _ in 1.. client_key[i] ^ signature[i])
let server_signature = hmac(hmac(salted, b"Server Key"), auth)
(
final_message + ",p=" + @base64.encode(proof),
"v=" + @base64.encode(server_signature),
)
}