///|
fn sha256_hex(data : String) -> String {
@crypto.bytes_to_hex_string(@crypto.sha256(@utf8.encode(data)))
}
///|
fn sha256_data_hex(data : &@io.Data) -> String {
@crypto.bytes_to_hex_string(@crypto.sha256(data.binary()))
}
///|
fn hmac_sha256(key : BytesView, data : String) -> FixedArray[Byte] {
@crypto.hmac(@crypto.SHA256::new(), key, @utf8.encode(data))
}
///|
fn hmac_sha256_hex(key : BytesView, data : String) -> String {
@crypto.bytes_to_hex_string(hmac_sha256(key, data))
}
///|
fn signing_key(
secret : String,
date : String,
region : String,
) -> FixedArray[Byte] {
let k_date = hmac_sha256(@utf8.encode("AWS4\{secret}"), date)
let k_region = hmac_sha256(k_date.unsafe_reinterpret_as_bytes(), region)
let k_service = hmac_sha256(k_region.unsafe_reinterpret_as_bytes(), "s3")
hmac_sha256(k_service.unsafe_reinterpret_as_bytes(), "aws4_request")
}
///|
fn method_name(meth : S3Method) -> String {
match meth {
Get => "GET"
Head => "HEAD"
Put => "PUT"
Delete => "DELETE"
}
}
///|
fn normalize_header_value(value : String) -> String {
let out = StringBuilder()
let mut in_whitespace = false
for ch in value.trim() {
if ch.is_ascii_whitespace() {
if !in_whitespace {
out.write_char(' ')
in_whitespace = true
}
} else {
out.write_char(ch)
in_whitespace = false
}
}
out.to_string()
}
///|
fn canonical_headers(headers : Map[String, String]) -> (String, String) {
let pairs : Array[(String, String)] = []
for key, value in headers {
pairs.push((key.to_lower(), normalize_header_value(value)))
}
pairs.sort_by(fn(left, right) { ascii_compare(left.0, right.0) })
let coalesced : Array[(String, Array[String])] = []
for pair in pairs {
let (name, value) = pair
match coalesced.last() {
Some((last_name, values)) if last_name == name => values.push(value)
_ => coalesced.push((name, [value]))
}
}
let canonical = coalesced
.map(fn(pair) { "\{pair.0}:\{pair.1.join(",")}\n" })
.join("")
let signed = coalesced.map(fn(pair) { pair.0 }).join(";")
(canonical, signed)
}
///|
fn ascii_compare(left : String, right : String) -> Int {
let min_len = Int::min(left.length(), right.length())
for i in 0.. Map[String, String] {
let date = timestamp[:8].to_owned()
let (canonical, signed_headers) = canonical_headers(headers)
let canonical_request = [
method_name(meth),
path,
canonical_query(query),
canonical,
signed_headers,
payload_hash,
].join("\n")
let scope = "\{date}/\{region}/s3/aws4_request"
let string_to_sign = [
"AWS4-HMAC-SHA256",
timestamp,
scope,
sha256_hex(canonical_request),
].join("\n")
let signature = hmac_sha256_hex(
signing_key(credentials.secret_access_key, date, region).unsafe_reinterpret_as_bytes(),
string_to_sign,
)
let signed = headers.copy()
signed["Authorization"] = "AWS4-HMAC-SHA256 Credential=\{credentials.access_key_id}/\{scope}, SignedHeaders=\{signed_headers}, Signature=\{signature}"
signed
}