Skip to content

test(auth): check SigV4 decisions against the AWS SDK signer - #792

Open
LeeroyHannigan wants to merge 3 commits into
mainfrom
test/sigv4-differential
Open

LeeroyHannigan wants to merge 3 commits into
mainfrom
test/sigv4-differential

Conversation

@LeeroyHannigan

@LeeroyHannigan LeeroyHannigan commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

What

This PR adds the test suite crates/auth/tests/sigv4_differential. The suite signs requests with aws-sigv4, the SigV4 signer in the AWS SDK for Rust. It passes each request to BuiltinAuthProvider::authenticate, the call that the HTTP layer makes for a DynamoDB request. The credential store is in memory and holds a long-term key and a session key with its token.

  • Accept checks (2,017): the server must accept a request as the signer made it. The canonical request that the server builds must equal the one aws-sigv4 signed. The requests use the header sets that DynamoDB clients sign and send. They also cover 42 region names (39 AWS region codes plus local, localhost and LOCAL), five endpoints and both keys. Others use header values with extra spaces, mixed-case header names, large bodies and clock offsets inside the skew window. Some change, add or remove unsigned headers after signing.
  • Reject checks (12,536): each request changes after signing, in a part that the signature covers. It must get the error and message of the check that covers that part. These requests carry new signed header values, fewer signed headers, or one more name in SignedHeaders. Some carry different body bytes, with and without x-amz-content-sha256, or a timestamp moved by one second. Others use the wrong secret, a different credential scope or signature, or another session token or none. A request signed for s3 has a valid signature, so only the service check can refuse it.
  • Skew window (1,088 of the reject checks): each request has a date outside the 15 minute window. The time check must refuse it, before the scope date check and the signature comparison run. Here the suite accepts InvalidSignatureException or UnrecognizedClientException, with any message. The server's error and message differ from DynamoDB's, and A request outside the 15 minute SigV4 window gets UnrecognizedClientException; DynamoDB answers InvalidSignatureException #805 tracks that difference.
  • Generated requests: proptest makes 256 of the requests from a fixed seed, so every run checks the same cases.

aws-sigv4, aws-credential-types and proptest are already in Cargo.lock. They are dev-dependencies of extenddb-auth, and no package version changes.

Scope: the requests are the ones that DynamoDB clients send. That is POST / with no query string, header values in visible ASCII, and each signed header sent once. The harness builds the header map and body that authenticate receives. It does not run the HTTP layer.

Merge order: #355 and #512 change BuiltinAuthProvider::new and some of the checks that this suite asserts. The PR that merges after this one updates the harness to match.

Why

The server checks SigV4 signatures with its own implementation. The unit tests check it against the two AWS test vectors and against hand-written cases. This suite checks it against the SDK signer, across the header, body, scope and token combinations that clients produce.

The first version of the skew window cases expected UnrecognizedClientException and the server's own message. DynamoDB answers InvalidSignatureException. Its message starts "Signature expired:" for a past date and "Signature not yet current:" for a future date. DynamoDB also checks the time before it compares the signature. We measured these answers in us-east-1 on 2026-11-03, two times for each case.

Testing done

  • cargo test -p extenddb-auth: 274 unit tests, the 13 suite tests and 3 doctests pass. The unit tests include the AWS SigV4 test vectors. The suite takes about 2.5 seconds in a debug build.
  • Each of these changes to the server code makes the suite fail:
    • no service check
    • no scope date check
    • the payload hash of an empty body, or the hash from x-amz-content-sha256
    • repeated inner spaces kept
    • no session token check
    • no time check
    • the time check after the signature comparison
    • a time check that answers IncompleteSignature
  • A time check that answers InvalidSignatureException with DynamoDB's message prefixes passes the suite.
  • cargo +1.97.0 clippy --all-targets -- -D warnings, cargo fmt --check and cargo +1.88.0 test -p extenddb-auth are clean. The four license-notice checks pass.

Checklist

  • I have read CONTRIBUTING.md
  • All tests pass (cargo test --workspace)
  • Code is formatted (cargo fmt --check)
  • Clippy is clean (cargo clippy -- -W clippy::pedantic)
  • I have added or updated tests for new functionality
  • I have updated documentation if behavior changed
  • Breaking changes are noted below (if any)
  • If this changes the wire protocol, Storage trait, auth model, on-disk
    format, or public CLI surface, an RFC has been accepted or is linked
    below. Otherwise, an ADR captures the decision (link below).

ADR / RFC: n/a. The PR adds tests and changes no server code.


By submitting this pull request, I confirm that my contribution is made under
the terms of the Apache License 2.0 and I agree to the Developer Certificate of
Origin (DCO). See CONTRIBUTING.md for details.

Sign requests with aws-sigv4, the SigV4 signer in the AWS SDK for Rust,
and pass them to BuiltinAuthProvider::authenticate. A request as signed
must verify, and the server's canonical request must equal the signer's.
A change to any part the signature covers must be refused with the
message of the check that covers it.

The fixed corpus covers the header sets DynamoDB clients sign and send,
39 region codes and three local endpoint names, a long-term key and a
session key. 256 more requests come from proptest with a fixed seed.
aws-sigv4, aws-credential-types and proptest are already in Cargo.lock
and are dev-dependencies only.
@LeeroyHannigan LeeroyHannigan added the v1 Needed for the 1.0 release label Nov 3, 2026
@LeeroyHannigan LeeroyHannigan added the release-blocker Must ship for its milestone's release label Nov 3, 2026
@navareamol navareamol self-assigned this Nov 3, 2026
@navareamol navareamol added this to the 1.0 milestone Nov 3, 2026

@navareamol navareamol left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things:

  1. The skew window cases expect UnrecognizedClientException and our own message. Has anyone checked what real DynamoDB returns? Details inline on mutate.rs.
  2. The description says "39 region codes and three local endpoint names". local, localhost and LOCAL are in REGIONS, so they're region names. ENDPOINTS has five entries.

I'll approve once 1 is checked (or the assertion is loosened) and the description is fixed.

wire: changed(&signed.wire, |wire| {
wire.replace("x-amz-date", date.clone())
}),
expected: Refusal::Client,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test expects UnrecognizedClientException and our own message when the request's time is outside the 15 minute window. That copies what our server does today. Has anyone checked what real DynamoDB returns for this? If it's the same, great. If not, I'd check only that the request is refused here, and file the error type as a separate issue, so this test doesn't lock in an answer that might be wrong.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked, and it differs. DynamoDB answers InvalidSignatureException for a date outside the window. The message starts "Signature expired:" for a past date and "Signature not yet current:" for a future one (us-east-1, 2026-11-03, each case twice). Our server answers UnrecognizedClientException with its own text, so the old assertion locked in the wrong answer.

062f439 changes the four cases per shape. The time check must refuse each one, with either error and any message except the scope date and signature mismatch messages. A request with X-Amz-Date moved after signing also breaks the signature, so these cases still check that the time check runs first, as it does on DynamoDB. The error type and message are now #805.

The skew window cases expected UnrecognizedClientException and the
server's own message. DynamoDB answers InvalidSignatureException (#805).
Its message starts "Signature expired:" for a past date and "Signature
not yet current:" for a future one.

Each of the four requests per shape must now be refused by the time
check. Either error passes, with any message except those of the scope
date check and the signature comparison. A request with X-Amz-Date moved
after signing also breaks the signature, so it still checks that the
time check runs first, as it does on DynamoDB. Its credential scope date
moves with X-Amz-Date. The scope date check then passes when the move
crosses midnight UTC.
@LeeroyHannigan

Copy link
Copy Markdown
Collaborator Author

Thanks. Both points are in 062f439 and the updated description.

  1. The skew window cases: details in the thread on mutate.rs. DynamoDB answers differently, so the cases now check only that the time check refuses the request, and A request outside the 15 minute SigV4 window gets UnrecognizedClientException; DynamoDB answers InvalidSignatureException #805 tracks the error and message.
  2. The description now says 42 region names (39 AWS region codes plus local, localhost and LOCAL) and five endpoints.

Cargo.lock conflicted. Main moved extenddb-auth from hmac to aws-lc-rs.
The lock file is main's, with extenddb-auth's dependency list extended
by the three test dependencies this branch adds: aws-credential-types,
aws-sigv4 and proptest.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-blocker Must ship for its milestone's release security v1 Needed for the 1.0 release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants