Skip to content

SnsMessageManager fails signature validation for messages with a whole-second timestamp (.000 ms) #7010

@henricook

Description

@henricook

Describe the bug

SnsMessageManager throws The computed signature did not match the expected signature for a small but steady fraction (~1 in 1000) of genuine, Amazon-signed SNS messages. The messages are valid - the SDK's verification seems to have a bug.

When it rebuilds the string-to-sign, SignatureValidator.buildCanonicalMessage(...) emits the timestamp via notification.timestamp().toString(). SNS signs over the literal wire value, which always carries millisecond precision (...:ss.SSSZ), but Instant#toString() omits the fraction when it is zero. So any message whose Timestamp lands exactly on a second (...:ss.000Z) gets recomputed as ...:ssZ, the canonical strings differ, and verification fails. The same round-trip is used for the SubscriptionConfirmation and UnsubscribeConfirmation canonical strings.

This bit us processing SES delivery/open/bounce events over SNS - roughly 0.1% of notifications silently fail validation and get dropped, which is exactly the 1-in-1000 chance of the millisecond component being 000. Only the all-zero .000 case is affected; non-zero milliseconds (e.g. .001, .010, .100) round-trip fine because Instant#toString() pads those to three digits.

Regression Issue

No - present since the module was introduced.

Expected Behavior

A genuine, Amazon-signed SNS message validates regardless of whether its timestamp happens to fall on a whole second.

Current Behavior

parseMessage throws:

software.amazon.awssdk.core.exception.SdkClientException: The computed signature did not match the expected signature

Reproduction Steps

The defect is the lossy timestamp round-trip used to build the string-to-sign:

String onTheWire  = "2024-01-01T00:00:00.000Z";          // what SNS sends and signs
String recomputed = Instant.parse(onTheWire).toString();
System.out.println(recomputed);                           // 2024-01-01T00:00:00Z   <- ".000" dropped
System.out.println(onTheWire.equals(recomputed));         // false

Because buildCanonicalMessage feeds timestamp().toString() into the canonical string, the recomputed value differs from the bytes SNS signed whenever the milliseconds are 000, so signature.verify(...) returns false. Confirmed end-to-end against a real Amazon-signed message: it verifies against the raw .000Z timestamp and fails against the Instant#toString() form.

Possible Solution

Build the timestamp portion with fixed millisecond precision instead of Instant#toString(), e.g.

DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ROOT).withZone(ZoneOffset.UTC)

I have a fix plus a failing/passing test ready and am happy to open a PR.

AWS Java SDK version used

2.44.12 (also reproduces on 2.45.1)

JDK version used

21 (Azul Zulu 21.0.11)

Operating System and version

Linux

Metadata

Metadata

Assignees

Labels

bugThis issue is a bug.p1This is a high priority issue

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions