objectstore · API

objectstore.sigv4

objectstore.sigv4 — AWS Signature Version 4, header and query flavours.

Split out of s3 because it is pure string algebra over crypto: given a method, a path, query parameters, headers and a payload hash it produces the canonical request, the string to sign, and the signature. That shape is what makes it testable against the published aws-sig-v4-test-suite vectors without a network or an S3 server anywhere in the picture.

Two callers use it: S3Client, which signs with an Authorization header, and presign_url, which moves the same material into the query string so a URL can be handed to something that knows nothing about AWS.

Structs

QueryParam

struct QueryParam

One query parameter, unencoded. Signing encodes it.

Implements Copyable, Movable

Fields
name: String
value: String

AmzTime

struct AmzTime

A signing timestamp in the two forms SigV4 needs.

Implements Copyable, Movable

Fields
amz_date: String: 20150830T123600Z — the X-Amz-Date header value.
date_stamp: String: 20150830 — the date half of the credential scope.

from_epoch

def from_epoch(epoch: Int) -> Self

UTC calendar date from a Unix timestamp.

Days-to-civil from Howard Hinnant's chrono-compatible algorithm: it shifts the era so that leap days land at the end, which makes the whole conversion branch-free integer arithmetic. Mojo has no calendar library, and re-deriving this is cheaper than another FFI call.

now

def now() -> Self

from_amz_date

def from_amz_date(amz_date: String) -> Self

Reuses an X-Amz-Date value the caller already has (the test vectors, or a retried request that must keep its timestamp).

SignedRequest

struct SignedRequest

Everything a signing pass produced, so tests can check each stage.

Implements Copyable, Movable

Fields
canonical_request: String
string_to_sign: String
signature: String
signed_headers: String
authorization: String

Functions

epoch_now

def epoch_now() -> Int

Seconds since the Unix epoch, via the FFI shim.

Mojo's std.time offers only a monotonic clock, and SigV4 needs a real UTC timestamp — AWS rejects requests more than 15 minutes out of skew.

canonical_path

def canonical_path(path: String, normalize: Bool = False) -> String

The canonical URI: each segment percent-encoded once, / left alone.

normalize collapses ./.. and duplicate slashes, which is what every AWS service except S3 wants. S3 signs the un-normalized path — an S3 key may legitimately contain a literal .. segment — so the S3 client leaves this off.

canonical_query

def canonical_query(params: List[QueryParam]) -> String

Encoded k=v pairs sorted by encoded name, then encoded value.

canonical_headers

def canonical_headers(headers: List[Header]) -> Tuple[String, String]

Returns (canonical header block, signed header list).

Names are lowercased and sorted; repeated names are joined with commas in the order they appeared, per the SigV4 spec.

canonical_request

def canonical_request(method: String, path: String, params: List[QueryParam], headers: List[Header], payload_hash: String, normalize: Bool = False) -> Tuple[String, String]

Returns (canonical request, signed headers).

credential_scope

def credential_scope(date_stamp: String, region: String, service: String) -> String

string_to_sign

def string_to_sign(amz_date: String, scope: String, canonical_req: String) -> String

signing_key

def signing_key(secret_key: String, date_stamp: String, region: String, service: String) -> List[UInt8]

The four-step HMAC ladder: date, region, service, aws4_request.

sign

def sign(secret_key: String, date_stamp: String, region: String, service: String, to_sign: String) -> String

sign_request

def sign_request(method: String, path: String, params: List[QueryParam], headers: List[Header], payload_hash: String, access_key: String, secret_key: String, region: String, service: String, when: AmzTime, normalize: Bool = False) -> SignedRequest

Header-flavoured SigV4.

headers must already contain host, x-amz-date and, when a session token is in play, x-amz-security-token: which headers are signed is the caller's decision, and S3 in particular insists that x-amz-content-sha256 be among them.

presign_query

def presign_query(method: String, path: String, params: List[QueryParam], headers: List[Header], access_key: String, secret_key: String, session_token: String, region: String, service: String, when: AmzTime, expires_seconds: Int, normalize: Bool = False) -> String

Query-flavoured SigV4: returns the full canonical query string, X-Amz-Signature included.

The payload hash for a presigned URL is UNSIGNED-PAYLOAD: the point of presigning is that whoever follows the URL never sees the credentials, so they cannot be expected to hash a body the signer never had.

Aliases

comptime ALGORITHM = String("AWS4-HMAC-SHA256")
comptime UNSIGNED_PAYLOAD = String("UNSIGNED-PAYLOAD")
comptime EMPTY_SHA256 = String("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")

SHA-256 of the empty string — the payload hash of every bodyless request.