This commit is contained in:
dela
2026-02-20 13:16:51 +08:00
commit 0ac4b23f07
36 changed files with 5042 additions and 0 deletions

289
src/sandbox/mocks/crypto.js Normal file
View File

@@ -0,0 +1,289 @@
/**
* Crypto Mock
*
* Web Crypto API implementation using Node.js crypto module.
*/
import nodeCrypto from '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;
},
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);
},
},
};
}