Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

switch signature validation from SHA1 to SHA256. fixes #68. #69

Merged
merged 1 commit into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import validator from 'validator'

const TIMESTAMP_TOLERANCE = 150
const SIGNATURE_FORMAT = 'base64'
const CHARACTER_ENCODING = 'utf8'


function getCert (cert_url, callback) {
Expand All @@ -17,6 +18,7 @@ function getCert (cert_url, callback) {
return process.nextTick(callback, result)

fetchCert(options, function (er, pem_cert) {

if (er)
return callback(er)

Expand All @@ -31,8 +33,8 @@ function getCert (cert_url, callback) {

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

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"validator": "^13.7.0"
},
"devDependencies": {
"esmock": "^2.6.0",
"nock": "^13.0.0",
"sinon": "^14.0.0",
"tap": "^16.0.0",
Expand Down
71 changes: 58 additions & 13 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
import { test } from 'tap'
import crypto from 'crypto'
import esmock from 'esmock'
import fs from 'fs'
import url from 'url'
import verifier from '../index.js'
import sinon from 'sinon'
import { dirname } from 'path'
import { fileURLToPath } from 'url'


const __dirname = dirname(fileURLToPath(import.meta.url))

const cert_url = 'https://s3.amazonaws.com/echo.api/echo-api-cert-12.pem' // latest valid cert

const rsaSha256Key = fs.readFileSync(`${__dirname}/mocks/rsa_sha256`).toString()
const validPem = fs.readFileSync(`${__dirname}/mocks/rsa_sha256_pub`).toString()


test('handle missing cert_url parameter', function (t) {
const signature = 'JbWZ4iO5ogpq1NhsOqyqq/QRrvc1/XyDwjcBO9wWSk//c11+gImmtWzMG9tDEW40t0Xwt1cnGU93DwUZQzMyzJ5CMi+09qVQUSIHiSmPekKaQRxS0Ibu7l7cXXuCcOBupbkheD/Dsd897Bm5SQwd1cFKRv+PJlpmGKimgh2QmbivogsEkFl8b9SW48kjKWazwj/XP2SrHY0bTvwMTVu7zvTcp0ZenEGlY2DNr5zSd1n6lmS6rgAt1IPwhBzqI0PVMngaM0DQhB0wUPj3QoIUh0IyMVAQzRFbQpS4UGrA4M9a5a+AGy0jCQKiRCI+Yi9iZYEVYvfafF/lyOUHHYcpOg=='
const now = new Date()
Expand Down Expand Up @@ -112,12 +123,27 @@ test('handle invalid base64-encoded signature parameter', function (t) {
})


test('handle valid signature', function (t) {
const ts = '2017-02-10T07:27:59Z'
test('handle valid signature', async function (t) {

const verifier = await esmock('../index.js', {
'../fetch-cert.js': {
default: function fetchCert (options, callback) {
callback(undefined, validPem)
}
},
'../validate-cert.js': {
default: function validateCert (pem_cert) {
// we're using our mocked sha256 pub/private keypair, so skip all the validation unrelated to
// signature checking.
}
},
})


const ts = '2019-09-01T07:27:59Z'
const now = new Date(ts)
const clock = sinon.useFakeTimers(now.getTime())
const cert_url = 'https://s3.amazonaws.com/echo.api/echo-api-cert-4.pem'
const signature = 'Qc8OuaGEHWeL/39XTEDYFbOCufYWpwi45rqmM2R4WaSEYcSXq+hUko/88wv48+6SPUiEddWSEEINJFAFV5auYZsnBzqCK+SO8mGNOGHmLYpcFuSEHI3eA3nDIEARrXTivqqbH/LCPJHc0tqNYr3yPZRIR2mYFndJOxgDNSOooZX+tp2GafHHsjjShCjmePaLxJiGG1DmrL6fyOJoLrzc0olUxLmnJviS6Q5wBir899TMEZ/zX+aiBTt/khVvwIh+hI/PZsRq/pQw4WAvQz1bcnGNamvMA/TKSJtR0elJP+TgCqbVoYisDgQXkhi8/wonkLhs68pN+TurbR7GyC1vxw=='

const body = {
"version": "1.0",
"session": {
Expand All @@ -143,20 +169,37 @@ test('handle valid signature', function (t) {
}
}

verifier(cert_url, signature, JSON.stringify(body), function (er) {
const requestEnvelope = JSON.stringify(body)
const signer = crypto.createSign('RSA-SHA256')
signer.update(requestEnvelope)
const signature = signer.sign(rsaSha256Key, 'base64');

verifier(cert_url, signature, requestEnvelope, function (er) {
t.equal(er, undefined)
clock.restore()
t.end()
})
})


test('handle valid signature with double byte utf8 encodings', function (t) {
test('handle valid signature with double byte utf8 encodings', async function (t) {
const verifier = await esmock('../index.js', {
'../fetch-cert.js': {
default: function fetchCert (options, callback) {
callback(undefined, validPem)
}
},
'../validate-cert.js': {
default: function validateCert (pem_cert) {
// we're using our mocked sha256 pub/private keypair, so skip all the validation unrelated to
// signature checking.
}
},
})

const ts = '2017-04-05T12:02:36Z'
const now = new Date(ts)
const clock = sinon.useFakeTimers(now.getTime())
const cert_url = 'https://s3.amazonaws.com/echo.api/echo-api-cert-4.pem'
const signature = 'WLShxe8KMwHUt8hVD5+iE4tDO+J8Li21yocDWnq8LVRpE2PMMWCxjQzOCzyoFm4i/yW07UKtKQxcnzB44ZEdP6e6HelwBwEdP4lb8jQcc5knk8SuUth4N7cu6Em8FPOdOJdd9idHbO/p8BTb14wgua5n+1SDKHm+wPikOVsfCMYsXcwRWx5FsgP1wVPrDsCHN/ISiCXz+UuMnd6H0uRNdLZ/x/ikPkknh+P1kuFa2a2LN4r57IwBDAxkdf9MzXEexSOO0nWLnyJY2VAFB+O7JKE39CwMJ1+YDOwTTTLjilkCnSlfnr6DP4HPGHnYhh2HQZle8UBrSDm4ntflErpISQ=='

const body = {
"version":"1.0",
"session": {
Expand Down Expand Up @@ -189,6 +232,11 @@ test('handle valid signature with double byte utf8 encodings', function (t) {
}
}

const requestEnvelope = JSON.stringify(body)
const signer = crypto.createSign('RSA-SHA256')
signer.update(requestEnvelope)
const signature = signer.sign(rsaSha256Key, 'base64');

verifier(cert_url, signature, JSON.stringify(body), function (er) {
t.equal(er, undefined)
clock.restore()
Expand All @@ -199,10 +247,7 @@ test('handle valid signature with double byte utf8 encodings', function (t) {

test('invocation', function (t) {
const ts = '2017-04-05T12:02:36Z'
const now = new Date(ts)
const clock = sinon.useFakeTimers(now.getTime())
const cert_url = 'https://s3.amazonaws.com/echo.api/echo-api-cert-4.pem'
const signature = 'Qc8OuaGEHWeL/39XTEDYFbOCufYWpwi45rqmM2R4WaSEYcSXq+hUko/88wv48+6SPUiEddWSEEINJFAFV5auYZsnBzqCK+SO8mGNOGHmLYpcFuSEHI3eA3nDIEARrXTivqqbH/LCPJHc0tqNYr3yPZRIR2mYFndJOxgDNSOooZX+tp2GafHHsjjShCjmePaLxJiGG1DmrL6fyOJoLrzc0olUxLmnJviS6Q5wBir899TMEZ/zX+aiBTt/khVvwIh+hI/PZsRq/pQw4WAvQz1bcnGNamvMA/TKSJtR0elJP+TgCqbVoYisDgQXkhi8/wonkLhs68pN+TurbR7GyC1vxw=='
const signature = ''
const body = {
"version": "1.0",
"session": {
Expand Down
File renamed without changes.
File renamed without changes.
51 changes: 51 additions & 0 deletions test/mocks/rsa_sha256
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----
14 changes: 14 additions & 0 deletions test/mocks/rsa_sha256_pub
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5z4oJtvKEKb/JrbKYfQw
kRsa/T46XGCxJEbUDQ0OyS++KXOTBn/wILCKgShRE5jxvWaG4xOPEjQjuVWuuUee
tuG5zsmybqyMaQKiRbp9WNwL3dSheQWee4Kn3F86AeM8HBu1XQIR9YAqY4utLVG4
J8cUUaP2ScgtJ7rSHXvak61ZTUThWi4uDhINK96Tz2UvN7qB0j5gqLE828Ii+Qdk
F1TmF3XwVRwIZ6o0xhAiLmoCqta9dQO7m6etPNbQrj5Kbb4zrgaVXBABenwHh3Ix
VyIILs3V9opVeuInDAmq0mMBVPEK5LARrbC3/eZKJOIuqrwMmv8tTL+vcaWgrQ4b
jA0YL5gYx3w3uDVwHm0kj8IwRPYWRxxSjXvOqVPtIYN0zetjjAfJkzaRQlZO93k1
rxS39PkwmeNpfk97PgM8ca9+ZHyoHQujcUTd2pCa0jAC/AJSzlr6zF/nBfTU3dIu
tbxc0PhUCegh3KVjA38lRXMhSkAFaN5TAXZM1NClAUFaYaoBZdFu/MwRefNqpg+N
zy47jJmD/4S56vdp/mZxWxb5rkUG1RtbHhTI9AEZsF9W3LfeO1D+hR134uvnapwp
fGpTFl6lqQDcvj9nqNCpuLfM77roBNigwLnhlC30U4bHtmPlZrp4Ys5NojSkJUO1
jhdLMKdwJZVUKYl7rhmhCcMCAwEAAQ==
-----END PUBLIC KEY-----
4 changes: 2 additions & 2 deletions test/validate-cert.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,13 @@ test('fails on non amazon subject alt name', function (t) {
})

test('fails on expired certificate (Not After)', function (t) {
const pem = fs.readFileSync(__dirname + '/cert-expired.pem')
const pem = fs.readFileSync(__dirname + '/mocks/cert-expired.pem')
t.ok(validate(pem) === 'invalid certificate validity (past expired date)')
t.end()
})

test('approves valid certifcate', function (t) {
const pem = fs.readFileSync(__dirname + '/echo-api-cert-12.cer')
const pem = fs.readFileSync(__dirname + '/mocks/echo-api-cert-12.cer')
t.ok(validate(pem) === undefined, 'Certificate should be valid')
t.end()
})
Loading