Skip to content

Commit 674188c

Browse files
committed
Update for webpush changes
1 parent 2836ee8 commit 674188c

4 files changed

Lines changed: 170 additions & 85 deletions

File tree

nodejs/decrypt-dh.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ var ece = require('./ece.js');
66

77
if (process.argv.length < 7) {
88
console.warn('Usage: ' + process.argv.slice(0, 2).join(' ') +
9-
' <receiver-private> <receiver-public> <sender-public> <salt> <message> [JSON args]');
9+
' <auth-secret> <receiver-private> <receiver-public> <sender-public> <message> [JSON args]');
1010
process.exit(2);
1111
}
1212

@@ -16,14 +16,16 @@ var receiver = crypto.createECDH('prime256v1');
1616
// 2. it barfs when you try to access the public key, even after you set it
1717
// This hack squelches the complaints at the cost of a few wasted cycles
1818
receiver.generateKeys();
19-
receiver.setPublicKey(base64.decode(process.argv[3]));
20-
receiver.setPrivateKey(base64.decode(process.argv[2]));
21-
ece.saveKey('keyid', receiver, "P-256");
19+
receiver.setPublicKey(base64.decode(process.argv[4]));
20+
receiver.setPrivateKey(base64.decode(process.argv[3]));
21+
var keymap = {};
22+
keymap[''] = receiver;
2223

2324
var params = {
24-
keyid: 'keyid',
25-
dh: process.argv[4],
26-
salt: process.argv[5]
25+
keyid: '',
26+
authSecret: process.argv[2],
27+
dh: process.argv[5],
28+
keymap: keymap
2729
};
2830

2931
if (process.argv.length > 7) {

nodejs/ece.js

Lines changed: 119 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,14 @@ function HMAC_hash(key, input) {
5252

5353
/* HKDF as defined in RFC5869, using SHA-256 */
5454
function HKDF_extract(salt, ikm) {
55-
return HMAC_hash(salt, ikm);
55+
keylog('salt', salt);
56+
keylog('ikm', ikm);
57+
return keylog('extract', HMAC_hash(salt, ikm));
5658
}
5759

5860
function HKDF_expand(prk, info, l) {
61+
keylog('prk', prk);
62+
keylog('info', info);
5963
var output = new Buffer(0);
6064
var T = new Buffer(0);
6165
info = new Buffer(info, 'ascii');
@@ -67,7 +71,7 @@ function HKDF_expand(prk, info, l) {
6771
output = Buffer.concat([output, T]);
6872
}
6973

70-
return output.slice(0, l);
74+
return keylog('expand', output.slice(0, l));
7175
}
7276

7377
function HKDF(salt, ikm, info, len) {
@@ -136,33 +140,83 @@ function extractSecretAndContext(header, mode) {
136140
keylog('secret', result.secret);
137141
keylog('context', result.context);
138142
if (header.authSecret) {
139-
result.secret = HKDF(base64.decode(header.authSecret), result.secret,
143+
result.secret = HKDF(header.authSecret, result.secret,
140144
info('auth', new Buffer(0)), SHA_256_LENGTH);
141145
keylog('authsecret', result.secret);
142146
}
143147
return result;
144148
}
145149

150+
function extractSecret(header, mode) {
151+
if (header.key) {
152+
if (header.key.length !== KEY_LENGTH) {
153+
throw new Error('An explicit key must be ' + KEY_LENGTH + ' bytes');
154+
}
155+
return keylog('secret key', header.key);
156+
}
157+
158+
// We need a key identifier for all the rest.
159+
var key = header.keymap && header.keymap[header.keyid];
160+
if (!key) {
161+
throw new Error('No saved key (keyid: "' + header.keyid + '")');
162+
}
163+
164+
// Simple key
165+
if (!header.dh) {
166+
return keylog('secret saved', key);
167+
}
168+
169+
// We are doing DH
170+
var senderPubKey, receiverPubKey;
171+
if (mode === MODE_ENCRYPT) {
172+
senderPubKey = key.getPublicKey();
173+
receiverPubKey = header.dh;
174+
} else if (mode === MODE_DECRYPT) {
175+
senderPubKey = header.dh;
176+
receiverPubKey = key.getPublicKey();
177+
} else {
178+
throw new Error('Unknown mode only ' + MODE_ENCRYPT +
179+
' and ' + MODE_DECRYPT + ' supported');
180+
}
181+
keylog('authsecret', header.authSecret);
182+
return keylog('secret dh',
183+
HKDF(header.authSecret || Buffer.from(''),
184+
key.computeSecret(header.dh),
185+
Buffer.concat([
186+
Buffer.from('WebPush: info\0'),
187+
receiverPubKey,
188+
senderPubKey
189+
]),
190+
SHA_256_LENGTH));
191+
}
192+
146193
function deriveKeyAndNonce(header, mode) {
147194
if (!header.salt) {
148195
throw new Error('must include a salt parameter for ' + header.type);
149196
}
150-
var s = extractSecretAndContext(header, mode);
151-
var prk = HKDF_extract(header.salt, s.secret);
152197
var keyInfo;
153198
var nonceInfo;
199+
var secret;
154200
if (header.type === 'aesgcm128') {
201+
// really old
155202
keyInfo = 'Content-Encoding: aesgcm128';
156203
nonceInfo = 'Content-Encoding: nonce';
204+
secret = extractSecretAndContext(header, mode).secret;
157205
} else if (header.type === 'aesgcm') {
206+
// old
207+
var s = extractSecretAndContext(header, mode);
158208
keyInfo = info('aesgcm', s.context);
159209
nonceInfo = info('nonce', s.context);
210+
secret = s.secret;
160211
} else if (header.type === 'aes128gcm') {
161-
keyInfo = 'Content-Encoding: aesgcm128\0';
162-
nonceInfo = 'Content-Encoding: nonce\0';
212+
// latest
213+
keyInfo = Buffer.from('Content-Encoding: aesgcm128\0');
214+
nonceInfo = Buffer.from('Content-Encoding: nonce\0');
215+
secret = extractSecret(header, mode);
163216
} else {
164217
throw new Error('Unable to set context for mode ' + params.type);
165218
}
219+
var prk = HKDF_extract(header.salt, secret);
166220
var result = {
167221
key: HKDF_expand(prk, keyInfo, KEY_LENGTH),
168222
nonce: HKDF_expand(prk, nonceInfo, NONCE_LENGTH)
@@ -172,57 +226,56 @@ function deriveKeyAndNonce(header, mode) {
172226
return result;
173227
}
174228

175-
function determineRecordSize(rs, type) {
176-
rs = parseInt(rs, 10);
177-
if (isNaN(rs)) {
178-
return 4096;
229+
function fillCommonParams(header, params) {
230+
if (params.key) {
231+
header.key = base64.decode(params.key);
179232
}
180-
var padSize = PAD_SIZE[type];
181-
if (rs <= padSize) {
182-
throw new Error('The rs parameter has to be greater than ' + padSize);
233+
header.keymap = params.keymap;
234+
if (params.dh) {
235+
header.dh = base64.decode(params.dh);
183236
}
184-
return rs;
185-
}
186-
187-
function extractSalt(salt) {
188-
if (!salt) {
189-
throw new Error('A salt is required');
237+
if (params.authSecret) {
238+
header.authSecret = base64.decode(params.authSecret);
190239
}
191-
salt = base64.decode(salt);
192-
if (salt.length !== KEY_LENGTH) {
193-
throw new Error('The salt parameter must be ' + KEY_LENGTH + ' bytes');
194-
}
195-
return salt;
240+
return header;
196241
}
197242

198243
/* Used when decrypting aes128gcm to populate the header values. */
199244
function readHeader(buffer, params) {
200245
var idsz = buffer.readUIntBE(20, 1);
201-
keylog('header', buffer.slice(0, 21 + idsz));
202-
return {
246+
var header = {
203247
type: 'aes128gcm',
204248
salt: buffer.slice(0, KEY_LENGTH),
205249
rs: buffer.readUIntBE(KEY_LENGTH, 4),
206250
keyid: buffer.slice(21, 21 + idsz).toString('utf-8'),
207-
key: params.key ? base64.decode(params.key) : undefined,
208-
dh: params.dh ? base64.decode(params.dh) : undefined,
209-
authSecret: params.authSecret ? base64.decode(params.authSecret) : undefined,
210251
headerLength: 21 + idsz
211252
};
253+
return fillCommonParams(header, params);
212254
}
213255

214-
/* Used when decrypting to populate the header values for aesgcm[128]. */
256+
/* Used when decrypting to populate the header values for aesgcm[128]. Used also
257+
* to parse the |param| argument when encrypting. */
215258
function parseParams(params) {
216-
var type = (params.padSize === 1) ? 'aesgcm128' : 'aesgcm';
217-
return {
218-
type: type,
219-
salt: params.salt ? extractSalt(params.salt) : undefined,
220-
rs: determineRecordSize(params.rs, type),
259+
var header = {
260+
type: (params.padSize === 1) ? 'aesgcm128' : 'aesgcm',
221261
keyid: params.keyid,
222-
key: params.key ? base64.decode(params.key) : undefined,
223-
dh: params.dh ? base64.decode(params.dh) : undefined,
224-
authSecret: params.authSecret ? base64.decode(params.authSecret) : undefined
262+
headerLength: 0
225263
};
264+
header.rs = parseInt(params.rs, 10);
265+
if (isNaN(header.rs)) {
266+
header.rs = 4096;
267+
}
268+
if (header.rs <= PAD_SIZE[header.type]) {
269+
throw new Error('The rs parameter has to be greater than ' +
270+
PAD_SIZE[header.type]);
271+
}
272+
if (params.salt) {
273+
header.salt = base64.decode(params.salt);
274+
if (header.salt.length !== KEY_LENGTH) {
275+
throw new Error('The salt parameter must be ' + KEY_LENGTH + ' bytes');
276+
}
277+
}
278+
return fillCommonParams(header, params);
226279
}
227280

228281
function generateNonce(base, counter) {
@@ -261,11 +314,14 @@ function decryptRecord(key, counter, buffer, header) {
261314
/**
262315
* Decrypt some bytes. This uses the parameters to determine the key and block
263316
* size, which are described in the draft. Binary values are base64url encoded.
264-
* For an explicit key that key is used. For a keyid on its own, the value of
265-
* the key is a buffer that is stored with saveKey(). For ECDH, the p256-dh
266-
* parameter identifies the public share of the recipient and the keyid is
267-
* anECDH key pair (created by crypto.createECDH()) that is stored using
268-
* saveKey().
317+
*
318+
* If |params.key| is specified, that value is used as the key.
319+
*
320+
* If |params.keyid| is specified without |params.dh|, the keyid value is used
321+
* to lookup the |params.keymap| for a buffer containing the key.
322+
*
323+
* For ECDH, |params.dh| includes the public key of the sender. The ECDH key
324+
* pair used to decrypt is looked up using |params.keymap[params.keyid]|.
269325
*/
270326
function decrypt(buffer, params) {
271327
var header;
@@ -322,15 +378,21 @@ function encodeHeader(header) {
322378
}
323379
ints.writeUIntBE(header.rs, 0, 4);
324380
ints.writeUIntBE(keyid.length, 4, 1);
325-
return keylog('header', Buffer.concat([header.salt, ints, keyid]));
381+
return Buffer.concat([header.salt, ints, keyid]);
326382
}
327383

328384
/**
329385
* Encrypt some bytes. This uses the parameters to determine the key and block
330-
* size, which are described in the draft. Note that for encryption, the
331-
* p256-dh parameter identifies the public share of the recipient and the keyid
332-
* identifies a local DH key pair (created by crypto.createECDH() or
333-
* crypto.createDiffieHellman()).
386+
* size, which are described in the draft.
387+
*
388+
* If |params.key| is specified, that value is used as the key.
389+
*
390+
* If |params.keyid| is specified without |params.dh|, the keyid value is used
391+
* to lookup the |params.keymap| for a buffer containing the key.
392+
*
393+
* For Diffie-Hellman, |params.dh| includes the public key of the receiver. The
394+
* ECDH key pair used to encrypt is looked up using |params.keymap[params.keyid]|.
395+
* Key pairs can be created using |crypto.createECDH()|.
334396
*/
335397
function encrypt(buffer, params) {
336398
if (!Buffer.isBuffer(buffer)) {
@@ -346,7 +408,6 @@ function encrypt(buffer, params) {
346408
header.salt = crypto.randomBytes(KEY_LENGTH);
347409
result = encodeHeader(header);
348410
}
349-
header.rs = determineRecordSize(params.rs, header.type);
350411

351412
var key = deriveKeyAndNonce(header, MODE_ENCRYPT);
352413
var start = 0;
@@ -374,10 +435,14 @@ function encrypt(buffer, params) {
374435
}
375436

376437
/**
377-
* This function saves a key under the provided identifier. This is used to
378-
* save the keys that are used to decrypt and encrypt blobs that are identified
379-
* by a 'keyid'. DH or ECDH keys that are used with the 'dh' parameter need to
380-
* include a label (included in 'dhLabel') that identifies them.
438+
* This function saves a key under the provided identifier. This is only used
439+
* by aesgcm and aesgcm128 codings. For the aes128gcm coding, use the |keymap|
440+
* parameter to encrypt() and decrypt().
441+
*
442+
* This is used to save the keys that are used to decrypt and encrypt blobs that
443+
* are identified by a 'keyid'. DH or ECDH keys that are used with the 'dh'
444+
* parameter need to include a label (included in 'dhLabel') that identifies
445+
* them.
381446
*/
382447
function saveKey(id, key, dhLabel) {
383448
savedKeys[id] = key;

nodejs/encrypt-dh.js

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,20 @@ var base64 = require('urlsafe-base64');
44
var crypto = require('crypto');
55
var ece = require('./ece.js');
66

7-
if (process.argv.length < 4) {
7+
if (process.argv.length < 5) {
88
console.warn('Usage: ' + process.argv.slice(0, 2).join(' ') +
9-
' <receiver-public> <message> [JSON args]');
9+
' <auth-secret> <receiver-public> <message> [JSON args]');
1010
process.exit(2);
1111
}
1212

1313
var params = {
14-
keyid: 'keyid',
15-
dh: process.argv[2]
14+
keyid: '',
15+
authSecret: process.argv[2],
16+
dh: process.argv[3]
1617
};
1718

18-
if (process.argv.length > 4) {
19-
var extra = JSON.parse(process.argv[4]);
19+
if (process.argv.length > 5) {
20+
var extra = JSON.parse(process.argv[5]);
2021
Object.keys(extra).forEach(function(k) {
2122
params[k] = extra[k];
2223
});
@@ -34,14 +35,12 @@ if (params.senderPublic) {
3435
} else {
3536
params.senderPublic = base64.encode(sender.getPublicKey());
3637
}
37-
ece.saveKey('keyid', sender, "P-256");
38-
39-
if (!params.salt) {
40-
params.salt = base64.encode(crypto.randomBytes(16));
41-
}
38+
var keymap = {};
39+
keymap[''] = sender;
40+
params.keymap = keymap;
4241

4342
console.log("Params: " + JSON.stringify(params, null, 2));
44-
var result = ece.encrypt(base64.decode(process.argv[3]), params);
43+
var result = ece.encrypt(base64.decode(process.argv[4]), params);
4544

4645
console.log("Public Key: " + base64.encode(sender.getPublicKey()));
4746
console.log("Encrypted Message: " + base64.encode(result));

0 commit comments

Comments
 (0)