44 *
55 * === Note about versions ===
66 *
7- * This code supports multiple versions of the draft:
7+ * This code supports multiple versions of the draft. This is selected using
8+ * the |version| parameter.
89 *
910 * aes128gcm: The most recent version, the salt, record size and key identifier
10- * are included in a header that is part of the encrypted content coding. In
11- * order to select this version, the |salt| parameter is omitted from calls
12- * to encrypt() and decrypt(). A value for the salt is automatically
13- * generated by this API when encrypting, which avoids errors.
11+ * are included in a header that is part of the encrypted content coding.
1412 *
15- * aesgcm: The version that is widely deployed with WebPush (as of 2016-10).
16- * This version is selected by default if you provide a |salt| parameter and
17- * either omit the |padSize| parameter, or set it to 2.
13+ * aesgcm: The version that is widely deployed with WebPush (as of 2016-11).
14+ * This version is selected by default, unless you specify a |padSize| of 1.
1815 *
1916 * aesgcm128: This version is old and will be removed in an upcoming release.
20- * Select this version by providing |salt| and a |padSize| of 1.
17+ * This version is selected by providing a |padSize| parameter of 1.
2118 */
2219
2320var crypto = require ( 'crypto' ) ;
2421var base64 = require ( 'urlsafe-base64' ) ;
2522
26- var savedKeys = { } ;
27- var keyLabels = { } ;
23+ var saved = {
24+ keymap : { } ,
25+ keylabels : { }
26+ } ;
2827var AES_GCM = 'aes-128-gcm' ;
2928var PAD_SIZE = { 'aes128gcm' : 2 , 'aesgcm' : 2 , 'aesgcm128' : 1 } ;
3029var TAG_LENGTH = 16 ;
@@ -44,6 +43,14 @@ if (process.env.ECE_KEYLOG === '1') {
4443 keylog = function ( m , k ) { return k ; } ;
4544}
4645
46+ /* Optionally base64 decode something. */
47+ function decode ( b ) {
48+ if ( typeof b === 'string' ) {
49+ return base64 . decode ( b ) ;
50+ }
51+ return b ;
52+ }
53+
4754function HMAC_hash ( key , input ) {
4855 var hmac = crypto . createHmac ( 'sha256' , key ) ;
4956 hmac . update ( input ) ;
@@ -93,29 +100,30 @@ function lengthPrefix(buffer) {
93100 return b ;
94101}
95102
96- function extractDH ( keyid , share , mode ) {
97- if ( ! savedKeys [ keyid ] ) {
98- throw new Error ( 'No known DH key for ' + keyid ) ;
103+ function extractDH ( header , mode ) {
104+ if ( ! header . keymap [ header . keyid ] ) {
105+ throw new Error ( 'No known DH key for ' + header . keyid ) ;
99106 }
100- if ( ! keyLabels [ keyid ] ) {
101- throw new Error ( 'No known DH key label for ' + keyid ) ;
107+ if ( ! header . keylabels [ header . keyid ] ) {
108+ throw new Error ( 'No known DH key label for ' + header . keyid ) ;
102109 }
103- var key = savedKeys [ keyid ] ;
110+ var key = header . keymap [ header . keyid ] ;
104111 var senderPubKey , receiverPubKey ;
105112 if ( mode === MODE_ENCRYPT ) {
106113 senderPubKey = key . getPublicKey ( ) ;
107- receiverPubKey = share ;
114+ receiverPubKey = header . dh ;
108115 } else if ( mode === MODE_DECRYPT ) {
109- senderPubKey = share ;
116+ senderPubKey = header . dh ;
110117 receiverPubKey = key . getPublicKey ( ) ;
111118 } else {
112119 throw new Error ( 'Unknown mode only ' + MODE_ENCRYPT +
113120 ' and ' + MODE_DECRYPT + ' supported' ) ;
114121 }
115122 return {
116- secret : key . computeSecret ( share ) ,
123+ secret : key . computeSecret ( header . dh ) ,
117124 context : Buffer . concat ( [
118- keyLabels [ keyid ] ,
125+ decode ( header . keylabels [ header . keyid ] ) ,
126+ Buffer . from ( [ 0 ] ) ,
119127 lengthPrefix ( receiverPubKey ) ,
120128 lengthPrefix ( senderPubKey )
121129 ] )
@@ -130,9 +138,9 @@ function extractSecretAndContext(header, mode) {
130138 throw new Error ( 'An explicit key must be ' + KEY_LENGTH + ' bytes' ) ;
131139 }
132140 } else if ( header . dh ) { // receiver/decrypt
133- result = extractDH ( header . keyid , header . dh , mode ) ;
141+ result = extractDH ( header , mode ) ;
134142 } else if ( header . keyid ) {
135- result . secret = savedKeys [ header . keyid ] ;
143+ result . secret = header . keymap [ header . keyid ] ;
136144 }
137145 if ( ! result . secret ) {
138146 throw new Error ( 'Unable to determine key' ) ;
@@ -192,29 +200,29 @@ function extractSecret(header, mode) {
192200
193201function deriveKeyAndNonce ( header , mode ) {
194202 if ( ! header . salt ) {
195- throw new Error ( 'must include a salt parameter for ' + header . type ) ;
203+ throw new Error ( 'must include a salt parameter for ' + header . version ) ;
196204 }
197205 var keyInfo ;
198206 var nonceInfo ;
199207 var secret ;
200- if ( header . type === 'aesgcm128' ) {
208+ if ( header . version === 'aesgcm128' ) {
201209 // really old
202210 keyInfo = 'Content-Encoding: aesgcm128' ;
203211 nonceInfo = 'Content-Encoding: nonce' ;
204212 secret = extractSecretAndContext ( header , mode ) . secret ;
205- } else if ( header . type === 'aesgcm' ) {
213+ } else if ( header . version === 'aesgcm' ) {
206214 // old
207215 var s = extractSecretAndContext ( header , mode ) ;
208216 keyInfo = info ( 'aesgcm' , s . context ) ;
209217 nonceInfo = info ( 'nonce' , s . context ) ;
210218 secret = s . secret ;
211- } else if ( header . type === 'aes128gcm' ) {
219+ } else if ( header . version === 'aes128gcm' ) {
212220 // latest
213221 keyInfo = Buffer . from ( 'Content-Encoding: aes128gcm\0' ) ;
214222 nonceInfo = Buffer . from ( 'Content-Encoding: nonce\0' ) ;
215223 secret = extractSecret ( header , mode ) ;
216224 } else {
217- throw new Error ( 'Unable to set context for mode ' + params . type ) ;
225+ throw new Error ( 'Unable to set context for mode ' + params . version ) ;
218226 }
219227 var prk = HKDF_extract ( header . salt , secret ) ;
220228 var result = {
@@ -227,55 +235,47 @@ function deriveKeyAndNonce(header, mode) {
227235}
228236
229237function fillCommonParams ( header , params ) {
230- if ( params . key ) {
231- header . key = base64 . decode ( params . key ) ;
232- }
233- header . keymap = params . keymap ;
234- if ( params . dh ) {
235- header . dh = base64 . decode ( params . dh ) ;
236- }
237- if ( params . authSecret ) {
238- header . authSecret = base64 . decode ( params . authSecret ) ;
239- }
240238 return header ;
241239}
242240
243- /* Used when decrypting aes128gcm to populate the header values. */
244- function readHeader ( buffer , params ) {
245- var idsz = buffer . readUIntBE ( 20 , 1 ) ;
246- var header = {
247- type : 'aes128gcm' ,
248- salt : buffer . slice ( 0 , KEY_LENGTH ) ,
249- rs : buffer . readUIntBE ( KEY_LENGTH , 4 ) ,
250- keyid : buffer . slice ( 21 , 21 + idsz ) . toString ( 'utf-8' ) ,
251- headerLength : 21 + idsz
252- } ;
253- return fillCommonParams ( header , params ) ;
254- }
255-
256- /* Used when decrypting to populate the header values for aesgcm[128]. Used also
257- * to parse the |param| argument when encrypting. */
241+ /* Parse command-line arguments. */
258242function parseParams ( params ) {
259- var header = {
260- type : ( params . padSize === 1 ) ? 'aesgcm128' : 'aesgcm' ,
261- keyid : params . keyid ,
262- headerLength : 0
263- } ;
243+ var header = { } ;
244+ if ( params . version ) {
245+ header . version = params . version ;
246+ } else {
247+ header . version = ( params . padSize === 1 ) ? 'aesgcm128' : 'aesgcm' ;
248+ }
249+
264250 header . rs = parseInt ( params . rs , 10 ) ;
265251 if ( isNaN ( header . rs ) ) {
266252 header . rs = 4096 ;
267253 }
268- if ( header . rs <= PAD_SIZE [ header . type ] ) {
254+ if ( header . rs <= PAD_SIZE [ header . version ] ) {
269255 throw new Error ( 'The rs parameter has to be greater than ' +
270- PAD_SIZE [ header . type ] ) ;
256+ PAD_SIZE [ header . version ] ) ;
271257 }
258+
272259 if ( params . salt ) {
273- header . salt = base64 . decode ( params . salt ) ;
260+ header . salt = decode ( params . salt ) ;
274261 if ( header . salt . length !== KEY_LENGTH ) {
275262 throw new Error ( 'The salt parameter must be ' + KEY_LENGTH + ' bytes' ) ;
276263 }
277264 }
278- return fillCommonParams ( header , params ) ;
265+ header . keyid = params . keyid ;
266+ if ( params . key ) {
267+ header . key = decode ( params . key ) ;
268+ } else {
269+ header . keymap = params . keymap || saved . keymap ;
270+ header . keylabels = params . keylabels || saved . keylabels ;
271+ if ( params . dh ) {
272+ header . dh = decode ( params . dh ) ;
273+ }
274+ }
275+ if ( params . authSecret ) {
276+ header . authSecret = decode ( params . authSecret ) ;
277+ }
278+ return header ;
279279}
280280
281281function generateNonce ( base , counter ) {
@@ -288,6 +288,16 @@ function generateNonce(base, counter) {
288288 return nonce ;
289289}
290290
291+ /* Used when decrypting aes128gcm to populate the header values. Modifies the
292+ * header values in place and returns the size of the header. */
293+ function readHeader ( buffer , header ) {
294+ var idsz = buffer . readUIntBE ( 20 , 1 ) ;
295+ header . salt = buffer . slice ( 0 , KEY_LENGTH ) ;
296+ header . rs = buffer . readUIntBE ( KEY_LENGTH , 4 ) ;
297+ header . keyid = buffer . slice ( 21 , 21 + idsz ) . toString ( 'utf-8' ) ;
298+ return 21 + idsz ;
299+ }
300+
291301function decryptRecord ( key , counter , buffer , header ) {
292302 keylog ( 'decrypt' , buffer ) ;
293303 var nonce = generateNonce ( key . nonce , counter ) ;
@@ -296,11 +306,12 @@ function decryptRecord(key, counter, buffer, header) {
296306 var data = gcm . update ( buffer . slice ( 0 , buffer . length - TAG_LENGTH ) ) ;
297307 data = Buffer . concat ( [ data , gcm . final ( ) ] ) ;
298308 keylog ( 'decrypted' , data ) ;
299- var padSize = PAD_SIZE [ header . type ] ;
309+ var padSize = PAD_SIZE [ header . version ] ;
300310 var pad = data . readUIntBE ( 0 , padSize ) ;
301311 if ( pad + padSize > data . length ) {
302312 throw new Error ( 'padding exceeds block size' ) ;
303313 }
314+ keylog ( 'padding' , data . slice ( 0 , padSize + pad ) ) ;
304315 var padCheck = new Buffer ( pad ) ;
305316 padCheck . fill ( 0 ) ;
306317 if ( padCheck . compare ( data . slice ( padSize , padSize + pad ) ) !== 0 ) {
@@ -315,6 +326,11 @@ function decryptRecord(key, counter, buffer, header) {
315326 * Decrypt some bytes. This uses the parameters to determine the key and block
316327 * size, which are described in the draft. Binary values are base64url encoded.
317328 *
329+ * |params.version| contains the version of encoding to use: aes128gcm is the latest,
330+ * but aesgcm and aesgcm128 are also accepted (though the latter two might
331+ * disappear in a future release). If omitted, assume aesgcm, unless
332+ * |params.padSize| is set to 1, which means aesgcm128.
333+ *
318334 * If |params.key| is specified, that value is used as the key.
319335 *
320336 * If |params.keyid| is specified without |params.dh|, the keyid value is used
@@ -324,14 +340,12 @@ function decryptRecord(key, counter, buffer, header) {
324340 * pair used to decrypt is looked up using |params.keymap[params.keyid]|.
325341 */
326342function decrypt ( buffer , params ) {
327- var header ;
328- if ( params . salt ) {
329- header = parseParams ( params ) ;
330- } else {
331- header = readHeader ( buffer , params ) ;
343+ var header = parseParams ( params ) ;
344+ if ( header . version === 'aes128gcm' ) {
345+ var headerLength = readHeader ( buffer , header ) ;
346+ buffer = buffer . slice ( headerLength ) ;
332347 }
333348 var key = deriveKeyAndNonce ( header , MODE_DECRYPT ) ;
334- buffer = buffer . slice ( header . headerLength || 0 ) ;
335349 var start = 0 ;
336350 var result = new Buffer ( 0 ) ;
337351
@@ -360,6 +374,7 @@ function encryptRecord(key, counter, buffer, pad, padSize) {
360374 var padding = new Buffer ( pad + padSize ) ;
361375 padding . fill ( 0 ) ;
362376 padding . writeUIntBE ( pad , 0 , padSize ) ;
377+ keylog ( 'padding' , padding ) ;
363378 var epadding = gcm . update ( padding ) ;
364379 var ebuffer = gcm . update ( buffer ) ;
365380 gcm . final ( ) ;
@@ -370,9 +385,9 @@ function encryptRecord(key, counter, buffer, pad, padSize) {
370385 return keylog ( 'encrypted' , Buffer . concat ( [ epadding , ebuffer , tag ] ) ) ;
371386}
372387
373- function encodeHeader ( header ) {
388+ function writeHeader ( header ) {
374389 var ints = new Buffer ( 5 ) ;
375- var keyid = Buffer . from ( header . keyid || '' ) ;
390+ var keyid = Buffer . from ( header . keyid || [ ] ) ;
376391 if ( keyid . length > 255 ) {
377392 throw new Error ( 'keyid is too large' ) ;
378393 }
@@ -385,6 +400,11 @@ function encodeHeader(header) {
385400 * Encrypt some bytes. This uses the parameters to determine the key and block
386401 * size, which are described in the draft.
387402 *
403+ * |params.version| contains the version of encoding to use: aes128gcm is the latest,
404+ * but aesgcm and aesgcm128 are also accepted (though the latter two might
405+ * disappear in a future release). If omitted, assume aesgcm, unless
406+ * |params.padSize| is set to 1, which means aesgcm128.
407+ *
388408 * If |params.key| is specified, that value is used as the key.
389409 *
390410 * If |params.keyid| is specified without |params.dh|, the keyid value is used
@@ -398,20 +418,22 @@ function encrypt(buffer, params) {
398418 if ( ! Buffer . isBuffer ( buffer ) ) {
399419 throw new Error ( 'buffer argument must be a Buffer' ) ;
400420 }
421+ var header = parseParams ( params ) ;
422+ if ( ! header . salt ) {
423+ header . salt = crypto . randomBytes ( KEY_LENGTH ) ;
424+ }
425+
401426 var result ;
402- var header ;
403- header = parseParams ( params ) ;
404- if ( params . salt ) { // old versions
405- result = new Buffer ( 0 ) ;
427+ if ( header . version === 'aes128gcm' ) {
428+ result = writeHeader ( header ) ;
406429 } else {
407- header . type = 'aes128gcm' ;
408- header . salt = crypto . randomBytes ( KEY_LENGTH ) ;
409- result = encodeHeader ( header ) ;
430+ // No header on other versions
431+ result = new Buffer ( 0 ) ;
410432 }
411433
412434 var key = deriveKeyAndNonce ( header , MODE_ENCRYPT ) ;
413435 var start = 0 ;
414- var padSize = PAD_SIZE [ header . type ] ;
436+ var padSize = PAD_SIZE [ header . version ] ;
415437 var pad = isNaN ( parseInt ( params . pad , 10 ) ) ? 0 : parseInt ( params . pad , 10 ) ;
416438
417439 // Note the <= here ensures that we write out a padding-only block at the end
@@ -435,19 +457,12 @@ function encrypt(buffer, params) {
435457}
436458
437459/**
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.
460+ * Deprecated. Use the keymap and keylabels arguments to encrypt()/decrypt().
446461 */
447462function saveKey ( id , key , dhLabel ) {
448- savedKeys [ id ] = key ;
463+ saved . keymap [ id ] = key ;
449464 if ( dhLabel ) {
450- keyLabels [ id ] = new Buffer ( dhLabel + '\0' , 'ascii' ) ;
465+ saved . keylabels [ id ] = dhLabel ;
451466 }
452467}
453468
0 commit comments