From 0bd515160475adb91e270f56e48b49c87414950f Mon Sep 17 00:00:00 2001 From: microshine Date: Tue, 24 May 2022 15:06:18 +0300 Subject: [PATCH] add web package --- .mocharc.yml | 2 +- packages/web/README.md | 11 + packages/web/package.json | 44 + packages/web/src/crypto.ts | 17 + packages/web/src/debug.ts | 44 + packages/web/src/error.ts | 8 + packages/web/src/helper.ts | 95 +++ packages/web/src/index.ts | 3 + packages/web/src/init.ts | 45 + packages/web/src/key.ts | 15 + packages/web/src/lib.ts | 9 + packages/web/src/mechs/aes/aes_cbc.ts | 33 + packages/web/src/mechs/aes/aes_ctr.ts | 38 + packages/web/src/mechs/aes/aes_ecb.ts | 33 + packages/web/src/mechs/aes/aes_gcm.ts | 33 + packages/web/src/mechs/aes/aes_kw.ts | 27 + packages/web/src/mechs/aes/crypto.ts | 99 +++ packages/web/src/mechs/aes/index.ts | 6 + packages/web/src/mechs/aes/key.ts | 38 + packages/web/src/mechs/des/crypto.ts | 107 +++ packages/web/src/mechs/des/des_cbc.ts | 37 + packages/web/src/mechs/des/des_ede3_cbc.ts | 39 + packages/web/src/mechs/des/index.ts | 3 + packages/web/src/mechs/des/key.ts | 35 + packages/web/src/mechs/ec/crypto.ts | 230 ++++++ packages/web/src/mechs/ec/ec_dh.ts | 43 + packages/web/src/mechs/ec/ec_dsa.ts | 76 ++ packages/web/src/mechs/ec/helper.ts | 42 + packages/web/src/mechs/ec/index.ts | 3 + packages/web/src/mechs/ec/key.ts | 13 + packages/web/src/mechs/ed/crypto.ts | 214 +++++ packages/web/src/mechs/ed/ecdh_es.ts | 39 + packages/web/src/mechs/ed/eddsa.ts | 40 + packages/web/src/mechs/ed/helper.ts | 32 + packages/web/src/mechs/ed/index.ts | 4 + packages/web/src/mechs/ed/private_key.ts | 50 ++ packages/web/src/mechs/ed/public_key.ts | 49 ++ packages/web/src/mechs/hmac/hmac.ts | 100 +++ packages/web/src/mechs/hmac/index.ts | 1 + packages/web/src/mechs/hmac/key.ts | 56 ++ packages/web/src/mechs/index.ts | 8 + packages/web/src/mechs/pbkdf/index.ts | 1 + packages/web/src/mechs/pbkdf/key.ts | 10 + packages/web/src/mechs/pbkdf/pbkdf2.ts | 45 + packages/web/src/mechs/rsa/crypto.ts | 228 ++++++ packages/web/src/mechs/rsa/index.ts | 5 + packages/web/src/mechs/rsa/key.ts | 12 + packages/web/src/mechs/rsa/rsa_es.ts | 119 +++ packages/web/src/mechs/rsa/rsa_oaep.ts | 53 ++ packages/web/src/mechs/rsa/rsa_pss.ts | 44 + packages/web/src/mechs/rsa/rsa_ssa.ts | 44 + packages/web/src/mechs/sha/crypto.ts | 32 + packages/web/src/mechs/sha/index.ts | 8 + packages/web/src/mechs/sha/sha3_256.ts | 13 + packages/web/src/mechs/sha/sha3_384.ts | 13 + packages/web/src/mechs/sha/sha3_512.ts | 13 + packages/web/src/mechs/sha/sha_1.ts | 13 + packages/web/src/mechs/sha/sha_256.ts | 5 + packages/web/src/mechs/sha/sha_512.ts | 5 + packages/web/src/mechs/sha/shake128.ts | 14 + packages/web/src/mechs/sha/shake256.ts | 14 + packages/web/src/native.ts | 25 + packages/web/src/shim.ts | 23 + packages/web/src/subtle.ts | 474 +++++++++++ packages/web/src/typings/des.d.ts | 28 + packages/web/src/typings/elliptic.d.ts | 102 +++ packages/web/src/typings/index.d.ts | 4 + packages/web/src/utils.ts | 5 + packages/web/src/wrapped_native_key.ts | 23 + packages/web/test/aes.spec.ts | 901 +++++++++++++++++++++ packages/web/test/des.spec.ts | 125 +++ packages/web/test/ec.spec.ts | 555 +++++++++++++ packages/web/test/ed.spec.ts | 79 ++ packages/web/test/hmac.spec.ts | 175 ++++ packages/web/test/pbkdf.spec.ts | 61 ++ packages/web/test/rsa.spec.ts | 420 ++++++++++ packages/web/test/sha.spec.ts | 82 ++ packages/web/test/utils/helper.ts | 371 +++++++++ packages/web/test/utils/index.ts | 2 + packages/web/test/utils/init.ts | 46 ++ tsconfig.json | 3 + yarn.lock | 99 ++- 82 files changed, 6111 insertions(+), 4 deletions(-) create mode 100644 packages/web/README.md create mode 100644 packages/web/package.json create mode 100644 packages/web/src/crypto.ts create mode 100644 packages/web/src/debug.ts create mode 100644 packages/web/src/error.ts create mode 100644 packages/web/src/helper.ts create mode 100644 packages/web/src/index.ts create mode 100644 packages/web/src/init.ts create mode 100644 packages/web/src/key.ts create mode 100644 packages/web/src/lib.ts create mode 100644 packages/web/src/mechs/aes/aes_cbc.ts create mode 100644 packages/web/src/mechs/aes/aes_ctr.ts create mode 100644 packages/web/src/mechs/aes/aes_ecb.ts create mode 100644 packages/web/src/mechs/aes/aes_gcm.ts create mode 100644 packages/web/src/mechs/aes/aes_kw.ts create mode 100644 packages/web/src/mechs/aes/crypto.ts create mode 100644 packages/web/src/mechs/aes/index.ts create mode 100644 packages/web/src/mechs/aes/key.ts create mode 100644 packages/web/src/mechs/des/crypto.ts create mode 100644 packages/web/src/mechs/des/des_cbc.ts create mode 100644 packages/web/src/mechs/des/des_ede3_cbc.ts create mode 100644 packages/web/src/mechs/des/index.ts create mode 100644 packages/web/src/mechs/des/key.ts create mode 100644 packages/web/src/mechs/ec/crypto.ts create mode 100644 packages/web/src/mechs/ec/ec_dh.ts create mode 100644 packages/web/src/mechs/ec/ec_dsa.ts create mode 100644 packages/web/src/mechs/ec/helper.ts create mode 100644 packages/web/src/mechs/ec/index.ts create mode 100644 packages/web/src/mechs/ec/key.ts create mode 100644 packages/web/src/mechs/ed/crypto.ts create mode 100644 packages/web/src/mechs/ed/ecdh_es.ts create mode 100644 packages/web/src/mechs/ed/eddsa.ts create mode 100644 packages/web/src/mechs/ed/helper.ts create mode 100644 packages/web/src/mechs/ed/index.ts create mode 100644 packages/web/src/mechs/ed/private_key.ts create mode 100644 packages/web/src/mechs/ed/public_key.ts create mode 100644 packages/web/src/mechs/hmac/hmac.ts create mode 100644 packages/web/src/mechs/hmac/index.ts create mode 100644 packages/web/src/mechs/hmac/key.ts create mode 100644 packages/web/src/mechs/index.ts create mode 100644 packages/web/src/mechs/pbkdf/index.ts create mode 100644 packages/web/src/mechs/pbkdf/key.ts create mode 100644 packages/web/src/mechs/pbkdf/pbkdf2.ts create mode 100644 packages/web/src/mechs/rsa/crypto.ts create mode 100644 packages/web/src/mechs/rsa/index.ts create mode 100644 packages/web/src/mechs/rsa/key.ts create mode 100644 packages/web/src/mechs/rsa/rsa_es.ts create mode 100644 packages/web/src/mechs/rsa/rsa_oaep.ts create mode 100644 packages/web/src/mechs/rsa/rsa_pss.ts create mode 100644 packages/web/src/mechs/rsa/rsa_ssa.ts create mode 100644 packages/web/src/mechs/sha/crypto.ts create mode 100644 packages/web/src/mechs/sha/index.ts create mode 100644 packages/web/src/mechs/sha/sha3_256.ts create mode 100644 packages/web/src/mechs/sha/sha3_384.ts create mode 100644 packages/web/src/mechs/sha/sha3_512.ts create mode 100644 packages/web/src/mechs/sha/sha_1.ts create mode 100644 packages/web/src/mechs/sha/sha_256.ts create mode 100644 packages/web/src/mechs/sha/sha_512.ts create mode 100644 packages/web/src/mechs/sha/shake128.ts create mode 100644 packages/web/src/mechs/sha/shake256.ts create mode 100644 packages/web/src/native.ts create mode 100644 packages/web/src/shim.ts create mode 100644 packages/web/src/subtle.ts create mode 100644 packages/web/src/typings/des.d.ts create mode 100644 packages/web/src/typings/elliptic.d.ts create mode 100644 packages/web/src/typings/index.d.ts create mode 100644 packages/web/src/utils.ts create mode 100644 packages/web/src/wrapped_native_key.ts create mode 100644 packages/web/test/aes.spec.ts create mode 100644 packages/web/test/des.spec.ts create mode 100644 packages/web/test/ec.spec.ts create mode 100644 packages/web/test/ed.spec.ts create mode 100644 packages/web/test/hmac.spec.ts create mode 100644 packages/web/test/pbkdf.spec.ts create mode 100644 packages/web/test/rsa.spec.ts create mode 100644 packages/web/test/sha.spec.ts create mode 100644 packages/web/test/utils/helper.ts create mode 100644 packages/web/test/utils/index.ts create mode 100644 packages/web/test/utils/init.ts diff --git a/.mocharc.yml b/.mocharc.yml index 71b066d..9066746 100644 --- a/.mocharc.yml +++ b/.mocharc.yml @@ -4,5 +4,5 @@ require: extension: - ts spec: - - "packages/**/*.ts" + - "packages/**/*.spec.ts" exit: true diff --git a/packages/web/README.md b/packages/web/README.md new file mode 100644 index 0000000..1d992f6 --- /dev/null +++ b/packages/web/README.md @@ -0,0 +1,11 @@ +# `@peculiar/webcrypto-web` + +> TODO: description + +## Usage + +``` +const webcryptoWeb = require('@peculiar/webcrypto-web'); + +// TODO: DEMONSTRATE API +``` diff --git a/packages/web/package.json b/packages/web/package.json new file mode 100644 index 0000000..b5c6117 --- /dev/null +++ b/packages/web/package.json @@ -0,0 +1,44 @@ +{ + "name": "@peculiar/webcrypto-web", + "version": "3.0.0", + "description": "> TODO: description", + "author": "microshine ", + "homepage": "https://github.com/PeculiarVentures/webcrypto/tree/master/packages/webcrypto-types#readme", + "license": "MIT", + "types": "src/index.d.ts", + "directories": { + "lib": "lib", + "test": "__tests__" + }, + "files": [ + "build", + "README.md", + "LICENSE" + ], + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/PeculiarVentures/webcrypto.git" + }, + "scripts": { + "test": "echo \"Error: run tests from root\" && exit 1" + }, + "bugs": { + "url": "https://github.com/PeculiarVentures/webcrypto/issues" + }, + "dependencies": { + "@peculiar/asn1-schema": "^2.1.6", + "@peculiar/json-schema": "^1.1.12", + "@peculiar/webcrypto-core": "^3.0.0", + "@peculiar/webcrypto-types": "^3.0.0", + "@stablelib/sha3": "^1.0.1", + "asmcrypto.js": "^2.3.2", + "asn1js": "^3.0.5", + "des.js": "^1.0.1", + "elliptic": "https://github.com/mahrud/elliptic", + "pvtsutils": "^1.3.2", + "tslib": "^2.4.0" + } +} \ No newline at end of file diff --git a/packages/web/src/crypto.ts b/packages/web/src/crypto.ts new file mode 100644 index 0000000..2a6e518 --- /dev/null +++ b/packages/web/src/crypto.ts @@ -0,0 +1,17 @@ +import * as core from "@peculiar/webcrypto-core"; +import { nativeCrypto } from "./native"; +import { SubtleCrypto } from "./subtle"; + +export class Crypto extends core.Crypto { + + public get nativeCrypto() { + return nativeCrypto; + } + + public subtle = new SubtleCrypto(); + + getRandomValues(array: T): T { + return nativeCrypto.getRandomValues(array as any); + } + +} diff --git a/packages/web/src/debug.ts b/packages/web/src/debug.ts new file mode 100644 index 0000000..6b4c27c --- /dev/null +++ b/packages/web/src/debug.ts @@ -0,0 +1,44 @@ +declare const self: any; + +export class Debug { + + public static get enabled() { + return typeof self !== "undefined" && (self as any).PV_WEBCRYPTO_LINER_LOG; + } + + public static log(message?: any, ...optionalParams: any[]): void; + public static log(...args: any[]) { + if (this.enabled) { + console.log.apply(console, args); + } + } + + public static error(message?: any, ...optionalParams: any[]): void; + public static error(...args: any[]) { + if (this.enabled) { + console.error.apply(console, args); + } + } + + public static info(message?: any, ...optionalParams: any[]): void; + public static info(...args: any[]) { + if (this.enabled) { + console.info.apply(console, args); + } + } + + public static warn(message?: any, ...optionalParams: any[]): void; + public static warn(...args: any[]) { + if (this.enabled) { + console.warn.apply(console, args); + } + } + + public static trace(message?: any, ...optionalParams: any[]): void; + public static trace(...args: any[]) { + if (this.enabled) { + console.trace.apply(console, args); + } + } + +} diff --git a/packages/web/src/error.ts b/packages/web/src/error.ts new file mode 100644 index 0000000..dd04b9d --- /dev/null +++ b/packages/web/src/error.ts @@ -0,0 +1,8 @@ +import { CryptoError } from "@peculiar/webcrypto-core"; + +export class LinerError extends CryptoError { + public static MODULE_NOT_FOUND = "Module '%1' is not found. Download it from %2"; + public static UNSUPPORTED_ALGORITHM = "Unsupported algorithm '%1'"; + + public code = 10; +} diff --git a/packages/web/src/helper.ts b/packages/web/src/helper.ts new file mode 100644 index 0000000..95c2917 --- /dev/null +++ b/packages/web/src/helper.ts @@ -0,0 +1,95 @@ +declare const self: any; + +export enum Browser { + Unknown = "Unknown", + IE = "Internet Explorer", + Safari = "Safari", + Edge = "Edge", + Chrome = "Chrome", + Firefox = "Firefox Mozilla", + Mobile = "Mobile", +} + +export interface IBrowserInfo { + name: Browser; + version: string; +} + +/** + * Returns info about browser + */ +export function BrowserInfo() { + const res: IBrowserInfo = { + name: Browser.Unknown, + version: "0", + }; + if (typeof self === "undefined") { + return res; + } + const userAgent = self.navigator.userAgent; + + let reg: string[] | null; + if (reg = /edge\/([\d\.]+)/i.exec(userAgent)) { + res.name = Browser.Edge; + res.version = reg[1]; + } else if (/msie/i.test(userAgent)) { + res.name = Browser.IE; + res.version = /msie ([\d\.]+)/i.exec(userAgent)![1]; + } else if (/Trident/i.test(userAgent)) { + res.name = Browser.IE; + res.version = /rv:([\d\.]+)/i.exec(userAgent)![1]; + } else if (/chrome/i.test(userAgent)) { + res.name = Browser.Chrome; + res.version = /chrome\/([\d\.]+)/i.exec(userAgent)![1]; + } else if (/firefox/i.test(userAgent)) { + res.name = Browser.Firefox; + res.version = /firefox\/([\d\.]+)/i.exec(userAgent)![1]; + } else if (/mobile/i.test(userAgent)) { + res.name = Browser.Mobile; + res.version = /mobile\/([\w]+)/i.exec(userAgent)![1]; + } else if (/safari/i.test(userAgent)) { + res.name = Browser.Safari; + res.version = /version\/([\d\.]+)/i.exec(userAgent)![1]; + } + return res; +} + +export function string2buffer(binaryString: string) { + const res = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + res[i] = binaryString.charCodeAt(i); + } + return res; +} + +export function buffer2string(buffer: Uint8Array) { + let res = ""; + for (let i = 0; i < buffer.length; i++) { + res += String.fromCharCode(buffer[i]); + } + return res; +} + +export function concat(...buf: Uint8Array[]) { + const res = new Uint8Array(buf.map((item) => item.length).reduce((prev, cur) => prev + cur)); + let offset = 0; + buf.forEach((item, index) => { + for (let i = 0; i < item.length; i++) { + res[offset + i] = item[i]; + } + offset += item.length; + }); + return res; +} + +export function assign(target: any, ...sources: any[]): any; +export function assign(...args: any[]) { + const res = args[0]; + for (let i = 1; i < args.length; i++) { + const obj = args[i]; + for (const prop in obj) { + res[prop] = obj[prop]; + } + } + return res; +} diff --git a/packages/web/src/index.ts b/packages/web/src/index.ts new file mode 100644 index 0000000..47d7276 --- /dev/null +++ b/packages/web/src/index.ts @@ -0,0 +1,3 @@ +export * from "./native"; +export * from "./crypto"; +export * from "./key"; diff --git a/packages/web/src/init.ts b/packages/web/src/init.ts new file mode 100644 index 0000000..1ce2fd6 --- /dev/null +++ b/packages/web/src/init.ts @@ -0,0 +1,45 @@ +import { nativeSubtle } from "./native"; + +declare const self: any; + +function WrapFunction(subtle: any, name: string) { + const fn = subtle[name]; + subtle[name] = function () { + const args = arguments; + return new Promise((resolve, reject) => { + const op: any = fn.apply(subtle, args); + op.oncomplete = (e: any) => { + resolve(e.target.result); + }; + op.onerror = (e: any) => { + reject(`Error on running '${name}' function`); + }; + }); + }; +} + +if (typeof self !== "undefined" && self["msCrypto"]) { + WrapFunction(nativeSubtle, "generateKey"); + WrapFunction(nativeSubtle, "digest"); + WrapFunction(nativeSubtle, "sign"); + WrapFunction(nativeSubtle, "verify"); + WrapFunction(nativeSubtle, "encrypt"); + WrapFunction(nativeSubtle, "decrypt"); + WrapFunction(nativeSubtle, "importKey"); + WrapFunction(nativeSubtle, "exportKey"); + WrapFunction(nativeSubtle, "wrapKey"); + WrapFunction(nativeSubtle, "unwrapKey"); + WrapFunction(nativeSubtle, "deriveKey"); + WrapFunction(nativeSubtle, "deriveBits"); +} + +// fix: Math.imul for IE +if (!(Math as any).imul) { + (Math as any).imul = function imul(a: number, b: number) { + const ah = (a >>> 16) & 0xffff; + const al = a & 0xffff; + const bh = (b >>> 16) & 0xffff; + const bl = b & 0xffff; + return ((al * bl) + (((ah * bl + al * bh) << 16) >>> 0) | 0); + }; +} diff --git a/packages/web/src/key.ts b/packages/web/src/key.ts new file mode 100644 index 0000000..dfd8068 --- /dev/null +++ b/packages/web/src/key.ts @@ -0,0 +1,15 @@ +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; + +export class CryptoKey extends core.CryptoKey { + public override algorithm: types.KeyAlgorithm; + constructor( + algorithm: types.KeyAlgorithm, + public override extractable: boolean, + public override type: types.KeyType, + public override usages: types.KeyUsage[], + ) { + super(); + this.algorithm = { ...algorithm }; + } +} diff --git a/packages/web/src/lib.ts b/packages/web/src/lib.ts new file mode 100644 index 0000000..2a71b58 --- /dev/null +++ b/packages/web/src/lib.ts @@ -0,0 +1,9 @@ +import { Crypto, nativeCrypto } from "."; +import "./init"; + +if (nativeCrypto) { + Object.freeze(nativeCrypto.getRandomValues); +} + +export const crypto = new Crypto(); +export * from "."; diff --git a/packages/web/src/mechs/aes/aes_cbc.ts b/packages/web/src/mechs/aes/aes_cbc.ts new file mode 100644 index 0000000..9b87788 --- /dev/null +++ b/packages/web/src/mechs/aes/aes_cbc.ts @@ -0,0 +1,33 @@ +import * as core from "@peculiar/webcrypto-core"; +import { AesKeyGenParams, KeyUsage, CryptoKey, AesCbcParams, KeyFormat, JsonWebKey, Algorithm } from "@peculiar/webcrypto-types"; +import { AesCrypto } from "./crypto"; +import { AesCryptoKey } from "./key"; + +export class AesCbcProvider extends core.AesCbcProvider { + + public async onGenerateKey(algorithm: AesKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise { + return AesCrypto.generateKey(algorithm, extractable, keyUsages); + } + + public async onEncrypt(algorithm: AesCbcParams, key: AesCryptoKey, data: ArrayBuffer): Promise { + return AesCrypto.encrypt(algorithm, key, data); + } + + public async onDecrypt(algorithm: AesCbcParams, key: AesCryptoKey, data: ArrayBuffer): Promise { + return AesCrypto.decrypt(algorithm, key, data); + } + + public async onExportKey(format: KeyFormat, key: AesCryptoKey): Promise { + return AesCrypto.exportKey(format, key); + } + + public onImportKey(format: KeyFormat, keyData: JsonWebKey | ArrayBuffer, algorithm: Algorithm, extractable: boolean, keyUsages: KeyUsage[], ...args: any[]): Promise { + return AesCrypto.importKey(format, keyData, algorithm, extractable, keyUsages); + } + + public override checkCryptoKey(key: CryptoKey, keyUsage: KeyUsage): asserts key is AesCryptoKey { + super.checkCryptoKey(key, keyUsage); + AesCrypto.checkCryptoKey(key); + } + +} diff --git a/packages/web/src/mechs/aes/aes_ctr.ts b/packages/web/src/mechs/aes/aes_ctr.ts new file mode 100644 index 0000000..76eb768 --- /dev/null +++ b/packages/web/src/mechs/aes/aes_ctr.ts @@ -0,0 +1,38 @@ +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import * as pvtsutils from "pvtsutils"; +import * as asmCrypto from "asmcrypto.js"; +import { AesCrypto } from "./crypto"; +import { AesCryptoKey } from "./key"; + +export class AesCtrProvider extends core.AesCtrProvider { + + public async onEncrypt(algorithm: types.AesCtrParams, key: AesCryptoKey, data: ArrayBuffer): Promise { + const result = new asmCrypto.AES_CTR(key.raw, pvtsutils.BufferSourceConverter.toUint8Array(algorithm.counter)) + .encrypt(pvtsutils.BufferSourceConverter.toUint8Array(data)); + return pvtsutils.BufferSourceConverter.toArrayBuffer(result); + } + + public async onDecrypt(algorithm: types.AesCtrParams, key: AesCryptoKey, data: ArrayBuffer): Promise { + const result = new asmCrypto.AES_CTR(key.raw, pvtsutils.BufferSourceConverter.toUint8Array(algorithm.counter)) + .decrypt(pvtsutils.BufferSourceConverter.toUint8Array(data)); + return pvtsutils.BufferSourceConverter.toArrayBuffer(result); + } + + public async onGenerateKey(algorithm: types.AesKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + return AesCrypto.generateKey(algorithm, extractable, keyUsages); + } + + public async onExportKey(format: types.KeyFormat, key: AesCryptoKey): Promise { + return AesCrypto.exportKey(format, key); + } + + public async onImportKey(format: types.KeyFormat, keyData: ArrayBuffer | types.JsonWebKey, algorithm: types.Algorithm, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + return AesCrypto.importKey(format, keyData, algorithm, extractable, keyUsages); + } + + public override checkCryptoKey(key: types.CryptoKey, keyUsage: types.KeyUsage): asserts key is AesCryptoKey { + super.checkCryptoKey(key, keyUsage); + AesCrypto.checkCryptoKey(key); + } +} diff --git a/packages/web/src/mechs/aes/aes_ecb.ts b/packages/web/src/mechs/aes/aes_ecb.ts new file mode 100644 index 0000000..f2aabc9 --- /dev/null +++ b/packages/web/src/mechs/aes/aes_ecb.ts @@ -0,0 +1,33 @@ +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import { AesCrypto } from "./crypto"; +import { AesCryptoKey } from "./key"; + +export class AesEcbProvider extends core.AesEcbProvider { + + public async onGenerateKey(algorithm: types.AesKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + return AesCrypto.generateKey(algorithm, extractable, keyUsages); + } + + public async onEncrypt(algorithm: types.Algorithm, key: AesCryptoKey, data: ArrayBuffer): Promise { + return AesCrypto.encrypt(algorithm, key, data); + } + + public async onDecrypt(algorithm: types.Algorithm, key: AesCryptoKey, data: ArrayBuffer): Promise { + return AesCrypto.decrypt(algorithm, key, data); + } + + public async onExportKey(format: types.KeyFormat, key: AesCryptoKey): Promise { + return AesCrypto.exportKey(format, key); + } + + public async onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.Algorithm, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + return AesCrypto.importKey(format, keyData, algorithm, extractable, keyUsages); + } + + public override checkCryptoKey(key: types.CryptoKey, keyUsage: types.KeyUsage) { + super.checkCryptoKey(key, keyUsage); + AesCrypto.checkCryptoKey(key); + } + +} diff --git a/packages/web/src/mechs/aes/aes_gcm.ts b/packages/web/src/mechs/aes/aes_gcm.ts new file mode 100644 index 0000000..8cf786d --- /dev/null +++ b/packages/web/src/mechs/aes/aes_gcm.ts @@ -0,0 +1,33 @@ +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import { AesCrypto } from "./crypto"; +import { AesCryptoKey } from "./key"; + +export class AesGcmProvider extends core.AesGcmProvider { + + public async onGenerateKey(algorithm: types.AesKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + return AesCrypto.generateKey(algorithm, extractable, keyUsages); + } + + public async onEncrypt(algorithm: types.AesGcmParams, key: AesCryptoKey, data: ArrayBuffer): Promise { + return AesCrypto.encrypt(algorithm, key, data); + } + + public async onDecrypt(algorithm: types.AesGcmParams, key: AesCryptoKey, data: ArrayBuffer): Promise { + return AesCrypto.decrypt(algorithm, key, data); + } + + public async onExportKey(format: types.KeyFormat, key: AesCryptoKey): Promise { + return AesCrypto.exportKey(format, key); + } + + public async onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.Algorithm, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + return AesCrypto.importKey(format, keyData, algorithm, extractable, keyUsages); + } + + public override checkCryptoKey(key: types.CryptoKey, keyUsage: types.KeyUsage): asserts key is AesCryptoKey { + super.checkCryptoKey(key, keyUsage); + AesCrypto.checkCryptoKey(key); + } + +} diff --git a/packages/web/src/mechs/aes/aes_kw.ts b/packages/web/src/mechs/aes/aes_kw.ts new file mode 100644 index 0000000..c75592c --- /dev/null +++ b/packages/web/src/mechs/aes/aes_kw.ts @@ -0,0 +1,27 @@ +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import { AesCrypto } from "./crypto"; +import { AesCryptoKey } from "./key"; + +export class AesKwProvider extends core.AesKwProvider { + public override async onEncrypt(algorithm: types.Algorithm, key: types.CryptoKey, data: ArrayBuffer): Promise { + throw new Error("Method not implemented."); + } + public override async onDecrypt(algorithm: types.Algorithm, key: types.CryptoKey, data: ArrayBuffer): Promise { + throw new Error("Method not implemented."); + } + public async onGenerateKey(algorithm: types.AesKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + throw new Error("Method not implemented."); + } + public async onExportKey(format: types.KeyFormat, key: types.CryptoKey): Promise { + throw new Error("Method not implemented."); + } + public async onImportKey(format: types.KeyFormat, keyData: ArrayBuffer | types.JsonWebKey, algorithm: types.Algorithm, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + throw new Error("Method not implemented."); + } + + public override checkCryptoKey(key: types.CryptoKey, keyUsage: types.KeyUsage): asserts key is AesCryptoKey { + super.checkCryptoKey(key, keyUsage); + AesCrypto.checkCryptoKey(key); + } +} diff --git a/packages/web/src/mechs/aes/crypto.ts b/packages/web/src/mechs/aes/crypto.ts new file mode 100644 index 0000000..0fc3c29 --- /dev/null +++ b/packages/web/src/mechs/aes/crypto.ts @@ -0,0 +1,99 @@ +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import * as asmCrypto from "asmcrypto.js"; +import * as pvtsutils from "pvtsutils"; +import { nativeCrypto } from "../../native"; +import { isAlgorithm } from "../../utils"; +import { AesCryptoKey } from "./key"; + +export class AesCrypto { + + public static AesCBC = "AES-CBC"; + public static AesECB = "AES-ECB"; + public static AesGCM = "AES-GCM"; + + public static checkCryptoKey(key: any) { + if (!(key instanceof AesCryptoKey)) { + throw new TypeError("key: Is not AesCryptoKey"); + } + } + + public static async generateKey(algorithm: types.AesKeyGenParams, extractable: boolean, usages: types.KeyUsage[]) { + // gat random bytes for key + const raw = nativeCrypto.getRandomValues(new Uint8Array(algorithm.length / 8)); + + return new AesCryptoKey(algorithm, extractable, usages, raw); + } + + public static async encrypt(algorithm: types.Algorithm, key: AesCryptoKey, data: ArrayBuffer) { + return this.cipher(algorithm, key, pvtsutils.BufferSourceConverter.toUint8Array(data), true); + } + + public static async decrypt(algorithm: types.Algorithm, key: AesCryptoKey, data: ArrayBuffer) { + return this.cipher(algorithm, key, pvtsutils.BufferSourceConverter.toUint8Array(data), false); + } + + public static async exportKey(format: string, key: AesCryptoKey): Promise { + switch (format) { + case "jwk": + return key.toJSON(); + case "raw": + return key.raw.buffer; + default: + throw new core.OperationError("format: Must be 'jwk' or 'raw'"); + } + } + + public static async importKey(format: string, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.Algorithm, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + let raw: ArrayBuffer; + + if (core.isJWK(keyData)) { + if (!keyData.k) { + throw new core.RequiredPropertyError("k", "JWK"); + } + raw = pvtsutils.Convert.FromBase64Url(keyData.k); + } else { + raw = pvtsutils.BufferSourceConverter.toArrayBuffer(keyData); + } + + // check key length + switch (raw.byteLength << 3) { + case 128: + case 192: + case 256: + break; + default: + throw new core.OperationError("keyData: Is wrong key length"); + } + + const key = new AesCryptoKey({ name: algorithm.name, length: raw.byteLength << 3 }, extractable, keyUsages, new Uint8Array(raw)); + return key; + } + + private static async cipher(algorithm: types.Algorithm, key: AesCryptoKey, data: Uint8Array, encrypt: boolean) { + const action = encrypt ? "encrypt" : "decrypt"; + let result: Uint8Array; + if (isAlgorithm(algorithm, AesCrypto.AesCBC)) { + // AES-CBC + const iv = pvtsutils.BufferSourceConverter.toUint8Array(algorithm.iv); + result = asmCrypto.AES_CBC[action](data, key.raw, undefined, iv); + } else if (isAlgorithm(algorithm, AesCrypto.AesGCM)) { + // AES-GCM + const iv = pvtsutils.BufferSourceConverter.toUint8Array(algorithm.iv); + let additionalData; + if (algorithm.additionalData) { + additionalData = pvtsutils.BufferSourceConverter.toUint8Array(algorithm.additionalData); + } + const tagLength = (algorithm.tagLength || 128) / 8; + result = asmCrypto.AES_GCM[action](data, key.raw, iv, additionalData, tagLength); + } else if (isAlgorithm(algorithm, AesCrypto.AesECB)) { + // // AES-ECB + result = asmCrypto.AES_ECB[action](data, key.raw, true); + } else { + throw new core.OperationError(`algorithm: Is not recognized`); + } + + return pvtsutils.BufferSourceConverter.toArrayBuffer(result); + } + +} diff --git a/packages/web/src/mechs/aes/index.ts b/packages/web/src/mechs/aes/index.ts new file mode 100644 index 0000000..af5fdc6 --- /dev/null +++ b/packages/web/src/mechs/aes/index.ts @@ -0,0 +1,6 @@ +export * from "./crypto"; +export * from "./aes_cbc"; +export * from "./aes_ecb"; +export * from "./aes_gcm"; +export * from "./aes_ctr"; +export * from "./aes_kw"; diff --git a/packages/web/src/mechs/aes/key.ts b/packages/web/src/mechs/aes/key.ts new file mode 100644 index 0000000..0290bc4 --- /dev/null +++ b/packages/web/src/mechs/aes/key.ts @@ -0,0 +1,38 @@ +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import * as pvtsutils from "pvtsutils"; +import { CryptoKey } from "../../key"; + +export class AesCryptoKey extends CryptoKey { + declare public algorithm: types.AesKeyAlgorithm; + + constructor(algorithm: types.AesKeyAlgorithm, extractable: boolean, usages: types.KeyUsage[], public raw: Uint8Array) { + super(algorithm, extractable, "secret", usages); + } + + public toJSON() { + const jwk: types.JsonWebKey = { + kty: "oct", + alg: this.getJwkAlgorithm(), + k: pvtsutils.Convert.ToBase64Url(this.raw), + ext: this.extractable, + key_ops: this.usages, + }; + return jwk; + } + + private getJwkAlgorithm() { + switch (this.algorithm.name.toUpperCase()) { + case "AES-CBC": + return `A${this.algorithm.length}CBC`; + case "AES-CTR": + return `A${this.algorithm.length}CTR`; + case "AES-GCM": + return `A${this.algorithm.length}GCM`; + case "AES-ECB": + return `A${this.algorithm.length}ECB`; + default: + throw new core.AlgorithmError("Unsupported algorithm name"); + } + } +} diff --git a/packages/web/src/mechs/des/crypto.ts b/packages/web/src/mechs/des/crypto.ts new file mode 100644 index 0000000..95c10cc --- /dev/null +++ b/packages/web/src/mechs/des/crypto.ts @@ -0,0 +1,107 @@ +/// + +import * as core from "@peculiar/webcrypto-core"; +import { RequiredPropertyError } from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import * as des from "des.js"; +import * as pvtsutils from "pvtsutils"; +import { nativeCrypto } from "../../native"; +import { DesCryptoKey } from "./key"; + +export class DesCrypto { + + public static checkLib() { + if (typeof (des) === "undefined") { + throw new core.OperationError("Cannot implement DES mechanism. Add 'https://peculiarventures.github.io/pv-webcrypto-tests/src/des.js' script to your project"); + } + } + + public static checkCryptoKey(key: any) { + if (!(key instanceof DesCryptoKey)) { + throw new TypeError("key: Is not DesCryptoKey"); + } + } + + public static async generateKey(algorithm: types.DesKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + this.checkLib(); + + // gat random bytes for key + const raw = nativeCrypto.getRandomValues(new Uint8Array(algorithm.length / 8)); + + return new DesCryptoKey(algorithm, extractable, keyUsages, raw); + } + + public static async exportKey(format: types.KeyFormat, key: DesCryptoKey): Promise { + this.checkLib(); + + switch (format) { + case "jwk": + return key.toJSON(); + case "raw": + return key.raw.buffer; + default: + throw new core.OperationError("format: Must be 'jwk' or 'raw'"); + } + } + + public static async importKey(format: string, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.DesImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + this.checkLib(); + + let raw: ArrayBuffer; + + if (core.isJWK(keyData)) { + if (!keyData.k) { + throw new RequiredPropertyError("k", "JWK"); + } + raw = pvtsutils.Convert.FromBase64Url(keyData.k); + } else { + raw = pvtsutils.BufferSourceConverter.toArrayBuffer(keyData); + } + + // check key length + if ((algorithm.name === "DES-CBC" && raw.byteLength !== 8) + || (algorithm.name === "DES-EDE3-CBC" && raw.byteLength !== 24)) { + throw new core.OperationError("keyData: Is wrong key length"); + } + + const key = new DesCryptoKey({ name: algorithm.name, length: raw.byteLength << 3 }, extractable, keyUsages, new Uint8Array(raw)); + return key; + } + + public static async encrypt(algorithm: types.DesParams, key: DesCryptoKey, data: ArrayBuffer): Promise { + return this.cipher(algorithm, key, data, true); + } + + public static async decrypt(algorithm: types.DesParams, key: DesCryptoKey, data: ArrayBuffer): Promise { + return this.cipher(algorithm, key, data, false); + } + + private static async cipher(algorithm: types.DesParams, key: DesCryptoKey, data: ArrayBuffer, encrypt: boolean): Promise { + this.checkLib(); + + const type = encrypt ? "encrypt" : "decrypt"; + let DesCipher: des.Cipher; + const iv = pvtsutils.BufferSourceConverter.toUint8Array(algorithm.iv); + switch (algorithm.name.toUpperCase()) { + case "DES-CBC": + DesCipher = des.CBC.instantiate(des.DES).create({ + key: key.raw, + type, + iv, + }); + break; + case "DES-EDE3-CBC": + DesCipher = des.CBC.instantiate(des.EDE).create({ + key: key.raw, + type, + iv, + }); + break; + default: + throw new core.OperationError("algorithm: Is not recognized"); + } + const enc = DesCipher.update(new Uint8Array(data)).concat(DesCipher.final()); + return new Uint8Array(enc).buffer; + } + +} diff --git a/packages/web/src/mechs/des/des_cbc.ts b/packages/web/src/mechs/des/des_cbc.ts new file mode 100644 index 0000000..1ad19c4 --- /dev/null +++ b/packages/web/src/mechs/des/des_cbc.ts @@ -0,0 +1,37 @@ +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import { DesCrypto } from "./crypto"; +import { DesCryptoKey } from "./key"; + +export class DesCbcProvider extends core.DesProvider { + + public keySizeBits = 64; + public ivSize = 8; + public name = "DES-CBC"; + + public async onGenerateKey(algorithm: types.DesKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + return DesCrypto.generateKey(algorithm, extractable, keyUsages); + } + + public async onExportKey(format: types.KeyFormat, key: DesCryptoKey): Promise { + return DesCrypto.exportKey(format, key); + } + + public async onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.DesImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + return DesCrypto.importKey(format, keyData, algorithm, extractable, keyUsages); + } + + public async onEncrypt(algorithm: types.DesParams, key: DesCryptoKey, data: ArrayBuffer): Promise { + return DesCrypto.encrypt(algorithm, key, data); + } + + public async onDecrypt(algorithm: types.DesParams, key: DesCryptoKey, data: ArrayBuffer): Promise { + return DesCrypto.decrypt(algorithm, key, data); + } + + public override checkCryptoKey(key: types.CryptoKey, keyUsage: types.KeyUsage): asserts key is DesCryptoKey { + super.checkCryptoKey(key, keyUsage); + DesCrypto.checkCryptoKey(key); + } + +} diff --git a/packages/web/src/mechs/des/des_ede3_cbc.ts b/packages/web/src/mechs/des/des_ede3_cbc.ts new file mode 100644 index 0000000..8208f45 --- /dev/null +++ b/packages/web/src/mechs/des/des_ede3_cbc.ts @@ -0,0 +1,39 @@ +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import { DesCrypto } from "./crypto"; +import { DesCryptoKey } from "./key"; + +export type DesEde3CbcParams = types.DesParams; + +export class DesEde3CbcProvider extends core.DesProvider { + + public keySizeBits = 192; + public ivSize = 8; + public name = "DES-EDE3-CBC"; + + public async onGenerateKey(algorithm: types.DesKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + return DesCrypto.generateKey(algorithm, extractable, keyUsages); + } + + public async onExportKey(format: types.KeyFormat, key: DesCryptoKey): Promise { + return DesCrypto.exportKey(format, key); + } + + public async onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.DesImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + return DesCrypto.importKey(format, keyData, algorithm, extractable, keyUsages); + } + + public async onEncrypt(algorithm: types.DesParams, key: DesCryptoKey, data: ArrayBuffer): Promise { + return DesCrypto.encrypt(algorithm, key, data); + } + + public async onDecrypt(algorithm: types.DesParams, key: DesCryptoKey, data: ArrayBuffer): Promise { + return DesCrypto.decrypt(algorithm, key, data); + } + + public override checkCryptoKey(key: DesCryptoKey, keyUsage: types.KeyUsage): asserts key is DesCryptoKey { + super.checkCryptoKey(key, keyUsage); + DesCrypto.checkCryptoKey(key); + } + +} diff --git a/packages/web/src/mechs/des/index.ts b/packages/web/src/mechs/des/index.ts new file mode 100644 index 0000000..a11db65 --- /dev/null +++ b/packages/web/src/mechs/des/index.ts @@ -0,0 +1,3 @@ +export * from "./crypto"; +export * from "./des_cbc"; +export * from "./des_ede3_cbc"; diff --git a/packages/web/src/mechs/des/key.ts b/packages/web/src/mechs/des/key.ts new file mode 100644 index 0000000..17ffdb3 --- /dev/null +++ b/packages/web/src/mechs/des/key.ts @@ -0,0 +1,35 @@ +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import * as pvtsutils from "pvtsutils"; +import { CryptoKey } from "../../key"; + +export class DesCryptoKey extends CryptoKey { + declare public algorithm: types.DesKeyAlgorithm; + + constructor(algorithm: types.DesKeyAlgorithm, extractable: boolean, usages: types.KeyUsage[], public raw: Uint8Array) { + super(algorithm, extractable, "secret", usages); + } + + public toJSON() { + const jwk: types.JsonWebKey = { + kty: "oct", + alg: this.getJwkAlgorithm(), + k: pvtsutils.Convert.ToBase64Url(this.raw), + ext: this.extractable, + key_ops: this.usages, + }; + return jwk; + } + + private getJwkAlgorithm() { + switch (this.algorithm.name.toUpperCase()) { + case "DES-CBC": + return `DES-CBC`; + case "DES-EDE3-CBC": + return `3DES-CBC`; + default: + throw new core.AlgorithmError("Unsupported algorithm name"); + } + } + +} diff --git a/packages/web/src/mechs/ec/crypto.ts b/packages/web/src/mechs/ec/crypto.ts new file mode 100644 index 0000000..31a74be --- /dev/null +++ b/packages/web/src/mechs/ec/crypto.ts @@ -0,0 +1,230 @@ +import { AsnConvert } from "@peculiar/asn1-schema"; +import { JsonParser, JsonSerializer } from "@peculiar/json-schema"; +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import * as elliptic from "elliptic"; +import { concat } from "../../helper"; +import { getOidByNamedCurve } from "./helper"; +import { EcCryptoKey } from "./key"; + +export class EcCrypto { + + public static privateUsages: types.KeyUsage[] = ["sign", "deriveKey", "deriveBits"]; + public static publicUsages: types.KeyUsage[] = ["verify"]; + + public static readonly ASN_ALGORITHM = "1.2.840.10045.2.1"; + + public static checkLib() { + if (typeof (elliptic) === "undefined") { + throw new core.OperationError("Cannot implement EC mechanism. Add 'https://peculiarventures.github.io/pv-webcrypto-tests/src/elliptic.js' script to your project"); + } + } + + public static async generateKey(algorithm: types.EcKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + this.checkLib(); + + const key = this.initEcKey(algorithm.namedCurve); + const ecKey = key.genKeyPair(); + ecKey.getPublic(); // Fills internal `pub` field + // set key params + const prvKey = new EcCryptoKey( + { ...algorithm }, + extractable, + "private", + keyUsages.filter((usage) => ~this.privateUsages.indexOf(usage)), + ecKey, + ); + const pubKey = new EcCryptoKey( + { ...algorithm }, + true, + "public", + keyUsages.filter((usage) => ~this.publicUsages.indexOf(usage)), + ecKey, + ); + + return { + privateKey: prvKey, + publicKey: pubKey, + }; + } + + public static checkCryptoKey(key: unknown) { + if (!(key instanceof EcCryptoKey)) { + throw new TypeError("key: Is not EcCryptoKey"); + } + } + + public static concat(...buf: Uint8Array[]) { + const res = new Uint8Array(buf.map((item) => item.length).reduce((prev, cur) => prev + cur)); + let offset = 0; + buf.forEach((item, index) => { + for (let i = 0; i < item.length; i++) { + res[offset + i] = item[i]; + } + offset += item.length; + }); + return res; + } + + public static async exportKey(format: types.KeyFormat, key: EcCryptoKey): Promise { + this.checkLib(); + + switch (format) { + case "pkcs8": + return this.exportPkcs8Key(key); + case "spki": + return this.exportSpkiKey(key); + case "jwk": + return this.exportJwkKey(key); + case "raw": + return new Uint8Array(key.data.getPublic("der")).buffer; + default: + throw new core.OperationError("format: Must be 'jwk', 'raw, 'pkcs8' or 'spki'"); + } + } + + public static async importKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.EcKeyImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + this.checkLib(); + + let ecKey: EllipticJS.EllipticKeyPair; + switch (format) { + case "pkcs8": + ecKey = this.importPkcs8Key(keyData as ArrayBuffer, algorithm.namedCurve); + break; + case "spki": + ecKey = this.importSpkiKey(keyData as ArrayBuffer, algorithm.namedCurve); + break; + case "raw": + ecKey = this.importEcKey(new core.asn1.EcPublicKey(keyData as ArrayBuffer), algorithm.namedCurve); + break; + case "jwk": + ecKey = this.importJwkKey(keyData as types.JsonWebKey); + break; + default: + throw new core.OperationError("format: Must be 'jwk', 'raw', 'pkcs8' or 'spki'"); + } + const key = new EcCryptoKey( + { + ...algorithm, + } as types.EcKeyAlgorithm, + extractable, + ecKey.priv ? "private" : "public", + keyUsages, + ecKey, + ); + return key; + } + + protected static getNamedCurve(wcNamedCurve: string) { + const crv = wcNamedCurve.toUpperCase(); + let res = ""; + if (["P-256", "P-384", "P-521"].indexOf(crv) > -1) { + res = crv.replace("-", "").toLowerCase(); + } else if (crv === "K-256") { + res = "secp256k1"; + } else if (["brainpoolP256r1", "brainpoolP384r1", "brainpoolP512r1"].includes(wcNamedCurve)) { + res = wcNamedCurve; + } else { + throw new core.OperationError(`Unsupported named curve '${wcNamedCurve}'`); + } + return res; + } + + private static initEcKey(namedCurve: string) { + return elliptic.ec(this.getNamedCurve(namedCurve)); + } + + private static exportPkcs8Key(key: EcCryptoKey) { + const keyInfo = new core.asn1.PrivateKeyInfo(); + keyInfo.privateKeyAlgorithm.algorithm = this.ASN_ALGORITHM; + keyInfo.privateKeyAlgorithm.parameters = AsnConvert.serialize( + new core.asn1.ObjectIdentifier(getOidByNamedCurve(key.algorithm.namedCurve)), + ); + keyInfo.privateKey = AsnConvert.serialize(this.exportEcKey(key)); + + return AsnConvert.serialize(keyInfo); + } + + private static importPkcs8Key(data: ArrayBuffer, namedCurve: string) { + const keyInfo = AsnConvert.parse(data, core.asn1.PrivateKeyInfo); + const privateKey = AsnConvert.parse(keyInfo.privateKey, core.asn1.EcPrivateKey); + return this.importEcKey(privateKey, namedCurve); + } + + private static importSpkiKey(data: ArrayBuffer, namedCurve: string) { + const keyInfo = AsnConvert.parse(data, core.asn1.PublicKeyInfo); + const publicKey = new core.asn1.EcPublicKey(keyInfo.publicKey); + return this.importEcKey(publicKey, namedCurve); + } + + private static exportSpkiKey(key: EcCryptoKey) { + const publicKey = new core.asn1.EcPublicKey(new Uint8Array(key.data.getPublic("der")).buffer); + + const keyInfo = new core.asn1.PublicKeyInfo(); + keyInfo.publicKeyAlgorithm.algorithm = this.ASN_ALGORITHM; + keyInfo.publicKeyAlgorithm.parameters = AsnConvert.serialize( + new core.asn1.ObjectIdentifier(getOidByNamedCurve(key.algorithm.namedCurve)), + ); + keyInfo.publicKey = publicKey.value; + return AsnConvert.serialize(keyInfo); + } + + private static importJwkKey(data: types.JsonWebKey) { + let key: core.asn1.EcPrivateKey | core.asn1.EcPublicKey; + if (data.d) { + // private + key = JsonParser.fromJSON(data, { targetSchema: core.asn1.EcPrivateKey }); + } else { + // public + key = JsonParser.fromJSON(data, { targetSchema: core.asn1.EcPublicKey }); + } + if (!data.crv) { + throw new Error(); + } + return this.importEcKey(key, data.crv); + } + + private static exportJwkKey(key: EcCryptoKey) { + const asnKey = this.exportEcKey(key); + const jwk = JsonSerializer.toJSON(asnKey) as types.JsonWebKey; + + jwk.ext = true; + jwk.key_ops = key.usages; + jwk.crv = key.algorithm.namedCurve; + jwk.kty = "EC"; + + return jwk; + } + + private static exportEcKey(ecKey: EcCryptoKey): core.asn1.EcPrivateKey | core.asn1.EcPublicKey { + if (ecKey.type === "private") { + // private + const privateKey = new core.asn1.EcPrivateKey(); + const point = new Uint8Array(ecKey.data.getPrivate("der").toArray()); + const pointPad = new Uint8Array(this.getPointSize(ecKey.algorithm.namedCurve) - point.length); + + privateKey.privateKey = concat(pointPad, point); + privateKey.publicKey = new Uint8Array(ecKey.data.getPublic("der")); + return privateKey; + } else if (ecKey.data.pub) { + // public + return new core.asn1.EcPublicKey(new Uint8Array(ecKey.data.getPublic("der")).buffer); + } else { + throw new Error("Cannot get private or public key"); + } + } + + private static importEcKey(key: core.asn1.EcPrivateKey | core.asn1.EcPublicKey, namedCurve: string) { + const ecKey = this.initEcKey(namedCurve); + + if (key instanceof core.asn1.EcPublicKey) { + return ecKey.keyFromPublic(new Uint8Array(key.value)); + } + return ecKey.keyFromPrivate(new Uint8Array(key.privateKey)); + } + + private static getPointSize(namedCurve: string) { + return core.EcCurves.get(namedCurve).size + 7 >> 3; + } + +} diff --git a/packages/web/src/mechs/ec/ec_dh.ts b/packages/web/src/mechs/ec/ec_dh.ts new file mode 100644 index 0000000..60b01aa --- /dev/null +++ b/packages/web/src/mechs/ec/ec_dh.ts @@ -0,0 +1,43 @@ +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import { EcCrypto } from "./crypto"; +import { EcCryptoKey } from "./key"; + +export class EcdhProvider extends core.EcdhProvider { + + public override namedCurves = ["P-256", "P-384", "P-521", "K-256", "brainpoolP256r1", "brainpoolP384r1", "brainpoolP512r1"]; + + public async onGenerateKey(algorithm: types.EcKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + return EcCrypto.generateKey(algorithm, extractable, keyUsages); + } + + public async onExportKey(format: types.KeyFormat, key: EcCryptoKey): Promise { + return EcCrypto.exportKey(format, key); + } + + public async onImportKey(format: types.KeyFormat, keyData: ArrayBuffer | types.JsonWebKey, algorithm: types.EcKeyImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + return EcCrypto.importKey(format, keyData, algorithm, extractable, keyUsages); + } + + public async onDeriveBits(algorithm: types.EcdhKeyDeriveParams, baseKey: EcCryptoKey, length: number): Promise { + EcCrypto.checkLib(); + + const shared = baseKey.data.derive((algorithm.public as EcCryptoKey).data.getPublic()); + let array = new Uint8Array(shared.toArray()); + + // Padding + let len = array.length; + len = (len > 32 ? (len > 48 ? 66 : 48) : 32); + if (array.length < len) { + array = EcCrypto.concat(new Uint8Array(len - array.length), array); + } + const buf = array.slice(0, length / 8).buffer; + return buf; + } + + public override checkCryptoKey(key: EcCryptoKey, keyUsage: types.KeyUsage): asserts key is EcCryptoKey { + super.checkCryptoKey(key, keyUsage); + EcCrypto.checkCryptoKey(key); + } + +} diff --git a/packages/web/src/mechs/ec/ec_dsa.ts b/packages/web/src/mechs/ec/ec_dsa.ts new file mode 100644 index 0000000..d7d7cd9 --- /dev/null +++ b/packages/web/src/mechs/ec/ec_dsa.ts @@ -0,0 +1,76 @@ +import * as core from "@peculiar/webcrypto-core"; +import { EcKeyGenParams, KeyUsage, CryptoKeyPair, KeyFormat, JsonWebKey, EcKeyImportParams, CryptoKey, EcdsaParams } from "@peculiar/webcrypto-types"; +import { Crypto } from "../../crypto"; +import { EcCrypto } from "./crypto"; +import { EcCryptoKey } from "./key"; + +/** + * Converts buffer to number array + * @param buffer ArrayBuffer or ArrayBufferView + */ +export function b2a(buffer: ArrayBuffer | ArrayBufferView) { + const buf = new Uint8Array(buffer as ArrayBuffer); + const res: number[] = []; + for (let i = 0; i < buf.length; i++) { + res.push(buf[i]); + } + return res; +} + +export class EcdsaProvider extends core.EcdsaProvider { + + public override hashAlgorithms = ["SHA-1", "SHA-256", "SHA-384", "SHA-512", "SHA3-256", "SHA3-384", "SHA3-512"]; + public override namedCurves = ["P-256", "P-384", "P-521", "K-256", "brainpoolP256r1", "brainpoolP384r1", "brainpoolP512r1"]; + + public async onGenerateKey(algorithm: EcKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise { + return EcCrypto.generateKey(algorithm, extractable, keyUsages); + } + + public async onExportKey(format: KeyFormat, key: EcCryptoKey): Promise { + return EcCrypto.exportKey(format, key); + } + + public async onImportKey(format: KeyFormat, keyData: ArrayBuffer | JsonWebKey, algorithm: EcKeyImportParams, extractable: boolean, keyUsages: KeyUsage[]): Promise { + return EcCrypto.importKey(format, keyData, algorithm, extractable, keyUsages); + } + + public async onSign(algorithm: EcdsaParams, key: EcCryptoKey, data: ArrayBuffer): Promise { + EcCrypto.checkLib(); + + // get digests + const crypto = new Crypto(); + let array; + + const hash = await crypto.subtle.digest(algorithm.hash, data); + array = b2a(hash); + const signature = await key.data.sign(array); + const asnSignature = new core.asn1.EcDsaSignature(); + asnSignature.r = new Uint8Array(signature.r.toArray()).buffer; + asnSignature.s = new Uint8Array(signature.s.toArray()).buffer; + + return asnSignature.toWebCryptoSignature(); + } + + public async onVerify(algorithm: EcdsaParams, key: EcCryptoKey, signature: ArrayBuffer, data: ArrayBuffer): Promise { + EcCrypto.checkLib(); + + const crypto = new Crypto(); + + const sig = { + r: new Uint8Array(signature.slice(0, signature.byteLength / 2)), + s: new Uint8Array(signature.slice(signature.byteLength / 2)), + }; + + // get digest + const hashedData = await crypto.subtle.digest(algorithm.hash, data); + const array = b2a(hashedData); + + return key.data.verify(array, sig); + } + + public override checkCryptoKey(key: CryptoKey, keyUsage: KeyUsage): asserts key is EcCryptoKey { + super.checkCryptoKey(key, keyUsage); + EcCrypto.checkCryptoKey(key); + } + +} diff --git a/packages/web/src/mechs/ec/helper.ts b/packages/web/src/mechs/ec/helper.ts new file mode 100644 index 0000000..3ee1773 --- /dev/null +++ b/packages/web/src/mechs/ec/helper.ts @@ -0,0 +1,42 @@ +import * as core from "@peculiar/webcrypto-core"; + +// TODO Use EcCurve from core +const namedOIDs: { [key: string]: string; } = { + // P-256 + "1.2.840.10045.3.1.7": "P-256", + "P-256": "1.2.840.10045.3.1.7", + // P-384 + "1.3.132.0.34": "P-384", + "P-384": "1.3.132.0.34", + // P-521 + "1.3.132.0.35": "P-521", + "P-521": "1.3.132.0.35", + // K-256 + "1.3.132.0.10": "K-256", + "K-256": "1.3.132.0.10", + // brainpoolP256r1 + "1.3.36.3.3.2.8.1.1.7": "brainpoolP256r1", + "brainpoolP256r1": "1.3.36.3.3.2.8.1.1.7", + // brainpoolP384r1 + "1.3.36.3.3.2.8.1.1.11": "brainpoolP384r1", + "brainpoolP384r1": "1.3.36.3.3.2.8.1.1.11", + // brainpoolP512r1 + "1.3.36.3.3.2.8.1.1.13": "brainpoolP512r1", + "brainpoolP512r1": "1.3.36.3.3.2.8.1.1.13", +}; + +export function getNamedCurveByOid(oid: string) { + const namedCurve = namedOIDs[oid]; + if (!namedCurve) { + throw new core.OperationError(`Cannot convert OID(${oid}) to WebCrypto named curve`); + } + return namedCurve; +} + +export function getOidByNamedCurve(namedCurve: string) { + const oid = namedOIDs[namedCurve]; + if (!oid) { + throw new core.OperationError(`Cannot convert WebCrypto named curve '${namedCurve}' to OID`); + } + return oid; +} diff --git a/packages/web/src/mechs/ec/index.ts b/packages/web/src/mechs/ec/index.ts new file mode 100644 index 0000000..071aed3 --- /dev/null +++ b/packages/web/src/mechs/ec/index.ts @@ -0,0 +1,3 @@ +export * from "./ec_dh"; +export * from "./ec_dsa"; +export * from "./crypto"; diff --git a/packages/web/src/mechs/ec/key.ts b/packages/web/src/mechs/ec/key.ts new file mode 100644 index 0000000..d6d9fbc --- /dev/null +++ b/packages/web/src/mechs/ec/key.ts @@ -0,0 +1,13 @@ +/// + +import { EcKeyAlgorithm, KeyType, KeyUsage } from "@peculiar/webcrypto-types"; +import { CryptoKey } from "../../key"; + +export class EcCryptoKey extends CryptoKey { + + declare public algorithm: EcKeyAlgorithm; + + constructor(algorithm: EcKeyAlgorithm, extractable: boolean, type: KeyType, usages: KeyUsage[], public data: EllipticJS.EllipticKeyPair) { + super(algorithm, extractable, type, usages); + } +} diff --git a/packages/web/src/mechs/ed/crypto.ts b/packages/web/src/mechs/ed/crypto.ts new file mode 100644 index 0000000..63fdfb8 --- /dev/null +++ b/packages/web/src/mechs/ed/crypto.ts @@ -0,0 +1,214 @@ +import { AsnConvert, OctetString } from "@peculiar/asn1-schema"; +import { JsonParser, JsonSerializer } from "@peculiar/json-schema"; +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import * as elliptic from "elliptic"; +import { Convert } from "pvtsutils"; +import { CryptoKey } from "../../key"; +import { nativeCrypto } from "../../native"; +import { b2a } from "../ec"; +import { getOidByNamedCurve } from "./helper"; +import { EdPrivateKey } from "./private_key"; +import { EdPublicKey } from "./public_key"; + +export class EdCrypto { + + public static publicKeyUsages = ["verify"]; + public static privateKeyUsages = ["sign", "deriveKey", "deriveBits"]; + + public static checkLib() { + if (typeof (elliptic) === "undefined") { + throw new core.OperationError("Cannot implement EC mechanism. Add 'https://peculiarventures.github.io/pv-webcrypto-tests/src/elliptic.js' script to your project"); + } + } + + public static concat(...buf: Uint8Array[]) { + const res = new Uint8Array(buf.map((item) => item.length).reduce((prev, cur) => prev + cur)); + let offset = 0; + buf.forEach((item, index) => { + for (let i = 0; i < item.length; i++) { + res[offset + i] = item[i]; + } + offset += item.length; + }); + return res; + } + + public static async generateKey(algorithm: types.EcKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + this.checkLib(); + + // const curve = algorithm.namedCurve.toLowerCase() === "x25519" ? "curve25519" : "ed25519"; // "x25519" | "ed25519" + const curve = "ed25519"; + let edKey: EllipticJS.EllipticKeyPair; + if (curve === "ed25519") { + const raw = nativeCrypto.getRandomValues(new Uint8Array(32)); + const eddsa = new elliptic.eddsa(curve); + edKey = eddsa.keyFromSecret(raw); + } else { + edKey = elliptic.ec(curve).genKeyPair(); + edKey.getPublic(); // Fills internal `pub` field + } + + // set key params + const prvKey = new EdPrivateKey( + algorithm, + extractable, + keyUsages.filter((usage) => this.privateKeyUsages.indexOf(usage) !== -1), + edKey, + ); + const pubKey = new EdPublicKey( + algorithm, + true, + keyUsages.filter((usage) => this.publicKeyUsages.indexOf(usage) !== -1), + edKey, + ); + + return { + privateKey: prvKey, + publicKey: pubKey, + }; + } + + public static async sign(algorithm: types.Algorithm, key: EdPrivateKey, data: Uint8Array): Promise { + this.checkLib(); + + const array = b2a(data); + const signature = key.data.sign(array).toHex(); + + return Convert.FromHex(signature); + } + + public static async verify(algorithm: types.EcdsaParams, key: EdPublicKey, signature: Uint8Array, data: Uint8Array): Promise { + this.checkLib(); + + const array = b2a(data); + const ok = key.data.verify(array, Convert.ToHex(signature)); + return ok; + } + + public static async deriveBits(algorithm: types.EcdhKeyDeriveParams, baseKey: EdPrivateKey, length: number): Promise { + this.checkLib(); + + const key = new Uint8Array(Convert.FromHex(baseKey.data.getSecret("hex"))); + // key[0] &= 248; + // key[31] &= 127; + // key[31] |= 64; + // key.reverse(); + + // @ts-ignore + const ecdh = new elliptic.ec("curve25519"); + const privateKey = ecdh.keyFromPrivate(Convert.ToHex(key), "hex"); + + const publicHex = (algorithm.public as EdPublicKey).data.getPublic("hex") as string; + const publicView = new Uint8Array(Convert.FromHex(publicHex)); + // publicView.reverse(); + // const publicKey = ecdh.keyFromPublic(Convert.ToHex(publicView), "hex").getPublic(); + const publicKey = (algorithm.public as EdPublicKey).data.getPublic(); + const shared = privateKey.derive(publicKey); + let array = new Uint8Array(shared.toArray()); + + // Padding + let len = array.length; + len = (len > 32 ? (len > 48 ? 66 : 48) : 32); + if (array.length < len) { + array = EdCrypto.concat(new Uint8Array(len - array.length), array); + } + const buf = array.slice(0, length / 8).buffer; + return buf; + } + + public static async exportKey(format: types.KeyFormat, key: EdPrivateKey | EdPublicKey): Promise { + this.checkLib(); + + switch (format.toLowerCase()) { + case "jwk": + return JsonSerializer.toJSON(key); + case "pkcs8": { + // const raw = Convert.FromHex(/^x/i.test(key.algorithm.namedCurve) + // ? key.data.getPrivate("hex") + // : key.data.getSecret("hex")); + const raw = Convert.FromHex(key.data.getSecret("hex")); + const keyInfo = new core.asn1.PrivateKeyInfo(); + keyInfo.privateKeyAlgorithm.algorithm = getOidByNamedCurve(key.algorithm.namedCurve); + keyInfo.privateKey = AsnConvert.serialize(new OctetString(raw)); + + return AsnConvert.serialize(keyInfo); + } + case "spki": { + const raw = Convert.FromHex(key.data.getPublic("hex")); + const keyInfo = new core.asn1.PublicKeyInfo(); + keyInfo.publicKeyAlgorithm.algorithm = getOidByNamedCurve(key.algorithm.namedCurve); + keyInfo.publicKey = raw; + + return AsnConvert.serialize(keyInfo); + } + case "raw": { + return Convert.FromHex(key.data.getPublic("hex")); + } + default: + throw new core.OperationError("format: Must be 'jwk', 'raw', pkcs8' or 'spki'"); + } + } + + public static async importKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.EcKeyImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + this.checkLib(); + + switch (format.toLowerCase()) { + case "jwk": { + const jwk = keyData as types.JsonWebKey; + if (jwk.d) { + const asnKey = JsonParser.fromJSON(keyData, { targetSchema: core.asn1.CurvePrivateKey }); + return this.importPrivateKey(asnKey, algorithm, extractable, keyUsages); + } else { + if (!jwk.x) { + throw new TypeError("keyData: Cannot get required 'x' field"); + } + return this.importPublicKey(Convert.FromBase64Url(jwk.x), algorithm, extractable, keyUsages); + } + } + case "raw": { + return this.importPublicKey(keyData as ArrayBuffer, algorithm, extractable, keyUsages); + } + case "spki": { + const keyInfo = AsnConvert.parse(new Uint8Array(keyData as ArrayBuffer), core.asn1.PublicKeyInfo); + return this.importPublicKey(keyInfo.publicKey, algorithm, extractable, keyUsages); + } + case "pkcs8": { + const keyInfo = AsnConvert.parse(new Uint8Array(keyData as ArrayBuffer), core.asn1.PrivateKeyInfo); + const asnKey = AsnConvert.parse(keyInfo.privateKey, core.asn1.CurvePrivateKey); + return this.importPrivateKey(asnKey, algorithm, extractable, keyUsages); + } + default: + throw new core.OperationError("format: Must be 'jwk', 'raw', 'pkcs8' or 'spki'"); + } + } + + protected static importPrivateKey(asnKey: core.asn1.CurvePrivateKey, algorithm: types.EcKeyImportParams, extractable: boolean, keyUsages: types.KeyUsage[]) { + const key = new EdPrivateKey( + Object.assign({}, algorithm), + extractable, + keyUsages, null as any); // key.data inits in key.fromJSON + + key.fromJSON({ + crv: algorithm.namedCurve, + d: Convert.ToBase64Url(asnKey.d), + }); + + return key; + } + + protected static async importPublicKey(asnKey: ArrayBuffer, algorithm: types.EcKeyImportParams, extractable: boolean, keyUsages: types.KeyUsage[]) { + const key = new EdPublicKey( + Object.assign({}, algorithm), + extractable, + keyUsages, null as any); // key.data inits in key.fromJSON + + key.fromJSON({ + crv: algorithm.namedCurve, + x: Convert.ToBase64Url(asnKey), + }); + + return key; + } + +} diff --git a/packages/web/src/mechs/ed/ecdh_es.ts b/packages/web/src/mechs/ed/ecdh_es.ts new file mode 100644 index 0000000..343f3b7 --- /dev/null +++ b/packages/web/src/mechs/ed/ecdh_es.ts @@ -0,0 +1,39 @@ +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import { CryptoKey } from "../../key"; +import { EdCrypto } from "./crypto"; +import { EdPrivateKey } from "./private_key"; +import { EdPublicKey } from "./public_key"; + +export class EcdhEsProvider extends core.EcdhEsProvider { + + public override namedCurves: string[] = ["X25519"]; + + public async onGenerateKey(algorithm: types.EcKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + const keys = await EdCrypto.generateKey( + { + name: this.name, + namedCurve: algorithm.namedCurve.replace(/^x/i, "X"), + }, + extractable, + keyUsages); + + return keys; + } + + public async onDeriveBits(algorithm: types.EcdhKeyDeriveParams, baseKey: EdPrivateKey, length: number): Promise { + const bits = await EdCrypto.deriveBits({ ...algorithm, public: algorithm.public as EdPublicKey }, baseKey, length); + return bits; + } + + public async onExportKey(format: types.KeyFormat, key: EdPrivateKey | EdPublicKey): Promise { + return EdCrypto.exportKey(format, key); + } + + public async onImportKey(format: types.KeyFormat, keyData: ArrayBuffer | types.JsonWebKey, algorithm: types.EcKeyImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + const key = await EdCrypto.importKey(format, keyData, { ...algorithm, name: this.name }, extractable, keyUsages); + + return key; + } + +} \ No newline at end of file diff --git a/packages/web/src/mechs/ed/eddsa.ts b/packages/web/src/mechs/ed/eddsa.ts new file mode 100644 index 0000000..05569f2 --- /dev/null +++ b/packages/web/src/mechs/ed/eddsa.ts @@ -0,0 +1,40 @@ +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import { EdCrypto } from "./crypto"; +import { EdPrivateKey } from "./private_key"; +import { EdPublicKey } from "./public_key"; + +export class EdDsaProvider extends core.EdDsaProvider { + + public override namedCurves: string[] = ["Ed25519"]; + + public async onGenerateKey(algorithm: types.EcKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + const keys = await EdCrypto.generateKey( + { + name: this.name, + namedCurve: algorithm.namedCurve.replace(/^ed/i, "Ed"), + }, + extractable, + keyUsages); + + return keys; + } + + public async onSign(algorithm: types.EcdsaParams, key: EdPrivateKey, data: ArrayBuffer): Promise { + return EdCrypto.sign(algorithm, key, new Uint8Array(data)); + } + + public async onVerify(algorithm: types.EcdsaParams, key: EdPublicKey, signature: ArrayBuffer, data: ArrayBuffer): Promise { + return EdCrypto.verify(algorithm, key, new Uint8Array(signature), new Uint8Array(data)); + } + + public async onExportKey(format: types.KeyFormat, key: EdPrivateKey | EdPublicKey): Promise { + return EdCrypto.exportKey(format, key); + } + + public async onImportKey(format: types.KeyFormat, keyData: ArrayBuffer | types.JsonWebKey, algorithm: types.EcKeyImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + const key = await EdCrypto.importKey(format, keyData, { ...algorithm, name: this.name }, extractable, keyUsages); + return key; + } + +} \ No newline at end of file diff --git a/packages/web/src/mechs/ed/helper.ts b/packages/web/src/mechs/ed/helper.ts new file mode 100644 index 0000000..f19e31c --- /dev/null +++ b/packages/web/src/mechs/ed/helper.ts @@ -0,0 +1,32 @@ +import * as core from "@peculiar/webcrypto-core"; + +const edOIDs: { [key: string]: string; } = { + // Ed448 + [core.asn1.idEd448]: "Ed448", + "ed448": core.asn1.idEd448, + // X448 + [core.asn1.idX448]: "X448", + "x448": core.asn1.idX448, + // Ed25519 + [core.asn1.idEd25519]: "Ed25519", + "ed25519": core.asn1.idEd25519, + // X25519 + [core.asn1.idX25519]: "X25519", + "x25519": core.asn1.idX25519, +}; + +export function getNamedCurveByOid(oid: string) { + const namedCurve = edOIDs[oid]; + if (!namedCurve) { + throw new core.OperationError(`Cannot convert OID(${oid}) to WebCrypto named curve`); + } + return namedCurve; +} + +export function getOidByNamedCurve(namedCurve: string) { + const oid = edOIDs[namedCurve.toLowerCase()]; + if (!oid) { + throw new core.OperationError(`Cannot convert WebCrypto named curve '${namedCurve}' to OID`); + } + return oid; +} diff --git a/packages/web/src/mechs/ed/index.ts b/packages/web/src/mechs/ed/index.ts new file mode 100644 index 0000000..efa765c --- /dev/null +++ b/packages/web/src/mechs/ed/index.ts @@ -0,0 +1,4 @@ +export * from "./eddsa"; +export * from "./ecdh_es"; +export * from "./private_key"; +export * from "./public_key"; diff --git a/packages/web/src/mechs/ed/private_key.ts b/packages/web/src/mechs/ed/private_key.ts new file mode 100644 index 0000000..970e0a7 --- /dev/null +++ b/packages/web/src/mechs/ed/private_key.ts @@ -0,0 +1,50 @@ +import { IJsonConvertible } from "@peculiar/json-schema"; +import * as core from "@peculiar/webcrypto-core"; +import { CryptoKey } from "../../key"; +import * as elliptic from "elliptic"; +import { Convert } from "pvtsutils"; +import { EcKeyAlgorithm, KeyUsage, JsonWebKey } from "@peculiar/webcrypto-types"; + +export class EdPrivateKey extends CryptoKey implements IJsonConvertible { + + declare public algorithm: EcKeyAlgorithm; + + public constructor(algorithm: EcKeyAlgorithm, extractable: boolean, usages: KeyUsage[], public data: EllipticJS.EllipticKeyPair) { + super(algorithm, extractable, "private", usages); + } + + public toJSON(): JsonWebKey { + const json = { + kty: "OKP", + crv: this.algorithm.namedCurve, + key_ops: this.usages, + ext: this.extractable, + }; + + return Object.assign(json, { + d: Convert.ToBase64Url(Convert.FromHex(/^ed/i.test(json.crv) ? this.data.getSecret("hex") : this.data.getPrivate("hex"))), + }); + } + + public fromJSON(json: JsonWebKey) { + if (!json.d) { + throw new core.OperationError(`Cannot get private data from JWK. Property 'd' is required`); + } + if (!json.crv) { + throw new core.OperationError(`Cannot get named curve from JWK. Property 'crv' is required`); + } + + const hexPrivateKey = Convert.ToHex(Convert.FromBase64Url(json.d)); + if (true || /^ed/i.test(json.crv!)) { + // const eddsa = new elliptic.eddsa(json.crv.toLowerCase()); + const eddsa = new elliptic.eddsa("ed25519"); + this.data = eddsa.keyFromSecret(hexPrivateKey); + } else { + const ecdhEs = elliptic.ec(json.crv!.replace(/^x/i, "curve")); + this.data = ecdhEs.keyFromPrivate(hexPrivateKey, "hex"); + } + + return this; + } + +} diff --git a/packages/web/src/mechs/ed/public_key.ts b/packages/web/src/mechs/ed/public_key.ts new file mode 100644 index 0000000..4309230 --- /dev/null +++ b/packages/web/src/mechs/ed/public_key.ts @@ -0,0 +1,49 @@ +import * as jsonSchema from "@peculiar/json-schema"; +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import * as elliptic from "elliptic"; +import * as pvtsutils from "pvtsutils"; +import { CryptoKey } from "../../key"; + +export class EdPublicKey extends CryptoKey implements jsonSchema.IJsonConvertible { + + declare public algorithm: types.EcKeyAlgorithm; + + public constructor(algorithm: types.EcKeyAlgorithm, extractable: boolean, usages: types.KeyUsage[], public data: EllipticJS.EllipticKeyPair) { + super(algorithm, extractable, "public", usages); + } + + public toJSON() { + const json: types.JsonWebKey = { + kty: "OKP", + crv: this.algorithm.namedCurve, + key_ops: this.usages, + ext: this.extractable, + }; + + return Object.assign(json, { + x: pvtsutils.Convert.ToBase64Url(pvtsutils.Convert.FromHex(this.data.getPublic("hex"))), + }); + } + + public fromJSON(json: types.JsonWebKey) { + if (!json.crv) { + // TODO use core.RequiredPropertyError + throw new core.OperationError(`Cannot get named curve from JWK. Property 'crv' is required`); + } + if (!json.x) { + throw new core.OperationError(`Cannot get property from JWK. Property 'x' is required`); + } + + const hexPublicKey = pvtsutils.Convert.ToHex(pvtsutils.Convert.FromBase64Url(json.x)); + if (/^ed/i.test(json.crv)) { + const eddsa = new elliptic.eddsa(json.crv.toLowerCase()); + this.data = eddsa.keyFromPublic(hexPublicKey, "hex"); + } else { + const ecdhEs = elliptic.ec(json.crv.replace(/^x/i, "curve")); + this.data = ecdhEs.keyFromPublic(hexPublicKey, "hex"); + } + + return this; + } +} diff --git a/packages/web/src/mechs/hmac/hmac.ts b/packages/web/src/mechs/hmac/hmac.ts new file mode 100644 index 0000000..410de8e --- /dev/null +++ b/packages/web/src/mechs/hmac/hmac.ts @@ -0,0 +1,100 @@ +import * as jsonSchema from "@peculiar/json-schema"; +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import * as asmCrypto from "asmcrypto.js"; +import * as pvtsutils from "pvtsutils"; +import { nativeCrypto } from "../../native"; +import { HmacCryptoKey } from "./key"; + +export class HmacProvider extends core.HmacProvider { + + public async onGenerateKey(algorithm: types.HmacKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + const length = algorithm.length || this.getDefaultLength((algorithm.hash as types.Algorithm).name); + + // get random bytes for key + const raw = nativeCrypto.getRandomValues(new Uint8Array(length >> 3)); + + const key = new HmacCryptoKey(algorithm, extractable, keyUsages, raw); + + return key; + } + + public override async onSign(algorithm: types.Algorithm, key: HmacCryptoKey, data: ArrayBuffer): Promise { + let fn: typeof asmCrypto.HmacSha1 | typeof asmCrypto.HmacSha256 | typeof asmCrypto.HmacSha512; + switch (key.algorithm.hash.name.toUpperCase()) { + case "SHA-1": + fn = asmCrypto.HmacSha1; + break; + case "SHA-256": + fn = asmCrypto.HmacSha256; + break; + case "SHA-512": + fn = asmCrypto.HmacSha512; + break; + default: + throw new core.OperationError("key.algorithm.hash: Is not recognized"); + } + + const result = new fn(key.data) + .process(pvtsutils.BufferSourceConverter.toUint8Array(data)) + .finish().result; + if (!result) { + throw new core.OperationError("HMAC signing result is empty"); + } + + return pvtsutils.BufferSourceConverter.toArrayBuffer(result); + } + + public override async onVerify(algorithm: types.Algorithm, key: HmacCryptoKey, signature: ArrayBuffer, data: ArrayBuffer): Promise { + const signature2 = await this.onSign(algorithm, key, data); + return pvtsutils.Convert.ToHex(signature2) === pvtsutils.Convert.ToHex(signature); + } + + public async onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.HmacImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + let key: HmacCryptoKey; + + switch (format.toLowerCase()) { + case "jwk": + key = jsonSchema.JsonParser.fromJSON(keyData, { targetSchema: HmacCryptoKey }); + break; + case "raw": + if (!pvtsutils.BufferSourceConverter.isBufferSource(keyData)) { + throw new TypeError("keyData: Is not ArrayBuffer or ArrayBufferView"); + } + key = new HmacCryptoKey(algorithm, extractable, keyUsages, pvtsutils.BufferSourceConverter.toUint8Array(keyData)); + break; + default: + throw new core.OperationError("format: Must be 'jwk' or 'raw'"); + } + + key.algorithm = { + hash: { name: (algorithm.hash as types.Algorithm).name }, + name: this.name, + length: key.data.length << 3, + }; + key.extractable = extractable; + key.usages = keyUsages; + + return key; + } + + public async onExportKey(format: types.KeyFormat, key: HmacCryptoKey): Promise { + switch (format.toLowerCase()) { + case "jwk": + const jwk = jsonSchema.JsonSerializer.toJSON(key) as types.JsonWebKey; + return jwk; + case "raw": + return new Uint8Array(key.data).buffer; + default: + throw new core.OperationError("format: Must be 'jwk' or 'raw'"); + } + } + + public override checkCryptoKey(key: types.CryptoKey, keyUsage?: types.KeyUsage) { + super.checkCryptoKey(key, keyUsage); + if (!(key instanceof HmacCryptoKey)) { + throw new TypeError("key: Is not HMAC CryptoKey"); + } + } + +} diff --git a/packages/web/src/mechs/hmac/index.ts b/packages/web/src/mechs/hmac/index.ts new file mode 100644 index 0000000..0188ebc --- /dev/null +++ b/packages/web/src/mechs/hmac/index.ts @@ -0,0 +1 @@ +export * from "./hmac"; diff --git a/packages/web/src/mechs/hmac/key.ts b/packages/web/src/mechs/hmac/key.ts new file mode 100644 index 0000000..409baf6 --- /dev/null +++ b/packages/web/src/mechs/hmac/key.ts @@ -0,0 +1,56 @@ +import * as jsonSchema from "@peculiar/json-schema"; +import * as types from "@peculiar/webcrypto-types"; +import * as pvtsutils from "pvtsutils"; +import { CryptoKey } from "../../key"; + +export const JsonBase64UrlConverter: jsonSchema.IJsonConverter = { + fromJSON: (value: string) => Buffer.from(pvtsutils.Convert.FromBase64Url(value)), + toJSON: (value: Buffer) => pvtsutils.Convert.ToBase64Url(value), +}; + +export class HmacCryptoKey extends CryptoKey { + + @jsonSchema.JsonProp({ name: "ext", type: jsonSchema.JsonPropTypes.Boolean, optional: true }) + public override extractable!: boolean; + + declare public readonly type: "secret"; + + @jsonSchema.JsonProp({ name: "key_ops", type: jsonSchema.JsonPropTypes.String, repeated: true, optional: true }) + public override usages!: types.KeyUsage[]; + + @jsonSchema.JsonProp({ name: "k", converter: JsonBase64UrlConverter }) + public data: Uint8Array; + + declare public algorithm: types.HmacKeyAlgorithm; + + @jsonSchema.JsonProp({ type: jsonSchema.JsonPropTypes.String }) + protected readonly kty: string = "oct"; + + @jsonSchema.JsonProp({ type: jsonSchema.JsonPropTypes.String }) + protected get alg() { + const hash = this.algorithm.hash.name.toUpperCase(); + return `HS${hash.replace("SHA-", "")}`; + } + + protected set alg(value: string) { + // nothing, cause set is needed for json-schema, but is not used by module + } + + constructor(); + constructor( + algorithm: types.KeyAlgorithm, + extractable: boolean, + usages: types.KeyUsage[], + data: Uint8Array, + ); + constructor( + algorithm = { name: "HMAC" }, + extractable = false, + usages: types.KeyUsage[] = [], + data = new Uint8Array(0), + ) { + super(algorithm, extractable, "secret", usages); + this.data = data; + } + +} diff --git a/packages/web/src/mechs/index.ts b/packages/web/src/mechs/index.ts new file mode 100644 index 0000000..f2e38ef --- /dev/null +++ b/packages/web/src/mechs/index.ts @@ -0,0 +1,8 @@ +export * from "./aes"; +export * from "./rsa"; +export * from "./ec"; +export * from "./ed"; +export * from "./sha"; +export * from "./pbkdf"; +export * from "./des"; +export * from "./hmac"; diff --git a/packages/web/src/mechs/pbkdf/index.ts b/packages/web/src/mechs/pbkdf/index.ts new file mode 100644 index 0000000..b038b9d --- /dev/null +++ b/packages/web/src/mechs/pbkdf/index.ts @@ -0,0 +1 @@ +export * from "./pbkdf2"; diff --git a/packages/web/src/mechs/pbkdf/key.ts b/packages/web/src/mechs/pbkdf/key.ts new file mode 100644 index 0000000..c215c00 --- /dev/null +++ b/packages/web/src/mechs/pbkdf/key.ts @@ -0,0 +1,10 @@ +import * as types from "@peculiar/webcrypto-types"; +import { CryptoKey } from "../../key"; + +export class PbkdfCryptoKey extends CryptoKey { + + constructor(algorithm: types.KeyAlgorithm, extractable: boolean, usages: types.KeyUsage[], public raw: Uint8Array) { + super(algorithm, extractable, "secret", usages); + } + +} diff --git a/packages/web/src/mechs/pbkdf/pbkdf2.ts b/packages/web/src/mechs/pbkdf/pbkdf2.ts new file mode 100644 index 0000000..740b48f --- /dev/null +++ b/packages/web/src/mechs/pbkdf/pbkdf2.ts @@ -0,0 +1,45 @@ +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import * as asmCrypto from "asmcrypto.js"; +import * as pvtsutils from "pvtsutils"; +import { PbkdfCryptoKey } from "./key"; + +export class Pbkdf2Provider extends core.Pbkdf2Provider { + + public async onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.Algorithm, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + return new PbkdfCryptoKey( + algorithm, + extractable, + keyUsages, + pvtsutils.BufferSourceConverter.toUint8Array(keyData as ArrayBuffer), + ); + } + + public async onDeriveBits(algorithm: types.Pbkdf2Params, baseKey: PbkdfCryptoKey, length: number): Promise { + let result: Uint8Array; + const salt = pvtsutils.BufferSourceConverter.toUint8Array(algorithm.salt); + const password = baseKey.raw; + switch ((algorithm.hash as types.Algorithm).name.toUpperCase()) { + case "SHA-1": + result = asmCrypto.Pbkdf2HmacSha1(password, salt, algorithm.iterations, length >> 3); + break; + case "SHA-256": + result = asmCrypto.Pbkdf2HmacSha256(password, salt, algorithm.iterations, length >> 3); + break; + case "SHA-512": + result = asmCrypto.Pbkdf2HmacSha512(password, salt, algorithm.iterations, length >> 3); + break; + default: + throw new core.OperationError(`algorithm.hash: '${(algorithm.hash as types.Algorithm).name}' hash algorithm is not supported`); + } + return pvtsutils.BufferSourceConverter.toArrayBuffer(result); + } + + public override checkCryptoKey(key: types.CryptoKey, keyUsage: types.KeyUsage): asserts key is PbkdfCryptoKey { + super.checkCryptoKey(key, keyUsage); + if (!(key instanceof PbkdfCryptoKey)) { + throw new TypeError("key: Is not PbkdfCryptoKey"); + } + } + +} diff --git a/packages/web/src/mechs/rsa/crypto.ts b/packages/web/src/mechs/rsa/crypto.ts new file mode 100644 index 0000000..93c2de3 --- /dev/null +++ b/packages/web/src/mechs/rsa/crypto.ts @@ -0,0 +1,228 @@ +import * as asn1Schema from "@peculiar/asn1-schema"; +import * as jsonSchema from "@peculiar/json-schema"; +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import { Crypto } from "../../crypto"; +import { concat } from "../../helper"; +import { nativeCrypto, nativeSubtle } from "../../native"; +import { RsaCryptoKey } from "./key"; + +export type AsmCryptoRsaKey = Uint8Array[]; + +export class RsaCrypto { + + public static RsaSsa = "RSASSA-PKCS1-v1_5"; + public static RsaPss = "RSA-PSS"; + public static RsaOaep = "RSA-OAEP"; + + public static privateUsages: types.KeyUsage[] = ["sign", "decrypt", "unwrapKey"]; + public static publicUsages: types.KeyUsage[] = ["verify", "encrypt", "wrapKey"]; + + /** + * Tests whether the specified object is RsaCryptoKey and throws a TypeError if it is not + * @param key The object the test expects to be RsaCryptoKey + */ + public static checkCryptoKey(key: any): asserts key is RsaCryptoKey { + if (!(key instanceof RsaCryptoKey)) { + throw new TypeError("key: Is not RsaCryptoKey"); + } + } + + public static async generateKey(algorithm: types.RsaHashedKeyGenParams | types.RsaKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + const alg: types.RsaHashedKeyGenParams = { + name: "RSA-PSS", + hash: "SHA-256", + publicExponent: algorithm.publicExponent, + modulusLength: algorithm.modulusLength, + }; + // generate keys using native crypto + if (!nativeSubtle) { + throw new core.OperationError("Native SubtleCrypto is unavailable"); + } + const keys = (await nativeSubtle.generateKey(alg, true, ["sign", "verify"])) as types.CryptoKeyPair; + const crypto = new Crypto(); + + // create private key + const pkcs8 = await crypto.subtle.exportKey("pkcs8", keys.privateKey); + const privateKey = await crypto.subtle.importKey("pkcs8", pkcs8, algorithm, extractable, keyUsages.filter((o) => this.privateUsages.includes(o))); + + // create public key + const spki = await crypto.subtle.exportKey("spki", keys.publicKey); + const publicKey = await crypto.subtle.importKey("spki", spki, algorithm, true, keyUsages.filter((o) => this.publicUsages.includes(o))); + + return { privateKey, publicKey }; + } + + public static async exportKey(format: types.KeyFormat, key: RsaCryptoKey): Promise { + switch (format) { + case "pkcs8": + return this.exportPkcs8Key(key); + case "spki": + return this.exportSpkiKey(key); + case "jwk": + return this.exportJwkKey(key); + default: + throw new core.OperationError("format: Must be 'jwk', 'pkcs8' or 'spki'"); + } + } + + public static async importKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.RsaHashedImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + let asmKey: AsmCryptoRsaKey; + switch (format) { + case "pkcs8": + asmKey = this.importPkcs8Key(keyData as ArrayBuffer); + break; + case "spki": + asmKey = this.importSpkiKey(keyData as ArrayBuffer); + break; + case "jwk": + asmKey = this.importJwkKey(keyData as types.JsonWebKey); + break; + default: + throw new core.OperationError("format: Must be 'jwk', 'pkcs8' or 'spki'"); + } + const key = new RsaCryptoKey( + { + publicExponent: asmKey[1][1] === 1 + ? asmKey[1].slice(1) + : asmKey[1].slice(3), + modulusLength: asmKey[0].byteLength << 3, + ...algorithm, + } as types.RsaHashedKeyAlgorithm, + extractable, + asmKey.length === 2 ? "public" : "private", + keyUsages, + asmKey, + ); + return key; + } + + public static randomNonZeroValues(data: Uint8Array) { + data = nativeCrypto.getRandomValues(data); + return data.map((n) => { + while (!n) { + n = nativeCrypto.getRandomValues(new Uint8Array(1))[0]; + } + return n; + }); + } + + private static exportPkcs8Key(key: RsaCryptoKey) { + const keyInfo = new core.asn1.PrivateKeyInfo(); + keyInfo.privateKeyAlgorithm.algorithm = "1.2.840.113549.1.1.1"; + keyInfo.privateKeyAlgorithm.parameters = null; + keyInfo.privateKey = asn1Schema.AsnConvert.serialize(this.exportAsmKey(key.data)); + + return asn1Schema.AsnConvert.serialize(keyInfo); + } + + private static importPkcs8Key(data: ArrayBuffer) { + const keyInfo = asn1Schema.AsnConvert.parse(data, core.asn1.PrivateKeyInfo); + const privateKey = asn1Schema.AsnConvert.parse(keyInfo.privateKey, core.asn1.RsaPrivateKey); + return this.importAsmKey(privateKey); + } + + private static importSpkiKey(data: ArrayBuffer) { + const keyInfo = asn1Schema.AsnConvert.parse(data, core.asn1.PublicKeyInfo); + const publicKey = asn1Schema.AsnConvert.parse(keyInfo.publicKey, core.asn1.RsaPublicKey); + return this.importAsmKey(publicKey); + } + + private static exportSpkiKey(key: RsaCryptoKey) { + const publicKey = new core.asn1.RsaPublicKey(); + publicKey.modulus = key.data[0].buffer; + publicKey.publicExponent = key.data[1][1] === 1 + ? key.data[1].buffer.slice(1) + : key.data[1].buffer.slice(3); + + const keyInfo = new core.asn1.PublicKeyInfo(); + keyInfo.publicKeyAlgorithm.algorithm = "1.2.840.113549.1.1.1"; + keyInfo.publicKeyAlgorithm.parameters = null; + keyInfo.publicKey = asn1Schema.AsnConvert.serialize(publicKey); + + return asn1Schema.AsnConvert.serialize(keyInfo); + } + + private static importJwkKey(data: types.JsonWebKey) { + let key: core.asn1.RsaPrivateKey | core.asn1.RsaPublicKey; + if (data.d) { + // private + key = jsonSchema.JsonParser.fromJSON(data, { targetSchema: core.asn1.RsaPrivateKey }); + } else { + // public + key = jsonSchema.JsonParser.fromJSON(data, { targetSchema: core.asn1.RsaPublicKey }); + } + return this.importAsmKey(key); + } + + private static exportJwkKey(key: RsaCryptoKey) { + const asnKey = this.exportAsmKey(key.data); + const jwk = jsonSchema.JsonSerializer.toJSON(asnKey) as types.JsonWebKey; + + jwk.ext = true; + jwk.key_ops = key.usages; + jwk.kty = "RSA"; + jwk.alg = this.getJwkAlgorithm(key.algorithm); + + return jwk; + } + + private static getJwkAlgorithm(algorithm: types.RsaHashedKeyAlgorithm) { + switch (algorithm.name.toUpperCase()) { + case "RSA-OAEP": + const mdSize = /(\d+)$/.exec(algorithm.hash.name)![1]; + return `RSA-OAEP${mdSize !== "1" ? `-${mdSize}` : ""}`; + case "RSASSA-PKCS1-V1_5": + return `RS${/(\d+)$/.exec(algorithm.hash.name)![1]}`; + case "RSA-PSS": + return `PS${/(\d+)$/.exec(algorithm.hash.name)![1]}`; + case "RSAES-PKCS1-V1_5": + return `PS1`; + default: + throw new core.OperationError("algorithm: Is not recognized"); + } + } + + private static exportAsmKey(asmKey: AsmCryptoRsaKey): core.asn1.RsaPrivateKey | core.asn1.RsaPublicKey { + let key: core.asn1.RsaPrivateKey | core.asn1.RsaPublicKey; + if (asmKey.length > 2) { + // private + const privateKey = new core.asn1.RsaPrivateKey(); + privateKey.privateExponent = asmKey[2].buffer; + privateKey.prime1 = asmKey[3].buffer; + privateKey.prime2 = asmKey[4].buffer; + privateKey.exponent1 = asmKey[5].buffer; + privateKey.exponent2 = asmKey[6].buffer; + privateKey.coefficient = asmKey[7].buffer; + key = privateKey; + } else { + // public + key = new core.asn1.RsaPublicKey(); + } + key.modulus = asmKey[0].buffer; + key.publicExponent = asmKey[1][1] === 1 + ? asmKey[1].buffer.slice(1) + : asmKey[1].buffer.slice(3); + + return key; + } + + private static importAsmKey(key: core.asn1.RsaPrivateKey | core.asn1.RsaPublicKey) { + const expPadding = new Uint8Array(4 - key.publicExponent.byteLength); + const asmKey: AsmCryptoRsaKey = [ + new Uint8Array(key.modulus), + concat(expPadding, new Uint8Array(key.publicExponent)), + ]; + if (key instanceof core.asn1.RsaPrivateKey) { + asmKey.push(new Uint8Array(key.privateExponent)); + asmKey.push(new Uint8Array(key.prime1)); + asmKey.push(new Uint8Array(key.prime2)); + asmKey.push(new Uint8Array(key.exponent1)); + asmKey.push(new Uint8Array(key.exponent2)); + asmKey.push(new Uint8Array(key.coefficient)); + } + + return asmKey; + } + +} diff --git a/packages/web/src/mechs/rsa/index.ts b/packages/web/src/mechs/rsa/index.ts new file mode 100644 index 0000000..4f91ed7 --- /dev/null +++ b/packages/web/src/mechs/rsa/index.ts @@ -0,0 +1,5 @@ +export * from "./crypto"; +export * from "./rsa_oaep"; +export * from "./rsa_pss"; +export * from "./rsa_ssa"; +export * from "./rsa_es"; diff --git a/packages/web/src/mechs/rsa/key.ts b/packages/web/src/mechs/rsa/key.ts new file mode 100644 index 0000000..dd47aa4 --- /dev/null +++ b/packages/web/src/mechs/rsa/key.ts @@ -0,0 +1,12 @@ +import * as types from "@peculiar/webcrypto-types"; +import { CryptoKey } from "../../key"; +import { AsmCryptoRsaKey } from "./crypto"; + +export class RsaCryptoKey extends CryptoKey { + + declare public algorithm: types.RsaHashedKeyAlgorithm; + + constructor(algorithm: types.RsaHashedKeyAlgorithm, extractable: boolean, type: types.KeyType, usages: types.KeyUsage[], public data: AsmCryptoRsaKey) { + super(algorithm, extractable, type, usages); + } +} diff --git a/packages/web/src/mechs/rsa/rsa_es.ts b/packages/web/src/mechs/rsa/rsa_es.ts new file mode 100644 index 0000000..33604c5 --- /dev/null +++ b/packages/web/src/mechs/rsa/rsa_es.ts @@ -0,0 +1,119 @@ +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import * as asmCrypto from "asmcrypto.js"; +import * as pvtsutils from "pvtsutils"; +import { Crypto } from "../../crypto"; +import { RsaCrypto } from "./crypto"; +import { RsaCryptoKey } from "./key"; + +export type RsaPkcs1Params = types.Algorithm; +export type RsaPkcs1SignParams = types.HashedAlgorithm; + +export class RsaEsProvider extends core.ProviderCrypto { + + public name = "RSAES-PKCS1-v1_5"; + public usages = { + publicKey: ["encrypt", "wrapKey"] as types.KeyUsages, + privateKey: ["decrypt", "unwrapKey"] as types.KeyUsages, + }; + public hashAlgorithms = ["SHA-1", "SHA-256", "SHA-384", "SHA-512"]; + + public override async onGenerateKey(algorithm: types.RsaKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + return RsaCrypto.generateKey(algorithm, extractable, keyUsages); + } + + public override checkGenerateKeyParams(algorithm: types.RsaKeyGenParams) { + // public exponent + this.checkRequiredProperty(algorithm, "publicExponent"); + if (!(algorithm.publicExponent && algorithm.publicExponent instanceof Uint8Array)) { + throw new TypeError("publicExponent: Missing or not a Uint8Array"); + } + const publicExponent = pvtsutils.Convert.ToBase64(algorithm.publicExponent); + if (!(publicExponent === "Aw==" || publicExponent === "AQAB")) { + throw new TypeError("publicExponent: Must be [3] or [1,0,1]"); + } + + // modulus length + this.checkRequiredProperty(algorithm, "modulusLength"); + switch (algorithm.modulusLength) { + case 1024: + case 2048: + case 4096: + break; + default: + throw new TypeError("modulusLength: Must be 1024, 2048, or 4096"); + } + } + + public override async onDecrypt(algorithm: RsaPkcs1Params, key: RsaCryptoKey, data: ArrayBuffer): Promise { + // EM = 0x00 || 0x02 || PS || 0x00 || M + const EM = new asmCrypto.RSA(key.data).decrypt(new asmCrypto.BigNumber(pvtsutils.BufferSourceConverter.toUint8Array(data))).result; + const k = key.algorithm.modulusLength >> 3; + if (data.byteLength !== k) { + throw new core.CryptoError("Decryption error. Encrypted message size doesn't match to key length"); + } + // If the first octet of EM does not have hexadecimal value 0x00, if + // the second octet of EM does not have hexadecimal value 0x02, if + // there is no octet with hexadecimal value 0x00 to separate PS from + // M, or if the length of PS is less than 8 octets, output + // "decryption error" and stop. + let offset = 0; + if (EM[offset++] || EM[offset++] !== 2) { + throw new core.CryptoError("Decryption error"); + } + do { + if (EM[offset++] === 0) { + break; + } + } while (offset < EM.length); + + if (offset < 11) { + throw new core.CryptoError("Decryption error. PS is less than 8 octets."); + } + + if (offset === EM.length) { + throw new core.CryptoError("Decryption error. There is no octet with hexadecimal value 0x00 to separate PS from M"); + } + + return EM.buffer.slice(offset); + } + + public override async onEncrypt(algorithm: RsaPkcs1Params, key: RsaCryptoKey, data: ArrayBuffer): Promise { + const k = key.algorithm.modulusLength >> 3; + if (data.byteLength > k - 11) { + throw new core.CryptoError("Message too long"); + } + + // EM = 0x00 || 0x02 || PS || 0x00 || M + const psLen = k - data.byteLength - 3; + const PS = RsaCrypto.randomNonZeroValues(new Uint8Array(psLen)); + const EM = new Uint8Array(k); + EM[0] = 0; + EM[1] = 2; + EM.set(PS, 2); // PS + EM[2 + psLen] = 0; + EM.set(new Uint8Array(data), 3 + psLen); + + const result = new asmCrypto.RSA(key.data).encrypt(new asmCrypto.BigNumber(EM)).result; + return pvtsutils.BufferSourceConverter.toArrayBuffer(result); + } + + public override async onExportKey(format: types.KeyFormat, key: RsaCryptoKey): Promise { + return RsaCrypto.exportKey(format, key); + } + + public override async onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.RsaHashedImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + const key = await RsaCrypto.importKey(format, keyData, { ...algorithm, name: this.name }, extractable, keyUsages); + return key; + } + + public override checkCryptoKey(key: types.CryptoKey, keyUsage: types.KeyUsage): asserts key is RsaCryptoKey { + super.checkCryptoKey(key, keyUsage); + RsaCrypto.checkCryptoKey(key); + } + + private async prepareSignData(algorithm: RsaPkcs1SignParams, data: ArrayBuffer) { + const crypto = new Crypto(); + return crypto.subtle.digest(algorithm.hash, data); + } +} diff --git a/packages/web/src/mechs/rsa/rsa_oaep.ts b/packages/web/src/mechs/rsa/rsa_oaep.ts new file mode 100644 index 0000000..fde4bbb --- /dev/null +++ b/packages/web/src/mechs/rsa/rsa_oaep.ts @@ -0,0 +1,53 @@ +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import * as asmCrypto from "asmcrypto.js"; +import * as pvtsutils from "pvtsutils"; +import { ShaCrypto } from "../sha/crypto"; +import { RsaCrypto } from "./crypto"; +import { RsaCryptoKey } from "./key"; + +export class RsaOaepProvider extends core.RsaOaepProvider { + + public async onGenerateKey(algorithm: types.RsaHashedKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + return RsaCrypto.generateKey(algorithm, extractable, keyUsages); + } + + public async onExportKey(format: types.KeyFormat, key: RsaCryptoKey): Promise { + return RsaCrypto.exportKey(format, key); + } + + public async onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.RsaHashedImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + return RsaCrypto.importKey(format, keyData, algorithm, extractable, keyUsages); + } + + public async onEncrypt(algorithm: types.RsaOaepParams, key: RsaCryptoKey, data: ArrayBuffer): Promise { + return this.cipher(algorithm, key, data); + } + + public async onDecrypt(algorithm: types.RsaOaepParams, key: RsaCryptoKey, data: ArrayBuffer): Promise { + return this.cipher(algorithm, key, data); + } + + public override checkCryptoKey(key: types.CryptoKey, keyUsage: types.KeyUsage): asserts key is RsaCryptoKey { + super.checkCryptoKey(key, keyUsage); + RsaCrypto.checkCryptoKey(key); + } + + private cipher(algorithm: types.RsaOaepParams, key: RsaCryptoKey, data: ArrayBuffer) { + const digest = ShaCrypto.getDigest(key.algorithm.hash.name); + let label: Uint8Array | undefined; + if (algorithm.label) { + label = pvtsutils.BufferSourceConverter.toUint8Array(algorithm.label); + } + const cipher = new asmCrypto.RSA_OAEP(key.data, digest, label); + let res: Uint8Array; + const u8Data = pvtsutils.BufferSourceConverter.toUint8Array(data); + if (key.type === "public") { + res = cipher.encrypt(u8Data); + } else { + res = cipher.decrypt(u8Data); + } + return pvtsutils.BufferSourceConverter.toArrayBuffer(res); + } + +} diff --git a/packages/web/src/mechs/rsa/rsa_pss.ts b/packages/web/src/mechs/rsa/rsa_pss.ts new file mode 100644 index 0000000..0d6c600 --- /dev/null +++ b/packages/web/src/mechs/rsa/rsa_pss.ts @@ -0,0 +1,44 @@ +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import * as asmCrypto from "asmcrypto.js"; +import * as pvtsutils from "pvtsutils"; +import { ShaCrypto } from "../sha/crypto"; +import { RsaCrypto } from "./crypto"; +import { RsaCryptoKey } from "./key"; + +export class RsaPssProvider extends core.RsaPssProvider { + + public async onGenerateKey(algorithm: types.RsaHashedKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + return RsaCrypto.generateKey(algorithm, extractable, keyUsages); + } + + public async onExportKey(format: types.KeyFormat, key: RsaCryptoKey): Promise { + return RsaCrypto.exportKey(format, key); + } + + public async onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.RsaHashedImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + return RsaCrypto.importKey(format, keyData, algorithm, extractable, keyUsages); + } + + public async onSign(algorithm: types.RsaPssParams, key: RsaCryptoKey, data: ArrayBuffer): Promise { + const rsa = new asmCrypto.RSA_PSS(key.data, ShaCrypto.getDigest(key.algorithm.hash.name), algorithm.saltLength); + const result = rsa.sign(pvtsutils.BufferSourceConverter.toUint8Array(data)); + return pvtsutils.BufferSourceConverter.toArrayBuffer(result); + } + + public async onVerify(algorithm: types.RsaPssParams, key: RsaCryptoKey, signature: ArrayBuffer, data: ArrayBuffer): Promise { + const rsa = new asmCrypto.RSA_PSS(key.data, ShaCrypto.getDigest(key.algorithm.hash.name), algorithm.saltLength); + try { + rsa.verify(pvtsutils.BufferSourceConverter.toUint8Array(signature), pvtsutils.BufferSourceConverter.toUint8Array(data)); + } catch { + return false; + } + return true; + } + + public override checkCryptoKey(key: types.CryptoKey, keyUsage: types.KeyUsage): asserts key is RsaCryptoKey { + super.checkCryptoKey(key, keyUsage); + RsaCrypto.checkCryptoKey(key); + } + +} diff --git a/packages/web/src/mechs/rsa/rsa_ssa.ts b/packages/web/src/mechs/rsa/rsa_ssa.ts new file mode 100644 index 0000000..ba4cab4 --- /dev/null +++ b/packages/web/src/mechs/rsa/rsa_ssa.ts @@ -0,0 +1,44 @@ +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import * as asmCrypto from "asmcrypto.js"; +import * as pvtsutils from "pvtsutils"; +import { ShaCrypto } from "../sha/crypto"; +import { RsaCrypto } from "./crypto"; +import { RsaCryptoKey } from "./key"; + +export class RsaSsaProvider extends core.RsaSsaProvider { + + public async onGenerateKey(algorithm: types.RsaHashedKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + return RsaCrypto.generateKey(algorithm, extractable, keyUsages); + } + + public async onExportKey(format: types.KeyFormat, key: RsaCryptoKey): Promise { + return RsaCrypto.exportKey(format, key); + } + + public async onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.RsaHashedImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise { + return RsaCrypto.importKey(format, keyData, algorithm, extractable, keyUsages); + } + + public async onSign(algorithm: types.Algorithm, key: RsaCryptoKey, data: ArrayBuffer): Promise { + const rsa = new asmCrypto.RSA_PKCS1_v1_5(key.data, ShaCrypto.getDigest(key.algorithm.hash.name)); + const result = rsa.sign(pvtsutils.BufferSourceConverter.toUint8Array(data)); + return pvtsutils.BufferSourceConverter.toArrayBuffer(result); + } + + public async onVerify(algorithm: types.Algorithm, key: RsaCryptoKey, signature: ArrayBuffer, data: ArrayBuffer): Promise { + const rsa = new asmCrypto.RSA_PKCS1_v1_5(key.data, ShaCrypto.getDigest(key.algorithm.hash.name)); + try { + rsa.verify(pvtsutils.BufferSourceConverter.toUint8Array(signature), pvtsutils.BufferSourceConverter.toUint8Array(data)); + } catch { + return false; + } + return true; + } + + public override checkCryptoKey(key: types.CryptoKey, keyUsage: types.KeyUsage): asserts key is RsaCryptoKey { + super.checkCryptoKey(key, keyUsage); + RsaCrypto.checkCryptoKey(key); + } + +} diff --git a/packages/web/src/mechs/sha/crypto.ts b/packages/web/src/mechs/sha/crypto.ts new file mode 100644 index 0000000..05a2ca1 --- /dev/null +++ b/packages/web/src/mechs/sha/crypto.ts @@ -0,0 +1,32 @@ +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import * as asmCrypto from "asmcrypto.js"; +import * as pvtsutils from "pvtsutils"; + +export class ShaCrypto { + + public static getDigest(name: string) { + switch (name) { + case "SHA-1": + return new asmCrypto.Sha1(); + case "SHA-256": + return new asmCrypto.Sha256(); + case "SHA-512": + return new asmCrypto.Sha512(); + default: + throw new core.AlgorithmError("keyAlgorithm.hash: Is not recognized"); + } + } + + public static async digest(algorithm: types.Algorithm, data: ArrayBuffer): Promise { + const mech = this.getDigest(algorithm.name); + + const result = mech + .process(pvtsutils.BufferSourceConverter.toUint8Array(data)) + .finish().result; + if (!result) { + throw new core.OperationError("SHA digest result is empty"); + } + return pvtsutils.BufferSourceConverter.toArrayBuffer(result); + } +} diff --git a/packages/web/src/mechs/sha/index.ts b/packages/web/src/mechs/sha/index.ts new file mode 100644 index 0000000..3769313 --- /dev/null +++ b/packages/web/src/mechs/sha/index.ts @@ -0,0 +1,8 @@ +export * from "./sha_1"; +export * from "./sha_256"; +export * from "./sha_512"; +export * from "./sha3_256"; +export * from "./sha3_384"; +export * from "./sha3_512"; +export * from "./shake128"; +export * from "./shake256"; diff --git a/packages/web/src/mechs/sha/sha3_256.ts b/packages/web/src/mechs/sha/sha3_256.ts new file mode 100644 index 0000000..634c2d5 --- /dev/null +++ b/packages/web/src/mechs/sha/sha3_256.ts @@ -0,0 +1,13 @@ +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import { hash256 } from "@stablelib/sha3"; + +export class Sha3256Provider extends core.ProviderCrypto { + public name = "SHA3-256"; + public usages: types.ProviderKeyUsage = []; + + public override async onDigest(algorithm: types.Algorithm, data: ArrayBuffer): Promise { + return hash256(new Uint8Array(data)).buffer; + } + +} diff --git a/packages/web/src/mechs/sha/sha3_384.ts b/packages/web/src/mechs/sha/sha3_384.ts new file mode 100644 index 0000000..d447e09 --- /dev/null +++ b/packages/web/src/mechs/sha/sha3_384.ts @@ -0,0 +1,13 @@ +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import { hash384 } from "@stablelib/sha3"; + +export class Sha3384Provider extends core.ProviderCrypto { + public name = "SHA3-384"; + public usages: types.ProviderKeyUsage = []; + + public override async onDigest(algorithm: types.Algorithm, data: ArrayBuffer): Promise { + return hash384(new Uint8Array(data)).buffer; + } + +} diff --git a/packages/web/src/mechs/sha/sha3_512.ts b/packages/web/src/mechs/sha/sha3_512.ts new file mode 100644 index 0000000..889899c --- /dev/null +++ b/packages/web/src/mechs/sha/sha3_512.ts @@ -0,0 +1,13 @@ +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import { hash512 } from "@stablelib/sha3"; + +export class Sha3512Provider extends core.ProviderCrypto { + public name = "SHA3-512"; + public usages: types.ProviderKeyUsage = []; + + public override async onDigest(algorithm: types.Algorithm, data: ArrayBuffer): Promise { + return hash512(new Uint8Array(data)).buffer; + } + +} diff --git a/packages/web/src/mechs/sha/sha_1.ts b/packages/web/src/mechs/sha/sha_1.ts new file mode 100644 index 0000000..c9c8da9 --- /dev/null +++ b/packages/web/src/mechs/sha/sha_1.ts @@ -0,0 +1,13 @@ +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import { ShaCrypto } from "./crypto"; + +export class Sha1Provider extends core.ProviderCrypto { + public name = "SHA-1"; + public usages: types.ProviderKeyUsage = []; + + public override async onDigest(algorithm: types.Algorithm, data: ArrayBuffer): Promise { + return ShaCrypto.digest(algorithm, data); + } + +} diff --git a/packages/web/src/mechs/sha/sha_256.ts b/packages/web/src/mechs/sha/sha_256.ts new file mode 100644 index 0000000..10af342 --- /dev/null +++ b/packages/web/src/mechs/sha/sha_256.ts @@ -0,0 +1,5 @@ +import { Sha1Provider } from "./sha_1"; + +export class Sha256Provider extends Sha1Provider { + public override name = "SHA-256"; +} diff --git a/packages/web/src/mechs/sha/sha_512.ts b/packages/web/src/mechs/sha/sha_512.ts new file mode 100644 index 0000000..1b499cc --- /dev/null +++ b/packages/web/src/mechs/sha/sha_512.ts @@ -0,0 +1,5 @@ +import { Sha1Provider } from "./sha_1"; + +export class Sha512Provider extends Sha1Provider { + public override name = "SHA-512"; +} diff --git a/packages/web/src/mechs/sha/shake128.ts b/packages/web/src/mechs/sha/shake128.ts new file mode 100644 index 0000000..b1f92f6 --- /dev/null +++ b/packages/web/src/mechs/sha/shake128.ts @@ -0,0 +1,14 @@ +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import { SHAKE128 } from "@stablelib/sha3"; + +export class Shake128Provider extends core.Shake128Provider { + + public async onDigest(algorithm: Required, data: ArrayBuffer): Promise { + const output = new Uint8Array(algorithm.length); + new SHAKE128().update(new Uint8Array(data)).stream(output); + + return output.buffer; + } + +} diff --git a/packages/web/src/mechs/sha/shake256.ts b/packages/web/src/mechs/sha/shake256.ts new file mode 100644 index 0000000..34cf486 --- /dev/null +++ b/packages/web/src/mechs/sha/shake256.ts @@ -0,0 +1,14 @@ +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import { SHAKE256 } from "@stablelib/sha3"; + +export class Shake256Provider extends core.Shake256Provider { + + public async onDigest(algorithm: Required, data: ArrayBuffer): Promise { + const output = new Uint8Array(algorithm.length); + new SHAKE256().update(new Uint8Array(data)).stream(output); + + return output.buffer; + } + +} diff --git a/packages/web/src/native.ts b/packages/web/src/native.ts new file mode 100644 index 0000000..7677c58 --- /dev/null +++ b/packages/web/src/native.ts @@ -0,0 +1,25 @@ +import * as types from "@peculiar/webcrypto-types"; + +declare const self: any; + +let window: any = {}; +if (typeof self !== "undefined") { + window = self; +} + +export let nativeCrypto: types.Crypto = + window["msCrypto"] // IE + || window.crypto // other browsers + || {}; // if crypto is empty +export let nativeSubtle: types.SubtleCrypto | null = null; +try { + nativeSubtle = nativeCrypto?.subtle || (nativeCrypto as any)?.["webkitSubtle"] || null; +} catch (err) { + console.warn("Cannot get subtle from crypto", err); + // Safari throws error on crypto.webkitSubtle in Worker +} + +export function setCrypto(crypto: types.Crypto) { + nativeCrypto = crypto; + nativeSubtle = crypto.subtle; +} diff --git a/packages/web/src/shim.ts b/packages/web/src/shim.ts new file mode 100644 index 0000000..e369889 --- /dev/null +++ b/packages/web/src/shim.ts @@ -0,0 +1,23 @@ +import { Crypto, nativeCrypto } from "."; +import { Debug } from "./debug"; +import "./init"; + +declare const self: any; + +const window = self as any; + +if (nativeCrypto) { + Object.freeze(nativeCrypto.getRandomValues); +} + +try { + // Replace original crypto by liner + delete (self as any).crypto; + window.crypto = new Crypto(); + Object.freeze(window.crypto); +} catch (e) { + Debug.error(e); +} + +export const crypto = window.crypto; +export * from "."; diff --git a/packages/web/src/subtle.ts b/packages/web/src/subtle.ts new file mode 100644 index 0000000..c6e998f --- /dev/null +++ b/packages/web/src/subtle.ts @@ -0,0 +1,474 @@ +import * as asn1Schema from "@peculiar/asn1-schema"; +import * as jsonSchema from "@peculiar/json-schema"; +import * as core from "@peculiar/webcrypto-core"; +import * as types from "@peculiar/webcrypto-types"; +import * as pvtsutils from "pvtsutils"; +import { Debug } from "./debug"; +import { Browser, BrowserInfo } from "./helper"; +import { CryptoKey } from "./key"; +import { + AesCbcProvider, AesCtrProvider, AesEcbProvider, AesGcmProvider, AesKwProvider, + DesCbcProvider, DesEde3CbcProvider, + EcCrypto, EcdhProvider, + EcdsaProvider, + HmacProvider, + Pbkdf2Provider, + RsaEsProvider, RsaOaepProvider, RsaPssProvider, RsaSsaProvider, + Sha1Provider, Sha256Provider, Sha512Provider, + EdDsaProvider, EcdhEsProvider, Sha3256Provider, Sha3384Provider, Sha3512Provider, Shake128Provider, Shake256Provider, +} from "./mechs"; +import { getOidByNamedCurve } from "./mechs/ec/helper"; +import { nativeSubtle } from "./native"; +import { WrappedNativeCryptoKey } from "./wrapped_native_key"; + +type SubtleMethods = keyof types.SubtleCrypto; + +function errorToString(error: unknown) { + return error instanceof Error ? error.message : "Unknown error"; +} + +export class SubtleCrypto extends core.SubtleCrypto { + + private static readonly methods: SubtleMethods[] = ["digest", "importKey", "exportKey", "sign", "verify", "generateKey", "encrypt", "decrypt", "deriveBits", "deriveKey", "wrapKey", "unwrapKey"]; + + /** + * Returns true if key is CryptoKey and is not liner key + * > WARN Some browsers doesn't have CryptKey class in `self`. + * @param key + */ + private static isAnotherKey(key: any): key is types.CryptoKey { + if (typeof key === "object" + && typeof key.type === "string" + && typeof key.extractable === "boolean" + && typeof key.algorithm === "object") { + return !(key instanceof CryptoKey); + } + return false; + } + + public readonly browserInfo = BrowserInfo(); + + public constructor() { + super(); + + //#region AES + this.providers.set(new AesCbcProvider()); + this.providers.set(new AesCtrProvider()); + this.providers.set(new AesEcbProvider()); + this.providers.set(new AesGcmProvider()); + this.providers.set(new AesKwProvider()); + //#endregion + + //#region DES + this.providers.set(new DesCbcProvider()); + this.providers.set(new DesEde3CbcProvider()); + //#endregion + + //#region RSA + this.providers.set(new RsaSsaProvider()); + this.providers.set(new RsaPssProvider()); + this.providers.set(new RsaOaepProvider()); + this.providers.set(new RsaEsProvider()); + //#endregion + + //#region EC + this.providers.set(new EcdsaProvider()); + this.providers.set(new EcdhProvider()); + //#endregion + + //#region SHA + this.providers.set(new Sha1Provider()); + this.providers.set(new Sha256Provider()); + this.providers.set(new Sha512Provider()); + //#endregion + + //#region PBKDF + this.providers.set(new Pbkdf2Provider()); + //#endregion + + //#region HMAC + this.providers.set(new HmacProvider()); + //#endregion + + //#region EdDSA + this.providers.set(new EdDsaProvider()); + //#endregion + + //#region ECDH-ES + // TODO Elliptic.js has got issue (https://github.com/indutny/elliptic/issues/243). Uncomment the next line after fix + this.providers.set(new EcdhEsProvider()); + //#endregion + + //#region SHA3 + this.providers.set(new Sha3256Provider()); + this.providers.set(new Sha3384Provider()); + this.providers.set(new Sha3512Provider()); + //#endregion + + //#region SHAKE + this.providers.set(new Shake128Provider()); + this.providers.set(new Shake256Provider()); + //#endregion + } + + public override digest(algorithm: types.DigestAlgorithms, data: pvtsutils.BufferSource, ...args: any[]): Promise; + public override digest(...args: any[]): Promise { + return this.wrapNative("digest", ...args); + } + + public override async importKey(format: types.KeyFormat, keyData: types.JsonWebKey | pvtsutils.BufferSource, algorithm: types.AlgorithmIdentifier, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise; + public override async importKey(...args: any[]): Promise { + this.fixFirefoxEcImportPkcs8(args); + return this.wrapNative("importKey", ...args); + } + + public override async exportKey(format: "raw" | "spki" | "pkcs8", key: types.CryptoKey, ...args: any[]): Promise; + public override async exportKey(format: "jwk", key: types.CryptoKey, ...args: any[]): Promise; + public override async exportKey(format: types.KeyFormat, key: types.CryptoKey, ...args: any[]): Promise; + public override async exportKey(...args: any[]): Promise { + return await this.fixFirefoxEcExportPkcs8(args) || + await this.wrapNative("exportKey", ...args); + } + + + public override async generateKey(algorithm: types.RsaHashedKeyGenParams | types.EcKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise; + public override async generateKey(algorithm: types.AesKeyGenParams | types.HmacKeyGenParams | types.Pbkdf2Params, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise; + public override async generateKey(algorithm: types.AlgorithmIdentifier, extractable: boolean, keyUsages: Iterable, ...args: any[]): Promise; + public override async generateKey(...args: any[]): Promise { + return this.wrapNative("generateKey", ...args); + } + + public override async sign(algorithm: types.SignAlgorithms, key: types.CryptoKey, data: pvtsutils.BufferSource, ...args: any[]): Promise; + public override async sign(...args: any[]): Promise { + return this.wrapNative("sign", ...args); + } + + + public override async verify(algorithm: types.SignAlgorithms, key: types.CryptoKey, signature: pvtsutils.BufferSource, data: pvtsutils.BufferSource, ...args: any[]): Promise; + public override async verify(...args: any[]): Promise { + return this.wrapNative("verify", ...args); + } + + public override async encrypt(algorithm: types.AlgorithmIdentifier, key: types.CryptoKey, data: pvtsutils.BufferSource, ...args: any[]): Promise; + public override async encrypt(...args: any[]): Promise { + return this.wrapNative("encrypt", ...args); + } + + public override async decrypt(algorithm: types.AlgorithmIdentifier, key: types.CryptoKey, data: pvtsutils.BufferSource, ...args: any[]): Promise; + public override async decrypt(...args: any[]): Promise { + return this.wrapNative("decrypt", ...args); + } + + public override async wrapKey(format: types.KeyFormat, key: types.CryptoKey, wrappingKey: types.CryptoKey, wrapAlgorithm: types.AlgorithmIdentifier, ...args: any[]): Promise; + public override async wrapKey(...args: any[]): Promise { + return this.wrapNative("wrapKey", ...args); + } + + public override async unwrapKey(format: types.KeyFormat, wrappedKey: pvtsutils.BufferSource, unwrappingKey: types.CryptoKey, unwrapAlgorithm: types.AlgorithmIdentifier, unwrappedKeyAlgorithm: types.AlgorithmIdentifier, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise; + public override async unwrapKey(...args: any[]): Promise { + return this.wrapNative("unwrapKey", ...args); + } + + public override async deriveBits(algorithm: types.AlgorithmIdentifier, baseKey: types.CryptoKey, length: number, ...args: any[]): Promise; + public override async deriveBits(...args: any[]): Promise { + return this.wrapNative("deriveBits", ...args); + } + + public override async deriveKey(algorithm: types.AlgorithmIdentifier, baseKey: types.CryptoKey, derivedKeyType: types.AlgorithmIdentifier, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise; + public override async deriveKey(...args: any[]): Promise { + return this.wrapNative("deriveKey", ...args); + } + + private async wrapNative(method: SubtleMethods, ...args: any[]) { + if (~["generateKey", "unwrapKey", "deriveKey", "importKey"].indexOf(method)) { + this.fixAlgorithmName(args); + } + + try { + if (method !== "digest" || !args.some((a) => a instanceof CryptoKey)) { + const nativeArgs = this.fixNativeArguments(method, args); + + Debug.info(`Call native '${method}' method`, nativeArgs); + if (!nativeSubtle) { + throw new Error("Native SubtleCrypto is empty"); + } + const res = await (nativeSubtle as any)[method].apply(nativeSubtle, nativeArgs); + + return this.fixNativeResult(method, args, res); + } + } catch (e) { + Debug.warn(`Error on native '${method}' calling. ${errorToString(e)}`, e); + } + + if (method === "wrapKey") { + try { + Debug.info(`Trying to wrap key by using native functions`, args); + // wrapKey(format, key, wrappingKey, wrapAlgorithm); + // indexes 0 1 2 3 + const data = await this.exportKey(args[0], args[1]); + const keyData = (args[0] === "jwk") ? pvtsutils.Convert.FromUtf8String(JSON.stringify(data)) : data as ArrayBuffer; + const res = await this.encrypt(args[3], args[2], keyData); + return res; + } catch (e) { + Debug.warn(`Cannot wrap key by native functions. ${errorToString(e)}`, e); + } + } + + if (method === "unwrapKey") { + try { + Debug.info(`Trying to unwrap key by using native functions`, args); + // unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgorithm, unwrappedKeyAlgorithm, extractable, keyUsages); + // indexes 0 1 2 3 4 5 6 + const data = await this.decrypt(args[3], args[2], args[1]); + const keyData = (args[0] === "jwk") ? JSON.parse(pvtsutils.Convert.ToUtf8String(data)) : data; + const res = await this.importKey(args[0], keyData, args[4], args[5], args[6]); + return res; + } catch (e) { + Debug.warn(`Cannot unwrap key by native functions. ${errorToString(e)}`, e); + } + } + + if (method === "deriveKey") { + try { + Debug.info(`Trying to derive key by using native functions`, args); + const data = await this.deriveBits(args[0], args[1], args[2].length); + const res = await this.importKey("raw", data, args[2], args[3], args[4]); + return res; + } catch (e) { + Debug.warn(`Cannot derive key by native functions. ${errorToString(e)}`, e); + } + } + + if (method === "deriveBits" || method === "deriveKey") { + // Cast public keys from algorithm + for (const arg of args) { + if (typeof arg === "object" && arg.public && SubtleCrypto.isAnotherKey(arg.public)) { + arg.public = await this.castKey(arg.public); + } + } + } + + // Cast native keys to liner keys + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + if (SubtleCrypto.isAnotherKey(arg)) { + args[i] = await this.castKey(arg); + } + } + + const fn = super[method] as any; + if (typeof fn === "function") { + return fn.apply(this, args); + } + + throw new Error("Incorrect type of 'method'. Must be 'function'."); + } + + private fixNativeArguments(method: SubtleMethods, args: any[]) { + const res = [...args]; + if (method === "importKey") { + if (this.browserInfo.name === Browser.IE && res[0]?.toLowerCase?.() === "jwk" && !pvtsutils.BufferSourceConverter.isBufferSource(res[1])) { + // IE11 uses ArrayBuffer instead of JSON object + res[1] = pvtsutils.Convert.FromUtf8String(JSON.stringify(res[1])); + } + } + + if (this.browserInfo.name === Browser.IE && args[1] instanceof WrappedNativeCryptoKey) { + // Fix algs for IE11 + switch (method) { + case "sign": + case "verify": + case "encrypt": + case "decrypt": + res[0] = { ...this.prepareAlgorithm(res[0]), hash: (res[1]?.algorithm as types.RsaHashedKeyAlgorithm)?.hash?.name }; + break; + case "wrapKey": + case "unwrapKey": + res[4] = { ...this.prepareAlgorithm(res[4]), hash: (res[3]?.algorithm as types.RsaHashedKeyAlgorithm)?.hash?.name }; + break; + } + } + + for (let i = 0; i < res.length; i++) { + const arg = res[i]; + if (arg instanceof WrappedNativeCryptoKey) { + // Convert wrapped key to Native CryptoKey + res[i] = arg.getNative(); + } + } + + return res; + } + + private fixNativeResult(method: SubtleMethods, args: any[], res: any): any { + if (this.browserInfo.name === Browser.IE) { + if (method === "exportKey") { + if (args[0]?.toLowerCase?.() === "jwk" && res instanceof ArrayBuffer) { + // IE11 uses ArrayBuffer instead of JSON object + return JSON.parse(pvtsutils.Convert.ToUtf8String(res)); + } + } + // wrap IE11 native key + if ("privateKey" in res) { + const privateKeyUsages = ["sign", "decrypt", "unwrapKey", "deriveKey", "deriveBits"]; + const publicKeyUsages = ["verify", "encrypt", "wrapKey"]; + return { + privateKey: this.wrapNativeKey(res.privateKey, args[0], args[1], args[2].filter((o: string) => privateKeyUsages.includes(o))), + publicKey: this.wrapNativeKey(res.publicKey, args[0], args[1], args[2].filter((o: string) => publicKeyUsages.includes(o))), + }; + } else if ("extractable" in res) { + let algorithm: types.Algorithm; + let usages: types.KeyUsage[]; + switch (method) { + case "importKey": + algorithm = args[2]; + usages = args[4]; + break; + case "unwrapKey": + algorithm = args[4]; + usages = args[6]; + + break; + case "generateKey": + algorithm = args[0]; + usages = args[2]; + break; + + default: + throw new core.OperationError("Cannot wrap native key. Unsupported method in use"); + } + return this.wrapNativeKey(res, algorithm, res.extractable, usages); + } + } + + return res; + } + + private wrapNativeKey(key: types.CryptoKey, algorithm: types.AlgorithmIdentifier, extractable: boolean, keyUsages: types.KeyUsage[]): types.CryptoKey { + if (this.browserInfo.name === Browser.IE) { + const algs = [ + "RSASSA-PKCS1-v1_5", "RSA-PSS", "RSA-OAEP", + "AES-CBC", "AES-CTR", "AES-KW", "HMAC", + ]; + const index = algs.map((o) => o.toLowerCase()).indexOf(key.algorithm.name.toLowerCase()); + if (index !== -1) { + const alg = this.prepareAlgorithm(algorithm); + const newAlg: any = { + ...key.algorithm, + name: algs[index], + }; + if (core.SubtleCrypto.isHashedAlgorithm(alg)) { + newAlg.hash = { + name: (alg.hash as any).name.toUpperCase(), + }; + } + Debug.info(`Wrapping ${algs[index]} crypto key to WrappedNativeCryptoKey`); + return new WrappedNativeCryptoKey(newAlg, extractable, key.type, keyUsages, key); + } + } + return key; + } + + private async castKey(key: types.CryptoKey) { + Debug.info("Cast native CryptoKey to linter key.", key); + if (!key.extractable) { + throw new Error("Cannot cast unextractable crypto key"); + } + + const provider = this.getProvider(key.algorithm.name); + const jwk = await this.exportKey("jwk", key); + + return provider.importKey("jwk", jwk, key.algorithm, true, key.usages); + } + + /** + * Fixes name of the algorithms. Edge doesn't normilize algorithm names in keys + * @param args + */ + private fixAlgorithmName(args: any[]) { + if (this.browserInfo.name === Browser.Edge) { + for (let i = 0; i < args.length; i++) { + const arg = args[0]; + if (typeof arg === "string") { + // algorithm + for (const algorithm of this.providers.algorithms) { + if (algorithm.toLowerCase() === arg.toLowerCase()) { + args[i] = algorithm; + break; + } + } + } else if (typeof arg === "object" && typeof arg.name === "string") { + // algorithm.name + for (const algorithm of this.providers.algorithms) { + if (algorithm.toLowerCase() === arg.name.toLowerCase()) { + arg.name = algorithm; + } + if ((typeof arg.hash === "string" && algorithm.toLowerCase() === arg.hash.toLowerCase()) + || (typeof arg.hash === "object" && typeof arg.hash.name === "string" && algorithm.toLowerCase() === arg.hash.name.toLowerCase())) { + arg.hash = { name: algorithm }; + } + } + } + } + } + } + + /** + * Firefox doesn't support import PKCS8 key for ECDSA/ECDH + */ + private fixFirefoxEcImportPkcs8(args: any[]) { + const preparedAlgorithm = this.prepareAlgorithm(args[2]) as types.EcKeyImportParams; + const algName = preparedAlgorithm.name.toUpperCase(); + if (this.browserInfo.name === Browser.Firefox + && args[0] === "pkcs8" + && ~["ECDSA", "ECDH"].indexOf(algName) + && ~["P-256", "P-384", "P-521"].indexOf(preparedAlgorithm.namedCurve)) { + if (!pvtsutils.BufferSourceConverter.isBufferSource(args[1])) { + throw new TypeError("data: Is not ArrayBuffer or ArrayBufferView"); + } + const preparedData = pvtsutils.BufferSourceConverter.toArrayBuffer(args[1]); + + // Convert PKCS8 to JWK + const keyInfo = asn1Schema.AsnConvert.parse(preparedData, core.asn1.PrivateKeyInfo); + const privateKey = asn1Schema.AsnConvert.parse(keyInfo.privateKey, core.asn1.EcPrivateKey); + const jwk: types.JsonWebKey = jsonSchema.JsonSerializer.toJSON(privateKey); + jwk.ext = true; + jwk.key_ops = args[4]; + jwk.crv = preparedAlgorithm.namedCurve; + jwk.kty = "EC"; + + args[0] = "jwk"; + args[1] = jwk; + } + } + + /** + * Firefox doesn't support export PKCS8 key for ECDSA/ECDH + */ + private async fixFirefoxEcExportPkcs8(args: any[]) { + try { + if (this.browserInfo.name === Browser.Firefox + && args[0] === "pkcs8" + && ~["ECDSA", "ECDH"].indexOf(args[1].algorithm.name) + && ~["P-256", "P-384", "P-521"].indexOf(args[1].algorithm.namedCurve)) { + const jwk = await this.exportKey("jwk", args[1]); + + // Convert JWK to PKCS8 + const ecKey = jsonSchema.JsonParser.fromJSON(jwk, { targetSchema: core.asn1.EcPrivateKey }); + + const keyInfo = new core.asn1.PrivateKeyInfo(); + keyInfo.privateKeyAlgorithm.algorithm = EcCrypto.ASN_ALGORITHM; + keyInfo.privateKeyAlgorithm.parameters = asn1Schema.AsnConvert.serialize( + new core.asn1.ObjectIdentifier(getOidByNamedCurve(args[1].algorithm.namedCurve)), + ); + keyInfo.privateKey = asn1Schema.AsnConvert.serialize(ecKey); + + return asn1Schema.AsnConvert.serialize(keyInfo); + } + } catch (err) { + Debug.error(err); + return null; + } + } + +} diff --git a/packages/web/src/typings/des.d.ts b/packages/web/src/typings/des.d.ts new file mode 100644 index 0000000..f6af6fc --- /dev/null +++ b/packages/web/src/typings/des.d.ts @@ -0,0 +1,28 @@ +declare module "des.js" { + + type DesOperationType = "encrypt" | "decrypt" | string; + + class Cipher { + public update(data: Uint8Array): number[]; + public final(): number[]; + } + + interface IDesCreateParams { + key: Uint8Array; + type: DesOperationType; + iv?: Uint8Array; + } + + class DES extends Cipher { + public static create(params: IDesCreateParams): DES; + } + + class EDE extends DES { + public static create(params: IDesCreateParams): EDE; + } + + class CBC { + public static instantiate(type: typeof DES): typeof DES; + } + +} diff --git a/packages/web/src/typings/elliptic.d.ts b/packages/web/src/typings/elliptic.d.ts new file mode 100644 index 0000000..b1ab503 --- /dev/null +++ b/packages/web/src/typings/elliptic.d.ts @@ -0,0 +1,102 @@ +declare namespace EllipticJS { + class EC { + constructor(); + genKeyPair(): EllipticKeyPair; + keyFromPrivate(hexString: string, encoding: "hex" | "der"): EllipticKeyPair; + keyFromPrivate(hexString: string | number[] | ArrayBuffer): EllipticKeyPair; + keyFromPublic(hexString: string | number[] | ArrayBuffer, enc?: string): EllipticKeyPair; + } + + class BN { + toArray(): number[]; + toBytes(): ArrayBuffer; + } + + class Point { + x: BN; + y: BN; + } + + type EncodeFormat = "hex" | "der"; + + class EllipticKeyPair { + getSecret(enc?: string): any; + getPrivate(enc?: string): any; + getPublic(enc: "der"): number[]; + getPublic(enc: "hex"): string; + getPublic(): Point; + getPublic(enc?: EncodeFormat): string | number[] | Point; + priv?: any; + pub?: Point; + sign(data: number[]): any; + verify(data: number[], hexSignature: string): boolean; + verify(data: number[], signature: object): boolean; + derive(point: any): BN; + } + + class EllipticModule { + version: string; + utils: { + assert: Function; + toArray: Function; + zero2: Function; + toHex: Function; + encode: Function; + getNAF: Function; + getJSF: Function; + cachedProperty: Function; + parseBytes: Function; + intFromLE: Function; + }; + hmacDRBG: Function; + curves: { + PresetCurve: any; + p192: any; + p224: any; + p256: any; + p384: any; + p521: any; + curve25519: any; + ed25519: any; + secp256k1: any; + }; + ec: typeof EC; + eddsa: any; + } +} + +declare const elliptic: { + ec: (namedCurve: string) => EllipticJS.EC; +}; + +declare module "elliptic" { + + const version: string; + const utils: { + assert: Function; + toArray: Function; + zero2: Function; + toHex: Function; + encode: Function; + getNAF: Function; + getJSF: Function; + cachedProperty: Function; + parseBytes: Function; + intFromLE: Function; + }; + const hmacDRBG: Function; + const curves: { + PresetCurve: any; + p192: any; + p224: any; + p256: any; + p384: any; + p521: any; + curve25519: any; + ed25519: any; + secp256k1: any; + }; + function ec(namedCurve: string): EllipticJS.EC; + const eddsa: any; + +} diff --git a/packages/web/src/typings/index.d.ts b/packages/web/src/typings/index.d.ts new file mode 100644 index 0000000..b8db03f --- /dev/null +++ b/packages/web/src/typings/index.d.ts @@ -0,0 +1,4 @@ +interface AlgorithmConverter { + jwk2alg(alg: string): Algorithm; + alg2jwk(alg: Algorithm): string; +} \ No newline at end of file diff --git a/packages/web/src/utils.ts b/packages/web/src/utils.ts new file mode 100644 index 0000000..07979d1 --- /dev/null +++ b/packages/web/src/utils.ts @@ -0,0 +1,5 @@ +import * as types from "@peculiar/webcrypto-types"; + +export function isAlgorithm(algorithm: types.Algorithm, name: string): algorithm is T { + return algorithm.name.toUpperCase() === name.toUpperCase(); +} diff --git a/packages/web/src/wrapped_native_key.ts b/packages/web/src/wrapped_native_key.ts new file mode 100644 index 0000000..3bd4990 --- /dev/null +++ b/packages/web/src/wrapped_native_key.ts @@ -0,0 +1,23 @@ +import * as types from "@peculiar/webcrypto-types"; +import { CryptoKey } from "./key"; + +export class WrappedNativeCryptoKey extends CryptoKey { + + #nativeKey: types.CryptoKey; + + constructor( + algorithm: types.KeyAlgorithm, + extractable: boolean, + type: types.KeyType, + usages: types.KeyUsage[], + nativeKey: types.CryptoKey) { + super(algorithm, extractable, type, usages); + this.#nativeKey = nativeKey; + } + + // @internal + public getNative() { + return this.#nativeKey; + } + +} diff --git a/packages/web/test/aes.spec.ts b/packages/web/test/aes.spec.ts new file mode 100644 index 0000000..8c55e5c --- /dev/null +++ b/packages/web/test/aes.spec.ts @@ -0,0 +1,901 @@ +import * as types from "@peculiar/webcrypto-types"; +import * as pvtsutils from "pvtsutils"; +import { Browser } from "../src/helper"; +import { browser, testCrypto, webCrypto } from "./utils"; + +context("AES", () => { + + testCrypto(webCrypto, [ + //#region AES-CBC + { + name: "AES-128-CBC", + actions: { + generateKey: [ + { + algorithm: { name: "AES-CBC", length: 128 } as types.AesKeyGenParams, + extractable: true, + keyUsages: ["encrypt", "decrypt"], + }, + ], + encrypt: [ + { + algorithm: { + name: "AES-CBC", + iv: pvtsutils.Convert.FromUtf8String("1234567890abcdef"), + } as types.AesCbcParams, + data: pvtsutils.Convert.FromUtf8String("test message"), + encData: pvtsutils.Convert.FromHex("d5df3ea1598defe7446420802baef28e"), + key: { + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef"), + algorithm: { name: "AES-CBC" }, + extractable: true, + keyUsages: ["encrypt", "decrypt"], + }, + }, + ], + import: [ + { + name: "raw", + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef"), + algorithm: "AES-CBC", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "wrong key size", + error: true, + format: "raw", + data: pvtsutils.Convert.FromUtf8String("12345678"), + algorithm: "AES-CBC", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "jwk", + format: "jwk", + data: { + kty: "oct", + alg: "A128CBC", + k: "MTIzNDU2Nzg5MGFiY2RlZg", + ext: true, + key_ops: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + algorithm: "AES-CBC", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + ], + wrapKey: [ + { + key: { + format: "raw", + algorithm: "AES-CBC", + data: pvtsutils.Convert.FromBase64Url("AQIDBAUGBwgJAAECAwQFBg"), + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + wKey: { + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef"), + algorithm: "AES-CBC", + extractable: true, + keyUsages: ["encrypt", "decrypt"], + }, + algorithm: { + name: "AES-CBC", + iv: pvtsutils.Convert.FromUtf8String("1234567890abcdef"), + } as types.AesCbcParams, + wrappedKey: pvtsutils.Convert.FromHex("c630c4bf95977db13f386cc950b18e98521d54c4fda0ba15b2884d2695638bd9"), + }, + ], + }, + }, + { + name: "AES-192-CBC", + actions: { + generateKey: [ + { + algorithm: { name: "AES-CBC", length: 192 } as types.AesKeyGenParams, + extractable: true, + keyUsages: ["encrypt", "decrypt"], + }, + ], + encrypt: [ + { + algorithm: { + name: "AES-CBC", + iv: pvtsutils.Convert.FromUtf8String("1234567890abcdef"), + } as types.AesCbcParams, + data: pvtsutils.Convert.FromUtf8String("test message"), + encData: pvtsutils.Convert.FromHex("67d0b3022149829bf009ad4aff19963a"), + key: { + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef12345678"), + algorithm: { name: "AES-CBC" }, + extractable: true, + keyUsages: ["encrypt", "decrypt"], + }, + }, + ], + import: [ + { + name: "raw", + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef12345678"), + algorithm: "AES-CBC", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "jwk", + format: "jwk", + data: { + kty: "oct", + alg: "A192CBC", + k: "MTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4", + ext: true, + key_ops: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + algorithm: "AES-CBC", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + ], + }, + }, + { + name: "AES-256-CBC", + actions: { + generateKey: [ + { + algorithm: { name: "AES-CBC", length: 256 } as types.AesKeyGenParams, + extractable: true, + keyUsages: ["encrypt", "decrypt"], + }, + ], + encrypt: [ + { + algorithm: { + name: "AES-CBC", + iv: pvtsutils.Convert.FromUtf8String("1234567890abcdef"), + } as types.AesCbcParams, + data: pvtsutils.Convert.FromUtf8String("test message"), + encData: pvtsutils.Convert.FromHex("d827c1c6aee9f0f552c62f30ddee83af"), + key: { + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef1234567809abcdef"), + algorithm: { name: "AES-CBC" }, + extractable: true, + keyUsages: ["encrypt", "decrypt"], + }, + }, + ], + import: [ + { + name: "raw", + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef1234567890abcdef"), + algorithm: "AES-CBC", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "jwk", + format: "jwk", + data: { + kty: "oct", + alg: "A256CBC", + k: "MTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4OTBhYmNkZWY", + ext: true, + key_ops: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + algorithm: "AES-CBC", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + ], + }, + }, + //#endregion + + //#region AES-CTR + { + // skip: browser.name === Browser.Edge, + name: "AES-128-CTR", + actions: { + generateKey: [ + { + algorithm: { name: "AES-CTR", length: 128 } as types.AesKeyGenParams, + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + ], + encrypt: [ + { + algorithm: { + name: "AES-CTR", + counter: pvtsutils.Convert.FromUtf8String("1234567890abcdef"), + length: 128, + } as types.AesCtrParams, + data: pvtsutils.Convert.FromUtf8String("test message"), + encData: pvtsutils.Convert.FromHex("e1d561c49ce4eb2f448f8a00"), + key: { + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef"), + algorithm: { name: "AES-CTR" }, + extractable: true, + keyUsages: ["encrypt", "decrypt"], + }, + }, + ], + import: [ + { + name: "raw", + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef"), + algorithm: "AES-CTR", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "jwk", + format: "jwk", + data: { + kty: "oct", + alg: "A128CTR", + k: "MTIzNDU2Nzg5MGFiY2RlZg", + ext: true, + key_ops: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + algorithm: "AES-CTR", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + ], + }, + }, + { + name: "AES-192-CTR", + skip: browser.name === Browser.Chrome // Chrome doesn't implement this alg + || browser.name === Browser.Edge, + actions: { + generateKey: [ + { + algorithm: { name: "AES-CTR", length: 192 } as types.AesKeyGenParams, + extractable: true, + keyUsages: ["encrypt", "decrypt"], + }, + ], + encrypt: [ + { + algorithm: { + name: "AES-CTR", + counter: pvtsutils.Convert.FromUtf8String("1234567890abcdef"), + length: 128, + } as types.AesCtrParams, + data: pvtsutils.Convert.FromUtf8String("test message"), + encData: pvtsutils.Convert.FromHex("55a00e2851f00aba53bbd02c"), + key: { + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef12345678"), + algorithm: { name: "AES-CTR" }, + extractable: true, + keyUsages: ["encrypt", "decrypt"], + }, + }, + ], + import: [ + { + name: "raw", + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef12345678"), + algorithm: "AES-CTR", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "jwk", + format: "jwk", + data: { + kty: "oct", + alg: "A192CTR", + k: "MTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4", + ext: true, + key_ops: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + algorithm: "AES-CTR", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + ], + }, + }, + { + name: "AES-256-CTR", + skip: browser.name === Browser.Edge, + actions: { + generateKey: [ + { + algorithm: { name: "AES-CTR", length: 256 } as types.AesKeyGenParams, + extractable: true, + keyUsages: ["encrypt", "decrypt"], + }, + ], + encrypt: [ + { + algorithm: { + name: "AES-CTR", + counter: pvtsutils.Convert.FromUtf8String("1234567890abcdef"), + length: 128, + } as types.AesCtrParams, + data: pvtsutils.Convert.FromUtf8String("test message"), + encData: pvtsutils.Convert.FromHex("8208d011a20162c8af7a9ce5"), + key: { + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef1234567809abcdef"), + algorithm: { name: "AES-CTR" }, + extractable: true, + keyUsages: ["encrypt", "decrypt"], + }, + }, + ], + import: [ + { + name: "raw", + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef1234567890abcdef"), + algorithm: "AES-CTR", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "jwk", + format: "jwk", + data: { + kty: "oct", + alg: "A256CTR", + k: "MTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4OTBhYmNkZWY", + ext: true, + key_ops: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + algorithm: "AES-CTR", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + ], + }, + }, + //#endregion + + //#region AES-GCM + { + name: "AES-128-GCM", + actions: { + generateKey: [ + { + algorithm: { name: "AES-GCM", length: 128 } as types.AesKeyGenParams, + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + ], + encrypt: [ + { + algorithm: { + name: "AES-GCM", + iv: pvtsutils.Convert.FromUtf8String("1234567890ab"), + } as types.AesGcmParams, + data: pvtsutils.Convert.FromUtf8String("test message"), + encData: pvtsutils.Convert.FromHex("68d645649ddf8152a253304d698185072f28cdcf7644ac6064bcb240"), + key: { + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef"), + algorithm: { name: "AES-GCM" }, + extractable: true, + keyUsages: ["encrypt", "decrypt"], + }, + }, + ], + import: [ + { + name: "raw", + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef"), + algorithm: "AES-GCM", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "jwk", + format: "jwk", + data: { + kty: "oct", + alg: "A128GCM", + k: "MTIzNDU2Nzg5MGFiY2RlZg", + ext: true, + key_ops: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + algorithm: "AES-GCM", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + ], + }, + }, + { + name: "AES-192-GCM", + actions: { + generateKey: [ + { + algorithm: { name: "AES-GCM", length: 192 } as types.AesKeyGenParams, + extractable: true, + keyUsages: ["encrypt", "decrypt"], + }, + ], + encrypt: [ + { + algorithm: { + name: "AES-GCM", + iv: pvtsutils.Convert.FromUtf8String("1234567890ab"), + } as types.AesGcmParams, + data: pvtsutils.Convert.FromUtf8String("test message"), + encData: pvtsutils.Convert.FromHex("d8eab579ed2418f41ca9c4567226f54cb391d3ca2cb6819dace35691"), + key: { + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef12345678"), + algorithm: { name: "AES-GCM" }, + extractable: true, + keyUsages: ["encrypt", "decrypt"], + }, + }, + ], + import: [ + { + name: "raw", + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef12345678"), + algorithm: "AES-GCM", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "jwk", + format: "jwk", + data: { + kty: "oct", + alg: "A192GCM", + k: "MTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4", + ext: true, + key_ops: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + algorithm: "AES-GCM", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + ], + }, + }, + { + name: "AES-256-GCM", + actions: { + generateKey: [ + { + algorithm: { name: "AES-GCM", length: 256 } as types.AesKeyGenParams, + extractable: true, + keyUsages: ["encrypt", "decrypt"], + }, + ], + encrypt: [ + { + algorithm: { + name: "AES-GCM", + iv: pvtsutils.Convert.FromUtf8String("1234567890ab"), + } as types.AesGcmParams, + data: pvtsutils.Convert.FromUtf8String("test message"), + encData: pvtsutils.Convert.FromHex("f961f2aadbe689ffce86fcaf2619ab647950afcf19e55b71b857c79d"), + key: { + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef1234567809abcdef"), + algorithm: { name: "AES-GCM" }, + extractable: true, + keyUsages: ["encrypt", "decrypt"], + }, + }, + ], + import: [ + { + name: "raw", + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef1234567890abcdef"), + algorithm: "AES-GCM", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "jwk", + format: "jwk", + data: { + kty: "oct", + alg: "A256GCM", + k: "MTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4OTBhYmNkZWY", + ext: true, + key_ops: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + algorithm: "AES-GCM", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + ], + }, + }, + //#endregion + + //#region AES-KW + { + name: "AES-128-KW", + skip: typeof module !== "undefined", // skip for nodejs + actions: { + generateKey: [ + { + algorithm: { name: "AES-KW", length: 128 } as types.AesKeyGenParams, + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + ], + wrapKey: [ + { + skip: browser.name === Browser.Firefox, // Firefox: Operation is not supported on unwrapKey + key: { + format: "raw", + algorithm: "AES-KW", + data: pvtsutils.Convert.FromHex("000102030405060708090A0B0C0D0E0F"), + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + wKey: { + format: "raw", + data: pvtsutils.Convert.FromHex("00112233445566778899AABBCCDDEEFF"), + algorithm: "AES-KW", + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + algorithm: { + name: "AES-KW", + }, + wrappedKey: pvtsutils.Convert.FromHex("1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5"), + }, + ], + import: [ + { + name: "raw", + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef"), + algorithm: "AES-KW", + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + { + name: "jwk", + format: "jwk", + data: { + kty: "oct", + alg: "A128KW", + k: "MTIzNDU2Nzg5MGFiY2RlZg", + ext: true, + key_ops: ["wrapKey", "unwrapKey"], + }, + algorithm: "AES-KW", + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + ], + }, + }, + { + name: "AES-192-KW", + skip: typeof module !== "undefined" // skip for nodejs + || browser.name === Browser.Chrome, // Chrome doesn't support AES-192-KW + actions: { + generateKey: [ + { + algorithm: { name: "AES-KW", length: 192 } as types.AesKeyGenParams, + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + ], + wrapKey: [ + { + skip: browser.name === Browser.Firefox, // Firefox: Operation is not supported on unwrapKey + key: { + format: "raw", + algorithm: "AES-KW", + data: pvtsutils.Convert.FromHex("000102030405060708090A0B0C0D0E0F1011121314151617"), + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + wKey: { + format: "raw", + data: pvtsutils.Convert.FromHex("00112233445566778899AABBCCDDEEFF0001020304050607"), + algorithm: "AES-KW", + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + algorithm: { + name: "AES-KW", + }, + wrappedKey: pvtsutils.Convert.FromHex("031D33264E15D33268F24EC260743EDCE1C6C7DDEE725A936BA814915C6762D2"), + }, + ], + import: [ + { + name: "raw", + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef12345678"), + algorithm: "AES-KW", + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + { + name: "jwk", + format: "jwk", + data: { + kty: "oct", + alg: "A192KW", + k: "MTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4", + ext: true, + key_ops: ["wrapKey", "unwrapKey"], + }, + algorithm: "AES-KW", + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + ], + }, + }, + { + name: "AES-256-KW", + skip: typeof module !== "undefined", // skip for nodejs + actions: { + generateKey: [ + { + algorithm: { name: "AES-KW", length: 256 } as types.AesKeyGenParams, + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + ], + wrapKey: [ + { + skip: browser.name === Browser.Firefox, // Firefox: Operation is not supported on unwrapKey + key: { + format: "raw", + algorithm: "AES-KW", + data: pvtsutils.Convert.FromHex("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"), + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + wKey: { + format: "raw", + data: pvtsutils.Convert.FromHex("00112233445566778899AABBCCDDEEFF000102030405060708090A0B0C0D0E0F"), + algorithm: "AES-KW", + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + algorithm: { + name: "AES-KW", + }, + wrappedKey: pvtsutils.Convert.FromHex("28C9F404C4B810F4CBCCB35CFB87F8263F5786E2D80ED326CBC7F0E71A99F43BFB988B9B7A02DD21"), + }, + ], + import: [ + { + name: "raw", + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef1234567890abcdef"), + algorithm: "AES-KW", + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + { + name: "jwk", + format: "jwk", + data: { + kty: "oct", + alg: "A256KW", + k: "MTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4OTBhYmNkZWY", + ext: true, + key_ops: ["wrapKey", "unwrapKey"], + }, + algorithm: "AES-KW", + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + ], + }, + }, + //#endregion + + //#region AES-ECB + { + name: "AES-128-ECB", + actions: { + generateKey: [ + { + algorithm: { name: "AES-ECB", length: 128 } as types.AesKeyGenParams, + extractable: true, + keyUsages: ["encrypt", "decrypt"], + }, + ], + encrypt: [ + { + algorithm: { + name: "AES-ECB", + } as types.Algorithm, + data: pvtsutils.Convert.FromUtf8String("test message"), + encData: pvtsutils.Convert.FromHex("c6ec2f91a9f48e10062ae41e86cb299f"), + key: { + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef"), + algorithm: { name: "AES-ECB" }, + extractable: true, + keyUsages: ["encrypt", "decrypt"], + }, + }, + ], + import: [ + { + name: "raw", + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef"), + algorithm: "AES-ECB", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "jwk", + format: "jwk", + data: { + kty: "oct", + alg: "A128ECB", + k: "MTIzNDU2Nzg5MGFiY2RlZg", + ext: true, + key_ops: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + algorithm: "AES-ECB", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + ], + wrapKey: [ + { + key: { + format: "raw", + algorithm: "AES-ECB", + data: pvtsutils.Convert.FromBase64Url("AQIDBAUGBwgJAAECAwQFBg"), + extractable: true, + keyUsages: ["wrapKey", "unwrapKey"], + }, + wKey: { + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef"), + algorithm: "AES-ECB", + extractable: true, + keyUsages: ["encrypt", "decrypt"], + }, + algorithm: { + name: "AES-ECB", + } as types.Algorithm, + wrappedKey: pvtsutils.Convert.FromHex("039ec14b350bd92efd02dac2c01cdee6ea9953cfbdc067f20f5f47bb4459da79"), + }, + ], + }, + }, + { + name: "AES-192-ECB", + actions: { + generateKey: [ + { + algorithm: { name: "AES-ECB", length: 192 } as types.AesKeyGenParams, + extractable: true, + keyUsages: ["encrypt", "decrypt"], + }, + ], + encrypt: [ + { + algorithm: { + name: "AES-ECB", + } as types.Algorithm, + data: pvtsutils.Convert.FromUtf8String("test message"), + encData: pvtsutils.Convert.FromHex("8c9f297827ad6aaa9e7501e79fb45ca5"), + key: { + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef12345678"), + algorithm: { name: "AES-ECB" }, + extractable: true, + keyUsages: ["encrypt", "decrypt"], + }, + }, + ], + import: [ + { + name: "raw", + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef12345678"), + algorithm: "AES-ECB", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "jwk", + format: "jwk", + data: { + kty: "oct", + alg: "A192ECB", + k: "MTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4", + ext: true, + key_ops: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + algorithm: "AES-ECB", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + ], + }, + }, + { + name: "AES-256-ECB", + actions: { + generateKey: [ + { + algorithm: { name: "AES-ECB", length: 256 } as types.AesKeyGenParams, + extractable: true, + keyUsages: ["encrypt", "decrypt"], + }, + ], + encrypt: [ + { + algorithm: { + name: "AES-ECB", + } as types.Algorithm, + data: pvtsutils.Convert.FromUtf8String("test message"), + encData: pvtsutils.Convert.FromHex("84ccef71a364b112eb2b3b8b99587a95"), + key: { + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef1234567809abcdef"), + algorithm: { name: "AES-ECB" }, + extractable: true, + keyUsages: ["encrypt", "decrypt"], + }, + }, + ], + import: [ + { + name: "raw", + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef1234567890abcdef"), + algorithm: "AES-ECB", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "jwk", + format: "jwk", + data: { + kty: "oct", + alg: "A256ECB", + k: "MTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4OTBhYmNkZWY", + ext: true, + key_ops: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + algorithm: "AES-ECB", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + ], + }, + }, + //#endregion + + ]); + +}); diff --git a/packages/web/test/des.spec.ts b/packages/web/test/des.spec.ts new file mode 100644 index 0000000..5881550 --- /dev/null +++ b/packages/web/test/des.spec.ts @@ -0,0 +1,125 @@ +import * as types from "@peculiar/webcrypto-types"; +import * as pvtsutils from "pvtsutils"; +import { testCrypto, webCrypto } from "./utils"; + +context("DES", () => { + + testCrypto(webCrypto, [ + { + name: "DES-CBC", + actions: { + generateKey: [ + { + algorithm: { name: "DES-CBC", length: 64 } as types.DesKeyGenParams, + extractable: false, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + ], + encrypt: [ + { + algorithm: { + name: "DES-CBC", + iv: pvtsutils.Convert.FromUtf8String("12345678"), + } as types.DesParams, + data: pvtsutils.Convert.FromUtf8String("test message"), + encData: pvtsutils.Convert.FromHex("3af3f901ff01fe0102dfbbf37d9bdb94"), + key: { + format: "raw", + algorithm: { name: "DES-CBC" }, + extractable: true, + keyUsages: ["encrypt", "decrypt"], + data: pvtsutils.Convert.FromUtf8String("12345678"), + }, + }, + ], + import: [ + { + name: "raw", + format: "raw", + data: pvtsutils.Convert.FromUtf8String("12345678"), + algorithm: "DES-CBC", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "jwk", + format: "jwk", + data: { + kty: "oct", + alg: "DES-CBC", + k: "MTIzNDU2Nzg", + ext: true, + key_ops: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + algorithm: "DES-CBC", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + ], + }, + }, + { + name: "DES-EDE3-CBC", + actions: { + generateKey: [ + { + algorithm: { name: "DES-EDE3-CBC", length: 192 } as types.DesKeyGenParams, + extractable: false, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + ], + encrypt: [ + { + algorithm: { + name: "DES-EDE3-CBC", + iv: pvtsutils.Convert.FromUtf8String("12345678"), + } as types.DesParams, + data: pvtsutils.Convert.FromUtf8String("test message"), + encData: pvtsutils.Convert.FromHex("b9ef20e7db926490e4ff8680d99d2141"), + key: { + format: "raw", + algorithm: { name: "DES-EDE3-CBC" }, + extractable: true, + keyUsages: ["encrypt", "decrypt"], + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef12345678"), + }, + }, + ], + import: [ + { + name: "raw", + format: "raw", + data: pvtsutils.Convert.FromUtf8String("1234567890abcdef12345678"), + algorithm: "DES-EDE3-CBC", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "wrong key size", + error: true, + format: "raw", + data: pvtsutils.Convert.FromUtf8String("12345678"), + algorithm: "DES-EDE3-CBC", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + { + name: "jwk", + format: "jwk", + data: { + kty: "oct", + alg: "3DES-CBC", + k: "MTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4", + ext: true, + key_ops: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + algorithm: "DES-EDE3-CBC", + extractable: true, + keyUsages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + }, + ], + }, + }, + ]); + +}); diff --git a/packages/web/test/ec.spec.ts b/packages/web/test/ec.spec.ts new file mode 100644 index 0000000..0b6e165 --- /dev/null +++ b/packages/web/test/ec.spec.ts @@ -0,0 +1,555 @@ +import * as types from "@peculiar/webcrypto-types"; +import * as assert from "assert"; +import * as pvtsutils from "pvtsutils"; +import { Browser } from "../src/helper"; +import { crypto } from "../src/lib"; +import { browser, ITestGenerateKeyAction, testCrypto, webCrypto } from "./utils"; + +context("EC", () => { + + testCrypto(webCrypto, [ + { + name: "ECDSA", + actions: { + generateKey: ["P-256", "P-384", "P-521", "K-256"].map((namedCurve) => { + return { + name: namedCurve, + algorithm: { + name: "ECDSA", + namedCurve, + } as types.EcKeyGenParams, + extractable: false, + keyUsages: ["sign", "verify"], + } as ITestGenerateKeyAction; + }), + import: [ + { + name: "JWK public key P-256", + format: "jwk", + data: { + crv: "P-256", + ext: true, + key_ops: ["verify"], + kty: "EC", + x: "dJ9C3NyXDa3fMeZ477NWdp9W6faytA7A_U1ub-tyRcs", + y: "aS0_VVe_SeIm8w5TBWjUEco7us6EJUMPKKJaIh36Lho", + }, + algorithm: { + name: "ECDSA", + namedCurve: "P-256", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: ["verify"], + }, + { + name: "JWK public key P-384", + format: "jwk", + data: { + crv: "P-384", + ext: true, + key_ops: ["verify"], + kty: "EC", + x: "eHlLZ4jnt_Drs-qoVxK-SZZvhNhi34jLCgyaEZ9XI6bdlK3y1ettm8K5SnLtDhWO", + y: "qbr3pOOViYDQ2wWG-_9pwQ0S8cHV0LP-x9JO5dl-dsFYtbGix9YH7fRNOl8GkP-6", + }, + algorithm: { + name: "ECDSA", + namedCurve: "P-384", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: ["verify"], + }, + { + name: "JWK public key P-521", + format: "jwk", + data: { + crv: "P-521", + ext: true, + key_ops: ["verify"], + kty: "EC", + x: "Adqn62IVQX8LIauAXrUtxH05DHlRygKcsP9qWAnd9tfJvpaG7bzIs16WMEUe1V-f4AxbQJceU4xCP8dJppK_fzdC", + y: "AEo3s1eExCOvpuBtBWnWlr7TuFhq_fMzqX9eqDHiy8qWl4I_koQtMePodrAc85mVrJAjvsa77Y3Ul3QtIWpXXBqa", + }, + algorithm: { + name: "ECDSA", + namedCurve: "P-521", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: ["verify"], + }, + //#region SPKI + { + skip: browser.name === Browser.Firefox, // Firefox uses 1.3.132.112 instead of 1.2.840.10045.2.1 for algorithm + name: "SPKI P-256", + format: "spki", + data: pvtsutils.Convert.FromBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoZMMqyfA16N6bvloFHmalk/SGMisr3zSXFZdR8F9UkaY7hF13hHiQtwp2YO+1zd7jwYi1Y7SMA9iUrC+ap2OCw=="), + algorithm: { + name: "ECDSA", + namedCurve: "P-256", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: ["verify"], + }, + { + skip: browser.name === Browser.Firefox, // Firefox uses 1.3.132.112 instead of 1.2.840.10045.2.1 for algorithm + name: "SPKI P-384", + format: "spki", + data: pvtsutils.Convert.FromBase64("MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8Kf5Wv21nksy0LuMlkMZv9sxTVAmzNWt81b6MVlYuzxl9D2/obwoVp86pTe4BM79gWWj8pfLc1XrjaIyMSrV8+05IejRLB3i4c0KTGA6QARGm3/AOm0MbTt6kMQF7drL"), + algorithm: { + name: "ECDSA", + namedCurve: "P-384", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: ["verify"], + }, + { + skip: browser.name === Browser.Firefox, // Firefox uses 1.3.132.112 instead of 1.2.840.10045.2.1 for algorithm + name: "SPKI P-521", + format: "spki", + data: pvtsutils.Convert.FromBase64("MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQB+/g37ii0T5iLHCAaXcYRRoNpT0LhfeAr88OwQY4cUpQm1S9lkR0EVUtyuYrYsMB8FarhAZYsLtOiyhjl/Y5f+lQAZ6veWILhbDcbrSNhTPSp3wamAm8QT3EjPUkJlYjHefuAUBIYS9pl5FWjK1pI9fkYe3bdAemkjP1ccHVzqZU9sjg="), + algorithm: { + name: "ECDSA", + namedCurve: "P-521", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: ["verify"], + }, + //#endregion + //#region RAW + { + name: "RAW P-256", + format: "raw", + data: pvtsutils.Convert.FromBase64("BEehen4AavxgJkx5EPZpBeopzgZuY+1i3cMR9iYdZj+IY7/h98Q/GboC2BKS6lT0hEyt6y1DFFXj8ytuof4zXR4="), + algorithm: { + name: "ECDSA", + namedCurve: "P-256", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: ["verify"], + }, + { + name: "RAW P-384", + format: "raw", + data: pvtsutils.Convert.FromBase64("BGYoCpP3Qv4o0s2GWg5xFnasdkI8h6K/LeBm4TV+9HCsqnoXFUJDM5SDeZ0rcCAUUuaPJVn5sedPEKEGW80zmLM1rBOG2RzaBq+uhEJkLpibongnzMZNX2LB58wGJ05f2g=="), + algorithm: { + name: "ECDSA", + namedCurve: "P-384", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: ["verify"], + }, + { + name: "RAW P-521", + format: "raw", + data: pvtsutils.Convert.FromBase64("BABIiZ3f90HQsl4CYHt7Q1WnOIOs+dxeecfQrew/z+73yI/bUrMlmR3mOVARtvg7ZPX7h3lSSqzA1Vv6iv7bPYekcwDKQPeLJkem//H7zY8xtKY+YrYnLUVv6vPE9jyk2vYkj8QPxQRdeIT5bzY2BzTiTcLHDwi2+w2Eonkt7M+zb4G6xw=="), + algorithm: { + name: "ECDSA", + namedCurve: "P-521", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: ["verify"], + }, + //#endregion + //#region JWK Private key + { + name: "JWK private key P-256", + format: "jwk", + data: { + crv: "P-256", + d: "RIrfLaesGcEeNy7fOoVIkgMiImJOFw1Y44kdrtK_49I", + ext: true, + key_ops: ["sign"], + kty: "EC", + x: "wJls5KwIfRDxJEvyAlo3G84qNY0HjvsujyxDSMYAlm4", + y: "I61bQbFgnzfDom68P86kRo98fTrV_9HLeqa4gYnGOdw", + }, + algorithm: { + name: "ECDSA", + namedCurve: "P-256", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: ["sign"], + }, + { + name: "JWK private key P-384", + format: "jwk", + data: { + crv: "P-384", + d: "4YQRcOD-4LMLEr-qsRhQ1oq8hfPKa66BfGVUv3LUlsf2OU3aFG5FxabG5xFUoAE2", + ext: true, + key_ops: ["sign"], + kty: "EC", + x: "XKewC5QCVW9w-SFyZd3z1vlmCqbYYuJmoGRzKtjwkpYQD_RhNAc3ck29d_t0QmaT", + y: "6oSrri3ry1_8c2NKM8aiaJcjwd146ITViezQ7-BpsE1-wDH18P1QkbmR3-Ho54We", + }, + algorithm: { + name: "ECDSA", + namedCurve: "P-384", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: ["sign"], + }, + { + name: "JWK private key P-521", + format: "jwk", + data: { + crv: "P-521", + d: "AItxxufCXVzwPVePNe9Acy8HfbmYeUVkiEyFXdsYRnHxqgDpwucVnIJ44-ZWRpuWu5Ep5KVV3vY9Hp8nJfksi7z2", + ext: true, + key_ops: ["sign"], + kty: "EC", + x: "AJGuTezC-8F-d_0bBpS502OK0z63vo87Dw99a3NUm6gm5pQC1rwu7LcblGqFWOuFBZhsF8I6OFjYvsR-z3u7hhCA", + y: "AFQT8BB9hBf7UwwBUV4im8bFJ7_MD0qOZMVetmdbooMjfec1q3wU5cSoy4LvCnWAaFqu5havUxwnAUuPUWGG_InR", + }, + algorithm: { + name: "ECDSA", + namedCurve: "P-521", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: ["sign"], + }, + //#endregion + //#region PKCS8 + { + name: "PKCS8 P-256", + format: "pkcs8", + data: pvtsutils.Convert.FromBase64("MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgiVEY5OFo3J7g1BnSw/WEWykY/alrhNmpEBLy/7cNnuGhRANCAAQ4SFnMDGYc5kWv7D0gtgUj/Bzbu0B6Bq6XK1vqOo//2m8FS1D4kYKV4KDfFRWehKEtrMBjjkW6OZcM/n0qZ6Uw"), + algorithm: { + name: "ECDSA", + namedCurve: "P-256", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: ["sign"], + }, + { + name: "PKCS8 P-384", + format: "pkcs8", + data: pvtsutils.Convert.FromBase64("MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCY18ajGPCgLv4aF1UkkohMEaB5MU1MyfkuFQSQVDYHLWFTn8f9czce7aTIDjkCx0OhZANiAAR1fni8TC1N1NdXvx25kJyK3y3rpVVaAmA44Wm9jIFseGmSzm/EgmKOFclSzQdEpSC6jxi3olIJ4iYetjl36Ygfwed/xqrsiV6BUb/ny2mimzk3r0M9H6yvbEVQFd7rEAA="), + algorithm: { + name: "ECDSA", + namedCurve: "P-384", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: ["sign"], + }, + { + name: "PKCS8 P-521", + format: "pkcs8", + data: pvtsutils.Convert.FromBase64("MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAbHGkGfik5q0l+ZMI70dbpTGWeKy1+c3mG98wHmnpU+d2bArcYDOXcoqg5Ic/pnmtHvxmk+El33u3XogGONKPlouhgYkDgYYABAH16CoJzEx+Oncpeam6ysUG17y9ttNm5Eg8WqD+BJkP9ju3R22I5PVyYYYZ3ICc1IyDGxFCS7leO1N7tqQLaLi8NAEFTkwCy1G6AAK7LbSa1hNC2fUAaC9L8QJNUNJpjgYiXPDmEnaRNT1XXL00Bjo5iMpE2Ddc/Kp6ktTAo2jOMnfmow=="), + algorithm: { + name: "ECDSA", + namedCurve: "P-521", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: ["sign"], + }, + //#endregion + ], + sign: [ + { + name: "P-256", + key: { + privateKey: { + format: "pkcs8", + data: pvtsutils.Convert.FromBase64("MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgsY5TBHM+9mLXGpFaPmrigl6+jl0XWzazxu1lbwb5KRahRANCAATqDP2L/xxSOlckG+j6oPHfzBE4WpmjA/YE9sP2rXpXW1qe9I/GJ7wjlOTXpqHUxQeBbps8jSvV+A7DzQqzjOst"), + algorithm: { + name: "ECDSA", + namedCurve: "P-256", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: ["sign"], + }, + publicKey: { + format: "spki", + data: pvtsutils.Convert.FromBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6gz9i/8cUjpXJBvo+qDx38wROFqZowP2BPbD9q16V1tanvSPxie8I5Tk16ah1MUHgW6bPI0r1fgOw80Ks4zrLQ=="), + algorithm: { + name: "ECDSA", + namedCurve: "P-256", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: ["verify"], + }, + }, + data: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]), + signature: pvtsutils.Convert.FromBase64("gsTh0IcWfzj3hjjourRgzTIsNa+wcDEDlKnkEA4Jv8ygLF2IDIOXpCD7ocCGo7xlSMGTme78CyrPqWGSz95mZg=="), + algorithm: { + name: "ECDSA", + hash: "SHA-256", + } as types.EcdsaParams, + }, + { + name: "K-256", + key: { + privateKey: { + format: "pkcs8", + data: pvtsutils.Convert.FromBase64("MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQg0h6+W+/4eFVP+i79hrzYeiEJ6UrveFYhuhoXRW+g/LGhRANCAASiJU6MaFN5fshUv6X5rCf/RjLQ0nAXj06gBdo3ruYiKZf8daAcYImniAq81PjF0j6eTwCy4bYbkyfBQtrtCTKR"), + algorithm: { + name: "ECDSA", + namedCurve: "K-256", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: ["sign"], + }, + publicKey: { + format: "spki", + data: pvtsutils.Convert.FromBase64("MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEoiVOjGhTeX7IVL+l+awn/0Yy0NJwF49OoAXaN67mIimX/HWgHGCJp4gKvNT4xdI+nk8AsuG2G5MnwULa7QkykQ=="), + algorithm: { + name: "ECDSA", + namedCurve: "K-256", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: ["verify"], + }, + }, + data: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]), + signature: pvtsutils.Convert.FromBase64("lqUTZHqf9v9KcOCw5r5wR1sCt9RPA0ONVW6vqejpoALehd6vtAb+ybVrDEtyUDpBFw9UIRIW6GnXRrAz4KaO4Q=="), + algorithm: { + name: "ECDSA", + hash: "SHA-256", + } as types.EcdsaParams, + }, + ], + deriveBits: [ + { + name: "P-256 128", + key: { + privateKey: { + format: "pkcs8", + data: pvtsutils.Convert.FromBase64("MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgQA7bkTNYlIYVb9+DavBlJ3b08f0892or3XwfscA3tLGhRANCAARzsy+ZcbrNchF7SrpL0hYnGp6ICX77jXUrpMYkq0BuzfaPFWcu9YZH5ASUzQJGz9eCK3mDXEbLCuiHRw3dwkFs"), + algorithm: { + name: "ECDH", + namedCurve: "P-256", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: ["deriveBits"], + }, + publicKey: { + format: "spki", + data: pvtsutils.Convert.FromBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEc7MvmXG6zXIRe0q6S9IWJxqeiAl++411K6TGJKtAbs32jxVnLvWGR+QElM0CRs/Xgit5g1xGywroh0cN3cJBbA=="), + algorithm: { + name: "ECDH", + namedCurve: "P-256", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: [], + }, + }, + data: pvtsutils.Convert.FromBase64("Jlc1/Zqi/8mH1oQT8+YfCA=="), + algorithm: { + name: "ECDH", + }, + length: 128, + }, + { + name: "P-384 192", + key: { + privateKey: { + format: "pkcs8", + data: pvtsutils.Convert.FromBase64("MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAFOXcWxQ+YqPdUqc9Iar3ZDf012ZtQAFajBMApKpd2WPQccBmyPzvDZJSWKe3d5jShZANiAAQ4Z43bP7d5fUFIBorLA1pBFTwDLb6XA7J871VUwyu64q8L5qidV7iBZK3P+9m7eMMQWm0drWPvrEszE+4jEsS4HIbBeuduBU+6R46Orv+V6VXU1hAXKSdMFZOCzdbDFlE="), + algorithm: { + name: "ECDH", + namedCurve: "P-384", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: ["deriveBits"], + }, + publicKey: { + format: "spki", + data: pvtsutils.Convert.FromBase64("MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEOGeN2z+3eX1BSAaKywNaQRU8Ay2+lwOyfO9VVMMruuKvC+aonVe4gWStz/vZu3jDEFptHa1j76xLMxPuIxLEuByGwXrnbgVPukeOjq7/lelV1NYQFyknTBWTgs3WwxZR"), + algorithm: { + name: "ECDH", + namedCurve: "P-384", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: [], + }, + }, + data: pvtsutils.Convert.FromBase64("2EKT/nmV68wIXFMZiCv4CyOEhWzpwdQ5"), + algorithm: { + name: "ECDH", + }, + length: 192, + }, + { + name: "P-521 256", + key: { + privateKey: { + format: "pkcs8", + data: pvtsutils.Convert.FromBase64("MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIB6PyCXpJ4TWPpwlGAmayLz5ecYHT+1ilxD64HytpTaViUS72sEzG1JMApD31+STX0zeVcARfG+yh71dXLCTlqqHGhgYkDgYYABADgIblBbth8vnOZt/HLU9VdUJHmenwRRADVZWL+P5IeCDQs6B87API41R3+91xFDHnjst9VKksYl/NJIIfl6b9cmABO6z80mTz3+0klquIpSQLidK2aFaFbqiGnMdCO+AZfwxu2qBx+1f5MwbHXUW5HXsfmEvzBUC9xCQKLpQ8oZYBrSg=="), + algorithm: { + name: "ECDH", + namedCurve: "P-521", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: ["deriveBits"], + }, + publicKey: { + format: "spki", + data: pvtsutils.Convert.FromBase64("MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQA4CG5QW7YfL5zmbfxy1PVXVCR5np8EUQA1WVi/j+SHgg0LOgfOwDyONUd/vdcRQx547LfVSpLGJfzSSCH5em/XJgATus/NJk89/tJJariKUkC4nStmhWhW6ohpzHQjvgGX8MbtqgcftX+TMGx11FuR17H5hL8wVAvcQkCi6UPKGWAa0o="), + algorithm: { + name: "ECDH", + namedCurve: "P-521", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: [], + }, + }, + data: pvtsutils.Convert.FromBase64("AS2ene28pmWYdJwW6dyTXUe1eq1p2i8QEIo/rXSiJRo="), + algorithm: { + name: "ECDH", + }, + length: 256, + }, + { + name: "K-256 128", + key: { + privateKey: { + format: "pkcs8", + data: pvtsutils.Convert.FromBase64("MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQghgLhhrz/EYuB0G08/UoM5nV9jS7Pl/rtIcXeJkc2b3uhRANCAARgMfEiAPcF7pmEuLRGRRFXEKSwcJwqURKK/Pqo8MaqU0cl7eNQmLJ7mFpBtTDY8hr9xxJeIP9sI/u83A1F5ag7"), + algorithm: { + name: "ECDH", + namedCurve: "K-256", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: ["deriveBits"], + }, + publicKey: { + format: "spki", + data: pvtsutils.Convert.FromBase64("MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEYDHxIgD3Be6ZhLi0RkURVxCksHCcKlESivz6qPDGqlNHJe3jUJiye5haQbUw2PIa/ccSXiD/bCP7vNwNReWoOw=="), + algorithm: { + name: "ECDH", + namedCurve: "K-256", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: [], + }, + }, + data: pvtsutils.Convert.FromBase64("3+2JX3D4/veBGJXnvU+aTg=="), + algorithm: { + name: "ECDH", + }, + length: 128, + }, + ], + deriveKey: [ + { + name: "P-256 128", + key: { + privateKey: { + format: "pkcs8", + data: pvtsutils.Convert.FromBase64("MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgQA7bkTNYlIYVb9+DavBlJ3b08f0892or3XwfscA3tLGhRANCAARzsy+ZcbrNchF7SrpL0hYnGp6ICX77jXUrpMYkq0BuzfaPFWcu9YZH5ASUzQJGz9eCK3mDXEbLCuiHRw3dwkFs"), + algorithm: { + name: "ECDH", + namedCurve: "P-256", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: ["deriveKey"], + }, + publicKey: { + format: "spki", + data: pvtsutils.Convert.FromBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEc7MvmXG6zXIRe0q6S9IWJxqeiAl++411K6TGJKtAbs32jxVnLvWGR+QElM0CRs/Xgit5g1xGywroh0cN3cJBbA=="), + algorithm: { + name: "ECDH", + namedCurve: "P-256", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: [], + }, + }, + algorithm: { + name: "ECDH", + }, + derivedKeyType: { + name: "AES-CBC", + length: 128, + } as types.AesKeyAlgorithm, + keyUsages: ["encrypt", "decrypt"], + format: "raw", + keyData: pvtsutils.Convert.FromBase64("Jlc1/Zqi/8mH1oQT8+YfCA=="), + }, + { + name: "P-384 192", + key: { + privateKey: { + format: "pkcs8", + data: pvtsutils.Convert.FromBase64("MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAFOXcWxQ+YqPdUqc9Iar3ZDf012ZtQAFajBMApKpd2WPQccBmyPzvDZJSWKe3d5jShZANiAAQ4Z43bP7d5fUFIBorLA1pBFTwDLb6XA7J871VUwyu64q8L5qidV7iBZK3P+9m7eMMQWm0drWPvrEszE+4jEsS4HIbBeuduBU+6R46Orv+V6VXU1hAXKSdMFZOCzdbDFlE="), + algorithm: { + name: "ECDH", + namedCurve: "P-384", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: ["deriveKey"], + }, + publicKey: { + format: "spki", + data: pvtsutils.Convert.FromBase64("MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEOGeN2z+3eX1BSAaKywNaQRU8Ay2+lwOyfO9VVMMruuKvC+aonVe4gWStz/vZu3jDEFptHa1j76xLMxPuIxLEuByGwXrnbgVPukeOjq7/lelV1NYQFyknTBWTgs3WwxZR"), + algorithm: { + name: "ECDH", + namedCurve: "P-384", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: [], + }, + }, + algorithm: { + name: "ECDH", + }, + derivedKeyType: { + name: "AES-GCM", + length: 192, + } as types.AesKeyAlgorithm, + keyUsages: ["encrypt", "decrypt"], + format: "raw", + keyData: pvtsutils.Convert.FromBase64("2EKT/nmV68wIXFMZiCv4CyOEhWzpwdQ5"), + }, + { + name: "P-521 256", + key: { + privateKey: { + format: "pkcs8", + data: pvtsutils.Convert.FromBase64("MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIB6PyCXpJ4TWPpwlGAmayLz5ecYHT+1ilxD64HytpTaViUS72sEzG1JMApD31+STX0zeVcARfG+yh71dXLCTlqqHGhgYkDgYYABADgIblBbth8vnOZt/HLU9VdUJHmenwRRADVZWL+P5IeCDQs6B87API41R3+91xFDHnjst9VKksYl/NJIIfl6b9cmABO6z80mTz3+0klquIpSQLidK2aFaFbqiGnMdCO+AZfwxu2qBx+1f5MwbHXUW5HXsfmEvzBUC9xCQKLpQ8oZYBrSg=="), + algorithm: { + name: "ECDH", + namedCurve: "P-521", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: ["deriveKey"], + }, + publicKey: { + format: "spki", + data: pvtsutils.Convert.FromBase64("MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQA4CG5QW7YfL5zmbfxy1PVXVCR5np8EUQA1WVi/j+SHgg0LOgfOwDyONUd/vdcRQx547LfVSpLGJfzSSCH5em/XJgATus/NJk89/tJJariKUkC4nStmhWhW6ohpzHQjvgGX8MbtqgcftX+TMGx11FuR17H5hL8wVAvcQkCi6UPKGWAa0o="), + algorithm: { + name: "ECDH", + namedCurve: "P-521", + } as types.EcKeyImportParams, + extractable: true, + keyUsages: [], + }, + }, + algorithm: { + name: "ECDH", + }, + derivedKeyType: { + name: "AES-CBC", + length: 256, + } as types.AesKeyAlgorithm, + keyUsages: ["encrypt", "decrypt"], + format: "raw", + keyData: pvtsutils.Convert.FromBase64("AS2ene28pmWYdJwW6dyTXUe1eq1p2i8QEIo/rXSiJRo="), + }, + ], + }, + }, + ]); + + it("sig", async () => { + const data = new Uint8Array(10); + const keys = await crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "brainpoolP512r1" }, false, ["sign", "verify"]); + const spki = await crypto.subtle.exportKey("spki", keys.publicKey); + + const signature = await crypto.subtle.sign({ ...keys.privateKey.algorithm, hash: "SHA-256" }, keys.privateKey, data); + const ok = await crypto.subtle.verify({ name: "ECDSA", hash: "SHA-256" }, keys.publicKey, signature, data); + assert.strictEqual(ok, true); + }); + +}); diff --git a/packages/web/test/ed.spec.ts b/packages/web/test/ed.spec.ts new file mode 100644 index 0000000..9aae34c --- /dev/null +++ b/packages/web/test/ed.spec.ts @@ -0,0 +1,79 @@ +import { Crypto as NodeCrypto } from "@peculiar/webcrypto"; +import { Crypto as WebCrypto } from "@peculiar/webcrypto-web"; +import * as assert from "assert"; + +const nodeCrypto = new NodeCrypto(); +const webCrypto = new WebCrypto(); + +context("ED", () => { + + context("generate/export/import/sign/verify", () => { + const alg = { name: "EdDSA", namedCurve: "Ed25519" }; + const data = Buffer.from("Some message to sign"); + + it("pkcs8/spki", async () => { + const linerKeys = await webCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const pkcs8 = await webCrypto.subtle.exportKey("pkcs8", linerKeys.privateKey); + const spki = await webCrypto.subtle.exportKey("spki", linerKeys.publicKey); + + const nodePrivateKey = await nodeCrypto.subtle.importKey("pkcs8", pkcs8, alg, false, ["sign"]); + const nodePublicKey = await nodeCrypto.subtle.importKey("spki", spki, alg, false, ["verify"]); + const linerPrivateKey = await webCrypto.subtle.importKey("pkcs8", pkcs8, alg, false, ["sign"]); + const linerPublicKey = await webCrypto.subtle.importKey("spki", spki, alg, false, ["verify"]); + + const nodeSignature = await nodeCrypto.subtle.sign(alg, nodePrivateKey, data); + const linerSignature = await webCrypto.subtle.sign(alg, linerPrivateKey, data); + + assert.strictEqual(Buffer.from(linerSignature).toString("hex"), Buffer.from(nodeSignature).toString("hex")); + + const nodeOk = await nodeCrypto.subtle.verify(alg, nodePublicKey, nodeSignature, data); + const linerOk = await webCrypto.subtle.verify(alg, linerPublicKey, nodeSignature, data); + + assert.strictEqual(linerOk, nodeOk); + }); + + it("jwk", async () => { + const linerKeys = await webCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const privateJwk = await webCrypto.subtle.exportKey("jwk", linerKeys.privateKey); + const publicJwk = await webCrypto.subtle.exportKey("jwk", linerKeys.publicKey); + + const nodePrivateKey = await nodeCrypto.subtle.importKey("jwk", privateJwk, alg, false, ["sign"]); + const nodePublicKey = await nodeCrypto.subtle.importKey("jwk", publicJwk, alg, false, ["verify"]); + const linerPrivateKey = await webCrypto.subtle.importKey("jwk", privateJwk, alg, false, ["sign"]); + const linerPublicKey = await webCrypto.subtle.importKey("jwk", publicJwk, alg, false, ["verify"]); + + const nodeSignature = await nodeCrypto.subtle.sign(alg, nodePrivateKey, data); + const linerSignature = await webCrypto.subtle.sign(alg, linerPrivateKey, data); + + assert.strictEqual(Buffer.from(linerSignature).toString("hex"), Buffer.from(nodeSignature).toString("hex")); + + const nodeOk = await nodeCrypto.subtle.verify(alg, nodePublicKey, nodeSignature, data); + const linerOk = await webCrypto.subtle.verify(alg, linerPublicKey, nodeSignature, data); + + assert.strictEqual(linerOk, nodeOk); + }); + + it("pkcs8/raw", async () => { + const linerKeys = await webCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const pkcs8 = await webCrypto.subtle.exportKey("pkcs8", linerKeys.privateKey); + const raw = await webCrypto.subtle.exportKey("raw", linerKeys.publicKey); + + const nodePrivateKey = await nodeCrypto.subtle.importKey("pkcs8", pkcs8, alg, false, ["sign"]); + const nodePublicKey = await nodeCrypto.subtle.importKey("raw", raw, alg, false, ["verify"]); + const linerPrivateKey = await webCrypto.subtle.importKey("pkcs8", pkcs8, alg, false, ["sign"]); + const linerPublicKey = await webCrypto.subtle.importKey("raw", raw, alg, false, ["verify"]); + + const nodeSignature = await nodeCrypto.subtle.sign(alg, nodePrivateKey, data); + const linerSignature = await webCrypto.subtle.sign(alg, linerPrivateKey, data); + + assert.strictEqual(Buffer.from(linerSignature).toString("hex"), Buffer.from(nodeSignature).toString("hex")); + + const nodeOk = await nodeCrypto.subtle.verify(alg, nodePublicKey, nodeSignature, data); + const linerOk = await webCrypto.subtle.verify(alg, linerPublicKey, nodeSignature, data); + + assert.strictEqual(linerOk, nodeOk); + }); + + }); + +}); diff --git a/packages/web/test/hmac.spec.ts b/packages/web/test/hmac.spec.ts new file mode 100644 index 0000000..bade0c2 --- /dev/null +++ b/packages/web/test/hmac.spec.ts @@ -0,0 +1,175 @@ +import * as types from "@peculiar/webcrypto-types"; +import { Convert } from "pvtsutils"; +import { ITestGenerateKeyAction, testCrypto, webCrypto } from "./utils"; + +context("HMAC", () => { + + testCrypto(webCrypto, [ + { + name: "HMAC", + actions: { + generateKey: [ + { + name: "default length", + algorithm: { + name: "HMAC", + hash: "SHA-256", + } as types.HmacKeyGenParams, + extractable: true, + keyUsages: ["sign", "verify"], + }, + ...["SHA-1", "SHA-256", "SHA-384", "SHA-512"].map((hash) => { + return { + name: hash, + algorithm: { + name: "HMAC", + hash, + length: 128, + }, + extractable: true, + keyUsages: ["sign", "verify"], + } as ITestGenerateKeyAction; + }), + { + name: "length:160", + algorithm: { + name: "HMAC", + hash: "SHA-256", + length: 160, + }, + extractable: true, + keyUsages: ["sign", "verify"], + } as ITestGenerateKeyAction, + ], + sign: [ + { + name: "HMAC-SHA256 with length param which is less than hash size", + key: { + format: "raw", + data: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6]), + algorithm: { + name: "HMAC", + hash: "SHA-256", + length: 128, + } as types.HmacImportParams, + extractable: false, + keyUsages: ["sign", "verify"], + }, + algorithm: { name: "HMAC" }, + data: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]), + signature: Convert.FromBase64("9yMF9ReX1EhdBWTRjSR+AC21NA05H9W8vx0HZGVmgNc="), + }, + { + name: "HMAC-SHA256 without length param", + key: { + format: "raw", + data: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6]), + algorithm: { + name: "HMAC", + hash: "SHA-256", + } as types.HmacImportParams, + extractable: false, + keyUsages: ["sign", "verify"], + }, + algorithm: { name: "HMAC" }, + data: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]), + signature: Convert.FromHex("ad05febab44cd369e27433bbf00e63e6271f6a350614bec453f5d0efd6503a31"), + }, + ], + import: [ + { // JWK SHA-1 + name: "JWK SHA-1", + format: "jwk", + data: { + alg: "HS1", + ext: true, + k: "AQIDBAUGBwgJAAECAwQFBg", + key_ops: ["sign", "verify"], + kty: "oct", + }, + algorithm: { + name: "HMAC", + hash: "SHA-1", + length: 128, + } as types.HmacImportParams, + extractable: true, + keyUsages: ["sign", "verify"], + }, + { // JWK SHA-256 + name: "JWK SHA-256", + format: "jwk", + data: { + alg: "HS256", + ext: true, + k: "AQIDBAUGBwgJAAECAwQFBg", + key_ops: ["sign", "verify"], + kty: "oct", + }, + algorithm: { + name: "HMAC", + hash: "SHA-256", + } as types.HmacImportParams, + extractable: true, + keyUsages: ["sign", "verify"], + }, + { // JWK SHA-384 + name: "JWK SHA-384", + format: "jwk", + data: { + alg: "HS384", + ext: true, + k: "AQIDBAUGBwgJAAECAwQFBg", + key_ops: ["sign", "verify"], + kty: "oct", + }, + algorithm: { + name: "HMAC", + hash: "SHA-384", + } as types.HmacImportParams, + extractable: true, + keyUsages: ["sign", "verify"], + }, + { // JWK SHA-512 + name: "JWK SHA-512", + format: "jwk", + data: { + alg: "HS512", + ext: true, + k: "AQIDBAUGBwgJAAECAwQFBg", + key_ops: ["sign", "verify"], + kty: "oct", + }, + algorithm: { + name: "HMAC", + hash: "SHA-512", + } as types.HmacImportParams, + extractable: true, + keyUsages: ["sign", "verify"], + }, + { // raw 128 + name: "raw 128", + format: "raw", + data: Convert.FromBase64Url("AQIDBAUGBwgJAAECAwQFBg"), + algorithm: { + name: "HMAC", + hash: "SHA-512", + } as types.HmacImportParams, + extractable: true, + keyUsages: ["sign", "verify"], + }, + { // raw 160 + name: "raw 160", + format: "raw", + data: new Uint8Array(20), + algorithm: { + name: "HMAC", + hash: "SHA-512", + } as types.HmacImportParams, + extractable: true, + keyUsages: ["sign", "verify"], + }, + ], + }, + }, + ]); +}); diff --git a/packages/web/test/pbkdf.spec.ts b/packages/web/test/pbkdf.spec.ts new file mode 100644 index 0000000..dd07fa9 --- /dev/null +++ b/packages/web/test/pbkdf.spec.ts @@ -0,0 +1,61 @@ +import * as types from "@peculiar/webcrypto-types"; +import * as pvtsutils from "pvtsutils"; +import { testCrypto, webCrypto } from "./utils"; + +context("PBKDF", () => { + + testCrypto(webCrypto, [ + { + name: "PBKDF2", + actions: { + deriveBits: [ + { + key: { + format: "raw", + data: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]), + algorithm: { + name: "PBKDF2", + }, + extractable: false, + keyUsages: ["deriveBits"], + }, + algorithm: { + name: "PBKDF2", + salt: new Uint8Array([1, 2, 3, 4]), + hash: "SHA-256", + iterations: 1000, + } as types.Pbkdf2Params, + data: pvtsutils.Convert.FromBase64("3GK58/4RT+UPLooz5HT1MQ=="), + length: 128, + }, + ], + deriveKey: [ + { + key: { + format: "raw", + data: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]), + algorithm: { + name: "PBKDF2", + }, + extractable: false, + keyUsages: ["deriveKey"], + }, + algorithm: { + name: "PBKDF2", + salt: new Uint8Array([1, 2, 3, 4]), + hash: "SHA-256", + iterations: 1000, + } as types.Pbkdf2Params, + derivedKeyType: { + name: "AES-CBC", + length: 128, + } as types.AesDerivedKeyParams, + keyUsages: ["encrypt"], + format: "raw", + keyData: pvtsutils.Convert.FromBase64("3GK58/4RT+UPLooz5HT1MQ=="), + }, + ], + }, + }, + ]); +}); diff --git a/packages/web/test/rsa.spec.ts b/packages/web/test/rsa.spec.ts new file mode 100644 index 0000000..8868eda --- /dev/null +++ b/packages/web/test/rsa.spec.ts @@ -0,0 +1,420 @@ +import * as types from "@peculiar/webcrypto-types"; +import * as pvtsutils from "pvtsutils"; +import { Browser } from "../src/helper"; +import { browser, ITestGenerateKeyAction, testCrypto, webCrypto } from "./utils"; + +context("RSA", () => { + + testCrypto(webCrypto, [ + // RSASSA-PKCS1-v1_5 + { + name: "RSASSA-PKCS1-v1_5", + actions: { + generateKey: (() => { + const res: ITestGenerateKeyAction[] = []; + ["SHA-1", "SHA-256"].forEach((hash) => + ["SHA-1", "SHA-256", "SHA-512"].forEach((hash) => + [new Uint8Array([3]), new Uint8Array([1, 0, 1])].forEach((publicExponent) => + [1024, 2048].forEach((modulusLength) => { + res.push({ + name: `h:${hash} e:${pvtsutils.Convert.ToHex(publicExponent)} n:${modulusLength}`, + skip: false, + algorithm: { + name: "RSASSA-PKCS1-v1_5", + hash, + publicExponent, + modulusLength, + } as types.RsaHashedKeyGenParams, + extractable: false, + keyUsages: ["sign", "verify"], + } as ITestGenerateKeyAction); + }), + ), + ), + ); + return res; + })(), + sign: [ + { + name: "SHA-256, e:010001, n:2048", + algorithm: { + name: "RSASSA-PKCS1-v1_5", + }, + data: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]), + signature: pvtsutils.Convert.FromBase64("f8OvbYnwX5YPVPjWkOTalYTFJjS1Ks7iNmPdLEby/kK6BEGk5uPvY/ebcok6sTQpQXJXJFJbOcMrZftmJXpm1szcgOdNgVW6FDc3722a9Mzvk/YfvNUCQRNEMON9lYKdpOLSXAFpXR5ovZytbFQ2w2ztpKkJvNY2QZQlizcZKSg="), + key: { + publicKey: { + format: "jwk", + algorithm: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" } as types.RsaHashedImportParams, + data: { + alg: "RS256", + e: "AQAB", + ext: true, + key_ops: ["verify"], + kty: "RSA", + n: "vqpvdxuyZ6rKYnWTj_ZzDBFZAAAlpe5hpoiYHqa2j5kK7v8U5EaPY2bLib9m4B40j-n3FV9xUCGiplWdqMJJKT-4PjGO5E3S4N9kjFhu57noYT7z7302J0sJXeoFbXxlgE-4G55Oxlm52ID2_RJesP5nzcGTriQwoRbrJP5OEt0", + }, + extractable: true, + keyUsages: ["verify"], + }, + privateKey: { + format: "jwk", + algorithm: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" } as types.RsaHashedImportParams, + data: { + alg: "RS256", + d: "YZzAFCqJ26kElAO92CZEIBmBhw6MN7cjJy8nMgoHzNx9TH4rI_M71Zf6_DqRYIwWPNd7N-X1DSErNB0A6jUNXr42l3ChBsBB31vjHqQKx95-M6iXVgjJFTzxirNjUuCm_skFYIcXS5oEaXjy5XI3dT8KAEf1M2UA6__LwGrAD8E", + dp: "pOolqL7HwnmWLn7GDX8zGkm0Q1IAj-ouBL7ZZbaTm3wETLtwu-dGsQheEdzP_mfL_CTiCAwGuQBcSItimD0DdQ", + dq: "FTSY59AnkgmB7TsErWNBE3xlVB_pMpE2xWyCBCz96gyDOUOFDz8vlSV-clhjawJeRd1n30nZOPSBtOHozhwZmQ", + e: "AQAB", + ext: true, + key_ops: ["sign"], + kty: "RSA", + n: "vqpvdxuyZ6rKYnWTj_ZzDBFZAAAlpe5hpoiYHqa2j5kK7v8U5EaPY2bLib9m4B40j-n3FV9xUCGiplWdqMJJKT-4PjGO5E3S4N9kjFhu57noYT7z7302J0sJXeoFbXxlgE-4G55Oxlm52ID2_RJesP5nzcGTriQwoRbrJP5OEt0", + p: "6jFtmBJJQFIlQUXXZYIgvH70Y9a03oWKjNuF2veb5Zf09EtLNE86NpnIm463OnoHJPW0m8wHFXZZfcYVTIPR_w", + q: "0GttDMl1kIzSV2rNzGXpOS8tUqr5Lz0EtVZwIb9GJPMmJ0P3gZ801zEgZZ4-esU7cLUf-BSZEAmfnKA80G2jIw", + qi: "FByTxX4G2eXkk1xe0IuiEv7I5NS-CnFyp8iB4XLG0rabnfcIZFKpf__X0sNyVOAVo5-jJMuUYjCRTdaXNAWhkg", + }, + extractable: true, + keyUsages: ["sign"], + }, + }, + }, + { + name: "SHA-1 e:03 n:1024", + algorithm: { + name: "RSASSA-PKCS1-v1_5", + }, + data: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]), + signature: pvtsutils.Convert.FromHex("2f4cab4f67ca544934e462fd324ea0b52f9040f1453c8c425e818411bf54c3c0cd1d7f2a1d04a820ce28fec996b94a0971d481ec8adee2ee0d8b003c2cb75862d7699a73b798d7fab788956ae17388fed764e7a1a944abf9799534b66e830a5c5f4ea7253b937af6b4fcbd11310da3daebf1f3181041bdd550cbe4ea8ff2e1ed"), + key: { + publicKey: { + format: "spki", + algorithm: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-1" } as types.Algorithm, + data: pvtsutils.Convert.FromBase64("MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDL51DUp2Jxqjr18k5mpAvFBzTLtzK4qL6Pq8H4nXU+8gheGYP2+Vi3J+PSLVTIKk7jPNJ2gQtgnA27TNZxYA0QplEyxq0WQwTMp8vz/PAJYjsLNx8O4g433Ve60dUzZWjjbawX8JeggET37m2EoCsgHXJPe3puloMfD0qRR3BoZwIBAw=="), + extractable: true, + keyUsages: ["verify"], + }, + privateKey: { + format: "pkcs8", + algorithm: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-1" } as types.Algorithm, + data: pvtsutils.Convert.FromBase64("MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMvnUNSnYnGqOvXyTmakC8UHNMu3Mriovo+rwfiddT7yCF4Zg/b5WLcn49ItVMgqTuM80naBC2CcDbtM1nFgDRCmUTLGrRZDBMyny/P88AliOws3Hw7iDjfdV7rR1TNlaONtrBfwl6CARPfubYSgKyAdck97em6Wgx8PSpFHcGhnAgEDAoGAIfvizhvlvZxfKP23u8YB9iveIfPdyXF1F/H1qW+Tin2sD67rU9Q5c9v7TbI4zAcNJd94aRWB5W9Xnzd5EuVXgnnU/wz54Bk6zXMLq/L6oouSLzcRVwz0riaXBa007OTejfS+jVhCAlMM4hqYnCxrRr4BBIEi+WidyHKSs8ynSE8CQQD9BRizPsw8eZXDcJz1TVrNYVk4ZGgWfmgGkdyeSh2A5Smdcmvzcm32dNVH9fqL9P33qoJUw+CoSRKuEB/szIjjAkEAzk4fxZMJbypmMhVPVcLfT2yWtFKcfdO67zu8JE2Ih0xmE8Jb65kkl4LWBuPhCbJ5scGyH+S1eodZsco6jrgtrQJBAKiuEHd/MtL7uSz1vfjePIjrkNBC8A7+8ARhPb7cE6tDcROhnUz28/mjONqj/F1N/qUcVuMtQHAwtx61ap3dsJcCQQCJiWqDt1ufcZl2uN+Ogeo08w8i4b2pN9H00n1tiQWviEQNLD1Hu226VzlZ7UCxIaZ2gSFqmHj8WjvL3CcJ0B5zAkEAlmRgnALghAcJ/WfTMphPKJXhY+H+CgkeE3si2ZgPW1YaDAyhp/xdQabkgbFy70Nq32fuJyxDDS4WhF0aOYz6pw=="), + extractable: true, + keyUsages: ["sign"], + }, + }, + }, + ], + import: [ + { // public key JWK + name: "public key JWK", + format: "jwk", + algorithm: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" } as types.Algorithm, + data: { + alg: "RS256", + e: "AQAB", + ext: true, + key_ops: ["verify"], + kty: "RSA", + n: "vqpvdxuyZ6rKYnWTj_ZzDBFZAAAlpe5hpoiYHqa2j5kK7v8U5EaPY2bLib9m4B40j-n3FV9xUCGiplWdqMJJKT-4PjGO5E3S4N9kjFhu57noYT7z7302J0sJXeoFbXxlgE-4G55Oxlm52ID2_RJesP5nzcGTriQwoRbrJP5OEt0", + }, + extractable: true, + keyUsages: ["verify"], + }, + { // public key SPKI + name: "public key SPKI", + format: "spki", + algorithm: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" } as types.Algorithm, + data: pvtsutils.Convert.FromBase64("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+qm93G7JnqspidZOP9nMMEVkAACWl7mGmiJgepraPmQru/xTkRo9jZsuJv2bgHjSP6fcVX3FQIaKmVZ2owkkpP7g+MY7kTdLg32SMWG7nuehhPvPvfTYnSwld6gVtfGWAT7gbnk7GWbnYgPb9El6w/mfNwZOuJDChFusk/k4S3QIDAQAB"), + extractable: true, + keyUsages: ["verify"], + }, + { // private key JWK + name: "private key JWK", + format: "jwk", + algorithm: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" } as types.Algorithm, + data: { + alg: "RS256", + d: "YZzAFCqJ26kElAO92CZEIBmBhw6MN7cjJy8nMgoHzNx9TH4rI_M71Zf6_DqRYIwWPNd7N-X1DSErNB0A6jUNXr42l3ChBsBB31vjHqQKx95-M6iXVgjJFTzxirNjUuCm_skFYIcXS5oEaXjy5XI3dT8KAEf1M2UA6__LwGrAD8E", + dp: "pOolqL7HwnmWLn7GDX8zGkm0Q1IAj-ouBL7ZZbaTm3wETLtwu-dGsQheEdzP_mfL_CTiCAwGuQBcSItimD0DdQ", + dq: "FTSY59AnkgmB7TsErWNBE3xlVB_pMpE2xWyCBCz96gyDOUOFDz8vlSV-clhjawJeRd1n30nZOPSBtOHozhwZmQ", + e: "AQAB", + ext: true, + key_ops: ["sign"], + kty: "RSA", + n: "vqpvdxuyZ6rKYnWTj_ZzDBFZAAAlpe5hpoiYHqa2j5kK7v8U5EaPY2bLib9m4B40j-n3FV9xUCGiplWdqMJJKT-4PjGO5E3S4N9kjFhu57noYT7z7302J0sJXeoFbXxlgE-4G55Oxlm52ID2_RJesP5nzcGTriQwoRbrJP5OEt0", + p: "6jFtmBJJQFIlQUXXZYIgvH70Y9a03oWKjNuF2veb5Zf09EtLNE86NpnIm463OnoHJPW0m8wHFXZZfcYVTIPR_w", + q: "0GttDMl1kIzSV2rNzGXpOS8tUqr5Lz0EtVZwIb9GJPMmJ0P3gZ801zEgZZ4-esU7cLUf-BSZEAmfnKA80G2jIw", + qi: "FByTxX4G2eXkk1xe0IuiEv7I5NS-CnFyp8iB4XLG0rabnfcIZFKpf__X0sNyVOAVo5-jJMuUYjCRTdaXNAWhkg", + }, + extractable: true, + keyUsages: ["sign"], + }, + { + skip: browser.name === Browser.Edge, // Edge returns PKCS8 with KeyUsages extension + name: "private key pkcs8", + format: "pkcs8", + algorithm: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" } as types.Algorithm, + data: pvtsutils.Convert.FromBase64("MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAL6qb3cbsmeqymJ1k4/2cwwRWQAAJaXuYaaImB6mto+ZCu7/FORGj2Nmy4m/ZuAeNI/p9xVfcVAhoqZVnajCSSk/uD4xjuRN0uDfZIxYbue56GE+8+99NidLCV3qBW18ZYBPuBueTsZZudiA9v0SXrD+Z83Bk64kMKEW6yT+ThLdAgMBAAECgYACR4hYnLCn059iyPQQKwqaENUHDnlkv/JT6tsitqyFD/fU/qCxz/Qj5JU3Wt3wfPv04n+tNjxlEFng8jIV0+jK+6jlqkd0AcfquIkrEMdY/GET5F41UQ9JOIXWvLwNJ7nMLvD0Eucf9AzxuQ3hw6e+CquDsRusZaiYAYlW+hHA4wJBAOoxbZgSSUBSJUFF12WCILx+9GPWtN6Fiozbhdr3m+WX9PRLSzRPOjaZyJuOtzp6ByT1tJvMBxV2WX3GFUyD0f8CQQDQa20MyXWQjNJXas3MZek5Ly1SqvkvPQS1VnAhv0Yk8yYnQ/eBnzTXMSBlnj56xTtwtR/4FJkQCZ+coDzQbaMjAkEApOolqL7HwnmWLn7GDX8zGkm0Q1IAj+ouBL7ZZbaTm3wETLtwu+dGsQheEdzP/mfL/CTiCAwGuQBcSItimD0DdQJAFTSY59AnkgmB7TsErWNBE3xlVB/pMpE2xWyCBCz96gyDOUOFDz8vlSV+clhjawJeRd1n30nZOPSBtOHozhwZmQJAFByTxX4G2eXkk1xe0IuiEv7I5NS+CnFyp8iB4XLG0rabnfcIZFKpf//X0sNyVOAVo5+jJMuUYjCRTdaXNAWhkg=="), + extractable: true, + keyUsages: ["sign"], + }, + { + name: "pkcs8 e:03 n:1024", + skip: browser.name === Browser.Edge, + format: "pkcs8", + algorithm: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-1" } as types.Algorithm, + data: pvtsutils.Convert.FromBase64("MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMvnUNSnYnGqOvXyTmakC8UHNMu3Mriovo+rwfiddT7yCF4Zg/b5WLcn49ItVMgqTuM80naBC2CcDbtM1nFgDRCmUTLGrRZDBMyny/P88AliOws3Hw7iDjfdV7rR1TNlaONtrBfwl6CARPfubYSgKyAdck97em6Wgx8PSpFHcGhnAgEDAoGAIfvizhvlvZxfKP23u8YB9iveIfPdyXF1F/H1qW+Tin2sD67rU9Q5c9v7TbI4zAcNJd94aRWB5W9Xnzd5EuVXgnnU/wz54Bk6zXMLq/L6oouSLzcRVwz0riaXBa007OTejfS+jVhCAlMM4hqYnCxrRr4BBIEi+WidyHKSs8ynSE8CQQD9BRizPsw8eZXDcJz1TVrNYVk4ZGgWfmgGkdyeSh2A5Smdcmvzcm32dNVH9fqL9P33qoJUw+CoSRKuEB/szIjjAkEAzk4fxZMJbypmMhVPVcLfT2yWtFKcfdO67zu8JE2Ih0xmE8Jb65kkl4LWBuPhCbJ5scGyH+S1eodZsco6jrgtrQJBAKiuEHd/MtL7uSz1vfjePIjrkNBC8A7+8ARhPb7cE6tDcROhnUz28/mjONqj/F1N/qUcVuMtQHAwtx61ap3dsJcCQQCJiWqDt1ufcZl2uN+Ogeo08w8i4b2pN9H00n1tiQWviEQNLD1Hu226VzlZ7UCxIaZ2gSFqmHj8WjvL3CcJ0B5zAkEAlmRgnALghAcJ/WfTMphPKJXhY+H+CgkeE3si2ZgPW1YaDAyhp/xdQabkgbFy70Nq32fuJyxDDS4WhF0aOYz6pw=="), + extractable: true, + keyUsages: ["sign"], + }, + { + name: "spki e:03 n:1024", + format: "spki", + algorithm: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-1" } as types.Algorithm, + data: pvtsutils.Convert.FromBase64("MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDL51DUp2Jxqjr18k5mpAvFBzTLtzK4qL6Pq8H4nXU+8gheGYP2+Vi3J+PSLVTIKk7jPNJ2gQtgnA27TNZxYA0QplEyxq0WQwTMp8vz/PAJYjsLNx8O4g433Ve60dUzZWjjbawX8JeggET37m2EoCsgHXJPe3puloMfD0qRR3BoZwIBAw=="), + extractable: true, + keyUsages: ["verify"], + }, + ], + }, + }, + // RSA-PSS + { + name: "RSA-PSS", + actions: { + generateKey: ["SHA-1", "SHA-256", "SHA-384", "SHA-512"].map((hash) => { + return { + name: hash, + algorithm: { + name: "RSA-PSS", + hash, + publicExponent: new Uint8Array([1, 0, 1]), + modulusLength: 1024, + } as types.RsaHashedKeyGenParams, + extractable: false, + keyUsages: ["sign", "verify"], + } as ITestGenerateKeyAction; + }), + sign: [ + { + algorithm: { + name: "RSA-PSS", + saltLength: 64, + } as types.RsaPssParams, + data: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]), + signature: pvtsutils.Convert.FromBase64("OYz/7fv71ELOs5kuz5IiYq1NsXuOazl22xqIFjiY++hYFzJMWaR+ZI0WPoMOifvb1PNKmdQ4dY+QbpYC1vdzlAKfkLe22l5htLyQaXzjD/yeMZYrL0KmrabC9ayL6bxrMW+ccePStkbrF1Jn0LT09l22aX/r1y3SPrl0b+zwo/Q="), + key: { + publicKey: { + format: "jwk", + algorithm: { name: "RSA-PSS", hash: "SHA-256" } as types.Algorithm, + data: { + alg: "PS256", + e: "AQAB", + ext: true, + key_ops: ["verify"], + kty: "RSA", + n: "vqpvdxuyZ6rKYnWTj_ZzDBFZAAAlpe5hpoiYHqa2j5kK7v8U5EaPY2bLib9m4B40j-n3FV9xUCGiplWdqMJJKT-4PjGO5E3S4N9kjFhu57noYT7z7302J0sJXeoFbXxlgE-4G55Oxlm52ID2_RJesP5nzcGTriQwoRbrJP5OEt0", + }, + extractable: true, + keyUsages: ["verify"], + }, + privateKey: { + format: "jwk", + algorithm: { name: "RSA-PSS", hash: "SHA-256" } as types.Algorithm, + data: { + alg: "PS256", + d: "YZzAFCqJ26kElAO92CZEIBmBhw6MN7cjJy8nMgoHzNx9TH4rI_M71Zf6_DqRYIwWPNd7N-X1DSErNB0A6jUNXr42l3ChBsBB31vjHqQKx95-M6iXVgjJFTzxirNjUuCm_skFYIcXS5oEaXjy5XI3dT8KAEf1M2UA6__LwGrAD8E", + dp: "pOolqL7HwnmWLn7GDX8zGkm0Q1IAj-ouBL7ZZbaTm3wETLtwu-dGsQheEdzP_mfL_CTiCAwGuQBcSItimD0DdQ", + dq: "FTSY59AnkgmB7TsErWNBE3xlVB_pMpE2xWyCBCz96gyDOUOFDz8vlSV-clhjawJeRd1n30nZOPSBtOHozhwZmQ", + e: "AQAB", + ext: true, + key_ops: ["sign"], + kty: "RSA", + n: "vqpvdxuyZ6rKYnWTj_ZzDBFZAAAlpe5hpoiYHqa2j5kK7v8U5EaPY2bLib9m4B40j-n3FV9xUCGiplWdqMJJKT-4PjGO5E3S4N9kjFhu57noYT7z7302J0sJXeoFbXxlgE-4G55Oxlm52ID2_RJesP5nzcGTriQwoRbrJP5OEt0", + p: "6jFtmBJJQFIlQUXXZYIgvH70Y9a03oWKjNuF2veb5Zf09EtLNE86NpnIm463OnoHJPW0m8wHFXZZfcYVTIPR_w", + q: "0GttDMl1kIzSV2rNzGXpOS8tUqr5Lz0EtVZwIb9GJPMmJ0P3gZ801zEgZZ4-esU7cLUf-BSZEAmfnKA80G2jIw", + qi: "FByTxX4G2eXkk1xe0IuiEv7I5NS-CnFyp8iB4XLG0rabnfcIZFKpf__X0sNyVOAVo5-jJMuUYjCRTdaXNAWhkg", + }, + extractable: true, + keyUsages: ["sign"], + }, + }, + }, + ], + }, + }, + // RSA-OAEP + { + name: "RSA-OAEP", + actions: { + generateKey: ["SHA-1", "SHA-256", "SHA-384", "SHA-512"].map((hash) => { + return { + name: hash, + algorithm: { + name: "RSA-OAEP", + hash, + publicExponent: new Uint8Array([1, 0, 1]), + modulusLength: 1024, + } as types.RsaHashedKeyGenParams, + extractable: false, + keyUsages: ["encrypt", "decrypt"], + } as ITestGenerateKeyAction; + }), + encrypt: [ + { + name: "with label", + algorithm: { + name: "RSA-OAEP", + label: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]), + } as types.RsaOaepParams, + data: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]), + encData: pvtsutils.Convert.FromBase64("aHu8PBZuctYecfINKgUdB8gBoLyUUFxTZDTzTHUk9KKxtYywYml48HoijBG5DyaIWUUbOIdPgap9C8pFG2iYShQnE9Aj3gzKLHacBbFw1P79+Ei/Tm0j/THiXqCplBZC4dIp4jhTDepmdrlXZcY0slmjG+h8h8TpSmWKP3pEGGk="), + key: { + publicKey: { + format: "jwk", + algorithm: { name: "RSA-OAEP", hash: "SHA-256" } as types.Algorithm, + data: { + alg: "RSA-OAEP-256", + e: "AQAB", + ext: true, + key_ops: ["encrypt"], + kty: "RSA", + n: "vqpvdxuyZ6rKYnWTj_ZzDBFZAAAlpe5hpoiYHqa2j5kK7v8U5EaPY2bLib9m4B40j-n3FV9xUCGiplWdqMJJKT-4PjGO5E3S4N9kjFhu57noYT7z7302J0sJXeoFbXxlgE-4G55Oxlm52ID2_RJesP5nzcGTriQwoRbrJP5OEt0", + }, + extractable: true, + keyUsages: ["encrypt"], + }, + privateKey: { + format: "jwk", + algorithm: { name: "RSA-OAEP", hash: "SHA-256" } as types.Algorithm, + data: { + alg: "RSA-OAEP-256", + d: "YZzAFCqJ26kElAO92CZEIBmBhw6MN7cjJy8nMgoHzNx9TH4rI_M71Zf6_DqRYIwWPNd7N-X1DSErNB0A6jUNXr42l3ChBsBB31vjHqQKx95-M6iXVgjJFTzxirNjUuCm_skFYIcXS5oEaXjy5XI3dT8KAEf1M2UA6__LwGrAD8E", + dp: "pOolqL7HwnmWLn7GDX8zGkm0Q1IAj-ouBL7ZZbaTm3wETLtwu-dGsQheEdzP_mfL_CTiCAwGuQBcSItimD0DdQ", + dq: "FTSY59AnkgmB7TsErWNBE3xlVB_pMpE2xWyCBCz96gyDOUOFDz8vlSV-clhjawJeRd1n30nZOPSBtOHozhwZmQ", + e: "AQAB", + ext: true, + key_ops: ["decrypt"], + kty: "RSA", + n: "vqpvdxuyZ6rKYnWTj_ZzDBFZAAAlpe5hpoiYHqa2j5kK7v8U5EaPY2bLib9m4B40j-n3FV9xUCGiplWdqMJJKT-4PjGO5E3S4N9kjFhu57noYT7z7302J0sJXeoFbXxlgE-4G55Oxlm52ID2_RJesP5nzcGTriQwoRbrJP5OEt0", + p: "6jFtmBJJQFIlQUXXZYIgvH70Y9a03oWKjNuF2veb5Zf09EtLNE86NpnIm463OnoHJPW0m8wHFXZZfcYVTIPR_w", + q: "0GttDMl1kIzSV2rNzGXpOS8tUqr5Lz0EtVZwIb9GJPMmJ0P3gZ801zEgZZ4-esU7cLUf-BSZEAmfnKA80G2jIw", + qi: "FByTxX4G2eXkk1xe0IuiEv7I5NS-CnFyp8iB4XLG0rabnfcIZFKpf__X0sNyVOAVo5-jJMuUYjCRTdaXNAWhkg", + }, + extractable: true, + keyUsages: ["decrypt"], + }, + }, + }, + { + name: "without label", + algorithm: { + name: "RSA-OAEP", + } as types.RsaOaepParams, + data: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]), + encData: pvtsutils.Convert.FromBase64("d91eZMLqHTOIGC+GqfSj13x8aQHkTKqxImwmybFFpR/00n5y4e7tL7XX49izZO/wwgCYkDCentX7BGoPhOv/4RhW9vVlfrjFAFdwZFAOFlumt+9jp2QjBDnwxuoCO/IOhjFFf7rq5hTBUB9eoHsSMp42LA6F/Q3IuxFLaejOWGw="), + key: { + publicKey: { + format: "jwk", + algorithm: { name: "RSA-OAEP", hash: "SHA-256" } as types.Algorithm, + data: { + alg: "RSA-OAEP-256", + e: "AQAB", + ext: true, + key_ops: ["encrypt"], + kty: "RSA", + n: "vqpvdxuyZ6rKYnWTj_ZzDBFZAAAlpe5hpoiYHqa2j5kK7v8U5EaPY2bLib9m4B40j-n3FV9xUCGiplWdqMJJKT-4PjGO5E3S4N9kjFhu57noYT7z7302J0sJXeoFbXxlgE-4G55Oxlm52ID2_RJesP5nzcGTriQwoRbrJP5OEt0", + }, + extractable: true, + keyUsages: ["encrypt"], + }, + privateKey: { + format: "jwk", + algorithm: { name: "RSA-OAEP", hash: "SHA-256" } as types.Algorithm, + data: { + alg: "RSA-OAEP-256", + d: "YZzAFCqJ26kElAO92CZEIBmBhw6MN7cjJy8nMgoHzNx9TH4rI_M71Zf6_DqRYIwWPNd7N-X1DSErNB0A6jUNXr42l3ChBsBB31vjHqQKx95-M6iXVgjJFTzxirNjUuCm_skFYIcXS5oEaXjy5XI3dT8KAEf1M2UA6__LwGrAD8E", + dp: "pOolqL7HwnmWLn7GDX8zGkm0Q1IAj-ouBL7ZZbaTm3wETLtwu-dGsQheEdzP_mfL_CTiCAwGuQBcSItimD0DdQ", + dq: "FTSY59AnkgmB7TsErWNBE3xlVB_pMpE2xWyCBCz96gyDOUOFDz8vlSV-clhjawJeRd1n30nZOPSBtOHozhwZmQ", + e: "AQAB", + ext: true, + key_ops: ["decrypt"], + kty: "RSA", + n: "vqpvdxuyZ6rKYnWTj_ZzDBFZAAAlpe5hpoiYHqa2j5kK7v8U5EaPY2bLib9m4B40j-n3FV9xUCGiplWdqMJJKT-4PjGO5E3S4N9kjFhu57noYT7z7302J0sJXeoFbXxlgE-4G55Oxlm52ID2_RJesP5nzcGTriQwoRbrJP5OEt0", + p: "6jFtmBJJQFIlQUXXZYIgvH70Y9a03oWKjNuF2veb5Zf09EtLNE86NpnIm463OnoHJPW0m8wHFXZZfcYVTIPR_w", + q: "0GttDMl1kIzSV2rNzGXpOS8tUqr5Lz0EtVZwIb9GJPMmJ0P3gZ801zEgZZ4-esU7cLUf-BSZEAmfnKA80G2jIw", + qi: "FByTxX4G2eXkk1xe0IuiEv7I5NS-CnFyp8iB4XLG0rabnfcIZFKpf__X0sNyVOAVo5-jJMuUYjCRTdaXNAWhkg", + }, + extractable: true, + keyUsages: ["decrypt"], + }, + }, + }, + ], + }, + }, + // RSAES-PKCS1-v1_5 + { + name: "RSAES-PKCS1-v1_5", + actions: { + generateKey: [ + { + algorithm: { + name: "RSAES-PKCS1-v1_5", + publicExponent: new Uint8Array([1, 0, 1]), + modulusLength: 1024, + } as types.RsaKeyGenParams, + extractable: false, + keyUsages: ["encrypt", "decrypt"], + } as ITestGenerateKeyAction, + ], + encrypt: [ + { + algorithm: { + name: "RSAES-PKCS1-v1_5", + } as types.Algorithm, + data: pvtsutils.Convert.FromHex("01435e62ad3ec4850720e34f8cab620e203749f2315b203d"), + encData: pvtsutils.Convert.FromHex("76e5ea6e1df52471454f790923f60e2baa7adf5017fe0a36c0af3e32f6390d570e1d592375ba6035fdf4ffa70764b797ab54d0ab1efe89cf31d7fc98240a4d08c2476b7eb4c2d92355b8bf60e3897c3fcbfe09f20c7b159d9a9c4a6b2ce5021dd313e492afa762c24930f97f03a429f7b2b1e1d6088651d60e323835807c6fefe7952f74e5da29e8e327ea46e69a0a6684272f022bf18ec602ffcd10a62666b35a51ec7c7d101096f663ddfa0924a86bdbcde0433b4f71dc42bfd9facf329558026f8667f1a71c3365e09843a12339d8aaf31987b0d800e53fd0835e990096cb145e278153faf1188cd5713c6fcd289cb77d80515e1d200139b8ccac4d3bcebc"), + key: { + publicKey: { + format: "jwk", + algorithm: { name: "RSAES-PKCS1-v1_5" } as types.Algorithm, + data: { + alg: "RS1", + e: "AQAB", + ext: true, + key_ops: ["encrypt"], + kty: "RSA", + n: "xr8ELXq5dGFycys8jrc8vVPkWl2GzuRgyOxATtjcNIy5MD7j1XVsUH62VVdIVUUGt0IQ7K288ij3gkIPcIkRO6GmV0vbQAqHrjSHYUAtKQXbIgNRIuJGZvO5AXsxSo1X-tfhOxe140pseOkaehz1bGduhdcYWNR3xLmp7i-GQTRDo-v6CQXtFvSUwG_EIOXnl1trN2Q1Yw4wA1dbtY9FDz69uH-dEWTx7BFCAXVTQMjNe7BTvgGeQcX7XZIw5e2pd0pXjdIgb0xMgziwmc5bbABrGlhK7TmKqA47RlWzY_Lcj7VcTUfMfh7YKKichGTUbqxlgsRTma_e-0-vgDEz6w", + }, + extractable: true, + keyUsages: ["encrypt"], + }, + privateKey: { + format: "jwk", + algorithm: { name: "RSAES-PKCS1-v1_5" } as types.Algorithm, + data: { + kty: "RSA", + alg: "RS1", + key_ops: ["decrypt"], + ext: true, + n: "xr8ELXq5dGFycys8jrc8vVPkWl2GzuRgyOxATtjcNIy5MD7j1XVsUH62VVdIVUUGt0IQ7K288ij3gkIPcIkRO6GmV0vbQAqHrjSHYUAtKQXbIgNRIuJGZvO5AXsxSo1X-tfhOxe140pseOkaehz1bGduhdcYWNR3xLmp7i-GQTRDo-v6CQXtFvSUwG_EIOXnl1trN2Q1Yw4wA1dbtY9FDz69uH-dEWTx7BFCAXVTQMjNe7BTvgGeQcX7XZIw5e2pd0pXjdIgb0xMgziwmc5bbABrGlhK7TmKqA47RlWzY_Lcj7VcTUfMfh7YKKichGTUbqxlgsRTma_e-0-vgDEz6w", + e: "AQAB", + d: "kZ2IoQ3G7UcshMdL8kC85vadW7wktldLtkqqf1qSVIo6cOfTJCWJe5yrWPG_VIJjfkeQgOh2hHKRjcV67HfwwWEZr-IrPMu6R1_DRPSxYdohiNUnUEi7TlkJ1tT882OF74rWQeaIZIS13wzjUk7_XjKWHsfO1d6t9dwWbiYx1nj4syQCcUrvHIgVXCfL85Tyu3NHqpxOdbzRb2OLmkv5ciHFExm4ai98xAgsEXbNvZQeSOOfKNsiCb-NjBXLYrbaDIsakAEV75893JubfeD51UHn7dPT8M8MmKEvrTOKCscShf01scTDHfx_hiOXK3XG4tVx9l2YGEkt3xCedljocQ", + p: "_dWMJ57SECcBbOjPRCvT97ypDyw9ydvnSZXTsn9c7ScxvUxBk6-wuMtgsLI8OWkhZGDBLyVrn-I3RMAN-A5QI_adoGdK7fq5lFWmQYvb1u1xUaGEInVFsM3BW7RBBF8N7OzHwULEQLTXb4jkpgwyCynsX0OEbVVvVerqrcr7osM", + q: "yHEjuQe9TNo-leMrL6cu-yDPfA85M8xQuBM59Cwz06-ggBRi9EOpbV-CrejGUbVlE9QmKGqIBT8C3NVBQwybzlgUihgIpnVgkb01lLEf13ohQ_GWV1mS8ybznjMgaVtVF5Lva4WixIDlXbOu4svVQpkr-KRpKvEMUCTsX-Sxx7k", + dp: "jMP4TaCN7dczuyoAh1Wm3yQIvRlTyrXgtbYZCEwJRJsPwmKfmz87Sb-_hz3QmCXtFrVxbKvb23agH8hB9uY5GziQgXvG2eLJN7Gn2YGuEKrsxNBFbraKR1pTeH-l7r6oAlPtEwfrvdaMApZv9oWc2wQMyWev8NIIRCVar7Z5hfE", + dq: "wi2g3sJZp9cRpGEDWFHM2KnrdxLEZqK7W-f8T8h2mM9eXFXjmyDlRLivP0zuuv9QoUn3gVXa2cI2QrsxUwQm-Fop47Hux1uUpvs2qgqBf1yoV0r2Sz7Sdk442fxLnOVG5OSKno5Cpbz89q54cOvoeHEswN59p4UHWai7eRZzB7k", + qi: "k9hlEyvZCWj8Fvxrknj5WHgaLrSqaVku3PVod2wUJox3aZ8vUsGmmD27lfiWwVKNRmgxLiazY40pLPu07SEmlJgF8QjzDb33k5Pcn9wRuezcCi-53LBRK6-EptZ-UjEINBlM_Cx_WOuxs7P77pwcCo2NV76ilxP5PP_34SUZ0ts", + }, + extractable: true, + keyUsages: ["decrypt"], + }, + }, + }, + ], + }, + }, + ]); + +}); diff --git a/packages/web/test/sha.spec.ts b/packages/web/test/sha.spec.ts new file mode 100644 index 0000000..0091445 --- /dev/null +++ b/packages/web/test/sha.spec.ts @@ -0,0 +1,82 @@ +import * as types from "@peculiar/webcrypto-types"; +import * as pvtsutils from "pvtsutils"; +import { testCrypto } from "./utils"; +import { webCrypto } from "./utils"; + +context("SHA", () => { + + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]); + + testCrypto(webCrypto, [ + { + name: "SHA", + actions: { + digest: [ + { + name: "SHA-1", + algorithm: "SHA-1", + data, + hash: pvtsutils.Convert.FromBase64("6JrVqWMcPv3e1+Psznm00P7c4b8="), + }, + { + name: "SHA-256", + algorithm: "SHA-256", + data, + hash: pvtsutils.Convert.FromBase64("monGjExeKLjEpVZ2c9Ri//UV20YRb5kAYk0JxHT1k/s="), + }, + { + name: "SHA-384", + skip: typeof module !== "undefined", // skip for nodejs + algorithm: "SHA-384", + data, + hash: pvtsutils.Convert.FromBase64("E9WqubQC9JnxffIniWwf0soI91o5z0Kbvk+s/32Fi3z28kAh+Fcne7Hgy1nnW4rR"), + }, + { + name: "SHA-512", + algorithm: "SHA-512", + data, + hash: pvtsutils.Convert.FromBase64("OtPzaXlFDU9TNmJE7PEBD0+RIdaIgoX/FBBP1a3thdSKoXG/HjOhEmAvkrenCIsph4kBL7h7kFYyEkGhn7dOCw=="), + }, + ], + }, + }, + { + name: "SHAKE", + actions: { + digest: [ + { + name: "shake128 default", + algorithm: "shake128", + data, + hash: pvtsutils.Convert.FromHex("83eb77696796112190033833050fbd57"), + }, + { + name: "shake128 128 byte length", + algorithm: { + name: "shake128", + length: 128, + } as types.ShakeParams, + data, + hash: pvtsutils.Convert.FromHex("83eb77696796112190033833050fbd57c6b678d762053e931c978d9c1586b5c4c09fb0cfa40f68094cd6520bec7c21ac47072053243ba42283322a4aeebe23f7675f96c7fa22a9f8b4d63b0b6634dca3b6a6138870c1afc3ada61a3bd816d576b4783101205a1ddf364210c05d6c72ef861936828c446e3c3584d0607d53e46e"), + }, + { + name: "shake256 default", + algorithm: "shake256", + data, + hash: pvtsutils.Convert.FromHex("5719c4fb8351b11f091815582a33cb5f7caba174f2dd7429d3298383e67af205"), + }, + { + name: "shake256 256 byte length", + algorithm: { + name: "shake256", + length: 256, + } as types.ShakeParams, + data, + hash: pvtsutils.Convert.FromHex("5719c4fb8351b11f091815582a33cb5f7caba174f2dd7429d3298383e67af20588ce4967a3867f6d7fde600336b14188dba8f14b999970223395e53de9d09285ee861c1817a1e2c66c894d230944ec16e0f65b605fb7ee707b114702905037df89dfa9910dd850e1b789eb6efbfc5002a335d9270a9bb66d409df65d8b0755e5081918f8d0d9e49f4aca83d5a097bde0ccd5cecbe2724f22e5aab61fb43cd22f108aa5db02cb122c84a860037a4bb292b3f2a6a1193c642c61ab83f9e6310c896fdf3487c23863c9c7b7cb806ffeff44bc21fbd2c4e65ee6c76bf4336e4a11008368ae9264eab5f728bb2924f3410dd1d821b0d8a18b30a7420ad469f1bfd04d"), + }, + ], + }, + }, + ]); + +}); diff --git a/packages/web/test/utils/helper.ts b/packages/web/test/utils/helper.ts new file mode 100644 index 0000000..8d91306 --- /dev/null +++ b/packages/web/test/utils/helper.ts @@ -0,0 +1,371 @@ +import * as types from "@peculiar/webcrypto-types"; +import * as assert from "assert"; +import * as pvtsutils from "pvtsutils"; +import { BrowserInfo } from "../../src/helper"; + +// fix type errors +type Crypto = any; + +export const browser = BrowserInfo(); + +export interface ITestMochaFunction { + skip?: boolean; + only?: boolean; +} + +export interface ITestAction extends ITestMochaFunction { + name?: string; + error?: any; +} + +export interface ITestGenerateKeyAction extends ITestAction { + algorithm: types.Algorithm; + extractable: boolean; + keyUsages: types.KeyUsage[]; +} + +export interface IImportKeyParams { + format: types.KeyFormat; + data: types.JsonWebKey | types.BufferSource; + algorithm: types.AlgorithmIdentifier; + extractable: boolean; + keyUsages: types.KeyUsage[]; +} + +export interface IImportKeyPairParams { + privateKey: IImportKeyParams; + publicKey: IImportKeyParams; +} + +export interface ITestEncryptAction extends ITestAction { + algorithm: types.Algorithm; + data: types.BufferSource; + encData: types.BufferSource; + key: IImportKeyParams | IImportKeyPairParams; +} + +export interface ITestSignAction extends ITestAction { + algorithm: types.Algorithm; + data: types.BufferSource; + signature: types.BufferSource; + key: IImportKeyParams | IImportKeyPairParams; +} + +export interface ITestDeriveBitsAction extends ITestAction { + algorithm: types.Algorithm; + key: IImportKeyParams | IImportKeyPairParams; + data: types.BufferSource; + length: number; +} + +export interface ITestDeriveKeyAction extends ITestAction { + algorithm: types.Algorithm; + key: IImportKeyParams | IImportKeyPairParams; + derivedKeyType: types.Algorithm; + keyUsages: types.KeyUsage[]; + format: types.KeyFormat; + keyData: types.BufferSource | types.JsonWebKey; +} + +export interface ITestWrapKeyAction extends ITestAction { + key: IImportKeyParams | IImportKeyPairParams; + algorithm: types.Algorithm; + wKey: IImportKeyParams; + wrappedKey?: types.BufferSource; +} + +export interface ITestImportAction extends IImportKeyParams, ITestAction { +} + +export interface ITestDigestAction extends ITestAction { + algorithm: types.AlgorithmIdentifier; + data: types.BufferSource; + hash: types.BufferSource; +} + +export interface ITestActions { + generateKey?: ITestGenerateKeyAction[]; + encrypt?: ITestEncryptAction[]; + wrapKey?: ITestWrapKeyAction[]; + sign?: ITestSignAction[]; + import?: ITestImportAction[]; + deriveBits?: ITestDeriveBitsAction[]; + deriveKey?: ITestDeriveKeyAction[]; + digest?: ITestDigestAction[]; +} + +export interface ITestParams extends ITestMochaFunction { + name: string; + actions: ITestActions; +} + +async function getKeys(crypto: Crypto, key: IImportKeyParams | IImportKeyPairParams) { + const keys = {} as types.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; +} + +function wrapSkipOnly(item: Mocha.TestFunction, params: ITestMochaFunction): Mocha.PendingTestFunction; +function wrapSkipOnly(item: Mocha.SuiteFunction, params: ITestMochaFunction): Mocha.PendingSuiteFunction; +function wrapSkipOnly(item: Mocha.TestFunction | Mocha.SuiteFunction, params: ITestMochaFunction) { + return params.skip + ? item.skip + : params.only + ? item.only + : item; +} + +async function wrapTest(promise: () => Promise, action: ITestAction, index: number) { + wrapSkipOnly(it, action)(action.name || `#${index + 1}`, async () => { + if (action.error) { + if (typeof (action.error) === "boolean") { + await assert.rejects(promise()); + } else { + await assert.rejects(promise(), action.error); + } + } else { + await promise(); + } + }); +} + +export function testCrypto(crypto: Crypto, params: ITestParams[]) { + params.forEach((param) => { + wrapSkipOnly(context, param)(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.equal(!!key, true); + if (!key.privateKey) { + 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].sort(), [...action.keyUsages].sort()); + + } else { + assert.equal(!!key.privateKey, true); + 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.equal(!!key.publicKey, true); + 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(pvtsutils.Convert.ToHex(dec), pvtsutils.Convert.ToHex(action.data)); + + dec = await crypto.subtle.decrypt(algorithm, decKey, action.encData); + assert.equal(pvtsutils.Convert.ToHex(dec), pvtsutils.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 extractable. + if (!action.extractable) { + return; + } + + const exportedData = await crypto.subtle.exportKey( + action.format, + importedKey); + + if (action.format === "jwk") { + exportedData.key_ops.sort(); + (action.data as Required).key_ops.sort(); + assert.deepEqual(exportedData, action.data); + } else { + assert.equal(pvtsutils.Convert.ToHex(exportedData as ArrayBuffer), pvtsutils.Convert.ToHex(action.data as ArrayBuffer)); + } + }, 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(pvtsutils.Convert.ToHex(signature), pvtsutils.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 }); + algorithm.name = algorithm.name.toLowerCase(); + + // derive bits + const derivedBits = await crypto.subtle.deriveBits(algorithm, keys.privateKey, action.length); + assert.equal(pvtsutils.Convert.ToHex(derivedBits), pvtsutils.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 }); + 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(pvtsutils.Convert.ToHex(keyData as ArrayBuffer), pvtsutils.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(pvtsutils.Convert.ToHex(hash), pvtsutils.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(pvtsutils.Convert.ToHex(wrappedKey), pvtsutils.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 + }); + }); +} diff --git a/packages/web/test/utils/index.ts b/packages/web/test/utils/index.ts new file mode 100644 index 0000000..61d6648 --- /dev/null +++ b/packages/web/test/utils/index.ts @@ -0,0 +1,2 @@ +export * from "./init"; +export * from "./helper"; diff --git a/packages/web/test/utils/init.ts b/packages/web/test/utils/init.ts new file mode 100644 index 0000000..28aa856 --- /dev/null +++ b/packages/web/test/utils/init.ts @@ -0,0 +1,46 @@ +import * as types from "@peculiar/webcrypto-types"; +import { Crypto as NodeCrypto } from "@peculiar/webcrypto"; +import { Crypto as WebCrypto, setCrypto } from "../../src"; + +export const nodeCrypto = new NodeCrypto(); +const nativeGenerateKey = nodeCrypto.subtle.generateKey; +const nativeExportKey = nodeCrypto.subtle.exportKey; + +// asmCrypto doesn't have key generation function and uses native generateKey with RSA-PSS +nodeCrypto.subtle.generateKey = async function (this: types.SubtleCrypto, ...args: any[]) { + if (args[0]?.name !== "RSA-PSS") { + throw new Error("Function is broken for test cases"); + } + return nativeGenerateKey.apply(this, args as any); +} as any; + +// asmCrypto doesn't have key generation function and uses native exportKey with RSA-PSS +nodeCrypto.subtle.exportKey = async function (this: types.SubtleCrypto, ...args: any[]) { + if (!( + (args[0] === "pkcs8" + || args[0] === "spki") + && args[1].algorithm.name === "RSA-PSS" + )) { + throw new Error("Function is broken for test cases"); + } + return nativeExportKey.apply(this, args as any); +} as any; + +// break crypto functions +[ + "decrypt", "encrypt", + "wrapKey", "unwrapKey", + "sign", "verify", + "deriveBits", "deriveKey", + "importKey", + "digest", +].forEach((o) => { + (nodeCrypto.subtle as any)[o] = async () => { + throw new Error("Function is broken for test cases"); + }; +}); + +// set native crypto +setCrypto(nodeCrypto as types.Crypto); + +export const webCrypto = new WebCrypto(); diff --git a/tsconfig.json b/tsconfig.json index da9c87d..83bcbff 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,6 +18,9 @@ "@peculiar/webcrypto-pkcs11": [ "./packages/pkcs11/src" ], + "@peculiar/webcrypto-web": [ + "./packages/web/src" + ], }, "skipLibCheck": true, // TODO @peculiar/x509 should use @peculiar/webcrypto-types. Remove this line when it's fixed }, diff --git a/yarn.lock b/yarn.lock index e55a3fc..d19b90f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -162,7 +162,7 @@ asn1js "^3.0.4" tslib "^2.4.0" -"@peculiar/asn1-schema@^2.1.7", "@peculiar/asn1-schema@^2.1.8": +"@peculiar/asn1-schema@^2.1.6", "@peculiar/asn1-schema@^2.1.7", "@peculiar/asn1-schema@^2.1.8": version "2.1.8" resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.1.8.tgz#552300a1ed7991b22c9abf789a3920a3cb94c26b" integrity sha512-u34H/bpqCdDuqrCVZvH0vpwFBT/dNEdNY+eE8u4IuC26yYnhDkXF4+Hliqca88Avbb7hyN2EF/eokyDdyS7G/A== @@ -224,6 +224,37 @@ estree-walker "^2.0.1" picomatch "^2.2.2" +"@stablelib/binary@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/binary/-/binary-1.0.1.tgz#c5900b94368baf00f811da5bdb1610963dfddf7f" + integrity sha512-ClJWvmL6UBM/wjkvv/7m5VP3GMr9t0osr4yVgLZsLCOz4hGN9gIAFEqnJ0TsSMAN+n840nf2cHZnA5/KFqHC7Q== + dependencies: + "@stablelib/int" "^1.0.1" + +"@stablelib/hash@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/hash/-/hash-1.0.1.tgz#3c944403ff2239fad8ebb9015e33e98444058bc5" + integrity sha512-eTPJc/stDkdtOcrNMZ6mcMK1e6yBbqRBaNW55XA1jU8w/7QdnCF0CmMmOD1m7VSkBR44PWrMHU2l6r8YEQHMgg== + +"@stablelib/int@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/int/-/int-1.0.1.tgz#75928cc25d59d73d75ae361f02128588c15fd008" + integrity sha512-byr69X/sDtDiIjIV6m4roLVWnNNlRGzsvxw+agj8CIEazqWGOQp2dTYgQhtyVXV9wpO6WyXRQUzLV/JRNumT2w== + +"@stablelib/sha3@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/sha3/-/sha3-1.0.1.tgz#c2d2955be3d184031200a2fa04f801eea4c8982b" + integrity sha512-82OHZcxWsJAS34L64VItIbqZdcdYgBJmeToYaou9lUA+iMjajdfOVZDDrditfV8C8yXUDrlS3BuMRWmKf9NQhQ== + dependencies: + "@stablelib/binary" "^1.0.1" + "@stablelib/hash" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/wipe@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/wipe/-/wipe-1.0.1.tgz#d21401f1d59ade56a62e139462a97f104ed19a36" + integrity sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg== + "@tsconfig/node10@^1.0.7": version "1.0.8" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" @@ -430,7 +461,12 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -asn1js@^3.0.4: +asmcrypto.js@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/asmcrypto.js/-/asmcrypto.js-2.3.2.tgz#b9f84bd0a1fb82f21f8c29cc284a707ad17bba2e" + integrity sha512-3FgFARf7RupsZETQ1nHnhLUUvpcttcCq1iZCaVAbJZbCZ5VNRrNyvpDyHTOb0KC3llFcsyOT/a99NZcCbeiEsA== + +asn1js@^3.0.4, asn1js@^3.0.5: version "3.0.5" resolved "https://registry.yarnpkg.com/asn1js/-/asn1js-3.0.5.tgz#5ea36820443dbefb51cc7f88a2ebb5b462114f38" integrity sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ== @@ -449,6 +485,11 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== +bn.js@^4.4.0: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -464,6 +505,11 @@ braces@^3.0.2, braces@~3.0.2: dependencies: fill-range "^7.0.1" +brorand@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== + browser-stdout@1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" @@ -592,6 +638,14 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== +des.js@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" + integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== + dependencies: + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + diff@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" @@ -616,6 +670,18 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +"elliptic@https://github.com/mahrud/elliptic": + version "6.5.0" + resolved "https://github.com/mahrud/elliptic#75637c76678e83c31682fd967c2fa9ff4761b3fc" + dependencies: + bn.js "^4.4.0" + brorand "^1.0.1" + hash.js "^1.0.0" + hmac-drbg "^1.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.0" + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -969,11 +1035,28 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + he@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +hmac-drbg@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + ignore@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" @@ -1000,7 +1083,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2: +inherits@2, inherits@^2.0.1, inherits@^2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -1173,6 +1256,16 @@ micromatch@^4.0.4: braces "^3.0.2" picomatch "^2.3.1" +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= + minimatch@4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-4.2.1.tgz#40d9d511a46bdc4e563c22c3080cde9c0d8299b4"