Skip to content

Commit

Permalink
Migrate urlsafe-base64 to be internal to web-push and switch to Buffe…
Browse files Browse the repository at this point in the history
…r.from (#813)

Fixes #785

Co-authored-by: Dan Lee <[email protected]>
  • Loading branch information
haouarihk and hikarunoryoma authored Jun 11, 2023
1 parent 2eb5ea2 commit de2aa16
Show file tree
Hide file tree
Showing 12 changed files with 115 additions and 85 deletions.
39 changes: 19 additions & 20 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@
"http_ece": "1.1.0",
"https-proxy-agent": "^5.0.0",
"jws": "^4.0.0",
"minimist": "^1.2.5",
"urlsafe-base64": "^1.0.0"
"minimist": "^1.2.5"
},
"devDependencies": {
"chromedriver": "101.0.0",
Expand Down
8 changes: 4 additions & 4 deletions src/encryption-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

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

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

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

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

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

const salt = urlBase64.encode(crypto.randomBytes(16));
const salt = urlBase64Helper.encode(crypto.randomBytes(16));

const cipherText = ece.encrypt(payload, {
version: contentEncoding,
Expand Down
32 changes: 32 additions & 0 deletions src/urlsafe-base64-helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// 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');
}

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

module.exports = {
encode: encode,
decode: decode,
validate: validate
};
16 changes: 8 additions & 8 deletions src/vapid-helper.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
'use strict';

const crypto = require('crypto');
const urlBase64 = require('urlsafe-base64');
const asn1 = require('asn1.js');
const jws = require('jws');
const { URL } = require('url');

const WebPushConstants = require('./web-push-constants.js');
const urlBase64Helper = require('./urlsafe-base64-helper');

/**
* DEFAULT_EXPIRATION is set to seconds in 12 hours
Expand Down Expand Up @@ -60,8 +60,8 @@ function generateVAPIDKeys() {
}

return {
publicKey: urlBase64.encode(publicKeyBuffer),
privateKey: urlBase64.encode(privateKeyBuffer)
publicKey: urlBase64Helper.encode(publicKeyBuffer),
privateKey: urlBase64Helper.encode(privateKeyBuffer)
};
}

Expand Down Expand Up @@ -100,11 +100,11 @@ function validatePublicKey(publicKey) {
+ 'encoded string.');
}

if (!urlBase64.validate(publicKey)) {
if (!urlBase64Helper.validate(publicKey)) {
throw new Error('Vapid public key must be a URL safe Base 64 (without "=")');
}

publicKey = urlBase64.decode(publicKey);
publicKey = urlBase64Helper.decode(publicKey);

if (publicKey.length !== 65) {
throw new Error('Vapid public key should be 65 bytes long when decoded.');
Expand All @@ -121,11 +121,11 @@ function validatePrivateKey(privateKey) {
+ 'encoded string.');
}

if (!urlBase64.validate(privateKey)) {
if (!urlBase64Helper.validate(privateKey)) {
throw new Error('Vapid private key must be a URL safe Base 64 (without "=")');
}

privateKey = urlBase64.decode(privateKey);
privateKey = urlBase64Helper.decode(privateKey);

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 = urlBase64.decode(privateKey);
privateKey = urlBase64Helper.decode(privateKey);

if (expiration) {
validateExpiration(expiration);
Expand Down
6 changes: 3 additions & 3 deletions src/web-push-lib.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
'use strict';

const urlBase64 = require('urlsafe-base64');
const url = require('url');
const https = require('https');

const WebPushError = require('./web-push-error.js');
const vapidHelper = require('./vapid-helper.js');
const encryptionHelper = require('./encryption-helper.js');
const webPushConstants = require('./web-push-constants.js');
const urlBase64Helper = require('./urlsafe-base64-helper');

// Default TTL is four weeks.
const DEFAULT_TTL = 2419200;
Expand Down Expand Up @@ -189,7 +189,7 @@ WebPushLib.prototype.generateRequestDetails = function(subscription, payload, op
}

if (options.topic) {
if (!urlBase64.validate(options.topic)) {
if (!urlBase64Helper.validate(options.topic)) {
throw new Error('Unsupported characters set use the URL or filename-safe Base64 characters set');
}
if (options.topic.length > 32) {
Expand Down 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=' + urlBase64.encode(encrypted.localPublicKey);
requestDetails.headers['Crypto-Key'] = 'dh=' + urlBase64Helper.encode(encrypted.localPublicKey);
}

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

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

const cliPath = 'src/cli.js';

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

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

assert.equal(urlBase64.decode(vapidKeys.privateKey).length, 32);
assert.equal(urlBase64.decode(vapidKeys.publicKey).length, 65);
assert.equal(urlBase64Helper.decode(vapidKeys.privateKey).length, 32);
assert.equal(urlBase64Helper.decode(vapidKeys.publicKey).length, 65);

resolve();
});
Expand Down
8 changes: 4 additions & 4 deletions test/test-encryption-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
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 urlBase64 = require('urlsafe-base64');

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

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

return ece.decrypt(encrypted.cipherText, {
version: contentEncoding,
dh: urlBase64.encode(encrypted.localPublicKey),
dh: urlBase64Helper.encode(encrypted.localPublicKey),
privateKey: userCurve,
salt: encrypted.salt,
authSecret: VALID_AUTH
Expand Down
Loading

0 comments on commit de2aa16

Please sign in to comment.