ctjs
Version:
CTjs is a full set of classes necessary to work with any kind of Certificate Transparency log (V1 as from RFC6962, or V2 as from RFC6962-bis). In CTjs you could find all necessary validation/verification functions for all related data shipped with full-fe
496 lines (424 loc) • 17.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.utils = undefined;
var _asn1js = require("asn1js");
var asn1js = _interopRequireWildcard(_asn1js);
var _pvutils = require("pvutils");
var _pkijs = require("pkijs");
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } /* eslint-disable no-constant-condition */
//**************************************************************************************
class utils {
//**********************************************************************************
constructor() {
throw new Error("Only calls to static functions allowed for namespace 'utils'");
}
//**********************************************************************************
/**
* Calculating hash of two chilren leafs
* @param {MerkleTreeLeaf|ArrayBuffer} left
* @param {MerkleTreeLeaf|ArrayBuffer} right
* @param {String} [hashName=SHA-256] Name of hashing function, default SHA-256
* @return {Promise<ArrayBuffer>}
*/
static hashChildren(left, right, hashName = "SHA-256") {
return _asyncToGenerator(function* () {
//region Initial variables
let leftHash;
let rightHash;
//endregion
//region Get a "crypto" extension
const crypto = (0, _pkijs.getCrypto)();
if (typeof crypto === "undefined") throw new Error("Unable to create WebCrypto object");
//endregion
//region Correctly initialize "left" hash
if ("byteLength" in left) leftHash = left;else leftHash = yield left.hash(hashName);
//endregion
//region Correctly initialize "right" hash
if ("byteLength" in right) rightHash = right;else rightHash = yield right.hash(hashName);
//endregion
const prefixedBuffer = (0, _pvutils.utilConcatBuf)(new Uint8Array([0x01]).buffer, leftHash, rightHash);
return yield crypto.digest({ name: hashName }, prefixedBuffer);
})();
}
//**********************************************************************************
/**
* Nearest power of 2 less than "length" (got it here: https://stackoverflow.com/questions/2679815/previous-power-of-2)
* @param {Number} v Value for which computation will be performed
* @return {Number}
*/
static flp2(v) {
let k = v;
if (k && !(k & k - 1)) k >>= 1;else {
k |= k >> 1;
k |= k >> 2;
k |= k >> 4;
k |= k >> 8;
k |= k >> 16;
k -= k >> 1;
}
return k;
}
//**********************************************************************************
/**
* Calculate a Tree Head Given Proofs (RFC6962-bis, 2.1.3.2)
* @param {ArrayBuffer|MerkleTreeLeaf} hash MerkleTreeLeaf object or hash of the MerkleTreeLeaf
* @param {Number} index Index of the MerkleTreeLeaf
* @param {Number} treeSize The Merkle tree size to which we need to proof existance
* @param {Array.<ArrayBuffer>} proof Array of hashes with proof
* @param {String} [hashName=SHA-256] Name of hashing function, default SHA-256
* @return {Promise<Object>} Object(r:ArrayBuffer, sn:Number)
*/
static calculateRootHashByProof(hash, index, treeSize, proof, hashName = "SHA-256") {
return _asyncToGenerator(function* () {
//region Initial variables
let fn = index;
let sn = treeSize - 1;
let r;
if ("byteLength" in hash) r = hash;else r = yield hash.hash(hashName);
//endregion
//region Check leaf index
if (index >= treeSize) return false; //throw new Error(`Incorrect index=${index} for the treeSize=${treeSize}`);
//endregion
//region Perform verification for all proofs from the array
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = proof[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
const p = _step.value;
if (sn === 0) return false; //throw new Error("Proof verification failed");
if (fn & 1 || fn === sn) {
r = yield utils.hashChildren(p, r, hashName);
if ((fn & 1) === 0) {
while (true) {
fn >>= 1;
sn >>= 1;
if (fn & 1 || fn === 0) break;
}
}
} else r = yield utils.hashChildren(r, p, hashName);
fn >>= 1;
sn >>= 1;
}
//endregion
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
return {
r,
sn
};
})();
}
//**********************************************************************************
/**
* Verifying an Inclusion Proof for any MerkleTreeLeaf with specified index (RFC6962-bis, 2.1.3.2)
* @param {ArrayBuffer|MerkleTreeLeaf} hash MerkleTreeLeaf object or hash of the MerkleTreeLeaf
* @param {Number} index Index of the MerkleTreeLeaf
* @param {Number} treeSize The Merkle tree size to which we need to proof existance
* @param {ArrayBuffer} rootHash Hash of the root to compare with
* @param {Array.<ArrayBuffer>} proof Array of hashes with proof
* @param {String} [hashName=SHA-256] Name of hashing function, default SHA-256
* @return {Promise<boolean>}
*/
static verifyInclusionProof(hash, index, treeSize, rootHash, proof, hashName = "SHA-256") {
return _asyncToGenerator(function* () {
const calculatedRootHash = yield utils.calculateRootHashByProof(hash, index, treeSize, proof, hashName);
return calculatedRootHash.sn === 0 && (0, _pvutils.isEqualBuffer)(calculatedRootHash.r, rootHash);
})();
}
//**********************************************************************************
/**
* Verifying Consistency between Two Tree Heads (RFC6962-bis, 2.1.4.2)
* @param {Number} first First tree size to compare
* @param {ArrayBuffer} firstHash Hash of the root for first tree size
* @param {Number} second Second tree size to compare
* @param {ArrayBuffer} secondHash Hash of the root for second tree size
* @param {Array.<ArrayBuffer>} consistency Array of hashes necessary for consistency verification
* @param {String} [hashName=SHA-256] Name of hashing function, default SHA-256
* @return {Promise<boolean>}
*/
static verifyConsistency(first, firstHash, second, secondHash, consistency, hashName = "SHA-256") {
return _asyncToGenerator(function* () {
//region Check input values
if (first === second) return (0, _pvutils.isEqualBuffer)(firstHash, secondHash);
//endregion
//region Initial variables
if (first && !(first & first - 1)) // Check "first" for being power of 2
consistency = [firstHash, ...consistency];
let fn = first - 1;
let sn = second - 1;
if (fn & 1) {
do {
fn >>= 1;
sn >>= 1;
} while (fn & 1);
}
let fr = consistency[0];
let sr = consistency[0];
//endregion
//region Calculate consistency hashes
for (let i = 1; i < consistency.length; i++) {
if (sn === 0) return false; //throw new Error("Consistency verification failed");
if (fn & 1 || fn === sn) {
fr = yield utils.hashChildren(consistency[i], fr, hashName);
sr = yield utils.hashChildren(consistency[i], sr, hashName);
if ((fn & 1) === 0) {
while (true) {
fn >>= 1;
sn >>= 1;
if (fn & 1 || fn === 0) break;
}
}
} else sr = yield utils.hashChildren(sr, consistency[i], hashName);
fn >>= 1;
sn >>= 1;
}
//endregion
return (0, _pvutils.isEqualBuffer)(fr, firstHash) && (0, _pvutils.isEqualBuffer)(sr, secondHash) && sn === 0;
})();
}
//**********************************************************************************
/**
* Calculate a Tree Head Given Entries (RFC6962-bis, 2.1.2 (modified))
* @param {Array.<MerkleTreeLeaf|ArrayBuffer>} entries Array of entries to verify against
* @param {String} [hashName=SHA-256] Name of hashing function, default SHA-256
* @param {?ArrayBuffer} [root] If not null tree root would be built starting from the "root"
* @param {?Number} [size] Tree size at which we got the "root". ONLY EVEN SIZES ALLOWED.
* @return {Promise<ArrayBuffer>}
*/
static calculateRootHashByEntries(entries, hashName = "SHA-256", root = null, size = 0) {
return _asyncToGenerator(function* () {
//region Initial variables
const stack = [];
//endregion
//region Check we have "root" set
if (root !== null) {
if ((size & 1) === 0) throw new Error("Calculation new root from old root possible only for even tree sizes");
stack.push(root);
}
//endregion
for (let i = size; i < size + entries.length; i++) {
//region Initial variables
let merge_count = 0;
let entryHash;
//endregion
if ("byteLength" in entries[i - size]) entryHash = entries[i - size];else entryHash = yield entries[i - size].hash(hashName);
stack.push(entryHash);
// noinspection JSBitwiseOperatorUsage
while (i >> merge_count & 1) merge_count++;
for (let j = 0; j < merge_count; j++) {
const right = stack.pop();
const left = stack.pop();
const hash = yield utils.hashChildren(left, right, hashName);
stack.push(hash);
}
}
if (stack.length > 1) {
do {
const right = stack.pop();
const left = stack.pop();
const hash = yield utils.hashChildren(left, right, hashName);
stack.push(hash);
} while (stack.length > 1);
}
return stack.pop();
})();
}
//**********************************************************************************
// noinspection JSUnusedGlobalSymbols
/**
* Verifying a Tree Head Given Entries (RFC6962-bis, 2.1.2)
* @param {Array.<MerkleTreeLeaf|ArrayBuffer>} entries Array of entries to verify against
* @param {ArrayBuffer} rootHash Root hash to compare
* @param {String} [hashName=SHA-256] Name of hashing function, default SHA-256
* @return {Promise<boolean>}
*/
static verifyTreeHeadGivenEntries(entries, rootHash, hashName = "SHA-256") {
return _asyncToGenerator(function* () {
const calculatedRootHash = yield utils.calculateRootHashByEntries(entries, hashName);
return (0, _pvutils.isEqualBuffer)(calculatedRootHash, rootHash);
})();
}
//**********************************************************************************
/**
* Calculate MTH function (RFC6962-bis, 2.1.1)
* @param {Array.<MerkleTreeLeaf|ArrayBuffer>} leafs Array of Merkle Tree Leafs
* @param {String} [hashName=SHA-256] Name of hashing function, default SHA-256
* @return {Promise<ArrayBuffer>}
*/
static MTH(leafs, hashName = "SHA-256") {
return _asyncToGenerator(function* () {
//region Get a "crypto" extension
const crypto = (0, _pkijs.getCrypto)();
if (typeof crypto === "undefined") throw new Error("Unable to create WebCrypto object");
//endregion
//region Special case for empty leaf's list
if (leafs.length === 0) return crypto.digest({ name: hashName }, new ArrayBuffer(0));
//endregion
//region Check special cases
switch (leafs.length) {
case 0:
return crypto.digest({ name: hashName }, new ArrayBuffer(0));
case 1:
if ("byteLength" in leafs[0]) return leafs[0];
return leafs[0].hash(hashName);
default:
}
//endregion
//region Calculate nearest power of 2
const k = utils.flp2(leafs.length);
//endregion
//region Recursivelly calculate "left" and "right"
const left = yield utils.MTH(leafs.slice(0, k));
const right = yield utils.MTH(leafs.slice(k));
//endregion
return utils.hashChildren(left, right, hashName);
})();
}
//**********************************************************************************
/**
* Calculate PATH function (RFC6962-bis, 2.1.3.1)
* @param {Number} index Zero-based index of item to find path for
* @param {Array.<MerkleTreeLeaf>} leafs Array of Merkle Tree Leafs
* @param {String} [hashName=SHA-256] Name of hashing function, default SHA-256
* @return {Promise<Array.<ArrayBuffer>>}
*/
static PATH(index, leafs, hashName = "SHA-256") {
return _asyncToGenerator(function* () {
//region Iniital variables
let mth;
let path;
//endregion
//region Check special case
if (index === 0 && leafs.length === 1) return [];
//endregion
//region Calculate nearest power of 2
const k = utils.flp2(leafs.length);
//endregion
if (index < k) {
mth = yield utils.MTH(leafs.slice(k), hashName);
path = yield utils.PATH(index, leafs.slice(0, k), hashName);
} else {
mth = yield utils.MTH(leafs.slice(0, k), hashName);
path = yield utils.PATH(index - k, leafs.slice(k), hashName);
}
return [...path, mth];
})();
}
//**********************************************************************************
/**
* Calculate SYBPROOF function (RFC6962-bis, 2.1.4.1)
* @param {Number} size Tree size to find consistency proof for
* @param {Array.<MerkleTreeLeaf>} leafs Array of Merkle Tree Leafs
* @param {Boolean} b Value represents whether the subtree created from D[0:m] is a complete subtree of the Merkle Tree
* @param {String} [hashName=SHA-256] Name of hashing function, default SHA-256
* @return {Promise<Array.<ArrayBuffer>>}
*/
static SUBPROOF(size, leafs, b, hashName = "SHA-256") {
return _asyncToGenerator(function* () {
//region Initial variables
let mth;
let proof;
//endregion
//region Check input values
if (size > leafs.length) return [];
//endregion
//region Check special cases
if (size === leafs.length && b === true) return [];
if (size === leafs.length && b === false) return [yield utils.MTH(leafs, hashName)];
//endregion
//region Calculate nearest power of 2
const k = utils.flp2(leafs.length);
//endregion
if (size <= k) {
mth = yield utils.MTH(leafs.slice(k), hashName);
proof = yield utils.SUBPROOF(size, leafs.slice(0, k), b, hashName);
} else {
mth = yield utils.MTH(leafs.slice(0, k), hashName);
proof = yield utils.SUBPROOF(size - k, leafs.slice(k), false, hashName);
}
return [...proof, mth];
})();
}
//**********************************************************************************
/**
* Calculate PROOF function (RFC6962-bis, 2.1.4.1)
* @param {Number} size Tree size to find consistency proof for
* @param {Array.<MerkleTreeLeaf>} leafs Array of Merkle Tree Leafs
* @param {String} [hashName=SHA-256] Name of hashing function, default SHA-256
* @return {Promise<Array.<ArrayBuffer>>}
*/
static PROOF(size, leafs, hashName = "SHA-256") {
return _asyncToGenerator(function* () {
//region Check input values
if (size === 0) return [];
//endregion
return utils.SUBPROOF(size, leafs, true, hashName);
})();
}
//**********************************************************************************
/**
* Get numeric representation of 8-byte value in SeqStream
* @param {SeqStream} stream The SeqStream object to read the value from
* @return {number}
*/
static getUint64(stream) {
return (0, _pvutils.utilFromBase)(new Uint8Array(stream.getBlock(8)), 8);
}
//**********************************************************************************
/**
* Append 8-byte buffer with representation of numeric value
* @param {Number} value The value for conversion
* @param {SeqStream} stream The SeqStream object to be updated
*/
static appendUint64(value, stream) {
const valueBuffer = new ArrayBuffer(8);
const valueView = new Uint8Array(valueBuffer);
const baseArray = (0, _pvutils.utilToBase)(value, 8);
valueView.set(new Uint8Array(baseArray), 8 - baseArray.byteLength);
stream.appendView(valueView);
}
//**********************************************************************************
/**
* Get string representation of OID stored in SeqStream
* @param stream
* @param objectName
* @return {string}
*/
static getOID(stream, objectName) {
const oidLength = stream.getBlock(1)[0];
const asn1 = asn1js.fromBER(new Uint8Array(stream.getBlock(oidLength)).buffer.slice(0));
if (asn1.offset === -1) throw new Error(`Object's stream was not correct for ${objectName}`);
return asn1.result.valueBlock.toString();
}
//**********************************************************************************
/**
* Append OID representation to SeqStream
* @param {String} value String representation of the OID
* @param {SeqStream} stream The SeqStream object to be updated
*/
static appendOID(value, stream) {
const oid = new asn1js.ObjectIdentifier({ value });
const oidBER = oid.toBER(false);
stream.appendChar(oidBER.byteLength);
stream.appendView(new Uint8Array(oidBER));
}
//**********************************************************************************
}
exports.utils = utils; //**************************************************************************************
//# sourceMappingURL=utils.js.map