# 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 = "\n#{cert}\n" 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