rittenhop-dev/versions/5.94.2/node_modules/node-jose/lib/algorithms/aes-cbc-hmac-sha2.js
2024-09-23 19:40:12 -04:00

504 lines
12 KiB
JavaScript

/*!
* algorithms/aes-cbc-hmac-sha2.js - AES-CBC-HMAC-SHA2 Composited Encryption
*
* Copyright (c) 2015 Cisco Systems, Inc. See LICENSE file.
*/
"use strict";
var helpers = require("./helpers.js"),
HMAC = require("./hmac.js"),
sha = require("./sha.js"),
forge = require("../deps/forge.js"),
DataBuffer = require("../util/databuffer.js"),
util = require("../util");
function checkIv(iv) {
if (16 !== iv.length) {
throw new Error("invalid iv");
}
}
function commonCbcEncryptFN(size) {
// ### 'fallback' implementation -- uses forge
var fallback = function(encKey, pdata, iv) {
try {
checkIv(iv);
} catch (err) {
return Promise.reject(err);
}
var promise = Promise.resolve();
promise = promise.then(function() {
var cipher = forge.cipher.createCipher("AES-CBC", new DataBuffer(encKey));
cipher.start({
iv: new DataBuffer(iv)
});
// TODO: chunk data
cipher.update(new DataBuffer(pdata));
if (!cipher.finish()) {
return Promise.reject(new Error("encryption failed"));
}
var cdata = Buffer.from(cipher.output.bytes(), "binary");
return cdata;
});
return promise;
};
// ### WebCryptoAPI implementation
// TODO: cache CryptoKey sooner
var webcrypto = function(encKey, pdata, iv) {
try {
checkIv(iv);
} catch (err) {
return Promise.reject(err);
}
var promise = Promise.resolve();
promise = promise.then(function() {
var alg = {
name: "AES-CBC"
};
return helpers.subtleCrypto.importKey("raw", encKey, alg, true, ["encrypt"]);
});
promise = promise.then(function(key) {
var alg = {
name: "AES-CBC",
iv: iv
};
return helpers.subtleCrypto.encrypt(alg, key, pdata);
});
promise = promise.then(function(cdata) {
cdata = Buffer.from(cdata);
return cdata;
});
return promise;
};
// ### NodeJS implementation
var nodejs = function(encKey, pdata, iv) {
try {
checkIv(iv);
} catch (err) {
return Promise.reject(err);
}
var promise = Promise.resolve(pdata);
promise = promise.then(function(pdata) {
var name = "AES-" + size + "-CBC";
var cipher = helpers.nodeCrypto.createCipheriv(name, encKey, iv);
var cdata = Buffer.concat([
cipher.update(pdata),
cipher.final()
]);
return cdata;
});
return promise;
};
return helpers.setupFallback(nodejs, webcrypto, fallback);
}
function commonCbcDecryptFN(size) {
// ### 'fallback' implementation -- uses forge
var fallback = function(encKey, cdata, iv) {
// validate inputs
try {
checkIv(iv);
} catch (err) {
return Promise.reject(err);
}
var promise = Promise.resolve();
promise = promise.then(function() {
var cipher = forge.cipher.createDecipher("AES-CBC", new DataBuffer(encKey));
cipher.start({
iv: new DataBuffer(iv)
});
// TODO: chunk data
cipher.update(new DataBuffer(cdata));
if (!cipher.finish()) {
return Promise.reject(new Error("encryption failed"));
}
var pdata = Buffer.from(cipher.output.bytes(), "binary");
return pdata;
});
return promise;
};
// ### WebCryptoAPI implementation
// TODO: cache CryptoKey sooner
var webcrypto = function(encKey, cdata, iv) {
// validate inputs
try {
checkIv(iv);
} catch (err) {
return Promise.reject(err);
}
var promise = Promise.resolve();
promise = promise.then(function() {
var alg = {
name: "AES-CBC"
};
return helpers.subtleCrypto.importKey("raw", encKey, alg, true, ["decrypt"]);
});
promise = promise.then(function(key) {
var alg = {
name: "AES-CBC",
iv: iv
};
return helpers.subtleCrypto.decrypt(alg, key, cdata);
});
promise = promise.then(function(pdata) {
pdata = Buffer.from(pdata);
return pdata;
});
return promise;
};
// ### NodeJS implementation
var nodejs = function(encKey, cdata, iv) {
// validate inputs
try {
checkIv(iv);
} catch (err) {
return Promise.reject(err);
}
var promise = Promise.resolve();
promise = promise.then(function() {
var name = "AES-" + size + "-CBC";
var cipher = helpers.nodeCrypto.createDecipheriv(name, encKey, iv);
var pdata = Buffer.concat([
cipher.update(cdata),
cipher.final()
]);
return pdata;
});
return promise;
};
return helpers.setupFallback(nodejs, webcrypto, fallback);
}
function checkKey(key, size) {
if ((size << 1) !== (key.length << 3)) {
throw new Error("invalid encryption key size");
}
}
function cbcHmacEncryptFN(size) {
var commonEncrypt = commonCbcEncryptFN(size);
return function(key, pdata, props) {
// validate inputs
try {
checkKey(key, size);
} catch (err) {
return Promise.reject(err);
}
var eKey = key.slice(size / 8),
iKey = key.slice(0, size / 8),
iv = props.iv || Buffer.alloc(0),
adata = props.aad || props.adata || Buffer.alloc(0);
// STEP 1 -- Encrypt
var promise = commonEncrypt(eKey, pdata, iv);
// STEP 2 -- MAC
promise = promise.then(function(cdata){
var mdata = Buffer.concat([
adata,
iv,
cdata,
helpers.int64ToBuffer(adata.length * 8)
]);
var promise;
promise = HMAC["HS" + (size * 2)].sign(iKey, mdata, {
length: size
});
promise = promise.then(function(result) {
// TODO: move slice to hmac.js
var tag = result.mac.slice(0, size / 8);
return {
data: cdata,
tag: tag
};
});
return promise;
});
return promise;
};
}
function cbcHmacDecryptFN(size) {
var commonDecrypt = commonCbcDecryptFN(size);
return function(key, cdata, props) {
// validate inputs
try {
checkKey(key, size);
} catch (err) {
return Promise.reject(err);
}
var eKey = key.slice(size / 8),
iKey = key.slice(0, size / 8),
iv = props.iv || Buffer.alloc(0),
adata = props.aad || props.adata || Buffer.alloc(0),
tag = props.tag || props.mac || Buffer.alloc(0);
var promise = Promise.resolve();
// STEP 1 -- MAC
promise = promise.then(function() {
var promise;
// construct MAC input
var mdata = Buffer.concat([
adata,
iv,
cdata,
helpers.int64ToBuffer(adata.length * 8)
]);
promise = HMAC["HS" + (size * 2)].verify(iKey, mdata, tag, {
length: size
});
promise = promise.then(function() {
return cdata;
}, function() {
// failure -- invalid tag error
throw new Error("mac check failed");
});
return promise;
});
// STEP 2 -- Decrypt
promise = promise.then(function(){
return commonDecrypt(eKey, cdata, iv);
});
return promise;
};
}
var EncryptionLabel = Buffer.from("Encryption", "utf8");
var IntegrityLabel = Buffer.from("Integrity", "utf8");
var DotLabel = Buffer.from(".", "utf8");
function generateCek(masterKey, alg, epu, epv) {
var masterSize = masterKey.length * 8;
var cekSize = masterSize / 2;
var promise = Promise.resolve();
promise = promise.then(function(){
var input = Buffer.concat([
helpers.int32ToBuffer(1),
masterKey,
helpers.int32ToBuffer(cekSize),
Buffer.from(alg, "utf8"),
epu,
epv,
EncryptionLabel
]);
return input;
});
promise = promise.then( function(input) {
return sha["SHA-" + masterSize].digest(input).then(function(digest) {
return digest.slice(0, cekSize / 8);
});
});
promise = Promise.resolve(promise);
return promise;
}
function generateCik(masterKey, alg, epu, epv) {
var masterSize = masterKey.length * 8;
var cikSize = masterSize;
var promise = Promise.resolve();
promise = promise.then(function(){
var input = Buffer.concat([
helpers.int32ToBuffer(1),
masterKey,
helpers.int32ToBuffer(cikSize),
Buffer.from(alg, "utf8"),
epu,
epv,
IntegrityLabel
]);
return input;
});
promise = promise.then( function(input) {
return sha["SHA-" + masterSize].digest(input).then(function(digest) {
return digest.slice(0, cikSize / 8);
});
});
promise = Promise.resolve(promise);
return promise;
}
function concatKdfCbcHmacEncryptFN(size, alg) {
var commonEncrypt = commonCbcEncryptFN(size);
return function(key, pdata, props) {
var epu = props.epu || helpers.int32ToBuffer(0),
epv = props.epv || helpers.int32ToBuffer(0),
iv = props.iv || Buffer.alloc(0),
adata = props.aad || props.adata || Buffer.alloc(0),
kdata = props.kdata || Buffer.alloc(0);
// Pre Step 1 -- Generate Keys
var promises = [
generateCek(key, alg, epu, epv),
generateCik(key, alg, epu, epv)
];
var cek,
cik;
var promise = Promise.all(promises).then(function(keys) {
cek = keys[0];
cik = keys[1];
});
// STEP 1 -- Encrypt
promise = promise.then(function(){
return commonEncrypt(cek, pdata, iv);
});
// STEP 2 -- Mac
promise = promise.then(function(cdata){
var mdata = Buffer.concat([
adata,
DotLabel,
Buffer.from(kdata),
DotLabel,
Buffer.from(util.base64url.encode(iv), "utf8"),
DotLabel,
Buffer.from(util.base64url.encode(cdata), "utf8")
]);
return Promise.all([
Promise.resolve(cdata),
HMAC["HS" + (size * 2)].sign(cik, mdata, { length: size })
]);
});
promise = promise.then(function(result){
return {
data: result[0],
tag: result[1].mac
};
});
return promise;
};
}
function concatKdfCbcHmacDecryptFN(size, alg) {
var commonDecrypt = commonCbcDecryptFN(size);
return function(key, cdata, props) {
var epu = props.epu || helpers.int32ToBuffer(0),
epv = props.epv || helpers.int32ToBuffer(0),
iv = props.iv || Buffer.alloc(0),
adata = props.aad || props.adata || Buffer.alloc(0),
kdata = props.kdata || Buffer.alloc(0),
tag = props.tag || props.mac || Buffer.alloc(0);
// Pre Step 1 -- Generate Keys
var promises = [
generateCek(key, alg, epu, epv),
generateCik(key, alg, epu, epv)
];
var cek,
cik;
var promise = Promise.all(promises).then(function(keys){
cek = keys[0];
cik = keys[1];
});
// STEP 1 -- MAC
promise = promise.then(function() {
// construct MAC input
var mdata = Buffer.concat([
adata,
DotLabel,
Buffer.from(kdata),
DotLabel,
Buffer.from(util.base64url.encode(iv), "utf8"),
DotLabel,
Buffer.from(util.base64url.encode(cdata), "utf8")
]);
try {
return HMAC["HS" + (size * 2)].verify(cik, mdata, tag, {
loose: false
});
} catch (e) {
throw new Error("mac check failed");
}
});
// STEP 2 -- Decrypt
promise = promise.then(function(){
return commonDecrypt(cek, cdata, iv);
});
return promise;
};
}
// ### Public API
// * [name].encrypt
// * [name].decrypt
var aesCbcHmacSha2 = {};
[
"A128CBC-HS256",
"A192CBC-HS384",
"A256CBC-HS512"
].forEach(function(alg) {
var size = parseInt(/A(\d+)CBC-HS(\d+)?/g.exec(alg)[1]);
aesCbcHmacSha2[alg] = {
encrypt: cbcHmacEncryptFN(size),
decrypt: cbcHmacDecryptFN(size)
};
});
[
"A128CBC+HS256",
"A192CBC+HS384",
"A256CBC+HS512"
].forEach(function(alg) {
var size = parseInt(/A(\d+)CBC\+HS(\d+)?/g.exec(alg)[1]);
aesCbcHmacSha2[alg] = {
encrypt: concatKdfCbcHmacEncryptFN(size, alg),
decrypt: concatKdfCbcHmacDecryptFN(size, alg)
};
});
module.exports = aesCbcHmacSha2;