Skip to content

Commit 2a507d1

Browse files
committed
Fix PAE encoding.
1 parent cb107cd commit 2a507d1

File tree

3 files changed

+73
-22
lines changed

3 files changed

+73
-22
lines changed

lib/protocol/V1.js

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,14 @@ function sign(data, key, footer, cb) {
236236

237237
// sign
238238

239-
const payload = utils.pae(header, data, footer);
240-
const signer = crypto.createSign('SHA384');
239+
let payload;
240+
try {
241+
payload = utils.pae(header, data, footer);
242+
} catch (ex) {
243+
return done(ex);
244+
}
245+
246+
const signer = crypto.createSign('SHA384');
241247
signer.update(payload);
242248
signer.end();
243249

@@ -292,7 +298,13 @@ function verify(token, key, footer, cb) {
292298

293299
// verify signature
294300

295-
const expected = utils.pae(header, data, footer);
301+
let expected;
302+
try {
303+
expected = utils.pae(header, data, footer);
304+
} catch (ex) {
305+
return done(ex);
306+
}
307+
296308
const verifier = crypto.createVerify('SHA384');
297309
verifier.update(expected);
298310
verifier.end();
@@ -377,7 +389,12 @@ function aeadEncrypt(key, header, plaintext, footer, nonce) {
377389
// an empty buffer is truthy, if no plaintext
378390
if (!ciphertext) { return reject(new PasetoError('Encryption failed.')); }
379391

380-
const payload = utils.pae(header, nonce, ciphertext, footer);
392+
let payload;
393+
try {
394+
payload = utils.pae(header, nonce, ciphertext, footer);
395+
} catch (ex) {
396+
return done(ex);
397+
}
381398

382399
const authenticator = crypto.createHmac(self._constants.HASH_ALGO, authkey);
383400
const mac = authenticator.update(payload).digest();
@@ -428,7 +445,12 @@ function aeadDecrypt(key, header, payload, footer) {
428445
if (err) { return reject(err); }
429446
const [ enckey, authkey ] = keys;
430447

431-
const payload = utils.pae(header, nonce, ciphertext, footer);
448+
let payload;
449+
try {
450+
payload = utils.pae(header, nonce, ciphertext, footer);
451+
} catch (ex) {
452+
return done(ex);
453+
}
432454

433455
const authenticator = crypto.createHmac(self._constants.HASH_ALGO, authkey);
434456
const calc = authenticator.update(payload).digest();

lib/protocol/V2.js

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,13 @@ function sign(data, key, footer, cb) {
243243

244244
// sign
245245

246-
const payload = utils.pae(header, data, footer);
246+
let payload;
247+
try {
248+
payload = utils.pae(header, data, footer);
249+
} catch (ex) {
250+
return done(ex);
251+
}
252+
247253
const _signature = sodium.crypto_sign_detached(payload, key.raw());
248254
const signature = Buffer.from(_signature);
249255

@@ -299,8 +305,14 @@ function verify(token, key, footer, cb) {
299305

300306
// verify signature
301307

302-
const expected = utils.pae(header, data, footer);
303-
const valid = sodium.crypto_sign_verify_detached(signature, expected, key.raw());
308+
let expected;
309+
try {
310+
expected = utils.pae(header, data, footer);
311+
} catch (ex) {
312+
return done(ex);
313+
}
314+
315+
const valid = sodium.crypto_sign_verify_detached(signature, expected, key.raw());
304316

305317
if (!valid) { return done(new PasetoError('Invalid signature for this message')); }
306318

@@ -339,7 +351,13 @@ function aeadEncrypt(key, header, plaintext, footer, nonce) {
339351

340352
// encrypt
341353

342-
const ad = utils.pae(header, nonce, footer);
354+
let ad;
355+
try {
356+
ad = utils.pae(header, nonce, footer);
357+
} catch (ex) {
358+
return done(ex);
359+
}
360+
343361
const _ciphertext = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, ad, null, nonce, key.raw());
344362
const ciphertext = Buffer.from(_ciphertext);
345363

@@ -380,7 +398,13 @@ function aeadDecrypt(key, header, payload, footer) {
380398

381399
// decrypt and verify
382400

383-
const ad = utils.pae(header, nonce, footer);
401+
let ad;
402+
try {
403+
ad = utils.pae(header, nonce, footer);
404+
} catch (ex) {
405+
return done(ex);
406+
}
407+
384408
const ciphertext = Buffer.from(payload).slice(nlen, plen);
385409

386410
const _plaintext = sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(null, ciphertext, ad, nonce, key.raw());

lib/utils.js

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
const crypto = require('crypto');
22
const sodium = require('libsodium-wrappers-sumo');
33

4-
const PasetoError = require('./error/PasetoError');
4+
const PasetoError = require('./error/PasetoError');
5+
const EncodingError = require('./error/EncodingError');
56

67

78
/***
@@ -154,20 +155,24 @@ module.exports.pae = pae;
154155
function pae() {
155156
const pieces = (parse('utf-8'))(...arguments);
156157

157-
// TODO: According to the spec this should be able to be a 64-bit
158-
// integer. However, the likelihood a token would be large
159-
// enough to where a 32-bit descriptor would be insufficient
160-
// is pretty negligable. As such, this is okay for the moment
161-
// as all tests pass, but should be cleaned up and made fully
162-
// conformant.
158+
const LE64 = (n) => {
159+
// also guarantees that msb will be zero as required
160+
if (n > Number.MAX_SAFE_INTEGER) { throw new EncodingError('Message too long to encode'); }
163161

164-
let accumulator = Buffer.alloc(8);
165-
accumulator.writeInt32LE(pieces.length);
162+
const up = ~~(n / 0xFFFFFFFF);
163+
const dn = (n % 0xFFFFFFFF) - up;
166164

167-
pieces.forEach((piece) => {
168-
let len = Buffer.alloc(8);
169-
len.writeInt32LE(Buffer.byteLength(piece));
165+
let buf = Buffer.alloc(8);
166+
167+
buf.writeUInt32LE(up, 4);
168+
buf.writeUInt32LE(dn, 0);
170169

170+
return buf;
171+
}
172+
173+
let accumulator = LE64(pieces.length);
174+
pieces.forEach((piece) => {
175+
let len = LE64(Buffer.byteLength(piece));
171176
accumulator = Buffer.concat([ accumulator, len, piece ]);
172177
});
173178

0 commit comments

Comments
 (0)