Compare commits
7 Commits
Author | SHA1 | Date |
---|---|---|
microshine | 5e0049cacb | |
microshine | 0bd5151604 | |
microshine | 5a5412ecd3 | |
microshine | 181477c574 | |
microshine | 188fa90116 | |
microshine | 1578e6ceeb | |
microshine | 387f0d928d |
|
@ -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
|
||||||
}
|
}
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
require:
|
||||||
|
- "tsconfig-paths/register"
|
||||||
|
- "ts-node/register"
|
||||||
|
extension:
|
||||||
|
- ts
|
||||||
|
spec:
|
||||||
|
- "packages/**/*.spec.ts"
|
||||||
|
exit: true
|
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
2377
build/webcrypto.js
2377
build/webcrypto.js
File diff suppressed because it is too large
Load Diff
|
@ -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[];
|
|
||||||
}
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"npmClient": "yarn",
|
||||||
|
"packages": [
|
||||||
|
"packages/*"
|
||||||
|
],
|
||||||
|
"version": "3.0.0",
|
||||||
|
"useWorkspaces": true,
|
||||||
|
"command": {
|
||||||
|
"publish": {
|
||||||
|
"registry": "https://npm.pkg.github.com/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
122
package.json
122
package.json
|
@ -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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
# `@peculiar/webcypto-core`
|
||||||
|
|
||||||
|
> TODO: description
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
const webcyptoCore = require('@peculiar/webcypto-core');
|
||||||
|
|
||||||
|
// TODO: DEMONSTRATE API
|
||||||
|
```
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>;
|
||||||
|
|
||||||
|
}
|
|
@ -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>;
|
||||||
|
|
||||||
|
}
|
|
@ -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>;
|
||||||
|
|
||||||
|
}
|
|
@ -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>;
|
||||||
|
|
||||||
|
}
|
|
@ -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>;
|
||||||
|
|
||||||
|
}
|
|
@ -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>;
|
||||||
|
|
||||||
|
}
|
|
@ -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";
|
|
@ -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"];
|
||||||
|
|
||||||
|
}
|
|
@ -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)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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";
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "./base";
|
|
@ -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>;
|
||||||
|
|
||||||
|
}
|
|
@ -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 });
|
|
@ -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>;
|
||||||
|
|
||||||
|
}
|
|
@ -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"];
|
||||||
|
}
|
|
@ -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>;
|
||||||
|
|
||||||
|
}
|
|
@ -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>;
|
||||||
|
|
||||||
|
}
|
|
@ -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";
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { CryptoError } from "./crypto";
|
||||||
|
|
||||||
|
export class AlgorithmError extends CryptoError {
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
export class CryptoError extends Error {
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
export * from "./algorithm";
|
||||||
|
export * from "./crypto";
|
||||||
|
export * from "./not_implemented";
|
||||||
|
export * from "./operation";
|
||||||
|
export * from "./required_property";
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { CryptoError } from "./crypto";
|
||||||
|
|
||||||
|
export class UnsupportedOperationError extends CryptoError {
|
||||||
|
constructor(methodName?: string) {
|
||||||
|
super(`Unsupported operation: ${methodName ? `${methodName}` : ""}`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { CryptoError } from "./crypto";
|
||||||
|
|
||||||
|
export class OperationError extends CryptoError {
|
||||||
|
}
|
|
@ -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}` : ""}`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>;
|
||||||
|
|
||||||
|
}
|
|
@ -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>;
|
||||||
|
|
||||||
|
}
|
|
@ -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";
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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>;
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
export * from "./base";
|
||||||
|
export * from "./ssa";
|
||||||
|
export * from "./pss";
|
||||||
|
export * from "./oaep";
|
|
@ -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>;
|
||||||
|
|
||||||
|
}
|
|
@ -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>;
|
||||||
|
|
||||||
|
}
|
|
@ -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>;
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./integer_without_paddings";
|
||||||
|
export * from "./integer_converter";
|
|
@ -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();
|
||||||
|
},
|
||||||
|
};
|
|
@ -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);
|
||||||
|
},
|
||||||
|
};
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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";
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./curve_private_key";
|
||||||
|
export * from "./object_identifiers";
|
|
@ -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";
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * as asn1 from "./asn1";
|
||||||
|
export * as json from "./json";
|
|
@ -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)),
|
||||||
|
};
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "./converters";
|
|
@ -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>;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
export * from "./base";
|
||||||
|
export * from "./shake128";
|
||||||
|
export * from "./shake256";
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { ShakeProvider } from "./base";
|
||||||
|
|
||||||
|
export abstract class Shake128Provider extends ShakeProvider {
|
||||||
|
public override name = "shake128";
|
||||||
|
public override defaultLength = 16;
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { ShakeProvider } from "./base";
|
||||||
|
|
||||||
|
export abstract class Shake256Provider extends ShakeProvider {
|
||||||
|
public override name = "shake256";
|
||||||
|
public override defaultLength = 32;
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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'`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./pem_converter";
|
||||||
|
export * from "./is_jwk";
|
|
@ -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`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -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);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -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);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -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)),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -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"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -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=");
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -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"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -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);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -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);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -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);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -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);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -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");
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -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
Reference in New Issue