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
Describe the bug
SnsMessageManagerthrowsThe computed signature did not match the expected signaturefor 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 vianotification.timestamp().toString(). SNS signs over the literal wire value, which always carries millisecond precision (...:ss.SSSZ), butInstant#toString()omits the fraction when it is zero. So any message whoseTimestamplands exactly on a second (...:ss.000Z) gets recomputed as...:ssZ, the canonical strings differ, and verification fails. The same round-trip is used for theSubscriptionConfirmationandUnsubscribeConfirmationcanonical 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.000case is affected; non-zero milliseconds (e.g..001,.010,.100) round-trip fine becauseInstant#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
parseMessagethrows:Reproduction Steps
The defect is the lossy timestamp round-trip used to build the string-to-sign:
Because
buildCanonicalMessagefeedstimestamp().toString()into the canonical string, the recomputed value differs from the bytes SNS signed whenever the milliseconds are000, sosignature.verify(...)returns false. Confirmed end-to-end against a real Amazon-signed message: it verifies against the raw.000Ztimestamp and fails against theInstant#toString()form.Possible Solution
Build the timestamp portion with fixed millisecond precision instead of
Instant#toString(), e.g.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