Sample HTTP SNS Subscriber
Description
Simple node express application that can confirm subscription to an AWS SNS Topic and process messages that are published to subscribed topics. The axios and express packages are dependencies of this example.
app.js
Main function that receives /POST
requests:
- Only processes messages from SNS by checking the presence of
x-amz-sns-message-type
header - Verifies the signature included in the message is from AWS SNS -
messageValidator.js
- Depending on the message type, it will confirm a subscription or print out the message received
const express = require('express')
const bodyParser = require('body-parser')
const https = require('https')
const app = express()
const port = 3000
const messageValidator = require('./messageValidator')
app.use(bodyParser.text())
app.post('/', async (req, res) => {
const body = JSON.parse(req.body)
if (req.get('x-amz-sns-message-type') == null){
return
}
if (await messageValidator.isValidSignature(body)){
handleMessage(body)
} else {
throw 'Message signature is not valid'
}
})
app.listen(port, () => console.log(`HTTP subscriber listening at http://localhost:${port}`))
function handleMessage(body){
switch(body.Type) {
case 'SubscriptionConfirmation':
confirmSubscription(body.SubscribeURL)
break
case 'Notification':
handleNotification(body)
break
default:
return
}
}
function confirmSubscription(subscriptionUrl){
https.get(subscriptionUrl)
console.log('Subscription confirmed')
}
function handleNotification(body){
console.log(`Received message from SNS: ${body.Message}`)
}
messageValidator.js
Checks signature version and verifies message signature by:
- Downloading certificate from provided certificate url
- Encrypting message with the certificate and verifying that the encrypted message matches the signature provided
Futher information here: https://docs.aws.amazon.com/sns/latest/dg/sns-verify-signature-of-message.html
const url = require('url');
const crypto = require('crypto')
const axios = require('axios')
async function isValidSignature(body) {
verifyMessageSignatureVersion(body.SignatureVersion)
const certificate = await downloadCertificate(body.SigningCertURL)
return validateSignature(body, certificate)
}
function verifyMessageSignatureVersion(version) {
if (version != 1){
throw "Signature verification failed"
}
}
function verifyMessageSignatureURL(certURL) {
if (url.parse(certURL).protocol != 'https:') {
throw "SigningCertURL was not using HTTPS"
}
}
async function downloadCertificate(certURL){
verifyMessageSignatureURL(certURL)
try {
const response = await axios.get(certURL)
return response.data
} catch (err){
throw `Error fetching certificate: ${err}`
}
}
async function validateSignature(message, certificate) {
const verify = crypto.createVerify('sha1WithRSAEncryption');
verify.write(getMessageToSign(message))
verify.end();
return verify.verify(certificate, message.Signature, 'base64')
}
function getMessageToSign(body){
switch(body.Type) {
case 'SubscriptionConfirmation':
return buildSubscriptionStringToSign(body)
case 'Notification':
return buildNotificationStringToSign(body)
default:
return
}
}
function buildNotificationStringToSign(body) {
let stringToSign = ''
stringToSign = "Message\n"
stringToSign += body.Message + "\n"
stringToSign += "MessageId\n"
stringToSign += body.MessageId + "\n"
if (body.Subject) {
stringToSign += "Subject\n"
stringToSign += body.Subject + "\n"
}
stringToSign += "Timestamp\n"
stringToSign += body.Timestamp + "\n"
stringToSign += "TopicArn\n"
stringToSign += body.TopicArn + "\n"
stringToSign += "Type\n"
stringToSign += body.Type + "\n"
return stringToSign
}
function buildSubscriptionStringToSign(body) {
let stringToSign = ''
stringToSign = "Message\n";
stringToSign += body.Message + "\n";
stringToSign += "MessageId\n";
stringToSign += body.MessageId + "\n";
stringToSign += "SubscribeURL\n";
stringToSign += body.SubscribeURL + "\n";
stringToSign += "Timestamp\n";
stringToSign += body.Timestamp + "\n";
stringToSign += "Token\n";
stringToSign += body.Token + "\n";
stringToSign += "TopicArn\n";
stringToSign += body.TopicArn + "\n";
stringToSign += "Type\n";
stringToSign += body.Type + "\n";
return stringToSign;
}
module.exports = { isValidSignature }