Skip to content

Commit 4acc7a1

Browse files
committed
All round improvements to testing and API for WebPush changes
1 parent ae2f1f8 commit 4acc7a1

2 files changed

Lines changed: 170 additions & 107 deletions

File tree

nodejs/ece.js

Lines changed: 62 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,16 @@ function lengthPrefix(buffer) {
101101
}
102102

103103
function extractDH(header, mode) {
104-
if (!header.keymap[header.keyid]) {
105-
throw new Error('No known DH key for ' + header.keyid);
104+
var key = header.privateKey;
105+
if (!key) {
106+
if (!header.keymap || !header.keyid || !header.keymap[header.keyid]) {
107+
throw new Error('No known DH key for ' + header.keyid);
108+
}
109+
key = header.keymap[header.keyid];
106110
}
107111
if (!header.keylabels[header.keyid]) {
108112
throw new Error('No known DH key label for ' + header.keyid);
109113
}
110-
var key = header.keymap[header.keyid];
111114
var senderPubKey, receiverPubKey;
112115
if (mode === MODE_ENCRYPT) {
113116
senderPubKey = key.getPublicKey();
@@ -139,7 +142,7 @@ function extractSecretAndContext(header, mode) {
139142
}
140143
} else if (header.dh) { // receiver/decrypt
141144
result = extractDH(header, mode);
142-
} else if (header.keyid) {
145+
} else if (typeof header.keyid !== undefined) {
143146
result.secret = header.keymap[header.keyid];
144147
}
145148
if (!result.secret) {
@@ -155,41 +158,27 @@ function extractSecretAndContext(header, mode) {
155158
return result;
156159
}
157160

158-
function extractSecret(header, mode) {
159-
if (header.key) {
160-
if (header.key.length !== KEY_LENGTH) {
161-
throw new Error('An explicit key must be ' + KEY_LENGTH + ' bytes');
162-
}
163-
return keylog('secret key', header.key);
164-
}
165-
166-
// We need a key identifier for all the rest.
167-
var key = header.keymap && header.keymap[header.keyid];
168-
if (!key) {
169-
throw new Error('No saved key (keyid: "' + header.keyid + '")');
170-
}
171-
172-
// Simple key
173-
if (!header.dh) {
174-
return keylog('secret saved', key);
161+
function webpushSecret(header, mode) {
162+
if (!header.authSecret) {
163+
throw new Error('No authentication secret for webpush');
175164
}
165+
keylog('authsecret', header.authSecret);
176166

177-
// We are doing DH
178-
var senderPubKey, receiverPubKey;
167+
var remotePubKey, senderPubKey, receiverPubKey;
179168
if (mode === MODE_ENCRYPT) {
180-
senderPubKey = key.getPublicKey();
181-
receiverPubKey = header.dh;
169+
senderPubKey = header.privateKey.getPublicKey();
170+
remotePubKey = receiverPubKey = header.dh;
182171
} else if (mode === MODE_DECRYPT) {
183-
senderPubKey = header.dh;
184-
receiverPubKey = key.getPublicKey();
172+
remotePubKey = senderPubKey = header.keyid;
173+
receiverPubKey = header.privateKey.getPublicKey();
185174
} else {
186175
throw new Error('Unknown mode only ' + MODE_ENCRYPT +
187176
' and ' + MODE_DECRYPT + ' supported');
188177
}
189-
keylog('authsecret', header.authSecret);
178+
keylog('remote pubkey', remotePubKey);
190179
return keylog('secret dh',
191-
HKDF(header.authSecret || Buffer.from(''),
192-
key.computeSecret(header.dh),
180+
HKDF(header.authSecret,
181+
header.privateKey.computeSecret(remotePubKey),
193182
Buffer.concat([
194183
Buffer.from('WebPush: info\0'),
195184
receiverPubKey,
@@ -198,6 +187,26 @@ function extractSecret(header, mode) {
198187
SHA_256_LENGTH));
199188
}
200189

190+
function extractSecret(header, mode) {
191+
if (header.key) {
192+
if (header.key.length !== KEY_LENGTH) {
193+
throw new Error('An explicit key must be ' + KEY_LENGTH + ' bytes');
194+
}
195+
return keylog('secret key', header.key);
196+
}
197+
198+
if (!header.privateKey) {
199+
// Lookup based on keyid
200+
var key = header.keymap && header.keymap[header.keyid];
201+
if (!key) {
202+
throw new Error('No saved key (keyid: "' + header.keyid + '")');
203+
}
204+
return key;
205+
}
206+
207+
return webpushSecret(header, mode);
208+
}
209+
201210
function deriveKeyAndNonce(header, mode) {
202211
if (!header.salt) {
203212
throw new Error('must include a salt parameter for ' + header.version);
@@ -234,10 +243,6 @@ function deriveKeyAndNonce(header, mode) {
234243
return result;
235244
}
236245

237-
function fillCommonParams(header, params) {
238-
return header;
239-
}
240-
241246
/* Parse command-line arguments. */
242247
function parseParams(params) {
243248
var header = {};
@@ -266,8 +271,13 @@ function parseParams(params) {
266271
if (params.key) {
267272
header.key = decode(params.key);
268273
} else {
269-
header.keymap = params.keymap || saved.keymap;
270-
header.keylabels = params.keylabels || saved.keylabels;
274+
header.privateKey = params.privateKey;
275+
if (!header.privateKey) {
276+
header.keymap = params.keymap || saved.keymap;
277+
}
278+
if (header.version !== 'aes128gcm') {
279+
header.keylabels = params.keylabels || saved.keylabels;
280+
}
271281
if (params.dh) {
272282
header.dh = decode(params.dh);
273283
}
@@ -294,7 +304,7 @@ function readHeader(buffer, header) {
294304
var idsz = buffer.readUIntBE(20, 1);
295305
header.salt = buffer.slice(0, KEY_LENGTH);
296306
header.rs = buffer.readUIntBE(KEY_LENGTH, 4);
297-
header.keyid = buffer.slice(21, 21 + idsz).toString('utf-8');
307+
header.keyid = buffer.slice(21, 21 + idsz);
298308
return 21 + idsz;
299309
}
300310

@@ -336,8 +346,12 @@ function decryptRecord(key, counter, buffer, header) {
336346
* If |params.keyid| is specified without |params.dh|, the keyid value is used
337347
* to lookup the |params.keymap| for a buffer containing the key.
338348
*
339-
* For ECDH, |params.dh| includes the public key of the sender. The ECDH key
349+
* For version aesgcm and aesgcm128, |params.dh| includes the public key of the sender. The ECDH key
340350
* pair used to decrypt is looked up using |params.keymap[params.keyid]|.
351+
*
352+
* Version aes128gcm is stricter. The |params.privateKey| includes the private
353+
* key of the receiver. The keyid is extracted from the header and used as the
354+
* ECDH public key of the sender.
341355
*/
342356
function decrypt(buffer, params) {
343357
var header = parseParams(params);
@@ -410,9 +424,12 @@ function writeHeader(header) {
410424
* If |params.keyid| is specified without |params.dh|, the keyid value is used
411425
* to lookup the |params.keymap| for a buffer containing the key.
412426
*
413-
* For Diffie-Hellman, |params.dh| includes the public key of the receiver. The
414-
* ECDH key pair used to encrypt is looked up using |params.keymap[params.keyid]|.
415-
* Key pairs can be created using |crypto.createECDH()|.
427+
* For Diffie-Hellman (WebPush), |params.dh| includes the public key of the
428+
* receiver. |params.privateKey| is used to establish a shared secret. For
429+
* versions aesgcm and aesgcm128, if a private key is not provided, the ECDH key
430+
* pair used to encrypt is looked up using |params.keymap[params.keyid]|, and
431+
* |params.keymap| defaults to the values saved with saveKey(). Key pairs can
432+
* be created using |crypto.createECDH()|.
416433
*/
417434
function encrypt(buffer, params) {
418435
if (!Buffer.isBuffer(buffer)) {
@@ -425,6 +442,10 @@ function encrypt(buffer, params) {
425442

426443
var result;
427444
if (header.version === 'aes128gcm') {
445+
// Save the DH public key in the header.
446+
if (header.privateKey && !header.keyid) {
447+
header.keyid = header.privateKey.getPublicKey();
448+
}
428449
result = writeHeader(header);
429450
} else {
430451
// No header on other versions

0 commit comments

Comments
 (0)