# SigV4 — `@suaveplan/sms-providers` ## Purpose Inline AWS Signature V4 signer used by `AwsSnsProvider`. Replaces the `@aws-sdk/signature-v4` dependency tree (15+ MB) with an under-80-LOC implementation of the canonical-request → string-to-sign → signing-key → final-signature chain. HMAC computations are delegated to the injectable `HmacSign` seam from `@suaveplan/sms-providers`, so the signer stays universal and tests can substitute a deterministic adapter. The signer returns a fresh headers object containing the input headers plus `Host`, `X-Amz-Date`, `X-Amz-Content-Sha256`, the optional `X-Amz-Security-Token` (when a session token is supplied), and the `Authorization` header in the documented form `AWS4-HMAC-SHA256 Credential=…, SignedHeaders=…, Signature=<64 hex>`. ## Features - 64-character lowercase-hex signature per the AWS spec. - Canonical request built from method + path + canonical headers (lower-cased keys, trimmed values) + signed-headers list + body-sha256 hex. - `dateStamp` (`YYYYMMDD`) and `amzDate` (`YYYYMMDDTHHMMSSZ`) derived from a caller-supplied `Date` so tests can pin the clock and produce stable signatures. - Signing key chain: `kDate → kRegion → kService → kSigning` via HMAC-SHA256 over `AWS4`, `region`, `service`, `aws4_request`. - Body SHA-256 hashing routed through `globalThis.crypto.subtle.digest("SHA-256", …)` (universal: Bun, browsers, Workers, Deno, Node 19+). ## Usage: Basic ```ts import { defaultHmac } from "@suaveplan/sms-providers"; import { signRequest, sha256OfUtf8 } from "@suaveplan/sms-providers/aws-sns/sigv4"; const body = "Action=Publish&Message=hi"; const out = await signRequest({ accessKeyId: "AKIA…", secretAccessKey: "…", region: "us-east-1", service: "sns", method: "POST", host: "sns.us-east-1.amazonaws.com", path: "/", headers: { "Content-Type": "application/x-www-form-urlencoded" }, bodySha256Hex: await sha256OfUtf8(body), now: new Date(), hmac: defaultHmac, }); // out.headers.Authorization === "AWS4-HMAC-SHA256 Credential=AKIA…//us-east-1/sns/aws4_request, SignedHeaders=…, Signature=<64 hex>" ``` ## Usage: Advanced ```ts import { signRequest, sha256OfUtf8 } from "@suaveplan/sms-providers/aws-sns/sigv4"; import { createHmac } from "node:crypto"; // Drive the signer with a Node-side HMAC adapter and a pinned date so // the resulting Authorization header is byte-stable between test runs. const hmac = async (algo: "SHA-1" | "SHA-256", key: Uint8Array, msg: Uint8Array) => new Uint8Array( createHmac(algo === "SHA-1" ? "sha1" : "sha256", Buffer.from(key)) .update(Buffer.from(msg)) .digest() ); const out = await signRequest({ accessKeyId: "AKIDEXAMPLE", secretAccessKey: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY", region: "us-east-1", service: "sns", method: "POST", host: "sns.us-east-1.amazonaws.com", path: "/", headers: { "Content-Type": "application/x-www-form-urlencoded" }, bodySha256Hex: await sha256OfUtf8("Action=Publish"), now: new Date("2026-04-24T10:00:00Z"), hmac, }); ``` ## API Reference | Symbol | Kind | Notes | |---|---|---| | `signRequest(input)` | function | Returns `{ headers }` with the SigV4 signature applied. | | `sha256OfUtf8(s)` | function | SHA-256-of-UTF-8 helper returning lowercase hex. | | `SignRequestInput` | interface | Required inputs to the signer. | | `SignRequestOutput` | interface | Returned headers object. | ## Implementation Notes The canonical-request newline before the body-sha256 is intentional per the AWS docs (the canonical request ends with `\n`, no trailing newline). The signed-headers list is computed by lower-casing every input key, sorting, and joining with `;` — this matches the canonical-request header rendering exactly.