🐛 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:
Andrey Antukh 2026-08-05 16:00:09 +00:00
parent 2101d657e5
commit a4b9ccee8b
5 changed files with 263 additions and 78 deletions

View File

@ -77,7 +77,7 @@
org.clojure/data.csv {:mvn/version "1.1.1"}
com.clojure-goes-fast/clj-async-profiler {:mvn/version "2.0.0-beta1"}
mockery/mockery {:mvn/version "0.1.4"}}
:extra-paths ["test" "dev"]}
:extra-paths ["test" "test/resources" "dev"]}
:build
{:extra-deps

View File

@ -35,7 +35,8 @@
(defn- valid-sns-url?
"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]
(when (string? url)
(try
@ -49,21 +50,52 @@
(catch Exception _
false))))
(def ^:private notification-fields
["Message" "MessageId" "Subject" "Timestamp"
"TopicArn" "Type" "SigningCertURL" "SignatureVersion" "Signature"])
;; AWS SNS Signature Verification Field Sets
;; See: https://docs.aws.amazon.com/sns/latest/dg/sns-verify-signature-of-message.html
;;
;; 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
["Message" "MessageId" "SubscribeURL" "Timestamp"
"TopicArn" "Type" "SigningCertURL" "SignatureVersion" "Signature"])
;; V1 Notification: Message, MessageId, Subject (if present), Timestamp, TopicArn, Type
(def ^:private v1-notification-fields
["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
"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"
[body]
(let [fields (if (= "SubscriptionConfirmation" (get body "Type"))
subscription-fields
notification-fields)]
(let [sig-version (get body "SignatureVersion")
msg-type (get body "Type")
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
(filter #(contains? body %))
(map #(str % "\n" (get body %) "\n"))
@ -71,36 +103,52 @@
(defn- fetch-certificate
"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]
(let [response (http/req cfg {:uri cert-url :method :get :timeout 10000}
{:sync? true :response-type :input-stream})]
(when (= 200 (:status response))
(:body response))))
(when-not (= 200 (:status 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
"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]
(let [cert-url (get body "SigningCertURL")
signature (get body "Signature")
sig-version (get body "SignatureVersion")
string-sign (build-string-to-sign body)
algorithm (case sig-version
"1" "SHA1withRSA"
"2" "SHA256withRSA"
nil)]
(when-not algorithm
(throw (ex-info "Unsupported SNS signature version"
{:version sig-version})))
{:type :validation :version sig-version})))
(when (and cert-url signature)
(try
(with-open [cert-stream (fetch-certificate cfg cert-url)]
(let [cf (CertificateFactory/getInstance "X.509")
cert (.generateCertificate cf cert-stream)
sig (Signature/getInstance algorithm)]
(.initVerify sig (.getPublicKey cert))
(.update sig (.getBytes string-sign java.nio.charset.StandardCharsets/UTF_8))
(.verify sig (.decode (Base64/getDecoder) signature))))
(let [string-sign (build-string-to-sign body)]
(with-open [cert-stream (fetch-certificate cfg cert-url)]
(let [cf (CertificateFactory/getInstance "X.509")
cert (.generateCertificate cf cert-stream)
sig (Signature/getInstance algorithm)]
(.initVerify sig (.getPublicKey cert))
(.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
(l/wrn :hint "SNS signature verification exception"
:action "sns-signature-verification-exception"
@ -109,7 +157,8 @@
(defn- verify-sns-message!
"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]
(let [cert-url (get body "SigningCertURL")
subscribe-url (get body "SubscribeURL")

View File

@ -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.ap-southeast-1.amazonaws.com/cert.pem"))))
(t/deftest test-verify-signature-version-1
(let [keypair (java.security.KeyPairGenerator/getInstance "RSA")
_ (.initialize keypair 2048)
kp (.generateKeyPair keypair)
private-key (.getPrivate kp)
public-key (.getPublic kp)
;; Helper to load test certificate and private key from resources
;; See: https://docs.aws.amazon.com/sns/latest/dg/sns-verify-signature-of-message.html
(defn- load-test-cert-and-key
"Loads the test certificate and private key from test resources."
[]
(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
msg {"Type" "Notification"
"MessageId" "test-msg-1"
"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"}
(t/deftest test-verify-signature-end-to-end-v1
(let [{:keys [cert-bytes private-key]} (load-test-cert-and-key)
msg {"Type" "Notification"
"MessageId" "test-msg-1"
"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"}
;; Build string to sign
string-to-sign (#'awsns/build-string-to-sign msg)
;; Sign with SHA1
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))
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)]
;; Test that signature verification logic would work
(t/is (string? string-to-sign))
(t/is (string? 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-version-2
(let [keypair (java.security.KeyPairGenerator/getInstance "RSA")
_ (.initialize keypair 2048)
kp (.generateKeyPair keypair)
private-key (.getPrivate kp)
(t/deftest test-verify-signature-end-to-end-v2
(let [{:keys [cert-bytes private-key]} (load-test-cert-and-key)
;; Create a simple message
msg {"Type" "Notification"
"MessageId" "test-msg-2"
"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" "2"}
msg {"Type" "Notification"
"MessageId" "test-msg-2"
"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" "2"}
;; Build string to sign
string-to-sign (#'awsns/build-string-to-sign msg)
;; Sign with SHA256
sig (java.security.Signature/getInstance "SHA256withRSA")
_ (.initSign sig private-key)
_ (.update sig (.getBytes string-to-sign java.nio.charset.StandardCharsets/UTF_8))
signature (.encodeToString (java.util.Base64/getEncoder) (.sign sig))
sig (java.security.Signature/getInstance "SHA256withRSA")
_ (.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)]
;; Test that signature verification logic would work
(t/is (string? string-to-sign))
(t/is (string? 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-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
(let [msg {"Type" "Notification"
@ -379,11 +443,10 @@
"SignatureVersion" "3"
"Signature" "fake=="}]
;; Should throw exception for unsupported version
(t/is (thrown? clojure.lang.ExceptionInfo
(#'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"
"MessageId" "msg-123"
"TopicArn" "arn:aws:sns:eu-central-1:123:topic"
@ -399,8 +462,30 @@
(t/is (.contains result "TopicArn"))
(t/is (.contains result "Message"))
(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 "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
(let [msg {"Type" "SubscriptionConfirmation"
@ -408,6 +493,7 @@
"TopicArn" "arn:aws:sns:eu-central-1:123:topic"
"Message" "You have chosen to subscribe"
"Timestamp" "2021-02-04T14:41:37.020Z"
"Token" "test-token-123"
"SigningCertURL" "https://sns.eu-central-1.amazonaws.com/cert.pem"
"SignatureVersion" "1"
"Signature" "xyz789=="
@ -415,7 +501,10 @@
result (#'awsns/build-string-to-sign msg)]
(t/is (string? result))
(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
(let [body (j/write-str

View 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-----

View 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-----