Compare commits

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

7 Commits

Author SHA1 Message Date
microshine 5e0049cacb ci: update test action 2022-05-24 15:09:03 +03:00
microshine 0bd5151604 add web package 2022-05-24 15:06:18 +03:00
microshine 5a5412ecd3 update types 2022-05-24 15:05:52 +03:00
microshine 181477c574 remove tslint comments 2022-05-24 10:39:23 +03:00
microshine 188fa90116 update core types 2022-05-24 00:09:47 +03:00
microshine 1578e6ceeb add pkcs11 2022-05-24 00:09:26 +03:00
microshine 387f0d928d create packages core, types, node, test 2022-05-23 16:12:03 +03:00
364 changed files with 20745 additions and 2728 deletions

View File

@ -23,12 +23,19 @@
"@typescript-eslint/semi": 1,
"@typescript-eslint/explicit-module-boundary-types": 0,
"semi": "off",
"import/order": ["error", {
"groups": ["builtin", "external", "internal"],
"alphabetize": {
"order": "asc",
"caseInsensitive": true
"import/order": [
"error",
{
"groups": [
"builtin",
"external",
"internal"
],
"alphabetize": {
"order": "asc",
"caseInsensitive": true
}
}
}]
]
}
}

View File

@ -38,14 +38,11 @@ jobs:
${{ runner.os }}-build-
${{ runner.os }}-
- name: Install global dependencies
run: npm i yarn -g
- name: Install dependencies
run: yarn
- name: Run linter
run: npm run lint
# - name: Run linter
# run: npm run lint
- name: Run test with coverage
run: npm run coverage

8
.mocharc.yml Normal file
View File

@ -0,0 +1,8 @@
require:
- "tsconfig-paths/register"
- "ts-node/register"
extension:
- ts
spec:
- "packages/**/*.spec.ts"
exit: true

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020 Peculiar Ventures, LLC
Copyright (c) 2016-2022 Peculiar Ventures, LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

12
index.d.ts vendored
View File

@ -1,12 +0,0 @@
export declare class Crypto implements globalThis.Crypto {
public subtle: SubtleCrypto;
public getRandomValues<T extends ArrayBufferView | null>(array: T): T;
public randomUUID(): string;
}
export declare class CryptoKey implements globalThis.CryptoKey {
public algorithm: KeyAlgorithm;
public extractable: boolean;
public type: KeyType;
public usages: KeyUsage[];
}

13
lerna.json Normal file
View File

@ -0,0 +1,13 @@
{
"npmClient": "yarn",
"packages": [
"packages/*"
],
"version": "3.0.0",
"useWorkspaces": true,
"command": {
"publish": {
"registry": "https://npm.pkg.github.com/"
}
}
}

View File

@ -1,106 +1,42 @@
{
"name": "@peculiar/webcrypto",
"version": "1.4.0",
"description": "A WebCrypto Polyfill for NodeJS",
"repository": {
"type": "git",
"url": "https://github.com/PeculiarVentures/webcrypto.git"
},
"files": [
"build/**/*.{ts,js}",
"index.d.ts",
"README.md",
"LICENSE.md"
"name": "webcrypto",
"private": true,
"description": "",
"workspaces": [
"packages/*"
],
"main": "build/webcrypto.js",
"module": "build/webcrypto.es.js",
"types": "index.d.ts",
"scripts": {
"test": "mocha",
"coverage": "nyc npm test",
"coveralls": "nyc report --reporter=text-lcov | coveralls",
"build": "rollup -c",
"clear": "rimraf build/*",
"rebuild": "npm run clear && npm run build",
"lint": "eslint . --ext .ts",
"lint:fix": "eslint --fix . --ext .ts"
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"build": "lerna run build",
"rebuild": "lerna run rebuild",
"pack": "lerna exec npm pack",
"clear": "lerna run clear",
"upgrade": "yarn upgrade-interactive --latest"
},
"keywords": [
"webcrypto",
"crypto",
"sha",
"rsa",
"ec",
"aes",
"des",
"hmac",
"pbkdf2",
"eddsa",
"x25519",
"ed25519",
"x448",
"ed448",
"shake128",
"shake256"
],
"author": "PeculiarVentures",
"contributors": [
"Miroshin Stepan<microshine@mail.ru>"
],
"repository": {
"type": "git",
"url": "git+https://github.com/PeculiarVentures/webcrypto.git"
},
"author": "",
"license": "MIT",
"bugs": {
"url": "https://github.com/PeculiarVentures/webcrypto/issues"
},
"homepage": "https://github.com/PeculiarVentures/webcrypto#readme",
"devDependencies": {
"@peculiar/webcrypto-test": "^1.0.7",
"@types/mocha": "^9.1.1",
"@types/node": "^17.0.32",
"@typescript-eslint/eslint-plugin": "^5.23.0",
"@typescript-eslint/parser": "^5.23.0",
"coveralls": "^3.1.1",
"eslint": "^8.15.0",
"eslint-plugin-import": "^2.26.0",
"mocha": "^10.0.0",
"nyc": "^15.1.0",
"rimraf": "^3.0.2",
"rollup": "^2.72.1",
"@types/node": "^17.0.27",
"@typescript-eslint/eslint-plugin": "^5.21.0",
"@typescript-eslint/parser": "^5.21.0",
"eslint": "^8.14.0",
"mocha": "^9.2.2",
"rollup": "^2.70.2",
"rollup-plugin-dts": "^4.2.1",
"rollup-plugin-typescript2": "^0.31.2",
"ts-node": "^10.7.0",
"typescript": "^4.6.4"
},
"dependencies": {
"@peculiar/asn1-schema": "^2.1.6",
"@peculiar/json-schema": "^1.1.12",
"pvtsutils": "^1.3.2",
"tslib": "^2.4.0",
"webcrypto-core": "^1.7.4"
},
"nyc": {
"extension": [
".ts",
".tsx"
],
"include": [
"src/**/*.ts"
],
"exclude": [
"**/*.d.ts"
],
"reporter": [
"lcov",
"text-summary",
"html"
]
},
"engines": {
"node": ">=10.12.0"
},
"mocha": {
"require": "ts-node/register",
"extension": [
"ts"
],
"spec": "test/**/*.ts"
"tsconfig-paths": "^3.14.1",
"typescript": "^4.6.3"
}
}

11
packages/core/README.md Normal file
View File

@ -0,0 +1,11 @@
# `@peculiar/webcypto-core`
> TODO: description
## Usage
```
const webcyptoCore = require('@peculiar/webcypto-core');
// TODO: DEMONSTRATE API
```

View File

@ -0,0 +1,50 @@
{
"name": "@peculiar/webcrypto-core",
"version": "3.0.0",
"description": "Common layer to be used by crypto libraries based on WebCrypto API for input validation.",
"author": "PeculiarVentures",
"contributors": [
"Miroshin Stepan<microshine@mail.ru>"
],
"homepage": "https://github.com/PeculiarVentures/webcrypto/tree/master/packages/core#readme",
"license": "MIT",
"main": "build/index.js",
"module": "build/index.es.js",
"types": "build/index.d.ts",
"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"
},
"keywords": [
"webcrypto",
"crypto",
"polyfill",
"aes",
"rsa",
"sha",
"sha3",
"ec",
"shake"
],
"dependencies": {
"@peculiar/asn1-schema": "^2.1.7",
"@peculiar/json-schema": "^1.1.12",
"@peculiar/webcrypto-types": "^3.0.0",
"pvtsutils": "^1.3.2",
"tslib": "^2.4.0"
}
}

View File

@ -0,0 +1,31 @@
import * as types from "@peculiar/webcrypto-types";
import { ProviderCrypto } from "../provider";
import { CryptoKey } from "../crypto_key";
export abstract class AesProvider extends ProviderCrypto {
public override checkGenerateKeyParams(algorithm: types.AesKeyGenParams) {
// length
this.checkRequiredProperty(algorithm, "length");
if (typeof algorithm.length !== "number") {
throw new TypeError("length: Is not of type Number");
}
switch (algorithm.length) {
case 128:
case 192:
case 256:
break;
default:
throw new TypeError("length: Must be 128, 192, or 256");
}
}
public override checkDerivedKeyParams(algorithm: types.AesKeyGenParams) {
this.checkGenerateKeyParams(algorithm);
}
public abstract override onGenerateKey(algorithm: types.AesKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise<CryptoKey>;
public abstract override onExportKey(format: types.KeyFormat, key: CryptoKey, ...args: any[]): Promise<types.JsonWebKey | ArrayBuffer>;
public abstract override onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.Algorithm, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise<CryptoKey>;
}

View File

@ -0,0 +1,23 @@
import * as types from "@peculiar/webcrypto-types";
import { AesProvider } from "./base";
export abstract class AesCbcProvider extends AesProvider {
public readonly name = "AES-CBC";
public usages: types.KeyUsages = ["encrypt", "decrypt", "wrapKey", "unwrapKey"];
public override checkAlgorithmParams(algorithm: types.AesCbcParams) {
this.checkRequiredProperty(algorithm, "iv");
if (!(algorithm.iv instanceof ArrayBuffer || ArrayBuffer.isView(algorithm.iv))) {
throw new TypeError("iv: Is not of type '(ArrayBuffer or ArrayBufferView)'");
}
if (algorithm.iv.byteLength !== 16) {
throw new TypeError("iv: Must have length 16 bytes");
}
}
public abstract override onEncrypt(algorithm: types.AesCbcParams, key: types.CryptoKey, data: ArrayBuffer, ...args: any[]): Promise<ArrayBuffer>;
public abstract override onDecrypt(algorithm: types.AesCbcParams, key: types.CryptoKey, data: ArrayBuffer, ...args: any[]): Promise<ArrayBuffer>;
}

View File

@ -0,0 +1,24 @@
import * as types from "@peculiar/webcrypto-types";
import { OperationError } from "../errors";
import { AesProvider } from "./base";
export abstract class AesCmacProvider extends AesProvider {
public readonly name = "AES-CMAC";
public usages: types.KeyUsages = ["sign", "verify"];
public override checkAlgorithmParams(algorithm: types.AesCmacParams) {
this.checkRequiredProperty(algorithm, "length");
if (typeof algorithm.length !== "number") {
throw new TypeError("length: Is not a Number");
}
if (algorithm.length < 1) {
throw new OperationError("length: Must be more than 0");
}
}
public abstract override onSign(algorithm: types.AesCmacParams, key: types.CryptoKey, data: ArrayBuffer, ...args: any[]): Promise<ArrayBuffer>;
public abstract override onVerify(algorithm: types.AesCmacParams, key: types.CryptoKey, signature: ArrayBuffer, data: ArrayBuffer, ...args: any[]): Promise<boolean>;
}

View File

@ -0,0 +1,33 @@
import * as types from "@peculiar/webcrypto-types";
import { OperationError } from "../errors";
import { AesProvider } from "./base";
export abstract class AesCtrProvider extends AesProvider {
public readonly name = "AES-CTR";
public usages: types.KeyUsages = ["encrypt", "decrypt", "wrapKey", "unwrapKey"];
public override checkAlgorithmParams(algorithm: types.AesCtrParams) {
// counter
this.checkRequiredProperty(algorithm, "counter");
if (!(algorithm.counter instanceof ArrayBuffer || ArrayBuffer.isView(algorithm.counter))) {
throw new TypeError("counter: Is not of type '(ArrayBuffer or ArrayBufferView)'");
}
if (algorithm.counter.byteLength !== 16) {
throw new TypeError("iv: Must have length 16 bytes");
}
// length
this.checkRequiredProperty(algorithm, "length");
if (typeof algorithm.length !== "number") {
throw new TypeError("length: Is not a Number");
}
if (algorithm.length < 1) {
throw new OperationError("length: Must be more than 0");
}
}
public abstract override onEncrypt(algorithm: types.AesCtrParams, key: types.CryptoKey, data: ArrayBuffer, ...args: any[]): Promise<ArrayBuffer>;
public abstract override onDecrypt(algorithm: types.AesCtrParams, key: types.CryptoKey, data: ArrayBuffer, ...args: any[]): Promise<ArrayBuffer>;
}

View File

@ -0,0 +1,13 @@
import * as types from "@peculiar/webcrypto-types";
import { AesProvider } from "./base";
export abstract class AesEcbProvider extends AesProvider {
public readonly name = "AES-ECB";
public usages: types.KeyUsages = ["encrypt", "decrypt", "wrapKey", "unwrapKey"];
public abstract override onEncrypt(algorithm: types.Algorithm, key: types.CryptoKey, data: ArrayBuffer, ...args: any[]): Promise<ArrayBuffer>;
public abstract override onDecrypt(algorithm: types.Algorithm, key: types.CryptoKey, data: ArrayBuffer, ...args: any[]): Promise<ArrayBuffer>;
}

View File

@ -0,0 +1,41 @@
import * as types from "@peculiar/webcrypto-types";
import { OperationError } from "../errors";
import { AesProvider } from "./base";
export abstract class AesGcmProvider extends AesProvider {
public readonly name = "AES-GCM";
public usages: types.KeyUsages = ["encrypt", "decrypt", "wrapKey", "unwrapKey"];
public override checkAlgorithmParams(algorithm: types.AesGcmParams) {
// iv
this.checkRequiredProperty(algorithm, "iv");
if (!(algorithm.iv instanceof ArrayBuffer || ArrayBuffer.isView(algorithm.iv))) {
throw new TypeError("iv: Is not of type '(ArrayBuffer or ArrayBufferView)'");
}
if (algorithm.iv.byteLength < 1) {
throw new OperationError("iv: Must have length more than 0 and less than 2^64 - 1");
}
// tagLength
if (!("tagLength" in algorithm)) {
algorithm.tagLength = 128;
}
switch (algorithm.tagLength) {
case 32:
case 64:
case 96:
case 104:
case 112:
case 120:
case 128:
break;
default:
throw new OperationError("tagLength: Must be one of 32, 64, 96, 104, 112, 120 or 128");
}
}
public abstract override onEncrypt(algorithm: types.AesGcmParams, key: types.CryptoKey, data: ArrayBuffer, ...args: any[]): Promise<ArrayBuffer>;
public abstract override onDecrypt(algorithm: types.AesGcmParams, key: types.CryptoKey, data: ArrayBuffer, ...args: any[]): Promise<ArrayBuffer>;
}

View File

@ -0,0 +1,7 @@
export * from "./base";
export * from "./cbc";
export * from "./cmac";
export * from "./ctr";
export * from "./ecb";
export * from "./gsm";
export * from "./kw";

View File

@ -0,0 +1,10 @@
import * as types from "@peculiar/webcrypto-types";
import { AesProvider } from "./base";
export abstract class AesKwProvider extends AesProvider {
public readonly name = "AES-KW";
public usages: types.KeyUsages = ["wrapKey", "unwrapKey"];
}

View File

@ -0,0 +1,25 @@
import { Convert } from "pvtsutils";
import { SubtleCrypto } from "./subtle";
import * as types from "@peculiar/webcrypto-types";
export abstract class Crypto implements types.Crypto {
public abstract readonly subtle: SubtleCrypto;
// @internal
public get [Symbol.toStringTag]() {
return "Crypto";
}
public abstract getRandomValues<T extends ArrayBufferView | null>(array: T): T;
public randomUUID(): string {
const b = this.getRandomValues(new Uint8Array(16));
b[6] = (b[6] & 0x0f) | 0x40;
b[8] = (b[8] & 0x3f) | 0x80;
const uuid = Convert.ToHex(b).toLowerCase();
return `${uuid.substring(0, 8)}-${uuid.substring(8, 12)}-${uuid.substring(12, 16)}-${uuid.substring(16)}`;
}
}

View File

@ -0,0 +1,30 @@
import * as types from "@peculiar/webcrypto-types";
const KEY_TYPES = ["secret", "private", "public"];
export class CryptoKey implements types.CryptoKey {
public static create<T extends CryptoKey>(this: new () => T, algorithm: types.KeyAlgorithm, type: types.KeyType, extractable: boolean, usages: types.KeyUsages): T {
const key = new this();
key.algorithm = algorithm;
key.type = type;
key.extractable = extractable;
key.usages = usages;
return key;
}
public static isKeyType(data: any): data is types.KeyType {
return KEY_TYPES.indexOf(data) !== -1;
}
public algorithm: types.KeyAlgorithm = { name: "" };
public type: types.KeyType = "secret";
public usages: types.KeyUsages = [];
public extractable: boolean = false;
// @internal
public get [Symbol.toStringTag]() {
return "CryptoKey";
}
}

View File

@ -0,0 +1,46 @@
import * as types from "@peculiar/webcrypto-types";
import { OperationError } from "../errors";
import { CryptoKey } from "../crypto_key";
import { ProviderCrypto } from "../provider";
export abstract class DesProvider extends ProviderCrypto {
public usages: types.KeyUsages = ["encrypt", "decrypt", "wrapKey", "unwrapKey"];
public abstract keySizeBits: number;
public abstract ivSize: number;
public override checkAlgorithmParams(algorithm: types.AesCbcParams) {
if (this.ivSize) {
this.checkRequiredProperty(algorithm, "iv");
if (!(algorithm.iv instanceof ArrayBuffer || ArrayBuffer.isView(algorithm.iv))) {
throw new TypeError("iv: Is not of type '(ArrayBuffer or ArrayBufferView)'");
}
if (algorithm.iv.byteLength !== this.ivSize) {
throw new TypeError(`iv: Must have length ${this.ivSize} bytes`);
}
}
}
public override checkGenerateKeyParams(algorithm: types.DesKeyGenParams) {
// length
this.checkRequiredProperty(algorithm, "length");
if (typeof algorithm.length !== "number") {
throw new TypeError("length: Is not of type Number");
}
if (algorithm.length !== this.keySizeBits) {
throw new OperationError(`algorithm.length: Must be ${this.keySizeBits}`);
}
}
public override checkDerivedKeyParams(algorithm: types.DesDerivedKeyParams) {
this.checkGenerateKeyParams(algorithm);
}
public abstract override onGenerateKey(algorithm: types.DesKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise<CryptoKey>;
public abstract override onExportKey(format: types.KeyFormat, key: CryptoKey, ...args: any[]): Promise<types.JsonWebKey | ArrayBuffer>;
public abstract override onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.DesImportParams, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise<CryptoKey>;
public abstract override onEncrypt(algorithm: types.DesParams, key: CryptoKey, data: ArrayBuffer, ...args: any[]): Promise<ArrayBuffer>;
public abstract override onDecrypt(algorithm: types.DesParams, key: CryptoKey, data: ArrayBuffer, ...args: any[]): Promise<ArrayBuffer>;
}

View File

@ -0,0 +1 @@
export * from "./base";

View File

@ -0,0 +1,29 @@
import * as types from "@peculiar/webcrypto-types";
import { OperationError } from "../errors";
import { CryptoKey } from "../crypto_key";
import { ProviderCrypto } from "../provider";
export abstract class EllipticProvider extends ProviderCrypto {
public abstract namedCurves: string[];
public override checkGenerateKeyParams(algorithm: types.EcKeyGenParams) {
// named curve
this.checkRequiredProperty(algorithm, "namedCurve");
this.checkNamedCurve(algorithm.namedCurve);
}
public checkNamedCurve(namedCurve: string) {
for (const item of this.namedCurves) {
if (item.toLowerCase() === namedCurve.toLowerCase()) {
return;
}
}
throw new OperationError(`namedCurve: Must be one of ${this.namedCurves.join(", ")}`);
}
public abstract override onGenerateKey(algorithm: types.EcKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise<types.CryptoKeyPair>;
public abstract override onExportKey(format: types.KeyFormat, key: CryptoKey, ...args: any[]): Promise<types.JsonWebKey | ArrayBuffer>;
public abstract override onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.EcKeyImportParams, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise<CryptoKey>;
}

View File

@ -0,0 +1,81 @@
import { AsnConvert } from "@peculiar/asn1-schema";
import * as asn1 from "../schema/asn1";
export interface EcCurveParams {
/**
* The name of the curve
*/
name: string;
/**
* The object identifier of the curve
*/
id: string;
/**
* Curve point size in bits
*/
size: number;
}
export interface EcCurve extends EcCurveParams {
raw: ArrayBuffer;
}
export class EcCurves {
protected static items: EcCurve[] = [];
public static readonly names: string[] = [];
private constructor() { }
public static register(item: EcCurveParams) {
const oid = new asn1.ObjectIdentifier();
oid.value = item.id;
const raw = AsnConvert.serialize(oid);
this.items.push({
...item,
raw,
});
this.names.push(item.name);
}
public static find(nameOrId: string): EcCurve | null {
nameOrId = nameOrId.toUpperCase();
for (const item of this.items) {
if (item.name.toUpperCase() === nameOrId || item.id.toUpperCase() === nameOrId) {
return item;
}
}
return null;
}
public static get(nameOrId: string): EcCurve {
const res = this.find(nameOrId);
if (!res) {
throw new Error(`Unsupported EC named curve '${nameOrId}'`);
}
return res;
}
}
EcCurves.register({ name: "P-256", id: asn1.idSecp256r1, size: 256 });
EcCurves.register({ name: "P-384", id: asn1.idSecp384r1, size: 384 });
EcCurves.register({ name: "P-521", id: asn1.idSecp521r1, size: 521 });
EcCurves.register({ name: "K-256", id: asn1.idSecp256k1, size: 256 });
EcCurves.register({ name: "brainpoolP160r1", id: asn1.idBrainpoolP160r1, size: 160 });
EcCurves.register({ name: "brainpoolP160t1", id: asn1.idBrainpoolP160t1, size: 160 });
EcCurves.register({ name: "brainpoolP192r1", id: asn1.idBrainpoolP192r1, size: 192 });
EcCurves.register({ name: "brainpoolP192t1", id: asn1.idBrainpoolP192t1, size: 192 });
EcCurves.register({ name: "brainpoolP224r1", id: asn1.idBrainpoolP224r1, size: 224 });
EcCurves.register({ name: "brainpoolP224t1", id: asn1.idBrainpoolP224t1, size: 224 });
EcCurves.register({ name: "brainpoolP256r1", id: asn1.idBrainpoolP256r1, size: 256 });
EcCurves.register({ name: "brainpoolP256t1", id: asn1.idBrainpoolP256t1, size: 256 });
EcCurves.register({ name: "brainpoolP320r1", id: asn1.idBrainpoolP320r1, size: 320 });
EcCurves.register({ name: "brainpoolP320t1", id: asn1.idBrainpoolP320t1, size: 320 });
EcCurves.register({ name: "brainpoolP384r1", id: asn1.idBrainpoolP384r1, size: 384 });
EcCurves.register({ name: "brainpoolP384t1", id: asn1.idBrainpoolP384t1, size: 384 });
EcCurves.register({ name: "brainpoolP512r1", id: asn1.idBrainpoolP512r1, size: 512 });
EcCurves.register({ name: "brainpoolP512t1", id: asn1.idBrainpoolP512t1, size: 512 });

View File

@ -0,0 +1,33 @@
import * as types from "@peculiar/webcrypto-types";
import { OperationError } from "../errors";
import { CryptoKey } from "../crypto_key";
import { EllipticProvider } from "./base";
export abstract class EcdhProvider extends EllipticProvider {
public readonly name: string = "ECDH";
public usages: types.ProviderKeyUsages = {
privateKey: ["deriveBits", "deriveKey"],
publicKey: [],
};
public namedCurves = ["P-256", "P-384", "P-521", "K-256"];
public override checkAlgorithmParams(algorithm: types.EcdhKeyDeriveParams) {
// public
this.checkRequiredProperty(algorithm, "public");
if (!(algorithm.public instanceof CryptoKey)) {
throw new TypeError("public: Is not a CryptoKey");
}
if (algorithm.public.type !== "public") {
throw new OperationError("public: Is not a public key");
}
if (algorithm.public.algorithm.name !== this.name) {
throw new OperationError(`public: Is not ${this.name} key`);
}
}
public abstract override onDeriveBits(algorithm: types.EcdhKeyDeriveParams, baseKey: CryptoKey, length: number, ...args: any[]): Promise<ArrayBuffer>;
}

View File

@ -0,0 +1,7 @@
import { EcdhProvider } from "./ecdh";
export abstract class EcdhEsProvider extends EcdhProvider {
public override readonly name: string = "ECDH-ES";
public override namedCurves = ["X25519", "X448"];
}

View File

@ -0,0 +1,25 @@
import { Algorithm, CryptoKey, EcdsaParams, ProviderKeyUsages } from "@peculiar/webcrypto-types";
import { EllipticProvider } from "./base";
export abstract class EcdsaProvider extends EllipticProvider {
public readonly name: string = "ECDSA";
public readonly hashAlgorithms = ["SHA-1", "SHA-256", "SHA-384", "SHA-512"];
public usages: ProviderKeyUsages = {
privateKey: ["sign"],
publicKey: ["verify"],
};
public namedCurves = ["P-256", "P-384", "P-521", "K-256"];
public override checkAlgorithmParams(algorithm: EcdsaParams) {
this.checkRequiredProperty(algorithm, "hash");
this.checkHashAlgorithm(algorithm.hash as Algorithm, this.hashAlgorithms);
}
public abstract override onSign(algorithm: EcdsaParams, key: CryptoKey, data: ArrayBuffer, ...args: any[]): Promise<ArrayBuffer>;
public abstract override onVerify(algorithm: EcdsaParams, key: CryptoKey, signature: ArrayBuffer, data: ArrayBuffer, ...args: any[]): Promise<boolean>;
}

View File

@ -0,0 +1,18 @@
import { CryptoKey, EcdsaParams, ProviderKeyUsages } from "@peculiar/webcrypto-types";
import { EllipticProvider } from "./base";
export abstract class EdDsaProvider extends EllipticProvider {
public readonly name: string = "EdDSA";
public usages: ProviderKeyUsages = {
privateKey: ["sign"],
publicKey: ["verify"],
};
public namedCurves = ["Ed25519", "Ed448"];
public abstract override onSign(algorithm: EcdsaParams, key: CryptoKey, data: ArrayBuffer, ...args: any[]): Promise<ArrayBuffer>;
public abstract override onVerify(algorithm: EcdsaParams, key: CryptoKey, signature: ArrayBuffer, data: ArrayBuffer, ...args: any[]): Promise<boolean>;
}

View File

@ -0,0 +1,7 @@
export * from "./base";
export * from "./ecdsa";
export * from "./ecdh";
export * from "./ecdh_es";
export * from "./eddsa";
export * from "./curves";
export * from "./utils";

View File

@ -0,0 +1,121 @@
import { BufferSource, BufferSourceConverter } from "pvtsutils";
interface EcPoint {
x: BufferSource;
y: BufferSource;
}
interface EcSignaturePoint {
r: BufferSource;
s: BufferSource;
}
export class EcUtils {
/**
* Decodes ANSI X9.62 encoded point
* @note Used by SunPKCS11 and SunJSSE
* @param data ANSI X9.62 encoded point
* @param pointSize Size of the point in bits
* @returns Decoded point with x and y coordinates
*/
public static decodePoint(data: BufferSource, pointSize: number): EcPoint {
const view = BufferSourceConverter.toUint8Array(data);
if ((view.length === 0) || (view[0] !== 4)) {
throw new Error("Only uncompressed point format supported");
}
// Per ANSI X9.62, an encoded point is a 1 byte type followed by
// ceiling(log base 2 field-size / 8) bytes of x and the same of y.
const n = (view.length - 1) / 2;
if (n !== (Math.ceil(pointSize / 8))) {
throw new Error("Point does not match field size");
}
const xb = view.slice(1, n + 1);
const yb = view.slice(n + 1, n + 1 + n);
return { x: xb, y: yb };
}
/**
* Encodes EC point to ANSI X9.62 encoded point
* @param point EC point
* @param pointSize Size of the point in bits
* @returns ANSI X9.62 encoded point
*/
public static encodePoint(point: EcPoint, pointSize: number): Uint8Array {
// get field size in bytes (rounding up)
const size = Math.ceil(pointSize / 8);
// Check point data size
if (point.x.byteLength !== size || point.y.byteLength !== size) {
throw new Error("X,Y coordinates don't match point size criteria");
}
const x = BufferSourceConverter.toUint8Array(point.x);
const y = BufferSourceConverter.toUint8Array(point.y);
const res = new Uint8Array(size * 2 + 1);
res[0] = 4;
res.set(x, 1);
res.set(y, size + 1);
return res;
}
public static getSize(pointSize: number): number {
return Math.ceil(pointSize / 8);
}
public static encodeSignature(signature: EcSignaturePoint, pointSize: number): Uint8Array {
const size = this.getSize(pointSize);
const r = BufferSourceConverter.toUint8Array(signature.r);
const s = BufferSourceConverter.toUint8Array(signature.s);
const res = new Uint8Array(size * 2);
res.set(this.padStart(r, size));
res.set(this.padStart(s, size), size);
return res;
}
public static decodeSignature(data: BufferSource, pointSize: number): EcSignaturePoint {
const size = this.getSize(pointSize);
const view = BufferSourceConverter.toUint8Array(data);
if (view.length !== (size * 2)) {
throw new Error("Incorrect size of the signature");
}
const r = view.slice(0, size);
const s = view.slice(size);
return {
r: this.trimStart(r),
s: this.trimStart(s),
}
}
public static trimStart(data: Uint8Array): Uint8Array {
let i = 0;
while ((i < data.length - 1) && (data[i] === 0)) {
i++;
}
if (i === 0) {
return data;
}
return data.slice(i, data.length);
}
public static padStart(data: Uint8Array, size: number): Uint8Array {
if (size === data.length) {
return data;
}
const res = new Uint8Array(size);
res.set(data, size - data.length)
return res;
}
}

View File

@ -0,0 +1,4 @@
import { CryptoError } from "./crypto";
export class AlgorithmError extends CryptoError {
}

View File

@ -0,0 +1,2 @@
export class CryptoError extends Error {
}

View File

@ -0,0 +1,5 @@
export * from "./algorithm";
export * from "./crypto";
export * from "./not_implemented";
export * from "./operation";
export * from "./required_property";

View File

@ -0,0 +1,7 @@
import { CryptoError } from "./crypto";
export class UnsupportedOperationError extends CryptoError {
constructor(methodName?: string) {
super(`Unsupported operation: ${methodName ? `${methodName}` : ""}`);
}
}

View File

@ -0,0 +1,4 @@
import { CryptoError } from "./crypto";
export class OperationError extends CryptoError {
}

View File

@ -0,0 +1,7 @@
import { CryptoError } from "./crypto";
export class RequiredPropertyError extends CryptoError {
constructor(propName: string, target?: string) {
super(`${propName}: Missing required property${target ? ` in ${target}` : ""}`);
}
}

View File

@ -0,0 +1,41 @@
import * as types from "@peculiar/webcrypto-types";
import { BufferSourceConverter } from "pvtsutils";
import { CryptoKey } from "../crypto_key";
import { ProviderCrypto } from "../provider";
export abstract class HkdfProvider extends ProviderCrypto {
public name = "HKDF";
public hashAlgorithms = ["SHA-1", "SHA-256", "SHA-384", "SHA-512"];
public usages: types.KeyUsages = ["deriveKey", "deriveBits"];
public override checkAlgorithmParams(algorithm: types.HkdfParams) {
// hash
this.checkRequiredProperty(algorithm, "hash");
this.checkHashAlgorithm(algorithm.hash as types.Algorithm, this.hashAlgorithms);
// salt
this.checkRequiredProperty(algorithm, "salt");
if (!BufferSourceConverter.isBufferSource(algorithm.salt)) {
throw new TypeError("salt: Is not of type '(ArrayBuffer or ArrayBufferView)'");
}
// info
this.checkRequiredProperty(algorithm, "info");
if (!BufferSourceConverter.isBufferSource(algorithm.info)) {
throw new TypeError("salt: Is not of type '(ArrayBuffer or ArrayBufferView)'");
}
}
public override checkImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.Algorithm, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]) {
super.checkImportKey(format, keyData, algorithm, extractable, keyUsages);
if (extractable) {
// If extractable is not false, then throw a SyntaxError
throw new SyntaxError("extractable: Must be 'false'");
}
}
public abstract override onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.Algorithm, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise<CryptoKey>;
public abstract override onDeriveBits(algorithm: types.HkdfParams, baseKey: CryptoKey, length: number, ...args: any[]): Promise<ArrayBuffer>;
}

View File

@ -0,0 +1,56 @@
import * as types from "@peculiar/webcrypto-types";
import { CryptoKey } from "../crypto_key";
import { ProviderCrypto } from "../provider";
export abstract class HmacProvider extends ProviderCrypto {
public name = "HMAC";
public hashAlgorithms = ["SHA-1", "SHA-256", "SHA-384", "SHA-512"];
public usages: types.KeyUsages = ["sign", "verify"];
/**
* Returns default size in bits by hash algorithm name
* @param algName Name of the hash algorithm
*/
public getDefaultLength(algName: string) {
switch (algName.toUpperCase()) {
// Chrome, Safari and Firefox returns 512
case "SHA-1":
case "SHA-256":
case "SHA-384":
case "SHA-512":
return 512;
default:
throw new Error(`Unknown algorithm name '${algName}'`);
}
}
public override checkGenerateKeyParams(algorithm: types.HmacKeyGenParams) {
// hash
this.checkRequiredProperty(algorithm, "hash");
this.checkHashAlgorithm(algorithm.hash as types.Algorithm, this.hashAlgorithms);
// length
if ("length" in algorithm) {
if (typeof algorithm.length !== "number") {
throw new TypeError("length: Is not a Number");
}
if (algorithm.length < 1) {
throw new RangeError("length: Number is out of range");
}
}
}
public override checkImportParams(algorithm: types.HmacImportParams) {
// hash
this.checkRequiredProperty(algorithm, "hash");
this.checkHashAlgorithm(algorithm.hash as types.Algorithm, this.hashAlgorithms);
}
public abstract override onGenerateKey(algorithm: types.PreparedHashedAlgorithm<types.HmacKeyGenParams>, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise<CryptoKey>;
public abstract override onExportKey(format: types.KeyFormat, key: CryptoKey, ...args: any[]): Promise<types.JsonWebKey | ArrayBuffer>;
public abstract override onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.HmacImportParams, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise<CryptoKey>;
}

View File

@ -0,0 +1,17 @@
export * from "./errors";
export * from "./aes";
export * from "./des";
export * from "./rsa";
export * from "./ec";
export * from "./hmac";
export * from "./pbkdf";
export * from "./hkdf";
export * from "./shake";
export * from "./crypto";
export * from "./provider";
export * from "./storage";
export * from "./subtle";
export * from "./crypto_key";
export * from "./utils";
export * from "./jwk_utils";
export * from "./schema";

View File

@ -0,0 +1,26 @@
import * as types from "@peculiar/webcrypto-types";
import { Convert } from "pvtsutils";
const REQUIRED_FIELDS = ["crv", "e", "k", "kty", "n", "x", "y"];
export class JwkUtils {
public static async thumbprint(hash: types.AlgorithmIdentifier, jwk: types.JsonWebKey, crypto: types.Crypto): Promise<ArrayBuffer> {
const data = this.format(jwk, true);
return crypto.subtle.digest(hash, Convert.FromBinary(JSON.stringify(data)));
}
public static format(jwk: types.JsonWebKey, remove = false): types.JsonWebKey {
let res = Object.entries(jwk);
if (remove) {
res = res.filter(o => REQUIRED_FIELDS.includes(o[0]));
}
res = res.sort(([keyA], [keyB]) =>
keyA > keyB ? 1 : keyA < keyB ? -1 : 0);
return Object.fromEntries(res) as types.JsonWebKey;
}
}

View File

@ -0,0 +1,45 @@
import * as types from "@peculiar/webcrypto-types";
import { CryptoKey } from "../crypto_key";
import { ProviderCrypto } from "../provider";
export abstract class Pbkdf2Provider extends ProviderCrypto {
public name = "PBKDF2";
public hashAlgorithms = ["SHA-1", "SHA-256", "SHA-384", "SHA-512"];
public usages: types.KeyUsages = ["deriveBits", "deriveKey"];
public override checkAlgorithmParams(algorithm: types.Pbkdf2Params) {
// hash
this.checkRequiredProperty(algorithm, "hash");
this.checkHashAlgorithm(algorithm.hash as types.Algorithm, this.hashAlgorithms);
// salt
this.checkRequiredProperty(algorithm, "salt");
if (!(algorithm.salt instanceof ArrayBuffer || ArrayBuffer.isView(algorithm.salt))) {
throw new TypeError("salt: Is not of type '(ArrayBuffer or ArrayBufferView)'");
}
// iterations
this.checkRequiredProperty(algorithm, "iterations");
if (typeof algorithm.iterations !== "number") {
throw new TypeError("iterations: Is not a Number");
}
if (algorithm.iterations < 1) {
throw new TypeError("iterations: Is less than 1");
}
}
public override checkImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.Algorithm, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]) {
super.checkImportKey(format, keyData, algorithm, extractable, keyUsages);
if (extractable) {
// If extractable is not false, then throw a SyntaxError
throw new SyntaxError("extractable: Must be 'false'");
}
}
public abstract override onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.Algorithm, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise<CryptoKey>;
public abstract override onDeriveBits(algorithm: types.Pbkdf2Params, baseKey: CryptoKey, length: number, ...args: any[]): Promise<ArrayBuffer>;
}

View File

@ -0,0 +1,268 @@
import * as types from "@peculiar/webcrypto-types";
import { BufferSourceConverter } from "pvtsutils";
import { AlgorithmError, CryptoError, OperationError, RequiredPropertyError, UnsupportedOperationError } from "./errors";
import { assertJWK } from "./utils";
export interface IProviderCheckOptions {
keyUsage?: boolean;
}
export abstract class ProviderCrypto {
/**
* Name of the algorithm
*/
public abstract readonly name: string;
/**
* Key usages for secret key or key pair
*/
public abstract readonly usages: types.ProviderKeyUsages;
//#region Digest
public async digest(algorithm: types.Algorithm, data: ArrayBuffer, ...args: any[]): Promise<ArrayBuffer>;
public async digest(...args: any[]): Promise<ArrayBuffer> {
this.checkDigest.apply(this, args as unknown as any);
return this.onDigest.apply(this, args as unknown as any);
}
public checkDigest(algorithm: types.Algorithm, data: ArrayBuffer) {
this.checkAlgorithmName(algorithm);
}
public async onDigest(algorithm: types.Algorithm, data: ArrayBuffer): Promise<ArrayBuffer> {
throw new UnsupportedOperationError("digest");
}
//#endregion
//#region Generate key
public async generateKey(algorithm: types.RsaHashedKeyGenParams | types.EcKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<types.CryptoKeyPair>;
public async generateKey(algorithm: types.AesKeyGenParams | types.HmacKeyGenParams | types.Pbkdf2Params, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<types.CryptoKey>;
public async generateKey(algorithm: types.Algorithm, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise<types.CryptoKeyPair | types.CryptoKey>;
public async generateKey(...args: any[]): Promise<types.CryptoKeyPair | types.CryptoKey> {
this.checkGenerateKey.apply(this, args as unknown as any);
return this.onGenerateKey.apply(this, args as unknown as any);
}
public checkGenerateKey(algorithm: types.Algorithm, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]) {
this.checkAlgorithmName(algorithm);
this.checkGenerateKeyParams(algorithm);
if (!(keyUsages && keyUsages.length)) {
throw new TypeError(`Usages cannot be empty when creating a key.`);
}
let allowedUsages: types.KeyUsages;
if (Array.isArray(this.usages)) {
allowedUsages = this.usages;
} else {
allowedUsages = this.usages.privateKey.concat(this.usages.publicKey);
}
this.checkKeyUsages(keyUsages, allowedUsages);
}
public checkGenerateKeyParams(algorithm: types.Algorithm) {
// nothing
}
public async onGenerateKey(algorithm: types.Algorithm, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise<types.CryptoKeyPair | types.CryptoKey> {
throw new UnsupportedOperationError("generateKey");
}
//#endregion
//#region Sign
public async sign(algorithm: types.Algorithm, key: types.CryptoKey, data: ArrayBuffer, ...args: any[]): Promise<ArrayBuffer>;
public async sign(...args: any[]): Promise<ArrayBuffer> {
this.checkSign.apply(this, args as unknown as any);
return this.onSign.apply(this, args as unknown as any);
}
public checkSign(algorithm: types.Algorithm, key: types.CryptoKey, data: ArrayBuffer, ...args: any[]) {
this.checkAlgorithmName(algorithm);
this.checkAlgorithmParams(algorithm);
this.checkCryptoKey(key, "sign");
}
public async onSign(algorithm: types.Algorithm, key: types.CryptoKey, data: ArrayBuffer, ...args: any[]): Promise<ArrayBuffer> {
throw new UnsupportedOperationError("sign");
}
//#endregion
//#region Verify
public async verify(algorithm: types.Algorithm, key: types.CryptoKey, signature: ArrayBuffer, data: ArrayBuffer, ...args: any[]): Promise<boolean>;
public async verify(...args: any[]): Promise<boolean> {
this.checkVerify.apply(this, args as unknown as any);
return this.onVerify.apply(this, args as unknown as any);
}
public checkVerify(algorithm: types.Algorithm, key: types.CryptoKey, signature: ArrayBuffer, data: ArrayBuffer, ...args: any[]) {
this.checkAlgorithmName(algorithm);
this.checkAlgorithmParams(algorithm);
this.checkCryptoKey(key, "verify");
}
public async onVerify(algorithm: types.Algorithm, key: types.CryptoKey, signature: ArrayBuffer, data: ArrayBuffer, ...args: any[]): Promise<boolean> {
throw new UnsupportedOperationError("verify");
}
//#endregion
//#region Encrypt
public async encrypt(algorithm: types.Algorithm, key: types.CryptoKey, data: ArrayBuffer, options?: IProviderCheckOptions, ...args: any[]): Promise<ArrayBuffer>;
public async encrypt(...args: any[]): Promise<ArrayBuffer> {
this.checkEncrypt.apply(this, args as unknown as any);
return this.onEncrypt.apply(this, args as unknown as any);
}
public checkEncrypt(algorithm: types.Algorithm, key: types.CryptoKey, data: ArrayBuffer, options: IProviderCheckOptions = {}, ...args: any[]) {
this.checkAlgorithmName(algorithm);
this.checkAlgorithmParams(algorithm);
this.checkCryptoKey(key, options.keyUsage ? "encrypt" : void 0);
}
public async onEncrypt(algorithm: types.Algorithm, key: types.CryptoKey, data: ArrayBuffer, ...args: any[]): Promise<ArrayBuffer> {
throw new UnsupportedOperationError("encrypt");
}
//#endregion
//#region Decrypt
public async decrypt(algorithm: types.Algorithm, key: types.CryptoKey, data: ArrayBuffer, options?: IProviderCheckOptions, ...args: any[]): Promise<ArrayBuffer>;
public async decrypt(...args: any[]): Promise<ArrayBuffer> {
this.checkDecrypt.apply(this, args as unknown as any);
return this.onDecrypt.apply(this, args as unknown as any);
}
public checkDecrypt(algorithm: types.Algorithm, key: types.CryptoKey, data: ArrayBuffer, options: IProviderCheckOptions = {}, ...args: any[]) {
this.checkAlgorithmName(algorithm);
this.checkAlgorithmParams(algorithm);
this.checkCryptoKey(key, options.keyUsage ? "decrypt" : void 0);
}
public async onDecrypt(algorithm: types.Algorithm, key: types.CryptoKey, data: ArrayBuffer, ...args: any[]): Promise<ArrayBuffer> {
throw new UnsupportedOperationError("decrypt");
}
//#endregion
//#region Derive bits
public async deriveBits(algorithm: types.Algorithm, baseKey: types.CryptoKey, length: number, options?: IProviderCheckOptions, ...args: any[]): Promise<ArrayBuffer>;
public async deriveBits(...args: any[]): Promise<ArrayBuffer> {
this.checkDeriveBits.apply(this, args as unknown as any);
return this.onDeriveBits.apply(this, args as unknown as any);
}
public checkDeriveBits(algorithm: types.Algorithm, baseKey: types.CryptoKey, length: number, options: IProviderCheckOptions = {}, ...args: any[]) {
this.checkAlgorithmName(algorithm);
this.checkAlgorithmParams(algorithm);
this.checkCryptoKey(baseKey, options.keyUsage ? "deriveBits" : void 0);
if (length % 8 !== 0) {
throw new OperationError("length: Is not multiple of 8");
}
}
public async onDeriveBits(algorithm: types.Algorithm, baseKey: types.CryptoKey, length: number, ...args: any[]): Promise<ArrayBuffer> {
throw new UnsupportedOperationError("deriveBits");
}
//#endregion
//#region Export key
public async exportKey(format: types.KeyFormat, key: types.CryptoKey, ...args: any[]): Promise<types.JsonWebKey | ArrayBuffer>;
public async exportKey(...args: any[]): Promise<types.JsonWebKey | ArrayBuffer> {
this.checkExportKey.apply(this, args as unknown as any);
return this.onExportKey.apply(this, args as unknown as any);
}
public checkExportKey(format: types.KeyFormat, key: types.CryptoKey, ...args: any[]) {
this.checkKeyFormat(format);
this.checkCryptoKey(key);
if (!key.extractable) {
throw new CryptoError("key: Is not extractable");
}
}
public async onExportKey(format: types.KeyFormat, key: types.CryptoKey, ...args: any[]): Promise<types.JsonWebKey | ArrayBuffer> {
throw new UnsupportedOperationError("exportKey");
}
//#endregion
//#region Import key
public async importKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.Algorithm, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise<types.CryptoKey>;
public async importKey(...args: any[]): Promise<types.CryptoKey> {
this.checkImportKey.apply(this, args as unknown as any);
return this.onImportKey.apply(this, args as unknown as any);
}
public checkImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.Algorithm, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]) {
this.checkKeyFormat(format);
this.checkKeyData(format, keyData);
this.checkAlgorithmName(algorithm);
this.checkImportParams(algorithm);
// check key usages
if (Array.isArray(this.usages)) {
// symmetric provider
this.checkKeyUsages(keyUsages, this.usages);
} else {
// asymmetric provider
// TODO: implement
}
}
public async onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.Algorithm, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise<types.CryptoKey> {
throw new UnsupportedOperationError("importKey");
}
//#endregion
public checkAlgorithmName(algorithm: types.Algorithm) {
if (algorithm.name.toLowerCase() !== this.name.toLowerCase()) {
throw new AlgorithmError("Unrecognized name");
}
}
public checkAlgorithmParams(algorithm: types.Algorithm) {
// nothing
}
public checkDerivedKeyParams(algorithm: types.Algorithm) {
// nothing
}
public checkKeyUsages(usages: types.KeyUsages, allowed: types.KeyUsages) {
for (const usage of usages) {
if (allowed.indexOf(usage) === -1) {
throw new TypeError("Cannot create a key using the specified key usages");
}
}
}
public checkCryptoKey(key: types.CryptoKey, keyUsage?: types.KeyUsage) {
this.checkAlgorithmName(key.algorithm);
if (keyUsage && key.usages.indexOf(keyUsage) === -1) {
throw new CryptoError(`key does not match that of operation`);
}
}
public checkRequiredProperty(data: object, propName: string) {
if (!(propName in data)) {
throw new RequiredPropertyError(propName);
}
}
public checkHashAlgorithm(algorithm: types.Algorithm, hashAlgorithms: string[]) {
for (const item of hashAlgorithms) {
if (item.toLowerCase() === algorithm.name.toLowerCase()) {
return;
}
}
throw new OperationError(`hash: Must be one of ${hashAlgorithms.join(", ")}`);
}
public checkImportParams(algorithm: types.Algorithm) {
// nothing
}
public checkKeyFormat(format: any) {
switch (format) {
case "raw":
case "pkcs8":
case "spki":
case "jwk":
break;
default:
throw new TypeError("format: Is invalid value. Must be 'jwk', 'raw', 'spki', or 'pkcs8'");
}
}
public checkKeyData(format: types.KeyFormat, keyData: any) {
if (!keyData) {
throw new TypeError("keyData: Cannot be empty on empty on key importing");
}
if (format === "jwk") {
assertJWK(keyData, "keyData");
} else if (!BufferSourceConverter.isBufferSource(keyData)) {
throw new TypeError("keyData: Is not ArrayBufferView or ArrayBuffer");
}
}
protected prepareData(data: any) {
return BufferSourceConverter.toArrayBuffer(data);
}
}

View File

@ -0,0 +1,43 @@
import * as types from "@peculiar/webcrypto-types";
import { Convert } from "pvtsutils";
import { CryptoKey } from "../crypto_key";
import { ProviderCrypto } from "../provider";
export abstract class RsaProvider extends ProviderCrypto {
public hashAlgorithms = ["SHA-1", "SHA-256", "SHA-384", "SHA-512"];
public override checkGenerateKeyParams(algorithm: types.RsaHashedKeyGenParams) {
// hash
this.checkRequiredProperty(algorithm, "hash");
this.checkHashAlgorithm(algorithm.hash as types.Algorithm, this.hashAlgorithms);
// public exponent
this.checkRequiredProperty(algorithm, "publicExponent");
if (!(algorithm.publicExponent && algorithm.publicExponent instanceof Uint8Array)) {
throw new TypeError("publicExponent: Missing or not a Uint8Array");
}
const publicExponent = 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");
if (algorithm.modulusLength % 8
|| algorithm.modulusLength < 256
|| algorithm.modulusLength > 16384) {
throw new TypeError("The modulus length must be a multiple of 8 bits and >= 256 and <= 16384");
}
}
public override checkImportParams(algorithm: types.RsaHashedImportParams) {
this.checkRequiredProperty(algorithm, "hash");
this.checkHashAlgorithm(algorithm.hash as types.Algorithm, this.hashAlgorithms);
}
public abstract override onGenerateKey(algorithm: types.RsaHashedKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise<types.CryptoKeyPair>;
public abstract override onExportKey(format: types.KeyFormat, key: CryptoKey, ...args: any[]): Promise<types.JsonWebKey | ArrayBuffer>;
public abstract override onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.RsaHashedImportParams, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise<CryptoKey>;
}

View File

@ -0,0 +1,4 @@
export * from "./base";
export * from "./ssa";
export * from "./pss";
export * from "./oaep";

View File

@ -0,0 +1,24 @@
import * as types from "@peculiar/webcrypto-types";
import { RsaProvider } from "./base";
export abstract class RsaOaepProvider extends RsaProvider {
public readonly name = "RSA-OAEP";
public usages: types.ProviderKeyUsages = {
privateKey: ["decrypt", "unwrapKey"],
publicKey: ["encrypt", "wrapKey"],
};
public override checkAlgorithmParams(algorithm: types.RsaOaepParams) {
// label
if (algorithm.label
&& !(algorithm.label instanceof ArrayBuffer || ArrayBuffer.isView(algorithm.label))) {
throw new TypeError("label: Is not of type '(ArrayBuffer or ArrayBufferView)'");
}
}
public abstract override onEncrypt(algorithm: types.RsaOaepParams, key: types.CryptoKey, data: ArrayBuffer, ...args: any[]): Promise<ArrayBuffer>;
public abstract override onDecrypt(algorithm: types.RsaOaepParams, key: types.CryptoKey, data: ArrayBuffer, ...args: any[]): Promise<ArrayBuffer>;
}

View File

@ -0,0 +1,26 @@
import * as types from "@peculiar/webcrypto-types";
import { RsaProvider } from "./base";
export abstract class RsaPssProvider extends RsaProvider {
public readonly name = "RSA-PSS";
public usages: types.ProviderKeyUsages = {
privateKey: ["sign"],
publicKey: ["verify"],
};
public override checkAlgorithmParams(algorithm: types.RsaPssParams) {
this.checkRequiredProperty(algorithm, "saltLength");
if (typeof algorithm.saltLength !== "number") {
throw new TypeError("saltLength: Is not a Number");
}
if (algorithm.saltLength < 0) {
throw new RangeError("saltLength: Must be positive number");
}
}
public abstract override onSign(algorithm: types.RsaPssParams, key: types.CryptoKey, data: ArrayBuffer, ...args: any[]): Promise<ArrayBuffer>;
public abstract override onVerify(algorithm: types.RsaPssParams, key: types.CryptoKey, signature: ArrayBuffer, data: ArrayBuffer, ...args: any[]): Promise<boolean>;
}

View File

@ -0,0 +1,18 @@
import * as types from "@peculiar/webcrypto-types";
import { RsaProvider } from "./base";
export interface RsaSsaParams extends types.Algorithm { }
export abstract class RsaSsaProvider extends RsaProvider {
public readonly name = "RSASSA-PKCS1-v1_5";
public usages: types.ProviderKeyUsages = {
privateKey: ["sign"],
publicKey: ["verify"],
};
public abstract override onSign(algorithm: RsaSsaParams, key: types.CryptoKey, data: ArrayBuffer, ...args: any[]): Promise<ArrayBuffer>;
public abstract override onVerify(algorithm: RsaSsaParams, key: types.CryptoKey, signature: ArrayBuffer, data: ArrayBuffer, ...args: any[]): Promise<boolean>;
}

View File

@ -0,0 +1,31 @@
import { AsnProp, AsnPropTypes } from "@peculiar/asn1-schema";
// RFC 5280
// https://tools.ietf.org/html/rfc5280#section-4.1.1.2
//
// AlgorithmIdentifier ::= SEQUENCE {
// algorithm OBJECT IDENTIFIER,
// parameters ANY DEFINED BY algorithm OPTIONAL }
// -- contains a value of the type
// -- registered for use with the
// -- algorithm object identifier value
export type ParametersType = ArrayBuffer | null;
export class AlgorithmIdentifier {
@AsnProp({
type: AsnPropTypes.ObjectIdentifier,
})
public algorithm!: string;
@AsnProp({
type: AsnPropTypes.Any,
optional: true,
})
public parameters?: ParametersType;
constructor(params?: Partial<AlgorithmIdentifier>) {
Object.assign(this, params);
}
}

View File

@ -0,0 +1,2 @@
export * from "./integer_without_paddings";
export * from "./integer_converter";

View File

@ -0,0 +1,11 @@
import { IAsnConverter } from "@peculiar/asn1-schema";
import * as asn1 from "asn1js";
export const AsnIntegerArrayBufferConverter: IAsnConverter<ArrayBuffer> = {
fromASN: (value: asn1.Integer) => {
return value.convertFromDER().valueBlock.valueHexView.slice().buffer;
},
toASN: (value: ArrayBuffer) => {
return new asn1.Integer({ valueHex: value }).convertToDER();
},
};

View File

@ -0,0 +1,20 @@
import { IAsnConverter } from "@peculiar/asn1-schema";
import * as asn1 from "asn1js";
export const AsnIntegerWithoutPaddingConverter: IAsnConverter<ArrayBuffer> = {
fromASN: (value: any) => {
const bytes = new Uint8Array(value.valueBlock.valueHex);
return (bytes[0] === 0)
? bytes.buffer.slice(1)
: bytes.buffer;
},
toASN: (value: ArrayBuffer): any => {
const bytes = new Uint8Array(value);
if (bytes[0] > 127) {
const newValue = new Uint8Array(bytes.length + 1);
newValue.set(bytes, 1);
return new asn1.Integer({ valueHex: newValue.buffer } as any);
}
return new asn1.Integer({ valueHex: value } as any);
},
};

View File

@ -0,0 +1,55 @@
import * as types from "@peculiar/webcrypto-types";
import { AsnIntegerConverter, AsnProp, AsnPropTypes, AsnSerializer } from "@peculiar/asn1-schema";
import { IJsonConvertible } from "@peculiar/json-schema";
import { Convert } from "pvtsutils";
import { EcPublicKey } from "./ec_public_key";
// RFC 5915
// https://tools.ietf.org/html/rfc5915#section-3
//
// ECPrivateKey ::= SEQUENCE {
// version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
// privateKey OCTET STRING,
// parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
// publicKey [1] BIT STRING OPTIONAL
// }
export class EcPrivateKey implements IJsonConvertible {
@AsnProp({ type: AsnPropTypes.Integer, converter: AsnIntegerConverter })
public version = 1;
@AsnProp({ type: AsnPropTypes.OctetString })
public privateKey = new ArrayBuffer(0);
@AsnProp({ context: 0, type: AsnPropTypes.Any, optional: true })
public parameters?: ArrayBuffer;
@AsnProp({ context: 1, type: AsnPropTypes.BitString, optional: true })
public publicKey?: ArrayBuffer;
public fromJSON(json: any): this {
if (!("d" in json)) {
throw new Error("d: Missing required property");
}
this.privateKey = Convert.FromBase64Url(json.d);
if ("x" in json) {
const publicKey = new EcPublicKey();
publicKey.fromJSON(json);
this.publicKey = AsnSerializer.toASN(publicKey).valueBlock.valueHex;
}
return this;
}
public toJSON() {
const jwk: types.JsonWebKey = {};
jwk.d = Convert.ToBase64Url(this.privateKey);
if (this.publicKey) {
Object.assign(jwk, new EcPublicKey(this.publicKey).toJSON());
}
return jwk;
}
}

View File

@ -0,0 +1,63 @@
import { AsnProp, AsnPropTypes, AsnType, AsnTypeTypes } from "@peculiar/asn1-schema";
import { IJsonConvertible } from "@peculiar/json-schema";
import { combine, Convert } from "pvtsutils";
// RFC 5480
// https://tools.ietf.org/html/rfc5480#section-2.2
//
// ECPoint ::= OCTET STRING
@AsnType({ type: AsnTypeTypes.Choice })
export class EcPublicKey implements IJsonConvertible {
@AsnProp({ type: AsnPropTypes.OctetString })
public value = new ArrayBuffer(0);
constructor(value?: ArrayBuffer) {
if (value) {
this.value = value;
}
}
public toJSON() {
let bytes = new Uint8Array(this.value);
if (bytes[0] !== 0x04) {
throw new Error("Wrong ECPoint. Current version supports only Uncompressed (0x04) point");
}
bytes = new Uint8Array(this.value.slice(1));
const size = bytes.length / 2;
const offset = 0;
const json = {
x: Convert.ToBase64Url(bytes.buffer.slice(offset, offset + size)),
y: Convert.ToBase64Url(bytes.buffer.slice(offset + size, offset + size + size)),
};
return json;
}
public fromJSON(json: any): this {
if (!("x" in json)) {
throw new Error("x: Missing required property");
}
if (!("y" in json)) {
throw new Error("y: Missing required property");
}
const x = Convert.FromBase64Url(json.x);
const y = Convert.FromBase64Url(json.y);
const value = combine(
new Uint8Array([0x04]).buffer, // uncompressed bit
x,
y,
);
this.value = new Uint8Array(value).buffer;
return this;
}
}

View File

@ -0,0 +1,51 @@
import { AsnProp, AsnPropTypes } from "@peculiar/asn1-schema";
import { BufferSourceConverter, BufferSource } from "pvtsutils";
import { EcUtils } from "../../ec/utils";
import { AsnIntegerWithoutPaddingConverter } from "./converters";
// RFC 3279
// https://tools.ietf.org/html/rfc3279#section-2.2.3
//
// ECDSA-Sig-Value ::= SEQUENCE {
// r INTEGER,
// s INTEGER
// }
export class EcDsaSignature {
/**
* Create EcDsaSignature from X9.62 signature
* @param value X9.62 signature
* @returns EcDsaSignature
*/
public static fromWebCryptoSignature(value: BufferSource): EcDsaSignature {
const pointSize = value.byteLength / 2;
const point = EcUtils.decodeSignature(value, pointSize * 8);
const ecSignature = new EcDsaSignature();
ecSignature.r = BufferSourceConverter.toArrayBuffer(point.r);
ecSignature.s = BufferSourceConverter.toArrayBuffer(point.s);
return ecSignature;
}
@AsnProp({ type: AsnPropTypes.Integer, converter: AsnIntegerWithoutPaddingConverter })
public r = new ArrayBuffer(0);
@AsnProp({ type: AsnPropTypes.Integer, converter: AsnIntegerWithoutPaddingConverter })
public s = new ArrayBuffer(0);
/**
* Converts ECDSA signature into X9.62 signature format
* @param pointSize EC point size in bits
* @returns ECDSA signature in X9.62 signature format
*/
public toWebCryptoSignature(pointSize?: number): ArrayBuffer {
pointSize ??= Math.max(this.r.byteLength, this.s.byteLength) * 8;
const signature = EcUtils.encodeSignature(this, pointSize);
return signature.buffer;
}
}

View File

@ -0,0 +1,27 @@
import { AsnProp, AsnPropTypes, AsnType, AsnTypeTypes } from "@peculiar/asn1-schema";
import { IJsonConvertible } from "@peculiar/json-schema";
import * as types from "@peculiar/webcrypto-types";
import { Convert } from "pvtsutils";
@AsnType({ type: AsnTypeTypes.Choice })
export class EdPrivateKey implements IJsonConvertible {
@AsnProp({ type: AsnPropTypes.OctetString })
public value = new ArrayBuffer(0);
public fromJSON(json: any): this {
if (!json.d) {
throw new Error("d: Missing required property");
}
this.value = Convert.FromBase64Url(json.d);
return this;
}
public toJSON() {
const jwk: types.JsonWebKey = {
d: Convert.ToBase64Url(this.value),
};
return jwk;
}
}

View File

@ -0,0 +1,40 @@
import { AsnProp, AsnPropTypes, AsnType, AsnTypeTypes } from "@peculiar/asn1-schema";
import { IJsonConvertible } from "@peculiar/json-schema";
import { Convert } from "pvtsutils";
// RFC 8410
// https://datatracker.ietf.org/doc/html/rfc8410
//
// PublicKey ::= BIT STRING
@AsnType({ type: AsnTypeTypes.Choice })
export class EdPublicKey implements IJsonConvertible {
@AsnProp({ type: AsnPropTypes.BitString })
public value = new ArrayBuffer(0);
constructor(value?: ArrayBuffer) {
if (value) {
this.value = value;
}
}
public toJSON() {
const json = {
x: Convert.ToBase64Url(this.value),
};
return json;
}
public fromJSON(json: any): this {
if (!("x" in json)) {
throw new Error("x: Missing required property");
}
this.value = Convert.FromBase64Url(json.x);
return this;
}
}

View File

@ -0,0 +1,14 @@
export * from "./object_identifier";
export * from "./algorithm_identifier";
export * from "./private_key_info";
export * from "./public_key_info";
export * from "./rsa_private_key";
export * from "./rsa_public_key";
export * from "./ec_private_key";
export * from "./ec_public_key";
export * from "./ec_signature";
export * from "./one_asymmetric_key";
export * from "./ed_private_key";
export * from "./ed_public_key";
export * from "./rfc8410";
export * from "./converters";

View File

@ -0,0 +1,14 @@
import { AsnProp, AsnPropTypes, AsnType, AsnTypeTypes } from "@peculiar/asn1-schema";
@AsnType({ type: AsnTypeTypes.Choice })
export class ObjectIdentifier {
@AsnProp({type: AsnPropTypes.ObjectIdentifier})
public value!: string;
constructor(value?: string) {
if (value) {
this.value = value;
}
}
}

View File

@ -0,0 +1,26 @@
import { AsnProp, AsnPropTypes } from "@peculiar/asn1-schema";
import { PrivateKeyInfo } from "./private_key_info";
/**
* ```asn
* OneAsymmetricKey ::= SEQUENCE {
* version Version,
* privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
* privateKey PrivateKey,
* attributes [0] IMPLICIT Attributes OPTIONAL,
* ...,
* [[2: publicKey [1] IMPLICIT PublicKey OPTIONAL ]],
* ...
* }
*
* PrivateKey ::= OCTET STRING
*
* PublicKey ::= BIT STRING
* ```
*/
export class OneAsymmetricKey extends PrivateKeyInfo {
@AsnProp({ context: 1, implicit: true, type: AsnPropTypes.BitString, optional: true })
public publicKey?: ArrayBuffer;
}

View File

@ -0,0 +1,35 @@
import { AsnProp, AsnPropTypes } from "@peculiar/asn1-schema";
import { AlgorithmIdentifier } from "./algorithm_identifier";
// RFC 5208
// https://tools.ietf.org/html/rfc5208#section-5
//
// PrivateKeyInfo ::= SEQUENCE {
// version Version,
// privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
// privateKey PrivateKey,
// attributes [0] IMPLICIT Attributes OPTIONAL }
//
// Version ::= INTEGER
//
// PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
//
// PrivateKey ::= OCTET STRING
//
// Attributes ::= SET OF Attribute
export class PrivateKeyInfo {
@AsnProp({ type: AsnPropTypes.Integer })
public version = 0;
@AsnProp({ type: AlgorithmIdentifier })
public privateKeyAlgorithm = new AlgorithmIdentifier();
@AsnProp({ type: AsnPropTypes.OctetString })
public privateKey = new ArrayBuffer(0);
@AsnProp({ type: AsnPropTypes.Any, optional: true })
public attributes?: ArrayBuffer;
}

View File

@ -0,0 +1,19 @@
import { AsnProp, AsnPropTypes } from "@peculiar/asn1-schema";
import { AlgorithmIdentifier } from "./algorithm_identifier";
// RFC 5280
// https://tools.ietf.org/html/rfc5280#section-4.1
//
// SubjectPublicKeyInfo ::= SEQUENCE {
// algorithm AlgorithmIdentifier,
// subjectPublicKey BIT STRING
export class PublicKeyInfo {
@AsnProp({ type: AlgorithmIdentifier })
public publicKeyAlgorithm = new AlgorithmIdentifier();
@AsnProp({ type: AsnPropTypes.BitString })
public publicKey = new ArrayBuffer(0);
}

View File

@ -0,0 +1,24 @@
import { AsnProp, AsnPropTypes, AsnType, AsnTypeTypes } from "@peculiar/asn1-schema";
import { JsonProp, JsonPropTypes } from "@peculiar/json-schema";
import { JsonBase64UrlArrayBufferConverter } from "../../json/converters";
/**
* ASN.1
* ```
* CurvePrivateKey ::= OCTET STRING
* ```
*
* JSON
* ```json
* {
* "d": "base64url"
* }
* ```
*/
@AsnType({ type: AsnTypeTypes.Choice })
export class CurvePrivateKey {
@AsnProp({ type: AsnPropTypes.OctetString })
@JsonProp({ type: JsonPropTypes.String, converter: JsonBase64UrlArrayBufferConverter })
public d!: ArrayBuffer;
}

View File

@ -0,0 +1,2 @@
export * from "./curve_private_key";
export * from "./object_identifiers";

View File

@ -0,0 +1,152 @@
/**
* ```
* secp256r1 OBJECT IDENTIFIER ::= {
* iso(1) member-body(2) us(840) ansi-X9-62(10045) curves(3)
* prime(1) 7 }
* ```
*/
export const idSecp256r1 = "1.2.840.10045.3.1.7";
/**
* ```
* ellipticCurve OBJECT IDENTIFIER ::= {
* iso(1) identified-organization(3) certicom(132) curve(0) }
* ```
*/
export const idEllipticCurve = "1.3.132.0";
/**
* ```
* secp384r1 OBJECT IDENTIFIER ::= { ellipticCurve 34 }
* ```
*/
export const idSecp384r1 = `${idEllipticCurve}.34`;
/**
* ```
* secp521r1 OBJECT IDENTIFIER ::= { ellipticCurve 35 }
* ```
*/
export const idSecp521r1 = `${idEllipticCurve}.35`;
/**
* ```
* secp256k1 OBJECT IDENTIFIER ::= { ellipticCurve 10 }
* ```
*/
export const idSecp256k1 = `${idEllipticCurve}.10`;
/**
* ```
* ecStdCurvesAndGeneration OBJECT IDENTIFIER ::= {
* iso(1) identified-organization(3) teletrust(36) algorithm(3)
* signature-algorithm(3) ecSign(2) ecStdCurvesAndGeneration(8)
* }
* ellipticCurve OBJECT IDENTIFIER ::= { ecStdCurvesAndGeneration 1 }
* versionOne OBJECT IDENTIFIER ::= { ellipticCurve 1 }
* ```
*/
export const idVersionOne = "1.3.36.3.3.2.8.1.1";
/**
* ```
* brainpoolP160r1 OBJECT IDENTIFIER ::= { versionOne 1 }
* ```
*/
export const idBrainpoolP160r1 = `${idVersionOne}.1`;
/**
* ```
* brainpoolP160t1 OBJECT IDENTIFIER ::= { versionOne 2 }
* ```
*/
export const idBrainpoolP160t1 = `${idVersionOne}.2`;
/**
* ```
* brainpoolP192r1 OBJECT IDENTIFIER ::= { versionOne 3 }
* ```
*/
export const idBrainpoolP192r1 = `${idVersionOne}.3`;
/**
* ```
* brainpoolP192t1 OBJECT IDENTIFIER ::= { versionOne 4 }
* ```
*/
export const idBrainpoolP192t1 = `${idVersionOne}.4`;
/**
* ```
* brainpoolP224r1 OBJECT IDENTIFIER ::= { versionOne 5 }
* ```
*/
export const idBrainpoolP224r1 = `${idVersionOne}.5`;
/**
* ```
* brainpoolP224t1 OBJECT IDENTIFIER ::= { versionOne 6 }
* ```
*/
export const idBrainpoolP224t1 = `${idVersionOne}.6`;
/**
* ```
* brainpoolP256r1 OBJECT IDENTIFIER ::= { versionOne 7 }
* ```
*/
export const idBrainpoolP256r1 = `${idVersionOne}.7`;
/**
* ```
* brainpoolP256t1 OBJECT IDENTIFIER ::= { versionOne 8 }
* ```
*/
export const idBrainpoolP256t1 = `${idVersionOne}.8`;
/**
* ```
* brainpoolP320r1 OBJECT IDENTIFIER ::= { versionOne 9 }
* ```
*/
export const idBrainpoolP320r1 = `${idVersionOne}.9`;
/**
* ```
* brainpoolP320t1 OBJECT IDENTIFIER ::= { versionOne 10 }
* ```
*/
export const idBrainpoolP320t1 = `${idVersionOne}.10`;
/**
* ```
* brainpoolP384r1 OBJECT IDENTIFIER ::= { versionOne 11 }
* ```
*/
export const idBrainpoolP384r1 = `${idVersionOne}.11`;
/**
* ```
* brainpoolP384t1 OBJECT IDENTIFIER ::= { versionOne 12 }
* ```
*/
export const idBrainpoolP384t1 = `${idVersionOne}.12`;
/**
* ```
* brainpoolP512r1 OBJECT IDENTIFIER ::= { versionOne 13 }
* ```
*/
export const idBrainpoolP512r1 = `${idVersionOne}.13`;
/**
* ```
* brainpoolP512t1 OBJECT IDENTIFIER ::= { versionOne 14 }
* ```
*/
export const idBrainpoolP512t1 = `${idVersionOne}.14`;
/**
* ```
* id-X25519 OBJECT IDENTIFIER ::= { 1 3 101 110 }
* ```
*/
export const idX25519 = "1.3.101.110";
/**
* ```
* id-X448 OBJECT IDENTIFIER ::= { 1 3 101 111 }
* ```
*/
export const idX448 = "1.3.101.111";
/**
* ```
* id-Ed25519 OBJECT IDENTIFIER ::= { 1 3 101 112 }
* ```
*/
export const idEd25519 = "1.3.101.112";
/**
* ```
* id-Ed448 OBJECT IDENTIFIER ::= { 1 3 101 113 }
* ```
*/
export const idEd448 = "1.3.101.113";

View File

@ -0,0 +1,63 @@
import { AsnIntegerConverter, AsnProp, AsnPropTypes, AsnType, AsnTypeTypes } from "@peculiar/asn1-schema";
import { JsonProp } from "@peculiar/json-schema";
import { JsonBase64UrlArrayBufferConverter } from "../json/converters";
import { AsnIntegerArrayBufferConverter } from "./converters";
// RFC 3437
// https://tools.ietf.org/html/rfc3447#appendix-A.1.2
//
// RSAPrivateKey ::= SEQUENCE {
// version Version,
// modulus INTEGER, -- n
// publicExponent INTEGER, -- e
// privateExponent INTEGER, -- d
// prime1 INTEGER, -- p
// prime2 INTEGER, -- q
// exponent1 INTEGER, -- d mod (p-1)
// exponent2 INTEGER, -- d mod (q-1)
// coefficient INTEGER, -- (inverse of q) mod p
// otherPrimeInfos OtherPrimeInfos OPTIONAL
// }
@AsnType({ type: AsnTypeTypes.Sequence })
export class RsaPrivateKey {
@AsnProp({ type: AsnPropTypes.Integer, converter: AsnIntegerConverter })
public version = 0;
@AsnProp({ type: AsnPropTypes.Integer, converter: AsnIntegerArrayBufferConverter })
@JsonProp({ name: "n", converter: JsonBase64UrlArrayBufferConverter })
public modulus = new ArrayBuffer(0);
@AsnProp({ type: AsnPropTypes.Integer, converter: AsnIntegerArrayBufferConverter })
@JsonProp({ name: "e", converter: JsonBase64UrlArrayBufferConverter })
public publicExponent = new ArrayBuffer(0);
@AsnProp({ type: AsnPropTypes.Integer, converter: AsnIntegerArrayBufferConverter })
@JsonProp({ name: "d", converter: JsonBase64UrlArrayBufferConverter })
public privateExponent = new ArrayBuffer(0);
@AsnProp({ type: AsnPropTypes.Integer, converter: AsnIntegerArrayBufferConverter })
@JsonProp({ name: "p", converter: JsonBase64UrlArrayBufferConverter })
public prime1 = new ArrayBuffer(0);
@AsnProp({ type: AsnPropTypes.Integer, converter: AsnIntegerArrayBufferConverter })
@JsonProp({ name: "q", converter: JsonBase64UrlArrayBufferConverter })
public prime2 = new ArrayBuffer(0);
@AsnProp({ type: AsnPropTypes.Integer, converter: AsnIntegerArrayBufferConverter })
@JsonProp({ name: "dp", converter: JsonBase64UrlArrayBufferConverter })
public exponent1 = new ArrayBuffer(0);
@AsnProp({ type: AsnPropTypes.Integer, converter: AsnIntegerArrayBufferConverter })
@JsonProp({ name: "dq", converter: JsonBase64UrlArrayBufferConverter })
public exponent2 = new ArrayBuffer(0);
@AsnProp({ type: AsnPropTypes.Integer, converter: AsnIntegerArrayBufferConverter })
@JsonProp({ name: "qi", converter: JsonBase64UrlArrayBufferConverter })
public coefficient = new ArrayBuffer(0);
@AsnProp({ type: AsnPropTypes.Any, optional: true })
public otherPrimeInfos?: ArrayBuffer;
}

View File

@ -0,0 +1,24 @@
import { AsnProp, AsnPropTypes } from "@peculiar/asn1-schema";
import { JsonProp } from "@peculiar/json-schema";
import { JsonBase64UrlArrayBufferConverter } from "../json/converters";
import { AsnIntegerArrayBufferConverter } from "./converters";
// RFC 3437
// https://tools.ietf.org/html/rfc3447#appendix-A.1.1
//
// RSAPublicKey ::= SEQUENCE {
// modulus INTEGER, -- n
// publicExponent INTEGER, -- e
// }
export class RsaPublicKey {
@AsnProp({ type: AsnPropTypes.Integer, converter: AsnIntegerArrayBufferConverter })
@JsonProp({ name: "n", converter: JsonBase64UrlArrayBufferConverter })
public modulus = new ArrayBuffer(0);
@AsnProp({ type: AsnPropTypes.Integer, converter: AsnIntegerArrayBufferConverter })
@JsonProp({ name: "e", converter: JsonBase64UrlArrayBufferConverter })
public publicExponent = new ArrayBuffer(0);
}

View File

@ -0,0 +1,2 @@
export * as asn1 from "./asn1";
export * as json from "./json";

View File

@ -0,0 +1,7 @@
import { IJsonConverter } from "@peculiar/json-schema";
import { Convert, BufferSourceConverter } from "pvtsutils";
export const JsonBase64UrlArrayBufferConverter: IJsonConverter<ArrayBuffer, string> = {
fromJSON: (value: string) => Convert.FromBase64Url(value),
toJSON: (value: ArrayBuffer) => Convert.ToBase64Url(new Uint8Array(value)),
};

View File

@ -0,0 +1 @@
export * from "./converters";

View File

@ -0,0 +1,30 @@
import { ProviderCrypto } from "../provider";
import * as types from "@peculiar/webcrypto-types";
export abstract class ShakeProvider extends ProviderCrypto {
public usages = [];
public defaultLength = 0;
public override digest(algorithm: types.ShakeParams, data: ArrayBuffer, ...args: any[]): Promise<ArrayBuffer>;
public override digest(...args: any[]): Promise<ArrayBuffer> {
args[0] = { length: this.defaultLength, ...args[0] };
return super.digest.apply(this, args as unknown as any);
}
public override checkDigest(algorithm: types.ShakeParams, data: ArrayBuffer): void {
super.checkDigest(algorithm, data);
const length = algorithm.length || 0;
if (typeof length !== "number") {
throw new TypeError("length: Is not a Number");
}
if (length < 0) {
throw new TypeError("length: Is negative");
}
}
public abstract override onDigest(algorithm: Required<types.ShakeParams>, data: ArrayBuffer): Promise<ArrayBuffer>;
}

View File

@ -0,0 +1,3 @@
export * from "./base";
export * from "./shake128";
export * from "./shake256";

View File

@ -0,0 +1,6 @@
import { ShakeProvider } from "./base";
export abstract class Shake128Provider extends ShakeProvider {
public override name = "shake128";
public override defaultLength = 16;
}

View File

@ -0,0 +1,6 @@
import { ShakeProvider } from "./base";
export abstract class Shake256Provider extends ShakeProvider {
public override name = "shake256";
public override defaultLength = 32;
}

View File

@ -0,0 +1,39 @@
import { ProviderCrypto } from "./provider";
export class ProviderStorage {
private items: { [algorithmName: string]: ProviderCrypto } = {};
public get(algorithmName: string): ProviderCrypto | null {
return this.items[algorithmName.toLowerCase()] || null;
}
public set(provider: ProviderCrypto) {
this.items[provider.name.toLowerCase()] = provider;
}
public removeAt(algorithmName: string) {
const provider = this.get(algorithmName.toLowerCase());
if (provider) {
delete this.items[algorithmName];
}
return provider;
}
public has(name: string) {
return !!this.get(name);
}
public get length() {
return Object.keys(this.items).length;
}
public get algorithms() {
const algorithms: string[] = [];
for (const key in this.items) {
const provider = this.items[key];
algorithms.push(provider.name);
}
return algorithms.sort();
}
}

250
packages/core/src/subtle.ts Normal file
View File

@ -0,0 +1,250 @@
import * as types from "@peculiar/webcrypto-types";
import { BufferSource, BufferSourceConverter, Convert } from "pvtsutils";
import { AlgorithmError } from "./errors";
import { ProviderCrypto } from "./provider";
import { ProviderStorage } from "./storage";
import { CryptoKey } from './crypto_key';
export class SubtleCrypto implements types.SubtleCrypto {
public static isHashedAlgorithm(data: any): data is types.HashedAlgorithm {
return data
&& typeof data === "object"
&& "name" in data
&& "hash" in data
? true
: false;
}
public providers = new ProviderStorage();
// @internal
public get [Symbol.toStringTag]() {
return "SubtleCrypto";
}
public async digest(algorithm: types.AlgorithmIdentifier | types.ShakeParams, data: BufferSource, ...args: any[]): Promise<ArrayBuffer>;
public async digest(...args: any[]): Promise<ArrayBuffer> {
this.checkRequiredArguments(args, 2, "digest");
const [algorithm, data, ...params] = args;
const preparedAlgorithm = this.prepareAlgorithm(algorithm);
const preparedData = BufferSourceConverter.toArrayBuffer(data);
const provider = this.getProvider(preparedAlgorithm.name);
const result = await provider.digest(preparedAlgorithm, preparedData, ...params);
return result;
}
public async generateKey(algorithm: types.RsaHashedKeyGenParams | types.EcKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise<types.CryptoKeyPair>;
public async generateKey(algorithm: types.AesKeyGenParams | types.HmacKeyGenParams | types.Pbkdf2Params, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise<types.CryptoKey>;
public async generateKey(algorithm: types.AlgorithmIdentifier, extractable: boolean, keyUsages: Iterable<types.KeyUsage>, ...args: any[]): Promise<types.CryptoKeyPair | types.CryptoKey>;
public async generateKey(...args: any[]): Promise<types.CryptoKeyPair | types.CryptoKey> {
this.checkRequiredArguments(args, 3, "generateKey");
const [algorithm, extractable, keyUsages, ...params] = args;
const preparedAlgorithm = this.prepareAlgorithm(algorithm);
const provider = this.getProvider(preparedAlgorithm.name);
const result = await provider.generateKey({ ...preparedAlgorithm, name: provider.name }, extractable, keyUsages, ...params);
return result;
}
public async sign(algorithm: types.SignAlgorithms, key: types.CryptoKey, data: BufferSource, ...args: any[]): Promise<ArrayBuffer>;
public async sign(...args: any[]): Promise<ArrayBuffer> {
this.checkRequiredArguments(args, 3, "sign");
const [algorithm, key, data, ...params] = args;
this.checkCryptoKey(key);
const preparedAlgorithm = this.prepareAlgorithm(algorithm);
const preparedData = BufferSourceConverter.toArrayBuffer(data);
const provider = this.getProvider(preparedAlgorithm.name);
const result = await provider.sign({ ...preparedAlgorithm, name: provider.name }, key, preparedData, ...params);
return result;
}
public async verify(algorithm: types.SignAlgorithms, key: types.CryptoKey, signature: BufferSource, data: BufferSource, ...args: any[]): Promise<boolean>;
public async verify(...args: any[]): Promise<boolean> {
this.checkRequiredArguments(args, 4, "verify");
const [algorithm, key, signature, data, ...params] = args;
this.checkCryptoKey(key);
const preparedAlgorithm = this.prepareAlgorithm(algorithm);
const preparedData = BufferSourceConverter.toArrayBuffer(data);
const preparedSignature = BufferSourceConverter.toArrayBuffer(signature);
const provider = this.getProvider(preparedAlgorithm.name);
const result = await provider.verify({ ...preparedAlgorithm, name: provider.name }, key, preparedSignature, preparedData, ...params);
return result;
}
public async encrypt(algorithm: types.AlgorithmIdentifier, key: types.CryptoKey, data: BufferSource, ...args: any[]): Promise<ArrayBuffer>;
public async encrypt(...args: any[]): Promise<ArrayBuffer> {
this.checkRequiredArguments(args, 3, "encrypt");
const [algorithm, key, data, ...params] = args;
this.checkCryptoKey(key);
const preparedAlgorithm = this.prepareAlgorithm(algorithm);
const preparedData = BufferSourceConverter.toArrayBuffer(data);
const provider = this.getProvider(preparedAlgorithm.name);
const result = await provider.encrypt({ ...preparedAlgorithm, name: provider.name }, key, preparedData, { keyUsage: true }, ...params);
return result;
}
public async decrypt(algorithm: types.AlgorithmIdentifier, key: types.CryptoKey, data: BufferSource, ...args: any[]): Promise<ArrayBuffer>;
public async decrypt(...args: any[]): Promise<ArrayBuffer> {
this.checkRequiredArguments(args, 3, "decrypt");
const [algorithm, key, data, ...params] = args;
this.checkCryptoKey(key);
const preparedAlgorithm = this.prepareAlgorithm(algorithm);
const preparedData = BufferSourceConverter.toArrayBuffer(data);
const provider = this.getProvider(preparedAlgorithm.name);
const result = await provider.decrypt({ ...preparedAlgorithm, name: provider.name }, key, preparedData, { keyUsage: true }, ...params);
return result;
}
public async deriveBits(algorithm: types.AlgorithmIdentifier, baseKey: types.CryptoKey, length: number, ...args: any[]): Promise<ArrayBuffer>;
public async deriveBits(...args: any[]): Promise<ArrayBuffer> {
this.checkRequiredArguments(args, 3, "deriveBits");
const [algorithm, baseKey, length, ...params] = args;
this.checkCryptoKey(baseKey);
const preparedAlgorithm = this.prepareAlgorithm(algorithm);
const provider = this.getProvider(preparedAlgorithm.name);
const result = await provider.deriveBits({ ...preparedAlgorithm, name: provider.name }, baseKey, length, { keyUsage: true }, ...params);
return result;
}
public async deriveKey(algorithm: types.AlgorithmIdentifier, baseKey: types.CryptoKey, derivedKeyType: types.AlgorithmIdentifier, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise<types.CryptoKey>;
public async deriveKey(...args: any[]): Promise<types.CryptoKey> {
this.checkRequiredArguments(args, 5, "deriveKey");
const [algorithm, baseKey, derivedKeyType, extractable, keyUsages, ...params] = args;
// check derivedKeyType
const preparedDerivedKeyType = this.prepareAlgorithm(derivedKeyType);
const importProvider = this.getProvider(preparedDerivedKeyType.name);
importProvider.checkDerivedKeyParams(preparedDerivedKeyType);
// derive bits
const preparedAlgorithm = this.prepareAlgorithm(algorithm);
const provider = this.getProvider(preparedAlgorithm.name);
provider.checkCryptoKey(baseKey, "deriveKey");
const derivedBits = await provider.deriveBits({ ...preparedAlgorithm, name: provider.name }, baseKey, (derivedKeyType as any).length || 512, { keyUsage: false }, ...params);
// import derived key
return this.importKey("raw", derivedBits, derivedKeyType, extractable, keyUsages, ...params);
}
public async exportKey(format: "raw" | "spki" | "pkcs8", key: types.CryptoKey, ...args: any[]): Promise<ArrayBuffer>;
public async exportKey(format: "jwk", key: types.CryptoKey, ...args: any[]): Promise<types.JsonWebKey>;
public async exportKey(format: types.KeyFormat, key: types.CryptoKey, ...args: any[]): Promise<types.JsonWebKey | ArrayBuffer>;
public async exportKey(...args: any[]): Promise<types.JsonWebKey | ArrayBuffer> {
this.checkRequiredArguments(args, 2, "exportKey");
const [format, key, ...params] = args;
this.checkCryptoKey(key);
const provider = this.getProvider(key.algorithm.name);
const result = await provider.exportKey(format, key, ...params);
return result;
}
public async importKey(format: types.KeyFormat, keyData: types.JsonWebKey | BufferSource, algorithm: types.AlgorithmIdentifier, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise<types.CryptoKey>;
public async importKey(...args: any[]): Promise<types.CryptoKey> {
this.checkRequiredArguments(args, 5, "importKey");
const [format, keyData, algorithm, extractable, keyUsages, ...params] = args;
const preparedAlgorithm = this.prepareAlgorithm(algorithm);
const provider = this.getProvider(preparedAlgorithm.name);
if (["pkcs8", "spki", "raw"].indexOf(format) !== -1) {
const preparedData = BufferSourceConverter.toArrayBuffer(keyData as BufferSource);
return provider.importKey(format, preparedData, { ...preparedAlgorithm, name: provider.name }, extractable, keyUsages, ...params);
} else {
if (!(keyData as types.JsonWebKey).kty) {
throw new TypeError("keyData: Is not JSON");
}
}
return provider.importKey(format, keyData as types.JsonWebKey, { ...preparedAlgorithm, name: provider.name }, extractable, keyUsages, ...params);
}
public async wrapKey(format: types.KeyFormat, key: types.CryptoKey, wrappingKey: types.CryptoKey, wrapAlgorithm: types.AlgorithmIdentifier, ...args: any[]): Promise<ArrayBuffer> {
let keyData = await this.exportKey(format, key, ...args);
if (format === "jwk") {
const json = JSON.stringify(keyData);
keyData = Convert.FromUtf8String(json);
}
// encrypt key data
const preparedAlgorithm = this.prepareAlgorithm(wrapAlgorithm);
const preparedData = BufferSourceConverter.toArrayBuffer(keyData as ArrayBuffer);
const provider = this.getProvider(preparedAlgorithm.name);
return provider.encrypt({ ...preparedAlgorithm, name: provider.name }, wrappingKey, preparedData, { keyUsage: false }, ...args);
}
public async unwrapKey(format: types.KeyFormat, wrappedKey: BufferSource, unwrappingKey: types.CryptoKey, unwrapAlgorithm: types.AlgorithmIdentifier, unwrappedKeyAlgorithm: types.AlgorithmIdentifier, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise<types.CryptoKey> {
// decrypt wrapped key
const preparedAlgorithm = this.prepareAlgorithm(unwrapAlgorithm);
const preparedData = BufferSourceConverter.toArrayBuffer(wrappedKey);
const provider = this.getProvider(preparedAlgorithm.name);
let keyData = await provider.decrypt({ ...preparedAlgorithm, name: provider.name }, unwrappingKey, preparedData, { keyUsage: false }, ...args);
if (format === "jwk") {
try {
keyData = JSON.parse(Convert.ToUtf8String(keyData));
} catch (e) {
const error = new TypeError("wrappedKey: Is not a JSON");
(error as any).internal = e;
throw error;
}
}
// import key
return this.importKey(format, keyData, unwrappedKeyAlgorithm, extractable, keyUsages, ...args);
}
protected checkRequiredArguments(args: any[], size: number, methodName: string) {
if (args.length < size) {
throw new TypeError(`Failed to execute '${methodName}' on 'SubtleCrypto': ${size} arguments required, but only ${args.length} present`);
}
}
protected prepareAlgorithm(algorithm: types.AlgorithmIdentifier): types.Algorithm | types.HashedAlgorithm {
if (typeof algorithm === "string") {
return {
name: algorithm,
} as types.Algorithm;
}
if (SubtleCrypto.isHashedAlgorithm(algorithm)) {
const preparedAlgorithm = { ...algorithm };
preparedAlgorithm.hash = this.prepareAlgorithm(algorithm.hash);
return preparedAlgorithm as types.HashedAlgorithm;
}
return { ...algorithm };
}
protected getProvider(name: string): ProviderCrypto {
const provider = this.providers.get(name);
if (!provider) {
throw new AlgorithmError("Unrecognized name");
}
return provider;
}
protected checkCryptoKey(key: types.CryptoKey): asserts key is CryptoKey {
if (!(key instanceof CryptoKey)) {
throw new TypeError(`Key is not of type 'CryptoKey'`);
}
}
}

View File

@ -0,0 +1,2 @@
export * from "./pem_converter";
export * from "./is_jwk";

View File

@ -0,0 +1,11 @@
import * as types from "@peculiar/webcrypto-types";
export function isJWK(data: unknown): data is types.JsonWebKey {
return !!(data && typeof data === "object" && "kty" in data);
}
export function assertJWK(data: unknown, paramName: string): asserts data is types.JsonWebKey {
if (!isJWK(data)) {
throw new TypeError(`${paramName}: is not JsonWebKey`);
}
}

View File

@ -0,0 +1,105 @@
import { BufferSource, Convert } from "pvtsutils";
/**
* PEM converter
*/
export class PemConverter {
/**
* Converts PEM to Array buffer
* @param pem PEM string
*/
public static toArrayBuffer(pem: string) {
const base64 = pem
.replace(/-{5}(BEGIN|END) .*-{5}/g, "")
.replace("\r", "")
.replace("\n", "");
return Convert.FromBase64(base64);
}
/**
* Converts PEM to Uint8Array
* @param pem PEM string
*/
public static toUint8Array(pem: string) {
const bytes = this.toArrayBuffer(pem);
return new Uint8Array(bytes);
}
/**
* Converts buffer source to PEM
* @param buffer Buffer source
* @param tag PEM tag name
*/
public static fromBufferSource(buffer: BufferSource, tag: string) {
const base64 = Convert.ToBase64(buffer);
let sliced: string;
let offset = 0;
const rows: string[] = [];
while (true) {
sliced = base64.slice(offset, offset = offset + 64);
if (sliced.length) {
rows.push(sliced);
if (sliced.length < 64) {
break;
}
} else {
break;
}
}
const upperCaseTag = tag.toUpperCase();
return `-----BEGIN ${upperCaseTag}-----\n${rows.join("\n")}\n-----END ${upperCaseTag}-----`;
}
/**
* Returns `true` if incoming data is PEM string, otherwise `false`
* @param data Data
*/
public static isPEM(data: string) {
return /-----BEGIN .+-----[A-Za-z0-9+\/\+\=\s\n]+-----END .+-----/i.test(data);
}
/**
* Returns tag name from PEM string
* @param pem PEM string
*/
public static getTagName(pem: string) {
if (!this.isPEM(pem)) {
throw new Error("Bad parameter. Incoming data is not right PEM");
}
const res = /-----BEGIN (.+)-----/.exec(pem);
if (!res) {
throw new Error("Cannot get tag from PEM");
}
return res[1];
}
/**
* Returns `true` if tag name from PEM matches to tagName parameter
* @param pem PEM string
* @param tagName Tag name for comparison
*/
public static hasTagName(pem: string, tagName: string) {
const tag = this.getTagName(pem);
return tagName.toLowerCase() === tag.toLowerCase();
}
public static isCertificate(pem: string) {
return this.hasTagName(pem, "certificate");
}
public static isCertificateRequest(pem: string) {
return this.hasTagName(pem, "certificate request");
}
public static isCRL(pem: string) {
return this.hasTagName(pem, "x509 crl");
}
public static isPublicKey(pem: string) {
return this.hasTagName(pem, "public key");
}
}

View File

@ -0,0 +1,255 @@
import * as assert from "assert";
import * as core from "@peculiar/webcrypto-core";
context("AES", () => {
context("AES-CBC", () => {
const provider = Reflect.construct(core.AesCbcProvider, []) as core.AesCbcProvider;
context("checkGenerateKeyParams", () => {
it("error if `length` is not present", () => {
assert.throws(() => {
provider.checkGenerateKeyParams({ name: "AES-CBC" } as any);
}, Error);
});
it("error if `length` has wrong type", () => {
assert.throws(() => {
provider.checkGenerateKeyParams({ name: "AES-CBC", length: "s" } as any);
}, TypeError);
});
it("error if `length` has wrong value", () => {
assert.throws(() => {
provider.checkGenerateKeyParams({ name: "AES-CBC", length: 1 } as any);
}, TypeError);
});
[128, 192, 256].forEach((length) => {
it(`correct length:${length}`, () => {
provider.checkGenerateKeyParams({ name: "AES-CBC", length } as any);
});
});
});
});
context("AES-CBC", () => {
const provider = Reflect.construct(core.AesCbcProvider, []) as core.AesCbcProvider;
context("checkAlgorithmParams", () => {
it("error if parameter `iv` is not present", () => {
assert.throws(() => {
provider.checkAlgorithmParams({} as any);
}, Error);
});
it("error if parameter `iv` has wrong type", () => {
assert.throws(() => {
provider.checkAlgorithmParams({
iv: "wrong type",
} as any);
}, TypeError);
});
it("error if parameter `iv` has wrong length", () => {
assert.throws(() => {
provider.checkAlgorithmParams({
iv: new Uint8Array(20),
} as any);
}, TypeError);
});
it("correct parameter `iv`", () => {
provider.checkAlgorithmParams({
iv: new Uint8Array(16),
} as any);
});
});
});
context("AES-CMAC", () => {
const provider = Reflect.construct(core.AesCmacProvider, []) as core.AesCmacProvider;
context("checkAlgorithmParams", () => {
it("error if parameter `length` is not present", () => {
assert.throws(() => {
provider.checkAlgorithmParams({} as any);
}, Error);
});
it("error if parameter `length` has wrong type", () => {
assert.throws(() => {
provider.checkAlgorithmParams({
length: "128",
} as any);
}, TypeError);
});
it("error if parameter `length` less than 1", () => {
assert.throws(() => {
provider.checkAlgorithmParams({
length: 0,
} as any);
}, core.OperationError);
});
it("correct parameter `length`", () => {
provider.checkAlgorithmParams({
length: 1,
} as any);
});
});
});
context("AES-CTR", () => {
const provider = Reflect.construct(core.AesCtrProvider, []) as core.AesCtrProvider;
context("checkAlgorithmParams", () => {
it("error if parameter `counter` is not present", () => {
assert.throws(() => {
provider.checkAlgorithmParams({
length: 1,
} as any);
}, Error);
});
it("error if parameter `counter` has wrong type", () => {
assert.throws(() => {
provider.checkAlgorithmParams({
counter: "wrong type",
length: 1,
} as any);
}, TypeError);
});
it("error if parameter `counter` has wrong length", () => {
assert.throws(() => {
provider.checkAlgorithmParams({
counter: new ArrayBuffer(10),
length: 1,
} as any);
}, TypeError);
});
it("counter is ArrayBuffer", () => {
provider.checkAlgorithmParams({
counter: new ArrayBuffer(16),
length: 1,
} as any);
});
it("counter is ArrayBufferView", () => {
provider.checkAlgorithmParams({
counter: new Uint8Array(16),
length: 1,
} as any);
});
it("error if parameter `length` is not present", () => {
assert.throws(() => {
provider.checkAlgorithmParams({
counter: new Uint8Array(16),
} as any);
}, Error);
});
it("error if parameter `length` has wrong type", () => {
assert.throws(() => {
provider.checkAlgorithmParams({
counter: new Uint8Array(16),
length: "1",
} as any);
}, TypeError);
});
it("error if parameter `length` less than 1", () => {
assert.throws(() => {
provider.checkAlgorithmParams({
counter: new Uint8Array(16),
length: 0,
} as any);
}, core.OperationError);
});
it("correct parameter `length`", () => {
provider.checkAlgorithmParams({
counter: new Uint8Array(16),
length: 1,
} as any);
});
});
});
context("AES-GCM", () => {
const provider = Reflect.construct(core.AesGcmProvider, []) as core.AesGcmProvider;
context("checkAlgorithmParams", () => {
it("error if parameter `iv` is not present", () => {
assert.throws(() => {
provider.checkAlgorithmParams({} as any);
}, Error);
});
it("error if parameter `iv` has wrong type", () => {
assert.throws(() => {
provider.checkAlgorithmParams({
iv: "wrong type",
} as any);
}, TypeError);
});
it("error if parameter `iv` has wrong length", () => {
assert.throws(() => {
provider.checkAlgorithmParams({
iv: new Uint8Array(0),
} as any);
}, core.OperationError);
});
it("correct parameter `iv`", () => {
provider.checkAlgorithmParams({
iv: new ArrayBuffer(1),
} as any);
});
it("error if parameter `tagLength` has wrong value", () => {
assert.throws(() => {
provider.checkAlgorithmParams({
iv: new ArrayBuffer(1),
tagLength: 33,
} as any);
}, core.OperationError);
});
[32, 64, 96, 104, 112, 120, 128].forEach((tagLength) => {
it(`correct tagLength: ${tagLength}`, () => {
provider.checkAlgorithmParams({
iv: new ArrayBuffer(1),
tagLength,
} as any);
});
});
});
});
});

View File

@ -0,0 +1,21 @@
import * as assert from "assert";
import * as core from "@peculiar/webcrypto-core";
import * as types from "@peculiar/webcrypto-types";
context("Crypto", () => {
it("Crypto matches to globalThis.Crypto", () => {
class MyCrypto extends core.Crypto {
public subtle = new core.SubtleCrypto();
public getRandomValues<T extends ArrayBufferView | null>(array: T): T {
throw new Error("Method not implemented.");
}
}
let crypto: types.Crypto;
crypto = new MyCrypto();
assert.ok(crypto);
});
});

View File

@ -0,0 +1,90 @@
import * as types from "@peculiar/webcrypto-types";
import * as core from "@peculiar/webcrypto-core";
import * as assert from "assert";
class DesTestProvider extends core.DesProvider {
public keySizeBits = 64;
public ivSize = 8;
public name = "DES-TEST";
public onGenerateKey(algorithm: types.DesKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<core.CryptoKey> {
throw new Error("Method not implemented.");
}
public onExportKey(format: types.KeyFormat, key: core.CryptoKey): Promise<types.JsonWebKey | ArrayBuffer> {
throw new Error("Method not implemented.");
}
public onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.DesImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<core.CryptoKey> {
throw new Error("Method not implemented.");
}
public onEncrypt(algorithm: types.DesParams, key: core.CryptoKey, data: ArrayBuffer): Promise<ArrayBuffer> {
throw new Error("Method not implemented.");
}
public onDecrypt(algorithm: types.DesParams, key: core.CryptoKey, data: ArrayBuffer): Promise<ArrayBuffer> {
throw new Error("Method not implemented.");
}
}
context("DES", () => {
const provider = new DesTestProvider();
context("checkAlgorithmParams", () => {
it("error if `iv` is not present", () => {
assert.throws(() => {
provider.checkAlgorithmParams({
} as any);
}, Error);
});
it("error if `iv` has wrong type", () => {
assert.throws(() => {
provider.checkAlgorithmParams({
iv: "wrong type",
} as any);
}, TypeError);
});
it("error if `iv` has wrong length", () => {
assert.throws(() => {
provider.checkAlgorithmParams({
iv: new ArrayBuffer(9),
} as any);
}, TypeError);
});
it("correct `iv` length", () => {
provider.checkAlgorithmParams({
iv: new Uint8Array(8),
} as any);
});
});
context("checkGenerateKeyParams", () => {
it("error if `length` is not present", () => {
assert.throws(() => {
provider.checkGenerateKeyParams({} as any);
}, Error);
});
it("error if `length` has wrong type", () => {
assert.throws(() => {
provider.checkGenerateKeyParams({ length: "8" } as any);
}, TypeError);
});
it("error if `length` has wrong value", () => {
assert.throws(() => {
provider.checkGenerateKeyParams({ length: 8 } as any);
}, core.OperationError);
});
it("correct value", () => {
provider.checkGenerateKeyParams({ length: 64 } as any);
});
});
});

View File

@ -0,0 +1,229 @@
import * as core from "@peculiar/webcrypto-core";
import * as types from "@peculiar/webcrypto-types";
import * as assert from "assert";
import { Convert } from "pvtsutils";
context("EC", () => {
context("EcUtils", () => {
context("public point", () => {
it("encode/decode point without padding", () => {
const point = {
x: new Uint8Array([1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4]),
y: new Uint8Array([5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8]),
};
const encoded = core.EcUtils.encodePoint(point, 160);
assert.strictEqual(Convert.ToHex(encoded), "0401010101010202020202030303030304040404040505050505060606060607070707070808080808");
const decoded = core.EcUtils.decodePoint(encoded, 160);
assert.strictEqual(Convert.ToHex(decoded.x), Convert.ToHex(point.x));
assert.strictEqual(Convert.ToHex(decoded.y), Convert.ToHex(point.y));
});
it("decode uncompressed point ", () => {
const uncompressedPoint = new Uint8Array(Convert.FromHex("0400010101010202020202030303030304040404040005050505060606060607070707070808080808"));
const decoded = core.EcUtils.decodePoint(uncompressedPoint, 160);
assert.strictEqual(Convert.ToHex(decoded.x), "0001010101020202020203030303030404040404");
assert.strictEqual(Convert.ToHex(decoded.y), "0005050505060606060607070707070808080808");
});
});
context("signature point", () => {
it("encode/decode", () => {
const encodedHex = "00f3e308185c2d6cb59ec216ba8ce31e0a27db431be250807e604cd858494eb9d1de066b0dc7964f64b31e2f8da7f00741b5ba7e3972fe476099d53f5c5a39905a1f009fc215304c42100a0eec7b9d0bbc5f59c838b604bcceb6ebffd4870c83e76d8eca92e689032caddc69aa87a833216163589f97ce6cb4d10c84b7d6a949e73ca1c5";
const decoded = core.EcUtils.decodeSignature(Convert.FromHex(encodedHex), 521);
assert.strictEqual(Convert.ToHex(decoded.r), "f3e308185c2d6cb59ec216ba8ce31e0a27db431be250807e604cd858494eb9d1de066b0dc7964f64b31e2f8da7f00741b5ba7e3972fe476099d53f5c5a39905a1f");
assert.strictEqual(Convert.ToHex(decoded.s), "9fc215304c42100a0eec7b9d0bbc5f59c838b604bcceb6ebffd4870c83e76d8eca92e689032caddc69aa87a833216163589f97ce6cb4d10c84b7d6a949e73ca1c5");
const encoded = core.EcUtils.encodeSignature(decoded, 521);
assert.strictEqual(Convert.ToHex(encoded), encodedHex);
});
});
});
context("Base", () => {
class EcTestProvider extends core.EllipticProvider {
public namedCurves = ["P-1", "P-2"];
public name = "ECC";
public usages: types.ProviderKeyUsages = {
privateKey: ["sign"],
publicKey: ["verify"],
};
public onGenerateKey(algorithm: types.EcKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<types.CryptoKeyPair> {
throw new Error("Method not implemented.");
}
public onExportKey(format: types.KeyFormat, key: core.CryptoKey): Promise<types.JsonWebKey | ArrayBuffer> {
throw new Error("Method not implemented.");
}
public onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.EcKeyImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<core.CryptoKey> {
throw new Error("Method not implemented.");
}
}
const provider = new EcTestProvider();
context("checkGenerateKeyParams", () => {
it("error if `namedCurve` is missing", () => {
assert.throws(() => {
provider.checkGenerateKeyParams({} as any);
}, Error);
});
it("error if `namedCurve` is not of type String", () => {
assert.throws(() => {
provider.checkGenerateKeyParams({ namedCurve: 123 } as any);
}, TypeError);
});
it("error if `namedCurve` is not value from list", () => {
assert.throws(() => {
provider.checkGenerateKeyParams({ namedCurve: "P-256" } as any);
}, core.OperationError);
});
it("correct `namedCurve`", () => {
provider.checkGenerateKeyParams({ namedCurve: "P-2" } as any);
});
});
});
context("ECDH", () => {
const provider = Reflect.construct(core.EcdhProvider, []) as core.EcdhProvider;
context("", () => {
context("checkAlgorithmParams", () => {
it("error if `public` is missing", () => {
assert.throws(() => {
provider.checkAlgorithmParams({} as any);
}, Error);
});
it("error if `public` is not instance of CryptoKey", () => {
assert.throws(() => {
const key = {};
provider.checkAlgorithmParams({ public: key } as any);
}, Error);
});
it("error if `public` is not public CryptoKey", () => {
assert.throws(() => {
const key = new core.CryptoKey();
key.type = "secret";
provider.checkAlgorithmParams({ public: key } as any);
}, Error);
});
it("error if `public` is wrong CryptoKey alg", () => {
assert.throws(() => {
const key = new core.CryptoKey();
key.type = "public";
key.algorithm = { name: "ECDSA" };
provider.checkAlgorithmParams({ public: key } as any);
}, Error);
});
it("correct `public`", () => {
const key = new core.CryptoKey();
key.type = "public";
key.algorithm = { name: "ECDH" };
provider.checkAlgorithmParams({ public: key } as any);
});
});
});
});
context("ECDSA", () => {
const provider = Reflect.construct(core.EcdsaProvider, []) as core.EcdsaProvider;
context("checkAlgorithmParams", () => {
it("error if `hash` is missing", () => {
assert.throws(() => {
provider.checkAlgorithmParams({} as any);
}, Error);
});
it("error if `hash` has wrong value", () => {
assert.throws(() => {
provider.checkAlgorithmParams({ hash: { name: "wrong" } } as any);
}, core.OperationError);
});
it("correct `hash`", () => {
provider.checkAlgorithmParams({ hash: { name: "SHA-1" } } as any);
});
});
});
context("ECDH-ES", () => {
class TestEcdhEsProvider extends core.EcdhEsProvider {
public async onDeriveBits(algorithm: types.EcdhKeyDeriveParams, baseKey: core.CryptoKey, length: number, ...args: any[]): Promise<ArrayBuffer> {
return null as any;
}
public async onGenerateKey(algorithm: types.EcKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise<types.CryptoKeyPair> {
return null as any;
}
public async onExportKey(format: types.KeyFormat, key: core.CryptoKey, ...args: any[]): Promise<ArrayBuffer | types.JsonWebKey> {
return null as any;
}
public async onImportKey(format: types.KeyFormat, keyData: ArrayBuffer | types.JsonWebKey, algorithm: types.EcKeyImportParams, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise<core.CryptoKey> {
return null as any;
}
}
const provider = new TestEcdhEsProvider();
context("generateKey", () => {
["X25519", "x448"].forEach((namedCurve) => {
it(namedCurve, async () => {
const keys = await provider.generateKey({ name: "ECDH-ES", namedCurve } as types.EcKeyGenParams, false, ["deriveBits", "deriveKey"]);
assert.strictEqual(keys, null);
});
});
});
});
context("EdDSA", () => {
class TestEdDsaProvider extends core.EdDsaProvider {
public async onSign(algorithm: types.EcdsaParams, key: core.CryptoKey, data: ArrayBuffer, ...args: any[]): Promise<ArrayBuffer> {
return null as any;
}
public async onVerify(algorithm: types.EcdsaParams, key: core.CryptoKey, signature: ArrayBuffer, data: ArrayBuffer, ...args: any[]): Promise<boolean> {
return true;
}
public async onGenerateKey(algorithm: types.EcKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise<types.CryptoKeyPair> {
return null as any;
}
public onExportKey(format: types.KeyFormat, key: core.CryptoKey, ...args: any[]): Promise<ArrayBuffer | types.JsonWebKey> {
return null as any;
}
public onImportKey(format: types.KeyFormat, keyData: ArrayBuffer | types.JsonWebKey, algorithm: types.EcKeyImportParams, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise<core.CryptoKey> {
return null as any;
}
}
const provider = new TestEdDsaProvider();
context("generateKey", () => {
["Ed25519", "ed448"].forEach((namedCurve) => {
it(namedCurve, async () => {
const keys = await provider.generateKey({ name: "EdDSA", namedCurve } as types.EcKeyGenParams, false, ["sign", "verify"]);
assert.strictEqual(keys, null);
});
});
});
});
});

View File

@ -0,0 +1,63 @@
import { AsnConvert, AsnSerializer } from "@peculiar/asn1-schema";
import * as assert from "assert";
import { Convert } from "pvtsutils";
import * as asn1 from "../src/schema/asn1";
context("ED", () => {
context("asn", () => {
it("spki - jwk", () => {
const pem = "MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE=";
const keyInfo = AsnConvert.parse(Convert.FromBase64(pem), asn1.PublicKeyInfo);
const key = new asn1.EdPublicKey(keyInfo.publicKey);
const jwk = key.toJSON();
const key2 = new asn1.EdPublicKey();
key2.fromJSON(jwk);
assert.strictEqual(
Convert.ToBase64(AsnSerializer.serialize(key2)),
Convert.ToBase64(AsnSerializer.serialize(key)),
);
});
context("pkcs8 -jwk", () => {
it("without public key", () => {
const pem = "MC4CAQAwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC";
const keyInfo = AsnConvert.parse(Convert.FromBase64(pem), asn1.OneAsymmetricKey);
assert.strictEqual(keyInfo.publicKey, undefined);
const key = AsnConvert.parse(keyInfo.privateKey, asn1.EdPrivateKey);
const jwk = key.toJSON();
const key2 = new asn1.EdPrivateKey();
key2.fromJSON(jwk);
assert.strictEqual(
Convert.ToBase64(AsnSerializer.serialize(key2)),
Convert.ToBase64(AsnSerializer.serialize(key)),
);
});
it("with public key", () => {
const pem = "MHICAQEwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhCoB8wHQYKKoZIhvcNAQkJFDEPDA1DdXJkbGUgQ2hhaXJzgSEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE=";
const keyInfo = AsnConvert.parse(Convert.FromBase64(pem), asn1.OneAsymmetricKey);
assert.ok(keyInfo.publicKey);
const key = AsnConvert.parse(keyInfo.privateKey, asn1.EdPrivateKey);
const jwk = key.toJSON();
const key2 = new asn1.EdPrivateKey();
key2.fromJSON(jwk);
assert.strictEqual(
Convert.ToBase64(AsnSerializer.serialize(key2)),
Convert.ToBase64(AsnSerializer.serialize(key)),
);
});
});
});
});

View File

@ -0,0 +1,66 @@
import * as core from "@peculiar/webcrypto-core";
import * as assert from "assert";
context("HKDF", () => {
const provider = Reflect.construct(core.HkdfProvider, []) as core.HkdfProvider;
context("checkAlgorithmParams", () => {
it("error if `hash` is missing", () => {
assert.throws(() => {
provider.checkAlgorithmParams({ salt: new Uint8Array(4), info: new Uint8Array(4) } as any);
}, Error);
});
it("error if `hash` is wrong", () => {
assert.throws(() => {
provider.checkAlgorithmParams({ hash: { name: "WRONG" }, salt: new Uint8Array(4), info: new Uint8Array(4) } as any);
}, core.OperationError);
});
it("error if `salt` is missing", () => {
assert.throws(() => {
provider.checkAlgorithmParams({ hash: { name: "SHA-256" }, info: new Uint8Array(4) } as any);
}, Error);
});
it("error if `salt` wrong type", () => {
assert.throws(() => {
provider.checkAlgorithmParams({ hash: { name: "SHA-256" }, salt: "wrong", info: new Uint8Array(4) } as any);
}, TypeError);
});
it("error if `info` is missing", () => {
assert.throws(() => {
provider.checkAlgorithmParams({ hash: { name: "SHA-256" }, salt: new Uint8Array(4) } as any);
}, Error);
});
it("error if `info` wrong type", () => {
assert.throws(() => {
provider.checkAlgorithmParams({ hash: { name: "SHA-256" }, info: "wrong", salt: new Uint8Array(4) } as any);
}, TypeError);
});
it("correct value", () => {
provider.checkAlgorithmParams({ hash: { name: "SHA-256" }, salt: new Uint8Array(4), info: new Uint8Array(4) } as any);
});
});
context("checkImportKey", () => {
it("throw error if extractable is true", () => {
assert.throws(() => {
provider.checkImportKey("raw", new ArrayBuffer(0), { name: "HKDF" }, true, ["deriveBits"]);
}, SyntaxError);
});
it("correct extractable value", () => {
provider.checkImportKey("raw", new ArrayBuffer(0), { name: "HKDF" }, false, ["deriveBits"]);
});
});
});

View File

@ -0,0 +1,88 @@
import * as core from "@peculiar/webcrypto-core";
import * as assert from "assert";
context("HMAC", () => {
const provider = Reflect.construct(core.HmacProvider, []) as core.HmacProvider;
context("checkGenerateKeyParams", () => {
it("error if `hash` is missing", () => {
assert.throws(() => {
provider.checkGenerateKeyParams({} as any);
}, Error);
});
it("error if `hash` is wrong", () => {
assert.throws(() => {
provider.checkGenerateKeyParams({ hash: { name: "WRONG" } } as any);
}, core.OperationError);
});
it("error if `length` is not of type Number", () => {
assert.throws(() => {
provider.checkGenerateKeyParams({ hash: { name: "SHA-256" }, length: "128" } as any);
}, TypeError);
});
it("error if `length` is less than 1", () => {
assert.throws(() => {
provider.checkGenerateKeyParams({ hash: { name: "SHA-256" }, length: 0 } as any);
}, RangeError);
});
it("default length", () => {
provider.checkGenerateKeyParams({ hash: { name: "SHA-256" } } as any);
});
it("custom length", () => {
provider.checkGenerateKeyParams({ hash: { name: "SHA-256", length: 128 } } as any);
});
});
context("getDefaultLength", () => {
it("SHA-1", () => {
const len = provider.getDefaultLength("SHA-1");
assert.equal(len, 512);
});
it("SHA-256", () => {
const len = provider.getDefaultLength("SHA-256");
assert.equal(len, 512);
});
it("SHA-384", () => {
const len = provider.getDefaultLength("SHA-384");
assert.equal(len, 512);
});
it("SHA-512", () => {
const len = provider.getDefaultLength("SHA-512");
assert.equal(len, 512);
});
it("error if unknown name", () => {
assert.throws(() => {
provider.getDefaultLength("SHA-521");
}, Error);
});
});
context("checkImportParams", () => {
it("error if `hash` is missing", () => {
assert.throws(() => {
provider.checkImportParams({} as any);
}, Error);
});
it("error if `hash` is wrong", () => {
assert.throws(() => {
provider.checkImportParams({ hash: { name: "WRONG" } } as any);
}, core.OperationError);
});
});
});

View File

@ -0,0 +1,50 @@
import * as core from "@peculiar/webcrypto-core";
import * as types from "@peculiar/webcrypto-types";
import * as assert from "assert";
import * as crypto from "crypto";
import { Convert } from "pvtsutils";
// crypto.webcrypto
const ctx = crypto.webcrypto ? context : context.skip;
ctx("JWK utils", () => {
it("format with odd removing", () => {
const jwk: types.JsonWebKey = {
n: "n value",
ext: true,
e: "e value",
};
const formattedJwk = core.JwkUtils.format(jwk, true);
assert.strictEqual(JSON.stringify(formattedJwk), JSON.stringify({
e: "e value",
n: "n value",
}));
});
it("format without removing", () => {
const jwk: types.JsonWebKey = {
n: "n value",
ext: true,
e: "e value",
};
const formattedJwk = core.JwkUtils.format(jwk, false);
assert.strictEqual(JSON.stringify(formattedJwk), JSON.stringify({
e: "e value",
ext: true,
n: "n value",
}));
});
it("thumbprint", async () => {
const digest = await core.JwkUtils.thumbprint("SHA-256", {
e: "e value",
n: "n value",
}, crypto.webcrypto as any);
assert.strictEqual(Convert.ToBase64(digest), "MkHJT3yHfy0O9t4OHK/331Pb3HNa4LRG62yPa4NNnSc=");
});
});

View File

@ -0,0 +1,15 @@
import * as core from "@peculiar/webcrypto-core";
import * as assert from "assert";
context("CryptoKey", () => {
context("isKeyType", () => {
it("correct key type", () => {
assert.equal(core.CryptoKey.isKeyType("secret"), true);
});
it("incorrect key type", () => {
assert.equal(core.CryptoKey.isKeyType("Secret"), false);
});
});
});

View File

@ -0,0 +1,72 @@
import * as core from "@peculiar/webcrypto-core";
import * as assert from "assert";
context("HMAC", () => {
const provider = Reflect.construct(core.Pbkdf2Provider, []) as core.Pbkdf2Provider;
context("checkAlgorithmParams", () => {
it("error if `hash` is missing", () => {
assert.throws(() => {
provider.checkAlgorithmParams({ salt: new Uint8Array(4), iterations: 1000 } as any);
}, Error);
});
it("error if `hash` is wrong", () => {
assert.throws(() => {
provider.checkAlgorithmParams({ hash: { name: "WRONG" }, salt: new Uint8Array(4), iterations: 1000 } as any);
}, core.OperationError);
});
it("error if `salt` is missing", () => {
assert.throws(() => {
provider.checkAlgorithmParams({ hash: { name: "SHA-256" }, iterations: 1000 } as any);
}, Error);
});
it("error if `salt` wrong type", () => {
assert.throws(() => {
provider.checkAlgorithmParams({ hash: { name: "SHA-256" }, salt: "wrong", iterations: 1000 } as any);
}, TypeError);
});
it("error if `iterations` is missing", () => {
assert.throws(() => {
provider.checkAlgorithmParams({ hash: { name: "SHA-256" }, salt: new Uint8Array(4) } as any);
}, Error);
});
it("error if `iterations` wrong type", () => {
assert.throws(() => {
provider.checkAlgorithmParams({ hash: { name: "SHA-256" }, salt: new Uint8Array(4), iterations: "123" } as any);
}, TypeError);
});
it("error if `iterations` less than 1", () => {
assert.throws(() => {
provider.checkAlgorithmParams({ hash: { name: "SHA-256" }, salt: new Uint8Array(4), iterations: 0 } as any);
}, TypeError);
});
it("correct value", () => {
provider.checkAlgorithmParams({ hash: { name: "SHA-256" }, salt: new Uint8Array(4), iterations: 1000 } as any);
});
});
context("checkImportKey", () => {
it("throw error if extractable is true", () => {
assert.throws(() => {
provider.checkImportKey("raw", new ArrayBuffer(0), { name: "PBKDF2" }, true, ["deriveBits"]);
}, SyntaxError);
});
it("correct extractable value", () => {
provider.checkImportKey("raw", new ArrayBuffer(0), { name: "PBKDF2" }, false, ["deriveBits"]);
});
});
});

View File

@ -0,0 +1,108 @@
import * as core from "@peculiar/webcrypto-core";
import * as assert from "assert";
import { Convert } from "pvtsutils";
context("PemConverter", () => {
const bytes = Convert.FromHex("30819f300d06092a864886f70d010101050003818d0030818902818100f615b745314ffe4669255dfe68953184bb8e5db54eecd35b4c51ee899ce7e60aaf19cc765d924f94be93d6809ba506fab26b9f8ef0cf6ab2aec1942da222992f8dad2e621845f014f9e831a529665faf0a9b8ca97356a602ce8d17cd3469aafa2de82546773540fa480510d1906c78c87b81850c26fdaeccce37cd5fdeba7e050203010001");
const vector = "-----BEGIN PUBLIC KEY-----\n" +
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD2FbdFMU/+RmklXf5olTGEu45d\n" +
"tU7s01tMUe6JnOfmCq8ZzHZdkk+UvpPWgJulBvqya5+O8M9qsq7BlC2iIpkvja0u\n" +
"YhhF8BT56DGlKWZfrwqbjKlzVqYCzo0XzTRpqvot6CVGdzVA+kgFENGQbHjIe4GF\n" +
"DCb9rszON81f3rp+BQIDAQAB\n" +
"-----END PUBLIC KEY-----";
it("fromBufferSource", () => {
const pem = core.PemConverter.fromBufferSource(bytes, "public key");
assert.equal(pem, vector);
});
it("fromBufferSource multiple 64", () => {
const pem = core.PemConverter.fromBufferSource(Buffer.from("1234567890abcdef1234567890abcdef1234567890abcdef"), "public key");
assert.equal(pem, "-----BEGIN PUBLIC KEY-----\n" +
"MTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4OTBhYmNkZWYxMjM0NTY3ODkwYWJjZGVm\n" +
"-----END PUBLIC KEY-----");
});
it("toArrayBuffer", () => {
const buf = core.PemConverter.toArrayBuffer(vector);
assert.equal(Convert.ToHex(buf), Convert.ToHex(bytes));
});
it("toUint8Array", () => {
const buf = core.PemConverter.toUint8Array(vector);
assert.equal(Convert.ToHex(buf), Convert.ToHex(bytes));
});
context("isPEM", () => {
const pem = "-----BEGIN CERTIFICATE------\n" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\n" +
"-----END CERTIFICATE------";
it("return true if correct PEM", () => {
assert.equal(core.PemConverter.isPEM(pem), true);
});
it("return true if inline PEM", () => {
assert.equal(core.PemConverter.isPEM(pem.replace(/\n/g, "")), true);
});
it("return false if correct PEM", () => {
const wrongPem = "----- BEGIN CERTIFICATE ------\n" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\n" +
"----- END CERTIFICATE ------";
assert.equal(core.PemConverter.isPEM(wrongPem), false);
});
});
context("getTagName", () => {
it("get tag name from spki", () => {
const tagName = core.PemConverter.getTagName(vector);
assert.equal(tagName, "PUBLIC KEY");
});
it("throw error if data is wrong PEM", () => {
assert.throws(() => core.PemConverter.getTagName("----- BEGIN CERTIFICATE ------"));
});
});
context("hasTagName", () => {
it("return true if tag names are equal", () => {
assert.equal(core.PemConverter.hasTagName(vector, "public key"), true);
});
it("return false if tag names are not equal", () => {
assert.equal(core.PemConverter.hasTagName(vector, "PRIVATE KEY"), false);
});
});
it("isCertificate", () => {
const pem = core.PemConverter.fromBufferSource(new Uint8Array([1, 0, 1]), "certificate");
assert.equal(core.PemConverter.isCertificate(pem), true);
});
it("isCRL", () => {
const pem = core.PemConverter.fromBufferSource(new Uint8Array([1, 0, 1]), "X509 CRL");
assert.equal(core.PemConverter.isCRL(pem), true);
});
it("isCertificateRequest", () => {
const pem = core.PemConverter.fromBufferSource(new Uint8Array([1, 0, 1]), "certificate request");
assert.equal(core.PemConverter.isCertificateRequest(pem), true);
});
it("isPublicKey", () => {
const pem = core.PemConverter.fromBufferSource(new Uint8Array([1, 0, 1]), "public key");
assert.equal(core.PemConverter.isPublicKey(pem), true);
});
});

View File

@ -0,0 +1,174 @@
import * as core from "@peculiar/webcrypto-core";
import * as types from "@peculiar/webcrypto-types";
import * as assert from "assert";
class TestProvider extends core.ProviderCrypto {
public name = "CUSTOM-ALG";
public usages: types.KeyUsage[] = ["sign"];
}
class TestAsymmetricProvider extends core.ProviderCrypto {
public name = "CUSTOM-ALG";
public usages: types.ProviderKeyPairUsage = {
privateKey: ["sign"],
publicKey: ["verify"],
};
}
context("ProviderCrypto", () => {
const crypto = new TestProvider();
context("checkGenerateKey", () => {
it("error if `keyUsages` argument is empty list", () => {
assert.throws(() => {
crypto.checkGenerateKey({ name: "CUSTOM-ALG" }, true, []);
}, TypeError);
});
it("check usages for symmetric key", () => {
const aProv = new TestAsymmetricProvider();
aProv.checkGenerateKey({ name: "CUSTOM-ALG" }, true, ["sign", "verify"]);
});
});
context("digest", () => {
it("correct data", async () => {
await assert.rejects(
crypto.digest({ name: "custom-alg" }, new ArrayBuffer(0)),
core.UnsupportedOperationError,
);
});
it("wrong name of algorithm", async () => {
await assert.rejects(
crypto.digest({ name: "wrong" }, new ArrayBuffer(0)),
);
});
});
context("generateKey", () => {
it("correct data", async () => {
await assert.rejects(
crypto.generateKey({ name: "custom-alg" }, true, ["sign"]),
core.UnsupportedOperationError,
);
});
it("wrong name of algorithm", async () => {
await assert.rejects(
crypto.generateKey({ name: "wrong" }, false, ["sign"]),
);
});
it("wrong key usages", async () => {
await assert.rejects(
crypto.generateKey({ name: "custom-alg" }, false, ["verify"]),
);
});
});
context("sign", () => {
const correctKey = core.CryptoKey.create(
{ name: "custom-alg" },
"secret",
false,
["sign"],
);
it("correct data", async () => {
await assert.rejects(
crypto.sign(
{ name: "custom-alg" },
correctKey,
new ArrayBuffer(0),
),
core.UnsupportedOperationError,
);
});
it("wrong name of algorithm", async () => {
await assert.rejects(
crypto.sign(
{ name: "wrong" },
correctKey,
new ArrayBuffer(0),
),
);
});
it("wrong key type", async () => {
await assert.rejects(
crypto.sign(
{ name: "custom-alg" },
{} as core.CryptoKey,
new ArrayBuffer(0),
),
TypeError,
);
});
it("wrong key algorithm", async () => {
await assert.rejects(
crypto.sign(
{ name: "custom-alg" },
core.CryptoKey.create(
{ name: "wrong" },
"secret",
true,
["sign", "decrypt"],
),
new ArrayBuffer(0),
),
core.AlgorithmError,
);
});
it("wrong key usage", async () => {
await assert.rejects(
crypto.sign(
{ name: "custom-alg" },
core.CryptoKey.create(
{ name: "custom-alg" },
"secret",
true,
["verify"],
),
new ArrayBuffer(0),
),
core.CryptoError,
);
});
});
context("checkDeriveBits", () => {
it("error if length is not multiple 8", () => {
const algorithm: types.Algorithm = { name: "custom-alg" };
const key = core.CryptoKey.create(algorithm, "secret", false, ["deriveBits"]);
assert.throws(() => {
crypto.checkDeriveBits(algorithm, key, 7);
}, core.OperationError);
});
});
context("checkKeyFormat", () => {
it("error if wrong value", () => {
assert.throws(() => {
crypto.checkKeyFormat("wrong");
}, TypeError);
});
});
});

View File

@ -0,0 +1,186 @@
import * as core from "@peculiar/webcrypto-core";
import * as assert from "assert";
context("RSA", () => {
context("RSASSA-PKCS1-v1_5", () => {
const provider = Reflect.construct(core.RsaSsaProvider, []) as core.RsaSsaProvider;
context("checkGenerateKeyParams", () => {
it("error if `hash` is missing", () => {
assert.throws(() => {
provider.checkGenerateKeyParams({
publicExponent: new Uint8Array([1, 0, 1]),
modulusLength: 2048,
} as any);
}, Error);
});
it("error if `hash` is wrong", () => {
assert.throws(() => {
provider.checkGenerateKeyParams({
hash: { name: "SHA-WRONG" },
publicExponent: new Uint8Array([1, 0, 1]),
modulusLength: 2048,
} as any);
}, Error);
});
it("error if `publicExponent` is missing", () => {
assert.throws(() => {
provider.checkGenerateKeyParams({
hash: { name: "SHA-256" },
modulusLength: 2048,
} as any);
}, Error);
});
it("error if `publicExponent` is wrong of type", () => {
assert.throws(() => {
provider.checkGenerateKeyParams({
hash: { name: "SHA-256" },
publicExponent: [1, 0, 1],
modulusLength: 2048,
} as any);
}, TypeError);
});
it("error if `publicExponent` is value", () => {
assert.throws(() => {
provider.checkGenerateKeyParams({
hash: { name: "SHA-256" },
publicExponent: new Uint8Array([1, 1, 0]),
modulusLength: 2048,
} as any);
}, TypeError);
});
it("error if `modulusLength` is missing", () => {
assert.throws(() => {
provider.checkGenerateKeyParams({
hash: { name: "SHA-256" },
publicExponent: new Uint8Array([1, 0, 1]),
} as any);
}, Error);
});
it("error if `modulusLength` is wrong value", () => {
it("not multiple of 8 bits", () => {
assert.throws(() => {
provider.checkGenerateKeyParams({
hash: { name: "SHA-256" },
publicExponent: new Uint8Array([1, 0, 1]),
modulusLength: 12345,
} as any);
}, TypeError);
});
it("less than 256", () => {
assert.throws(() => {
provider.checkGenerateKeyParams({
hash: { name: "SHA-256" },
publicExponent: new Uint8Array([1, 0, 1]),
modulusLength: 256 - 8,
} as any);
}, TypeError);
});
it("more than 16384", () => {
assert.throws(() => {
provider.checkGenerateKeyParams({
hash: { name: "SHA-256" },
publicExponent: new Uint8Array([1, 0, 1]),
modulusLength: 16384 + 8,
} as any);
}, TypeError);
});
});
it("correct value", () => {
provider.checkGenerateKeyParams({
hash: { name: "SHA-256" },
publicExponent: new Uint8Array([1, 0, 1]),
modulusLength: 4096,
} as any);
});
});
context("checkImportParams", () => {
it("error if `hash` is missing", () => {
assert.throws(() => {
provider.checkImportParams({
publicExponent: new Uint8Array([1, 0, 1]),
modulusLength: 2048,
} as any);
}, Error);
});
it("error if `hash` is wrong", () => {
assert.throws(() => {
provider.checkImportParams({
hash: { name: "SHA-WRONG" },
publicExponent: new Uint8Array([1, 0, 1]),
modulusLength: 2048,
} as any);
}, Error);
});
});
});
context("RSA-OAEP", () => {
const provider = Reflect.construct(core.RsaOaepProvider, []) as core.RsaOaepProvider;
context("checkAlgorithmParams", () => {
it("error if `label` is wrong type", () => {
assert.throws(() => {
provider.checkAlgorithmParams({ label: "WRONG" } as any);
}, TypeError);
});
it("correct `label`", () => {
provider.checkAlgorithmParams({ label: new Uint8Array(4) } as any);
});
});
});
context("RSA-PSS", () => {
const provider = Reflect.construct(core.RsaPssProvider, []) as core.RsaPssProvider;
context("checkAlgorithmParams", () => {
it("error if `saltLength` is missing", () => {
assert.throws(() => {
provider.checkAlgorithmParams({} as any);
}, Error);
});
it("error if `saltLength` is not of type Number", () => {
assert.throws(() => {
provider.checkAlgorithmParams({ saltLength: "123" } as any);
}, TypeError);
});
it("error if `saltLength` is less than 0", () => {
assert.throws(() => {
provider.checkAlgorithmParams({ saltLength: -1 } as any);
}, RangeError);
});
it("correct `saltLength`", () => {
provider.checkAlgorithmParams({ saltLength: 8 } as any);
});
});
});
});

View File

@ -0,0 +1,114 @@
import * as assert from "assert";
import { AsnParser, AsnSerializer } from "@peculiar/asn1-schema";
import { JsonParser, JsonSerializer } from "@peculiar/json-schema";
import * as core from "@peculiar/webcrypto-core";
context("ASN1", () => {
context("RSA", () => {
context("PrivateKey", () => {
const bytes = Buffer.from("308204bd020100300d06092a864886f70d0101010500048204a7308204a30201000282010100faa3f76e5daf5dc288a54b7a99d7c7a9437581a184d80bb1aaf54cdfe245b28f52edb4db4888199e78ce993407bfd93c8102231b173eca09d44bb127c152b2e7f3a806852ed1dc323a1a33a5fecc0217ea98ddeb7068c14ffbbbd14a7904b3657a663ab76d2d88b10db77bc1b22e8881e58665508af0c31da225d08f3b07ad05bd8cf4d25943bffd3bb57d4266582276089e126db030d8ef04e0c30eeb65c325558d98bfc43b2b0c52212f3294d3ad7a91fa2ec2e838b2e73a4db616ec6aad5592dfc046f7e3589741fa0f4662fcf4ffca767611475f05272b3ddbd09b757b56f0293089d39e2cdd35722cd69ea00b3181e6e7c5e531f6608db3da31ce82afad02030100010282010025698d3901adf8125e204244822b45e7dba4721da07d393da375ab2c6e139644338e3ce5508dd43925f23cc719f306a3b3e414466a715a6a1e30d0384d70a138e3536ce9bb63e2f8f2584fe652c2b3fb4aeed78d59c1a13d65a792e5896becb5549066ea53572d24b495f358a5d6b154a664a9c1dc8374b47b2c26d6026b3265b1d6e4448bd2253ce467ae99017c53af1fb085cdd5c8dc3cc66941beaa480295e907a936776f49e5e619d5e2e89e5a1bf220121c965b08b658d669464a4d0eb414efe11c8fa20987fae0758542ec69a9d335b01a78b8770b499272105629b4e81f04065644928f0b01bfb0294fbeb0e0e4e3ad6129d356fce820d35171126b1702818100fe042d914643ad84d8e5a0d0c3c7dc9b35ac60d96ef9305e74ead8419a1937c3a4d5b6b2d589f818ab0cfa4f6ee12fbeb85f7c117ba4eb489b31f0eecac4f8368b52b3339043a3160fa3535e1e45634947b582438149b062b73029bd2279793154153da0e120c48fe71c466783e6537ea9157d776bedd272e08fe4e961fe14ff02818100fc990a2846cf6c0f38ad798855a81f2386795425523f99a8660968be450564d97d62b58533bb9e65c36d56d28480b39bbbfaf7ddfdb8b08aa080740038b1786e659dfb342cdf197bcb8c40af1719814c734cec50b3e915cf927b6eb8e880d4073ab2d6c1e106c78e5add9042476f78edfb76d2d7ef5e1ae4c33f51c558691f5302818100b4a329f86e5c40700182427b534eb4add75c6f3f10b0ba59e191041a9ac82624c5fa88c2e2220c41169ad3025bda5d86a63c98d121f964ac2c593679c9ce8aa8d7290770babdaea348999ff6855658c5caede3e5b7723cb1e68da490f08c2bc80d8051642fd48a93bf09177413935e7aeb28f22153aa3b0720749397f7eca4e702818067661215589f11c1cd569d98245014a70b25e13f01c30d1834e4871ed3cc18733af34c10c1937c8c7589ed6f7153e9b1c72a3d8a7e90ba9b9485e07632bedae87dea44692031171268c8f9b572843b3c5b3a52c5da4f80611eba2e21bcf2f7581a3c18d2f6553b1cd7af389d18f6d58ebd4fef90fae80fa433145959aa0e260702818045ef370e79be4352cae716a92244f37f0b4a5133442b49de4a8bc5342d40a00eed284dcb5061d6dcde01baa12fde1ee965f66acabf58bd08d2f78c8f5f00a9156242ea971940611c8f9892335e48e211d2667aa0c5186af712cbab48802f2fc37488316e72d8dfd28a9e311e962fba79324e14d61a61d4afc4646da76dc650ae", "hex");
const json = {
d: "JWmNOQGt-BJeIEJEgitF59ukch2gfTk9o3WrLG4TlkQzjjzlUI3UOSXyPMcZ8wajs-QURmpxWmoeMNA4TXChOONTbOm7Y-L48lhP5lLCs_tK7teNWcGhPWWnkuWJa-y1VJBm6lNXLSS0lfNYpdaxVKZkqcHcg3S0eywm1gJrMmWx1uREi9IlPORnrpkBfFOvH7CFzdXI3DzGaUG-qkgClekHqTZ3b0nl5hnV4uieWhvyIBIcllsItljWaUZKTQ60FO_hHI-iCYf64HWFQuxpqdM1sBp4uHcLSZJyEFYptOgfBAZWRJKPCwG_sClPvrDg5OOtYSnTVvzoINNRcRJrFw",
dp: "tKMp-G5cQHABgkJ7U060rddcbz8QsLpZ4ZEEGprIJiTF-ojC4iIMQRaa0wJb2l2GpjyY0SH5ZKwsWTZ5yc6KqNcpB3C6va6jSJmf9oVWWMXK7ePlt3I8seaNpJDwjCvIDYBRZC_UipO_CRd0E5Neeuso8iFTqjsHIHSTl_fspOc",
dq: "Z2YSFVifEcHNVp2YJFAUpwsl4T8Bww0YNOSHHtPMGHM680wQwZN8jHWJ7W9xU-mxxyo9in6QupuUheB2Mr7a6H3qRGkgMRcSaMj5tXKEOzxbOlLF2k-AYR66LiG88vdYGjwY0vZVOxzXrzidGPbVjr1P75D66A-kMxRZWaoOJgc",
e: "AQAB",
n: "-qP3bl2vXcKIpUt6mdfHqUN1gaGE2AuxqvVM3-JFso9S7bTbSIgZnnjOmTQHv9k8gQIjGxc-ygnUS7EnwVKy5_OoBoUu0dwyOhozpf7MAhfqmN3rcGjBT_u70Up5BLNlemY6t20tiLENt3vBsi6IgeWGZVCK8MMdoiXQjzsHrQW9jPTSWUO__Tu1fUJmWCJ2CJ4SbbAw2O8E4MMO62XDJVWNmL_EOysMUiEvMpTTrXqR-i7C6Diy5zpNthbsaq1Vkt_ARvfjWJdB-g9GYvz0_8p2dhFHXwUnKz3b0Jt1e1bwKTCJ054s3TVyLNaeoAsxgebnxeUx9mCNs9oxzoKvrQ",
p: "_gQtkUZDrYTY5aDQw8fcmzWsYNlu-TBedOrYQZoZN8Ok1bay1Yn4GKsM-k9u4S--uF98EXuk60ibMfDuysT4NotSszOQQ6MWD6NTXh5FY0lHtYJDgUmwYrcwKb0ieXkxVBU9oOEgxI_nHEZng-ZTfqkVfXdr7dJy4I_k6WH-FP8",
q: "_JkKKEbPbA84rXmIVagfI4Z5VCVSP5moZglovkUFZNl9YrWFM7ueZcNtVtKEgLObu_r33f24sIqggHQAOLF4bmWd-zQs3xl7y4xArxcZgUxzTOxQs-kVz5J7brjogNQHOrLWweEGx45a3ZBCR2947ft20tfvXhrkwz9RxVhpH1M",
qi: "Re83Dnm-Q1LK5xapIkTzfwtKUTNEK0neSovFNC1AoA7tKE3LUGHW3N4BuqEv3h7pZfZqyr9YvQjS94yPXwCpFWJC6pcZQGEcj5iSM15I4hHSZnqgxRhq9xLLq0iALy_DdIgxbnLY39KKnjEeli-6eTJOFNYaYdSvxGRtp23GUK4",
};
it("parse", () => {
const keyInfo = AsnParser.parse(bytes, core.asn1.PrivateKeyInfo);
const key = AsnParser.parse(keyInfo.privateKey, core.asn1.RsaPrivateKey);
const jsonKey = JsonSerializer.toJSON(key);
assert.deepEqual(jsonKey, json);
});
it("serialize", () => {
const key = JsonParser.fromJSON(json, { targetSchema: core.asn1.RsaPrivateKey });
const keyInfo = new core.asn1.PrivateKeyInfo();
keyInfo.privateKeyAlgorithm.algorithm = "1.2.840.113549.1.1.1";
keyInfo.privateKeyAlgorithm.parameters = null;
keyInfo.privateKey = AsnSerializer.serialize(key);
const asnKeyInfo = Buffer.from(AsnSerializer.serialize(keyInfo));
assert.equal(asnKeyInfo.equals(bytes), true);
});
});
context("PublicKey", () => {
const bytes = Buffer.from("30820122300d06092a864886f70d01010105000382010f003082010a0282010100faa3f76e5daf5dc288a54b7a99d7c7a9437581a184d80bb1aaf54cdfe245b28f52edb4db4888199e78ce993407bfd93c8102231b173eca09d44bb127c152b2e7f3a806852ed1dc323a1a33a5fecc0217ea98ddeb7068c14ffbbbd14a7904b3657a663ab76d2d88b10db77bc1b22e8881e58665508af0c31da225d08f3b07ad05bd8cf4d25943bffd3bb57d4266582276089e126db030d8ef04e0c30eeb65c325558d98bfc43b2b0c52212f3294d3ad7a91fa2ec2e838b2e73a4db616ec6aad5592dfc046f7e3589741fa0f4662fcf4ffca767611475f05272b3ddbd09b757b56f0293089d39e2cdd35722cd69ea00b3181e6e7c5e531f6608db3da31ce82afad0203010001", "hex");
const json = {
n: "-qP3bl2vXcKIpUt6mdfHqUN1gaGE2AuxqvVM3-JFso9S7bTbSIgZnnjOmTQHv9k8gQIjGxc-ygnUS7EnwVKy5_OoBoUu0dwyOhozpf7MAhfqmN3rcGjBT_u70Up5BLNlemY6t20tiLENt3vBsi6IgeWGZVCK8MMdoiXQjzsHrQW9jPTSWUO__Tu1fUJmWCJ2CJ4SbbAw2O8E4MMO62XDJVWNmL_EOysMUiEvMpTTrXqR-i7C6Diy5zpNthbsaq1Vkt_ARvfjWJdB-g9GYvz0_8p2dhFHXwUnKz3b0Jt1e1bwKTCJ054s3TVyLNaeoAsxgebnxeUx9mCNs9oxzoKvrQ",
e: "AQAB",
};
it("parse", () => {
const keyInfo = AsnParser.parse(bytes, core.asn1.PublicKeyInfo);
const key = AsnParser.parse(keyInfo.publicKey, core.asn1.RsaPublicKey);
const jsonKey = JsonSerializer.toJSON(key);
assert.deepEqual(jsonKey, json);
});
it("serialize", () => {
const key = JsonParser.fromJSON(json, { targetSchema: core.asn1.RsaPublicKey });
const keyInfo = new core.asn1.PublicKeyInfo();
keyInfo.publicKeyAlgorithm.algorithm = "1.2.840.113549.1.1.1";
keyInfo.publicKeyAlgorithm.parameters = null;
keyInfo.publicKey = AsnSerializer.serialize(key);
const asnKeyInfo = Buffer.from(AsnSerializer.serialize(keyInfo));
assert.equal(asnKeyInfo.equals(bytes), true);
});
});
});
context("EC", () => {
context("PrivateKey", () => {
const bytes = Buffer.from("308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b0201010420db0964fc2a963e9a2aef561f57db3556fa87e83ceb2e5f6dc84b00c18aa873e3a144034200043266c1386af7a0993b169393df1f7c4016e27fd48642e8d512c775b31c8f06722baef1310974a6c63aff2ef8832fba27f021f5ae2f2c6c2d56fde5be5ade78f5", "hex");
const json = {
d: "2wlk_CqWPpoq71YfV9s1VvqH6DzrLl9tyEsAwYqoc-M",
x: "MmbBOGr3oJk7FpOT3x98QBbif9SGQujVEsd1sxyPBnI",
y: "K67xMQl0psY6_y74gy-6J_Ah9a4vLGwtVv3lvlreePU",
};
it("parse", () => {
const keyInfo = AsnParser.parse(bytes, core.asn1.PrivateKeyInfo);
const key = AsnParser.parse(keyInfo.privateKey, core.asn1.EcPrivateKey);
const jsonKey = JsonSerializer.toJSON(key);
assert.deepEqual(jsonKey, json);
});
it("serialize", () => {
const keyInfo = new core.asn1.PrivateKeyInfo();
keyInfo.privateKeyAlgorithm.algorithm = "1.2.840.10045.2.1";
keyInfo.privateKeyAlgorithm.parameters = AsnSerializer.serialize(
new core.asn1.ObjectIdentifier("1.2.840.10045.3.1.7"),
);
const key = JsonParser.fromJSON(json, { targetSchema: core.asn1.EcPrivateKey });
keyInfo.privateKey = AsnSerializer.serialize(key);
const asnKeyInfo = Buffer.from(AsnSerializer.serialize(keyInfo));
assert.equal(asnKeyInfo.equals(bytes), true);
});
});
});
});

View File

@ -0,0 +1,71 @@
import * as assert from "assert";
import { Convert } from "pvtsutils";
import { AsnSerializer, AsnParser } from "@peculiar/asn1-schema";
import * as core from "@peculiar/webcrypto-core";
interface IEcSignatureTestVector {
name: string;
asn1: string;
webCrypto: string;
}
context("ASN1", () => {
context("ECDSA Signature Value", () => {
const vectors: IEcSignatureTestVector[] = [
{
name: "P-256 #1",
asn1: "3045022100d50b6b8b2f84ec9e8704fd7651eed26d1c9e60a773666ec122e135669eb435fe02206e08432c943aec0b3f223014731475277ff7a3840ac9dbd065aab04a540c9a28",
webCrypto: "d50b6b8b2f84ec9e8704fd7651eed26d1c9e60a773666ec122e135669eb435fe6e08432c943aec0b3f223014731475277ff7a3840ac9dbd065aab04a540c9a28",
},
{
name: "P-256 #2",
asn1: "304402205cce26e35066669ee84acad747d39abe8b882a569004a6d4c1992d66b3b26caf0220791e998153331d52a3b972b77fb1b6e2caf3cb7b8cdc60fd486443819ff08208",
webCrypto: "5cce26e35066669ee84acad747d39abe8b882a569004a6d4c1992d66b3b26caf791e998153331d52a3b972b77fb1b6e2caf3cb7b8cdc60fd486443819ff08208",
},
{
name: "P-384 #1",
asn1: "3066023100f56792d36a7bd7836e94947343c308f528b5eb9327c468d6cab1b40498824f6f165d5335eabfcc553403b00579a5b68c023100a9daad0d1fbf5903eb1b42cca280f1a39baa33a2b32c19523c3967f7c9a3d23a9fdaab39b6bfedd82ba12abbedda24b1",
webCrypto: "f56792d36a7bd7836e94947343c308f528b5eb9327c468d6cab1b40498824f6f165d5335eabfcc553403b00579a5b68ca9daad0d1fbf5903eb1b42cca280f1a39baa33a2b32c19523c3967f7c9a3d23a9fdaab39b6bfedd82ba12abbedda24b1",
},
{
name: "P-384 #2",
asn1: "3064023020f5cacedfe6d32ef782027f3cd58dddc6d27ab92cef562eca2d9e7089b450673246141a41c3d0d14f61ffa012a2a100023034cdcb83981758c58bcd92666393a85799b4f5a073347833f22d301aae0bb415cfaf2c6eade9fde00d79365ab6ca93da",
webCrypto: "20f5cacedfe6d32ef782027f3cd58dddc6d27ab92cef562eca2d9e7089b450673246141a41c3d0d14f61ffa012a2a10034cdcb83981758c58bcd92666393a85799b4f5a073347833f22d301aae0bb415cfaf2c6eade9fde00d79365ab6ca93da",
},
{
name: "P-521 #1",
asn1: "30818702417767d9adbc3994e25c9c0328ab591f0ca6d9b24152c5f692ae4c62efa0b9317a0a26fcaf83ce87d337e8c3945fe8281e738f25ead6999c9521a2c2724f06bdc0e8024201a0aa892238ee98902b4a25c5efb940677cfd11a43df382f633f32d1c6b751ccd00fedfa106c298f652292b16dc1964521a04e42e0c8eaeb368222c6e94b42f325d",
webCrypto: "007767d9adbc3994e25c9c0328ab591f0ca6d9b24152c5f692ae4c62efa0b9317a0a26fcaf83ce87d337e8c3945fe8281e738f25ead6999c9521a2c2724f06bdc0e801a0aa892238ee98902b4a25c5efb940677cfd11a43df382f633f32d1c6b751ccd00fedfa106c298f652292b16dc1964521a04e42e0c8eaeb368222c6e94b42f325d",
},
{
name: "P-521 #2",
asn1: "3081880242011e0ff3c825b1133ef2779bbffd05374b17eeeff37444108a4c480b881ba3f3f426c3344fb1173dcec305f3e49408965092f946e609dfb845efaaaa25a43c679b0c024201a1366cd7b11efe7a41418cf83156bfdac56bb6253fd018a23974fc182948f3a84d5241922f09b8a60c4366f58b2b86461886515bd79872bb55e9840c412db766da",
webCrypto: "011e0ff3c825b1133ef2779bbffd05374b17eeeff37444108a4c480b881ba3f3f426c3344fb1173dcec305f3e49408965092f946e609dfb845efaaaa25a43c679b0c01a1366cd7b11efe7a41418cf83156bfdac56bb6253fd018a23974fc182948f3a84d5241922f09b8a60c4366f58b2b86461886515bd79872bb55e9840c412db766da",
},
];
context("From WebCrypto to DER", () => {
vectors.forEach((vector) => {
it(vector.name, () => {
const value = core.asn1.EcDsaSignature.fromWebCryptoSignature(Convert.FromHex(vector.webCrypto));
const der = AsnSerializer.serialize(value);
assert.strictEqual(Convert.ToHex(der), vector.asn1);
});
});
});
context("From DER to WebCrypto", () => {
vectors.forEach((vector) => {
it(vector.name, () => {
const value = AsnParser.parse(Convert.FromHex(vector.asn1), core.asn1.EcDsaSignature);
const signature = value.toWebCryptoSignature();
assert.strictEqual(Convert.ToHex(signature), vector.webCrypto);
});
});
});
});
});

View File

@ -0,0 +1,30 @@
import * as assert from "assert";
import { AsnConvert } from "@peculiar/asn1-schema";
import { JsonSerializer } from "@peculiar/json-schema";
import * as core from "@peculiar/webcrypto-core";
import { Convert } from "pvtsutils";
context("EdDSA and ECDH-ES keys", () => {
it("Private key", () => {
const b64 = "MEYCAQAwBQYDK2VvBDoEOPhm20uZC//c0wk1EEapNDcIIlgSGVxnWhwRJvT5K3+iwjtcyV2inuEihA5Soa5BO2OHh5leznW+";
const raw = Buffer.from(b64, "base64");
const pki = AsnConvert.parse(raw, core.asn1.PrivateKeyInfo);
assert.strictEqual(pki.privateKeyAlgorithm.algorithm, core.asn1.idX448);
const privateKey = AsnConvert.parse(pki.privateKey, core.asn1.CurvePrivateKey);
assert.deepStrictEqual(JsonSerializer.toJSON(privateKey), { d: "-GbbS5kL_9zTCTUQRqk0NwgiWBIZXGdaHBEm9Pkrf6LCO1zJXaKe4SKEDlKhrkE7Y4eHmV7Odb4" });
});
it("Public key", () => {
const b64 = "MCowBQYDK2VuAyEAR-a_Z6rz2HuBXn7m7v_pjef6nHfCWSIObVWCTr5nxjg";
const raw = Convert.FromBase64Url(b64);
const spki = AsnConvert.parse(raw, core.asn1.PublicKeyInfo);
assert.strictEqual(spki.publicKeyAlgorithm.algorithm, core.asn1.idX25519);
assert.strictEqual(Convert.ToBase64Url(spki.publicKey), "R-a_Z6rz2HuBXn7m7v_pjef6nHfCWSIObVWCTr5nxjg");
});
});

View File

@ -0,0 +1,55 @@
import * as assert from "assert";
import * as core from "@peculiar/webcrypto-core";
import * as types from "@peculiar/webcrypto-types";
class TestShake128Provider extends core.Shake128Provider {
public async onDigest(algorithm: Required<types.ShakeParams>, data: ArrayBuffer): Promise<ArrayBuffer> {
return new ArrayBuffer(algorithm.length);
}
}
class TestShake256Provider extends core.Shake256Provider {
public async onDigest(algorithm: Required<types.ShakeParams>, data: ArrayBuffer): Promise<ArrayBuffer> {
return new ArrayBuffer(algorithm.length);
}
}
context("SHAKE", () => {
const data = new Uint8Array();
const shake128 = new TestShake128Provider();
const shake256 = new TestShake256Provider();
context("check parameters", () => {
context("algorithm.length", () => {
it("negative value", async () => {
assert.rejects(shake128.digest({ name: "Shake128", length: -1 } as types.Algorithm, data), TypeError);
});
it("wrong type", async () => {
assert.rejects(shake128.digest({ name: "Shake128", length: "wrong" } as types.Algorithm, data), TypeError);
});
});
});
context("shake128", () => {
it("default length", async () => {
const digest = await shake128.digest({ name: "shake128" }, data);
assert.strictEqual(digest.byteLength, 16);
});
});
context("shake256", () => {
it("default length", async () => {
const digest = await shake256.digest({ name: "Shake256" }, data);
assert.strictEqual(digest.byteLength, 32);
});
});
});

View File

@ -0,0 +1,98 @@
import * as core from "@peculiar/webcrypto-core";
import * as types from "@peculiar/webcrypto-types";
import * as assert from "assert";
class RsaSsaProvider extends core.RsaSsaProvider {
public onSign(algorithm: core.RsaSsaParams, key: core.CryptoKey, data: ArrayBuffer): Promise<ArrayBuffer> {
throw new Error("Method not implemented.");
}
public onVerify(algorithm: core.RsaSsaParams, key: core.CryptoKey, signature: ArrayBuffer, data: ArrayBuffer): Promise<boolean> {
throw new Error("Method not implemented.");
}
public onGenerateKey(algorithm: types.RsaHashedKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<types.CryptoKeyPair> {
throw new Error("Method not implemented.");
}
public onExportKey(format: types.KeyFormat, key: core.CryptoKey): Promise<ArrayBuffer | types.JsonWebKey> {
throw new Error("Method not implemented.");
}
public onImportKey(format: types.KeyFormat, keyData: ArrayBuffer | types.JsonWebKey, algorithm: types.RsaHashedImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<core.CryptoKey> {
throw new Error("Method not implemented.");
}
}
class RsaOaepProvider extends core.RsaOaepProvider {
public onEncrypt(algorithm: types.RsaOaepParams, key: core.CryptoKey, data: ArrayBuffer): Promise<ArrayBuffer> {
throw new Error("Method not implemented.");
}
public onDecrypt(algorithm: types.RsaOaepParams, key: core.CryptoKey, data: ArrayBuffer): Promise<ArrayBuffer> {
throw new Error("Method not implemented.");
}
public onGenerateKey(algorithm: types.RsaHashedKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<types.CryptoKeyPair> {
throw new Error("Method not implemented.");
}
public onExportKey(format: types.KeyFormat, key: core.CryptoKey): Promise<ArrayBuffer | types.JsonWebKey> {
throw new Error("Method not implemented.");
}
public onImportKey(format: types.KeyFormat, keyData: ArrayBuffer | types.JsonWebKey, algorithm: types.RsaHashedImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<core.CryptoKey> {
throw new Error("Method not implemented.");
}
}
context("ProviderStorage", () => {
it("set", () => {
const storage = new core.ProviderStorage();
assert.equal(storage.length, 0);
storage.set(new RsaSsaProvider());
storage.set(new RsaOaepProvider());
storage.set(new RsaOaepProvider());
assert.equal(storage.length, 2);
});
it("get", () => {
const storage = new core.ProviderStorage();
storage.set(new RsaSsaProvider());
storage.set(new RsaOaepProvider());
const provider = storage.get("rsa-oaep");
assert.equal(provider!.name, "RSA-OAEP");
});
it("has", () => {
const storage = new core.ProviderStorage();
storage.set(new RsaSsaProvider());
storage.set(new RsaOaepProvider());
const ok = storage.has("rsa-oaep");
assert.equal(ok, true);
});
it("algorithms", () => {
const storage = new core.ProviderStorage();
storage.set(new RsaSsaProvider());
storage.set(new RsaOaepProvider());
assert.deepEqual(storage.algorithms, ["RSA-OAEP", "RSASSA-PKCS1-v1_5"]);
});
it("removeAt", () => {
const storage = new core.ProviderStorage();
storage.set(new RsaSsaProvider());
storage.set(new RsaOaepProvider());
storage.removeAt("rsa-wrong");
assert.deepEqual(storage.length, 2);
const removedProvider = storage.removeAt("rsa-oaep");
assert.deepEqual(removedProvider!.name, "RSA-OAEP");
assert.deepEqual(storage.length, 1);
});
});

View File

@ -0,0 +1,219 @@
import * as core from "@peculiar/webcrypto-core";
import * as types from "@peculiar/webcrypto-types";
import * as assert from "assert";
context("SubtleCrypto", () => {
class TestProvider extends core.ProviderCrypto {
public name = "TEST";
public usages: types.KeyUsage[] = ["sign", "verify", "deriveKey", "deriveBits", "encrypt", "decrypt", "wrapKey", "unwrapKey"];
public override async onDigest(algorithm: types.Algorithm, data: ArrayBuffer): Promise<ArrayBuffer> {
return new ArrayBuffer(0);
}
public override async onGenerateKey(algorithm: types.Algorithm, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<core.CryptoKey> {
return key;
}
public override async onSign(algorithm: types.Algorithm, sKey: core.CryptoKey, data: ArrayBuffer): Promise<ArrayBuffer> {
return new ArrayBuffer(0);
}
public override async onVerify(algorithm: types.Algorithm, sKey: core.CryptoKey, signature: ArrayBuffer, data: ArrayBuffer): Promise<boolean> {
return true;
}
public override async onEncrypt(algorithm: types.Algorithm, sKey: core.CryptoKey, data: ArrayBuffer): Promise<ArrayBuffer> {
return new ArrayBuffer(0);
}
public override async onDecrypt(algorithm: types.Algorithm, sKey: core.CryptoKey, data: ArrayBuffer): Promise<ArrayBuffer> {
return new ArrayBuffer(0);
}
public override async onDeriveBits(algorithm: types.Algorithm, sKey: core.CryptoKey, length: number): Promise<ArrayBuffer> {
return new ArrayBuffer(0);
}
public override async onExportKey(format: types.KeyFormat, sKey: core.CryptoKey): Promise<types.JsonWebKey | ArrayBuffer> {
return new ArrayBuffer(0);
}
public override async onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.Algorithm, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<core.CryptoKey> {
return key;
}
}
class TestSubtleCrypto extends core.SubtleCrypto {
constructor() {
super();
this.providers.set(new TestProvider());
}
}
const subtle = new TestSubtleCrypto();
const key = new core.CryptoKey();
key.algorithm = { name: "TEST" };
key.type = "secret",
key.usages = ["sign", "verify", "deriveKey", "deriveBits", "encrypt", "decrypt", "wrapKey", "unwrapKey"];
key.extractable = true;
context("generateKey", () => {
it("correct values", async () => {
const res = await subtle.generateKey("test", false, ["sign"]);
assert.equal(!!res, true);
});
});
context("digest", () => {
it("correct values", async () => {
const res = await subtle.digest("test", new Uint8Array(0));
assert.equal(!!res, true);
});
});
context("sign", () => {
it("correct values", async () => {
const res = await subtle.sign({ name: "test", hash: "SHA-1" } as any, key, new Uint8Array(0));
assert.equal(!!res, true);
});
});
context("verify", () => {
it("correct values", async () => {
const res = await subtle.verify({ name: "test", hash: { name: "SHA-1" } } as any, key, new ArrayBuffer(0), new Uint8Array(0));
assert.equal(!!res, true);
});
});
context("encrypt", () => {
it("correct values", async () => {
const res = await subtle.encrypt("test", key, new Uint8Array(0));
assert.equal(!!res, true);
});
});
context("decrypt", () => {
it("correct values", async () => {
const res = await subtle.decrypt("test", key, new Uint8Array(0));
assert.equal(!!res, true);
});
});
context("deriveBits", () => {
it("correct values", async () => {
const res = await subtle.deriveBits("test", key, 128);
assert.equal(!!res, true);
});
});
context("deriveKey", () => {
it("correct values", async () => {
const res = await subtle.deriveKey("test", key, { name: "test", length: 128 } as any, false, ["verify"]);
assert.equal(!!res, true);
});
});
context("exportKey", () => {
it("correct values", async () => {
const res = await subtle.exportKey("raw", key);
assert.equal(!!res, true);
});
});
context("importKey", () => {
it("correct values", async () => {
const res = await subtle.importKey("raw", new ArrayBuffer(0), "test", false, ["sign"]);
assert.equal(!!res, true);
});
it("json", async () => {
const res = await subtle.importKey("jwk", { kty: "RSA" }, "test", false, ["sign"]);
assert.equal(!!res, true);
});
it("Uint8Array", async () => {
const res = await subtle.importKey("raw", new Uint8Array(10), "test", false, ["sign"]);
assert.equal(!!res, true);
});
it("Buffer", async () => {
const res = await subtle.importKey("raw", Buffer.alloc(10), "test", false, ["sign"]);
assert.equal(!!res, true);
});
it("ArrayBuffer", async () => {
const res = await subtle.importKey("raw", new ArrayBuffer(10), "test", false, ["sign"]);
assert.equal(!!res, true);
});
});
context("wrapKey", () => {
it("correct values", async () => {
const res = await subtle.wrapKey("raw", key, key, "test");
assert.equal(!!res, true);
});
});
context("unwrapKey", () => {
it("correct values", async () => {
const res = await subtle.unwrapKey("raw", new ArrayBuffer(0), key, "test", "test", false, ["deriveKey"]);
assert.equal(!!res, true);
});
});
context("checkRequiredArguments", () => {
it("error if less than required", async () => {
await assert.rejects(subtle.digest.apply(subtle, ["test"] as unknown as any));
});
it("no error if greater than required", async () => {
await assert.doesNotReject(subtle.digest.apply(subtle, ["test", new Uint8Array(0), 1, 2, 3]));
});
});
context("getProvider", () => {
it("error if there is not provider with given name", async () => {
await assert.rejects(subtle.digest("wrong", new Uint8Array(0)));
});
});
context("prepareData", () => {
it("error if wrong data", async () => {
await assert.rejects(subtle.digest("test", [1, 2, 3, 4] as any));
});
it("from Buffer", async () => {
await subtle.digest("test", Buffer.from([1, 2, 3, 4]));
});
});
});

View File

@ -0,0 +1,60 @@
{
"name": "@peculiar/webcrypto",
"version": "3.0.0",
"description": "A WebCrypto Polyfill for NodeJS",
"author": "PeculiarVentures",
"contributors": [
"Miroshin Stepan<microshine@mail.ru>"
],
"homepage": "https://github.com/PeculiarVentures/webcrypto/tree/master/packages/node#readme",
"license": "MIT",
"main": "build/index.js",
"module": "build/index.es.js",
"types": "build/index.d.ts",
"files": [
"build",
"README.md",
"LICENSE.md"
],
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/PeculiarVentures/webcrypto.git"
},
"scripts": {
"test": "echo \"Error: run tests from root\" && exit 1"
},
"bugs": {
"url": "https://github.com/PeculiarVentures/webcrypto/issues"
},
"keywords": [
"webcrypto",
"crypto",
"sha",
"rsa",
"ec",
"aes",
"des",
"hmac",
"pbkdf2",
"eddsa",
"x25519",
"ed25519",
"x448",
"ed448",
"shake128",
"shake256"
],
"dependencies": {
"@peculiar/asn1-schema": "^2.1.7",
"@peculiar/json-schema": "^1.1.12",
"@peculiar/webcrypto-core": "^3.0.0",
"pvtsutils": "^1.3.2",
"tslib": "^2.4.0"
},
"engines": {
"node": ">=10.12.0"
}
}

Some files were not shown because too many files have changed in this diff Show More