Skip to content

Commit de2aa16

Browse files
Migrate urlsafe-base64 to be internal to web-push and switch to Buffer.from (#813)
Fixes #785 Co-authored-by: Dan Lee <[email protected]>
1 parent 2eb5ea2 commit de2aa16

12 files changed

+115
-85
lines changed

package-lock.json

Lines changed: 19 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@
3333
"http_ece": "1.1.0",
3434
"https-proxy-agent": "^5.0.0",
3535
"jws": "^4.0.0",
36-
"minimist": "^1.2.5",
37-
"urlsafe-base64": "^1.0.0"
36+
"minimist": "^1.2.5"
3837
},
3938
"devDependencies": {
4039
"chromedriver": "101.0.0",

src/encryption-helper.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
const crypto = require('crypto');
44
const ece = require('http_ece');
5-
const urlBase64 = require('urlsafe-base64');
5+
const urlBase64Helper = require('./urlsafe-base64-helper');
66

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

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

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

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

44-
const salt = urlBase64.encode(crypto.randomBytes(16));
44+
const salt = urlBase64Helper.encode(crypto.randomBytes(16));
4545

4646
const cipherText = ece.encrypt(payload, {
4747
version: contentEncoding,

src/urlsafe-base64-helper.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Largely ported from https://github.com/RGBboy/urlsafe-base64
2+
3+
'use strict';
4+
5+
function encode(buffer) {
6+
return buffer.toString('base64')
7+
.replace(/\+/g, '-') // Convert '+' to '-'
8+
.replace(/\//g, '_') // Convert '/' to '_'
9+
.replace(/=+$/, ''); // Remove ending '='
10+
}
11+
12+
function decode(base64) {
13+
// Add removed at end '='
14+
base64 += Array(5 - (base64.length % 4)).join('=');
15+
16+
base64 = base64
17+
.replace(/-/g, '+') // Convert '-' to '+'
18+
.replace(/_/g, '/'); // Convert '_' to '/'
19+
20+
// change from urlsafe-base64 since new Buffer() is deprecated
21+
return Buffer.from(base64, 'base64');
22+
}
23+
24+
function validate(base64) {
25+
return /^[A-Za-z0-9\-_]+$/.test(base64);
26+
}
27+
28+
module.exports = {
29+
encode: encode,
30+
decode: decode,
31+
validate: validate
32+
};

src/vapid-helper.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
'use strict';
22

33
const crypto = require('crypto');
4-
const urlBase64 = require('urlsafe-base64');
54
const asn1 = require('asn1.js');
65
const jws = require('jws');
76
const { URL } = require('url');
87

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

1111
/**
1212
* DEFAULT_EXPIRATION is set to seconds in 12 hours
@@ -60,8 +60,8 @@ function generateVAPIDKeys() {
6060
}
6161

6262
return {
63-
publicKey: urlBase64.encode(publicKeyBuffer),
64-
privateKey: urlBase64.encode(privateKeyBuffer)
63+
publicKey: urlBase64Helper.encode(publicKeyBuffer),
64+
privateKey: urlBase64Helper.encode(privateKeyBuffer)
6565
};
6666
}
6767

@@ -100,11 +100,11 @@ function validatePublicKey(publicKey) {
100100
+ 'encoded string.');
101101
}
102102

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

107-
publicKey = urlBase64.decode(publicKey);
107+
publicKey = urlBase64Helper.decode(publicKey);
108108

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

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

128-
privateKey = urlBase64.decode(privateKey);
128+
privateKey = urlBase64Helper.decode(privateKey);
129129

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

206-
privateKey = urlBase64.decode(privateKey);
206+
privateKey = urlBase64Helper.decode(privateKey);
207207

208208
if (expiration) {
209209
validateExpiration(expiration);

src/web-push-lib.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
'use strict';
22

3-
const urlBase64 = require('urlsafe-base64');
43
const url = require('url');
54
const https = require('https');
65

76
const WebPushError = require('./web-push-error.js');
87
const vapidHelper = require('./vapid-helper.js');
98
const encryptionHelper = require('./encryption-helper.js');
109
const webPushConstants = require('./web-push-constants.js');
10+
const urlBase64Helper = require('./urlsafe-base64-helper');
1111

1212
// Default TTL is four weeks.
1313
const DEFAULT_TTL = 2419200;
@@ -189,7 +189,7 @@ WebPushLib.prototype.generateRequestDetails = function(subscription, payload, op
189189
}
190190

191191
if (options.topic) {
192-
if (!urlBase64.validate(options.topic)) {
192+
if (!urlBase64Helper.validate(options.topic)) {
193193
throw new Error('Unsupported characters set use the URL or filename-safe Base64 characters set');
194194
}
195195
if (options.topic.length > 32) {
@@ -251,7 +251,7 @@ WebPushLib.prototype.generateRequestDetails = function(subscription, payload, op
251251
} else if (contentEncoding === webPushConstants.supportedContentEncodings.AES_GCM) {
252252
requestDetails.headers['Content-Encoding'] = webPushConstants.supportedContentEncodings.AES_GCM;
253253
requestDetails.headers.Encryption = 'salt=' + encrypted.salt;
254-
requestDetails.headers['Crypto-Key'] = 'dh=' + urlBase64.encode(encrypted.localPublicKey);
254+
requestDetails.headers['Crypto-Key'] = 'dh=' + urlBase64Helper.encode(encrypted.localPublicKey);
255255
}
256256

257257
requestPayload = encrypted.cipherText;

test/test-cli.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
}
99

1010
const assert = require('assert');
11-
const urlBase64 = require('urlsafe-base64');
1211
const spawn = require('child_process').spawn;
12+
const urlBase64Helper = require('../src/urlsafe-base64-helper');
1313

1414
const cliPath = 'src/cli.js';
1515

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

152152
const privateKeyTitleIndex = lines.findIndex(function(line) {
153153
return line.indexOf('Private Key:') !== -1;
154154
});
155155
const privateKey = lines[privateKeyTitleIndex + 1].trim();
156-
assert.equal(urlBase64.decode(privateKey).length, 32);
156+
assert.equal(urlBase64Helper.decode(privateKey).length, 32);
157157
resolve();
158158
});
159159
});
@@ -185,8 +185,8 @@
185185
assert(vapidKeys.publicKey);
186186
assert(vapidKeys.privateKey);
187187

188-
assert.equal(urlBase64.decode(vapidKeys.privateKey).length, 32);
189-
assert.equal(urlBase64.decode(vapidKeys.publicKey).length, 65);
188+
assert.equal(urlBase64Helper.decode(vapidKeys.privateKey).length, 32);
189+
assert.equal(urlBase64Helper.decode(vapidKeys.publicKey).length, 65);
190190

191191
resolve();
192192
});

test/test-encryption-helper.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
const assert = require('assert');
44
const crypto = require('crypto');
55
const webPush = require('../src/index');
6+
const urlBase64Helper = require('../src/urlsafe-base64-helper');
67
const ece = require('http_ece');
7-
const urlBase64 = require('urlsafe-base64');
88

99
const userCurve = crypto.createECDH('prime256v1');
10-
const VALID_PUBLIC_KEY = urlBase64.encode(userCurve.generateKeys());
11-
const VALID_AUTH = urlBase64.encode(crypto.randomBytes(16));
10+
const VALID_PUBLIC_KEY = urlBase64Helper.encode(userCurve.generateKeys());
11+
const VALID_AUTH = urlBase64Helper.encode(crypto.randomBytes(16));
1212

1313
suite('Test Encryption Helpers', function() {
1414
test('is defined', function() {
@@ -20,7 +20,7 @@ suite('Test Encryption Helpers', function() {
2020

2121
return ece.decrypt(encrypted.cipherText, {
2222
version: contentEncoding,
23-
dh: urlBase64.encode(encrypted.localPublicKey),
23+
dh: urlBase64Helper.encode(encrypted.localPublicKey),
2424
privateKey: userCurve,
2525
salt: encrypted.salt,
2626
authSecret: VALID_AUTH

0 commit comments

Comments
 (0)