@@ -4,10 +4,15 @@ var crypto = require('crypto');
44var base64 = require ( 'urlsafe-base64' ) ;
55
66var savedKeys = { } ;
7+ var keyLabels = { } ;
78var AES_GCM = 'id-aes128-GCM' ;
9+ var PAD_SIZE = 2 ;
810var TAG_LENGTH = 16 ;
911var KEY_LENGTH = 16 ;
1012var NONCE_LENGTH = 12 ;
13+ var SHA_256_LENGTH = 32 ;
14+ var MODE_ENCRYPT = 'encrypt' ;
15+ var MODE_DECRYPT = 'decrypt' ;
1116
1217function HMAC_hash ( key , input ) {
1318 var hmac = crypto . createHmac ( 'sha256' , key ) ;
@@ -35,45 +40,105 @@ function HKDF_expand(prk, info, l) {
3540 return output . slice ( 0 , l ) ;
3641}
3742
38- function extractKey ( params ) {
39- var secret ;
43+ function HKDF ( salt , ikm , info , len ) {
44+ return HKDF_expand ( HKDF_extract ( salt , ikm ) , info , len ) ;
45+ }
46+
47+ function info ( base , context ) {
48+ return Buffer . concat ( [
49+ new Buffer ( 'Content-Encoding: ' + base + '\0' , 'ascii' ) ,
50+ context
51+ ] ) ;
52+ }
53+
54+ function extractSalt ( salt ) {
55+ if ( ! salt ) {
56+ throw new Error ( 'A salt is required' ) ;
57+ }
58+ salt = base64 . decode ( salt ) ;
59+ if ( salt . length !== KEY_LENGTH ) {
60+ throw new Error ( 'The salt parameter must be ' + KEY_LENGTH + ' bytes' ) ;
61+ }
62+ return salt ;
63+ }
64+
65+ function lengthPrefix ( buffer ) {
66+ var b = Buffer . concat ( [ new Buffer ( 2 ) , buffer ] ) ;
67+ b . writeUIntBE ( buffer . length , 0 , 2 ) ;
68+ return b ;
69+ }
70+
71+ function extractDH ( keyid , dh , mode ) {
72+ if ( ! savedKeys [ keyid ] ) {
73+ throw new Error ( 'No known DH key for ' + keyid ) ;
74+ }
75+ if ( ! keyLabels [ keyid ] ) {
76+ throw new Error ( 'No known DH key label for ' + keyid ) ;
77+ }
78+ var share = base64 . decode ( dh ) ;
79+ var key = savedKeys [ keyid ] ;
80+ var senderPubKey , receiverPubKey ;
81+ if ( mode === MODE_ENCRYPT ) {
82+ senderPubKey = key . getPublicKey ( ) ;
83+ receiverPubKey = share ;
84+ } else if ( mode === MODE_DECRYPT ) {
85+ senderPubKey = share ;
86+ receiverPubKey = key . getPublicKey ( ) ;
87+ } else {
88+ throw new Error ( 'Unknown mode only ' + MODE_ENCRYPT +
89+ ' and ' + MODE_DECRYPT + ' supported' ) ;
90+ }
91+ return {
92+ secret : key . computeSecret ( share ) ,
93+ context : Buffer . concat ( [
94+ keyLabels [ keyid ] ,
95+ lengthPrefix ( receiverPubKey ) ,
96+ lengthPrefix ( senderPubKey )
97+ ] )
98+ } ;
99+ }
100+
101+ function extractSecretAndContext ( params , mode ) {
102+ var result = { secret : null , context : new Buffer ( 0 ) } ;
40103 if ( params . key ) {
41- secret = base64 . decode ( params . key ) ;
42- if ( secret . length !== KEY_LENGTH ) {
104+ result . secret = base64 . decode ( params . key ) ;
105+ if ( result . secret . length !== KEY_LENGTH ) {
43106 throw new Error ( 'An explicit key must be ' + KEY_LENGTH + ' bytes' ) ;
44107 }
45108 } else if ( params . dh ) { // receiver/decrypt
46- var share = base64 . decode ( params . dh ) ;
47- var key = savedKeys [ params . keyid ] ;
48- secret = key . computeSecret ( share ) ;
109+ result = extractDH ( params . keyid , params . dh , mode ) ;
49110 } else if ( params . keyid ) {
50- secret = savedKeys [ params . keyid ] ;
111+ result . secret = savedKeys [ params . keyid ] ;
51112 }
52- if ( ! secret ) {
113+ if ( ! result . secret ) {
53114 throw new Error ( 'Unable to determine key' ) ;
54115 }
55- if ( ! params . salt ) {
56- throw new Error ( 'A salt is required' ) ;
116+ if ( params . authSecret ) {
117+ result . secret = HKDF ( base64 . decode ( params . authSecret ) , result . secret ,
118+ info ( 'auth' , new Buffer ( 0 ) ) , SHA_256_LENGTH ) ;
57119 }
120+ return result ;
121+ }
58122
59- var salt = base64 . decode ( params . salt ) ;
60- if ( salt . length !== KEY_LENGTH ) {
61- throw new Error ( 'The salt parameter must be ' + KEY_LENGTH + ' bytes' ) ;
62- }
63- var prk = HKDF_extract ( salt , secret ) ;
64- return {
65- key : HKDF_expand ( prk , 'Content-Encoding: aesgcm128' , KEY_LENGTH ) ,
66- nonce : HKDF_expand ( prk , 'Content-Encoding: nonce' , NONCE_LENGTH )
123+ function deriveKeyAndNonce ( params , mode ) {
124+ var salt = extractSalt ( params . salt ) ;
125+ var s = extractSecretAndContext ( params , mode ) ;
126+ var prk = HKDF_extract ( salt , s . secret ) ;
127+ var result = {
128+ key : HKDF_expand ( prk , info ( 'aesgcm128' , s . context ) , KEY_LENGTH ) ,
129+ nonce : HKDF_expand ( prk , info ( 'nonce' , s . context ) , NONCE_LENGTH )
67130 } ;
131+ return result ;
68132}
69133
70134function determineRecordSize ( params ) {
71135 var rs = parseInt ( params . rs , 10 ) ;
72136 if ( isNaN ( rs ) ) {
73137 return 4096 ;
74138 }
75- if ( rs <= 1 ) {
76- throw new Error ( 'The rs parameter has to be greater than 1' ) ;
139+ var padSize = params . padSize || PAD_SIZE ;
140+ if ( rs <= padSize ) {
141+ throw new Error ( 'The rs parameter has to be greater than ' + padSize ) ;
77142 }
78143 return rs ;
79144}
@@ -87,22 +152,23 @@ function generateNonce(base, counter) {
87152 return nonce ;
88153}
89154
90- function decryptRecord ( key , counter , buffer ) {
155+ function decryptRecord ( key , counter , buffer , padSize ) {
91156 var nonce = generateNonce ( key . nonce , counter ) ;
92157 var gcm = crypto . createDecipheriv ( AES_GCM , key . key , nonce ) ;
93158 gcm . setAuthTag ( buffer . slice ( buffer . length - TAG_LENGTH ) ) ;
94159 var data = gcm . update ( buffer . slice ( 0 , buffer . length - TAG_LENGTH ) ) ;
95160 data = Buffer . concat ( [ data , gcm . final ( ) ] ) ;
96- var pad = data . readUInt8 ( 0 ) ;
97- if ( pad + 1 > data . length ) {
161+ padSize = padSize || PAD_SIZE
162+ var pad = data . readUIntBE ( 0 , padSize ) ;
163+ if ( pad + padSize > data . length ) {
98164 throw new Error ( 'padding exceeds block size' ) ;
99165 }
100166 var padCheck = new Buffer ( pad ) ;
101167 padCheck . fill ( 0 ) ;
102- if ( padCheck . compare ( data . slice ( 1 , 1 + pad ) ) !== 0 ) {
168+ if ( padCheck . compare ( data . slice ( padSize , padSize + pad ) ) !== 0 ) {
103169 throw new Error ( 'invalid padding' ) ;
104170 }
105- return data . slice ( 1 + pad ) ;
171+ return data . slice ( padSize + pad ) ;
106172}
107173
108174// TODO: this really should use the node streams stuff
@@ -117,7 +183,7 @@ function decryptRecord(key, counter, buffer) {
117183 * saveKey().
118184 */
119185function decrypt ( buffer , params ) {
120- var key = extractKey ( params ) ;
186+ var key = deriveKeyAndNonce ( params , MODE_DECRYPT ) ;
121187 var rs = determineRecordSize ( params ) ;
122188 var start = 0 ;
123189 var result = new Buffer ( 0 ) ;
@@ -131,20 +197,22 @@ function decrypt(buffer, params) {
131197 if ( end - start <= TAG_LENGTH ) {
132198 throw new Error ( 'Invalid block: too small at ' + i ) ;
133199 }
134- var block = decryptRecord ( key , i , buffer . slice ( start , end ) ) ;
200+ var block = decryptRecord ( key , i , buffer . slice ( start , end ) ,
201+ params . padSize ) ;
135202 result = Buffer . concat ( [ result , block ] ) ;
136203 start = end ;
137204 }
138205 return result ;
139206}
140207
141- function encryptRecord ( key , counter , buffer , pad ) {
208+ function encryptRecord ( key , counter , buffer , pad , padSize ) {
142209 pad = pad || 0 ;
143210 var nonce = generateNonce ( key . nonce , counter ) ;
144211 var gcm = crypto . createCipheriv ( AES_GCM , key . key , nonce ) ;
145- var padding = new Buffer ( pad + 1 ) ;
212+ padSize = padSize || PAD_SIZE ;
213+ var padding = new Buffer ( pad + padSize ) ;
146214 padding . fill ( 0 ) ;
147- padding . writeUIntBE ( pad , 0 , 1 ) ;
215+ padding . writeUIntBE ( pad , 0 , padSize ) ;
148216 var epadding = gcm . update ( padding ) ;
149217 var ebuffer = gcm . update ( buffer ) ;
150218 gcm . final ( ) ;
@@ -159,30 +227,48 @@ function encryptRecord(key, counter, buffer, pad) {
159227 * Encrypt some bytes. This uses the parameters to determine the key and block
160228 * size, which are described in the draft. Note that for encryption, the
161229 * p256-dh parameter identifies the public share of the recipient and the keyid
162- * identifies a local ECDH key pair (created by crypto.createECDH()).
230+ * identifies a local DH key pair (created by crypto.createECDH() or
231+ * crypto.createDiffieHellman()).
163232 */
164233function encrypt ( buffer , params ) {
165- var key = extractKey ( params ) ;
234+ var key = deriveKeyAndNonce ( params , MODE_ENCRYPT ) ;
166235 var rs = determineRecordSize ( params ) ;
167236 var start = 0 ;
168237 var result = new Buffer ( 0 ) ;
238+ var padSize = params . padSize || PAD_SIZE ;
239+ var pad = isNaN ( parseInt ( params . pad , 10 ) ) ? 0 : parseInt ( params . pad , 10 ) ;
169240
241+ // Note the <= here ensures that we write out a padding-only block at the end
242+ // of a buffer.
170243 for ( var i = 0 ; start <= buffer . length ; ++ i ) {
171- var end = Math . min ( start + rs - 1 , buffer . length ) ;
172- var block = encryptRecord ( key , i , buffer . slice ( start , end ) ) ;
244+ // Pad so that at least one data byte is in a block.
245+ var recordPad = Math . min ( ( 1 << ( padSize * 8 ) ) - 1 , // maximum padding
246+ Math . min ( rs - padSize - 1 , pad ) ) ;
247+ pad -= recordPad ;
248+
249+ var end = Math . min ( start + rs - padSize - recordPad , buffer . length ) ;
250+ var block = encryptRecord ( key , i , buffer . slice ( start , end ) ,
251+ recordPad , padSize ) ;
173252 result = Buffer . concat ( [ result , block ] ) ;
174- start += rs - 1 ;
253+ start += rs - padSize - recordPad ;
254+ }
255+ if ( pad ) {
256+ throw new Error ( 'Unable to pad by requested amount, ' + pad + ' remaining' ) ;
175257 }
176258 return result ;
177259}
178260
179261/**
180262 * This function saves a key under the provided identifier. This is used to
181263 * save the keys that are used to decrypt and encrypt blobs that are identified
182- * by a 'keyid'.
264+ * by a 'keyid'. DH or ECDH keys that are used with the 'dh' parameter need to
265+ * include a label (included in 'dhLabel') that identifies them.
183266 */
184- function saveKey ( id , key ) {
267+ function saveKey ( id , key , dhLabel ) {
185268 savedKeys [ id ] = key ;
269+ if ( dhLabel ) {
270+ keyLabels [ id ] = new Buffer ( dhLabel + '\0' , 'ascii' ) ;
271+ }
186272}
187273
188274module . exports = {
0 commit comments