This repository has been archived on 2023-04-04. You can view files and clone it, but cannot push or open issues or pull requests.
webcrypto/test/helper.ts

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
});
});
}