This commit is contained in:
dela
2026-02-21 18:27:49 +08:00
parent 0ac4b23f07
commit 5dc86ccfbf
270 changed files with 49508 additions and 4636 deletions

View File

@@ -1,289 +1,66 @@
'use strict';
/**
* Crypto Mock
*
* Web Crypto API implementation using Node.js crypto module.
* P1: Crypto / Storage / IDBFactory / atob / btoa mock
*/
import nodeCrypto from 'crypto';
const { createNative, nativeClass } = require('./native');
const nodeCrypto = require('crypto');
export function createCrypto() {
return {
getRandomValues(array) {
const bytes = nodeCrypto.randomBytes(array.byteLength);
const view = new Uint8Array(array.buffer, array.byteOffset, array.byteLength);
view.set(new Uint8Array(bytes));
return array;
},
// ── Crypto ───────────────────────────────────────────────────
const cryptoMock = {
getRandomValues: createNative('getRandomValues', function (array) {
return nodeCrypto.randomFillSync(array);
}),
randomUUID: createNative('randomUUID', function () {
return nodeCrypto.randomUUID();
}),
subtle: {
digest: createNative('digest', function () { return Promise.resolve(new ArrayBuffer(32)); }),
encrypt: createNative('encrypt', function () { return Promise.resolve(new ArrayBuffer(0)); }),
decrypt: createNative('decrypt', function () { return Promise.resolve(new ArrayBuffer(0)); }),
sign: createNative('sign', function () { return Promise.resolve(new ArrayBuffer(32)); }),
verify: createNative('verify', function () { return Promise.resolve(true); }),
generateKey: createNative('generateKey', function () { return Promise.resolve({}); }),
importKey: createNative('importKey', function () { return Promise.resolve({}); }),
exportKey: createNative('exportKey', function () { return Promise.resolve({}); }),
},
};
randomUUID() {
return nodeCrypto.randomUUID();
},
subtle: {
async digest(algorithm, data) {
const algoName = typeof algorithm === 'string'
? algorithm
: algorithm.name;
const hashMap = {
'SHA-1': 'sha1',
'SHA-256': 'sha256',
'SHA-384': 'sha384',
'SHA-512': 'sha512',
};
const nodeAlgo = hashMap[algoName.toUpperCase()] || 'sha256';
const hash = nodeCrypto.createHash(nodeAlgo);
// Handle different data types
if (data instanceof ArrayBuffer) {
hash.update(Buffer.from(data));
} else if (ArrayBuffer.isView(data)) {
hash.update(Buffer.from(data.buffer, data.byteOffset, data.byteLength));
} else {
hash.update(Buffer.from(data));
}
const result = hash.digest();
return result.buffer.slice(result.byteOffset, result.byteOffset + result.byteLength);
},
async encrypt(algorithm, key, data) {
const algoName = algorithm.name || algorithm;
if (algoName === 'AES-GCM') {
const cipher = nodeCrypto.createCipheriv(
'aes-256-gcm',
Buffer.from(key.key || key),
Buffer.from(algorithm.iv)
);
const encrypted = Buffer.concat([
cipher.update(Buffer.from(data)),
cipher.final(),
cipher.getAuthTag()
]);
return encrypted.buffer;
}
if (algoName === 'AES-CBC') {
const cipher = nodeCrypto.createCipheriv(
'aes-256-cbc',
Buffer.from(key.key || key),
Buffer.from(algorithm.iv)
);
const encrypted = Buffer.concat([
cipher.update(Buffer.from(data)),
cipher.final()
]);
return encrypted.buffer;
}
throw new Error(`Unsupported encryption algorithm: ${algoName}`);
},
async decrypt(algorithm, key, data) {
const algoName = algorithm.name || algorithm;
if (algoName === 'AES-GCM') {
const buffer = Buffer.from(data);
const authTag = buffer.slice(-16);
const encrypted = buffer.slice(0, -16);
const decipher = nodeCrypto.createDecipheriv(
'aes-256-gcm',
Buffer.from(key.key || key),
Buffer.from(algorithm.iv)
);
decipher.setAuthTag(authTag);
const decrypted = Buffer.concat([
decipher.update(encrypted),
decipher.final()
]);
return decrypted.buffer;
}
if (algoName === 'AES-CBC') {
const decipher = nodeCrypto.createDecipheriv(
'aes-256-cbc',
Buffer.from(key.key || key),
Buffer.from(algorithm.iv)
);
const decrypted = Buffer.concat([
decipher.update(Buffer.from(data)),
decipher.final()
]);
return decrypted.buffer;
}
throw new Error(`Unsupported decryption algorithm: ${algoName}`);
},
async sign(algorithm, key, data) {
const algoName = algorithm.name || algorithm;
if (algoName === 'HMAC') {
const hashAlgo = algorithm.hash?.name || 'SHA-256';
const nodeHash = hashAlgo.replace('-', '').toLowerCase();
const hmac = nodeCrypto.createHmac(nodeHash, Buffer.from(key.key || key));
hmac.update(Buffer.from(data));
return hmac.digest().buffer;
}
throw new Error(`Unsupported signing algorithm: ${algoName}`);
},
async verify(algorithm, key, signature, data) {
const expected = await this.sign(algorithm, key, data);
const sig = Buffer.from(signature);
const exp = Buffer.from(expected);
return sig.length === exp.length && nodeCrypto.timingSafeEqual(sig, exp);
},
async generateKey(algorithm, extractable, keyUsages) {
const algoName = algorithm.name || algorithm;
if (algoName === 'AES-GCM' || algoName === 'AES-CBC') {
const length = algorithm.length || 256;
const key = nodeCrypto.randomBytes(length / 8);
return {
type: 'secret',
extractable,
algorithm: { name: algoName, length },
usages: keyUsages,
key,
};
}
if (algoName === 'HMAC') {
const hashAlgo = algorithm.hash?.name || 'SHA-256';
const length = algorithm.length || 256;
const key = nodeCrypto.randomBytes(length / 8);
return {
type: 'secret',
extractable,
algorithm: { name: algoName, hash: { name: hashAlgo }, length },
usages: keyUsages,
key,
};
}
throw new Error(`Unsupported key generation algorithm: ${algoName}`);
},
async importKey(format, keyData, algorithm, extractable, keyUsages) {
const algoName = algorithm.name || algorithm;
let key;
if (format === 'raw') {
key = Buffer.from(keyData);
} else if (format === 'jwk') {
// Basic JWK support
key = Buffer.from(keyData.k, 'base64url');
} else {
throw new Error(`Unsupported key format: ${format}`);
}
return {
type: 'secret',
extractable,
algorithm: typeof algorithm === 'string' ? { name: algorithm } : algorithm,
usages: keyUsages,
key,
};
},
async exportKey(format, key) {
if (format === 'raw') {
return key.key.buffer;
}
if (format === 'jwk') {
return {
kty: 'oct',
k: key.key.toString('base64url'),
alg: key.algorithm.name,
ext: key.extractable,
key_ops: key.usages,
};
}
throw new Error(`Unsupported export format: ${format}`);
},
async deriveBits(algorithm, baseKey, length) {
const algoName = algorithm.name || algorithm;
if (algoName === 'PBKDF2') {
const salt = Buffer.from(algorithm.salt);
const iterations = algorithm.iterations;
const hashAlgo = algorithm.hash?.name?.replace('-', '').toLowerCase() || 'sha256';
const derived = nodeCrypto.pbkdf2Sync(
Buffer.from(baseKey.key || baseKey),
salt,
iterations,
length / 8,
hashAlgo
);
return derived.buffer;
}
if (algoName === 'HKDF') {
const salt = Buffer.from(algorithm.salt || []);
const info = Buffer.from(algorithm.info || []);
const hashAlgo = algorithm.hash?.name?.replace('-', '').toLowerCase() || 'sha256';
const derived = nodeCrypto.hkdfSync(
hashAlgo,
Buffer.from(baseKey.key || baseKey),
salt,
info,
length / 8
);
return Buffer.from(derived).buffer;
}
throw new Error(`Unsupported deriveBits algorithm: ${algoName}`);
},
async deriveKey(algorithm, baseKey, derivedKeyAlgorithm, extractable, keyUsages) {
const bits = await this.deriveBits(algorithm, baseKey, derivedKeyAlgorithm.length || 256);
return {
type: 'secret',
extractable,
algorithm: derivedKeyAlgorithm,
usages: keyUsages,
key: Buffer.from(bits),
};
},
async wrapKey(format, key, wrappingKey, wrapAlgorithm) {
const exported = await this.exportKey(format, key);
const data = format === 'raw' ? exported : Buffer.from(JSON.stringify(exported));
return this.encrypt(wrapAlgorithm, wrappingKey, data);
},
async unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgorithm, unwrappedKeyAlgorithm, extractable, keyUsages) {
const decrypted = await this.decrypt(unwrapAlgorithm, unwrappingKey, wrappedKey);
const keyData = format === 'raw' ? decrypted : JSON.parse(Buffer.from(decrypted).toString());
return this.importKey(format, keyData, unwrappedKeyAlgorithm, extractable, keyUsages);
},
},
};
// ── Storage (localStorage / sessionStorage) ──────────────────
class Storage {
constructor() { this._store = {}; }
get length() { return Object.keys(this._store).length; }
key(i) { return Object.keys(this._store)[i] || null; }
getItem(k) { return Object.prototype.hasOwnProperty.call(this._store, k) ? this._store[k] : null; }
setItem(k, v) { this._store[String(k)] = String(v); }
removeItem(k) { delete this._store[k]; }
clear() { this._store = {}; }
}
nativeClass(Storage);
// ── IDBFactory (indexedDB) ────────────────────────────────────
class IDBFactory {
open() { return { result: null, onerror: null, onsuccess: null }; }
deleteDatabase() { return {}; }
databases() { return Promise.resolve([]); }
cmp() { return 0; }
}
nativeClass(IDBFactory);
// ── Notification ──────────────────────────────────────────────
class Notification {
constructor(title, opts) {
this.title = title;
this.options = opts || {};
}
close() {}
static get permission() { return 'denied'; } // P2: denied 或 default
static requestPermission() { return Promise.resolve('denied'); }
}
nativeClass(Notification);
// ── atob / btoa ───────────────────────────────────────────────
const atob = createNative('atob', (str) => Buffer.from(str, 'base64').toString('binary'));
const btoa = createNative('btoa', (str) => Buffer.from(str, 'binary').toString('base64'));
module.exports = { cryptoMock, Storage, IDBFactory, Notification, atob, btoa };