gems/aws-sdk-sns/spec/message_verifier_spec.rb (195 lines of code) (raw):
# frozen_string_literal: true
require_relative 'spec_helper'
module Aws
module SNS
describe MessageVerifier do
let(:signing_cert_url) {
"https://sns.us-east-2.amazonaws.com/SimpleNotificationService-7ff5318490ec183fbaddaa2a969abfda.pem"
}
let(:message_SHA1) { <<-JSON.strip }
{
"Type" : "Notification",
"MessageId" : "792cda85-518f-5dd3-9163-81d851212f3a",
"TopicArn" : "arn:aws:sns:us-east-2:295079676684:publish-and-verify-892f85fe-4836-424d-8188-ab85bef0f362",
"Message" : "Hello world",
"Timestamp" : "2022-07-28T21:23:58.317Z",
"SignatureVersion" : "1",
"Signature" : "ghtf+deOBAzHJJZ6s6CdRLfTQAlcGzq9naoFM1wi0CJiq//uVRuZnamrkWNF0fhouMFvuLVRwcz8PZLUMSfnmd5VpdTKpTyiKmy1qJAZXma0w+yi7G+I33hD1Jyk1Nbym2n0kqp3fVu2aoooiN2ZeLAT2bH0/BtjLSfN1yAOKNoprco4qV9gGUZinXJdj9a1YdNhDR2jKi33ldlsVtEXAtiaDklGEk7DgRKX38GerBPiLg3FdtgY6KC7cdeGpU/dGK+4hjc83Ive1HoFkAwqhpgInM2sMytBosoiXfCmOKmU4xeGD0gHDNZTlJUJQDlzw8Eag0H9f/5zXF9d3uy0YQ==",
"SigningCertURL" : #{signing_cert_url.inspect},
"UnsubscribeURL" : "https://sns.us-east-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-2:295079676684:publish-and-verify-892f85fe-4836-424d-8188-ab85bef0f362:2296bc94-7992-4be1-b15f-b97229b5c1d8"
}
JSON
let(:message_SHA256) { <<-JSON.strip }
{
"Type" : "Notification",
"MessageId" : "17dea24b-55c2-540b-8362-f916557af765",
"TopicArn" : "arn:aws:sns:us-east-2:295079676684:publish-and-verify-62674b1d-4295-426b-88e7-5fb75652a04e",
"Message" : "Hello world",
"Timestamp" : "2022-07-28T21:24:08.324Z",
"SignatureVersion" : "2",
"Signature" : "CXVqp9PfZAL+4JHS3Zxo1PFbQsvnOjvmYhtIf17TWpwc+iIVas8kZ8GopuzVzVMdatE7rCl/O4P91Zp05Dwz8lk8dLhfp8gSu3Njlzxlyrmzo9x3va3Jb7zFnedgS2GKnZWHGBdwTXho+TosNUE+3e10OMSlwN5XGDwX7+R3WL+rn+AXmFAqp3alg27sYa55h1dLE9cGszGPjScPdtF3BmZsUDMx9wSdNKsCk+vSvE8yBjnCmUl7laSFj3LzPVrlSwgNYCF3kYnNAkah7NplK4SFhJYLwS0HCVCQJKa8rVbQLf9cBTu60U402mrgy0bN8xWoyimzbYbrOMJjalqkUg==",
"SigningCertURL" : #{signing_cert_url.inspect},
"UnsubscribeURL" : "https://sns.us-east-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-2:295079676684:publish-and-verify-62674b1d-4295-426b-88e7-5fb75652a04e:ad7d16e3-0a7c-46aa-b23e-ffaf02250cbe"
}
JSON
let(:lambda_message) { <<-JSON.strip }
{
"Type" : "Notification",
"MessageId" : "792cda85-518f-5dd3-9163-81d851212f3a",
"TopicArn" : "arn:aws:sns:us-east-2:295079676684:publish-and-verify-892f85fe-4836-424d-8188-ab85bef0f362",
"Message" : "Hello world",
"Timestamp" : "2022-07-28T21:23:58.317Z",
"SignatureVersion" : "1",
"Signature" : "ghtf+deOBAzHJJZ6s6CdRLfTQAlcGzq9naoFM1wi0CJiq//uVRuZnamrkWNF0fhouMFvuLVRwcz8PZLUMSfnmd5VpdTKpTyiKmy1qJAZXma0w+yi7G+I33hD1Jyk1Nbym2n0kqp3fVu2aoooiN2ZeLAT2bH0/BtjLSfN1yAOKNoprco4qV9gGUZinXJdj9a1YdNhDR2jKi33ldlsVtEXAtiaDklGEk7DgRKX38GerBPiLg3FdtgY6KC7cdeGpU/dGK+4hjc83Ive1HoFkAwqhpgInM2sMytBosoiXfCmOKmU4xeGD0gHDNZTlJUJQDlzw8Eag0H9f/5zXF9d3uy0YQ==",
"SigningCertUrl" : #{signing_cert_url.inspect},
"UnsubscribeUrl" : "https://sns.us-east-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-2:295079676684:publish-and-verify-892f85fe-4836-424d-8188-ab85bef0f362:2296bc94-7992-4be1-b15f-b97229b5c1d8"
}
JSON
let(:cert) { <<-CERT.strip }
-----BEGIN CERTIFICATE-----
MIIF1zCCBL+gAwIBAgIQB9pYWG3Mi7xej22g9pobJTANBgkqhkiG9w0BAQsFADBG
MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRUwEwYDVQQLEwxTZXJ2ZXIg
Q0EgMUIxDzANBgNVBAMTBkFtYXpvbjAeFw0yMTA5MDcwMDAwMDBaFw0yMjA4MTcy
MzU5NTlaMBwxGjAYBgNVBAMTEXNucy5hbWF6b25hd3MuY29tMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAutFqueT3XgP13udzxE6UpbdjOtVO5DwoMpSM
iDNMnGzF1TYH5/R2LPUOBeTB0SkKnR4kpNcUZhicpGD4aKciz/GEZ6wu65xncfT9
H/KBOQwoXYTuClHwp6fYpGzcGFaFoEYMnijL/o4qmTSd+ukglQUgKpsDw4ofw6rU
m2CttJo+GQSNQ9NfGR1h/0J+zsApkeSYrXRx5wNlu87z8os1C/6PBrUHwt3xXeaf
Xzfwut8aRRYsS8BySOA9DAgLfNHlfdQCjKPXKrG/ussgReyWD6n/HH+j7Uha3xos
TzQqJifcxlTq6MxWdPR6fDaJNvqw6DOE7UjUNxHguXHlVfxhlQIDAQABo4IC6TCC
AuUwHwYDVR0jBBgwFoAUWaRmBlKge5WSPKOUByeWdFv5PdAwHQYDVR0OBBYEFAqz
C+vyouneE7mWWLbi9i0UsWUbMBwGA1UdEQQVMBOCEXNucy5hbWF6b25hd3MuY29t
MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw
OwYDVR0fBDQwMjAwoC6gLIYqaHR0cDovL2NybC5zY2ExYi5hbWF6b250cnVzdC5j
b20vc2NhMWIuY3JsMBMGA1UdIAQMMAowCAYGZ4EMAQIBMHUGCCsGAQUFBwEBBGkw
ZzAtBggrBgEFBQcwAYYhaHR0cDovL29jc3Auc2NhMWIuYW1hem9udHJ1c3QuY29t
MDYGCCsGAQUFBzAChipodHRwOi8vY3J0LnNjYTFiLmFtYXpvbnRydXN0LmNvbS9z
Y2ExYi5jcnQwDAYDVR0TAQH/BAIwADCCAX0GCisGAQQB1nkCBAIEggFtBIIBaQFn
AHYAKXm+8J45OSHwVnOfY6V35b5XfZxgCvj5TV0mXCVdx4QAAAF7vfDVkQAABAMA
RzBFAiEA2XfHuy36aqRFiaL8c3md2mH451go8707+fRE0pEdSRACIE/g5FXTUXUZ
PFcmOhm9TZ+uMY1i4CIQ/CKVWln6C3t+AHYAUaOw9f0BeZxWbbg3eI8MpHrMGyfL
956IQpoN/tSLBeUAAAF7vfDVjAAABAMARzBFAiBF1MhhFP0+FQt3daDFfMYoWwnr
muTInrjNpwfzlvQBugIhAPYadFzr+LaxSJoiZEbEHBvTts7bT0M3eCQONA2O7w6n
AHUAQcjKsd8iRkoQxqE6CUKHXk4xixsD6+tLx2jwkGKWBvYAAAF7vfDVdAAABAMA
RjBEAiAtPapmFAuA71ih4NoSd5hJelzAltNQpxDMcDfDyHyU8gIgWxmaa6+2KbBu
9xdv379zvnJACFR7jc+4asl08Dn4aagwDQYJKoZIhvcNAQELBQADggEBAA54QX0u
oFWXfMmv02CGZv4NWo5TapyeeixQ2kKpZHRdVZjxZrw+hoF6HD7P3kGjH8ztyJll
tDxB0qgMltbPhQdScwhA6iTgoaBYqEUC/VHKd4PmmPT6yIsM36NBZVmkGlzl5uNo
/dBgBaG0SsVJnhr5zro3c2quC7n6fVGEZhf/UgQwRnnvThnvbNKguglDMq4uEqv8
njKyleht+glkcmXO0m9qLKt6BOS0amy6U2GlAwRn0Wx02ndJtnRCSC6kPuRWK/SQ
FEjB7gCK4hdKaAOuWdZpI55vF6ifOeM8toC3g7ofO8qLTnJupAG+ZitY5J3cvHWr
HqOUdKigPDHYLRo=
-----END CERTIFICATE-----
CERT
let(:verifier) { MessageVerifier.new }
before(:each) do
stub_request(:get, signing_cert_url).to_return(status:200, body:cert)
end
describe '#authenticate!' do
[:message_sha1, :message_sha256, :lambda_message].each do |message_type|
let(:message) { send(message_type) }
let(:cert_url_key) { message_type == :lambda_message ? 'SigningCertUrl' : 'SigningCertURL' }
context "message type is #{message_type}" do
it 'returns true for a valid message' do
expect(verifier.authenticate!(message)).to be(true)
end
it 'raises when the message signature fails validation' do
msg = Json.load(message)
msg['Signature'] = 'bad'
msg = Json.dump(msg)
expect {
verifier.authenticate!(msg)
}.to raise_error(MessageVerifier::VerificationError, /cannot be verified/)
end
it 'raises when SignatureVersion is not a valid value' do
msg = Json.load(message)
msg['SignatureVersion'] = '3'
msg = Json.dump(msg)
expect {
verifier.authenticate!(msg)
}.to raise_error(MessageVerifier::VerificationError, /Invalid SignatureVersion/)
end
it 'raises when the SigningCertURL is not https' do
msg = Json.load(message)
msg[cert_url_key] = msg[cert_url_key].sub(/https/, 'http')
msg = Json.dump(msg)
expect {
verifier.authenticate!(msg)
}.to raise_error(MessageVerifier::VerificationError, /must be https/)
end
it 'raises when the SigningCertURL is not AWS hosted' do
msg = Json.load(message)
msg[cert_url_key] = 'https://internetbadguys.com/cert.pem'
msg = Json.dump(msg)
expect {
verifier.authenticate!(msg)
}.to raise_error(MessageVerifier::VerificationError, /hosted by AWS/)
end
it 'raises when the SigningCertURL is not a pem file' do
msg = Json.load(message)
msg[cert_url_key] = msg[cert_url_key].sub(/pem$/, 'key')
msg = Json.dump(msg)
expect {
verifier.authenticate!(msg)
}.to raise_error(MessageVerifier::VerificationError, /a \.pem file/)
end
end
end
it 'can use a configured :http_proxy' do
proxy_url = 'https://peccy:password@amazonaws.com:12345'
verifier = MessageVerifier.new(http_proxy: proxy_url)
verifier.authenticate!(message_SHA1)
expect(a_request(:get, signing_cert_url)).to have_been_made
end
it 'caches the pem file' do
verifier.authenticate!(message_SHA1)
verifier.authenticate!(message_SHA1)
expect(a_request(:get, signing_cert_url)).to have_been_made.once
end
it 'attempts to download the cert 3 times' do
stub_request(:get, signing_cert_url).
to_return(status: 500, body: '').
to_return(status: 500, body: '').
to_return(status: 200, body: cert)
verifier.authenticate!(message_SHA1)
expect(a_request(:get, signing_cert_url)).to have_been_made.times(3)
end
it 'raises when the signing cert can not be downloaded due to networking errors' do
stub_request(:get, signing_cert_url).to_raise(StandardError, 'oops')
expect {
verifier.authenticate!(message_SHA1)
}.to raise_error(MessageVerifier::VerificationError, 'oops')
end
it 'raises when the signing cert can not be downloaded' do
stub_request(:get, signing_cert_url).to_return(status:500, body:'bad')
expect {
verifier.authenticate!(message_SHA1)
}.to raise_error(MessageVerifier::VerificationError, 'bad')
end
it 'raises when the signing cert contains additional characters' do
cert_with_extra = "<xml><value>\n#{cert}\n<value></xml>"
stub_request(:get, signing_cert_url).to_return(status:200, body: cert_with_extra)
expect {
verifier.authenticate!(message_SHA1)
}.to raise_error(MessageVerifier::VerificationError,
/certificate does not match expected X509 PEM format/)
end
end
describe '#authentic?' do
[:message_sha1, :message_sha256, :lambda_message].each do |message_type|
let(:message) { send(message_type) }
context "message type is #{message_type}" do
it 'returns true if the message can be authenticated' do
expect(verifier.authentic?(message)).to be(true)
end
it 'returns false if the message can not be authenticated' do
msg = Json.load(message)
msg['Signature'] = 'bad'
msg = Json.dump(msg)
expect(verifier.authentic?(msg)).to be(false)
end
end
end
end
end
end
end