Skip to content

Commit cc26d8a

Browse files
authored
Use builtin encoding base64url (#819)
Node 16+ supports base64url
1 parent 6fc5ce1 commit cc26d8a

10 files changed

+58
-84
lines changed

src/encryption-helper.js

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

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

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

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

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

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

44-
const salt = urlBase64Helper.encode(crypto.randomBytes(16));
43+
const salt = crypto.randomBytes(16).toString('base64url');
4544

4645
const cipherText = ece.encrypt(payload, {
4746
version: contentEncoding,

src/urlsafe-base64-helper.js

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,13 @@
1-
// Largely ported from https://github.com/RGBboy/urlsafe-base64
2-
31
'use strict';
42

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-
3+
/**
4+
* @param {string} base64
5+
* @returns {boolean}
6+
*/
247
function validate(base64) {
25-
return /^[A-Za-z0-9\-_]+$/.test(base64);
26-
}
8+
return /^[A-Za-z0-9\-_]+$/.test(base64);
9+
}
2710

2811
module.exports = {
29-
encode: encode,
30-
decode: decode,
31-
validate: validate
12+
validate: validate
3213
};

src/vapid-helper.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ function generateVAPIDKeys() {
6060
}
6161

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

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

107-
publicKey = urlBase64Helper.decode(publicKey);
107+
publicKey = Buffer.from(publicKey, 'base64url');
108108

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

128-
privateKey = urlBase64Helper.decode(privateKey);
128+
privateKey = Buffer.from(privateKey, 'base64url');
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 = urlBase64Helper.decode(privateKey);
206+
privateKey = Buffer.from(privateKey, 'base64url');
207207

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

src/web-push-lib.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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=' + urlBase64Helper.encode(encrypted.localPublicKey);
254+
requestDetails.headers['Crypto-Key'] = 'dh=' + encrypted.localPublicKey.toString('base64url');
255255
}
256256

257257
requestPayload = encrypted.cipherText;

test/test-cli.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

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

1413
const cliPath = 'src/cli.js';
1514

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

152151
const privateKeyTitleIndex = lines.findIndex(function(line) {
153152
return line.indexOf('Private Key:') !== -1;
154153
});
155154
const privateKey = lines[privateKeyTitleIndex + 1].trim();
156-
assert.equal(urlBase64Helper.decode(privateKey).length, 32);
155+
assert.equal(Buffer.from(privateKey, 'base64url').length, 32);
157156
resolve();
158157
});
159158
});
@@ -185,8 +184,8 @@
185184
assert(vapidKeys.publicKey);
186185
assert(vapidKeys.privateKey);
187186

188-
assert.equal(urlBase64Helper.decode(vapidKeys.privateKey).length, 32);
189-
assert.equal(urlBase64Helper.decode(vapidKeys.publicKey).length, 65);
187+
assert.equal(Buffer.from(vapidKeys.privateKey, 'base64url').length, 32);
188+
assert.equal(Buffer.from(vapidKeys.publicKey, 'base64url').length, 65);
190189

191190
resolve();
192191
});

test/test-encryption-helper.js

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

98
const userCurve = crypto.createECDH('prime256v1');
10-
const VALID_PUBLIC_KEY = urlBase64Helper.encode(userCurve.generateKeys());
11-
const VALID_AUTH = urlBase64Helper.encode(crypto.randomBytes(16));
9+
const VALID_PUBLIC_KEY = userCurve.generateKeys().toString('base64url');
10+
const VALID_AUTH = crypto.randomBytes(16).toString('base64url');
1211

1312
suite('Test Encryption Helpers', function() {
1413
test('is defined', function() {
@@ -20,7 +19,7 @@ suite('Test Encryption Helpers', function() {
2019

2120
return ece.decrypt(encrypted.cipherText, {
2221
version: contentEncoding,
23-
dh: urlBase64Helper.encode(encrypted.localPublicKey),
22+
dh: encrypted.localPublicKey.toString('base64url'),
2423
privateKey: userCurve,
2524
salt: encrypted.salt,
2625
authSecret: VALID_AUTH

test/test-generate-request-details.js

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

33
const assert = require('assert');
44
const webPush = require('../src/index');
5-
const urlBase64Helper = require('../src/urlsafe-base64-helper');
65
const crypto = require('crypto');
76
const jws = require('jws');
87
const urlParse = require('url').parse;
@@ -19,8 +18,8 @@ suite('Test Generate Request Details', function() {
1918
const vapidKeys = require('../src/vapid-helper').generateVAPIDKeys();
2019

2120
const VALID_KEYS = {
22-
p256dh: urlBase64Helper.encode(userPublicKey),
23-
auth: urlBase64Helper.encode(userAuth)
21+
p256dh: userPublicKey.toString('base64url'),
22+
auth: userAuth.toString('base64url')
2423
};
2524

2625
const invalidRequests = [
@@ -85,7 +84,7 @@ suite('Test Generate Request Details', function() {
8584
subscription: {
8685
endpoint: true,
8786
keys: {
88-
p256dh: urlBase64Helper.encode(userPublicKey)
87+
p256dh: userPublicKey.toString('base64url')
8988
}
9089
},
9190
message: 'hello'
@@ -96,7 +95,7 @@ suite('Test Generate Request Details', function() {
9695
subscription: {
9796
endpoint: true,
9897
keys: {
99-
auth: urlBase64Helper.encode(userAuth)
98+
auth: userAuth.toString('base64url')
10099
}
101100
},
102101
message: 'hello'
@@ -107,7 +106,7 @@ suite('Test Generate Request Details', function() {
107106
subscription: {
108107
keys: {
109108
p256dh: userPublicKey,
110-
auth: urlBase64Helper.encode(userAuth)
109+
auth: userAuth.toString('base64url')
111110
}
112111
},
113112
message: 'hello'
@@ -118,7 +117,7 @@ suite('Test Generate Request Details', function() {
118117
requestOptions: {
119118
subscription: {
120119
keys: {
121-
p256dh: urlBase64Helper.encode(userPublicKey),
120+
p256dh: userPublicKey.toString('base64url'),
122121
auth: userAuth
123122
}
124123
},
@@ -130,8 +129,8 @@ suite('Test Generate Request Details', function() {
130129
requestOptions: {
131130
subscription: {
132131
keys: {
133-
p256dh: urlBase64Helper.encode(Buffer.concat([userPublicKey, Buffer.alloc(1)])),
134-
auth: urlBase64Helper.encode(userAuth)
132+
p256dh: Buffer.concat([userPublicKey, Buffer.alloc(1)]).toString('base64url'),
133+
auth: userAuth.toString('base64url')
135134
}
136135
},
137136
message: 'hello'
@@ -142,8 +141,8 @@ suite('Test Generate Request Details', function() {
142141
requestOptions: {
143142
subscription: {
144143
keys: {
145-
p256dh: urlBase64Helper.encode(userPublicKey.slice(1)),
146-
auth: urlBase64Helper.encode(userAuth)
144+
p256dh: userPublicKey.slice(1).toString('base64url'),
145+
auth: userAuth.toString('base64url')
147146
}
148147
},
149148
message: 'hello'
@@ -154,8 +153,8 @@ suite('Test Generate Request Details', function() {
154153
requestOptions: {
155154
subscription: {
156155
keys: {
157-
p256dh: urlBase64Helper.encode(userPublicKey),
158-
auth: urlBase64Helper.encode(userAuth.slice(1))
156+
p256dh: userPublicKey.toString('base64url'),
157+
auth: userAuth.slice(1).toString('base64url')
159158
}
160159
},
161160
message: 'hello'

test/test-set-vapid-details.js

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

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

76
const VALID_SUBJECT_MAILTO = 'mailto: [email protected]';
87
const VALID_SUBJECT_URL = 'https://exampe.com/contact';
9-
const VALID_PUBLIC_KEY = urlBase64Helper.encode(Buffer.alloc(65));
10-
const VALID_PRIVATE_KEY = urlBase64Helper.encode(Buffer.alloc(32));
8+
const VALID_PUBLIC_KEY = Buffer.alloc(65).toString('base64url');
9+
const VALID_PRIVATE_KEY = Buffer.alloc(32).toString('base64url');
1110

1211
suite('setVapidDetails()', function() {
1312
test('is defined', function() {
@@ -55,7 +54,7 @@ suite('setVapidDetails()', function() {
5554
},
5655
{
5756
subject: VALID_SUBJECT_URL,
58-
publicKey: urlBase64Helper.encode(Buffer.alloc(60)),
57+
publicKey: Buffer.alloc(60).toString('base64url'),
5958
privateKey: VALID_PRIVATE_KEY
6059
},
6160
{
@@ -81,7 +80,7 @@ suite('setVapidDetails()', function() {
8180
{
8281
subject: VALID_SUBJECT_URL,
8382
publicKey: VALID_PUBLIC_KEY,
84-
privateKey: urlBase64Helper.encode(Buffer.alloc(60))
83+
privateKey: Buffer.alloc(60).toString('base64url')
8584
},
8685
{
8786
subject: VALID_SUBJECT_URL,

test/test-vapid-helper.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ const crypto = require('crypto');
66
const mocha = require('mocha');
77
const webPush = require('../src/index');
88
const vapidHelper = require('../src/vapid-helper');
9-
const urlBase64Helper = require('../src/urlsafe-base64-helper');
109

1110
const VALID_AUDIENCE = 'https://example.com';
1211
const VALID_SUBJECT_MAILTO = 'mailto:[email protected]';
@@ -15,9 +14,9 @@ const VALID_SUBJECT_URL = 'https://example.com/contact';
1514
const WARN_SUBJECT_LOCALHOST_URL = 'https://localhost';
1615
const INVALID_SUBJECT_URL_1 = 'http://example.gov';
1716
const INVALID_SUBJECT_URL_2 = 'ftp://example.net';
18-
const VALID_PUBLIC_KEY = urlBase64Helper.encode(Buffer.alloc(65));
17+
const VALID_PUBLIC_KEY = Buffer.alloc(65).toString('base64url');
1918
const VALID_UNSAFE_BASE64_PUBLIC_KEY = Buffer.alloc(65).toString('base64');
20-
const VALID_PRIVATE_KEY = urlBase64Helper.encode(Buffer.alloc(32));
19+
const VALID_PRIVATE_KEY = Buffer.alloc(32).toString('base64url');
2120
const VALID_UNSAFE_BASE64_PRIVATE_KEY = Buffer.alloc(32).toString('base64');
2221
const VALID_CONTENT_ENCODING = webPush.supportedContentEncodings.AES_GCM;
2322
const VALID_EXPIRATION = Math.floor(Date.now() / 1000) + (60 * 60 * 12);
@@ -46,8 +45,8 @@ suite('Test Vapid Helpers', function() {
4645
assert.equal(typeof keys.privateKey, 'string');
4746
assert.equal(typeof keys.publicKey, 'string');
4847

49-
assert.equal(urlBase64Helper.decode(keys.privateKey).length, 32);
50-
assert.equal(urlBase64Helper.decode(keys.publicKey).length, 65);
48+
assert.equal(Buffer.from(keys.privateKey, 'base64url').length, 32);
49+
assert.equal(Buffer.from(keys.publicKey, 'base64url').length, 65);
5150
});
5251

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

69-
assert.equal(urlBase64Helper.decode(keys.privateKey).length, 32);
70-
assert.equal(urlBase64Helper.decode(keys.publicKey).length, 65);
68+
assert.equal(Buffer.from(keys.privateKey, 'base64url').length, 32);
69+
assert.equal(Buffer.from(keys.publicKey, 'base64url').length, 65);
7170
});
7271

7372
test('generate new vapid keys between calls', function() {

0 commit comments

Comments
 (0)