Skip to content

Commit

Permalink
Use builtin encoding base64url (#819)
Browse files Browse the repository at this point in the history
Node 16+ supports base64url
  • Loading branch information
nikeee authored Jun 21, 2023
1 parent 6fc5ce1 commit cc26d8a
Show file tree
Hide file tree
Showing 10 changed files with 58 additions and 84 deletions.
7 changes: 3 additions & 4 deletions src/encryption-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

const crypto = require('crypto');
const ece = require('http_ece');
const urlBase64Helper = require('./urlsafe-base64-helper');

const encrypt = function(userPublicKey, userAuth, payload, contentEncoding) {
if (!userPublicKey) {
Expand All @@ -13,7 +12,7 @@ const encrypt = function(userPublicKey, userAuth, payload, contentEncoding) {
throw new Error('The subscription p256dh value must be a string.');
}

if (urlBase64Helper.decode(userPublicKey).length !== 65) {
if (Buffer.from(userPublicKey, 'base64url').length !== 65) {
throw new Error('The subscription p256dh value should be 65 bytes long.');
}

Expand All @@ -25,7 +24,7 @@ const encrypt = function(userPublicKey, userAuth, payload, contentEncoding) {
throw new Error('The subscription auth key must be a string.');
}

if (urlBase64Helper.decode(userAuth).length < 16) {
if (Buffer.from(userAuth, 'base64url').length < 16) {
throw new Error('The subscription auth key should be at least 16 '
+ 'bytes long');
}
Expand All @@ -41,7 +40,7 @@ const encrypt = function(userPublicKey, userAuth, payload, contentEncoding) {
const localCurve = crypto.createECDH('prime256v1');
const localPublicKey = localCurve.generateKeys();

const salt = urlBase64Helper.encode(crypto.randomBytes(16));
const salt = crypto.randomBytes(16).toString('base64url');

const cipherText = ece.encrypt(payload, {
version: contentEncoding,
Expand Down
33 changes: 7 additions & 26 deletions src/urlsafe-base64-helper.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,13 @@
// Largely ported from https://github.com/RGBboy/urlsafe-base64

'use strict';

function encode(buffer) {
return buffer.toString('base64')
.replace(/\+/g, '-') // Convert '+' to '-'
.replace(/\//g, '_') // Convert '/' to '_'
.replace(/=+$/, ''); // Remove ending '='
}

function decode(base64) {
// Add removed at end '='
base64 += Array(5 - (base64.length % 4)).join('=');

base64 = base64
.replace(/-/g, '+') // Convert '-' to '+'
.replace(/_/g, '/'); // Convert '_' to '/'

// change from urlsafe-base64 since new Buffer() is deprecated
return Buffer.from(base64, 'base64');
}

/**
* @param {string} base64
* @returns {boolean}
*/
function validate(base64) {
return /^[A-Za-z0-9\-_]+$/.test(base64);
}
return /^[A-Za-z0-9\-_]+$/.test(base64);
}

module.exports = {
encode: encode,
decode: decode,
validate: validate
validate: validate
};
10 changes: 5 additions & 5 deletions src/vapid-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ function generateVAPIDKeys() {
}

return {
publicKey: urlBase64Helper.encode(publicKeyBuffer),
privateKey: urlBase64Helper.encode(privateKeyBuffer)
publicKey: publicKeyBuffer.toString('base64url'),
privateKey: privateKeyBuffer.toString('base64url')
};
}

Expand Down Expand Up @@ -104,7 +104,7 @@ function validatePublicKey(publicKey) {
throw new Error('Vapid public key must be a URL safe Base 64 (without "=")');
}

publicKey = urlBase64Helper.decode(publicKey);
publicKey = Buffer.from(publicKey, 'base64url');

if (publicKey.length !== 65) {
throw new Error('Vapid public key should be 65 bytes long when decoded.');
Expand All @@ -125,7 +125,7 @@ function validatePrivateKey(privateKey) {
throw new Error('Vapid private key must be a URL safe Base 64 (without "=")');
}

privateKey = urlBase64Helper.decode(privateKey);
privateKey = Buffer.from(privateKey, 'base64url');

if (privateKey.length !== 32) {
throw new Error('Vapid private key should be 32 bytes long when decoded.');
Expand Down Expand Up @@ -203,7 +203,7 @@ function getVapidHeaders(audience, subject, publicKey, privateKey, contentEncodi
validatePublicKey(publicKey);
validatePrivateKey(privateKey);

privateKey = urlBase64Helper.decode(privateKey);
privateKey = Buffer.from(privateKey, 'base64url');

if (expiration) {
validateExpiration(expiration);
Expand Down
2 changes: 1 addition & 1 deletion src/web-push-lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ WebPushLib.prototype.generateRequestDetails = function(subscription, payload, op
} else if (contentEncoding === webPushConstants.supportedContentEncodings.AES_GCM) {
requestDetails.headers['Content-Encoding'] = webPushConstants.supportedContentEncodings.AES_GCM;
requestDetails.headers.Encryption = 'salt=' + encrypted.salt;
requestDetails.headers['Crypto-Key'] = 'dh=' + urlBase64Helper.encode(encrypted.localPublicKey);
requestDetails.headers['Crypto-Key'] = 'dh=' + encrypted.localPublicKey.toString('base64url');
}

requestPayload = encrypted.cipherText;
Expand Down
9 changes: 4 additions & 5 deletions test/test-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

const assert = require('assert');
const spawn = require('child_process').spawn;
const urlBase64Helper = require('../src/urlsafe-base64-helper');

const cliPath = 'src/cli.js';

Expand Down Expand Up @@ -147,13 +146,13 @@
return line.indexOf('Public Key:') !== -1;
});
const publicKey = lines[publicKeyTitleIndex + 1].trim();
assert.equal(urlBase64Helper.decode(publicKey).length, 65);
assert.equal(Buffer.from(publicKey, 'base64url').length, 65);

const privateKeyTitleIndex = lines.findIndex(function(line) {
return line.indexOf('Private Key:') !== -1;
});
const privateKey = lines[privateKeyTitleIndex + 1].trim();
assert.equal(urlBase64Helper.decode(privateKey).length, 32);
assert.equal(Buffer.from(privateKey, 'base64url').length, 32);
resolve();
});
});
Expand Down Expand Up @@ -185,8 +184,8 @@
assert(vapidKeys.publicKey);
assert(vapidKeys.privateKey);

assert.equal(urlBase64Helper.decode(vapidKeys.privateKey).length, 32);
assert.equal(urlBase64Helper.decode(vapidKeys.publicKey).length, 65);
assert.equal(Buffer.from(vapidKeys.privateKey, 'base64url').length, 32);
assert.equal(Buffer.from(vapidKeys.publicKey, 'base64url').length, 65);

resolve();
});
Expand Down
7 changes: 3 additions & 4 deletions test/test-encryption-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@
const assert = require('assert');
const crypto = require('crypto');
const webPush = require('../src/index');
const urlBase64Helper = require('../src/urlsafe-base64-helper');
const ece = require('http_ece');

const userCurve = crypto.createECDH('prime256v1');
const VALID_PUBLIC_KEY = urlBase64Helper.encode(userCurve.generateKeys());
const VALID_AUTH = urlBase64Helper.encode(crypto.randomBytes(16));
const VALID_PUBLIC_KEY = userCurve.generateKeys().toString('base64url');
const VALID_AUTH = crypto.randomBytes(16).toString('base64url');

suite('Test Encryption Helpers', function() {
test('is defined', function() {
Expand All @@ -20,7 +19,7 @@ suite('Test Encryption Helpers', function() {

return ece.decrypt(encrypted.cipherText, {
version: contentEncoding,
dh: urlBase64Helper.encode(encrypted.localPublicKey),
dh: encrypted.localPublicKey.toString('base64url'),
privateKey: userCurve,
salt: encrypted.salt,
authSecret: VALID_AUTH
Expand Down
25 changes: 12 additions & 13 deletions test/test-generate-request-details.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

const assert = require('assert');
const webPush = require('../src/index');
const urlBase64Helper = require('../src/urlsafe-base64-helper');
const crypto = require('crypto');
const jws = require('jws');
const urlParse = require('url').parse;
Expand All @@ -19,8 +18,8 @@ suite('Test Generate Request Details', function() {
const vapidKeys = require('../src/vapid-helper').generateVAPIDKeys();

const VALID_KEYS = {
p256dh: urlBase64Helper.encode(userPublicKey),
auth: urlBase64Helper.encode(userAuth)
p256dh: userPublicKey.toString('base64url'),
auth: userAuth.toString('base64url')
};

const invalidRequests = [
Expand Down Expand Up @@ -85,7 +84,7 @@ suite('Test Generate Request Details', function() {
subscription: {
endpoint: true,
keys: {
p256dh: urlBase64Helper.encode(userPublicKey)
p256dh: userPublicKey.toString('base64url')
}
},
message: 'hello'
Expand All @@ -96,7 +95,7 @@ suite('Test Generate Request Details', function() {
subscription: {
endpoint: true,
keys: {
auth: urlBase64Helper.encode(userAuth)
auth: userAuth.toString('base64url')
}
},
message: 'hello'
Expand All @@ -107,7 +106,7 @@ suite('Test Generate Request Details', function() {
subscription: {
keys: {
p256dh: userPublicKey,
auth: urlBase64Helper.encode(userAuth)
auth: userAuth.toString('base64url')
}
},
message: 'hello'
Expand All @@ -118,7 +117,7 @@ suite('Test Generate Request Details', function() {
requestOptions: {
subscription: {
keys: {
p256dh: urlBase64Helper.encode(userPublicKey),
p256dh: userPublicKey.toString('base64url'),
auth: userAuth
}
},
Expand All @@ -130,8 +129,8 @@ suite('Test Generate Request Details', function() {
requestOptions: {
subscription: {
keys: {
p256dh: urlBase64Helper.encode(Buffer.concat([userPublicKey, Buffer.alloc(1)])),
auth: urlBase64Helper.encode(userAuth)
p256dh: Buffer.concat([userPublicKey, Buffer.alloc(1)]).toString('base64url'),
auth: userAuth.toString('base64url')
}
},
message: 'hello'
Expand All @@ -142,8 +141,8 @@ suite('Test Generate Request Details', function() {
requestOptions: {
subscription: {
keys: {
p256dh: urlBase64Helper.encode(userPublicKey.slice(1)),
auth: urlBase64Helper.encode(userAuth)
p256dh: userPublicKey.slice(1).toString('base64url'),
auth: userAuth.toString('base64url')
}
},
message: 'hello'
Expand All @@ -154,8 +153,8 @@ suite('Test Generate Request Details', function() {
requestOptions: {
subscription: {
keys: {
p256dh: urlBase64Helper.encode(userPublicKey),
auth: urlBase64Helper.encode(userAuth.slice(1))
p256dh: userPublicKey.toString('base64url'),
auth: userAuth.slice(1).toString('base64url')
}
},
message: 'hello'
Expand Down
9 changes: 4 additions & 5 deletions test/test-set-vapid-details.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@

const assert = require('assert');
const webPush = require('../src/index');
const urlBase64Helper = require('../src/urlsafe-base64-helper');

const VALID_SUBJECT_MAILTO = 'mailto: [email protected]';
const VALID_SUBJECT_URL = 'https://exampe.com/contact';
const VALID_PUBLIC_KEY = urlBase64Helper.encode(Buffer.alloc(65));
const VALID_PRIVATE_KEY = urlBase64Helper.encode(Buffer.alloc(32));
const VALID_PUBLIC_KEY = Buffer.alloc(65).toString('base64url');
const VALID_PRIVATE_KEY = Buffer.alloc(32).toString('base64url');

suite('setVapidDetails()', function() {
test('is defined', function() {
Expand Down Expand Up @@ -55,7 +54,7 @@ suite('setVapidDetails()', function() {
},
{
subject: VALID_SUBJECT_URL,
publicKey: urlBase64Helper.encode(Buffer.alloc(60)),
publicKey: Buffer.alloc(60).toString('base64url'),
privateKey: VALID_PRIVATE_KEY
},
{
Expand All @@ -81,7 +80,7 @@ suite('setVapidDetails()', function() {
{
subject: VALID_SUBJECT_URL,
publicKey: VALID_PUBLIC_KEY,
privateKey: urlBase64Helper.encode(Buffer.alloc(60))
privateKey: Buffer.alloc(60).toString('base64url')
},
{
subject: VALID_SUBJECT_URL,
Expand Down
13 changes: 6 additions & 7 deletions test/test-vapid-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ const crypto = require('crypto');
const mocha = require('mocha');
const webPush = require('../src/index');
const vapidHelper = require('../src/vapid-helper');
const urlBase64Helper = require('../src/urlsafe-base64-helper');

const VALID_AUDIENCE = 'https://example.com';
const VALID_SUBJECT_MAILTO = 'mailto:[email protected]';
Expand All @@ -15,9 +14,9 @@ const VALID_SUBJECT_URL = 'https://example.com/contact';
const WARN_SUBJECT_LOCALHOST_URL = 'https://localhost';
const INVALID_SUBJECT_URL_1 = 'http://example.gov';
const INVALID_SUBJECT_URL_2 = 'ftp://example.net';
const VALID_PUBLIC_KEY = urlBase64Helper.encode(Buffer.alloc(65));
const VALID_PUBLIC_KEY = Buffer.alloc(65).toString('base64url');
const VALID_UNSAFE_BASE64_PUBLIC_KEY = Buffer.alloc(65).toString('base64');
const VALID_PRIVATE_KEY = urlBase64Helper.encode(Buffer.alloc(32));
const VALID_PRIVATE_KEY = Buffer.alloc(32).toString('base64url');
const VALID_UNSAFE_BASE64_PRIVATE_KEY = Buffer.alloc(32).toString('base64');
const VALID_CONTENT_ENCODING = webPush.supportedContentEncodings.AES_GCM;
const VALID_EXPIRATION = Math.floor(Date.now() / 1000) + (60 * 60 * 12);
Expand Down Expand Up @@ -46,8 +45,8 @@ suite('Test Vapid Helpers', function() {
assert.equal(typeof keys.privateKey, 'string');
assert.equal(typeof keys.publicKey, 'string');

assert.equal(urlBase64Helper.decode(keys.privateKey).length, 32);
assert.equal(urlBase64Helper.decode(keys.publicKey).length, 65);
assert.equal(Buffer.from(keys.privateKey, 'base64url').length, 32);
assert.equal(Buffer.from(keys.publicKey, 'base64url').length, 65);
});

test('generate vapid keys with padding', function() {
Expand All @@ -66,8 +65,8 @@ suite('Test Vapid Helpers', function() {
assert.equal(typeof keys.privateKey, 'string');
assert.equal(typeof keys.publicKey, 'string');

assert.equal(urlBase64Helper.decode(keys.privateKey).length, 32);
assert.equal(urlBase64Helper.decode(keys.publicKey).length, 65);
assert.equal(Buffer.from(keys.privateKey, 'base64url').length, 32);
assert.equal(Buffer.from(keys.publicKey, 'base64url').length, 65);
});

test('generate new vapid keys between calls', function() {
Expand Down
Loading

0 comments on commit cc26d8a

Please sign in to comment.