diff --git a/services-custom/sns-message-manager/pom.xml b/services-custom/sns-message-manager/pom.xml
index fb5619d5e5c..9fa4f195f6d 100644
--- a/services-custom/sns-message-manager/pom.xml
+++ b/services-custom/sns-message-manager/pom.xml
@@ -68,6 +68,11 @@
sdk-core
${project.version}
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
software.amazon.awssdk
regions
@@ -93,11 +98,6 @@
httpclient5
${httpcomponents.client5.version}
-
- org.junit.jupiter
- junit-jupiter
- test
-
org.assertj
assertj-core
diff --git a/services-custom/sns-message-manager/src/main/java/software/amazon/awssdk/messagemanager/sns/internal/SignatureValidator.java b/services-custom/sns-message-manager/src/main/java/software/amazon/awssdk/messagemanager/sns/internal/SignatureValidator.java
new file mode 100644
index 00000000000..963e6901d93
--- /dev/null
+++ b/services-custom/sns-message-manager/src/main/java/software/amazon/awssdk/messagemanager/sns/internal/SignatureValidator.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.messagemanager.sns.internal;
+
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.util.StringJoiner;
+import software.amazon.awssdk.annotations.SdkInternalApi;
+import software.amazon.awssdk.core.SdkBytes;
+import software.amazon.awssdk.core.exception.SdkClientException;
+import software.amazon.awssdk.messagemanager.sns.model.SignatureVersion;
+import software.amazon.awssdk.messagemanager.sns.model.SnsMessage;
+import software.amazon.awssdk.messagemanager.sns.model.SnsNotification;
+import software.amazon.awssdk.messagemanager.sns.model.SnsSubscriptionConfirmation;
+import software.amazon.awssdk.messagemanager.sns.model.SnsUnsubscribeConfirmation;
+import software.amazon.awssdk.utils.Logger;
+import software.amazon.awssdk.utils.Validate;
+
+/**
+ * See
+ *
+ * The official documentation.
+ */
+@SdkInternalApi
+public final class SignatureValidator {
+ private static final Logger LOG = Logger.loggerFor(SignatureValidator.class);
+
+ private static final String MESSAGE = "Message";
+ private static final String MESSAGE_ID = "MessageId";
+ private static final String SUBJECT = "Subject";
+ private static final String SUBSCRIBE_URL = "SubscribeURL";
+ private static final String TIMESTAMP = "Timestamp";
+ private static final String TOKEN = "Token";
+ private static final String TOPIC_ARN = "TopicArn";
+ private static final String TYPE = "Type";
+
+ private static final String NEWLINE = "\n";
+
+ public void validateSignature(SnsMessage message, PublicKey publicKey) {
+ Validate.paramNotNull(message, "message");
+ Validate.paramNotNull(publicKey, "publicKey");
+
+ SdkBytes messageSignature = message.signature();
+ if (messageSignature == null) {
+ throw SdkClientException.create("Message signature cannot be null");
+ }
+
+ SignatureVersion signatureVersion = message.signatureVersion();
+ if (signatureVersion == null) {
+ throw SdkClientException.create("Message signature version cannot be null");
+ }
+
+ if (message.timestamp() == null) {
+ throw SdkClientException.create("Message timestamp cannot be null");
+ }
+
+ String canonicalMessage = buildCanonicalMessage(message);
+ LOG.debug(() -> String.format("Canonical message: %s%n", canonicalMessage));
+
+ Signature signature = getSignature(signatureVersion);
+
+ verifySignature(canonicalMessage, messageSignature, publicKey, signature);
+ }
+
+ private static String buildCanonicalMessage(SnsMessage message) {
+ switch (message.type()) {
+ case NOTIFICATION:
+ return buildCanonicalMessage((SnsNotification) message);
+ case SUBSCRIPTION_CONFIRMATION:
+ return buildCanonicalMessage((SnsSubscriptionConfirmation) message);
+ case UNSUBSCRIBE_CONFIRMATION:
+ return buildCanonicalMessage((SnsUnsubscribeConfirmation) message);
+ default:
+ throw new IllegalStateException(String.format("Unsupported SNS message type: %s", message.type()));
+ }
+ }
+
+ private static String buildCanonicalMessage(SnsNotification notification) {
+ StringJoiner joiner = new StringJoiner(NEWLINE, "", NEWLINE);
+ joiner.add(MESSAGE).add(notification.message());
+ joiner.add(MESSAGE_ID).add(notification.messageId());
+
+ if (notification.subject() != null) {
+ joiner.add(SUBJECT).add(notification.subject());
+ }
+
+ joiner.add(TIMESTAMP).add(notification.timestamp().toString());
+ joiner.add(TOPIC_ARN).add(notification.topicArn());
+ joiner.add(TYPE).add(notification.type().toString());
+
+ return joiner.toString();
+ }
+
+ // Message, MessageId, SubscribeURL, Timestamp, Token, TopicArn, and Type.
+ private static String buildCanonicalMessage(SnsSubscriptionConfirmation message) {
+ StringJoiner joiner = new StringJoiner(NEWLINE, "", NEWLINE);
+ joiner.add(MESSAGE).add(message.message());
+ joiner.add(MESSAGE_ID).add(message.messageId());
+ joiner.add(SUBSCRIBE_URL).add(message.subscribeUrl().toString());
+ joiner.add(TIMESTAMP).add(message.timestamp().toString());
+ joiner.add(TOKEN).add(message.token());
+ joiner.add(TOPIC_ARN).add(message.topicArn());
+ joiner.add(TYPE).add(message.type().toString());
+
+ return joiner.toString();
+ }
+
+ private static String buildCanonicalMessage(SnsUnsubscribeConfirmation message) {
+ StringJoiner joiner = new StringJoiner(NEWLINE, "", NEWLINE);
+ joiner.add(MESSAGE).add(message.message());
+ joiner.add(MESSAGE_ID).add(message.messageId());
+ joiner.add(SUBSCRIBE_URL).add(message.subscribeUrl().toString());
+ joiner.add(TIMESTAMP).add(message.timestamp().toString());
+ joiner.add(TOKEN).add(message.token());
+ joiner.add(TOPIC_ARN).add(message.topicArn());
+ joiner.add(TYPE).add(message.type().toString());
+
+ return joiner.toString();
+ }
+
+ private static void verifySignature(String canonicalMessage, SdkBytes messageSignature, PublicKey publicKey,
+ Signature signature) {
+
+ try {
+ signature.initVerify(publicKey);
+ signature.update(canonicalMessage.getBytes(StandardCharsets.UTF_8));
+
+ boolean isValid = signature.verify(messageSignature.asByteArray());
+
+ if (!isValid) {
+ throw SdkClientException.create("The computed signature did not match the expected signature");
+ }
+ } catch (InvalidKeyException e) {
+ throw SdkClientException.create("The public key is invalid", e);
+ } catch (SignatureException e) {
+ throw SdkClientException.create("The signature is invalid", e);
+ }
+ }
+
+ private static Signature getSignature(SignatureVersion signatureVersion) {
+ try {
+ switch (signatureVersion) {
+ case VERSION_1:
+ return Signature.getInstance("SHA1withRSA");
+ case VERSION_2:
+ return Signature.getInstance("SHA256withRSA");
+ default:
+ throw new IllegalArgumentException("Unsupported signature version: " + signatureVersion);
+ }
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("Unable to create Signature for " + signatureVersion, e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/services-custom/sns-message-manager/src/test/java/software/amazon/awssdk/messagemanager/sns/internal/SignatureValidatorTest.java b/services-custom/sns-message-manager/src/test/java/software/amazon/awssdk/messagemanager/sns/internal/SignatureValidatorTest.java
new file mode 100644
index 00000000000..fe30a03d16a
--- /dev/null
+++ b/services-custom/sns-message-manager/src/test/java/software/amazon/awssdk/messagemanager/sns/internal/SignatureValidatorTest.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.messagemanager.sns.internal;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.InputStream;
+import java.net.URI;
+import java.security.PublicKey;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.time.Instant;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import software.amazon.awssdk.core.SdkBytes;
+import software.amazon.awssdk.core.exception.SdkClientException;
+import software.amazon.awssdk.messagemanager.sns.model.SignatureVersion;
+import software.amazon.awssdk.messagemanager.sns.model.SnsMessage;
+import software.amazon.awssdk.messagemanager.sns.model.SnsNotification;
+
+class SignatureValidatorTest {
+ private static final String RESOURCE_ROOT = "/software/amazon/awssdk/messagemanager/sns/internal/";
+ private static final String SIGNING_CERT_RESOURCE = "SimpleNotificationService-7506a1e35b36ef5a444dd1a8e7cc3ed8.pem";
+ private static final SignatureValidator VALIDATOR = new SignatureValidator();
+ private static X509Certificate signingCertificate;
+
+ @BeforeAll
+ static void setup() throws CertificateException {
+ InputStream is = resourceAsStream(SIGNING_CERT_RESOURCE);
+ CertificateFactory factory = CertificateFactory.getInstance("X.509");
+ signingCertificate = (X509Certificate) factory.generateCertificate(is);
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("validMessages")
+ void validateSignature_signatureValid_doesNotThrow(TestCase tc) {
+ SnsMessageUnmarshaller unmarshaller = new SnsMessageUnmarshaller();
+ SnsMessage msg = unmarshaller.unmarshall(resourceAsStream(tc.messageJsonResource));
+ VALIDATOR.validateSignature(msg, signingCertificate.getPublicKey());
+ }
+
+ @Test
+ void validateSignature_signatureMismatch_throws() {
+ SnsNotification notification = SnsNotification.builder()
+ .message("hello world")
+ .messageId("message-id")
+ .timestamp(Instant.now())
+ .signature(SdkBytes.fromByteArray(new byte[256]))
+ .signatureVersion(SignatureVersion.VERSION_1)
+ .build();
+
+ assertThatThrownBy(() -> VALIDATOR.validateSignature(notification, signingCertificate.getPublicKey()))
+ .isInstanceOf(SdkClientException.class)
+ .hasMessageContaining("The computed signature did not match the expected signature");
+ }
+
+ @Test
+ void validateSignature_signatureMissing_throws() {
+ SnsNotification notification = SnsNotification.builder()
+ .subject("hello world")
+ .message("hello world")
+ .messageId("message-id")
+ .timestamp(Instant.now())
+ .unsubscribeUrl(URI.create("https://my-test-service.amazonaws.com"))
+ .signingCertUrl(URI.create("https://my-test-service.amazonaws.com/cert"
+ + ".pem"))
+ .signatureVersion(SignatureVersion.VERSION_1)
+ .build();
+
+ assertThatThrownBy(() -> VALIDATOR.validateSignature(notification, signingCertificate.getPublicKey()))
+ .isInstanceOf(SdkClientException.class)
+ .hasMessage("Message signature cannot be null");
+ }
+
+ @Test
+ void validateSignature_timestampMissing_throws() {
+ SnsNotification notification = SnsNotification.builder()
+ .subject("hello world")
+ .message("hello world")
+ .messageId("message-id")
+ .signature(SdkBytes.fromByteArray(new byte[256]))
+ .unsubscribeUrl(URI.create("https://my-test-service.amazonaws.com"))
+ .signingCertUrl(URI.create("https://my-test-service.amazonaws.com/cert"
+ + ".pem"))
+ .signatureVersion(SignatureVersion.VERSION_1)
+ .build();
+
+ assertThatThrownBy(() -> VALIDATOR.validateSignature(notification, signingCertificate.getPublicKey()))
+ .isInstanceOf(SdkClientException.class)
+ .hasMessage("Message timestamp cannot be null");
+ }
+
+ @Test
+ void validateSignature_signatureVersionMissing_throws() {
+ SnsNotification notification = SnsNotification.builder()
+ .subject("hello world")
+ .message("hello world")
+ .messageId("message-id")
+ .signature(SdkBytes.fromByteArray(new byte[256]))
+ .timestamp(Instant.now())
+ .unsubscribeUrl(URI.create("https://my-test-service.amazonaws.com"))
+ .signingCertUrl(URI.create("https://my-test-service.amazonaws.com/cert"
+ + ".pem"))
+ .build();
+
+
+ assertThatThrownBy(() -> VALIDATOR.validateSignature(notification, signingCertificate.getPublicKey()))
+ .isInstanceOf(SdkClientException.class)
+ .hasMessage("Message signature version cannot be null");
+ }
+
+ @Test
+ void validateSignature_certInvalid_throws() throws CertificateException {
+ SnsNotification notification = SnsNotification.builder()
+ .signature(SdkBytes.fromByteArray(new byte[1]))
+ .signatureVersion(SignatureVersion.VERSION_1)
+ .timestamp(Instant.now())
+ .build();
+
+ PublicKey badKey = mock(PublicKey.class);
+ when(badKey.getFormat()).thenReturn("X.509");
+ when(badKey.getAlgorithm()).thenReturn("RSA");
+ when(badKey.getEncoded()).thenReturn(new byte[1]);
+
+ assertThatThrownBy(() -> VALIDATOR.validateSignature(notification, badKey))
+ .isInstanceOf(SdkClientException.class)
+ .hasMessage("The public key is invalid");
+ }
+
+ @Test
+ void validateSignature_signatureInvalid_throws() throws CertificateException {
+ SnsNotification notification = SnsNotification.builder()
+ .subject("hello world")
+ .message("hello world")
+ .messageId("message-id")
+ .signature(SdkBytes.fromByteArray(new byte[1]))
+ .signatureVersion(SignatureVersion.VERSION_1)
+ .timestamp(Instant.now())
+ .unsubscribeUrl(URI.create("https://my-test-service.amazonaws.com"))
+ .signingCertUrl(URI.create("https://my-test-service.amazonaws.com/cert"
+ + ".pem"))
+ .build();
+
+ assertThatThrownBy(() -> VALIDATOR.validateSignature(notification, signingCertificate.getPublicKey()))
+ .isInstanceOf(SdkClientException.class)
+ .hasMessage("The signature is invalid");
+ }
+
+ private static List validMessages() {
+ return Stream.of(
+ new TestCase("Notification - No Subject", "test-notification-no-subject.json"),
+ new TestCase("Notification - Version 2 signature", "test-notification-signature-v2.json"),
+ new TestCase("Notification with subject", "test-notification-with-subject.json"),
+ new TestCase("Subscription confirmation", "test-subscription-confirmation.json"),
+ new TestCase("Unsubscribe confirmation", "test-unsubscribe-confirmation.json")
+ )
+ .collect(Collectors.toList());
+ }
+
+ private static InputStream resourceAsStream(String resourceName) {
+ return SignatureValidatorTest.class.getResourceAsStream(RESOURCE_ROOT + resourceName);
+ }
+
+ private static class TestCase {
+ private String desription;
+ private String messageJsonResource;
+
+ public TestCase(String desription, String messageJsonResource) {
+ this.desription = desription;
+ this.messageJsonResource = messageJsonResource;
+ }
+
+ @Override
+ public String toString() {
+ return desription;
+ }
+ }
+}
\ No newline at end of file
diff --git a/services-custom/sns-message-manager/src/test/resources/software/amazon/awssdk/messagemanager/sns/internal/SimpleNotificationService-7506a1e35b36ef5a444dd1a8e7cc3ed8.pem b/services-custom/sns-message-manager/src/test/resources/software/amazon/awssdk/messagemanager/sns/internal/SimpleNotificationService-7506a1e35b36ef5a444dd1a8e7cc3ed8.pem
new file mode 100644
index 00000000000..f5f615db34a
--- /dev/null
+++ b/services-custom/sns-message-manager/src/test/resources/software/amazon/awssdk/messagemanager/sns/internal/SimpleNotificationService-7506a1e35b36ef5a444dd1a8e7cc3ed8.pem
@@ -0,0 +1,33 @@
+-----BEGIN CERTIFICATE-----
+MIIFxTCCBK2gAwIBAgIQBkyRNHAyygLkObkxH5ju+zANBgkqhkiG9w0BAQsFADA8
+MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRwwGgYDVQQDExNBbWF6b24g
+UlNBIDIwNDggTTAxMB4XDTI1MTExMDAwMDAwMFoXDTI2MTAxNDIzNTk1OVowHDEa
+MBgGA1UEAxMRc25zLmFtYXpvbmF3cy5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQChbVNQ+DEaT5WN+l+bg0iEeF666Z6LDFc1KJr4lNl0odcXQSsk
+KsZYCMVdp4I/Ikk9zWyckRTMkmQGYimpD+bTsMCP5C/sJIU0E9Y2OXjjl5Videkq
+qnRL9VdE5N41Rvks7S/5MpYcSmCsmaHfwcv82Gvx2zm9CrA5Xu7K2ZqhzRQPCRSP
+mHTHQr4ED63ujE+qTpPgQZ0TvgmcrAc08Z75Z7Zzs17aTNc3YhAtQx9WowN8uiHk
+kgc+ONNdtwKvWZNmnbmxMHYtYc291WCP1K1Nh4TV5XNyjIebzQKDrqTCN0ItAr1x
+e6Bl3BYS3SG9XHAy0q3ekITzQsKypcuaK6dTAgMBAAGjggLhMIIC3TAfBgNVHSME
+GDAWgBSBuA5jiokSGOX6OztQlZ/m5ZAThTAdBgNVHQ4EFgQUBQEUzzzuVJAKzAlR
+d0Q//W6HvZUwHAYDVR0RBBUwE4IRc25zLmFtYXpvbmF3cy5jb20wEwYDVR0gBAww
+CjAIBgZngQwBAgEwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMB
+MDsGA1UdHwQ0MDIwMKAuoCyGKmh0dHA6Ly9jcmwucjJtMDEuYW1hem9udHJ1c3Qu
+Y29tL3IybTAxLmNybDB1BggrBgEFBQcBAQRpMGcwLQYIKwYBBQUHMAGGIWh0dHA6
+Ly9vY3NwLnIybTAxLmFtYXpvbnRydXN0LmNvbTA2BggrBgEFBQcwAoYqaHR0cDov
+L2NydC5yMm0wMS5hbWF6b250cnVzdC5jb20vcjJtMDEuY2VyMAwGA1UdEwEB/wQC
+MAAwggF/BgorBgEEAdZ5AgQCBIIBbwSCAWsBaQB3ANdtfRDRp/V3wsfpX9cAv/mC
+yTNaZeHQswFzF8DIxWl3AAABmnAb33MAAAQDAEgwRgIhANXAgMEZWU326cqxqwnf
+ntD0XcBjRbmvVMOp3hZ8boPXAiEAkI3NTMXcdLCChHak1ipOae7K7r3spMGUMk6Q
+cEUKqBEAdgDCMX5XRRmjRe5/ON6ykEHrx8IhWiK/f9W1rXaa2Q5SzQAAAZpwG9+u
+AAAEAwBHMEUCIQD+Qyok8NYaFuQkccW+XIMmxHxj6Yka2wjtg6UN/tbU5wIgcp4z
+8MyC/dUfeLRuHAt0owtVgEu7xXqrMBOYlbXrfC4AdgDLOPcViXyEoURfW8Hd+8lu
+8ppZzUcKaQWFsMsUwxRY5wAAAZpwG9+yAAAEAwBHMEUCIQCmu1jQUG9A2br7OqwM
+FvQXtBX10yIvvDa6a2Ov1Zgg6gIgKlqRvN6WhX0pSHAN2bKmjRwIUI3X9kjS/A/m
+DYULaV4wDQYJKoZIhvcNAQELBQADggEBAIbTQWQhqrG6SzwLq79tKCpI0aL0xlD+
+/jrGPxpwTlK7rtAArHiEVh/bsmwLYRlIxPBiU+NE/kqeT0lhJA6/e3X3KRmnpJ6u
+Z70PJGYwmZbSRsFom0vGFBMncX0qYx1iD0dooPeAkkSnM5j81rBh+FepV3HfF+tA
+9gaglWLsU7Z/A0fdn9D32UPreVHloOut5EyEinvXRUAWZco9CRloyEXJr7Mxq9MZ
+vYOK+5wuSPHnPij+fGlQsPxEgoTnCmU03PqNS49kL13jjEnYYdijqL+4rfHtuZbc
+4hlVoZfttwg8wD3m5brhQ+GkBBNYN8HFtdVy/BnFi+SPJqv6BgB3Y00=
+-----END CERTIFICATE-----
diff --git a/services-custom/sns-message-manager/src/test/resources/software/amazon/awssdk/messagemanager/sns/internal/test-notification-no-subject.json b/services-custom/sns-message-manager/src/test/resources/software/amazon/awssdk/messagemanager/sns/internal/test-notification-no-subject.json
new file mode 100644
index 00000000000..45b2e4a0121
--- /dev/null
+++ b/services-custom/sns-message-manager/src/test/resources/software/amazon/awssdk/messagemanager/sns/internal/test-notification-no-subject.json
@@ -0,0 +1,11 @@
+{
+ "Type" : "Notification",
+ "MessageId" : "c0e5a280-53c9-5a98-93bb-c0a560b3d192",
+ "TopicArn" : "arn:aws:sns:us-west-2:131990247566:dongie-standard-topic",
+ "Message" : "This notification has no subject.",
+ "Timestamp" : "2026-03-06T19:55:38.903Z",
+ "SignatureVersion" : "1",
+ "Signature" : "h5/2kLNyPKvMH+ipOA1kueaA1GLFlcKbO/9o6GbH7BtKk/8lM8krPFDrV+Lwuq8yZDXTxeSXHR8pG1w9MkKQ3VlsHhMigy+5ejIsb+ZfpkDDO6dOACbkTg1twy9tLeKCyPfU01OokKTR6jlKJGCgXXDQm5Q1Fs4NfUz22xGqteycG1GIioqq2Hvd1PLsDkcoyUAhciugGFcZKIGjUbBtdhMTPFX1ed8iH+Y/p6o/eD0G2i614oRSXYRJyV2iNpl54LCCiCrAafAJAM5NXAJJq9CWyTjxHFC18FnPqSukW1/6Qa502oujhPJ/pyl+6mMBoF6LjnpbIyR8/Gmm+RY6fg==",
+ "SigningCertURL" : "https://sns.us-west-2.amazonaws.com/SimpleNotificationService-7506a1e35b36ef5a444dd1a8e7cc3ed8.pem",
+ "UnsubscribeURL" : "https://sns.us-west-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-west-2:131990247566:dongie-standard-topic:b24e11a2-06bd-4e73-aa29-0ed2de3b41b5"
+}
\ No newline at end of file
diff --git a/services-custom/sns-message-manager/src/test/resources/software/amazon/awssdk/messagemanager/sns/internal/test-notification-signature-v2.json b/services-custom/sns-message-manager/src/test/resources/software/amazon/awssdk/messagemanager/sns/internal/test-notification-signature-v2.json
new file mode 100644
index 00000000000..2f946f54080
--- /dev/null
+++ b/services-custom/sns-message-manager/src/test/resources/software/amazon/awssdk/messagemanager/sns/internal/test-notification-signature-v2.json
@@ -0,0 +1,11 @@
+{
+ "Type" : "Notification",
+ "MessageId" : "9d2c5580-b61d-550c-8583-37a582870907",
+ "TopicArn" : "arn:aws:sns:us-west-2:131990247566:dongie-standard-topic",
+ "Message" : "This message uses signature version 2.",
+ "Timestamp" : "2026-03-06T20:00:26.893Z",
+ "SignatureVersion" : "2",
+ "Signature" : "kGNJ7FIM5DJxRv9bdoSJBty2WHpADFWrJ0ZQU/A2sALy6tBE5hqfUvPRlklTJ+PACFMguoFOO7W6ac/Bs4gR+JBRFhx4598X8/HWzrmaLrtB2f7nzViHbpzYN+u+OewXhxjj2lNsu0bLKv6fogkACytDIOhjZylbfJoPphDYAB7T3Ila7u+K3m2wLHH4k1cPYKyBf5FI/kAoQAVsY6qFHeb6CWfHsdM98I2gwyEvwJSzW0iyYBfqckmthQwH3Ex53jrhQlZylXFDYmstSJAdvbLeGqIUxKyk+O25wYgBjbbbeR+2qE2UCGEQrk1Bq018wmTxdHZmDhoBjJXiGmFkfg==",
+ "SigningCertURL" : "https://sns.us-west-2.amazonaws.com/SimpleNotificationService-7506a1e35b36ef5a444dd1a8e7cc3ed8.pem",
+ "UnsubscribeURL" : "https://sns.us-west-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-west-2:131990247566:dongie-standard-topic:b24e11a2-06bd-4e73-aa29-0ed2de3b41b5"
+}
\ No newline at end of file
diff --git a/services-custom/sns-message-manager/src/test/resources/software/amazon/awssdk/messagemanager/sns/internal/test-notification-with-subject.json b/services-custom/sns-message-manager/src/test/resources/software/amazon/awssdk/messagemanager/sns/internal/test-notification-with-subject.json
new file mode 100644
index 00000000000..50636ad7165
--- /dev/null
+++ b/services-custom/sns-message-manager/src/test/resources/software/amazon/awssdk/messagemanager/sns/internal/test-notification-with-subject.json
@@ -0,0 +1,12 @@
+{
+ "Type" : "Notification",
+ "MessageId" : "26858270-545e-5848-82a2-e05566af3dcf",
+ "TopicArn" : "arn:aws:sns:us-west-2:131990247566:dongie-standard-topic",
+ "Subject" : "My message subject",
+ "Message" : "Hello! This message has a subject.",
+ "Timestamp" : "2026-03-06T19:53:21.424Z",
+ "SignatureVersion" : "1",
+ "Signature" : "jvNSgHVOF9KUOKI3bmQZhURD0kDN6txhfjiyuvnV95Dw6b2SRa7nq3AhM8agP0KUCIrRgKHqUlN6K2XBA35Lzkx1eI5RT0oXIN00pvvzGxSz0ddnuSGmDJM7bd/b1vl2RlLjz+KvkhzEtspxLnxjLsGl2EpOmztt+um7owb4475sOLdGTx1QiFV/8HqB2J0DyjeWdRZvu7Xpsf1Yd2rgmijFoz8x89Vw8pIksjczr4q16pxdh/XKWjbN2uPoC+3EeYdljL8KUvLYH9ueHSs5E9qq2iLdze4+FtANnE73XRvvSGE0KI+FGOvz1igest6qon3g1/CVy4mq3n19TsCSmA==",
+ "SigningCertURL" : "https://sns.us-west-2.amazonaws.com/SimpleNotificationService-7506a1e35b36ef5a444dd1a8e7cc3ed8.pem",
+ "UnsubscribeURL" : "https://sns.us-west-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-west-2:131990247566:dongie-standard-topic:b24e11a2-06bd-4e73-aa29-0ed2de3b41b5"
+}
\ No newline at end of file
diff --git a/services-custom/sns-message-manager/src/test/resources/software/amazon/awssdk/messagemanager/sns/internal/test-subscription-confirmation.json b/services-custom/sns-message-manager/src/test/resources/software/amazon/awssdk/messagemanager/sns/internal/test-subscription-confirmation.json
new file mode 100644
index 00000000000..bc1ea5de289
--- /dev/null
+++ b/services-custom/sns-message-manager/src/test/resources/software/amazon/awssdk/messagemanager/sns/internal/test-subscription-confirmation.json
@@ -0,0 +1,12 @@
+{
+ "Type" : "SubscriptionConfirmation",
+ "MessageId" : "f3f2fc87-c426-4c2f-9044-b55890c0939c",
+ "Token" : "2336412f37fb687f5d51e6e2425929f52e8e353134fe72d55ef8b330217d82da14007cbccefffd35b695feb8f0294d01fd9644f41e1ddfad261519a2c0bac743ff375eeb64530147d2da57f3acc54739da14c8dbd48a9d58ce996eff0d498ea6b4b9f58720d8959b52b99b8de148081720d7da17434e376614a00c992e7823e4",
+ "TopicArn" : "arn:aws:sns:us-west-2:131990247566:dongie-standard-topic",
+ "Message" : "You have chosen to subscribe to the topic arn:aws:sns:us-west-2:131990247566:dongie-standard-topic.\nTo confirm the subscription, visit the SubscribeURL included in this message.",
+ "SubscribeURL" : "https://sns.us-west-2.amazonaws.com/?Action=ConfirmSubscription&TopicArn=arn:aws:sns:us-west-2:131990247566:dongie-standard-topic&Token=2336412f37fb687f5d51e6e2425929f52e8e353134fe72d55ef8b330217d82da14007cbccefffd35b695feb8f0294d01fd9644f41e1ddfad261519a2c0bac743ff375eeb64530147d2da57f3acc54739da14c8dbd48a9d58ce996eff0d498ea6b4b9f58720d8959b52b99b8de148081720d7da17434e376614a00c992e7823e4",
+ "Timestamp" : "2026-03-06T19:31:32.895Z",
+ "SignatureVersion" : "1",
+ "Signature" : "Ce2Ax8UbEq0RrVH7gsjP9WZ9bzif7dUkAY4OH99Pv7FSrvs8cK+uiH3a6ig54rXWUnZRUejk8K6KeK5Q80O9iJp8KbPRKdQ0PkJ3YASdabhfK0e7evC05YR2pFoS6nNaqNzT7HrR1d1Z9iLoFxluo2Dzb7kWVB7e9jp/y+ZBXRzRIaZgscM84kahBc6QgXOFJA1/PFA4cB5qaa6tBcUFtTE63o9vJ4wMmKk93W9MoszC+dNJeVFr8VHmt3toR+3JauBCZnGdgXgsDhni4PduSfWJJxEpuOK41iYf1hW+xFNFFsv46siTFiHdHiYDfI7/VlaY4spMgkq3EndRr7rHzQ==",
+ "SigningCertURL" : "https://sns.us-west-2.amazonaws.com/SimpleNotificationService-7506a1e35b36ef5a444dd1a8e7cc3ed8.pem"
+}
\ No newline at end of file
diff --git a/services-custom/sns-message-manager/src/test/resources/software/amazon/awssdk/messagemanager/sns/internal/test-unsubscribe-confirmation.json b/services-custom/sns-message-manager/src/test/resources/software/amazon/awssdk/messagemanager/sns/internal/test-unsubscribe-confirmation.json
new file mode 100644
index 00000000000..378cb97be67
--- /dev/null
+++ b/services-custom/sns-message-manager/src/test/resources/software/amazon/awssdk/messagemanager/sns/internal/test-unsubscribe-confirmation.json
@@ -0,0 +1,12 @@
+{
+ "Type" : "UnsubscribeConfirmation",
+ "MessageId" : "0b71e34e-42c3-486a-9e32-b1928ec994b5",
+ "Token" : "2336412f37fb687f5d51e6e2425929f52e8e353134fd0284eae5672847af082a982c7dd18215a1485e04e559716f8b42054594757f42dee2d63c46fd3a0fa59b4d688de03db956f8bf9f8cb3167587a28b74c5d325e6a7594d24a43ac534de2dfca614ca89cebd490342c721c30a76ea03def13673437a6a3e450642580119d0",
+ "TopicArn" : "arn:aws:sns:us-west-2:131990247566:dongie-standard-topic",
+ "Message" : "You have chosen to deactivate subscription arn:aws:sns:us-west-2:131990247566:dongie-standard-topic:b24e11a2-06bd-4e73-aa29-0ed2de3b41b5.\nTo cancel this operation and restore the subscription, visit the SubscribeURL included in this message.",
+ "SubscribeURL" : "https://sns.us-west-2.amazonaws.com/?Action=ConfirmSubscription&TopicArn=arn:aws:sns:us-west-2:131990247566:dongie-standard-topic&Token=2336412f37fb687f5d51e6e2425929f52e8e353134fd0284eae5672847af082a982c7dd18215a1485e04e559716f8b42054594757f42dee2d63c46fd3a0fa59b4d688de03db956f8bf9f8cb3167587a28b74c5d325e6a7594d24a43ac534de2dfca614ca89cebd490342c721c30a76ea03def13673437a6a3e450642580119d0",
+ "Timestamp" : "2026-03-06T20:02:25.038Z",
+ "SignatureVersion" : "2",
+ "Signature" : "RLW/cGWZAdY96iEcvXGmAZQ851DUyWehJIUMHT3Qa0/kX7Wu2QimI44TfE8d9hTqyYFreA6ln4AS67m225DuySpBeiUBtzB/ibiU7jdZmfSiT/I/RTEU3eD0lHHZIYW0M7HAlhHyaSXdcSAbtfnhMBMION93IVl6UOR4kWYdJaJ17/u3by3zlSRs8wCIpj06S6BzuzxrINZPJl+NKZYCVdMlrjuT4etX/UfZkC0Tjchpmu/CQcuimAgswlesm8cVue0WqNPXkOr8h7HmsiGVXPN1jjkYKf2/tVJyBxur/lSksMopER8wtAIsrqY1caAZSI8YJkIndg/b+7eCwkjTBA==",
+ "SigningCertURL" : "https://sns.us-west-2.amazonaws.com/SimpleNotificationService-7506a1e35b36ef5a444dd1a8e7cc3ed8.pem"
+}
\ No newline at end of file