Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

http_ece

Package Overview
Dependencies
Maintainers
1
Versions
22
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

http_ece - npm Package Compare versions

Comparing version 0.5.2 to 0.6.0

tmp.js

357

ece.js
'use strict';
/*
* Encrypted content coding
*
* === Note about versions ===
*
* This code supports multiple versions of the draft. This is selected using
* the |version| parameter.
*
* aes128gcm: The most recent version, the salt, record size and key identifier
* are included in a header that is part of the encrypted content coding.
*
* aesgcm: The version that is widely deployed with WebPush (as of 2016-11).
* This version is selected by default, unless you specify a |padSize| of 1.
*
* aesgcm128: This version is old and will be removed in an upcoming release.
* This version is selected by providing a |padSize| parameter of 1.
*/

@@ -6,6 +23,8 @@ var crypto = require('crypto');

var savedKeys = {};
var keyLabels = {};
var saved = {
keymap: {},
keylabels: {}
};
var AES_GCM = 'aes-128-gcm';
var PAD_SIZE = 2;
var PAD_SIZE = { 'aes128gcm': 2, 'aesgcm': 2, 'aesgcm128': 1 };
var TAG_LENGTH = 16;

@@ -22,7 +41,16 @@ var KEY_LENGTH = 16;

console.warn(m + ' [' + k.length + ']: ' + base64.encode(k));
return k;
};
} else {
keylog = function() {};
keylog = function(m, k) { return k; };
}
/* Optionally base64 decode something. */
function decode(b) {
if (typeof b === 'string') {
return base64.decode(b);
}
return b;
}
function HMAC_hash(key, input) {

@@ -36,6 +64,10 @@ var hmac = crypto.createHmac('sha256', key);

function HKDF_extract(salt, ikm) {
return HMAC_hash(salt, ikm);
keylog('salt', salt);
keylog('ikm', ikm);
return keylog('extract', HMAC_hash(salt, ikm));
}
function HKDF_expand(prk, info, l) {
keylog('prk', prk);
keylog('info', info);
var output = new Buffer(0);

@@ -52,3 +84,3 @@ var T = new Buffer(0);

return output.slice(0, l);
return keylog('expand', output.slice(0, l));
}

@@ -69,15 +101,4 @@

function extractSalt(salt) {
if (!salt) {
throw new Error('A salt is required');
}
salt = base64.decode(salt);
if (salt.length !== KEY_LENGTH) {
throw new Error('The salt parameter must be ' + KEY_LENGTH + ' bytes');
}
return salt;
}
function lengthPrefix(buffer) {
var b = Buffer.concat([ new Buffer(2), buffer ]);
var b = Buffer.concat([new Buffer(2), buffer]);
b.writeUIntBE(buffer.length, 0, 2);

@@ -87,17 +108,19 @@ return b;

function extractDH(keyid, dh, mode) {
if (!savedKeys[keyid]) {
throw new Error('No known DH key for ' + keyid);
function extractDH(header, mode) {
var key = header.privateKey;
if (!key) {
if (!header.keymap || !header.keyid || !header.keymap[header.keyid]) {
throw new Error('No known DH key for ' + header.keyid);
}
key = header.keymap[header.keyid];
}
if (!keyLabels[keyid]) {
throw new Error('No known DH key label for ' + keyid);
if (!header.keylabels[header.keyid]) {
throw new Error('No known DH key label for ' + header.keyid);
}
var share = base64.decode(dh);
var key = savedKeys[keyid];
var senderPubKey, receiverPubKey;
if (mode === MODE_ENCRYPT) {
senderPubKey = key.getPublicKey();
receiverPubKey = share;
receiverPubKey = header.dh;
} else if (mode === MODE_DECRYPT) {
senderPubKey = share;
senderPubKey = header.dh;
receiverPubKey = key.getPublicKey();

@@ -109,7 +132,8 @@ } else {

return {
secret: key.computeSecret(share),
secret: key.computeSecret(header.dh),
context: Buffer.concat([
keyLabels[keyid],
lengthPrefix(receiverPubKey),
lengthPrefix(senderPubKey)
decode(header.keylabels[header.keyid]),
Buffer.from([0]),
lengthPrefix(receiverPubKey), // user agent
lengthPrefix(senderPubKey) // application server
])

@@ -119,13 +143,13 @@ };

function extractSecretAndContext(params, mode) {
function extractSecretAndContext(header, mode) {
var result = { secret: null, context: new Buffer(0) };
if (params.key) {
result.secret = base64.decode(params.key);
if (header.key) {
result.secret = header.key;
if (result.secret.length !== KEY_LENGTH) {
throw new Error('An explicit key must be ' + KEY_LENGTH + ' bytes');
}
} else if (params.dh) { // receiver/decrypt
result = extractDH(params.keyid, params.dh, mode);
} else if (params.keyid) {
result.secret = savedKeys[params.keyid];
} else if (header.dh) { // receiver/decrypt
result = extractDH(header, mode);
} else if (typeof header.keyid !== undefined) {
result.secret = header.keymap[header.keyid];
}

@@ -137,4 +161,4 @@ if (!result.secret) {

keylog('context', result.context);
if (params.authSecret) {
result.secret = HKDF(base64.decode(params.authSecret), result.secret,
if (header.authSecret) {
result.secret = HKDF(header.authSecret, result.secret,
info('auth', new Buffer(0)), SHA_256_LENGTH);

@@ -146,18 +170,80 @@ keylog('authsecret', result.secret);

function deriveKeyAndNonce(params, mode) {
var padSize = params.padSize || PAD_SIZE;
var salt = extractSalt(params.salt);
var s = extractSecretAndContext(params, mode);
var prk = HKDF_extract(salt, s.secret);
function webpushSecret(header, mode) {
if (!header.authSecret) {
throw new Error('No authentication secret for webpush');
}
keylog('authsecret', header.authSecret);
var remotePubKey, senderPubKey, receiverPubKey;
if (mode === MODE_ENCRYPT) {
senderPubKey = header.privateKey.getPublicKey();
remotePubKey = receiverPubKey = header.dh;
} else if (mode === MODE_DECRYPT) {
remotePubKey = senderPubKey = header.keyid;
receiverPubKey = header.privateKey.getPublicKey();
} else {
throw new Error('Unknown mode only ' + MODE_ENCRYPT +
' and ' + MODE_DECRYPT + ' supported');
}
keylog('remote pubkey', remotePubKey);
keylog('sender pubkey', senderPubKey);
keylog('receiver pubkey', receiverPubKey);
return keylog('secret dh',
HKDF(header.authSecret,
header.privateKey.computeSecret(remotePubKey),
Buffer.concat([
Buffer.from('WebPush: info\0'),
receiverPubKey,
senderPubKey
]),
SHA_256_LENGTH));
}
function extractSecret(header, mode) {
if (header.key) {
if (header.key.length !== KEY_LENGTH) {
throw new Error('An explicit key must be ' + KEY_LENGTH + ' bytes');
}
return keylog('secret key', header.key);
}
if (!header.privateKey) {
// Lookup based on keyid
var key = header.keymap && header.keymap[header.keyid];
if (!key) {
throw new Error('No saved key (keyid: "' + header.keyid + '")');
}
return key;
}
return webpushSecret(header, mode);
}
function deriveKeyAndNonce(header, mode) {
if (!header.salt) {
throw new Error('must include a salt parameter for ' + header.version);
}
var keyInfo;
var nonceInfo;
if (padSize === 1) {
var secret;
if (header.version === 'aesgcm128') {
// really old
keyInfo = 'Content-Encoding: aesgcm128';
nonceInfo = 'Content-Encoding: nonce';
} else if (padSize === 2) {
secret = extractSecretAndContext(header, mode).secret;
} else if (header.version === 'aesgcm') {
// old
var s = extractSecretAndContext(header, mode);
keyInfo = info('aesgcm', s.context);
nonceInfo = info('nonce', s.context);
secret = s.secret;
} else if (header.version === 'aes128gcm') {
// latest
keyInfo = Buffer.from('Content-Encoding: aes128gcm\0');
nonceInfo = Buffer.from('Content-Encoding: nonce\0');
secret = extractSecret(header, mode);
} else {
throw new Error('Unable to set context for padSize ' + params.padSize);
throw new Error('Unable to set context for mode ' + params.version);
}
var prk = HKDF_extract(header.salt, secret);
var result = {

@@ -172,12 +258,45 @@ key: HKDF_expand(prk, keyInfo, KEY_LENGTH),

function determineRecordSize(params) {
var rs = parseInt(params.rs, 10);
if (isNaN(rs)) {
return 4096;
/* Parse command-line arguments. */
function parseParams(params) {
var header = {};
if (params.version) {
header.version = params.version;
} else {
header.version = (params.padSize === 1) ? 'aesgcm128' : 'aesgcm';
}
var padSize = params.padSize || PAD_SIZE;
if (rs <= padSize) {
throw new Error('The rs parameter has to be greater than ' + padSize);
header.rs = parseInt(params.rs, 10);
if (isNaN(header.rs)) {
header.rs = 4096;
}
return rs;
if (header.rs <= PAD_SIZE[header.version]) {
throw new Error('The rs parameter has to be greater than ' +
PAD_SIZE[header.version]);
}
if (params.salt) {
header.salt = decode(params.salt);
if (header.salt.length !== KEY_LENGTH) {
throw new Error('The salt parameter must be ' + KEY_LENGTH + ' bytes');
}
}
header.keyid = params.keyid;
if (params.key) {
header.key = decode(params.key);
} else {
header.privateKey = params.privateKey;
if (!header.privateKey) {
header.keymap = params.keymap || saved.keymap;
}
if (header.version !== 'aes128gcm') {
header.keylabels = params.keylabels || saved.keylabels;
}
if (params.dh) {
header.dh = decode(params.dh);
}
}
if (params.authSecret) {
header.authSecret = decode(params.authSecret);
}
return header;
}

@@ -195,3 +314,13 @@

function decryptRecord(key, counter, buffer, padSize) {
/* Used when decrypting aes128gcm to populate the header values. Modifies the
* header values in place and returns the size of the header. */
function readHeader(buffer, header) {
var idsz = buffer.readUIntBE(20, 1);
header.salt = buffer.slice(0, KEY_LENGTH);
header.rs = buffer.readUIntBE(KEY_LENGTH, 4);
header.keyid = buffer.slice(21, 21 + idsz);
return 21 + idsz;
}
function decryptRecord(key, counter, buffer, header) {
keylog('decrypt', buffer);

@@ -204,3 +333,3 @@ var nonce = generateNonce(key.nonce, counter);

keylog('decrypted', data);
padSize = padSize || PAD_SIZE
var padSize = PAD_SIZE[header.version];
var pad = data.readUIntBE(0, padSize);

@@ -210,2 +339,3 @@ if (pad + padSize > data.length) {

}
keylog('padding', data.slice(0, padSize + pad));
var padCheck = new Buffer(pad);

@@ -224,11 +354,27 @@ padCheck.fill(0);

* size, which are described in the draft. Binary values are base64url encoded.
* For an explicit key that key is used. For a keyid on its own, the value of
* the key is a buffer that is stored with saveKey(). For ECDH, the p256-dh
* parameter identifies the public share of the recipient and the keyid is
* anECDH key pair (created by crypto.createECDH()) that is stored using
* saveKey().
*
* |params.version| contains the version of encoding to use: aes128gcm is the latest,
* but aesgcm and aesgcm128 are also accepted (though the latter two might
* disappear in a future release). If omitted, assume aesgcm, unless
* |params.padSize| is set to 1, which means aesgcm128.
*
* If |params.key| is specified, that value is used as the key.
*
* If |params.keyid| is specified without |params.dh|, the keyid value is used
* to lookup the |params.keymap| for a buffer containing the key.
*
* For version aesgcm and aesgcm128, |params.dh| includes the public key of the sender. The ECDH key
* pair used to decrypt is looked up using |params.keymap[params.keyid]|.
*
* Version aes128gcm is stricter. The |params.privateKey| includes the private
* key of the receiver. The keyid is extracted from the header and used as the
* ECDH public key of the sender.
*/
function decrypt(buffer, params) {
var key = deriveKeyAndNonce(params, MODE_DECRYPT);
var rs = determineRecordSize(params);
var header = parseParams(params);
if (header.version === 'aes128gcm') {
var headerLength = readHeader(buffer, header);
buffer = buffer.slice(headerLength);
}
var key = deriveKeyAndNonce(header, MODE_DECRYPT);
var start = 0;

@@ -238,3 +384,3 @@ var result = new Buffer(0);

for (var i = 0; start < buffer.length; ++i) {
var end = start + rs + TAG_LENGTH;
var end = start + header.rs + TAG_LENGTH;
if (end === buffer.length) {

@@ -248,3 +394,3 @@ throw new Error('Truncated payload');

var block = decryptRecord(key, i, buffer.slice(start, end),
params.padSize);
header);
result = Buffer.concat([result, block]);

@@ -261,6 +407,6 @@ start = end;

var gcm = crypto.createCipheriv(AES_GCM, key.key, nonce);
padSize = padSize || PAD_SIZE;
var padding = new Buffer(pad + padSize);
padding.fill(0);
padding.writeUIntBE(pad, 0, padSize);
keylog('padding', padding);
var epadding = gcm.update(padding);

@@ -273,13 +419,36 @@ var ebuffer = gcm.update(buffer);

}
var encrypted = Buffer.concat([epadding, ebuffer, tag]);
keylog('encrypted', encrypted);
return encrypted;
return keylog('encrypted', Buffer.concat([epadding, ebuffer, tag]));
}
function writeHeader(header) {
var ints = new Buffer(5);
var keyid = Buffer.from(header.keyid || []);
if (keyid.length > 255) {
throw new Error('keyid is too large');
}
ints.writeUIntBE(header.rs, 0, 4);
ints.writeUIntBE(keyid.length, 4, 1);
return Buffer.concat([header.salt, ints, keyid]);
}
/**
* Encrypt some bytes. This uses the parameters to determine the key and block
* size, which are described in the draft. Note that for encryption, the
* p256-dh parameter identifies the public share of the recipient and the keyid
* identifies a local DH key pair (created by crypto.createECDH() or
* crypto.createDiffieHellman()).
* size, which are described in the draft.
*
* |params.version| contains the version of encoding to use: aes128gcm is the latest,
* but aesgcm and aesgcm128 are also accepted (though the latter two might
* disappear in a future release). If omitted, assume aesgcm, unless
* |params.padSize| is set to 1, which means aesgcm128.
*
* If |params.key| is specified, that value is used as the key.
*
* If |params.keyid| is specified without |params.dh|, the keyid value is used
* to lookup the |params.keymap| for a buffer containing the key.
*
* For Diffie-Hellman (WebPush), |params.dh| includes the public key of the
* receiver. |params.privateKey| is used to establish a shared secret. For
* versions aesgcm and aesgcm128, if a private key is not provided, the ECDH key
* pair used to encrypt is looked up using |params.keymap[params.keyid]|, and
* |params.keymap| defaults to the values saved with saveKey(). Key pairs can
* be created using |crypto.createECDH()|.
*/

@@ -290,7 +459,22 @@ function encrypt(buffer, params) {

}
var key = deriveKeyAndNonce(params, MODE_ENCRYPT);
var rs = determineRecordSize(params);
var header = parseParams(params);
if (!header.salt) {
header.salt = crypto.randomBytes(KEY_LENGTH);
}
var result;
if (header.version === 'aes128gcm') {
// Save the DH public key in the header.
if (header.privateKey && !header.keyid) {
header.keyid = header.privateKey.getPublicKey();
}
result = writeHeader(header);
} else {
// No header on other versions
result = new Buffer(0);
}
var key = deriveKeyAndNonce(header, MODE_ENCRYPT);
var start = 0;
var result = new Buffer(0);
var padSize = params.padSize || PAD_SIZE;
var padSize = PAD_SIZE[header.version];
var pad = isNaN(parseInt(params.pad, 10)) ? 0 : parseInt(params.pad, 10);

@@ -303,10 +487,10 @@

var recordPad = Math.min((1 << (padSize * 8)) - 1, // maximum padding
Math.min(rs - padSize - 1, pad));
Math.min(header.rs - padSize - 1, pad));
pad -= recordPad;
var end = Math.min(start + rs - padSize - recordPad, buffer.length);
var end = Math.min(start + header.rs - padSize - recordPad, buffer.length);
var block = encryptRecord(key, i, buffer.slice(start, end),
recordPad, padSize);
result = Buffer.concat([result, block]);
start += rs - padSize - recordPad;
start += header.rs - padSize - recordPad;
}

@@ -320,11 +504,8 @@ if (pad) {

/**
* This function saves a key under the provided identifier. This is used to
* save the keys that are used to decrypt and encrypt blobs that are identified
* by a 'keyid'. DH or ECDH keys that are used with the 'dh' parameter need to
* include a label (included in 'dhLabel') that identifies them.
* Deprecated. Use the keymap and keylabels arguments to encrypt()/decrypt().
*/
function saveKey(id, key, dhLabel) {
savedKeys[id] = key;
saved.keymap[id] = key;
if (dhLabel) {
keyLabels[id] = new Buffer(dhLabel + '\0', 'ascii');
saved.keylabels[id] = dhLabel;
}

@@ -331,0 +512,0 @@ }

{
"name": "http_ece",
"version": "0.5.2",
"version": "0.6.0",
"description": "Encrypted Content-Encoding for HTTP",

@@ -11,2 +11,6 @@ "homepage": "https://github.com/martinthomson/encrypted-content-encoding",

},
"contributors": [{
"name": "Marco Castelluccio",
"email": "mcastelluccio@mozilla.com"
}],
"repository": {

@@ -13,0 +17,0 @@ "type": "git",

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc