Files

181 lines
5.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const { scaffoldModule } = require("./scaffold-module");
const DEFAULT_SUBTLE_METHODS = [
"digest",
"generateKey",
"importKey",
"exportKey",
"sign",
"verify",
"encrypt",
"decrypt",
];
function toFiniteNumber(value, fallback) {
return Number.isFinite(value) ? value : fallback;
}
function normalizeCryptoSeed(seed = {}) {
const cryptoSeed = seed.crypto && typeof seed.crypto === "object" ? seed.crypto : seed;
return {
exposeCrypto: cryptoSeed.exposeCrypto !== false,
installMsCrypto: cryptoSeed.installMsCrypto !== false,
maxGetRandomValuesLength: toFiniteNumber(cryptoSeed.maxGetRandomValuesLength, 65536),
hasSubtle: cryptoSeed.hasSubtle !== false,
subtleMethods: Array.isArray(cryptoSeed.subtleMethods) && cryptoSeed.subtleMethods.length
? cryptoSeed.subtleMethods.slice()
: DEFAULT_SUBTLE_METHODS.slice(),
randomValues: Array.isArray(cryptoSeed.randomValues)
? cryptoSeed.randomValues.map((item) => Number(item) & 255)
: [],
};
}
function renderCryptoPatch(seed) {
const serializedSeed = JSON.stringify(seed, null, 2);
return `(
function installCryptoModule(globalObject) {
const seed = ${serializedSeed};
const protector = globalObject.__envNativeProtector || null;
const integerArrayTags = new Set([
"[object Int8Array]",
"[object Uint8Array]",
"[object Uint8ClampedArray]",
"[object Int16Array]",
"[object Uint16Array]",
"[object Int32Array]",
"[object Uint32Array]"
]);
const randomPool = Array.isArray(seed.randomValues) ? seed.randomValues.slice() : [];
function defineValue(target, key, value) {
Object.defineProperty(target, key, {
configurable: true,
enumerable: false,
writable: true,
value: value
});
}
function createQuotaError(message) {
if (typeof DOMException === "function") {
return new DOMException(message, "QuotaExceededError");
}
const error = new Error(message);
error.name = "QuotaExceededError";
return error;
}
function assertIntegerTypedArray(view) {
const tag = Object.prototype.toString.call(view);
if (!integerArrayTags.has(tag)) {
throw new TypeError("Failed to execute 'getRandomValues' on 'Crypto': parameter 1 is not an integer array view.");
}
if (view.byteLength > seed.maxGetRandomValuesLength) {
throw createQuotaError("Failed to execute 'getRandomValues' on 'Crypto': The ArrayBufferView's byte length exceeds the number of bytes of entropy available via this API.");
}
return view;
}
function nextByte(index) {
if (randomPool.length) {
return randomPool[index % randomPool.length] & 255;
}
return (index * 73 + 19) & 255;
}
function getRandomValues(view) {
assertIntegerTypedArray(view);
for (let index = 0; index < view.length; index += 1) {
view[index] = nextByte(index);
}
return view;
}
const subtle = {};
for (const methodName of seed.subtleMethods) {
subtle[methodName] = function subtlePlaceholder() {
return Promise.reject(new Error("SubtleCrypto method not implemented: " + methodName));
};
if (protector && protector.repairFunctionMeta) {
protector.repairFunctionMeta(subtle[methodName], { name: methodName, length: 0 });
}
if (protector && protector.protectFunction) {
protector.protectFunction(subtle[methodName], methodName);
}
}
const cryptoObject = {};
defineValue(cryptoObject, "getRandomValues", getRandomValues);
if (seed.hasSubtle) {
defineValue(cryptoObject, "subtle", subtle);
}
if (protector && protector.repairFunctionMeta) {
protector.repairFunctionMeta(getRandomValues, { name: "getRandomValues", length: 1 });
}
if (protector && protector.protectFunction) {
protector.protectFunction(getRandomValues, "getRandomValues");
}
if (seed.exposeCrypto) {
Object.defineProperty(globalObject, "crypto", {
configurable: true,
enumerable: false,
writable: true,
value: cryptoObject
});
}
if (seed.installMsCrypto) {
Object.defineProperty(globalObject, "msCrypto", {
configurable: true,
enumerable: false,
writable: true,
value: cryptoObject
});
}
return cryptoObject;
})(typeof globalThis !== "undefined" ? globalThis : window);
`;
}
function buildCryptoModule(seed = {}, options = {}) {
const module = scaffoldModule("crypto-module");
const normalizedSeed = normalizeCryptoSeed(seed);
module.patchPlan = [
"挂载 window.crypto 与可选的 window.msCrypto。",
"按种子构建 getRandomValues,并复刻整数 TypedArray 校验和长度上限。",
"按模块契约补出 subtle 对象的基础方法占位,并接入 native 保护接口。",
];
module.patchCode = renderCryptoPatch(normalizedSeed, options);
module.runtimeState = {
crypto: normalizedSeed,
};
module.validation = [
"调用 crypto.getRandomValues(new Uint8Array(16)),确认返回原数组实例。",
"调用 crypto.getRandomValues(new Uint16Array(8)) 与 new Uint32Array(4)。",
"传入 Float32Array 时应抛出 TypeError。",
"传入超出 maxGetRandomValuesLength 的视图时应抛出 QuotaExceededError。",
"检查 crypto.getRandomValues.toString() 与 crypto.getRandomValues.name/length。",
];
module.residualRisk = [
"subtle 仍为占位实现,只适合绕过存在性检测,不适合真实密码流程。",
"如果目标站点会比对 DOMException message/code,仍需依据真实浏览器日志继续收敛。",
];
if (options.includeLegacyAlias === false) {
module.runtimeState.crypto.installMsCrypto = false;
}
return module;
}
module.exports = {
buildCryptoModule,
normalizeCryptoSeed,
};