use super::{sign, uri_encode_path};
const ACCESS_KEY: &str = "AKIAIOSFODNN7EXAMPLE";
const SECRET_KEY: &str = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY";
const REGION: &str = "us-east-1";
const HOST: &str = "examplebucket.s3.amazonaws.com";
const EPOCH: u64 = 1_369_353_600;
fn header<'a>(headers: &'a [(String, String)], name: &str) -> &'a str {
headers.iter().find(|(k, _)| k == name).map(|(_, v)| v.as_str()).unwrap()
}
#[test]
fn signs_expected_headers() {
let headers = sign("GET", HOST, "/examplebucket/test.txt", b"", REGION, ACCESS_KEY, SECRET_KEY, EPOCH);
assert_eq!(HOST, header(&headers, "host"));
assert_eq!(
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
header(&headers, "x-amz-content-sha256"),
"SHA-256 of an empty payload is a well-known constant"
);
assert_eq!("20130524T000000Z", header(&headers, "x-amz-date"));
}
#[test]
fn authorization_header_has_expected_shape() {
let headers = sign("PUT", HOST, "/examplebucket/test.txt", b"hello", REGION, ACCESS_KEY, SECRET_KEY, EPOCH);
let auth = header(&headers, "Authorization");
assert!(auth.starts_with("AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request, "));
assert!(auth.contains("SignedHeaders=host;x-amz-content-sha256;x-amz-date"));
let sig = auth.rsplit("Signature=").next().unwrap();
assert_eq!(64, sig.len(), "signature must be a 32-byte hex digest");
assert!(sig.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn same_inputs_produce_same_signature() {
let a = sign("GET", HOST, "/examplebucket/test.txt", b"", REGION, ACCESS_KEY, SECRET_KEY, EPOCH);
let b = sign("GET", HOST, "/examplebucket/test.txt", b"", REGION, ACCESS_KEY, SECRET_KEY, EPOCH);
assert_eq!(header(&a, "Authorization"), header(&b, "Authorization"));
}
#[test]
fn signature_changes_with_payload() {
let a = sign("PUT", HOST, "/examplebucket/test.txt", b"hello", REGION, ACCESS_KEY, SECRET_KEY, EPOCH);
let b = sign("PUT", HOST, "/examplebucket/test.txt", b"world", REGION, ACCESS_KEY, SECRET_KEY, EPOCH);
assert_ne!(header(&a, "Authorization"), header(&b, "Authorization"));
assert_ne!(header(&a, "x-amz-content-sha256"), header(&b, "x-amz-content-sha256"));
}
#[test]
fn signature_changes_with_method() {
let a = sign("GET", HOST, "/examplebucket/test.txt", b"", REGION, ACCESS_KEY, SECRET_KEY, EPOCH);
let b = sign("DELETE", HOST, "/examplebucket/test.txt", b"", REGION, ACCESS_KEY, SECRET_KEY, EPOCH);
assert_ne!(header(&a, "Authorization"), header(&b, "Authorization"));
}
#[test]
fn signature_changes_with_path() {
let a = sign("GET", HOST, "/examplebucket/test.txt", b"", REGION, ACCESS_KEY, SECRET_KEY, EPOCH);
let b = sign("GET", HOST, "/examplebucket/other.txt", b"", REGION, ACCESS_KEY, SECRET_KEY, EPOCH);
assert_ne!(header(&a, "Authorization"), header(&b, "Authorization"));
}
#[test]
fn signature_changes_with_secret_key() {
let a = sign("GET", HOST, "/examplebucket/test.txt", b"", REGION, ACCESS_KEY, SECRET_KEY, EPOCH);
let b = sign("GET", HOST, "/examplebucket/test.txt", b"", REGION, ACCESS_KEY, "a-different-secret", EPOCH);
assert_ne!(header(&a, "Authorization"), header(&b, "Authorization"));
}
#[test]
fn credential_scope_uses_the_given_date() {
let headers = sign("GET", HOST, "/examplebucket/test.txt", b"", REGION, ACCESS_KEY, SECRET_KEY, 1_609_459_200);
assert_eq!("20210101T000000Z", header(&headers, "x-amz-date"));
assert!(header(&headers, "Authorization").contains("Credential=AKIAIOSFODNN7EXAMPLE/20210101/us-east-1/s3/aws4_request"));
}
#[test]
fn uri_encode_path_preserves_slashes_and_encodes_special_chars() {
assert_eq!("/bucket/key", uri_encode_path("/bucket/key"));
assert_eq!("/bucket/a%20b.png", uri_encode_path("/bucket/a b.png"));
assert_eq!("/bucket/nested/dir/file.txt", uri_encode_path("/bucket/nested/dir/file.txt"));
assert_eq!("/bucket/~file", uri_encode_path("/bucket/~file"));
}