///|
/// Base64-encode a string (used for AUTH LOGIN/PLAIN payloads).
pub fn base64_encode_str(s : String) -> String {
@base64.std_encode2str(string_to_bytes(s).to_fixedarray())
}
///|
/// Base64-decode a string.
pub fn base64_decode_str(s : String) -> String raise MailFailure {
@base64.std_decode2str(s) catch {
_ => raise MailFailure::encode("invalid base64 in AUTH exchange")
}
}
///|
/// RFC 4616 PLAIN credentials: `\0username\0password`, base64 encoded.
pub fn auth_plain_credentials(username : String, password : String) -> String {
base64_encode_str("\u{0000}\{username}\u{0000}\{password}")
}
///|
/// MD5 digest of `data` as raw bytes (built on `gmlewis/md5`).
fn md5_bytes(data : Bytes) -> Bytes raise MailFailure {
let d = @md5.Digest::new()
for b in data {
d.write(b)
}
hex_to_bytes(d.check_sum())
}
///|
/// HMAC-MD5 (RFC 2104) with a 64-byte block, built on the MD5 dependency.
/// Returns the digest as a lowercase hex string (MM_AUTH_001).
pub fn hmac_md5(key : Bytes, msg : Bytes) -> String raise MailFailure {
let mut k = key
if k.length() > 64 {
k = md5_bytes(k)
}
let padded = FixedArray::make(64, b'\x00')
for i = 0; i < k.length(); i = i + 1 {
padded[i] = k[i]
}
let ipad = FixedArray::make(64, b'\x00')
let opad = FixedArray::make(64, b'\x00')
for i = 0; i < 64; i = i + 1 {
ipad[i] = (padded[i].to_int() ^ 0x36).to_byte()
opad[i] = (padded[i].to_int() ^ 0x5c).to_byte()
}
let inner = md5_bytes(Bytes::from_iter(ipad.iter()) + msg)
let outer = md5_bytes(Bytes::from_iter(opad.iter()) + inner)
bytes_to_hex(outer)
}
///|
/// Build the CRAM-MD5 client response: `username `.
/// `challenge_b64` is the text after `334 ` in the server challenge.
pub fn cram_md5_response(
username : String,
password : String,
challenge_b64 : String,
) -> String raise MailFailure {
let challenge = base64_decode_str(challenge_b64)
let digest = hmac_md5(string_to_bytes(password), string_to_bytes(challenge))
"\{username} \{digest}"
}
///|
/// A CRAM-MD5 test vector for documentation purposes (RFC 2195 example values
/// come from the `smtplib` documentation).
pub const CRAM_MD5_VECTOR_CHALLENGE : String = "PDc1MDU2MzAxMDM2OTk0NDY3MDU1Pj5NSTcxNDcyNDU3OTQ3NTk4MDc0OTQ0NjkxPj4="