Compare commits

..

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
383 changed files with 21817 additions and 8947 deletions

View File

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

View File

@ -16,7 +16,7 @@ jobs:
strategy: strategy:
matrix: matrix:
node-version: [14.x, 16.x, 18.x] node-version: [14.x, 16.x]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@ -38,14 +38,11 @@ jobs:
${{ runner.os }}-build- ${{ runner.os }}-build-
${{ runner.os }}- ${{ runner.os }}-
- name: Install global dependencies
run: npm i yarn nyc coveralls -g
- name: Install dependencies - name: Install dependencies
run: yarn run: yarn
- name: Run linter # - name: Run linter
run: npm run lint # run: npm run lint
- name: Run test with coverage - name: Run test with coverage
run: npm run 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

@ -2,22 +2,6 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [1.4.3](https://github.com/PeculiarVentures/webcrypto/compare/v1.4.2...v1.4.3) (2023-03-24)
### Bug Fixes
* type declaration for Crypto ([335c2cb](https://github.com/PeculiarVentures/webcrypto/commit/335c2cb45236a4832b4b5cccb869f19f458bfc2b))
### [1.4.2](https://github.com/PeculiarVentures/webcrypto/compare/v1.4.0...v1.4.2) (2023-03-21)
### Bug Fixes
* disable DES-CBC for Node v18 ([1ebf900](https://github.com/PeculiarVentures/webcrypto/commit/1ebf9006e67102b16aada2e54d5a32419d8cc3b8))
* ECDH with null length ([48f5a8c](https://github.com/PeculiarVentures/webcrypto/commit/48f5a8c19d81732a89b897fad0e6ac0e084c6333))
* publish ([e2f61b8](https://github.com/PeculiarVentures/webcrypto/commit/e2f61b8b5619767a4bd82d91631a4a15e4e5de92))
## [1.4.0](https://github.com/PeculiarVentures/webcrypto/compare/v1.3.3...v1.4.0) (2022-05-12) ## [1.4.0](https://github.com/PeculiarVentures/webcrypto/compare/v1.3.3...v1.4.0) (2022-05-12)

View File

@ -1,6 +1,6 @@
MIT License 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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;
randomUUID(): `${string}-${string}-${string}-${string}-${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", "name": "webcrypto",
"version": "1.4.3", "private": true,
"description": "A WebCrypto Polyfill for NodeJS", "description": "",
"repository": { "workspaces": [
"type": "git", "packages/*"
"url": "https://github.com/PeculiarVentures/webcrypto.git"
},
"files": [
"build/**/*.{ts,js}",
"index.d.ts",
"README.md",
"LICENSE.md"
], ],
"main": "build/webcrypto.js",
"module": "build/webcrypto.es.js",
"types": "index.d.ts",
"scripts": { "scripts": {
"test": "mocha", "test": "mocha",
"coverage": "nyc npm test", "lint": "eslint .",
"coveralls": "nyc report --reporter=text-lcov | coveralls", "lint:fix": "eslint . --fix",
"build": "rollup -c", "build": "lerna run build",
"clear": "rimraf build/*", "rebuild": "lerna run rebuild",
"rebuild": "npm run clear && npm run build", "pack": "lerna exec npm pack",
"lint": "eslint . --ext .ts", "clear": "lerna run clear",
"lint:fix": "eslint --fix . --ext .ts" "upgrade": "yarn upgrade-interactive --latest"
}, },
"keywords": [ "repository": {
"webcrypto", "type": "git",
"crypto", "url": "git+https://github.com/PeculiarVentures/webcrypto.git"
"sha", },
"rsa", "author": "",
"ec",
"aes",
"des",
"hmac",
"pbkdf2",
"eddsa",
"x25519",
"ed25519",
"x448",
"ed448",
"shake128",
"shake256"
],
"author": "PeculiarVentures",
"contributors": [
"Miroshin Stepan<microshine@mail.ru>"
],
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {
"url": "https://github.com/PeculiarVentures/webcrypto/issues" "url": "https://github.com/PeculiarVentures/webcrypto/issues"
}, },
"homepage": "https://github.com/PeculiarVentures/webcrypto#readme", "homepage": "https://github.com/PeculiarVentures/webcrypto#readme",
"devDependencies": { "devDependencies": {
"@peculiar/webcrypto-test": "^1.0.7", "@types/mocha": "^9.1.1",
"@types/mocha": "^10.0.1", "@types/node": "^17.0.27",
"@types/node": "^18.15.5", "@typescript-eslint/eslint-plugin": "^5.21.0",
"@typescript-eslint/eslint-plugin": "^5.56.0", "@typescript-eslint/parser": "^5.21.0",
"@typescript-eslint/parser": "^5.56.0", "eslint": "^8.14.0",
"eslint": "^8.36.0", "mocha": "^9.2.2",
"eslint-plugin-import": "^2.27.5", "rollup": "^2.70.2",
"mocha": "^10.2.0", "rollup-plugin-dts": "^4.2.1",
"prettier": "^2.8.7", "rollup-plugin-typescript2": "^0.31.2",
"rimraf": "^4.4.0", "ts-node": "^10.7.0",
"rollup": "^3.20.0", "tsconfig-paths": "^3.14.1",
"rollup-plugin-typescript2": "^0.34.1", "typescript": "^4.6.3"
"ts-node": "^10.9.1",
"typescript": "^5.0.2"
},
"dependencies": {
"@peculiar/asn1-schema": "^2.3.6",
"@peculiar/json-schema": "^1.1.12",
"buffer": "^6.0.3",
"pvtsutils": "^1.3.2",
"tslib": "^2.5.0",
"webcrypto-core": "^1.7.7"
},
"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"
} }
} }

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

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