Skip to content

Commit

Permalink
convert to pure esm module and es6 everywhere. add changelog. update …
Browse files Browse the repository at this point in the history
…tap. fixes #65
  • Loading branch information
mreinstein committed May 24, 2021
1 parent b534932 commit ba33cff
Show file tree
Hide file tree
Showing 14 changed files with 551 additions and 535 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ node_modules

# Don't checkin package-lock.json for libraries
package-lock.json

.nyc_output
2 changes: 2 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
engine-strict=true

32 changes: 14 additions & 18 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,31 @@ sudo: false
language: node_js

node_js:
- '4'
- '5'
- '6'
- '7'
- '8'
- '9'
- '10'
- '12'
- '14'
- '16'

matrix:
include:
- node_js: '4'
- node_js: '12'
env: BROWSER_NAME=chrome BROWSER_VERSION=latest
- node_js: '4'
- node_js: '12'
env: BROWSER_NAME=chrome BROWSER_VERSION=29
- node_js: '4'
- node_js: '12'
env: BROWSER_NAME=firefox BROWSER_VERSION=latest
- node_js: '4'
- node_js: '12'
env: BROWSER_NAME=opera BROWSER_VERSION=latest
- node_js: '4'
- node_js: '12'
env: BROWSER_NAME=safari BROWSER_VERSION=latest
- node_js: '4'
- node_js: '12'
env: BROWSER_NAME=safari BROWSER_VERSION=7
- node_js: '4'
- node_js: '12'
env: BROWSER_NAME=safari BROWSER_VERSION=6
- node_js: '4'
- node_js: '12'
env: BROWSER_NAME=safari BROWSER_VERSION=5
- node_js: '4'
- node_js: '12'
env: BROWSER_NAME=ie BROWSER_VERSION=11
- node_js: '4'
- node_js: '12'
env: BROWSER_NAME=ie BROWSER_VERSION=10
- node_js: '4'
- node_js: '12'
env: BROWSER_NAME=ie BROWSER_VERSION=9
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# 3.0.0
* BREAKING: publish as a pure ES module
* BREAKING: only support node >= 12.17
* BREAKING: switch to es6 everywhere
* update tap dependency
58 changes: 28 additions & 30 deletions fetch-cert.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,39 @@
'use strict'
import https from 'https'

var https = require('https')

const globalCache = { } // default in-memory cache for downloaded certificates

var globalCache = {} // default in-memory cache for downloaded certificates

module.exports = function fetchCert (options, callback) {
var url = options.url
var cache = options.cache || globalCache
var cachedResponse = cache[url.href]
var servedFromCache = false
if (cachedResponse) {
servedFromCache = true
process.nextTick(callback, undefined, cachedResponse, servedFromCache)
return
}
export default function fetchCert (options, callback) {
const url = options.url
const cache = options.cache || globalCache
const cachedResponse = cache[url.href]
let servedFromCache = false
if (cachedResponse) {
servedFromCache = true
process.nextTick(callback, undefined, cachedResponse, servedFromCache)
return
}

var body = ''
let body = ''

https.get(url.href, function (response) {
var statusCode
https.get(url.href, function (response) {
if (!response || 200 !== response.statusCode) {
const statusCode = response ? response.statusCode : 0
return callback('Failed to download certificate at: ' + url.href + '. Response code: ' + statusCode)
}

if (!response || 200 !== response.statusCode) {
statusCode = response ? response.statusCode : 0
return callback('Failed to download certificate at: ' + url.href + '. Response code: ' + statusCode)
}
response.setEncoding('utf8')
response.on('data', function (chunk) {
body += chunk
})

response.setEncoding('utf8')
response.on('data', function (chunk) {
body += chunk
response.on('end', function () {
cache[url.href] = body
callback(undefined, body, servedFromCache)
})
})
response.on('end', function () {
cache[url.href] = body
callback(undefined, body, servedFromCache)
.on('error', function (er) {
callback('Failed to download certificate at: ' + url.href +'. Error: ' + er)
})
})
.on('error', function(er) {
callback('Failed to download certificate at: ' + url.href +'. Error: ' + er)
})
}
139 changes: 69 additions & 70 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,108 +1,107 @@
'use strict'
import crypto from 'crypto'
import fetchCert from './fetch-cert.js'
import url from 'url'
import validateCert from './validate-cert.js'
import validateCertUri from './validate-cert-uri.js'
import validator from 'validator'

var crypto = require('crypto')
var fetchCert = require('./fetch-cert')
var url = require('url')
var validateCert = require('./validate-cert')
var validateCertUri = require('./validate-cert-uri')
var validator = require('validator')

const TIMESTAMP_TOLERANCE = 150
const SIGNATURE_FORMAT = 'base64'

// constants
var TIMESTAMP_TOLERANCE = 150
var SIGNATURE_FORMAT = 'base64'

function getCert (cert_url, callback) {
var options = { url: url.parse(cert_url) }
var result = validateCertUri(options.url)
if (result !== true)
return process.nextTick(callback, result)
const options = { url: url.parse(cert_url) }
const result = validateCertUri(options.url)
if (result !== true)
return process.nextTick(callback, result)

fetchCert(options, function (er, pem_cert) {
if (er)
return callback(er)
fetchCert(options, function (er, pem_cert) {
if (er)
return callback(er)

er = validateCert(pem_cert)
if (er)
return callback(er)
er = validateCert(pem_cert)
if (er)
return callback(er)

callback(er, pem_cert)
})
callback(er, pem_cert)
})
}


// returns true if the signature for the request body is valid, false otherwise
function isValidSignature (pem_cert, signature, requestBody) {
var verifier = crypto.createVerify('RSA-SHA1')
verifier.update(requestBody, 'utf8')
return verifier.verify(pem_cert, signature, SIGNATURE_FORMAT)
const verifier = crypto.createVerify('RSA-SHA1')
verifier.update(requestBody, 'utf8')
return verifier.verify(pem_cert, signature, SIGNATURE_FORMAT)
}


// determine if a timestamp is valid for a given request with a tolerance of
// TIMESTAMP_TOLERANCE seconds
// returns undefined if valid, or an error string otherwise
function validateTimestamp (requestBody) {
var d, e, error, now, oldestTime, request_json
try {
request_json = JSON.parse(requestBody)
} catch (error) {
e = error
return 'request body invalid json'
}
if (!(request_json.request && request_json.request.timestamp))
return 'Timestamp field not present in request'

d = new Date(request_json.request.timestamp)
now = new Date()
oldestTime = now.getTime() - (TIMESTAMP_TOLERANCE * 1000)
if (d.getTime() < oldestTime)
return 'Request is from more than ' + TIMESTAMP_TOLERANCE + ' seconds ago'
}
let e, error, request_json

try {
request_json = JSON.parse(requestBody)
} catch (error) {
e = error
return 'request body invalid json'
}

function verifier (cert_url, signature, requestBody, callback) {
var er
if (!(request_json.request && request_json.request.timestamp))
return 'Timestamp field not present in request'

if(!cert_url)
return process.nextTick(callback, 'missing certificate url')
const d = new Date(request_json.request.timestamp)
const now = new Date()
const oldestTime = now.getTime() - (TIMESTAMP_TOLERANCE * 1000)

if (!signature)
return process.nextTick(callback, 'missing signature')
if (d.getTime() < oldestTime)
return 'Request is from more than ' + TIMESTAMP_TOLERANCE + ' seconds ago'
}

if (!requestBody)
return process.nextTick(callback, 'missing request (certificate) body')

if (!validator.isBase64(signature))
return process.nextTick(callback, 'invalid signature (not base64 encoded)')
function verifier (cert_url, signature, requestBody, callback) {
if (!cert_url)
return process.nextTick(callback, 'missing certificate url')

if (!signature)
return process.nextTick(callback, 'missing signature')

if (!requestBody)
return process.nextTick(callback, 'missing request (certificate) body')

er = validateTimestamp(requestBody)
if (!validator.isBase64(signature))
return process.nextTick(callback, 'invalid signature (not base64 encoded)')

if (er)
return process.nextTick(callback, er)
const er = validateTimestamp(requestBody)

getCert(cert_url, function (er, pem_cert) {
if (er)
return callback(er)
return process.nextTick(callback, er)

if (!isValidSignature(pem_cert, signature, requestBody))
return callback('invalid signature')
getCert(cert_url, function (er, pem_cert) {
if (er)
return callback(er)

callback()
})
if (!isValidSignature(pem_cert, signature, requestBody))
return callback('invalid signature')

callback()
})
}


// certificate validator for amazon echo
module.exports = function (cert_url, signature, requestBody, cb) {
if(cb)
return verifier(cert_url, signature, requestBody, cb)

return new Promise(function( resolve, reject) {
verifier(cert_url, signature, requestBody, function(er) {
if(er)
return reject(er)
resolve()
})
export default function alexaVerifier (cert_url, signature, requestBody, cb) {
if (cb)
return verifier(cert_url, signature, requestBody, cb)

return new Promise(function (resolve, reject) {
verifier(cert_url, signature, requestBody, function (er) {
if (er)
return reject(er)
resolve()
})
})
}
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "2.0.2",
"description": "Verify HTTP requests sent to an Alexa skill are sent from Amazon",
"main": "index.js",
"type": "module",
"repository": {
"type": "git",
"url": "https://github.com/mreinstein/alexa-verifier.git"
Expand All @@ -26,10 +27,10 @@
"devDependencies": {
"nock": "^9.0.2",
"sinon": "^4.1.5",
"tap": "^11.0.0",
"tap": "^15.0.9",
"unroll": "1.4.0"
},
"engine": {
"node": ">=4"
"node": ">=12.17"
}
}
Loading

0 comments on commit ba33cff

Please sign in to comment.