mirror of
https://github.com/penpot/penpot.git
synced 2026-09-30 15:56:17 +00:00
🐛 Fix SNS signature verification field sets per AWS spec
Address critical code review findings: - Remove 'Signature' field from string-to-sign (it's the output, not input) - Differentiate V1 vs V2 field sets per AWS SNS documentation: - V1 Notification: Message, MessageId, Subject, Timestamp, TopicArn, Type - V1 SubscriptionConfirmation: adds Token, SubscribeURL (excludes SigningCertURL, SignatureVersion) - V2 Notification/Subscription: all fields except Signature - Add 'Token' field to SubscriptionConfirmation (required by AWS spec) - Add AWS documentation URL comments for future reference - Improve error handling in fetch-certificate and verify-signature - Add end-to-end signature verification tests with real key pairs - Add test resources (certificate and private key) for signature tests See: https://docs.aws.amazon.com/sns/latest/dg/sns-verify-signature-of-message.html Closes #11092 AI-assisted-by: qwen3.7-plus
This commit is contained in:
parent
94a30a4634
commit
874f681506
@ -78,7 +78,7 @@
|
|||||||
org.clojure/data.csv {:mvn/version "1.1.1"}
|
org.clojure/data.csv {:mvn/version "1.1.1"}
|
||||||
com.clojure-goes-fast/clj-async-profiler {:mvn/version "2.0.0-beta1"}
|
com.clojure-goes-fast/clj-async-profiler {:mvn/version "2.0.0-beta1"}
|
||||||
mockery/mockery {:mvn/version "0.1.4"}}
|
mockery/mockery {:mvn/version "0.1.4"}}
|
||||||
:extra-paths ["test" "dev"]}
|
:extra-paths ["test" "test/resources" "dev"]}
|
||||||
|
|
||||||
:build
|
:build
|
||||||
{:extra-deps
|
{:extra-deps
|
||||||
|
|||||||
@ -35,7 +35,8 @@
|
|||||||
|
|
||||||
(defn- valid-sns-url?
|
(defn- valid-sns-url?
|
||||||
"Validates that a URL originates from an SNS endpoint.
|
"Validates that a URL originates from an SNS endpoint.
|
||||||
Only accepts sns.<region>.amazonaws.com hosts."
|
Only accepts sns.<region>.amazonaws.com hosts.
|
||||||
|
See: https://docs.aws.amazon.com/sns/latest/dg/sns-verify-signature-of-message.html"
|
||||||
[url]
|
[url]
|
||||||
(when (string? url)
|
(when (string? url)
|
||||||
(try
|
(try
|
||||||
@ -49,21 +50,52 @@
|
|||||||
(catch Exception _
|
(catch Exception _
|
||||||
false))))
|
false))))
|
||||||
|
|
||||||
(def ^:private notification-fields
|
;; AWS SNS Signature Verification Field Sets
|
||||||
["Message" "MessageId" "Subject" "Timestamp"
|
;; See: https://docs.aws.amazon.com/sns/latest/dg/sns-verify-signature-of-message.html
|
||||||
"TopicArn" "Type" "SigningCertURL" "SignatureVersion" "Signature"])
|
;;
|
||||||
|
;; Signature Version 1 signs only specific fields (excludes SigningCertURL, SignatureVersion, Signature)
|
||||||
|
;; Signature Version 2 signs all fields except Signature
|
||||||
|
;;
|
||||||
|
;; IMPORTANT: The "Signature" field is NEVER part of the string-to-sign (it's the output, not input)
|
||||||
|
|
||||||
(def ^:private subscription-fields
|
;; V1 Notification: Message, MessageId, Subject (if present), Timestamp, TopicArn, Type
|
||||||
["Message" "MessageId" "SubscribeURL" "Timestamp"
|
(def ^:private v1-notification-fields
|
||||||
"TopicArn" "Type" "SigningCertURL" "SignatureVersion" "Signature"])
|
["Message" "MessageId" "Subject" "Timestamp" "TopicArn" "Type"])
|
||||||
|
|
||||||
|
;; V1 SubscriptionConfirmation: Message, MessageId, SubscribeURL, Timestamp, Token, TopicArn, Type
|
||||||
|
(def ^:private v1-subscription-fields
|
||||||
|
["Message" "MessageId" "SubscribeURL" "Timestamp" "Token" "TopicArn" "Type"])
|
||||||
|
|
||||||
|
;; V2 Notification: All fields except Signature
|
||||||
|
(def ^:private v2-notification-fields
|
||||||
|
["Message" "MessageId" "Subject" "Timestamp" "TopicArn" "Type"
|
||||||
|
"SigningCertURL" "SignatureVersion"])
|
||||||
|
|
||||||
|
;; V2 SubscriptionConfirmation: All fields except Signature (includes Token)
|
||||||
|
(def ^:private v2-subscription-fields
|
||||||
|
["Message" "MessageId" "SubscribeURL" "Timestamp" "Token" "TopicArn" "Type"
|
||||||
|
"SigningCertURL" "SignatureVersion"])
|
||||||
|
|
||||||
(defn- build-string-to-sign
|
(defn- build-string-to-sign
|
||||||
"Builds the string-to-sign for AWS SNS signature verification.
|
"Builds the string-to-sign for AWS SNS signature verification.
|
||||||
See: https://docs.aws.amazon.com/sns/latest/dg/sns-verify-signature-of-message.html"
|
See: https://docs.aws.amazon.com/sns/latest/dg/sns-verify-signature-of-message.html"
|
||||||
[body]
|
[body]
|
||||||
(let [fields (if (= "SubscriptionConfirmation" (get body "Type"))
|
(let [sig-version (get body "SignatureVersion")
|
||||||
subscription-fields
|
msg-type (get body "Type")
|
||||||
notification-fields)]
|
fields (cond
|
||||||
|
(= "1" sig-version)
|
||||||
|
(if (= "SubscriptionConfirmation" msg-type)
|
||||||
|
v1-subscription-fields
|
||||||
|
v1-notification-fields)
|
||||||
|
|
||||||
|
(= "2" sig-version)
|
||||||
|
(if (= "SubscriptionConfirmation" msg-type)
|
||||||
|
v2-subscription-fields
|
||||||
|
v2-notification-fields)
|
||||||
|
|
||||||
|
:else
|
||||||
|
(throw (ex-info "Unsupported SNS signature version"
|
||||||
|
{:version sig-version})))]
|
||||||
(->> fields
|
(->> fields
|
||||||
(filter #(contains? body %))
|
(filter #(contains? body %))
|
||||||
(map #(str % "\n" (get body %) "\n"))
|
(map #(str % "\n" (get body %) "\n"))
|
||||||
@ -71,36 +103,52 @@
|
|||||||
|
|
||||||
(defn- fetch-certificate
|
(defn- fetch-certificate
|
||||||
"Fetches the X.509 certificate from the given URL.
|
"Fetches the X.509 certificate from the given URL.
|
||||||
Returns an InputStream that must be closed by the caller."
|
Returns an InputStream that must be closed by the caller.
|
||||||
|
See: https://docs.aws.amazon.com/sns/latest/dg/sns-verify-signature-of-message.html"
|
||||||
[cfg cert-url]
|
[cfg cert-url]
|
||||||
(let [response (http/req cfg {:uri cert-url :method :get :timeout 10000}
|
(let [response (http/req cfg {:uri cert-url :method :get :timeout 10000}
|
||||||
{:sync? true :response-type :input-stream})]
|
{:sync? true :response-type :input-stream})]
|
||||||
(when (= 200 (:status response))
|
(when-not (= 200 (:status response))
|
||||||
(:body response))))
|
(l/wrn :hint "failed to fetch SNS signing certificate"
|
||||||
|
:action "sns-cert-fetch-failed"
|
||||||
|
:status (:status response)
|
||||||
|
:cert-url cert-url)
|
||||||
|
(ex/raise :type :internal :code :cert-fetch-failed))
|
||||||
|
(:body response)))
|
||||||
|
|
||||||
(defn- verify-signature
|
(defn- verify-signature
|
||||||
"Verifies the RSA signature of the message."
|
"Verifies the RSA signature of the message.
|
||||||
|
See: https://docs.aws.amazon.com/sns/latest/dg/sns-verify-signature-of-message.html"
|
||||||
[cfg body]
|
[cfg body]
|
||||||
(let [cert-url (get body "SigningCertURL")
|
(let [cert-url (get body "SigningCertURL")
|
||||||
signature (get body "Signature")
|
signature (get body "Signature")
|
||||||
sig-version (get body "SignatureVersion")
|
sig-version (get body "SignatureVersion")
|
||||||
string-sign (build-string-to-sign body)
|
|
||||||
algorithm (case sig-version
|
algorithm (case sig-version
|
||||||
"1" "SHA1withRSA"
|
"1" "SHA1withRSA"
|
||||||
"2" "SHA256withRSA"
|
"2" "SHA256withRSA"
|
||||||
nil)]
|
nil)]
|
||||||
(when-not algorithm
|
(when-not algorithm
|
||||||
(throw (ex-info "Unsupported SNS signature version"
|
(throw (ex-info "Unsupported SNS signature version"
|
||||||
{:version sig-version})))
|
{:type :validation :version sig-version})))
|
||||||
(when (and cert-url signature)
|
(when (and cert-url signature)
|
||||||
(try
|
(try
|
||||||
(with-open [cert-stream (fetch-certificate cfg cert-url)]
|
(let [string-sign (build-string-to-sign body)]
|
||||||
(let [cf (CertificateFactory/getInstance "X.509")
|
(with-open [cert-stream (fetch-certificate cfg cert-url)]
|
||||||
cert (.generateCertificate cf cert-stream)
|
(let [cf (CertificateFactory/getInstance "X.509")
|
||||||
sig (Signature/getInstance algorithm)]
|
cert (.generateCertificate cf cert-stream)
|
||||||
(.initVerify sig (.getPublicKey cert))
|
sig (Signature/getInstance algorithm)]
|
||||||
(.update sig (.getBytes string-sign java.nio.charset.StandardCharsets/UTF_8))
|
(.initVerify sig (.getPublicKey cert))
|
||||||
(.verify sig (.decode (Base64/getDecoder) signature))))
|
(.update sig (.getBytes string-sign java.nio.charset.StandardCharsets/UTF_8))
|
||||||
|
(.verify sig (.decode (Base64/getDecoder) signature)))))
|
||||||
|
(catch clojure.lang.ExceptionInfo e
|
||||||
|
(let [data (ex-data e)]
|
||||||
|
(if (= :validation (:type data))
|
||||||
|
(throw e)
|
||||||
|
(do
|
||||||
|
(l/wrn :hint "SNS signature verification exception"
|
||||||
|
:action "sns-signature-verification-exception"
|
||||||
|
:cause e)
|
||||||
|
false))))
|
||||||
(catch Exception e
|
(catch Exception e
|
||||||
(l/wrn :hint "SNS signature verification exception"
|
(l/wrn :hint "SNS signature verification exception"
|
||||||
:action "sns-signature-verification-exception"
|
:action "sns-signature-verification-exception"
|
||||||
@ -109,7 +157,8 @@
|
|||||||
|
|
||||||
(defn- verify-sns-message!
|
(defn- verify-sns-message!
|
||||||
"Verifies the AWS SNS message signature and URL validity.
|
"Verifies the AWS SNS message signature and URL validity.
|
||||||
Throws if verification fails."
|
Throws if verification fails.
|
||||||
|
See: https://docs.aws.amazon.com/sns/latest/dg/sns-verify-signature-of-message.html"
|
||||||
[cfg body]
|
[cfg body]
|
||||||
(let [cert-url (get body "SigningCertURL")
|
(let [cert-url (get body "SigningCertURL")
|
||||||
subscribe-url (get body "SubscribeURL")
|
subscribe-url (get body "SubscribeURL")
|
||||||
|
|||||||
@ -308,66 +308,130 @@
|
|||||||
(t/is (true? (#'awsns/valid-sns-url? "https://sns.us-east-1.amazonaws.com/cert.pem")))
|
(t/is (true? (#'awsns/valid-sns-url? "https://sns.us-east-1.amazonaws.com/cert.pem")))
|
||||||
(t/is (true? (#'awsns/valid-sns-url? "https://sns.ap-southeast-1.amazonaws.com/cert.pem"))))
|
(t/is (true? (#'awsns/valid-sns-url? "https://sns.ap-southeast-1.amazonaws.com/cert.pem"))))
|
||||||
|
|
||||||
(t/deftest test-verify-signature-version-1
|
;; Helper to load test certificate and private key from resources
|
||||||
(let [keypair (java.security.KeyPairGenerator/getInstance "RSA")
|
;; See: https://docs.aws.amazon.com/sns/latest/dg/sns-verify-signature-of-message.html
|
||||||
_ (.initialize keypair 2048)
|
(defn- load-test-cert-and-key
|
||||||
kp (.generateKeyPair keypair)
|
"Loads the test certificate and private key from test resources."
|
||||||
private-key (.getPrivate kp)
|
[]
|
||||||
public-key (.getPublic kp)
|
(let [cert-pem (slurp (clojure.java.io/resource "sns-test-cert.pem"))
|
||||||
|
key-pem (slurp (clojure.java.io/resource "sns-test-key.pem"))
|
||||||
|
;; Parse certificate
|
||||||
|
cert-bytes (.getBytes (-> cert-pem
|
||||||
|
(clojure.string/replace "-----BEGIN CERTIFICATE-----" "")
|
||||||
|
(clojure.string/replace "-----END CERTIFICATE-----" "")
|
||||||
|
(clojure.string/replace #"\s+" ""))
|
||||||
|
java.nio.charset.StandardCharsets/UTF_8)
|
||||||
|
cert-input (java.io.ByteArrayInputStream. (.decode (java.util.Base64/getDecoder) cert-bytes))
|
||||||
|
cf (java.security.cert.CertificateFactory/getInstance "X.509")
|
||||||
|
cert (.generateCertificate cf cert-input)
|
||||||
|
;; Parse private key
|
||||||
|
key-bytes (.getBytes (-> key-pem
|
||||||
|
(clojure.string/replace "-----BEGIN PRIVATE KEY-----" "")
|
||||||
|
(clojure.string/replace "-----END PRIVATE KEY-----" "")
|
||||||
|
(clojure.string/replace #"\s+" ""))
|
||||||
|
java.nio.charset.StandardCharsets/UTF_8)
|
||||||
|
key-spec (java.security.spec.PKCS8EncodedKeySpec. (.decode (java.util.Base64/getDecoder) key-bytes))
|
||||||
|
kf (java.security.KeyFactory/getInstance "RSA")
|
||||||
|
private-key (.generatePrivate kf key-spec)]
|
||||||
|
{:cert cert
|
||||||
|
:cert-bytes (.getEncoded cert)
|
||||||
|
:private-key private-key
|
||||||
|
:public-key (.getPublicKey cert)}))
|
||||||
|
|
||||||
;; Create a simple message
|
(t/deftest test-verify-signature-end-to-end-v1
|
||||||
msg {"Type" "Notification"
|
(let [{:keys [cert-bytes private-key]} (load-test-cert-and-key)
|
||||||
"MessageId" "test-msg-1"
|
|
||||||
"TopicArn" "arn:aws:sns:us-east-1:123:topic"
|
msg {"Type" "Notification"
|
||||||
"Message" "test message"
|
"MessageId" "test-msg-1"
|
||||||
"Timestamp" "2021-02-04T14:41:37.020Z"
|
"TopicArn" "arn:aws:sns:us-east-1:123:topic"
|
||||||
"SigningCertURL" "https://sns.us-east-1.amazonaws.com/cert.pem"
|
"Message" "test message"
|
||||||
"SignatureVersion" "1"}
|
"Timestamp" "2021-02-04T14:41:37.020Z"
|
||||||
|
"SigningCertURL" "https://sns.us-east-1.amazonaws.com/cert.pem"
|
||||||
|
"SignatureVersion" "1"}
|
||||||
|
|
||||||
;; Build string to sign
|
|
||||||
string-to-sign (#'awsns/build-string-to-sign msg)
|
string-to-sign (#'awsns/build-string-to-sign msg)
|
||||||
|
sig (java.security.Signature/getInstance "SHA1withRSA")
|
||||||
;; Sign with SHA1
|
_ (.initSign sig private-key)
|
||||||
sig (java.security.Signature/getInstance "SHA1withRSA")
|
_ (.update sig (.getBytes string-to-sign java.nio.charset.StandardCharsets/UTF_8))
|
||||||
_ (.initSign sig private-key)
|
signature (.encodeToString (java.util.Base64/getEncoder) (.sign sig))
|
||||||
_ (.update sig (.getBytes string-to-sign java.nio.charset.StandardCharsets/UTF_8))
|
|
||||||
signature (.encodeToString (java.util.Base64/getEncoder) (.sign sig))
|
|
||||||
|
|
||||||
msg-with-sig (assoc msg "Signature" signature)]
|
msg-with-sig (assoc msg "Signature" signature)]
|
||||||
|
|
||||||
;; Test that signature verification logic would work
|
(with-redefs [awsns/fetch-certificate (fn [_ _]
|
||||||
(t/is (string? string-to-sign))
|
(java.io.ByteArrayInputStream. cert-bytes))]
|
||||||
(t/is (string? signature))))
|
(t/is (true? (#'awsns/verify-signature {} msg-with-sig))))))
|
||||||
|
|
||||||
(t/deftest test-verify-signature-version-2
|
(t/deftest test-verify-signature-end-to-end-v2
|
||||||
(let [keypair (java.security.KeyPairGenerator/getInstance "RSA")
|
(let [{:keys [cert-bytes private-key]} (load-test-cert-and-key)
|
||||||
_ (.initialize keypair 2048)
|
|
||||||
kp (.generateKeyPair keypair)
|
|
||||||
private-key (.getPrivate kp)
|
|
||||||
|
|
||||||
;; Create a simple message
|
msg {"Type" "Notification"
|
||||||
msg {"Type" "Notification"
|
"MessageId" "test-msg-2"
|
||||||
"MessageId" "test-msg-2"
|
"TopicArn" "arn:aws:sns:us-east-1:123:topic"
|
||||||
"TopicArn" "arn:aws:sns:us-east-1:123:topic"
|
"Message" "test message"
|
||||||
"Message" "test message"
|
"Timestamp" "2021-02-04T14:41:37.020Z"
|
||||||
"Timestamp" "2021-02-04T14:41:37.020Z"
|
"SigningCertURL" "https://sns.us-east-1.amazonaws.com/cert.pem"
|
||||||
"SigningCertURL" "https://sns.us-east-1.amazonaws.com/cert.pem"
|
"SignatureVersion" "2"}
|
||||||
"SignatureVersion" "2"}
|
|
||||||
|
|
||||||
;; Build string to sign
|
|
||||||
string-to-sign (#'awsns/build-string-to-sign msg)
|
string-to-sign (#'awsns/build-string-to-sign msg)
|
||||||
|
sig (java.security.Signature/getInstance "SHA256withRSA")
|
||||||
;; Sign with SHA256
|
_ (.initSign sig private-key)
|
||||||
sig (java.security.Signature/getInstance "SHA256withRSA")
|
_ (.update sig (.getBytes string-to-sign java.nio.charset.StandardCharsets/UTF_8))
|
||||||
_ (.initSign sig private-key)
|
signature (.encodeToString (java.util.Base64/getEncoder) (.sign sig))
|
||||||
_ (.update sig (.getBytes string-to-sign java.nio.charset.StandardCharsets/UTF_8))
|
|
||||||
signature (.encodeToString (java.util.Base64/getEncoder) (.sign sig))
|
|
||||||
|
|
||||||
msg-with-sig (assoc msg "Signature" signature)]
|
msg-with-sig (assoc msg "Signature" signature)]
|
||||||
|
|
||||||
;; Test that signature verification logic would work
|
(with-redefs [awsns/fetch-certificate (fn [_ _]
|
||||||
(t/is (string? string-to-sign))
|
(java.io.ByteArrayInputStream. cert-bytes))]
|
||||||
(t/is (string? signature))))
|
(t/is (true? (#'awsns/verify-signature {} msg-with-sig))))))
|
||||||
|
|
||||||
|
(t/deftest test-verify-signature-end-to-end-subscription-confirmation
|
||||||
|
(let [{:keys [cert-bytes private-key]} (load-test-cert-and-key)
|
||||||
|
|
||||||
|
msg {"Type" "SubscriptionConfirmation"
|
||||||
|
"MessageId" "test-msg-3"
|
||||||
|
"TopicArn" "arn:aws:sns:us-east-1:123:topic"
|
||||||
|
"Message" "You have chosen to subscribe"
|
||||||
|
"Timestamp" "2021-02-04T14:41:37.020Z"
|
||||||
|
"Token" "test-token-123"
|
||||||
|
"SubscribeURL" "https://sns.us-east-1.amazonaws.com/confirm"
|
||||||
|
"SigningCertURL" "https://sns.us-east-1.amazonaws.com/cert.pem"
|
||||||
|
"SignatureVersion" "1"}
|
||||||
|
|
||||||
|
string-to-sign (#'awsns/build-string-to-sign msg)
|
||||||
|
sig (java.security.Signature/getInstance "SHA1withRSA")
|
||||||
|
_ (.initSign sig private-key)
|
||||||
|
_ (.update sig (.getBytes string-to-sign java.nio.charset.StandardCharsets/UTF_8))
|
||||||
|
signature (.encodeToString (java.util.Base64/getEncoder) (.sign sig))
|
||||||
|
msg-with-sig (assoc msg "Signature" signature)]
|
||||||
|
|
||||||
|
(with-redefs [awsns/fetch-certificate (fn [_ _]
|
||||||
|
(java.io.ByteArrayInputStream. cert-bytes))]
|
||||||
|
(t/is (true? (#'awsns/verify-signature {} msg-with-sig))))))
|
||||||
|
|
||||||
|
(t/deftest test-verify-signature-rejects-wrong-key
|
||||||
|
(let [{:keys [cert-bytes]} (load-test-cert-and-key)
|
||||||
|
;; Generate a different keypair for signing
|
||||||
|
keypair-gen (java.security.KeyPairGenerator/getInstance "RSA")
|
||||||
|
_ (.initialize keypair-gen 2048)
|
||||||
|
kp (.generateKeyPair keypair-gen)
|
||||||
|
wrong-private-key (.getPrivate kp)
|
||||||
|
|
||||||
|
msg {"Type" "Notification"
|
||||||
|
"MessageId" "test-msg-4"
|
||||||
|
"TopicArn" "arn:aws:sns:us-east-1:123:topic"
|
||||||
|
"Message" "test message"
|
||||||
|
"Timestamp" "2021-02-04T14:41:37.020Z"
|
||||||
|
"SigningCertURL" "https://sns.us-east-1.amazonaws.com/cert.pem"
|
||||||
|
"SignatureVersion" "1"}
|
||||||
|
|
||||||
|
string-to-sign (#'awsns/build-string-to-sign msg)
|
||||||
|
sig (java.security.Signature/getInstance "SHA1withRSA")
|
||||||
|
_ (.initSign sig wrong-private-key)
|
||||||
|
_ (.update sig (.getBytes string-to-sign java.nio.charset.StandardCharsets/UTF_8))
|
||||||
|
signature (.encodeToString (java.util.Base64/getEncoder) (.sign sig))
|
||||||
|
msg-with-sig (assoc msg "Signature" signature)]
|
||||||
|
|
||||||
|
(with-redefs [awsns/fetch-certificate (fn [_ _]
|
||||||
|
(java.io.ByteArrayInputStream. cert-bytes))]
|
||||||
|
(t/is (false? (#'awsns/verify-signature {} msg-with-sig))))))
|
||||||
|
|
||||||
(t/deftest test-verify-signature-rejects-unsupported-version
|
(t/deftest test-verify-signature-rejects-unsupported-version
|
||||||
(let [msg {"Type" "Notification"
|
(let [msg {"Type" "Notification"
|
||||||
@ -379,11 +443,10 @@
|
|||||||
"SignatureVersion" "3"
|
"SignatureVersion" "3"
|
||||||
"Signature" "fake=="}]
|
"Signature" "fake=="}]
|
||||||
|
|
||||||
;; Should throw exception for unsupported version
|
|
||||||
(t/is (thrown? clojure.lang.ExceptionInfo
|
(t/is (thrown? clojure.lang.ExceptionInfo
|
||||||
(#'awsns/verify-signature {} msg)))))
|
(#'awsns/verify-signature {} msg)))))
|
||||||
|
|
||||||
(t/deftest test-build-string-to-sign-notification
|
(t/deftest test-build-string-to-sign-v1-notification
|
||||||
(let [msg {"Type" "Notification"
|
(let [msg {"Type" "Notification"
|
||||||
"MessageId" "msg-123"
|
"MessageId" "msg-123"
|
||||||
"TopicArn" "arn:aws:sns:eu-central-1:123:topic"
|
"TopicArn" "arn:aws:sns:eu-central-1:123:topic"
|
||||||
@ -399,8 +462,30 @@
|
|||||||
(t/is (.contains result "TopicArn"))
|
(t/is (.contains result "TopicArn"))
|
||||||
(t/is (.contains result "Message"))
|
(t/is (.contains result "Message"))
|
||||||
(t/is (.contains result "Timestamp"))
|
(t/is (.contains result "Timestamp"))
|
||||||
|
;; V1 does NOT include SigningCertURL, SignatureVersion, or Signature
|
||||||
|
(t/is (not (.contains result "SigningCertURL")))
|
||||||
|
(t/is (not (.contains result "SignatureVersion")))
|
||||||
|
(t/is (not (.contains result "Signature")))))
|
||||||
|
|
||||||
|
(t/deftest test-build-string-to-sign-v2-notification
|
||||||
|
(let [msg {"Type" "Notification"
|
||||||
|
"MessageId" "msg-123"
|
||||||
|
"TopicArn" "arn:aws:sns:eu-central-1:123:topic"
|
||||||
|
"Message" "{\"notificationType\":\"Bounce\"}"
|
||||||
|
"Timestamp" "2021-02-04T14:41:37.020Z"
|
||||||
|
"SigningCertURL" "https://sns.eu-central-1.amazonaws.com/cert.pem"
|
||||||
|
"SignatureVersion" "2"
|
||||||
|
"Signature" "abc123=="}
|
||||||
|
result (#'awsns/build-string-to-sign msg)]
|
||||||
|
(t/is (string? result))
|
||||||
|
(t/is (.contains result "MessageId"))
|
||||||
|
(t/is (.contains result "TopicArn"))
|
||||||
|
;; V2 includes SigningCertURL and SignatureVersion but NOT Signature
|
||||||
(t/is (.contains result "SigningCertURL"))
|
(t/is (.contains result "SigningCertURL"))
|
||||||
(t/is (.contains result "SignatureVersion"))))
|
(t/is (.contains result "SignatureVersion"))
|
||||||
|
;; Check that "Signature\n" (the field name) is NOT in the result
|
||||||
|
;; Note: "SignatureVersion" contains "Signature" as substring, so we check for the exact field pattern
|
||||||
|
(t/is (not (.contains result "Signature\n")))))
|
||||||
|
|
||||||
(t/deftest test-build-string-to-sign-subscription-confirmation
|
(t/deftest test-build-string-to-sign-subscription-confirmation
|
||||||
(let [msg {"Type" "SubscriptionConfirmation"
|
(let [msg {"Type" "SubscriptionConfirmation"
|
||||||
@ -408,6 +493,7 @@
|
|||||||
"TopicArn" "arn:aws:sns:eu-central-1:123:topic"
|
"TopicArn" "arn:aws:sns:eu-central-1:123:topic"
|
||||||
"Message" "You have chosen to subscribe"
|
"Message" "You have chosen to subscribe"
|
||||||
"Timestamp" "2021-02-04T14:41:37.020Z"
|
"Timestamp" "2021-02-04T14:41:37.020Z"
|
||||||
|
"Token" "test-token-123"
|
||||||
"SigningCertURL" "https://sns.eu-central-1.amazonaws.com/cert.pem"
|
"SigningCertURL" "https://sns.eu-central-1.amazonaws.com/cert.pem"
|
||||||
"SignatureVersion" "1"
|
"SignatureVersion" "1"
|
||||||
"Signature" "xyz789=="
|
"Signature" "xyz789=="
|
||||||
@ -415,7 +501,10 @@
|
|||||||
result (#'awsns/build-string-to-sign msg)]
|
result (#'awsns/build-string-to-sign msg)]
|
||||||
(t/is (string? result))
|
(t/is (string? result))
|
||||||
(t/is (.contains result "SubscribeURL"))
|
(t/is (.contains result "SubscribeURL"))
|
||||||
(t/is (.contains result "https://sns.eu-central-1.amazonaws.com/confirm"))))
|
(t/is (.contains result "https://sns.eu-central-1.amazonaws.com/confirm"))
|
||||||
|
;; Token must be included for SubscriptionConfirmation
|
||||||
|
(t/is (.contains result "Token"))
|
||||||
|
(t/is (.contains result "test-token-123"))))
|
||||||
|
|
||||||
(t/deftest test-handle-request-returns-4xx-for-invalid-signature
|
(t/deftest test-handle-request-returns-4xx-for-invalid-signature
|
||||||
(let [body (j/write-str
|
(let [body (j/write-str
|
||||||
|
|||||||
19
backend/test/resources/sns-test-cert.pem
Normal file
19
backend/test/resources/sns-test-cert.pem
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDGDCCAgCgAwIBAgIJAMp8qfcfOi+AMA0GCSqGSIb3DQEBDAUAMDoxCzAJBgNV
|
||||||
|
BAYTAlVTMQ0wCwYDVQQHEwRUZXN0MQ0wCwYDVQQKEwRUZXN0MQ0wCwYDVQQDEwRU
|
||||||
|
ZXN0MB4XDTI2MDgwNTE1NTQxN1oXDTI3MDgwNTE1NTQxN1owOjELMAkGA1UEBhMC
|
||||||
|
VVMxDTALBgNVBAcTBFRlc3QxDTALBgNVBAoTBFRlc3QxDTALBgNVBAMTBFRlc3Qw
|
||||||
|
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDj18ep0Pm5Y8VdXOSH9kXv
|
||||||
|
7RWzmxfNvN/zD1Fp2scADGBXnuVUhHYoR09Pe8U26X2gmDRg+JS7h6XXcGaWGUyh
|
||||||
|
28HISH3J0XrnKQmDKO77kxWFOAeu5apP2NVv1Nndbv+VqQczZqNZ6Kq9juflHjfK
|
||||||
|
hnnYiQqKZZ118IMCnu0uX26ShTwZJe7rAtO4MB4UNnfmAeRdrBOdI+1fp7QItYRc
|
||||||
|
2ppK3pXo2MAh8WhFeUy2UrlW6w1FpRjG/8nMmr14mD/dP9aWW85vcht5sA19bU3I
|
||||||
|
sf8ZOxOO9SGAk8Tg5Ey8aCdL5BU05OF8PNClOuqhHdGKShul3n2Pgojb2yGBHTYB
|
||||||
|
AgMBAAGjITAfMB0GA1UdDgQWBBTQbRwLjgbx/S7j1S/QFNB5HakDEjANBgkqhkiG
|
||||||
|
9w0BAQwFAAOCAQEAklxu68LkEdTFthA/ASyzm7KNxiWw+k+9IaEcViuwVa3XR2Ly
|
||||||
|
cO3L00NznsctwtcbevIT6ZlaK454snnpc5qZWHZSTYjQP34svT+XUZkgT29JdiYe
|
||||||
|
r5so+cUdorJyngMAen/tzpAHXV2l9SbRsi5+fRkR8DjPv9Ctz+kmh/Oy5fF3x/nu
|
||||||
|
AVZiwO8Oq4M3r+ElEfFed+9MkIQ6OZ3+ezgRhJYlXzVzii1OYQYpndocGNB+Jgwy
|
||||||
|
1FqzP1/02dzT6Oz95Md3B9fxj0OSCpCCBMo3ihkOKuNixaqyQ2+n/06Y+qpvnfuM
|
||||||
|
YrXnhbdpH7hsJM0/xNjWJMQlP04+zr1CQ9V9tg==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
28
backend/test/resources/sns-test-key.pem
Normal file
28
backend/test/resources/sns-test-key.pem
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDj18ep0Pm5Y8Vd
|
||||||
|
XOSH9kXv7RWzmxfNvN/zD1Fp2scADGBXnuVUhHYoR09Pe8U26X2gmDRg+JS7h6XX
|
||||||
|
cGaWGUyh28HISH3J0XrnKQmDKO77kxWFOAeu5apP2NVv1Nndbv+VqQczZqNZ6Kq9
|
||||||
|
juflHjfKhnnYiQqKZZ118IMCnu0uX26ShTwZJe7rAtO4MB4UNnfmAeRdrBOdI+1f
|
||||||
|
p7QItYRc2ppK3pXo2MAh8WhFeUy2UrlW6w1FpRjG/8nMmr14mD/dP9aWW85vcht5
|
||||||
|
sA19bU3Isf8ZOxOO9SGAk8Tg5Ey8aCdL5BU05OF8PNClOuqhHdGKShul3n2Pgojb
|
||||||
|
2yGBHTYBAgMBAAECggEAIPku65wTL+nI+9iAOE8DUxQoIlyNJtixPl9WpG+lghPI
|
||||||
|
c5XK0Z7z7KNZToL2iRpkdHPijLAc8kDQ1uts5UcXCIuhsUcQcT8wPrj5J/KqF11z
|
||||||
|
bVqs/fo92h1i0jLnLr0sHvAd2yn89PuPjixa0hU79MLeamB21o2bKqDajOwMHjwq
|
||||||
|
bAFUED3rmQe0nLv1pJbCIUUqd4ZElx+/XV+Y0c329zfA0WzjJI2EkHCnsS4tQn50
|
||||||
|
KX77Mzcbm9ujVQf2xsr5pnRscsCk2LrzXmAW4nOw5jpeWxxudvVTzARhpv46mj0j
|
||||||
|
MN0EN00YSw9FAHXBwDrjzS6cvvkchmBTJCUpKcoEWQKBgQDmO23YlsHrtnpWZRKf
|
||||||
|
BTu/uIObLHXgNt1yXby0mFk2BSe6L6yJDxalJQ1jxdyay73+LLBUdJwfg2K/jRdw
|
||||||
|
PFDdOvSRpb42LxTJ0TQxNaXsr91FDmbkjj0rFk1ygZKd/79HZq7xKWsfHsyTfUI7
|
||||||
|
wgYz9QaapYYM1t1+diXlWxupJQKBgQD9V+T0EsuOTof8goKcBNw6C8zPnif8mKwy
|
||||||
|
qtFWVySWpcreq74Q9+qyhMzNKDQ2flb1Akc6J3CSSvZBR21IKuqk9Cgu3qMH5CrX
|
||||||
|
pe8smw8LpPaO5adnHvRgPTih7tJ0u7jUErWhmavkqroEq6DmvngmQ/6w9nhwTaC4
|
||||||
|
tWfJzPvIrQKBgQDA19qEZpJ7y1bhcruMMyf+yKCDo1QAwDPwjY94fXuMAflqvG/6
|
||||||
|
RYckQMrcXWkQx8OWWPxBYYM76iMWaynMutjI1Y7xSDDw1bLF8NOUvGkEvbHLG+sX
|
||||||
|
WgTmSEIKvXl/mi4vslSqb5TodjXI/Ew0HapwbrZfZnHH41mXiYLof83FeQKBgQDK
|
||||||
|
91T1ee1c6GuoEINFLdumIXgHyeStST+EJDgsXQpyKwd6F8vhWk3MkfpmTtRt6BAQ
|
||||||
|
oK+h1qEogygBKpFR5Rgx6W4cBsBEfTcZp9YTPXLzWEk0OKdCRZlxVPr/OQ+g+Bhe
|
||||||
|
x1J+0lfVjjYTsdDprCUkOwtciUn6ZybhdGxfT3tUzQKBgQC2BxPNlzPJvRkLvfMJ
|
||||||
|
fJXfM9g96Uy7to7a7+LcLB2+OEAt/5kQAMg6ishwwoNvxVMtdy5uLEYNpq74lnQ2
|
||||||
|
bCVc7qc834edVMykhjlK5CLu0GiK2NM4aQavBNRGBoLF7I1Ih+tcToZFUC6W8YEU
|
||||||
|
djPrILOfRncjBj+epHXo92Z0iA==
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
Loading…
x
Reference in New Issue
Block a user