Skip to content

Commit 76e10ff

Browse files
committed
Old version works
1 parent eaf0cf4 commit 76e10ff

1 file changed

Lines changed: 112 additions & 49 deletions

File tree

nodejs/ece.js

Lines changed: 112 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ var base64 = require('urlsafe-base64');
66
var savedKeys = {};
77
var keyLabels = {};
88
var AES_GCM = 'aes-128-gcm';
9-
var PAD_SIZE = 2;
9+
var PAD_SIZE = { 'aes128gcm': 2, 'aesgcm': 2, 'aesgcm128': 1 };
1010
var TAG_LENGTH = 16;
1111
var KEY_LENGTH = 16;
1212
var NONCE_LENGTH = 12;
@@ -62,31 +62,19 @@ function info(base, context) {
6262
return result;
6363
}
6464

65-
function extractSalt(salt) {
66-
if (!salt) {
67-
throw new Error('A salt is required');
68-
}
69-
salt = base64.decode(salt);
70-
if (salt.length !== KEY_LENGTH) {
71-
throw new Error('The salt parameter must be ' + KEY_LENGTH + ' bytes');
72-
}
73-
return salt;
74-
}
75-
7665
function lengthPrefix(buffer) {
77-
var b = Buffer.concat([ new Buffer(2), buffer ]);
66+
var b = Buffer.concat([new Buffer(2), buffer]);
7867
b.writeUIntBE(buffer.length, 0, 2);
7968
return b;
8069
}
8170

82-
function extractDH(keyid, dh, mode) {
71+
function extractDH(keyid, share, mode) {
8372
if (!savedKeys[keyid]) {
8473
throw new Error('No known DH key for ' + keyid);
8574
}
8675
if (!keyLabels[keyid]) {
8776
throw new Error('No known DH key label for ' + keyid);
8877
}
89-
var share = base64.decode(dh);
9078
var key = savedKeys[keyid];
9179
var senderPubKey, receiverPubKey;
9280
if (mode === MODE_ENCRYPT) {
@@ -109,46 +97,51 @@ function extractDH(keyid, dh, mode) {
10997
};
11098
}
11199

112-
function extractSecretAndContext(params, mode) {
100+
function extractSecretAndContext(header, mode) {
113101
var result = { secret: null, context: new Buffer(0) };
114-
if (params.key) {
115-
result.secret = base64.decode(params.key);
102+
if (header.key) {
103+
result.secret = header.key;
116104
if (result.secret.length !== KEY_LENGTH) {
117105
throw new Error('An explicit key must be ' + KEY_LENGTH + ' bytes');
118106
}
119-
} else if (params.dh) { // receiver/decrypt
120-
result = extractDH(params.keyid, params.dh, mode);
121-
} else if (params.keyid) {
122-
result.secret = savedKeys[params.keyid];
107+
} else if (header.dh) { // receiver/decrypt
108+
result = extractDH(header.keyid, header.dh, mode);
109+
} else if (header.keyid) {
110+
result.secret = savedKeys[header.keyid];
123111
}
124112
if (!result.secret) {
113+
console.warn(header);
125114
throw new Error('Unable to determine key');
126115
}
127116
keylog('secret', result.secret);
128117
keylog('context', result.context);
129-
if (params.authSecret) {
130-
result.secret = HKDF(base64.decode(params.authSecret), result.secret,
118+
if (header.authSecret) {
119+
result.secret = HKDF(base64.decode(header.authSecret), result.secret,
131120
info('auth', new Buffer(0)), SHA_256_LENGTH);
132121
keylog('authsecret', result.secret);
133122
}
134123
return result;
135124
}
136125

137-
function deriveKeyAndNonce(params, mode) {
138-
var padSize = params.padSize || PAD_SIZE;
139-
var salt = extractSalt(params.salt);
140-
var s = extractSecretAndContext(params, mode);
141-
var prk = HKDF_extract(salt, s.secret);
126+
function deriveKeyAndNonce(header, mode) {
127+
if (!header.salt) {
128+
throw new Error('must include a salt parameter for ' + header.type);
129+
}
130+
var s = extractSecretAndContext(header, mode);
131+
var prk = HKDF_extract(header.salt, s.secret);
142132
var keyInfo;
143133
var nonceInfo;
144-
if (padSize === 1) {
134+
if (header.type === 'aesgcm128') {
145135
keyInfo = 'Content-Encoding: aesgcm128';
146136
nonceInfo = 'Content-Encoding: nonce';
147-
} else if (padSize === 2) {
137+
} else if (header.type === 'aesgcm') {
148138
keyInfo = info('aesgcm', s.context);
149139
nonceInfo = info('nonce', s.context);
140+
} else if (header.type === 'aes128gcm') {
141+
keyInfo = 'Content-Encoding: aesgcm128\0';
142+
nonceInfo = 'Content-Encoding: nonce\0';
150143
} else {
151-
throw new Error('Unable to set context for padSize ' + params.padSize);
144+
throw new Error('Unable to set context for mode ' + params.type);
152145
}
153146
var result = {
154147
key: HKDF_expand(prk, keyInfo, KEY_LENGTH),
@@ -159,18 +152,58 @@ function deriveKeyAndNonce(params, mode) {
159152
return result;
160153
}
161154

162-
function determineRecordSize(params) {
163-
var rs = parseInt(params.rs, 10);
155+
function determineRecordSize(rs, type) {
156+
rs = parseInt(rs, 10);
164157
if (isNaN(rs)) {
165158
return 4096;
166159
}
167-
var padSize = params.padSize || PAD_SIZE;
160+
var padSize = PAD_SIZE[type];
168161
if (rs <= padSize) {
169162
throw new Error('The rs parameter has to be greater than ' + padSize);
170163
}
171164
return rs;
172165
}
173166

167+
function extractSalt(salt) {
168+
if (!salt) {
169+
throw new Error('A salt is required');
170+
}
171+
salt = base64.decode(salt);
172+
if (salt.length !== KEY_LENGTH) {
173+
throw new Error('The salt parameter must be ' + KEY_LENGTH + ' bytes');
174+
}
175+
return salt;
176+
}
177+
178+
/* Used when decrypting aes128gcm to populate the header values. */
179+
function readHeader(params, buffer) {
180+
var idsz = buffer.readUIntBE(20, 1);
181+
return {
182+
type: 'aes128gcm',
183+
salt: buffer.slice(0, KEY_LENGTH),
184+
rs: buffer.readUIntBE(KEY_LENGTH, 4),
185+
keyid: buffer.slice(21, 21 + idsz).toString('utf-8'),
186+
key: params.key ? base64.decode(params.key) : undefined,
187+
dh: params.dh ? base64.decode(params.dh) : undefined,
188+
authSecret: params.authSecret ? base64.decode(params.authSecret) : undefined
189+
};
190+
}
191+
192+
/* Used when decrypting to populate the header values for aesgcm[128]. */
193+
function parseParams(params) {
194+
console.warn(params);
195+
var type = (params.padSize === 1) ? 'aesgcm128' : 'aesgcm';
196+
return {
197+
type: type,
198+
salt: params.salt ? extractSalt(params.salt) : undefined,
199+
rs: determineRecordSize(params.rs, type),
200+
keyid: params.keyid,
201+
key: params.key ? base64.decode(params.key) : undefined,
202+
dh: params.dh ? base64.decode(params.dh) : undefined,
203+
authSecret: params.authSecret ? base64.decode(params.authSecret) : undefined
204+
};
205+
}
206+
174207
function generateNonce(base, counter) {
175208
var nonce = new Buffer(base);
176209
var m = nonce.readUIntBE(nonce.length - 6, 6);
@@ -181,17 +214,21 @@ function generateNonce(base, counter) {
181214
return nonce;
182215
}
183216

184-
function decryptRecord(key, counter, buffer, padSize) {
217+
function decryptRecord(key, counter, buffer, header) {
185218
keylog('decrypt', buffer);
186219
var nonce = generateNonce(key.nonce, counter);
187220
var gcm = crypto.createDecipheriv(AES_GCM, key.key, nonce);
188221
gcm.setAuthTag(buffer.slice(buffer.length - TAG_LENGTH));
189222
var data = gcm.update(buffer.slice(0, buffer.length - TAG_LENGTH));
190223
data = Buffer.concat([data, gcm.final()]);
191224
keylog('decrypted', data);
192-
padSize = padSize || PAD_SIZE
225+
var padSize = PAD_SIZE[header.type];
193226
var pad = data.readUIntBE(0, padSize);
194227
if (pad + padSize > data.length) {
228+
console.warn(header);
229+
console.warn(pad);
230+
console.warn(padSize);
231+
console.warn(data.length);
195232
throw new Error('padding exceeds block size');
196233
}
197234
var padCheck = new Buffer(pad);
@@ -214,13 +251,19 @@ function decryptRecord(key, counter, buffer, padSize) {
214251
* saveKey().
215252
*/
216253
function decrypt(buffer, params) {
217-
var key = deriveKeyAndNonce(params, MODE_DECRYPT);
218-
var rs = determineRecordSize(params);
254+
var header;
255+
if (params.salt) {
256+
header = parseParams(params);
257+
} else {
258+
header = readHeader(buffer, params);
259+
}
260+
var key = deriveKeyAndNonce(header, MODE_DECRYPT);
261+
buffer = buffer.slice(header.len);
219262
var start = 0;
220263
var result = new Buffer(0);
221264

222265
for (var i = 0; start < buffer.length; ++i) {
223-
var end = start + rs + TAG_LENGTH;
266+
var end = start + header.rs + TAG_LENGTH;
224267
if (end === buffer.length) {
225268
throw new Error('Truncated payload');
226269
}
@@ -229,7 +272,7 @@ function decrypt(buffer, params) {
229272
throw new Error('Invalid block: too small at ' + i);
230273
}
231274
var block = decryptRecord(key, i, buffer.slice(start, end),
232-
params.padSize);
275+
header);
233276
result = Buffer.concat([result, block]);
234277
start = end;
235278
}
@@ -241,7 +284,6 @@ function encryptRecord(key, counter, buffer, pad, padSize) {
241284
pad = pad || 0;
242285
var nonce = generateNonce(key.nonce, counter);
243286
var gcm = crypto.createCipheriv(AES_GCM, key.key, nonce);
244-
padSize = padSize || PAD_SIZE;
245287
var padding = new Buffer(pad + padSize);
246288
padding.fill(0);
247289
padding.writeUIntBE(pad, 0, padSize);
@@ -257,6 +299,17 @@ function encryptRecord(key, counter, buffer, pad, padSize) {
257299
return encrypted;
258300
}
259301

302+
function encodeHeader(header) {
303+
var ints = new Buffer(5);
304+
var keyid = Buffer.from(header.keyid || '');
305+
if (keyid.length > 255) {
306+
throw new Error('keyid is too large');
307+
}
308+
ints.writeUIntBE(header.rs, 0, 4);
309+
ints.writeUIntBE(keyid.length, 4, 1);
310+
return Buffer.concat([header.salt, ints, keyid]);
311+
}
312+
260313
/**
261314
* Encrypt some bytes. This uses the parameters to determine the key and block
262315
* size, which are described in the draft. Note that for encryption, the
@@ -268,26 +321,36 @@ function encrypt(buffer, params) {
268321
if (!Buffer.isBuffer(buffer)) {
269322
throw new Error('buffer argument must be a Buffer');
270323
}
271-
var key = deriveKeyAndNonce(params, MODE_ENCRYPT);
272-
var rs = determineRecordSize(params);
324+
var result;
325+
var header;
326+
header = parseParams(params);
327+
if (params.salt) { // old versions
328+
result = new Buffer(0);
329+
} else {
330+
header.type = 'aes128gcm';
331+
header.salt = crypto.randomBytes(KEY_LENGTH);
332+
result = encodeHeader(header);
333+
}
334+
header.rs = determineRecordSize(params.rs, header.type);
335+
336+
var key = deriveKeyAndNonce(header, MODE_ENCRYPT);
273337
var start = 0;
274-
var result = new Buffer(0);
275-
var padSize = params.padSize || PAD_SIZE;
338+
var padSize = PAD_SIZE[header.type];
276339
var pad = isNaN(parseInt(params.pad, 10)) ? 0 : parseInt(params.pad, 10);
277340

278341
// Note the <= here ensures that we write out a padding-only block at the end
279342
// of a buffer.
280343
for (var i = 0; start <= buffer.length; ++i) {
281344
// Pad so that at least one data byte is in a block.
282345
var recordPad = Math.min((1 << (padSize * 8)) - 1, // maximum padding
283-
Math.min(rs - padSize - 1, pad));
346+
Math.min(header.rs - padSize - 1, pad));
284347
pad -= recordPad;
285348

286-
var end = Math.min(start + rs - padSize - recordPad, buffer.length);
349+
var end = Math.min(start + header.rs - padSize - recordPad, buffer.length);
287350
var block = encryptRecord(key, i, buffer.slice(start, end),
288351
recordPad, padSize);
289352
result = Buffer.concat([result, block]);
290-
start += rs - padSize - recordPad;
353+
start += header.rs - padSize - recordPad;
291354
}
292355
if (pad) {
293356
throw new Error('Unable to pad by requested amount, ' + pad + ' remaining');

0 commit comments

Comments
 (0)