367 lines
12 KiB
TypeScript
367 lines
12 KiB
TypeScript
import assert from "assert";
|
|
import { Convert } from "pvtsutils";
|
|
import { Crypto, CryptoKey } from "webcrypto-core";
|
|
|
|
/**
|
|
* Returns true if blobs from keys are equal
|
|
* @param a Crypto key
|
|
* @param b Crypto key
|
|
*/
|
|
export function isKeyEqual(a: CryptoKey, b: CryptoKey) {
|
|
if (a instanceof CryptoKey && b instanceof CryptoKey) {
|
|
return (a as any).data.equals((b as any).data);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export interface ITestAction {
|
|
name?: string;
|
|
only?: boolean;
|
|
skip?: boolean;
|
|
error?: any;
|
|
}
|
|
|
|
export interface ITestGenerateKeyAction extends ITestAction {
|
|
algorithm: Algorithm;
|
|
extractable: boolean;
|
|
keyUsages: KeyUsage[];
|
|
}
|
|
|
|
export interface IImportKeyParams {
|
|
format: KeyFormat;
|
|
data: JsonWebKey | BufferSource;
|
|
algorithm: AlgorithmIdentifier;
|
|
extractable: boolean;
|
|
keyUsages: KeyUsage[];
|
|
}
|
|
|
|
export interface IImportKeyPairParams {
|
|
privateKey: IImportKeyParams;
|
|
publicKey: IImportKeyParams;
|
|
}
|
|
|
|
export interface ITestEncryptAction extends ITestAction {
|
|
algorithm: Algorithm;
|
|
data: BufferSource;
|
|
encData: BufferSource;
|
|
key: IImportKeyParams | IImportKeyPairParams;
|
|
}
|
|
|
|
export interface ITestSignAction extends ITestAction {
|
|
algorithm: Algorithm;
|
|
data: BufferSource;
|
|
signature: BufferSource;
|
|
key: IImportKeyParams | IImportKeyPairParams;
|
|
}
|
|
|
|
export interface ITestDeriveBitsAction extends ITestAction {
|
|
algorithm: Algorithm;
|
|
key: IImportKeyParams | IImportKeyPairParams;
|
|
data: BufferSource;
|
|
length: number;
|
|
}
|
|
|
|
export interface ITestDeriveKeyAction extends ITestAction {
|
|
algorithm: Algorithm;
|
|
key: IImportKeyParams | IImportKeyPairParams;
|
|
derivedKeyType: Algorithm;
|
|
keyUsages: KeyUsage[];
|
|
format: KeyFormat;
|
|
keyData: BufferSource | JsonWebKey;
|
|
}
|
|
|
|
export interface ITestWrapKeyAction extends ITestAction {
|
|
key: IImportKeyParams | IImportKeyPairParams;
|
|
algorithm: Algorithm;
|
|
wKey: IImportKeyParams;
|
|
wrappedKey?: BufferSource;
|
|
}
|
|
|
|
export interface ITestImportAction extends IImportKeyParams, ITestAction {
|
|
}
|
|
|
|
export interface ITestDigestAction extends ITestAction {
|
|
algorithm: AlgorithmIdentifier;
|
|
data: BufferSource;
|
|
hash: BufferSource;
|
|
}
|
|
|
|
export interface ITestActions {
|
|
generateKey?: ITestGenerateKeyAction[];
|
|
encrypt?: ITestEncryptAction[];
|
|
wrapKey?: ITestWrapKeyAction[];
|
|
sign?: ITestSignAction[];
|
|
import?: ITestImportAction[];
|
|
deriveBits?: ITestDeriveBitsAction[];
|
|
deriveKey?: ITestDeriveKeyAction[];
|
|
digest?: ITestDigestAction[];
|
|
}
|
|
|
|
export interface ITestParams {
|
|
name: string;
|
|
only?: boolean;
|
|
actions: ITestActions;
|
|
}
|
|
|
|
async function getKeys(crypto: Crypto, key: IImportKeyParams | IImportKeyPairParams) {
|
|
const keys = {} as CryptoKeyPair;
|
|
if ("privateKey" in key) {
|
|
keys.privateKey = await crypto.subtle.importKey(
|
|
key.privateKey.format,
|
|
key.privateKey.data,
|
|
key.privateKey.algorithm,
|
|
key.privateKey.extractable,
|
|
key.privateKey.keyUsages);
|
|
keys.publicKey = await crypto.subtle.importKey(
|
|
key.publicKey.format,
|
|
key.publicKey.data,
|
|
key.publicKey.algorithm,
|
|
key.publicKey.extractable,
|
|
key.publicKey.keyUsages);
|
|
} else {
|
|
keys.privateKey = keys.publicKey = await crypto.subtle.importKey(
|
|
key.format,
|
|
key.data,
|
|
key.algorithm,
|
|
key.extractable,
|
|
key.keyUsages);
|
|
}
|
|
|
|
return keys;
|
|
}
|
|
|
|
async function wrapTest(promise: () => Promise<void>, action: ITestAction, index: number) {
|
|
const test = action.skip
|
|
? it.skip
|
|
: action.only
|
|
? it.only
|
|
: it;
|
|
|
|
test(action.name || `#${index + 1}`, async () => {
|
|
if (action.error) {
|
|
await assert.rejects(promise(), action.error);
|
|
} else {
|
|
await promise();
|
|
}
|
|
});
|
|
}
|
|
|
|
export function testCrypto(crypto: Crypto, params: ITestParams[]) {
|
|
params.forEach((param) => {
|
|
context(param.name, () => {
|
|
//#region Generate key
|
|
if (param.actions.generateKey) {
|
|
context("Generate Key", () => {
|
|
param.actions.generateKey!.forEach((action, index) => {
|
|
wrapTest(async () => {
|
|
const algorithm = Object.assign({}, action.algorithm);
|
|
algorithm.name = algorithm.name.toLowerCase();
|
|
|
|
const key = await crypto.subtle.generateKey(
|
|
algorithm,
|
|
action.extractable,
|
|
action.keyUsages,
|
|
);
|
|
|
|
assert(key);
|
|
if (key instanceof CryptoKey) {
|
|
assert.equal(key.algorithm.name, action.algorithm.name, "Algorithm name MUST be equal to incoming algorithm and in the same case");
|
|
assert.equal(key.extractable, action.extractable);
|
|
assert.deepEqual(key.usages, action.keyUsages);
|
|
} else {
|
|
assert(key.privateKey);
|
|
assert.equal(key.privateKey.algorithm.name, action.algorithm.name, "Algorithm name MUST be equal to incoming algorithm and in the same case");
|
|
assert.equal(key.privateKey.extractable, action.extractable);
|
|
|
|
assert(key.publicKey);
|
|
assert.equal(key.publicKey.algorithm.name, action.algorithm.name, "Algorithm name MUST be equal to incoming algorithm and in the same case");
|
|
assert.equal(key.publicKey.extractable, true);
|
|
}
|
|
}, action, index);
|
|
});
|
|
});
|
|
}
|
|
//#endregion
|
|
|
|
//#region encrypt
|
|
if (param.actions.encrypt) {
|
|
context("Encrypt/Decrypt", () => {
|
|
param.actions.encrypt!.forEach((action, index) => {
|
|
wrapTest(async () => {
|
|
// import keys
|
|
const keys = await getKeys(crypto, action.key);
|
|
const encKey = keys.publicKey;
|
|
const decKey = keys.privateKey;
|
|
|
|
const algorithm = Object.assign({}, action.algorithm);
|
|
algorithm.name = algorithm.name.toLowerCase();
|
|
|
|
// encrypt
|
|
const enc = await crypto.subtle.encrypt(algorithm, encKey, action.data);
|
|
|
|
// decrypt
|
|
let dec = await crypto.subtle.decrypt(algorithm, decKey, enc);
|
|
assert.equal(Convert.ToHex(dec), Convert.ToHex(action.data));
|
|
|
|
dec = await crypto.subtle.decrypt(algorithm, decKey, action.encData);
|
|
assert.equal(Convert.ToHex(dec), Convert.ToHex(action.data));
|
|
}, action, index);
|
|
});
|
|
});
|
|
}
|
|
//#endregion
|
|
|
|
//#region Import/Export
|
|
if (param.actions.import) {
|
|
context("Import/Export", () => {
|
|
param.actions.import!.forEach((action, index) => {
|
|
wrapTest(async () => {
|
|
const importedKey = await crypto.subtle.importKey(
|
|
action.format,
|
|
action.data,
|
|
action.algorithm,
|
|
action.extractable,
|
|
action.keyUsages);
|
|
|
|
// Can't continue if key is not exctractable.
|
|
if (!action.extractable) {
|
|
return;
|
|
}
|
|
|
|
const exportedData = await crypto.subtle.exportKey(
|
|
action.format,
|
|
importedKey);
|
|
|
|
if (action.format === "jwk") {
|
|
assert.deepEqual(exportedData, action.data);
|
|
} else {
|
|
assert.equal(Buffer.from(exportedData as ArrayBuffer).toString("hex"), Buffer.from(action.data as ArrayBuffer).toString("hex"));
|
|
}
|
|
}, action, index);
|
|
});
|
|
});
|
|
}
|
|
//#endregion
|
|
|
|
//#region Sign/Verify
|
|
if (param.actions.sign) {
|
|
context("Sign/Verify", () => {
|
|
param.actions.sign!.forEach((action, index) => {
|
|
wrapTest(async () => {
|
|
// import keys
|
|
const keys = await getKeys(crypto, action.key);
|
|
const verifyKey = keys.publicKey;
|
|
const signKey = keys.privateKey;
|
|
|
|
const algorithm = Object.assign({}, action.algorithm);
|
|
algorithm.name = algorithm.name.toLowerCase();
|
|
|
|
// sign
|
|
const signature = await crypto.subtle.sign(algorithm, signKey, action.data);
|
|
|
|
// verify
|
|
let ok = await crypto.subtle.verify(algorithm, verifyKey, signature, action.data);
|
|
assert.equal(true, ok, "Cannot verify signature from Action data");
|
|
|
|
ok = await crypto.subtle.verify(algorithm, verifyKey, action.signature, action.data);
|
|
if (!ok) {
|
|
assert.equal(Convert.ToHex(signature), Convert.ToHex(action.signature));
|
|
}
|
|
assert.equal(true, ok);
|
|
}, action, index);
|
|
});
|
|
});
|
|
}
|
|
//#endregion
|
|
|
|
//#region Derive bits
|
|
if (param.actions.deriveBits) {
|
|
context("Derive bits", () => {
|
|
param.actions.deriveBits!.forEach((action, index) => {
|
|
wrapTest(async () => {
|
|
// import keys
|
|
const keys = await getKeys(crypto, action.key);
|
|
|
|
const algorithm = Object.assign({}, action.algorithm, { public: keys.publicKey }) as any;
|
|
algorithm.name = algorithm.name.toLowerCase();
|
|
|
|
// derive bits
|
|
const derivedBits = await crypto.subtle.deriveBits(algorithm, keys.privateKey, action.length);
|
|
assert.equal(Convert.ToHex(derivedBits), Convert.ToHex(action.data));
|
|
}, action, index);
|
|
});
|
|
});
|
|
}
|
|
//#endregion
|
|
|
|
//#region Derive key
|
|
if (param.actions.deriveKey) {
|
|
context("Derive key", () => {
|
|
param.actions.deriveKey!.forEach((action, index) => {
|
|
wrapTest(async () => {
|
|
// import keys
|
|
const keys = await getKeys(crypto, action.key);
|
|
|
|
const algorithm = Object.assign({}, action.algorithm, { public: keys.publicKey }) as any;
|
|
algorithm.name = algorithm.name.toLowerCase();
|
|
|
|
// derive key
|
|
const derivedKey = await crypto.subtle.deriveKey(algorithm, keys.privateKey, action.derivedKeyType, true, action.keyUsages);
|
|
const keyData = await crypto.subtle.exportKey(action.format, derivedKey);
|
|
if (action.format === "jwk") {
|
|
assert.deepEqual(keyData, action.keyData);
|
|
} else {
|
|
assert.equal(Convert.ToHex(keyData as ArrayBuffer), Convert.ToHex(action.keyData as ArrayBuffer));
|
|
}
|
|
}, action, index);
|
|
});
|
|
});
|
|
}
|
|
//#endregion
|
|
|
|
//#region Digest
|
|
if (param.actions.digest) {
|
|
context("Digest", () => {
|
|
param.actions.digest!.forEach((action, index) => {
|
|
wrapTest(async () => {
|
|
const hash = await crypto.subtle.digest(action.algorithm, action.data);
|
|
assert.equal(Convert.ToHex(hash), Convert.ToHex(action.hash));
|
|
}, action, index);
|
|
});
|
|
});
|
|
}
|
|
//#endregion
|
|
|
|
//#region Wrap/Unwrap key
|
|
if (param.actions.wrapKey) {
|
|
context("Wrap/Unwrap key", () => {
|
|
param.actions.wrapKey!.forEach((action, index) => {
|
|
wrapTest(async () => {
|
|
const wKey = (await getKeys(crypto, action.wKey)).privateKey;
|
|
const key = await getKeys(crypto, action.key);
|
|
|
|
const wrappedKey = await crypto.subtle.wrapKey(action.wKey.format, wKey, key.publicKey, action.algorithm);
|
|
|
|
if (action.wrappedKey) {
|
|
assert.equal(Convert.ToHex(wrappedKey), Convert.ToHex(action.wrappedKey));
|
|
}
|
|
|
|
const unwrappedKey = await crypto.subtle.unwrapKey(
|
|
action.wKey.format,
|
|
wrappedKey,
|
|
key.privateKey,
|
|
action.algorithm,
|
|
action.wKey.algorithm,
|
|
action.wKey.extractable,
|
|
action.wKey.keyUsages);
|
|
|
|
assert.deepEqual(unwrappedKey.algorithm, wKey.algorithm);
|
|
}, action, index);
|
|
});
|
|
});
|
|
}
|
|
//#endregion
|
|
});
|
|
});
|
|
}
|