ExAwsSnsVerifier

CIHex.pmHex DocsDownloadsLicense

Verify AWS SNS HTTPS message authenticity โ€” RSA-SHA256 signature verification for Elixir applications.

ExAwsSnsVerifier validates Notification, SubscriptionConfirmation, and UnsubscribeConfirmation payloads sent via the AWS SNS HTTPS transport. It is the Elixir equivalent of Ruby's Aws::SNS::MessageVerifier.

No runtime dependencies โ€” uses :public_key for RSA verification and :httpc for certificate fetching.

Features

Installation

Add ex_aws_sns_verifier to your mix.exs:

def deps do
[
{:ex_aws_sns_verifier, "~> 0.2.0"}
]
end

Then run:

mix deps.get

Quick Start

1. One-shot verification

Pass the raw JSON body and a set of allowed topic ARNs:

raw_body = ~s({
"Type": "Notification",
"MessageId": "22b80b92-fdea-4c2c-8f9d-bdfb0c7bf324",
"TopicArn": "arn:aws:sns:us-east-1:123456789012:MyTopic",
"Message": "Hello from SNS!",
"Timestamp": "2026-05-13T12:00:00.000Z",
"SignatureVersion": "2",
"Signature": "...",
"SigningCertURL": "https://sns.us-east-1.amazonaws.com/...pem",
...
})
opts = [
allowed_topic_arns: ["arn:aws:sns:us-east-1:123456789012:MyTopic"]
]
{:ok, payload} = ExAwsSnsVerifier.verify(raw_body, opts)

2. With a Verifier struct (reusable config)

verifier = ExAwsSnsVerifier.new(
allowed_topic_arns: ["arn:aws:sns:us-east-1:123456789012:MyTopic"],
timestamp_window_seconds: 300 # 5 minutes
)
{:ok, payload} = ExAwsSnsVerifier.verify(verifier, raw_body)

3. Raise on failure

payload = ExAwsSnsVerifier.verify!(raw_body, opts)
# Raises ExAwsSnsVerifier.VerificationError on failure

4. With a Plug pipeline

# In your router or endpoint
plug ExAwsSnsVerifier.Plug,
allowed_topic_arns: ["arn:aws:sns:us-east-1:123456789012:MyTopic"]

The plug reads the raw body, verifies the SNS signature, and assigns {:ok, payload} or {:error, reason} to conn.assigns.sns_verification. On failure, the connection is halted with 403.

Configuration

Verifier options

OptionDefaultDescription
allowed_topic_arns(required)List of allowed TopicArn values
allowed_regionsAll commercial regionsList of AWS regions for SigningCertURL validation
timestamp_window_seconds3600Replay protection window in seconds
http_clientExAwsSnsVerifier.Cert.HttpClientModule implementing get/1 for cert fetching
cert_cacheExAwsSnsVerifier.Cert.CacheModule implementing get/1 and put/2

Custom HTTP client

Swap in your own HTTP client (Tesla, Req, Finch, etc.):

defmodule MyApp.MyHttpClient do
@behaviour ExAwsSnsVerifier.Cert.HttpClientBehaviour
@impl true
def get(url) do
# Return {:ok, body} or {:error, reason}
Req.get!(url).body
end
end
verifier = ExAwsSnsVerifier.new(
allowed_topic_arns: ["..."],
http_client: MyApp.MyHttpClient
)

Custom cert cache

defmodule MyApp.MyCache do
@behaviour ExAwsSnsVerifier.Cert.CacheBehaviour # get/1, put/2
@impl true
def get(key), do: # ...
@impl true
def put(key, value), do: # ...
end

Error Reasons

verify/2 returns {:error, reason} with one of the following atoms:

ErrorMeaning
:invalid_jsonBody is not valid JSON
:unknown_message_typeType is not Notification/SubscriptionConfirmation/UnsubscribeConfirmation
:missing_signature_versionNo SignatureVersion field
:unsupported_signature_versionOnly Version 2 (SHA256) supported
:missing_timestampNo Timestamp field
:invalid_timestampTimestamp is not valid ISO 8601
:timestamp_out_of_windowMessage is outside the replay window
:missing_topic_arnNo TopicArn field
:no_allowed_topicsAllowlist is empty
:topic_not_allowedTopicArn not in allowlist
:missing_signatureNo Signature field
:invalid_signature_encodingSignature is not valid Base64
:invalid_cert_urlSigningCertURL failed host/path validation
:missing_signing_cert_urlNo SigningCertURL field
:signature_invalidRSA-SHA256 signature does not verify

How it works

  1. Parse โ€” decodes the JSON body
  2. Validate type โ€” confirms one of the three supported message types
  3. Validate signature version โ€” only SignatureVersion 2 (RSA-SHA256)
  4. Validate timestamp โ€” checks the message is within the replay window
  5. Validate topic โ€” confirms the TopicArn is in the allowlist
  6. Build canonical string โ€” constructs the signed payload per AWS spec
  7. Fetch cert โ€” downloads and caches the signing certificate from SigningCertURL
  8. Verify signature โ€” RSA-SHA256 verification using :public_key

Development

git clone https://github.com/GustavoZiaugra/ex_aws_sns_verifier.git
cd ex_aws_sns_verifier
mix deps.get
mix compile --warnings-as-errors
# Run tests
mix test
# Quality checks
mix format --check-formatted
mix credo --strict
mix dialyzer
# Generate docs
mix docs

License

MIT ยฉ Gustavo Ziaugra