alexa-verifier
Advanced tools
Comparing version 0.1.0 to 0.1.1
146
index.js
@@ -1,36 +0,36 @@ | ||
var crypto = require('crypto'); | ||
var fs = require('fs'); | ||
var os = require('os'); | ||
var request = require('request'); | ||
var tools = require('openssl-cert-tools'); | ||
var url = require('url'); | ||
var validator = require('validator'); | ||
var crypto = require('crypto') | ||
var fs = require('fs') | ||
var os = require('os') | ||
var request = require('request') | ||
var tools = require('openssl-cert-tools') | ||
var url = require('url') | ||
var validator = require('validator') | ||
// global constants | ||
var TIMESTAMP_TOLERANCE = 150; | ||
var VALID_CERT_HOSTNAME = 's3.amazonaws.com'; | ||
var VALID_CERT_PATH_START = '/echo.api/'; | ||
var VALID_CERT_PORT = '443'; | ||
var SIGNATURE_FORMAT = 'base64'; | ||
var TIMESTAMP_TOLERANCE = 150 | ||
var VALID_CERT_HOSTNAME = 's3.amazonaws.com' | ||
var VALID_CERT_PATH_START = '/echo.api/' | ||
var VALID_CERT_PORT = '443' | ||
var SIGNATURE_FORMAT = 'base64' | ||
function md5(input) { | ||
return crypto.createHash('sha1').update(input).digest('hex'); | ||
return crypto.createHash('sha1').update(input).digest('hex') | ||
} | ||
function getCert (cert_url, callback) { | ||
var tmpdir = '/tmp'; // os.tmpdir() | ||
var cert_filepath = tmpdir + '/' + md5(cert_url) + '.pem'; | ||
var tmpdir = '/tmp' // os.tmpdir() | ||
var cert_filepath = tmpdir + '/' + md5(cert_url) + '.pem' | ||
fs.stat(cert_filepath, function(er, stat) { | ||
var cert_uri, result; | ||
var cert_uri, result | ||
if (stat) { | ||
return fs.readFile(cert_filepath, 'utf8', callback); | ||
return fs.readFile(cert_filepath, 'utf8', callback) | ||
} | ||
cert_uri = url.parse(cert_url); | ||
result = validateCertUri(cert_uri); | ||
cert_uri = url.parse(cert_url) | ||
result = validateCertUri(cert_uri) | ||
if (result !== true) { | ||
return callback(result); | ||
return callback(result) | ||
} | ||
@@ -40,3 +40,3 @@ | ||
if (er) { | ||
return callback(er); | ||
return callback(er) | ||
} | ||
@@ -46,10 +46,10 @@ | ||
if (er) { | ||
return callback(er); | ||
return callback(er) | ||
} | ||
fs.writeFile(cert_filepath, pem_cert, 'utf8', function(er) { | ||
callback(er, pem_cert); | ||
}); | ||
}); | ||
}); | ||
}); | ||
callback(er, pem_cert) | ||
}) | ||
}) | ||
}) | ||
}) | ||
} | ||
@@ -59,11 +59,11 @@ | ||
function fetchCert(uri, callback) { | ||
var cert_url; | ||
cert_url = "https://" + uri.host + ":" + (uri.port || '') + "/" + uri.path; | ||
var cert_url | ||
cert_url = "https://" + uri.host + ":" + (uri.port || '') + "/" + uri.path | ||
request.get(cert_url, function(er, response, body) { | ||
if (body) { | ||
callback(null, body); | ||
callback(null, body) | ||
} else { | ||
callback("Failed to download certificate at: " + cert_url + ". Response code: " + response.code + ", error: " + body); | ||
callback("Failed to download certificate at: " + cert_url + ". Response code: " + response.code + ", error: " + body) | ||
} | ||
}); | ||
}) | ||
} | ||
@@ -75,3 +75,3 @@ | ||
if (er) { | ||
return callback(er); | ||
return callback(er) | ||
} | ||
@@ -82,3 +82,3 @@ | ||
if (info.subject.CN.indexOf('echo-api.amazon.com') === -1) { | ||
return callback('subjectAltName Check Failed'); | ||
return callback('subjectAltName Check Failed') | ||
} | ||
@@ -89,6 +89,6 @@ | ||
if (info.remainingDays < 1) { | ||
return callback('certificate expiration check failed'); | ||
return callback('certificate expiration check failed') | ||
} | ||
callback(); | ||
}); | ||
callback() | ||
}) | ||
} | ||
@@ -100,14 +100,14 @@ | ||
if (cert_uri.protocol !== 'https:') { | ||
return "Certificate URI MUST be https: " + cert_uri; | ||
return "Certificate URI MUST be https: " + cert_uri | ||
} | ||
if (cert_uri.port && (cert_uri.port !== VALID_CERT_PORT)) { | ||
return "Certificate URI port MUST be " + VALID_CERT_PORT + ", was: " + cert_uri.port; | ||
return "Certificate URI port MUST be " + VALID_CERT_PORT + ", was: " + cert_uri.port | ||
} | ||
if (cert_uri.hostname !== VALID_CERT_HOSTNAME) { | ||
return "Certificate URI hostname must be " + VALID_CERT_HOSTNAME + ": " + cert_uri.hostname; | ||
return "Certificate URI hostname must be " + VALID_CERT_HOSTNAME + ": " + cert_uri.hostname | ||
} | ||
if (cert_uri.path.indexOf(VALID_CERT_PATH_START) !== 0) { | ||
return "Certificate URI path must start with " + VALID_CERT_PATH_START + ": " + cert_uri; | ||
return "Certificate URI path must start with " + VALID_CERT_PATH_START + ": " + cert_uri | ||
} | ||
return true; | ||
return true | ||
} | ||
@@ -118,6 +118,6 @@ | ||
function validateSignature(pem_cert, signature, requestBody) { | ||
var verifier; | ||
verifier = crypto.createVerify('RSA-SHA1'); | ||
verifier.update(requestBody); | ||
return verifier.verify(pem_cert, signature, SIGNATURE_FORMAT); | ||
var verifier | ||
verifier = crypto.createVerify('RSA-SHA1') | ||
verifier.update(requestBody) | ||
return verifier.verify(pem_cert, signature, SIGNATURE_FORMAT) | ||
} | ||
@@ -130,20 +130,20 @@ | ||
function validateTimestamp(requestBody) { | ||
var d, e, error, now, oldestTime, request_json; | ||
request_json = null; | ||
var d, e, error, now, oldestTime, request_json | ||
request_json = null | ||
try { | ||
request_json = JSON.parse(requestBody); | ||
request_json = JSON.parse(requestBody) | ||
} catch (error) { | ||
e = error; | ||
return 'request body invalid json'; | ||
e = error | ||
return 'request body invalid json' | ||
} | ||
if (!(request_json.request && request_json.request.timestamp)) { | ||
return 'Timestamp field not present in request'; | ||
return 'Timestamp field not present in request' | ||
} | ||
d = new Date(request_json.request.timestamp); | ||
now = new Date(); | ||
oldestTime = now.getTime() - (TIMESTAMP_TOLERANCE * 1000); | ||
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"; | ||
return "Request is from more than " + TIMESTAMP_TOLERANCE + " seconds ago" | ||
} | ||
return null; | ||
return null | ||
} | ||
@@ -154,38 +154,38 @@ | ||
var verifier = module.exports = function(cert_url, signature, requestBody, callback) { | ||
var er; | ||
var er | ||
if (cert_url == null) { | ||
cert_url = ''; | ||
cert_url = '' | ||
} | ||
if (signature == null) { | ||
signature = ''; | ||
signature = '' | ||
} | ||
if (requestBody == null) { | ||
requestBody = ''; | ||
requestBody = '' | ||
} | ||
if (callback == null) { | ||
callback = function(){}; | ||
callback = function() { } | ||
} | ||
if (!validator.isBase64(signature)) { | ||
return callback('signature is not base64 encoded'); | ||
return callback('signature is not base64 encoded') | ||
} | ||
er = validateTimestamp(requestBody); | ||
er = validateTimestamp(requestBody) | ||
if (er) { | ||
return callback(er); | ||
return callback(er) | ||
} | ||
getCert(cert_url, function(er, pem_cert) { | ||
var success; | ||
var success | ||
if (er) { | ||
return callback(er); | ||
return callback(er) | ||
} | ||
success = validateSignature(pem_cert, signature, requestBody); | ||
success = validateSignature(pem_cert, signature, requestBody) | ||
if (success !== true) { | ||
return callback('certificate verification failed'); | ||
return callback('certificate verification failed') | ||
} | ||
callback(); | ||
}); | ||
}; | ||
callback() | ||
}) | ||
} | ||
// Export to make unit testing easier: | ||
verifier.validateCertUri = validateCertUri; | ||
verifier.validateCertUri = validateCertUri |
{ | ||
"name": "alexa-verifier", | ||
"version": "0.1.0", | ||
"version": "0.1.1", | ||
"description": "Verify HTTP requests sent to an Alexa skill are sent from Amazon", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -7,6 +7,8 @@ # alexa-verifier | ||
This module is framework-agnostic. If you're using expressjs, [alexa-verifier-middleware](https://github.com/mreinstein/alexa-verifier-middleware) is easier to integrate. | ||
This module is framework-agnostic. | ||
If you're using expressjs, you should check out [alexa-verifier-middleware](https://github.com/mreinstein/alexa-verifier-middleware) which is a lot easier to integrate. | ||
### motivation | ||
@@ -25,8 +27,11 @@ Part of the certication process for alexa skills hosted on a generic web service (i.e., not AWS Lambda) is that your skill must validate requests are actually coming from Amazon. This is enforced by checking: | ||
```javascript | ||
verifier(cert_url, signature, requestRawBody, callback); | ||
const verifier = require('alexa-verifier') | ||
verifier(cert_url, signature, requestRawBody, callback) | ||
``` | ||
* `cert_url` full url of the certificate to verify (from the HTTP request header named `signaturecertchainurl`) | ||
* `signature` signature of the request (from the HTTP request header named `signature`) | ||
* `cert_url` full url of the certificate to verify (from HTTP request header named `signaturecertchainurl`) | ||
* `signature` signature of the request (from HTTP request header named `signature`) | ||
* `requestRawBody` full body string from POST request | ||
* `callback` completion function. has 1 argument which indicates error. falsey when verification passes |
@@ -1,22 +0,22 @@ | ||
var test = require('tap').test; | ||
var unroll = require('unroll'); | ||
unroll.use(test); | ||
var test = require('tap').test | ||
var unroll = require('unroll') | ||
unroll.use(test) | ||
var url = require('url'); | ||
var verifier = require('../'); | ||
var url = require('url') | ||
var verifier = require('../') | ||
unroll('verifier.validateCertUri should be #valid for #url', | ||
function(t, testArgs) { | ||
var cert_uri = url.parse(testArgs['url']); | ||
var result = verifier.validateCertUri(cert_uri); | ||
var valid = testArgs['valid']; | ||
t.notEqual(valid, undefined); | ||
var cert_uri = url.parse(testArgs['url']) | ||
var result = verifier.validateCertUri(cert_uri) | ||
var valid = testArgs['valid'] | ||
t.notEqual(valid, undefined) | ||
if (valid === true) { | ||
t.equal(result, true); | ||
t.equal(result, true) | ||
} else { | ||
// I don't care too much about the error message, so do negated | ||
// comparison with 'true': | ||
t.notEqual(result, true); | ||
t.notEqual(result, true) | ||
} | ||
t.end(); | ||
t.end() | ||
}, | ||
@@ -36,9 +36,9 @@ [ | ||
] | ||
); | ||
) | ||
test('handle invalid cert_url parameter', function(t) { | ||
var body, now, signature; | ||
signature = 'JbWZ4iO5ogpq1NhsOqyqq/QRrvc1/XyDwjcBO9wWSk//c11+gImmtWzMG9tDEW40t0Xwt1cnGU93DwUZQzMyzJ5CMi+09qVQUSIHiSmPekKaQRxS0Ibu7l7cXXuCcOBupbkheD/Dsd897Bm5SQwd1cFKRv+PJlpmGKimgh2QmbivogsEkFl8b9SW48kjKWazwj/XP2SrHY0bTvwMTVu7zvTcp0ZenEGlY2DNr5zSd1n6lmS6rgAt1IPwhBzqI0PVMngaM0DQhB0wUPj3QoIUh0IyMVAQzRFbQpS4UGrA4M9a5a+AGy0jCQKiRCI+Yi9iZYEVYvfafF/lyOUHHYcpOg=='; | ||
now = new Date(); | ||
var body, now, signature | ||
signature = 'JbWZ4iO5ogpq1NhsOqyqq/QRrvc1/XyDwjcBO9wWSk//c11+gImmtWzMG9tDEW40t0Xwt1cnGU93DwUZQzMyzJ5CMi+09qVQUSIHiSmPekKaQRxS0Ibu7l7cXXuCcOBupbkheD/Dsd897Bm5SQwd1cFKRv+PJlpmGKimgh2QmbivogsEkFl8b9SW48kjKWazwj/XP2SrHY0bTvwMTVu7zvTcp0ZenEGlY2DNr5zSd1n6lmS6rgAt1IPwhBzqI0PVMngaM0DQhB0wUPj3QoIUh0IyMVAQzRFbQpS4UGrA4M9a5a+AGy0jCQKiRCI+Yi9iZYEVYvfafF/lyOUHHYcpOg==' | ||
now = new Date() | ||
body = { | ||
@@ -48,37 +48,37 @@ request: { | ||
} | ||
}; | ||
} | ||
verifier(void 0, signature, JSON.stringify(body), function(er) { | ||
t.equal(er.indexOf('Certificate URI MUST be https'), 0); | ||
t.end(); | ||
}); | ||
}); | ||
t.equal(er.indexOf('Certificate URI MUST be https'), 0) | ||
t.end() | ||
}) | ||
}) | ||
test('handle invalid body json', function(t) { | ||
var cert_url, signature; | ||
cert_url = 'https://s3.amazonaws.com/echo.api/echo-api-cert.pem'; | ||
signature = 'JbWZ4iO5ogpq1NhsOqyqq/QRrvc1/XyDwjcBO9wWSk//c11+gImmtWzMG9tDEW40t0Xwt1cnGU93DwUZQzMyzJ5CMi+09qVQUSIHiSmPekKaQRxS0Ibu7l7cXXuCcOBupbkheD/Dsd897Bm5SQwd1cFKRv+PJlpmGKimgh2QmbivogsEkFl8b9SW48kjKWazwj/XP2SrHY0bTvwMTVu7zvTcp0ZenEGlY2DNr5zSd1n6lmS6rgAt1IPwhBzqI0PVMngaM0DQhB0wUPj3QoIUh0IyMVAQzRFbQpS4UGrA4M9a5a+AGy0jCQKiRCI+Yi9iZYEVYvfafF/lyOUHHYcpOg=='; | ||
var cert_url, signature | ||
cert_url = 'https://s3.amazonaws.com/echo.api/echo-api-cert.pem' | ||
signature = 'JbWZ4iO5ogpq1NhsOqyqq/QRrvc1/XyDwjcBO9wWSk//c11+gImmtWzMG9tDEW40t0Xwt1cnGU93DwUZQzMyzJ5CMi+09qVQUSIHiSmPekKaQRxS0Ibu7l7cXXuCcOBupbkheD/Dsd897Bm5SQwd1cFKRv+PJlpmGKimgh2QmbivogsEkFl8b9SW48kjKWazwj/XP2SrHY0bTvwMTVu7zvTcp0ZenEGlY2DNr5zSd1n6lmS6rgAt1IPwhBzqI0PVMngaM0DQhB0wUPj3QoIUh0IyMVAQzRFbQpS4UGrA4M9a5a+AGy0jCQKiRCI+Yi9iZYEVYvfafF/lyOUHHYcpOg==' | ||
verifier(cert_url, signature, '', function(er) { | ||
t.equal(er, 'request body invalid json'); | ||
t.end(); | ||
}); | ||
}); | ||
t.equal(er, 'request body invalid json') | ||
t.end() | ||
}) | ||
}) | ||
test('handle missing timestamp field', function(t) { | ||
var cert_url, signature; | ||
cert_url = 'https://s3.amazonaws.com/echo.api/echo-api-cert.pem'; | ||
signature = 'JbWZ4iO5ogpq1NhsOqyqq/QRrvc1/XyDwjcBO9wWSk//c11+gImmtWzMG9tDEW40t0Xwt1cnGU93DwUZQzMyzJ5CMi+09qVQUSIHiSmPekKaQRxS0Ibu7l7cXXuCcOBupbkheD/Dsd897Bm5SQwd1cFKRv+PJlpmGKimgh2QmbivogsEkFl8b9SW48kjKWazwj/XP2SrHY0bTvwMTVu7zvTcp0ZenEGlY2DNr5zSd1n6lmS6rgAt1IPwhBzqI0PVMngaM0DQhB0wUPj3QoIUh0IyMVAQzRFbQpS4UGrA4M9a5a+AGy0jCQKiRCI+Yi9iZYEVYvfafF/lyOUHHYcpOg=='; | ||
var cert_url, signature | ||
cert_url = 'https://s3.amazonaws.com/echo.api/echo-api-cert.pem' | ||
signature = 'JbWZ4iO5ogpq1NhsOqyqq/QRrvc1/XyDwjcBO9wWSk//c11+gImmtWzMG9tDEW40t0Xwt1cnGU93DwUZQzMyzJ5CMi+09qVQUSIHiSmPekKaQRxS0Ibu7l7cXXuCcOBupbkheD/Dsd897Bm5SQwd1cFKRv+PJlpmGKimgh2QmbivogsEkFl8b9SW48kjKWazwj/XP2SrHY0bTvwMTVu7zvTcp0ZenEGlY2DNr5zSd1n6lmS6rgAt1IPwhBzqI0PVMngaM0DQhB0wUPj3QoIUh0IyMVAQzRFbQpS4UGrA4M9a5a+AGy0jCQKiRCI+Yi9iZYEVYvfafF/lyOUHHYcpOg==' | ||
verifier(cert_url, signature, '{}', function(er) { | ||
t.equal(er, 'Timestamp field not present in request'); | ||
t.end(); | ||
}); | ||
}); | ||
t.equal(er, 'Timestamp field not present in request') | ||
t.end() | ||
}) | ||
}) | ||
test('handle outdated timestamp field', function(t) { | ||
var body, cert_url, now, signature; | ||
cert_url = 'https://s3.amazonaws.com/echo.api/echo-api-cert.pem'; | ||
signature = 'JbWZ4iO5ogpq1NhsOqyqq/QRrvc1/XyDwjcBO9wWSk//c11+gImmtWzMG9tDEW40t0Xwt1cnGU93DwUZQzMyzJ5CMi+09qVQUSIHiSmPekKaQRxS0Ibu7l7cXXuCcOBupbkheD/Dsd897Bm5SQwd1cFKRv+PJlpmGKimgh2QmbivogsEkFl8b9SW48kjKWazwj/XP2SrHY0bTvwMTVu7zvTcp0ZenEGlY2DNr5zSd1n6lmS6rgAt1IPwhBzqI0PVMngaM0DQhB0wUPj3QoIUh0IyMVAQzRFbQpS4UGrA4M9a5a+AGy0jCQKiRCI+Yi9iZYEVYvfafF/lyOUHHYcpOg=='; | ||
now = new Date(); | ||
var body, cert_url, now, signature | ||
cert_url = 'https://s3.amazonaws.com/echo.api/echo-api-cert.pem' | ||
signature = 'JbWZ4iO5ogpq1NhsOqyqq/QRrvc1/XyDwjcBO9wWSk//c11+gImmtWzMG9tDEW40t0Xwt1cnGU93DwUZQzMyzJ5CMi+09qVQUSIHiSmPekKaQRxS0Ibu7l7cXXuCcOBupbkheD/Dsd897Bm5SQwd1cFKRv+PJlpmGKimgh2QmbivogsEkFl8b9SW48kjKWazwj/XP2SrHY0bTvwMTVu7zvTcp0ZenEGlY2DNr5zSd1n6lmS6rgAt1IPwhBzqI0PVMngaM0DQhB0wUPj3QoIUh0IyMVAQzRFbQpS4UGrA4M9a5a+AGy0jCQKiRCI+Yi9iZYEVYvfafF/lyOUHHYcpOg==' | ||
now = new Date() | ||
body = { | ||
@@ -88,14 +88,14 @@ request: { | ||
} | ||
}; | ||
} | ||
verifier(cert_url, signature, JSON.stringify(body), function(er) { | ||
t.equal(er, 'Request is from more than 150 seconds ago'); | ||
t.end(); | ||
}); | ||
}); | ||
t.equal(er, 'Request is from more than 150 seconds ago') | ||
t.end() | ||
}) | ||
}) | ||
test('handle missing signature parameter', function(t) { | ||
var body, cert_url, now; | ||
cert_url = 'https://s3.amazonaws.com/echo.api/echo-api-cert.pem'; | ||
now = new Date(); | ||
var body, cert_url, now | ||
cert_url = 'https://s3.amazonaws.com/echo.api/echo-api-cert.pem' | ||
now = new Date() | ||
body = { | ||
@@ -105,14 +105,14 @@ request: { | ||
} | ||
}; | ||
} | ||
verifier(cert_url, void 0, JSON.stringify(body), function(er) { | ||
t.equal(er, 'signature is not base64 encoded'); | ||
t.end(); | ||
}); | ||
}); | ||
t.equal(er, 'signature is not base64 encoded') | ||
t.end() | ||
}) | ||
}) | ||
test('handle invalid signature parameter', function(t) { | ||
var body, cert_url, now; | ||
cert_url = 'https://s3.amazonaws.com/echo.api/echo-api-cert.pem'; | ||
now = new Date(); | ||
var body, cert_url, now | ||
cert_url = 'https://s3.amazonaws.com/echo.api/echo-api-cert.pem' | ||
now = new Date() | ||
body = { | ||
@@ -122,7 +122,7 @@ request: { | ||
} | ||
}; | ||
} | ||
verifier(cert_url, '....$#%@$se', JSON.stringify(body), function(er) { | ||
t.equal(er, 'signature is not base64 encoded'); | ||
t.end(); | ||
}); | ||
}); | ||
t.equal(er, 'signature is not base64 encoded') | ||
t.end() | ||
}) | ||
}) |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
36
13799
1