Skip to main content

coven_crypto/
s3_signing.rs

1//! Cryptographic primitives for native S3 request authentication (ยง20.7).
2//! Request canonicalization belongs to the storage adapter.
3
4use hmac::{Hmac, KeyInit, Mac};
5use sha2::{Digest, Sha256};
6use zeroize::Zeroizing;
7
8/// SHA-256 of a native request body or canonical request, before V4 signing.
9pub fn s3_request_hash(bytes: &[u8]) -> [u8; 32] {
10    Sha256::digest(bytes).into()
11}
12
13/// Google V4 HMAC signature using the account's interoperability secret.
14/// Date and region are the exact credential-scope components in the request.
15/// Intermediate signing keys and the prefixed secret are erased on drop.
16pub fn gcs_request_signature(secret: &str, date: &str, region: &str, message: &[u8]) -> [u8; 32] {
17    v4_signature(("GOOG4", "goog4_request"), secret, [date, region, "storage"], message)
18}
19
20/// AWS Signature Version 4 over a request's string to sign. The signing key
21/// derives from the secret through the date, region and service scope;
22/// intermediate keys and the prefixed secret are erased on drop.
23pub fn aws_request_signature(
24    secret: &str,
25    date: &str,
26    region: &str,
27    service: &str,
28    message: &[u8],
29) -> [u8; 32] {
30    v4_signature(("AWS4", "aws4_request"), secret, [date, region, service], message)
31}
32
33/// The V4 scheme both services share: the signing key chains an HMAC from
34/// the prefixed secret through each scope part and the scope's terminator,
35/// then signs `message`. Only the prefix and terminator name the service.
36fn v4_signature(
37    (prefix, terminator): (&str, &str),
38    secret: &str,
39    scope: [&str; 3],
40    message: &[u8],
41) -> [u8; 32] {
42    let secret = Zeroizing::new(format!("{prefix}{secret}"));
43    let mut key = hmac(secret.as_bytes(), scope[0].as_bytes());
44    for part in [scope[1], scope[2], terminator] {
45        key = hmac(key.as_ref(), part.as_bytes());
46    }
47    *hmac(key.as_ref(), message)
48}
49
50fn hmac(key: &[u8], bytes: &[u8]) -> Zeroizing<[u8; 32]> {
51    let mut mac = Hmac::<Sha256>::new_from_slice(key).expect("HMAC accepts any key length");
52    mac.update(bytes);
53    Zeroizing::new(mac.finalize().into_bytes().into())
54}
55
56#[cfg(test)]
57#[path = "s3_signing_tests.rs"]
58mod tests;