diff options
Diffstat (limited to 'third_party/js')
154 files changed, 48479 insertions, 0 deletions
diff --git a/third_party/js/PKI.js/LICENSE b/third_party/js/PKI.js/LICENSE new file mode 100644 index 0000000000..0df18e1a31 --- /dev/null +++ b/third_party/js/PKI.js/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2014, GlobalSign +Copyright (c) 2015-2019, Peculiar Ventures +All rights reserved. + +Author 2014-2019, Yury Strozhevsky + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +* Neither the name of the {organization} nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/third_party/js/PKI.js/make-esmodule-bundle.patch b/third_party/js/PKI.js/make-esmodule-bundle.patch new file mode 100644 index 0000000000..3f0641b91e --- /dev/null +++ b/third_party/js/PKI.js/make-esmodule-bundle.patch @@ -0,0 +1,70 @@ +diff --git a/rollup.config.js b/rollup.config.js +index 8c8a7c2..b6e61c0 100644 +--- a/rollup.config.js ++++ b/rollup.config.js +@@ -3,6 +3,7 @@ import fs from "fs"; + import typescript from "rollup-plugin-typescript2"; + import dts from "rollup-plugin-dts"; + import pkg from "./package.json"; ++import { nodeResolve } from '@rollup/plugin-node-resolve'; + + const LICENSE = fs.readFileSync("LICENSE", { encoding: "utf-8" }); + const banner = [ +@@ -12,7 +13,6 @@ const banner = [ + "", + ].join("\n"); + const input = "src/index.ts"; +-const external = Object.keys(pkg.dependencies || {}); + + export default [ + { +@@ -26,10 +26,10 @@ export default [ + module: "ES2015", + removeComments: true, + } +- } ++ }, + }), ++ nodeResolve(), + ], +- external: [...external], + output: [ + { + banner, +@@ -45,7 +45,6 @@ export default [ + }, + { + input, +- external: [...external], + plugins: [ + dts({ + tsconfig: path.resolve(__dirname, "./tsconfig.json") +@@ -58,4 +57,4 @@ export default [ + } + ] + }, +-]; +\ No newline at end of file ++]; +diff --git a/tsconfig.json b/tsconfig.json +index ffd67ec..e72bdb6 100644 +--- a/tsconfig.json ++++ b/tsconfig.json +@@ -1,7 +1,8 @@ + { + "compilerOptions": { +- "target": "ES2019", +- "module": "CommonJS", ++ "target": "ES6", ++ "module": "ES6", ++ "moduleResolution": "node", + "strict": true, + "importHelpers": true, + "noImplicitOverride": true, +@@ -11,4 +12,4 @@ + "exclude": [ + "build/*.ts" + ] +-} +\ No newline at end of file ++} diff --git a/third_party/js/PKI.js/make_bundle.sh b/third_party/js/PKI.js/make_bundle.sh new file mode 100755 index 0000000000..5368d34cc1 --- /dev/null +++ b/third_party/js/PKI.js/make_bundle.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +patch -p1 < make-esmodule-bundle.patch +npm install +npm run build diff --git a/third_party/js/PKI.js/moz.yaml b/third_party/js/PKI.js/moz.yaml new file mode 100644 index 0000000000..0fa7eb251c --- /dev/null +++ b/third_party/js/PKI.js/moz.yaml @@ -0,0 +1,85 @@ +# Version of this schema +schema: 1 + +bugzilla: + # Bugzilla product and component for this directory and subdirectories + product: Firefox + component: Security + +# Document the source of externally hosted code +origin: + + # Short name of the package/library + name: PKIjs + + description: TypeScript library for interacting with public key infrastructure types. + + # Full URL for the package's homepage/etc + # Usually different from repository url + url: https://pkijs.org/ + + # Human-readable identifier for this version/release + # Generally "version NNN", "tag SSS", "bookmark SSS" + release: 72d175c9edbc8e00c550dee610a8dac6204f4383 (2023-01-15T09:12:35Z). + + # Revision to pull in + # Must be a long or short commit SHA (long preferred) + revision: 72d175c9edbc8e00c550dee610a8dac6204f4383 + + # The package's license, where possible using the mnemonic from + # https://spdx.org/licenses/ + # Multiple licenses can be specified (as a YAML list) + # A "LICENSE" file must exist containing the full license text + license: BSD-3-Clause + + # If the package's license is specified in a particular file, + # this is the name of the file. + # optional + license-file: LICENSE + +# Configuration for the automated vendoring system. +# optional +vendoring: + + # Repository URL to vendor from + # eg. https://github.com/kinetiknz/nestegg + # Any repository host can be specified here, however initially we'll only + # support automated vendoring from selected sources. + url: https://github.com/PeculiarVentures/PKI.js + + # Type of hosting for the upstream repository + # Valid values are 'gitlab', 'github', googlesource + source-hosting: github + + # Files/paths that will not be vendored from the upstream repository + # Implicitly contains ".git", and ".gitignore" + # optional + exclude: + - .eslintrc.json + - .github/ + - .gitignore + - .mocharc.yaml + - .nycrc + - CNAME + - FEATURES.md + - MAPPING.md + - README.md + - examples/ + - test/ + - typedoc.json + + keep: + - make_bundle.sh + - make-esmodule-bundle.patch + + update-actions: + - action: run-script + script: 'make_bundle.sh' + cwd: '{yaml_dir}' + + - action: move-file + from: 'build/index.es.js' + to: '../../../toolkit/components/certviewer/content/vendor/pkijs.js' + + - action: delete-path + path: 'build' diff --git a/third_party/js/PKI.js/package.json b/third_party/js/PKI.js/package.json new file mode 100644 index 0000000000..00ec44b1a6 --- /dev/null +++ b/third_party/js/PKI.js/package.json @@ -0,0 +1,86 @@ +{
+ "author": {
+ "email": "yury@strozhevsky.com",
+ "name": "Yury Strozhevsky"
+ },
+ "description": "Public Key Infrastructure (PKI) is the basis of how identity and key management is performed on the web today. PKIjs is a pure JavaScript library implementing the formats that are used in PKI applications. It is built on WebCrypto and aspires to make it possible to build native web applications that utilize X.509 and the related formats on the web without plug-ins",
+ "contributors": [
+ {
+ "email": "rmh@unmitigatedrisk.com",
+ "name": "Ryan Hurst"
+ }
+ ],
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/PeculiarVentures/PKI.js.git"
+ },
+ "devDependencies": {
+ "@peculiar/webcrypto": "^1.4.0",
+ "@rollup/plugin-alias": "^3.1.9",
+ "@rollup/plugin-commonjs": "^22.0.1",
+ "@rollup/plugin-node-resolve": "^13.3.0",
+ "@types/mocha": "^9.1.1",
+ "@types/node": "^18.6.3",
+ "@typescript-eslint/eslint-plugin": "^5.32.0",
+ "@typescript-eslint/parser": "^5.32.0",
+ "assert": "^2.0.0",
+ "emailjs-mime-builder": "^2.0.5",
+ "emailjs-mime-parser": "^2.0.7",
+ "eslint": "^8.21.0",
+ "eslint-plugin-deprecation": "^1.3.2",
+ "mocha": "^10.0.0",
+ "nyc": "^15.1.0",
+ "rollup": "^2.77.2",
+ "rollup-plugin-dts": "^4.2.2",
+ "rollup-plugin-typescript2": "^0.32.1",
+ "ts-node": "^10.9.1",
+ "typedoc": "^0.23.10",
+ "typescript": "^4.7.4"
+ },
+ "dependencies": {
+ "asn1js": "^3.0.5",
+ "bytestreamjs": "^2.0.0",
+ "pvtsutils": "^1.3.2",
+ "pvutils": "^1.1.3",
+ "tslib": "^2.4.0"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "scripts": {
+ "build": "rollup -c",
+ "build:examples": "rollup -c examples/rollup.config.js",
+ "build:docs": "typedoc",
+ "lint": "eslint . --ext .ts",
+ "lint:fix": "eslint --fix . --ext .ts",
+ "coverage": "nyc npm test",
+ "test": "mocha",
+ "prepublishOnly": "npm run build"
+ },
+ "keywords": [
+ "ES6",
+ "ES2015",
+ "WebCrypto",
+ "Web Cryptography API",
+ "X.509",
+ "certificate",
+ "crl",
+ "cms",
+ "encrypted",
+ "enveloped",
+ "OCSP",
+ "timestamp",
+ "PKCS"
+ ],
+ "files": [
+ "build",
+ "README.md",
+ "LICENSE"
+ ],
+ "module": "build/index.es.js",
+ "main": "./build/index.js",
+ "types": "./build/index.d.ts",
+ "name": "pkijs",
+ "version": "3.0.11",
+ "license": "BSD-3-Clause"
+}
diff --git a/third_party/js/PKI.js/rollup.config.js b/third_party/js/PKI.js/rollup.config.js new file mode 100644 index 0000000000..b6e61c0833 --- /dev/null +++ b/third_party/js/PKI.js/rollup.config.js @@ -0,0 +1,60 @@ +import path from "path"; +import fs from "fs"; +import typescript from "rollup-plugin-typescript2"; +import dts from "rollup-plugin-dts"; +import pkg from "./package.json"; +import { nodeResolve } from '@rollup/plugin-node-resolve'; + +const LICENSE = fs.readFileSync("LICENSE", { encoding: "utf-8" }); +const banner = [ + "/*!", + ...LICENSE.split("\n").map(o => ` * ${o}`), + " */", + "", +].join("\n"); +const input = "src/index.ts"; + +export default [ + { + input, + plugins: [ + typescript({ + check: true, + clean: true, + tsconfigOverride: { + compilerOptions: { + module: "ES2015", + removeComments: true, + } + }, + }), + nodeResolve(), + ], + output: [ + { + banner, + file: pkg.main, + format: "cjs", + }, + { + banner, + file: pkg.module, + format: "es", + }, + ], + }, + { + input, + plugins: [ + dts({ + tsconfig: path.resolve(__dirname, "./tsconfig.json") + }) + ], + output: [ + { + banner, + file: pkg.types, + } + ] + }, +]; diff --git a/third_party/js/PKI.js/src/AccessDescription.ts b/third_party/js/PKI.js/src/AccessDescription.ts new file mode 100644 index 0000000000..e1dd222606 --- /dev/null +++ b/third_party/js/PKI.js/src/AccessDescription.ts @@ -0,0 +1,151 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { GeneralName, GeneralNameJson, GeneralNameSchema } from "./GeneralName"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const ACCESS_METHOD = "accessMethod"; +const ACCESS_LOCATION = "accessLocation"; +const CLEAR_PROPS = [ + ACCESS_METHOD, + ACCESS_LOCATION, +]; + +export interface IAccessDescription { + /** + * The type and format of the information are specified by the accessMethod field. This profile defines two accessMethod OIDs: id-ad-caIssuers and id-ad-ocsp + */ + accessMethod: string; + /** + * The accessLocation field specifies the location of the information + */ + accessLocation: GeneralName; +} + +export type AccessDescriptionParameters = PkiObjectParameters & Partial<IAccessDescription>; + +/** + * JSON representation of {@link AccessDescription} + */ +export interface AccessDescriptionJson { + accessMethod: string; + accessLocation: GeneralNameJson; +} + +/** + * Represents the AccessDescription structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + * + * The authority information access extension indicates how to access + * information and services for the issuer of the certificate in which + * the extension appears. Information and services may include on-line + * validation services and CA policy data. This extension may be included in + * end entity or CA certificates. Conforming CAs MUST mark this + * extension as non-critical. + */ +export class AccessDescription extends PkiObject implements IAccessDescription { + + public static override CLASS_NAME = "AccessDescription"; + + public accessMethod!: string; + public accessLocation!: GeneralName; + + /** + * Initializes a new instance of the {@link AccessDescription} class + * @param parameters Initialization parameters + */ + constructor(parameters: AccessDescriptionParameters = {}) { + super(); + + this.accessMethod = pvutils.getParametersValue(parameters, ACCESS_METHOD, AccessDescription.defaultValues(ACCESS_METHOD)); + this.accessLocation = pvutils.getParametersValue(parameters, ACCESS_LOCATION, AccessDescription.defaultValues(ACCESS_LOCATION)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof ACCESS_METHOD): string; + public static override defaultValues(memberName: typeof ACCESS_LOCATION): GeneralName; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case ACCESS_METHOD: + return EMPTY_STRING; + case ACCESS_LOCATION: + return new GeneralName(); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * AccessDescription ::= SEQUENCE { + * accessMethod OBJECT IDENTIFIER, + * accessLocation GeneralName } + *``` + */ + static override schema(parameters: Schema.SchemaParameters<{ accessMethod?: string; accessLocation?: GeneralNameSchema; }> = {}) { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.ObjectIdentifier({ name: (names.accessMethod || EMPTY_STRING) }), + GeneralName.schema(names.accessLocation || {}) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + AccessDescription.schema({ + names: { + accessMethod: ACCESS_METHOD, + accessLocation: { + names: { + blockName: ACCESS_LOCATION + } + } + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.accessMethod = asn1.result.accessMethod.valueBlock.toString(); + this.accessLocation = new GeneralName({ schema: asn1.result.accessLocation }); + } + + public toSchema(): asn1js.Sequence { + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: [ + new asn1js.ObjectIdentifier({ value: this.accessMethod }), + this.accessLocation.toSchema() + ] + })); + //#endregion + } + + public toJSON(): AccessDescriptionJson { + return { + accessMethod: this.accessMethod, + accessLocation: this.accessLocation.toJSON() + }; + } + +} diff --git a/third_party/js/PKI.js/src/Accuracy.ts b/third_party/js/PKI.js/src/Accuracy.ts new file mode 100644 index 0000000000..5c21794131 --- /dev/null +++ b/third_party/js/PKI.js/src/Accuracy.ts @@ -0,0 +1,243 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +export const SECONDS = "seconds"; +export const MILLIS = "millis"; +export const MICROS = "micros"; + +export interface IAccuracy { + /** + * Seconds + */ + seconds?: number; + /** + * Milliseconds + */ + millis?: number; + /** + * Microseconds + */ + micros?: number; +} + +export type AccuracyParameters = PkiObjectParameters & Partial<IAccuracy>; + +export type AccuracySchema = Schema.SchemaParameters<{ + seconds?: string; + millis?: string; + micros?: string; +}>; + +/** + * JSON representation of {@link Accuracy} + */ +export interface AccuracyJson { + seconds?: number; + millis?: number; + micros?: number; +} + +/** + * Represents the time deviation around the UTC time contained in GeneralizedTime. Described in [RFC3161](https://www.ietf.org/rfc/rfc3161.txt) + */ +export class Accuracy extends PkiObject implements IAccuracy { + + public static override CLASS_NAME = "Accuracy"; + + public seconds?: number; + public millis?: number; + public micros?: number; + + /** + * Initializes a new instance of the {@link Accuracy} class + * @param parameters Initialization parameters + */ + constructor(parameters: AccuracyParameters = {}) { + super(); + + if (SECONDS in parameters) { + this.seconds = pvutils.getParametersValue(parameters, SECONDS, Accuracy.defaultValues(SECONDS)); + } + if (MILLIS in parameters) { + this.millis = pvutils.getParametersValue(parameters, MILLIS, Accuracy.defaultValues(MILLIS)); + } + if (MICROS in parameters) { + this.micros = pvutils.getParametersValue(parameters, MICROS, Accuracy.defaultValues(MICROS)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof SECONDS): number; + public static override defaultValues(memberName: typeof MILLIS): number; + public static override defaultValues(memberName: typeof MICROS): number; + public static override defaultValues(memberName: string): any; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case SECONDS: + case MILLIS: + case MICROS: + return 0; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: typeof SECONDS | typeof MILLIS | typeof MICROS, memberValue: number): boolean; + public static compareWithDefault(memberName: string, memberValue: any): boolean; + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case SECONDS: + case MILLIS: + case MICROS: + return (memberValue === Accuracy.defaultValues(memberName)); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * Accuracy ::= SEQUENCE { + * seconds INTEGER OPTIONAL, + * millis [0] INTEGER (1..999) OPTIONAL, + * micros [1] INTEGER (1..999) OPTIONAL } + *``` + */ + public static override schema(parameters: AccuracySchema = {}): any { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + optional: true, + value: [ + new asn1js.Integer({ + optional: true, + name: (names.seconds || EMPTY_STRING) + }), + new asn1js.Primitive({ + name: (names.millis || EMPTY_STRING), + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + } + }), + new asn1js.Primitive({ + name: (names.micros || EMPTY_STRING), + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + } + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, [ + SECONDS, + MILLIS, + MICROS, + ]); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + Accuracy.schema({ + names: { + seconds: SECONDS, + millis: MILLIS, + micros: MICROS, + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + if ("seconds" in asn1.result) { + this.seconds = asn1.result.seconds.valueBlock.valueDec; + } + if ("millis" in asn1.result) { + const intMillis = new asn1js.Integer({ valueHex: asn1.result.millis.valueBlock.valueHex }); + this.millis = intMillis.valueBlock.valueDec; + } + if ("micros" in asn1.result) { + const intMicros = new asn1js.Integer({ valueHex: asn1.result.micros.valueBlock.valueHex }); + this.micros = intMicros.valueBlock.valueDec; + } + } + + public toSchema(): asn1js.Sequence { + //#region Create array of output sequence + const outputArray = []; + + if (this.seconds !== undefined) + outputArray.push(new asn1js.Integer({ value: this.seconds })); + + if (this.millis !== undefined) { + const intMillis = new asn1js.Integer({ value: this.millis }); + + outputArray.push(new asn1js.Primitive({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + valueHex: intMillis.valueBlock.valueHexView + })); + } + + if (this.micros !== undefined) { + const intMicros = new asn1js.Integer({ value: this.micros }); + + outputArray.push(new asn1js.Primitive({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + valueHex: intMicros.valueBlock.valueHexView + })); + } + //#endregion + + return (new asn1js.Sequence({ + value: outputArray + })); + } + public toJSON(): AccuracyJson { + const _object: AccuracyJson = {}; + + if (this.seconds !== undefined) + _object.seconds = this.seconds; + + if (this.millis !== undefined) + _object.millis = this.millis; + + if (this.micros !== undefined) + _object.micros = this.micros; + + return _object; + } + +} + diff --git a/third_party/js/PKI.js/src/AlgorithmIdentifier.ts b/third_party/js/PKI.js/src/AlgorithmIdentifier.ts new file mode 100644 index 0000000000..ce9ecdffb5 --- /dev/null +++ b/third_party/js/PKI.js/src/AlgorithmIdentifier.ts @@ -0,0 +1,209 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const ALGORITHM_ID = "algorithmId"; +const ALGORITHM_PARAMS = "algorithmParams"; +const ALGORITHM = "algorithm"; +const PARAMS = "params"; +const CLEAR_PROPS = [ + ALGORITHM, + PARAMS +]; + +export interface IAlgorithmIdentifier { + /** + * ObjectIdentifier for algorithm (string representation) + */ + algorithmId: string; + /** + * Any algorithm parameters + */ + algorithmParams?: any; +} + +export type AlgorithmIdentifierParameters = PkiObjectParameters & Partial<IAlgorithmIdentifier>; + +/** + * JSON representation of {@link AlgorithmIdentifier} + */ +export interface AlgorithmIdentifierJson { + algorithmId: string; + algorithmParams?: any; +} + +export type AlgorithmIdentifierSchema = Schema.SchemaParameters<{ + algorithmIdentifier?: string; + algorithmParams?: string; +}>; + +/** + * Represents the AlgorithmIdentifier structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class AlgorithmIdentifier extends PkiObject implements IAlgorithmIdentifier { + + public static override CLASS_NAME = "AlgorithmIdentifier"; + + public algorithmId!: string; + public algorithmParams?: any; + + /** + * Initializes a new instance of the {@link AlgorithmIdentifier} class + * @param parameters Initialization parameters + */ + constructor(parameters: AlgorithmIdentifierParameters = {}) { + super(); + + this.algorithmId = pvutils.getParametersValue(parameters, ALGORITHM_ID, AlgorithmIdentifier.defaultValues(ALGORITHM_ID)); + if (ALGORITHM_PARAMS in parameters) { + this.algorithmParams = pvutils.getParametersValue(parameters, ALGORITHM_PARAMS, AlgorithmIdentifier.defaultValues(ALGORITHM_PARAMS)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof ALGORITHM_ID): string; + public static override defaultValues(memberName: typeof ALGORITHM_PARAMS): any; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case ALGORITHM_ID: + return EMPTY_STRING; + case ALGORITHM_PARAMS: + return new asn1js.Any(); + default: + return super.defaultValues(memberName); + } + } + + /** + * Compares values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case ALGORITHM_ID: + return (memberValue === EMPTY_STRING); + case ALGORITHM_PARAMS: + return (memberValue instanceof asn1js.Any); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * AlgorithmIdentifier ::= Sequence { + * algorithm OBJECT IDENTIFIER, + * parameters ANY DEFINED BY algorithm OPTIONAL } + *``` + */ + public static override schema(parameters: AlgorithmIdentifierSchema = {}): any { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + optional: (names.optional || false), + value: [ + new asn1js.ObjectIdentifier({ name: (names.algorithmIdentifier || EMPTY_STRING) }), + new asn1js.Any({ name: (names.algorithmParams || EMPTY_STRING), optional: true }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + AlgorithmIdentifier.schema({ + names: { + algorithmIdentifier: ALGORITHM, + algorithmParams: PARAMS + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.algorithmId = asn1.result.algorithm.valueBlock.toString(); + if (PARAMS in asn1.result) { + this.algorithmParams = asn1.result.params; + } + } + + public toSchema(): asn1js.Sequence { + // Create array for output sequence + const outputArray = []; + outputArray.push(new asn1js.ObjectIdentifier({ value: this.algorithmId })); + if (this.algorithmParams && !(this.algorithmParams instanceof asn1js.Any)) { + outputArray.push(this.algorithmParams); + } + + // Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + } + + public toJSON(): AlgorithmIdentifierJson { + const object: AlgorithmIdentifierJson = { + algorithmId: this.algorithmId + }; + + if (this.algorithmParams && !(this.algorithmParams instanceof asn1js.Any)) { + object.algorithmParams = this.algorithmParams.toJSON(); + } + + return object; + } + + /** + * Checks that two "AlgorithmIdentifiers" are equal + * @param algorithmIdentifier + */ + public isEqual(algorithmIdentifier: unknown): boolean { + //#region Check input type + if (!(algorithmIdentifier instanceof AlgorithmIdentifier)) { + return false; + } + //#endregion + + //#region Check "algorithm_id" + if (this.algorithmId !== algorithmIdentifier.algorithmId) { + return false; + } + //#endregion + + //#region Check "algorithm_params" + if (this.algorithmParams) { + if (algorithmIdentifier.algorithmParams) { + return JSON.stringify(this.algorithmParams) === JSON.stringify(algorithmIdentifier.algorithmParams); + } + + return false; + } + + if (algorithmIdentifier.algorithmParams) { + return false; + } + //#endregion + + return true; + } + +} diff --git a/third_party/js/PKI.js/src/AltName.ts b/third_party/js/PKI.js/src/AltName.ts new file mode 100644 index 0000000000..a7a4b72331 --- /dev/null +++ b/third_party/js/PKI.js/src/AltName.ts @@ -0,0 +1,121 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { GeneralName, GeneralNameJson } from "./GeneralName"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const ALT_NAMES = "altNames"; +const CLEAR_PROPS = [ + ALT_NAMES +]; + +export interface IAltName { + /** + * Array of alternative names in GeneralName type + */ + altNames: GeneralName[]; +} + +export type AltNameParameters = PkiObjectParameters & Partial<IAltName>; + +export interface AltNameJson { + altNames: GeneralNameJson[]; +} + +/** + * Represents the AltName structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class AltName extends PkiObject implements IAltName { + + public static override CLASS_NAME = "AltName"; + + public altNames!: GeneralName[]; + + /** + * Initializes a new instance of the {@link AltName} class + * @param parameters Initialization parameters + */ + constructor(parameters: AltNameParameters = {}) { + super(); + + this.altNames = pvutils.getParametersValue(parameters, ALT_NAMES, AltName.defaultValues(ALT_NAMES)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof ALT_NAMES): GeneralName[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case ALT_NAMES: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * AltName ::= GeneralNames + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ altNames?: string; }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Repeated({ + name: (names.altNames || EMPTY_STRING), + value: GeneralName.schema() + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + AltName.schema({ + names: { + altNames: ALT_NAMES + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + if (ALT_NAMES in asn1.result) { + this.altNames = Array.from(asn1.result.altNames, element => new GeneralName({ schema: element })); + } + } + + public toSchema(): asn1js.Sequence { + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: Array.from(this.altNames, o => o.toSchema()) + })); + //#endregion + } + + public toJSON(): AltNameJson { + return { + altNames: Array.from(this.altNames, o => o.toJSON()) + }; + } + +} diff --git a/third_party/js/PKI.js/src/Attribute.ts b/third_party/js/PKI.js/src/Attribute.ts new file mode 100644 index 0000000000..81de5655e1 --- /dev/null +++ b/third_party/js/PKI.js/src/Attribute.ts @@ -0,0 +1,170 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const TYPE = "type"; +const VALUES = "values"; +const CLEAR_PROPS = [ + TYPE, + VALUES +]; + +export interface IAttribute { + /** + * Specifies type of attribute value + */ + type: string; + /** + * List of attribute values + */ + values: any[]; +} + +export type AttributeParameters = PkiObjectParameters & Partial<IAttribute>; + +export type AttributeSchema = Schema.SchemaParameters<{ + setName?: string; + type?: string; + values?: string; +}>; + +export interface AttributeJson { + type: string; + values: any[]; +} + +/** + * Represents the Attribute structure described in [RFC2986](https://datatracker.ietf.org/doc/html/rfc2986) + */ +export class Attribute extends PkiObject implements IAttribute { + + public static override CLASS_NAME = "Attribute"; + + public type!: string; + public values!: any[]; + + /** + * Initializes a new instance of the {@link Attribute} class + * @param parameters Initialization parameters + */ + constructor(parameters: AttributeParameters = {}) { + super(); + + this.type = pvutils.getParametersValue(parameters, TYPE, Attribute.defaultValues(TYPE)); + this.values = pvutils.getParametersValue(parameters, VALUES, Attribute.defaultValues(VALUES)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof TYPE): string; + public static override defaultValues(memberName: typeof VALUES): any[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case TYPE: + return EMPTY_STRING; + case VALUES: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compares values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case TYPE: + return (memberValue === EMPTY_STRING); + case VALUES: + return (memberValue.length === 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * Attribute { ATTRIBUTE:IOSet } ::= SEQUENCE { + * type ATTRIBUTE.&id({IOSet}), + * values SET SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{@type}) + * } + *``` + */ + public static override schema(parameters: AttributeSchema = {}) { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.ObjectIdentifier({ name: (names.type || EMPTY_STRING) }), + new asn1js.Set({ + name: (names.setName || EMPTY_STRING), + value: [ + new asn1js.Repeated({ + name: (names.values || EMPTY_STRING), + value: new asn1js.Any() + }) + ] + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + Attribute.schema({ + names: { + type: TYPE, + values: VALUES + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.type = asn1.result.type.valueBlock.toString(); + this.values = asn1.result.values; + } + + public toSchema(): asn1js.Sequence { + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: [ + new asn1js.ObjectIdentifier({ value: this.type }), + new asn1js.Set({ + value: this.values + }) + ] + })); + //#endregion + } + + public toJSON(): AttributeJson { + return { + type: this.type, + values: Array.from(this.values, o => o.toJSON()) + }; + } + +} + diff --git a/third_party/js/PKI.js/src/AttributeCertificateV1/AttCertValidityPeriod.ts b/third_party/js/PKI.js/src/AttributeCertificateV1/AttCertValidityPeriod.ts new file mode 100644 index 0000000000..670aaad873 --- /dev/null +++ b/third_party/js/PKI.js/src/AttributeCertificateV1/AttCertValidityPeriod.ts @@ -0,0 +1,134 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "../constants"; +import { AsnError } from "../errors"; +import { PkiObject, PkiObjectParameters } from "../PkiObject"; +import * as Schema from "../Schema"; + +const NOT_BEFORE_TIME = "notBeforeTime"; +const NOT_AFTER_TIME = "notAfterTime"; +const CLEAR_PROPS = [ + NOT_BEFORE_TIME, + NOT_AFTER_TIME, +]; + +export interface IAttCertValidityPeriod { + notBeforeTime: Date; + notAfterTime: Date; +} + +export type AttCertValidityPeriodParameters = PkiObjectParameters & Partial<IAttCertValidityPeriod>; + +export type AttCertValidityPeriodSchema = Schema.SchemaParameters<{ + notBeforeTime?: string; + notAfterTime?: string; +}>; + +export interface AttCertValidityPeriodJson { + notBeforeTime: Date; + notAfterTime: Date; +} + +/** + * Represents the AttCertValidityPeriod structure described in [RFC5755 Section 4.1](https://datatracker.ietf.org/doc/html/rfc5755#section-4.1) + */ +export class AttCertValidityPeriod extends PkiObject implements IAttCertValidityPeriod { + + public static override CLASS_NAME = "AttCertValidityPeriod"; + + public notBeforeTime!: Date; + public notAfterTime!: Date; + + /** + * Initializes a new instance of the {@link AttCertValidityPeriod} class + * @param parameters Initialization parameters + */ + constructor(parameters: AttCertValidityPeriodParameters = {}) { + super(); + + this.notBeforeTime = pvutils.getParametersValue(parameters, NOT_BEFORE_TIME, AttCertValidityPeriod.defaultValues(NOT_BEFORE_TIME)); + this.notAfterTime = pvutils.getParametersValue(parameters, NOT_AFTER_TIME, AttCertValidityPeriod.defaultValues(NOT_AFTER_TIME)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof NOT_BEFORE_TIME): Date; + public static override defaultValues(memberName: typeof NOT_AFTER_TIME): Date; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case NOT_BEFORE_TIME: + case NOT_AFTER_TIME: + return new Date(0, 0, 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * AttCertValidityPeriod ::= SEQUENCE { + * notBeforeTime GeneralizedTime, + * notAfterTime GeneralizedTime + * } + *``` + */ + public static override schema(parameters: AttCertValidityPeriodSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.GeneralizedTime({ name: (names.notBeforeTime || EMPTY_STRING) }), + new asn1js.GeneralizedTime({ name: (names.notAfterTime || EMPTY_STRING) }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + AttCertValidityPeriod.schema({ + names: { + notBeforeTime: NOT_BEFORE_TIME, + notAfterTime: NOT_AFTER_TIME + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.notBeforeTime = asn1.result.notBeforeTime.toDate(); + this.notAfterTime = asn1.result.notAfterTime.toDate(); + } + + public toSchema(): asn1js.Sequence { + // Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: [ + new asn1js.GeneralizedTime({ valueDate: this.notBeforeTime }), + new asn1js.GeneralizedTime({ valueDate: this.notAfterTime }), + ] + })); + } + + public toJSON(): AttCertValidityPeriodJson { + return { + notBeforeTime: this.notBeforeTime, + notAfterTime: this.notAfterTime + }; + } + +} diff --git a/third_party/js/PKI.js/src/AttributeCertificateV1/AttributeCertificateInfoV1.ts b/third_party/js/PKI.js/src/AttributeCertificateV1/AttributeCertificateInfoV1.ts new file mode 100644 index 0000000000..73e5004bc7 --- /dev/null +++ b/third_party/js/PKI.js/src/AttributeCertificateV1/AttributeCertificateInfoV1.ts @@ -0,0 +1,407 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { GeneralNames, GeneralNamesJson } from "../GeneralNames"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "../AlgorithmIdentifier"; +import { Attribute, AttributeJson } from "../Attribute"; +import { Extensions, ExtensionsJson, ExtensionsSchema } from "../Extensions"; +import { AttCertValidityPeriod, AttCertValidityPeriodJson, AttCertValidityPeriodSchema } from "./AttCertValidityPeriod"; +import { IssuerSerial, IssuerSerialJson } from "./IssuerSerial"; +import * as Schema from "../Schema"; +import { PkiObject, PkiObjectParameters } from "../PkiObject"; +import { AsnError } from "../errors"; +import { EMPTY_STRING } from "../constants"; + +const VERSION = "version"; +const BASE_CERTIFICATE_ID = "baseCertificateID"; +const SUBJECT_NAME = "subjectName"; +const ISSUER = "issuer"; +const SIGNATURE = "signature"; +const SERIAL_NUMBER = "serialNumber"; +const ATTR_CERT_VALIDITY_PERIOD = "attrCertValidityPeriod"; +const ATTRIBUTES = "attributes"; +const ISSUER_UNIQUE_ID = "issuerUniqueID"; +const EXTENSIONS = "extensions"; +const CLEAR_PROPS = [ + VERSION, + BASE_CERTIFICATE_ID, + SUBJECT_NAME, + ISSUER, + SIGNATURE, + SERIAL_NUMBER, + ATTR_CERT_VALIDITY_PERIOD, + ATTRIBUTES, + ISSUER_UNIQUE_ID, + EXTENSIONS, +]; + +export interface IAttributeCertificateInfoV1 { + /** + * The version field MUST have the value of v2 + */ + version: number; + baseCertificateID?: IssuerSerial; + subjectName?: GeneralNames; + issuer: GeneralNames; + /** + * Contains the algorithm identifier used to validate the AC signature + */ + signature: AlgorithmIdentifier; + serialNumber: asn1js.Integer; + /** + * Specifies the period for which the AC issuer certifies that the binding between + * the holder and the attributes fields will be valid + */ + attrCertValidityPeriod: AttCertValidityPeriod; + /** + * The attributes field gives information about the AC holder + */ + attributes: Attribute[]; + + /** + * Issuer unique identifier + */ + issuerUniqueID?: asn1js.BitString; + /** + * The extensions field generally gives information about the AC as opposed + * to information about the AC holder + */ + extensions?: Extensions; +} + +export interface AttributeCertificateInfoV1Json { + version: number; + baseCertificateID?: IssuerSerialJson; + subjectName?: GeneralNamesJson; + issuer: GeneralNamesJson; + signature: AlgorithmIdentifierJson; + serialNumber: asn1js.IntegerJson; + attrCertValidityPeriod: AttCertValidityPeriodJson; + attributes: AttributeJson[]; + issuerUniqueID: asn1js.BitStringJson; + extensions: ExtensionsJson; +} + +export type AttributeCertificateInfoV1Parameters = PkiObjectParameters & Partial<IAttributeCertificateInfoV1>; + +export type AttributeCertificateInfoV1Schema = Schema.SchemaParameters<{ + version?: string; + baseCertificateID?: string; + subjectName?: string; + signature?: AlgorithmIdentifierSchema; + issuer?: string; + attrCertValidityPeriod?: AttCertValidityPeriodSchema; + serialNumber?: string; + attributes?: string; + issuerUniqueID?: string; + extensions?: ExtensionsSchema; +}>; + +/** + * Represents the AttributeCertificateInfoV1 structure described in [RFC5755](https://datatracker.ietf.org/doc/html/rfc5755) + */ +export class AttributeCertificateInfoV1 extends PkiObject implements IAttributeCertificateInfoV1 { + + public static override CLASS_NAME = "AttributeCertificateInfoV1"; + + version!: number; + baseCertificateID?: IssuerSerial; + subjectName?: GeneralNames; + issuer!: GeneralNames; + signature!: AlgorithmIdentifier; + serialNumber!: asn1js.Integer; + attrCertValidityPeriod!: AttCertValidityPeriod; + attributes!: Attribute[]; + issuerUniqueID?: asn1js.BitString; + extensions?: Extensions; + + /** + * Initializes a new instance of the {@link AttributeCertificateInfoV1} class + * @param parameters Initialization parameters + */ + constructor(parameters: AttributeCertificateInfoV1Parameters = {}) { + super(); + + this.version = pvutils.getParametersValue(parameters, VERSION, AttributeCertificateInfoV1.defaultValues(VERSION)); + if (BASE_CERTIFICATE_ID in parameters) { + this.baseCertificateID = pvutils.getParametersValue(parameters, BASE_CERTIFICATE_ID, AttributeCertificateInfoV1.defaultValues(BASE_CERTIFICATE_ID)); + } + if (SUBJECT_NAME in parameters) { + this.subjectName = pvutils.getParametersValue(parameters, SUBJECT_NAME, AttributeCertificateInfoV1.defaultValues(SUBJECT_NAME)); + } + this.issuer = pvutils.getParametersValue(parameters, ISSUER, AttributeCertificateInfoV1.defaultValues(ISSUER)); + this.signature = pvutils.getParametersValue(parameters, SIGNATURE, AttributeCertificateInfoV1.defaultValues(SIGNATURE)); + this.serialNumber = pvutils.getParametersValue(parameters, SERIAL_NUMBER, AttributeCertificateInfoV1.defaultValues(SERIAL_NUMBER)); + this.attrCertValidityPeriod = pvutils.getParametersValue(parameters, ATTR_CERT_VALIDITY_PERIOD, AttributeCertificateInfoV1.defaultValues(ATTR_CERT_VALIDITY_PERIOD)); + this.attributes = pvutils.getParametersValue(parameters, ATTRIBUTES, AttributeCertificateInfoV1.defaultValues(ATTRIBUTES)); + if (ISSUER_UNIQUE_ID in parameters) + this.issuerUniqueID = pvutils.getParametersValue(parameters, ISSUER_UNIQUE_ID, AttributeCertificateInfoV1.defaultValues(ISSUER_UNIQUE_ID)); + + if (EXTENSIONS in parameters) { + this.extensions = pvutils.getParametersValue(parameters, EXTENSIONS, AttributeCertificateInfoV1.defaultValues(EXTENSIONS)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof VERSION): number; + public static override defaultValues(memberName: typeof BASE_CERTIFICATE_ID): IssuerSerial; + public static override defaultValues(memberName: typeof SUBJECT_NAME): GeneralNames; + public static override defaultValues(memberName: typeof ISSUER): GeneralNames; + public static override defaultValues(memberName: typeof SIGNATURE): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof SERIAL_NUMBER): asn1js.Integer; + public static override defaultValues(memberName: typeof ATTR_CERT_VALIDITY_PERIOD): AttCertValidityPeriod; + public static override defaultValues(memberName: typeof ATTRIBUTES): Attribute[]; + public static override defaultValues(memberName: typeof ISSUER_UNIQUE_ID): asn1js.BitString; + public static override defaultValues(memberName: typeof EXTENSIONS): Extensions; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case VERSION: + return 0; + case BASE_CERTIFICATE_ID: + return new IssuerSerial(); + case SUBJECT_NAME: + return new GeneralNames(); + case ISSUER: + return new GeneralNames(); + case SIGNATURE: + return new AlgorithmIdentifier(); + case SERIAL_NUMBER: + return new asn1js.Integer(); + case ATTR_CERT_VALIDITY_PERIOD: + return new AttCertValidityPeriod(); + case ATTRIBUTES: + return []; + case ISSUER_UNIQUE_ID: + return new asn1js.BitString(); + case EXTENSIONS: + return new Extensions(); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * AttributeCertificateInfo ::= SEQUENCE { + * version Version DEFAULT v1, + * subject CHOICE { + * baseCertificateID [0] IssuerSerial, -- associated with a Public Key Certificate + * subjectName [1] GeneralNames }, -- associated with a name + * issuer GeneralNames, -- CA issuing the attribute certificate + * signature AlgorithmIdentifier, + * serialNumber CertificateSerialNumber, + * attrCertValidityPeriod AttCertValidityPeriod, + * attributes SEQUENCE OF Attribute, + * issuerUniqueID UniqueIdentifier OPTIONAL, + * extensions Extensions OPTIONAL + * } + *``` + */ + public static override schema(parameters: AttributeCertificateInfoV1Schema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Integer({ name: (names.version || EMPTY_STRING) }), + new asn1js.Choice({ + value: [ + new asn1js.Constructed({ + name: (names.baseCertificateID || EMPTY_STRING), + idBlock: { + tagClass: 3, + tagNumber: 0 // [0] + }, + value: IssuerSerial.schema().valueBlock.value + }), + new asn1js.Constructed({ + name: (names.subjectName || EMPTY_STRING), + idBlock: { + tagClass: 3, + tagNumber: 1 // [2] + }, + value: GeneralNames.schema().valueBlock.value + }), + ] + }), + GeneralNames.schema({ + names: { + blockName: (names.issuer || EMPTY_STRING) + } + }), + AlgorithmIdentifier.schema(names.signature || {}), + new asn1js.Integer({ name: (names.serialNumber || EMPTY_STRING) }), + AttCertValidityPeriod.schema(names.attrCertValidityPeriod || {}), + new asn1js.Sequence({ + name: (names.attributes || EMPTY_STRING), + value: [ + new asn1js.Repeated({ + value: Attribute.schema() + }) + ] + }), + new asn1js.BitString({ + optional: true, + name: (names.issuerUniqueID || EMPTY_STRING) + }), + Extensions.schema(names.extensions || {}, true) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + //#region Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + AttributeCertificateInfoV1.schema({ + names: { + version: VERSION, + baseCertificateID: BASE_CERTIFICATE_ID, + subjectName: SUBJECT_NAME, + issuer: ISSUER, + signature: { + names: { + blockName: SIGNATURE + } + }, + serialNumber: SERIAL_NUMBER, + attrCertValidityPeriod: { + names: { + blockName: ATTR_CERT_VALIDITY_PERIOD + } + }, + attributes: ATTRIBUTES, + issuerUniqueID: ISSUER_UNIQUE_ID, + extensions: { + names: { + blockName: EXTENSIONS + } + } + } + }) + ); + + AsnError.assertSchema(asn1, this.className); + //#endregion + + //#region Get internal properties from parsed schema + this.version = asn1.result.version.valueBlock.valueDec; + + if (BASE_CERTIFICATE_ID in asn1.result) { + this.baseCertificateID = new IssuerSerial({ + schema: new asn1js.Sequence({ + value: asn1.result.baseCertificateID.valueBlock.value + }) + }); + } + + if (SUBJECT_NAME in asn1.result) { + this.subjectName = new GeneralNames({ + schema: new asn1js.Sequence({ + value: asn1.result.subjectName.valueBlock.value + }) + }); + } + + this.issuer = asn1.result.issuer; + this.signature = new AlgorithmIdentifier({ schema: asn1.result.signature }); + this.serialNumber = asn1.result.serialNumber; + this.attrCertValidityPeriod = new AttCertValidityPeriod({ schema: asn1.result.attrCertValidityPeriod }); + this.attributes = Array.from(asn1.result.attributes.valueBlock.value, element => new Attribute({ schema: element })); + + if (ISSUER_UNIQUE_ID in asn1.result) { + this.issuerUniqueID = asn1.result.issuerUniqueID; + } + + if (EXTENSIONS in asn1.result) { + this.extensions = new Extensions({ schema: asn1.result.extensions }); + } + //#endregion + } + + public toSchema(): asn1js.Sequence { + const result = new asn1js.Sequence({ + value: [new asn1js.Integer({ value: this.version })] + }); + + if (this.baseCertificateID) { + result.valueBlock.value.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, + tagNumber: 0 // [0] + }, + value: this.baseCertificateID.toSchema().valueBlock.value + })); + } + + if (this.subjectName) { + result.valueBlock.value.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, + tagNumber: 1 // [1] + }, + value: this.subjectName.toSchema().valueBlock.value + })); + } + + result.valueBlock.value.push(this.issuer.toSchema()); + result.valueBlock.value.push(this.signature.toSchema()); + result.valueBlock.value.push(this.serialNumber); + result.valueBlock.value.push(this.attrCertValidityPeriod.toSchema()); + result.valueBlock.value.push(new asn1js.Sequence({ + value: Array.from(this.attributes, o => o.toSchema()) + })); + + if (this.issuerUniqueID) { + result.valueBlock.value.push(this.issuerUniqueID); + } + + if (this.extensions) { + result.valueBlock.value.push(this.extensions.toSchema()); + } + + return result; + } + + public toJSON(): AttributeCertificateInfoV1Json { + const result = { + version: this.version + } as AttributeCertificateInfoV1Json; + + if (this.baseCertificateID) { + result.baseCertificateID = this.baseCertificateID.toJSON(); + } + + if (this.subjectName) { + result.subjectName = this.subjectName.toJSON(); + } + + result.issuer = this.issuer.toJSON(); + result.signature = this.signature.toJSON(); + result.serialNumber = this.serialNumber.toJSON(); + result.attrCertValidityPeriod = this.attrCertValidityPeriod.toJSON(); + result.attributes = Array.from(this.attributes, o => o.toJSON()); + + if (this.issuerUniqueID) { + result.issuerUniqueID = this.issuerUniqueID.toJSON(); + } + + if (this.extensions) { + result.extensions = this.extensions.toJSON(); + } + + return result; + } + +} diff --git a/third_party/js/PKI.js/src/AttributeCertificateV1/AttributeCertificateV1.ts b/third_party/js/PKI.js/src/AttributeCertificateV1/AttributeCertificateV1.ts new file mode 100644 index 0000000000..50730a36d5 --- /dev/null +++ b/third_party/js/PKI.js/src/AttributeCertificateV1/AttributeCertificateV1.ts @@ -0,0 +1,169 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "../AlgorithmIdentifier"; +import { AttributeCertificateInfoV1, AttributeCertificateInfoV1Json, AttributeCertificateInfoV1Schema } from "./AttributeCertificateInfoV1"; +import * as Schema from "../Schema"; +import { PkiObject, PkiObjectParameters } from "../PkiObject"; +import { AsnError } from "../errors"; +import { EMPTY_STRING } from "../constants"; + +const ACINFO = "acinfo"; +const SIGNATURE_ALGORITHM = "signatureAlgorithm"; +const SIGNATURE_VALUE = "signatureValue"; +const CLEAR_PROPS = [ + ACINFO, + SIGNATURE_VALUE, + SIGNATURE_ALGORITHM +]; + +export interface IAttributeCertificateV1 { + /** + * Attribute certificate information + */ + acinfo: AttributeCertificateInfoV1; + /** + * Signature algorithm + */ + signatureAlgorithm: AlgorithmIdentifier; + /** + * Signature value + */ + signatureValue: asn1js.BitString; +} + +export interface AttributeCertificateV1Json { + acinfo: AttributeCertificateInfoV1Json; + signatureAlgorithm: AlgorithmIdentifierJson; + signatureValue: asn1js.BitStringJson; +} + +export type AttributeCertificateV1Parameters = PkiObjectParameters & Partial<IAttributeCertificateV1>; + +/** + * Class from X.509:1997 + */ +export class AttributeCertificateV1 extends PkiObject implements IAttributeCertificateV1 { + + public static override CLASS_NAME = "AttributeCertificateV1"; + + public acinfo!: AttributeCertificateInfoV1; + public signatureAlgorithm!: AlgorithmIdentifier; + public signatureValue!: asn1js.BitString; + + /** + * Initializes a new instance of the {@link AttributeCertificateV1} class + * @param parameters Initialization parameters + */ + constructor(parameters: AttributeCertificateV1Parameters = {}) { + super(); + + this.acinfo = pvutils.getParametersValue(parameters, ACINFO, AttributeCertificateV1.defaultValues(ACINFO)); + this.signatureAlgorithm = pvutils.getParametersValue(parameters, SIGNATURE_ALGORITHM, AttributeCertificateV1.defaultValues(SIGNATURE_ALGORITHM)); + this.signatureValue = pvutils.getParametersValue(parameters, SIGNATURE_VALUE, AttributeCertificateV1.defaultValues(SIGNATURE_VALUE)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof ACINFO): AttributeCertificateInfoV1; + public static override defaultValues(memberName: typeof SIGNATURE_ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof SIGNATURE_VALUE): asn1js.BitString; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case ACINFO: + return new AttributeCertificateInfoV1(); + case SIGNATURE_ALGORITHM: + return new AlgorithmIdentifier(); + case SIGNATURE_VALUE: + return new asn1js.BitString(); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * AttributeCertificate ::= SEQUENCE { + * acinfo AttributeCertificateInfoV1, + * signatureAlgorithm AlgorithmIdentifier, + * signatureValue BIT STRING + * } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + acinfo?: AttributeCertificateInfoV1Schema; + signatureAlgorithm?: AlgorithmIdentifierSchema; + signatureValue?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + AttributeCertificateInfoV1.schema(names.acinfo || {}), + AlgorithmIdentifier.schema(names.signatureAlgorithm || {}), + new asn1js.BitString({ name: (names.signatureValue || EMPTY_STRING) }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + //#region Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + AttributeCertificateV1.schema({ + names: { + acinfo: { + names: { + blockName: ACINFO + } + }, + signatureAlgorithm: { + names: { + blockName: SIGNATURE_ALGORITHM + } + }, + signatureValue: SIGNATURE_VALUE + } + }) + ); + AsnError.assertSchema(asn1, this.className); + //#endregion + + //#region Get internal properties from parsed schema + this.acinfo = new AttributeCertificateInfoV1({ schema: asn1.result.acinfo }); + this.signatureAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.signatureAlgorithm }); + this.signatureValue = asn1.result.signatureValue; + //#endregion + } + + public toSchema(): asn1js.Sequence { + return (new asn1js.Sequence({ + value: [ + this.acinfo.toSchema(), + this.signatureAlgorithm.toSchema(), + this.signatureValue + ] + })); + } + + public toJSON(): AttributeCertificateV1Json { + return { + acinfo: this.acinfo.toJSON(), + signatureAlgorithm: this.signatureAlgorithm.toJSON(), + signatureValue: this.signatureValue.toJSON(), + }; + } + +} diff --git a/third_party/js/PKI.js/src/AttributeCertificateV1/IssuerSerial.ts b/third_party/js/PKI.js/src/AttributeCertificateV1/IssuerSerial.ts new file mode 100644 index 0000000000..f5c355b858 --- /dev/null +++ b/third_party/js/PKI.js/src/AttributeCertificateV1/IssuerSerial.ts @@ -0,0 +1,183 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "../constants"; +import { AsnError } from "../errors"; +import { GeneralNames, GeneralNamesJson, GeneralNamesSchema } from "../GeneralNames"; +import { PkiObject, PkiObjectParameters } from "../PkiObject"; +import * as Schema from "../Schema"; + +const ISSUER = "issuer"; +const SERIAL_NUMBER = "serialNumber"; +const ISSUER_UID = "issuerUID"; +const CLEAR_PROPS = [ + ISSUER, + SERIAL_NUMBER, + ISSUER_UID, +]; + +export interface IIssuerSerial { + /** + * Issuer name + */ + issuer: GeneralNames; + /** + * Serial number + */ + serialNumber: asn1js.Integer; + /** + * Issuer unique identifier + */ + issuerUID?: asn1js.BitString; +} + +export type IssuerSerialParameters = PkiObjectParameters & Partial<IIssuerSerial>; + +export interface IssuerSerialJson { + issuer: GeneralNamesJson; + serialNumber: asn1js.IntegerJson; + issuerUID?: asn1js.BitStringJson; +} + +/** + * Represents the IssuerSerial structure described in [RFC5755](https://datatracker.ietf.org/doc/html/rfc5755) + */ +export class IssuerSerial extends PkiObject implements IIssuerSerial { + + public static override CLASS_NAME = "IssuerSerial"; + + public issuer!: GeneralNames; + public serialNumber!: asn1js.Integer; + public issuerUID?: asn1js.BitString; + + /** + * Initializes a new instance of the {@link IssuerSerial} class + * @param parameters Initialization parameters + */ + constructor(parameters: IssuerSerialParameters = {}) { + super(); + + this.issuer = pvutils.getParametersValue(parameters, ISSUER, IssuerSerial.defaultValues(ISSUER)); + this.serialNumber = pvutils.getParametersValue(parameters, SERIAL_NUMBER, IssuerSerial.defaultValues(SERIAL_NUMBER)); + if (ISSUER_UID in parameters) { + this.issuerUID = pvutils.getParametersValue(parameters, ISSUER_UID, IssuerSerial.defaultValues(ISSUER_UID)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof ISSUER): GeneralNames; + public static override defaultValues(memberName: typeof SERIAL_NUMBER): asn1js.Integer; + public static override defaultValues(memberName: typeof ISSUER_UID): asn1js.BitString; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case ISSUER: + return new GeneralNames(); + case SERIAL_NUMBER: + return new asn1js.Integer(); + case ISSUER_UID: + return new asn1js.BitString(); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * IssuerSerial ::= SEQUENCE { + * issuer GeneralNames, + * serial CertificateSerialNumber, + * issuerUID UniqueIdentifier OPTIONAL + * } + * + * CertificateSerialNumber ::= INTEGER + * UniqueIdentifier ::= BIT STRING + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + issuer?: GeneralNamesSchema; + serialNumber?: string; + issuerUID?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + GeneralNames.schema(names.issuer || {}), + new asn1js.Integer({ name: (names.serialNumber || EMPTY_STRING) }), + new asn1js.BitString({ + optional: true, + name: (names.issuerUID || EMPTY_STRING) + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + //#region Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + IssuerSerial.schema({ + names: { + issuer: { + names: { + blockName: ISSUER + } + }, + serialNumber: SERIAL_NUMBER, + issuerUID: ISSUER_UID + } + }) + ); + + AsnError.assertSchema(asn1, this.className); + //#endregion + //#region Get internal properties from parsed schema + this.issuer = new GeneralNames({ schema: asn1.result.issuer }); + this.serialNumber = asn1.result.serialNumber; + + if (ISSUER_UID in asn1.result) + this.issuerUID = asn1.result.issuerUID; + //#endregion + } + + public toSchema(): asn1js.Sequence { + const result = new asn1js.Sequence({ + value: [ + this.issuer.toSchema(), + this.serialNumber + ] + }); + + if (this.issuerUID) { + result.valueBlock.value.push(this.issuerUID); + } + + return result; + } + + public toJSON(): IssuerSerialJson { + const result = { + issuer: this.issuer.toJSON(), + serialNumber: this.serialNumber.toJSON() + } as IssuerSerialJson; + + if (this.issuerUID) { + result.issuerUID = this.issuerUID.toJSON(); + } + + return result; + } + +} diff --git a/third_party/js/PKI.js/src/AttributeCertificateV1/index.ts b/third_party/js/PKI.js/src/AttributeCertificateV1/index.ts new file mode 100644 index 0000000000..ece9050d4e --- /dev/null +++ b/third_party/js/PKI.js/src/AttributeCertificateV1/index.ts @@ -0,0 +1,4 @@ +export * from "./AttCertValidityPeriod"; +export * from "./AttributeCertificateInfoV1"; +export * from "./AttributeCertificateV1"; +export * from "./IssuerSerial";
\ No newline at end of file diff --git a/third_party/js/PKI.js/src/AttributeCertificateV2/AttributeCertificateInfoV2.ts b/third_party/js/PKI.js/src/AttributeCertificateV2/AttributeCertificateInfoV2.ts new file mode 100644 index 0000000000..cb7bb12f7a --- /dev/null +++ b/third_party/js/PKI.js/src/AttributeCertificateV2/AttributeCertificateInfoV2.ts @@ -0,0 +1,341 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { GeneralNames, GeneralNamesJson } from "../GeneralNames"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "../AlgorithmIdentifier"; +import { Attribute, AttributeJson } from "../Attribute"; +import { Extensions, ExtensionsJson, ExtensionsSchema } from "../Extensions"; +import { AttCertValidityPeriod, AttCertValidityPeriodJson, AttCertValidityPeriodSchema } from "../AttributeCertificateV1"; +import { V2Form, V2FormJson } from "./V2Form"; +import { Holder, HolderJson, HolderSchema } from "./Holder"; +import * as Schema from "../Schema"; +import { PkiObject, PkiObjectParameters } from "../PkiObject"; +import { AsnError } from "../errors"; +import { EMPTY_STRING } from "../constants"; + +const VERSION = "version"; +const HOLDER = "holder"; +const ISSUER = "issuer"; +const SIGNATURE = "signature"; +const SERIAL_NUMBER = "serialNumber"; +const ATTR_CERT_VALIDITY_PERIOD = "attrCertValidityPeriod"; +const ATTRIBUTES = "attributes"; +const ISSUER_UNIQUE_ID = "issuerUniqueID"; +const EXTENSIONS = "extensions"; +const CLEAR_PROPS = [ + VERSION, + HOLDER, + ISSUER, + SIGNATURE, + SERIAL_NUMBER, + ATTR_CERT_VALIDITY_PERIOD, + ATTRIBUTES, + ISSUER_UNIQUE_ID, + EXTENSIONS +]; + +export interface IAttributeCertificateInfoV2 { + version: number; + holder: Holder; + issuer: GeneralNames | V2Form; + signature: AlgorithmIdentifier; + serialNumber: asn1js.Integer; + attrCertValidityPeriod: AttCertValidityPeriod; + attributes: Attribute[]; + issuerUniqueID?: asn1js.BitString; + extensions?: Extensions; +} + +export type AttributeCertificateInfoV2Parameters = PkiObjectParameters & Partial<AttributeCertificateInfoV2>; + +export type AttributeCertificateInfoV2Schema = Schema.SchemaParameters<{ + version?: string; + holder?: HolderSchema; + issuer?: string; + signature?: AlgorithmIdentifierSchema; + serialNumber?: string; + attrCertValidityPeriod?: AttCertValidityPeriodSchema; + attributes?: string; + issuerUniqueID?: string; + extensions?: ExtensionsSchema; +}>; + +export interface AttributeCertificateInfoV2Json { + version: number; + holder: HolderJson; + issuer: GeneralNamesJson | V2FormJson; + signature: AlgorithmIdentifierJson; + serialNumber: asn1js.IntegerJson; + attrCertValidityPeriod: AttCertValidityPeriodJson; + attributes: AttributeJson[]; + issuerUniqueID?: asn1js.BitStringJson; + extensions?: ExtensionsJson; +} + +/** + * Represents the AttributeCertificateInfoV2 structure described in [RFC5755](https://datatracker.ietf.org/doc/html/rfc5755) + */ +export class AttributeCertificateInfoV2 extends PkiObject implements IAttributeCertificateInfoV2 { + + public static override CLASS_NAME = "AttributeCertificateInfoV2"; + + public version!: number; + public holder!: Holder; + public issuer!: GeneralNames | V2Form; + public signature!: AlgorithmIdentifier; + public serialNumber!: asn1js.Integer; + public attrCertValidityPeriod!: AttCertValidityPeriod; + public attributes!: Attribute[]; + public issuerUniqueID?: asn1js.BitString; + public extensions?: Extensions; + + /** + * Initializes a new instance of the {@link AttributeCertificateInfoV2} class + * @param parameters Initialization parameters + */ + constructor(parameters: AttributeCertificateInfoV2Parameters = {}) { + super(); + + this.version = pvutils.getParametersValue(parameters, VERSION, AttributeCertificateInfoV2.defaultValues(VERSION)); + this.holder = pvutils.getParametersValue(parameters, HOLDER, AttributeCertificateInfoV2.defaultValues(HOLDER)); + this.issuer = pvutils.getParametersValue(parameters, ISSUER, AttributeCertificateInfoV2.defaultValues(ISSUER)); + this.signature = pvutils.getParametersValue(parameters, SIGNATURE, AttributeCertificateInfoV2.defaultValues(SIGNATURE)); + this.serialNumber = pvutils.getParametersValue(parameters, SERIAL_NUMBER, AttributeCertificateInfoV2.defaultValues(SERIAL_NUMBER)); + this.attrCertValidityPeriod = pvutils.getParametersValue(parameters, ATTR_CERT_VALIDITY_PERIOD, AttributeCertificateInfoV2.defaultValues(ATTR_CERT_VALIDITY_PERIOD)); + this.attributes = pvutils.getParametersValue(parameters, ATTRIBUTES, AttributeCertificateInfoV2.defaultValues(ATTRIBUTES)); + if (ISSUER_UNIQUE_ID in parameters) { + this.issuerUniqueID = pvutils.getParametersValue(parameters, ISSUER_UNIQUE_ID, AttributeCertificateInfoV2.defaultValues(ISSUER_UNIQUE_ID)); + } + if (EXTENSIONS in parameters) { + this.extensions = pvutils.getParametersValue(parameters, EXTENSIONS, AttributeCertificateInfoV2.defaultValues(EXTENSIONS)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof VERSION): number; + public static override defaultValues(memberName: typeof HOLDER): Holder; + public static override defaultValues(memberName: typeof ISSUER): GeneralNames | V2Form; + public static override defaultValues(memberName: typeof SIGNATURE): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof SERIAL_NUMBER): asn1js.Integer; + public static override defaultValues(memberName: typeof ATTR_CERT_VALIDITY_PERIOD): AttCertValidityPeriod; + public static override defaultValues(memberName: typeof ATTRIBUTES): Attribute[]; + public static override defaultValues(memberName: typeof ISSUER_UNIQUE_ID): asn1js.BitString; + public static override defaultValues(memberName: typeof EXTENSIONS): Extensions; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case VERSION: + return 1; + case HOLDER: + return new Holder(); + case ISSUER: + return {}; + case SIGNATURE: + return new AlgorithmIdentifier(); + case SERIAL_NUMBER: + return new asn1js.Integer(); + case ATTR_CERT_VALIDITY_PERIOD: + return new AttCertValidityPeriod(); + case ATTRIBUTES: + return []; + case ISSUER_UNIQUE_ID: + return new asn1js.BitString(); + case EXTENSIONS: + return new Extensions(); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * AttributeCertificateInfoV2 ::= SEQUENCE { + * version AttCertVersion, -- version is v2 + * holder Holder, + * issuer AttCertIssuer, + * signature AlgorithmIdentifier, + * serialNumber CertificateSerialNumber, + * attrCertValidityPeriod AttCertValidityPeriod, + * attributes SEQUENCE OF Attribute, + * issuerUniqueID UniqueIdentifier OPTIONAL, + * extensions Extensions OPTIONAL + * } + *``` + */ + public static override schema(parameters: AttributeCertificateInfoV2Schema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Integer({ name: (names.version || EMPTY_STRING) }), + Holder.schema(names.holder || {}), + new asn1js.Choice({ + value: [ + GeneralNames.schema({ + names: { + blockName: (names.issuer || EMPTY_STRING) + } + }), + new asn1js.Constructed({ + name: (names.issuer || EMPTY_STRING), + idBlock: { + tagClass: 3, + tagNumber: 0 // [0] + }, + value: V2Form.schema().valueBlock.value + }) + ] + }), + AlgorithmIdentifier.schema(names.signature || {}), + new asn1js.Integer({ name: (names.serialNumber || EMPTY_STRING) }), + AttCertValidityPeriod.schema(names.attrCertValidityPeriod || {}), + new asn1js.Sequence({ + name: (names.attributes || EMPTY_STRING), + value: [ + new asn1js.Repeated({ + value: Attribute.schema() + }) + ] + }), + new asn1js.BitString({ + optional: true, + name: (names.issuerUniqueID || EMPTY_STRING) + }), + Extensions.schema(names.extensions || {}, true) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + AttributeCertificateInfoV2.schema({ + names: { + version: VERSION, + holder: { + names: { + blockName: HOLDER + } + }, + issuer: ISSUER, + signature: { + names: { + blockName: SIGNATURE + } + }, + serialNumber: SERIAL_NUMBER, + attrCertValidityPeriod: { + names: { + blockName: ATTR_CERT_VALIDITY_PERIOD + } + }, + attributes: ATTRIBUTES, + issuerUniqueID: ISSUER_UNIQUE_ID, + extensions: { + names: { + blockName: EXTENSIONS + } + } + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + //#region Get internal properties from parsed schema + this.version = asn1.result.version.valueBlock.valueDec; + this.holder = new Holder({ schema: asn1.result.holder }); + + switch (asn1.result.issuer.idBlock.tagClass) { + case 3: // V2Form + this.issuer = new V2Form({ + schema: new asn1js.Sequence({ + value: asn1.result.issuer.valueBlock.value + }) + }); + break; + case 1: // GeneralNames (should not be used) + default: + throw new Error("Incorrect value for 'issuer' in AttributeCertificateInfoV2"); + } + + this.signature = new AlgorithmIdentifier({ schema: asn1.result.signature }); + this.serialNumber = asn1.result.serialNumber; + this.attrCertValidityPeriod = new AttCertValidityPeriod({ schema: asn1.result.attrCertValidityPeriod }); + this.attributes = Array.from(asn1.result.attributes.valueBlock.value, element => new Attribute({ schema: element })); + + if (ISSUER_UNIQUE_ID in asn1.result) { + this.issuerUniqueID = asn1.result.issuerUniqueID; + } + + if (EXTENSIONS in asn1.result) { + this.extensions = new Extensions({ schema: asn1.result.extensions }); + } + //#endregion + } + + public toSchema(): asn1js.Sequence { + const result = new asn1js.Sequence({ + value: [ + new asn1js.Integer({ value: this.version }), + this.holder.toSchema(), + new asn1js.Constructed({ + idBlock: { + tagClass: 3, + tagNumber: 0 // [0] + }, + value: this.issuer.toSchema().valueBlock.value + }), + this.signature.toSchema(), + this.serialNumber, + this.attrCertValidityPeriod.toSchema(), + new asn1js.Sequence({ + value: Array.from(this.attributes, o => o.toSchema()) + }) + ] + }); + + if (this.issuerUniqueID) { + result.valueBlock.value.push(this.issuerUniqueID); + } + if (this.extensions) { + result.valueBlock.value.push(this.extensions.toSchema()); + } + + return result; + } + + public toJSON(): AttributeCertificateInfoV2Json { + const result: AttributeCertificateInfoV2Json = { + version: this.version, + holder: this.holder.toJSON(), + issuer: this.issuer.toJSON(), + signature: this.signature.toJSON(), + serialNumber: this.serialNumber.toJSON(), + attrCertValidityPeriod: this.attrCertValidityPeriod.toJSON(), + attributes: Array.from(this.attributes, o => o.toJSON()) + }; + + if (this.issuerUniqueID) { + result.issuerUniqueID = this.issuerUniqueID.toJSON(); + } + if (this.extensions) { + result.extensions = this.extensions.toJSON(); + } + + return result; + } + +} diff --git a/third_party/js/PKI.js/src/AttributeCertificateV2/AttributeCertificateV2.ts b/third_party/js/PKI.js/src/AttributeCertificateV2/AttributeCertificateV2.ts new file mode 100644 index 0000000000..822ce03a34 --- /dev/null +++ b/third_party/js/PKI.js/src/AttributeCertificateV2/AttributeCertificateV2.ts @@ -0,0 +1,169 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "../AlgorithmIdentifier"; +import { AttributeCertificateInfoV2, AttributeCertificateInfoV2Json, AttributeCertificateInfoV2Schema } from "./AttributeCertificateInfoV2"; +import * as Schema from "../Schema"; +import { PkiObject, PkiObjectParameters } from "../PkiObject"; +import { AsnError } from "../errors"; +import { EMPTY_STRING } from "../constants"; + +const ACINFO = "acinfo"; +const SIGNATURE_ALGORITHM = "signatureAlgorithm"; +const SIGNATURE_VALUE = "signatureValue"; +const CLEAR_PROPS = [ + ACINFO, + SIGNATURE_ALGORITHM, + SIGNATURE_VALUE, +]; + +export interface IAttributeCertificateV2 { + /** + * Attribute certificate information + */ + acinfo: AttributeCertificateInfoV2; + /** + * Signature algorithm + */ + signatureAlgorithm: AlgorithmIdentifier; + /** + * Signature value + */ + signatureValue: asn1js.BitString; +} + +export type AttributeCertificateV2Parameters = PkiObjectParameters & Partial<IAttributeCertificateV2>; + +export interface AttributeCertificateV2Json { + acinfo: AttributeCertificateInfoV2Json; + signatureAlgorithm: AlgorithmIdentifierJson; + signatureValue: asn1js.BitStringJson; +} + +/** + * Represents the AttributeCertificateV2 structure described in [RFC5755](https://datatracker.ietf.org/doc/html/rfc5755) + */ +export class AttributeCertificateV2 extends PkiObject implements IAttributeCertificateV2 { + + public static override CLASS_NAME = "AttributeCertificateV2"; + + public acinfo!: AttributeCertificateInfoV2; + public signatureAlgorithm!: AlgorithmIdentifier; + public signatureValue!: asn1js.BitString; + + /** + * Initializes a new instance of the {@link AttributeCertificateV2} class + * @param parameters Initialization parameters + */ + constructor(parameters: AttributeCertificateV2Parameters = {}) { + super(); + + this.acinfo = pvutils.getParametersValue(parameters, ACINFO, AttributeCertificateV2.defaultValues(ACINFO)); + this.signatureAlgorithm = pvutils.getParametersValue(parameters, SIGNATURE_ALGORITHM, AttributeCertificateV2.defaultValues(SIGNATURE_ALGORITHM)); + this.signatureValue = pvutils.getParametersValue(parameters, SIGNATURE_VALUE, AttributeCertificateV2.defaultValues(SIGNATURE_VALUE)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof ACINFO): AttributeCertificateInfoV2; + public static override defaultValues(memberName: typeof SIGNATURE_ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof SIGNATURE_VALUE): asn1js.BitString; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case ACINFO: + return new AttributeCertificateInfoV2(); + case SIGNATURE_ALGORITHM: + return new AlgorithmIdentifier(); + case SIGNATURE_VALUE: + return new asn1js.BitString(); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * AttributeCertificate ::= SEQUENCE { + * acinfo AttributeCertificateInfoV2, + * signatureAlgorithm AlgorithmIdentifier, + * signatureValue BIT STRING + * } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + acinfo?: AttributeCertificateInfoV2Schema; + signatureAlgorithm?: AlgorithmIdentifierSchema; + signatureValue?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + AttributeCertificateInfoV2.schema(names.acinfo || {}), + AlgorithmIdentifier.schema(names.signatureAlgorithm || {}), + new asn1js.BitString({ name: (names.signatureValue || EMPTY_STRING) }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + //#region Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + AttributeCertificateV2.schema({ + names: { + acinfo: { + names: { + blockName: ACINFO + } + }, + signatureAlgorithm: { + names: { + blockName: SIGNATURE_ALGORITHM + } + }, + signatureValue: SIGNATURE_VALUE + } + }) + ); + AsnError.assertSchema(asn1, this.className); + //#endregion + + // Get internal properties from parsed schema + this.acinfo = new AttributeCertificateInfoV2({ schema: asn1.result.acinfo }); + this.signatureAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.signatureAlgorithm }); + this.signatureValue = asn1.result.signatureValue; + } + + public toSchema(): asn1js.Sequence { + return (new asn1js.Sequence({ + value: [ + this.acinfo.toSchema(), + this.signatureAlgorithm.toSchema(), + this.signatureValue + ] + })); + } + + public toJSON(): AttributeCertificateV2Json { + return { + acinfo: this.acinfo.toJSON(), + signatureAlgorithm: this.signatureAlgorithm.toJSON(), + signatureValue: this.signatureValue.toJSON(), + }; + } + +} + diff --git a/third_party/js/PKI.js/src/AttributeCertificateV2/Holder.ts b/third_party/js/PKI.js/src/AttributeCertificateV2/Holder.ts new file mode 100644 index 0000000000..dc2bffd7f8 --- /dev/null +++ b/third_party/js/PKI.js/src/AttributeCertificateV2/Holder.ts @@ -0,0 +1,242 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { GeneralNames, GeneralNamesJson } from "../GeneralNames"; +import { IssuerSerial, IssuerSerialJson } from "../AttributeCertificateV1"; +import { ObjectDigestInfo, ObjectDigestInfoJson } from "./ObjectDigestInfo"; +import * as Schema from "../Schema"; +import { PkiObject, PkiObjectParameters } from "../PkiObject"; +import { AsnError } from "../errors"; +import { EMPTY_STRING } from "../constants"; + +const BASE_CERTIFICATE_ID = "baseCertificateID"; +const ENTITY_NAME = "entityName"; +const OBJECT_DIGEST_INFO = "objectDigestInfo"; +const CLEAR_PROPS = [ + BASE_CERTIFICATE_ID, + ENTITY_NAME, + OBJECT_DIGEST_INFO +]; + +export interface IHolder { + baseCertificateID?: IssuerSerial; + entityName?: GeneralNames; + objectDigestInfo?: ObjectDigestInfo; +} + +export type HolderParameters = PkiObjectParameters & Partial<IHolder>; + +export type HolderSchema = Schema.SchemaParameters<{ + baseCertificateID?: string; + entityName?: string; + objectDigestInfo?: string; +}>; + +export interface HolderJson { + baseCertificateID?: IssuerSerialJson; + entityName?: GeneralNamesJson; + objectDigestInfo?: ObjectDigestInfoJson; +} + +/** + * Represents the Holder structure described in [RFC5755](https://datatracker.ietf.org/doc/html/rfc5755) + */ +export class Holder extends PkiObject implements IHolder { + + public static override CLASS_NAME = "Holder"; + + public baseCertificateID?: IssuerSerial; + public entityName?: GeneralNames; + public objectDigestInfo?: ObjectDigestInfo; + + /** + * Initializes a new instance of the {@link AttributeCertificateInfoV1} class + * @param parameters Initialization parameters + */ + constructor(parameters: HolderParameters = {}) { + super(); + + if (BASE_CERTIFICATE_ID in parameters) { + this.baseCertificateID = pvutils.getParametersValue(parameters, BASE_CERTIFICATE_ID, Holder.defaultValues(BASE_CERTIFICATE_ID)); + } + if (ENTITY_NAME in parameters) { + this.entityName = pvutils.getParametersValue(parameters, ENTITY_NAME, Holder.defaultValues(ENTITY_NAME)); + } + if (OBJECT_DIGEST_INFO in parameters) { + this.objectDigestInfo = pvutils.getParametersValue(parameters, OBJECT_DIGEST_INFO, Holder.defaultValues(OBJECT_DIGEST_INFO)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof BASE_CERTIFICATE_ID): IssuerSerial; + public static override defaultValues(memberName: typeof ENTITY_NAME): GeneralNames; + public static override defaultValues(memberName: typeof OBJECT_DIGEST_INFO): ObjectDigestInfo; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case BASE_CERTIFICATE_ID: + return new IssuerSerial(); + case ENTITY_NAME: + return new GeneralNames(); + case OBJECT_DIGEST_INFO: + return new ObjectDigestInfo(); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * Holder ::= SEQUENCE { + * baseCertificateID [0] IssuerSerial OPTIONAL, + * -- the issuer and serial number of + * -- the holder's Public Key Certificate + * entityName [1] GeneralNames OPTIONAL, + * -- the name of the claimant or role + * objectDigestInfo [2] ObjectDigestInfo OPTIONAL + * -- used to directly authenticate the holder, + * -- for example, an executable + * } + *``` + */ + public static override schema(parameters: HolderSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Constructed({ + optional: true, + name: (names.baseCertificateID || EMPTY_STRING), + idBlock: { + tagClass: 3, + tagNumber: 0 // [0] + }, + value: IssuerSerial.schema().valueBlock.value + }), + new asn1js.Constructed({ + optional: true, + name: (names.entityName || EMPTY_STRING), + idBlock: { + tagClass: 3, + tagNumber: 1 // [2] + }, + value: GeneralNames.schema().valueBlock.value + }), + new asn1js.Constructed({ + optional: true, + name: (names.objectDigestInfo || EMPTY_STRING), + idBlock: { + tagClass: 3, + tagNumber: 2 // [2] + }, + value: ObjectDigestInfo.schema().valueBlock.value + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + Holder.schema({ + names: { + baseCertificateID: BASE_CERTIFICATE_ID, + entityName: ENTITY_NAME, + objectDigestInfo: OBJECT_DIGEST_INFO + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + if (BASE_CERTIFICATE_ID in asn1.result) { + this.baseCertificateID = new IssuerSerial({ + schema: new asn1js.Sequence({ + value: asn1.result.baseCertificateID.valueBlock.value + }) + }); + } + if (ENTITY_NAME in asn1.result) { + this.entityName = new GeneralNames({ + schema: new asn1js.Sequence({ + value: asn1.result.entityName.valueBlock.value + }) + }); + } + if (OBJECT_DIGEST_INFO in asn1.result) { + this.objectDigestInfo = new ObjectDigestInfo({ + schema: new asn1js.Sequence({ + value: asn1.result.objectDigestInfo.valueBlock.value + }) + }); + } + } + + public toSchema(): asn1js.Sequence { + const result = new asn1js.Sequence(); + + if (this.baseCertificateID) { + result.valueBlock.value.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, + tagNumber: 0 // [0] + }, + value: this.baseCertificateID.toSchema().valueBlock.value + })); + } + + if (this.entityName) { + result.valueBlock.value.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, + tagNumber: 1 // [1] + }, + value: this.entityName.toSchema().valueBlock.value + })); + } + + if (this.objectDigestInfo) { + result.valueBlock.value.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, + tagNumber: 2 // [2] + }, + value: this.objectDigestInfo.toSchema().valueBlock.value + })); + } + + return result; + } + + public toJSON(): HolderJson { + const result: HolderJson = {}; + + if (this.baseCertificateID) { + result.baseCertificateID = this.baseCertificateID.toJSON(); + } + + if (this.entityName) { + result.entityName = this.entityName.toJSON(); + } + + if (this.objectDigestInfo) { + result.objectDigestInfo = this.objectDigestInfo.toJSON(); + } + + return result; + } + +} diff --git a/third_party/js/PKI.js/src/AttributeCertificateV2/ObjectDigestInfo.ts b/third_party/js/PKI.js/src/AttributeCertificateV2/ObjectDigestInfo.ts new file mode 100644 index 0000000000..32ab2ff7eb --- /dev/null +++ b/third_party/js/PKI.js/src/AttributeCertificateV2/ObjectDigestInfo.ts @@ -0,0 +1,193 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "../AlgorithmIdentifier"; +import { EMPTY_STRING } from "../constants"; +import { AsnError } from "../errors"; +import { PkiObject, PkiObjectParameters } from "../PkiObject"; +import * as Schema from "../Schema"; + +const DIGESTED_OBJECT_TYPE = "digestedObjectType"; +const OTHER_OBJECT_TYPE_ID = "otherObjectTypeID"; +const DIGEST_ALGORITHM = "digestAlgorithm"; +const OBJECT_DIGEST = "objectDigest"; +const CLEAR_PROPS = [ + DIGESTED_OBJECT_TYPE, + OTHER_OBJECT_TYPE_ID, + DIGEST_ALGORITHM, + OBJECT_DIGEST, +]; + +export interface IObjectDigestInfo { + digestedObjectType: asn1js.Enumerated; + otherObjectTypeID?: asn1js.ObjectIdentifier; + digestAlgorithm: AlgorithmIdentifier; + objectDigest: asn1js.BitString; +} + +export type ObjectDigestInfoParameters = PkiObjectParameters & Partial<IObjectDigestInfo>; + +export interface ObjectDigestInfoJson { + digestedObjectType: asn1js.EnumeratedJson; + otherObjectTypeID?: asn1js.ObjectIdentifierJson; + digestAlgorithm: AlgorithmIdentifierJson; + objectDigest: asn1js.BitStringJson; +} + +/** + * Represents the ObjectDigestInfo structure described in [RFC5755](https://datatracker.ietf.org/doc/html/rfc5755) + */ +export class ObjectDigestInfo extends PkiObject implements IObjectDigestInfo { + + public static override CLASS_NAME = "ObjectDigestInfo"; + + public digestedObjectType!: asn1js.Enumerated; + public otherObjectTypeID?: asn1js.ObjectIdentifier; + public digestAlgorithm!: AlgorithmIdentifier; + public objectDigest!: asn1js.BitString; + + /** + * Initializes a new instance of the {@link ObjectDigestInfo} class + * @param parameters Initialization parameters + */ + constructor(parameters: ObjectDigestInfoParameters = {}) { + super(); + + this.digestedObjectType = pvutils.getParametersValue(parameters, DIGESTED_OBJECT_TYPE, ObjectDigestInfo.defaultValues(DIGESTED_OBJECT_TYPE)); + if (OTHER_OBJECT_TYPE_ID in parameters) { + this.otherObjectTypeID = pvutils.getParametersValue(parameters, OTHER_OBJECT_TYPE_ID, ObjectDigestInfo.defaultValues(OTHER_OBJECT_TYPE_ID)); + } + this.digestAlgorithm = pvutils.getParametersValue(parameters, DIGEST_ALGORITHM, ObjectDigestInfo.defaultValues(DIGEST_ALGORITHM)); + this.objectDigest = pvutils.getParametersValue(parameters, OBJECT_DIGEST, ObjectDigestInfo.defaultValues(OBJECT_DIGEST)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof DIGESTED_OBJECT_TYPE): asn1js.Enumerated; + public static override defaultValues(memberName: typeof OTHER_OBJECT_TYPE_ID): asn1js.ObjectIdentifier; + public static override defaultValues(memberName: typeof DIGEST_ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof OBJECT_DIGEST): asn1js.BitString; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case DIGESTED_OBJECT_TYPE: + return new asn1js.Enumerated(); + case OTHER_OBJECT_TYPE_ID: + return new asn1js.ObjectIdentifier(); + case DIGEST_ALGORITHM: + return new AlgorithmIdentifier(); + case OBJECT_DIGEST: + return new asn1js.BitString(); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * ObjectDigestInfo ::= SEQUENCE { + * digestedObjectType ENUMERATED { + * publicKey (0), + * publicKeyCert (1), + * otherObjectTypes (2) }, + * -- otherObjectTypes MUST NOT + * -- be used in this profile + * otherObjectTypeID OBJECT IDENTIFIER OPTIONAL, + * digestAlgorithm AlgorithmIdentifier, + * objectDigest BIT STRING + * } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + digestedObjectType?: string; + otherObjectTypeID?: string; + digestAlgorithm?: AlgorithmIdentifierSchema; + objectDigest?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Enumerated({ name: (names.digestedObjectType || EMPTY_STRING) }), + new asn1js.ObjectIdentifier({ + optional: true, + name: (names.otherObjectTypeID || EMPTY_STRING) + }), + AlgorithmIdentifier.schema(names.digestAlgorithm || {}), + new asn1js.BitString({ name: (names.objectDigest || EMPTY_STRING) }), + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + ObjectDigestInfo.schema({ + names: { + digestedObjectType: DIGESTED_OBJECT_TYPE, + otherObjectTypeID: OTHER_OBJECT_TYPE_ID, + digestAlgorithm: { + names: { + blockName: DIGEST_ALGORITHM + } + }, + objectDigest: OBJECT_DIGEST + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + //#region Get internal properties from parsed schema + this.digestedObjectType = asn1.result.digestedObjectType; + + if (OTHER_OBJECT_TYPE_ID in asn1.result) { + this.otherObjectTypeID = asn1.result.otherObjectTypeID; + } + + this.digestAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.digestAlgorithm }); + this.objectDigest = asn1.result.objectDigest; + //#endregion + } + + public toSchema(): asn1js.Sequence { + const result = new asn1js.Sequence({ + value: [this.digestedObjectType] + }); + + if (this.otherObjectTypeID) { + result.valueBlock.value.push(this.otherObjectTypeID); + } + + result.valueBlock.value.push(this.digestAlgorithm.toSchema()); + result.valueBlock.value.push(this.objectDigest); + + return result; + } + + public toJSON(): ObjectDigestInfoJson { + const result: ObjectDigestInfoJson = { + digestedObjectType: this.digestedObjectType.toJSON(), + digestAlgorithm: this.digestAlgorithm.toJSON(), + objectDigest: this.objectDigest.toJSON(), + }; + + if (this.otherObjectTypeID) { + result.otherObjectTypeID = this.otherObjectTypeID.toJSON(); + } + + return result; + } + +} diff --git a/third_party/js/PKI.js/src/AttributeCertificateV2/V2Form.ts b/third_party/js/PKI.js/src/AttributeCertificateV2/V2Form.ts new file mode 100644 index 0000000000..dd8b69d51b --- /dev/null +++ b/third_party/js/PKI.js/src/AttributeCertificateV2/V2Form.ts @@ -0,0 +1,223 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { GeneralNames, GeneralNamesJson } from "../GeneralNames"; +import { IssuerSerial, IssuerSerialJson } from "../AttributeCertificateV1"; +import { ObjectDigestInfo, ObjectDigestInfoJson } from "./ObjectDigestInfo"; +import * as Schema from "../Schema"; +import { PkiObject, PkiObjectParameters } from "../PkiObject"; +import { AsnError } from "../errors"; +import { EMPTY_STRING } from "../constants"; + +const ISSUER_NAME = "issuerName"; +const BASE_CERTIFICATE_ID = "baseCertificateID"; +const OBJECT_DIGEST_INFO = "objectDigestInfo"; +const CLEAR_PROPS = [ + ISSUER_NAME, + BASE_CERTIFICATE_ID, + OBJECT_DIGEST_INFO +]; + +export interface IV2Form { + issuerName?: GeneralNames; + baseCertificateID?: IssuerSerial; + objectDigestInfo?: ObjectDigestInfo; +} + +export type V2FormParameters = PkiObjectParameters & Partial<IV2Form>; + +export interface V2FormJson { + issuerName?: GeneralNamesJson; + baseCertificateID?: IssuerSerialJson; + objectDigestInfo?: ObjectDigestInfoJson; +} + +/** + * Represents the V2Form structure described in [RFC5755](https://datatracker.ietf.org/doc/html/rfc5755) + */ +export class V2Form extends PkiObject implements IV2Form { + + public static override CLASS_NAME = "V2Form"; + + public issuerName?: GeneralNames; + public baseCertificateID?: IssuerSerial; + public objectDigestInfo?: ObjectDigestInfo; + + /** + * Initializes a new instance of the {@link V2Form} class + * @param parameters Initialization parameters + */ + constructor(parameters: V2FormParameters = {}) { + super(); + + if (ISSUER_NAME in parameters) { + this.issuerName = pvutils.getParametersValue(parameters, ISSUER_NAME, V2Form.defaultValues(ISSUER_NAME)); + } + if (BASE_CERTIFICATE_ID in parameters) { + this.baseCertificateID = pvutils.getParametersValue(parameters, BASE_CERTIFICATE_ID, V2Form.defaultValues(BASE_CERTIFICATE_ID)); + } + if (OBJECT_DIGEST_INFO in parameters) { + this.objectDigestInfo = pvutils.getParametersValue(parameters, OBJECT_DIGEST_INFO, V2Form.defaultValues(OBJECT_DIGEST_INFO)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof ISSUER_NAME): GeneralNames; + public static override defaultValues(memberName: typeof BASE_CERTIFICATE_ID): IssuerSerial; + public static override defaultValues(memberName: typeof OBJECT_DIGEST_INFO): ObjectDigestInfo; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case ISSUER_NAME: + return new GeneralNames(); + case BASE_CERTIFICATE_ID: + return new IssuerSerial(); + case OBJECT_DIGEST_INFO: + return new ObjectDigestInfo(); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * V2Form ::= SEQUENCE { + * issuerName GeneralNames OPTIONAL, + * baseCertificateID [0] IssuerSerial OPTIONAL, + * objectDigestInfo [1] ObjectDigestInfo OPTIONAL + * -- issuerName MUST be present in this profile + * -- baseCertificateID and objectDigestInfo MUST NOT + * -- be present in this profile + * } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + issuerName?: string; + baseCertificateID?: string; + objectDigestInfo?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + GeneralNames.schema({ + names: { + blockName: names.issuerName + } + }, true), + new asn1js.Constructed({ + optional: true, + name: (names.baseCertificateID || EMPTY_STRING), + idBlock: { + tagClass: 3, + tagNumber: 0 // [0] + }, + value: IssuerSerial.schema().valueBlock.value + }), + new asn1js.Constructed({ + optional: true, + name: (names.objectDigestInfo || EMPTY_STRING), + idBlock: { + tagClass: 3, + tagNumber: 1 // [1] + }, + value: ObjectDigestInfo.schema().valueBlock.value + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + V2Form.schema({ + names: { + issuerName: ISSUER_NAME, + baseCertificateID: BASE_CERTIFICATE_ID, + objectDigestInfo: OBJECT_DIGEST_INFO + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + //#region Get internal properties from parsed schema + if (ISSUER_NAME in asn1.result) + this.issuerName = new GeneralNames({ schema: asn1.result.issuerName }); + + if (BASE_CERTIFICATE_ID in asn1.result) { + this.baseCertificateID = new IssuerSerial({ + schema: new asn1js.Sequence({ + value: asn1.result.baseCertificateID.valueBlock.value + }) + }); + } + + if (OBJECT_DIGEST_INFO in asn1.result) { + this.objectDigestInfo = new ObjectDigestInfo({ + schema: new asn1js.Sequence({ + value: asn1.result.objectDigestInfo.valueBlock.value + }) + }); + } + //#endregion + } + + public toSchema(): asn1js.Sequence { + const result = new asn1js.Sequence(); + + if (this.issuerName) + result.valueBlock.value.push(this.issuerName.toSchema()); + + if (this.baseCertificateID) { + result.valueBlock.value.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, + tagNumber: 0 // [0] + }, + value: this.baseCertificateID.toSchema().valueBlock.value + })); + } + + if (this.objectDigestInfo) { + result.valueBlock.value.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, + tagNumber: 1 // [1] + }, + value: this.objectDigestInfo.toSchema().valueBlock.value + })); + } + + return result; + } + + public toJSON(): V2FormJson { + const result: V2FormJson = {}; + + if (this.issuerName) { + result.issuerName = this.issuerName.toJSON(); + } + if (this.baseCertificateID) { + result.baseCertificateID = this.baseCertificateID.toJSON(); + } + if (this.objectDigestInfo) { + result.objectDigestInfo = this.objectDigestInfo.toJSON(); + } + + return result; + } + +} diff --git a/third_party/js/PKI.js/src/AttributeCertificateV2/index.ts b/third_party/js/PKI.js/src/AttributeCertificateV2/index.ts new file mode 100644 index 0000000000..53ca2f7ba7 --- /dev/null +++ b/third_party/js/PKI.js/src/AttributeCertificateV2/index.ts @@ -0,0 +1,5 @@ +export * from "./AttributeCertificateInfoV2"; +export * from "./Holder"; +export * from "./ObjectDigestInfo"; +export * from "./V2Form"; +export * from "./AttributeCertificateV2";
\ No newline at end of file diff --git a/third_party/js/PKI.js/src/AttributeTypeAndValue.ts b/third_party/js/PKI.js/src/AttributeTypeAndValue.ts new file mode 100644 index 0000000000..786b39f9ca --- /dev/null +++ b/third_party/js/PKI.js/src/AttributeTypeAndValue.ts @@ -0,0 +1,225 @@ +import * as asn1js from "asn1js"; +import * as pvtsutils from "pvtsutils"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { stringPrep } from "./Helpers"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const TYPE = "type"; +const VALUE = "value"; + +export interface IAttributeTypeAndValue { + type: string; + value: AttributeValueType; +} + +export type AttributeTypeAndValueParameters = PkiObjectParameters & Partial<IAttributeTypeAndValue>; + +export type AttributeValueType = asn1js.Utf8String + | asn1js.BmpString + | asn1js.UniversalString + | asn1js.NumericString + | asn1js.PrintableString + | asn1js.TeletexString + | asn1js.VideotexString + | asn1js.IA5String + | asn1js.GraphicString + | asn1js.VisibleString + | asn1js.GeneralString + | asn1js.CharacterString; + +export interface AttributeTypeAndValueJson { + type: string; + value: any; +} + +/** + * Represents the AttributeTypeAndValue structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class AttributeTypeAndValue extends PkiObject implements IAttributeTypeAndValue { + + public static override CLASS_NAME = "AttributeTypeAndValue"; + + public type!: string; + public value!: AttributeValueType; + + /** + * Initializes a new instance of the {@link AttributeTypeAndValue} class + * @param parameters Initialization parameters + */ + constructor(parameters: AttributeTypeAndValueParameters = {}) { + super(); + + this.type = pvutils.getParametersValue(parameters, TYPE, AttributeTypeAndValue.defaultValues(TYPE)); + this.value = pvutils.getParametersValue(parameters, VALUE, AttributeTypeAndValue.defaultValues(VALUE)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof TYPE): string; + public static override defaultValues(memberName: typeof VALUE): AttributeValueType; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case TYPE: + return EMPTY_STRING; + case VALUE: + return {}; + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * AttributeTypeAndValue ::= Sequence { + * type AttributeType, + * value AttributeValue } + * + * AttributeType ::= OBJECT IDENTIFIER + * + * AttributeValue ::= ANY -- DEFINED BY AttributeType + *``` + */ + static override schema(parameters: Schema.SchemaParameters<{ type?: string, value?: string; }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.ObjectIdentifier({ name: (names.type || EMPTY_STRING) }), + new asn1js.Any({ name: (names.value || EMPTY_STRING) }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType) { + //#region Clear input data first + pvutils.clearProps(schema, [ + TYPE, + "typeValue" + ]); + //#endregion + + //#region Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + AttributeTypeAndValue.schema({ + names: { + type: TYPE, + value: "typeValue" + } + }) + ); + + AsnError.assertSchema(asn1, this.className); + //#endregion + + //#region Get internal properties from parsed schema + this.type = asn1.result.type.valueBlock.toString(); + this.value = asn1.result.typeValue; + //#endregion + } + + public toSchema(): asn1js.Sequence { + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: [ + new asn1js.ObjectIdentifier({ value: this.type }), + this.value + ] + })); + //#endregion + } + + public toJSON(): AttributeTypeAndValueJson { + const _object = { + type: this.type + } as AttributeTypeAndValueJson; + + if (Object.keys(this.value).length !== 0) { + _object.value = (this.value).toJSON(); + } else { + _object.value = this.value; + } + + return _object; + } + + /** + * Compares two AttributeTypeAndValue values, or AttributeTypeAndValue with ArrayBuffer value + * @param compareTo The value compare to current + */ + public isEqual(compareTo: AttributeTypeAndValue | ArrayBuffer): boolean { + const stringBlockNames = [ + asn1js.Utf8String.blockName(), + asn1js.BmpString.blockName(), + asn1js.UniversalString.blockName(), + asn1js.NumericString.blockName(), + asn1js.PrintableString.blockName(), + asn1js.TeletexString.blockName(), + asn1js.VideotexString.blockName(), + asn1js.IA5String.blockName(), + asn1js.GraphicString.blockName(), + asn1js.VisibleString.blockName(), + asn1js.GeneralString.blockName(), + asn1js.CharacterString.blockName() + ]; + + if (compareTo instanceof ArrayBuffer) { + return pvtsutils.BufferSourceConverter.isEqual(this.value.valueBeforeDecodeView, compareTo); + } + + if ((compareTo.constructor as typeof AttributeTypeAndValue).blockName() === AttributeTypeAndValue.blockName()) { + if (this.type !== compareTo.type) + return false; + + //#region Check we do have both strings + const isStringPair = [false, false]; + const thisName = (this.value.constructor as typeof asn1js.BaseBlock).blockName(); + for (const name of stringBlockNames) { + if (thisName === name) { + isStringPair[0] = true; + } + if ((compareTo.value.constructor as typeof asn1js.BaseBlock).blockName() === name) { + isStringPair[1] = true; + } + } + + if (isStringPair[0] !== isStringPair[1]) { + return false; + } + + const isString = (isStringPair[0] && isStringPair[1]); + //#endregion + + if (isString) { + const value1 = stringPrep(this.value.valueBlock.value); + const value2 = stringPrep(compareTo.value.valueBlock.value); + + if (value1.localeCompare(value2) !== 0) + return false; + } + else // Comparing as two ArrayBuffers + { + if (!pvtsutils.BufferSourceConverter.isEqual(this.value.valueBeforeDecodeView, compareTo.value.valueBeforeDecodeView)) + return false; + } + + return true; + } + + return false; + } + +} diff --git a/third_party/js/PKI.js/src/AuthenticatedSafe.ts b/third_party/js/PKI.js/src/AuthenticatedSafe.ts new file mode 100644 index 0000000000..ea85afbd38 --- /dev/null +++ b/third_party/js/PKI.js/src/AuthenticatedSafe.ts @@ -0,0 +1,379 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { ContentInfo, ContentInfoJson } from "./ContentInfo"; +import { SafeContents } from "./SafeContents"; +import { EnvelopedData } from "./EnvelopedData"; +import { EncryptedData } from "./EncryptedData"; +import * as Schema from "./Schema"; +import { id_ContentType_Data, id_ContentType_EncryptedData, id_ContentType_EnvelopedData } from "./ObjectIdentifiers"; +import { ArgumentError, AsnError, ParameterError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { EMPTY_STRING } from "./constants"; +import * as common from "./common"; + +const SAFE_CONTENTS = "safeContents"; +const PARSED_VALUE = "parsedValue"; +const CONTENT_INFOS = "contentInfos"; + +export interface IAuthenticatedSafe { + safeContents: ContentInfo[]; + parsedValue: any; +} + +export type AuthenticatedSafeParameters = PkiObjectParameters & Partial<IAuthenticatedSafe>; + +export interface AuthenticatedSafeJson { + safeContents: ContentInfoJson[]; +} + +export type SafeContent = ContentInfo | EncryptedData | EnvelopedData | object; + +/** + * Represents the AuthenticatedSafe structure described in [RFC7292](https://datatracker.ietf.org/doc/html/rfc7292) + */ +export class AuthenticatedSafe extends PkiObject implements IAuthenticatedSafe { + + public static override CLASS_NAME = "AuthenticatedSafe"; + + public safeContents!: ContentInfo[]; + public parsedValue: any; + + /** + * Initializes a new instance of the {@link AuthenticatedSafe} class + * @param parameters Initialization parameters + */ + constructor(parameters: AuthenticatedSafeParameters = {}) { + super(); + + this.safeContents = pvutils.getParametersValue(parameters, SAFE_CONTENTS, AuthenticatedSafe.defaultValues(SAFE_CONTENTS)); + if (PARSED_VALUE in parameters) { + this.parsedValue = pvutils.getParametersValue(parameters, PARSED_VALUE, AuthenticatedSafe.defaultValues(PARSED_VALUE)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof SAFE_CONTENTS): ContentInfo[]; + public static override defaultValues(memberName: typeof PARSED_VALUE): any; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case SAFE_CONTENTS: + return []; + case PARSED_VALUE: + return {}; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case SAFE_CONTENTS: + return (memberValue.length === 0); + case PARSED_VALUE: + return ((memberValue instanceof Object) && (Object.keys(memberValue).length === 0)); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * AuthenticatedSafe ::= SEQUENCE OF ContentInfo + * -- Data if unencrypted + * -- EncryptedData if password-encrypted + * -- EnvelopedData if public key-encrypted + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + contentInfos?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Repeated({ + name: (names.contentInfos || EMPTY_STRING), + value: ContentInfo.schema() + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, [ + CONTENT_INFOS + ]); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + AuthenticatedSafe.schema({ + names: { + contentInfos: CONTENT_INFOS + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.safeContents = Array.from(asn1.result.contentInfos, element => new ContentInfo({ schema: element })); + } + + public toSchema(): asn1js.Sequence { + return (new asn1js.Sequence({ + value: Array.from(this.safeContents, o => o.toSchema()) + })); + } + + public toJSON(): AuthenticatedSafeJson { + return { + safeContents: Array.from(this.safeContents, o => o.toJSON()) + }; + } + + public async parseInternalValues(parameters: { safeContents: SafeContent[]; }, crypto = common.getCrypto(true)): Promise<void> { + //#region Check input data from "parameters" + ParameterError.assert(parameters, SAFE_CONTENTS); + ArgumentError.assert(parameters.safeContents, SAFE_CONTENTS, "Array"); + if (parameters.safeContents.length !== this.safeContents.length) { + throw new ArgumentError("Length of \"parameters.safeContents\" must be equal to \"this.safeContents.length\""); + } + //#endregion + + //#region Create value for "this.parsedValue.authenticatedSafe" + this.parsedValue = { + safeContents: [] as any[], + }; + + for (const [index, content] of this.safeContents.entries()) { + const safeContent = parameters.safeContents[index]; + const errorTarget = `parameters.safeContents[${index}]`; + switch (content.contentType) { + //#region data + case id_ContentType_Data: + { + // Check that we do have OCTET STRING as "content" + ArgumentError.assert(content.content, "this.safeContents[j].content", asn1js.OctetString); + + //#region Check we have "constructive encoding" for AuthSafe content + const authSafeContent = content.content.getValue(); + //#endregion + + //#region Finally initialize initial values of SAFE_CONTENTS type + this.parsedValue.safeContents.push({ + privacyMode: 0, // No privacy, clear data + value: SafeContents.fromBER(authSafeContent) + }); + //#endregion + } + break; + //#endregion + //#region envelopedData + case id_ContentType_EnvelopedData: + { + //#region Initial variables + const cmsEnveloped = new EnvelopedData({ schema: content.content }); + //#endregion + + //#region Check mandatory parameters + ParameterError.assert(errorTarget, safeContent, "recipientCertificate", "recipientKey"); + const envelopedData = safeContent as any; + const recipientCertificate = envelopedData.recipientCertificate; + const recipientKey = envelopedData.recipientKey; + //#endregion + + //#region Decrypt CMS EnvelopedData using first recipient information + const decrypted = await cmsEnveloped.decrypt(0, { + recipientCertificate, + recipientPrivateKey: recipientKey + }, crypto); + + this.parsedValue.safeContents.push({ + privacyMode: 2, // Public-key privacy mode + value: SafeContents.fromBER(decrypted), + }); + //#endregion + } + break; + //#endregion + //#region encryptedData + case id_ContentType_EncryptedData: + { + //#region Initial variables + const cmsEncrypted = new EncryptedData({ schema: content.content }); + //#endregion + + //#region Check mandatory parameters + ParameterError.assert(errorTarget, safeContent, "password"); + + const password = (safeContent as any).password; + //#endregion + + //#region Decrypt CMS EncryptedData using password + const decrypted = await cmsEncrypted.decrypt({ + password + }, crypto); + //#endregion + + //#region Initialize internal data + this.parsedValue.safeContents.push({ + privacyMode: 1, // Password-based privacy mode + value: SafeContents.fromBER(decrypted), + }); + //#endregion + } + break; + //#endregion + //#region default + default: + throw new Error(`Unknown "contentType" for AuthenticatedSafe: " ${content.contentType}`); + //#endregion + } + } + //#endregion + } + public async makeInternalValues(parameters: { + safeContents: any[]; + }, crypto = common.getCrypto(true)): Promise<this> { + //#region Check data in PARSED_VALUE + if (!(this.parsedValue)) { + throw new Error("Please run \"parseValues\" first or add \"parsedValue\" manually"); + } + ArgumentError.assert(this.parsedValue, "this.parsedValue", "object"); + ArgumentError.assert(this.parsedValue.safeContents, "this.parsedValue.safeContents", "Array"); + + //#region Check input data from "parameters" + ArgumentError.assert(parameters, "parameters", "object"); + ParameterError.assert(parameters, "safeContents"); + ArgumentError.assert(parameters.safeContents, "parameters.safeContents", "Array"); + if (parameters.safeContents.length !== this.parsedValue.safeContents.length) { + throw new ArgumentError("Length of \"parameters.safeContents\" must be equal to \"this.parsedValue.safeContents\""); + } + //#endregion + + //#region Create internal values from already parsed values + this.safeContents = []; + + for (const [index, content] of this.parsedValue.safeContents.entries()) { + //#region Check current "content" value + ParameterError.assert("content", content, "privacyMode", "value"); + ArgumentError.assert(content.value, "content.value", SafeContents); + //#endregion + + switch (content.privacyMode) { + //#region No privacy + case 0: + { + const contentBuffer = content.value.toSchema().toBER(false); + + this.safeContents.push(new ContentInfo({ + contentType: "1.2.840.113549.1.7.1", + content: new asn1js.OctetString({ valueHex: contentBuffer }) + })); + } + break; + //#endregion + //#region Privacy with password + case 1: + { + //#region Initial variables + const cmsEncrypted = new EncryptedData(); + + const currentParameters = parameters.safeContents[index]; + currentParameters.contentToEncrypt = content.value.toSchema().toBER(false); + //#endregion + + //#region Encrypt CMS EncryptedData using password + await cmsEncrypted.encrypt(currentParameters); + //#endregion + + //#region Store result content in CMS_CONTENT_INFO type + this.safeContents.push(new ContentInfo({ + contentType: "1.2.840.113549.1.7.6", + content: cmsEncrypted.toSchema() + })); + //#endregion + } + break; + //#endregion + //#region Privacy with public key + case 2: + { + //#region Initial variables + const cmsEnveloped = new EnvelopedData(); + const contentToEncrypt = content.value.toSchema().toBER(false); + const safeContent = parameters.safeContents[index]; + //#endregion + + //#region Check mandatory parameters + ParameterError.assert(`parameters.safeContents[${index}]`, safeContent, "encryptingCertificate", "encryptionAlgorithm"); + + switch (true) { + case (safeContent.encryptionAlgorithm.name.toLowerCase() === "aes-cbc"): + case (safeContent.encryptionAlgorithm.name.toLowerCase() === "aes-gcm"): + break; + default: + throw new Error(`Incorrect parameter "encryptionAlgorithm" in "parameters.safeContents[i]": ${safeContent.encryptionAlgorithm}`); + } + + switch (true) { + case (safeContent.encryptionAlgorithm.length === 128): + case (safeContent.encryptionAlgorithm.length === 192): + case (safeContent.encryptionAlgorithm.length === 256): + break; + default: + throw new Error(`Incorrect parameter "encryptionAlgorithm.length" in "parameters.safeContents[i]": ${safeContent.encryptionAlgorithm.length}`); + } + //#endregion + + //#region Making correct "encryptionAlgorithm" variable + const encryptionAlgorithm = safeContent.encryptionAlgorithm; + //#endregion + + //#region Append recipient for enveloped data + cmsEnveloped.addRecipientByCertificate(safeContent.encryptingCertificate, {}, undefined, crypto); + //#endregion + + //#region Making encryption + await cmsEnveloped.encrypt(encryptionAlgorithm, contentToEncrypt, crypto); + + this.safeContents.push(new ContentInfo({ + contentType: "1.2.840.113549.1.7.3", + content: cmsEnveloped.toSchema() + })); + //#endregion + } + break; + //#endregion + //#region default + default: + throw new Error(`Incorrect value for "content.privacyMode": ${content.privacyMode}`); + //#endregion + } + } + //#endregion + + //#region Return result of the function + return this; + //#endregion + } + +} + diff --git a/third_party/js/PKI.js/src/AuthorityKeyIdentifier.ts b/third_party/js/PKI.js/src/AuthorityKeyIdentifier.ts new file mode 100644 index 0000000000..8cb6c6e902 --- /dev/null +++ b/third_party/js/PKI.js/src/AuthorityKeyIdentifier.ts @@ -0,0 +1,231 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { GeneralName, GeneralNameJson } from "./GeneralName"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const KEY_IDENTIFIER = "keyIdentifier"; +const AUTHORITY_CERT_ISSUER = "authorityCertIssuer"; +const AUTHORITY_CERT_SERIAL_NUMBER = "authorityCertSerialNumber"; +const CLEAR_PROPS = [ + KEY_IDENTIFIER, + AUTHORITY_CERT_ISSUER, + AUTHORITY_CERT_SERIAL_NUMBER, +]; + +export interface IAuthorityKeyIdentifier { + keyIdentifier?: asn1js.OctetString; + authorityCertIssuer?: GeneralName[]; + authorityCertSerialNumber?: asn1js.Integer; +} + +export type AuthorityKeyIdentifierParameters = PkiObjectParameters & Partial<IAuthorityKeyIdentifier>; + +export interface AuthorityKeyIdentifierJson { + keyIdentifier?: asn1js.OctetStringJson; + authorityCertIssuer?: GeneralNameJson[]; + authorityCertSerialNumber?: asn1js.IntegerJson; +} + +/** + * Represents the AuthorityKeyIdentifier structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class AuthorityKeyIdentifier extends PkiObject implements IAuthorityKeyIdentifier { + + public static override CLASS_NAME = "AuthorityKeyIdentifier"; + + public keyIdentifier?: asn1js.OctetString; + public authorityCertIssuer?: GeneralName[]; + public authorityCertSerialNumber?: asn1js.Integer; + + /** + * Initializes a new instance of the {@link AuthorityKeyIdentifier} class + * @param parameters Initialization parameters + */ + constructor(parameters: AuthorityKeyIdentifierParameters = {}) { + super(); + + if (KEY_IDENTIFIER in parameters) { + this.keyIdentifier = pvutils.getParametersValue(parameters, KEY_IDENTIFIER, AuthorityKeyIdentifier.defaultValues(KEY_IDENTIFIER)); + } + if (AUTHORITY_CERT_ISSUER in parameters) { + this.authorityCertIssuer = pvutils.getParametersValue(parameters, AUTHORITY_CERT_ISSUER, AuthorityKeyIdentifier.defaultValues(AUTHORITY_CERT_ISSUER)); + } + if (AUTHORITY_CERT_SERIAL_NUMBER in parameters) { + this.authorityCertSerialNumber = pvutils.getParametersValue(parameters, AUTHORITY_CERT_SERIAL_NUMBER, AuthorityKeyIdentifier.defaultValues(AUTHORITY_CERT_SERIAL_NUMBER)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof KEY_IDENTIFIER): asn1js.OctetString; + public static override defaultValues(memberName: typeof AUTHORITY_CERT_ISSUER): GeneralName[]; + public static override defaultValues(memberName: typeof AUTHORITY_CERT_SERIAL_NUMBER): asn1js.Integer; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case KEY_IDENTIFIER: + return new asn1js.OctetString(); + case AUTHORITY_CERT_ISSUER: + return []; + case AUTHORITY_CERT_SERIAL_NUMBER: + return new asn1js.Integer(); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * AuthorityKeyIdentifier OID ::= 2.5.29.35 + * + * AuthorityKeyIdentifier ::= SEQUENCE { + * keyIdentifier [0] KeyIdentifier OPTIONAL, + * authorityCertIssuer [1] GeneralNames OPTIONAL, + * authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL } + * + * KeyIdentifier ::= OCTET STRING + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + keyIdentifier?: string; + authorityCertIssuer?: string; + authorityCertSerialNumber?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Primitive({ + name: (names.keyIdentifier || EMPTY_STRING), + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + } + }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [ + new asn1js.Repeated({ + name: (names.authorityCertIssuer || EMPTY_STRING), + value: GeneralName.schema() + }) + ] + }), + new asn1js.Primitive({ + name: (names.authorityCertSerialNumber || EMPTY_STRING), + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 2 // [2] + } + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + AuthorityKeyIdentifier.schema({ + names: { + keyIdentifier: KEY_IDENTIFIER, + authorityCertIssuer: AUTHORITY_CERT_ISSUER, + authorityCertSerialNumber: AUTHORITY_CERT_SERIAL_NUMBER + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + if (KEY_IDENTIFIER in asn1.result) + this.keyIdentifier = new asn1js.OctetString({ valueHex: asn1.result.keyIdentifier.valueBlock.valueHex }); + + if (AUTHORITY_CERT_ISSUER in asn1.result) + this.authorityCertIssuer = Array.from(asn1.result.authorityCertIssuer, o => new GeneralName({ schema: o })); + + if (AUTHORITY_CERT_SERIAL_NUMBER in asn1.result) + this.authorityCertSerialNumber = new asn1js.Integer({ valueHex: asn1.result.authorityCertSerialNumber.valueBlock.valueHex }); + //#endregion + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray = []; + + if (this.keyIdentifier) { + outputArray.push(new asn1js.Primitive({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + valueHex: this.keyIdentifier.valueBlock.valueHexView + })); + } + + if (this.authorityCertIssuer) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: Array.from(this.authorityCertIssuer, o => o.toSchema()) + })); + } + + if (this.authorityCertSerialNumber) { + outputArray.push(new asn1js.Primitive({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 2 // [2] + }, + valueHex: this.authorityCertSerialNumber.valueBlock.valueHexView + })); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): AuthorityKeyIdentifierJson { + const object: AuthorityKeyIdentifierJson = {}; + + if (this.keyIdentifier) { + object.keyIdentifier = this.keyIdentifier.toJSON(); + } + if (this.authorityCertIssuer) { + object.authorityCertIssuer = Array.from(this.authorityCertIssuer, o => o.toJSON()); + } + if (this.authorityCertSerialNumber) { + object.authorityCertSerialNumber = this.authorityCertSerialNumber.toJSON(); + } + + return object; + } + +} + diff --git a/third_party/js/PKI.js/src/BasicConstraints.ts b/third_party/js/PKI.js/src/BasicConstraints.ts new file mode 100644 index 0000000000..928ae52fb1 --- /dev/null +++ b/third_party/js/PKI.js/src/BasicConstraints.ts @@ -0,0 +1,172 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const PATH_LENGTH_CONSTRAINT = "pathLenConstraint"; +const CA = "cA"; + +export interface IBasicConstraints { + cA: boolean; + pathLenConstraint?: number | asn1js.Integer; +} + +export type BasicConstraintsParameters = PkiObjectParameters & Partial<IBasicConstraints>; + +export interface BasicConstraintsJson { + cA?: boolean; + pathLenConstraint?: asn1js.IntegerJson | number; +} + +/** + * Represents the BasicConstraints structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class BasicConstraints extends PkiObject implements IBasicConstraints { + + public static override CLASS_NAME = "BasicConstraints"; + + public cA!: boolean; + public pathLenConstraint?: number | asn1js.Integer; + + /** + * Initializes a new instance of the {@link AuthorityKeyIdentifier} class + * @param parameters Initialization parameters + */ + constructor(parameters: BasicConstraintsParameters = {}) { + super(); + + this.cA = pvutils.getParametersValue(parameters, CA, false); + if (PATH_LENGTH_CONSTRAINT in parameters) { + this.pathLenConstraint = pvutils.getParametersValue(parameters, PATH_LENGTH_CONSTRAINT, 0); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof CA): boolean; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case CA: + return false; + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * BasicConstraints ::= SEQUENCE { + * cA BOOLEAN DEFAULT FALSE, + * pathLenConstraint INTEGER (0..MAX) OPTIONAL } + *``` + */ + static override schema(parameters: Schema.SchemaParameters<{ cA?: string; pathLenConstraint?: string; }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Boolean({ + optional: true, + name: (names.cA || EMPTY_STRING) + }), + new asn1js.Integer({ + optional: true, + name: (names.pathLenConstraint || EMPTY_STRING) + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, [ + CA, + PATH_LENGTH_CONSTRAINT + ]); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + BasicConstraints.schema({ + names: { + cA: CA, + pathLenConstraint: PATH_LENGTH_CONSTRAINT + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + //#region Get internal properties from parsed schema + if (CA in asn1.result) { + this.cA = asn1.result.cA.valueBlock.value; + } + + if (PATH_LENGTH_CONSTRAINT in asn1.result) { + if (asn1.result.pathLenConstraint.valueBlock.isHexOnly) { + this.pathLenConstraint = asn1.result.pathLenConstraint; + } else { + this.pathLenConstraint = asn1.result.pathLenConstraint.valueBlock.valueDec; + } + } + //#endregion + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray = []; + + if (this.cA !== BasicConstraints.defaultValues(CA)) + outputArray.push(new asn1js.Boolean({ value: this.cA })); + + if (PATH_LENGTH_CONSTRAINT in this) { + if (this.pathLenConstraint instanceof asn1js.Integer) { + outputArray.push(this.pathLenConstraint); + } else { + outputArray.push(new asn1js.Integer({ value: this.pathLenConstraint })); + } + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + /** + * Conversion for the class to JSON object + * @returns + */ + public toJSON(): BasicConstraintsJson { + const object: BasicConstraintsJson = {}; + + if (this.cA !== BasicConstraints.defaultValues(CA)) { + object.cA = this.cA; + } + + if (PATH_LENGTH_CONSTRAINT in this) { + if (this.pathLenConstraint instanceof asn1js.Integer) { + object.pathLenConstraint = this.pathLenConstraint.toJSON(); + } else { + object.pathLenConstraint = this.pathLenConstraint; + } + } + + return object; + } + +} + diff --git a/third_party/js/PKI.js/src/BasicOCSPResponse.ts b/third_party/js/PKI.js/src/BasicOCSPResponse.ts new file mode 100644 index 0000000000..46efec0108 --- /dev/null +++ b/third_party/js/PKI.js/src/BasicOCSPResponse.ts @@ -0,0 +1,442 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import * as common from "./common"; +import { ResponseData, ResponseDataJson, ResponseDataSchema } from "./ResponseData"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier"; +import { Certificate, CertificateJson, CertificateSchema, checkCA } from "./Certificate"; +import { CertID } from "./CertID"; +import { RelativeDistinguishedNames } from "./RelativeDistinguishedNames"; +import { CertificateChainValidationEngine } from "./CertificateChainValidationEngine"; +import * as Schema from "./Schema"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { AsnError } from "./errors"; +import { EMPTY_STRING } from "./constants"; + +const TBS_RESPONSE_DATA = "tbsResponseData"; +const SIGNATURE_ALGORITHM = "signatureAlgorithm"; +const SIGNATURE = "signature"; +const CERTS = "certs"; +const BASIC_OCSP_RESPONSE = "BasicOCSPResponse"; +const BASIC_OCSP_RESPONSE_TBS_RESPONSE_DATA = `${BASIC_OCSP_RESPONSE}.${TBS_RESPONSE_DATA}`; +const BASIC_OCSP_RESPONSE_SIGNATURE_ALGORITHM = `${BASIC_OCSP_RESPONSE}.${SIGNATURE_ALGORITHM}`; +const BASIC_OCSP_RESPONSE_SIGNATURE = `${BASIC_OCSP_RESPONSE}.${SIGNATURE}`; +const BASIC_OCSP_RESPONSE_CERTS = `${BASIC_OCSP_RESPONSE}.${CERTS}`; +const CLEAR_PROPS = [ + BASIC_OCSP_RESPONSE_TBS_RESPONSE_DATA, + BASIC_OCSP_RESPONSE_SIGNATURE_ALGORITHM, + BASIC_OCSP_RESPONSE_SIGNATURE, + BASIC_OCSP_RESPONSE_CERTS +]; + +export interface IBasicOCSPResponse { + tbsResponseData: ResponseData; + signatureAlgorithm: AlgorithmIdentifier; + signature: asn1js.BitString; + certs?: Certificate[]; +} + +export interface CertificateStatus { + isForCertificate: boolean; + /** + * 0 = good, 1 = revoked, 2 = unknown + */ + status: number; +} + +export type BasicOCSPResponseParameters = PkiObjectParameters & Partial<IBasicOCSPResponse>; + +export interface BasicOCSPResponseVerifyParams { + trustedCerts?: Certificate[]; +} + +export interface BasicOCSPResponseJson { + tbsResponseData: ResponseDataJson; + signatureAlgorithm: AlgorithmIdentifierJson; + signature: asn1js.BitStringJson; + certs?: CertificateJson[]; +} + +/** + * Represents the BasicOCSPResponse structure described in [RFC6960](https://datatracker.ietf.org/doc/html/rfc6960) + */ +export class BasicOCSPResponse extends PkiObject implements IBasicOCSPResponse { + + public static override CLASS_NAME = "BasicOCSPResponse"; + + public tbsResponseData!: ResponseData; + public signatureAlgorithm!: AlgorithmIdentifier; + public signature!: asn1js.BitString; + public certs?: Certificate[]; + + /** + * Initializes a new instance of the {@link BasicOCSPResponse} class + * @param parameters Initialization parameters + */ + constructor(parameters: BasicOCSPResponseParameters = {}) { + super(); + + this.tbsResponseData = pvutils.getParametersValue(parameters, TBS_RESPONSE_DATA, BasicOCSPResponse.defaultValues(TBS_RESPONSE_DATA)); + this.signatureAlgorithm = pvutils.getParametersValue(parameters, SIGNATURE_ALGORITHM, BasicOCSPResponse.defaultValues(SIGNATURE_ALGORITHM)); + this.signature = pvutils.getParametersValue(parameters, SIGNATURE, BasicOCSPResponse.defaultValues(SIGNATURE)); + if (CERTS in parameters) { + this.certs = pvutils.getParametersValue(parameters, CERTS, BasicOCSPResponse.defaultValues(CERTS)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof TBS_RESPONSE_DATA): ResponseData; + public static override defaultValues(memberName: typeof SIGNATURE_ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof SIGNATURE): asn1js.BitString; + public static override defaultValues(memberName: typeof CERTS): Certificate[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case TBS_RESPONSE_DATA: + return new ResponseData(); + case SIGNATURE_ALGORITHM: + return new AlgorithmIdentifier(); + case SIGNATURE: + return new asn1js.BitString(); + case CERTS: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case "type": + { + let comparisonResult = ((ResponseData.compareWithDefault("tbs", memberValue.tbs)) && + (ResponseData.compareWithDefault("responderID", memberValue.responderID)) && + (ResponseData.compareWithDefault("producedAt", memberValue.producedAt)) && + (ResponseData.compareWithDefault("responses", memberValue.responses))); + + if ("responseExtensions" in memberValue) + comparisonResult = comparisonResult && (ResponseData.compareWithDefault("responseExtensions", memberValue.responseExtensions)); + + return comparisonResult; + } + case SIGNATURE_ALGORITHM: + return ((memberValue.algorithmId === EMPTY_STRING) && (("algorithmParams" in memberValue) === false)); + case SIGNATURE: + return (memberValue.isEqual(BasicOCSPResponse.defaultValues(memberName))); + case CERTS: + return (memberValue.length === 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * BasicOCSPResponse ::= SEQUENCE { + * tbsResponseData ResponseData, + * signatureAlgorithm AlgorithmIdentifier, + * signature BIT STRING, + * certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + tbsResponseData?: ResponseDataSchema; + signatureAlgorithm?: AlgorithmIdentifierSchema; + signature?: string; + certs?: CertificateSchema; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || BASIC_OCSP_RESPONSE), + value: [ + ResponseData.schema(names.tbsResponseData || { + names: { + blockName: BASIC_OCSP_RESPONSE_TBS_RESPONSE_DATA + } + }), + AlgorithmIdentifier.schema(names.signatureAlgorithm || { + names: { + blockName: BASIC_OCSP_RESPONSE_SIGNATURE_ALGORITHM + } + }), + new asn1js.BitString({ name: (names.signature || BASIC_OCSP_RESPONSE_SIGNATURE) }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + new asn1js.Sequence({ + value: [new asn1js.Repeated({ + name: BASIC_OCSP_RESPONSE_CERTS, + value: Certificate.schema(names.certs || {}) + })] + }) + ] + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + //#endregion + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + BasicOCSPResponse.schema() + ); + AsnError.assertSchema(asn1, this.className); + + //#region Get internal properties from parsed schema + this.tbsResponseData = new ResponseData({ schema: asn1.result[BASIC_OCSP_RESPONSE_TBS_RESPONSE_DATA] }); + this.signatureAlgorithm = new AlgorithmIdentifier({ schema: asn1.result[BASIC_OCSP_RESPONSE_SIGNATURE_ALGORITHM] }); + this.signature = asn1.result[BASIC_OCSP_RESPONSE_SIGNATURE]; + + if (BASIC_OCSP_RESPONSE_CERTS in asn1.result) { + this.certs = Array.from(asn1.result[BASIC_OCSP_RESPONSE_CERTS], element => new Certificate({ schema: element })); + } + //#endregion + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray = []; + + outputArray.push(this.tbsResponseData.toSchema()); + outputArray.push(this.signatureAlgorithm.toSchema()); + outputArray.push(this.signature); + + //#region Create array of certificates + if (this.certs) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + new asn1js.Sequence({ + value: Array.from(this.certs, o => o.toSchema()) + }) + ] + })); + } + //#endregion + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): BasicOCSPResponseJson { + const res: BasicOCSPResponseJson = { + tbsResponseData: this.tbsResponseData.toJSON(), + signatureAlgorithm: this.signatureAlgorithm.toJSON(), + signature: this.signature.toJSON(), + }; + + if (this.certs) { + res.certs = Array.from(this.certs, o => o.toJSON()); + } + + return res; + } + + /** + * Get OCSP response status for specific certificate + * @param certificate Certificate to be checked + * @param issuerCertificate Certificate of issuer for certificate to be checked + * @param crypto Crypto engine + */ + public async getCertificateStatus(certificate: Certificate, issuerCertificate: Certificate, crypto = common.getCrypto(true)): Promise<CertificateStatus> { + //#region Initial variables + const result = { + isForCertificate: false, + status: 2 // 0 = good, 1 = revoked, 2 = unknown + }; + + const hashesObject: Record<string, number> = {}; + + const certIDs: CertID[] = []; + //#endregion + + //#region Create all "certIDs" for input certificates + for (const response of this.tbsResponseData.responses) { + const hashAlgorithm = crypto.getAlgorithmByOID(response.certID.hashAlgorithm.algorithmId, true, "CertID.hashAlgorithm"); + + if (!hashesObject[hashAlgorithm.name]) { + hashesObject[hashAlgorithm.name] = 1; + + const certID = new CertID(); + + certIDs.push(certID); + await certID.createForCertificate(certificate, { + hashAlgorithm: hashAlgorithm.name, + issuerCertificate + }, crypto); + } + } + //#endregion + + //#region Compare all response's "certIDs" with identifiers for input certificate + for (const response of this.tbsResponseData.responses) { + for (const id of certIDs) { + if (response.certID.isEqual(id)) { + result.isForCertificate = true; + + try { + switch (response.certStatus.idBlock.isConstructed) { + case true: + if (response.certStatus.idBlock.tagNumber === 1) + result.status = 1; // revoked + + break; + case false: + switch (response.certStatus.idBlock.tagNumber) { + case 0: // good + result.status = 0; + break; + case 2: // unknown + result.status = 2; + break; + default: + } + + break; + default: + } + } + catch (ex) { + // nothing + } + + return result; + } + } + } + + return result; + //#endregion + } + + /** + * Make signature for current OCSP Basic Response + * @param privateKey Private key for "subjectPublicKeyInfo" structure + * @param hashAlgorithm Hashing algorithm. Default SHA-1 + * @param crypto Crypto engine + */ + async sign(privateKey: CryptoKey, hashAlgorithm = "SHA-1", crypto = common.getCrypto(true)): Promise<void> { + // Get a private key from function parameter + if (!privateKey) { + throw new Error("Need to provide a private key for signing"); + } + + //#region Get a "default parameters" for current algorithm and set correct signature algorithm + const signatureParams = await crypto.getSignatureParameters(privateKey, hashAlgorithm); + + const algorithm = signatureParams.parameters.algorithm; + if (!("name" in algorithm)) { + throw new Error("Empty algorithm"); + } + this.signatureAlgorithm = signatureParams.signatureAlgorithm; + //#endregion + + //#region Create TBS data for signing + this.tbsResponseData.tbsView = new Uint8Array(this.tbsResponseData.toSchema(true).toBER()); + //#endregion + + //#region Signing TBS data on provided private key + const signature = await crypto.signWithPrivateKey(this.tbsResponseData.tbsView, privateKey, { algorithm }); + this.signature = new asn1js.BitString({ valueHex: signature }); + //#endregion + } + + /** + * Verify existing OCSP Basic Response + * @param params Additional parameters + * @param crypto Crypto engine + */ + public async verify(params: BasicOCSPResponseVerifyParams = {}, crypto = common.getCrypto(true)): Promise<boolean> { + //#region Initial variables + let signerCert: Certificate | null = null; + let certIndex = -1; + const trustedCerts: Certificate[] = params.trustedCerts || []; + //#endregion + + //#region Check amount of certificates + if (!this.certs) { + throw new Error("No certificates attached to the BasicOCSPResponse"); + } + //#endregion + + //#region Find correct value for "responderID" + switch (true) { + case (this.tbsResponseData.responderID instanceof RelativeDistinguishedNames): // [1] Name + for (const [index, certificate] of this.certs.entries()) { + if (certificate.subject.isEqual(this.tbsResponseData.responderID)) { + certIndex = index; + break; + } + } + break; + case (this.tbsResponseData.responderID instanceof asn1js.OctetString): // [2] KeyHash + for (const [index, cert] of this.certs.entries()) { + const hash = await crypto.digest({ name: "sha-1" }, cert.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHexView); + if (pvutils.isEqualBuffer(hash, this.tbsResponseData.responderID.valueBlock.valueHex)) { + certIndex = index; + break; + } + } + break; + default: + throw new Error("Wrong value for responderID"); + } + //#endregion + + //#region Make additional verification for signer's certificate + if (certIndex === (-1)) + throw new Error("Correct certificate was not found in OCSP response"); + + signerCert = this.certs[certIndex]; + + const additionalCerts: Certificate[] = [signerCert]; + for (const cert of this.certs) { + const caCert = await checkCA(cert, signerCert); + if (caCert) { + additionalCerts.push(caCert); + } + } + const certChain = new CertificateChainValidationEngine({ + certs: additionalCerts, + trustedCerts, + }); + + const verificationResult = await certChain.verify({}, crypto); + if (!verificationResult.result) { + throw new Error("Validation of signer's certificate failed"); + } + + return crypto.verifyWithPublicKey(this.tbsResponseData.tbsView, this.signature, this.certs[certIndex].subjectPublicKeyInfo, this.signatureAlgorithm); + } + +} diff --git a/third_party/js/PKI.js/src/CAVersion.ts b/third_party/js/PKI.js/src/CAVersion.ts new file mode 100644 index 0000000000..6d1533a8ee --- /dev/null +++ b/third_party/js/PKI.js/src/CAVersion.ts @@ -0,0 +1,170 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const CERTIFICATE_INDEX = "certificateIndex"; +const KEY_INDEX = "keyIndex"; + +export interface ICAVersion { + certificateIndex: number; + keyIndex: number; +} + +export type CAVersionParameters = PkiObjectParameters & Partial<ICAVersion>; + +export interface CAVersionJson { + certificateIndex: number; + keyIndex: number; +} + +/** + * Represents an CAVersion described in [Certification Authority Renewal](https://docs.microsoft.com/en-us/windows/desktop/seccrypto/certification-authority-renewal) + */ +export class CAVersion extends PkiObject implements ICAVersion { + + public static override CLASS_NAME = "CAVersion"; + + public certificateIndex!: number; + public keyIndex!: number; + + /** + * Initializes a new instance of the {@link CAVersion} class + * @param parameters Initialization parameters + */ + constructor(parameters: CAVersionParameters = {}) { + super(); + + this.certificateIndex = pvutils.getParametersValue(parameters, CERTIFICATE_INDEX, CAVersion.defaultValues(CERTIFICATE_INDEX)); + this.keyIndex = pvutils.getParametersValue(parameters, KEY_INDEX, CAVersion.defaultValues(KEY_INDEX)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof CERTIFICATE_INDEX): number; + public static override defaultValues(memberName: typeof KEY_INDEX): number; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case CERTIFICATE_INDEX: + case KEY_INDEX: + return 0; + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * CAVersion ::= INTEGER + *``` + */ + public static override schema(): Schema.SchemaType { + return (new asn1js.Integer()); + } + + public fromSchema(schema: Schema.SchemaType) { + //#region Check the schema is valid + if (schema.constructor.blockName() !== asn1js.Integer.blockName()) { + throw new Error("Object's schema was not verified against input data for CAVersion"); + } + //#endregion + + //#region Check length of the input value and correct it if needed + let value = schema.valueBlock.valueHex.slice(0); + const valueView = new Uint8Array(value); + + switch (true) { + case (value.byteLength < 4): + { + const tempValue = new ArrayBuffer(4); + const tempValueView = new Uint8Array(tempValue); + + tempValueView.set(valueView, 4 - value.byteLength); + + value = tempValue.slice(0); + } + break; + case (value.byteLength > 4): + { + const tempValue = new ArrayBuffer(4); + const tempValueView = new Uint8Array(tempValue); + + tempValueView.set(valueView.slice(0, 4)); + + value = tempValue.slice(0); + } + break; + default: + } + //#endregion + + //#region Get internal properties from parsed schema + const keyIndexBuffer = value.slice(0, 2); + const keyIndexView8 = new Uint8Array(keyIndexBuffer); + let temp = keyIndexView8[0]; + keyIndexView8[0] = keyIndexView8[1]; + keyIndexView8[1] = temp; + + const keyIndexView16 = new Uint16Array(keyIndexBuffer); + + this.keyIndex = keyIndexView16[0]; + + const certificateIndexBuffer = value.slice(2); + const certificateIndexView8 = new Uint8Array(certificateIndexBuffer); + temp = certificateIndexView8[0]; + certificateIndexView8[0] = certificateIndexView8[1]; + certificateIndexView8[1] = temp; + + const certificateIndexView16 = new Uint16Array(certificateIndexBuffer); + + this.certificateIndex = certificateIndexView16[0]; + //#endregion + } + + public toSchema(): asn1js.Integer { + //#region Create raw values + const certificateIndexBuffer = new ArrayBuffer(2); + const certificateIndexView = new Uint16Array(certificateIndexBuffer); + + certificateIndexView[0] = this.certificateIndex; + + const certificateIndexView8 = new Uint8Array(certificateIndexBuffer); + let temp = certificateIndexView8[0]; + certificateIndexView8[0] = certificateIndexView8[1]; + certificateIndexView8[1] = temp; + + const keyIndexBuffer = new ArrayBuffer(2); + const keyIndexView = new Uint16Array(keyIndexBuffer); + + keyIndexView[0] = this.keyIndex; + + const keyIndexView8 = new Uint8Array(keyIndexBuffer); + temp = keyIndexView8[0]; + keyIndexView8[0] = keyIndexView8[1]; + keyIndexView8[1] = temp; + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Integer({ + valueHex: pvutils.utilConcatBuf(keyIndexBuffer, certificateIndexBuffer) + })); + //#endregion + } + + public toJSON(): CAVersionJson { + return { + certificateIndex: this.certificateIndex, + keyIndex: this.keyIndex + }; + } + +} diff --git a/third_party/js/PKI.js/src/CRLBag.ts b/third_party/js/PKI.js/src/CRLBag.ts new file mode 100644 index 0000000000..13ee986e40 --- /dev/null +++ b/third_party/js/PKI.js/src/CRLBag.ts @@ -0,0 +1,190 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { CertificateRevocationList } from "./CertificateRevocationList"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { id_CRLBag_X509CRL } from "./ObjectIdentifiers"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const CRL_ID = "crlId"; +const CRL_VALUE = "crlValue"; +const PARSED_VALUE = "parsedValue"; +const CLEAR_PROPS = [ + CRL_ID, + CRL_VALUE, +]; + +export interface ICRLBag { + crlId: string; + crlValue: any; + parsedValue?: any; + certValue?: any; +} + +export interface CRLBagJson { + crlId: string; + crlValue: any; +} + +export type CRLBagParameters = PkiObjectParameters & Partial<ICRLBag>; + +/** + * Represents the CRLBag structure described in [RFC7292](https://datatracker.ietf.org/doc/html/rfc7292) + */ +export class CRLBag extends PkiObject implements ICRLBag { + + public static override CLASS_NAME = "CRLBag"; + + public crlId!: string; + public crlValue: any; + public parsedValue?: any; + public certValue?: any; + + /** + * Initializes a new instance of the {@link CRLBag} class + * @param parameters Initialization parameters + */ + constructor(parameters: CRLBagParameters = {}) { + super(); + + this.crlId = pvutils.getParametersValue(parameters, CRL_ID, CRLBag.defaultValues(CRL_ID)); + this.crlValue = pvutils.getParametersValue(parameters, CRL_VALUE, CRLBag.defaultValues(CRL_VALUE)); + if (PARSED_VALUE in parameters) { + this.parsedValue = pvutils.getParametersValue(parameters, PARSED_VALUE, CRLBag.defaultValues(PARSED_VALUE)); + } + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof CRL_ID): string; + public static override defaultValues(memberName: typeof CRL_VALUE): any; + public static override defaultValues(memberName: typeof PARSED_VALUE): any; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case CRL_ID: + return EMPTY_STRING; + case CRL_VALUE: + return (new asn1js.Any()); + case PARSED_VALUE: + return {}; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case CRL_ID: + return (memberValue === EMPTY_STRING); + case CRL_VALUE: + return (memberValue instanceof asn1js.Any); + case PARSED_VALUE: + return ((memberValue instanceof Object) && (Object.keys(memberValue).length === 0)); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * CRLBag ::= SEQUENCE { + * crlId BAG-TYPE.&id ({CRLTypes}), + * crlValue [0] EXPLICIT BAG-TYPE.&Type ({CRLTypes}{@crlId}) + *} + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + id?: string; + value?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.ObjectIdentifier({ name: (names.id || "id") }), + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [new asn1js.Any({ name: (names.value || "value") })] // EXPLICIT ANY value + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + CRLBag.schema({ + names: { + id: CRL_ID, + value: CRL_VALUE + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.crlId = asn1.result.crlId.valueBlock.toString(); + this.crlValue = asn1.result.crlValue; + + switch (this.crlId) { + case id_CRLBag_X509CRL: // x509CRL + { + this.parsedValue = CertificateRevocationList.fromBER(this.certValue.valueBlock.valueHex); + } + break; + default: + throw new Error(`Incorrect CRL_ID value in CRLBag: ${this.crlId}`); + } + } + + public toSchema(): asn1js.Sequence { + // Construct and return new ASN.1 schema for this object + if (this.parsedValue) { + this.crlId = id_CRLBag_X509CRL; + this.crlValue = new asn1js.OctetString({ valueHex: this.parsedValue.toSchema().toBER(false) }); + } + + return (new asn1js.Sequence({ + value: [ + new asn1js.ObjectIdentifier({ value: this.crlId }), + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [this.crlValue.toSchema()] + }) + ] + })); + } + + public toJSON(): CRLBagJson { + return { + crlId: this.crlId, + crlValue: this.crlValue.toJSON() + }; + } + +} diff --git a/third_party/js/PKI.js/src/CRLDistributionPoints.ts b/third_party/js/PKI.js/src/CRLDistributionPoints.ts new file mode 100644 index 0000000000..b26b92b238 --- /dev/null +++ b/third_party/js/PKI.js/src/CRLDistributionPoints.ts @@ -0,0 +1,122 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { DistributionPoint, DistributionPointJson } from "./DistributionPoint"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const DISTRIBUTION_POINTS = "distributionPoints"; +const CLEAR_PROPS = [ + DISTRIBUTION_POINTS +]; + +export interface ICRLDistributionPoints { + distributionPoints: DistributionPoint[]; +} + +export interface CRLDistributionPointsJson { + distributionPoints: DistributionPointJson[]; +} + +export type CRLDistributionPointsParameters = PkiObjectParameters & Partial<ICRLDistributionPoints>; + +/** + * Represents the CRLDistributionPoints structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class CRLDistributionPoints extends PkiObject implements ICRLDistributionPoints { + + public static override CLASS_NAME = "CRLDistributionPoints"; + + public distributionPoints!: DistributionPoint[]; + + /** + * Initializes a new instance of the {@link CRLDistributionPoints} class + * @param parameters Initialization parameters + */ + constructor(parameters: CRLDistributionPointsParameters = {}) { + super(); + + this.distributionPoints = pvutils.getParametersValue(parameters, DISTRIBUTION_POINTS, CRLDistributionPoints.defaultValues(DISTRIBUTION_POINTS)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: string): DistributionPoint[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case DISTRIBUTION_POINTS: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * CRLDistributionPoints ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + distributionPoints?: string; + }> = {}): Schema.SchemaType { + /** + * @type {Object} + * @property {string} [blockName] + * @property {string} [distributionPoints] + */ + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Repeated({ + name: (names.distributionPoints || EMPTY_STRING), + value: DistributionPoint.schema() + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + CRLDistributionPoints.schema({ + names: { + distributionPoints: DISTRIBUTION_POINTS + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.distributionPoints = Array.from(asn1.result.distributionPoints, element => new DistributionPoint({ schema: element })); + } + + public toSchema(): asn1js.Sequence { + // Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: Array.from(this.distributionPoints, o => o.toSchema()) + })); + } + + public toJSON(): CRLDistributionPointsJson { + return { + distributionPoints: Array.from(this.distributionPoints, o => o.toJSON()) + }; + } + +} diff --git a/third_party/js/PKI.js/src/CertBag.ts b/third_party/js/PKI.js/src/CertBag.ts new file mode 100644 index 0000000000..4124d1bcca --- /dev/null +++ b/third_party/js/PKI.js/src/CertBag.ts @@ -0,0 +1,210 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { Certificate } from "./Certificate"; +import { AttributeCertificateV2 } from "./AttributeCertificateV2"; +import * as Schema from "./Schema"; +import { id_CertBag_AttributeCertificate, id_CertBag_SDSICertificate, id_CertBag_X509Certificate } from "./ObjectIdentifiers"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { EMPTY_STRING } from "./constants"; + +const CERT_ID = "certId"; +const CERT_VALUE = "certValue"; +const PARSED_VALUE = "parsedValue"; +const CLEAR_PROPS = [ + CERT_ID, + CERT_VALUE +]; + +export interface ICertBag { + certId: string; + certValue: asn1js.OctetString | PkiObject; + parsedValue: any; +} + +export type CertBagParameters = PkiObjectParameters & Partial<ICertBag>; + +export interface CertBagJson { + certId: string; + certValue: any; +} + +/** + * Represents the CertBag structure described in [RFC7292](https://datatracker.ietf.org/doc/html/rfc7292) + */ +export class CertBag extends PkiObject implements ICertBag { + + public static override CLASS_NAME = "CertBag"; + + public certId!: string; + public certValue: PkiObject | asn1js.OctetString; + public parsedValue: any; + + /** + * Initializes a new instance of the {@link CertBag} class + * @param parameters Initialization parameters + */ + constructor(parameters: CertBagParameters = {}) { + super(); + + this.certId = pvutils.getParametersValue(parameters, CERT_ID, CertBag.defaultValues(CERT_ID)); + this.certValue = pvutils.getParametersValue(parameters, CERT_VALUE, CertBag.defaultValues(CERT_VALUE)); + if (PARSED_VALUE in parameters) { + this.parsedValue = pvutils.getParametersValue(parameters, PARSED_VALUE, CertBag.defaultValues(PARSED_VALUE)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof CERT_ID): string; + public static override defaultValues(memberName: typeof CERT_VALUE): any; + public static override defaultValues(memberName: typeof PARSED_VALUE): any; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case CERT_ID: + return EMPTY_STRING; + case CERT_VALUE: + return (new asn1js.Any()); + case PARSED_VALUE: + return {}; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case CERT_ID: + return (memberValue === EMPTY_STRING); + case CERT_VALUE: + return (memberValue instanceof asn1js.Any); + case PARSED_VALUE: + return ((memberValue instanceof Object) && (Object.keys(memberValue).length === 0)); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * CertBag ::= SEQUENCE { + * certId BAG-TYPE.&id ({CertTypes}), + * certValue [0] EXPLICIT BAG-TYPE.&Type ({CertTypes}{@certId}) + * } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + id?: string; + value?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.ObjectIdentifier({ name: (names.id || "id") }), + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [new asn1js.Any({ name: (names.value || "value") })] // EXPLICIT ANY value + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + CertBag.schema({ + names: { + id: CERT_ID, + value: CERT_VALUE + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + //#region Get internal properties from parsed schema + this.certId = asn1.result.certId.valueBlock.toString(); + this.certValue = asn1.result.certValue as asn1js.OctetString; + + const certValueHex = this.certValue.valueBlock.valueHexView; + switch (this.certId) { + case id_CertBag_X509Certificate: // x509Certificate + { + try { + this.parsedValue = Certificate.fromBER(certValueHex); + } + catch (ex) // In some realizations the same OID used for attribute certificates + { + AttributeCertificateV2.fromBER(certValueHex); + } + } + break; + case id_CertBag_AttributeCertificate: // attributeCertificate - (!!!) THIS OID IS SUBJECT FOR CHANGE IN FUTURE (!!!) + { + this.parsedValue = AttributeCertificateV2.fromBER(certValueHex); + } + break; + case id_CertBag_SDSICertificate: // sdsiCertificate + default: + throw new Error(`Incorrect CERT_ID value in CertBag: ${this.certId}`); + } + //#endregion + } + + public toSchema(): asn1js.Sequence { + //#region Construct and return new ASN.1 schema for this object + if (PARSED_VALUE in this) { + if ("acinfo" in this.parsedValue) {// attributeCertificate + this.certId = id_CertBag_AttributeCertificate; + } else {// x509Certificate + this.certId = id_CertBag_X509Certificate; + } + + this.certValue = new asn1js.OctetString({ valueHex: this.parsedValue.toSchema().toBER(false) }); + } + + return (new asn1js.Sequence({ + value: [ + new asn1js.ObjectIdentifier({ value: this.certId }), + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [(("toSchema" in this.certValue) ? this.certValue.toSchema() : this.certValue)] + }) + ] + })); + //#endregion + } + + public toJSON(): CertBagJson { + return { + certId: this.certId, + certValue: this.certValue.toJSON() + }; + } + +} diff --git a/third_party/js/PKI.js/src/CertID.ts b/third_party/js/PKI.js/src/CertID.ts new file mode 100644 index 0000000000..7594242847 --- /dev/null +++ b/third_party/js/PKI.js/src/CertID.ts @@ -0,0 +1,284 @@ +import * as asn1js from "asn1js"; +import * as pvtsutils from "pvtsutils"; +import * as pvutils from "pvutils"; +import * as common from "./common"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier"; +import { Certificate } from "./Certificate"; +import * as Schema from "./Schema"; +import { AsnError, ParameterError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { EMPTY_STRING } from "./constants"; + +const HASH_ALGORITHM = "hashAlgorithm"; +const ISSUER_NAME_HASH = "issuerNameHash"; +const ISSUER_KEY_HASH = "issuerKeyHash"; +const SERIAL_NUMBER = "serialNumber"; +const CLEAR_PROPS = [ + HASH_ALGORITHM, + ISSUER_NAME_HASH, + ISSUER_KEY_HASH, + SERIAL_NUMBER, +]; + +export interface ICertID { + /** + * Hash algorithm used to generate the `issuerNameHash` and `issuerKeyHash` values + */ + hashAlgorithm: AlgorithmIdentifier; + /** + * Hash of the issuer's distinguished name (DN). The hash shall be calculated over the DER encoding + * of the issuer's name field in the certificate being checked. + */ + issuerNameHash: asn1js.OctetString; + /** + * Hash of the issuer's public key. The hash shall be calculated over the value (excluding tag and length) + * of the subject public key field in the issuer's certificate. + */ + issuerKeyHash: asn1js.OctetString; + /** + * Serial number of the certificate for which status is being requested + */ + serialNumber: asn1js.Integer; +} + +export type CertIDParameters = PkiObjectParameters & Partial<ICertID>; + +export type CertIDSchema = Schema.SchemaParameters<{ + hashAlgorithm?: string; + hashAlgorithmObject?: AlgorithmIdentifierSchema; + issuerNameHash?: string; + issuerKeyHash?: string; + serialNumber?: string; +}>; + +export interface CertIDJson { + hashAlgorithm: AlgorithmIdentifierJson; + issuerNameHash: asn1js.OctetStringJson; + issuerKeyHash: asn1js.OctetStringJson; + serialNumber: asn1js.IntegerJson; +} + +export interface CertIDCreateParams { + issuerCertificate: Certificate; + hashAlgorithm: string; +} + +/** + * Represents an CertID described in [RFC6960](https://datatracker.ietf.org/doc/html/rfc6960) + */ +export class CertID extends PkiObject implements ICertID { + + public static override CLASS_NAME = "CertID"; + + /** + * Making OCSP certificate identifier for specific certificate + * @param certificate Certificate making OCSP Request for + * @param parameters Additional parameters + * @param crypto Crypto engine + * @returns Returns created CertID object + */ + public static async create(certificate: Certificate, parameters: CertIDCreateParams, crypto = common.getCrypto(true)): Promise<CertID> { + const certID = new CertID(); + await certID.createForCertificate(certificate, parameters, crypto); + + return certID; + } + + public hashAlgorithm!: AlgorithmIdentifier; + public issuerNameHash!: asn1js.OctetString; + public issuerKeyHash!: asn1js.OctetString; + public serialNumber!: asn1js.Integer; + + /** + * Initializes a new instance of the {@link CertID} class + * @param parameters Initialization parameters + */ + constructor(parameters: CertIDParameters = {}) { + super(); + + this.hashAlgorithm = pvutils.getParametersValue(parameters, HASH_ALGORITHM, CertID.defaultValues(HASH_ALGORITHM)); + this.issuerNameHash = pvutils.getParametersValue(parameters, ISSUER_NAME_HASH, CertID.defaultValues(ISSUER_NAME_HASH)); + this.issuerKeyHash = pvutils.getParametersValue(parameters, ISSUER_KEY_HASH, CertID.defaultValues(ISSUER_KEY_HASH)); + this.serialNumber = pvutils.getParametersValue(parameters, SERIAL_NUMBER, CertID.defaultValues(SERIAL_NUMBER)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof HASH_ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof ISSUER_NAME_HASH): asn1js.OctetString; + public static override defaultValues(memberName: typeof ISSUER_KEY_HASH): asn1js.OctetString; + public static override defaultValues(memberName: typeof SERIAL_NUMBER): asn1js.Integer; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case HASH_ALGORITHM: + return new AlgorithmIdentifier(); + case ISSUER_NAME_HASH: + case ISSUER_KEY_HASH: + return new asn1js.OctetString(); + case SERIAL_NUMBER: + return new asn1js.Integer(); + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case HASH_ALGORITHM: + return ((memberValue.algorithmId === EMPTY_STRING) && (("algorithmParams" in memberValue) === false)); + case ISSUER_NAME_HASH: + case ISSUER_KEY_HASH: + case SERIAL_NUMBER: + return (memberValue.isEqual(CertID.defaultValues(SERIAL_NUMBER))); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * CertID ::= SEQUENCE { + * hashAlgorithm AlgorithmIdentifier, + * issuerNameHash OCTET STRING, -- Hash of issuer's DN + * issuerKeyHash OCTET STRING, -- Hash of issuer's public key + * serialNumber CertificateSerialNumber } + *``` + */ + public static override schema(parameters: CertIDSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + AlgorithmIdentifier.schema(names.hashAlgorithmObject || { + names: { + blockName: (names.hashAlgorithm || EMPTY_STRING) + } + }), + new asn1js.OctetString({ name: (names.issuerNameHash || EMPTY_STRING) }), + new asn1js.OctetString({ name: (names.issuerKeyHash || EMPTY_STRING) }), + new asn1js.Integer({ name: (names.serialNumber || EMPTY_STRING) }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + CertID.schema({ + names: { + hashAlgorithm: HASH_ALGORITHM, + issuerNameHash: ISSUER_NAME_HASH, + issuerKeyHash: ISSUER_KEY_HASH, + serialNumber: SERIAL_NUMBER + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.hashAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.hashAlgorithm }); + this.issuerNameHash = asn1.result.issuerNameHash; + this.issuerKeyHash = asn1.result.issuerKeyHash; + this.serialNumber = asn1.result.serialNumber; + } + + public toSchema(): asn1js.Sequence { + return (new asn1js.Sequence({ + value: [ + this.hashAlgorithm.toSchema(), + this.issuerNameHash, + this.issuerKeyHash, + this.serialNumber + ] + })); + } + + public toJSON(): CertIDJson { + return { + hashAlgorithm: this.hashAlgorithm.toJSON(), + issuerNameHash: this.issuerNameHash.toJSON(), + issuerKeyHash: this.issuerKeyHash.toJSON(), + serialNumber: this.serialNumber.toJSON(), + }; + } + + /** + * Checks that two "CertIDs" are equal + * @param certificateID Identifier of the certificate to be checked + */ + public isEqual(certificateID: CertID): boolean { + // Check HASH_ALGORITHM + if (this.hashAlgorithm.algorithmId !== certificateID.hashAlgorithm.algorithmId) { + return false; + } + + // Check ISSUER_NAME_HASH + if (!pvtsutils.BufferSourceConverter.isEqual(this.issuerNameHash.valueBlock.valueHexView, certificateID.issuerNameHash.valueBlock.valueHexView)) { + return false; + } + + // Check ISSUER_KEY_HASH + if (!pvtsutils.BufferSourceConverter.isEqual(this.issuerKeyHash.valueBlock.valueHexView, certificateID.issuerKeyHash.valueBlock.valueHexView)) { + return false; + } + + // Check SERIAL_NUMBER + if (!this.serialNumber.isEqual(certificateID.serialNumber)) { + return false; + } + + return true; + } + + /** + * Making OCSP certificate identifier for specific certificate + * @param certificate Certificate making OCSP Request for + * @param parameters Additional parameters + * @param crypto Crypto engine + */ + public async createForCertificate(certificate: Certificate, parameters: CertIDCreateParams, crypto = common.getCrypto(true)): Promise<void> { + //#region Check input parameters + ParameterError.assert(parameters, HASH_ALGORITHM, "issuerCertificate"); + + const hashOID = crypto.getOIDByAlgorithm({ name: parameters.hashAlgorithm }, true, "hashAlgorithm"); + + this.hashAlgorithm = new AlgorithmIdentifier({ + algorithmId: hashOID, + algorithmParams: new asn1js.Null() + }); + const issuerCertificate = parameters.issuerCertificate; + //#endregion + + // Initialize SERIAL_NUMBER field + this.serialNumber = certificate.serialNumber; + + // Create ISSUER_NAME_HASH + const hashIssuerName = await crypto.digest({ name: parameters.hashAlgorithm }, issuerCertificate.subject.toSchema().toBER(false)); + this.issuerNameHash = new asn1js.OctetString({ valueHex: hashIssuerName }); + + // Create ISSUER_KEY_HASH + const issuerKeyBuffer = issuerCertificate.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHexView; + const hashIssuerKey = await crypto.digest({ name: parameters.hashAlgorithm }, issuerKeyBuffer); + this.issuerKeyHash = new asn1js.OctetString({ valueHex: hashIssuerKey }); + } + +} diff --git a/third_party/js/PKI.js/src/Certificate.ts b/third_party/js/PKI.js/src/Certificate.ts new file mode 100644 index 0000000000..1f93ed240b --- /dev/null +++ b/third_party/js/PKI.js/src/Certificate.ts @@ -0,0 +1,828 @@ +import * as asn1js from "asn1js"; +import * as pvtsutils from "pvtsutils"; +import * as pvutils from "pvutils"; +import * as common from "./common"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier"; +import { RelativeDistinguishedNames, RelativeDistinguishedNamesJson, RelativeDistinguishedNamesSchema } from "./RelativeDistinguishedNames"; +import { Time, TimeJson, TimeSchema } from "./Time"; +import { PublicKeyInfo, PublicKeyInfoJson, PublicKeyInfoSchema } from "./PublicKeyInfo"; +import { Extension, ExtensionJson } from "./Extension"; +import { Extensions, ExtensionsSchema } from "./Extensions"; +import * as Schema from "./Schema"; +import { id_BasicConstraints } from "./ObjectIdentifiers"; +import { BasicConstraints } from "./BasicConstraints"; +import { CryptoEnginePublicKeyParams } from "./CryptoEngine/CryptoEngineInterface"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { EMPTY_BUFFER, EMPTY_STRING } from "./constants"; + +const TBS = "tbs"; +const VERSION = "version"; +const SERIAL_NUMBER = "serialNumber"; +const SIGNATURE = "signature"; +const ISSUER = "issuer"; +const NOT_BEFORE = "notBefore"; +const NOT_AFTER = "notAfter"; +const SUBJECT = "subject"; +const SUBJECT_PUBLIC_KEY_INFO = "subjectPublicKeyInfo"; +const ISSUER_UNIQUE_ID = "issuerUniqueID"; +const SUBJECT_UNIQUE_ID = "subjectUniqueID"; +const EXTENSIONS = "extensions"; +const SIGNATURE_ALGORITHM = "signatureAlgorithm"; +const SIGNATURE_VALUE = "signatureValue"; +const TBS_CERTIFICATE = "tbsCertificate"; +const TBS_CERTIFICATE_VERSION = `${TBS_CERTIFICATE}.${VERSION}`; +const TBS_CERTIFICATE_SERIAL_NUMBER = `${TBS_CERTIFICATE}.${SERIAL_NUMBER}`; +const TBS_CERTIFICATE_SIGNATURE = `${TBS_CERTIFICATE}.${SIGNATURE}`; +const TBS_CERTIFICATE_ISSUER = `${TBS_CERTIFICATE}.${ISSUER}`; +const TBS_CERTIFICATE_NOT_BEFORE = `${TBS_CERTIFICATE}.${NOT_BEFORE}`; +const TBS_CERTIFICATE_NOT_AFTER = `${TBS_CERTIFICATE}.${NOT_AFTER}`; +const TBS_CERTIFICATE_SUBJECT = `${TBS_CERTIFICATE}.${SUBJECT}`; +const TBS_CERTIFICATE_SUBJECT_PUBLIC_KEY = `${TBS_CERTIFICATE}.${SUBJECT_PUBLIC_KEY_INFO}`; +const TBS_CERTIFICATE_ISSUER_UNIQUE_ID = `${TBS_CERTIFICATE}.${ISSUER_UNIQUE_ID}`; +const TBS_CERTIFICATE_SUBJECT_UNIQUE_ID = `${TBS_CERTIFICATE}.${SUBJECT_UNIQUE_ID}`; +const TBS_CERTIFICATE_EXTENSIONS = `${TBS_CERTIFICATE}.${EXTENSIONS}`; +const CLEAR_PROPS = [ + TBS_CERTIFICATE, + TBS_CERTIFICATE_VERSION, + TBS_CERTIFICATE_SERIAL_NUMBER, + TBS_CERTIFICATE_SIGNATURE, + TBS_CERTIFICATE_ISSUER, + TBS_CERTIFICATE_NOT_BEFORE, + TBS_CERTIFICATE_NOT_AFTER, + TBS_CERTIFICATE_SUBJECT, + TBS_CERTIFICATE_SUBJECT_PUBLIC_KEY, + TBS_CERTIFICATE_ISSUER_UNIQUE_ID, + TBS_CERTIFICATE_SUBJECT_UNIQUE_ID, + TBS_CERTIFICATE_EXTENSIONS, + SIGNATURE_ALGORITHM, + SIGNATURE_VALUE +]; + +export type TBSCertificateSchema = Schema.SchemaParameters<{ + tbsCertificateVersion?: string; + tbsCertificateSerialNumber?: string; + signature?: AlgorithmIdentifierSchema; + issuer?: RelativeDistinguishedNamesSchema; + tbsCertificateValidity?: string; + notBefore?: TimeSchema; + notAfter?: TimeSchema; + subject?: RelativeDistinguishedNamesSchema; + subjectPublicKeyInfo?: PublicKeyInfoSchema; + tbsCertificateIssuerUniqueID?: string; + tbsCertificateSubjectUniqueID?: string; + extensions?: ExtensionsSchema; +}>; + +function tbsCertificate(parameters: TBSCertificateSchema = {}): Schema.SchemaType { + //TBSCertificate ::= SEQUENCE { + // version [0] EXPLICIT Version DEFAULT v1, + // serialNumber CertificateSerialNumber, + // signature AlgorithmIdentifier, + // issuer Name, + // validity Validity, + // subject Name, + // subjectPublicKeyInfo SubjectPublicKeyInfo, + // issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, + // -- If present, version MUST be v2 or v3 + // subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, + // -- If present, version MUST be v2 or v3 + // extensions [3] EXPLICIT Extensions OPTIONAL + // -- If present, version MUST be v3 + //} + + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || TBS_CERTIFICATE), + value: [ + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + new asn1js.Integer({ name: (names.tbsCertificateVersion || TBS_CERTIFICATE_VERSION) }) // EXPLICIT integer value + ] + }), + new asn1js.Integer({ name: (names.tbsCertificateSerialNumber || TBS_CERTIFICATE_SERIAL_NUMBER) }), + AlgorithmIdentifier.schema(names.signature || { + names: { + blockName: TBS_CERTIFICATE_SIGNATURE + } + }), + RelativeDistinguishedNames.schema(names.issuer || { + names: { + blockName: TBS_CERTIFICATE_ISSUER + } + }), + new asn1js.Sequence({ + name: (names.tbsCertificateValidity || "tbsCertificate.validity"), + value: [ + Time.schema(names.notBefore || { + names: { + utcTimeName: TBS_CERTIFICATE_NOT_BEFORE, + generalTimeName: TBS_CERTIFICATE_NOT_BEFORE + } + }), + Time.schema(names.notAfter || { + names: { + utcTimeName: TBS_CERTIFICATE_NOT_AFTER, + generalTimeName: TBS_CERTIFICATE_NOT_AFTER + } + }) + ] + }), + RelativeDistinguishedNames.schema(names.subject || { + names: { + blockName: TBS_CERTIFICATE_SUBJECT + } + }), + PublicKeyInfo.schema(names.subjectPublicKeyInfo || { + names: { + blockName: TBS_CERTIFICATE_SUBJECT_PUBLIC_KEY + } + }), + new asn1js.Primitive({ + name: (names.tbsCertificateIssuerUniqueID || TBS_CERTIFICATE_ISSUER_UNIQUE_ID), + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + } + }), // IMPLICIT BIT_STRING value + new asn1js.Primitive({ + name: (names.tbsCertificateSubjectUniqueID || TBS_CERTIFICATE_SUBJECT_UNIQUE_ID), + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 2 // [2] + } + }), // IMPLICIT BIT_STRING value + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 3 // [3] + }, + value: [Extensions.schema(names.extensions || { + names: { + blockName: TBS_CERTIFICATE_EXTENSIONS + } + })] + }) // EXPLICIT SEQUENCE value + ] + })); +} + +export interface ICertificate { + /** + * ToBeSigned (TBS) part of the certificate + */ + tbs: ArrayBuffer; + /** + * Version number + */ + version: number; + /** + * Serial number of the certificate + */ + serialNumber: asn1js.Integer; + /** + * This field contains the algorithm identifier for the algorithm used by the CA to sign the certificate + */ + signature: AlgorithmIdentifier; + /** + * The issuer field identifies the entity that has signed and issued the certificate + */ + issuer: RelativeDistinguishedNames; + /** + * The date on which the certificate validity period begins + */ + notBefore: Time; + /** + * The date on which the certificate validity period ends + */ + notAfter: Time; + /** + * The subject field identifies the entity associated with the public key stored in the subject public key field + */ + subject: RelativeDistinguishedNames; + /** + * This field is used to carry the public key and identify the algorithm with which the key is used + */ + subjectPublicKeyInfo: PublicKeyInfo; + /** + * The subject and issuer unique identifiers are present in the certificate to handle the possibility of reuse of subject and/or issuer names over time + */ + issuerUniqueID?: ArrayBuffer; + /** + * The subject and issuer unique identifiers are present in the certificate to handle the possibility of reuse of subject and/or issuer names over time + */ + subjectUniqueID?: ArrayBuffer; + /** + * If present, this field is a SEQUENCE of one or more certificate extensions + */ + extensions?: Extension[]; + /** + * The signatureAlgorithm field contains the identifier for the cryptographic algorithm used by the CA to sign this certificate + */ + signatureAlgorithm: AlgorithmIdentifier; + /** + * The signatureValue field contains a digital signature computed upon the ASN.1 DER encoded tbsCertificate + */ + signatureValue: asn1js.BitString; +} + +/** + * Constructor parameters for the {@link Certificate} class + */ +export type CertificateParameters = PkiObjectParameters & Partial<ICertificate>; + +/** + * Parameters for {@link Certificate} schema generation + */ +export type CertificateSchema = Schema.SchemaParameters<{ + tbsCertificate?: TBSCertificateSchema; + signatureAlgorithm?: AlgorithmIdentifierSchema; + signatureValue?: string; +}>; + +export interface CertificateJson { + tbs: string; + version: number; + serialNumber: asn1js.IntegerJson; + signature: AlgorithmIdentifierJson; + issuer: RelativeDistinguishedNamesJson; + notBefore: TimeJson; + notAfter: TimeJson; + subject: RelativeDistinguishedNamesJson; + subjectPublicKeyInfo: PublicKeyInfoJson | JsonWebKey; + issuerUniqueID?: string; + subjectUniqueID?: string; + extensions?: ExtensionJson[]; + signatureAlgorithm: AlgorithmIdentifierJson; + signatureValue: asn1js.BitStringJson; +} + +/** + * Represents an X.509 certificate described in [RFC5280 Section 4](https://datatracker.ietf.org/doc/html/rfc5280#section-4). + * + * @example The following example demonstrates how to parse X.509 Certificate + * ```js + * const asn1 = asn1js.fromBER(raw); + * if (asn1.offset === -1) { + * throw new Error("Incorrect encoded ASN.1 data"); + * } + * + * const cert = new pkijs.Certificate({ schema: asn1.result }); + * ``` + * + * @example The following example demonstrates how to create self-signed certificate + * ```js + * const crypto = pkijs.getCrypto(true); + * + * // Create certificate + * const certificate = new pkijs.Certificate(); + * certificate.version = 2; + * certificate.serialNumber = new asn1js.Integer({ value: 1 }); + * certificate.issuer.typesAndValues.push(new pkijs.AttributeTypeAndValue({ + * type: "2.5.4.3", // Common name + * value: new asn1js.BmpString({ value: "Test" }) + * })); + * certificate.subject.typesAndValues.push(new pkijs.AttributeTypeAndValue({ + * type: "2.5.4.3", // Common name + * value: new asn1js.BmpString({ value: "Test" }) + * })); + * + * certificate.notBefore.value = new Date(); + * const notAfter = new Date(); + * notAfter.setUTCFullYear(notAfter.getUTCFullYear() + 1); + * certificate.notAfter.value = notAfter; + * + * certificate.extensions = []; // Extensions are not a part of certificate by default, it's an optional array + * + * // "BasicConstraints" extension + * const basicConstr = new pkijs.BasicConstraints({ + * cA: true, + * pathLenConstraint: 3 + * }); + * certificate.extensions.push(new pkijs.Extension({ + * extnID: "2.5.29.19", + * critical: false, + * extnValue: basicConstr.toSchema().toBER(false), + * parsedValue: basicConstr // Parsed value for well-known extensions + * })); + * + * // "KeyUsage" extension + * const bitArray = new ArrayBuffer(1); + * const bitView = new Uint8Array(bitArray); + * bitView[0] |= 0x02; // Key usage "cRLSign" flag + * bitView[0] |= 0x04; // Key usage "keyCertSign" flag + * const keyUsage = new asn1js.BitString({ valueHex: bitArray }); + * certificate.extensions.push(new pkijs.Extension({ + * extnID: "2.5.29.15", + * critical: false, + * extnValue: keyUsage.toBER(false), + * parsedValue: keyUsage // Parsed value for well-known extensions + * })); + * + * const algorithm = pkijs.getAlgorithmParameters("RSASSA-PKCS1-v1_5", "generateKey"); + * if ("hash" in algorithm.algorithm) { + * algorithm.algorithm.hash.name = "SHA-256"; + * } + * + * const keys = await crypto.generateKey(algorithm.algorithm, true, algorithm.usages); + * + * // Exporting public key into "subjectPublicKeyInfo" value of certificate + * await certificate.subjectPublicKeyInfo.importKey(keys.publicKey); + * + * // Signing final certificate + * await certificate.sign(keys.privateKey, "SHA-256"); + * + * const raw = certificate.toSchema().toBER(); + * ``` + */ +export class Certificate extends PkiObject implements ICertificate { + + public static override CLASS_NAME = "Certificate"; + + public tbsView!: Uint8Array; + /** + * @deprecated Since version 3.0.0 + */ + public get tbs(): ArrayBuffer { + return pvtsutils.BufferSourceConverter.toArrayBuffer(this.tbsView); + } + + /** + * @deprecated Since version 3.0.0 + */ + public set tbs(value: ArrayBuffer) { + this.tbsView = new Uint8Array(value); + } + + public version!: number; + public serialNumber!: asn1js.Integer; + public signature!: AlgorithmIdentifier; + public issuer!: RelativeDistinguishedNames; + public notBefore!: Time; + public notAfter!: Time; + public subject!: RelativeDistinguishedNames; + public subjectPublicKeyInfo!: PublicKeyInfo; + public issuerUniqueID?: ArrayBuffer; + public subjectUniqueID?: ArrayBuffer; + public extensions?: Extension[]; + public signatureAlgorithm!: AlgorithmIdentifier; + public signatureValue!: asn1js.BitString; + + /** + * Initializes a new instance of the {@link Certificate} class + * @param parameters Initialization parameters + */ + constructor(parameters: CertificateParameters = {}) { + super(); + + this.tbsView = new Uint8Array(pvutils.getParametersValue(parameters, TBS, Certificate.defaultValues(TBS))); + this.version = pvutils.getParametersValue(parameters, VERSION, Certificate.defaultValues(VERSION)); + this.serialNumber = pvutils.getParametersValue(parameters, SERIAL_NUMBER, Certificate.defaultValues(SERIAL_NUMBER)); + this.signature = pvutils.getParametersValue(parameters, SIGNATURE, Certificate.defaultValues(SIGNATURE)); + this.issuer = pvutils.getParametersValue(parameters, ISSUER, Certificate.defaultValues(ISSUER)); + this.notBefore = pvutils.getParametersValue(parameters, NOT_BEFORE, Certificate.defaultValues(NOT_BEFORE)); + this.notAfter = pvutils.getParametersValue(parameters, NOT_AFTER, Certificate.defaultValues(NOT_AFTER)); + this.subject = pvutils.getParametersValue(parameters, SUBJECT, Certificate.defaultValues(SUBJECT)); + this.subjectPublicKeyInfo = pvutils.getParametersValue(parameters, SUBJECT_PUBLIC_KEY_INFO, Certificate.defaultValues(SUBJECT_PUBLIC_KEY_INFO)); + if (ISSUER_UNIQUE_ID in parameters) { + this.issuerUniqueID = pvutils.getParametersValue(parameters, ISSUER_UNIQUE_ID, Certificate.defaultValues(ISSUER_UNIQUE_ID)); + } + if (SUBJECT_UNIQUE_ID in parameters) { + this.subjectUniqueID = pvutils.getParametersValue(parameters, SUBJECT_UNIQUE_ID, Certificate.defaultValues(SUBJECT_UNIQUE_ID)); + } + if (EXTENSIONS in parameters) { + this.extensions = pvutils.getParametersValue(parameters, EXTENSIONS, Certificate.defaultValues(EXTENSIONS)); + } + this.signatureAlgorithm = pvutils.getParametersValue(parameters, SIGNATURE_ALGORITHM, Certificate.defaultValues(SIGNATURE_ALGORITHM)); + this.signatureValue = pvutils.getParametersValue(parameters, SIGNATURE_VALUE, Certificate.defaultValues(SIGNATURE_VALUE)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Return default values for all class members + * @param memberName String name for a class member + * @returns Predefined default value + */ + public static override defaultValues(memberName: typeof TBS): ArrayBuffer; + public static override defaultValues(memberName: typeof VERSION): number; + public static override defaultValues(memberName: typeof SERIAL_NUMBER): asn1js.Integer; + public static override defaultValues(memberName: typeof SIGNATURE): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof ISSUER): RelativeDistinguishedNames; + public static override defaultValues(memberName: typeof NOT_BEFORE): Time; + public static override defaultValues(memberName: typeof NOT_AFTER): Time; + public static override defaultValues(memberName: typeof SUBJECT): RelativeDistinguishedNames; + public static override defaultValues(memberName: typeof SUBJECT_PUBLIC_KEY_INFO): PublicKeyInfo; + public static override defaultValues(memberName: typeof ISSUER_UNIQUE_ID): ArrayBuffer; + public static override defaultValues(memberName: typeof SUBJECT_UNIQUE_ID): ArrayBuffer; + public static override defaultValues(memberName: typeof EXTENSIONS): Extension[]; + public static override defaultValues(memberName: typeof SIGNATURE_ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof SIGNATURE_VALUE): asn1js.BitString; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case TBS: + return EMPTY_BUFFER; + case VERSION: + return 0; + case SERIAL_NUMBER: + return new asn1js.Integer(); + case SIGNATURE: + return new AlgorithmIdentifier(); + case ISSUER: + return new RelativeDistinguishedNames(); + case NOT_BEFORE: + return new Time(); + case NOT_AFTER: + return new Time(); + case SUBJECT: + return new RelativeDistinguishedNames(); + case SUBJECT_PUBLIC_KEY_INFO: + return new PublicKeyInfo(); + case ISSUER_UNIQUE_ID: + return EMPTY_BUFFER; + case SUBJECT_UNIQUE_ID: + return EMPTY_BUFFER; + case EXTENSIONS: + return []; + case SIGNATURE_ALGORITHM: + return new AlgorithmIdentifier(); + case SIGNATURE_VALUE: + return new asn1js.BitString(); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * Certificate ::= SEQUENCE { + * tbsCertificate TBSCertificate, + * signatureAlgorithm AlgorithmIdentifier, + * signatureValue BIT STRING } + * + * TBSCertificate ::= SEQUENCE { + * version [0] EXPLICIT Version DEFAULT v1, + * serialNumber CertificateSerialNumber, + * signature AlgorithmIdentifier, + * issuer Name, + * validity Validity, + * subject Name, + * subjectPublicKeyInfo SubjectPublicKeyInfo, + * issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, + * -- If present, version MUST be v2 or v3 + * subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, + * -- If present, version MUST be v2 or v3 + * extensions [3] EXPLICIT Extensions OPTIONAL + * -- If present, version MUST be v3 + * } + * + * Version ::= INTEGER { v1(0), v2(1), v3(2) } + * + * CertificateSerialNumber ::= INTEGER + * + * Validity ::= SEQUENCE { + * notBefore Time, + * notAfter Time } + * + * Time ::= CHOICE { + * utcTime UTCTime, + * generalTime GeneralizedTime } + * + * UniqueIdentifier ::= BIT STRING + * + * SubjectPublicKeyInfo ::= SEQUENCE { + * algorithm AlgorithmIdentifier, + * subjectPublicKey BIT STRING } + * + * Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension + * + * Extension ::= SEQUENCE { + * extnID OBJECT IDENTIFIER, + * critical BOOLEAN DEFAULT FALSE, + * extnValue OCTET STRING + * -- contains the DER encoding of an ASN.1 value + * -- corresponding to the extension type identified + * -- by extnID + * } + *``` + */ + public static override schema(parameters: CertificateSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + tbsCertificate(names.tbsCertificate), + AlgorithmIdentifier.schema(names.signatureAlgorithm || { + names: { + blockName: SIGNATURE_ALGORITHM + } + }), + new asn1js.BitString({ name: (names.signatureValue || SIGNATURE_VALUE) }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + //#region Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + Certificate.schema({ + names: { + tbsCertificate: { + names: { + extensions: { + names: { + extensions: TBS_CERTIFICATE_EXTENSIONS + } + } + } + } + } + }) + ); + AsnError.assertSchema(asn1, this.className); + //#endregion + + //#region Get internal properties from parsed schema + this.tbsView = (asn1.result.tbsCertificate as asn1js.Sequence).valueBeforeDecodeView; + + if (TBS_CERTIFICATE_VERSION in asn1.result) + this.version = asn1.result[TBS_CERTIFICATE_VERSION].valueBlock.valueDec; + this.serialNumber = asn1.result[TBS_CERTIFICATE_SERIAL_NUMBER]; + this.signature = new AlgorithmIdentifier({ schema: asn1.result[TBS_CERTIFICATE_SIGNATURE] }); + this.issuer = new RelativeDistinguishedNames({ schema: asn1.result[TBS_CERTIFICATE_ISSUER] }); + this.notBefore = new Time({ schema: asn1.result[TBS_CERTIFICATE_NOT_BEFORE] }); + this.notAfter = new Time({ schema: asn1.result[TBS_CERTIFICATE_NOT_AFTER] }); + this.subject = new RelativeDistinguishedNames({ schema: asn1.result[TBS_CERTIFICATE_SUBJECT] }); + this.subjectPublicKeyInfo = new PublicKeyInfo({ schema: asn1.result[TBS_CERTIFICATE_SUBJECT_PUBLIC_KEY] }); + if (TBS_CERTIFICATE_ISSUER_UNIQUE_ID in asn1.result) + this.issuerUniqueID = asn1.result[TBS_CERTIFICATE_ISSUER_UNIQUE_ID].valueBlock.valueHex; + if (TBS_CERTIFICATE_SUBJECT_UNIQUE_ID in asn1.result) + this.subjectUniqueID = asn1.result[TBS_CERTIFICATE_SUBJECT_UNIQUE_ID].valueBlock.valueHex; + if (TBS_CERTIFICATE_EXTENSIONS in asn1.result) + this.extensions = Array.from(asn1.result[TBS_CERTIFICATE_EXTENSIONS], element => new Extension({ schema: element })); + + this.signatureAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.signatureAlgorithm }); + this.signatureValue = asn1.result.signatureValue; + //#endregion + } + + /** + * Creates ASN.1 schema for existing values of TBS part for the certificate + * @returns ASN.1 SEQUENCE + */ + public encodeTBS(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray = []; + + if ((VERSION in this) && (this.version !== Certificate.defaultValues(VERSION))) { + outputArray.push(new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + new asn1js.Integer({ value: this.version }) // EXPLICIT integer value + ] + })); + } + + outputArray.push(this.serialNumber); + outputArray.push(this.signature.toSchema()); + outputArray.push(this.issuer.toSchema()); + + outputArray.push(new asn1js.Sequence({ + value: [ + this.notBefore.toSchema(), + this.notAfter.toSchema() + ] + })); + + outputArray.push(this.subject.toSchema()); + outputArray.push(this.subjectPublicKeyInfo.toSchema()); + + if (this.issuerUniqueID) { + outputArray.push(new asn1js.Primitive({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + valueHex: this.issuerUniqueID + })); + } + if (this.subjectUniqueID) { + outputArray.push(new asn1js.Primitive({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 2 // [2] + }, + valueHex: this.subjectUniqueID + })); + } + + if (this.extensions) { + outputArray.push(new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 3 // [3] + }, + value: [new asn1js.Sequence({ + value: Array.from(this.extensions, o => o.toSchema()) + })] + })); + } + //#endregion + + //#region Create and return output sequence + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toSchema(encodeFlag = false): asn1js.Sequence { + let tbsSchema: asn1js.AsnType; + + // Decode stored TBS value + if (encodeFlag === false) { + if (!this.tbsView.byteLength) { // No stored certificate TBS part + return Certificate.schema().value[0]; + } + + const asn1 = asn1js.fromBER(this.tbsView); + AsnError.assert(asn1, "TBS Certificate"); + + tbsSchema = asn1.result; + } else { + // Create TBS schema via assembling from TBS parts + tbsSchema = this.encodeTBS(); + } + + // Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: [ + tbsSchema, + this.signatureAlgorithm.toSchema(), + this.signatureValue + ] + })); + } + + public toJSON(): CertificateJson { + const res: CertificateJson = { + tbs: pvtsutils.Convert.ToHex(this.tbsView), + version: this.version, + serialNumber: this.serialNumber.toJSON(), + signature: this.signature.toJSON(), + issuer: this.issuer.toJSON(), + notBefore: this.notBefore.toJSON(), + notAfter: this.notAfter.toJSON(), + subject: this.subject.toJSON(), + subjectPublicKeyInfo: this.subjectPublicKeyInfo.toJSON(), + signatureAlgorithm: this.signatureAlgorithm.toJSON(), + signatureValue: this.signatureValue.toJSON(), + }; + + if ((VERSION in this) && (this.version !== Certificate.defaultValues(VERSION))) { + res.version = this.version; + } + + if (this.issuerUniqueID) { + res.issuerUniqueID = pvtsutils.Convert.ToHex(this.issuerUniqueID); + } + + if (this.subjectUniqueID) { + res.subjectUniqueID = pvtsutils.Convert.ToHex(this.subjectUniqueID); + } + + if (this.extensions) { + res.extensions = Array.from(this.extensions, o => o.toJSON()); + } + + return res; + } + + /** + * Importing public key for current certificate + * @param parameters Public key export parameters + * @param crypto Crypto engine + * @returns WebCrypto public key + */ + public async getPublicKey(parameters?: CryptoEnginePublicKeyParams, crypto = common.getCrypto(true)): Promise<CryptoKey> { + return crypto.getPublicKey(this.subjectPublicKeyInfo, this.signatureAlgorithm, parameters); + } + + /** + * Get hash value for subject public key (default SHA-1) + * @param hashAlgorithm Hashing algorithm name + * @param crypto Crypto engine + * @returns Computed hash value from `Certificate.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey` + */ + public async getKeyHash(hashAlgorithm = "SHA-1", crypto = common.getCrypto(true)): Promise<ArrayBuffer> { + return crypto.digest({ name: hashAlgorithm }, this.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHexView); + } + + /** + * Make a signature for current value from TBS section + * @param privateKey Private key for SUBJECT_PUBLIC_KEY_INFO structure + * @param hashAlgorithm Hashing algorithm + * @param crypto Crypto engine + */ + public async sign(privateKey: CryptoKey, hashAlgorithm = "SHA-1", crypto = common.getCrypto(true)): Promise<void> { + // Initial checking + if (!privateKey) { + throw new Error("Need to provide a private key for signing"); + } + + // Get a "default parameters" for current algorithm and set correct signature algorithm + const signatureParameters = await crypto.getSignatureParameters(privateKey, hashAlgorithm); + const parameters = signatureParameters.parameters; + this.signature = signatureParameters.signatureAlgorithm; + this.signatureAlgorithm = signatureParameters.signatureAlgorithm; + + // Create TBS data for signing + this.tbsView = new Uint8Array(this.encodeTBS().toBER()); + + // Signing TBS data on provided private key + // TODO remove any + const signature = await crypto.signWithPrivateKey(this.tbsView, privateKey, parameters as any); + this.signatureValue = new asn1js.BitString({ valueHex: signature }); + } + + /** + * Verifies the certificate signature + * @param issuerCertificate + * @param crypto Crypto engine + */ + public async verify(issuerCertificate?: Certificate, crypto = common.getCrypto(true)): Promise<boolean> { + let subjectPublicKeyInfo: PublicKeyInfo | undefined; + + // Set correct SUBJECT_PUBLIC_KEY_INFO value + if (issuerCertificate) { + subjectPublicKeyInfo = issuerCertificate.subjectPublicKeyInfo; + } else if (this.issuer.isEqual(this.subject)) { + // Self-signed certificate + subjectPublicKeyInfo = this.subjectPublicKeyInfo; + } + + if (!(subjectPublicKeyInfo instanceof PublicKeyInfo)) { + throw new Error("Please provide issuer certificate as a parameter"); + } + + return crypto.verifyWithPublicKey(this.tbsView, this.signatureValue, subjectPublicKeyInfo, this.signatureAlgorithm); + } + +} + +/** + * Check CA flag for the certificate + * @param cert Certificate to find CA flag for + * @returns Returns {@link Certificate} if `cert` is CA certificate otherwise return `null` + */ +export function checkCA(cert: Certificate, signerCert: Certificate | null = null): Certificate | null { + //#region Do not include signer's certificate + if (signerCert && cert.issuer.isEqual(signerCert.issuer) && cert.serialNumber.isEqual(signerCert.serialNumber)) { + return null; + } + //#endregion + + let isCA = false; + + if (cert.extensions) { + for (const extension of cert.extensions) { + if (extension.extnID === id_BasicConstraints && extension.parsedValue instanceof BasicConstraints) { + if (extension.parsedValue.cA) { + isCA = true; + break; + } + } + } + } + + if (isCA) { + return cert; + } + + return null; +} diff --git a/third_party/js/PKI.js/src/CertificateChainValidationEngine.ts b/third_party/js/PKI.js/src/CertificateChainValidationEngine.ts new file mode 100644 index 0000000000..66d57b035c --- /dev/null +++ b/third_party/js/PKI.js/src/CertificateChainValidationEngine.ts @@ -0,0 +1,1828 @@ +import * as asn1js from "asn1js"; +import * as pvtsutils from "pvtsutils"; +import * as pvutils from "pvutils"; +import { AuthorityKeyIdentifier } from "./AuthorityKeyIdentifier"; +import { BasicOCSPResponse } from "./BasicOCSPResponse"; +import { Certificate } from "./Certificate"; +import { CertificateRevocationList } from "./CertificateRevocationList"; +import * as common from "./common"; +import * as Helpers from "./Helpers"; +import { GeneralName } from "./GeneralName"; +import { id_AnyPolicy, id_AuthorityInfoAccess, id_AuthorityKeyIdentifier, id_BasicConstraints, id_CertificatePolicies, id_CRLDistributionPoints, id_FreshestCRL, id_InhibitAnyPolicy, id_KeyUsage, id_NameConstraints, id_PolicyConstraints, id_PolicyMappings, id_SubjectAltName, id_SubjectKeyIdentifier } from "./ObjectIdentifiers"; +import { RelativeDistinguishedNames } from "./RelativeDistinguishedNames"; +import { GeneralSubtree } from "./GeneralSubtree"; +import { EMPTY_STRING } from "./constants"; + +const TRUSTED_CERTS = "trustedCerts"; +const CERTS = "certs"; +const CRLS = "crls"; +const OCSPS = "ocsps"; +const CHECK_DATE = "checkDate"; +const FIND_ORIGIN = "findOrigin"; +const FIND_ISSUER = "findIssuer"; + + +export enum ChainValidationCode { + unknown = -1, + success = 0, + noRevocation = 11, + noPath = 60, + noValidPath = 97, +} + +export class ChainValidationError extends Error { + + public static readonly NAME = "ChainValidationError"; + + public code: ChainValidationCode; + + constructor(code: ChainValidationCode, message: string) { + super(message); + + this.name = ChainValidationError.NAME; + this.code = code; + this.message = message; + } + +} + +export interface CertificateChainValidationEngineVerifyResult { + result: boolean; + resultCode: number; + resultMessage: string; + error?: Error | ChainValidationError; + authConstrPolicies?: string[]; + userConstrPolicies?: string[]; + explicitPolicyIndicator?: boolean; + policyMappings?: string[]; + certificatePath?: Certificate[]; +} + +export type FindOriginCallback = (certificate: Certificate, validationEngine: CertificateChainValidationEngine) => string; +export type FindIssuerCallback = (certificate: Certificate, validationEngine: CertificateChainValidationEngine, crypto?: common.ICryptoEngine) => Promise<Certificate[]>; + +export interface CertificateChainValidationEngineParameters { + trustedCerts?: Certificate[]; + certs?: Certificate[]; + crls?: CertificateRevocationList[]; + ocsps?: BasicOCSPResponse[]; + checkDate?: Date; + findOrigin?: FindOriginCallback; + findIssuer?: FindIssuerCallback; +} +interface CrlAndCertificate { + crl: CertificateRevocationList; + certificate: Certificate; +} + +interface FindCrlResult { + status: number; + statusMessage: string; + result?: CrlAndCertificate[]; +} + +export interface CertificateChainValidationEngineVerifyParams { + initialPolicySet?: string[]; + initialExplicitPolicy?: boolean; + initialPolicyMappingInhibit?: boolean; + initialInhibitPolicy?: boolean; + initialPermittedSubtreesSet?: GeneralSubtree[]; + initialExcludedSubtreesSet?: GeneralSubtree[]; + initialRequiredNameForms?: GeneralSubtree[]; + passedWhenNotRevValues?: boolean; +} + +/** + * Returns `true` if the certificate is in the trusted list, otherwise `false` + * @param cert A certificate that is expected to be in the trusted list + * @param trustedList List of trusted certificates + * @returns + */ +function isTrusted(cert: Certificate, trustedList: Certificate[]): boolean { + for (let i = 0; i < trustedList.length; i++) { + if (pvtsutils.BufferSourceConverter.isEqual(cert.tbsView, trustedList[i].tbsView)) { + return true; + } + } + + return false; +} + +/** + * Represents a chain-building engine for {@link Certificate} certificates. + * + * @example + * ```js The following example demonstrates how to verify certificate chain + * const rootCa = pkijs.Certificate.fromBER(certRaw1); + * const intermediateCa = pkijs.Certificate.fromBER(certRaw2); + * const leafCert = pkijs.Certificate.fromBER(certRaw3); + * const crl1 = pkijs.CertificateRevocationList.fromBER(crlRaw1); + * const ocsp1 = pkijs.BasicOCSPResponse.fromBER(ocspRaw1); + * + * const chainEngine = new pkijs.CertificateChainValidationEngine({ + * certs: [rootCa, intermediateCa, leafCert], + * crls: [crl1], + * ocsps: [ocsp1], + * checkDate: new Date("2015-07-13"), // optional + * trustedCerts: [rootCa], + * }); + * + * const chain = await chainEngine.verify(); + * ``` + */ +export class CertificateChainValidationEngine { + + /** + * Array of pre-defined trusted (by user) certificates + */ + public trustedCerts: Certificate[]; + /** + * Array with certificate chain. Could be only one end-user certificate in there! + */ + public certs: Certificate[]; + /** + * Array of all CRLs for all certificates from certificate chain + */ + public crls: CertificateRevocationList[]; + /** + * Array of all OCSP responses + */ + public ocsps: BasicOCSPResponse[]; + /** + * The date at which the check would be + */ + public checkDate: Date; + /** + * The date at which the check would be + */ + public findOrigin: FindOriginCallback; + /** + * The date at which the check would be + */ + public findIssuer: FindIssuerCallback; + + /** + * Constructor for CertificateChainValidationEngine class + * @param parameters + */ + constructor(parameters: CertificateChainValidationEngineParameters = {}) { + //#region Internal properties of the object + this.trustedCerts = pvutils.getParametersValue(parameters, TRUSTED_CERTS, this.defaultValues(TRUSTED_CERTS)); + this.certs = pvutils.getParametersValue(parameters, CERTS, this.defaultValues(CERTS)); + this.crls = pvutils.getParametersValue(parameters, CRLS, this.defaultValues(CRLS)); + this.ocsps = pvutils.getParametersValue(parameters, OCSPS, this.defaultValues(OCSPS)); + this.checkDate = pvutils.getParametersValue(parameters, CHECK_DATE, this.defaultValues(CHECK_DATE)); + this.findOrigin = pvutils.getParametersValue(parameters, FIND_ORIGIN, this.defaultValues(FIND_ORIGIN)); + this.findIssuer = pvutils.getParametersValue(parameters, FIND_ISSUER, this.defaultValues(FIND_ISSUER)); + //#endregion + } + + public static defaultFindOrigin(certificate: Certificate, validationEngine: CertificateChainValidationEngine): string { + //#region Firstly encode TBS for certificate + if (certificate.tbsView.byteLength === 0) { + certificate.tbsView = new Uint8Array(certificate.encodeTBS().toBER()); + } + //#endregion + + //#region Search in Intermediate Certificates + for (const localCert of validationEngine.certs) { + //#region Firstly encode TBS for certificate + if (localCert.tbsView.byteLength === 0) { + localCert.tbsView = new Uint8Array(localCert.encodeTBS().toBER()); + } + //#endregion + + if (pvtsutils.BufferSourceConverter.isEqual(certificate.tbsView, localCert.tbsView)) + return "Intermediate Certificates"; + } + //#endregion + + //#region Search in Trusted Certificates + for (const trustedCert of validationEngine.trustedCerts) { + //#region Firstly encode TBS for certificate + if (trustedCert.tbsView.byteLength === 0) + trustedCert.tbsView = new Uint8Array(trustedCert.encodeTBS().toBER()); + //#endregion + + if (pvtsutils.BufferSourceConverter.isEqual(certificate.tbsView, trustedCert.tbsView)) + return "Trusted Certificates"; + } + //#endregion + + return "Unknown"; + } + + public async defaultFindIssuer(certificate: Certificate, validationEngine: CertificateChainValidationEngine, crypto = common.getCrypto(true)): Promise<Certificate[]> { + //#region Initial variables + const result: Certificate[] = []; + + let keyIdentifier: asn1js.OctetString | null = null; + let authorityCertIssuer: GeneralName[] | null = null; + let authorityCertSerialNumber: asn1js.Integer | null = null; + //#endregion + + //#region Speed-up searching in case of self-signed certificates + if (certificate.subject.isEqual(certificate.issuer)) { + try { + const verificationResult = await certificate.verify(undefined, crypto); + if (verificationResult) { + return [certificate]; + } + } + catch (ex) { + // nothing + } + } + //#endregion + + //#region Find values to speed-up search + if (certificate.extensions) { + for (const extension of certificate.extensions) { + if (extension.extnID === id_AuthorityKeyIdentifier && extension.parsedValue instanceof AuthorityKeyIdentifier) { + if (extension.parsedValue.keyIdentifier) { + keyIdentifier = extension.parsedValue.keyIdentifier; + } else { + if (extension.parsedValue.authorityCertIssuer) { + authorityCertIssuer = extension.parsedValue.authorityCertIssuer; + } + if (extension.parsedValue.authorityCertSerialNumber) { + authorityCertSerialNumber = extension.parsedValue.authorityCertSerialNumber; + } + } + + break; + } + } + } + //#endregion + + // Aux function + function checkCertificate(possibleIssuer: Certificate): void { + //#region Firstly search for appropriate extensions + if (keyIdentifier !== null) { + if (possibleIssuer.extensions) { + let extensionFound = false; + + for (const extension of possibleIssuer.extensions) { + if (extension.extnID === id_SubjectKeyIdentifier && extension.parsedValue) { + extensionFound = true; + + if (pvtsutils.BufferSourceConverter.isEqual(extension.parsedValue.valueBlock.valueHex, keyIdentifier.valueBlock.valueHexView)) { + result.push(possibleIssuer); + } + + break; + } + } + + if (extensionFound) { + return; + } + } + } + //#endregion + + //#region Now search for authorityCertSerialNumber + let authorityCertSerialNumberEqual = false; + + if (authorityCertSerialNumber !== null) + authorityCertSerialNumberEqual = possibleIssuer.serialNumber.isEqual(authorityCertSerialNumber); + //#endregion + + //#region And at least search for Issuer data + if (authorityCertIssuer !== null) { + if (possibleIssuer.subject.isEqual(authorityCertIssuer)) { + if (authorityCertSerialNumberEqual) + result.push(possibleIssuer); + } + } + else { + if (certificate.issuer.isEqual(possibleIssuer.subject)) + result.push(possibleIssuer); + } + //#endregion + } + + // Search in Trusted Certificates + for (const trustedCert of validationEngine.trustedCerts) { + checkCertificate(trustedCert); + } + + // Search in Intermediate Certificates + for (const intermediateCert of validationEngine.certs) { + checkCertificate(intermediateCert); + } + + // Now perform certificate verification checking + for (let i = 0; i < result.length; i++) { + try { + const verificationResult = await certificate.verify(result[i], crypto); + if (verificationResult === false) + result.splice(i, 1); + } + catch (ex) { + result.splice(i, 1); // Something wrong, remove the certificate + } + } + + return result; + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public defaultValues(memberName: typeof TRUSTED_CERTS): Certificate[]; + public defaultValues(memberName: typeof CERTS): Certificate[]; + public defaultValues(memberName: typeof CRLS): CertificateRevocationList[]; + public defaultValues(memberName: typeof OCSPS): BasicOCSPResponse[]; + public defaultValues(memberName: typeof CHECK_DATE): Date; + public defaultValues(memberName: typeof FIND_ORIGIN): FindOriginCallback; + public defaultValues(memberName: typeof FIND_ISSUER): FindIssuerCallback; + public defaultValues(memberName: string): any { + switch (memberName) { + case TRUSTED_CERTS: + return []; + case CERTS: + return []; + case CRLS: + return []; + case OCSPS: + return []; + case CHECK_DATE: + return new Date(); + case FIND_ORIGIN: + return CertificateChainValidationEngine.defaultFindOrigin; + case FIND_ISSUER: + return this.defaultFindIssuer; + default: + throw new Error(`Invalid member name for CertificateChainValidationEngine class: ${memberName}`); + } + } + + public async sort(passedWhenNotRevValues = false, crypto = common.getCrypto(true)): Promise<Certificate[]> { + // Initial variables + const localCerts: Certificate[] = []; + + //#region Building certificate path + const buildPath = async (certificate: Certificate, crypto: common.ICryptoEngine): Promise<Certificate[][]> => { + const result: Certificate[][] = []; + + // Aux function checking array for unique elements + function checkUnique(array: Certificate[]): boolean { + let unique = true; + + for (let i = 0; i < array.length; i++) { + for (let j = 0; j < array.length; j++) { + if (j === i) + continue; + + if (array[i] === array[j]) { + unique = false; + break; + } + } + + if (!unique) + break; + } + + return unique; + } + + if (isTrusted(certificate, this.trustedCerts)) { + return [[certificate]]; + } + + const findIssuerResult = await this.findIssuer(certificate, this, crypto); + if (findIssuerResult.length === 0) { + throw new Error("No valid certificate paths found"); + } + + for (let i = 0; i < findIssuerResult.length; i++) { + if (pvtsutils.BufferSourceConverter.isEqual(findIssuerResult[i].tbsView, certificate.tbsView)) { + result.push([findIssuerResult[i]]); + continue; + } + + const buildPathResult = await buildPath(findIssuerResult[i], crypto); + + for (let j = 0; j < buildPathResult.length; j++) { + const copy = buildPathResult[j].slice(); + copy.splice(0, 0, findIssuerResult[i]); + + if (checkUnique(copy)) + result.push(copy); + else + result.push(buildPathResult[j]); + } + } + + return result; + }; + //#endregion + + //#region Find CRL for specific certificate + const findCRL = async (certificate: Certificate): Promise<FindCrlResult> => { + //#region Initial variables + const issuerCertificates: Certificate[] = []; + const crls: CertificateRevocationList[] = []; + const crlsAndCertificates: CrlAndCertificate[] = []; + //#endregion + + //#region Find all possible CRL issuers + issuerCertificates.push(...localCerts.filter(element => certificate.issuer.isEqual(element.subject))); + if (issuerCertificates.length === 0) { + return { + status: 1, + statusMessage: "No certificate's issuers" + }; + } + //#endregion + + //#region Find all CRLs for certificate's issuer + crls.push(...this.crls.filter(o => o.issuer.isEqual(certificate.issuer))); + if (crls.length === 0) { + return { + status: 2, + statusMessage: "No CRLs for specific certificate issuer" + }; + } + //#endregion + + //#region Find specific certificate of issuer for each CRL + for (let i = 0; i < crls.length; i++) { + const crl = crls[i]; + //#region Check "nextUpdate" for the CRL + // The "nextUpdate" is older than CHECK_DATE. + // Thus we should do have another, updated CRL. + // Thus the CRL assumed to be invalid. + if (crl.nextUpdate && crl.nextUpdate.value < this.checkDate) { + continue; + } + //#endregion + + for (let j = 0; j < issuerCertificates.length; j++) { + try { + const result = await crls[i].verify({ issuerCertificate: issuerCertificates[j] }, crypto); + if (result) { + crlsAndCertificates.push({ + crl: crls[i], + certificate: issuerCertificates[j] + }); + + break; + } + } + catch (ex) { + // nothing + } + } + } + //#endregion + + if (crlsAndCertificates.length) { + return { + status: 0, + statusMessage: EMPTY_STRING, + result: crlsAndCertificates + }; + } + + return { + status: 3, + statusMessage: "No valid CRLs found" + }; + }; + //#endregion + + //#region Find OCSP for specific certificate + const findOCSP = async (certificate: Certificate, issuerCertificate: Certificate): Promise<number> => { + //#region Get hash algorithm from certificate + const hashAlgorithm = crypto.getAlgorithmByOID<any>(certificate.signatureAlgorithm.algorithmId); + if (!hashAlgorithm.name) { + return 1; + } + if (!hashAlgorithm.hash) { + return 1; + } + //#endregion + + //#region Search for OCSP response for the certificate + for (let i = 0; i < this.ocsps.length; i++) { + const ocsp = this.ocsps[i]; + const result = await ocsp.getCertificateStatus(certificate, issuerCertificate, crypto); + if (result.isForCertificate) { + if (result.status === 0) + return 0; + + return 1; + } + } + //#endregion + + return 2; + }; + //#endregion + + //#region Check for certificate to be CA + async function checkForCA(certificate: Certificate, needToCheckCRL = false) { + //#region Initial variables + let isCA = false; + let mustBeCA = false; + let keyUsagePresent = false; + let cRLSign = false; + //#endregion + + if (certificate.extensions) { + for (let j = 0; j < certificate.extensions.length; j++) { + const extension = certificate.extensions[j]; + if (extension.critical && !extension.parsedValue) { + return { + result: false, + resultCode: 6, + resultMessage: `Unable to parse critical certificate extension: ${extension.extnID}` + }; + } + + if (extension.extnID === id_KeyUsage) // KeyUsage + { + keyUsagePresent = true; + + const view = new Uint8Array(extension.parsedValue.valueBlock.valueHex); + + if ((view[0] & 0x04) === 0x04) // Set flag "keyCertSign" + mustBeCA = true; + + if ((view[0] & 0x02) === 0x02) // Set flag "cRLSign" + cRLSign = true; + } + + if (extension.extnID === id_BasicConstraints) // BasicConstraints + { + if ("cA" in extension.parsedValue) { + if (extension.parsedValue.cA === true) + isCA = true; + } + } + } + + if ((mustBeCA === true) && (isCA === false)) { + return { + result: false, + resultCode: 3, + resultMessage: "Unable to build certificate chain - using \"keyCertSign\" flag set without BasicConstraints" + }; + } + + if ((keyUsagePresent === true) && (isCA === true) && (mustBeCA === false)) { + return { + result: false, + resultCode: 4, + resultMessage: "Unable to build certificate chain - \"keyCertSign\" flag was not set" + }; + } + + if ((isCA === true) && (keyUsagePresent === true) && ((needToCheckCRL) && (cRLSign === false))) { + return { + result: false, + resultCode: 5, + resultMessage: "Unable to build certificate chain - intermediate certificate must have \"cRLSign\" key usage flag" + }; + } + } + + if (isCA === false) { + return { + result: false, + resultCode: 7, + resultMessage: "Unable to build certificate chain - more than one possible end-user certificate" + }; + } + + return { + result: true, + resultCode: 0, + resultMessage: EMPTY_STRING + }; + } + //#endregion + + //#region Basic check for certificate path + const basicCheck = async (path: Certificate[], checkDate: Date): Promise<{ result: boolean; resultCode?: number; resultMessage?: string; }> => { + //#region Check that all dates are valid + for (let i = 0; i < path.length; i++) { + if ((path[i].notBefore.value > checkDate) || + (path[i].notAfter.value < checkDate)) { + return { + result: false, + resultCode: 8, + resultMessage: "The certificate is either not yet valid or expired" + }; + } + } + //#endregion + + //#region Check certificate name chain + + // We should have at least two certificates: end entity and trusted root + if (path.length < 2) { + return { + result: false, + resultCode: 9, + resultMessage: "Too short certificate path" + }; + } + + for (let i = (path.length - 2); i >= 0; i--) { + //#region Check that we do not have a "self-signed" certificate + if (path[i].issuer.isEqual(path[i].subject) === false) { + if (path[i].issuer.isEqual(path[i + 1].subject) === false) { + return { + result: false, + resultCode: 10, + resultMessage: "Incorrect name chaining" + }; + } + } + //#endregion + } + //#endregion + + //#region Check each certificate (except "trusted root") to be non-revoked + if ((this.crls.length !== 0) || (this.ocsps.length !== 0)) // If CRLs and OCSPs are empty then we consider all certificates to be valid + { + for (let i = 0; i < (path.length - 1); i++) { + //#region Initial variables + let ocspResult = 2; + let crlResult: FindCrlResult = { + status: 0, + statusMessage: EMPTY_STRING + }; + //#endregion + + //#region Check OCSPs first + if (this.ocsps.length !== 0) { + ocspResult = await findOCSP(path[i], path[i + 1]); + + switch (ocspResult) { + case 0: + continue; + case 1: + return { + result: false, + resultCode: 12, + resultMessage: "One of certificates was revoked via OCSP response" + }; + case 2: // continue to check the certificate with CRL + break; + default: + } + } + //#endregion + + //#region Check CRLs + if (this.crls.length !== 0) { + crlResult = await findCRL(path[i]); + + if (crlResult.status === 0 && crlResult.result) { + for (let j = 0; j < crlResult.result.length; j++) { + //#region Check that the CRL issuer certificate have not been revoked + const isCertificateRevoked = crlResult.result[j].crl.isCertificateRevoked(path[i]); + if (isCertificateRevoked) { + return { + result: false, + resultCode: 12, + resultMessage: "One of certificates had been revoked" + }; + } + //#endregion + + //#region Check that the CRL issuer certificate is a CA certificate + const isCertificateCA = await checkForCA(crlResult.result[j].certificate, true); + if (isCertificateCA.result === false) { + return { + result: false, + resultCode: 13, + resultMessage: "CRL issuer certificate is not a CA certificate or does not have crlSign flag" + }; + } + //#endregion + } + } else { + if (passedWhenNotRevValues === false) { + throw new ChainValidationError(ChainValidationCode.noRevocation, `No revocation values found for one of certificates: ${crlResult.statusMessage}`); + } + } + } else { + if (ocspResult === 2) { + return { + result: false, + resultCode: 11, + resultMessage: "No revocation values found for one of certificates" + }; + } + } + //#endregion + + //#region Check we do have links to revocation values inside issuer's certificate + if ((ocspResult === 2) && (crlResult.status === 2) && passedWhenNotRevValues) { + const issuerCertificate = path[i + 1]; + let extensionFound = false; + + if (issuerCertificate.extensions) { + for (const extension of issuerCertificate.extensions) { + switch (extension.extnID) { + case id_CRLDistributionPoints: + case id_FreshestCRL: + case id_AuthorityInfoAccess: + extensionFound = true; + break; + default: + } + } + } + + if (extensionFound) { + throw new ChainValidationError(ChainValidationCode.noRevocation, `No revocation values found for one of certificates: ${crlResult.statusMessage}`); + } + } + //#endregion + } + } + //#endregion + + //#region Check each certificate (except "end entity") in the path to be a CA certificate + for (const [i, cert] of path.entries()) { + if (!i) { + // Skip entity certificate + continue; + } + + const result = await checkForCA(cert); + if (!result.result) { + return { + result: false, + resultCode: 14, + resultMessage: "One of intermediate certificates is not a CA certificate" + }; + } + } + //#endregion + + return { + result: true + }; + }; + //#endregion + + //#region Do main work + //#region Initialize "localCerts" by value of "this.certs" + "this.trustedCerts" arrays + localCerts.push(...this.trustedCerts); + localCerts.push(...this.certs); + //#endregion + + //#region Check all certificates for been unique + for (let i = 0; i < localCerts.length; i++) { + for (let j = 0; j < localCerts.length; j++) { + if (i === j) + continue; + + if (pvtsutils.BufferSourceConverter.isEqual(localCerts[i].tbsView, localCerts[j].tbsView)) { + localCerts.splice(j, 1); + i = 0; + break; + } + } + } + //#endregion + + const leafCert = localCerts[localCerts.length - 1]; + + //#region Initial variables + let result; + const certificatePath = [leafCert]; // The "end entity" certificate must be the least in CERTS array + //#endregion + + //#region Build path for "end entity" certificate + result = await buildPath(leafCert, crypto); + if (result.length === 0) { + throw new ChainValidationError(ChainValidationCode.noPath, "Unable to find certificate path"); + } + //#endregion + + //#region Exclude certificate paths not ended with "trusted roots" + for (let i = 0; i < result.length; i++) { + let found = false; + + for (let j = 0; j < (result[i]).length; j++) { + const certificate = (result[i])[j]; + + for (let k = 0; k < this.trustedCerts.length; k++) { + if (pvtsutils.BufferSourceConverter.isEqual(certificate.tbsView, this.trustedCerts[k].tbsView)) { + found = true; + break; + } + } + + if (found) + break; + } + + if (!found) { + result.splice(i, 1); + i = 0; + } + } + + if (result.length === 0) { + throw new ChainValidationError(ChainValidationCode.noValidPath, "No valid certificate paths found"); + } + //#endregion + + //#region Find shortest certificate path (for the moment it is the only criteria) + let shortestLength = result[0].length; + let shortestIndex = 0; + + for (let i = 0; i < result.length; i++) { + if (result[i].length < shortestLength) { + shortestLength = result[i].length; + shortestIndex = i; + } + } + //#endregion + + //#region Create certificate path for basic check + for (let i = 0; i < result[shortestIndex].length; i++) + certificatePath.push((result[shortestIndex])[i]); + //#endregion + + //#region Perform basic checking for all certificates in the path + result = await basicCheck(certificatePath, this.checkDate); + if (result.result === false) + throw result; + //#endregion + + return certificatePath; + //#endregion + } + + /** + * Major verification function for certificate chain. + * @param parameters + * @param crypto Crypto engine + * @returns + */ + async verify(parameters: CertificateChainValidationEngineVerifyParams = {}, crypto = common.getCrypto(true)): Promise<CertificateChainValidationEngineVerifyResult> { + //#region Auxiliary functions for name constraints checking + /** + * Compare two dNSName values + * @param name DNS from name + * @param constraint Constraint for DNS from name + * @returns Boolean result - valid or invalid the "name" against the "constraint" + */ + function compareDNSName(name: string, constraint: string): boolean { + //#region Make a "string preparation" for both name and constrain + const namePrepared = Helpers.stringPrep(name); + const constraintPrepared = Helpers.stringPrep(constraint); + //#endregion + + //#region Make a "splitted" versions of "constraint" and "name" + const nameSplitted = namePrepared.split("."); + const constraintSplitted = constraintPrepared.split("."); + //#endregion + + //#region Length calculation and additional check + const nameLen = nameSplitted.length; + const constrLen = constraintSplitted.length; + + if ((nameLen === 0) || (constrLen === 0) || (nameLen < constrLen)) { + return false; + } + //#endregion + + //#region Check that no part of "name" has zero length + for (let i = 0; i < nameLen; i++) { + if (nameSplitted[i].length === 0) { + return false; + } + } + //#endregion + + //#region Check that no part of "constraint" has zero length + for (let i = 0; i < constrLen; i++) { + if (constraintSplitted[i].length === 0) { + if (i === 0) { + if (constrLen === 1) { + return false; + } + + continue; + } + + return false; + } + } + //#endregion + + //#region Check that "name" has a tail as "constraint" + + for (let i = 0; i < constrLen; i++) { + if (constraintSplitted[constrLen - 1 - i].length === 0) { + continue; + } + + if (nameSplitted[nameLen - 1 - i].localeCompare(constraintSplitted[constrLen - 1 - i]) !== 0) { + return false; + } + } + //#endregion + + return true; + } + + /** + * Compare two rfc822Name values + * @param name E-mail address from name + * @param constraint Constraint for e-mail address from name + * @returns Boolean result - valid or invalid the "name" against the "constraint" + */ + function compareRFC822Name(name: string, constraint: string): boolean { + //#region Make a "string preparation" for both name and constrain + const namePrepared = Helpers.stringPrep(name); + const constraintPrepared = Helpers.stringPrep(constraint); + //#endregion + + //#region Make a "splitted" versions of "constraint" and "name" + const nameSplitted = namePrepared.split("@"); + const constraintSplitted = constraintPrepared.split("@"); + //#endregion + + //#region Splitted array length checking + if ((nameSplitted.length === 0) || (constraintSplitted.length === 0) || (nameSplitted.length < constraintSplitted.length)) + return false; + //#endregion + + if (constraintSplitted.length === 1) { + const result = compareDNSName(nameSplitted[1], constraintSplitted[0]); + + if (result) { + //#region Make a "splitted" versions of domain name from "constraint" and "name" + const ns = nameSplitted[1].split("."); + const cs = constraintSplitted[0].split("."); + //#endregion + + if (cs[0].length === 0) + return true; + + return ns.length === cs.length; + } + + return false; + } + + return (namePrepared.localeCompare(constraintPrepared) === 0); + } + + /** + * Compare two uniformResourceIdentifier values + * @param name uniformResourceIdentifier from name + * @param constraint Constraint for uniformResourceIdentifier from name + * @returns Boolean result - valid or invalid the "name" against the "constraint" + */ + function compareUniformResourceIdentifier(name: string, constraint: string): boolean { + //#region Make a "string preparation" for both name and constrain + let namePrepared = Helpers.stringPrep(name); + const constraintPrepared = Helpers.stringPrep(constraint); + //#endregion + + //#region Find out a major URI part to compare with + const ns = namePrepared.split("/"); + const cs = constraintPrepared.split("/"); + + if (cs.length > 1) // Malformed constraint + return false; + + if (ns.length > 1) // Full URI string + { + for (let i = 0; i < ns.length; i++) { + if ((ns[i].length > 0) && (ns[i].charAt(ns[i].length - 1) !== ":")) { + const nsPort = ns[i].split(":"); + namePrepared = nsPort[0]; + break; + } + } + } + //#endregion + + const result = compareDNSName(namePrepared, constraintPrepared); + + if (result) { + //#region Make a "splitted" versions of "constraint" and "name" + const nameSplitted = namePrepared.split("."); + const constraintSplitted = constraintPrepared.split("."); + //#endregion + + if (constraintSplitted[0].length === 0) + return true; + + return nameSplitted.length === constraintSplitted.length; + } + + return false; + } + + /** + * Compare two iPAddress values + * @param name iPAddress from name + * @param constraint Constraint for iPAddress from name + * @returns Boolean result - valid or invalid the "name" against the "constraint" + */ + function compareIPAddress(name: asn1js.OctetString, constraint: asn1js.OctetString): boolean { + //#region Common variables + const nameView = name.valueBlock.valueHexView; + const constraintView = constraint.valueBlock.valueHexView; + //#endregion + + //#region Work with IPv4 addresses + if ((nameView.length === 4) && (constraintView.length === 8)) { + for (let i = 0; i < 4; i++) { + if ((nameView[i] ^ constraintView[i]) & constraintView[i + 4]) + return false; + } + + return true; + } + //#endregion + + //#region Work with IPv6 addresses + if ((nameView.length === 16) && (constraintView.length === 32)) { + for (let i = 0; i < 16; i++) { + if ((nameView[i] ^ constraintView[i]) & constraintView[i + 16]) + return false; + } + + return true; + } + //#endregion + + return false; + } + + /** + * Compare two directoryName values + * @param name directoryName from name + * @param constraint Constraint for directoryName from name + * @returns Boolean result - valid or invalid the "name" against the "constraint" + */ + function compareDirectoryName(name: RelativeDistinguishedNames, constraint: RelativeDistinguishedNames): boolean { + //#region Initial check + if ((name.typesAndValues.length === 0) || (constraint.typesAndValues.length === 0)) + return true; + + if (name.typesAndValues.length < constraint.typesAndValues.length) + return false; + //#endregion + + //#region Initial variables + let result = true; + let nameStart = 0; + //#endregion + + for (let i = 0; i < constraint.typesAndValues.length; i++) { + let localResult = false; + + for (let j = nameStart; j < name.typesAndValues.length; j++) { + localResult = name.typesAndValues[j].isEqual(constraint.typesAndValues[i]); + + if (name.typesAndValues[j].type === constraint.typesAndValues[i].type) + result = result && localResult; + + if (localResult === true) { + if ((nameStart === 0) || (nameStart === j)) { + nameStart = j + 1; + break; + } + else // Structure of "name" must be the same with "constraint" + return false; + } + } + + if (localResult === false) + return false; + } + + return (nameStart === 0) ? false : result; + } + //#endregion + + try { + //#region Initial checks + if (this.certs.length === 0) + throw new Error("Empty certificate array"); + //#endregion + + //#region Get input variables + const passedWhenNotRevValues = parameters.passedWhenNotRevValues || false; + + const initialPolicySet = parameters.initialPolicySet || [id_AnyPolicy]; + + const initialExplicitPolicy = parameters.initialExplicitPolicy || false; + const initialPolicyMappingInhibit = parameters.initialPolicyMappingInhibit || false; + const initialInhibitPolicy = parameters.initialInhibitPolicy || false; + + const initialPermittedSubtreesSet = parameters.initialPermittedSubtreesSet || []; + const initialExcludedSubtreesSet = parameters.initialExcludedSubtreesSet || []; + const initialRequiredNameForms = parameters.initialRequiredNameForms || []; + + let explicitPolicyIndicator = initialExplicitPolicy; + let policyMappingInhibitIndicator = initialPolicyMappingInhibit; + let inhibitAnyPolicyIndicator = initialInhibitPolicy; + + const pendingConstraints = [ + false, // For "explicitPolicyPending" + false, // For "policyMappingInhibitPending" + false, // For "inhibitAnyPolicyPending" + ]; + + let explicitPolicyPending = 0; + let policyMappingInhibitPending = 0; + let inhibitAnyPolicyPending = 0; + + let permittedSubtrees = initialPermittedSubtreesSet; + let excludedSubtrees = initialExcludedSubtreesSet; + const requiredNameForms = initialRequiredNameForms; + + let pathDepth = 1; + //#endregion + + //#region Sorting certificates in the chain array + this.certs = await this.sort(passedWhenNotRevValues, crypto); + //#endregion + + //#region Work with policies + //#region Support variables + const allPolicies: string[] = []; // Array of all policies (string values) + allPolicies.push(id_AnyPolicy); // Put "anyPolicy" at first place + + const policiesAndCerts = []; // In fact "array of array" where rows are for each specific policy, column for each certificate and value is "true/false" + + const anyPolicyArray = new Array(this.certs.length - 1); // Minus "trusted anchor" + for (let ii = 0; ii < (this.certs.length - 1); ii++) + anyPolicyArray[ii] = true; + + policiesAndCerts.push(anyPolicyArray); + + const policyMappings = new Array(this.certs.length - 1); // Array of "PolicyMappings" for each certificate + const certPolicies = new Array(this.certs.length - 1); // Array of "CertificatePolicies" for each certificate + + let explicitPolicyStart = (explicitPolicyIndicator) ? (this.certs.length - 1) : (-1); + //#endregion + + //#region Gather all necessary information from certificate chain + for (let i = (this.certs.length - 2); i >= 0; i--, pathDepth++) { + const cert = this.certs[i]; + if (cert.extensions) { + //#region Get information about certificate extensions + for (let j = 0; j < cert.extensions.length; j++) { + const extension = cert.extensions[j]; + //#region CertificatePolicies + if (extension.extnID === id_CertificatePolicies) { + certPolicies[i] = extension.parsedValue; + + //#region Remove entry from "anyPolicies" for the certificate + for (let s = 0; s < allPolicies.length; s++) { + if (allPolicies[s] === id_AnyPolicy) { + delete (policiesAndCerts[s])[i]; + break; + } + } + //#endregion + + for (let k = 0; k < extension.parsedValue.certificatePolicies.length; k++) { + let policyIndex = (-1); + const policyId = extension.parsedValue.certificatePolicies[k].policyIdentifier; + + //#region Try to find extension in "allPolicies" array + for (let s = 0; s < allPolicies.length; s++) { + if (policyId === allPolicies[s]) { + policyIndex = s; + break; + } + } + //#endregion + + if (policyIndex === (-1)) { + allPolicies.push(policyId); + + const certArray = new Array(this.certs.length - 1); + certArray[i] = true; + + policiesAndCerts.push(certArray); + } + else + (policiesAndCerts[policyIndex])[i] = true; + } + } + //#endregion + + //#region PolicyMappings + if (extension.extnID === id_PolicyMappings) { + if (policyMappingInhibitIndicator) { + return { + result: false, + resultCode: 98, + resultMessage: "Policy mapping prohibited" + }; + } + + policyMappings[i] = extension.parsedValue; + } + //#endregion + + //#region PolicyConstraints + if (extension.extnID === id_PolicyConstraints) { + if (explicitPolicyIndicator === false) { + //#region requireExplicitPolicy + if (extension.parsedValue.requireExplicitPolicy === 0) { + explicitPolicyIndicator = true; + explicitPolicyStart = i; + } + else { + if (pendingConstraints[0] === false) { + pendingConstraints[0] = true; + explicitPolicyPending = extension.parsedValue.requireExplicitPolicy; + } + else + explicitPolicyPending = (explicitPolicyPending > extension.parsedValue.requireExplicitPolicy) ? extension.parsedValue.requireExplicitPolicy : explicitPolicyPending; + } + //#endregion + + //#region inhibitPolicyMapping + if (extension.parsedValue.inhibitPolicyMapping === 0) + policyMappingInhibitIndicator = true; + else { + if (pendingConstraints[1] === false) { + pendingConstraints[1] = true; + policyMappingInhibitPending = extension.parsedValue.inhibitPolicyMapping + 1; + } + else + policyMappingInhibitPending = (policyMappingInhibitPending > (extension.parsedValue.inhibitPolicyMapping + 1)) ? (extension.parsedValue.inhibitPolicyMapping + 1) : policyMappingInhibitPending; + } + //#endregion + } + } + //#endregion + + //#region InhibitAnyPolicy + if (extension.extnID === id_InhibitAnyPolicy) { + if (inhibitAnyPolicyIndicator === false) { + if (extension.parsedValue.valueBlock.valueDec === 0) + inhibitAnyPolicyIndicator = true; + else { + if (pendingConstraints[2] === false) { + pendingConstraints[2] = true; + inhibitAnyPolicyPending = extension.parsedValue.valueBlock.valueDec; + } + else + inhibitAnyPolicyPending = (inhibitAnyPolicyPending > extension.parsedValue.valueBlock.valueDec) ? extension.parsedValue.valueBlock.valueDec : inhibitAnyPolicyPending; + } + } + } + //#endregion + } + //#endregion + + //#region Check "inhibitAnyPolicyIndicator" + if (inhibitAnyPolicyIndicator === true) { + let policyIndex = (-1); + + //#region Find "anyPolicy" index + for (let searchAnyPolicy = 0; searchAnyPolicy < allPolicies.length; searchAnyPolicy++) { + if (allPolicies[searchAnyPolicy] === id_AnyPolicy) { + policyIndex = searchAnyPolicy; + break; + } + } + //#endregion + + if (policyIndex !== (-1)) + delete (policiesAndCerts[0])[i]; // Unset value to "undefined" for "anyPolicies" value for current certificate + } + //#endregion + + //#region Process with "pending constraints" + if (explicitPolicyIndicator === false) { + if (pendingConstraints[0] === true) { + explicitPolicyPending--; + if (explicitPolicyPending === 0) { + explicitPolicyIndicator = true; + explicitPolicyStart = i; + + pendingConstraints[0] = false; + } + } + } + + if (policyMappingInhibitIndicator === false) { + if (pendingConstraints[1] === true) { + policyMappingInhibitPending--; + if (policyMappingInhibitPending === 0) { + policyMappingInhibitIndicator = true; + pendingConstraints[1] = false; + } + } + } + + if (inhibitAnyPolicyIndicator === false) { + if (pendingConstraints[2] === true) { + inhibitAnyPolicyPending--; + if (inhibitAnyPolicyPending === 0) { + inhibitAnyPolicyIndicator = true; + pendingConstraints[2] = false; + } + } + } + //#endregion + } + } + //#endregion + + //#region Working with policy mappings + for (let i = 0; i < (this.certs.length - 1); i++) { + //#region Check that there is "policy mapping" for level "i + 1" + if ((i < (this.certs.length - 2)) && (typeof policyMappings[i + 1] !== "undefined")) { + for (let k = 0; k < policyMappings[i + 1].mappings.length; k++) { + //#region Check that we do not have "anyPolicy" in current mapping + if ((policyMappings[i + 1].mappings[k].issuerDomainPolicy === id_AnyPolicy) || (policyMappings[i + 1].mappings[k].subjectDomainPolicy === id_AnyPolicy)) { + return { + result: false, + resultCode: 99, + resultMessage: "The \"anyPolicy\" should not be a part of policy mapping scheme" + }; + } + //#endregion + + //#region Initial variables + let issuerDomainPolicyIndex = (-1); + let subjectDomainPolicyIndex = (-1); + //#endregion + + //#region Search for index of policies indexes + for (let n = 0; n < allPolicies.length; n++) { + if (allPolicies[n] === policyMappings[i + 1].mappings[k].issuerDomainPolicy) + issuerDomainPolicyIndex = n; + + if (allPolicies[n] === policyMappings[i + 1].mappings[k].subjectDomainPolicy) + subjectDomainPolicyIndex = n; + } + //#endregion + + //#region Delete existing "issuerDomainPolicy" because on the level we mapped the policy to another one + if (typeof (policiesAndCerts[issuerDomainPolicyIndex])[i] !== "undefined") + delete (policiesAndCerts[issuerDomainPolicyIndex])[i]; + //#endregion + + //#region Check all policies for the certificate + for (let j = 0; j < certPolicies[i].certificatePolicies.length; j++) { + if (policyMappings[i + 1].mappings[k].subjectDomainPolicy === certPolicies[i].certificatePolicies[j].policyIdentifier) { + //#region Set mapped policy for current certificate + if ((issuerDomainPolicyIndex !== (-1)) && (subjectDomainPolicyIndex !== (-1))) { + for (let m = 0; m <= i; m++) { + if (typeof (policiesAndCerts[subjectDomainPolicyIndex])[m] !== "undefined") { + (policiesAndCerts[issuerDomainPolicyIndex])[m] = true; + delete (policiesAndCerts[subjectDomainPolicyIndex])[m]; + } + } + } + //#endregion + } + } + //#endregion + } + } + //#endregion + } + //#endregion + + //#region Working with "explicitPolicyIndicator" and "anyPolicy" + for (let i = 0; i < allPolicies.length; i++) { + if (allPolicies[i] === id_AnyPolicy) { + for (let j = 0; j < explicitPolicyStart; j++) + delete (policiesAndCerts[i])[j]; + } + } + //#endregion + + //#region Create "set of authorities-constrained policies" + const authConstrPolicies = []; + + for (let i = 0; i < policiesAndCerts.length; i++) { + let found = true; + + for (let j = 0; j < (this.certs.length - 1); j++) { + let anyPolicyFound = false; + + if ((j < explicitPolicyStart) && (allPolicies[i] === id_AnyPolicy) && (allPolicies.length > 1)) { + found = false; + break; + } + + if (typeof (policiesAndCerts[i])[j] === "undefined") { + if (j >= explicitPolicyStart) { + //#region Search for "anyPolicy" in the policy set + for (let k = 0; k < allPolicies.length; k++) { + if (allPolicies[k] === id_AnyPolicy) { + if ((policiesAndCerts[k])[j] === true) + anyPolicyFound = true; + + break; + } + } + //#endregion + } + + if (!anyPolicyFound) { + found = false; + break; + } + } + } + + if (found === true) + authConstrPolicies.push(allPolicies[i]); + } + //#endregion + + //#region Create "set of user-constrained policies" + let userConstrPolicies: string[] = []; + + if ((initialPolicySet.length === 1) && (initialPolicySet[0] === id_AnyPolicy) && (explicitPolicyIndicator === false)) + userConstrPolicies = initialPolicySet; + else { + if ((authConstrPolicies.length === 1) && (authConstrPolicies[0] === id_AnyPolicy)) + userConstrPolicies = initialPolicySet; + else { + for (let i = 0; i < authConstrPolicies.length; i++) { + for (let j = 0; j < initialPolicySet.length; j++) { + if ((initialPolicySet[j] === authConstrPolicies[i]) || (initialPolicySet[j] === id_AnyPolicy)) { + userConstrPolicies.push(authConstrPolicies[i]); + break; + } + } + } + } + } + + //#endregion + + //#region Combine output object + const policyResult: CertificateChainValidationEngineVerifyResult = { + result: (userConstrPolicies.length > 0), + resultCode: 0, + resultMessage: (userConstrPolicies.length > 0) ? EMPTY_STRING : "Zero \"userConstrPolicies\" array, no intersections with \"authConstrPolicies\"", + authConstrPolicies, + userConstrPolicies, + explicitPolicyIndicator, + policyMappings, + certificatePath: this.certs + }; + + if (userConstrPolicies.length === 0) + return policyResult; + //#endregion + //#endregion + + //#region Work with name constraints + //#region Check a result from "policy checking" part + if (policyResult.result === false) + return policyResult; + //#endregion + + //#region Check all certificates, excluding "trust anchor" + pathDepth = 1; + + for (let i = (this.certs.length - 2); i >= 0; i--, pathDepth++) { + const cert = this.certs[i]; + //#region Support variables + let subjectAltNames: GeneralName[] = []; + + let certPermittedSubtrees: GeneralSubtree[] = []; + let certExcludedSubtrees: GeneralSubtree[] = []; + //#endregion + + if (cert.extensions) { + for (let j = 0; j < cert.extensions.length; j++) { + const extension = cert.extensions[j]; + //#region NameConstraints + if (extension.extnID === id_NameConstraints) { + if ("permittedSubtrees" in extension.parsedValue) + certPermittedSubtrees = certPermittedSubtrees.concat(extension.parsedValue.permittedSubtrees); + + if ("excludedSubtrees" in extension.parsedValue) + certExcludedSubtrees = certExcludedSubtrees.concat(extension.parsedValue.excludedSubtrees); + } + //#endregion + + //#region SubjectAltName + if (extension.extnID === id_SubjectAltName) + subjectAltNames = subjectAltNames.concat(extension.parsedValue.altNames); + //#endregion + } + } + + //#region Checking for "required name forms" + let formFound = (requiredNameForms.length <= 0); + + for (let j = 0; j < requiredNameForms.length; j++) { + switch (requiredNameForms[j].base.type) { + case 4: // directoryName + { + if (requiredNameForms[j].base.value.typesAndValues.length !== cert.subject.typesAndValues.length) + continue; + + formFound = true; + + for (let k = 0; k < cert.subject.typesAndValues.length; k++) { + if (cert.subject.typesAndValues[k].type !== requiredNameForms[j].base.value.typesAndValues[k].type) { + formFound = false; + break; + } + } + + if (formFound === true) + break; + } + break; + default: // ??? Probably here we should reject the certificate ??? + } + } + + if (formFound === false) { + policyResult.result = false; + policyResult.resultCode = 21; + policyResult.resultMessage = "No necessary name form found"; + + throw policyResult; + } + //#endregion + + //#region Checking for "permited sub-trees" + //#region Make groups for all types of constraints + const constrGroups: GeneralSubtree[][] = [ // Array of array for groupped constraints + [], // rfc822Name + [], // dNSName + [], // directoryName + [], // uniformResourceIdentifier + [], // iPAddress + ]; + + for (let j = 0; j < permittedSubtrees.length; j++) { + switch (permittedSubtrees[j].base.type) { + //#region rfc822Name + case 1: + constrGroups[0].push(permittedSubtrees[j]); + break; + //#endregion + //#region dNSName + case 2: + constrGroups[1].push(permittedSubtrees[j]); + break; + //#endregion + //#region directoryName + case 4: + constrGroups[2].push(permittedSubtrees[j]); + break; + //#endregion + //#region uniformResourceIdentifier + case 6: + constrGroups[3].push(permittedSubtrees[j]); + break; + //#endregion + //#region iPAddress + case 7: + constrGroups[4].push(permittedSubtrees[j]); + break; + //#endregion + //#region default + default: + //#endregion + } + } + //#endregion + + //#region Check name constraints groupped by type, one-by-one + for (let p = 0; p < 5; p++) { + let groupPermitted = false; + let valueExists = false; + const group = constrGroups[p]; + + for (let j = 0; j < group.length; j++) { + switch (p) { + //#region rfc822Name + case 0: + if (subjectAltNames.length > 0) { + for (let k = 0; k < subjectAltNames.length; k++) { + if (subjectAltNames[k].type === 1) // rfc822Name + { + valueExists = true; + groupPermitted = groupPermitted || compareRFC822Name(subjectAltNames[k].value, group[j].base.value); + } + } + } + else // Try to find out "emailAddress" inside "subject" + { + for (let k = 0; k < cert.subject.typesAndValues.length; k++) { + if ((cert.subject.typesAndValues[k].type === "1.2.840.113549.1.9.1") || // PKCS#9 e-mail address + (cert.subject.typesAndValues[k].type === "0.9.2342.19200300.100.1.3")) // RFC1274 "rfc822Mailbox" e-mail address + { + valueExists = true; + groupPermitted = groupPermitted || compareRFC822Name(cert.subject.typesAndValues[k].value.valueBlock.value, group[j].base.value); + } + } + } + break; + //#endregion + //#region dNSName + case 1: + if (subjectAltNames.length > 0) { + for (let k = 0; k < subjectAltNames.length; k++) { + if (subjectAltNames[k].type === 2) // dNSName + { + valueExists = true; + groupPermitted = groupPermitted || compareDNSName(subjectAltNames[k].value, group[j].base.value); + } + } + } + break; + //#endregion + //#region directoryName + case 2: + valueExists = true; + groupPermitted = compareDirectoryName(cert.subject, group[j].base.value); + break; + //#endregion + //#region uniformResourceIdentifier + case 3: + if (subjectAltNames.length > 0) { + for (let k = 0; k < subjectAltNames.length; k++) { + if (subjectAltNames[k].type === 6) // uniformResourceIdentifier + { + valueExists = true; + groupPermitted = groupPermitted || compareUniformResourceIdentifier(subjectAltNames[k].value, group[j].base.value); + } + } + } + break; + //#endregion + //#region iPAddress + case 4: + if (subjectAltNames.length > 0) { + for (let k = 0; k < subjectAltNames.length; k++) { + if (subjectAltNames[k].type === 7) // iPAddress + { + valueExists = true; + groupPermitted = groupPermitted || compareIPAddress(subjectAltNames[k].value, group[j].base.value); + } + } + } + break; + //#endregion + //#region default + default: + //#endregion + } + + if (groupPermitted) + break; + } + + if ((groupPermitted === false) && (group.length > 0) && valueExists) { + policyResult.result = false; + policyResult.resultCode = 41; + policyResult.resultMessage = "Failed to meet \"permitted sub-trees\" name constraint"; + + throw policyResult; + } + } + //#endregion + //#endregion + + //#region Checking for "excluded sub-trees" + let excluded = false; + + for (let j = 0; j < excludedSubtrees.length; j++) { + switch (excludedSubtrees[j].base.type) { + //#region rfc822Name + case 1: + if (subjectAltNames.length >= 0) { + for (let k = 0; k < subjectAltNames.length; k++) { + if (subjectAltNames[k].type === 1) // rfc822Name + excluded = excluded || compareRFC822Name(subjectAltNames[k].value, excludedSubtrees[j].base.value); + } + } + else // Try to find out "emailAddress" inside "subject" + { + for (let k = 0; k < cert.subject.typesAndValues.length; k++) { + if ((cert.subject.typesAndValues[k].type === "1.2.840.113549.1.9.1") || // PKCS#9 e-mail address + (cert.subject.typesAndValues[k].type === "0.9.2342.19200300.100.1.3")) // RFC1274 "rfc822Mailbox" e-mail address + excluded = excluded || compareRFC822Name(cert.subject.typesAndValues[k].value.valueBlock.value, excludedSubtrees[j].base.value); + } + } + break; + //#endregion + //#region dNSName + case 2: + if (subjectAltNames.length > 0) { + for (let k = 0; k < subjectAltNames.length; k++) { + if (subjectAltNames[k].type === 2) // dNSName + excluded = excluded || compareDNSName(subjectAltNames[k].value, excludedSubtrees[j].base.value); + } + } + break; + //#endregion + //#region directoryName + case 4: + excluded = excluded || compareDirectoryName(cert.subject, excludedSubtrees[j].base.value); + break; + //#endregion + //#region uniformResourceIdentifier + case 6: + if (subjectAltNames.length > 0) { + for (let k = 0; k < subjectAltNames.length; k++) { + if (subjectAltNames[k].type === 6) // uniformResourceIdentifier + excluded = excluded || compareUniformResourceIdentifier(subjectAltNames[k].value, excludedSubtrees[j].base.value); + } + } + break; + //#endregion + //#region iPAddress + case 7: + if (subjectAltNames.length > 0) { + for (let k = 0; k < subjectAltNames.length; k++) { + if (subjectAltNames[k].type === 7) // iPAddress + excluded = excluded || compareIPAddress(subjectAltNames[k].value, excludedSubtrees[j].base.value); + } + } + break; + //#endregion + //#region default + default: // No action, but probably here we need to create a warning for "malformed constraint" + //#endregion + } + + if (excluded) + break; + } + + if (excluded === true) { + policyResult.result = false; + policyResult.resultCode = 42; + policyResult.resultMessage = "Failed to meet \"excluded sub-trees\" name constraint"; + + throw policyResult; + } + //#endregion + + //#region Append "cert_..._subtrees" to "..._subtrees" + permittedSubtrees = permittedSubtrees.concat(certPermittedSubtrees); + excludedSubtrees = excludedSubtrees.concat(certExcludedSubtrees); + //#endregion + } + //#endregion + + return policyResult; + //#endregion + } catch (error) { + if (error instanceof Error) { + if (error instanceof ChainValidationError) { + return { + result: false, + resultCode: error.code, + resultMessage: error.message, + error: error, + }; + } + + return { + result: false, + resultCode: ChainValidationCode.unknown, + resultMessage: error.message, + error: error, + }; + } + + if (error && typeof error === "object" && "resultMessage" in error) { + return error as CertificateChainValidationEngineVerifyResult; + } + + return { + result: false, + resultCode: -1, + resultMessage: `${error}`, + }; + } + } + +} diff --git a/third_party/js/PKI.js/src/CertificatePolicies.ts b/third_party/js/PKI.js/src/CertificatePolicies.ts new file mode 100644 index 0000000000..41768cd443 --- /dev/null +++ b/third_party/js/PKI.js/src/CertificatePolicies.ts @@ -0,0 +1,115 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { PolicyInformation, PolicyInformationJson } from "./PolicyInformation"; +import * as Schema from "./Schema"; + +const CERTIFICATE_POLICIES = "certificatePolicies"; +const CLEAR_PROPS = [ + CERTIFICATE_POLICIES, +]; + +export interface ICertificatePolicies { + certificatePolicies: PolicyInformation[]; +} + +export type CertificatePoliciesParameters = PkiObjectParameters & Partial<ICertificatePolicies>; + +export interface CertificatePoliciesJson { + certificatePolicies: PolicyInformationJson[]; +} + +/** + * Represents the CertificatePolicies structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class CertificatePolicies extends PkiObject implements ICertificatePolicies { + + public static override CLASS_NAME = "CertificatePolicies"; + + public certificatePolicies!: PolicyInformation[]; + + /** + * Initializes a new instance of the {@link CertificatePolicies} class + * @param parameters Initialization parameters + */ + constructor(parameters: CertificatePoliciesParameters = {}) { + super(); + + this.certificatePolicies = pvutils.getParametersValue(parameters, CERTIFICATE_POLICIES, CertificatePolicies.defaultValues(CERTIFICATE_POLICIES)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof CERTIFICATE_POLICIES): PolicyInformation[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case CERTIFICATE_POLICIES: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * certificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation + *``` + */ + static override schema(parameters: Schema.SchemaParameters<{ certificatePolicies?: string; }> = {}) { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Repeated({ + name: (names.certificatePolicies || EMPTY_STRING), + value: PolicyInformation.schema() + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + CertificatePolicies.schema({ + names: { + certificatePolicies: CERTIFICATE_POLICIES + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.certificatePolicies = Array.from(asn1.result.certificatePolicies, element => new PolicyInformation({ schema: element })); + } + + public toSchema(): asn1js.Sequence { + return (new asn1js.Sequence({ + value: Array.from(this.certificatePolicies, o => o.toSchema()) + })); + } + + public toJSON(): CertificatePoliciesJson { + return { + certificatePolicies: Array.from(this.certificatePolicies, o => o.toJSON()) + }; + } + +} + diff --git a/third_party/js/PKI.js/src/CertificateRevocationList.ts b/third_party/js/PKI.js/src/CertificateRevocationList.ts new file mode 100644 index 0000000000..a17bb638a3 --- /dev/null +++ b/third_party/js/PKI.js/src/CertificateRevocationList.ts @@ -0,0 +1,561 @@ +import * as asn1js from "asn1js"; +import * as pvtsutils from "pvtsutils"; +import * as pvutils from "pvutils"; +import * as common from "./common"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier"; +import { RelativeDistinguishedNames, RelativeDistinguishedNamesJson, RelativeDistinguishedNamesSchema } from "./RelativeDistinguishedNames"; +import { Time, TimeJson, TimeSchema } from "./Time"; +import { RevokedCertificate, RevokedCertificateJson } from "./RevokedCertificate"; +import { Extensions, ExtensionsJson, ExtensionsSchema } from "./Extensions"; +import * as Schema from "./Schema"; +import { Certificate } from "./Certificate"; +import { PublicKeyInfo } from "./PublicKeyInfo"; +import { id_AuthorityInfoAccess, id_AuthorityKeyIdentifier, id_BaseCRLNumber, id_CertificateIssuer, id_CRLNumber, id_CRLReason, id_FreshestCRL, id_InvalidityDate, id_IssuerAltName, id_IssuingDistributionPoint } from "./ObjectIdentifiers"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { EMPTY_BUFFER } from "./constants"; + +const TBS = "tbs"; +const VERSION = "version"; +const SIGNATURE = "signature"; +const ISSUER = "issuer"; +const THIS_UPDATE = "thisUpdate"; +const NEXT_UPDATE = "nextUpdate"; +const REVOKED_CERTIFICATES = "revokedCertificates"; +const CRL_EXTENSIONS = "crlExtensions"; +const SIGNATURE_ALGORITHM = "signatureAlgorithm"; +const SIGNATURE_VALUE = "signatureValue"; +const TBS_CERT_LIST = "tbsCertList"; +const TBS_CERT_LIST_VERSION = `${TBS_CERT_LIST}.version`; +const TBS_CERT_LIST_SIGNATURE = `${TBS_CERT_LIST}.signature`; +const TBS_CERT_LIST_ISSUER = `${TBS_CERT_LIST}.issuer`; +const TBS_CERT_LIST_THIS_UPDATE = `${TBS_CERT_LIST}.thisUpdate`; +const TBS_CERT_LIST_NEXT_UPDATE = `${TBS_CERT_LIST}.nextUpdate`; +const TBS_CERT_LIST_REVOKED_CERTIFICATES = `${TBS_CERT_LIST}.revokedCertificates`; +const TBS_CERT_LIST_EXTENSIONS = `${TBS_CERT_LIST}.extensions`; +const CLEAR_PROPS = [ + TBS_CERT_LIST, + TBS_CERT_LIST_VERSION, + TBS_CERT_LIST_SIGNATURE, + TBS_CERT_LIST_ISSUER, + TBS_CERT_LIST_THIS_UPDATE, + TBS_CERT_LIST_NEXT_UPDATE, + TBS_CERT_LIST_REVOKED_CERTIFICATES, + TBS_CERT_LIST_EXTENSIONS, + SIGNATURE_ALGORITHM, + SIGNATURE_VALUE +]; + +export interface ICertificateRevocationList { + tbs: ArrayBuffer; + version: number; + signature: AlgorithmIdentifier; + issuer: RelativeDistinguishedNames; + thisUpdate: Time; + nextUpdate?: Time; + revokedCertificates?: RevokedCertificate[]; + crlExtensions?: Extensions; + signatureAlgorithm: AlgorithmIdentifier; + signatureValue: asn1js.BitString; +} + +export type TBSCertListSchema = Schema.SchemaParameters<{ + tbsCertListVersion?: string; + signature?: AlgorithmIdentifierSchema; + issuer?: RelativeDistinguishedNamesSchema; + tbsCertListThisUpdate?: TimeSchema; + tbsCertListNextUpdate?: TimeSchema; + tbsCertListRevokedCertificates?: string; + crlExtensions?: ExtensionsSchema; +}>; + +export interface CertificateRevocationListJson { + tbs: string; + version: number; + signature: AlgorithmIdentifierJson; + issuer: RelativeDistinguishedNamesJson; + thisUpdate: TimeJson; + nextUpdate?: TimeJson; + revokedCertificates?: RevokedCertificateJson[]; + crlExtensions?: ExtensionsJson; + signatureAlgorithm: AlgorithmIdentifierJson; + signatureValue: asn1js.BitStringJson; +} + +function tbsCertList(parameters: TBSCertListSchema = {}): Schema.SchemaType { + //TBSCertList ::= SEQUENCE { + // version Version OPTIONAL, + // -- if present, MUST be v2 + // signature AlgorithmIdentifier, + // issuer Name, + // thisUpdate Time, + // nextUpdate Time OPTIONAL, + // revokedCertificates SEQUENCE OF SEQUENCE { + // userCertificate CertificateSerialNumber, + // revocationDate Time, + // crlEntryExtensions Extensions OPTIONAL + // -- if present, version MUST be v2 + // } OPTIONAL, + // crlExtensions [0] EXPLICIT Extensions OPTIONAL + // -- if present, version MUST be v2 + //} + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || TBS_CERT_LIST), + value: [ + new asn1js.Integer({ + optional: true, + name: (names.tbsCertListVersion || TBS_CERT_LIST_VERSION), + value: 2 + }), // EXPLICIT integer value (v2) + AlgorithmIdentifier.schema(names.signature || { + names: { + blockName: TBS_CERT_LIST_SIGNATURE + } + }), + RelativeDistinguishedNames.schema(names.issuer || { + names: { + blockName: TBS_CERT_LIST_ISSUER + } + }), + Time.schema(names.tbsCertListThisUpdate || { + names: { + utcTimeName: TBS_CERT_LIST_THIS_UPDATE, + generalTimeName: TBS_CERT_LIST_THIS_UPDATE + } + }), + Time.schema(names.tbsCertListNextUpdate || { + names: { + utcTimeName: TBS_CERT_LIST_NEXT_UPDATE, + generalTimeName: TBS_CERT_LIST_NEXT_UPDATE + } + }, true), + new asn1js.Sequence({ + optional: true, + value: [ + new asn1js.Repeated({ + name: (names.tbsCertListRevokedCertificates || TBS_CERT_LIST_REVOKED_CERTIFICATES), + value: new asn1js.Sequence({ + value: [ + new asn1js.Integer(), + Time.schema(), + Extensions.schema({}, true) + ] + }) + }) + ] + }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [Extensions.schema(names.crlExtensions || { + names: { + blockName: TBS_CERT_LIST_EXTENSIONS + } + })] + }) // EXPLICIT SEQUENCE value + ] + })); +} + +export type CertificateRevocationListParameters = PkiObjectParameters & Partial<ICertificateRevocationList>; + +export interface CertificateRevocationListVerifyParams { + issuerCertificate?: Certificate; + publicKeyInfo?: PublicKeyInfo; +} + +const WELL_KNOWN_EXTENSIONS = [ + id_AuthorityKeyIdentifier, + id_IssuerAltName, + id_CRLNumber, + id_BaseCRLNumber, + id_IssuingDistributionPoint, + id_FreshestCRL, + id_AuthorityInfoAccess, + id_CRLReason, + id_InvalidityDate, + id_CertificateIssuer, +]; +/** + * Represents the CertificateRevocationList structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class CertificateRevocationList extends PkiObject implements ICertificateRevocationList { + + public static override CLASS_NAME = "CertificateRevocationList"; + + public tbsView!: Uint8Array; + /** + * @deprecated Since version 3.0.0 + */ + public get tbs(): ArrayBuffer { + return pvtsutils.BufferSourceConverter.toArrayBuffer(this.tbsView); + } + + /** + * @deprecated Since version 3.0.0 + */ + public set tbs(value: ArrayBuffer) { + this.tbsView = new Uint8Array(value); + } + public version!: number; + public signature!: AlgorithmIdentifier; + public issuer!: RelativeDistinguishedNames; + public thisUpdate!: Time; + public nextUpdate?: Time; + public revokedCertificates?: RevokedCertificate[]; + public crlExtensions?: Extensions; + public signatureAlgorithm!: AlgorithmIdentifier; + public signatureValue!: asn1js.BitString; + + /** + * Initializes a new instance of the {@link CertificateRevocationList} class + * @param parameters Initialization parameters + */ + constructor(parameters: CertificateRevocationListParameters = {}) { + super(); + + this.tbsView = new Uint8Array(pvutils.getParametersValue(parameters, TBS, CertificateRevocationList.defaultValues(TBS))); + this.version = pvutils.getParametersValue(parameters, VERSION, CertificateRevocationList.defaultValues(VERSION)); + this.signature = pvutils.getParametersValue(parameters, SIGNATURE, CertificateRevocationList.defaultValues(SIGNATURE)); + this.issuer = pvutils.getParametersValue(parameters, ISSUER, CertificateRevocationList.defaultValues(ISSUER)); + this.thisUpdate = pvutils.getParametersValue(parameters, THIS_UPDATE, CertificateRevocationList.defaultValues(THIS_UPDATE)); + if (NEXT_UPDATE in parameters) { + this.nextUpdate = pvutils.getParametersValue(parameters, NEXT_UPDATE, CertificateRevocationList.defaultValues(NEXT_UPDATE)); + } + if (REVOKED_CERTIFICATES in parameters) { + this.revokedCertificates = pvutils.getParametersValue(parameters, REVOKED_CERTIFICATES, CertificateRevocationList.defaultValues(REVOKED_CERTIFICATES)); + } + if (CRL_EXTENSIONS in parameters) { + this.crlExtensions = pvutils.getParametersValue(parameters, CRL_EXTENSIONS, CertificateRevocationList.defaultValues(CRL_EXTENSIONS)); + } + this.signatureAlgorithm = pvutils.getParametersValue(parameters, SIGNATURE_ALGORITHM, CertificateRevocationList.defaultValues(SIGNATURE_ALGORITHM)); + this.signatureValue = pvutils.getParametersValue(parameters, SIGNATURE_VALUE, CertificateRevocationList.defaultValues(SIGNATURE_VALUE)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof TBS): ArrayBuffer; + public static override defaultValues(memberName: typeof VERSION): number; + public static override defaultValues(memberName: typeof SIGNATURE): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof ISSUER): RelativeDistinguishedNames; + public static override defaultValues(memberName: typeof THIS_UPDATE): Time; + public static override defaultValues(memberName: typeof NEXT_UPDATE): Time; + public static override defaultValues(memberName: typeof REVOKED_CERTIFICATES): RevokedCertificate[]; + public static override defaultValues(memberName: typeof CRL_EXTENSIONS): Extensions; + public static override defaultValues(memberName: typeof SIGNATURE_ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof SIGNATURE_VALUE): asn1js.BitString; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case TBS: + return EMPTY_BUFFER; + case VERSION: + return 0; + case SIGNATURE: + return new AlgorithmIdentifier(); + case ISSUER: + return new RelativeDistinguishedNames(); + case THIS_UPDATE: + return new Time(); + case NEXT_UPDATE: + return new Time(); + case REVOKED_CERTIFICATES: + return []; + case CRL_EXTENSIONS: + return new Extensions(); + case SIGNATURE_ALGORITHM: + return new AlgorithmIdentifier(); + case SIGNATURE_VALUE: + return new asn1js.BitString(); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * CertificateList ::= SEQUENCE { + * tbsCertList TBSCertList, + * signatureAlgorithm AlgorithmIdentifier, + * signatureValue BIT STRING } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + tbsCertListVersion?: string; + signature?: AlgorithmIdentifierSchema; + issuer?: RelativeDistinguishedNamesSchema; + tbsCertListThisUpdate?: TimeSchema; + tbsCertListNextUpdate?: TimeSchema; + tbsCertListRevokedCertificates?: string; + crlExtensions?: ExtensionsSchema; + signatureAlgorithm?: AlgorithmIdentifierSchema; + signatureValue?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || "CertificateList"), + value: [ + tbsCertList(parameters), + AlgorithmIdentifier.schema(names.signatureAlgorithm || { + names: { + blockName: SIGNATURE_ALGORITHM + } + }), + new asn1js.BitString({ name: (names.signatureValue || SIGNATURE_VALUE) }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + CertificateRevocationList.schema() + ); + AsnError.assertSchema(asn1, this.className); + + //#region Get internal properties from parsed schema + this.tbsView = (asn1.result.tbsCertList as asn1js.Sequence).valueBeforeDecodeView; + + if (TBS_CERT_LIST_VERSION in asn1.result) { + this.version = asn1.result[TBS_CERT_LIST_VERSION].valueBlock.valueDec; + } + this.signature = new AlgorithmIdentifier({ schema: asn1.result[TBS_CERT_LIST_SIGNATURE] }); + this.issuer = new RelativeDistinguishedNames({ schema: asn1.result[TBS_CERT_LIST_ISSUER] }); + this.thisUpdate = new Time({ schema: asn1.result[TBS_CERT_LIST_THIS_UPDATE] }); + if (TBS_CERT_LIST_NEXT_UPDATE in asn1.result) { + this.nextUpdate = new Time({ schema: asn1.result[TBS_CERT_LIST_NEXT_UPDATE] }); + } + if (TBS_CERT_LIST_REVOKED_CERTIFICATES in asn1.result) { + this.revokedCertificates = Array.from(asn1.result[TBS_CERT_LIST_REVOKED_CERTIFICATES], element => new RevokedCertificate({ schema: element })); + } + if (TBS_CERT_LIST_EXTENSIONS in asn1.result) { + this.crlExtensions = new Extensions({ schema: asn1.result[TBS_CERT_LIST_EXTENSIONS] }); + } + + this.signatureAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.signatureAlgorithm }); + this.signatureValue = asn1.result.signatureValue; + //#endregion + } + + protected encodeTBS() { + //#region Create array for output sequence + const outputArray: any[] = []; + + if (this.version !== CertificateRevocationList.defaultValues(VERSION)) { + outputArray.push(new asn1js.Integer({ value: this.version })); + } + + outputArray.push(this.signature.toSchema()); + outputArray.push(this.issuer.toSchema()); + outputArray.push(this.thisUpdate.toSchema()); + + if (this.nextUpdate) { + outputArray.push(this.nextUpdate.toSchema()); + } + + if (this.revokedCertificates) { + outputArray.push(new asn1js.Sequence({ + value: Array.from(this.revokedCertificates, o => o.toSchema()) + })); + } + + if (this.crlExtensions) { + outputArray.push(new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + this.crlExtensions.toSchema() + ] + })); + } + //#endregion + + return (new asn1js.Sequence({ + value: outputArray + })); + } + + /** + * Convert current object to asn1js object and set correct values + * @returns asn1js object + */ + public toSchema(encodeFlag = false) { + //#region Decode stored TBS value + let tbsSchema; + + if (!encodeFlag) { + if (!this.tbsView.byteLength) { // No stored TBS part + return CertificateRevocationList.schema(); + } + + const asn1 = asn1js.fromBER(this.tbsView); + AsnError.assert(asn1, "TBS Certificate Revocation List"); + tbsSchema = asn1.result; + } + //#endregion + //#region Create TBS schema via assembling from TBS parts + else { + tbsSchema = this.encodeTBS(); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: [ + tbsSchema, + this.signatureAlgorithm.toSchema(), + this.signatureValue + ] + })); + //#endregion + } + + public toJSON(): CertificateRevocationListJson { + const res: CertificateRevocationListJson = { + tbs: pvtsutils.Convert.ToHex(this.tbsView), + version: this.version, + signature: this.signature.toJSON(), + issuer: this.issuer.toJSON(), + thisUpdate: this.thisUpdate.toJSON(), + signatureAlgorithm: this.signatureAlgorithm.toJSON(), + signatureValue: this.signatureValue.toJSON() + }; + + if (this.version !== CertificateRevocationList.defaultValues(VERSION)) + res.version = this.version; + + if (this.nextUpdate) { + res.nextUpdate = this.nextUpdate.toJSON(); + } + + if (this.revokedCertificates) { + res.revokedCertificates = Array.from(this.revokedCertificates, o => o.toJSON()); + } + + if (this.crlExtensions) { + res.crlExtensions = this.crlExtensions.toJSON(); + } + + return res; + } + + /** + * Returns `true` if supplied certificate is revoked, otherwise `false` + * @param certificate + */ + public isCertificateRevoked(certificate: Certificate): boolean { + // Check that issuer of the input certificate is the same with issuer of this CRL + if (!this.issuer.isEqual(certificate.issuer)) { + return false; + } + + // Check that there are revoked certificates in this CRL + if (!this.revokedCertificates) { + return false; + } + + // Search for input certificate in revoked certificates array + for (const revokedCertificate of this.revokedCertificates) { + if (revokedCertificate.userCertificate.isEqual(certificate.serialNumber)) { + return true; + } + } + + return false; + } + + /** + * Make a signature for existing CRL data + * @param privateKey Private key for "subjectPublicKeyInfo" structure + * @param hashAlgorithm Hashing algorithm. Default SHA-1 + * @param crypto Crypto engine + */ + public async sign(privateKey: CryptoKey, hashAlgorithm = "SHA-1", crypto = common.getCrypto(true)): Promise<void> { + // Get a private key from function parameter + if (!privateKey) { + throw new Error("Need to provide a private key for signing"); + } + + //#region Get a "default parameters" for current algorithm and set correct signature algorithm + const signatureParameters = await crypto.getSignatureParameters(privateKey, hashAlgorithm); + const { parameters } = signatureParameters; + this.signature = signatureParameters.signatureAlgorithm; + this.signatureAlgorithm = signatureParameters.signatureAlgorithm; + //#endregion + + //#region Create TBS data for signing + this.tbsView = new Uint8Array(this.encodeTBS().toBER()); + //#endregion + + //#region Signing TBS data on provided private key + const signature = await crypto.signWithPrivateKey(this.tbsView, privateKey, parameters as any); + this.signatureValue = new asn1js.BitString({ valueHex: signature }); + //#endregion + } + + /** + * Verify existing signature + * @param parameters + * @param crypto Crypto engine + */ + public async verify(parameters: CertificateRevocationListVerifyParams = {}, crypto = common.getCrypto(true)): Promise<boolean> { + let subjectPublicKeyInfo: PublicKeyInfo | undefined; + + //#region Get information about CRL issuer certificate + if (parameters.issuerCertificate) { // "issuerCertificate" must be of type "Certificate" + subjectPublicKeyInfo = parameters.issuerCertificate.subjectPublicKeyInfo; + + // The CRL issuer name and "issuerCertificate" subject name are not equal + if (!this.issuer.isEqual(parameters.issuerCertificate.subject)) { + return false; + } + } + + //#region In case if there is only public key during verification + if (parameters.publicKeyInfo) { + subjectPublicKeyInfo = parameters.publicKeyInfo; // Must be of type "PublicKeyInfo" + } + //#endregion + + if (!subjectPublicKeyInfo) { + throw new Error("Issuer's certificate must be provided as an input parameter"); + } + //#endregion + + //#region Check the CRL for unknown critical extensions + if (this.crlExtensions) { + for (const extension of this.crlExtensions.extensions) { + if (extension.critical) { + // We can not be sure that unknown extension has no value for CRL signature + if (!WELL_KNOWN_EXTENSIONS.includes(extension.extnID)) + return false; + } + } + } + //#endregion + + return crypto.verifyWithPublicKey(this.tbsView, this.signatureValue, subjectPublicKeyInfo, this.signatureAlgorithm); + } + +} diff --git a/third_party/js/PKI.js/src/CertificateSet.ts b/third_party/js/PKI.js/src/CertificateSet.ts new file mode 100644 index 0000000000..386b01ea19 --- /dev/null +++ b/third_party/js/PKI.js/src/CertificateSet.ts @@ -0,0 +1,228 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { Certificate, CertificateJson } from "./Certificate"; +import { AttributeCertificateV1, AttributeCertificateV1Json } from "./AttributeCertificateV1"; +import { AttributeCertificateV2, AttributeCertificateV2Json } from "./AttributeCertificateV2"; +import { OtherCertificateFormat, OtherCertificateFormatJson } from "./OtherCertificateFormat"; +import * as Schema from "./Schema"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { AsnError } from "./errors"; +import { EMPTY_STRING } from "./constants"; + +const CERTIFICATES = "certificates"; +const CLEAR_PROPS = [ + CERTIFICATES, +]; + +export interface ICertificateSet { + certificates: CertificateSetItem[]; +} + +export interface CertificateSetJson { + certificates: CertificateSetItemJson[]; +} + +export type CertificateSetItemJson = CertificateJson | AttributeCertificateV1Json | AttributeCertificateV2Json | OtherCertificateFormatJson; + +export type CertificateSetItem = Certificate | AttributeCertificateV1 | AttributeCertificateV2 | OtherCertificateFormat; + +export type CertificateSetParameters = PkiObjectParameters & Partial<ICertificateSet>; + +/** + * Represents the CertificateSet structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + */ +export class CertificateSet extends PkiObject implements ICertificateSet { + + public static override CLASS_NAME = "CertificateSet"; + + public certificates!: CertificateSetItem[]; + + /** + * Initializes a new instance of the {@link CertificateSet} class + * @param parameters Initialization parameters + */ + constructor(parameters: CertificateSetParameters = {}) { + super(); + + this.certificates = pvutils.getParametersValue(parameters, CERTIFICATES, CertificateSet.defaultValues(CERTIFICATES)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof CERTIFICATES): CertificateSetItem[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case CERTIFICATES: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * CertificateSet ::= SET OF CertificateChoices + * + * CertificateChoices ::= CHOICE { + * certificate Certificate, + * extendedCertificate [0] IMPLICIT ExtendedCertificate, -- Obsolete + * v1AttrCert [1] IMPLICIT AttributeCertificateV1, -- Obsolete + * v2AttrCert [2] IMPLICIT AttributeCertificateV2, + * other [3] IMPLICIT OtherCertificateFormat } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + certificates?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return ( + new asn1js.Set({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Repeated({ + name: (names.certificates || CERTIFICATES), + value: new asn1js.Choice({ + value: [ + Certificate.schema(), + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + new asn1js.Any() + ] + }), // JUST A STUB + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [ + new asn1js.Sequence + ] + }), + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 2 // [2] + }, + value: AttributeCertificateV2.schema().valueBlock.value + }), + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 3 // [3] + }, + value: OtherCertificateFormat.schema().valueBlock.value + }) + ] + }) + }) + ] + }) + ); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + CertificateSet.schema() + ); + AsnError.assertSchema(asn1, this.className); + + //#region Get internal properties from parsed schema + this.certificates = Array.from(asn1.result.certificates || [], (element: any) => { + const initialTagNumber = element.idBlock.tagNumber; + + if (element.idBlock.tagClass === 1) + return new Certificate({ schema: element }); + + //#region Making "Sequence" from "Constructed" value + const elementSequence = new asn1js.Sequence({ + value: element.valueBlock.value + }); + //#endregion + + switch (initialTagNumber) { + case 1: + // WARN: It's possible that CMS contains AttributeCertificateV2 instead of AttributeCertificateV1 + // Check the certificate version + if ((elementSequence.valueBlock.value[0] as any).valueBlock.value[0].valueBlock.valueDec === 1) { + return new AttributeCertificateV2({ schema: elementSequence }); + } else { + return new AttributeCertificateV1({ schema: elementSequence }); + } + case 2: + return new AttributeCertificateV2({ schema: elementSequence }); + case 3: + return new OtherCertificateFormat({ schema: elementSequence }); + case 0: + default: + } + + return element; + }); + //#endregion + } + + public toSchema(): asn1js.Set { + // Construct and return new ASN.1 schema for this object + return (new asn1js.Set({ + value: Array.from(this.certificates, element => { + switch (true) { + case (element instanceof Certificate): + return element.toSchema(); + case (element instanceof AttributeCertificateV1): + return new asn1js.Constructed({ + idBlock: { + tagClass: 3, + tagNumber: 1 // [1] + }, + value: element.toSchema().valueBlock.value + }); + case (element instanceof AttributeCertificateV2): + return new asn1js.Constructed({ + idBlock: { + tagClass: 3, + tagNumber: 2 // [2] + }, + value: element.toSchema().valueBlock.value + }); + case (element instanceof OtherCertificateFormat): + return new asn1js.Constructed({ + idBlock: { + tagClass: 3, + tagNumber: 3 // [3] + }, + value: element.toSchema().valueBlock.value + }); + default: + } + + return element.toSchema(); + }) + })); + } + + public toJSON(): CertificateSetJson { + return { + certificates: Array.from(this.certificates, o => o.toJSON()) + }; + } + +} diff --git a/third_party/js/PKI.js/src/CertificateTemplate.ts b/third_party/js/PKI.js/src/CertificateTemplate.ts new file mode 100644 index 0000000000..054ab9ef05 --- /dev/null +++ b/third_party/js/PKI.js/src/CertificateTemplate.ts @@ -0,0 +1,172 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const TEMPLATE_ID = "templateID"; +const TEMPLATE_MAJOR_VERSION = "templateMajorVersion"; +const TEMPLATE_MINOR_VERSION = "templateMinorVersion"; +const CLEAR_PROPS = [ + TEMPLATE_ID, + TEMPLATE_MAJOR_VERSION, + TEMPLATE_MINOR_VERSION +]; + +export interface ICertificateTemplate { + templateID: string; + templateMajorVersion?: number; + templateMinorVersion?: number; +} + +export interface CertificateTemplateJson { + templateID: string; + templateMajorVersion?: number; + templateMinorVersion?: number; +} + +export type CertificateTemplateParameters = PkiObjectParameters & Partial<ICertificateTemplate>; + +/** + * Class from "[MS-WCCE]: Windows Client Certificate Enrollment Protocol" + */ +export class CertificateTemplate extends PkiObject implements ICertificateTemplate { + + public templateID!: string; + public templateMajorVersion?: number; + public templateMinorVersion?: number; + + /** + * Initializes a new instance of the {@link CertificateTemplate} class + * @param parameters Initialization parameters + */ + constructor(parameters: CertificateTemplateParameters = {}) { + super(); + + this.templateID = pvutils.getParametersValue(parameters, TEMPLATE_ID, CertificateTemplate.defaultValues(TEMPLATE_ID)); + if (TEMPLATE_MAJOR_VERSION in parameters) { + this.templateMajorVersion = pvutils.getParametersValue(parameters, TEMPLATE_MAJOR_VERSION, CertificateTemplate.defaultValues(TEMPLATE_MAJOR_VERSION)); + } + if (TEMPLATE_MINOR_VERSION in parameters) { + this.templateMinorVersion = pvutils.getParametersValue(parameters, TEMPLATE_MINOR_VERSION, CertificateTemplate.defaultValues(TEMPLATE_MINOR_VERSION)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof TEMPLATE_MINOR_VERSION): number; + public static override defaultValues(memberName: typeof TEMPLATE_MAJOR_VERSION): number; + public static override defaultValues(memberName: typeof TEMPLATE_ID): string; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case TEMPLATE_ID: + return EMPTY_STRING; + case TEMPLATE_MAJOR_VERSION: + case TEMPLATE_MINOR_VERSION: + return 0; + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * CertificateTemplateOID ::= SEQUENCE { + * templateID OBJECT IDENTIFIER, + * templateMajorVersion INTEGER (0..4294967295) OPTIONAL, + * templateMinorVersion INTEGER (0..4294967295) OPTIONAL + * } + *``` + */ + static override schema(parameters: Schema.SchemaParameters<{ + templateID?: string, + templateMajorVersion?: string, + templateMinorVersion?: string, + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.ObjectIdentifier({ name: (names.templateID || EMPTY_STRING) }), + new asn1js.Integer({ + name: (names.templateMajorVersion || EMPTY_STRING), + optional: true + }), + new asn1js.Integer({ + name: (names.templateMinorVersion || EMPTY_STRING), + optional: true + }), + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + CertificateTemplate.schema({ + names: { + templateID: TEMPLATE_ID, + templateMajorVersion: TEMPLATE_MAJOR_VERSION, + templateMinorVersion: TEMPLATE_MINOR_VERSION + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.templateID = asn1.result.templateID.valueBlock.toString(); + if (TEMPLATE_MAJOR_VERSION in asn1.result) { + this.templateMajorVersion = asn1.result.templateMajorVersion.valueBlock.valueDec; + } + if (TEMPLATE_MINOR_VERSION in asn1.result) { + this.templateMinorVersion = asn1.result.templateMinorVersion.valueBlock.valueDec; + } + } + + public toSchema(): asn1js.Sequence { + // Create array for output sequence + const outputArray = []; + outputArray.push(new asn1js.ObjectIdentifier({ value: this.templateID })); + if (TEMPLATE_MAJOR_VERSION in this) { + outputArray.push(new asn1js.Integer({ value: this.templateMajorVersion })); + } + if (TEMPLATE_MINOR_VERSION in this) { + outputArray.push(new asn1js.Integer({ value: this.templateMinorVersion })); + } + + // Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + } + + public toJSON(): CertificateTemplateJson { + const res: CertificateTemplateJson = { + templateID: this.templateID + }; + + if (TEMPLATE_MAJOR_VERSION in this) + res.templateMajorVersion = this.templateMajorVersion; + + if (TEMPLATE_MINOR_VERSION in this) + res.templateMinorVersion = this.templateMinorVersion; + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/CertificationRequest.ts b/third_party/js/PKI.js/src/CertificationRequest.ts new file mode 100644 index 0000000000..2ab161bb21 --- /dev/null +++ b/third_party/js/PKI.js/src/CertificationRequest.ts @@ -0,0 +1,487 @@ +import * as asn1js from "asn1js"; +import * as pvtsutils from "pvtsutils"; +import * as pvutils from "pvutils"; +import * as common from "./common"; +import { PublicKeyInfo, PublicKeyInfoJson } from "./PublicKeyInfo"; +import { RelativeDistinguishedNames, RelativeDistinguishedNamesJson, RelativeDistinguishedNamesSchema } from "./RelativeDistinguishedNames"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson } from "./AlgorithmIdentifier"; +import { Attribute, AttributeJson, AttributeSchema } from "./Attribute"; +import * as Schema from "./Schema"; +import { CryptoEnginePublicKeyParams } from "./CryptoEngine/CryptoEngineInterface"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { EMPTY_BUFFER } from "./constants"; + +const TBS = "tbs"; +const VERSION = "version"; +const SUBJECT = "subject"; +const SPKI = "subjectPublicKeyInfo"; +const ATTRIBUTES = "attributes"; +const SIGNATURE_ALGORITHM = "signatureAlgorithm"; +const SIGNATURE_VALUE = "signatureValue"; +const CSR_INFO = "CertificationRequestInfo"; +const CSR_INFO_VERSION = `${CSR_INFO}.version`; +const CSR_INFO_SUBJECT = `${CSR_INFO}.subject`; +const CSR_INFO_SPKI = `${CSR_INFO}.subjectPublicKeyInfo`; +const CSR_INFO_ATTRS = `${CSR_INFO}.attributes`; +const CLEAR_PROPS = [ + CSR_INFO, + CSR_INFO_VERSION, + CSR_INFO_SUBJECT, + CSR_INFO_SPKI, + CSR_INFO_ATTRS, + SIGNATURE_ALGORITHM, + SIGNATURE_VALUE +]; + +export interface ICertificationRequest { + /** + * Value being signed + */ + tbs: ArrayBuffer; + /** + * Version number. It should be 0 + */ + version: number; + /** + * Distinguished name of the certificate subject + */ + subject: RelativeDistinguishedNames; + /** + * Information about the public key being certified + */ + subjectPublicKeyInfo: PublicKeyInfo; + /** + * Collection of attributes providing additional information about the subject of the certificate + */ + attributes?: Attribute[]; + + /** + * signature algorithm (and any associated parameters) under which the certification-request information is signed + */ + signatureAlgorithm: AlgorithmIdentifier; + /** + * result of signing the certification request information with the certification request subject's private key + */ + signatureValue: asn1js.BitString; +} + +/** + * JSON representation of {@link CertificationRequest} + */ +export interface CertificationRequestJson { + tbs: string; + version: number; + subject: RelativeDistinguishedNamesJson; + subjectPublicKeyInfo: PublicKeyInfoJson | JsonWebKey; + attributes?: AttributeJson[]; + signatureAlgorithm: AlgorithmIdentifierJson; + signatureValue: asn1js.BitStringJson; +} + +export interface CertificationRequestInfoParameters { + names?: { + blockName?: string; + CertificationRequestInfo?: string; + CertificationRequestInfoVersion?: string; + subject?: RelativeDistinguishedNamesSchema; + CertificationRequestInfoAttributes?: string; + attributes?: AttributeSchema; + }; +} + +function CertificationRequestInfo(parameters: CertificationRequestInfoParameters = {}) { + //CertificationRequestInfo ::= SEQUENCE { + // version INTEGER { v1(0) } (v1,...), + // subject Name, + // subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }}, + // attributes [0] Attributes{{ CRIAttributes }} + //} + + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.CertificationRequestInfo || CSR_INFO), + value: [ + new asn1js.Integer({ name: (names.CertificationRequestInfoVersion || CSR_INFO_VERSION) }), + RelativeDistinguishedNames.schema(names.subject || { + names: { + blockName: CSR_INFO_SUBJECT + } + }), + PublicKeyInfo.schema({ + names: { + blockName: CSR_INFO_SPKI + } + }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + new asn1js.Repeated({ + optional: true, // Because OpenSSL makes wrong ATTRIBUTES field + name: (names.CertificationRequestInfoAttributes || CSR_INFO_ATTRS), + value: Attribute.schema(names.attributes || {}) + }) + ] + }) + ] + })); +} + +export type CertificationRequestParameters = PkiObjectParameters & Partial<ICertificationRequest>; + +/** + * Represents the CertificationRequest structure described in [RFC2986](https://datatracker.ietf.org/doc/html/rfc2986) + * + * @example The following example demonstrates how to parse PKCS#11 certification request + * and verify its challenge password extension and signature value + * ```js + * const pkcs10 = pkijs.CertificationRequest.fromBER(pkcs10Raw); + * + * // Get and validate challenge password extension + * if (pkcs10.attributes) { + * const attrExtensions = pkcs10.attributes.find(o => o.type === "1.2.840.113549.1.9.14"); // pkcs-9-at-extensionRequest + * if (attrExtensions) { + * const extensions = new pkijs.Extensions({ schema: attrExtensions.values[0] }); + * for (const extension of extensions.extensions) { + * if (extension.extnID === "1.2.840.113549.1.9.7") { // pkcs-9-at-challengePassword + * const asn = asn1js.fromBER(extension.extnValue.valueBlock.valueHex); + * if (asn.result.valueBlock.value !== "passwordChallenge") { + * throw new Error("PKCS#11 certification request is invalid. Challenge password is incorrect"); + * } + * } + * } + * } + * } + * + * // Verify signature value + * const ok = await pkcs10.verify(); + * if (!ok) { + * throw Error("PKCS#11 certification request is invalid. Signature is wrong") + * } + * ``` + * + * @example The following example demonstrates how to create PKCS#11 certification request + * ```js + * // Get a "crypto" extension + * const crypto = pkijs.getCrypto(true); + * + * const pkcs10 = new pkijs.CertificationRequest(); + * + * pkcs10.subject.typesAndValues.push(new pkijs.AttributeTypeAndValue({ + * type: "2.5.4.3", + * value: new asn1js.Utf8String({ value: "Test" }) + * })); + * + * + * await pkcs10.subjectPublicKeyInfo.importKey(keys.publicKey); + * + * pkcs10.attributes = []; + * + * // Subject Alternative Name + * const altNames = new pkijs.GeneralNames({ + * names: [ + * new pkijs.GeneralName({ // email + * type: 1, + * value: "email@address.com" + * }), + * new pkijs.GeneralName({ // domain + * type: 2, + * value: "www.domain.com" + * }), + * ] + * }); + * + * // SubjectKeyIdentifier + * const subjectKeyIdentifier = await crypto.digest({ name: "SHA-1" }, pkcs10.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHex); + * + * pkcs10.attributes.push(new pkijs.Attribute({ + * type: "1.2.840.113549.1.9.14", // pkcs-9-at-extensionRequest + * values: [(new pkijs.Extensions({ + * extensions: [ + * new pkijs.Extension({ + * extnID: "2.5.29.14", // id-ce-subjectKeyIdentifier + * critical: false, + * extnValue: (new asn1js.OctetString({ valueHex: subjectKeyIdentifier })).toBER(false) + * }), + * new pkijs.Extension({ + * extnID: "2.5.29.17", // id-ce-subjectAltName + * critical: false, + * extnValue: altNames.toSchema().toBER(false) + * }), + * new pkijs.Extension({ + * extnID: "1.2.840.113549.1.9.7", // pkcs-9-at-challengePassword + * critical: false, + * extnValue: (new asn1js.PrintableString({ value: "passwordChallenge" })).toBER(false) + * }) + * ] + * })).toSchema()] + * })); + * + * // Signing final PKCS#10 request + * await pkcs10.sign(keys.privateKey, "SHA-256"); + * + * const pkcs10Raw = pkcs10.toSchema(true).toBER(); + * ``` + */ +export class CertificationRequest extends PkiObject implements ICertificationRequest { + + public static override CLASS_NAME = "CertificationRequest"; + + public tbsView!: Uint8Array; + /** + * @deprecated Since version 3.0.0 + */ + public get tbs(): ArrayBuffer { + return pvtsutils.BufferSourceConverter.toArrayBuffer(this.tbsView); + } + + /** + * @deprecated Since version 3.0.0 + */ + public set tbs(value: ArrayBuffer) { + this.tbsView = new Uint8Array(value); + } + public version!: number; + public subject!: RelativeDistinguishedNames; + public subjectPublicKeyInfo!: PublicKeyInfo; + public attributes?: Attribute[]; + public signatureAlgorithm!: AlgorithmIdentifier; + public signatureValue!: asn1js.BitString; + + /** + * Initializes a new instance of the {@link CertificationRequest} class + * @param parameters Initialization parameters + */ + constructor(parameters: CertificationRequestParameters = {}) { + super(); + + this.tbsView = new Uint8Array(pvutils.getParametersValue(parameters, TBS, CertificationRequest.defaultValues(TBS))); + this.version = pvutils.getParametersValue(parameters, VERSION, CertificationRequest.defaultValues(VERSION)); + this.subject = pvutils.getParametersValue(parameters, SUBJECT, CertificationRequest.defaultValues(SUBJECT)); + this.subjectPublicKeyInfo = pvutils.getParametersValue(parameters, SPKI, CertificationRequest.defaultValues(SPKI)); + if (ATTRIBUTES in parameters) { + this.attributes = pvutils.getParametersValue(parameters, ATTRIBUTES, CertificationRequest.defaultValues(ATTRIBUTES)); + } + this.signatureAlgorithm = pvutils.getParametersValue(parameters, SIGNATURE_ALGORITHM, CertificationRequest.defaultValues(SIGNATURE_ALGORITHM)); + this.signatureValue = pvutils.getParametersValue(parameters, SIGNATURE_VALUE, CertificationRequest.defaultValues(SIGNATURE_VALUE)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof TBS): ArrayBuffer; + public static override defaultValues(memberName: typeof VERSION): number; + public static override defaultValues(memberName: typeof SUBJECT): RelativeDistinguishedNames; + public static override defaultValues(memberName: typeof SPKI): PublicKeyInfo; + public static override defaultValues(memberName: typeof ATTRIBUTES): Attribute[]; + public static override defaultValues(memberName: typeof SIGNATURE_ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof SIGNATURE_VALUE): asn1js.BitString; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case TBS: + return EMPTY_BUFFER; + case VERSION: + return 0; + case SUBJECT: + return new RelativeDistinguishedNames(); + case SPKI: + return new PublicKeyInfo(); + case ATTRIBUTES: + return []; + case SIGNATURE_ALGORITHM: + return new AlgorithmIdentifier(); + case SIGNATURE_VALUE: + return new asn1js.BitString(); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * CertificationRequest ::= SEQUENCE { + * certificationRequestInfo CertificationRequestInfo, + * signatureAlgorithm AlgorithmIdentifier{{ SignatureAlgorithms }}, + * signature BIT STRING + * } + *``` + */ + static override schema(parameters: Schema.SchemaParameters<{ + certificationRequestInfo?: CertificationRequestInfoParameters; + signatureAlgorithm?: string; + signatureValue?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + value: [ + CertificationRequestInfo(names.certificationRequestInfo || {}), + new asn1js.Sequence({ + name: (names.signatureAlgorithm || SIGNATURE_ALGORITHM), + value: [ + new asn1js.ObjectIdentifier(), + new asn1js.Any({ optional: true }) + ] + }), + new asn1js.BitString({ name: (names.signatureValue || SIGNATURE_VALUE) }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + CertificationRequest.schema() + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.tbsView = (asn1.result.CertificationRequestInfo as asn1js.Sequence).valueBeforeDecodeView; + this.version = asn1.result[CSR_INFO_VERSION].valueBlock.valueDec; + this.subject = new RelativeDistinguishedNames({ schema: asn1.result[CSR_INFO_SUBJECT] }); + this.subjectPublicKeyInfo = new PublicKeyInfo({ schema: asn1.result[CSR_INFO_SPKI] }); + if (CSR_INFO_ATTRS in asn1.result) { + this.attributes = Array.from(asn1.result[CSR_INFO_ATTRS], element => new Attribute({ schema: element })); + } + this.signatureAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.signatureAlgorithm }); + this.signatureValue = asn1.result.signatureValue; + } + + /** + * Aux function making ASN1js Sequence from current TBS + * @returns + */ + protected encodeTBS(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray = [ + new asn1js.Integer({ value: this.version }), + this.subject.toSchema(), + this.subjectPublicKeyInfo.toSchema() + ]; + + if (ATTRIBUTES in this) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: Array.from(this.attributes || [], o => o.toSchema()) + })); + } + //#endregion + + return (new asn1js.Sequence({ + value: outputArray + })); + } + + public toSchema(encodeFlag = false): asn1js.Sequence { + let tbsSchema; + + if (encodeFlag === false) { + if (this.tbsView.byteLength === 0) { // No stored TBS part + return CertificationRequest.schema(); + } + + const asn1 = asn1js.fromBER(this.tbsView); + AsnError.assert(asn1, "PKCS#10 Certificate Request"); + + tbsSchema = asn1.result; + } else { + tbsSchema = this.encodeTBS(); + } + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: [ + tbsSchema, + this.signatureAlgorithm.toSchema(), + this.signatureValue + ] + })); + //#endregion + } + + public toJSON(): CertificationRequestJson { + const object: CertificationRequestJson = { + tbs: pvtsutils.Convert.ToHex(this.tbsView), + version: this.version, + subject: this.subject.toJSON(), + subjectPublicKeyInfo: this.subjectPublicKeyInfo.toJSON(), + signatureAlgorithm: this.signatureAlgorithm.toJSON(), + signatureValue: this.signatureValue.toJSON(), + }; + + if (ATTRIBUTES in this) { + object.attributes = Array.from(this.attributes || [], o => o.toJSON()); + } + + return object; + } + + /** + * Makes signature for current certification request + * @param privateKey WebCrypto private key + * @param hashAlgorithm String representing current hashing algorithm + * @param crypto Crypto engine + */ + async sign(privateKey: CryptoKey, hashAlgorithm = "SHA-1", crypto = common.getCrypto(true)): Promise<void> { + // Initial checking + if (!privateKey) { + throw new Error("Need to provide a private key for signing"); + } + + //#region Get a "default parameters" for current algorithm and set correct signature algorithm + const signatureParams = await crypto.getSignatureParameters(privateKey, hashAlgorithm); + const parameters = signatureParams.parameters; + this.signatureAlgorithm = signatureParams.signatureAlgorithm; + //#endregion + + //#region Create TBS data for signing + this.tbsView = new Uint8Array(this.encodeTBS().toBER()); + //#endregion + + //#region Signing TBS data on provided private key + const signature = await crypto.signWithPrivateKey(this.tbsView, privateKey, parameters as any); + this.signatureValue = new asn1js.BitString({ valueHex: signature }); + //#endregion + } + + /** + * Verify existing certification request signature + * @param crypto Crypto engine + * @returns Returns `true` if signature value is valid, otherwise `false` + */ + public async verify(crypto = common.getCrypto(true)): Promise<boolean> { + return crypto.verifyWithPublicKey(this.tbsView, this.signatureValue, this.subjectPublicKeyInfo, this.signatureAlgorithm); + } + + /** + * Importing public key for current certificate request + * @param parameters + * @param crypto Crypto engine + * @returns WebCrypt public key + */ + public async getPublicKey(parameters?: CryptoEnginePublicKeyParams, crypto = common.getCrypto(true)): Promise<CryptoKey> { + return crypto.getPublicKey(this.subjectPublicKeyInfo, this.signatureAlgorithm, parameters); + } + +} + diff --git a/third_party/js/PKI.js/src/ContentInfo.ts b/third_party/js/PKI.js/src/ContentInfo.ts new file mode 100644 index 0000000000..020e424345 --- /dev/null +++ b/third_party/js/PKI.js/src/ContentInfo.ts @@ -0,0 +1,171 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { id_ContentType_Data, id_ContentType_EncryptedData, id_ContentType_EnvelopedData, id_ContentType_SignedData } from "./ObjectIdentifiers"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const CONTENT_TYPE = "contentType"; +const CONTENT = "content"; +const CLEAR_PROPS = [CONTENT_TYPE, CONTENT]; + +export interface IContentInfo { + contentType: string; + content: any; +} + +export type ContentInfoParameters = PkiObjectParameters & Partial<IContentInfo>; + +export type ContentInfoSchema = Schema.SchemaParameters<{ + contentType?: string; + content?: string; +}>; + +export interface ContentInfoJson { + contentType: string; + content?: any; +} + +/** + * Represents the ContentInfo structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + */ +export class ContentInfo extends PkiObject implements IContentInfo { + + public static override CLASS_NAME = "ContentInfo"; + public static readonly DATA = id_ContentType_Data; + public static readonly SIGNED_DATA = id_ContentType_SignedData; + public static readonly ENVELOPED_DATA = id_ContentType_EnvelopedData; + public static readonly ENCRYPTED_DATA = id_ContentType_EncryptedData; + + public contentType!: string; + public content: any; + + /** + * Initializes a new instance of the {@link ContentInfo} class + * @param parameters Initialization parameters + */ + constructor(parameters: ContentInfoParameters = {}) { + super(); + + this.contentType = pvutils.getParametersValue(parameters, CONTENT_TYPE, ContentInfo.defaultValues(CONTENT_TYPE)); + this.content = pvutils.getParametersValue(parameters, CONTENT, ContentInfo.defaultValues(CONTENT)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof CONTENT_TYPE): string; + public static override defaultValues(memberName: typeof CONTENT): any; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case CONTENT_TYPE: + return EMPTY_STRING; + case CONTENT: + return new asn1js.Any(); + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + static compareWithDefault<T>(memberName: string, memberValue: T): memberValue is T { + switch (memberName) { + case CONTENT_TYPE: + return (typeof memberValue === "string" && + memberValue === this.defaultValues(CONTENT_TYPE)); + case CONTENT: + return (memberValue instanceof asn1js.Any); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * ContentInfo ::= SEQUENCE { + * contentType ContentType, + * content [0] EXPLICIT ANY DEFINED BY contentType } + *``` + */ + public static override schema(parameters: ContentInfoSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + if (("optional" in names) === false) { + names.optional = false; + } + + return (new asn1js.Sequence({ + name: (names.blockName || "ContentInfo"), + optional: names.optional, + value: [ + new asn1js.ObjectIdentifier({ name: (names.contentType || CONTENT_TYPE) }), + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [new asn1js.Any({ name: (names.content || CONTENT) })] // EXPLICIT ANY value + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + ContentInfo.schema() + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.contentType = asn1.result.contentType.valueBlock.toString(); + this.content = asn1.result.content; + } + + public toSchema(): asn1js.Sequence { + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: [ + new asn1js.ObjectIdentifier({ value: this.contentType }), + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [this.content] // EXPLICIT ANY value + }) + ] + })); + //#endregion + } + + public toJSON(): ContentInfoJson { + const object: ContentInfoJson = { + contentType: this.contentType + }; + + if (!(this.content instanceof asn1js.Any)) { + object.content = this.content.toJSON(); + } + + return object; + } + +} diff --git a/third_party/js/PKI.js/src/CryptoEngine/AbstractCryptoEngine.ts b/third_party/js/PKI.js/src/CryptoEngine/AbstractCryptoEngine.ts new file mode 100644 index 0000000000..7a91ca84b1 --- /dev/null +++ b/third_party/js/PKI.js/src/CryptoEngine/AbstractCryptoEngine.ts @@ -0,0 +1,111 @@ +import { BitString, OctetString } from "asn1js"; +import * as pvutils from "pvutils"; +import { AlgorithmIdentifier } from "../AlgorithmIdentifier"; +import { EMPTY_STRING } from "../constants"; +import { EncryptedContentInfo } from "../EncryptedContentInfo"; +import { PublicKeyInfo } from "../PublicKeyInfo"; +import * as type from "./CryptoEngineInterface"; + +export abstract class AbstractCryptoEngine implements type.ICryptoEngine { + public name: string; + public crypto: Crypto; + public subtle: SubtleCrypto; + + /** + * Constructor for CryptoEngine class + * @param parameters + */ + constructor(parameters: type.CryptoEngineParameters) { + this.crypto = parameters.crypto; + this.subtle = "webkitSubtle" in parameters.crypto + ? (parameters.crypto as any).webkitSubtle + : parameters.crypto.subtle; + this.name = pvutils.getParametersValue(parameters, "name", EMPTY_STRING); + } + + public abstract getOIDByAlgorithm(algorithm: Algorithm, safety?: boolean, target?: string): string; + public abstract getAlgorithmParameters(algorithmName: string, operation: type.CryptoEngineAlgorithmOperation): type.CryptoEngineAlgorithmParams; + public abstract getAlgorithmByOID<T extends Algorithm = Algorithm>(oid: string, safety?: boolean, target?: string): object | T; + public abstract getAlgorithmByOID<T extends Algorithm = Algorithm>(oid: string, safety: true, target?: string): T; + public abstract getAlgorithmByOID(oid: any, safety?: any, target?: any): object; + public abstract getHashAlgorithm(signatureAlgorithm: AlgorithmIdentifier): string; + public abstract getSignatureParameters(privateKey: CryptoKey, hashAlgorithm?: string): Promise<type.CryptoEngineSignatureParams>; + public abstract signWithPrivateKey(data: BufferSource, privateKey: CryptoKey, parameters: type.CryptoEngineSignWithPrivateKeyParams): Promise<ArrayBuffer>; + public abstract verifyWithPublicKey(data: BufferSource, signature: BitString | OctetString, publicKeyInfo: PublicKeyInfo, signatureAlgorithm: AlgorithmIdentifier, shaAlgorithm?: string): Promise<boolean>; + public abstract getPublicKey(publicKeyInfo: PublicKeyInfo, signatureAlgorithm: AlgorithmIdentifier, parameters?: type.CryptoEnginePublicKeyParams): Promise<CryptoKey>; + public abstract encryptEncryptedContentInfo(parameters: type.CryptoEngineEncryptParams): Promise<EncryptedContentInfo>; + public abstract decryptEncryptedContentInfo(parameters: type.CryptoEngineDecryptParams): Promise<ArrayBuffer>; + public abstract stampDataWithPassword(parameters: type.CryptoEngineStampDataWithPasswordParams): Promise<ArrayBuffer>; + public abstract verifyDataStampedWithPassword(parameters: type.CryptoEngineVerifyDataStampedWithPasswordParams): Promise<boolean>; + public async encrypt(algorithm: globalThis.AlgorithmIdentifier | RsaOaepParams | AesCtrParams | AesCbcParams | AesGcmParams, key: CryptoKey, data: BufferSource): Promise<ArrayBuffer>; + public async encrypt(...args: any[]): Promise<ArrayBuffer> { + return (this.subtle.encrypt as any)(...args); + } + + public decrypt(algorithm: globalThis.AlgorithmIdentifier | RsaOaepParams | AesCtrParams | AesCbcParams | AesGcmParams, key: CryptoKey, data: BufferSource): Promise<ArrayBuffer>; + public async decrypt(...args: any[]): Promise<ArrayBuffer> { + return (this.subtle.decrypt as any)(...args); + } + + public sign(algorithm: globalThis.AlgorithmIdentifier | RsaPssParams | EcdsaParams, key: CryptoKey, data: BufferSource): Promise<ArrayBuffer>; + public sign(...args: any[]): Promise<ArrayBuffer> { + return (this.subtle.sign as any)(...args); + } + + public verify(algorithm: globalThis.AlgorithmIdentifier | RsaPssParams | EcdsaParams, key: CryptoKey, signature: BufferSource, data: BufferSource): Promise<boolean>; + public async verify(...args: any[]): Promise<boolean> { + return (this.subtle.verify as any)(...args); + } + + public digest(algorithm: globalThis.AlgorithmIdentifier, data: BufferSource): Promise<ArrayBuffer>; + public async digest(...args: any[]) { + return (this.subtle.digest as any)(...args); + } + + public generateKey(algorithm: RsaHashedKeyGenParams | EcKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKeyPair>; + public generateKey(algorithm: AesKeyGenParams | HmacKeyGenParams | Pbkdf2Params, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKey>; + public generateKey(algorithm: globalThis.AlgorithmIdentifier, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKeyPair | CryptoKey>; + public async generateKey(...args: any[]): Promise<CryptoKey | CryptoKeyPair> { + return (this.subtle.generateKey as any)(...args); + } + + public deriveKey(algorithm: globalThis.AlgorithmIdentifier | EcdhKeyDeriveParams | HkdfParams | Pbkdf2Params, baseKey: CryptoKey, derivedKeyType: globalThis.AlgorithmIdentifier | HkdfParams | Pbkdf2Params | AesDerivedKeyParams | HmacImportParams, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKey>; + public deriveKey(algorithm: globalThis.AlgorithmIdentifier | EcdhKeyDeriveParams | HkdfParams | Pbkdf2Params, baseKey: CryptoKey, derivedKeyType: globalThis.AlgorithmIdentifier | HkdfParams | Pbkdf2Params | AesDerivedKeyParams | HmacImportParams, extractable: boolean, keyUsages: Iterable<KeyUsage>): Promise<CryptoKey>; + public async deriveKey(...args: any[]): Promise<CryptoKey> { + return (this.subtle.deriveKey as any)(...args); + } + + public deriveBits(algorithm: globalThis.AlgorithmIdentifier | EcdhKeyDeriveParams | HkdfParams | Pbkdf2Params, baseKey: CryptoKey, length: number): Promise<ArrayBuffer>; + public async deriveBits(...args: any[]): Promise<ArrayBuffer> { + return (this.subtle.deriveBits as any)(...args); + } + + public wrapKey(format: KeyFormat, key: CryptoKey, wrappingKey: CryptoKey, wrapAlgorithm: globalThis.AlgorithmIdentifier | RsaOaepParams | AesCtrParams | AesCbcParams | AesGcmParams): Promise<ArrayBuffer>; + public async wrapKey(...args: any[]): Promise<ArrayBuffer> { + return (this.subtle.wrapKey as any)(...args); + } + + public unwrapKey(format: KeyFormat, wrappedKey: BufferSource, unwrappingKey: CryptoKey, unwrapAlgorithm: globalThis.AlgorithmIdentifier | RsaOaepParams | AesCtrParams | AesCbcParams | AesGcmParams, unwrappedKeyAlgorithm: globalThis.AlgorithmIdentifier | HmacImportParams | RsaHashedImportParams | EcKeyImportParams | AesKeyAlgorithm, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKey>; + public unwrapKey(format: KeyFormat, wrappedKey: BufferSource, unwrappingKey: CryptoKey, unwrapAlgorithm: globalThis.AlgorithmIdentifier | RsaOaepParams | AesCtrParams | AesCbcParams | AesGcmParams, unwrappedKeyAlgorithm: globalThis.AlgorithmIdentifier | HmacImportParams | RsaHashedImportParams | EcKeyImportParams | AesKeyAlgorithm, extractable: boolean, keyUsages: Iterable<KeyUsage>): Promise<CryptoKey>; + public async unwrapKey(...args: any[]): Promise<CryptoKey> { + return (this.subtle.unwrapKey as any)(...args); + } + + exportKey(format: "jwk", key: CryptoKey): Promise<JsonWebKey>; + exportKey(format: "pkcs8" | "raw" | "spki", key: CryptoKey): Promise<ArrayBuffer>; + exportKey(...args: any[]): Promise<ArrayBuffer> | Promise<JsonWebKey> { + return (this.subtle.exportKey as any)(...args); + } + importKey(format: "jwk", keyData: JsonWebKey, algorithm: globalThis.AlgorithmIdentifier | RsaHashedImportParams | EcKeyImportParams | HmacImportParams | AesKeyAlgorithm, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKey>; + importKey(format: "pkcs8" | "raw" | "spki", keyData: BufferSource, algorithm: globalThis.AlgorithmIdentifier | RsaHashedImportParams | EcKeyImportParams | HmacImportParams | AesKeyAlgorithm, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKey>; + importKey(format: "jwk", keyData: JsonWebKey, algorithm: globalThis.AlgorithmIdentifier | RsaHashedImportParams | EcKeyImportParams | HmacImportParams | AesKeyAlgorithm, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKey>; + importKey(format: "pkcs8" | "raw" | "spki", keyData: BufferSource, algorithm: globalThis.AlgorithmIdentifier | RsaHashedImportParams | EcKeyImportParams | HmacImportParams | AesKeyAlgorithm, extractable: boolean, keyUsages: Iterable<KeyUsage>): Promise<CryptoKey>; + importKey(...args: any[]): Promise<CryptoKey> { + return (this.subtle.importKey as any)(...args); + } + + public getRandomValues<T extends ArrayBufferView | null>(array: T): T { + return this.crypto.getRandomValues(array); + } + +} diff --git a/third_party/js/PKI.js/src/CryptoEngine/CryptoEngine.ts b/third_party/js/PKI.js/src/CryptoEngine/CryptoEngine.ts new file mode 100644 index 0000000000..45f63f48f5 --- /dev/null +++ b/third_party/js/PKI.js/src/CryptoEngine/CryptoEngine.ts @@ -0,0 +1,2112 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import * as pvtsutils from "pvtsutils"; +import * as common from "../common"; +import { PublicKeyInfo } from "../PublicKeyInfo"; +import { PrivateKeyInfo } from "../PrivateKeyInfo"; +import { AlgorithmIdentifier } from "../AlgorithmIdentifier"; +import { EncryptedContentInfo } from "../EncryptedContentInfo"; +import { IRSASSAPSSParams, RSASSAPSSParams } from "../RSASSAPSSParams"; +import { PBKDF2Params } from "../PBKDF2Params"; +import { PBES2Params } from "../PBES2Params"; +import { ArgumentError, AsnError, ParameterError } from "../errors"; +import * as type from "./CryptoEngineInterface"; +import { AbstractCryptoEngine } from "./AbstractCryptoEngine"; +import { EMPTY_STRING } from "../constants"; +import { ECNamedCurves } from "../ECNamedCurves"; + +/** + * Making MAC key using algorithm described in B.2 of PKCS#12 standard. + */ +async function makePKCS12B2Key(cryptoEngine: CryptoEngine, hashAlgorithm: string, keyLength: number, password: ArrayBuffer, salt: ArrayBuffer, iterationCount: number) { + //#region Initial variables + let u: number; + let v: number; + + const result: number[] = []; + //#endregion + + //#region Get "u" and "v" values + switch (hashAlgorithm.toUpperCase()) { + case "SHA-1": + u = 20; // 160 + v = 64; // 512 + break; + case "SHA-256": + u = 32; // 256 + v = 64; // 512 + break; + case "SHA-384": + u = 48; // 384 + v = 128; // 1024 + break; + case "SHA-512": + u = 64; // 512 + v = 128; // 1024 + break; + default: + throw new Error("Unsupported hashing algorithm"); + } + //#endregion + + //#region Main algorithm making key + //#region Transform password to UTF-8 like string + const passwordViewInitial = new Uint8Array(password); + + const passwordTransformed = new ArrayBuffer((password.byteLength * 2) + 2); + const passwordTransformedView = new Uint8Array(passwordTransformed); + + for (let i = 0; i < passwordViewInitial.length; i++) { + passwordTransformedView[i * 2] = 0x00; + passwordTransformedView[i * 2 + 1] = passwordViewInitial[i]; + } + + passwordTransformedView[passwordTransformedView.length - 2] = 0x00; + passwordTransformedView[passwordTransformedView.length - 1] = 0x00; + + password = passwordTransformed.slice(0); + //#endregion + + //#region Construct a string D (the "diversifier") by concatenating v/8 copies of ID + const D = new ArrayBuffer(v); + const dView = new Uint8Array(D); + + for (let i = 0; i < D.byteLength; i++) + dView[i] = 3; // The ID value equal to "3" for MACing (see B.3 of standard) + //#endregion + + //#region Concatenate copies of the salt together to create a string S of length v * ceil(s / v) bytes (the final copy of the salt may be trunacted to create S) + const saltLength = salt.byteLength; + + const sLen = v * Math.ceil(saltLength / v); + const S = new ArrayBuffer(sLen); + const sView = new Uint8Array(S); + + const saltView = new Uint8Array(salt); + + for (let i = 0; i < sLen; i++) + sView[i] = saltView[i % saltLength]; + //#endregion + + //#region Concatenate copies of the password together to create a string P of length v * ceil(p / v) bytes (the final copy of the password may be truncated to create P) + const passwordLength = password.byteLength; + + const pLen = v * Math.ceil(passwordLength / v); + const P = new ArrayBuffer(pLen); + const pView = new Uint8Array(P); + + const passwordView = new Uint8Array(password); + + for (let i = 0; i < pLen; i++) + pView[i] = passwordView[i % passwordLength]; + //#endregion + + //#region Set I=S||P to be the concatenation of S and P + const sPlusPLength = S.byteLength + P.byteLength; + + let I = new ArrayBuffer(sPlusPLength); + let iView = new Uint8Array(I); + + iView.set(sView); + iView.set(pView, sView.length); + //#endregion + + //#region Set c=ceil(n / u) + const c = Math.ceil((keyLength >> 3) / u); + //#endregion + + //#region Initial variables + let internalSequence = Promise.resolve(I); + //#endregion + + //#region For i=1, 2, ..., c, do the following: + for (let i = 0; i <= c; i++) { + internalSequence = internalSequence.then(_I => { + //#region Create contecanetion of D and I + const dAndI = new ArrayBuffer(D.byteLength + _I.byteLength); + const dAndIView = new Uint8Array(dAndI); + + dAndIView.set(dView); + dAndIView.set(iView, dView.length); + //#endregion + + return dAndI; + }); + + //#region Make "iterationCount" rounds of hashing + for (let j = 0; j < iterationCount; j++) + internalSequence = internalSequence.then(roundBuffer => cryptoEngine.digest({ name: hashAlgorithm }, new Uint8Array(roundBuffer))); + //#endregion + + internalSequence = internalSequence.then(roundBuffer => { + //#region Concatenate copies of Ai to create a string B of length v bits (the final copy of Ai may be truncated to create B) + const B = new ArrayBuffer(v); + const bView = new Uint8Array(B); + + for (let j = 0; j < B.byteLength; j++) + bView[j] = (roundBuffer as any)[j % roundBuffer.byteLength]; // TODO roundBuffer is ArrayBuffer. It doesn't have indexed values + //#endregion + + //#region Make new I value + const k = Math.ceil(saltLength / v) + Math.ceil(passwordLength / v); + const iRound = []; + + let sliceStart = 0; + let sliceLength = v; + + for (let j = 0; j < k; j++) { + const chunk = Array.from(new Uint8Array(I.slice(sliceStart, sliceStart + sliceLength))); + sliceStart += v; + if ((sliceStart + v) > I.byteLength) + sliceLength = I.byteLength - sliceStart; + + let x = 0x1ff; + + for (let l = (B.byteLength - 1); l >= 0; l--) { + x >>= 8; + x += bView[l] + chunk[l]; + chunk[l] = (x & 0xff); + } + + iRound.push(...chunk); + } + + I = new ArrayBuffer(iRound.length); + iView = new Uint8Array(I); + + iView.set(iRound); + //#endregion + + result.push(...(new Uint8Array(roundBuffer))); + + return I; + }); + } + //#endregion + + //#region Initialize final key + internalSequence = internalSequence.then(() => { + const resultBuffer = new ArrayBuffer(keyLength >> 3); + const resultView = new Uint8Array(resultBuffer); + + resultView.set((new Uint8Array(result)).slice(0, keyLength >> 3)); + + return resultBuffer; + }); + //#endregion + //#endregion + + return internalSequence; +} + +function prepareAlgorithm(data: globalThis.AlgorithmIdentifier | EcdsaParams): Algorithm & { hash?: Algorithm; } { + const res = typeof data === "string" + ? { name: data } + : data; + + // TODO fix type casting `as EcdsaParams` + if ("hash" in (res as EcdsaParams)) { + return { + ...res, + hash: prepareAlgorithm((res as EcdsaParams).hash) + }; + } + + return res; +} + +/** + * Default cryptographic engine for Web Cryptography API + */ +export class CryptoEngine extends AbstractCryptoEngine { + + public override async importKey(format: KeyFormat, keyData: BufferSource | JsonWebKey, algorithm: globalThis.AlgorithmIdentifier, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKey> { + //#region Initial variables + let jwk: JsonWebKey = {}; + //#endregion + + const alg = prepareAlgorithm(algorithm); + + switch (format.toLowerCase()) { + case "raw": + return this.subtle.importKey("raw", keyData as BufferSource, algorithm, extractable, keyUsages); + case "spki": + { + const asn1 = asn1js.fromBER(pvtsutils.BufferSourceConverter.toArrayBuffer(keyData as BufferSource)); + AsnError.assert(asn1, "keyData"); + + const publicKeyInfo = new PublicKeyInfo(); + try { + publicKeyInfo.fromSchema(asn1.result); + } catch { + throw new ArgumentError("Incorrect keyData"); + } + + switch (alg.name.toUpperCase()) { + case "RSA-PSS": + { + //#region Get information about used hash function + if (!alg.hash) { + throw new ParameterError("hash", "algorithm.hash", "Incorrect hash algorithm: Hash algorithm is missed"); + } + switch (alg.hash.name.toUpperCase()) { + case "SHA-1": + jwk.alg = "PS1"; + break; + case "SHA-256": + jwk.alg = "PS256"; + break; + case "SHA-384": + jwk.alg = "PS384"; + break; + case "SHA-512": + jwk.alg = "PS512"; + break; + default: + throw new Error(`Incorrect hash algorithm: ${alg.hash.name.toUpperCase()}`); + } + //#endregion + } + // break omitted + // eslint-disable-next-line no-fallthrough + case "RSASSA-PKCS1-V1_5": + { + keyUsages = ["verify"]; // Override existing keyUsages value since the key is a public key + + jwk.kty = "RSA"; + jwk.ext = extractable; + jwk.key_ops = keyUsages; + + if (publicKeyInfo.algorithm.algorithmId !== "1.2.840.113549.1.1.1") + throw new Error(`Incorrect public key algorithm: ${publicKeyInfo.algorithm.algorithmId}`); + + //#region Get information about used hash function + if (!jwk.alg) { + if (!alg.hash) { + throw new ParameterError("hash", "algorithm.hash", "Incorrect hash algorithm: Hash algorithm is missed"); + } + switch (alg.hash.name.toUpperCase()) { + case "SHA-1": + jwk.alg = "RS1"; + break; + case "SHA-256": + jwk.alg = "RS256"; + break; + case "SHA-384": + jwk.alg = "RS384"; + break; + case "SHA-512": + jwk.alg = "RS512"; + break; + default: + throw new Error(`Incorrect hash algorithm: ${alg.hash.name.toUpperCase()}`); + } + } + //#endregion + + //#region Create RSA Public Key elements + const publicKeyJSON = publicKeyInfo.toJSON(); + Object.assign(jwk, publicKeyJSON); + //#endregion + } + break; + case "ECDSA": + keyUsages = ["verify"]; // Override existing keyUsages value since the key is a public key + // break omitted + // eslint-disable-next-line no-fallthrough + case "ECDH": + { + //#region Initial variables + jwk = { + kty: "EC", + ext: extractable, + key_ops: keyUsages + }; + //#endregion + + //#region Get information about algorithm + if (publicKeyInfo.algorithm.algorithmId !== "1.2.840.10045.2.1") { + throw new Error(`Incorrect public key algorithm: ${publicKeyInfo.algorithm.algorithmId}`); + } + //#endregion + + //#region Create ECDSA Public Key elements + const publicKeyJSON = publicKeyInfo.toJSON(); + Object.assign(jwk, publicKeyJSON); + //#endregion + } + break; + case "RSA-OAEP": + { + jwk.kty = "RSA"; + jwk.ext = extractable; + jwk.key_ops = keyUsages; + + if (this.name.toLowerCase() === "safari") + jwk.alg = "RSA-OAEP"; + else { + if (!alg.hash) { + throw new ParameterError("hash", "algorithm.hash", "Incorrect hash algorithm: Hash algorithm is missed"); + } + switch (alg.hash.name.toUpperCase()) { + case "SHA-1": + jwk.alg = "RSA-OAEP"; + break; + case "SHA-256": + jwk.alg = "RSA-OAEP-256"; + break; + case "SHA-384": + jwk.alg = "RSA-OAEP-384"; + break; + case "SHA-512": + jwk.alg = "RSA-OAEP-512"; + break; + default: + throw new Error(`Incorrect hash algorithm: ${alg.hash.name.toUpperCase()}`); + } + } + + //#region Create ECDSA Public Key elements + const publicKeyJSON = publicKeyInfo.toJSON(); + Object.assign(jwk, publicKeyJSON); + //#endregion + } + break; + case "RSAES-PKCS1-V1_5": + { + jwk.kty = "RSA"; + jwk.ext = extractable; + jwk.key_ops = keyUsages; + jwk.alg = "PS1"; + + const publicKeyJSON = publicKeyInfo.toJSON(); + Object.assign(jwk, publicKeyJSON); + } + break; + default: + throw new Error(`Incorrect algorithm name: ${alg.name.toUpperCase()}`); + } + } + break; + case "pkcs8": + { + const privateKeyInfo = new PrivateKeyInfo(); + + //#region Parse "PrivateKeyInfo" object + const asn1 = asn1js.fromBER(pvtsutils.BufferSourceConverter.toArrayBuffer(keyData as BufferSource)); + AsnError.assert(asn1, "keyData"); + + try { + privateKeyInfo.fromSchema(asn1.result); + } + catch (ex) { + throw new Error("Incorrect keyData"); + } + + if (!privateKeyInfo.parsedKey) + throw new Error("Incorrect keyData"); + //#endregion + + switch (alg.name.toUpperCase()) { + case "RSA-PSS": + { + //#region Get information about used hash function + switch (alg.hash?.name.toUpperCase()) { + case "SHA-1": + jwk.alg = "PS1"; + break; + case "SHA-256": + jwk.alg = "PS256"; + break; + case "SHA-384": + jwk.alg = "PS384"; + break; + case "SHA-512": + jwk.alg = "PS512"; + break; + default: + throw new Error(`Incorrect hash algorithm: ${alg.hash?.name.toUpperCase()}`); + } + //#endregion + } + // break omitted + // eslint-disable-next-line no-fallthrough + case "RSASSA-PKCS1-V1_5": + { + keyUsages = ["sign"]; // Override existing keyUsages value since the key is a private key + + jwk.kty = "RSA"; + jwk.ext = extractable; + jwk.key_ops = keyUsages; + + //#region Get information about used hash function + if (privateKeyInfo.privateKeyAlgorithm.algorithmId !== "1.2.840.113549.1.1.1") + throw new Error(`Incorrect private key algorithm: ${privateKeyInfo.privateKeyAlgorithm.algorithmId}`); + //#endregion + + //#region Get information about used hash function + if (("alg" in jwk) === false) { + switch (alg.hash?.name.toUpperCase()) { + case "SHA-1": + jwk.alg = "RS1"; + break; + case "SHA-256": + jwk.alg = "RS256"; + break; + case "SHA-384": + jwk.alg = "RS384"; + break; + case "SHA-512": + jwk.alg = "RS512"; + break; + default: + throw new Error(`Incorrect hash algorithm: ${alg.hash?.name.toUpperCase()}`); + } + } + //#endregion + + //#region Create RSA Private Key elements + const privateKeyJSON = privateKeyInfo.toJSON(); + Object.assign(jwk, privateKeyJSON); + //#endregion + } + break; + case "ECDSA": + keyUsages = ["sign"]; // Override existing keyUsages value since the key is a private key + // break omitted + // eslint-disable-next-line no-fallthrough + case "ECDH": + { + //#region Initial variables + jwk = { + kty: "EC", + ext: extractable, + key_ops: keyUsages + }; + //#endregion + + //#region Get information about used hash function + if (privateKeyInfo.privateKeyAlgorithm.algorithmId !== "1.2.840.10045.2.1") + throw new Error(`Incorrect algorithm: ${privateKeyInfo.privateKeyAlgorithm.algorithmId}`); + //#endregion + + //#region Create ECDSA Private Key elements + const privateKeyJSON = privateKeyInfo.toJSON(); + Object.assign(jwk, privateKeyJSON); + //#endregion + } + break; + case "RSA-OAEP": + { + jwk.kty = "RSA"; + jwk.ext = extractable; + jwk.key_ops = keyUsages; + + //#region Get information about used hash function + if (this.name.toLowerCase() === "safari") + jwk.alg = "RSA-OAEP"; + else { + switch (alg.hash?.name.toUpperCase()) { + case "SHA-1": + jwk.alg = "RSA-OAEP"; + break; + case "SHA-256": + jwk.alg = "RSA-OAEP-256"; + break; + case "SHA-384": + jwk.alg = "RSA-OAEP-384"; + break; + case "SHA-512": + jwk.alg = "RSA-OAEP-512"; + break; + default: + throw new Error(`Incorrect hash algorithm: ${alg.hash?.name.toUpperCase()}`); + } + } + //#endregion + + //#region Create RSA Private Key elements + const privateKeyJSON = privateKeyInfo.toJSON(); + Object.assign(jwk, privateKeyJSON); + //#endregion + } + break; + case "RSAES-PKCS1-V1_5": + { + keyUsages = ["decrypt"]; // Override existing keyUsages value since the key is a private key + + jwk.kty = "RSA"; + jwk.ext = extractable; + jwk.key_ops = keyUsages; + jwk.alg = "PS1"; + + //#region Create RSA Private Key elements + const privateKeyJSON = privateKeyInfo.toJSON(); + Object.assign(jwk, privateKeyJSON); + //#endregion + } + break; + default: + throw new Error(`Incorrect algorithm name: ${alg.name.toUpperCase()}`); + } + } + break; + case "jwk": + jwk = keyData as JsonWebKey; + break; + default: + throw new Error(`Incorrect format: ${format}`); + } + + //#region Special case for Safari browser (since its acting not as WebCrypto standard describes) + if (this.name.toLowerCase() === "safari") { + // Try to use both ways - import using ArrayBuffer and pure JWK (for Safari Technology Preview) + try { + return this.subtle.importKey("jwk", pvutils.stringToArrayBuffer(JSON.stringify(jwk)) as any, algorithm, extractable, keyUsages); + } catch { + return this.subtle.importKey("jwk", jwk, algorithm, extractable, keyUsages); + } + } + //#endregion + + return this.subtle.importKey("jwk", jwk, algorithm, extractable, keyUsages); + } + + /** + * Export WebCrypto keys to different formats + * @param format + * @param key + */ + public override exportKey(format: "jwk", key: CryptoKey): Promise<JsonWebKey>; + public override exportKey(format: Exclude<KeyFormat, "jwk">, key: CryptoKey): Promise<ArrayBuffer>; + public override exportKey(format: string, key: CryptoKey): Promise<ArrayBuffer | JsonWebKey>; + public override async exportKey(format: KeyFormat, key: CryptoKey): Promise<ArrayBuffer | JsonWebKey> { + let jwk = await this.subtle.exportKey("jwk", key); + + //#region Currently Safari returns ArrayBuffer as JWK thus we need an additional transformation + if (this.name.toLowerCase() === "safari") { + // Some additional checks for Safari Technology Preview + if (jwk instanceof ArrayBuffer) { + jwk = JSON.parse(pvutils.arrayBufferToString(jwk)); + } + } + //#endregion + + switch (format.toLowerCase()) { + case "raw": + return this.subtle.exportKey("raw", key); + case "spki": { + const publicKeyInfo = new PublicKeyInfo(); + + try { + publicKeyInfo.fromJSON(jwk); + } + catch (ex) { + throw new Error("Incorrect key data"); + } + + return publicKeyInfo.toSchema().toBER(false); + } + case "pkcs8": { + const privateKeyInfo = new PrivateKeyInfo(); + + try { + privateKeyInfo.fromJSON(jwk); + } + catch (ex) { + throw new Error("Incorrect key data"); + } + + return privateKeyInfo.toSchema().toBER(false); + } + case "jwk": + return jwk; + default: + throw new Error(`Incorrect format: ${format}`); + } + } + + /** + * Convert WebCrypto keys between different export formats + * @param inputFormat + * @param outputFormat + * @param keyData + * @param algorithm + * @param extractable + * @param keyUsages + */ + public async convert(inputFormat: KeyFormat, outputFormat: KeyFormat, keyData: ArrayBuffer | JsonWebKey, algorithm: Algorithm, extractable: boolean, keyUsages: KeyUsage[]) { + if (inputFormat.toLowerCase() === outputFormat.toLowerCase()) { + return keyData; + } + + const key = await this.importKey(inputFormat, keyData, algorithm, extractable, keyUsages); + return this.exportKey(outputFormat, key); + } + + /** + * Gets WebCrypto algorithm by wel-known OID + * @param oid algorithm identifier + * @param safety if `true` throws exception on unknown algorithm identifier + * @param target name of the target + * @returns Returns WebCrypto algorithm or an empty object + */ + public getAlgorithmByOID<T extends Algorithm = Algorithm>(oid: string, safety?: boolean, target?: string): T | object; + /** + * Gets WebCrypto algorithm by wel-known OID + * @param oid algorithm identifier + * @param safety if `true` throws exception on unknown algorithm identifier + * @param target name of the target + * @returns Returns WebCrypto algorithm + * @throws Throws {@link Error} exception if unknown algorithm identifier + */ + public getAlgorithmByOID<T extends Algorithm = Algorithm>(oid: string, safety: true, target?: string): T; + public getAlgorithmByOID(oid: string, safety = false, target?: string): any { + switch (oid) { + case "1.2.840.113549.1.1.1": + return { + name: "RSAES-PKCS1-v1_5" + }; + case "1.2.840.113549.1.1.5": + return { + name: "RSASSA-PKCS1-v1_5", + hash: { + name: "SHA-1" + } + }; + case "1.2.840.113549.1.1.11": + return { + name: "RSASSA-PKCS1-v1_5", + hash: { + name: "SHA-256" + } + }; + case "1.2.840.113549.1.1.12": + return { + name: "RSASSA-PKCS1-v1_5", + hash: { + name: "SHA-384" + } + }; + case "1.2.840.113549.1.1.13": + return { + name: "RSASSA-PKCS1-v1_5", + hash: { + name: "SHA-512" + } + }; + case "1.2.840.113549.1.1.10": + return { + name: "RSA-PSS" + }; + case "1.2.840.113549.1.1.7": + return { + name: "RSA-OAEP" + }; + case "1.2.840.10045.2.1": + case "1.2.840.10045.4.1": + return { + name: "ECDSA", + hash: { + name: "SHA-1" + } + }; + case "1.2.840.10045.4.3.2": + return { + name: "ECDSA", + hash: { + name: "SHA-256" + } + }; + case "1.2.840.10045.4.3.3": + return { + name: "ECDSA", + hash: { + name: "SHA-384" + } + }; + case "1.2.840.10045.4.3.4": + return { + name: "ECDSA", + hash: { + name: "SHA-512" + } + }; + case "1.3.133.16.840.63.0.2": + return { + name: "ECDH", + kdf: "SHA-1" + }; + case "1.3.132.1.11.1": + return { + name: "ECDH", + kdf: "SHA-256" + }; + case "1.3.132.1.11.2": + return { + name: "ECDH", + kdf: "SHA-384" + }; + case "1.3.132.1.11.3": + return { + name: "ECDH", + kdf: "SHA-512" + }; + case "2.16.840.1.101.3.4.1.2": + return { + name: "AES-CBC", + length: 128 + }; + case "2.16.840.1.101.3.4.1.22": + return { + name: "AES-CBC", + length: 192 + }; + case "2.16.840.1.101.3.4.1.42": + return { + name: "AES-CBC", + length: 256 + }; + case "2.16.840.1.101.3.4.1.6": + return { + name: "AES-GCM", + length: 128 + }; + case "2.16.840.1.101.3.4.1.26": + return { + name: "AES-GCM", + length: 192 + }; + case "2.16.840.1.101.3.4.1.46": + return { + name: "AES-GCM", + length: 256 + }; + case "2.16.840.1.101.3.4.1.4": + return { + name: "AES-CFB", + length: 128 + }; + case "2.16.840.1.101.3.4.1.24": + return { + name: "AES-CFB", + length: 192 + }; + case "2.16.840.1.101.3.4.1.44": + return { + name: "AES-CFB", + length: 256 + }; + case "2.16.840.1.101.3.4.1.5": + return { + name: "AES-KW", + length: 128 + }; + case "2.16.840.1.101.3.4.1.25": + return { + name: "AES-KW", + length: 192 + }; + case "2.16.840.1.101.3.4.1.45": + return { + name: "AES-KW", + length: 256 + }; + case "1.2.840.113549.2.7": + return { + name: "HMAC", + hash: { + name: "SHA-1" + } + }; + case "1.2.840.113549.2.9": + return { + name: "HMAC", + hash: { + name: "SHA-256" + } + }; + case "1.2.840.113549.2.10": + return { + name: "HMAC", + hash: { + name: "SHA-384" + } + }; + case "1.2.840.113549.2.11": + return { + name: "HMAC", + hash: { + name: "SHA-512" + } + }; + case "1.2.840.113549.1.9.16.3.5": + return { + name: "DH" + }; + case "1.3.14.3.2.26": + return { + name: "SHA-1" + }; + case "2.16.840.1.101.3.4.2.1": + return { + name: "SHA-256" + }; + case "2.16.840.1.101.3.4.2.2": + return { + name: "SHA-384" + }; + case "2.16.840.1.101.3.4.2.3": + return { + name: "SHA-512" + }; + case "1.2.840.113549.1.5.12": + return { + name: "PBKDF2" + }; + //#region Special case - OIDs for ECC curves + case "1.2.840.10045.3.1.7": + return { + name: "P-256" + }; + case "1.3.132.0.34": + return { + name: "P-384" + }; + case "1.3.132.0.35": + return { + name: "P-521" + }; + //#endregion + default: + } + + if (safety) { + throw new Error(`Unsupported algorithm identifier ${target ? `for ${target} ` : EMPTY_STRING}: ${oid}`); + } + + return {}; + } + + public getOIDByAlgorithm(algorithm: Algorithm, safety = false, target?: string): string { + let result = EMPTY_STRING; + + switch (algorithm.name.toUpperCase()) { + case "RSAES-PKCS1-V1_5": + result = "1.2.840.113549.1.1.1"; + break; + case "RSASSA-PKCS1-V1_5": + switch ((algorithm as any).hash.name.toUpperCase()) { + case "SHA-1": + result = "1.2.840.113549.1.1.5"; + break; + case "SHA-256": + result = "1.2.840.113549.1.1.11"; + break; + case "SHA-384": + result = "1.2.840.113549.1.1.12"; + break; + case "SHA-512": + result = "1.2.840.113549.1.1.13"; + break; + default: + } + break; + case "RSA-PSS": + result = "1.2.840.113549.1.1.10"; + break; + case "RSA-OAEP": + result = "1.2.840.113549.1.1.7"; + break; + case "ECDSA": + switch ((algorithm as any).hash.name.toUpperCase()) { + case "SHA-1": + result = "1.2.840.10045.4.1"; + break; + case "SHA-256": + result = "1.2.840.10045.4.3.2"; + break; + case "SHA-384": + result = "1.2.840.10045.4.3.3"; + break; + case "SHA-512": + result = "1.2.840.10045.4.3.4"; + break; + default: + } + break; + case "ECDH": + switch ((algorithm as any).kdf.toUpperCase()) // Non-standard addition - hash algorithm of KDF function + { + case "SHA-1": + result = "1.3.133.16.840.63.0.2"; // dhSinglePass-stdDH-sha1kdf-scheme + break; + case "SHA-256": + result = "1.3.132.1.11.1"; // dhSinglePass-stdDH-sha256kdf-scheme + break; + case "SHA-384": + result = "1.3.132.1.11.2"; // dhSinglePass-stdDH-sha384kdf-scheme + break; + case "SHA-512": + result = "1.3.132.1.11.3"; // dhSinglePass-stdDH-sha512kdf-scheme + break; + default: + } + break; + case "AES-CTR": + break; + case "AES-CBC": + switch ((algorithm as any).length) { + case 128: + result = "2.16.840.1.101.3.4.1.2"; + break; + case 192: + result = "2.16.840.1.101.3.4.1.22"; + break; + case 256: + result = "2.16.840.1.101.3.4.1.42"; + break; + default: + } + break; + case "AES-CMAC": + break; + case "AES-GCM": + switch ((algorithm as any).length) { + case 128: + result = "2.16.840.1.101.3.4.1.6"; + break; + case 192: + result = "2.16.840.1.101.3.4.1.26"; + break; + case 256: + result = "2.16.840.1.101.3.4.1.46"; + break; + default: + } + break; + case "AES-CFB": + switch ((algorithm as any).length) { + case 128: + result = "2.16.840.1.101.3.4.1.4"; + break; + case 192: + result = "2.16.840.1.101.3.4.1.24"; + break; + case 256: + result = "2.16.840.1.101.3.4.1.44"; + break; + default: + } + break; + case "AES-KW": + switch ((algorithm as any).length) { + case 128: + result = "2.16.840.1.101.3.4.1.5"; + break; + case 192: + result = "2.16.840.1.101.3.4.1.25"; + break; + case 256: + result = "2.16.840.1.101.3.4.1.45"; + break; + default: + } + break; + case "HMAC": + switch ((algorithm as any).hash.name.toUpperCase()) { + case "SHA-1": + result = "1.2.840.113549.2.7"; + break; + case "SHA-256": + result = "1.2.840.113549.2.9"; + break; + case "SHA-384": + result = "1.2.840.113549.2.10"; + break; + case "SHA-512": + result = "1.2.840.113549.2.11"; + break; + default: + } + break; + case "DH": + result = "1.2.840.113549.1.9.16.3.5"; + break; + case "SHA-1": + result = "1.3.14.3.2.26"; + break; + case "SHA-256": + result = "2.16.840.1.101.3.4.2.1"; + break; + case "SHA-384": + result = "2.16.840.1.101.3.4.2.2"; + break; + case "SHA-512": + result = "2.16.840.1.101.3.4.2.3"; + break; + case "CONCAT": + break; + case "HKDF": + break; + case "PBKDF2": + result = "1.2.840.113549.1.5.12"; + break; + //#region Special case - OIDs for ECC curves + case "P-256": + result = "1.2.840.10045.3.1.7"; + break; + case "P-384": + result = "1.3.132.0.34"; + break; + case "P-521": + result = "1.3.132.0.35"; + break; + //#endregion + default: + } + + if (!result && safety) { + throw new Error(`Unsupported algorithm ${target ? `for ${target} ` : EMPTY_STRING}: ${algorithm.name}`); + } + + return result; + } + + public getAlgorithmParameters(algorithmName: string, operation: type.CryptoEngineAlgorithmOperation): type.CryptoEngineAlgorithmParams { + let result: type.CryptoEngineAlgorithmParams = { + algorithm: {}, + usages: [] + }; + + switch (algorithmName.toUpperCase()) { + case "RSAES-PKCS1-V1_5": + case "RSASSA-PKCS1-V1_5": + switch (operation.toLowerCase()) { + case "generatekey": + result = { + algorithm: { + name: "RSASSA-PKCS1-v1_5", + modulusLength: 2048, + publicExponent: new Uint8Array([0x01, 0x00, 0x01]), + hash: { + name: "SHA-256" + } + }, + usages: ["sign", "verify"] + }; + break; + case "verify": + case "sign": + case "importkey": + result = { + algorithm: { + name: "RSASSA-PKCS1-v1_5", + hash: { + name: "SHA-256" + } + }, + usages: ["verify"] // For importKey("pkcs8") usage must be "sign" only + }; + break; + case "exportkey": + default: + return { + algorithm: { + name: "RSASSA-PKCS1-v1_5" + }, + usages: [] + }; + } + break; + case "RSA-PSS": + switch (operation.toLowerCase()) { + case "sign": + case "verify": + result = { + algorithm: { + name: "RSA-PSS", + hash: { + name: "SHA-1" + }, + saltLength: 20 + }, + usages: ["sign", "verify"] + }; + break; + case "generatekey": + result = { + algorithm: { + name: "RSA-PSS", + modulusLength: 2048, + publicExponent: new Uint8Array([0x01, 0x00, 0x01]), + hash: { + name: "SHA-1" + } + }, + usages: ["sign", "verify"] + }; + break; + case "importkey": + result = { + algorithm: { + name: "RSA-PSS", + hash: { + name: "SHA-1" + } + }, + usages: ["verify"] // For importKey("pkcs8") usage must be "sign" only + }; + break; + case "exportkey": + default: + return { + algorithm: { + name: "RSA-PSS" + }, + usages: [] + }; + } + break; + case "RSA-OAEP": + switch (operation.toLowerCase()) { + case "encrypt": + case "decrypt": + result = { + algorithm: { + name: "RSA-OAEP" + }, + usages: ["encrypt", "decrypt"] + }; + break; + case "generatekey": + result = { + algorithm: { + name: "RSA-OAEP", + modulusLength: 2048, + publicExponent: new Uint8Array([0x01, 0x00, 0x01]), + hash: { + name: "SHA-256" + } + }, + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"] + }; + break; + case "importkey": + result = { + algorithm: { + name: "RSA-OAEP", + hash: { + name: "SHA-256" + } + }, + usages: ["encrypt"] // encrypt for "spki" and decrypt for "pkcs8" + }; + break; + case "exportkey": + default: + return { + algorithm: { + name: "RSA-OAEP" + }, + usages: [] + }; + } + break; + case "ECDSA": + switch (operation.toLowerCase()) { + case "generatekey": + result = { + algorithm: { + name: "ECDSA", + namedCurve: "P-256" + }, + usages: ["sign", "verify"] + }; + break; + case "importkey": + result = { + algorithm: { + name: "ECDSA", + namedCurve: "P-256" + }, + usages: ["verify"] // "sign" for "pkcs8" + }; + break; + case "verify": + case "sign": + result = { + algorithm: { + name: "ECDSA", + hash: { + name: "SHA-256" + } + }, + usages: ["sign"] + }; + break; + default: + return { + algorithm: { + name: "ECDSA" + }, + usages: [] + }; + } + break; + case "ECDH": + switch (operation.toLowerCase()) { + case "exportkey": + case "importkey": + case "generatekey": + result = { + algorithm: { + name: "ECDH", + namedCurve: "P-256" + }, + usages: ["deriveKey", "deriveBits"] + }; + break; + case "derivekey": + case "derivebits": + result = { + algorithm: { + name: "ECDH", + namedCurve: "P-256", + public: [] // Must be a "publicKey" + }, + usages: ["encrypt", "decrypt"] + }; + break; + default: + return { + algorithm: { + name: "ECDH" + }, + usages: [] + }; + } + break; + case "AES-CTR": + switch (operation.toLowerCase()) { + case "importkey": + case "exportkey": + case "generatekey": + result = { + algorithm: { + name: "AES-CTR", + length: 256 + }, + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"] + }; + break; + case "decrypt": + case "encrypt": + result = { + algorithm: { + name: "AES-CTR", + counter: new Uint8Array(16), + length: 10 + }, + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"] + }; + break; + default: + return { + algorithm: { + name: "AES-CTR" + }, + usages: [] + }; + } + break; + case "AES-CBC": + switch (operation.toLowerCase()) { + case "importkey": + case "exportkey": + case "generatekey": + result = { + algorithm: { + name: "AES-CBC", + length: 256 + }, + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"] + }; + break; + case "decrypt": + case "encrypt": + result = { + algorithm: { + name: "AES-CBC", + iv: this.getRandomValues(new Uint8Array(16)) // For "decrypt" the value should be replaced with value got on "encrypt" step + }, + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"] + }; + break; + default: + return { + algorithm: { + name: "AES-CBC" + }, + usages: [] + }; + } + break; + case "AES-GCM": + switch (operation.toLowerCase()) { + case "importkey": + case "exportkey": + case "generatekey": + result = { + algorithm: { + name: "AES-GCM", + length: 256 + }, + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"] + }; + break; + case "decrypt": + case "encrypt": + result = { + algorithm: { + name: "AES-GCM", + iv: this.getRandomValues(new Uint8Array(16)) // For "decrypt" the value should be replaced with value got on "encrypt" step + }, + usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"] + }; + break; + default: + return { + algorithm: { + name: "AES-GCM" + }, + usages: [] + }; + } + break; + case "AES-KW": + switch (operation.toLowerCase()) { + case "importkey": + case "exportkey": + case "generatekey": + case "wrapkey": + case "unwrapkey": + result = { + algorithm: { + name: "AES-KW", + length: 256 + }, + usages: ["wrapKey", "unwrapKey"] + }; + break; + default: + return { + algorithm: { + name: "AES-KW" + }, + usages: [] + }; + } + break; + case "HMAC": + switch (operation.toLowerCase()) { + case "sign": + case "verify": + result = { + algorithm: { + name: "HMAC" + }, + usages: ["sign", "verify"] + }; + break; + case "importkey": + case "exportkey": + case "generatekey": + result = { + algorithm: { + name: "HMAC", + length: 32, + hash: { + name: "SHA-256" + } + }, + usages: ["sign", "verify"] + }; + break; + default: + return { + algorithm: { + name: "HMAC" + }, + usages: [] + }; + } + break; + case "HKDF": + switch (operation.toLowerCase()) { + case "derivekey": + result = { + algorithm: { + name: "HKDF", + hash: "SHA-256", + salt: new Uint8Array([]), + info: new Uint8Array([]) + }, + usages: ["encrypt", "decrypt"] + }; + break; + default: + return { + algorithm: { + name: "HKDF" + }, + usages: [] + }; + } + break; + case "PBKDF2": + switch (operation.toLowerCase()) { + case "derivekey": + result = { + algorithm: { + name: "PBKDF2", + hash: { name: "SHA-256" }, + salt: new Uint8Array([]), + iterations: 10000 + }, + usages: ["encrypt", "decrypt"] + }; + break; + default: + return { + algorithm: { + name: "PBKDF2" + }, + usages: [] + }; + } + break; + default: + } + + return result; + } + + /** + * Getting hash algorithm by signature algorithm + * @param signatureAlgorithm Signature algorithm + */ + // TODO use safety + getHashAlgorithm(signatureAlgorithm: AlgorithmIdentifier): string { + let result = EMPTY_STRING; + + switch (signatureAlgorithm.algorithmId) { + case "1.2.840.10045.4.1": // ecdsa-with-SHA1 + case "1.2.840.113549.1.1.5": // rsa-encryption-with-SHA1 + result = "SHA-1"; + break; + case "1.2.840.10045.4.3.2": // ecdsa-with-SHA256 + case "1.2.840.113549.1.1.11": // rsa-encryption-with-SHA256 + result = "SHA-256"; + break; + case "1.2.840.10045.4.3.3": // ecdsa-with-SHA384 + case "1.2.840.113549.1.1.12": // rsa-encryption-with-SHA384 + result = "SHA-384"; + break; + case "1.2.840.10045.4.3.4": // ecdsa-with-SHA512 + case "1.2.840.113549.1.1.13": // rsa-encryption-with-SHA512 + result = "SHA-512"; + break; + case "1.2.840.113549.1.1.10": // RSA-PSS + { + try { + const params = new RSASSAPSSParams({ schema: signatureAlgorithm.algorithmParams }); + if (params.hashAlgorithm) { + const algorithm = this.getAlgorithmByOID(params.hashAlgorithm.algorithmId); + if ("name" in algorithm) { + result = algorithm.name; + } + else { + return EMPTY_STRING; + } + } + else + result = "SHA-1"; + } + catch { + // nothing + } + } + break; + default: + } + + return result; + } + + public async encryptEncryptedContentInfo(parameters: type.CryptoEngineEncryptParams): Promise<EncryptedContentInfo> { + //#region Check for input parameters + ParameterError.assert(parameters, + "password", "contentEncryptionAlgorithm", "hmacHashAlgorithm", + "iterationCount", "contentToEncrypt", "contentToEncrypt", "contentType"); + + const contentEncryptionOID = this.getOIDByAlgorithm(parameters.contentEncryptionAlgorithm, true, "contentEncryptionAlgorithm"); + + const pbkdf2OID = this.getOIDByAlgorithm({ + name: "PBKDF2" + }, true, "PBKDF2"); + const hmacOID = this.getOIDByAlgorithm({ + name: "HMAC", + hash: { + name: parameters.hmacHashAlgorithm + } + } as Algorithm, true, "hmacHashAlgorithm"); + //#endregion + + //#region Initial variables + + // TODO Should we reuse iv from parameters.contentEncryptionAlgorithm or use it's length for ivBuffer? + const ivBuffer = new ArrayBuffer(16); // For AES we need IV 16 bytes long + const ivView = new Uint8Array(ivBuffer); + this.getRandomValues(ivView); + + const saltBuffer = new ArrayBuffer(64); + const saltView = new Uint8Array(saltBuffer); + this.getRandomValues(saltView); + + const contentView = new Uint8Array(parameters.contentToEncrypt); + + const pbkdf2Params = new PBKDF2Params({ + salt: new asn1js.OctetString({ valueHex: saltBuffer }), + iterationCount: parameters.iterationCount, + prf: new AlgorithmIdentifier({ + algorithmId: hmacOID, + algorithmParams: new asn1js.Null() + }) + }); + //#endregion + + //#region Derive PBKDF2 key from "password" buffer + const passwordView = new Uint8Array(parameters.password); + + const pbkdfKey = await this.importKey("raw", + passwordView, + "PBKDF2", + false, + ["deriveKey"]); + + //#endregion + + //#region Derive key for "contentEncryptionAlgorithm" + const derivedKey = await this.deriveKey({ + name: "PBKDF2", + hash: { + name: parameters.hmacHashAlgorithm + }, + salt: saltView, + iterations: parameters.iterationCount + }, + pbkdfKey, + parameters.contentEncryptionAlgorithm, + false, + ["encrypt"]); + //#endregion + + //#region Encrypt content + // TODO encrypt doesn't use all parameters from parameters.contentEncryptionAlgorithm (eg additionalData and tagLength for AES-GCM) + const encryptedData = await this.encrypt( + { + name: parameters.contentEncryptionAlgorithm.name, + iv: ivView + }, + derivedKey, + contentView); + //#endregion + + //#region Store all parameters in EncryptedData object + const pbes2Parameters = new PBES2Params({ + keyDerivationFunc: new AlgorithmIdentifier({ + algorithmId: pbkdf2OID, + algorithmParams: pbkdf2Params.toSchema() + }), + encryptionScheme: new AlgorithmIdentifier({ + algorithmId: contentEncryptionOID, + algorithmParams: new asn1js.OctetString({ valueHex: ivBuffer }) + }) + }); + + return new EncryptedContentInfo({ + contentType: parameters.contentType, + contentEncryptionAlgorithm: new AlgorithmIdentifier({ + algorithmId: "1.2.840.113549.1.5.13", // pkcs5PBES2 + algorithmParams: pbes2Parameters.toSchema() + }), + encryptedContent: new asn1js.OctetString({ valueHex: encryptedData }) + }); + //#endregion + } + + /** + * Decrypt data stored in "EncryptedContentInfo" object using parameters + * @param parameters + */ + public async decryptEncryptedContentInfo(parameters: type.CryptoEngineDecryptParams): Promise<ArrayBuffer> { + //#region Check for input parameters + ParameterError.assert(parameters, "password", "encryptedContentInfo"); + + if (parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId !== "1.2.840.113549.1.5.13") // pkcs5PBES2 + throw new Error(`Unknown "contentEncryptionAlgorithm": ${parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId}`); + //#endregion + + //#region Initial variables + let pbes2Parameters: PBES2Params; + + try { + pbes2Parameters = new PBES2Params({ schema: parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmParams }); + } + catch (ex) { + throw new Error("Incorrectly encoded \"pbes2Parameters\""); + } + + let pbkdf2Params; + + try { + pbkdf2Params = new PBKDF2Params({ schema: pbes2Parameters.keyDerivationFunc.algorithmParams }); + } + catch (ex) { + throw new Error("Incorrectly encoded \"pbkdf2Params\""); + } + + const contentEncryptionAlgorithm = this.getAlgorithmByOID(pbes2Parameters.encryptionScheme.algorithmId, true); + + const ivBuffer = pbes2Parameters.encryptionScheme.algorithmParams.valueBlock.valueHex; + const ivView = new Uint8Array(ivBuffer); + + const saltBuffer = pbkdf2Params.salt.valueBlock.valueHex; + const saltView = new Uint8Array(saltBuffer); + + const iterationCount = pbkdf2Params.iterationCount; + + let hmacHashAlgorithm = "SHA-1"; + + if (pbkdf2Params.prf) { + const algorithm = this.getAlgorithmByOID<any>(pbkdf2Params.prf.algorithmId, true); + hmacHashAlgorithm = algorithm.hash.name; + } + //#endregion + + //#region Derive PBKDF2 key from "password" buffer + const pbkdfKey = await this.importKey("raw", + parameters.password, + "PBKDF2", + false, + ["deriveKey"]); + //#endregion + + //#region Derive key for "contentEncryptionAlgorithm" + const result = await this.deriveKey( + { + name: "PBKDF2", + hash: { + name: hmacHashAlgorithm + }, + salt: saltView, + iterations: iterationCount + }, + pbkdfKey, + contentEncryptionAlgorithm as any, + false, + ["decrypt"]); + //#endregion + + //#region Decrypt internal content using derived key + //#region Create correct data block for decryption + const dataBuffer = parameters.encryptedContentInfo.getEncryptedContent(); + //#endregion + + return this.decrypt({ + name: contentEncryptionAlgorithm.name, + iv: ivView + }, + result, + dataBuffer); + //#endregion + } + + public async stampDataWithPassword(parameters: type.CryptoEngineStampDataWithPasswordParams): Promise<ArrayBuffer> { + //#region Check for input parameters + if ((parameters instanceof Object) === false) + throw new Error("Parameters must have type \"Object\""); + + ParameterError.assert(parameters, "password", "hashAlgorithm", "iterationCount", "salt", "contentToStamp"); + //#endregion + + //#region Choose correct length for HMAC key + let length: number; + + switch (parameters.hashAlgorithm.toLowerCase()) { + case "sha-1": + length = 160; + break; + case "sha-256": + length = 256; + break; + case "sha-384": + length = 384; + break; + case "sha-512": + length = 512; + break; + default: + throw new Error(`Incorrect "parameters.hashAlgorithm" parameter: ${parameters.hashAlgorithm}`); + } + //#endregion + + //#region Initial variables + const hmacAlgorithm = { + name: "HMAC", + length, + hash: { + name: parameters.hashAlgorithm + } + }; + //#endregion + + //#region Create PKCS#12 key for integrity checking + const pkcsKey = await makePKCS12B2Key(this, parameters.hashAlgorithm, length, parameters.password, parameters.salt, parameters.iterationCount); + //#endregion + + //#region Import HMAC key + + const hmacKey = await this.importKey("raw", + new Uint8Array(pkcsKey), + hmacAlgorithm, + false, + ["sign"]); + //#endregion + + //#region Make signed HMAC value + return this.sign(hmacAlgorithm, hmacKey, new Uint8Array(parameters.contentToStamp)); + //#endregion + } + + public async verifyDataStampedWithPassword(parameters: type.CryptoEngineVerifyDataStampedWithPasswordParams): Promise<boolean> { + //#region Check for input parameters + ParameterError.assert(parameters, + "password", "hashAlgorithm", "salt", + "iterationCount", "contentToVerify", "signatureToVerify"); + //#endregion + + //#region Choose correct length for HMAC key + let length = 0; + + switch (parameters.hashAlgorithm.toLowerCase()) { + case "sha-1": + length = 160; + break; + case "sha-256": + length = 256; + break; + case "sha-384": + length = 384; + break; + case "sha-512": + length = 512; + break; + default: + throw new Error(`Incorrect "parameters.hashAlgorithm" parameter: ${parameters.hashAlgorithm}`); + } + //#endregion + + //#region Initial variables + const hmacAlgorithm = { + name: "HMAC", + length, + hash: { + name: parameters.hashAlgorithm + } + }; + //#endregion + + //#region Create PKCS#12 key for integrity checking + const pkcsKey = await makePKCS12B2Key(this, parameters.hashAlgorithm, length, parameters.password, parameters.salt, parameters.iterationCount); + //#endregion + + //#region Import HMAC key + const hmacKey = await this.importKey("raw", + new Uint8Array(pkcsKey), + hmacAlgorithm, + false, + ["verify"]); + //#endregion + + //#region Make signed HMAC value + return this.verify(hmacAlgorithm, hmacKey, new Uint8Array(parameters.signatureToVerify), new Uint8Array(parameters.contentToVerify)); + //#endregion + } + + public async getSignatureParameters(privateKey: CryptoKey, hashAlgorithm = "SHA-1"): Promise<type.CryptoEngineSignatureParams> { + // Check hashing algorithm + this.getOIDByAlgorithm({ name: hashAlgorithm }, true, "hashAlgorithm"); + + // Initial variables + const signatureAlgorithm = new AlgorithmIdentifier(); + + //#region Get a "default parameters" for current algorithm + const parameters = this.getAlgorithmParameters(privateKey.algorithm.name, "sign"); + if (!Object.keys(parameters.algorithm).length) { + throw new Error("Parameter 'algorithm' is empty"); + } + const algorithm = parameters.algorithm as any; // TODO remove `as any` + algorithm.hash.name = hashAlgorithm; + //#endregion + + //#region Fill internal structures base on "privateKey" and "hashAlgorithm" + switch (privateKey.algorithm.name.toUpperCase()) { + case "RSASSA-PKCS1-V1_5": + case "ECDSA": + signatureAlgorithm.algorithmId = this.getOIDByAlgorithm(algorithm, true); + break; + case "RSA-PSS": + { + //#region Set "saltLength" as a length (in octets) of hash function result + switch (hashAlgorithm.toUpperCase()) { + case "SHA-256": + algorithm.saltLength = 32; + break; + case "SHA-384": + algorithm.saltLength = 48; + break; + case "SHA-512": + algorithm.saltLength = 64; + break; + default: + } + //#endregion + + //#region Fill "RSASSA_PSS_params" object + const paramsObject: Partial<IRSASSAPSSParams> = {}; + + if (hashAlgorithm.toUpperCase() !== "SHA-1") { + const hashAlgorithmOID = this.getOIDByAlgorithm({ name: hashAlgorithm }, true, "hashAlgorithm"); + + paramsObject.hashAlgorithm = new AlgorithmIdentifier({ + algorithmId: hashAlgorithmOID, + algorithmParams: new asn1js.Null() + }); + + paramsObject.maskGenAlgorithm = new AlgorithmIdentifier({ + algorithmId: "1.2.840.113549.1.1.8", // MGF1 + algorithmParams: paramsObject.hashAlgorithm.toSchema() + }); + } + + if (algorithm.saltLength !== 20) + paramsObject.saltLength = algorithm.saltLength; + + const pssParameters = new RSASSAPSSParams(paramsObject); + //#endregion + + //#region Automatically set signature algorithm + signatureAlgorithm.algorithmId = "1.2.840.113549.1.1.10"; + signatureAlgorithm.algorithmParams = pssParameters.toSchema(); + //#endregion + } + break; + default: + throw new Error(`Unsupported signature algorithm: ${privateKey.algorithm.name}`); + } + //#endregion + + return { + signatureAlgorithm, + parameters + }; + } + + public async signWithPrivateKey(data: BufferSource, privateKey: CryptoKey, parameters: type.CryptoEngineSignWithPrivateKeyParams): Promise<ArrayBuffer> { + const signature = await this.sign(parameters.algorithm, + privateKey, + data); + + //#region Special case for ECDSA algorithm + if (parameters.algorithm.name === "ECDSA") { + return common.createCMSECDSASignature(signature); + } + //#endregion + + return signature; + } + + public fillPublicKeyParameters(publicKeyInfo: PublicKeyInfo, signatureAlgorithm: AlgorithmIdentifier): type.CryptoEnginePublicKeyParams { + const parameters = {} as any; + + //#region Find signer's hashing algorithm + const shaAlgorithm = this.getHashAlgorithm(signatureAlgorithm); + if (shaAlgorithm === EMPTY_STRING) + throw new Error(`Unsupported signature algorithm: ${signatureAlgorithm.algorithmId}`); + //#endregion + + //#region Get information about public key algorithm and default parameters for import + let algorithmId: string; + if (signatureAlgorithm.algorithmId === "1.2.840.113549.1.1.10") + algorithmId = signatureAlgorithm.algorithmId; + else + algorithmId = publicKeyInfo.algorithm.algorithmId; + + const algorithmObject = this.getAlgorithmByOID(algorithmId, true); + + parameters.algorithm = this.getAlgorithmParameters(algorithmObject.name, "importKey"); + if ("hash" in parameters.algorithm.algorithm) + parameters.algorithm.algorithm.hash.name = shaAlgorithm; + + //#region Special case for ECDSA + if (algorithmObject.name === "ECDSA") { + //#region Get information about named curve + const publicKeyAlgorithm = publicKeyInfo.algorithm; + if (!publicKeyAlgorithm.algorithmParams) { + throw new Error("Algorithm parameters for ECDSA public key are missed"); + } + const publicKeyAlgorithmParams = publicKeyAlgorithm.algorithmParams; + if ("idBlock" in publicKeyAlgorithm.algorithmParams) { + if (!((publicKeyAlgorithmParams.idBlock.tagClass === 1) && (publicKeyAlgorithmParams.idBlock.tagNumber === 6))) { + throw new Error("Incorrect type for ECDSA public key parameters"); + } + } + + const curveObject = this.getAlgorithmByOID(publicKeyAlgorithmParams.valueBlock.toString(), true); + //#endregion + + parameters.algorithm.algorithm.namedCurve = curveObject.name; + } + //#endregion + //#endregion + + return parameters; + } + + public async getPublicKey(publicKeyInfo: PublicKeyInfo, signatureAlgorithm: AlgorithmIdentifier, parameters?: type.CryptoEnginePublicKeyParams): Promise<CryptoKey> { + if (!parameters) { + parameters = this.fillPublicKeyParameters(publicKeyInfo, signatureAlgorithm); + } + + const publicKeyInfoBuffer = publicKeyInfo.toSchema().toBER(false); + + return this.importKey("spki", + publicKeyInfoBuffer, + parameters.algorithm.algorithm as Algorithm, + true, + parameters.algorithm.usages + ); + } + + public async verifyWithPublicKey(data: BufferSource, signature: asn1js.BitString | asn1js.OctetString, publicKeyInfo: PublicKeyInfo, signatureAlgorithm: AlgorithmIdentifier, shaAlgorithm?: string): Promise<boolean> { + //#region Find signer's hashing algorithm + let publicKey: CryptoKey; + if (!shaAlgorithm) { + shaAlgorithm = this.getHashAlgorithm(signatureAlgorithm); + if (!shaAlgorithm) + throw new Error(`Unsupported signature algorithm: ${signatureAlgorithm.algorithmId}`); + + //#region Import public key + publicKey = await this.getPublicKey(publicKeyInfo, signatureAlgorithm); + //#endregion + } else { + const parameters = {} as type.CryptoEnginePublicKeyParams; + + //#region Get information about public key algorithm and default parameters for import + let algorithmId; + if (signatureAlgorithm.algorithmId === "1.2.840.113549.1.1.10") + algorithmId = signatureAlgorithm.algorithmId; + else + algorithmId = publicKeyInfo.algorithm.algorithmId; + + const algorithmObject = this.getAlgorithmByOID(algorithmId, true); + + parameters.algorithm = this.getAlgorithmParameters(algorithmObject.name, "importKey"); + if ("hash" in parameters.algorithm.algorithm) + (parameters.algorithm.algorithm as any).hash.name = shaAlgorithm; + + //#region Special case for ECDSA + if (algorithmObject.name === "ECDSA") { + //#region Get information about named curve + let algorithmParamsChecked = false; + + if (("algorithmParams" in publicKeyInfo.algorithm) === true) { + if ("idBlock" in publicKeyInfo.algorithm.algorithmParams) { + if ((publicKeyInfo.algorithm.algorithmParams.idBlock.tagClass === 1) && (publicKeyInfo.algorithm.algorithmParams.idBlock.tagNumber === 6)) + algorithmParamsChecked = true; + } + } + + if (algorithmParamsChecked === false) { + throw new Error("Incorrect type for ECDSA public key parameters"); + } + + const curveObject = this.getAlgorithmByOID(publicKeyInfo.algorithm.algorithmParams.valueBlock.toString(), true); + //#endregion + + (parameters.algorithm.algorithm as any).namedCurve = curveObject.name; + } + //#endregion + //#endregion + + //#region Import public key + + publicKey = await this.getPublicKey(publicKeyInfo, null as any, parameters); // TODO null!!! + //#endregion + } + //#endregion + + //#region Verify signature + //#region Get default algorithm parameters for verification + const algorithm = this.getAlgorithmParameters(publicKey.algorithm.name, "verify"); + if ("hash" in algorithm.algorithm) + (algorithm.algorithm as any).hash.name = shaAlgorithm; + //#endregion + + //#region Special case for ECDSA signatures + let signatureValue: BufferSource = signature.valueBlock.valueHexView; + + if (publicKey.algorithm.name === "ECDSA") { + const namedCurve = ECNamedCurves.find((publicKey.algorithm as EcKeyAlgorithm).namedCurve); + if (!namedCurve) { + throw new Error("Unsupported named curve in use"); + } + const asn1 = asn1js.fromBER(signatureValue); + AsnError.assert(asn1, "Signature value"); + signatureValue = common.createECDSASignatureFromCMS(asn1.result, namedCurve.size); + } + //#endregion + + //#region Special case for RSA-PSS + if (publicKey.algorithm.name === "RSA-PSS") { + const pssParameters = new RSASSAPSSParams({ schema: signatureAlgorithm.algorithmParams }); + + if ("saltLength" in pssParameters) + (algorithm.algorithm as any).saltLength = pssParameters.saltLength; + else + (algorithm.algorithm as any).saltLength = 20; + + let hashAlgo = "SHA-1"; + + if ("hashAlgorithm" in pssParameters) { + const hashAlgorithm = this.getAlgorithmByOID(pssParameters.hashAlgorithm.algorithmId, true); + + hashAlgo = hashAlgorithm.name; + } + + (algorithm.algorithm as any).hash.name = hashAlgo; + } + //#endregion + + return this.verify((algorithm.algorithm as any), + publicKey, + signatureValue, + data, + ); + //#endregion + } + +} + diff --git a/third_party/js/PKI.js/src/CryptoEngine/CryptoEngineInit.ts b/third_party/js/PKI.js/src/CryptoEngine/CryptoEngineInit.ts new file mode 100644 index 0000000000..93422f8add --- /dev/null +++ b/third_party/js/PKI.js/src/CryptoEngine/CryptoEngineInit.ts @@ -0,0 +1,23 @@ +import * as common from "../common"; +import { CryptoEngine } from "./CryptoEngine"; + +export function initCryptoEngine() { + if (typeof self !== "undefined") { + if ("crypto" in self) { + let engineName = "webcrypto"; + + // Apple Safari support + if ("webkitSubtle" in self.crypto) { + engineName = "safari"; + } + + common.setEngine(engineName, new CryptoEngine({ name: engineName, crypto: crypto })); + } + } else if (typeof crypto !== "undefined" && "webcrypto" in crypto) { + // NodeJS ^15 + const name = "NodeJS ^15"; + const nodeCrypto = (crypto as any).webcrypto as Crypto; + common.setEngine(name, new CryptoEngine({ name, crypto: nodeCrypto })); + } + +} diff --git a/third_party/js/PKI.js/src/CryptoEngine/CryptoEngineInterface.ts b/third_party/js/PKI.js/src/CryptoEngine/CryptoEngineInterface.ts new file mode 100644 index 0000000000..8aab69c604 --- /dev/null +++ b/third_party/js/PKI.js/src/CryptoEngine/CryptoEngineInterface.ts @@ -0,0 +1,183 @@ +import type * as asn1js from "asn1js"; +import type { AlgorithmIdentifier } from "../AlgorithmIdentifier"; +import type { EncryptedContentInfo } from "../EncryptedContentInfo"; +import type { PublicKeyInfo } from "../PublicKeyInfo"; + +export type CryptoEngineAlgorithmOperation = "sign" | "encrypt" | "generateKey" | "importKey" | "exportKey" | "verify"; + +/** + * Algorithm parameters + */ +export interface CryptoEngineAlgorithmParams { + /** + * Algorithm + */ + algorithm: Algorithm | object; + /** + * Key usages + */ + usages: KeyUsage[]; +} + +export interface CryptoEngineSignatureParams { + signatureAlgorithm: AlgorithmIdentifier; + parameters: CryptoEngineAlgorithmParams; +} + +export interface CryptoEngineSignWithPrivateKeyParams { + algorithm: Algorithm; +} + +/** + * Public key parameters + */ +export interface CryptoEnginePublicKeyParams { + /** + * Algorithm + */ + algorithm: CryptoEngineAlgorithmParams; +} + + +export type ContentEncryptionAesCbcParams = AesCbcParams & AesDerivedKeyParams; +export type ContentEncryptionAesGcmParams = AesGcmParams & AesDerivedKeyParams; +export type ContentEncryptionAlgorithm = ContentEncryptionAesCbcParams | ContentEncryptionAesGcmParams; + +export interface CryptoEngineEncryptParams { + password: ArrayBuffer; + contentEncryptionAlgorithm: ContentEncryptionAlgorithm; + hmacHashAlgorithm: string; + iterationCount: number; + contentToEncrypt: ArrayBuffer; + contentType: string; +} + +export interface CryptoEngineDecryptParams { + password: ArrayBuffer; + encryptedContentInfo: EncryptedContentInfo; +} + +export interface CryptoEngineStampDataWithPasswordParams { + password: ArrayBuffer; + hashAlgorithm: string; + salt: ArrayBuffer; + iterationCount: number; + contentToStamp: ArrayBuffer; +} + +export interface CryptoEngineVerifyDataStampedWithPasswordParams { + password: ArrayBuffer; + hashAlgorithm: string; + salt: ArrayBuffer; + iterationCount: number; + contentToVerify: ArrayBuffer; + signatureToVerify: ArrayBuffer; +} + +export interface ICryptoEngine extends SubtleCrypto { + name: string; + crypto: Crypto; + subtle: SubtleCrypto; + + getRandomValues<T extends ArrayBufferView | null>(array: T): T; + + /** + * Get OID for each specific algorithm + * @param algorithm WebCrypto Algorithm + * @param safety If `true` throws exception on unknown algorithm. Default is `false` + * @param target Name of the target + * @throws Throws {@link Error} exception if unknown WebCrypto algorithm + */ + getOIDByAlgorithm(algorithm: Algorithm, safety?: boolean, target?: string): string; + /** + * Get default algorithm parameters for each kind of operation + * @param algorithmName Algorithm name to get common parameters for + * @param operation Kind of operation: "sign", "encrypt", "generateKey", "importKey", "exportKey", "verify" + */ + // TODO Use safety + getAlgorithmParameters(algorithmName: string, operation: CryptoEngineAlgorithmOperation): CryptoEngineAlgorithmParams; + + /** + * Gets WebCrypto algorithm by wel-known OID + * @param oid algorithm identifier + * @param safety if `true` throws exception on unknown algorithm identifier + * @param target name of the target + * @returns Returns WebCrypto algorithm or an empty object + */ + getAlgorithmByOID<T extends Algorithm = Algorithm>(oid: string, safety?: boolean, target?: string): T | object; + /** + * Gets WebCrypto algorithm by wel-known OID + * @param oid algorithm identifier + * @param safety if `true` throws exception on unknown algorithm identifier + * @param target name of the target + * @returns Returns WebCrypto algorithm + * @throws Throws {@link Error} exception if unknown algorithm identifier + */ + getAlgorithmByOID<T extends Algorithm = Algorithm>(oid: string, safety: true, target?: string): T; + + /** + * Getting hash algorithm by signature algorithm + * @param signatureAlgorithm Signature algorithm + */ + // TODO use safety + getHashAlgorithm(signatureAlgorithm: AlgorithmIdentifier): string; + + /** + * Get signature parameters by analyzing private key algorithm + * @param privateKey The private key user would like to use + * @param hashAlgorithm Hash algorithm user would like to use. Default is SHA-1 + */ + getSignatureParameters(privateKey: CryptoKey, hashAlgorithm?: string): Promise<CryptoEngineSignatureParams>; + + /** + * Sign data with pre-defined private key + * @param data Data to be signed + * @param privateKey Private key to use + * @param parameters Parameters for used algorithm + */ + signWithPrivateKey(data: BufferSource, privateKey: CryptoKey, parameters: CryptoEngineSignWithPrivateKeyParams): Promise<ArrayBuffer>; + + /** + * Verify data with the public key + * @param data Data to be verified + * @param signature Signature value + * @param publicKeyInfo Public key information + * @param signatureAlgorithm Signature algorithm + * @param shaAlgorithm Hash algorithm + */ + verifyWithPublicKey(data: BufferSource, signature: asn1js.BitString | asn1js.OctetString, publicKeyInfo: PublicKeyInfo, signatureAlgorithm: AlgorithmIdentifier, shaAlgorithm?: string): Promise<boolean>; + + getPublicKey(publicKeyInfo: PublicKeyInfo, signatureAlgorithm: AlgorithmIdentifier, parameters?: CryptoEnginePublicKeyParams): Promise<CryptoKey>; + + /** + * Specialized function encrypting "EncryptedContentInfo" object using parameters + * @param parameters + */ + encryptEncryptedContentInfo(parameters: CryptoEngineEncryptParams): Promise<EncryptedContentInfo>; + + /** + * Decrypt data stored in "EncryptedContentInfo" object using parameters + * @param parameters + */ + decryptEncryptedContentInfo(parameters: CryptoEngineDecryptParams): Promise<ArrayBuffer>; + + /** + * Stamping (signing) data using algorithm similar to HMAC + * @param parameters + */ + stampDataWithPassword(parameters: CryptoEngineStampDataWithPasswordParams): Promise<ArrayBuffer>; + verifyDataStampedWithPassword(parameters: CryptoEngineVerifyDataStampedWithPasswordParams): Promise<boolean>; +} + +export interface CryptoEngineParameters { + name?: string; + crypto: Crypto; + /** + * @deprecated + */ + subtle?: SubtleCrypto; +} + +export interface CryptoEngineConstructor { + new(params: CryptoEngineParameters): ICryptoEngine; +} diff --git a/third_party/js/PKI.js/src/DigestInfo.ts b/third_party/js/PKI.js/src/DigestInfo.ts new file mode 100644 index 0000000000..232276b68d --- /dev/null +++ b/third_party/js/PKI.js/src/DigestInfo.ts @@ -0,0 +1,162 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const DIGEST_ALGORITHM = "digestAlgorithm"; +const DIGEST = "digest"; +const CLEAR_PROPS = [ + DIGEST_ALGORITHM, + DIGEST +]; + +export interface IDigestInfo { + digestAlgorithm: AlgorithmIdentifier; + digest: asn1js.OctetString; +} + +export interface DigestInfoJson { + digestAlgorithm: AlgorithmIdentifierJson; + digest: asn1js.OctetStringJson; +} + +export type DigestInfoParameters = PkiObjectParameters & Partial<IDigestInfo>; + +export type DigestInfoSchema = Schema.SchemaParameters<{ + digestAlgorithm?: AlgorithmIdentifierSchema; + digest?: string; +}>; + +/** + * Represents the DigestInfo structure described in [RFC3447](https://datatracker.ietf.org/doc/html/rfc3447) + */ +export class DigestInfo extends PkiObject implements IDigestInfo { + + public static override CLASS_NAME = "DigestInfo"; + + public digestAlgorithm!: AlgorithmIdentifier; + public digest!: asn1js.OctetString; + + /** + * Initializes a new instance of the {@link DigestInfo} class + * @param parameters Initialization parameters + */ + constructor(parameters: DigestInfoParameters = {}) { + super(); + + this.digestAlgorithm = pvutils.getParametersValue(parameters, DIGEST_ALGORITHM, DigestInfo.defaultValues(DIGEST_ALGORITHM)); + this.digest = pvutils.getParametersValue(parameters, DIGEST, DigestInfo.defaultValues(DIGEST)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof DIGEST_ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof DIGEST): asn1js.OctetString; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case DIGEST_ALGORITHM: + return new AlgorithmIdentifier(); + case DIGEST: + return new asn1js.OctetString(); + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case DIGEST_ALGORITHM: + return ((AlgorithmIdentifier.compareWithDefault("algorithmId", memberValue.algorithmId)) && + (("algorithmParams" in memberValue) === false)); + case DIGEST: + return (memberValue.isEqual(DigestInfo.defaultValues(memberName))); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * DigestInfo ::= SEQUENCE { + * digestAlgorithm DigestAlgorithmIdentifier, + * digest Digest } + * + * Digest ::= OCTET STRING + *``` + */ + public static override schema(parameters: DigestInfoSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + AlgorithmIdentifier.schema(names.digestAlgorithm || { + names: { + blockName: DIGEST_ALGORITHM + } + }), + new asn1js.OctetString({ name: (names.digest || DIGEST) }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + DigestInfo.schema({ + names: { + digestAlgorithm: { + names: { + blockName: DIGEST_ALGORITHM + } + }, + digest: DIGEST + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.digestAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.digestAlgorithm }); + this.digest = asn1.result.digest; + } + + public toSchema(): asn1js.Sequence { + // Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: [ + this.digestAlgorithm.toSchema(), + this.digest + ] + })); + } + + public toJSON(): DigestInfoJson { + return { + digestAlgorithm: this.digestAlgorithm.toJSON(), + digest: this.digest.toJSON(), + }; + } + +} diff --git a/third_party/js/PKI.js/src/DistributionPoint.ts b/third_party/js/PKI.js/src/DistributionPoint.ts new file mode 100644 index 0000000000..a8345f0bd3 --- /dev/null +++ b/third_party/js/PKI.js/src/DistributionPoint.ts @@ -0,0 +1,328 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { GeneralName, GeneralNameJson } from "./GeneralName"; +import { DistributionPointName, DistributionPointNameJson } from "./IssuingDistributionPoint"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { RelativeDistinguishedNames } from "./RelativeDistinguishedNames"; +import * as Schema from "./Schema"; + +const DISTRIBUTION_POINT = "distributionPoint"; +const DISTRIBUTION_POINT_NAMES = "distributionPointNames"; +const REASONS = "reasons"; +const CRL_ISSUER = "cRLIssuer"; +const CRL_ISSUER_NAMES = "cRLIssuerNames"; +const CLEAR_PROPS = [ + DISTRIBUTION_POINT, + DISTRIBUTION_POINT_NAMES, + REASONS, + CRL_ISSUER, + CRL_ISSUER_NAMES, +]; + +export interface IDistributionPoint { + distributionPoint?: DistributionPointName; + reasons?: asn1js.BitString; + cRLIssuer?: GeneralName[]; +} + +export interface DistributionPointJson { + distributionPoint?: DistributionPointNameJson; + reasons?: asn1js.BitStringJson; + cRLIssuer?: GeneralNameJson[]; +} + +export type DistributionPointParameters = PkiObjectParameters & Partial<IDistributionPoint>; + +/** + * Represents the DistributionPoint structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class DistributionPoint extends PkiObject implements IDistributionPoint { + + public static override CLASS_NAME = "DistributionPoint"; + + public distributionPoint?: DistributionPointName; + public reasons?: asn1js.BitString; + public cRLIssuer?: GeneralName[]; + + /** + * Initializes a new instance of the {@link DistributionPoint} class + * @param parameters Initialization parameters + */ + constructor(parameters: DistributionPointParameters = {}) { + super(); + + if (DISTRIBUTION_POINT in parameters) { + this.distributionPoint = pvutils.getParametersValue(parameters, DISTRIBUTION_POINT, DistributionPoint.defaultValues(DISTRIBUTION_POINT)); + } + if (REASONS in parameters) { + this.reasons = pvutils.getParametersValue(parameters, REASONS, DistributionPoint.defaultValues(REASONS)); + } + if (CRL_ISSUER in parameters) { + this.cRLIssuer = pvutils.getParametersValue(parameters, CRL_ISSUER, DistributionPoint.defaultValues(CRL_ISSUER)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof DISTRIBUTION_POINT): DistributionPointName; + public static override defaultValues(memberName: typeof REASONS): asn1js.BitString; + public static override defaultValues(memberName: typeof CRL_ISSUER): GeneralName[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case DISTRIBUTION_POINT: + return []; + case REASONS: + return new asn1js.BitString(); + case CRL_ISSUER: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * DistributionPoint ::= SEQUENCE { + * distributionPoint [0] DistributionPointName OPTIONAL, + * reasons [1] ReasonFlags OPTIONAL, + * cRLIssuer [2] GeneralNames OPTIONAL } + * + * DistributionPointName ::= CHOICE { + * fullName [0] GeneralNames, + * nameRelativeToCRLIssuer [1] RelativeDistinguishedName } + * + * ReasonFlags ::= BIT STRING { + * unused (0), + * keyCompromise (1), + * cACompromise (2), + * affiliationChanged (3), + * superseded (4), + * cessationOfOperation (5), + * certificateHold (6), + * privilegeWithdrawn (7), + * aACompromise (8) } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + distributionPoint?: string; + distributionPointNames?: string; + reasons?: string; + cRLIssuer?: string; + cRLIssuerNames?: string; + }> = {}): Schema.SchemaType { + /** + * @type {Object} + * @property {string} [blockName] + * @property {string} [distributionPoint] + * @property {string} [distributionPointNames] + * @property {string} [reasons] + * @property {string} [cRLIssuer] + * @property {string} [cRLIssuerNames] + */ + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + new asn1js.Choice({ + value: [ + new asn1js.Constructed({ + name: (names.distributionPoint || EMPTY_STRING), + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + new asn1js.Repeated({ + name: (names.distributionPointNames || EMPTY_STRING), + value: GeneralName.schema() + }) + ] + }), + new asn1js.Constructed({ + name: (names.distributionPoint || EMPTY_STRING), + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: RelativeDistinguishedNames.schema().valueBlock.value + }) + ] + }) + ] + }), + new asn1js.Primitive({ + name: (names.reasons || EMPTY_STRING), + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + } + }), // IMPLICIT BitString value + new asn1js.Constructed({ + name: (names.cRLIssuer || EMPTY_STRING), + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 2 // [2] + }, + value: [ + new asn1js.Repeated({ + name: (names.cRLIssuerNames || EMPTY_STRING), + value: GeneralName.schema() + }) + ] + }) // IMPLICIT BitString value + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + DistributionPoint.schema({ + names: { + distributionPoint: DISTRIBUTION_POINT, + distributionPointNames: DISTRIBUTION_POINT_NAMES, + reasons: REASONS, + cRLIssuer: CRL_ISSUER, + cRLIssuerNames: CRL_ISSUER_NAMES + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + //#region Get internal properties from parsed schema + if (DISTRIBUTION_POINT in asn1.result) { + if (asn1.result.distributionPoint.idBlock.tagNumber === 0) { // GENERAL_NAMES variant + this.distributionPoint = Array.from(asn1.result.distributionPointNames, element => new GeneralName({ schema: element })); + } + + if (asn1.result.distributionPoint.idBlock.tagNumber === 1) {// RDN variant + this.distributionPoint = new RelativeDistinguishedNames({ + schema: new asn1js.Sequence({ + value: asn1.result.distributionPoint.valueBlock.value + }) + }); + } + } + + if (REASONS in asn1.result) { + this.reasons = new asn1js.BitString({ valueHex: asn1.result.reasons.valueBlock.valueHex }); + } + + if (CRL_ISSUER in asn1.result) { + this.cRLIssuer = Array.from(asn1.result.cRLIssuerNames, element => new GeneralName({ schema: element })); + } + //#endregion + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray = []; + + if (this.distributionPoint) { + let internalValue; + + if (this.distributionPoint instanceof Array) { + internalValue = new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: Array.from(this.distributionPoint, o => o.toSchema()) + }); + } else { + internalValue = new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [this.distributionPoint.toSchema()] + }); + } + + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [internalValue] + })); + } + + if (this.reasons) { + outputArray.push(new asn1js.Primitive({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + valueHex: this.reasons.valueBlock.valueHexView + })); + } + + if (this.cRLIssuer) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 2 // [2] + }, + value: Array.from(this.cRLIssuer, o => o.toSchema()) + })); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): DistributionPointJson { + const object: DistributionPointJson = {}; + + if (this.distributionPoint) { + if (this.distributionPoint instanceof Array) { + object.distributionPoint = Array.from(this.distributionPoint, o => o.toJSON()); + } else { + object.distributionPoint = this.distributionPoint.toJSON(); + } + } + + if (this.reasons) { + object.reasons = this.reasons.toJSON(); + } + + if (this.cRLIssuer) { + object.cRLIssuer = Array.from(this.cRLIssuer, o => o.toJSON()); + } + + return object; + } + +} diff --git a/third_party/js/PKI.js/src/ECCCMSSharedInfo.ts b/third_party/js/PKI.js/src/ECCCMSSharedInfo.ts new file mode 100644 index 0000000000..88386b28fe --- /dev/null +++ b/third_party/js/PKI.js/src/ECCCMSSharedInfo.ts @@ -0,0 +1,213 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const KEY_INFO = "keyInfo"; +const ENTITY_U_INFO = "entityUInfo"; +const SUPP_PUB_INFO = "suppPubInfo"; +const CLEAR_PROPS = [ + KEY_INFO, + ENTITY_U_INFO, + SUPP_PUB_INFO +]; + +export interface IECCCMSSharedInfo { + keyInfo: AlgorithmIdentifier; + entityUInfo?: asn1js.OctetString; + suppPubInfo: asn1js.OctetString; +} + +export interface ECCCMSSharedInfoJson { + keyInfo: AlgorithmIdentifierJson; + entityUInfo?: asn1js.OctetStringJson; + suppPubInfo: asn1js.OctetStringJson; +} + +export type ECCCMSSharedInfoParameters = PkiObjectParameters & Partial<IECCCMSSharedInfo>; + +/** + * Represents the ECCCMSSharedInfo structure described in [RFC6318](https://datatracker.ietf.org/doc/html/rfc6318) + */ +export class ECCCMSSharedInfo extends PkiObject implements IECCCMSSharedInfo { + + public static override CLASS_NAME = "ECCCMSSharedInfo"; + + public keyInfo!: AlgorithmIdentifier; + public entityUInfo?: asn1js.OctetString; + public suppPubInfo!: asn1js.OctetString; + + /** + * Initializes a new instance of the {@link ECCCMSSharedInfo} class + * @param parameters Initialization parameters + */ + constructor(parameters: ECCCMSSharedInfoParameters = {}) { + super(); + + this.keyInfo = pvutils.getParametersValue(parameters, KEY_INFO, ECCCMSSharedInfo.defaultValues(KEY_INFO)); + if (ENTITY_U_INFO in parameters) { + this.entityUInfo = pvutils.getParametersValue(parameters, ENTITY_U_INFO, ECCCMSSharedInfo.defaultValues(ENTITY_U_INFO)); + } + this.suppPubInfo = pvutils.getParametersValue(parameters, SUPP_PUB_INFO, ECCCMSSharedInfo.defaultValues(SUPP_PUB_INFO)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof KEY_INFO): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof ENTITY_U_INFO): asn1js.OctetString; + public static override defaultValues(memberName: typeof SUPP_PUB_INFO): asn1js.OctetString; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case KEY_INFO: + return new AlgorithmIdentifier(); + case ENTITY_U_INFO: + return new asn1js.OctetString(); + case SUPP_PUB_INFO: + return new asn1js.OctetString(); + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case KEY_INFO: + case ENTITY_U_INFO: + case SUPP_PUB_INFO: + return (memberValue.isEqual(ECCCMSSharedInfo.defaultValues(memberName as typeof SUPP_PUB_INFO))); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * ECC-CMS-SharedInfo ::= SEQUENCE { + * keyInfo AlgorithmIdentifier, + * entityUInfo [0] EXPLICIT OCTET STRING OPTIONAL, + * suppPubInfo [2] EXPLICIT OCTET STRING } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + keyInfo?: AlgorithmIdentifierSchema; + entityUInfo?: string; + suppPubInfo?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + AlgorithmIdentifier.schema(names.keyInfo || {}), + new asn1js.Constructed({ + name: (names.entityUInfo || EMPTY_STRING), + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + optional: true, + value: [new asn1js.OctetString()] + }), + new asn1js.Constructed({ + name: (names.suppPubInfo || EMPTY_STRING), + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 2 // [2] + }, + value: [new asn1js.OctetString()] + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + ECCCMSSharedInfo.schema({ + names: { + keyInfo: { + names: { + blockName: KEY_INFO + } + }, + entityUInfo: ENTITY_U_INFO, + suppPubInfo: SUPP_PUB_INFO + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.keyInfo = new AlgorithmIdentifier({ schema: asn1.result.keyInfo }); + if (ENTITY_U_INFO in asn1.result) + this.entityUInfo = asn1.result.entityUInfo.valueBlock.value[0]; + this.suppPubInfo = asn1.result.suppPubInfo.valueBlock.value[0]; + } + + public toSchema(): asn1js.Sequence { + //#region Create output array for sequence + const outputArray = []; + + outputArray.push(this.keyInfo.toSchema()); + + if (this.entityUInfo) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [this.entityUInfo] + })); + } + + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 2 // [2] + }, + value: [this.suppPubInfo] + })); + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return new asn1js.Sequence({ + value: outputArray + }); + //#endregion + } + + public toJSON(): ECCCMSSharedInfoJson { + const res: ECCCMSSharedInfoJson = { + keyInfo: this.keyInfo.toJSON(), + suppPubInfo: this.suppPubInfo.toJSON(), + }; + + if (this.entityUInfo) { + res.entityUInfo = this.entityUInfo.toJSON(); + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/ECNamedCurves.ts b/third_party/js/PKI.js/src/ECNamedCurves.ts new file mode 100644 index 0000000000..123bad1558 --- /dev/null +++ b/third_party/js/PKI.js/src/ECNamedCurves.ts @@ -0,0 +1,53 @@ +export interface ECNamedCurve { + /** + * The curve ASN.1 object identifier + */ + id: string; + /** + * The name of the curve + */ + name: string; + /** + * The coordinate length in bytes + */ + size: number; +} + +export class ECNamedCurves { + + public static readonly namedCurves: Record<string, ECNamedCurve> = {}; + + /** + * Registers an ECC named curve + * @param name The name o the curve + * @param id The curve ASN.1 object identifier + * @param size The coordinate length in bytes + */ + public static register(name: string, id: string, size: number): void { + this.namedCurves[name.toLowerCase()] = this.namedCurves[id] = { name, id, size }; + } + + /** + * Returns an ECC named curve object + * @param nameOrId Name or identifier of the named curve + * @returns + */ + static find(nameOrId: string): ECNamedCurve | null { + return this.namedCurves[nameOrId.toLowerCase()] || null; + } + + static { + // Register default curves + + // NIST + this.register("P-256", "1.2.840.10045.3.1.7", 32); + this.register("P-384", "1.3.132.0.34", 48); + this.register("P-521", "1.3.132.0.35", 66); + + // Brainpool + this.register("brainpoolP256r1", "1.3.36.3.3.2.8.1.1.7", 32); + this.register("brainpoolP384r1", "1.3.36.3.3.2.8.1.1.11", 48); + this.register("brainpoolP512r1", "1.3.36.3.3.2.8.1.1.13", 64); + } + +} diff --git a/third_party/js/PKI.js/src/ECPrivateKey.ts b/third_party/js/PKI.js/src/ECPrivateKey.ts new file mode 100644 index 0000000000..bc7b06df26 --- /dev/null +++ b/third_party/js/PKI.js/src/ECPrivateKey.ts @@ -0,0 +1,297 @@ +import * as asn1js from "asn1js"; +import * as pvtsutils from "pvtsutils"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { ECNamedCurves } from "./ECNamedCurves"; +import { ECPublicKey, ECPublicKeyParameters } from "./ECPublicKey"; +import { AsnError, ParameterError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const VERSION = "version"; +const PRIVATE_KEY = "privateKey"; +const NAMED_CURVE = "namedCurve"; +const PUBLIC_KEY = "publicKey"; +const CLEAR_PROPS = [ + VERSION, + PRIVATE_KEY, + NAMED_CURVE, + PUBLIC_KEY +]; + +export interface IECPrivateKey { + version: number; + privateKey: asn1js.OctetString; + namedCurve?: string; + publicKey?: ECPublicKey; +} + +export type ECPrivateKeyParameters = PkiObjectParameters & Partial<IECPrivateKey> & { json?: ECPrivateKeyJson; }; + +export interface ECPrivateKeyJson { + crv: string; + y?: string; + x?: string; + d: string; +} + +/** + * Represents the PrivateKeyInfo structure described in [RFC5915](https://datatracker.ietf.org/doc/html/rfc5915) + */ +export class ECPrivateKey extends PkiObject implements IECPrivateKey { + + public static override CLASS_NAME = "ECPrivateKey"; + + public version!: number; + public privateKey!: asn1js.OctetString; + public namedCurve?: string; + public publicKey?: ECPublicKey; + + /** + * Initializes a new instance of the {@link ECPrivateKey} class + * @param parameters Initialization parameters + */ + constructor(parameters: ECPrivateKeyParameters = {}) { + super(); + + this.version = pvutils.getParametersValue(parameters, VERSION, ECPrivateKey.defaultValues(VERSION)); + this.privateKey = pvutils.getParametersValue(parameters, PRIVATE_KEY, ECPrivateKey.defaultValues(PRIVATE_KEY)); + if (NAMED_CURVE in parameters) { + this.namedCurve = pvutils.getParametersValue(parameters, NAMED_CURVE, ECPrivateKey.defaultValues(NAMED_CURVE)); + } + if (PUBLIC_KEY in parameters) { + this.publicKey = pvutils.getParametersValue(parameters, PUBLIC_KEY, ECPrivateKey.defaultValues(PUBLIC_KEY)); + } + + if (parameters.json) { + this.fromJSON(parameters.json); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof VERSION): 1; + public static override defaultValues(memberName: typeof PRIVATE_KEY): asn1js.OctetString; + public static override defaultValues(memberName: typeof NAMED_CURVE): string; + public static override defaultValues(memberName: typeof PUBLIC_KEY): ECPublicKey; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case VERSION: + return 1; + case PRIVATE_KEY: + return new asn1js.OctetString(); + case NAMED_CURVE: + return EMPTY_STRING; + case PUBLIC_KEY: + return new ECPublicKey(); + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case VERSION: + return (memberValue === ECPrivateKey.defaultValues(memberName)); + case PRIVATE_KEY: + return (memberValue.isEqual(ECPrivateKey.defaultValues(memberName))); + case NAMED_CURVE: + return (memberValue === EMPTY_STRING); + case PUBLIC_KEY: + return ((ECPublicKey.compareWithDefault(NAMED_CURVE, memberValue.namedCurve)) && + (ECPublicKey.compareWithDefault("x", memberValue.x)) && + (ECPublicKey.compareWithDefault("y", memberValue.y))); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * ECPrivateKey ::= SEQUENCE { + * version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1), + * privateKey OCTET STRING, + * parameters [0] ECParameters {{ NamedCurve }} OPTIONAL, + * publicKey [1] BIT STRING OPTIONAL + * } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + version?: string; + privateKey?: string; + namedCurve?: string; + publicKey?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Integer({ name: (names.version || EMPTY_STRING) }), + new asn1js.OctetString({ name: (names.privateKey || EMPTY_STRING) }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + new asn1js.ObjectIdentifier({ name: (names.namedCurve || EMPTY_STRING) }) + ] + }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [ + new asn1js.BitString({ name: (names.publicKey || EMPTY_STRING) }) + ] + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + ECPrivateKey.schema({ + names: { + version: VERSION, + privateKey: PRIVATE_KEY, + namedCurve: NAMED_CURVE, + publicKey: PUBLIC_KEY + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + //#region Get internal properties from parsed schema + this.version = asn1.result.version.valueBlock.valueDec; + this.privateKey = asn1.result.privateKey; + + if (NAMED_CURVE in asn1.result) { + this.namedCurve = asn1.result.namedCurve.valueBlock.toString(); + } + + if (PUBLIC_KEY in asn1.result) { + const publicKeyData: ECPublicKeyParameters = { schema: asn1.result.publicKey.valueBlock.valueHex }; + if (NAMED_CURVE in this) { + publicKeyData.namedCurve = this.namedCurve; + } + + this.publicKey = new ECPublicKey(publicKeyData); + } + //#endregion + } + + public toSchema(): asn1js.Sequence { + const outputArray: any = [ + new asn1js.Integer({ value: this.version }), + this.privateKey + ]; + + if (this.namedCurve) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + new asn1js.ObjectIdentifier({ value: this.namedCurve }) + ] + })); + } + + if (this.publicKey) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [ + new asn1js.BitString({ valueHex: this.publicKey.toSchema().toBER(false) }) + ] + })); + } + + return new asn1js.Sequence({ + value: outputArray + }); + } + + public toJSON(): ECPrivateKeyJson { + if (!this.namedCurve || ECPrivateKey.compareWithDefault(NAMED_CURVE, this.namedCurve)) { + throw new Error("Not enough information for making JSON: absent \"namedCurve\" value"); + } + + const curve = ECNamedCurves.find(this.namedCurve); + + const privateKeyJSON: ECPrivateKeyJson = { + crv: curve ? curve.name : this.namedCurve, + d: pvtsutils.Convert.ToBase64Url(this.privateKey.valueBlock.valueHexView), + }; + + if (this.publicKey) { + const publicKeyJSON = this.publicKey.toJSON(); + + privateKeyJSON.x = publicKeyJSON.x; + privateKeyJSON.y = publicKeyJSON.y; + } + + return privateKeyJSON; + } + + /** + * Converts JSON value into current object + * @param json JSON object + */ + public fromJSON(json: any): void { + ParameterError.assert("json", json, "crv", "d"); + + let coordinateLength = 0; + + const curve = ECNamedCurves.find(json.crv); + if (curve) { + this.namedCurve = curve.id; + coordinateLength = curve.size; + } + + const convertBuffer = pvtsutils.Convert.FromBase64Url(json.d); + + if (convertBuffer.byteLength < coordinateLength) { + const buffer = new ArrayBuffer(coordinateLength); + const view = new Uint8Array(buffer); + const convertBufferView = new Uint8Array(convertBuffer); + view.set(convertBufferView, 1); + + this.privateKey = new asn1js.OctetString({ valueHex: buffer }); + } else { + this.privateKey = new asn1js.OctetString({ valueHex: convertBuffer.slice(0, coordinateLength) }); + } + + if (json.x && json.y) { + this.publicKey = new ECPublicKey({ json }); + } + } + +} diff --git a/third_party/js/PKI.js/src/ECPublicKey.ts b/third_party/js/PKI.js/src/ECPublicKey.ts new file mode 100644 index 0000000000..4e50e92f47 --- /dev/null +++ b/third_party/js/PKI.js/src/ECPublicKey.ts @@ -0,0 +1,192 @@ +import * as asn1js from "asn1js"; +import { BufferSourceConverter } from "pvtsutils"; +import * as pvutils from "pvutils"; +import { EMPTY_BUFFER, EMPTY_STRING } from "./constants"; +import { ECNamedCurves } from "./ECNamedCurves"; +import { ParameterError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const X = "x"; +const Y = "y"; +const NAMED_CURVE = "namedCurve"; + +export interface IECPublicKey { + namedCurve: string; + x: ArrayBuffer; + y: ArrayBuffer; +} + +export interface ECPublicKeyJson { + crv: string; + x: string; + y: string; +} + +export type ECPublicKeyParameters = PkiObjectParameters & Partial<IECPublicKey> & { json?: ECPublicKeyJson; }; + +/** + * Represents the PrivateKeyInfo structure described in [RFC5480](https://datatracker.ietf.org/doc/html/rfc5480) + */ +export class ECPublicKey extends PkiObject implements IECPublicKey { + + public static override CLASS_NAME = "ECPublicKey"; + + public namedCurve!: string; + public x!: ArrayBuffer; + public y!: ArrayBuffer; + + /** + * Initializes a new instance of the {@link ECPublicKey} class + * @param parameters Initialization parameters + */ + constructor(parameters: ECPublicKeyParameters = {}) { + super(); + + this.x = pvutils.getParametersValue(parameters, X, ECPublicKey.defaultValues(X)); + this.y = pvutils.getParametersValue(parameters, Y, ECPublicKey.defaultValues(Y)); + this.namedCurve = pvutils.getParametersValue(parameters, NAMED_CURVE, ECPublicKey.defaultValues(NAMED_CURVE)); + + if (parameters.json) { + this.fromJSON(parameters.json); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof NAMED_CURVE): string; + public static override defaultValues(memberName: typeof X | typeof Y): ArrayBuffer; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case X: + case Y: + return EMPTY_BUFFER; + case NAMED_CURVE: + return EMPTY_STRING; + default: + return super.defaultValues(memberName); + } + } + + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + static compareWithDefault<T>(memberName: string, memberValue: T): memberValue is T { + switch (memberName) { + case X: + case Y: + return memberValue instanceof ArrayBuffer && + (pvutils.isEqualBuffer(memberValue, ECPublicKey.defaultValues(memberName))); + case NAMED_CURVE: + return typeof memberValue === "string" && + memberValue === ECPublicKey.defaultValues(memberName); + default: + return super.defaultValues(memberName); + } + } + + /** + * Returns value of pre-defined ASN.1 schema for current class + * @param parameters Input parameters for the schema + * @returns ASN.1 schema object + */ + public static override schema(): Schema.SchemaType { + return new asn1js.RawData(); + } + + public fromSchema(schema1: BufferSource): any { + //#region Check the schema is valid + + const view = BufferSourceConverter.toUint8Array(schema1); + if (view[0] !== 0x04) { + throw new Error("Object's schema was not verified against input data for ECPublicKey"); + } + //#endregion + + //#region Get internal properties from parsed schema + const namedCurve = ECNamedCurves.find(this.namedCurve); + if (!namedCurve) { + throw new Error(`Incorrect curve OID: ${this.namedCurve}`); + } + const coordinateLength = namedCurve.size; + + if (view.byteLength !== (coordinateLength * 2 + 1)) { + throw new Error("Object's schema was not verified against input data for ECPublicKey"); + } + + this.namedCurve = namedCurve.name; + this.x = view.slice(1, coordinateLength + 1).buffer; + this.y = view.slice(1 + coordinateLength, coordinateLength * 2 + 1).buffer; + //#endregion + } + + public toSchema(): asn1js.RawData { + return new asn1js.RawData({ + data: pvutils.utilConcatBuf( + (new Uint8Array([0x04])).buffer, + this.x, + this.y + ) + }); + } + + public toJSON(): ECPublicKeyJson { + const namedCurve = ECNamedCurves.find(this.namedCurve); + + return { + crv: namedCurve ? namedCurve.name : this.namedCurve, + x: pvutils.toBase64(pvutils.arrayBufferToString(this.x), true, true, false), + y: pvutils.toBase64(pvutils.arrayBufferToString(this.y), true, true, false) + }; + } + + /** + * Converts JSON value into current object + * @param json JSON object + */ + public fromJSON(json: any): void { + ParameterError.assert("json", json, "crv", "x", "y"); + + let coordinateLength = 0; + const namedCurve = ECNamedCurves.find(json.crv); + if (namedCurve) { + this.namedCurve = namedCurve.id; + coordinateLength = namedCurve.size; + } + + // TODO Simplify Base64url encoding + const xConvertBuffer = pvutils.stringToArrayBuffer(pvutils.fromBase64(json.x, true)); + + if (xConvertBuffer.byteLength < coordinateLength) { + this.x = new ArrayBuffer(coordinateLength); + const view = new Uint8Array(this.x); + const convertBufferView = new Uint8Array(xConvertBuffer); + view.set(convertBufferView, 1); + } else { + this.x = xConvertBuffer.slice(0, coordinateLength); + } + + // TODO Simplify Base64url encoding + const yConvertBuffer = pvutils.stringToArrayBuffer(pvutils.fromBase64(json.y, true)); + + if (yConvertBuffer.byteLength < coordinateLength) { + this.y = new ArrayBuffer(coordinateLength); + const view = new Uint8Array(this.y); + const convertBufferView = new Uint8Array(yConvertBuffer); + view.set(convertBufferView, 1); + } else { + this.y = yConvertBuffer.slice(0, coordinateLength); + } + } + +} diff --git a/third_party/js/PKI.js/src/EncapsulatedContentInfo.ts b/third_party/js/PKI.js/src/EncapsulatedContentInfo.ts new file mode 100644 index 0000000000..8424aecb0d --- /dev/null +++ b/third_party/js/PKI.js/src/EncapsulatedContentInfo.ts @@ -0,0 +1,221 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const E_CONTENT_TYPE = "eContentType"; +const E_CONTENT = "eContent"; +const CLEAR_PROPS = [ + E_CONTENT_TYPE, + E_CONTENT, +]; + +export interface IEncapsulatedContentInfo { + eContentType: string; + eContent?: asn1js.OctetString; +} + +export interface EncapsulatedContentInfoJson { + eContentType: string; + eContent?: asn1js.OctetStringJson; +} + +export type EncapsulatedContentInfoParameters = PkiObjectParameters & Partial<IEncapsulatedContentInfo>; + +export type EncapsulatedContentInfoSchema = Schema.SchemaParameters<{ + eContentType?: string; + eContent?: string; +}>; + +/** + * Represents the EncapsulatedContentInfo structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + */ +export class EncapsulatedContentInfo extends PkiObject implements IEncapsulatedContentInfo { + + public static override CLASS_NAME = "EncapsulatedContentInfo"; + + public eContentType!: string; + public eContent?: asn1js.OctetString; + + /** + * Initializes a new instance of the {@link EncapsulatedContentInfo} class + * @param parameters Initialization parameters + */ + constructor(parameters: EncapsulatedContentInfoParameters = {}) { + super(); + + this.eContentType = pvutils.getParametersValue(parameters, E_CONTENT_TYPE, EncapsulatedContentInfo.defaultValues(E_CONTENT_TYPE)); + if (E_CONTENT in parameters) { + this.eContent = pvutils.getParametersValue(parameters, E_CONTENT, EncapsulatedContentInfo.defaultValues(E_CONTENT)); + if ((this.eContent.idBlock.tagClass === 1) && + (this.eContent.idBlock.tagNumber === 4)) { + //#region Divide OCTET STRING value down to small pieces + if (this.eContent.idBlock.isConstructed === false) { + const constrString = new asn1js.OctetString({ + idBlock: { isConstructed: true }, + isConstructed: true + }); + + let offset = 0; + const viewHex = this.eContent.valueBlock.valueHexView.slice().buffer; + let length = viewHex.byteLength; + + while (length > 0) { + const pieceView = new Uint8Array(viewHex, offset, ((offset + 65536) > viewHex.byteLength) ? (viewHex.byteLength - offset) : 65536); + const _array = new ArrayBuffer(pieceView.length); + const _view = new Uint8Array(_array); + + for (let i = 0; i < _view.length; i++) { + _view[i] = pieceView[i]; + } + + constrString.valueBlock.value.push(new asn1js.OctetString({ valueHex: _array })); + + length -= pieceView.length; + offset += pieceView.length; + } + + this.eContent = constrString; + } + //#endregion + } + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof E_CONTENT_TYPE): string; + public static override defaultValues(memberName: typeof E_CONTENT): asn1js.OctetString; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case E_CONTENT_TYPE: + return EMPTY_STRING; + case E_CONTENT: + return new asn1js.OctetString(); + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case E_CONTENT_TYPE: + return (memberValue === EMPTY_STRING); + case E_CONTENT: + { + if ((memberValue.idBlock.tagClass === 1) && (memberValue.idBlock.tagNumber === 4)) + return (memberValue.isEqual(EncapsulatedContentInfo.defaultValues(E_CONTENT))); + + return false; + } + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * EncapsulatedContentInfo ::= SEQUENCE { + * eContentType ContentType, + * eContent [0] EXPLICIT OCTET STRING OPTIONAL } * Changed it to ANY, as in PKCS#7 + *``` + */ + public static override schema(parameters: EncapsulatedContentInfoSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.ObjectIdentifier({ name: (names.eContentType || EMPTY_STRING) }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + new asn1js.Any({ name: (names.eContent || EMPTY_STRING) }) // In order to aling this with PKCS#7 and CMS as well + ] + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + EncapsulatedContentInfo.schema({ + names: { + eContentType: E_CONTENT_TYPE, + eContent: E_CONTENT + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.eContentType = asn1.result.eContentType.valueBlock.toString(); + if (E_CONTENT in asn1.result) + this.eContent = asn1.result.eContent; + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray = []; + + outputArray.push(new asn1js.ObjectIdentifier({ value: this.eContentType })); + if (this.eContent) { + if (EncapsulatedContentInfo.compareWithDefault(E_CONTENT, this.eContent) === false) { + outputArray.push(new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [this.eContent] + })); + } + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): EncapsulatedContentInfoJson { + const res: EncapsulatedContentInfoJson = { + eContentType: this.eContentType + }; + + if (this.eContent && EncapsulatedContentInfo.compareWithDefault(E_CONTENT, this.eContent) === false) { + res.eContent = this.eContent.toJSON(); + } + + return res; + } + +} + diff --git a/third_party/js/PKI.js/src/EncryptedContentInfo.ts b/third_party/js/PKI.js/src/EncryptedContentInfo.ts new file mode 100644 index 0000000000..9dd6b6c270 --- /dev/null +++ b/third_party/js/PKI.js/src/EncryptedContentInfo.ts @@ -0,0 +1,285 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const CONTENT_TYPE = "contentType"; +const CONTENT_ENCRYPTION_ALGORITHM = "contentEncryptionAlgorithm"; +const ENCRYPTED_CONTENT = "encryptedContent"; +const CLEAR_PROPS = [ + CONTENT_TYPE, + CONTENT_ENCRYPTION_ALGORITHM, + ENCRYPTED_CONTENT, +]; + +export interface IEncryptedContentInfo { + contentType: string; + contentEncryptionAlgorithm: AlgorithmIdentifier; + encryptedContent?: asn1js.OctetString; +} + +export interface EncryptedContentInfoJson { + contentType: string; + contentEncryptionAlgorithm: AlgorithmIdentifierJson; + encryptedContent?: asn1js.OctetStringJson; +} + +export type EncryptedContentParameters = PkiObjectParameters & Partial<IEncryptedContentInfo>; + +export type EncryptedContentInfoSchema = Schema.SchemaParameters<{ + contentType?: string; + contentEncryptionAlgorithm?: AlgorithmIdentifierSchema; + encryptedContent?: string; +}>; + +/** + * Represents the EncryptedContentInfo structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + */ +export class EncryptedContentInfo extends PkiObject implements IEncryptedContentInfo { + + public static override CLASS_NAME = "EncryptedContentInfo"; + + public contentType!: string; + public contentEncryptionAlgorithm!: AlgorithmIdentifier; + public encryptedContent?: asn1js.OctetString; + + /** + * Initializes a new instance of the {@link EncryptedContent} class + * @param parameters Initialization parameters + */ + constructor(parameters: EncryptedContentParameters = {}) { + super(); + + this.contentType = pvutils.getParametersValue(parameters, CONTENT_TYPE, EncryptedContentInfo.defaultValues(CONTENT_TYPE)); + this.contentEncryptionAlgorithm = pvutils.getParametersValue(parameters, CONTENT_ENCRYPTION_ALGORITHM, EncryptedContentInfo.defaultValues(CONTENT_ENCRYPTION_ALGORITHM)); + + if (ENCRYPTED_CONTENT in parameters && parameters.encryptedContent) { + // encryptedContent (!!!) could be constructive or primitive value (!!!) + this.encryptedContent = parameters.encryptedContent; + + if ((this.encryptedContent.idBlock.tagClass === 1) && + (this.encryptedContent.idBlock.tagNumber === 4)) { + //#region Divide OCTET STRING value down to small pieces + if (this.encryptedContent.idBlock.isConstructed === false) { + const constrString = new asn1js.OctetString({ + idBlock: { isConstructed: true }, + isConstructed: true + }); + + let offset = 0; + const valueHex = this.encryptedContent.valueBlock.valueHexView.slice().buffer; + let length = valueHex.byteLength; + + const pieceSize = 1024; + while (length > 0) { + const pieceView = new Uint8Array(valueHex, offset, ((offset + pieceSize) > valueHex.byteLength) ? (valueHex.byteLength - offset) : pieceSize); + const _array = new ArrayBuffer(pieceView.length); + const _view = new Uint8Array(_array); + + for (let i = 0; i < _view.length; i++) + _view[i] = pieceView[i]; + + constrString.valueBlock.value.push(new asn1js.OctetString({ valueHex: _array })); + + length -= pieceView.length; + offset += pieceView.length; + } + + this.encryptedContent = constrString; + } + //#endregion + } + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof CONTENT_TYPE): string; + public static override defaultValues(memberName: typeof CONTENT_ENCRYPTION_ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof ENCRYPTED_CONTENT): asn1js.OctetString; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case CONTENT_TYPE: + return EMPTY_STRING; + case CONTENT_ENCRYPTION_ALGORITHM: + return new AlgorithmIdentifier(); + case ENCRYPTED_CONTENT: + return new asn1js.OctetString(); + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case CONTENT_TYPE: + return (memberValue === EMPTY_STRING); + case CONTENT_ENCRYPTION_ALGORITHM: + return ((memberValue.algorithmId === EMPTY_STRING) && (("algorithmParams" in memberValue) === false)); + case ENCRYPTED_CONTENT: + return (memberValue.isEqual(EncryptedContentInfo.defaultValues(ENCRYPTED_CONTENT))); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * EncryptedContentInfo ::= SEQUENCE { + * contentType ContentType, + * contentEncryptionAlgorithm ContentEncryptionAlgorithmIdentifier, + * encryptedContent [0] IMPLICIT EncryptedContent OPTIONAL } + * + * Comment: Strange, but modern crypto engines create ENCRYPTED_CONTENT as "[0] EXPLICIT EncryptedContent" + * + * EncryptedContent ::= OCTET STRING + *``` + */ + public static override schema(parameters: EncryptedContentInfoSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.ObjectIdentifier({ name: (names.contentType || EMPTY_STRING) }), + AlgorithmIdentifier.schema(names.contentEncryptionAlgorithm || {}), + // The CHOICE we need because ENCRYPTED_CONTENT could have either "constructive" + // or "primitive" form of encoding and we need to handle both variants + new asn1js.Choice({ + value: [ + new asn1js.Constructed({ + name: (names.encryptedContent || EMPTY_STRING), + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + new asn1js.Repeated({ + value: new asn1js.OctetString() + }) + ] + }), + new asn1js.Primitive({ + name: (names.encryptedContent || EMPTY_STRING), + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + } + }) + ] + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + EncryptedContentInfo.schema({ + names: { + contentType: CONTENT_TYPE, + contentEncryptionAlgorithm: { + names: { + blockName: CONTENT_ENCRYPTION_ALGORITHM + } + }, + encryptedContent: ENCRYPTED_CONTENT + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.contentType = asn1.result.contentType.valueBlock.toString(); + this.contentEncryptionAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.contentEncryptionAlgorithm }); + if (ENCRYPTED_CONTENT in asn1.result) { + this.encryptedContent = asn1.result.encryptedContent as asn1js.OctetString; + + this.encryptedContent.idBlock.tagClass = 1; // UNIVERSAL + this.encryptedContent.idBlock.tagNumber = 4; // OCTET STRING (!!!) The value still has instance of "in_window.org.pkijs.asn1.ASN1_CONSTRUCTED / ASN1_PRIMITIVE" + } + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output sequence + const sequenceLengthBlock = { + isIndefiniteForm: false + }; + + const outputArray = []; + + outputArray.push(new asn1js.ObjectIdentifier({ value: this.contentType })); + outputArray.push(this.contentEncryptionAlgorithm.toSchema()); + + if (this.encryptedContent) { + sequenceLengthBlock.isIndefiniteForm = this.encryptedContent.idBlock.isConstructed; + + const encryptedValue = this.encryptedContent; + + encryptedValue.idBlock.tagClass = 3; // CONTEXT-SPECIFIC + encryptedValue.idBlock.tagNumber = 0; // [0] + + encryptedValue.lenBlock.isIndefiniteForm = this.encryptedContent.idBlock.isConstructed; + + outputArray.push(encryptedValue); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + lenBlock: sequenceLengthBlock, + value: outputArray + })); + //#endregion + } + + public toJSON(): EncryptedContentInfoJson { + const res: EncryptedContentInfoJson = { + contentType: this.contentType, + contentEncryptionAlgorithm: this.contentEncryptionAlgorithm.toJSON() + }; + + if (this.encryptedContent) { + res.encryptedContent = this.encryptedContent.toJSON(); + } + + return res; + } + + /** + * Returns concatenated buffer from `encryptedContent` field. + * @returns Array buffer + * @since 3.0.0 + * @throws Throws Error if `encryptedContent` is undefined + */ + public getEncryptedContent(): ArrayBuffer { + if (!this.encryptedContent) { + throw new Error("Parameter 'encryptedContent' is undefined"); + } + // NOTE encryptedContent can be CONSTRUCTED/PRIMITIVE + return asn1js.OctetString.prototype.getValue.call(this.encryptedContent); + } + +} + diff --git a/third_party/js/PKI.js/src/EncryptedData.ts b/third_party/js/PKI.js/src/EncryptedData.ts new file mode 100644 index 0000000000..72076b8db3 --- /dev/null +++ b/third_party/js/PKI.js/src/EncryptedData.ts @@ -0,0 +1,303 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import * as common from "./common"; +import { EncryptedContentInfo, EncryptedContentInfoJson, EncryptedContentInfoSchema } from "./EncryptedContentInfo"; +import { Attribute, AttributeJson } from "./Attribute"; +import * as Schema from "./Schema"; +import { ArgumentError, AsnError } from "./errors"; +import { CryptoEngineEncryptParams } from "./CryptoEngine/CryptoEngineInterface"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { EMPTY_STRING } from "./constants"; + +const VERSION = "version"; +const ENCRYPTED_CONTENT_INFO = "encryptedContentInfo"; +const UNPROTECTED_ATTRS = "unprotectedAttrs"; +const CLEAR_PROPS = [ + VERSION, + ENCRYPTED_CONTENT_INFO, + UNPROTECTED_ATTRS, +]; + +export interface IEncryptedData { + /** + * Version number. + * + * If `unprotectedAttrs` is present, then the version MUST be 2. If `unprotectedAttrs` is absent, then version MUST be 0. + */ + version: number; + /** + * Encrypted content information + */ + encryptedContentInfo: EncryptedContentInfo; + /** + * Collection of attributes that are not encrypted + */ + unprotectedAttrs?: Attribute[]; +} + +export interface EncryptedDataJson { + version: number; + encryptedContentInfo: EncryptedContentInfoJson; + unprotectedAttrs?: AttributeJson[]; +} + +export type EncryptedDataParameters = PkiObjectParameters & Partial<IEncryptedData>; + +export type EncryptedDataEncryptParams = Omit<CryptoEngineEncryptParams, "contentType">; + +/** + * Represents the EncryptedData structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + * + * @example The following example demonstrates how to create and encrypt CMS Encrypted Data + * ```js + * const cmsEncrypted = new pkijs.EncryptedData(); + * + * await cmsEncrypted.encrypt({ + * contentEncryptionAlgorithm: { + * name: "AES-GCM", + * length: 256, + * }, + * hmacHashAlgorithm: "SHA-256", + * iterationCount: 1000, + * password: password, + * contentToEncrypt: dataToEncrypt, + * }); + * + * // Add Encrypted Data into CMS Content Info + * const cmsContent = new pkijs.ContentInfo(); + * cmsContent.contentType = pkijs.ContentInfo.ENCRYPTED_DATA; + * cmsContent.content = cmsEncrypted.toSchema(); + * + * const cmsContentRaw = cmsContent.toSchema().toBER(); + * ``` + * + * @example The following example demonstrates how to decrypt CMS Encrypted Data + * ```js + * // Parse CMS Content Info + * const cmsContent = pkijs.ContentInfo.fromBER(cmsContentRaw); + * if (cmsContent.contentType !== pkijs.ContentInfo.ENCRYPTED_DATA) { + * throw new Error("CMS is not Encrypted Data"); + * } + * // Parse CMS Encrypted Data + * const cmsEncrypted = new pkijs.EncryptedData({ schema: cmsContent.content }); + * + * // Decrypt data + * const decryptedData = await cmsEncrypted.decrypt({ + * password: password, + * }); + * ``` + */ +export class EncryptedData extends PkiObject implements IEncryptedData { + + public static override CLASS_NAME = "EncryptedData"; + + public version!: number; + public encryptedContentInfo!: EncryptedContentInfo; + public unprotectedAttrs?: Attribute[]; + + /** + * Initializes a new instance of the {@link EncryptedData} class + * @param parameters Initialization parameters + */ + constructor(parameters: EncryptedDataParameters = {}) { + super(); + + this.version = pvutils.getParametersValue(parameters, VERSION, EncryptedData.defaultValues(VERSION)); + this.encryptedContentInfo = pvutils.getParametersValue(parameters, ENCRYPTED_CONTENT_INFO, EncryptedData.defaultValues(ENCRYPTED_CONTENT_INFO)); + if (UNPROTECTED_ATTRS in parameters) { + this.unprotectedAttrs = pvutils.getParametersValue(parameters, UNPROTECTED_ATTRS, EncryptedData.defaultValues(UNPROTECTED_ATTRS)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof VERSION): number; + public static override defaultValues(memberName: typeof ENCRYPTED_CONTENT_INFO): EncryptedContentInfo; + public static override defaultValues(memberName: typeof UNPROTECTED_ATTRS): Attribute[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case VERSION: + return 0; + case ENCRYPTED_CONTENT_INFO: + return new EncryptedContentInfo(); + case UNPROTECTED_ATTRS: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case VERSION: + return (memberValue === 0); + case ENCRYPTED_CONTENT_INFO: + // TODO move to isEmpty method + return ((EncryptedContentInfo.compareWithDefault("contentType", memberValue.contentType)) && + (EncryptedContentInfo.compareWithDefault("contentEncryptionAlgorithm", memberValue.contentEncryptionAlgorithm)) && + (EncryptedContentInfo.compareWithDefault("encryptedContent", memberValue.encryptedContent))); + case UNPROTECTED_ATTRS: + return (memberValue.length === 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * EncryptedData ::= SEQUENCE { + * version CMSVersion, + * encryptedContentInfo EncryptedContentInfo, + * unprotectedAttrs [1] IMPLICIT UnprotectedAttributes OPTIONAL } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + version?: string; + encryptedContentInfo?: EncryptedContentInfoSchema; + unprotectedAttrs?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Integer({ name: (names.version || EMPTY_STRING) }), + EncryptedContentInfo.schema(names.encryptedContentInfo || {}), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [ + new asn1js.Repeated({ + name: (names.unprotectedAttrs || EMPTY_STRING), + value: Attribute.schema() + }) + ] + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + EncryptedData.schema({ + names: { + version: VERSION, + encryptedContentInfo: { + names: { + blockName: ENCRYPTED_CONTENT_INFO + } + }, + unprotectedAttrs: UNPROTECTED_ATTRS + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.version = asn1.result.version.valueBlock.valueDec; + this.encryptedContentInfo = new EncryptedContentInfo({ schema: asn1.result.encryptedContentInfo }); + if (UNPROTECTED_ATTRS in asn1.result) + this.unprotectedAttrs = Array.from(asn1.result.unprotectedAttrs, element => new Attribute({ schema: element })); + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray = []; + + outputArray.push(new asn1js.Integer({ value: this.version })); + outputArray.push(this.encryptedContentInfo.toSchema()); + + if (this.unprotectedAttrs) { + outputArray.push(new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: Array.from(this.unprotectedAttrs, o => o.toSchema()) + })); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): EncryptedDataJson { + const res: EncryptedDataJson = { + version: this.version, + encryptedContentInfo: this.encryptedContentInfo.toJSON() + }; + + if (this.unprotectedAttrs) + res.unprotectedAttrs = Array.from(this.unprotectedAttrs, o => o.toJSON()); + + return res; + } + + /** + * Creates a new CMS Encrypted Data content + * @param parameters Parameters necessary for encryption + */ + public async encrypt(parameters: EncryptedDataEncryptParams): Promise<void> { + //#region Check for input parameters + ArgumentError.assert(parameters, "parameters", "object"); + //#endregion + + //#region Set "contentType" parameter + const encryptParams: CryptoEngineEncryptParams = { + ...parameters, + contentType: "1.2.840.113549.1.7.1", + }; + //#endregion + + this.encryptedContentInfo = await common.getCrypto(true).encryptEncryptedContentInfo(encryptParams); + } + + /** + * Creates a new CMS Encrypted Data content + * @param parameters Parameters necessary for encryption + * @param crypto Crypto engine + * @returns Returns decrypted raw data + */ + async decrypt(parameters: { + password: ArrayBuffer; + }, crypto = common.getCrypto(true)): Promise<ArrayBuffer> { + // Check for input parameters + ArgumentError.assert(parameters, "parameters", "object"); + + // Set ENCRYPTED_CONTENT_INFO value + const decryptParams = { + ...parameters, + encryptedContentInfo: this.encryptedContentInfo, + }; + + return crypto.decryptEncryptedContentInfo(decryptParams); + } + +} diff --git a/third_party/js/PKI.js/src/EnvelopedData.ts b/third_party/js/PKI.js/src/EnvelopedData.ts new file mode 100644 index 0000000000..82e0f60a56 --- /dev/null +++ b/third_party/js/PKI.js/src/EnvelopedData.ts @@ -0,0 +1,1531 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import * as common from "./common"; +import { OriginatorInfo, OriginatorInfoJson } from "./OriginatorInfo"; +import { RecipientInfo, RecipientInfoJson } from "./RecipientInfo"; +import { EncryptedContentInfo, EncryptedContentInfoJson, EncryptedContentInfoSchema } from "./EncryptedContentInfo"; +import { Attribute, AttributeJson } from "./Attribute"; +import { AlgorithmIdentifier, AlgorithmIdentifierParameters } from "./AlgorithmIdentifier"; +import { RSAESOAEPParams } from "./RSAESOAEPParams"; +import { KeyTransRecipientInfo } from "./KeyTransRecipientInfo"; +import { IssuerAndSerialNumber } from "./IssuerAndSerialNumber"; +import { RecipientKeyIdentifier } from "./RecipientKeyIdentifier"; +import { RecipientEncryptedKey } from "./RecipientEncryptedKey"; +import { KeyAgreeRecipientIdentifier } from "./KeyAgreeRecipientIdentifier"; +import { KeyAgreeRecipientInfo, KeyAgreeRecipientInfoParameters } from "./KeyAgreeRecipientInfo"; +import { RecipientEncryptedKeys } from "./RecipientEncryptedKeys"; +import { KEKRecipientInfo } from "./KEKRecipientInfo"; +import { KEKIdentifier } from "./KEKIdentifier"; +import { PBKDF2Params } from "./PBKDF2Params"; +import { PasswordRecipientinfo } from "./PasswordRecipientinfo"; +import { ECCCMSSharedInfo } from "./ECCCMSSharedInfo"; +import { OriginatorIdentifierOrKey } from "./OriginatorIdentifierOrKey"; +import { OriginatorPublicKey } from "./OriginatorPublicKey"; +import * as Schema from "./Schema"; +import { Certificate } from "./Certificate"; +import { ArgumentError, AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { EMPTY_STRING } from "./constants"; + +const VERSION = "version"; +const ORIGINATOR_INFO = "originatorInfo"; +const RECIPIENT_INFOS = "recipientInfos"; +const ENCRYPTED_CONTENT_INFO = "encryptedContentInfo"; +const UNPROTECTED_ATTRS = "unprotectedAttrs"; +const CLEAR_PROPS = [ + VERSION, + ORIGINATOR_INFO, + RECIPIENT_INFOS, + ENCRYPTED_CONTENT_INFO, + UNPROTECTED_ATTRS +]; + +const defaultEncryptionParams = { + kdfAlgorithm: "SHA-512", + kekEncryptionLength: 256 +}; +const curveLengthByName: Record<string, number> = { + "P-256": 256, + "P-384": 384, + "P-521": 528 +}; + +export interface IEnvelopedData { + /** + * Version number. + * + * The appropriate value depends on `originatorInfo`, `RecipientInfo`, and `unprotectedAttrs`. + * + * The version MUST be assigned as follows: + * ``` + * IF (originatorInfo is present) AND + * ((any certificates with a type of other are present) OR + * (any crls with a type of other are present)) + * THEN version is 4 + * ELSE + * IF ((originatorInfo is present) AND + * (any version 2 attribute certificates are present)) OR + * (any RecipientInfo structures include pwri) OR + * (any RecipientInfo structures include ori) + * THEN version is 3 + * ELSE + * IF (originatorInfo is absent) AND + * (unprotectedAttrs is absent) AND + * (all RecipientInfo structures are version 0) + * THEN version is 0 + * ELSE version is 2 + * ``` + */ + version: number; + /** + * Optionally provides information about the originator. It is present only if required by the key management algorithm. + * It may contain certificates and CRLs. + */ + originatorInfo?: OriginatorInfo; + /** + * Collection of per-recipient information. There MUST be at least one element in the collection. + */ + recipientInfos: RecipientInfo[]; + /** + * Encrypted content information + */ + encryptedContentInfo: EncryptedContentInfo; + /** + * Collection of attributes that are not encrypted + */ + unprotectedAttrs?: Attribute[]; +} + +/** + * JSON representation of {@link EnvelopedData} + */ +export interface EnvelopedDataJson { + version: number; + originatorInfo?: OriginatorInfoJson; + recipientInfos: RecipientInfoJson[]; + encryptedContentInfo: EncryptedContentInfoJson; + unprotectedAttrs?: AttributeJson[]; +} + +export type EnvelopedDataParameters = PkiObjectParameters & Partial<IEnvelopedData>; + +export interface EnvelopedDataEncryptionParams { + kekEncryptionLength: number; + kdfAlgorithm: string; +} + +/** + * Represents the EnvelopedData structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + * + * @example The following example demonstrates how to create and encrypt CMS Enveloped Data + * ```js + * const cmsEnveloped = new pkijs.EnvelopedData(); + * + * // Add recipient + * cmsEnveloped.addRecipientByCertificate(cert, { oaepHashAlgorithm: "SHA-256" }); + * + * // Secret key algorithm + * const alg = { + * name: "AES-GCM", + * length: 256, + * } + * await cmsEnveloped.encrypt(alg, dataToEncrypt); + * + * // Add Enveloped Data into CMS Content Info + * const cmsContent = new pkijs.ContentInfo(); + * cmsContent.contentType = pkijs.ContentInfo.ENVELOPED_DATA; + * cmsContent.content = cmsEnveloped.toSchema(); + * + * const cmsContentRaw = cmsContent.toSchema().toBER(); + * ``` + * + * @example The following example demonstrates how to decrypt CMS Enveloped Data + * ```js + * // Get a "crypto" extension + * const crypto = pkijs.getCrypto(); + * + * // Parse CMS Content Info + * const cmsContent = pkijs.ContentInfo.fromBER(cmsContentRaw); + * if (cmsContent.contentType !== pkijs.ContentInfo.ENVELOPED_DATA) { + * throw new Error("CMS is not Enveloped Data"); + * } + * // Parse CMS Enveloped Data + * const cmsEnveloped = new pkijs.EnvelopedData({ schema: cmsContent.content }); + * + * // Export private key to PKCS#8 + * const pkcs8 = await crypto.exportKey("pkcs8", keys.privateKey); + * + * // Decrypt data + * const decryptedData = await cmsEnveloped.decrypt(0, { + * recipientCertificate: cert, + * recipientPrivateKey: pkcs8, + * }); + * ``` + */ +export class EnvelopedData extends PkiObject implements IEnvelopedData { + + public static override CLASS_NAME = "EnvelopedData"; + + public version!: number; + public originatorInfo?: OriginatorInfo; + public recipientInfos!: RecipientInfo[]; + public encryptedContentInfo!: EncryptedContentInfo; + public unprotectedAttrs?: Attribute[]; + + /** + * Initializes a new instance of the {@link EnvelopedData} class + * @param parameters Initialization parameters + */ + constructor(parameters: EnvelopedDataParameters = {}) { + super(); + + this.version = pvutils.getParametersValue(parameters, VERSION, EnvelopedData.defaultValues(VERSION)); + if (ORIGINATOR_INFO in parameters) { + this.originatorInfo = pvutils.getParametersValue(parameters, ORIGINATOR_INFO, EnvelopedData.defaultValues(ORIGINATOR_INFO)); + } + this.recipientInfos = pvutils.getParametersValue(parameters, RECIPIENT_INFOS, EnvelopedData.defaultValues(RECIPIENT_INFOS)); + this.encryptedContentInfo = pvutils.getParametersValue(parameters, ENCRYPTED_CONTENT_INFO, EnvelopedData.defaultValues(ENCRYPTED_CONTENT_INFO)); + if (UNPROTECTED_ATTRS in parameters) { + this.unprotectedAttrs = pvutils.getParametersValue(parameters, UNPROTECTED_ATTRS, EnvelopedData.defaultValues(UNPROTECTED_ATTRS)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof VERSION): number; + public static override defaultValues(memberName: typeof ORIGINATOR_INFO): OriginatorInfo; + public static override defaultValues(memberName: typeof RECIPIENT_INFOS): RecipientInfo[]; + public static override defaultValues(memberName: typeof ENCRYPTED_CONTENT_INFO): EncryptedContentInfo; + public static override defaultValues(memberName: typeof UNPROTECTED_ATTRS): Attribute[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case VERSION: + return 0; + case ORIGINATOR_INFO: + return new OriginatorInfo(); + case RECIPIENT_INFOS: + return []; + case ENCRYPTED_CONTENT_INFO: + return new EncryptedContentInfo(); + case UNPROTECTED_ATTRS: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case VERSION: + return (memberValue === EnvelopedData.defaultValues(memberName)); + case ORIGINATOR_INFO: + return ((memberValue.certs.certificates.length === 0) && (memberValue.crls.crls.length === 0)); + case RECIPIENT_INFOS: + case UNPROTECTED_ATTRS: + return (memberValue.length === 0); + case ENCRYPTED_CONTENT_INFO: + return ((EncryptedContentInfo.compareWithDefault("contentType", memberValue.contentType)) && + (EncryptedContentInfo.compareWithDefault("contentEncryptionAlgorithm", memberValue.contentEncryptionAlgorithm) && + (EncryptedContentInfo.compareWithDefault("encryptedContent", memberValue.encryptedContent)))); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * EnvelopedData ::= SEQUENCE { + * version CMSVersion, + * originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL, + * recipientInfos RecipientInfos, + * encryptedContentInfo EncryptedContentInfo, + * unprotectedAttrs [1] IMPLICIT UnprotectedAttributes OPTIONAL } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + version?: string; + originatorInfo?: string; + recipientInfos?: string; + encryptedContentInfo?: EncryptedContentInfoSchema; + unprotectedAttrs?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Integer({ name: (names.version || EMPTY_STRING) }), + new asn1js.Constructed({ + name: (names.originatorInfo || EMPTY_STRING), + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: OriginatorInfo.schema().valueBlock.value + }), + new asn1js.Set({ + value: [ + new asn1js.Repeated({ + name: (names.recipientInfos || EMPTY_STRING), + value: RecipientInfo.schema() + }) + ] + }), + EncryptedContentInfo.schema(names.encryptedContentInfo || {}), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [ + new asn1js.Repeated({ + name: (names.unprotectedAttrs || EMPTY_STRING), + value: Attribute.schema() + }) + ] + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + EnvelopedData.schema({ + names: { + version: VERSION, + originatorInfo: ORIGINATOR_INFO, + recipientInfos: RECIPIENT_INFOS, + encryptedContentInfo: { + names: { + blockName: ENCRYPTED_CONTENT_INFO + } + }, + unprotectedAttrs: UNPROTECTED_ATTRS + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.version = asn1.result.version.valueBlock.valueDec; + + if (ORIGINATOR_INFO in asn1.result) { + this.originatorInfo = new OriginatorInfo({ + schema: new asn1js.Sequence({ + value: asn1.result.originatorInfo.valueBlock.value + }) + }); + } + + this.recipientInfos = Array.from(asn1.result.recipientInfos, o => new RecipientInfo({ schema: o })); + this.encryptedContentInfo = new EncryptedContentInfo({ schema: asn1.result.encryptedContentInfo }); + + if (UNPROTECTED_ATTRS in asn1.result) + this.unprotectedAttrs = Array.from(asn1.result.unprotectedAttrs, o => new Attribute({ schema: o })); + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray = []; + + outputArray.push(new asn1js.Integer({ value: this.version })); + + if (this.originatorInfo) { + outputArray.push(new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: this.originatorInfo.toSchema().valueBlock.value + })); + } + + outputArray.push(new asn1js.Set({ + value: Array.from(this.recipientInfos, o => o.toSchema()) + })); + + outputArray.push(this.encryptedContentInfo.toSchema()); + + if (this.unprotectedAttrs) { + outputArray.push(new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: Array.from(this.unprotectedAttrs, o => o.toSchema()) + })); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): EnvelopedDataJson { + const res: EnvelopedDataJson = { + version: this.version, + recipientInfos: Array.from(this.recipientInfos, o => o.toJSON()), + encryptedContentInfo: this.encryptedContentInfo.toJSON(), + }; + + if (this.originatorInfo) + res.originatorInfo = this.originatorInfo.toJSON(); + + if (this.unprotectedAttrs) + res.unprotectedAttrs = Array.from(this.unprotectedAttrs, o => o.toJSON()); + + return res; + } + + /** + * Helpers function for filling "RecipientInfo" based on recipient's certificate. + * Problem with WebCrypto is that for RSA certificates we have only one option - "key transport" and + * for ECC certificates we also have one option - "key agreement". As soon as Google will implement + * DH algorithm it would be possible to use "key agreement" also for RSA certificates. + * @param certificate Recipient's certificate + * @param parameters Additional parameters necessary for "fine tunning" of encryption process + * @param variant Variant = 1 is for "key transport", variant = 2 is for "key agreement". In fact the "variant" is unnecessary now because Google has no DH algorithm implementation. Thus key encryption scheme would be choosen by certificate type only: "key transport" for RSA and "key agreement" for ECC certificates. + * @param crypto Crypto engine + */ + public addRecipientByCertificate(certificate: Certificate, parameters?: { + // empty + }, variant?: number, crypto = common.getCrypto(true)): boolean { + //#region Initialize encryption parameters + const encryptionParameters = Object.assign( + { useOAEP: true, oaepHashAlgorithm: "SHA-512" }, + defaultEncryptionParams, + parameters || {} + ); + //#endregion + + //#region Check type of certificate + if (certificate.subjectPublicKeyInfo.algorithm.algorithmId.indexOf("1.2.840.113549") !== (-1)) + variant = 1; // For the moment it is the only variant for RSA-based certificates + else { + if (certificate.subjectPublicKeyInfo.algorithm.algorithmId.indexOf("1.2.840.10045") !== (-1)) + variant = 2; // For the moment it is the only variant for ECC-based certificates + else + throw new Error(`Unknown type of certificate's public key: ${certificate.subjectPublicKeyInfo.algorithm.algorithmId}`); + } + //#endregion + + //#region Add new "recipient" depends on "variant" and certificate type + switch (variant) { + case 1: // Key transport scheme + { + let algorithmId; + let algorithmParams; + + if (encryptionParameters.useOAEP === true) { + // keyEncryptionAlgorithm + algorithmId = crypto.getOIDByAlgorithm({ + name: "RSA-OAEP" + }, true, "keyEncryptionAlgorithm"); + + //#region RSAES-OAEP-params + const hashOID = crypto.getOIDByAlgorithm({ + name: encryptionParameters.oaepHashAlgorithm + }, true, "RSAES-OAEP-params"); + + const hashAlgorithm = new AlgorithmIdentifier({ + algorithmId: hashOID, + algorithmParams: new asn1js.Null() + }); + + const rsaOAEPParams = new RSAESOAEPParams({ + hashAlgorithm, + maskGenAlgorithm: new AlgorithmIdentifier({ + algorithmId: "1.2.840.113549.1.1.8", // id-mgf1 + algorithmParams: hashAlgorithm.toSchema() + }) + }); + + algorithmParams = rsaOAEPParams.toSchema(); + //#endregion + } + else // Use old RSAES-PKCS1-v1_5 schema instead + { + //#region keyEncryptionAlgorithm + algorithmId = crypto.getOIDByAlgorithm({ + name: "RSAES-PKCS1-v1_5" + }); + if (algorithmId === EMPTY_STRING) + throw new Error("Can not find OID for RSAES-PKCS1-v1_5"); + //#endregion + + algorithmParams = new asn1js.Null(); + } + + //#region KeyTransRecipientInfo + const keyInfo = new KeyTransRecipientInfo({ + version: 0, + rid: new IssuerAndSerialNumber({ + issuer: certificate.issuer, + serialNumber: certificate.serialNumber + }), + keyEncryptionAlgorithm: new AlgorithmIdentifier({ + algorithmId, + algorithmParams + }), + recipientCertificate: certificate, + // "encryptedKey" will be calculated in "encrypt" function + }); + //#endregion + + //#region Final values for "CMS_ENVELOPED_DATA" + this.recipientInfos.push(new RecipientInfo({ + variant: 1, + value: keyInfo + })); + //#endregion + } + break; + case 2: // Key agreement scheme + { + const recipientIdentifier = new KeyAgreeRecipientIdentifier({ + variant: 1, + value: new IssuerAndSerialNumber({ + issuer: certificate.issuer, + serialNumber: certificate.serialNumber + }) + }); + this._addKeyAgreeRecipientInfo( + recipientIdentifier, + encryptionParameters, + { recipientCertificate: certificate }, + crypto, + ); + } + break; + default: + throw new Error(`Unknown "variant" value: ${variant}`); + } + //#endregion + + return true; + } + + /** + * Add recipient based on pre-defined data like password or KEK + * @param preDefinedData ArrayBuffer with pre-defined data + * @param parameters Additional parameters necessary for "fine tunning" of encryption process + * @param variant Variant = 1 for pre-defined "key encryption key" (KEK). Variant = 2 for password-based encryption. + * @param crypto Crypto engine + */ + public addRecipientByPreDefinedData(preDefinedData: ArrayBuffer, parameters: { + keyIdentifier?: ArrayBuffer; + hmacHashAlgorithm?: string; + iterationCount?: number; + keyEncryptionAlgorithm?: AesKeyGenParams; + keyEncryptionAlgorithmParams?: any; + } = {}, variant: number, crypto = common.getCrypto(true)) { + //#region Check initial parameters + ArgumentError.assert(preDefinedData, "preDefinedData", "ArrayBuffer"); + if (!preDefinedData.byteLength) { + throw new Error("Pre-defined data could have zero length"); + } + //#endregion + + //#region Initialize encryption parameters + if (!parameters.keyIdentifier) { + const keyIdentifierBuffer = new ArrayBuffer(16); + const keyIdentifierView = new Uint8Array(keyIdentifierBuffer); + crypto.getRandomValues(keyIdentifierView); + + parameters.keyIdentifier = keyIdentifierBuffer; + } + + if (!parameters.hmacHashAlgorithm) + parameters.hmacHashAlgorithm = "SHA-512"; + + if (parameters.iterationCount === undefined) { + parameters.iterationCount = 2048; + } + + if (!parameters.keyEncryptionAlgorithm) { + parameters.keyEncryptionAlgorithm = { + name: "AES-KW", + length: 256 + }; + } + + if (!parameters.keyEncryptionAlgorithmParams) + parameters.keyEncryptionAlgorithmParams = new asn1js.Null(); + //#endregion + + //#region Add new recipient based on passed variant + switch (variant) { + case 1: // KEKRecipientInfo + { + // keyEncryptionAlgorithm + const kekOID = crypto.getOIDByAlgorithm(parameters.keyEncryptionAlgorithm, true, "keyEncryptionAlgorithm"); + + //#region KEKRecipientInfo + const keyInfo = new KEKRecipientInfo({ + version: 4, + kekid: new KEKIdentifier({ + keyIdentifier: new asn1js.OctetString({ valueHex: parameters.keyIdentifier }) + }), + keyEncryptionAlgorithm: new AlgorithmIdentifier({ + algorithmId: kekOID, + /* + For AES-KW params are NULL, but for other algorithm could another situation. + */ + algorithmParams: parameters.keyEncryptionAlgorithmParams + }), + preDefinedKEK: preDefinedData + // "encryptedKey" would be set in "ecrypt" function + }); + //#endregion + + //#region Final values for "CMS_ENVELOPED_DATA" + this.recipientInfos.push(new RecipientInfo({ + variant: 3, + value: keyInfo + })); + //#endregion + } + break; + case 2: // PasswordRecipientinfo + { + // keyDerivationAlgorithm + const pbkdf2OID = crypto.getOIDByAlgorithm({ name: "PBKDF2" }, true, "keyDerivationAlgorithm"); + + //#region Salt + const saltBuffer = new ArrayBuffer(64); + const saltView = new Uint8Array(saltBuffer); + crypto.getRandomValues(saltView); + //#endregion + + //#region HMAC-based algorithm + const hmacOID = crypto.getOIDByAlgorithm({ + name: "HMAC", + hash: { + name: parameters.hmacHashAlgorithm + } + } as Algorithm, true, "hmacHashAlgorithm"); + //#endregion + + //#region PBKDF2-params + const pbkdf2Params = new PBKDF2Params({ + salt: new asn1js.OctetString({ valueHex: saltBuffer }), + iterationCount: parameters.iterationCount, + prf: new AlgorithmIdentifier({ + algorithmId: hmacOID, + algorithmParams: new asn1js.Null() + }) + }); + //#endregion + + // keyEncryptionAlgorithm + const kekOID = crypto.getOIDByAlgorithm(parameters.keyEncryptionAlgorithm, true, "keyEncryptionAlgorithm"); + + //#region PasswordRecipientinfo + const keyInfo = new PasswordRecipientinfo({ + version: 0, + keyDerivationAlgorithm: new AlgorithmIdentifier({ + algorithmId: pbkdf2OID, + algorithmParams: pbkdf2Params.toSchema() + }), + keyEncryptionAlgorithm: new AlgorithmIdentifier({ + algorithmId: kekOID, + /* + For AES-KW params are NULL, but for other algorithm could be another situation. + */ + algorithmParams: parameters.keyEncryptionAlgorithmParams + }), + password: preDefinedData + // "encryptedKey" would be set in "encrypt" function + }); + //#endregion + + //#region Final values for "CMS_ENVELOPED_DATA" + this.recipientInfos.push(new RecipientInfo({ + variant: 4, + value: keyInfo + })); + //#endregion + } + break; + default: + throw new Error(`Unknown value for "variant": ${variant}`); + } + //#endregion + } + + /** + * Add a "RecipientInfo" using a KeyAgreeRecipientInfo of type RecipientKeyIdentifier. + * @param key Recipient's public key + * @param keyId The id for the recipient's public key + * @param parameters Additional parameters for "fine tuning" the encryption process + * @param crypto Crypto engine + */ + addRecipientByKeyIdentifier(key?: CryptoKey, keyId?: ArrayBuffer, parameters?: any, crypto = common.getCrypto(true)) { + //#region Initialize encryption parameters + const encryptionParameters = Object.assign({}, defaultEncryptionParams, parameters || {}); + //#endregion + + const recipientIdentifier = new KeyAgreeRecipientIdentifier({ + variant: 2, + value: new RecipientKeyIdentifier({ + subjectKeyIdentifier: new asn1js.OctetString({ valueHex: keyId }), + }) + }); + this._addKeyAgreeRecipientInfo( + recipientIdentifier, + encryptionParameters, + { recipientPublicKey: key }, + crypto, + ); + } + + /** + * Add a "RecipientInfo" using a KeyAgreeRecipientInfo of type RecipientKeyIdentifier. + * @param recipientIdentifier Recipient identifier + * @param encryptionParameters Additional parameters for "fine tuning" the encryption process + * @param extraRecipientInfoParams Additional params for KeyAgreeRecipientInfo + * @param crypto Crypto engine + */ + private _addKeyAgreeRecipientInfo(recipientIdentifier: KeyAgreeRecipientIdentifier, encryptionParameters: EnvelopedDataEncryptionParams, extraRecipientInfoParams: KeyAgreeRecipientInfoParameters, crypto = common.getCrypto(true)) { + //#region RecipientEncryptedKey + const encryptedKey = new RecipientEncryptedKey({ + rid: recipientIdentifier + // "encryptedKey" will be calculated in "encrypt" function + }); + //#endregion + + //#region keyEncryptionAlgorithm + const aesKWoid = crypto.getOIDByAlgorithm({ + name: "AES-KW", + length: encryptionParameters.kekEncryptionLength + } as Algorithm, true, "keyEncryptionAlgorithm"); + + const aesKW = new AlgorithmIdentifier({ + algorithmId: aesKWoid, + }); + //#endregion + + //#region KeyAgreeRecipientInfo + const ecdhOID = crypto.getOIDByAlgorithm({ + name: "ECDH", + kdf: encryptionParameters.kdfAlgorithm + } as Algorithm, true, "KeyAgreeRecipientInfo"); + + // In fact there is no need in so long UKM, but RFC2631 + // has requirement that "UserKeyMaterial" must be 512 bits long + const ukmBuffer = new ArrayBuffer(64); + const ukmView = new Uint8Array(ukmBuffer); + crypto.getRandomValues(ukmView); // Generate random values in 64 bytes long buffer + + const recipientInfoParams = { + version: 3, + // "originator" will be calculated in "encrypt" function because ephemeral key would be generated there + ukm: new asn1js.OctetString({ valueHex: ukmBuffer }), + keyEncryptionAlgorithm: new AlgorithmIdentifier({ + algorithmId: ecdhOID, + algorithmParams: aesKW.toSchema() + }), + recipientEncryptedKeys: new RecipientEncryptedKeys({ + encryptedKeys: [encryptedKey] + }) + }; + const keyInfo = new KeyAgreeRecipientInfo(Object.assign(recipientInfoParams, extraRecipientInfoParams)); + //#endregion + + //#region Final values for "CMS_ENVELOPED_DATA" + this.recipientInfos.push(new RecipientInfo({ + variant: 2, + value: keyInfo + })); + //#endregion + } + + /** + * Creates a new CMS Enveloped Data content with encrypted data + * @param contentEncryptionAlgorithm WebCrypto algorithm. For the moment here could be only "AES-CBC" or "AES-GCM" algorithms. + * @param contentToEncrypt Content to encrypt + * @param crypto Crypto engine + */ + public async encrypt(contentEncryptionAlgorithm: Algorithm, contentToEncrypt: ArrayBuffer, crypto = common.getCrypto(true)): Promise<(void | { ecdhPrivateKey: CryptoKey; })[]> { + //#region Initial variables + const ivBuffer = new ArrayBuffer(16); // For AES we need IV 16 bytes long + const ivView = new Uint8Array(ivBuffer); + crypto.getRandomValues(ivView); + + const contentView = new Uint8Array(contentToEncrypt); + //#endregion + + // Check for input parameters + const contentEncryptionOID = crypto.getOIDByAlgorithm(contentEncryptionAlgorithm, true, "contentEncryptionAlgorithm"); + + //#region Generate new content encryption key + const sessionKey = await crypto.generateKey(contentEncryptionAlgorithm as AesKeyAlgorithm, true, ["encrypt"]); + //#endregion + //#region Encrypt content + + const encryptedContent = await crypto.encrypt({ + name: contentEncryptionAlgorithm.name, + iv: ivView + }, + sessionKey, + contentView); + //#endregion + //#region Export raw content of content encryption key + const exportedSessionKey = await crypto.exportKey("raw", sessionKey); + + //#endregion + //#region Append common information to CMS_ENVELOPED_DATA + this.version = 2; + this.encryptedContentInfo = new EncryptedContentInfo({ + contentType: "1.2.840.113549.1.7.1", // "data" + contentEncryptionAlgorithm: new AlgorithmIdentifier({ + algorithmId: contentEncryptionOID, + algorithmParams: new asn1js.OctetString({ valueHex: ivBuffer }) + }), + encryptedContent: new asn1js.OctetString({ valueHex: encryptedContent }) + }); + //#endregion + + //#region Special sub-functions to work with each recipient's type + const SubKeyAgreeRecipientInfo = async (index: number) => { + //#region Initial variables + const recipientInfo = this.recipientInfos[index].value as KeyAgreeRecipientInfo; + let recipientCurve: string; + //#endregion + + //#region Get public key and named curve from recipient's certificate or public key + let recipientPublicKey: CryptoKey; + if (recipientInfo.recipientPublicKey) { + recipientCurve = (recipientInfo.recipientPublicKey.algorithm as EcKeyAlgorithm).namedCurve; + recipientPublicKey = recipientInfo.recipientPublicKey; + } else if (recipientInfo.recipientCertificate) { + const curveObject = recipientInfo.recipientCertificate.subjectPublicKeyInfo.algorithm.algorithmParams; + + if (curveObject.constructor.blockName() !== asn1js.ObjectIdentifier.blockName()) + throw new Error(`Incorrect "recipientCertificate" for index ${index}`); + + const curveOID = curveObject.valueBlock.toString(); + + switch (curveOID) { + case "1.2.840.10045.3.1.7": + recipientCurve = "P-256"; + break; + case "1.3.132.0.34": + recipientCurve = "P-384"; + break; + case "1.3.132.0.35": + recipientCurve = "P-521"; + break; + default: + throw new Error(`Incorrect curve OID for index ${index}`); + } + + recipientPublicKey = await recipientInfo.recipientCertificate.getPublicKey({ + algorithm: { + algorithm: { + name: "ECDH", + namedCurve: recipientCurve + } as EcKeyAlgorithm, + usages: [] + } + }, crypto); + } else { + throw new Error("Unsupported RecipientInfo"); + } + //#endregion + + //#region Generate ephemeral ECDH key + const recipientCurveLength = curveLengthByName[recipientCurve]; + + const ecdhKeys = await crypto.generateKey( + { name: "ECDH", namedCurve: recipientCurve } as EcKeyGenParams, + true, + ["deriveBits"] + ); + //#endregion + //#region Export public key of ephemeral ECDH key pair + + const exportedECDHPublicKey = await crypto.exportKey("spki", ecdhKeys.publicKey); + //#endregion + + //#region Create shared secret + const derivedBits = await crypto.deriveBits({ + name: "ECDH", + public: recipientPublicKey + }, + ecdhKeys.privateKey, + recipientCurveLength); + //#endregion + + //#region Apply KDF function to shared secret + + //#region Get length of used AES-KW algorithm + const aesKWAlgorithm = new AlgorithmIdentifier({ schema: recipientInfo.keyEncryptionAlgorithm.algorithmParams }); + + const kwAlgorithm = crypto.getAlgorithmByOID<AesKeyAlgorithm>(aesKWAlgorithm.algorithmId, true, "aesKWAlgorithm"); + //#endregion + + //#region Translate AES-KW length to ArrayBuffer + let kwLength = kwAlgorithm.length; + + const kwLengthBuffer = new ArrayBuffer(4); + const kwLengthView = new Uint8Array(kwLengthBuffer); + + for (let j = 3; j >= 0; j--) { + kwLengthView[j] = kwLength; + kwLength >>= 8; + } + //#endregion + + //#region Create and encode "ECC-CMS-SharedInfo" structure + const eccInfo = new ECCCMSSharedInfo({ + keyInfo: new AlgorithmIdentifier({ + algorithmId: aesKWAlgorithm.algorithmId + }), + entityUInfo: (recipientInfo as KeyAgreeRecipientInfo).ukm, // TODO remove `as KeyAgreeRecipientInfo` + suppPubInfo: new asn1js.OctetString({ valueHex: kwLengthBuffer }) + }); + + const encodedInfo = eccInfo.toSchema().toBER(false); + //#endregion + + //#region Get SHA algorithm used together with ECDH + const ecdhAlgorithm = crypto.getAlgorithmByOID<any>(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "ecdhAlgorithm"); + //#endregion + + const derivedKeyRaw = await common.kdf(ecdhAlgorithm.kdf, derivedBits, kwAlgorithm.length, encodedInfo, crypto); + //#endregion + //#region Import AES-KW key from result of KDF function + const awsKW = await crypto.importKey("raw", derivedKeyRaw, { name: "AES-KW" }, true, ["wrapKey"]); + //#endregion + //#region Finally wrap session key by using AES-KW algorithm + const wrappedKey = await crypto.wrapKey("raw", sessionKey, awsKW, { name: "AES-KW" }); + //#endregion + //#region Append all necessary data to current CMS_RECIPIENT_INFO object + //#region OriginatorIdentifierOrKey + const originator = new OriginatorIdentifierOrKey(); + originator.variant = 3; + originator.value = OriginatorPublicKey.fromBER(exportedECDHPublicKey); + + recipientInfo.originator = originator; + //#endregion + + //#region RecipientEncryptedKey + /* + We will not support using of same ephemeral key for many recipients + */ + recipientInfo.recipientEncryptedKeys.encryptedKeys[0].encryptedKey = new asn1js.OctetString({ valueHex: wrappedKey }); + //#endregion + + return { ecdhPrivateKey: ecdhKeys.privateKey }; + //#endregion + }; + + const SubKeyTransRecipientInfo = async (index: number) => { + const recipientInfo = this.recipientInfos[index].value as KeyTransRecipientInfo; // TODO Remove `as KeyTransRecipientInfo` + const algorithmParameters = crypto.getAlgorithmByOID<any>(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "keyEncryptionAlgorithm"); + + //#region RSA-OAEP case + if (algorithmParameters.name === "RSA-OAEP") { + const schema = recipientInfo.keyEncryptionAlgorithm.algorithmParams; + const rsaOAEPParams = new RSAESOAEPParams({ schema }); + + algorithmParameters.hash = crypto.getAlgorithmByOID(rsaOAEPParams.hashAlgorithm.algorithmId); + if (("name" in algorithmParameters.hash) === false) + throw new Error(`Incorrect OID for hash algorithm: ${rsaOAEPParams.hashAlgorithm.algorithmId}`); + } + //#endregion + + try { + const publicKey = await recipientInfo.recipientCertificate.getPublicKey({ + algorithm: { + algorithm: algorithmParameters, + usages: ["encrypt", "wrapKey"] + } + }, crypto); + + const encryptedKey = await crypto.encrypt(publicKey.algorithm, publicKey, exportedSessionKey); + + //#region RecipientEncryptedKey + recipientInfo.encryptedKey = new asn1js.OctetString({ valueHex: encryptedKey }); + //#endregion + } + catch { + // nothing + } + }; + + const SubKEKRecipientInfo = async (index: number) => { + //#region Initial variables + const recipientInfo = this.recipientInfos[index].value as KEKRecipientInfo; // TODO Remove `as KEKRecipientInfo` + //#endregion + + //#region Import KEK from pre-defined data + + //#region Get WebCrypto form of "keyEncryptionAlgorithm" + const kekAlgorithm = crypto.getAlgorithmByOID(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "kekAlgorithm"); + //#endregion + + const kekKey = await crypto.importKey("raw", + new Uint8Array(recipientInfo.preDefinedKEK), + kekAlgorithm, + true, + ["wrapKey"]); // Too specific for AES-KW + //#endregion + + //#region Wrap previously exported session key + + const wrappedKey = await crypto.wrapKey("raw", sessionKey, kekKey, kekAlgorithm); + //#endregion + //#region Append all necessary data to current CMS_RECIPIENT_INFO object + //#region RecipientEncryptedKey + recipientInfo.encryptedKey = new asn1js.OctetString({ valueHex: wrappedKey }); + //#endregion + //#endregion + }; + + const SubPasswordRecipientinfo = async (index: number) => { + //#region Initial variables + const recipientInfo = this.recipientInfos[index].value as PasswordRecipientinfo; // TODO Remove `as PasswordRecipientinfo` + let pbkdf2Params: PBKDF2Params; + //#endregion + + //#region Check that we have encoded "keyDerivationAlgorithm" plus "PBKDF2_params" in there + + if (!recipientInfo.keyDerivationAlgorithm) + throw new Error("Please append encoded \"keyDerivationAlgorithm\""); + + if (!recipientInfo.keyDerivationAlgorithm.algorithmParams) + throw new Error("Incorrectly encoded \"keyDerivationAlgorithm\""); + + try { + pbkdf2Params = new PBKDF2Params({ schema: recipientInfo.keyDerivationAlgorithm.algorithmParams }); + } + catch (ex) { + throw new Error("Incorrectly encoded \"keyDerivationAlgorithm\""); + } + + //#endregion + //#region Derive PBKDF2 key from "password" buffer + const passwordView = new Uint8Array(recipientInfo.password); + + const derivationKey = await crypto.importKey("raw", + passwordView, + "PBKDF2", + false, + ["deriveKey"]); + //#endregion + //#region Derive key for "keyEncryptionAlgorithm" + //#region Get WebCrypto form of "keyEncryptionAlgorithm" + const kekAlgorithm = crypto.getAlgorithmByOID<any>(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "kekAlgorithm"); + + //#endregion + + //#region Get HMAC hash algorithm + let hmacHashAlgorithm = "SHA-1"; + + if (pbkdf2Params.prf) { + const prfAlgorithm = crypto.getAlgorithmByOID<any>(pbkdf2Params.prf.algorithmId, true, "prfAlgorithm"); + hmacHashAlgorithm = prfAlgorithm.hash.name; + } + //#endregion + + //#region Get PBKDF2 "salt" value + const saltView = new Uint8Array(pbkdf2Params.salt.valueBlock.valueHex); + //#endregion + + //#region Get PBKDF2 iterations count + const iterations = pbkdf2Params.iterationCount; + //#endregion + + const derivedKey = await crypto.deriveKey({ + name: "PBKDF2", + hash: { + name: hmacHashAlgorithm + }, + salt: saltView, + iterations + }, + derivationKey, + kekAlgorithm, + true, + ["wrapKey"]); // Usages are too specific for KEK algorithm + + //#endregion + //#region Wrap previously exported session key (Also too specific for KEK algorithm) + const wrappedKey = await crypto.wrapKey("raw", sessionKey, derivedKey, kekAlgorithm); + //#endregion + //#region Append all necessary data to current CMS_RECIPIENT_INFO object + //#region RecipientEncryptedKey + recipientInfo.encryptedKey = new asn1js.OctetString({ valueHex: wrappedKey }); + //#endregion + //#endregion + }; + + //#endregion + + const res = []; + //#region Create special routines for each "recipient" + for (let i = 0; i < this.recipientInfos.length; i++) { + switch (this.recipientInfos[i].variant) { + case 1: // KeyTransRecipientInfo + res.push(await SubKeyTransRecipientInfo(i)); + break; + case 2: // KeyAgreeRecipientInfo + res.push(await SubKeyAgreeRecipientInfo(i)); + break; + case 3: // KEKRecipientInfo + res.push(await SubKEKRecipientInfo(i)); + break; + case 4: // PasswordRecipientinfo + res.push(await SubPasswordRecipientinfo(i)); + break; + default: + throw new Error(`Unknown recipient type in array with index ${i}`); + } + } + //#endregion + return res; + } + + /** + * Decrypts existing CMS Enveloped Data content + * @param recipientIndex Index of recipient + * @param parameters Additional parameters + * @param crypto Crypto engine + */ + async decrypt(recipientIndex: number, parameters: { + recipientCertificate?: Certificate; + recipientPrivateKey?: BufferSource; + preDefinedData?: BufferSource; + }, crypto = common.getCrypto(true)) { + //#region Initial variables + const decryptionParameters = parameters || {}; + //#endregion + + //#region Check for input parameters + if ((recipientIndex + 1) > this.recipientInfos.length) { + throw new Error(`Maximum value for "index" is: ${this.recipientInfos.length - 1}`); + } + //#endregion + + //#region Special sub-functions to work with each recipient's type + const SubKeyAgreeRecipientInfo = async (index: number) => { + //#region Initial variables + const recipientInfo = this.recipientInfos[index].value as KeyAgreeRecipientInfo; // TODO Remove `as KeyAgreeRecipientInfo` + //#endregion + + let curveOID: string; + let recipientCurve: string; + let recipientCurveLength: number; + const originator = recipientInfo.originator; + + //#region Get "namedCurve" parameter from recipient's certificate + + if (decryptionParameters.recipientCertificate) { + const curveObject = decryptionParameters.recipientCertificate.subjectPublicKeyInfo.algorithm.algorithmParams; + if (curveObject.constructor.blockName() !== asn1js.ObjectIdentifier.blockName()) { + throw new Error(`Incorrect "recipientCertificate" for index ${index}`); + } + curveOID = curveObject.valueBlock.toString(); + } else if (originator.value.algorithm.algorithmParams) { + const curveObject = originator.value.algorithm.algorithmParams; + if (curveObject.constructor.blockName() !== asn1js.ObjectIdentifier.blockName()) { + throw new Error(`Incorrect originator for index ${index}`); + } + curveOID = curveObject.valueBlock.toString(); + } else { + throw new Error("Parameter \"recipientCertificate\" is mandatory for \"KeyAgreeRecipientInfo\" if algorithm params are missing from originator"); + } + + if (!decryptionParameters.recipientPrivateKey) + throw new Error("Parameter \"recipientPrivateKey\" is mandatory for \"KeyAgreeRecipientInfo\""); + + switch (curveOID) { + case "1.2.840.10045.3.1.7": + recipientCurve = "P-256"; + recipientCurveLength = 256; + break; + case "1.3.132.0.34": + recipientCurve = "P-384"; + recipientCurveLength = 384; + break; + case "1.3.132.0.35": + recipientCurve = "P-521"; + recipientCurveLength = 528; + break; + default: + throw new Error(`Incorrect curve OID for index ${index}`); + } + + const ecdhPrivateKey = await crypto.importKey("pkcs8", + decryptionParameters.recipientPrivateKey, + { + name: "ECDH", + namedCurve: recipientCurve + } as EcKeyImportParams, + true, + ["deriveBits"] + ); + //#endregion + //#region Import sender's ephemeral public key + //#region Change "OriginatorPublicKey" if "curve" parameter absent + if (("algorithmParams" in originator.value.algorithm) === false) + originator.value.algorithm.algorithmParams = new asn1js.ObjectIdentifier({ value: curveOID }); + //#endregion + + //#region Create ArrayBuffer with sender's public key + const buffer = originator.value.toSchema().toBER(false); + //#endregion + + const ecdhPublicKey = await crypto.importKey("spki", + buffer, + { + name: "ECDH", + namedCurve: recipientCurve + } as EcKeyImportParams, + true, + []); + + //#endregion + //#region Create shared secret + const sharedSecret = await crypto.deriveBits({ + name: "ECDH", + public: ecdhPublicKey + }, + ecdhPrivateKey, + recipientCurveLength); + //#endregion + //#region Apply KDF function to shared secret + async function applyKDF(includeAlgorithmParams?: boolean) { + includeAlgorithmParams = includeAlgorithmParams || false; + + //#region Get length of used AES-KW algorithm + const aesKWAlgorithm = new AlgorithmIdentifier({ schema: recipientInfo.keyEncryptionAlgorithm.algorithmParams }); + + const kwAlgorithm = crypto.getAlgorithmByOID<any>(aesKWAlgorithm.algorithmId, true, "kwAlgorithm"); + //#endregion + + //#region Translate AES-KW length to ArrayBuffer + let kwLength = kwAlgorithm.length; + + const kwLengthBuffer = new ArrayBuffer(4); + const kwLengthView = new Uint8Array(kwLengthBuffer); + + for (let j = 3; j >= 0; j--) { + kwLengthView[j] = kwLength; + kwLength >>= 8; + } + //#endregion + + //#region Create and encode "ECC-CMS-SharedInfo" structure + const keyInfoAlgorithm: AlgorithmIdentifierParameters = { + algorithmId: aesKWAlgorithm.algorithmId + }; + if (includeAlgorithmParams) { + keyInfoAlgorithm.algorithmParams = new asn1js.Null(); + } + const eccInfo = new ECCCMSSharedInfo({ + keyInfo: new AlgorithmIdentifier(keyInfoAlgorithm), + entityUInfo: recipientInfo.ukm, + suppPubInfo: new asn1js.OctetString({ valueHex: kwLengthBuffer }) + }); + + const encodedInfo = eccInfo.toSchema().toBER(false); + //#endregion + + //#region Get SHA algorithm used together with ECDH + const ecdhAlgorithm = crypto.getAlgorithmByOID<any>(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "ecdhAlgorithm"); + if (!ecdhAlgorithm.name) { + throw new Error(`Incorrect OID for key encryption algorithm: ${recipientInfo.keyEncryptionAlgorithm.algorithmId}`); + } + //#endregion + + return common.kdf(ecdhAlgorithm.kdf, sharedSecret, kwAlgorithm.length, encodedInfo, crypto); + } + + const kdfResult = await applyKDF(); + //#endregion + //#region Import AES-KW key from result of KDF function + const importAesKwKey = async (kdfResult: ArrayBuffer) => { + return crypto.importKey("raw", + kdfResult, + { name: "AES-KW" }, + true, + ["unwrapKey"] + ); + }; + + const aesKwKey = await importAesKwKey(kdfResult); + + //#endregion + //#region Finally unwrap session key + const unwrapSessionKey = async (aesKwKey: CryptoKey) => { + //#region Get WebCrypto form of content encryption algorithm + const algorithmId = this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId; + const contentEncryptionAlgorithm = crypto.getAlgorithmByOID<any>(algorithmId, true, "contentEncryptionAlgorithm"); + //#endregion + + return crypto.unwrapKey("raw", + recipientInfo.recipientEncryptedKeys.encryptedKeys[0].encryptedKey.valueBlock.valueHexView, + aesKwKey, + { name: "AES-KW" }, + contentEncryptionAlgorithm, + true, + ["decrypt"]); + }; + + try { + return await unwrapSessionKey(aesKwKey); + } catch { + const kdfResult = await applyKDF(true); + const aesKwKey = await importAesKwKey(kdfResult); + return unwrapSessionKey(aesKwKey); + } + }; + //#endregion + + const SubKeyTransRecipientInfo = async (index: number) => { + const recipientInfo = this.recipientInfos[index].value as KeyTransRecipientInfo; // TODO Remove `as KeyTransRecipientInfo` + if (!decryptionParameters.recipientPrivateKey) { + throw new Error("Parameter \"recipientPrivateKey\" is mandatory for \"KeyTransRecipientInfo\""); + } + + const algorithmParameters = crypto.getAlgorithmByOID<any>(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "keyEncryptionAlgorithm"); + + //#region RSA-OAEP case + if (algorithmParameters.name === "RSA-OAEP") { + const schema = recipientInfo.keyEncryptionAlgorithm.algorithmParams; + const rsaOAEPParams = new RSAESOAEPParams({ schema }); + + algorithmParameters.hash = crypto.getAlgorithmByOID(rsaOAEPParams.hashAlgorithm.algorithmId); + if (("name" in algorithmParameters.hash) === false) + throw new Error(`Incorrect OID for hash algorithm: ${rsaOAEPParams.hashAlgorithm.algorithmId}`); + } + //#endregion + + const privateKey = await crypto.importKey( + "pkcs8", + decryptionParameters.recipientPrivateKey, + algorithmParameters, + true, + ["decrypt"] + ); + + const sessionKey = await crypto.decrypt( + privateKey.algorithm, + privateKey, + recipientInfo.encryptedKey.valueBlock.valueHexView + ); + + //#region Get WebCrypto form of content encryption algorithm + const algorithmId = this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId; + const contentEncryptionAlgorithm = crypto.getAlgorithmByOID(algorithmId, true, "contentEncryptionAlgorithm"); + if (("name" in contentEncryptionAlgorithm) === false) + throw new Error(`Incorrect "contentEncryptionAlgorithm": ${algorithmId}`); + //#endregion + + return crypto.importKey("raw", + sessionKey, + contentEncryptionAlgorithm, + true, + ["decrypt"] + ); + }; + + const SubKEKRecipientInfo = async (index: number) => { + //#region Initial variables + const recipientInfo = this.recipientInfos[index].value as KEKRecipientInfo; // TODO Remove `as KEKRecipientInfo` + //#endregion + + //#region Import KEK from pre-defined data + if (!decryptionParameters.preDefinedData) + throw new Error("Parameter \"preDefinedData\" is mandatory for \"KEKRecipientInfo\""); + + //#region Get WebCrypto form of "keyEncryptionAlgorithm" + const kekAlgorithm = crypto.getAlgorithmByOID<any>(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "kekAlgorithm"); + //#endregion + + const importedKey = await crypto.importKey("raw", + decryptionParameters.preDefinedData, + kekAlgorithm, + true, + ["unwrapKey"]); // Too specific for AES-KW + + //#endregion + //#region Unwrap previously exported session key + //#region Get WebCrypto form of content encryption algorithm + const algorithmId = this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId; + const contentEncryptionAlgorithm = crypto.getAlgorithmByOID<any>(algorithmId, true, "contentEncryptionAlgorithm"); + if (!contentEncryptionAlgorithm.name) { + throw new Error(`Incorrect "contentEncryptionAlgorithm": ${algorithmId}`); + } + //#endregion + + return crypto.unwrapKey("raw", + recipientInfo.encryptedKey.valueBlock.valueHexView, + importedKey, + kekAlgorithm, + contentEncryptionAlgorithm, + true, + ["decrypt"]); + //#endregion + }; + + const SubPasswordRecipientinfo = async (index: number) => { + //#region Initial variables + const recipientInfo = this.recipientInfos[index].value as PasswordRecipientinfo; // TODO Remove `as PasswordRecipientinfo` + let pbkdf2Params: PBKDF2Params; + //#endregion + + //#region Derive PBKDF2 key from "password" buffer + + if (!decryptionParameters.preDefinedData) { + throw new Error("Parameter \"preDefinedData\" is mandatory for \"KEKRecipientInfo\""); + } + + if (!recipientInfo.keyDerivationAlgorithm) { + throw new Error("Please append encoded \"keyDerivationAlgorithm\""); + } + + if (!recipientInfo.keyDerivationAlgorithm.algorithmParams) { + throw new Error("Incorrectly encoded \"keyDerivationAlgorithm\""); + } + + try { + pbkdf2Params = new PBKDF2Params({ schema: recipientInfo.keyDerivationAlgorithm.algorithmParams }); + } + catch (ex) { + throw new Error("Incorrectly encoded \"keyDerivationAlgorithm\""); + } + + const pbkdf2Key = await crypto.importKey("raw", + decryptionParameters.preDefinedData, + "PBKDF2", + false, + ["deriveKey"]); + //#endregion + //#region Derive key for "keyEncryptionAlgorithm" + //#region Get WebCrypto form of "keyEncryptionAlgorithm" + const kekAlgorithm = crypto.getAlgorithmByOID<any>(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "keyEncryptionAlgorithm"); + //#endregion + + // Get HMAC hash algorithm + const hmacHashAlgorithm = pbkdf2Params.prf + ? crypto.getAlgorithmByOID<any>(pbkdf2Params.prf.algorithmId, true, "prfAlgorithm").hash.name + : "SHA-1"; + + //#region Get PBKDF2 "salt" value + const saltView = new Uint8Array(pbkdf2Params.salt.valueBlock.valueHex); + //#endregion + + //#region Get PBKDF2 iterations count + const iterations = pbkdf2Params.iterationCount; + //#endregion + + const kekKey = await crypto.deriveKey({ + name: "PBKDF2", + hash: { + name: hmacHashAlgorithm + }, + salt: saltView, + iterations + }, + pbkdf2Key, + kekAlgorithm, + true, + ["unwrapKey"]); // Usages are too specific for KEK algorithm + //#endregion + //#region Unwrap previously exported session key + //#region Get WebCrypto form of content encryption algorithm + const algorithmId = this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId; + const contentEncryptionAlgorithm = crypto.getAlgorithmByOID<any>(algorithmId, true, "contentEncryptionAlgorithm"); + //#endregion + + return crypto.unwrapKey("raw", + recipientInfo.encryptedKey.valueBlock.valueHexView, + kekKey, + kekAlgorithm, + contentEncryptionAlgorithm, + true, + ["decrypt"]); + //#endregion + }; + + //#endregion + + //#region Perform steps, specific to each type of session key encryption + let unwrappedKey: CryptoKey; + switch (this.recipientInfos[recipientIndex].variant) { + case 1: // KeyTransRecipientInfo + unwrappedKey = await SubKeyTransRecipientInfo(recipientIndex); + break; + case 2: // KeyAgreeRecipientInfo + unwrappedKey = await SubKeyAgreeRecipientInfo(recipientIndex); + break; + case 3: // KEKRecipientInfo + unwrappedKey = await SubKEKRecipientInfo(recipientIndex); + break; + case 4: // PasswordRecipientinfo + unwrappedKey = await SubPasswordRecipientinfo(recipientIndex); + break; + default: + throw new Error(`Unknown recipient type in array with index ${recipientIndex}`); + } + //#endregion + + //#region Finally decrypt data by session key + //#region Get WebCrypto form of content encryption algorithm + const algorithmId = this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId; + const contentEncryptionAlgorithm = crypto.getAlgorithmByOID(algorithmId, true, "contentEncryptionAlgorithm"); + //#endregion + + //#region Get "initialization vector" for content encryption algorithm + const ivBuffer = this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmParams.valueBlock.valueHex; + const ivView = new Uint8Array(ivBuffer); + //#endregion + + //#region Create correct data block for decryption + if (!this.encryptedContentInfo.encryptedContent) { + throw new Error("Required property `encryptedContent` is empty"); + } + const dataBuffer = this.encryptedContentInfo.getEncryptedContent(); + //#endregion + + return crypto.decrypt( + { + name: (contentEncryptionAlgorithm as any).name, + iv: ivView + }, + unwrappedKey, + dataBuffer); + //#endregion + } + +} diff --git a/third_party/js/PKI.js/src/ExtKeyUsage.ts b/third_party/js/PKI.js/src/ExtKeyUsage.ts new file mode 100644 index 0000000000..0ba9c385e6 --- /dev/null +++ b/third_party/js/PKI.js/src/ExtKeyUsage.ts @@ -0,0 +1,123 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const KEY_PURPOSES = "keyPurposes"; +const CLEAR_PROPS = [ + KEY_PURPOSES, +]; + +export interface IExtKeyUsage { + keyPurposes: string[]; +} + +export interface ExtKeyUsageJson { + keyPurposes: string[]; +} + +export type ExtKeyUsageParameters = PkiObjectParameters & Partial<IExtKeyUsage>; + +/** + * Represents the ExtKeyUsage structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class ExtKeyUsage extends PkiObject implements IExtKeyUsage { + + public static override CLASS_NAME = "ExtKeyUsage"; + + public keyPurposes!: string[]; + + /** + * Initializes a new instance of the {@link ExtKeyUsage} class + * @param parameters Initialization parameters + */ + constructor(parameters: ExtKeyUsageParameters = {}) { + super(); + + this.keyPurposes = pvutils.getParametersValue(parameters, KEY_PURPOSES, ExtKeyUsage.defaultValues(KEY_PURPOSES)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof KEY_PURPOSES): string[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case KEY_PURPOSES: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * ExtKeyUsage ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId + * + * KeyPurposeId ::= OBJECT IDENTIFIER + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + keyPurposes?: string; + }> = {}): Schema.SchemaType { + /** + * @type {Object} + * @property {string} [blockName] + * @property {string} [keyPurposes] + */ + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Repeated({ + name: (names.keyPurposes || EMPTY_STRING), + value: new asn1js.ObjectIdentifier() + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + ExtKeyUsage.schema({ + names: { + keyPurposes: KEY_PURPOSES + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.keyPurposes = Array.from(asn1.result.keyPurposes, (element: asn1js.ObjectIdentifier) => element.valueBlock.toString()); + } + + public toSchema(): asn1js.Sequence { + // Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: Array.from(this.keyPurposes, element => new asn1js.ObjectIdentifier({ value: element })) + })); + } + + public toJSON(): ExtKeyUsageJson { + return { + keyPurposes: Array.from(this.keyPurposes) + }; + } + +} + diff --git a/third_party/js/PKI.js/src/Extension.ts b/third_party/js/PKI.js/src/Extension.ts new file mode 100644 index 0000000000..d724763d8f --- /dev/null +++ b/third_party/js/PKI.js/src/Extension.ts @@ -0,0 +1,206 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import * as Schema from "./Schema"; +import { ExtensionParsedValue, ExtensionValueFactory } from "./ExtensionValueFactory"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { AsnError } from "./errors"; +import { EMPTY_STRING } from "./constants"; + +const EXTN_ID = "extnID"; +const CRITICAL = "critical"; +const EXTN_VALUE = "extnValue"; +const PARSED_VALUE = "parsedValue"; +const CLEAR_PROPS = [ + EXTN_ID, + CRITICAL, + EXTN_VALUE +]; + +export interface IExtension { + extnID: string; + critical: boolean; + extnValue: asn1js.OctetString; + parsedValue?: ExtensionParsedValue; +} + +export interface ExtensionConstructorParameters { + extnID?: string; + critical?: boolean; + extnValue?: ArrayBuffer; + parsedValue?: ExtensionParsedValue; +} + +export type ExtensionParameters = PkiObjectParameters & ExtensionConstructorParameters; + +export type ExtensionSchema = Schema.SchemaParameters<{ + extnID?: string; + critical?: string; + extnValue?: string; +}>; + +export interface ExtensionJson { + extnID: string; + extnValue: asn1js.OctetStringJson; + critical?: boolean; + parsedValue?: any; +} + +/** + * Represents the Extension structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class Extension extends PkiObject implements IExtension { + + public static override CLASS_NAME = "Extension"; + + public extnID!: string; + public critical!: boolean; + public extnValue!: asn1js.OctetString; + + private _parsedValue?: ExtensionParsedValue | null; + public get parsedValue(): ExtensionParsedValue | undefined { + if (this._parsedValue === undefined) { + // Get PARSED_VALUE for well-known extensions + const parsedValue = ExtensionValueFactory.fromBER(this.extnID, this.extnValue.valueBlock.valueHexView); + this._parsedValue = parsedValue; + } + + return this._parsedValue || undefined; + } + public set parsedValue(value: ExtensionParsedValue | undefined) { + this._parsedValue = value; + } + + /** + * Initializes a new instance of the {@link Extension} class + * @param parameters Initialization parameters + */ + constructor(parameters: ExtensionParameters = {}) { + super(); + + this.extnID = pvutils.getParametersValue(parameters, EXTN_ID, Extension.defaultValues(EXTN_ID)); + this.critical = pvutils.getParametersValue(parameters, CRITICAL, Extension.defaultValues(CRITICAL)); + if (EXTN_VALUE in parameters) { + this.extnValue = new asn1js.OctetString({ valueHex: parameters.extnValue }); + } else { + this.extnValue = Extension.defaultValues(EXTN_VALUE); + } + if (PARSED_VALUE in parameters) { + this.parsedValue = pvutils.getParametersValue(parameters, PARSED_VALUE, Extension.defaultValues(PARSED_VALUE)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof EXTN_ID): string; + public static override defaultValues(memberName: typeof CRITICAL): boolean; + public static override defaultValues(memberName: typeof EXTN_VALUE): asn1js.OctetString; + public static override defaultValues(memberName: typeof PARSED_VALUE): ExtensionParsedValue; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case EXTN_ID: + return EMPTY_STRING; + case CRITICAL: + return false; + case EXTN_VALUE: + return new asn1js.OctetString(); + case PARSED_VALUE: + return {}; + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * Extension ::= SEQUENCE { + * extnID OBJECT IDENTIFIER, + * critical BOOLEAN DEFAULT FALSE, + * extnValue OCTET STRING + * } + *``` + */ + public static override schema(parameters: ExtensionSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.ObjectIdentifier({ name: (names.extnID || EMPTY_STRING) }), + new asn1js.Boolean({ + name: (names.critical || EMPTY_STRING), + optional: true + }), + new asn1js.OctetString({ name: (names.extnValue || EMPTY_STRING) }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + Extension.schema({ + names: { + extnID: EXTN_ID, + critical: CRITICAL, + extnValue: EXTN_VALUE + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.extnID = asn1.result.extnID.valueBlock.toString(); + if (CRITICAL in asn1.result) { + this.critical = asn1.result.critical.valueBlock.value; + } + this.extnValue = asn1.result.extnValue; + } + + public toSchema(): asn1js.Sequence { + // Create array for output sequence + const outputArray = []; + + outputArray.push(new asn1js.ObjectIdentifier({ value: this.extnID })); + + if (this.critical !== Extension.defaultValues(CRITICAL)) { + outputArray.push(new asn1js.Boolean({ value: this.critical })); + } + + outputArray.push(this.extnValue); + + // Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + } + + public toJSON(): ExtensionJson { + const object: ExtensionJson = { + extnID: this.extnID, + extnValue: this.extnValue.toJSON(), + }; + + if (this.critical !== Extension.defaultValues(CRITICAL)) { + object.critical = this.critical; + } + if (this.parsedValue && this.parsedValue.toJSON) { + object.parsedValue = this.parsedValue.toJSON(); + } + + return object; + } + +} diff --git a/third_party/js/PKI.js/src/ExtensionValueFactory.ts b/third_party/js/PKI.js/src/ExtensionValueFactory.ts new file mode 100644 index 0000000000..a74cb00e53 --- /dev/null +++ b/third_party/js/PKI.js/src/ExtensionValueFactory.ts @@ -0,0 +1,106 @@ +import * as asn1js from "asn1js"; +import * as OID from "./ObjectIdentifiers"; +import * as Schema from "./Schema"; + +export type ExtensionParsedValue = (Schema.SchemaCompatible & { + parsingError?: string; +}) | Schema.SchemaType; + +export interface ExtensionValueType { + name: string; + type: ExtensionValueConstructor; +} + +export interface ExtensionValueConstructor { + new(params?: { schema: any; }): Schema.SchemaCompatible; +} + +export class ExtensionValueFactory { + + public static types?: Record<string, ExtensionValueType>; + + private static getItems(): Record<string, ExtensionValueType> { + if (!this.types) { + this.types = {}; + + // Register wellknown extensions + ExtensionValueFactory.register(OID.id_SubjectAltName, "SubjectAltName", AltName); + ExtensionValueFactory.register(OID.id_IssuerAltName, "IssuerAltName", AltName); + ExtensionValueFactory.register(OID.id_AuthorityKeyIdentifier, "AuthorityKeyIdentifier", AuthorityKeyIdentifier); + ExtensionValueFactory.register(OID.id_BasicConstraints, "BasicConstraints", BasicConstraints); + ExtensionValueFactory.register(OID.id_MicrosoftCaVersion, "MicrosoftCaVersion", CAVersion); + ExtensionValueFactory.register(OID.id_CertificatePolicies, "CertificatePolicies", CertificatePolicies); + ExtensionValueFactory.register(OID.id_MicrosoftAppPolicies, "CertificatePoliciesMicrosoft", CertificatePolicies); + ExtensionValueFactory.register(OID.id_MicrosoftCertTemplateV2, "MicrosoftCertTemplateV2", CertificateTemplate); + ExtensionValueFactory.register(OID.id_CRLDistributionPoints, "CRLDistributionPoints", CRLDistributionPoints); + ExtensionValueFactory.register(OID.id_FreshestCRL, "FreshestCRL", CRLDistributionPoints); + ExtensionValueFactory.register(OID.id_ExtKeyUsage, "ExtKeyUsage", ExtKeyUsage); + ExtensionValueFactory.register(OID.id_CertificateIssuer, "CertificateIssuer", GeneralNames); + ExtensionValueFactory.register(OID.id_AuthorityInfoAccess, "AuthorityInfoAccess", InfoAccess); + ExtensionValueFactory.register(OID.id_SubjectInfoAccess, "SubjectInfoAccess", InfoAccess); + ExtensionValueFactory.register(OID.id_IssuingDistributionPoint, "IssuingDistributionPoint", IssuingDistributionPoint); + ExtensionValueFactory.register(OID.id_NameConstraints, "NameConstraints", NameConstraints); + ExtensionValueFactory.register(OID.id_PolicyConstraints, "PolicyConstraints", PolicyConstraints); + ExtensionValueFactory.register(OID.id_PolicyMappings, "PolicyMappings", PolicyMappings); + ExtensionValueFactory.register(OID.id_PrivateKeyUsagePeriod, "PrivateKeyUsagePeriod", PrivateKeyUsagePeriod); + ExtensionValueFactory.register(OID.id_QCStatements, "QCStatements", QCStatements); + ExtensionValueFactory.register(OID.id_SignedCertificateTimestampList, "SignedCertificateTimestampList", SignedCertificateTimestampList); + ExtensionValueFactory.register(OID.id_SubjectDirectoryAttributes, "SubjectDirectoryAttributes", SubjectDirectoryAttributes); + } + + return this.types; + } + + public static fromBER(id: string, raw: BufferSource): ExtensionParsedValue | null { + const asn1 = asn1js.fromBER(raw); + if (asn1.offset === -1) { + return null; + } + + const item = this.find(id); + if (item) { + try { + return new item.type({ schema: asn1.result }); + } catch (ex) { + const res: ExtensionParsedValue = new item.type(); + res.parsingError = `Incorrectly formatted value of extension ${item.name} (${id})`; + + return res; + } + } + + return asn1.result; + } + + public static find(id: string): ExtensionValueType | null { + const types = this.getItems(); + + return types[id] || null; + } + + public static register(id: string, name: string, type: ExtensionValueConstructor) { + this.getItems()[id] = { name, type }; + } + +} + +import { AltName } from "./AltName"; +import { AuthorityKeyIdentifier } from "./AuthorityKeyIdentifier"; +import { BasicConstraints } from "./BasicConstraints"; +import { CAVersion } from "./CAVersion"; +import { CertificatePolicies } from "./CertificatePolicies"; +import { CertificateTemplate } from "./CertificateTemplate"; +import { CRLDistributionPoints } from "./CRLDistributionPoints"; +import { ExtKeyUsage } from "./ExtKeyUsage"; +import { GeneralNames } from "./GeneralNames"; +import { InfoAccess } from "./InfoAccess"; +import { IssuingDistributionPoint } from "./IssuingDistributionPoint"; +import { NameConstraints } from "./NameConstraints"; +import { PolicyConstraints } from "./PolicyConstraints"; +import { PolicyMappings } from "./PolicyMappings"; +import { PrivateKeyUsagePeriod } from "./PrivateKeyUsagePeriod"; +import { QCStatements } from "./QCStatements"; +import { SignedCertificateTimestampList } from "./SignedCertificateTimestampList"; +import { SubjectDirectoryAttributes } from "./SubjectDirectoryAttributes"; + + diff --git a/third_party/js/PKI.js/src/Extensions.ts b/third_party/js/PKI.js/src/Extensions.ts new file mode 100644 index 0000000000..d5624332cb --- /dev/null +++ b/third_party/js/PKI.js/src/Extensions.ts @@ -0,0 +1,129 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { Extension, ExtensionJson, ExtensionSchema } from "./Extension"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const EXTENSIONS = "extensions"; +const CLEAR_PROPS = [ + EXTENSIONS, +]; + +export interface IExtensions { + /** + * List of extensions + */ + extensions: Extension[]; +} + +export type ExtensionsParameters = PkiObjectParameters & Partial<IExtensions>; + +export type ExtensionsSchema = Schema.SchemaParameters<{ + extensions?: string; + extension?: ExtensionSchema; +}>; + +export interface ExtensionsJson { + extensions: ExtensionJson[]; +} + +/** + * Represents the Extensions structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class Extensions extends PkiObject implements IExtensions { + + public static override CLASS_NAME = "Extensions"; + + public extensions!: Extension[]; + + /** + * Initializes a new instance of the {@link Extensions} class + * @param parameters Initialization parameters + */ + constructor(parameters: ExtensionsParameters = {}) { + super(); + + this.extensions = pvutils.getParametersValue(parameters, EXTENSIONS, Extensions.defaultValues(EXTENSIONS)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof EXTENSIONS): Extension[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case EXTENSIONS: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension + * ``` + * + * @param parameters Input parameters for the schema + * @param optional Flag that current schema should be optional + * @returns ASN.1 schema object + */ + public static override schema(parameters: ExtensionsSchema = {}, optional = false) { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + optional, + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Repeated({ + name: (names.extensions || EMPTY_STRING), + value: Extension.schema(names.extension || {}) + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + Extensions.schema({ + names: { + extensions: EXTENSIONS + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.extensions = Array.from(asn1.result.extensions, element => new Extension({ schema: element })); + } + + public toSchema(): asn1js.Sequence { + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: Array.from(this.extensions, o => o.toSchema()) + })); + //#endregion + } + + public toJSON(): ExtensionsJson { + return { + extensions: this.extensions.map(o => o.toJSON()) + }; + } + +} diff --git a/third_party/js/PKI.js/src/GeneralName.ts b/third_party/js/PKI.js/src/GeneralName.ts new file mode 100644 index 0000000000..7deba00891 --- /dev/null +++ b/third_party/js/PKI.js/src/GeneralName.ts @@ -0,0 +1,642 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { RelativeDistinguishedNames } from "./RelativeDistinguishedNames"; +import * as Schema from "./Schema"; + +export const TYPE = "type"; +export const VALUE = "value"; + +//#region Additional asn1js schema elements existing inside GeneralName schema + +/** + * Schema for "builtInStandardAttributes" of "ORAddress" + * @param parameters + * @property names + * @param optional + * @returns + */ +function builtInStandardAttributes(parameters: Schema.SchemaParameters<{ + country_name?: string; + administration_domain_name?: string; + network_address?: string; + terminal_identifier?: string; + private_domain_name?: string; + organization_name?: string; + numeric_user_identifier?: string; + personal_name?: string; + organizational_unit_names?: string; +}> = {}, optional = false) { + //builtInStandardAttributes ::= Sequence { + // country-name CountryName OPTIONAL, + // administration-domain-name AdministrationDomainName OPTIONAL, + // network-address [0] IMPLICIT NetworkAddress OPTIONAL, + // terminal-identifier [1] IMPLICIT TerminalIdentifier OPTIONAL, + // private-domain-name [2] PrivateDomainName OPTIONAL, + // organization-name [3] IMPLICIT OrganizationName OPTIONAL, + // numeric-user-identifier [4] IMPLICIT NumericUserIdentifier OPTIONAL, + // personal-name [5] IMPLICIT PersonalName OPTIONAL, + // organizational-unit-names [6] IMPLICIT OrganizationalUnitNames OPTIONAL } + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + optional, + value: [ + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 2, // APPLICATION-SPECIFIC + tagNumber: 1 // [1] + }, + name: (names.country_name || EMPTY_STRING), + value: [ + new asn1js.Choice({ + value: [ + new asn1js.NumericString(), + new asn1js.PrintableString() + ] + }) + ] + }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 2, // APPLICATION-SPECIFIC + tagNumber: 2 // [2] + }, + name: (names.administration_domain_name || EMPTY_STRING), + value: [ + new asn1js.Choice({ + value: [ + new asn1js.NumericString(), + new asn1js.PrintableString() + ] + }) + ] + }), + new asn1js.Primitive({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + name: (names.network_address || EMPTY_STRING), + isHexOnly: true + }), + new asn1js.Primitive({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + name: (names.terminal_identifier || EMPTY_STRING), + isHexOnly: true + }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 2 // [2] + }, + name: (names.private_domain_name || EMPTY_STRING), + value: [ + new asn1js.Choice({ + value: [ + new asn1js.NumericString(), + new asn1js.PrintableString() + ] + }) + ] + }), + new asn1js.Primitive({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 3 // [3] + }, + name: (names.organization_name || EMPTY_STRING), + isHexOnly: true + }), + new asn1js.Primitive({ + optional: true, + name: (names.numeric_user_identifier || EMPTY_STRING), + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 4 // [4] + }, + isHexOnly: true + }), + new asn1js.Constructed({ + optional: true, + name: (names.personal_name || EMPTY_STRING), + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 5 // [5] + }, + value: [ + new asn1js.Primitive({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + isHexOnly: true + }), + new asn1js.Primitive({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + isHexOnly: true + }), + new asn1js.Primitive({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 2 // [2] + }, + isHexOnly: true + }), + new asn1js.Primitive({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 3 // [3] + }, + isHexOnly: true + }) + ] + }), + new asn1js.Constructed({ + optional: true, + name: (names.organizational_unit_names || EMPTY_STRING), + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 6 // [6] + }, + value: [ + new asn1js.Repeated({ + value: new asn1js.PrintableString() + }) + ] + }) + ] + })); +} + +/** + * Schema for "builtInDomainDefinedAttributes" of "ORAddress" + * @param optional + */ +function builtInDomainDefinedAttributes(optional = false): Schema.SchemaType { + return (new asn1js.Sequence({ + optional, + value: [ + new asn1js.PrintableString(), + new asn1js.PrintableString() + ] + })); +} + +/** + * Schema for "builtInDomainDefinedAttributes" of "ORAddress" + * @param optional + */ +function extensionAttributes(optional = false): Schema.SchemaType { + return (new asn1js.Set({ + optional, + value: [ + new asn1js.Primitive({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + isHexOnly: true + }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [new asn1js.Any()] + }) + ] + })); +} + +//#endregion + +export interface IGeneralName { + /** + * value type - from a tagged value (0 for "otherName", 1 for "rfc822Name" etc.) + */ + type: number; + /** + * ASN.1 object having GeneralName value (type depends on TYPE value) + */ + value: any; +} + +export type GeneralNameParameters = PkiObjectParameters & Partial<{ type: 1 | 2 | 6; value: string; } | { type: 0 | 3 | 4 | 7 | 8; value: any; }>; + +export interface GeneralNameSchema { + names?: { + blockName?: string; + directoryName?: object; + builtInStandardAttributes?: object; + otherName?: string; + rfc822Name?: string; + dNSName?: string; + x400Address?: string; + ediPartyName?: string; + uniformResourceIdentifier?: string; + iPAddress?: string; + registeredID?: string; + }; +} + +export interface GeneralNameJson { + type: number; + value: string; +} + +/** + * Represents the GeneralName structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class GeneralName extends PkiObject implements IGeneralName { + + public static override CLASS_NAME = "GeneralName"; + + public type!: number; + public value: any; + + /** + * Initializes a new instance of the {@link GeneralName} class + * @param parameters Initialization parameters + */ + constructor(parameters: GeneralNameParameters = {}) { + super(); + + this.type = pvutils.getParametersValue(parameters, TYPE, GeneralName.defaultValues(TYPE)); + this.value = pvutils.getParametersValue(parameters, VALUE, GeneralName.defaultValues(VALUE)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof TYPE): number; + public static override defaultValues(memberName: typeof VALUE): any; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case TYPE: + return 9; + case VALUE: + return {}; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compares values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case TYPE: + return (memberValue === GeneralName.defaultValues(memberName)); + case VALUE: + return (Object.keys(memberValue).length === 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * GeneralName ::= Choice { + * otherName [0] OtherName, + * rfc822Name [1] IA5String, + * dNSName [2] IA5String, + * x400Address [3] ORAddress, + * directoryName [4] value, + * ediPartyName [5] EDIPartyName, + * uniformResourceIdentifier [6] IA5String, + * iPAddress [7] OCTET STRING, + * registeredID [8] OBJECT IDENTIFIER } + *``` + */ + static override schema(parameters: GeneralNameSchema = {}): asn1js.Choice { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Choice({ + value: [ + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.ObjectIdentifier(), + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [new asn1js.Any()] + }) + ] + }), + new asn1js.Primitive({ + name: (names.blockName || EMPTY_STRING), + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + } + }), + new asn1js.Primitive({ + name: (names.blockName || EMPTY_STRING), + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 2 // [2] + } + }), + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 3 // [3] + }, + name: (names.blockName || EMPTY_STRING), + value: [ + builtInStandardAttributes((names.builtInStandardAttributes || {}), false), + builtInDomainDefinedAttributes(true), + extensionAttributes(true) + ] + }), + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 4 // [4] + }, + name: (names.blockName || EMPTY_STRING), + value: [RelativeDistinguishedNames.schema(names.directoryName || {})] + }), + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 5 // [5] + }, + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + new asn1js.Choice({ + value: [ + new asn1js.TeletexString(), + new asn1js.PrintableString(), + new asn1js.UniversalString(), + new asn1js.Utf8String(), + new asn1js.BmpString() + ] + }) + ] + }), + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [ + new asn1js.Choice({ + value: [ + new asn1js.TeletexString(), + new asn1js.PrintableString(), + new asn1js.UniversalString(), + new asn1js.Utf8String(), + new asn1js.BmpString() + ] + }) + ] + }) + ] + }), + new asn1js.Primitive({ + name: (names.blockName || EMPTY_STRING), + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 6 // [6] + } + }), + new asn1js.Primitive({ + name: (names.blockName || EMPTY_STRING), + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 7 // [7] + } + }), + new asn1js.Primitive({ + name: (names.blockName || EMPTY_STRING), + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 8 // [8] + } + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + //#region Clear input data first + pvutils.clearProps(schema, [ + "blockName", + "otherName", + "rfc822Name", + "dNSName", + "x400Address", + "directoryName", + "ediPartyName", + "uniformResourceIdentifier", + "iPAddress", + "registeredID" + ]); + //#endregion + + //#region Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + GeneralName.schema({ + names: { + blockName: "blockName", + otherName: "otherName", + rfc822Name: "rfc822Name", + dNSName: "dNSName", + x400Address: "x400Address", + directoryName: { + names: { + blockName: "directoryName" + } + }, + ediPartyName: "ediPartyName", + uniformResourceIdentifier: "uniformResourceIdentifier", + iPAddress: "iPAddress", + registeredID: "registeredID" + } + }) + ); + + AsnError.assertSchema(asn1, this.className); + //#endregion + + //#region Get internal properties from parsed schema + this.type = asn1.result.blockName.idBlock.tagNumber; + + switch (this.type) { + case 0: // otherName + this.value = asn1.result.blockName; + break; + case 1: // rfc822Name + dNSName + uniformResourceIdentifier + case 2: + case 6: + { + const value = asn1.result.blockName; + + value.idBlock.tagClass = 1; // UNIVERSAL + value.idBlock.tagNumber = 22; // IA5STRING + + const valueBER = value.toBER(false); + + const asnValue = asn1js.fromBER(valueBER); + AsnError.assert(asnValue, "GeneralName value"); + + this.value = (asnValue.result as asn1js.BaseStringBlock).valueBlock.value; + } + break; + case 3: // x400Address + this.value = asn1.result.blockName; + break; + case 4: // directoryName + this.value = new RelativeDistinguishedNames({ schema: asn1.result.directoryName }); + break; + case 5: // ediPartyName + this.value = asn1.result.ediPartyName; + break; + case 7: // iPAddress + this.value = new asn1js.OctetString({ valueHex: asn1.result.blockName.valueBlock.valueHex }); + break; + case 8: // registeredID + { + const value = asn1.result.blockName; + + value.idBlock.tagClass = 1; // UNIVERSAL + value.idBlock.tagNumber = 6; // ObjectIdentifier + + const valueBER = value.toBER(false); + + const asnValue = asn1js.fromBER(valueBER); + AsnError.assert(asnValue, "GeneralName registeredID"); + this.value = asnValue.result.valueBlock.toString(); // Getting a string representation of the ObjectIdentifier + } + break; + default: + } + //#endregion + } + + public toSchema(): asn1js.Constructed | asn1js.IA5String | asn1js.ObjectIdentifier | asn1js.Choice { + //#region Construct and return new ASN.1 schema for this object + switch (this.type) { + case 0: + case 3: + case 5: + return new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: this.type + }, + value: [ + this.value + ] + }); + case 1: + case 2: + case 6: + { + const value = new asn1js.IA5String({ value: this.value }); + + value.idBlock.tagClass = 3; + value.idBlock.tagNumber = this.type; + + return value; + } + case 4: + return new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 4 + }, + value: [this.value.toSchema()] + }); + case 7: + { + const value = this.value; + + value.idBlock.tagClass = 3; + value.idBlock.tagNumber = this.type; + + return value; + } + case 8: + { + const value = new asn1js.ObjectIdentifier({ value: this.value }); + + value.idBlock.tagClass = 3; + value.idBlock.tagNumber = this.type; + + return value; + } + default: + return GeneralName.schema(); + } + //#endregion + } + + public toJSON(): GeneralNameJson { + const _object = { + type: this.type, + value: EMPTY_STRING + } as GeneralNameJson; + + if ((typeof this.value) === "string") + _object.value = this.value; + else { + try { + _object.value = this.value.toJSON(); + } + catch (ex) { + // nothing + } + } + + return _object; + } + +} diff --git a/third_party/js/PKI.js/src/GeneralNames.ts b/third_party/js/PKI.js/src/GeneralNames.ts new file mode 100644 index 0000000000..cb98b51271 --- /dev/null +++ b/third_party/js/PKI.js/src/GeneralNames.ts @@ -0,0 +1,130 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import * as Schema from "./Schema"; +import { GeneralName, GeneralNameJson } from "./GeneralName"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { AsnError } from "./errors"; +import { EMPTY_STRING } from "./constants"; + +const NAMES = "names"; +const GENERAL_NAMES = "generalNames"; + +export interface IGeneralNames { + names: GeneralName[]; +} + +export type GeneralNamesParameters = PkiObjectParameters & Partial<IGeneralNames>; + +export type GeneralNamesSchema = Schema.SchemaParameters<{ + generalNames?: string; +}>; + +export interface GeneralNamesJson { + names: GeneralNameJson[]; +} + +/** + * Represents the GeneralNames structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class GeneralNames extends PkiObject implements IGeneralNames { + + public static override CLASS_NAME = "GeneralNames"; + + /** + * Array of "general names" + */ + public names!: GeneralName[]; + + /** + * Initializes a new instance of the {@link GeneralNames} class + * @param parameters Initialization parameters + */ + constructor(parameters: GeneralNamesParameters = {}) { + super(); + + this.names = pvutils.getParametersValue(parameters, NAMES, GeneralNames.defaultValues(NAMES)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof NAMES): GeneralName[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case "names": + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName + * ``` + * + * @param parameters Input parameters for the schema + * @param optional Flag would be element optional or not + * @returns ASN.1 schema object + */ + public static override schema(parameters: GeneralNamesSchema = {}, optional = false): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, NAMES, {}); + + return (new asn1js.Sequence({ + optional, + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Repeated({ + name: (names.generalNames || EMPTY_STRING), + value: GeneralName.schema() + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + //#region Clear input data first + pvutils.clearProps(schema, [ + NAMES, + GENERAL_NAMES + ]); + //#endregion + + //#region Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + GeneralNames.schema({ + names: { + blockName: NAMES, + generalNames: GENERAL_NAMES + } + }) + ); + + AsnError.assertSchema(asn1, this.className); + //#endregion + + this.names = Array.from(asn1.result.generalNames, element => new GeneralName({ schema: element })); + } + + public toSchema(): asn1js.Sequence { + return (new asn1js.Sequence({ + value: Array.from(this.names, o => o.toSchema()) + })); + } + + public toJSON(): GeneralNamesJson { + return { + names: Array.from(this.names, o => o.toJSON()) + }; + } + +} diff --git a/third_party/js/PKI.js/src/GeneralSubtree.ts b/third_party/js/PKI.js/src/GeneralSubtree.ts new file mode 100644 index 0000000000..15f1c1f860 --- /dev/null +++ b/third_party/js/PKI.js/src/GeneralSubtree.ts @@ -0,0 +1,240 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { GeneralName, GeneralNameJson, GeneralNameSchema } from "./GeneralName"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const BASE = "base"; +const MINIMUM = "minimum"; +const MAXIMUM = "maximum"; +const CLEAR_PROPS = [ + BASE, + MINIMUM, + MAXIMUM +]; + +export interface IGeneralSubtree { + base: GeneralName; + minimum: number | asn1js.Integer; + maximum?: number | asn1js.Integer; +} + +export interface GeneralSubtreeJson { + base: GeneralNameJson; + minimum?: number | asn1js.IntegerJson; + maximum?: number | asn1js.IntegerJson; +} + +export type GeneralSubtreeParameters = PkiObjectParameters & Partial<IGeneralSubtree>; + +/** + * Represents the GeneralSubtree structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class GeneralSubtree extends PkiObject implements IGeneralSubtree { + + public static override CLASS_NAME = "GeneralSubtree"; + + public base!: GeneralName; + public minimum!: number | asn1js.Integer; + public maximum?: number | asn1js.Integer; + + /** + * Initializes a new instance of the {@link GeneralSubtree} class + * @param parameters Initialization parameters + */ + constructor(parameters: GeneralSubtreeParameters = {}) { + super(); + + this.base = pvutils.getParametersValue(parameters, BASE, GeneralSubtree.defaultValues(BASE)); + this.minimum = pvutils.getParametersValue(parameters, MINIMUM, GeneralSubtree.defaultValues(MINIMUM)); + if (MAXIMUM in parameters) { + this.maximum = pvutils.getParametersValue(parameters, MAXIMUM, GeneralSubtree.defaultValues(MAXIMUM)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof BASE): GeneralName; + public static override defaultValues(memberName: typeof MINIMUM): number; + public static override defaultValues(memberName: typeof MAXIMUM): number; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case BASE: + return new GeneralName(); + case MINIMUM: + return 0; + case MAXIMUM: + return 0; + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * GeneralSubtree ::= SEQUENCE { + * base GeneralName, + * minimum [0] BaseDistance DEFAULT 0, + * maximum [1] BaseDistance OPTIONAL } + * + * BaseDistance ::= INTEGER (0..MAX) + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + base?: GeneralNameSchema; + minimum?: string; + maximum?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + GeneralName.schema(names.base || {}), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [new asn1js.Integer({ name: (names.minimum || EMPTY_STRING) })] + }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [new asn1js.Integer({ name: (names.maximum || EMPTY_STRING) })] + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + GeneralSubtree.schema({ + names: { + base: { + names: { + blockName: BASE + } + }, + minimum: MINIMUM, + maximum: MAXIMUM + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.base = new GeneralName({ schema: asn1.result.base }); + + if (MINIMUM in asn1.result) { + if (asn1.result.minimum.valueBlock.isHexOnly) + this.minimum = asn1.result.minimum; + else + this.minimum = asn1.result.minimum.valueBlock.valueDec; + } + + if (MAXIMUM in asn1.result) { + if (asn1.result.maximum.valueBlock.isHexOnly) + this.maximum = asn1.result.maximum; + else + this.maximum = asn1.result.maximum.valueBlock.valueDec; + } + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray = []; + + outputArray.push(this.base.toSchema()); + + if (this.minimum !== 0) { + let valueMinimum: number | asn1js.Integer = 0; + + if (this.minimum instanceof asn1js.Integer) { + valueMinimum = this.minimum; + } else { + valueMinimum = new asn1js.Integer({ value: this.minimum }); + } + + outputArray.push(new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [valueMinimum] + })); + } + + if (MAXIMUM in this) { + let valueMaximum: number | asn1js.Integer = 0; + + if (this.maximum instanceof asn1js.Integer) { + valueMaximum = this.maximum; + } else { + valueMaximum = new asn1js.Integer({ value: this.maximum }); + } + + outputArray.push(new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [valueMaximum] + })); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): GeneralSubtreeJson { + const res: GeneralSubtreeJson = { + base: this.base.toJSON() + }; + + if (this.minimum !== 0) { + if (typeof this.minimum === "number") { + res.minimum = this.minimum; + } else { + res.minimum = this.minimum.toJSON(); + } + } + + if (this.maximum !== undefined) { + if (typeof this.maximum === "number") { + res.maximum = this.maximum; + } else { + res.maximum = this.maximum.toJSON(); + } + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/Helpers.ts b/third_party/js/PKI.js/src/Helpers.ts new file mode 100644 index 0000000000..c95f3e6cf7 --- /dev/null +++ b/third_party/js/PKI.js/src/Helpers.ts @@ -0,0 +1,34 @@ +import { EMPTY_STRING } from "./constants"; + +/** + * String preparation function. In a future here will be realization of algorithm from RFC4518 + * @param inputString JavaScript string. As soon as for each ASN.1 string type we have a specific + * transformation function here we will work with pure JavaScript string + * @returns Formatted string + */ +export function stringPrep(inputString: string): string { + //#region Initial variables + let isSpace = false; + let cutResult = EMPTY_STRING; + //#endregion + + const result = inputString.trim(); // Trim input string + + //#region Change all sequence of SPACE down to SPACE char + for (let i = 0; i < result.length; i++) { + if (result.charCodeAt(i) === 32) { + if (isSpace === false) + isSpace = true; + } else { + if (isSpace) { + cutResult += " "; + isSpace = false; + } + + cutResult += result[i]; + } + } + //#endregion + + return cutResult.toLowerCase(); +} diff --git a/third_party/js/PKI.js/src/InfoAccess.ts b/third_party/js/PKI.js/src/InfoAccess.ts new file mode 100644 index 0000000000..b844ad4577 --- /dev/null +++ b/third_party/js/PKI.js/src/InfoAccess.ts @@ -0,0 +1,118 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { AccessDescription, AccessDescriptionJson } from "./AccessDescription"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const ACCESS_DESCRIPTIONS = "accessDescriptions"; + +export interface IInfoAccess { + accessDescriptions: AccessDescription[]; +} + +export interface InfoAccessJson { + accessDescriptions: AccessDescriptionJson[]; +} + +export type InfoAccessParameters = PkiObjectParameters & Partial<IInfoAccess>; + +/** + * Represents the InfoAccess structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class InfoAccess extends PkiObject implements IInfoAccess { + + public static override CLASS_NAME = "InfoAccess"; + + public accessDescriptions!: AccessDescription[]; + + /** + * Initializes a new instance of the {@link InfoAccess} class + * @param parameters Initialization parameters + */ + constructor(parameters: InfoAccessParameters = {}) { + super(); + + this.accessDescriptions = pvutils.getParametersValue(parameters, ACCESS_DESCRIPTIONS, InfoAccess.defaultValues(ACCESS_DESCRIPTIONS)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof ACCESS_DESCRIPTIONS): AccessDescription[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case ACCESS_DESCRIPTIONS: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * AuthorityInfoAccessSyntax ::= + * SEQUENCE SIZE (1..MAX) OF AccessDescription + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + accessDescriptions?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Repeated({ + name: (names.accessDescriptions || EMPTY_STRING), + value: AccessDescription.schema() + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, [ + ACCESS_DESCRIPTIONS + ]); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + InfoAccess.schema({ + names: { + accessDescriptions: ACCESS_DESCRIPTIONS + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.accessDescriptions = Array.from(asn1.result.accessDescriptions, element => new AccessDescription({ schema: element })); + } + + public toSchema(): asn1js.Sequence { + // Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: Array.from(this.accessDescriptions, o => o.toSchema()) + })); + } + + public toJSON(): InfoAccessJson { + return { + accessDescriptions: Array.from(this.accessDescriptions, o => o.toJSON()) + }; + } + +} + diff --git a/third_party/js/PKI.js/src/IssuerAndSerialNumber.ts b/third_party/js/PKI.js/src/IssuerAndSerialNumber.ts new file mode 100644 index 0000000000..94972ccbb5 --- /dev/null +++ b/third_party/js/PKI.js/src/IssuerAndSerialNumber.ts @@ -0,0 +1,153 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { RelativeDistinguishedNames, RelativeDistinguishedNamesJson, RelativeDistinguishedNamesSchema } from "./RelativeDistinguishedNames"; +import * as Schema from "./Schema"; + +const ISSUER = "issuer"; +const SERIAL_NUMBER = "serialNumber"; +const CLEAR_PROPS = [ + ISSUER, + SERIAL_NUMBER, +]; + +export interface IIssuerAndSerialNumber { + /** + * Certificate issuer name + */ + issuer: RelativeDistinguishedNames; + /** + * Certificate serial number + */ + serialNumber: asn1js.Integer; +} + +export interface IssuerAndSerialNumberJson { + issuer: RelativeDistinguishedNamesJson; + serialNumber: asn1js.IntegerJson; +} + +export type IssuerAndSerialNumberParameters = PkiObjectParameters & Partial<IIssuerAndSerialNumber>; + +export type IssuerAndSerialNumberSchema = Schema.SchemaParameters<{ + issuer?: RelativeDistinguishedNamesSchema; + serialNumber?: string; +}>; + +/** + * Represents the IssuerAndSerialNumber structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + */ +export class IssuerAndSerialNumber extends PkiObject implements IIssuerAndSerialNumber { + + public static override CLASS_NAME = "IssuerAndSerialNumber"; + + public issuer!: RelativeDistinguishedNames; + public serialNumber!: asn1js.Integer; + + /** + * Initializes a new instance of the {@link IssuerAndSerialNumber} class + * @param parameters Initialization parameters + */ + constructor(parameters: IssuerAndSerialNumberParameters = {}) { + super(); + + this.issuer = pvutils.getParametersValue(parameters, ISSUER, IssuerAndSerialNumber.defaultValues(ISSUER)); + this.serialNumber = pvutils.getParametersValue(parameters, SERIAL_NUMBER, IssuerAndSerialNumber.defaultValues(SERIAL_NUMBER)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof ISSUER): RelativeDistinguishedNames; + public static override defaultValues(memberName: typeof SERIAL_NUMBER): asn1js.Integer; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case ISSUER: + return new RelativeDistinguishedNames(); + case SERIAL_NUMBER: + return new asn1js.Integer(); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * IssuerAndSerialNumber ::= SEQUENCE { + * issuer Name, + * serialNumber CertificateSerialNumber } + * + * CertificateSerialNumber ::= INTEGER + *``` + */ + public static override schema(parameters: IssuerAndSerialNumberSchema = {}): Schema.SchemaType { + /** + * @type {Object} + * @property {string} [blockName] + * @property {string} [issuer] + * @property {string} [serialNumber] + */ + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + RelativeDistinguishedNames.schema(names.issuer || {}), + new asn1js.Integer({ name: (names.serialNumber || EMPTY_STRING) }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + IssuerAndSerialNumber.schema({ + names: { + issuer: { + names: { + blockName: ISSUER + } + }, + serialNumber: SERIAL_NUMBER + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.issuer = new RelativeDistinguishedNames({ schema: asn1.result.issuer }); + this.serialNumber = asn1.result.serialNumber; + } + + public toSchema(): asn1js.Sequence { + // Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: [ + this.issuer.toSchema(), + this.serialNumber + ] + })); + } + + public toJSON(): IssuerAndSerialNumberJson { + return { + issuer: this.issuer.toJSON(), + serialNumber: this.serialNumber.toJSON(), + }; + } + +} diff --git a/third_party/js/PKI.js/src/IssuingDistributionPoint.ts b/third_party/js/PKI.js/src/IssuingDistributionPoint.ts new file mode 100644 index 0000000000..15f9258cac --- /dev/null +++ b/third_party/js/PKI.js/src/IssuingDistributionPoint.ts @@ -0,0 +1,429 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { GeneralName, GeneralNameJson } from "./GeneralName"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { RelativeDistinguishedNames, RelativeDistinguishedNamesJson } from "./RelativeDistinguishedNames"; +import * as Schema from "./Schema"; + +const DISTRIBUTION_POINT = "distributionPoint"; +const DISTRIBUTION_POINT_NAMES = "distributionPointNames"; +const ONLY_CONTAINS_USER_CERTS = "onlyContainsUserCerts"; +const ONLY_CONTAINS_CA_CERTS = "onlyContainsCACerts"; +const ONLY_SOME_REASON = "onlySomeReasons"; +const INDIRECT_CRL = "indirectCRL"; +const ONLY_CONTAINS_ATTRIBUTE_CERTS = "onlyContainsAttributeCerts"; +const CLEAR_PROPS = [ + DISTRIBUTION_POINT, + DISTRIBUTION_POINT_NAMES, + ONLY_CONTAINS_USER_CERTS, + ONLY_CONTAINS_CA_CERTS, + ONLY_SOME_REASON, + INDIRECT_CRL, + ONLY_CONTAINS_ATTRIBUTE_CERTS, +]; + +export interface IIssuingDistributionPoint { + distributionPoint?: DistributionPointName; + onlyContainsUserCerts: boolean; + onlyContainsCACerts: boolean; + onlySomeReasons?: number; + indirectCRL: boolean; + onlyContainsAttributeCerts: boolean; +} + +export interface IssuingDistributionPointJson { + distributionPoint?: DistributionPointNameJson; + onlyContainsUserCerts?: boolean; + onlyContainsCACerts?: boolean; + onlySomeReasons?: number; + indirectCRL?: boolean; + onlyContainsAttributeCerts?: boolean; +} + +export type DistributionPointName = GeneralName[] | RelativeDistinguishedNames; +export type DistributionPointNameJson = GeneralNameJson[] | RelativeDistinguishedNamesJson; + +export type IssuingDistributionPointParameters = PkiObjectParameters & Partial<IIssuingDistributionPoint>; + +/** + * Represents the IssuingDistributionPoint structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class IssuingDistributionPoint extends PkiObject implements IIssuingDistributionPoint { + + public static override CLASS_NAME = "IssuingDistributionPoint"; + + public distributionPoint?: DistributionPointName; + public onlyContainsUserCerts!: boolean; + public onlyContainsCACerts!: boolean; + public onlySomeReasons?: number; + public indirectCRL!: boolean; + public onlyContainsAttributeCerts!: boolean; + + /** + * Initializes a new instance of the {@link IssuingDistributionPoint} class + * @param parameters Initialization parameters + */ + constructor(parameters: IssuingDistributionPointParameters = {}) { + super(); + + if (DISTRIBUTION_POINT in parameters) { + this.distributionPoint = pvutils.getParametersValue(parameters, DISTRIBUTION_POINT, IssuingDistributionPoint.defaultValues(DISTRIBUTION_POINT)); + } + + this.onlyContainsUserCerts = pvutils.getParametersValue(parameters, ONLY_CONTAINS_USER_CERTS, IssuingDistributionPoint.defaultValues(ONLY_CONTAINS_USER_CERTS)); + this.onlyContainsCACerts = pvutils.getParametersValue(parameters, ONLY_CONTAINS_CA_CERTS, IssuingDistributionPoint.defaultValues(ONLY_CONTAINS_CA_CERTS)); + if (ONLY_SOME_REASON in parameters) { + this.onlySomeReasons = pvutils.getParametersValue(parameters, ONLY_SOME_REASON, IssuingDistributionPoint.defaultValues(ONLY_SOME_REASON)); + } + this.indirectCRL = pvutils.getParametersValue(parameters, INDIRECT_CRL, IssuingDistributionPoint.defaultValues(INDIRECT_CRL)); + this.onlyContainsAttributeCerts = pvutils.getParametersValue(parameters, ONLY_CONTAINS_ATTRIBUTE_CERTS, IssuingDistributionPoint.defaultValues(ONLY_CONTAINS_ATTRIBUTE_CERTS)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof DISTRIBUTION_POINT): DistributionPointName; + public static override defaultValues(memberName: typeof ONLY_CONTAINS_USER_CERTS): boolean; + public static override defaultValues(memberName: typeof ONLY_CONTAINS_CA_CERTS): boolean; + public static override defaultValues(memberName: typeof ONLY_SOME_REASON): number; + public static override defaultValues(memberName: typeof INDIRECT_CRL): boolean; + public static override defaultValues(memberName: typeof ONLY_CONTAINS_ATTRIBUTE_CERTS): boolean; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case DISTRIBUTION_POINT: + return []; + case ONLY_CONTAINS_USER_CERTS: + return false; + case ONLY_CONTAINS_CA_CERTS: + return false; + case ONLY_SOME_REASON: + return 0; + case INDIRECT_CRL: + return false; + case ONLY_CONTAINS_ATTRIBUTE_CERTS: + return false; + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * IssuingDistributionPoint ::= SEQUENCE { + * distributionPoint [0] DistributionPointName OPTIONAL, + * onlyContainsUserCerts [1] BOOLEAN DEFAULT FALSE, + * onlyContainsCACerts [2] BOOLEAN DEFAULT FALSE, + * onlySomeReasons [3] ReasonFlags OPTIONAL, + * indirectCRL [4] BOOLEAN DEFAULT FALSE, + * onlyContainsAttributeCerts [5] BOOLEAN DEFAULT FALSE } + * + * ReasonFlags ::= BIT STRING { + * unused (0), + * keyCompromise (1), + * cACompromise (2), + * affiliationChanged (3), + * superseded (4), + * cessationOfOperation (5), + * certificateHold (6), + * privilegeWithdrawn (7), + * aACompromise (8) } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + distributionPoint?: string; + distributionPointNames?: string; + onlyContainsUserCerts?: string; + onlyContainsCACerts?: string; + onlySomeReasons?: string; + indirectCRL?: string; + onlyContainsAttributeCerts?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + new asn1js.Choice({ + value: [ + new asn1js.Constructed({ + name: (names.distributionPoint || EMPTY_STRING), + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + new asn1js.Repeated({ + name: (names.distributionPointNames || EMPTY_STRING), + value: GeneralName.schema() + }) + ] + }), + new asn1js.Constructed({ + name: (names.distributionPoint || EMPTY_STRING), + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: RelativeDistinguishedNames.schema().valueBlock.value + }) + ] + }) + ] + }), + new asn1js.Primitive({ + name: (names.onlyContainsUserCerts || EMPTY_STRING), + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + } + }), // IMPLICIT boolean value + new asn1js.Primitive({ + name: (names.onlyContainsCACerts || EMPTY_STRING), + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 2 // [2] + } + }), // IMPLICIT boolean value + new asn1js.Primitive({ + name: (names.onlySomeReasons || EMPTY_STRING), + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 3 // [3] + } + }), // IMPLICIT BitString value + new asn1js.Primitive({ + name: (names.indirectCRL || EMPTY_STRING), + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 4 // [4] + } + }), // IMPLICIT boolean value + new asn1js.Primitive({ + name: (names.onlyContainsAttributeCerts || EMPTY_STRING), + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 5 // [5] + } + }) // IMPLICIT boolean value + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + IssuingDistributionPoint.schema({ + names: { + distributionPoint: DISTRIBUTION_POINT, + distributionPointNames: DISTRIBUTION_POINT_NAMES, + onlyContainsUserCerts: ONLY_CONTAINS_USER_CERTS, + onlyContainsCACerts: ONLY_CONTAINS_CA_CERTS, + onlySomeReasons: ONLY_SOME_REASON, + indirectCRL: INDIRECT_CRL, + onlyContainsAttributeCerts: ONLY_CONTAINS_ATTRIBUTE_CERTS + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + if (DISTRIBUTION_POINT in asn1.result) { + switch (true) { + case (asn1.result.distributionPoint.idBlock.tagNumber === 0): // GENERAL_NAMES variant + this.distributionPoint = Array.from(asn1.result.distributionPointNames, element => new GeneralName({ schema: element })); + break; + case (asn1.result.distributionPoint.idBlock.tagNumber === 1): // RDN variant + { + this.distributionPoint = new RelativeDistinguishedNames({ + schema: new asn1js.Sequence({ + value: asn1.result.distributionPoint.valueBlock.value + }) + }); + } + break; + default: + throw new Error("Unknown tagNumber for distributionPoint: {$asn1.result.distributionPoint.idBlock.tagNumber}"); + } + } + + if (ONLY_CONTAINS_USER_CERTS in asn1.result) { + const view = new Uint8Array(asn1.result.onlyContainsUserCerts.valueBlock.valueHex); + this.onlyContainsUserCerts = (view[0] !== 0x00); + } + + if (ONLY_CONTAINS_CA_CERTS in asn1.result) { + const view = new Uint8Array(asn1.result.onlyContainsCACerts.valueBlock.valueHex); + this.onlyContainsCACerts = (view[0] !== 0x00); + } + + if (ONLY_SOME_REASON in asn1.result) { + const view = new Uint8Array(asn1.result.onlySomeReasons.valueBlock.valueHex); + this.onlySomeReasons = view[0]; + } + + if (INDIRECT_CRL in asn1.result) { + const view = new Uint8Array(asn1.result.indirectCRL.valueBlock.valueHex); + this.indirectCRL = (view[0] !== 0x00); + } + + if (ONLY_CONTAINS_ATTRIBUTE_CERTS in asn1.result) { + const view = new Uint8Array(asn1.result.onlyContainsAttributeCerts.valueBlock.valueHex); + this.onlyContainsAttributeCerts = (view[0] !== 0x00); + } + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray = []; + + if (this.distributionPoint) { + let value; + + if (this.distributionPoint instanceof Array) { + value = new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: Array.from(this.distributionPoint, o => o.toSchema()) + }); + } else { + value = this.distributionPoint.toSchema(); + + value.idBlock.tagClass = 3; // CONTEXT - SPECIFIC + value.idBlock.tagNumber = 1; // [1] + } + + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [value] + })); + } + + if (this.onlyContainsUserCerts !== IssuingDistributionPoint.defaultValues(ONLY_CONTAINS_USER_CERTS)) { + outputArray.push(new asn1js.Primitive({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + valueHex: (new Uint8Array([0xFF])).buffer + })); + } + + if (this.onlyContainsCACerts !== IssuingDistributionPoint.defaultValues(ONLY_CONTAINS_CA_CERTS)) { + outputArray.push(new asn1js.Primitive({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 2 // [2] + }, + valueHex: (new Uint8Array([0xFF])).buffer + })); + } + + if (this.onlySomeReasons !== undefined) { + const buffer = new ArrayBuffer(1); + const view = new Uint8Array(buffer); + + view[0] = this.onlySomeReasons; + + outputArray.push(new asn1js.Primitive({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 3 // [3] + }, + valueHex: buffer + })); + } + + if (this.indirectCRL !== IssuingDistributionPoint.defaultValues(INDIRECT_CRL)) { + outputArray.push(new asn1js.Primitive({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 4 // [4] + }, + valueHex: (new Uint8Array([0xFF])).buffer + })); + } + + if (this.onlyContainsAttributeCerts !== IssuingDistributionPoint.defaultValues(ONLY_CONTAINS_ATTRIBUTE_CERTS)) { + outputArray.push(new asn1js.Primitive({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 5 // [5] + }, + valueHex: (new Uint8Array([0xFF])).buffer + })); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): IssuingDistributionPointJson { + const obj: IssuingDistributionPointJson = {}; + + if (this.distributionPoint) { + if (this.distributionPoint instanceof Array) { + obj.distributionPoint = Array.from(this.distributionPoint, o => o.toJSON()); + } else { + obj.distributionPoint = this.distributionPoint.toJSON(); + } + } + + if (this.onlyContainsUserCerts !== IssuingDistributionPoint.defaultValues(ONLY_CONTAINS_USER_CERTS)) { + obj.onlyContainsUserCerts = this.onlyContainsUserCerts; + } + + if (this.onlyContainsCACerts !== IssuingDistributionPoint.defaultValues(ONLY_CONTAINS_CA_CERTS)) { + obj.onlyContainsCACerts = this.onlyContainsCACerts; + } + + if (ONLY_SOME_REASON in this) { + obj.onlySomeReasons = this.onlySomeReasons; + } + + if (this.indirectCRL !== IssuingDistributionPoint.defaultValues(INDIRECT_CRL)) { + obj.indirectCRL = this.indirectCRL; + } + + if (this.onlyContainsAttributeCerts !== IssuingDistributionPoint.defaultValues(ONLY_CONTAINS_ATTRIBUTE_CERTS)) { + obj.onlyContainsAttributeCerts = this.onlyContainsAttributeCerts; + } + + return obj; + } + +} diff --git a/third_party/js/PKI.js/src/KEKIdentifier.ts b/third_party/js/PKI.js/src/KEKIdentifier.ts new file mode 100644 index 0000000000..46f2e04a13 --- /dev/null +++ b/third_party/js/PKI.js/src/KEKIdentifier.ts @@ -0,0 +1,207 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { OtherKeyAttribute, OtherKeyAttributeJson, OtherKeyAttributeSchema } from "./OtherKeyAttribute"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const KEY_IDENTIFIER = "keyIdentifier"; +const DATE = "date"; +const OTHER = "other"; +const CLEAR_PROPS = [ + KEY_IDENTIFIER, + DATE, + OTHER, +]; + +export interface IKEKIdentifier { + keyIdentifier: asn1js.OctetString; + date?: asn1js.GeneralizedTime; + other?: OtherKeyAttribute; +} + +export interface KEKIdentifierJson { + keyIdentifier: asn1js.OctetStringJson; + date?: asn1js.GeneralizedTime; + other?: OtherKeyAttributeJson; +} + +export type KEKIdentifierParameters = PkiObjectParameters & Partial<IKEKIdentifier>; + +export type KEKIdentifierSchema = Schema.SchemaParameters<{ + keyIdentifier?: string; + date?: string; + other?: OtherKeyAttributeSchema; +}>; + +/** + * Represents the KEKIdentifier structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + */ +export class KEKIdentifier extends PkiObject implements IKEKIdentifier { + + public static override CLASS_NAME = "KEKIdentifier"; + + public keyIdentifier!: asn1js.OctetString; + public date?: asn1js.GeneralizedTime; + public other?: OtherKeyAttribute; + + /** + * Initializes a new instance of the {@link KEKIdentifier} class + * @param parameters Initialization parameters + */ + constructor(parameters: KEKIdentifierParameters = {}) { + super(); + + this.keyIdentifier = pvutils.getParametersValue(parameters, KEY_IDENTIFIER, KEKIdentifier.defaultValues(KEY_IDENTIFIER)); + if (DATE in parameters) { + this.date = pvutils.getParametersValue(parameters, DATE, KEKIdentifier.defaultValues(DATE)); + } + if (OTHER in parameters) { + this.other = pvutils.getParametersValue(parameters, OTHER, KEKIdentifier.defaultValues(OTHER)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof KEY_IDENTIFIER): asn1js.OctetString; + public static override defaultValues(memberName: typeof DATE): asn1js.GeneralizedTime; + public static override defaultValues(memberName: typeof OTHER): OtherKeyAttribute; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case KEY_IDENTIFIER: + return new asn1js.OctetString(); + case DATE: + return new asn1js.GeneralizedTime(); + case OTHER: + return new OtherKeyAttribute(); + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case KEY_IDENTIFIER: + return (memberValue.isEqual(KEKIdentifier.defaultValues(KEY_IDENTIFIER))); + case DATE: + return ((memberValue.year === 0) && + (memberValue.month === 0) && + (memberValue.day === 0) && + (memberValue.hour === 0) && + (memberValue.minute === 0) && + (memberValue.second === 0) && + (memberValue.millisecond === 0)); + case OTHER: + return ((memberValue.compareWithDefault("keyAttrId", memberValue.keyAttrId)) && + (("keyAttr" in memberValue) === false)); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * KEKIdentifier ::= SEQUENCE { + * keyIdentifier OCTET STRING, + * date GeneralizedTime OPTIONAL, + * other OtherKeyAttribute OPTIONAL } + *``` + */ + public static override schema(parameters: KEKIdentifierSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.OctetString({ name: (names.keyIdentifier || EMPTY_STRING) }), + new asn1js.GeneralizedTime({ + optional: true, + name: (names.date || EMPTY_STRING) + }), + OtherKeyAttribute.schema(names.other || {}) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + KEKIdentifier.schema({ + names: { + keyIdentifier: KEY_IDENTIFIER, + date: DATE, + other: { + names: { + blockName: OTHER + } + } + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.keyIdentifier = asn1.result.keyIdentifier; + if (DATE in asn1.result) + this.date = asn1.result.date; + if (OTHER in asn1.result) + this.other = new OtherKeyAttribute({ schema: asn1.result.other }); + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray = []; + + outputArray.push(this.keyIdentifier); + + if (this.date) { + outputArray.push(this.date); + } + if (this.other) { + outputArray.push(this.other.toSchema()); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): KEKIdentifierJson { + const res: KEKIdentifierJson = { + keyIdentifier: this.keyIdentifier.toJSON() + }; + + if (this.date) { + res.date = this.date; + } + + if (this.other) { + res.other = this.other.toJSON(); + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/KEKRecipientInfo.ts b/third_party/js/PKI.js/src/KEKRecipientInfo.ts new file mode 100644 index 0000000000..03591bb752 --- /dev/null +++ b/third_party/js/PKI.js/src/KEKRecipientInfo.ts @@ -0,0 +1,213 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { KEKIdentifier, KEKIdentifierJson, KEKIdentifierSchema } from "./KEKIdentifier"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier"; +import * as Schema from "./Schema"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { AsnError } from "./errors"; +import { EMPTY_BUFFER, EMPTY_STRING } from "./constants"; + +const VERSION = "version"; +const KEK_ID = "kekid"; +const KEY_ENCRYPTION_ALGORITHM = "keyEncryptionAlgorithm"; +const ENCRYPTED_KEY = "encryptedKey"; +const PER_DEFINED_KEK = "preDefinedKEK"; +const CLEAR_PROPS = [ + VERSION, + KEK_ID, + KEY_ENCRYPTION_ALGORITHM, + ENCRYPTED_KEY, +]; + +export interface IKEKRecipientInfo { + version: number; + kekid: KEKIdentifier; + keyEncryptionAlgorithm: AlgorithmIdentifier; + encryptedKey: asn1js.OctetString; + preDefinedKEK: ArrayBuffer; +} + +export interface KEKRecipientInfoJson { + version: number; + kekid: KEKIdentifierJson; + keyEncryptionAlgorithm: AlgorithmIdentifierJson; + encryptedKey: asn1js.OctetStringJson; +} + +export type KEKRecipientInfoParameters = PkiObjectParameters & Partial<IKEKRecipientInfo>; + +/** + * Represents the KEKRecipientInfo structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + */ +export class KEKRecipientInfo extends PkiObject implements IKEKRecipientInfo { + + public static override CLASS_NAME = "KEKRecipientInfo"; + + public version!: number; + public kekid!: KEKIdentifier; + public keyEncryptionAlgorithm!: AlgorithmIdentifier; + public encryptedKey!: asn1js.OctetString; + public preDefinedKEK!: ArrayBuffer; + + /** + * Initializes a new instance of the {@link KEKRecipientInfo} class + * @param parameters Initialization parameters + */ + constructor(parameters: KEKRecipientInfoParameters = {}) { + super(); + + this.version = pvutils.getParametersValue(parameters, VERSION, KEKRecipientInfo.defaultValues(VERSION)); + this.kekid = pvutils.getParametersValue(parameters, KEK_ID, KEKRecipientInfo.defaultValues(KEK_ID)); + this.keyEncryptionAlgorithm = pvutils.getParametersValue(parameters, KEY_ENCRYPTION_ALGORITHM, KEKRecipientInfo.defaultValues(KEY_ENCRYPTION_ALGORITHM)); + this.encryptedKey = pvutils.getParametersValue(parameters, ENCRYPTED_KEY, KEKRecipientInfo.defaultValues(ENCRYPTED_KEY)); + this.preDefinedKEK = pvutils.getParametersValue(parameters, PER_DEFINED_KEK, KEKRecipientInfo.defaultValues(PER_DEFINED_KEK)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof VERSION): number; + public static override defaultValues(memberName: typeof KEK_ID): KEKIdentifier; + public static override defaultValues(memberName: typeof KEY_ENCRYPTION_ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof ENCRYPTED_KEY): asn1js.OctetString; + public static override defaultValues(memberName: typeof PER_DEFINED_KEK): ArrayBuffer; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case VERSION: + return 0; + case KEK_ID: + return new KEKIdentifier(); + case KEY_ENCRYPTION_ALGORITHM: + return new AlgorithmIdentifier(); + case ENCRYPTED_KEY: + return new asn1js.OctetString(); + case PER_DEFINED_KEK: + return EMPTY_BUFFER; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case "KEKRecipientInfo": + return (memberValue === KEKRecipientInfo.defaultValues(VERSION)); + case KEK_ID: + return ((memberValue.compareWithDefault("keyIdentifier", memberValue.keyIdentifier)) && + (("date" in memberValue) === false) && + (("other" in memberValue) === false)); + case KEY_ENCRYPTION_ALGORITHM: + return ((memberValue.algorithmId === EMPTY_STRING) && (("algorithmParams" in memberValue) === false)); + case ENCRYPTED_KEY: + return (memberValue.isEqual(KEKRecipientInfo.defaultValues(ENCRYPTED_KEY))); + case PER_DEFINED_KEK: + return (memberValue.byteLength === 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * KEKRecipientInfo ::= SEQUENCE { + * version CMSVersion, -- always set to 4 + * kekid KEKIdentifier, + * keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier, + * encryptedKey EncryptedKey } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + version?: string; + kekid?: KEKIdentifierSchema; + keyEncryptionAlgorithm?: AlgorithmIdentifierSchema; + encryptedKey?: string; + }> = {}): Schema.SchemaType { + /** + * @type {Object} + * @property {string} [blockName] + * @property {string} [version] + * @property {string} [kekid] + * @property {string} [keyEncryptionAlgorithm] + * @property {string} [encryptedKey] + */ + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Integer({ name: (names.version || EMPTY_STRING) }), + KEKIdentifier.schema(names.kekid || {}), + AlgorithmIdentifier.schema(names.keyEncryptionAlgorithm || {}), + new asn1js.OctetString({ name: (names.encryptedKey || EMPTY_STRING) }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + KEKRecipientInfo.schema({ + names: { + version: VERSION, + kekid: { + names: { + blockName: KEK_ID + } + }, + keyEncryptionAlgorithm: { + names: { + blockName: KEY_ENCRYPTION_ALGORITHM + } + }, + encryptedKey: ENCRYPTED_KEY + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.version = asn1.result.version.valueBlock.valueDec; + this.kekid = new KEKIdentifier({ schema: asn1.result.kekid }); + this.keyEncryptionAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.keyEncryptionAlgorithm }); + this.encryptedKey = asn1.result.encryptedKey; + } + + public toSchema(): asn1js.Sequence { + // Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: [ + new asn1js.Integer({ value: this.version }), + this.kekid.toSchema(), + this.keyEncryptionAlgorithm.toSchema(), + this.encryptedKey + ] + })); + } + + public toJSON(): KEKRecipientInfoJson { + return { + version: this.version, + kekid: this.kekid.toJSON(), + keyEncryptionAlgorithm: this.keyEncryptionAlgorithm.toJSON(), + encryptedKey: this.encryptedKey.toJSON(), + }; + } + +} diff --git a/third_party/js/PKI.js/src/KeyAgreeRecipientIdentifier.ts b/third_party/js/PKI.js/src/KeyAgreeRecipientIdentifier.ts new file mode 100644 index 0000000000..374867179c --- /dev/null +++ b/third_party/js/PKI.js/src/KeyAgreeRecipientIdentifier.ts @@ -0,0 +1,188 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { IssuerAndSerialNumber, IssuerAndSerialNumberJson, IssuerAndSerialNumberSchema } from "./IssuerAndSerialNumber"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { RecipientKeyIdentifier, RecipientKeyIdentifierJson, RecipientKeyIdentifierSchema } from "./RecipientKeyIdentifier"; +import * as Schema from "./Schema"; + +const VARIANT = "variant"; +const VALUE = "value"; +const CLEAR_PROPS = [ + "blockName", +]; + +export interface IKeyAgreeRecipientIdentifier { + variant: number; + value: any; +} + +export interface KeyAgreeRecipientIdentifierJson { + variant: number; + value?: IssuerAndSerialNumberJson | RecipientKeyIdentifierJson; +} + +export type KeyAgreeRecipientIdentifierParameters = PkiObjectParameters & Partial<IKeyAgreeRecipientIdentifier>; + +export type KeyAgreeRecipientIdentifierSchema = Schema.SchemaParameters<{ + issuerAndSerialNumber?: IssuerAndSerialNumberSchema; + rKeyId?: RecipientKeyIdentifierSchema; +}>; + +/** + * Represents the KeyAgreeRecipientIdentifier structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + */ +export class KeyAgreeRecipientIdentifier extends PkiObject implements IKeyAgreeRecipientIdentifier { + + public static override CLASS_NAME = "KeyAgreeRecipientIdentifier"; + + public variant!: number; + public value: any; + + /** + * Initializes a new instance of the {@link KeyAgreeRecipientIdentifier} class + * @param parameters Initialization parameters + */ + constructor(parameters: KeyAgreeRecipientIdentifierParameters = {}) { + super(); + + this.variant = pvutils.getParametersValue(parameters, VARIANT, KeyAgreeRecipientIdentifier.defaultValues(VARIANT)); + this.value = pvutils.getParametersValue(parameters, VALUE, KeyAgreeRecipientIdentifier.defaultValues(VALUE)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof VARIANT): number; + public static override defaultValues(memberName: typeof VALUE): any; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case VARIANT: + return (-1); + case VALUE: + return {}; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case VARIANT: + return (memberValue === (-1)); + case VALUE: + return (Object.keys(memberValue).length === 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * KeyAgreeRecipientIdentifier ::= CHOICE { + * issuerAndSerialNumber IssuerAndSerialNumber, + * rKeyId [0] IMPLICIT RecipientKeyIdentifier } + *``` + */ + public static override schema(parameters: KeyAgreeRecipientIdentifierSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Choice({ + value: [ + IssuerAndSerialNumber.schema(names.issuerAndSerialNumber || { + names: { + blockName: (names.blockName || EMPTY_STRING) + } + }), + new asn1js.Constructed({ + name: (names.blockName || EMPTY_STRING), + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: RecipientKeyIdentifier.schema(names.rKeyId || { + names: { + blockName: (names.blockName || EMPTY_STRING) + } + }).valueBlock.value + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + KeyAgreeRecipientIdentifier.schema({ + names: { + blockName: "blockName" + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + if (asn1.result.blockName.idBlock.tagClass === 1) { + this.variant = 1; + this.value = new IssuerAndSerialNumber({ schema: asn1.result.blockName }); + } else { + this.variant = 2; + + this.value = new RecipientKeyIdentifier({ + schema: new asn1js.Sequence({ + value: asn1.result.blockName.valueBlock.value + }) + }); + } + } + + public toSchema(): asn1js.BaseBlock<any> { + //#region Construct and return new ASN.1 schema for this object + switch (this.variant) { + case 1: + return this.value.toSchema(); + case 2: + return new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: this.value.toSchema().valueBlock.value + }); + default: + return new asn1js.Any() as any; + } + //#endregion + } + + public toJSON(): KeyAgreeRecipientIdentifierJson { + const res: KeyAgreeRecipientIdentifierJson = { + variant: this.variant, + }; + + if ((this.variant === 1) || (this.variant === 2)) { + res.value = this.value.toJSON(); + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/KeyAgreeRecipientInfo.ts b/third_party/js/PKI.js/src/KeyAgreeRecipientInfo.ts new file mode 100644 index 0000000000..8368c554e0 --- /dev/null +++ b/third_party/js/PKI.js/src/KeyAgreeRecipientInfo.ts @@ -0,0 +1,285 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { OriginatorIdentifierOrKey, OriginatorIdentifierOrKeyJson, OriginatorIdentifierOrKeySchema } from "./OriginatorIdentifierOrKey"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier"; +import { RecipientEncryptedKeys, RecipientEncryptedKeysJson, RecipientEncryptedKeysSchema } from "./RecipientEncryptedKeys"; +import { Certificate } from "./Certificate"; +import * as Schema from "./Schema"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { AsnError } from "./errors"; +import { EMPTY_STRING } from "./constants"; + +const VERSION = "version"; +const ORIGINATOR = "originator"; +const UKM = "ukm"; +const KEY_ENCRYPTION_ALGORITHM = "keyEncryptionAlgorithm"; +const RECIPIENT_ENCRYPTED_KEY = "recipientEncryptedKeys"; +const RECIPIENT_CERTIFICATE = "recipientCertificate"; +const RECIPIENT_PUBLIC_KEY = "recipientPublicKey"; +const CLEAR_PROPS = [ + VERSION, + ORIGINATOR, + UKM, + KEY_ENCRYPTION_ALGORITHM, + RECIPIENT_ENCRYPTED_KEY, +]; + +export interface IKeyAgreeRecipientInfo { + version: number; + originator: OriginatorIdentifierOrKey; + ukm?: asn1js.OctetString; + keyEncryptionAlgorithm: AlgorithmIdentifier; + recipientEncryptedKeys: RecipientEncryptedKeys; + recipientCertificate: Certificate; + recipientPublicKey: CryptoKey | null; +} + +export interface KeyAgreeRecipientInfoJson { + version: number; + originator: OriginatorIdentifierOrKeyJson; + ukm?: asn1js.OctetStringJson; + keyEncryptionAlgorithm: AlgorithmIdentifierJson; + recipientEncryptedKeys: RecipientEncryptedKeysJson; +} + +export type KeyAgreeRecipientInfoParameters = PkiObjectParameters & Partial<IKeyAgreeRecipientInfo>; + +/** + * Represents the KeyAgreeRecipientInfo structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + */ +export class KeyAgreeRecipientInfo extends PkiObject implements IKeyAgreeRecipientInfo { + + public static override CLASS_NAME = "KeyAgreeRecipientInfo"; + + public version!: number; + public originator!: OriginatorIdentifierOrKey; + public ukm?: asn1js.OctetString; + public keyEncryptionAlgorithm!: AlgorithmIdentifier; + public recipientEncryptedKeys!: RecipientEncryptedKeys; + public recipientCertificate!: Certificate; + public recipientPublicKey!: CryptoKey | null; + + /** + * Initializes a new instance of the {@link KeyAgreeRecipientInfo} class + * @param parameters Initialization parameters + */ + constructor(parameters: KeyAgreeRecipientInfoParameters = {}) { + super(); + + this.version = pvutils.getParametersValue(parameters, VERSION, KeyAgreeRecipientInfo.defaultValues(VERSION)); + this.originator = pvutils.getParametersValue(parameters, ORIGINATOR, KeyAgreeRecipientInfo.defaultValues(ORIGINATOR)); + if (UKM in parameters) { + this.ukm = pvutils.getParametersValue(parameters, UKM, KeyAgreeRecipientInfo.defaultValues(UKM)); + } + this.keyEncryptionAlgorithm = pvutils.getParametersValue(parameters, KEY_ENCRYPTION_ALGORITHM, KeyAgreeRecipientInfo.defaultValues(KEY_ENCRYPTION_ALGORITHM)); + this.recipientEncryptedKeys = pvutils.getParametersValue(parameters, RECIPIENT_ENCRYPTED_KEY, KeyAgreeRecipientInfo.defaultValues(RECIPIENT_ENCRYPTED_KEY)); + this.recipientCertificate = pvutils.getParametersValue(parameters, RECIPIENT_CERTIFICATE, KeyAgreeRecipientInfo.defaultValues(RECIPIENT_CERTIFICATE)); + this.recipientPublicKey = pvutils.getParametersValue(parameters, RECIPIENT_PUBLIC_KEY, KeyAgreeRecipientInfo.defaultValues(RECIPIENT_PUBLIC_KEY)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof VERSION): number; + public static override defaultValues(memberName: typeof ORIGINATOR): OriginatorIdentifierOrKey; + public static override defaultValues(memberName: typeof UKM): asn1js.OctetString; + public static override defaultValues(memberName: typeof KEY_ENCRYPTION_ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof RECIPIENT_ENCRYPTED_KEY): RecipientEncryptedKeys; + public static override defaultValues(memberName: typeof RECIPIENT_CERTIFICATE): Certificate; + public static override defaultValues(memberName: typeof RECIPIENT_PUBLIC_KEY): null; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case VERSION: + return 0; + case ORIGINATOR: + return new OriginatorIdentifierOrKey(); + case UKM: + return new asn1js.OctetString(); + case KEY_ENCRYPTION_ALGORITHM: + return new AlgorithmIdentifier(); + case RECIPIENT_ENCRYPTED_KEY: + return new RecipientEncryptedKeys(); + case RECIPIENT_CERTIFICATE: + return new Certificate(); + case RECIPIENT_PUBLIC_KEY: + return null; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case VERSION: + return (memberValue === 0); + case ORIGINATOR: + return ((memberValue.variant === (-1)) && (("value" in memberValue) === false)); + case UKM: + return (memberValue.isEqual(KeyAgreeRecipientInfo.defaultValues(UKM))); + case KEY_ENCRYPTION_ALGORITHM: + return ((memberValue.algorithmId === EMPTY_STRING) && (("algorithmParams" in memberValue) === false)); + case RECIPIENT_ENCRYPTED_KEY: + return (memberValue.encryptedKeys.length === 0); + case RECIPIENT_CERTIFICATE: + return false; // For now leave it as is + case RECIPIENT_PUBLIC_KEY: + return false; + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * KeyAgreeRecipientInfo ::= SEQUENCE { + * version CMSVersion, -- always set to 3 + * originator [0] EXPLICIT OriginatorIdentifierOrKey, + * ukm [1] EXPLICIT UserKeyingMaterial OPTIONAL, + * keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier, + * recipientEncryptedKeys RecipientEncryptedKeys } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + version?: string; + originator?: OriginatorIdentifierOrKeySchema; + ukm?: string; + keyEncryptionAlgorithm?: AlgorithmIdentifierSchema; + recipientEncryptedKeys?: RecipientEncryptedKeysSchema; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: names.blockName || EMPTY_STRING, + value: [ + new asn1js.Integer({ name: names.version || EMPTY_STRING }), + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + OriginatorIdentifierOrKey.schema(names.originator || {}) + ] + }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [new asn1js.OctetString({ name: names.ukm || EMPTY_STRING })] + }), + AlgorithmIdentifier.schema(names.keyEncryptionAlgorithm || {}), + RecipientEncryptedKeys.schema(names.recipientEncryptedKeys || {}) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + KeyAgreeRecipientInfo.schema({ + names: { + version: VERSION, + originator: { + names: { + blockName: ORIGINATOR + } + }, + ukm: UKM, + keyEncryptionAlgorithm: { + names: { + blockName: KEY_ENCRYPTION_ALGORITHM + } + }, + recipientEncryptedKeys: { + names: { + blockName: RECIPIENT_ENCRYPTED_KEY + } + } + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.version = asn1.result.version.valueBlock.valueDec; + this.originator = new OriginatorIdentifierOrKey({ schema: asn1.result.originator }); + if (UKM in asn1.result) + this.ukm = asn1.result.ukm; + this.keyEncryptionAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.keyEncryptionAlgorithm }); + this.recipientEncryptedKeys = new RecipientEncryptedKeys({ schema: asn1.result.recipientEncryptedKeys }); + } + + public toSchema(): asn1js.Sequence { + //#region Create array for final sequence + const outputArray = []; + + outputArray.push(new asn1js.Integer({ value: this.version })); + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [this.originator.toSchema()] + })); + + if (this.ukm) { + outputArray.push(new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [this.ukm] + })); + } + + outputArray.push(this.keyEncryptionAlgorithm.toSchema()); + outputArray.push(this.recipientEncryptedKeys.toSchema()); + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + /** + * Conversion for the class to JSON object + * @returns + */ + public toJSON(): KeyAgreeRecipientInfoJson { + const res: KeyAgreeRecipientInfoJson = { + version: this.version, + originator: this.originator.toJSON(), + keyEncryptionAlgorithm: this.keyEncryptionAlgorithm.toJSON(), + recipientEncryptedKeys: this.recipientEncryptedKeys.toJSON(), + }; + + if (this.ukm) { + res.ukm = this.ukm.toJSON(); + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/KeyBag.ts b/third_party/js/PKI.js/src/KeyBag.ts new file mode 100644 index 0000000000..462e26486c --- /dev/null +++ b/third_party/js/PKI.js/src/KeyBag.ts @@ -0,0 +1,18 @@ +import { PrivateKeyInfo } from "./PrivateKeyInfo"; + +/** + * Class from RFC5208 + */ +// TODO looks odd +export class KeyBag extends PrivateKeyInfo { + + /** + * Constructor for Attribute class + * @param parameters + */ + constructor(parameters = {}) { + super(parameters); + } + +} + diff --git a/third_party/js/PKI.js/src/KeyTransRecipientInfo.ts b/third_party/js/PKI.js/src/KeyTransRecipientInfo.ts new file mode 100644 index 0000000000..8caee3b74d --- /dev/null +++ b/third_party/js/PKI.js/src/KeyTransRecipientInfo.ts @@ -0,0 +1,233 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier"; +import { Certificate } from "./Certificate"; +import { RecipientIdentifier, RecipientIdentifierSchema } from "./RecipientIdentifier"; +import { IssuerAndSerialNumber, IssuerAndSerialNumberJson } from "./IssuerAndSerialNumber"; +import * as Schema from "./Schema"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { AsnError } from "./errors"; +import { EMPTY_STRING } from "./constants"; + +const VERSION = "version"; +const RID = "rid"; +const KEY_ENCRYPTION_ALGORITHM = "keyEncryptionAlgorithm"; +const ENCRYPTED_KEY = "encryptedKey"; +const RECIPIENT_CERTIFICATE = "recipientCertificate"; +const CLEAR_PROPS = [ + VERSION, + RID, + KEY_ENCRYPTION_ALGORITHM, + ENCRYPTED_KEY, +]; + +export interface IKeyTransRecipientInfo { + version: number; + rid: RecipientIdentifierType; + keyEncryptionAlgorithm: AlgorithmIdentifier; + encryptedKey: asn1js.OctetString; + recipientCertificate: Certificate; +} + +export interface KeyTransRecipientInfoJson { + version: number; + rid: RecipientIdentifierMixedJson; + keyEncryptionAlgorithm: AlgorithmIdentifierJson; + encryptedKey: asn1js.OctetStringJson; +} + +export type RecipientIdentifierType = IssuerAndSerialNumber | asn1js.OctetString; +export type RecipientIdentifierMixedJson = IssuerAndSerialNumberJson | asn1js.OctetStringJson; + +export type KeyTransRecipientInfoParameters = PkiObjectParameters & Partial<IKeyTransRecipientInfo>; + +/** + * Represents the KeyTransRecipientInfo structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + */ +export class KeyTransRecipientInfo extends PkiObject implements IKeyTransRecipientInfo { + + public static override CLASS_NAME = "KeyTransRecipientInfo"; + + public version!: number; + public rid!: RecipientIdentifierType; + public keyEncryptionAlgorithm!: AlgorithmIdentifier; + public encryptedKey!: asn1js.OctetString; + public recipientCertificate!: Certificate; + + /** + * Initializes a new instance of the {@link KeyTransRecipientInfo} class + * @param parameters Initialization parameters + */ + constructor(parameters: KeyTransRecipientInfoParameters = {}) { + super(); + + this.version = pvutils.getParametersValue(parameters, VERSION, KeyTransRecipientInfo.defaultValues(VERSION)); + this.rid = pvutils.getParametersValue(parameters, RID, KeyTransRecipientInfo.defaultValues(RID)); + this.keyEncryptionAlgorithm = pvutils.getParametersValue(parameters, KEY_ENCRYPTION_ALGORITHM, KeyTransRecipientInfo.defaultValues(KEY_ENCRYPTION_ALGORITHM)); + this.encryptedKey = pvutils.getParametersValue(parameters, ENCRYPTED_KEY, KeyTransRecipientInfo.defaultValues(ENCRYPTED_KEY)); + this.recipientCertificate = pvutils.getParametersValue(parameters, RECIPIENT_CERTIFICATE, KeyTransRecipientInfo.defaultValues(RECIPIENT_CERTIFICATE)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof VERSION): number; + public static override defaultValues(memberName: typeof RID): RecipientIdentifierType; + public static override defaultValues(memberName: typeof KEY_ENCRYPTION_ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof ENCRYPTED_KEY): asn1js.OctetString; + public static override defaultValues(memberName: typeof RECIPIENT_CERTIFICATE): Certificate; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case VERSION: + return (-1); + case RID: + return {}; + case KEY_ENCRYPTION_ALGORITHM: + return new AlgorithmIdentifier(); + case ENCRYPTED_KEY: + return new asn1js.OctetString(); + case RECIPIENT_CERTIFICATE: + return new Certificate(); + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case VERSION: + return (memberValue === KeyTransRecipientInfo.defaultValues(VERSION)); + case RID: + return (Object.keys(memberValue).length === 0); + case KEY_ENCRYPTION_ALGORITHM: + case ENCRYPTED_KEY: + return memberValue.isEqual(KeyTransRecipientInfo.defaultValues(memberName as typeof ENCRYPTED_KEY)); + case RECIPIENT_CERTIFICATE: + return false; // For now we do not need to compare any values with the RECIPIENT_CERTIFICATE + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * KeyTransRecipientInfo ::= SEQUENCE { + * version CMSVersion, -- always set to 0 or 2 + * rid RecipientIdentifier, + * keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier, + * encryptedKey EncryptedKey } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + version?: string; + rid?: RecipientIdentifierSchema; + keyEncryptionAlgorithm?: AlgorithmIdentifierSchema; + encryptedKey?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Integer({ name: (names.version || EMPTY_STRING) }), + RecipientIdentifier.schema(names.rid || {}), + AlgorithmIdentifier.schema(names.keyEncryptionAlgorithm || {}), + new asn1js.OctetString({ name: (names.encryptedKey || EMPTY_STRING) }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + KeyTransRecipientInfo.schema({ + names: { + version: VERSION, + rid: { + names: { + blockName: RID + } + }, + keyEncryptionAlgorithm: { + names: { + blockName: KEY_ENCRYPTION_ALGORITHM + } + }, + encryptedKey: ENCRYPTED_KEY + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.version = asn1.result.version.valueBlock.valueDec; + if (asn1.result.rid.idBlock.tagClass === 3) { + this.rid = new asn1js.OctetString({ valueHex: asn1.result.rid.valueBlock.valueHex }); // SubjectKeyIdentifier + } else { + this.rid = new IssuerAndSerialNumber({ schema: asn1.result.rid }); + } + this.keyEncryptionAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.keyEncryptionAlgorithm }); + this.encryptedKey = asn1.result.encryptedKey; + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray = []; + + if (this.rid instanceof IssuerAndSerialNumber) { + this.version = 0; + + outputArray.push(new asn1js.Integer({ value: this.version })); + outputArray.push(this.rid.toSchema()); + } + else { + this.version = 2; + + outputArray.push(new asn1js.Integer({ value: this.version })); + outputArray.push(new asn1js.Primitive({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + valueHex: this.rid.valueBlock.valueHexView + })); + } + + outputArray.push(this.keyEncryptionAlgorithm.toSchema()); + outputArray.push(this.encryptedKey); + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): KeyTransRecipientInfoJson { + return { + version: this.version, + rid: this.rid.toJSON(), + keyEncryptionAlgorithm: this.keyEncryptionAlgorithm.toJSON(), + encryptedKey: this.encryptedKey.toJSON(), + }; + } + +} diff --git a/third_party/js/PKI.js/src/MacData.ts b/third_party/js/PKI.js/src/MacData.ts new file mode 100644 index 0000000000..ec9ccc747d --- /dev/null +++ b/third_party/js/PKI.js/src/MacData.ts @@ -0,0 +1,199 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { DigestInfo, DigestInfoJson, DigestInfoSchema } from "./DigestInfo"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const MAC = "mac"; +const MAC_SALT = "macSalt"; +const ITERATIONS = "iterations"; +const CLEAR_PROPS = [ + MAC, + MAC_SALT, + ITERATIONS +]; + +export interface IMacData { + mac: DigestInfo; + macSalt: asn1js.OctetString; + iterations?: number; +} + +export interface MacDataJson { + mac: DigestInfoJson; + macSalt: asn1js.OctetStringJson; + iterations?: number; +} + +export type MacDataParameters = PkiObjectParameters & Partial<IMacData>; + +export type MacDataSchema = Schema.SchemaParameters<{ + mac?: DigestInfoSchema; + macSalt?: string; + iterations?: string; +}>; + +/** + * Represents the MacData structure described in [RFC7292](https://datatracker.ietf.org/doc/html/rfc7292) + */ +export class MacData extends PkiObject implements IMacData { + + public static override CLASS_NAME = "MacData"; + + public mac!: DigestInfo; + public macSalt!: asn1js.OctetString; + public iterations?: number; + + /** + * Initializes a new instance of the {@link MacData} class + * @param parameters Initialization parameters + */ + constructor(parameters: MacDataParameters = {}) { + super(); + + this.mac = pvutils.getParametersValue(parameters, MAC, MacData.defaultValues(MAC)); + this.macSalt = pvutils.getParametersValue(parameters, MAC_SALT, MacData.defaultValues(MAC_SALT)); + if (ITERATIONS in parameters) { + this.iterations = pvutils.getParametersValue(parameters, ITERATIONS, MacData.defaultValues(ITERATIONS)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof MAC): DigestInfo; + public static override defaultValues(memberName: typeof MAC_SALT): asn1js.OctetString; + public static override defaultValues(memberName: typeof ITERATIONS): number; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case MAC: + return new DigestInfo(); + case MAC_SALT: + return new asn1js.OctetString(); + case ITERATIONS: + return 1; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case MAC: + return ((DigestInfo.compareWithDefault("digestAlgorithm", memberValue.digestAlgorithm)) && + (DigestInfo.compareWithDefault("digest", memberValue.digest))); + case MAC_SALT: + return (memberValue.isEqual(MacData.defaultValues(memberName))); + case ITERATIONS: + return (memberValue === MacData.defaultValues(memberName)); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * MacData ::= SEQUENCE { + * mac DigestInfo, + * macSalt OCTET STRING, + * iterations INTEGER DEFAULT 1 + * -- Note: The default is for historical reasons and its use is + * -- deprecated. A higher value, like 1024 is recommended. + * } + *``` + */ + public static override schema(parameters: MacDataSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + optional: (names.optional || true), + value: [ + DigestInfo.schema(names.mac || { + names: { + blockName: MAC + } + }), + new asn1js.OctetString({ name: (names.macSalt || MAC_SALT) }), + new asn1js.Integer({ + optional: true, + name: (names.iterations || ITERATIONS) + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + MacData.schema({ + names: { + mac: { + names: { + blockName: MAC + } + }, + macSalt: MAC_SALT, + iterations: ITERATIONS + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.mac = new DigestInfo({ schema: asn1.result.mac }); + this.macSalt = asn1.result.macSalt; + if (ITERATIONS in asn1.result) + this.iterations = asn1.result.iterations.valueBlock.valueDec; + } + + public toSchema(): asn1js.Sequence { + //#region Construct and return new ASN.1 schema for this object + const outputArray: any[] = [ + this.mac.toSchema(), + this.macSalt + ]; + + if (this.iterations !== undefined) { + outputArray.push(new asn1js.Integer({ value: this.iterations })); + } + + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): MacDataJson { + const res: MacDataJson = { + mac: this.mac.toJSON(), + macSalt: this.macSalt.toJSON(), + }; + + if (this.iterations !== undefined) { + res.iterations = this.iterations; + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/MessageImprint.ts b/third_party/js/PKI.js/src/MessageImprint.ts new file mode 100644 index 0000000000..0684a9782c --- /dev/null +++ b/third_party/js/PKI.js/src/MessageImprint.ts @@ -0,0 +1,181 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; +import * as common from "./common"; +import { EMPTY_STRING } from "./constants"; + +export const HASH_ALGORITHM = "hashAlgorithm"; +export const HASHED_MESSAGE = "hashedMessage"; +const CLEAR_PROPS = [ + HASH_ALGORITHM, + HASHED_MESSAGE, +]; + +export interface IMessageImprint { + hashAlgorithm: AlgorithmIdentifier; + hashedMessage: asn1js.OctetString; +} + +export interface MessageImprintJson { + hashAlgorithm: AlgorithmIdentifierJson; + hashedMessage: asn1js.OctetStringJson; +} + +export type MessageImprintParameters = PkiObjectParameters & Partial<IMessageImprint>; + +export type MessageImprintSchema = Schema.SchemaParameters<{ + hashAlgorithm?: AlgorithmIdentifierSchema; + hashedMessage?: string; +}>; + +/** + * Represents the MessageImprint structure described in [RFC3161](https://www.ietf.org/rfc/rfc3161.txt) + */ +export class MessageImprint extends PkiObject implements IMessageImprint { + + public static override CLASS_NAME = "MessageImprint"; + + /** + * Creates and fills a new instance of {@link MessageImprint} + * @param hashAlgorithm + * @param message + * @param crypto Crypto engine + * @returns New instance of {@link MessageImprint} + */ + public static async create(hashAlgorithm: string, message: BufferSource, crypto = common.getCrypto(true)): Promise<MessageImprint> { + const hashAlgorithmOID = crypto.getOIDByAlgorithm({ name: hashAlgorithm }, true, "hashAlgorithm"); + + const hashedMessage = await crypto.digest(hashAlgorithm, message); + + const res = new MessageImprint({ + hashAlgorithm: new AlgorithmIdentifier({ + algorithmId: hashAlgorithmOID, + algorithmParams: new asn1js.Null(), + }), + hashedMessage: new asn1js.OctetString({ valueHex: hashedMessage }) + }); + + return res; + } + + public hashAlgorithm!: AlgorithmIdentifier; + public hashedMessage!: asn1js.OctetString; + + /** + * Initializes a new instance of the {@link MessageImprint} class + * @param parameters Initialization parameters + */ + constructor(parameters: MessageImprintParameters = {}) { + super(); + + this.hashAlgorithm = pvutils.getParametersValue(parameters, HASH_ALGORITHM, MessageImprint.defaultValues(HASH_ALGORITHM)); + this.hashedMessage = pvutils.getParametersValue(parameters, HASHED_MESSAGE, MessageImprint.defaultValues(HASHED_MESSAGE)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof HASH_ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof HASHED_MESSAGE): asn1js.OctetString; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case HASH_ALGORITHM: + return new AlgorithmIdentifier(); + case HASHED_MESSAGE: + return new asn1js.OctetString(); + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case HASH_ALGORITHM: + return ((memberValue.algorithmId === EMPTY_STRING) && (("algorithmParams" in memberValue) === false)); + case HASHED_MESSAGE: + return (memberValue.isEqual(MessageImprint.defaultValues(memberName)) === 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * MessageImprint ::= SEQUENCE { + * hashAlgorithm AlgorithmIdentifier, + * hashedMessage OCTET STRING } + *``` + */ + public static override schema(parameters: MessageImprintSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + AlgorithmIdentifier.schema(names.hashAlgorithm || {}), + new asn1js.OctetString({ name: (names.hashedMessage || EMPTY_STRING) }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + MessageImprint.schema({ + names: { + hashAlgorithm: { + names: { + blockName: HASH_ALGORITHM + } + }, + hashedMessage: HASHED_MESSAGE + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.hashAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.hashAlgorithm }); + this.hashedMessage = asn1.result.hashedMessage; + } + + public toSchema(): asn1js.Sequence { + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: [ + this.hashAlgorithm.toSchema(), + this.hashedMessage + ] + })); + //#endregion + } + + public toJSON(): MessageImprintJson { + return { + hashAlgorithm: this.hashAlgorithm.toJSON(), + hashedMessage: this.hashedMessage.toJSON(), + }; + } + +} + diff --git a/third_party/js/PKI.js/src/NameConstraints.ts b/third_party/js/PKI.js/src/NameConstraints.ts new file mode 100644 index 0000000000..48f86b11a5 --- /dev/null +++ b/third_party/js/PKI.js/src/NameConstraints.ts @@ -0,0 +1,191 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { GeneralSubtree, GeneralSubtreeJson } from "./GeneralSubtree"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const PERMITTED_SUBTREES = "permittedSubtrees"; +const EXCLUDED_SUBTREES = "excludedSubtrees"; +const CLEAR_PROPS = [ + PERMITTED_SUBTREES, + EXCLUDED_SUBTREES +]; + +export interface INameConstraints { + permittedSubtrees?: GeneralSubtree[]; + excludedSubtrees?: GeneralSubtree[]; +} + +export interface NameConstraintsJson { + permittedSubtrees?: GeneralSubtreeJson[]; + excludedSubtrees?: GeneralSubtreeJson[]; +} + +export type NameConstraintsParameters = PkiObjectParameters & Partial<INameConstraints>; + +/** + * Represents the NameConstraints structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class NameConstraints extends PkiObject implements INameConstraints { + + public static override CLASS_NAME = "NameConstraints"; + + public permittedSubtrees?: GeneralSubtree[]; + public excludedSubtrees?: GeneralSubtree[]; + + /** + * Initializes a new instance of the {@link NameConstraints} class + * @param parameters Initialization parameters + */ + constructor(parameters: NameConstraintsParameters = {}) { + super(); + + if (PERMITTED_SUBTREES in parameters) { + this.permittedSubtrees = pvutils.getParametersValue(parameters, PERMITTED_SUBTREES, NameConstraints.defaultValues(PERMITTED_SUBTREES)); + } + if (EXCLUDED_SUBTREES in parameters) { + this.excludedSubtrees = pvutils.getParametersValue(parameters, EXCLUDED_SUBTREES, NameConstraints.defaultValues(EXCLUDED_SUBTREES)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof PERMITTED_SUBTREES): GeneralSubtree[]; + public static override defaultValues(memberName: typeof EXCLUDED_SUBTREES): GeneralSubtree[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case PERMITTED_SUBTREES: + case EXCLUDED_SUBTREES: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * NameConstraints ::= SEQUENCE { + * permittedSubtrees [0] GeneralSubtrees OPTIONAL, + * excludedSubtrees [1] GeneralSubtrees OPTIONAL } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + permittedSubtrees?: string; + excludedSubtrees?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + new asn1js.Repeated({ + name: (names.permittedSubtrees || EMPTY_STRING), + value: GeneralSubtree.schema() + }) + ] + }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [ + new asn1js.Repeated({ + name: (names.excludedSubtrees || EMPTY_STRING), + value: GeneralSubtree.schema() + }) + ] + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + NameConstraints.schema({ + names: { + permittedSubtrees: PERMITTED_SUBTREES, + excludedSubtrees: EXCLUDED_SUBTREES + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + if (PERMITTED_SUBTREES in asn1.result) + this.permittedSubtrees = Array.from(asn1.result.permittedSubtrees, element => new GeneralSubtree({ schema: element })); + if (EXCLUDED_SUBTREES in asn1.result) + this.excludedSubtrees = Array.from(asn1.result.excludedSubtrees, element => new GeneralSubtree({ schema: element })); + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray = []; + + if (this.permittedSubtrees) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: Array.from(this.permittedSubtrees, o => o.toSchema()) + })); + } + + if (this.excludedSubtrees) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: Array.from(this.excludedSubtrees, o => o.toSchema()) + })); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): NameConstraintsJson { + const object: NameConstraintsJson = {}; + + if (this.permittedSubtrees) { + object.permittedSubtrees = Array.from(this.permittedSubtrees, o => o.toJSON()); + } + + if (this.excludedSubtrees) { + object.excludedSubtrees = Array.from(this.excludedSubtrees, o => o.toJSON()); + } + + return object; + } + +} diff --git a/third_party/js/PKI.js/src/OCSPRequest.ts b/third_party/js/PKI.js/src/OCSPRequest.ts new file mode 100644 index 0000000000..e5da994909 --- /dev/null +++ b/third_party/js/PKI.js/src/OCSPRequest.ts @@ -0,0 +1,279 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import * as common from "./common"; +import { TBSRequest, TBSRequestJson, TBSRequestSchema } from "./TBSRequest"; +import { Signature, SignatureJson, SignatureSchema } from "./Signature"; +import { Request } from "./Request"; +import { CertID, CertIDCreateParams } from "./CertID"; +import * as Schema from "./Schema"; +import { Certificate } from "./Certificate"; +import { AsnError, ParameterError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; + +const TBS_REQUEST = "tbsRequest"; +const OPTIONAL_SIGNATURE = "optionalSignature"; +const CLEAR_PROPS = [ + TBS_REQUEST, + OPTIONAL_SIGNATURE +]; + +export interface IOCSPRequest { + tbsRequest: TBSRequest; + optionalSignature?: Signature; +} + +export interface OCSPRequestJson { + tbsRequest: TBSRequestJson; + optionalSignature?: SignatureJson; +} + +export type OCSPRequestParameters = PkiObjectParameters & Partial<IOCSPRequest>; + +/** + * Represents an OCSP request described in [RFC6960 Section 4.1](https://datatracker.ietf.org/doc/html/rfc6960#section-4.1) + * + * @example The following example demonstrates how to create OCSP request + * ```js + * // Create OCSP request + * const ocspReq = new pkijs.OCSPRequest(); + * + * ocspReq.tbsRequest.requestorName = new pkijs.GeneralName({ + * type: 4, + * value: cert.subject, + * }); + * + * await ocspReq.createForCertificate(cert, { + * hashAlgorithm: "SHA-256", + * issuerCertificate: issuerCert, + * }); + * + * const nonce = pkijs.getRandomValues(new Uint8Array(10)); + * ocspReq.tbsRequest.requestExtensions = [ + * new pkijs.Extension({ + * extnID: "1.3.6.1.5.5.7.48.1.2", // nonce + * extnValue: new asn1js.OctetString({ valueHex: nonce.buffer }).toBER(), + * }) + * ]; + * + * // Encode OCSP request + * const ocspReqRaw = ocspReq.toSchema(true).toBER(); + * ``` + */ +export class OCSPRequest extends PkiObject implements IOCSPRequest { + + public static override CLASS_NAME = "OCSPRequest"; + + public tbsRequest!: TBSRequest; + public optionalSignature?: Signature; + + /** + * Initializes a new instance of the {@link OCSPRequest} class + * @param parameters Initialization parameters + */ + constructor(parameters: OCSPRequestParameters = {}) { + super(); + + this.tbsRequest = pvutils.getParametersValue(parameters, TBS_REQUEST, OCSPRequest.defaultValues(TBS_REQUEST)); + if (OPTIONAL_SIGNATURE in parameters) { + this.optionalSignature = pvutils.getParametersValue(parameters, OPTIONAL_SIGNATURE, OCSPRequest.defaultValues(OPTIONAL_SIGNATURE)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof TBS_REQUEST): TBSRequest; + public static override defaultValues(memberName: typeof OPTIONAL_SIGNATURE): Signature; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case TBS_REQUEST: + return new TBSRequest(); + case OPTIONAL_SIGNATURE: + return new Signature(); + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + * @returns Returns `true` if `memberValue` is equal to default value for selected class member + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case TBS_REQUEST: + return ((TBSRequest.compareWithDefault("tbs", memberValue.tbs)) && + (TBSRequest.compareWithDefault("version", memberValue.version)) && + (TBSRequest.compareWithDefault("requestorName", memberValue.requestorName)) && + (TBSRequest.compareWithDefault("requestList", memberValue.requestList)) && + (TBSRequest.compareWithDefault("requestExtensions", memberValue.requestExtensions))); + case OPTIONAL_SIGNATURE: + return ((Signature.compareWithDefault("signatureAlgorithm", memberValue.signatureAlgorithm)) && + (Signature.compareWithDefault("signature", memberValue.signature)) && + (Signature.compareWithDefault("certs", memberValue.certs))); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * OCSPRequest ::= SEQUENCE { + * tbsRequest TBSRequest, + * optionalSignature [0] EXPLICIT Signature OPTIONAL } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + tbsRequest?: TBSRequestSchema; + optionalSignature?: SignatureSchema; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: names.blockName || "OCSPRequest", + value: [ + TBSRequest.schema(names.tbsRequest || { + names: { + blockName: TBS_REQUEST + } + }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + Signature.schema(names.optionalSignature || { + names: { + blockName: OPTIONAL_SIGNATURE + } + }) + ] + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + OCSPRequest.schema() + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.tbsRequest = new TBSRequest({ schema: asn1.result.tbsRequest }); + if (OPTIONAL_SIGNATURE in asn1.result) + this.optionalSignature = new Signature({ schema: asn1.result.optionalSignature }); + } + + public toSchema(encodeFlag = false) { + //#region Create array for output sequence + const outputArray = []; + + outputArray.push(this.tbsRequest.toSchema(encodeFlag)); + if (this.optionalSignature) + outputArray.push( + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + this.optionalSignature.toSchema() + ] + })); + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): OCSPRequestJson { + const res: OCSPRequestJson = { + tbsRequest: this.tbsRequest.toJSON() + }; + + if (this.optionalSignature) { + res.optionalSignature = this.optionalSignature.toJSON(); + } + + return res; + } + + /** + * Making OCSP Request for specific certificate + * @param certificate Certificate making OCSP Request for + * @param parameters Additional parameters + * @param crypto Crypto engine + */ + public async createForCertificate(certificate: Certificate, parameters: CertIDCreateParams, crypto = common.getCrypto(true)): Promise<void> { + //#region Initial variables + const certID = new CertID(); + //#endregion + + //#region Create OCSP certificate identifier for the certificate + await certID.createForCertificate(certificate, parameters, crypto); + //#endregion + + //#region Make final request data + this.tbsRequest.requestList.push(new Request({ + reqCert: certID, + })); + //#endregion + } + + /** + * Make signature for current OCSP Request + * @param privateKey Private key for "subjectPublicKeyInfo" structure + * @param hashAlgorithm Hashing algorithm. Default SHA-1 + * @param crypto Crypto engine + */ + public async sign(privateKey: CryptoKey, hashAlgorithm = "SHA-1", crypto = common.getCrypto(true)) { + // Initial checking + ParameterError.assertEmpty(privateKey, "privateKey", "OCSPRequest.sign method"); + + // Check that OPTIONAL_SIGNATURE exists in the current request + if (!this.optionalSignature) { + throw new Error("Need to create \"optionalSignature\" field before signing"); + } + + //#region Get a "default parameters" for current algorithm and set correct signature algorithm + const signatureParams = await crypto.getSignatureParameters(privateKey, hashAlgorithm); + const parameters = signatureParams.parameters; + this.optionalSignature.signatureAlgorithm = signatureParams.signatureAlgorithm; + //#endregion + + //#region Create TBS data for signing + const tbs = this.tbsRequest.toSchema(true).toBER(false); + //#endregion + + // Signing TBS data on provided private key + const signature = await crypto.signWithPrivateKey(tbs, privateKey, parameters as any); + this.optionalSignature.signature = new asn1js.BitString({ valueHex: signature }); + } + + verify() { + // TODO: Create the function + } + +} diff --git a/third_party/js/PKI.js/src/OCSPResponse.ts b/third_party/js/PKI.js/src/OCSPResponse.ts new file mode 100644 index 0000000000..8bb263d2e9 --- /dev/null +++ b/third_party/js/PKI.js/src/OCSPResponse.ts @@ -0,0 +1,335 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { ResponseBytes, ResponseBytesJson, ResponseBytesSchema } from "./ResponseBytes"; +import { BasicOCSPResponse } from "./BasicOCSPResponse"; +import * as Schema from "./Schema"; +import { Certificate } from "./Certificate"; +import { id_PKIX_OCSP_Basic } from "./ObjectIdentifiers"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as common from "./common"; + +const RESPONSE_STATUS = "responseStatus"; +const RESPONSE_BYTES = "responseBytes"; + +export interface IOCSPResponse { + responseStatus: asn1js.Enumerated; + responseBytes?: ResponseBytes; +} + +export interface OCSPResponseJson { + responseStatus: asn1js.EnumeratedJson; + responseBytes?: ResponseBytesJson; +} + +export type OCSPResponseParameters = PkiObjectParameters & Partial<IOCSPResponse>; + +/** + * Represents an OCSP response described in [RFC6960 Section 4.2](https://datatracker.ietf.org/doc/html/rfc6960#section-4.2) + * + * @example The following example demonstrates how to verify OCSP response + * ```js + * const asnOcspResp = asn1js.fromBER(ocspRespRaw); + * const ocspResp = new pkijs.OCSPResponse({ schema: asnOcspResp.result }); + * + * if (!ocspResp.responseBytes) { + * throw new Error("No \"ResponseBytes\" in the OCSP Response - nothing to verify"); + * } + * + * const asnOcspRespBasic = asn1js.fromBER(ocspResp.responseBytes.response.valueBlock.valueHex); + * const ocspBasicResp = new pkijs.BasicOCSPResponse({ schema: asnOcspRespBasic.result }); + * const ok = await ocspBasicResp.verify({ trustedCerts: [cert] }); + * ``` + * + * @example The following example demonstrates how to create OCSP response + * ```js + * const ocspBasicResp = new pkijs.BasicOCSPResponse(); + * + * // Create specific TST info structure to sign + * ocspBasicResp.tbsResponseData.responderID = issuerCert.subject; + * ocspBasicResp.tbsResponseData.producedAt = new Date(); + * + * const certID = new pkijs.CertID(); + * await certID.createForCertificate(cert, { + * hashAlgorithm: "SHA-256", + * issuerCertificate: issuerCert, + * }); + * const response = new pkijs.SingleResponse({ + * certID, + * }); + * response.certStatus = new asn1js.Primitive({ + * idBlock: { + * tagClass: 3, // CONTEXT-SPECIFIC + * tagNumber: 0 // [0] + * }, + * lenBlockLength: 1 // The length contains one byte 0x00 + * }); // status - success + * response.thisUpdate = new Date(); + * + * ocspBasicResp.tbsResponseData.responses.push(response); + * + * // Add certificates for chain OCSP response validation + * ocspBasicResp.certs = [issuerCert]; + * + * await ocspBasicResp.sign(keys.privateKey, "SHA-256"); + * + * // Finally create completed OCSP response structure + * const ocspBasicRespRaw = ocspBasicResp.toSchema().toBER(false); + * + * const ocspResp = new pkijs.OCSPResponse({ + * responseStatus: new asn1js.Enumerated({ value: 0 }), // success + * responseBytes: new pkijs.ResponseBytes({ + * responseType: pkijs.id_PKIX_OCSP_Basic, + * response: new asn1js.OctetString({ valueHex: ocspBasicRespRaw }), + * }), + * }); + * + * const ocspRespRaw = ocspResp.toSchema().toBER(); + * ``` + */ +export class OCSPResponse extends PkiObject implements IOCSPResponse { + + public static override CLASS_NAME = "OCSPResponse"; + + public responseStatus!: asn1js.Enumerated; + public responseBytes?: ResponseBytes; + + /** + * Initializes a new instance of the {@link OCSPResponse} class + * @param parameters Initialization parameters + */ + constructor(parameters: OCSPResponseParameters = {}) { + super(); + + this.responseStatus = pvutils.getParametersValue(parameters, RESPONSE_STATUS, OCSPResponse.defaultValues(RESPONSE_STATUS)); + if (RESPONSE_BYTES in parameters) { + this.responseBytes = pvutils.getParametersValue(parameters, RESPONSE_BYTES, OCSPResponse.defaultValues(RESPONSE_BYTES)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof RESPONSE_STATUS): asn1js.Enumerated; + public static override defaultValues(memberName: typeof RESPONSE_BYTES): ResponseBytes; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case RESPONSE_STATUS: + return new asn1js.Enumerated(); + case RESPONSE_BYTES: + return new ResponseBytes(); + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case RESPONSE_STATUS: + return (memberValue.isEqual(OCSPResponse.defaultValues(memberName))); + case RESPONSE_BYTES: + return ((ResponseBytes.compareWithDefault("responseType", memberValue.responseType)) && + (ResponseBytes.compareWithDefault("response", memberValue.response))); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * OCSPResponse ::= SEQUENCE { + * responseStatus OCSPResponseStatus, + * responseBytes [0] EXPLICIT ResponseBytes OPTIONAL } + * + * OCSPResponseStatus ::= ENUMERATED { + * successful (0), -- Response has valid confirmations + * malformedRequest (1), -- Illegal confirmation request + * internalError (2), -- Internal error in issuer + * tryLater (3), -- Try again later + * -- (4) is not used + * sigRequired (5), -- Must sign the request + * unauthorized (6) -- Request unauthorized + * } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + responseStatus?: string; + responseBytes?: ResponseBytesSchema; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || "OCSPResponse"), + value: [ + new asn1js.Enumerated({ name: (names.responseStatus || RESPONSE_STATUS) }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + ResponseBytes.schema(names.responseBytes || { + names: { + blockName: RESPONSE_BYTES + } + }) + ] + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, [ + RESPONSE_STATUS, + RESPONSE_BYTES + ]); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + OCSPResponse.schema() + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.responseStatus = asn1.result.responseStatus; + if (RESPONSE_BYTES in asn1.result) + this.responseBytes = new ResponseBytes({ schema: asn1.result.responseBytes }); + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray = []; + + outputArray.push(this.responseStatus); + if (this.responseBytes) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [this.responseBytes.toSchema()] + })); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): OCSPResponseJson { + const res: OCSPResponseJson = { + responseStatus: this.responseStatus.toJSON() + }; + + if (this.responseBytes) { + res.responseBytes = this.responseBytes.toJSON(); + } + + return res; + } + + /** + * Get OCSP response status for specific certificate + * @param certificate + * @param issuerCertificate + * @param crypto Crypto engine + */ + public async getCertificateStatus(certificate: Certificate, issuerCertificate: Certificate, crypto = common.getCrypto(true)) { + //#region Initial variables + let basicResponse; + + const result = { + isForCertificate: false, + status: 2 // 0 = good, 1 = revoked, 2 = unknown + }; + //#endregion + + //#region Check that RESPONSE_BYTES contain "OCSP_BASIC_RESPONSE" + if (!this.responseBytes) + return result; + + if (this.responseBytes.responseType !== id_PKIX_OCSP_Basic) // id-pkix-ocsp-basic + return result; + + try { + const asn1Basic = asn1js.fromBER(this.responseBytes.response.valueBlock.valueHexView); + AsnError.assert(asn1Basic, "Basic OCSP response"); + basicResponse = new BasicOCSPResponse({ schema: asn1Basic.result }); + } + catch (ex) { + return result; + } + //#endregion + + return basicResponse.getCertificateStatus(certificate, issuerCertificate, crypto); + } + + /** + * Make a signature for current OCSP Response + * @param privateKey Private key for "subjectPublicKeyInfo" structure + * @param hashAlgorithm Hashing algorithm. Default SHA-1 + */ + public async sign(privateKey: CryptoKey, hashAlgorithm?: string, crypto = common.getCrypto(true)) { + //#region Check that ResponseData has type BasicOCSPResponse and sign it + if (this.responseBytes && this.responseBytes.responseType === id_PKIX_OCSP_Basic) { + const basicResponse = BasicOCSPResponse.fromBER(this.responseBytes.response.valueBlock.valueHexView); + + return basicResponse.sign(privateKey, hashAlgorithm, crypto); + } + + throw new Error(`Unknown ResponseBytes type: ${this.responseBytes?.responseType || "Unknown"}`); + //#endregion + } + + /** + * Verify current OCSP Response + * @param issuerCertificate In order to decrease size of resp issuer cert could be omitted. In such case you need manually provide it. + * @param crypto Crypto engine + */ + public async verify(issuerCertificate: Certificate | null = null, crypto = common.getCrypto(true)): Promise<boolean> { + //#region Check that ResponseBytes exists in the object + if ((RESPONSE_BYTES in this) === false) + throw new Error("Empty ResponseBytes field"); + //#endregion + + //#region Check that ResponseData has type BasicOCSPResponse and verify it + if (this.responseBytes && this.responseBytes.responseType === id_PKIX_OCSP_Basic) { + const basicResponse = BasicOCSPResponse.fromBER(this.responseBytes.response.valueBlock.valueHexView); + + if (issuerCertificate !== null) { + if (!basicResponse.certs) { + basicResponse.certs = []; + } + + basicResponse.certs.push(issuerCertificate); + } + + return basicResponse.verify({}, crypto); + } + + throw new Error(`Unknown ResponseBytes type: ${this.responseBytes?.responseType || "Unknown"}`); + //#endregion + } + +} + diff --git a/third_party/js/PKI.js/src/ObjectIdentifiers.ts b/third_party/js/PKI.js/src/ObjectIdentifiers.ts new file mode 100644 index 0000000000..851d523668 --- /dev/null +++ b/third_party/js/PKI.js/src/ObjectIdentifiers.ts @@ -0,0 +1,64 @@ +// Extensions +export const id_SubjectDirectoryAttributes = "2.5.29.9"; +export const id_SubjectKeyIdentifier = "2.5.29.14"; +export const id_KeyUsage = "2.5.29.15"; +export const id_PrivateKeyUsagePeriod = "2.5.29.16"; +export const id_SubjectAltName = "2.5.29.17"; +export const id_IssuerAltName = "2.5.29.18"; +export const id_BasicConstraints = "2.5.29.19"; +export const id_CRLNumber = "2.5.29.20"; +export const id_BaseCRLNumber = "2.5.29.27"; // BaseCRLNumber (delta CRL indicator) +export const id_CRLReason = "2.5.29.21"; +export const id_InvalidityDate = "2.5.29.24"; +export const id_IssuingDistributionPoint = "2.5.29.28"; +export const id_CertificateIssuer = "2.5.29.29"; +export const id_NameConstraints = "2.5.29.30"; +export const id_CRLDistributionPoints = "2.5.29.31"; +export const id_FreshestCRL = "2.5.29.46"; +export const id_CertificatePolicies = "2.5.29.32"; +export const id_AnyPolicy = "2.5.29.32.0"; +export const id_MicrosoftAppPolicies = "1.3.6.1.4.1.311.21.10"; // szOID_APPLICATION_CERT_POLICIES - Microsoft-specific OID +export const id_PolicyMappings = "2.5.29.33"; +export const id_AuthorityKeyIdentifier = "2.5.29.35"; +export const id_PolicyConstraints = "2.5.29.36"; +export const id_ExtKeyUsage = "2.5.29.37"; +export const id_InhibitAnyPolicy = "2.5.29.54"; +export const id_AuthorityInfoAccess = "1.3.6.1.5.5.7.1.1"; +export const id_SubjectInfoAccess = "1.3.6.1.5.5.7.1.11"; +export const id_SignedCertificateTimestampList = "1.3.6.1.4.1.11129.2.4.2"; +export const id_MicrosoftCertTemplateV1 = "1.3.6.1.4.1.311.20.2"; // szOID_ENROLL_CERTTYPE_EXTENSION - Microsoft-specific extension +export const id_MicrosoftPrevCaCertHash = "1.3.6.1.4.1.311.21.2"; // szOID_CERTSRV_PREVIOUS_CERT_HASH - Microsoft-specific extension +export const id_MicrosoftCertTemplateV2 = "1.3.6.1.4.1.311.21.7"; // szOID_CERTIFICATE_TEMPLATE - Microsoft-specific extension +export const id_MicrosoftCaVersion = "1.3.6.1.4.1.311.21.1"; // szOID_CERTSRV_CA_VERSION - Microsoft-specific extension +export const id_QCStatements = "1.3.6.1.5.5.7.1.3"; + +// ContentType +export const id_ContentType_Data = "1.2.840.113549.1.7.1"; +export const id_ContentType_SignedData = "1.2.840.113549.1.7.2"; +export const id_ContentType_EnvelopedData = "1.2.840.113549.1.7.3"; +export const id_ContentType_EncryptedData = "1.2.840.113549.1.7.6"; + +export const id_eContentType_TSTInfo = "1.2.840.113549.1.9.16.1.4"; + +// CertBag +export const id_CertBag_X509Certificate = "1.2.840.113549.1.9.22.1"; +export const id_CertBag_SDSICertificate = "1.2.840.113549.1.9.22.2"; +export const id_CertBag_AttributeCertificate = "1.2.840.113549.1.9.22.3"; + +// CRLBag +export const id_CRLBag_X509CRL = "1.2.840.113549.1.9.23.1"; + +export const id_pkix = "1.3.6.1.5.5.7"; +export const id_ad = `${id_pkix}.48`; + +export const id_PKIX_OCSP_Basic = `${id_ad}.1.1`; + +export const id_ad_caIssuers = `${id_ad}.2`; +export const id_ad_ocsp = `${id_ad}.1`; + +// Algorithms + +export const id_sha1 = "1.3.14.3.2.26"; +export const id_sha256 = "2.16.840.1.101.3.4.2.1"; +export const id_sha384 = "2.16.840.1.101.3.4.2.2"; +export const id_sha512 = "2.16.840.1.101.3.4.2.3"; diff --git a/third_party/js/PKI.js/src/OriginatorIdentifierOrKey.ts b/third_party/js/PKI.js/src/OriginatorIdentifierOrKey.ts new file mode 100644 index 0000000000..671b8aa936 --- /dev/null +++ b/third_party/js/PKI.js/src/OriginatorIdentifierOrKey.ts @@ -0,0 +1,209 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { IssuerAndSerialNumber } from "./IssuerAndSerialNumber"; +import { OriginatorPublicKey } from "./OriginatorPublicKey"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const VARIANT = "variant"; +const VALUE = "value"; +const CLEAR_PROPS = [ + "blockName", +]; + +export interface IOriginatorIdentifierOrKey { + variant: number; + value?: any; +} + +export interface OriginatorIdentifierOrKeyJson { + variant: number; + value?: any; +} + +export type OriginatorIdentifierOrKeyParameters = PkiObjectParameters & Partial<IOriginatorIdentifierOrKey>; + +export type OriginatorIdentifierOrKeySchema = Schema.SchemaParameters; + +/** + * Represents the OriginatorIdentifierOrKey structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + */ +export class OriginatorIdentifierOrKey extends PkiObject implements IOriginatorIdentifierOrKey { + + public static override CLASS_NAME = "OriginatorIdentifierOrKey"; + + public variant!: number; + public value?: any; + + /** + * Initializes a new instance of the {@link OriginatorIdentifierOrKey} class + * @param parameters Initialization parameters + */ + constructor(parameters: OriginatorIdentifierOrKeyParameters = {}) { + super(); + + this.variant = pvutils.getParametersValue(parameters, VARIANT, OriginatorIdentifierOrKey.defaultValues(VARIANT)); + if (VALUE in parameters) { + this.value = pvutils.getParametersValue(parameters, VALUE, OriginatorIdentifierOrKey.defaultValues(VALUE)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof VARIANT): number; + public static override defaultValues(memberName: typeof VALUE): any; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case VARIANT: + return (-1); + case VALUE: + return {}; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case VARIANT: + return (memberValue === (-1)); + case VALUE: + return (Object.keys(memberValue).length === 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * OriginatorIdentifierOrKey ::= CHOICE { + * issuerAndSerialNumber IssuerAndSerialNumber, + * subjectKeyIdentifier [0] SubjectKeyIdentifier, + * originatorKey [1] OriginatorPublicKey } + *``` + */ + public static override schema(parameters: OriginatorIdentifierOrKeySchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Choice({ + value: [ + IssuerAndSerialNumber.schema({ + names: { + blockName: (names.blockName || EMPTY_STRING) + } + }), + new asn1js.Primitive({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + name: (names.blockName || EMPTY_STRING) + }), + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + name: (names.blockName || EMPTY_STRING), + value: OriginatorPublicKey.schema().valueBlock.value + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + OriginatorIdentifierOrKey.schema({ + names: { + blockName: "blockName" + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + //#region Get internal properties from parsed schema + if (asn1.result.blockName.idBlock.tagClass === 1) { + this.variant = 1; + this.value = new IssuerAndSerialNumber({ schema: asn1.result.blockName }); + } + else { + if (asn1.result.blockName.idBlock.tagNumber === 0) { + //#region Create "OCTETSTRING" from "ASN1_PRIMITIVE" + asn1.result.blockName.idBlock.tagClass = 1; // UNIVERSAL + asn1.result.blockName.idBlock.tagNumber = 4; // OCTETSTRING + //#endregion + + this.variant = 2; + this.value = asn1.result.blockName; + } + else { + this.variant = 3; + this.value = new OriginatorPublicKey({ + schema: new asn1js.Sequence({ + value: asn1.result.blockName.valueBlock.value + }) + }); + } + } + //#endregion + } + + public toSchema(): asn1js.BaseBlock<any> { + //#region Construct and return new ASN.1 schema for this object + switch (this.variant) { + case 1: + return this.value.toSchema(); + case 2: + this.value.idBlock.tagClass = 3; // CONTEXT-SPECIFIC + this.value.idBlock.tagNumber = 0; // [0] + + return this.value; + case 3: + { + const _schema = this.value.toSchema(); + + _schema.idBlock.tagClass = 3; // CONTEXT-SPECIFIC + _schema.idBlock.tagNumber = 1; // [1] + + return _schema; + } + default: + return new asn1js.Any() as any; + } + //#endregion + } + + public toJSON(): OriginatorIdentifierOrKeyJson { + const res: OriginatorIdentifierOrKeyJson = { + variant: this.variant + }; + + if ((this.variant === 1) || (this.variant === 2) || (this.variant === 3)) { + res.value = this.value.toJSON(); + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/OriginatorInfo.ts b/third_party/js/PKI.js/src/OriginatorInfo.ts new file mode 100644 index 0000000000..21b24b6968 --- /dev/null +++ b/third_party/js/PKI.js/src/OriginatorInfo.ts @@ -0,0 +1,212 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { CertificateSet, CertificateSetJson } from "./CertificateSet"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { RevocationInfoChoices, RevocationInfoChoicesJson } from "./RevocationInfoChoices"; +import * as Schema from "./Schema"; + +const CERTS = "certs"; +const CRLS = "crls"; +const CLEAR_PROPS = [ + CERTS, + CRLS, +]; + +export interface IOriginatorInfo { + /** + * Collection of certificates. In may contain originator certificates associated with several different + * key management algorithms. It may also contain attribute certificates associated with the originator. + */ + certs?: CertificateSet; + /** + * Collection of CRLs. It is intended that the set contain information sufficient to determine whether + * or not the certificates in the certs field are valid, but such correspondence is not necessary + */ + crls?: RevocationInfoChoices; +} + +export interface OriginatorInfoJson { + certs?: CertificateSetJson; + crls?: RevocationInfoChoicesJson; +} + +export type OriginatorInfoParameters = PkiObjectParameters & Partial<IOriginatorInfo>; + +/** + * Represents the OriginatorInfo structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + */ +export class OriginatorInfo extends PkiObject implements IOriginatorInfo { + + public static override CLASS_NAME = "OriginatorInfo"; + + public certs?: CertificateSet; + public crls?: RevocationInfoChoices; + + /** + * Initializes a new instance of the {@link CertificateSet} class + * @param parameters Initialization parameters + */ + constructor(parameters: OriginatorInfoParameters = {}) { + super(); + + this.crls = pvutils.getParametersValue(parameters, CRLS, OriginatorInfo.defaultValues(CRLS)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof CERTS): CertificateSet; + public static override defaultValues(memberName: typeof CRLS): RevocationInfoChoices; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case CERTS: + return new CertificateSet(); + case CRLS: + return new RevocationInfoChoices(); + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case CERTS: + return (memberValue.certificates.length === 0); + case CRLS: + return ((memberValue.crls.length === 0) && (memberValue.otherRevocationInfos.length === 0)); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * OriginatorInfo ::= SEQUENCE { + * certs [0] IMPLICIT CertificateSet OPTIONAL, + * crls [1] IMPLICIT RevocationInfoChoices OPTIONAL } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + certs?: string; + crls?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Constructed({ + name: (names.certs || EMPTY_STRING), + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: CertificateSet.schema().valueBlock.value + }), + new asn1js.Constructed({ + name: (names.crls || EMPTY_STRING), + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: RevocationInfoChoices.schema().valueBlock.value + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + OriginatorInfo.schema({ + names: { + certs: CERTS, + crls: CRLS + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + if (CERTS in asn1.result) { + this.certs = new CertificateSet({ + schema: new asn1js.Set({ + value: asn1.result.certs.valueBlock.value + }) + }); + } + if (CRLS in asn1.result) { + this.crls = new RevocationInfoChoices({ + schema: new asn1js.Set({ + value: asn1.result.crls.valueBlock.value + }) + }); + } + } + + public toSchema(): asn1js.Sequence { + const sequenceValue = []; + + if (this.certs) { + sequenceValue.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: this.certs.toSchema().valueBlock.value + })); + } + + if (this.crls) { + sequenceValue.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: this.crls.toSchema().valueBlock.value + })); + } + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: sequenceValue + })); + //#endregion + } + + public toJSON(): OriginatorInfoJson { + const res: OriginatorInfoJson = {}; + + if (this.certs) { + res.certs = this.certs.toJSON(); + } + + if (this.crls) { + res.crls = this.crls.toJSON(); + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/OriginatorPublicKey.ts b/third_party/js/PKI.js/src/OriginatorPublicKey.ts new file mode 100644 index 0000000000..57e2a90275 --- /dev/null +++ b/third_party/js/PKI.js/src/OriginatorPublicKey.ts @@ -0,0 +1,154 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const ALGORITHM = "algorithm"; +const PUBLIC_KEY = "publicKey"; +const CLEAR_PROPS = [ + ALGORITHM, + PUBLIC_KEY +]; + +export interface IOriginatorPublicKey { + algorithm: AlgorithmIdentifier; + publicKey: asn1js.BitString; +} + +export interface OriginatorPublicKeyJson { + algorithm: AlgorithmIdentifierJson; + publicKey: asn1js.BitStringJson; +} + +export type OriginatorPublicKeyParameters = PkiObjectParameters & Partial<IOriginatorPublicKey>; + +/** + * Represents the OriginatorPublicKey structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + */ +export class OriginatorPublicKey extends PkiObject implements IOriginatorPublicKey { + + public static override CLASS_NAME = "OriginatorPublicKey"; + + public algorithm!: AlgorithmIdentifier; + public publicKey!: asn1js.BitString; + + /** + * Initializes a new instance of the {@link OriginatorPublicKey} class + * @param parameters Initialization parameters + */ + constructor(parameters: OriginatorPublicKeyParameters = {}) { + super(); + + this.algorithm = pvutils.getParametersValue(parameters, ALGORITHM, OriginatorPublicKey.defaultValues(ALGORITHM)); + this.publicKey = pvutils.getParametersValue(parameters, PUBLIC_KEY, OriginatorPublicKey.defaultValues(PUBLIC_KEY)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof PUBLIC_KEY): asn1js.BitString; + public static override defaultValues(memberName: string): any; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case ALGORITHM: + return new AlgorithmIdentifier(); + case PUBLIC_KEY: + return new asn1js.BitString(); + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault<T extends { isEqual(data: any): boolean; }>(memberName: string, memberValue: T): memberValue is T { + switch (memberName) { + case ALGORITHM: + case PUBLIC_KEY: + return (memberValue.isEqual(OriginatorPublicKey.defaultValues(memberName))); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * OriginatorPublicKey ::= SEQUENCE { + * algorithm AlgorithmIdentifier, + * publicKey BIT STRING } + *``` + */ + static override schema(parameters: Schema.SchemaParameters<{ + algorithm?: AlgorithmIdentifierSchema; + publicKey?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + AlgorithmIdentifier.schema(names.algorithm || {}), + new asn1js.BitString({ name: (names.publicKey || EMPTY_STRING) }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + OriginatorPublicKey.schema({ + names: { + algorithm: { + names: { + blockName: ALGORITHM + } + }, + publicKey: PUBLIC_KEY + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.algorithm = new AlgorithmIdentifier({ schema: asn1.result.algorithm }); + this.publicKey = asn1.result.publicKey; + } + + public toSchema(): asn1js.Sequence { + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: [ + this.algorithm.toSchema(), + this.publicKey + ] + })); + //#endregion + } + + public toJSON(): OriginatorPublicKeyJson { + return { + algorithm: this.algorithm.toJSON(), + publicKey: this.publicKey.toJSON(), + }; + } + +} diff --git a/third_party/js/PKI.js/src/OtherCertificateFormat.ts b/third_party/js/PKI.js/src/OtherCertificateFormat.ts new file mode 100644 index 0000000000..7127ca4359 --- /dev/null +++ b/third_party/js/PKI.js/src/OtherCertificateFormat.ts @@ -0,0 +1,130 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const OTHER_CERT_FORMAT = "otherCertFormat"; +const OTHER_CERT = "otherCert"; +const CLEAR_PROPS = [ + OTHER_CERT_FORMAT, + OTHER_CERT +]; + +export interface IOtherCertificateFormat { + otherCertFormat: string; + otherCert: any; +} + +export interface OtherCertificateFormatJson { + otherCertFormat: string; + otherCert?: any; +} + +export type OtherCertificateFormatParameters = PkiObjectParameters & Partial<IOtherCertificateFormat>; + +/** + * Represents the OtherCertificateFormat structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + */ +export class OtherCertificateFormat extends PkiObject implements IOtherCertificateFormat { + + public otherCertFormat!: string; + public otherCert: any; + + /** + * Initializes a new instance of the {@link OtherCertificateFormat} class + * @param parameters Initialization parameters + */ + constructor(parameters: OtherCertificateFormatParameters = {}) { + super(); + + this.otherCertFormat = pvutils.getParametersValue(parameters, OTHER_CERT_FORMAT, OtherCertificateFormat.defaultValues(OTHER_CERT_FORMAT)); + this.otherCert = pvutils.getParametersValue(parameters, OTHER_CERT, OtherCertificateFormat.defaultValues(OTHER_CERT)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof OTHER_CERT_FORMAT): string; + public static override defaultValues(memberName: typeof OTHER_CERT): asn1js.Any; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case OTHER_CERT_FORMAT: + return EMPTY_STRING; + case OTHER_CERT: + return new asn1js.Any(); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * OtherCertificateFormat ::= SEQUENCE { + * otherCertFormat OBJECT IDENTIFIER, + * otherCert ANY DEFINED BY otherCertFormat } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + otherCertFormat?: string; + otherCert?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.ObjectIdentifier({ name: (names.otherCertFormat || OTHER_CERT_FORMAT) }), + new asn1js.Any({ name: (names.otherCert || OTHER_CERT) }) + ] + })); + } + + public fromSchema(schema: any): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + OtherCertificateFormat.schema() + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.otherCertFormat = asn1.result.otherCertFormat.valueBlock.toString(); + this.otherCert = asn1.result.otherCert; + } + + public toSchema(): asn1js.Sequence { + // Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: [ + new asn1js.ObjectIdentifier({ value: this.otherCertFormat }), + this.otherCert + ] + })); + } + + public toJSON(): OtherCertificateFormatJson { + const res: OtherCertificateFormatJson = { + otherCertFormat: this.otherCertFormat + }; + + if (!(this.otherCert instanceof asn1js.Any)) { + res.otherCert = this.otherCert.toJSON(); + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/OtherKeyAttribute.ts b/third_party/js/PKI.js/src/OtherKeyAttribute.ts new file mode 100644 index 0000000000..80e243535f --- /dev/null +++ b/third_party/js/PKI.js/src/OtherKeyAttribute.ts @@ -0,0 +1,168 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const KEY_ATTR_ID = "keyAttrId"; +const KEY_ATTR = "keyAttr"; +const CLEAR_PROPS = [ + KEY_ATTR_ID, + KEY_ATTR, +]; + +export interface IOtherKeyAttribute { + keyAttrId: string; + keyAttr?: any; +} + +export interface OtherKeyAttributeJson { + keyAttrId: string; + keyAttr?: any; +} + +export type OtherKeyAttributeParameters = PkiObjectParameters & Partial<IOtherKeyAttribute>; + +export type OtherKeyAttributeSchema = Schema.SchemaType; + +/** + * Represents the OtherKeyAttribute structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + */ +export class OtherKeyAttribute extends PkiObject implements IOtherKeyAttribute { + + public static override CLASS_NAME = "OtherKeyAttribute"; + + public keyAttrId!: string; + public keyAttr?: any; + + /** + * Initializes a new instance of the {@link OtherKeyAttribute} class + * @param parameters Initialization parameters + */ + constructor(parameters: OtherKeyAttributeParameters = {}) { + super(); + + this.keyAttrId = pvutils.getParametersValue(parameters, KEY_ATTR_ID, OtherKeyAttribute.defaultValues(KEY_ATTR_ID)); + if (KEY_ATTR in parameters) { + this.keyAttr = pvutils.getParametersValue(parameters, KEY_ATTR, OtherKeyAttribute.defaultValues(KEY_ATTR)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof KEY_ATTR_ID): string; + public static override defaultValues(memberName: typeof KEY_ATTR): any; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case KEY_ATTR_ID: + return EMPTY_STRING; + case KEY_ATTR: + return {}; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault<T>(memberName: string, memberValue: T): memberValue is T { + switch (memberName) { + case KEY_ATTR_ID: + return (typeof memberValue === "string" && memberValue === EMPTY_STRING); + case KEY_ATTR: + return (Object.keys(memberValue).length === 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * OtherKeyAttribute ::= SEQUENCE { + * keyAttrId OBJECT IDENTIFIER, + * keyAttr ANY DEFINED BY keyAttrId OPTIONAL } + *``` + */ + public static override schema(parameters: OtherKeyAttributeSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + optional: (names.optional || true), + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.ObjectIdentifier({ name: (names.keyAttrId || EMPTY_STRING) }), + new asn1js.Any({ + optional: true, + name: (names.keyAttr || EMPTY_STRING) + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + OtherKeyAttribute.schema({ + names: { + keyAttrId: KEY_ATTR_ID, + keyAttr: KEY_ATTR + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.keyAttrId = asn1.result.keyAttrId.valueBlock.toString(); + if (KEY_ATTR in asn1.result) { + this.keyAttr = asn1.result.keyAttr; + } + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray = []; + + outputArray.push(new asn1js.ObjectIdentifier({ value: this.keyAttrId })); + + if (KEY_ATTR in this) { + outputArray.push(this.keyAttr); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray, + })); + //#endregion + } + + public toJSON(): OtherKeyAttributeJson { + const res: OtherKeyAttributeJson = { + keyAttrId: this.keyAttrId + }; + + if (KEY_ATTR in this) { + res.keyAttr = this.keyAttr.toJSON(); + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/OtherPrimeInfo.ts b/third_party/js/PKI.js/src/OtherPrimeInfo.ts new file mode 100644 index 0000000000..24cc9495ee --- /dev/null +++ b/third_party/js/PKI.js/src/OtherPrimeInfo.ts @@ -0,0 +1,166 @@ +import * as asn1js from "asn1js"; +import * as pvtsutils from "pvtsutils"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError, ParameterError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const PRIME = "prime"; +const EXPONENT = "exponent"; +const COEFFICIENT = "coefficient"; +const CLEAR_PROPS = [ + PRIME, + EXPONENT, + COEFFICIENT, +]; + +export interface IOtherPrimeInfo { + prime: asn1js.Integer; + exponent: asn1js.Integer; + coefficient: asn1js.Integer; +} + +export type OtherPrimeInfoParameters = PkiObjectParameters & Partial<IOtherPrimeInfo> & { json?: OtherPrimeInfoJson; }; + +export interface OtherPrimeInfoJson { + r: string; + d: string; + t: string; +} + +export type OtherPrimeInfoSchema = Schema.SchemaParameters<{ + prime?: string; + exponent?: string; + coefficient?: string; +}>; + +/** + * Represents the OtherPrimeInfo structure described in [RFC3447](https://datatracker.ietf.org/doc/html/rfc3447) + */ +export class OtherPrimeInfo extends PkiObject implements IOtherPrimeInfo { + + public static override CLASS_NAME = "OtherPrimeInfo"; + + public prime!: asn1js.Integer; + public exponent!: asn1js.Integer; + public coefficient!: asn1js.Integer; + + /** + * Initializes a new instance of the {@link OtherPrimeInfo} class + * @param parameters Initialization parameters + */ + constructor(parameters: OtherPrimeInfoParameters = {}) { + super(); + + this.prime = pvutils.getParametersValue(parameters, PRIME, OtherPrimeInfo.defaultValues(PRIME)); + this.exponent = pvutils.getParametersValue(parameters, EXPONENT, OtherPrimeInfo.defaultValues(EXPONENT)); + this.coefficient = pvutils.getParametersValue(parameters, COEFFICIENT, OtherPrimeInfo.defaultValues(COEFFICIENT)); + + if (parameters.json) { + this.fromJSON(parameters.json); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof PRIME | typeof EXPONENT | typeof COEFFICIENT): asn1js.Integer; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case PRIME: + return new asn1js.Integer(); + case EXPONENT: + return new asn1js.Integer(); + case COEFFICIENT: + return new asn1js.Integer(); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * OtherPrimeInfo ::= Sequence { + * prime Integer, -- ri + * exponent Integer, -- di + * coefficient Integer -- ti + * } + *``` + */ + public static override schema(parameters: OtherPrimeInfoSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Integer({ name: (names.prime || EMPTY_STRING) }), + new asn1js.Integer({ name: (names.exponent || EMPTY_STRING) }), + new asn1js.Integer({ name: (names.coefficient || EMPTY_STRING) }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + OtherPrimeInfo.schema({ + names: { + prime: PRIME, + exponent: EXPONENT, + coefficient: COEFFICIENT + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + //#region Get internal properties from parsed schema + this.prime = asn1.result.prime.convertFromDER(); + this.exponent = asn1.result.exponent.convertFromDER(); + this.coefficient = asn1.result.coefficient.convertFromDER(); + //#endregion + } + + public toSchema(): asn1js.Sequence { + return (new asn1js.Sequence({ + value: [ + this.prime.convertToDER(), + this.exponent.convertToDER(), + this.coefficient.convertToDER() + ] + })); + } + + public toJSON(): OtherPrimeInfoJson { + return { + r: pvtsutils.Convert.ToBase64Url(this.prime.valueBlock.valueHexView), + d: pvtsutils.Convert.ToBase64Url(this.exponent.valueBlock.valueHexView), + t: pvtsutils.Convert.ToBase64Url(this.coefficient.valueBlock.valueHexView), + }; + } + + /** + * Converts JSON value into current object + * @param json JSON object + */ + public fromJSON(json: OtherPrimeInfoJson): void { + ParameterError.assert("json", json, "r", "d", "r"); + + this.prime = new asn1js.Integer({ valueHex: pvtsutils.Convert.FromBase64Url(json.r) }); + this.exponent = new asn1js.Integer({ valueHex: pvtsutils.Convert.FromBase64Url(json.d) }); + this.coefficient = new asn1js.Integer({ valueHex: pvtsutils.Convert.FromBase64Url(json.t) }); + } + +} diff --git a/third_party/js/PKI.js/src/OtherRecipientInfo.ts b/third_party/js/PKI.js/src/OtherRecipientInfo.ts new file mode 100644 index 0000000000..a750aab564 --- /dev/null +++ b/third_party/js/PKI.js/src/OtherRecipientInfo.ts @@ -0,0 +1,154 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const ORI_TYPE = "oriType"; +const ORI_VALUE = "oriValue"; +const CLEAR_PROPS = [ + ORI_TYPE, + ORI_VALUE +]; + +export interface IOtherRecipientInfo { + oriType: string; + oriValue: any; +} + +export interface OtherRecipientInfoJson { + oriType: string; + oriValue?: any; +} + +export type OtherRecipientInfoParameters = PkiObjectParameters & Partial<IOtherRecipientInfo>; + +/** + * Represents the OtherRecipientInfo structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + */ +export class OtherRecipientInfo extends PkiObject implements IOtherRecipientInfo { + + public static override CLASS_NAME = "OtherRecipientInfo"; + + public oriType!: string; + public oriValue: any; + + /** + * Initializes a new instance of the {@link OtherRecipientInfo} class + * @param parameters Initialization parameters + */ + constructor(parameters: OtherRecipientInfoParameters = {}) { + super(); + + this.oriType = pvutils.getParametersValue(parameters, ORI_TYPE, OtherRecipientInfo.defaultValues(ORI_TYPE)); + this.oriValue = pvutils.getParametersValue(parameters, ORI_VALUE, OtherRecipientInfo.defaultValues(ORI_VALUE)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + static override defaultValues(memberName: typeof ORI_TYPE): string; + static override defaultValues(memberName: typeof ORI_VALUE): any; + static override defaultValues(memberName: string): any { + switch (memberName) { + case ORI_TYPE: + return EMPTY_STRING; + case ORI_VALUE: + return {}; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case ORI_TYPE: + return (memberValue === EMPTY_STRING); + case ORI_VALUE: + return (Object.keys(memberValue).length === 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * OtherRecipientInfo ::= SEQUENCE { + * oriType OBJECT IDENTIFIER, + * oriValue ANY DEFINED BY oriType } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + oriType?: string; + oriValue?: string; + }> = {}) { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.ObjectIdentifier({ name: (names.oriType || EMPTY_STRING) }), + new asn1js.Any({ name: (names.oriValue || EMPTY_STRING) }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + OtherRecipientInfo.schema({ + names: { + oriType: ORI_TYPE, + oriValue: ORI_VALUE + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.oriType = asn1.result.oriType.valueBlock.toString(); + this.oriValue = asn1.result.oriValue; + } + + public toSchema(): asn1js.Sequence { + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: [ + new asn1js.ObjectIdentifier({ value: this.oriType }), + this.oriValue + ] + })); + //#endregion + } + + public toJSON(): OtherRecipientInfoJson { + const res: OtherRecipientInfoJson = { + oriType: this.oriType + }; + + if (!OtherRecipientInfo.compareWithDefault(ORI_VALUE, this.oriValue)) { + res.oriValue = this.oriValue.toJSON(); + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/OtherRevocationInfoFormat.ts b/third_party/js/PKI.js/src/OtherRevocationInfoFormat.ts new file mode 100644 index 0000000000..e1de759383 --- /dev/null +++ b/third_party/js/PKI.js/src/OtherRevocationInfoFormat.ts @@ -0,0 +1,132 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const OTHER_REV_INFO_FORMAT = "otherRevInfoFormat"; +const OTHER_REV_INFO = "otherRevInfo"; +const CLEAR_PROPS = [ + OTHER_REV_INFO_FORMAT, + OTHER_REV_INFO +]; + +export interface IOtherRevocationInfoFormat { + otherRevInfoFormat: string; + otherRevInfo: any; +} + +export interface OtherRevocationInfoFormatJson { + otherRevInfoFormat: string; + otherRevInfo?: any; +} + +export type OtherRevocationInfoFormatParameters = PkiObjectParameters & Partial<IOtherRevocationInfoFormat>; + +/** + * Represents the OtherRevocationInfoFormat structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + */ +export class OtherRevocationInfoFormat extends PkiObject implements IOtherRevocationInfoFormat { + + public static override CLASS_NAME = "OtherRevocationInfoFormat"; + + public otherRevInfoFormat!: string; + public otherRevInfo: any; + + /** + * Initializes a new instance of the {@link OtherRevocationInfoFormat} class + * @param parameters Initialization parameters + */ + constructor(parameters: OtherRevocationInfoFormatParameters = {}) { + super(); + + this.otherRevInfoFormat = pvutils.getParametersValue(parameters, OTHER_REV_INFO_FORMAT, OtherRevocationInfoFormat.defaultValues(OTHER_REV_INFO_FORMAT)); + this.otherRevInfo = pvutils.getParametersValue(parameters, OTHER_REV_INFO, OtherRevocationInfoFormat.defaultValues(OTHER_REV_INFO)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + static override defaultValues(memberName: typeof OTHER_REV_INFO_FORMAT): string; + static override defaultValues(memberName: typeof OTHER_REV_INFO): any; + static override defaultValues(memberName: string): any { + switch (memberName) { + case OTHER_REV_INFO_FORMAT: + return EMPTY_STRING; + case OTHER_REV_INFO: + return new asn1js.Any(); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * OtherCertificateFormat ::= SEQUENCE { + * otherRevInfoFormat OBJECT IDENTIFIER, + * otherRevInfo ANY DEFINED BY otherCertFormat } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + otherRevInfoFormat?: string; + otherRevInfo?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.ObjectIdentifier({ name: (names.otherRevInfoFormat || OTHER_REV_INFO_FORMAT) }), + new asn1js.Any({ name: (names.otherRevInfo || OTHER_REV_INFO) }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + OtherRevocationInfoFormat.schema() + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.otherRevInfoFormat = asn1.result.otherRevInfoFormat.valueBlock.toString(); + this.otherRevInfo = asn1.result.otherRevInfo; + } + + public toSchema(): asn1js.Sequence { + // Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: [ + new asn1js.ObjectIdentifier({ value: this.otherRevInfoFormat }), + this.otherRevInfo + ] + })); + } + + public toJSON(): OtherRevocationInfoFormatJson { + const res: OtherRevocationInfoFormatJson = { + otherRevInfoFormat: this.otherRevInfoFormat + }; + + if (!(this.otherRevInfo instanceof asn1js.Any)) { + res.otherRevInfo = this.otherRevInfo.toJSON(); + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/PBES2Params.ts b/third_party/js/PKI.js/src/PBES2Params.ts new file mode 100644 index 0000000000..526964446b --- /dev/null +++ b/third_party/js/PKI.js/src/PBES2Params.ts @@ -0,0 +1,141 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const KEY_DERIVATION_FUNC = "keyDerivationFunc"; +const ENCRYPTION_SCHEME = "encryptionScheme"; +const CLEAR_PROPS = [ + KEY_DERIVATION_FUNC, + ENCRYPTION_SCHEME +]; + +export interface IPBES2Params { + keyDerivationFunc: AlgorithmIdentifier; + encryptionScheme: AlgorithmIdentifier; +} + +export interface PBES2ParamsJson { + keyDerivationFunc: AlgorithmIdentifierJson; + encryptionScheme: AlgorithmIdentifierJson; +} + +export type PBES2ParamsParameters = PkiObjectParameters & Partial<IPBES2Params>; + +/** + * Represents the PBES2Params structure described in [RFC2898](https://www.ietf.org/rfc/rfc2898.txt) + */ +export class PBES2Params extends PkiObject implements IPBES2Params { + + public static override CLASS_NAME = "PBES2Params"; + + public keyDerivationFunc!: AlgorithmIdentifier; + public encryptionScheme!: AlgorithmIdentifier; + + /** + * Initializes a new instance of the {@link PBES2Params} class + * @param parameters Initialization parameters + */ + constructor(parameters: PBES2ParamsParameters = {}) { + super(); + + this.keyDerivationFunc = pvutils.getParametersValue(parameters, KEY_DERIVATION_FUNC, PBES2Params.defaultValues(KEY_DERIVATION_FUNC)); + this.encryptionScheme = pvutils.getParametersValue(parameters, ENCRYPTION_SCHEME, PBES2Params.defaultValues(ENCRYPTION_SCHEME)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof KEY_DERIVATION_FUNC): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof ENCRYPTION_SCHEME): AlgorithmIdentifier; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case KEY_DERIVATION_FUNC: + return new AlgorithmIdentifier(); + case ENCRYPTION_SCHEME: + return new AlgorithmIdentifier(); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * PBES2-params ::= SEQUENCE { + * keyDerivationFunc AlgorithmIdentifier {{PBES2-KDFs}}, + * encryptionScheme AlgorithmIdentifier {{PBES2-Encs}} } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + keyDerivationFunc?: AlgorithmIdentifierSchema; + encryptionScheme?: AlgorithmIdentifierSchema; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + AlgorithmIdentifier.schema(names.keyDerivationFunc || {}), + AlgorithmIdentifier.schema(names.encryptionScheme || {}) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + PBES2Params.schema({ + names: { + keyDerivationFunc: { + names: { + blockName: KEY_DERIVATION_FUNC + } + }, + encryptionScheme: { + names: { + blockName: ENCRYPTION_SCHEME + } + } + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.keyDerivationFunc = new AlgorithmIdentifier({ schema: asn1.result.keyDerivationFunc }); + this.encryptionScheme = new AlgorithmIdentifier({ schema: asn1.result.encryptionScheme }); + } + + public toSchema(): asn1js.Sequence { + // Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: [ + this.keyDerivationFunc.toSchema(), + this.encryptionScheme.toSchema() + ] + })); + } + + public toJSON(): PBES2ParamsJson { + return { + keyDerivationFunc: this.keyDerivationFunc.toJSON(), + encryptionScheme: this.encryptionScheme.toJSON() + }; + } + +} diff --git a/third_party/js/PKI.js/src/PBKDF2Params.ts b/third_party/js/PKI.js/src/PBKDF2Params.ts new file mode 100644 index 0000000000..579abde1ca --- /dev/null +++ b/third_party/js/PKI.js/src/PBKDF2Params.ts @@ -0,0 +1,223 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const SALT = "salt"; +const ITERATION_COUNT = "iterationCount"; +const KEY_LENGTH = "keyLength"; +const PRF = "prf"; +const CLEAR_PROPS = [ + SALT, + ITERATION_COUNT, + KEY_LENGTH, + PRF +]; + +export interface IPBKDF2Params { + salt: any; + iterationCount: number; + keyLength?: number; + prf?: AlgorithmIdentifier; +} + +export interface PBKDF2ParamsJson { + salt: any; + iterationCount: number; + keyLength?: number; + prf?: AlgorithmIdentifierJson; +} + +export type PBKDF2ParamsParameters = PkiObjectParameters & Partial<IPBKDF2Params>; + +/** + * Represents the PBKDF2Params structure described in [RFC2898](https://www.ietf.org/rfc/rfc2898.txt) + */ +export class PBKDF2Params extends PkiObject implements IPBKDF2Params { + + public static override CLASS_NAME = "PBKDF2Params"; + + public salt: any; + public iterationCount!: number; + public keyLength?: number; + public prf?: AlgorithmIdentifier; + + /** + * Initializes a new instance of the {@link PBKDF2Params} class + * @param parameters Initialization parameters + */ + constructor(parameters: PBKDF2ParamsParameters = {}) { + super(); + + this.salt = pvutils.getParametersValue(parameters, SALT, PBKDF2Params.defaultValues(SALT)); + this.iterationCount = pvutils.getParametersValue(parameters, ITERATION_COUNT, PBKDF2Params.defaultValues(ITERATION_COUNT)); + if (KEY_LENGTH in parameters) { + this.keyLength = pvutils.getParametersValue(parameters, KEY_LENGTH, PBKDF2Params.defaultValues(KEY_LENGTH)); + } + if (PRF in parameters) { + this.prf = pvutils.getParametersValue(parameters, PRF, PBKDF2Params.defaultValues(PRF)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof SALT): any; + public static override defaultValues(memberName: typeof ITERATION_COUNT): number; + public static override defaultValues(memberName: typeof KEY_LENGTH): number; + public static override defaultValues(memberName: typeof PRF): AlgorithmIdentifier; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case SALT: + return {}; + case ITERATION_COUNT: + return (-1); + case KEY_LENGTH: + return 0; + case PRF: + return new AlgorithmIdentifier({ + algorithmId: "1.3.14.3.2.26", // SHA-1 + algorithmParams: new asn1js.Null() + }); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * PBKDF2-params ::= SEQUENCE { + * salt CHOICE { + * specified OCTET STRING, + * otherSource AlgorithmIdentifier }, + * iterationCount INTEGER (1..MAX), + * keyLength INTEGER (1..MAX) OPTIONAL, + * prf AlgorithmIdentifier + * DEFAULT { algorithm hMAC-SHA1, parameters NULL } } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + saltPrimitive?: string; + saltConstructed?: AlgorithmIdentifierSchema; + iterationCount?: string; + keyLength?: string; + prf?: AlgorithmIdentifierSchema; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Choice({ + value: [ + new asn1js.OctetString({ name: (names.saltPrimitive || EMPTY_STRING) }), + AlgorithmIdentifier.schema(names.saltConstructed || {}) + ] + }), + new asn1js.Integer({ name: (names.iterationCount || EMPTY_STRING) }), + new asn1js.Integer({ + name: (names.keyLength || EMPTY_STRING), + optional: true + }), + AlgorithmIdentifier.schema(names.prf || { + names: { + optional: true + } + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + PBKDF2Params.schema({ + names: { + saltPrimitive: SALT, + saltConstructed: { + names: { + blockName: SALT + } + }, + iterationCount: ITERATION_COUNT, + keyLength: KEY_LENGTH, + prf: { + names: { + blockName: PRF, + optional: true + } + } + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.salt = asn1.result.salt; + this.iterationCount = asn1.result.iterationCount.valueBlock.valueDec; + if (KEY_LENGTH in asn1.result) + this.keyLength = asn1.result.keyLength.valueBlock.valueDec; + if (PRF in asn1.result) + this.prf = new AlgorithmIdentifier({ schema: asn1.result.prf }); + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray = []; + + outputArray.push(this.salt); + outputArray.push(new asn1js.Integer({ value: this.iterationCount })); + + if (KEY_LENGTH in this) { + if (PBKDF2Params.defaultValues(KEY_LENGTH) !== this.keyLength) + outputArray.push(new asn1js.Integer({ value: this.keyLength })); + } + + if (this.prf) { + if (PBKDF2Params.defaultValues(PRF).isEqual(this.prf) === false) + outputArray.push(this.prf.toSchema()); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): PBKDF2ParamsJson { + const res: PBKDF2ParamsJson = { + salt: this.salt.toJSON(), + iterationCount: this.iterationCount + }; + + if (KEY_LENGTH in this) { + if (PBKDF2Params.defaultValues(KEY_LENGTH) !== this.keyLength) + res.keyLength = this.keyLength; + } + + if (this.prf) { + if (PBKDF2Params.defaultValues(PRF).isEqual(this.prf) === false) + res.prf = this.prf.toJSON(); + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/PFX.ts b/third_party/js/PKI.js/src/PFX.ts new file mode 100644 index 0000000000..25885f4f83 --- /dev/null +++ b/third_party/js/PKI.js/src/PFX.ts @@ -0,0 +1,533 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import * as common from "./common"; +import { ContentInfo, ContentInfoJson, ContentInfoSchema } from "./ContentInfo"; +import { MacData, MacDataJson, MacDataSchema } from "./MacData"; +import { DigestInfo } from "./DigestInfo"; +import { AlgorithmIdentifier } from "./AlgorithmIdentifier"; +import { SignedData } from "./SignedData"; +import { EncapsulatedContentInfo } from "./EncapsulatedContentInfo"; +import { Attribute } from "./Attribute"; +import { SignerInfo } from "./SignerInfo"; +import { IssuerAndSerialNumber } from "./IssuerAndSerialNumber"; +import { SignedAndUnsignedAttributes } from "./SignedAndUnsignedAttributes"; +import { AuthenticatedSafe } from "./AuthenticatedSafe"; +import * as Schema from "./Schema"; +import { Certificate } from "./Certificate"; +import { ArgumentError, AsnError, ParameterError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { BufferSourceConverter } from "pvtsutils"; +import { EMPTY_STRING } from "./constants"; + +const VERSION = "version"; +const AUTH_SAFE = "authSafe"; +const MAC_DATA = "macData"; +const PARSED_VALUE = "parsedValue"; +const CLERA_PROPS = [ + VERSION, + AUTH_SAFE, + MAC_DATA +]; + +export interface IPFX { + version: number; + authSafe: ContentInfo; + macData?: MacData; + parsedValue?: PFXParsedValue; +} + +export interface PFXJson { + version: number; + authSafe: ContentInfoJson; + macData?: MacDataJson; +} + +export type PFXParameters = PkiObjectParameters & Partial<IPFX>; + +export interface PFXParsedValue { + authenticatedSafe?: AuthenticatedSafe; + integrityMode?: number; +} + +export type MakeInternalValuesParams = + { + // empty + } + | + { + iterations: number; + pbkdf2HashAlgorithm: Algorithm; + hmacHashAlgorithm: string; + password: ArrayBuffer; + } + | + { + signingCertificate: Certificate; + privateKey: CryptoKey; + hashAlgorithm: string; + }; + +/** + * Represents the PFX structure described in [RFC7292](https://datatracker.ietf.org/doc/html/rfc7292) + */ +export class PFX extends PkiObject implements IPFX { + + public static override CLASS_NAME = "PFX"; + + public version!: number; + public authSafe!: ContentInfo; + public macData?: MacData; + public parsedValue?: PFXParsedValue; + + /** + * Initializes a new instance of the {@link PFX} class + * @param parameters Initialization parameters + */ + constructor(parameters: PFXParameters = {}) { + super(); + + this.version = pvutils.getParametersValue(parameters, VERSION, PFX.defaultValues(VERSION)); + this.authSafe = pvutils.getParametersValue(parameters, AUTH_SAFE, PFX.defaultValues(AUTH_SAFE)); + if (MAC_DATA in parameters) { + this.macData = pvutils.getParametersValue(parameters, MAC_DATA, PFX.defaultValues(MAC_DATA)); + } + if (PARSED_VALUE in parameters) { + this.parsedValue = pvutils.getParametersValue(parameters, PARSED_VALUE, PFX.defaultValues(PARSED_VALUE)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof VERSION): number; + public static override defaultValues(memberName: typeof AUTH_SAFE): ContentInfo; + public static override defaultValues(memberName: typeof MAC_DATA): MacData; + public static override defaultValues(memberName: typeof PARSED_VALUE): PFXParsedValue; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case VERSION: + return 3; + case AUTH_SAFE: + return (new ContentInfo()); + case MAC_DATA: + return (new MacData()); + case PARSED_VALUE: + return {}; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case VERSION: + return (memberValue === PFX.defaultValues(memberName)); + case AUTH_SAFE: + return ((ContentInfo.compareWithDefault("contentType", memberValue.contentType)) && + (ContentInfo.compareWithDefault("content", memberValue.content))); + case MAC_DATA: + return ((MacData.compareWithDefault("mac", memberValue.mac)) && + (MacData.compareWithDefault("macSalt", memberValue.macSalt)) && + (MacData.compareWithDefault("iterations", memberValue.iterations))); + case PARSED_VALUE: + return ((memberValue instanceof Object) && (Object.keys(memberValue).length === 0)); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * PFX ::= SEQUENCE { + * version INTEGER {v3(3)}(v3,...), + * authSafe ContentInfo, + * macData MacData OPTIONAL + * } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + version?: string; + authSafe?: ContentInfoSchema; + macData?: MacDataSchema; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Integer({ name: (names.version || VERSION) }), + ContentInfo.schema(names.authSafe || { + names: { + blockName: AUTH_SAFE + } + }), + MacData.schema(names.macData || { + names: { + blockName: MAC_DATA, + optional: true + } + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLERA_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + PFX.schema({ + names: { + version: VERSION, + authSafe: { + names: { + blockName: AUTH_SAFE + } + }, + macData: { + names: { + blockName: MAC_DATA + } + } + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.version = asn1.result.version.valueBlock.valueDec; + this.authSafe = new ContentInfo({ schema: asn1.result.authSafe }); + if (MAC_DATA in asn1.result) + this.macData = new MacData({ schema: asn1.result.macData }); + } + + public toSchema(): asn1js.Sequence { + //#region Construct and return new ASN.1 schema for this object + const outputArray = [ + new asn1js.Integer({ value: this.version }), + this.authSafe.toSchema() + ]; + + if (this.macData) { + outputArray.push(this.macData.toSchema()); + } + + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): PFXJson { + const output: PFXJson = { + version: this.version, + authSafe: this.authSafe.toJSON() + }; + + if (this.macData) { + output.macData = this.macData.toJSON(); + } + + return output; + } + + /** + * Making ContentInfo from PARSED_VALUE object + * @param parameters Parameters, specific to each "integrity mode" + * @param crypto Crypto engine + */ + public async makeInternalValues(parameters: MakeInternalValuesParams = {}, crypto = common.getCrypto(true)) { + //#region Check mandatory parameter + ArgumentError.assert(parameters, "parameters", "object"); + if (!this.parsedValue) { + throw new Error("Please call \"parseValues\" function first in order to make \"parsedValue\" data"); + } + ParameterError.assertEmpty(this.parsedValue.integrityMode, "integrityMode", "parsedValue"); + ParameterError.assertEmpty(this.parsedValue.authenticatedSafe, "authenticatedSafe", "parsedValue"); + //#endregion + + //#region Makes values for each particular integrity mode + switch (this.parsedValue.integrityMode) { + //#region HMAC-based integrity + case 0: + { + //#region Check additional mandatory parameters + if (!("iterations" in parameters)) + throw new ParameterError("iterations"); + ParameterError.assertEmpty(parameters.pbkdf2HashAlgorithm, "pbkdf2HashAlgorithm"); + ParameterError.assertEmpty(parameters.hmacHashAlgorithm, "hmacHashAlgorithm"); + ParameterError.assertEmpty(parameters.password, "password"); + //#endregion + + //#region Initial variables + const saltBuffer = new ArrayBuffer(64); + const saltView = new Uint8Array(saltBuffer); + + crypto.getRandomValues(saltView); + + const data = this.parsedValue.authenticatedSafe.toSchema().toBER(false); + + this.authSafe = new ContentInfo({ + contentType: ContentInfo.DATA, + content: new asn1js.OctetString({ valueHex: data }) + }); + //#endregion + + //#region Call current crypto engine for making HMAC-based data stamp + const result = await crypto.stampDataWithPassword({ + password: parameters.password, + hashAlgorithm: parameters.hmacHashAlgorithm, + salt: saltBuffer, + iterationCount: parameters.iterations, + contentToStamp: data + }); + //#endregion + + //#region Make MAC_DATA values + this.macData = new MacData({ + mac: new DigestInfo({ + digestAlgorithm: new AlgorithmIdentifier({ + algorithmId: crypto.getOIDByAlgorithm({ name: parameters.hmacHashAlgorithm }, true, "hmacHashAlgorithm"), + }), + digest: new asn1js.OctetString({ valueHex: result }) + }), + macSalt: new asn1js.OctetString({ valueHex: saltBuffer }), + iterations: parameters.iterations + }); + //#endregion + //#endregion + } + break; + //#endregion + //#region publicKey-based integrity + case 1: + { + //#region Check additional mandatory parameters + if (!("signingCertificate" in parameters)) { + throw new ParameterError("signingCertificate"); + } + ParameterError.assertEmpty(parameters.privateKey, "privateKey"); + ParameterError.assertEmpty(parameters.hashAlgorithm, "hashAlgorithm"); + //#endregion + + //#region Making data to be signed + // NOTE: all internal data for "authenticatedSafe" must be already prepared. + // Thus user must call "makeValues" for all internal "SafeContent" value with appropriate parameters. + // Or user can choose to use values from initial parsing of existing PKCS#12 data. + + const toBeSigned = this.parsedValue.authenticatedSafe.toSchema().toBER(false); + //#endregion + + //#region Initial variables + const cmsSigned = new SignedData({ + version: 1, + encapContentInfo: new EncapsulatedContentInfo({ + eContentType: "1.2.840.113549.1.7.1", // "data" content type + eContent: new asn1js.OctetString({ valueHex: toBeSigned }) + }), + certificates: [parameters.signingCertificate] + }); + //#endregion + + //#region Making additional attributes for CMS Signed Data + //#region Create a message digest + const result = await crypto.digest({ name: parameters.hashAlgorithm }, new Uint8Array(toBeSigned)); + //#endregion + + //#region Combine all signed extensions + //#region Initial variables + const signedAttr: Attribute[] = []; + //#endregion + + //#region contentType + signedAttr.push(new Attribute({ + type: "1.2.840.113549.1.9.3", + values: [ + new asn1js.ObjectIdentifier({ value: "1.2.840.113549.1.7.1" }) + ] + })); + //#endregion + //#region signingTime + signedAttr.push(new Attribute({ + type: "1.2.840.113549.1.9.5", + values: [ + new asn1js.UTCTime({ valueDate: new Date() }) + ] + })); + //#endregion + //#region messageDigest + signedAttr.push(new Attribute({ + type: "1.2.840.113549.1.9.4", + values: [ + new asn1js.OctetString({ valueHex: result }) + ] + })); + //#endregion + + //#region Making final value for "SignerInfo" type + cmsSigned.signerInfos.push(new SignerInfo({ + version: 1, + sid: new IssuerAndSerialNumber({ + issuer: parameters.signingCertificate.issuer, + serialNumber: parameters.signingCertificate.serialNumber + }), + signedAttrs: new SignedAndUnsignedAttributes({ + type: 0, + attributes: signedAttr + }) + })); + //#endregion + //#endregion + //#endregion + + //#region Signing CMS Signed Data + await cmsSigned.sign(parameters.privateKey, 0, parameters.hashAlgorithm, undefined, crypto); + //#endregion + + //#region Making final CMS_CONTENT_INFO type + this.authSafe = new ContentInfo({ + contentType: "1.2.840.113549.1.7.2", + content: cmsSigned.toSchema(true) + }); + //#endregion + } + break; + //#endregion + //#region default + default: + throw new Error(`Parameter "integrityMode" has unknown value: ${this.parsedValue.integrityMode}`); + //#endregion + } + //#endregion + } + + public async parseInternalValues(parameters: { + checkIntegrity?: boolean; + password?: ArrayBuffer; + }, crypto = common.getCrypto(true)) { + //#region Check input data from "parameters" + ArgumentError.assert(parameters, "parameters", "object"); + + if (parameters.checkIntegrity === undefined) { + parameters.checkIntegrity = true; + } + //#endregion + + //#region Create value for "this.parsedValue.authenticatedSafe" and check integrity + this.parsedValue = {}; + + switch (this.authSafe.contentType) { + //#region data + case ContentInfo.DATA: + { + //#region Check additional mandatory parameters + ParameterError.assertEmpty(parameters.password, "password"); + //#endregion + + //#region Integrity based on HMAC + this.parsedValue.integrityMode = 0; + //#endregion + + //#region Check that we do have OCTETSTRING as "content" + ArgumentError.assert(this.authSafe.content, "authSafe.content", asn1js.OctetString); + //#endregion + + //#region Check we have "constructive encoding" for AuthSafe content + const authSafeContent = this.authSafe.content.getValue(); + //#endregion + + //#region Set "authenticatedSafe" value + this.parsedValue.authenticatedSafe = AuthenticatedSafe.fromBER(authSafeContent); + //#endregion + + //#region Check integrity + if (parameters.checkIntegrity) { + //#region Check that MAC_DATA exists + if (!this.macData) { + throw new Error("Absent \"macData\" value, can not check PKCS#12 data integrity"); + } + //#endregion + + //#region Initial variables + const hashAlgorithm = crypto.getAlgorithmByOID(this.macData.mac.digestAlgorithm.algorithmId, true, "digestAlgorithm"); + //#endregion + + //#region Call current crypto engine for verifying HMAC-based data stamp + const result = await crypto.verifyDataStampedWithPassword({ + password: parameters.password, + hashAlgorithm: hashAlgorithm.name, + salt: BufferSourceConverter.toArrayBuffer(this.macData.macSalt.valueBlock.valueHexView), + iterationCount: this.macData.iterations || 1, + contentToVerify: authSafeContent, + signatureToVerify: BufferSourceConverter.toArrayBuffer(this.macData.mac.digest.valueBlock.valueHexView), + }); + //#endregion + + //#region Verify HMAC signature + if (!result) { + throw new Error("Integrity for the PKCS#12 data is broken!"); + } + //#endregion + } + //#endregion + } + break; + //#endregion + //#region signedData + case ContentInfo.SIGNED_DATA: + { + //#region Integrity based on signature using public key + this.parsedValue.integrityMode = 1; + //#endregion + + //#region Parse CMS Signed Data + const cmsSigned = new SignedData({ schema: this.authSafe.content }); + //#endregion + + //#region Check that we do have OCTET STRING as "content" + const eContent = cmsSigned.encapContentInfo.eContent; + ParameterError.assert(eContent, "eContent", "cmsSigned.encapContentInfo"); + ArgumentError.assert(eContent, "eContent", asn1js.OctetString); + //#endregion + + //#region Create correct data block for verification + const data = eContent.getValue(); + //#endregion + + //#region Set "authenticatedSafe" value + this.parsedValue.authenticatedSafe = AuthenticatedSafe.fromBER(data); + //#endregion + + //#region Check integrity + const ok = await cmsSigned.verify({ signer: 0, checkChain: false }, crypto); + if (!ok) { + throw new Error("Integrity for the PKCS#12 data is broken!"); + } + //#endregion + } + break; + //#endregion + //#region default + default: + throw new Error(`Incorrect value for "this.authSafe.contentType": ${this.authSafe.contentType}`); + //#endregion + } + //#endregion + } + +} diff --git a/third_party/js/PKI.js/src/PKCS8ShroudedKeyBag.ts b/third_party/js/PKI.js/src/PKCS8ShroudedKeyBag.ts new file mode 100644 index 0000000000..34458958a9 --- /dev/null +++ b/third_party/js/PKI.js/src/PKCS8ShroudedKeyBag.ts @@ -0,0 +1,248 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier"; +import { EncryptedData, EncryptedDataEncryptParams } from "./EncryptedData"; +import { EncryptedContentInfo } from "./EncryptedContentInfo"; +import { PrivateKeyInfo } from "./PrivateKeyInfo"; +import * as Schema from "./Schema"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { EMPTY_STRING } from "./constants"; +import * as common from "./common"; + +const ENCRYPTION_ALGORITHM = "encryptionAlgorithm"; +const ENCRYPTED_DATA = "encryptedData"; +const PARSED_VALUE = "parsedValue"; +const CLEAR_PROPS = [ + ENCRYPTION_ALGORITHM, + ENCRYPTED_DATA, +]; + +export interface IPKCS8ShroudedKeyBag { + encryptionAlgorithm: AlgorithmIdentifier; + encryptedData: asn1js.OctetString; + parsedValue?: PrivateKeyInfo; +} + +export type PKCS8ShroudedKeyBagParameters = PkiObjectParameters & Partial<IPKCS8ShroudedKeyBag>; + +export interface PKCS8ShroudedKeyBagJson { + encryptionAlgorithm: AlgorithmIdentifierJson; + encryptedData: asn1js.OctetStringJson; +} + +type PKCS8ShroudedKeyBagMakeInternalValuesParams = Omit<EncryptedDataEncryptParams, "contentToEncrypt">; + +/** + * Represents the PKCS8ShroudedKeyBag structure described in [RFC7292](https://datatracker.ietf.org/doc/html/rfc7292) + */ +export class PKCS8ShroudedKeyBag extends PkiObject implements IPKCS8ShroudedKeyBag { + + public static override CLASS_NAME = "PKCS8ShroudedKeyBag"; + + public encryptionAlgorithm!: AlgorithmIdentifier; + public encryptedData!: asn1js.OctetString; + public parsedValue?: PrivateKeyInfo; + + /** + * Initializes a new instance of the {@link PKCS8ShroudedKeyBag} class + * @param parameters Initialization parameters + */ + constructor(parameters: PKCS8ShroudedKeyBagParameters = {}) { + super(); + + this.encryptionAlgorithm = pvutils.getParametersValue(parameters, ENCRYPTION_ALGORITHM, PKCS8ShroudedKeyBag.defaultValues(ENCRYPTION_ALGORITHM)); + this.encryptedData = pvutils.getParametersValue(parameters, ENCRYPTED_DATA, PKCS8ShroudedKeyBag.defaultValues(ENCRYPTED_DATA)); + if (PARSED_VALUE in parameters) { + this.parsedValue = pvutils.getParametersValue(parameters, PARSED_VALUE, PKCS8ShroudedKeyBag.defaultValues(PARSED_VALUE)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof ENCRYPTION_ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof ENCRYPTED_DATA): asn1js.OctetString; + public static override defaultValues(memberName: typeof PARSED_VALUE): PrivateKeyInfo; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case ENCRYPTION_ALGORITHM: + return (new AlgorithmIdentifier()); + case ENCRYPTED_DATA: + return (new asn1js.OctetString()); + case PARSED_VALUE: + return {}; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case ENCRYPTION_ALGORITHM: + return ((AlgorithmIdentifier.compareWithDefault("algorithmId", memberValue.algorithmId)) && + (("algorithmParams" in memberValue) === false)); + case ENCRYPTED_DATA: + return (memberValue.isEqual(PKCS8ShroudedKeyBag.defaultValues(memberName))); + case PARSED_VALUE: + return ((memberValue instanceof Object) && (Object.keys(memberValue).length === 0)); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * PKCS8ShroudedKeyBag ::= EncryptedPrivateKeyInfo + * + * EncryptedPrivateKeyInfo ::= SEQUENCE { + * encryptionAlgorithm AlgorithmIdentifier {{KeyEncryptionAlgorithms}}, + * encryptedData EncryptedData + * } + * + * EncryptedData ::= OCTET STRING + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + encryptionAlgorithm?: AlgorithmIdentifierSchema; + encryptedData?: string; + }> = {}): Schema.SchemaType { + /** + * @type {Object} + * @property {string} [blockName] + * @property {string} [encryptionAlgorithm] + * @property {string} [encryptedData] + */ + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + AlgorithmIdentifier.schema(names.encryptionAlgorithm || { + names: { + blockName: ENCRYPTION_ALGORITHM + } + }), + new asn1js.Choice({ + value: [ + new asn1js.OctetString({ name: (names.encryptedData || ENCRYPTED_DATA) }), + new asn1js.OctetString({ + idBlock: { + isConstructed: true + }, + name: (names.encryptedData || ENCRYPTED_DATA) + }) + ] + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + PKCS8ShroudedKeyBag.schema({ + names: { + encryptionAlgorithm: { + names: { + blockName: ENCRYPTION_ALGORITHM + } + }, + encryptedData: ENCRYPTED_DATA + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.encryptionAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.encryptionAlgorithm }); + this.encryptedData = asn1.result.encryptedData; + } + + public toSchema(): asn1js.Sequence { + return (new asn1js.Sequence({ + value: [ + this.encryptionAlgorithm.toSchema(), + this.encryptedData + ] + })); + } + + public toJSON(): PKCS8ShroudedKeyBagJson { + return { + encryptionAlgorithm: this.encryptionAlgorithm.toJSON(), + encryptedData: this.encryptedData.toJSON(), + }; + } + + protected async parseInternalValues(parameters: { + password: ArrayBuffer; + }, crypto = common.getCrypto(true)) { + //#region Initial variables + const cmsEncrypted = new EncryptedData({ + encryptedContentInfo: new EncryptedContentInfo({ + contentEncryptionAlgorithm: this.encryptionAlgorithm, + encryptedContent: this.encryptedData + }) + }); + //#endregion + + //#region Decrypt internal data + const decryptedData = await cmsEncrypted.decrypt(parameters, crypto); + + //#endregion + + //#region Initialize PARSED_VALUE with decrypted PKCS#8 private key + + this.parsedValue = PrivateKeyInfo.fromBER(decryptedData); + //#endregion + } + + public async makeInternalValues(parameters: PKCS8ShroudedKeyBagMakeInternalValuesParams): Promise<void> { + //#region Check that we do have PARSED_VALUE + if (!this.parsedValue) { + throw new Error("Please initialize \"parsedValue\" first"); + } + //#endregion + + //#region Initial variables + const cmsEncrypted = new EncryptedData(); + //#endregion + + //#region Encrypt internal data + const encryptParams: EncryptedDataEncryptParams = { + ...parameters, + contentToEncrypt: this.parsedValue.toSchema().toBER(false), + }; + + await cmsEncrypted.encrypt(encryptParams); + if (!cmsEncrypted.encryptedContentInfo.encryptedContent) { + throw new Error("The filed `encryptedContent` in EncryptedContentInfo is empty"); + } + + //#endregion + + //#region Initialize internal values + this.encryptionAlgorithm = cmsEncrypted.encryptedContentInfo.contentEncryptionAlgorithm; + this.encryptedData = cmsEncrypted.encryptedContentInfo.encryptedContent; + //#endregion + } + +} diff --git a/third_party/js/PKI.js/src/PKIStatusInfo.ts b/third_party/js/PKI.js/src/PKIStatusInfo.ts new file mode 100644 index 0000000000..dca95b9a24 --- /dev/null +++ b/third_party/js/PKI.js/src/PKIStatusInfo.ts @@ -0,0 +1,224 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const STATUS = "status"; +const STATUS_STRINGS = "statusStrings"; +const FAIL_INFO = "failInfo"; +const CLEAR_PROPS = [ + STATUS, + STATUS_STRINGS, + FAIL_INFO +]; + +export interface IPKIStatusInfo { + status: PKIStatus; + statusStrings?: asn1js.Utf8String[]; + failInfo?: asn1js.BitString; +} + +export interface PKIStatusInfoJson { + status: PKIStatus; + statusStrings?: asn1js.Utf8StringJson[]; + failInfo?: asn1js.BitStringJson; +} + +export type PKIStatusInfoParameters = PkiObjectParameters & Partial<IPKIStatusInfo>; + +export type PKIStatusInfoSchema = Schema.SchemaParameters<{ + status?: string; + statusStrings?: string; + failInfo?: string; +}>; + +export enum PKIStatus { + granted = 0, + grantedWithMods = 1, + rejection = 2, + waiting = 3, + revocationWarning = 4, + revocationNotification = 5, +} + +/** + * Represents the PKIStatusInfo structure described in [RFC3161](https://www.ietf.org/rfc/rfc3161.txt) + */ +export class PKIStatusInfo extends PkiObject implements IPKIStatusInfo { + + public static override CLASS_NAME = "PKIStatusInfo"; + + public status!: PKIStatus; + public statusStrings?: asn1js.Utf8String[]; + public failInfo?: asn1js.BitString; + + /** + * Initializes a new instance of the {@link PBKDF2Params} class + * @param parameters Initialization parameters + */ + constructor(parameters: PKIStatusInfoParameters = {}) { + super(); + + this.status = pvutils.getParametersValue(parameters, STATUS, PKIStatusInfo.defaultValues(STATUS)); + if (STATUS_STRINGS in parameters) { + this.statusStrings = pvutils.getParametersValue(parameters, STATUS_STRINGS, PKIStatusInfo.defaultValues(STATUS_STRINGS)); + } + if (FAIL_INFO in parameters) { + this.failInfo = pvutils.getParametersValue(parameters, FAIL_INFO, PKIStatusInfo.defaultValues(FAIL_INFO)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof STATUS): number; + public static override defaultValues(memberName: typeof STATUS_STRINGS): asn1js.Utf8String[]; + public static override defaultValues(memberName: typeof FAIL_INFO): asn1js.BitString; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case STATUS: + return 2; + case STATUS_STRINGS: + return []; + case FAIL_INFO: + return new asn1js.BitString(); + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case STATUS: + return (memberValue === PKIStatusInfo.defaultValues(memberName)); + case STATUS_STRINGS: + return (memberValue.length === 0); + case FAIL_INFO: + return (memberValue.isEqual(PKIStatusInfo.defaultValues(memberName))); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * PKIStatusInfo ::= SEQUENCE { + * status PKIStatus, + * statusString PKIFreeText OPTIONAL, + * failInfo PKIFailureInfo OPTIONAL } + *``` + */ + public static override schema(parameters: PKIStatusInfoSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Integer({ name: (names.status || EMPTY_STRING) }), + new asn1js.Sequence({ + optional: true, + value: [ + new asn1js.Repeated({ + name: (names.statusStrings || EMPTY_STRING), + value: new asn1js.Utf8String() + }) + ] + }), + new asn1js.BitString({ + name: (names.failInfo || EMPTY_STRING), + optional: true + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + PKIStatusInfo.schema({ + names: { + status: STATUS, + statusStrings: STATUS_STRINGS, + failInfo: FAIL_INFO + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + //#region Get internal properties from parsed schema + const _status = asn1.result.status; + + if ((_status.valueBlock.isHexOnly === true) || + (_status.valueBlock.valueDec < 0) || + (_status.valueBlock.valueDec > 5)) + throw new Error("PKIStatusInfo \"status\" has invalid value"); + + this.status = _status.valueBlock.valueDec; + + if (STATUS_STRINGS in asn1.result) + this.statusStrings = asn1.result.statusStrings; + if (FAIL_INFO in asn1.result) + this.failInfo = asn1.result.failInfo; + //#endregion + } + + public toSchema(): asn1js.Sequence { + //#region Create array of output sequence + const outputArray = []; + + outputArray.push(new asn1js.Integer({ value: this.status })); + + if (this.statusStrings) { + outputArray.push(new asn1js.Sequence({ + optional: true, + value: this.statusStrings + })); + } + + if (this.failInfo) { + outputArray.push(this.failInfo); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): PKIStatusInfoJson { + const res: PKIStatusInfoJson = { + status: this.status + }; + + if (this.statusStrings) { + res.statusStrings = Array.from(this.statusStrings, o => o.toJSON()); + } + if (this.failInfo) { + res.failInfo = this.failInfo.toJSON(); + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/PasswordRecipientinfo.ts b/third_party/js/PKI.js/src/PasswordRecipientinfo.ts new file mode 100644 index 0000000000..4425b400e5 --- /dev/null +++ b/third_party/js/PKI.js/src/PasswordRecipientinfo.ts @@ -0,0 +1,235 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier"; +import { EMPTY_BUFFER, EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const VERSION = "version"; +const KEY_DERIVATION_ALGORITHM = "keyDerivationAlgorithm"; +const KEY_ENCRYPTION_ALGORITHM = "keyEncryptionAlgorithm"; +const ENCRYPTED_KEY = "encryptedKey"; +const PASSWORD = "password"; +const CLEAR_PROPS = [ + VERSION, + KEY_DERIVATION_ALGORITHM, + KEY_ENCRYPTION_ALGORITHM, + ENCRYPTED_KEY +]; + +export interface IPasswordRecipientInfo { + version: number; + keyDerivationAlgorithm?: AlgorithmIdentifier; + keyEncryptionAlgorithm: AlgorithmIdentifier; + encryptedKey: asn1js.OctetString; + password: ArrayBuffer; +} + +export interface PasswordRecipientInfoJson { + version: number; + keyDerivationAlgorithm?: AlgorithmIdentifierJson; + keyEncryptionAlgorithm: AlgorithmIdentifierJson; + encryptedKey: asn1js.OctetStringJson; +} + +export type PasswordRecipientinfoParameters = PkiObjectParameters & Partial<IPasswordRecipientInfo>; + +/** + * Represents the PasswordRecipientInfo structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + */ +// TODO rename to PasswordRecipientInfo +export class PasswordRecipientinfo extends PkiObject implements IPasswordRecipientInfo { + + public static override CLASS_NAME = "PasswordRecipientInfo"; + + public version!: number; + public keyDerivationAlgorithm?: AlgorithmIdentifier; + public keyEncryptionAlgorithm!: AlgorithmIdentifier; + public encryptedKey!: asn1js.OctetString; + public password!: ArrayBuffer; + + /** + * Initializes a new instance of the {@link PasswordRecipientinfo} class + * @param parameters Initialization parameters + */ + constructor(parameters: PasswordRecipientinfoParameters = {}) { + super(); + + this.version = pvutils.getParametersValue(parameters, VERSION, PasswordRecipientinfo.defaultValues(VERSION)); + if (KEY_DERIVATION_ALGORITHM in parameters) { + this.keyDerivationAlgorithm = pvutils.getParametersValue(parameters, KEY_DERIVATION_ALGORITHM, PasswordRecipientinfo.defaultValues(KEY_DERIVATION_ALGORITHM)); + } + this.keyEncryptionAlgorithm = pvutils.getParametersValue(parameters, KEY_ENCRYPTION_ALGORITHM, PasswordRecipientinfo.defaultValues(KEY_ENCRYPTION_ALGORITHM)); + this.encryptedKey = pvutils.getParametersValue(parameters, ENCRYPTED_KEY, PasswordRecipientinfo.defaultValues(ENCRYPTED_KEY)); + this.password = pvutils.getParametersValue(parameters, PASSWORD, PasswordRecipientinfo.defaultValues(PASSWORD)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof VERSION): number; + public static override defaultValues(memberName: typeof KEY_DERIVATION_ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof KEY_ENCRYPTION_ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof ENCRYPTED_KEY): asn1js.OctetString; + public static override defaultValues(memberName: typeof PASSWORD): ArrayBuffer; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case VERSION: + return (-1); + case KEY_DERIVATION_ALGORITHM: + return new AlgorithmIdentifier(); + case KEY_ENCRYPTION_ALGORITHM: + return new AlgorithmIdentifier(); + case ENCRYPTED_KEY: + return new asn1js.OctetString(); + case PASSWORD: + return EMPTY_BUFFER; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case VERSION: + return (memberValue === (-1)); + case KEY_DERIVATION_ALGORITHM: + case KEY_ENCRYPTION_ALGORITHM: + return ((memberValue.algorithmId === EMPTY_STRING) && (("algorithmParams" in memberValue) === false)); + case ENCRYPTED_KEY: + return (memberValue.isEqual(PasswordRecipientinfo.defaultValues(ENCRYPTED_KEY))); + case PASSWORD: + return (memberValue.byteLength === 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * PasswordRecipientInfo ::= SEQUENCE { + * version CMSVersion, -- Always set to 0 + * keyDerivationAlgorithm [0] KeyDerivationAlgorithmIdentifier OPTIONAL, + * keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier, + * encryptedKey EncryptedKey } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + version?: string; + keyDerivationAlgorithm?: string; + keyEncryptionAlgorithm?: AlgorithmIdentifierSchema; + encryptedKey?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Integer({ name: (names.version || EMPTY_STRING) }), + new asn1js.Constructed({ + name: (names.keyDerivationAlgorithm || EMPTY_STRING), + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: AlgorithmIdentifier.schema().valueBlock.value + }), + AlgorithmIdentifier.schema(names.keyEncryptionAlgorithm || {}), + new asn1js.OctetString({ name: (names.encryptedKey || EMPTY_STRING) }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + PasswordRecipientinfo.schema({ + names: { + version: VERSION, + keyDerivationAlgorithm: KEY_DERIVATION_ALGORITHM, + keyEncryptionAlgorithm: { + names: { + blockName: KEY_ENCRYPTION_ALGORITHM + } + }, + encryptedKey: ENCRYPTED_KEY + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.version = asn1.result.version.valueBlock.valueDec; + + if (KEY_DERIVATION_ALGORITHM in asn1.result) { + this.keyDerivationAlgorithm = new AlgorithmIdentifier({ + schema: new asn1js.Sequence({ + value: asn1.result.keyDerivationAlgorithm.valueBlock.value + }) + }); + } + this.keyEncryptionAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.keyEncryptionAlgorithm }); + this.encryptedKey = asn1.result.encryptedKey; + } + + public toSchema(): asn1js.Sequence { + //#region Create output array for sequence + const outputArray = []; + + outputArray.push(new asn1js.Integer({ value: this.version })); + + if (this.keyDerivationAlgorithm) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: this.keyDerivationAlgorithm.toSchema().valueBlock.value + })); + } + + outputArray.push(this.keyEncryptionAlgorithm.toSchema()); + outputArray.push(this.encryptedKey); + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): PasswordRecipientInfoJson { + const res: PasswordRecipientInfoJson = { + version: this.version, + keyEncryptionAlgorithm: this.keyEncryptionAlgorithm.toJSON(), + encryptedKey: this.encryptedKey.toJSON(), + }; + + if (this.keyDerivationAlgorithm) { + res.keyDerivationAlgorithm = this.keyDerivationAlgorithm.toJSON(); + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/PkiObject.ts b/third_party/js/PKI.js/src/PkiObject.ts new file mode 100644 index 0000000000..780d6fc328 --- /dev/null +++ b/third_party/js/PKI.js/src/PkiObject.ts @@ -0,0 +1,100 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import * as asn1js from "asn1js"; +import * as pvtsutils from "pvtsutils"; +import { AsnError } from "./errors"; +import * as Schema from "./Schema"; + +export interface PkiObjectParameters { + schema?: Schema.SchemaType; +} + +interface PkiObjectConstructor<T extends PkiObject = PkiObject> { + new(params: PkiObjectParameters): T; + CLASS_NAME: string; +} + +export abstract class PkiObject { + + /** + * Name of the class + */ + public static CLASS_NAME = "PkiObject"; + + /** + * Returns block name + * @returns Returns string block name + */ + public static blockName(): string { + return this.CLASS_NAME; + } + + /** + * Creates PKI object from the raw data + * @param raw ASN.1 encoded raw data + * @returns Initialized and filled current class object + */ + public static fromBER<T extends PkiObject>(this: PkiObjectConstructor<T>, raw: BufferSource): T { + const asn1 = asn1js.fromBER(raw); + AsnError.assert(asn1, this.name); + + try { + return new this({ schema: asn1.result }); + } catch (e) { + throw new AsnError(`Cannot create '${this.CLASS_NAME}' from ASN.1 object`); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static defaultValues(memberName: string): any { + throw new Error(`Invalid member name for ${this.CLASS_NAME} class: ${memberName}`); + } + + /** + * Returns value of pre-defined ASN.1 schema for current class + * @param parameters Input parameters for the schema + * @returns ASN.1 schema object + */ + public static schema(parameters: Schema.SchemaParameters = {}): Schema.SchemaType { + throw new Error(`Method '${this.CLASS_NAME}.schema' should be overridden`); + } + + public get className(): string { + return (this.constructor as unknown as { CLASS_NAME: string; }).CLASS_NAME; + } + + /** + * Converts parsed ASN.1 object into current class + * @param schema ASN.1 schema + */ + public abstract fromSchema(schema: Schema.SchemaType): void; + + /** + * Converts current object to ASN.1 object and sets correct values + * @param encodeFlag If param equal to `false` then creates schema via decoding stored value. In other case creates schema via assembling from cached parts + * @returns ASN.1 object + */ + public abstract toSchema(encodeFlag?: boolean): Schema.SchemaType; + + /** + * Converts the class to JSON object + * @returns JSON object + */ + public abstract toJSON(): any; + + public toString(encoding: "hex" | "base64" | "base64url" = "hex"): string { + let schema: Schema.SchemaType; + + try { + schema = this.toSchema(); + } catch { + schema = this.toSchema(true); + } + + return pvtsutils.Convert.ToString(schema.toBER(), encoding); + } + +}
\ No newline at end of file diff --git a/third_party/js/PKI.js/src/PolicyConstraints.ts b/third_party/js/PKI.js/src/PolicyConstraints.ts new file mode 100644 index 0000000000..9042b6daa1 --- /dev/null +++ b/third_party/js/PKI.js/src/PolicyConstraints.ts @@ -0,0 +1,203 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const REQUIRE_EXPLICIT_POLICY = "requireExplicitPolicy"; +const INHIBIT_POLICY_MAPPING = "inhibitPolicyMapping"; +const CLEAR_PROPS = [ + REQUIRE_EXPLICIT_POLICY, + INHIBIT_POLICY_MAPPING, +]; + +export interface IPolicyConstraints { + requireExplicitPolicy?: number; + inhibitPolicyMapping?: number; +} + +export interface PolicyConstraintsJson { + requireExplicitPolicy?: number; + inhibitPolicyMapping?: number; +} + +export type PolicyConstraintsParameters = PkiObjectParameters & Partial<IPolicyConstraints>; + +/** + * Represents the PolicyConstraints structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class PolicyConstraints extends PkiObject implements IPolicyConstraints { + + public static override CLASS_NAME = "PolicyConstraints"; + + public requireExplicitPolicy?: number; + public inhibitPolicyMapping?: number; + + /** + * Initializes a new instance of the {@link PolicyConstraints} class + * @param parameters Initialization parameters + */ + constructor(parameters: PolicyConstraintsParameters = {}) { + super(); + + if (REQUIRE_EXPLICIT_POLICY in parameters) { + this.requireExplicitPolicy = pvutils.getParametersValue(parameters, REQUIRE_EXPLICIT_POLICY, PolicyConstraints.defaultValues(REQUIRE_EXPLICIT_POLICY)); + } + if (INHIBIT_POLICY_MAPPING in parameters) { + this.inhibitPolicyMapping = pvutils.getParametersValue(parameters, INHIBIT_POLICY_MAPPING, PolicyConstraints.defaultValues(INHIBIT_POLICY_MAPPING)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof REQUIRE_EXPLICIT_POLICY): number; + public static override defaultValues(memberName: typeof INHIBIT_POLICY_MAPPING): number; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case REQUIRE_EXPLICIT_POLICY: + return 0; + case INHIBIT_POLICY_MAPPING: + return 0; + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * PolicyConstraints ::= SEQUENCE { + * requireExplicitPolicy [0] SkipCerts OPTIONAL, + * inhibitPolicyMapping [1] SkipCerts OPTIONAL } + * + * SkipCerts ::= INTEGER (0..MAX) + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + requireExplicitPolicy?: string; + inhibitPolicyMapping?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Primitive({ + name: (names.requireExplicitPolicy || EMPTY_STRING), + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + } + }), // IMPLICIT integer value + new asn1js.Primitive({ + name: (names.inhibitPolicyMapping || EMPTY_STRING), + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + } + }) // IMPLICIT integer value + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + PolicyConstraints.schema({ + names: { + requireExplicitPolicy: REQUIRE_EXPLICIT_POLICY, + inhibitPolicyMapping: INHIBIT_POLICY_MAPPING + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + //#region Get internal properties from parsed schema + if (REQUIRE_EXPLICIT_POLICY in asn1.result) { + const field1 = asn1.result.requireExplicitPolicy; + + field1.idBlock.tagClass = 1; // UNIVERSAL + field1.idBlock.tagNumber = 2; // INTEGER + + const ber1 = field1.toBER(false); + const int1 = asn1js.fromBER(ber1); + AsnError.assert(int1, "Integer"); + + this.requireExplicitPolicy = (int1.result as asn1js.Integer).valueBlock.valueDec; + } + + if (INHIBIT_POLICY_MAPPING in asn1.result) { + const field2 = asn1.result.inhibitPolicyMapping; + + field2.idBlock.tagClass = 1; // UNIVERSAL + field2.idBlock.tagNumber = 2; // INTEGER + + const ber2 = field2.toBER(false); + const int2 = asn1js.fromBER(ber2); + AsnError.assert(int2, "Integer"); + + this.inhibitPolicyMapping = (int2.result as asn1js.Integer).valueBlock.valueDec; + } + //#endregion + } + + public toSchema(): asn1js.Sequence { + //#region Create correct values for output sequence + const outputArray = []; + + if (REQUIRE_EXPLICIT_POLICY in this) { + const int1 = new asn1js.Integer({ value: this.requireExplicitPolicy }); + + int1.idBlock.tagClass = 3; // CONTEXT-SPECIFIC + int1.idBlock.tagNumber = 0; // [0] + + outputArray.push(int1); + } + + if (INHIBIT_POLICY_MAPPING in this) { + const int2 = new asn1js.Integer({ value: this.inhibitPolicyMapping }); + + int2.idBlock.tagClass = 3; // CONTEXT-SPECIFIC + int2.idBlock.tagNumber = 1; // [1] + + outputArray.push(int2); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): PolicyConstraintsJson { + const res: PolicyConstraintsJson = {}; + + if (REQUIRE_EXPLICIT_POLICY in this) { + res.requireExplicitPolicy = this.requireExplicitPolicy; + } + + if (INHIBIT_POLICY_MAPPING in this) { + res.inhibitPolicyMapping = this.inhibitPolicyMapping; + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/PolicyInformation.ts b/third_party/js/PKI.js/src/PolicyInformation.ts new file mode 100644 index 0000000000..4a6362f844 --- /dev/null +++ b/third_party/js/PKI.js/src/PolicyInformation.ts @@ -0,0 +1,160 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { PolicyQualifierInfo, PolicyQualifierInfoJson } from "./PolicyQualifierInfo"; +import * as Schema from "./Schema"; + +export const POLICY_IDENTIFIER = "policyIdentifier"; +export const POLICY_QUALIFIERS = "policyQualifiers"; +const CLEAR_PROPS = [ + POLICY_IDENTIFIER, + POLICY_QUALIFIERS +]; + +export interface IPolicyInformation { + policyIdentifier: string; + policyQualifiers?: PolicyQualifierInfo[]; +} + +export type PolicyInformationParameters = PkiObjectParameters & Partial<IPolicyInformation>; + +export interface PolicyInformationJson { + policyIdentifier: string; + policyQualifiers?: PolicyQualifierInfoJson[]; +} + +/** + * Represents the PolicyInformation structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class PolicyInformation extends PkiObject implements IPolicyInformation { + + public static override CLASS_NAME = "PolicyInformation"; + + public policyIdentifier!: string; + public policyQualifiers?: PolicyQualifierInfo[]; + + /** + * Initializes a new instance of the {@link PolicyInformation} class + * @param parameters Initialization parameters + */ + constructor(parameters: PolicyInformationParameters = {}) { + super(); + + this.policyIdentifier = pvutils.getParametersValue(parameters, POLICY_IDENTIFIER, PolicyInformation.defaultValues(POLICY_IDENTIFIER)); + if (POLICY_QUALIFIERS in parameters) { + this.policyQualifiers = pvutils.getParametersValue(parameters, POLICY_QUALIFIERS, PolicyInformation.defaultValues(POLICY_QUALIFIERS)); + } + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof POLICY_IDENTIFIER): string; + public static override defaultValues(memberName: typeof POLICY_QUALIFIERS): PolicyQualifierInfo[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case POLICY_IDENTIFIER: + return EMPTY_STRING; + case POLICY_QUALIFIERS: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * PolicyInformation ::= SEQUENCE { + * policyIdentifier CertPolicyId, + * policyQualifiers SEQUENCE SIZE (1..MAX) OF + * PolicyQualifierInfo OPTIONAL } + * + * CertPolicyId ::= OBJECT IDENTIFIER + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ policyIdentifier?: string; policyQualifiers?: string; }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.ObjectIdentifier({ name: (names.policyIdentifier || EMPTY_STRING) }), + new asn1js.Sequence({ + optional: true, + value: [ + new asn1js.Repeated({ + name: (names.policyQualifiers || EMPTY_STRING), + value: PolicyQualifierInfo.schema() + }) + ] + }) + ] + })); + } + + /** + * Converts parsed ASN.1 object into current class + * @param schema + */ + fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + PolicyInformation.schema({ + names: { + policyIdentifier: POLICY_IDENTIFIER, + policyQualifiers: POLICY_QUALIFIERS + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.policyIdentifier = asn1.result.policyIdentifier.valueBlock.toString(); + if (POLICY_QUALIFIERS in asn1.result) { + this.policyQualifiers = Array.from(asn1.result.policyQualifiers, element => new PolicyQualifierInfo({ schema: element })); + } + } + + public toSchema(): asn1js.Sequence { + //Create array for output sequence + const outputArray = []; + + outputArray.push(new asn1js.ObjectIdentifier({ value: this.policyIdentifier })); + + if (this.policyQualifiers) { + outputArray.push(new asn1js.Sequence({ + value: Array.from(this.policyQualifiers, o => o.toSchema()) + })); + } + + // Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + } + + public toJSON(): PolicyInformationJson { + const res: PolicyInformationJson = { + policyIdentifier: this.policyIdentifier + }; + + if (this.policyQualifiers) + res.policyQualifiers = Array.from(this.policyQualifiers, o => o.toJSON()); + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/PolicyMapping.ts b/third_party/js/PKI.js/src/PolicyMapping.ts new file mode 100644 index 0000000000..fd4dc1e414 --- /dev/null +++ b/third_party/js/PKI.js/src/PolicyMapping.ts @@ -0,0 +1,132 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const ISSUER_DOMAIN_POLICY = "issuerDomainPolicy"; +const SUBJECT_DOMAIN_POLICY = "subjectDomainPolicy"; +const CLEAR_PROPS = [ + ISSUER_DOMAIN_POLICY, + SUBJECT_DOMAIN_POLICY +]; + +export interface IPolicyMapping { + issuerDomainPolicy: string; + subjectDomainPolicy: string; +} + +export interface PolicyMappingJson { + issuerDomainPolicy: string; + subjectDomainPolicy: string; +} + +export type PolicyMappingParameters = PkiObjectParameters & Partial<IPolicyMapping>; + +/** + * Represents the PolicyMapping structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class PolicyMapping extends PkiObject implements IPolicyMapping { + + public static override CLASS_NAME = "PolicyMapping"; + + public issuerDomainPolicy!: string; + public subjectDomainPolicy!: string; + + /** + * Initializes a new instance of the {@link PolicyMapping} class + * @param parameters Initialization parameters + */ + constructor(parameters: PolicyMappingParameters = {}) { + super(); + + this.issuerDomainPolicy = pvutils.getParametersValue(parameters, ISSUER_DOMAIN_POLICY, PolicyMapping.defaultValues(ISSUER_DOMAIN_POLICY)); + this.subjectDomainPolicy = pvutils.getParametersValue(parameters, SUBJECT_DOMAIN_POLICY, PolicyMapping.defaultValues(SUBJECT_DOMAIN_POLICY)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof ISSUER_DOMAIN_POLICY): string; + public static override defaultValues(memberName: typeof SUBJECT_DOMAIN_POLICY): string; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case ISSUER_DOMAIN_POLICY: + return EMPTY_STRING; + case SUBJECT_DOMAIN_POLICY: + return EMPTY_STRING; + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * PolicyMapping ::= SEQUENCE { + * issuerDomainPolicy CertPolicyId, + * subjectDomainPolicy CertPolicyId } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + issuerDomainPolicy?: string; + subjectDomainPolicy?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.ObjectIdentifier({ name: (names.issuerDomainPolicy || EMPTY_STRING) }), + new asn1js.ObjectIdentifier({ name: (names.subjectDomainPolicy || EMPTY_STRING) }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + PolicyMapping.schema({ + names: { + issuerDomainPolicy: ISSUER_DOMAIN_POLICY, + subjectDomainPolicy: SUBJECT_DOMAIN_POLICY + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.issuerDomainPolicy = asn1.result.issuerDomainPolicy.valueBlock.toString(); + this.subjectDomainPolicy = asn1.result.subjectDomainPolicy.valueBlock.toString(); + } + + public toSchema(): asn1js.Sequence { + // Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: [ + new asn1js.ObjectIdentifier({ value: this.issuerDomainPolicy }), + new asn1js.ObjectIdentifier({ value: this.subjectDomainPolicy }) + ] + })); + } + + public toJSON(): PolicyMappingJson { + return { + issuerDomainPolicy: this.issuerDomainPolicy, + subjectDomainPolicy: this.subjectDomainPolicy + }; + } + +} diff --git a/third_party/js/PKI.js/src/PolicyMappings.ts b/third_party/js/PKI.js/src/PolicyMappings.ts new file mode 100644 index 0000000000..5d43763ab6 --- /dev/null +++ b/third_party/js/PKI.js/src/PolicyMappings.ts @@ -0,0 +1,117 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { PolicyMapping, PolicyMappingJson } from "./PolicyMapping"; +import * as Schema from "./Schema"; + +const MAPPINGS = "mappings"; +const CLEAR_PROPS = [ + MAPPINGS, +]; + +export interface IPolicyMappings { + mappings: PolicyMapping[]; +} + +export interface PolicyMappingsJson { + mappings: PolicyMappingJson[]; +} + +export type PolicyMappingsParameters = PkiObjectParameters & Partial<IPolicyMappings>; + +/** + * Represents the PolicyMappings structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class PolicyMappings extends PkiObject implements IPolicyMappings { + + public static override CLASS_NAME = "PolicyMappings"; + + public mappings!: PolicyMapping[]; + + /** + * Initializes a new instance of the {@link PolicyMappings} class + * @param parameters Initialization parameters + */ + constructor(parameters: PolicyMappingsParameters = {}) { + super(); + + this.mappings = pvutils.getParametersValue(parameters, MAPPINGS, PolicyMappings.defaultValues(MAPPINGS)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: string): PolicyMapping[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case MAPPINGS: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * PolicyMappings ::= SEQUENCE SIZE (1..MAX) OF PolicyMapping + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + mappings?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Repeated({ + name: (names.mappings || EMPTY_STRING), + value: PolicyMapping.schema() + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + PolicyMappings.schema({ + names: { + mappings: MAPPINGS + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.mappings = Array.from(asn1.result.mappings, element => new PolicyMapping({ schema: element })); + } + + public toSchema(): asn1js.Sequence { + // Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: Array.from(this.mappings, o => o.toSchema()) + })); + } + + public toJSON(): PolicyMappingsJson { + return { + mappings: Array.from(this.mappings, o => o.toJSON()) + }; + } + +} diff --git a/third_party/js/PKI.js/src/PolicyQualifierInfo.ts b/third_party/js/PKI.js/src/PolicyQualifierInfo.ts new file mode 100644 index 0000000000..f800a9d537 --- /dev/null +++ b/third_party/js/PKI.js/src/PolicyQualifierInfo.ts @@ -0,0 +1,134 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const POLICY_QUALIFIER_ID = "policyQualifierId"; +const QUALIFIER = "qualifier"; +const CLEAR_PROPS = [ + POLICY_QUALIFIER_ID, + QUALIFIER +]; + +export interface IPolicyQualifierInfo { + policyQualifierId: string; + qualifier: Schema.SchemaType; +} + +export type PolicyQualifierInfoParameters = PkiObjectParameters & Partial<IPolicyQualifierInfo>; + +export interface PolicyQualifierInfoJson { + policyQualifierId: string; + qualifier: any; +} + +/** + * Represents the PolicyQualifierInfo structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class PolicyQualifierInfo extends PkiObject implements IPolicyQualifierInfo { + + public static override CLASS_NAME = "PolicyQualifierInfo"; + + public policyQualifierId!: string; + public qualifier: Schema.SchemaType; + + /** + * Initializes a new instance of the {@link PolicyQualifierInfo} class + * @param parameters Initialization parameters + */ + constructor(parameters: PolicyQualifierInfoParameters = {}) { + super(); + + this.policyQualifierId = pvutils.getParametersValue(parameters, POLICY_QUALIFIER_ID, PolicyQualifierInfo.defaultValues(POLICY_QUALIFIER_ID)); + this.qualifier = pvutils.getParametersValue(parameters, QUALIFIER, PolicyQualifierInfo.defaultValues(QUALIFIER)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof POLICY_QUALIFIER_ID): string; + public static override defaultValues(memberName: typeof QUALIFIER): asn1js.Any; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case POLICY_QUALIFIER_ID: + return EMPTY_STRING; + case QUALIFIER: + return new asn1js.Any(); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * PolicyQualifierInfo ::= SEQUENCE { + * policyQualifierId PolicyQualifierId, + * qualifier ANY DEFINED BY policyQualifierId } + * + * id-qt OBJECT IDENTIFIER ::= { id-pkix 2 } + * id-qt-cps OBJECT IDENTIFIER ::= { id-qt 1 } + * id-qt-unotice OBJECT IDENTIFIER ::= { id-qt 2 } + * + * PolicyQualifierId ::= OBJECT IDENTIFIER ( id-qt-cps | id-qt-unotice ) + *``` + */ + static override schema(parameters: Schema.SchemaParameters<{ policyQualifierId?: string; qualifier?: string; }> = {}) { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.ObjectIdentifier({ name: (names.policyQualifierId || EMPTY_STRING) }), + new asn1js.Any({ name: (names.qualifier || EMPTY_STRING) }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + PolicyQualifierInfo.schema({ + names: { + policyQualifierId: POLICY_QUALIFIER_ID, + qualifier: QUALIFIER + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.policyQualifierId = asn1.result.policyQualifierId.valueBlock.toString(); + this.qualifier = asn1.result.qualifier; + } + + public toSchema(): asn1js.Sequence { + return (new asn1js.Sequence({ + value: [ + new asn1js.ObjectIdentifier({ value: this.policyQualifierId }), + this.qualifier + ] + })); + } + + public toJSON(): PolicyQualifierInfoJson { + return { + policyQualifierId: this.policyQualifierId, + qualifier: this.qualifier.toJSON() + }; + } + +} diff --git a/third_party/js/PKI.js/src/PrivateKeyInfo.ts b/third_party/js/PKI.js/src/PrivateKeyInfo.ts new file mode 100644 index 0000000000..d8e9049fb1 --- /dev/null +++ b/third_party/js/PKI.js/src/PrivateKeyInfo.ts @@ -0,0 +1,303 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier"; +import { Attribute, AttributeJson } from "./Attribute"; +import { EMPTY_STRING } from "./constants"; +import { ECPrivateKey } from "./ECPrivateKey"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { RSAPrivateKey } from "./RSAPrivateKey"; +import * as Schema from "./Schema"; + +const VERSION = "version"; +const PRIVATE_KEY_ALGORITHM = "privateKeyAlgorithm"; +const PRIVATE_KEY = "privateKey"; +const ATTRIBUTES = "attributes"; +const PARSED_KEY = "parsedKey"; +const CLEAR_PROPS = [ + VERSION, + PRIVATE_KEY_ALGORITHM, + PRIVATE_KEY, + ATTRIBUTES +]; + +export interface IPrivateKeyInfo { + version: number; + privateKeyAlgorithm: AlgorithmIdentifier; + privateKey: asn1js.OctetString; + attributes?: Attribute[]; + parsedKey?: RSAPrivateKey | ECPrivateKey; +} + +export type PrivateKeyInfoParameters = PkiObjectParameters & Partial<IPrivateKeyInfo> & { json?: JsonWebKey; }; + +export interface PrivateKeyInfoJson { + version: number; + privateKeyAlgorithm: AlgorithmIdentifierJson; + privateKey: asn1js.OctetStringJson; + attributes?: AttributeJson[]; +} + +/** + * Represents the PrivateKeyInfo structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5208) + */ +export class PrivateKeyInfo extends PkiObject implements IPrivateKeyInfo { + + public static override CLASS_NAME = "PrivateKeyInfo"; + + public version!: number; + public privateKeyAlgorithm!: AlgorithmIdentifier; + public privateKey!: asn1js.OctetString; + public attributes?: Attribute[]; + public parsedKey?: RSAPrivateKey | ECPrivateKey; + + /** + * Initializes a new instance of the {@link PrivateKeyInfo} class + * @param parameters Initialization parameters + */ + constructor(parameters: PrivateKeyInfoParameters = {}) { + super(); + + this.version = pvutils.getParametersValue(parameters, VERSION, PrivateKeyInfo.defaultValues(VERSION)); + this.privateKeyAlgorithm = pvutils.getParametersValue(parameters, PRIVATE_KEY_ALGORITHM, PrivateKeyInfo.defaultValues(PRIVATE_KEY_ALGORITHM)); + this.privateKey = pvutils.getParametersValue(parameters, PRIVATE_KEY, PrivateKeyInfo.defaultValues(PRIVATE_KEY)); + if (ATTRIBUTES in parameters) { + this.attributes = pvutils.getParametersValue(parameters, ATTRIBUTES, PrivateKeyInfo.defaultValues(ATTRIBUTES)); + } + if (PARSED_KEY in parameters) { + this.parsedKey = pvutils.getParametersValue(parameters, PARSED_KEY, PrivateKeyInfo.defaultValues(PARSED_KEY)); + } + + if (parameters.json) { + this.fromJSON(parameters.json); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: string): any { + switch (memberName) { + case VERSION: + return 0; + case PRIVATE_KEY_ALGORITHM: + return new AlgorithmIdentifier(); + case PRIVATE_KEY: + return new asn1js.OctetString(); + case ATTRIBUTES: + return []; + case PARSED_KEY: + return {}; + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * PrivateKeyInfo ::= SEQUENCE { + * version Version, + * privateKeyAlgorithm AlgorithmIdentifier {{PrivateKeyAlgorithms}}, + * privateKey PrivateKey, + * attributes [0] Attributes OPTIONAL } + * + * Version ::= INTEGER {v1(0)} (v1,...) + * + * PrivateKey ::= OCTET STRING + * + * Attributes ::= SET OF Attribute + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + version?: string; + privateKeyAlgorithm?: AlgorithmIdentifierSchema; + privateKey?: string; + attributes?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Integer({ name: (names.version || EMPTY_STRING) }), + AlgorithmIdentifier.schema(names.privateKeyAlgorithm || {}), + new asn1js.OctetString({ name: (names.privateKey || EMPTY_STRING) }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + new asn1js.Repeated({ + name: (names.attributes || EMPTY_STRING), + value: Attribute.schema() + }) + ] + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + PrivateKeyInfo.schema({ + names: { + version: VERSION, + privateKeyAlgorithm: { + names: { + blockName: PRIVATE_KEY_ALGORITHM + } + }, + privateKey: PRIVATE_KEY, + attributes: ATTRIBUTES + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + //#region Get internal properties from parsed schema + this.version = asn1.result.version.valueBlock.valueDec; + this.privateKeyAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.privateKeyAlgorithm }); + this.privateKey = asn1.result.privateKey; + + if (ATTRIBUTES in asn1.result) + this.attributes = Array.from(asn1.result.attributes, element => new Attribute({ schema: element })); + + // TODO Use factory + switch (this.privateKeyAlgorithm.algorithmId) { + case "1.2.840.113549.1.1.1": // RSA + { + const privateKeyASN1 = asn1js.fromBER(this.privateKey.valueBlock.valueHexView); + if (privateKeyASN1.offset !== -1) + this.parsedKey = new RSAPrivateKey({ schema: privateKeyASN1.result }); + } + break; + case "1.2.840.10045.2.1": // ECDSA + if ("algorithmParams" in this.privateKeyAlgorithm) { + if (this.privateKeyAlgorithm.algorithmParams instanceof asn1js.ObjectIdentifier) { + const privateKeyASN1 = asn1js.fromBER(this.privateKey.valueBlock.valueHexView); + if (privateKeyASN1.offset !== -1) { + this.parsedKey = new ECPrivateKey({ + namedCurve: this.privateKeyAlgorithm.algorithmParams.valueBlock.toString(), + schema: privateKeyASN1.result + }); + } + } + } + break; + default: + } + //#endregion + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray: any = [ + new asn1js.Integer({ value: this.version }), + this.privateKeyAlgorithm.toSchema(), + this.privateKey + ]; + + if (this.attributes) { + outputArray.push(new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: Array.from(this.attributes, o => o.toSchema()) + })); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): PrivateKeyInfoJson | JsonWebKey { + //#region Return common value in case we do not have enough info fo making JWK + if (!this.parsedKey) { + const object: PrivateKeyInfoJson = { + version: this.version, + privateKeyAlgorithm: this.privateKeyAlgorithm.toJSON(), + privateKey: this.privateKey.toJSON(), + }; + + if (this.attributes) { + object.attributes = Array.from(this.attributes, o => o.toJSON()); + } + + return object; + } + //#endregion + + //#region Making JWK + const jwk: JsonWebKey = {}; + + switch (this.privateKeyAlgorithm.algorithmId) { + case "1.2.840.10045.2.1": // ECDSA + jwk.kty = "EC"; + break; + case "1.2.840.113549.1.1.1": // RSA + jwk.kty = "RSA"; + break; + default: + } + + // TODO Unclear behavior + const publicKeyJWK = this.parsedKey.toJSON(); + Object.assign(jwk, publicKeyJWK); + + return jwk; + //#endregion + } + + /** + * Converts JSON value into current object + * @param json JSON object + */ + public fromJSON(json: any): void { + if ("kty" in json) { + switch (json.kty.toUpperCase()) { + case "EC": + this.parsedKey = new ECPrivateKey({ json }); + + this.privateKeyAlgorithm = new AlgorithmIdentifier({ + algorithmId: "1.2.840.10045.2.1", + algorithmParams: new asn1js.ObjectIdentifier({ value: this.parsedKey.namedCurve }) + }); + break; + case "RSA": + this.parsedKey = new RSAPrivateKey({ json }); + + this.privateKeyAlgorithm = new AlgorithmIdentifier({ + algorithmId: "1.2.840.113549.1.1.1", + algorithmParams: new asn1js.Null() + }); + break; + default: + throw new Error(`Invalid value for "kty" parameter: ${json.kty}`); + } + + this.privateKey = new asn1js.OctetString({ valueHex: this.parsedKey.toSchema().toBER(false) }); + } + } + +} diff --git a/third_party/js/PKI.js/src/PrivateKeyUsagePeriod.ts b/third_party/js/PKI.js/src/PrivateKeyUsagePeriod.ts new file mode 100644 index 0000000000..f3aebd5045 --- /dev/null +++ b/third_party/js/PKI.js/src/PrivateKeyUsagePeriod.ts @@ -0,0 +1,191 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const NOT_BEFORE = "notBefore"; +const NOT_AFTER = "notAfter"; +const CLEAR_PROPS = [ + NOT_BEFORE, + NOT_AFTER +]; + +export interface IPrivateKeyUsagePeriod { + notBefore?: Date; + notAfter?: Date; +} + +export interface PrivateKeyUsagePeriodJson { + notBefore?: Date; + notAfter?: Date; +} + +export type PrivateKeyUsagePeriodParameters = PkiObjectParameters & Partial<IPrivateKeyUsagePeriod>; + +/** + * Represents the PrivateKeyUsagePeriod structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class PrivateKeyUsagePeriod extends PkiObject implements IPrivateKeyUsagePeriod { + + public static override CLASS_NAME = "PrivateKeyUsagePeriod"; + + public notBefore?: Date; + public notAfter?: Date; + + /** + * Initializes a new instance of the {@link PrivateKeyUsagePeriod} class + * @param parameters Initialization parameters + */ + constructor(parameters: PrivateKeyUsagePeriodParameters = {}) { + super(); + + if (NOT_BEFORE in parameters) { + this.notBefore = pvutils.getParametersValue(parameters, NOT_BEFORE, PrivateKeyUsagePeriod.defaultValues(NOT_BEFORE)); + } + + if (NOT_AFTER in parameters) { + this.notAfter = pvutils.getParametersValue(parameters, NOT_AFTER, PrivateKeyUsagePeriod.defaultValues(NOT_AFTER)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof NOT_BEFORE): Date; + public static override defaultValues(memberName: typeof NOT_AFTER): Date; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case NOT_BEFORE: + return new Date(); + case NOT_AFTER: + return new Date(); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * PrivateKeyUsagePeriod OID ::= 2.5.29.16 + * + * PrivateKeyUsagePeriod ::= SEQUENCE { + * notBefore [0] GeneralizedTime OPTIONAL, + * notAfter [1] GeneralizedTime OPTIONAL } + * -- either notBefore or notAfter MUST be present + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + notBefore?: string; + notAfter?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Primitive({ + name: (names.notBefore || EMPTY_STRING), + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + } + }), + new asn1js.Primitive({ + name: (names.notAfter || EMPTY_STRING), + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + } + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + PrivateKeyUsagePeriod.schema({ + names: { + notBefore: NOT_BEFORE, + notAfter: NOT_AFTER + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + if (NOT_BEFORE in asn1.result) { + const localNotBefore = new asn1js.GeneralizedTime(); + localNotBefore.fromBuffer(asn1.result.notBefore.valueBlock.valueHex); + this.notBefore = localNotBefore.toDate(); + } + if (NOT_AFTER in asn1.result) { + const localNotAfter = new asn1js.GeneralizedTime({ valueHex: asn1.result.notAfter.valueBlock.valueHex }); + localNotAfter.fromBuffer(asn1.result.notAfter.valueBlock.valueHex); + this.notAfter = localNotAfter.toDate(); + } + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray = []; + + if (NOT_BEFORE in this) { + outputArray.push(new asn1js.Primitive({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + valueHex: (new asn1js.GeneralizedTime({ valueDate: this.notBefore })).valueBlock.valueHexView + })); + } + + if (NOT_AFTER in this) { + outputArray.push(new asn1js.Primitive({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + valueHex: (new asn1js.GeneralizedTime({ valueDate: this.notAfter })).valueBlock.valueHexView + })); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): PrivateKeyUsagePeriodJson { + const res: PrivateKeyUsagePeriodJson = {}; + + if (this.notBefore) { + res.notBefore = this.notBefore; + } + + if (this.notAfter) { + res.notAfter = this.notAfter; + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/PublicKeyInfo.ts b/third_party/js/PKI.js/src/PublicKeyInfo.ts new file mode 100644 index 0000000000..468668bd64 --- /dev/null +++ b/third_party/js/PKI.js/src/PublicKeyInfo.ts @@ -0,0 +1,274 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import * as common from "./common"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier"; +import { ECPublicKey } from "./ECPublicKey"; +import { RSAPublicKey } from "./RSAPublicKey"; +import * as Schema from "./Schema"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { AsnError } from "./errors"; +import { EMPTY_STRING } from "./constants"; + +const ALGORITHM = "algorithm"; +const SUBJECT_PUBLIC_KEY = "subjectPublicKey"; +const CLEAR_PROPS = [ALGORITHM, SUBJECT_PUBLIC_KEY]; + +export interface IPublicKeyInfo { + /** + * Algorithm identifier + */ + algorithm: AlgorithmIdentifier; + /** + * Subject public key value + */ + subjectPublicKey: asn1js.BitString; + /** + * Parsed public key value + */ + parsedKey?: ECPublicKey | RSAPublicKey; +} +export type PublicKeyInfoParameters = PkiObjectParameters & Partial<IPublicKeyInfo> & { json?: JsonWebKey; }; + +export interface PublicKeyInfoJson { + algorithm: AlgorithmIdentifierJson; + subjectPublicKey: asn1js.BitStringJson; +} + +export type PublicKeyInfoSchema = Schema.SchemaParameters<{ + algorithm?: AlgorithmIdentifierSchema; + subjectPublicKey?: string; +}>; + +/** + * Represents the PublicKeyInfo structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class PublicKeyInfo extends PkiObject implements IPublicKeyInfo { + + public static override CLASS_NAME = "PublicKeyInfo"; + + public algorithm!: AlgorithmIdentifier; + public subjectPublicKey!: asn1js.BitString; + private _parsedKey?: ECPublicKey | RSAPublicKey | null; + public get parsedKey(): ECPublicKey | RSAPublicKey | undefined { + if (this._parsedKey === undefined) { + switch (this.algorithm.algorithmId) { + // TODO Use fabric + case "1.2.840.10045.2.1": // ECDSA + if ("algorithmParams" in this.algorithm) { + if (this.algorithm.algorithmParams.constructor.blockName() === asn1js.ObjectIdentifier.blockName()) { + try { + this._parsedKey = new ECPublicKey({ + namedCurve: this.algorithm.algorithmParams.valueBlock.toString(), + schema: this.subjectPublicKey.valueBlock.valueHexView + }); + } + catch (ex) { + // nothing + } // Could be a problems during recognition of internal public key data here. Let's ignore them. + } + } + break; + case "1.2.840.113549.1.1.1": // RSA + { + const publicKeyASN1 = asn1js.fromBER(this.subjectPublicKey.valueBlock.valueHexView); + if (publicKeyASN1.offset !== -1) { + try { + this._parsedKey = new RSAPublicKey({ schema: publicKeyASN1.result }); + } + catch (ex) { + // nothing + } // Could be a problems during recognition of internal public key data here. Let's ignore them. + } + } + break; + default: + } + + this._parsedKey ||= null; + } + + return this._parsedKey || undefined; + } + public set parsedKey(value: ECPublicKey | RSAPublicKey | undefined) { + this._parsedKey = value; + } + + /** + * Initializes a new instance of the {@link PublicKeyInfo} class + * @param parameters Initialization parameters + */ + constructor(parameters: PublicKeyInfoParameters = {}) { + super(); + + this.algorithm = pvutils.getParametersValue(parameters, ALGORITHM, PublicKeyInfo.defaultValues(ALGORITHM)); + this.subjectPublicKey = pvutils.getParametersValue(parameters, SUBJECT_PUBLIC_KEY, PublicKeyInfo.defaultValues(SUBJECT_PUBLIC_KEY)); + const parsedKey = pvutils.getParametersValue(parameters, "parsedKey", null); + if (parsedKey) { + this.parsedKey = parsedKey; + } + + if (parameters.json) { + this.fromJSON(parameters.json); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof SUBJECT_PUBLIC_KEY): asn1js.BitString; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case ALGORITHM: + return new AlgorithmIdentifier(); + case SUBJECT_PUBLIC_KEY: + return new asn1js.BitString(); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * SubjectPublicKeyInfo ::= Sequence { + * algorithm AlgorithmIdentifier, + * subjectPublicKey BIT STRING } + *``` + */ + public static override schema(parameters: PublicKeyInfoSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + AlgorithmIdentifier.schema(names.algorithm || {}), + new asn1js.BitString({ name: (names.subjectPublicKey || EMPTY_STRING) }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + PublicKeyInfo.schema({ + names: { + algorithm: { + names: { + blockName: ALGORITHM + } + }, + subjectPublicKey: SUBJECT_PUBLIC_KEY + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + //#region Get internal properties from parsed schema + this.algorithm = new AlgorithmIdentifier({ schema: asn1.result.algorithm }); + this.subjectPublicKey = asn1.result.subjectPublicKey; + //#endregion + } + + public toSchema(): asn1js.Sequence { + return (new asn1js.Sequence({ + value: [ + this.algorithm.toSchema(), + this.subjectPublicKey + ] + })); + } + + public toJSON(): PublicKeyInfoJson | JsonWebKey { + //#region Return common value in case we do not have enough info fo making JWK + if (!this.parsedKey) { + return { + algorithm: this.algorithm.toJSON(), + subjectPublicKey: this.subjectPublicKey.toJSON(), + }; + } + //#endregion + + //#region Making JWK + const jwk: JsonWebKey = {}; + + switch (this.algorithm.algorithmId) { + case "1.2.840.10045.2.1": // ECDSA + jwk.kty = "EC"; + break; + case "1.2.840.113549.1.1.1": // RSA + jwk.kty = "RSA"; + break; + default: + } + + const publicKeyJWK = this.parsedKey.toJSON(); + Object.assign(jwk, publicKeyJWK); + + return jwk; + //#endregion + } + + /** + * Converts JSON value into current object + * @param json JSON object + */ + public fromJSON(json: any): void { + if ("kty" in json) { + switch (json.kty.toUpperCase()) { + case "EC": + this.parsedKey = new ECPublicKey({ json }); + + this.algorithm = new AlgorithmIdentifier({ + algorithmId: "1.2.840.10045.2.1", + algorithmParams: new asn1js.ObjectIdentifier({ value: this.parsedKey.namedCurve }) + }); + break; + case "RSA": + this.parsedKey = new RSAPublicKey({ json }); + + this.algorithm = new AlgorithmIdentifier({ + algorithmId: "1.2.840.113549.1.1.1", + algorithmParams: new asn1js.Null() + }); + break; + default: + throw new Error(`Invalid value for "kty" parameter: ${json.kty}`); + } + + this.subjectPublicKey = new asn1js.BitString({ valueHex: this.parsedKey.toSchema().toBER(false) }); + } + } + + public async importKey(publicKey: CryptoKey, crypto = common.getCrypto(true)): Promise<void> { + try { + if (!publicKey) { + throw new Error("Need to provide publicKey input parameter"); + } + + const exportedKey = await crypto.exportKey("spki", publicKey); + const asn1 = asn1js.fromBER(exportedKey); + try { + this.fromSchema(asn1.result); + } + catch (exception) { + throw new Error("Error during initializing object from schema"); + } + } catch (e) { + const message = e instanceof Error ? e.message : `${e}`; + throw new Error(`Error during exporting public key: ${message}`); + } + } + +} diff --git a/third_party/js/PKI.js/src/QCStatements.ts b/third_party/js/PKI.js/src/QCStatements.ts new file mode 100644 index 0000000000..7484235e42 --- /dev/null +++ b/third_party/js/PKI.js/src/QCStatements.ts @@ -0,0 +1,295 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const ID = "id"; +const TYPE = "type"; +const VALUES = "values"; +const QC_STATEMENT_CLEAR_PROPS = [ + ID, + TYPE +]; +const QC_STATEMENTS_CLEAR_PROPS = [ + VALUES +]; + +export interface IQCStatement { + id: string; + type?: any; +} + +export interface QCStatementJson { + id: string; + type?: any; +} + +export type QCStatementParameters = PkiObjectParameters & Partial<IQCStatement>; + +export type QCStatementSchema = Schema.SchemaParameters<{ + id?: string; + type?: string; +}>; + +/** + * Represents the QCStatement structure described in [RFC3739](https://datatracker.ietf.org/doc/html/rfc3739) + */ +export class QCStatement extends PkiObject implements IQCStatement { + + public static override CLASS_NAME = "QCStatement"; + + public id!: string; + public type?: any; + + /** + * Initializes a new instance of the {@link QCStatement} class + * @param parameters Initialization parameters + */ + constructor(parameters: QCStatementParameters = {}) { + super(); + + this.id = pvutils.getParametersValue(parameters, ID, QCStatement.defaultValues(ID)); + if (TYPE in parameters) { + this.type = pvutils.getParametersValue(parameters, TYPE, QCStatement.defaultValues(TYPE)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof ID): string; + public static override defaultValues(memberName: typeof TYPE): any; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case ID: + return EMPTY_STRING; + case TYPE: + return new asn1js.Null(); + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case ID: + return (memberValue === EMPTY_STRING); + case TYPE: + return (memberValue instanceof asn1js.Null); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * QCStatement ::= SEQUENCE { + * statementId QC-STATEMENT.&id({SupportedStatements}), + * statementInfo QC-STATEMENT.&Type({SupportedStatements}{@statementId}) OPTIONAL + * } + *``` + */ + public static override schema(parameters: QCStatementSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.ObjectIdentifier({ name: (names.id || EMPTY_STRING) }), + new asn1js.Any({ + name: (names.type || EMPTY_STRING), + optional: true + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, QC_STATEMENT_CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + QCStatement.schema({ + names: { + id: ID, + type: TYPE + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.id = asn1.result.id.valueBlock.toString(); + if (TYPE in asn1.result) + this.type = asn1.result.type; + } + + public toSchema(): asn1js.Sequence { + const value = [ + new asn1js.ObjectIdentifier({ value: this.id }) + ]; + + if (TYPE in this) + value.push(this.type); + + // Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value, + })); + } + + public toJSON(): QCStatementJson { + const object: any = { + id: this.id + }; + + if (this.type) { + object.type = this.type.toJSON(); + } + + return object; + } + +} + +export interface IQCStatements { + values: QCStatement[]; +} + +export interface QCStatementsJson { + values: QCStatementJson[]; +} + +export type QCStatementsParameters = PkiObjectParameters & Partial<IQCStatements>; + +/** + * Represents the QCStatements structure described in [RFC3739](https://datatracker.ietf.org/doc/html/rfc3739) + */ +export class QCStatements extends PkiObject implements IQCStatements { + + public static override CLASS_NAME = "QCStatements"; + + public values!: QCStatement[]; + + /** + * Initializes a new instance of the {@link QCStatement} class + * @param parameters Initialization parameters + */ + constructor(parameters: QCStatementParameters = {}) { + super(); + + this.values = pvutils.getParametersValue(parameters, VALUES, QCStatements.defaultValues(VALUES)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof VALUES): QCStatement[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case VALUES: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case VALUES: + return (memberValue.length === 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * QCStatements ::= SEQUENCE OF QCStatement + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + values?: string; + value?: QCStatementSchema; + }> = {}): Schema.SchemaType { + /** + * @type {Object} + * @property {string} [blockName] + * @property {string} [values] + */ + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Repeated({ + name: (names.values || EMPTY_STRING), + value: QCStatement.schema(names.value || {}) + }), + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, QC_STATEMENTS_CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + QCStatements.schema({ + names: { + values: VALUES + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.values = Array.from(asn1.result.values, element => new QCStatement({ schema: element })); + } + + public toSchema(): asn1js.Sequence { + // Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: Array.from(this.values, o => o.toSchema()) + })); + } + + public toJSON(): QCStatementsJson { + return { + values: Array.from(this.values, o => o.toJSON()) + }; + } + +} diff --git a/third_party/js/PKI.js/src/README.MD b/third_party/js/PKI.js/src/README.MD new file mode 100644 index 0000000000..00b09105ad --- /dev/null +++ b/third_party/js/PKI.js/src/README.MD @@ -0,0 +1,130 @@ +## DESCRIPTION OF THE PROJECT
+
+PKIjs designed to be a helper for everyone making any PKI-related applications.
+Currently PKI defined by a set of documents, and usually these documents have form in RFC (Requst For Comments) managed by IETF.
+PKIjs respects this situation and provide to user a flexible environment based on existing set of RFCs, related to PKI.
+
+| RFC number | RFC name |
+|----------------|----------------|
+|RFC5280|Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile|
+|RFC3161|Internet X.509 Public Key Infrastructure Time-Stamp Protocol (TSP)|
+|RFC5652|Cryptographic Message Syntax (CMS)|
+|RFC3447|Public-Key Cryptography Standards (PKCS) #1: RSA Cryptography Specifications Version 2.1|
+|RFC5753|Use of Elliptic Curve Cryptography (ECC) Algorithms in Cryptographic Message Syntax (CMS)|
+|RFC2898|PKCS #5: Password-Based Cryptography Specification|
+|RFC6960|X.509 Internet Public Key Infrastructure Online Certificate Status Protocol - OCSP|
+|RFC2986|PKCS #10: Certification Request Syntax Specification Version 1.7|
+|RFC7292|PKCS #12: Personal Information Exchange Syntax v1.1|
+|RFC6318|Suite B in Secure/Multipurpose Internet Mail Extensions (S/MIME)|
+|RFC5915|Elliptic Curve Private Key Structure|
+|RFC5480|Elliptic Curve Cryptography Subject Public Key Information|
+|RFC5208|Public-Key Cryptography Standards (PKCS) #8: Private-Key Information Syntax Specification Version 1.2|
+|RFC4055|Additional Algorithms and Identifiers for RSA Cryptography for use in the Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile|
+
+PKI.js made of many specialized classes, each of them is responsible for handling one structure from specific RFC. For example, in order to handle X.509 certificate, described in RFC5280, PKI.js has class "Certificate". Each class inside PKI.js is inside separate file.
+Name of each class equals to name from RFC document. Here is a table with PKI.js class names and related RFCs:
+
+| Class Name | RFC number |
+|----------------|----------------|
+|AccessDescription|RFC5280|
+|Accuracy|RFC3161|
+|AlgorithmIdentifier|RFC5280|
+|AltName|RFC5280|
+|Attribute|RFC2986|
+|AttributeTypeAndValue|RFC5280|
+|AuthenticatedSafe|RFC7292|
+|AuthorityKeyIdentifier|RFC5280|
+|BasicConstraints|RFC5280|
+|BasicOCSPResponse|RFC6960|
+|CRLBag|RFC7292|
+|CRLDistributionPoints|RFC5280|
+|CertBag|RFC7292|
+|CertID|RFC6960|
+|Certificate|RFC5280|
+|CertificatePolicies|RFC5280|
+|CertificateRevocationList|RFC5280|
+|CertificateSet|RFC5652|
+|CertificationRequest|RFC2986|
+|ContentInfo|RFC5652|
+|DigestInfo|RFC3447|
+|DistributionPoint|RFC5280|
+|ECCCMSSharedInfo|RFC6318|
+|ECPrivateKey|RFC5915|
+|ECPublicKey|RFC5480|
+|EncapsulatedContentInfo|RFC5652|
+|EncryptedContentInfo|RFC5652|
+|EncryptedData|RFC5652|
+|EnvelopedData|RFC5652|
+|ExtKeyUsage|RFC5280|
+|Extension|RFC5280|
+|Extensions|RFC5280|
+|GeneralName|RFC5280|
+|GeneralNames|RFC5280|
+|GeneralSubtree|RFC5280|
+|InfoAccess|RFC5280|
+|IssuerAndSerialNumber|RFC5652|
+|IssuingDistributionPoint|RFC5280|
+|KEKIdentifier|RFC5652|
+|KEKRecipientInfo|RFC5652|
+|KeyAgreeRecipientIdentifier|RFC5652|
+|KeyAgreeRecipientInfo|RFC5652|
+|KeyBag|RFC5208|
+|KeyTransRecipientInfo|RFC5652|
+|MacData|RFC7292|
+|MessageImprint|RFC3161|
+|NameConstraints|RFC5280|
+|OCSPRequest|RFC6960|
+|OCSPResponse|RFC6960|
+|OriginatorIdentifierOrKey|RFC5652|
+|OriginatorInfo|RFC5652|
+|OriginatorPublicKey|RFC5652|
+|OtherCertificateFormat|RFC5652|
+|OtherKeyAttribute|RFC5652|
+|OtherPrimeInfo|RFC3447|
+|OtherRecipientInfo|RFC5652|
+|OtherRevocationInfoFormat|RFC5652|
+|PBES2Params|RFC2898|
+|PBKDF2Params|RFC2898|
+|PFX|RFC7292|
+|PKCS8ShroudedKeyBag|RFC7292|
+|PKIStatusInfo|RFC3161|
+|PasswordRecipientinfo|RFC5652|
+|PolicyConstraints|RFC5280|
+|PolicyInformation|RFC5280|
+|PolicyMapping|RFC5280|
+|PolicyMappings|RFC5280|
+|PolicyQualifierInfo|RFC5280|
+|PrivateKeyInfo|RFC5208|
+|PrivateKeyUsagePeriod|RFC5280|
+|PublicKeyInfo|RFC5280|
+|RSAESOAEPParams|RFC3447|
+|RSAPrivateKey|RFC3447|
+|RSAPublicKey|RFC3447|
+|RSASSAPSSParams|RFC4055|
+|RecipientEncryptedKey|RFC5652|
+|RecipientEncryptedKeys|RFC5652|
+|RecipientIdentifier|RFC5652|
+|RecipientInfo|RFC5652|
+|RecipientKeyIdentifier|RFC5652|
+|RelativeDistinguishedNames|RFC5280|
+|Request|RFC6960|
+|ResponseBytes|RFC6960|
+|ResponseData|RFC6960|
+|RevocationInfoChoices|RFC5652|
+|RevokedCertificate|RFC5280|
+|SafeBag|RFC7292|
+|SafeContents|RFC7292|
+|SecretBag|RFC7292|
+|Signature|RFC6960|
+|SignedAndUnsignedAttributes|RFC5652|
+|SignedData|RFC5652|
+|SignerInfo|RFC5652|
+|SingleResponse|RFC6960|
+|SubjectDirectoryAttributes|RFC5280|
+|TBSRequest|RFC6960|
+|TSTInfo|RFC3161|
+|Time|RFC5280
+|TimeStampReq|RFC3161|
+|TimeStampResp|RFC3161|
+
+PKI.js library could be extended very easily to handle additional types from any RFC. If you have a special need for any RFC's new types please create issue on GitHub.
diff --git a/third_party/js/PKI.js/src/RSAESOAEPParams.ts b/third_party/js/PKI.js/src/RSAESOAEPParams.ts new file mode 100644 index 0000000000..efe33f3de1 --- /dev/null +++ b/third_party/js/PKI.js/src/RSAESOAEPParams.ts @@ -0,0 +1,241 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const HASH_ALGORITHM = "hashAlgorithm"; +const MASK_GEN_ALGORITHM = "maskGenAlgorithm"; +const P_SOURCE_ALGORITHM = "pSourceAlgorithm"; +const CLEAR_PROPS = [ + HASH_ALGORITHM, + MASK_GEN_ALGORITHM, + P_SOURCE_ALGORITHM +]; + +export interface IRSAESOAEPParams { + hashAlgorithm: AlgorithmIdentifier; + maskGenAlgorithm: AlgorithmIdentifier; + pSourceAlgorithm: AlgorithmIdentifier; +} + +export interface RSAESOAEPParamsJson { + hashAlgorithm?: AlgorithmIdentifierJson; + maskGenAlgorithm?: AlgorithmIdentifierJson; + pSourceAlgorithm?: AlgorithmIdentifierJson; +} + +export type RSAESOAEPParamsParameters = PkiObjectParameters & Partial<IRSAESOAEPParams>; + +/** + * Class from RFC3447 + */ +export class RSAESOAEPParams extends PkiObject implements IRSAESOAEPParams { + + public static override CLASS_NAME = "RSAESOAEPParams"; + + public hashAlgorithm!: AlgorithmIdentifier; + public maskGenAlgorithm!: AlgorithmIdentifier; + public pSourceAlgorithm!: AlgorithmIdentifier; + + /** + * Initializes a new instance of the {@link RSAESOAEPParams} class + * @param parameters Initialization parameters + */ + constructor(parameters: RSAESOAEPParamsParameters = {}) { + super(); + + this.hashAlgorithm = pvutils.getParametersValue(parameters, HASH_ALGORITHM, RSAESOAEPParams.defaultValues(HASH_ALGORITHM)); + this.maskGenAlgorithm = pvutils.getParametersValue(parameters, MASK_GEN_ALGORITHM, RSAESOAEPParams.defaultValues(MASK_GEN_ALGORITHM)); + this.pSourceAlgorithm = pvutils.getParametersValue(parameters, P_SOURCE_ALGORITHM, RSAESOAEPParams.defaultValues(P_SOURCE_ALGORITHM)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof HASH_ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof MASK_GEN_ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof P_SOURCE_ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case HASH_ALGORITHM: + return new AlgorithmIdentifier({ + algorithmId: "1.3.14.3.2.26", // SHA-1 + algorithmParams: new asn1js.Null() + }); + case MASK_GEN_ALGORITHM: + return new AlgorithmIdentifier({ + algorithmId: "1.2.840.113549.1.1.8", // MGF1 + algorithmParams: (new AlgorithmIdentifier({ + algorithmId: "1.3.14.3.2.26", // SHA-1 + algorithmParams: new asn1js.Null() + })).toSchema() + }); + case P_SOURCE_ALGORITHM: + return new AlgorithmIdentifier({ + algorithmId: "1.2.840.113549.1.1.9", // id-pSpecified + algorithmParams: new asn1js.OctetString({ valueHex: (new Uint8Array([0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09])).buffer }) // SHA-1 hash of empty string + }); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * RSAES-OAEP-params ::= SEQUENCE { + * hashAlgorithm [0] HashAlgorithm DEFAULT sha1, + * maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT mgf1SHA1, + * pSourceAlgorithm [2] PSourceAlgorithm DEFAULT pSpecifiedEmpty + * } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + hashAlgorithm?: AlgorithmIdentifierSchema; + maskGenAlgorithm?: AlgorithmIdentifierSchema; + pSourceAlgorithm?: AlgorithmIdentifierSchema; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + optional: true, + value: [AlgorithmIdentifier.schema(names.hashAlgorithm || {})] + }), + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + optional: true, + value: [AlgorithmIdentifier.schema(names.maskGenAlgorithm || {})] + }), + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 2 // [2] + }, + optional: true, + value: [AlgorithmIdentifier.schema(names.pSourceAlgorithm || {})] + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + RSAESOAEPParams.schema({ + names: { + hashAlgorithm: { + names: { + blockName: HASH_ALGORITHM + } + }, + maskGenAlgorithm: { + names: { + blockName: MASK_GEN_ALGORITHM + } + }, + pSourceAlgorithm: { + names: { + blockName: P_SOURCE_ALGORITHM + } + } + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + if (HASH_ALGORITHM in asn1.result) + this.hashAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.hashAlgorithm }); + + if (MASK_GEN_ALGORITHM in asn1.result) + this.maskGenAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.maskGenAlgorithm }); + + if (P_SOURCE_ALGORITHM in asn1.result) + this.pSourceAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.pSourceAlgorithm }); + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray = []; + + if (!this.hashAlgorithm.isEqual(RSAESOAEPParams.defaultValues(HASH_ALGORITHM))) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [this.hashAlgorithm.toSchema()] + })); + } + + if (!this.maskGenAlgorithm.isEqual(RSAESOAEPParams.defaultValues(MASK_GEN_ALGORITHM))) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [this.maskGenAlgorithm.toSchema()] + })); + } + + if (!this.pSourceAlgorithm.isEqual(RSAESOAEPParams.defaultValues(P_SOURCE_ALGORITHM))) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 2 // [2] + }, + value: [this.pSourceAlgorithm.toSchema()] + })); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): RSAESOAEPParamsJson { + const res: RSAESOAEPParamsJson = {}; + + if (!this.hashAlgorithm.isEqual(RSAESOAEPParams.defaultValues(HASH_ALGORITHM))) { + res.hashAlgorithm = this.hashAlgorithm.toJSON(); + } + + if (!this.maskGenAlgorithm.isEqual(RSAESOAEPParams.defaultValues(MASK_GEN_ALGORITHM))) { + res.maskGenAlgorithm = this.maskGenAlgorithm.toJSON(); + } + + if (!this.pSourceAlgorithm.isEqual(RSAESOAEPParams.defaultValues(P_SOURCE_ALGORITHM))) { + res.pSourceAlgorithm = this.pSourceAlgorithm.toJSON(); + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/RSAPrivateKey.ts b/third_party/js/PKI.js/src/RSAPrivateKey.ts new file mode 100644 index 0000000000..1a66e9dc41 --- /dev/null +++ b/third_party/js/PKI.js/src/RSAPrivateKey.ts @@ -0,0 +1,319 @@ +import * as asn1js from "asn1js"; +import * as pvtsutils from "pvtsutils"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError, ParameterError } from "./errors"; +import { OtherPrimeInfo, OtherPrimeInfoJson, OtherPrimeInfoSchema } from "./OtherPrimeInfo"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const VERSION = "version"; +const MODULUS = "modulus"; +const PUBLIC_EXPONENT = "publicExponent"; +const PRIVATE_EXPONENT = "privateExponent"; +const PRIME1 = "prime1"; +const PRIME2 = "prime2"; +const EXPONENT1 = "exponent1"; +const EXPONENT2 = "exponent2"; +const COEFFICIENT = "coefficient"; +const OTHER_PRIME_INFOS = "otherPrimeInfos"; +const CLEAR_PROPS = [ + VERSION, + MODULUS, + PUBLIC_EXPONENT, + PRIVATE_EXPONENT, + PRIME1, + PRIME2, + EXPONENT1, + EXPONENT2, + COEFFICIENT, + OTHER_PRIME_INFOS +]; + +export interface IRSAPrivateKey { + version: number; + modulus: asn1js.Integer; + publicExponent: asn1js.Integer; + privateExponent: asn1js.Integer; + prime1: asn1js.Integer; + prime2: asn1js.Integer; + exponent1: asn1js.Integer; + exponent2: asn1js.Integer; + coefficient: asn1js.Integer; + otherPrimeInfos?: OtherPrimeInfo[]; +} + +export type RSAPrivateKeyParameters = PkiObjectParameters & Partial<IRSAPrivateKey> & { json?: RSAPrivateKeyJson; }; + +export interface RSAPrivateKeyJson { + n: string; + e: string; + d: string; + p: string; + q: string; + dp: string; + dq: string; + qi: string; + oth?: OtherPrimeInfoJson[]; +} + +/** + * Represents the PrivateKeyInfo structure described in [RFC3447](https://datatracker.ietf.org/doc/html/rfc3447) + */ +export class RSAPrivateKey extends PkiObject implements IRSAPrivateKey { + + public static override CLASS_NAME = "RSAPrivateKey"; + + public version!: number; + public modulus!: asn1js.Integer; + public publicExponent!: asn1js.Integer; + public privateExponent!: asn1js.Integer; + public prime1!: asn1js.Integer; + public prime2!: asn1js.Integer; + public exponent1!: asn1js.Integer; + public exponent2!: asn1js.Integer; + public coefficient!: asn1js.Integer; + public otherPrimeInfos?: OtherPrimeInfo[]; + + /** + * Initializes a new instance of the {@link RSAPrivateKey} class + * @param parameters Initialization parameters + */ + constructor(parameters: RSAPrivateKeyParameters = {}) { + super(); + + this.version = pvutils.getParametersValue(parameters, VERSION, RSAPrivateKey.defaultValues(VERSION)); + this.modulus = pvutils.getParametersValue(parameters, MODULUS, RSAPrivateKey.defaultValues(MODULUS)); + this.publicExponent = pvutils.getParametersValue(parameters, PUBLIC_EXPONENT, RSAPrivateKey.defaultValues(PUBLIC_EXPONENT)); + this.privateExponent = pvutils.getParametersValue(parameters, PRIVATE_EXPONENT, RSAPrivateKey.defaultValues(PRIVATE_EXPONENT)); + this.prime1 = pvutils.getParametersValue(parameters, PRIME1, RSAPrivateKey.defaultValues(PRIME1)); + this.prime2 = pvutils.getParametersValue(parameters, PRIME2, RSAPrivateKey.defaultValues(PRIME2)); + this.exponent1 = pvutils.getParametersValue(parameters, EXPONENT1, RSAPrivateKey.defaultValues(EXPONENT1)); + this.exponent2 = pvutils.getParametersValue(parameters, EXPONENT2, RSAPrivateKey.defaultValues(EXPONENT2)); + this.coefficient = pvutils.getParametersValue(parameters, COEFFICIENT, RSAPrivateKey.defaultValues(COEFFICIENT)); + if (OTHER_PRIME_INFOS in parameters) { + this.otherPrimeInfos = pvutils.getParametersValue(parameters, OTHER_PRIME_INFOS, RSAPrivateKey.defaultValues(OTHER_PRIME_INFOS)); + } + + if (parameters.json) { + this.fromJSON(parameters.json); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof VERSION): number; + public static override defaultValues(memberName: typeof MODULUS): asn1js.Integer; + public static override defaultValues(memberName: typeof PUBLIC_EXPONENT): asn1js.Integer; + public static override defaultValues(memberName: typeof PRIVATE_EXPONENT): asn1js.Integer; + public static override defaultValues(memberName: typeof PRIME1): asn1js.Integer; + public static override defaultValues(memberName: typeof PRIME2): asn1js.Integer; + public static override defaultValues(memberName: typeof EXPONENT1): asn1js.Integer; + public static override defaultValues(memberName: typeof EXPONENT2): asn1js.Integer; + public static override defaultValues(memberName: typeof COEFFICIENT): asn1js.Integer; + public static override defaultValues(memberName: typeof OTHER_PRIME_INFOS): OtherPrimeInfo[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case VERSION: + return 0; + case MODULUS: + return new asn1js.Integer(); + case PUBLIC_EXPONENT: + return new asn1js.Integer(); + case PRIVATE_EXPONENT: + return new asn1js.Integer(); + case PRIME1: + return new asn1js.Integer(); + case PRIME2: + return new asn1js.Integer(); + case EXPONENT1: + return new asn1js.Integer(); + case EXPONENT2: + return new asn1js.Integer(); + case COEFFICIENT: + return new asn1js.Integer(); + case OTHER_PRIME_INFOS: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * 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 + * } + * + * OtherPrimeInfos ::= Sequence SIZE(1..MAX) OF OtherPrimeInfo + * ``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + version?: string; + modulus?: string; + publicExponent?: string; + privateExponent?: string; + prime1?: string; + prime2?: string; + exponent1?: string; + exponent2?: string; + coefficient?: string; + otherPrimeInfosName?: string; + otherPrimeInfo?: OtherPrimeInfoSchema; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Integer({ name: (names.version || EMPTY_STRING) }), + new asn1js.Integer({ name: (names.modulus || EMPTY_STRING) }), + new asn1js.Integer({ name: (names.publicExponent || EMPTY_STRING) }), + new asn1js.Integer({ name: (names.privateExponent || EMPTY_STRING) }), + new asn1js.Integer({ name: (names.prime1 || EMPTY_STRING) }), + new asn1js.Integer({ name: (names.prime2 || EMPTY_STRING) }), + new asn1js.Integer({ name: (names.exponent1 || EMPTY_STRING) }), + new asn1js.Integer({ name: (names.exponent2 || EMPTY_STRING) }), + new asn1js.Integer({ name: (names.coefficient || EMPTY_STRING) }), + new asn1js.Sequence({ + optional: true, + value: [ + new asn1js.Repeated({ + name: (names.otherPrimeInfosName || EMPTY_STRING), + value: OtherPrimeInfo.schema(names.otherPrimeInfo || {}) + }) + ] + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + RSAPrivateKey.schema({ + names: { + version: VERSION, + modulus: MODULUS, + publicExponent: PUBLIC_EXPONENT, + privateExponent: PRIVATE_EXPONENT, + prime1: PRIME1, + prime2: PRIME2, + exponent1: EXPONENT1, + exponent2: EXPONENT2, + coefficient: COEFFICIENT, + otherPrimeInfo: { + names: { + blockName: OTHER_PRIME_INFOS + } + } + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + //#region Get internal properties from parsed schema + this.version = asn1.result.version.valueBlock.valueDec; + this.modulus = asn1.result.modulus.convertFromDER(256); + this.publicExponent = asn1.result.publicExponent; + this.privateExponent = asn1.result.privateExponent.convertFromDER(256); + this.prime1 = asn1.result.prime1.convertFromDER(128); + this.prime2 = asn1.result.prime2.convertFromDER(128); + this.exponent1 = asn1.result.exponent1.convertFromDER(128); + this.exponent2 = asn1.result.exponent2.convertFromDER(128); + this.coefficient = asn1.result.coefficient.convertFromDER(128); + + if (OTHER_PRIME_INFOS in asn1.result) + this.otherPrimeInfos = Array.from(asn1.result.otherPrimeInfos, element => new OtherPrimeInfo({ schema: element })); + //#endregion + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray = []; + + outputArray.push(new asn1js.Integer({ value: this.version })); + outputArray.push(this.modulus.convertToDER()); + outputArray.push(this.publicExponent); + outputArray.push(this.privateExponent.convertToDER()); + outputArray.push(this.prime1.convertToDER()); + outputArray.push(this.prime2.convertToDER()); + outputArray.push(this.exponent1.convertToDER()); + outputArray.push(this.exponent2.convertToDER()); + outputArray.push(this.coefficient.convertToDER()); + + if (this.otherPrimeInfos) { + outputArray.push(new asn1js.Sequence({ + value: Array.from(this.otherPrimeInfos, o => o.toSchema()) + })); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): RSAPrivateKeyJson { + const jwk: RSAPrivateKeyJson = { + n: pvtsutils.Convert.ToBase64Url(this.modulus.valueBlock.valueHexView), + e: pvtsutils.Convert.ToBase64Url(this.publicExponent.valueBlock.valueHexView), + d: pvtsutils.Convert.ToBase64Url(this.privateExponent.valueBlock.valueHexView), + p: pvtsutils.Convert.ToBase64Url(this.prime1.valueBlock.valueHexView), + q: pvtsutils.Convert.ToBase64Url(this.prime2.valueBlock.valueHexView), + dp: pvtsutils.Convert.ToBase64Url(this.exponent1.valueBlock.valueHexView), + dq: pvtsutils.Convert.ToBase64Url(this.exponent2.valueBlock.valueHexView), + qi: pvtsutils.Convert.ToBase64Url(this.coefficient.valueBlock.valueHexView), + }; + if (this.otherPrimeInfos) { + jwk.oth = Array.from(this.otherPrimeInfos, o => o.toJSON()); + } + + return jwk; + } + + /** + * Converts JSON value into current object + * @param json JSON object + */ + public fromJSON(json: any): void { + ParameterError.assert("json", json, "n", "e", "d", "p", "q", "dp", "dq", "qi"); + + this.modulus = new asn1js.Integer({ valueHex: pvtsutils.Convert.FromBase64Url(json.n) }); + this.publicExponent = new asn1js.Integer({ valueHex: pvtsutils.Convert.FromBase64Url(json.e) }); + this.privateExponent = new asn1js.Integer({ valueHex: pvtsutils.Convert.FromBase64Url(json.d) }); + this.prime1 = new asn1js.Integer({ valueHex: pvtsutils.Convert.FromBase64Url(json.p) }); + this.prime2 = new asn1js.Integer({ valueHex: pvtsutils.Convert.FromBase64Url(json.q) }); + this.exponent1 = new asn1js.Integer({ valueHex: pvtsutils.Convert.FromBase64Url(json.dp) }); + this.exponent2 = new asn1js.Integer({ valueHex: pvtsutils.Convert.FromBase64Url(json.dq) }); + this.coefficient = new asn1js.Integer({ valueHex: pvtsutils.Convert.FromBase64Url(json.qi) }); + if (json.oth) { + this.otherPrimeInfos = Array.from(json.oth, (element: any) => new OtherPrimeInfo({ json: element })); + } + } + +} diff --git a/third_party/js/PKI.js/src/RSAPublicKey.ts b/third_party/js/PKI.js/src/RSAPublicKey.ts new file mode 100644 index 0000000000..088e08684f --- /dev/null +++ b/third_party/js/PKI.js/src/RSAPublicKey.ts @@ -0,0 +1,150 @@ +import * as asn1js from "asn1js"; +import * as pvtsutils from "pvtsutils"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError, ParameterError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +export interface IRSAPublicKey { + /** + * Modulus part of RSA public key + */ + modulus: asn1js.Integer; + /** + * Public exponent of RSA public key + */ + publicExponent: asn1js.Integer; +} + +export interface RSAPublicKeyJson { + n: string; + e: string; +} + +export type RSAPublicKeyParameters = PkiObjectParameters & Partial<IRSAPublicKey> & { json?: RSAPublicKeyJson; }; + +const MODULUS = "modulus"; +const PUBLIC_EXPONENT = "publicExponent"; +const CLEAR_PROPS = [MODULUS, PUBLIC_EXPONENT]; + +/** + * Represents the RSAPublicKey structure described in [RFC3447](https://datatracker.ietf.org/doc/html/rfc3447) + */ +export class RSAPublicKey extends PkiObject implements IRSAPublicKey { + + public static override CLASS_NAME = "RSAPublicKey"; + + public modulus!: asn1js.Integer; + public publicExponent!: asn1js.Integer; + + /** + * Initializes a new instance of the {@link RSAPublicKey} class + * @param parameters Initialization parameters + */ + constructor(parameters: RSAPublicKeyParameters = {}) { + super(); + + + this.modulus = pvutils.getParametersValue(parameters, MODULUS, RSAPublicKey.defaultValues(MODULUS)); + this.publicExponent = pvutils.getParametersValue(parameters, PUBLIC_EXPONENT, RSAPublicKey.defaultValues(PUBLIC_EXPONENT)); + + if (parameters.json) { + this.fromJSON(parameters.json); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof MODULUS | typeof PUBLIC_EXPONENT): asn1js.Integer; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case MODULUS: + return new asn1js.Integer(); + case PUBLIC_EXPONENT: + return new asn1js.Integer(); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * RSAPublicKey ::= Sequence { + * modulus Integer, -- n + * publicExponent Integer -- e + * } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ modulus?: string; publicExponent?: string; }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Integer({ name: (names.modulus || EMPTY_STRING) }), + new asn1js.Integer({ name: (names.publicExponent || EMPTY_STRING) }) + ] + })); + } + + public fromSchema(schema: asn1js.AsnType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + RSAPublicKey.schema({ + names: { + modulus: MODULUS, + publicExponent: PUBLIC_EXPONENT + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.modulus = asn1.result.modulus.convertFromDER(256); + this.publicExponent = asn1.result.publicExponent; + } + + public toSchema(): asn1js.Sequence { + return (new asn1js.Sequence({ + value: [ + this.modulus.convertToDER(), + this.publicExponent + ] + })); + } + + public toJSON(): RSAPublicKeyJson { + return { + n: pvtsutils.Convert.ToBase64Url(this.modulus.valueBlock.valueHexView), + e: pvtsutils.Convert.ToBase64Url(this.publicExponent.valueBlock.valueHexView), + }; + } + + /** + * Converts JSON value into current object + * @param json JSON object + */ + fromJSON(json: RSAPublicKeyJson): void { + ParameterError.assert("json", json, "n", "e"); + + const array = pvutils.stringToArrayBuffer(pvutils.fromBase64(json.n, true)); + this.modulus = new asn1js.Integer({ valueHex: array.slice(0, Math.pow(2, pvutils.nearestPowerOf2(array.byteLength))) }); + this.publicExponent = new asn1js.Integer({ valueHex: pvutils.stringToArrayBuffer(pvutils.fromBase64(json.e, true)).slice(0, 3) }); + } + +} + diff --git a/third_party/js/PKI.js/src/RSASSAPSSParams.ts b/third_party/js/PKI.js/src/RSASSAPSSParams.ts new file mode 100644 index 0000000000..0941bea162 --- /dev/null +++ b/third_party/js/PKI.js/src/RSASSAPSSParams.ts @@ -0,0 +1,282 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const HASH_ALGORITHM = "hashAlgorithm"; +const MASK_GEN_ALGORITHM = "maskGenAlgorithm"; +const SALT_LENGTH = "saltLength"; +const TRAILER_FIELD = "trailerField"; +const CLEAR_PROPS = [ + HASH_ALGORITHM, + MASK_GEN_ALGORITHM, + SALT_LENGTH, + TRAILER_FIELD +]; + +export interface IRSASSAPSSParams { + /** + * Algorithms of hashing (DEFAULT sha1) + */ + hashAlgorithm: AlgorithmIdentifier; + /** + * Salt length (DEFAULT 20) + */ + maskGenAlgorithm: AlgorithmIdentifier; + /** + * Salt length (DEFAULT 20) + */ + saltLength: number; + /** + * (DEFAULT 1) + */ + trailerField: number; +} + +export interface RSASSAPSSParamsJson { + hashAlgorithm?: AlgorithmIdentifierJson; + maskGenAlgorithm?: AlgorithmIdentifierJson; + saltLength?: number; + trailerField?: number; +} + +export type RSASSAPSSParamsParameters = PkiObjectParameters & Partial<IRSASSAPSSParams>; + +/** + * Represents the RSASSAPSSParams structure described in [RFC4055](https://datatracker.ietf.org/doc/html/rfc4055) + */ +export class RSASSAPSSParams extends PkiObject implements IRSASSAPSSParams { + + public static override CLASS_NAME = "RSASSAPSSParams"; + + public hashAlgorithm!: AlgorithmIdentifier; + public maskGenAlgorithm!: AlgorithmIdentifier; + public saltLength!: number; + public trailerField!: number; + + /** + * Initializes a new instance of the {@link RSASSAPSSParams} class + * @param parameters Initialization parameters + */ + constructor(parameters: RSASSAPSSParamsParameters = {}) { + super(); + + this.hashAlgorithm = pvutils.getParametersValue(parameters, HASH_ALGORITHM, RSASSAPSSParams.defaultValues(HASH_ALGORITHM)); + this.maskGenAlgorithm = pvutils.getParametersValue(parameters, MASK_GEN_ALGORITHM, RSASSAPSSParams.defaultValues(MASK_GEN_ALGORITHM)); + this.saltLength = pvutils.getParametersValue(parameters, SALT_LENGTH, RSASSAPSSParams.defaultValues(SALT_LENGTH)); + this.trailerField = pvutils.getParametersValue(parameters, TRAILER_FIELD, RSASSAPSSParams.defaultValues(TRAILER_FIELD)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof HASH_ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof MASK_GEN_ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof SALT_LENGTH): number; + public static override defaultValues(memberName: typeof TRAILER_FIELD): number; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case HASH_ALGORITHM: + return new AlgorithmIdentifier({ + algorithmId: "1.3.14.3.2.26", // SHA-1 + algorithmParams: new asn1js.Null() + }); + case MASK_GEN_ALGORITHM: + return new AlgorithmIdentifier({ + algorithmId: "1.2.840.113549.1.1.8", // MGF1 + algorithmParams: (new AlgorithmIdentifier({ + algorithmId: "1.3.14.3.2.26", // SHA-1 + algorithmParams: new asn1js.Null() + })).toSchema() + }); + case SALT_LENGTH: + return 20; + case TRAILER_FIELD: + return 1; + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * RSASSA-PSS-params ::= Sequence { + * hashAlgorithm [0] HashAlgorithm DEFAULT sha1Identifier, + * maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT mgf1SHA1Identifier, + * saltLength [2] Integer DEFAULT 20, + * trailerField [3] Integer DEFAULT 1 } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + hashAlgorithm?: AlgorithmIdentifierSchema; + maskGenAlgorithm?: AlgorithmIdentifierSchema; + saltLength?: string; + trailerField?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + optional: true, + value: [AlgorithmIdentifier.schema(names.hashAlgorithm || {})] + }), + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + optional: true, + value: [AlgorithmIdentifier.schema(names.maskGenAlgorithm || {})] + }), + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 2 // [2] + }, + optional: true, + value: [new asn1js.Integer({ name: (names.saltLength || EMPTY_STRING) })] + }), + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 3 // [3] + }, + optional: true, + value: [new asn1js.Integer({ name: (names.trailerField || EMPTY_STRING) })] + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + RSASSAPSSParams.schema({ + names: { + hashAlgorithm: { + names: { + blockName: HASH_ALGORITHM + } + }, + maskGenAlgorithm: { + names: { + blockName: MASK_GEN_ALGORITHM + } + }, + saltLength: SALT_LENGTH, + trailerField: TRAILER_FIELD + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + if (HASH_ALGORITHM in asn1.result) + this.hashAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.hashAlgorithm }); + + if (MASK_GEN_ALGORITHM in asn1.result) + this.maskGenAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.maskGenAlgorithm }); + + if (SALT_LENGTH in asn1.result) + this.saltLength = asn1.result.saltLength.valueBlock.valueDec; + + if (TRAILER_FIELD in asn1.result) + this.trailerField = asn1.result.trailerField.valueBlock.valueDec; + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray = []; + + if (!this.hashAlgorithm.isEqual(RSASSAPSSParams.defaultValues(HASH_ALGORITHM))) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [this.hashAlgorithm.toSchema()] + })); + } + + if (!this.maskGenAlgorithm.isEqual(RSASSAPSSParams.defaultValues(MASK_GEN_ALGORITHM))) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [this.maskGenAlgorithm.toSchema()] + })); + } + + if (this.saltLength !== RSASSAPSSParams.defaultValues(SALT_LENGTH)) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 2 // [2] + }, + value: [new asn1js.Integer({ value: this.saltLength })] + })); + } + + if (this.trailerField !== RSASSAPSSParams.defaultValues(TRAILER_FIELD)) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 3 // [3] + }, + value: [new asn1js.Integer({ value: this.trailerField })] + })); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): RSASSAPSSParamsJson { + const res: RSASSAPSSParamsJson = {}; + + if (!this.hashAlgorithm.isEqual(RSASSAPSSParams.defaultValues(HASH_ALGORITHM))) { + res.hashAlgorithm = this.hashAlgorithm.toJSON(); + } + + if (!this.maskGenAlgorithm.isEqual(RSASSAPSSParams.defaultValues(MASK_GEN_ALGORITHM))) { + res.maskGenAlgorithm = this.maskGenAlgorithm.toJSON(); + } + + if (this.saltLength !== RSASSAPSSParams.defaultValues(SALT_LENGTH)) { + res.saltLength = this.saltLength; + } + + if (this.trailerField !== RSASSAPSSParams.defaultValues(TRAILER_FIELD)) { + res.trailerField = this.trailerField; + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/RecipientEncryptedKey.ts b/third_party/js/PKI.js/src/RecipientEncryptedKey.ts new file mode 100644 index 0000000000..7748e80262 --- /dev/null +++ b/third_party/js/PKI.js/src/RecipientEncryptedKey.ts @@ -0,0 +1,156 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { KeyAgreeRecipientIdentifier, KeyAgreeRecipientIdentifierJson, KeyAgreeRecipientIdentifierSchema } from "./KeyAgreeRecipientIdentifier"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const RID = "rid"; +const ENCRYPTED_KEY = "encryptedKey"; +const CLEAR_PROPS = [ + RID, + ENCRYPTED_KEY, +]; + +export interface IRecipientEncryptedKey { + rid: KeyAgreeRecipientIdentifier; + encryptedKey: asn1js.OctetString; +} + +export interface RecipientEncryptedKeyJson { + rid: KeyAgreeRecipientIdentifierJson; + encryptedKey: asn1js.OctetStringJson; +} + +export type RecipientEncryptedKeyParameters = PkiObjectParameters & Partial<IRecipientEncryptedKey>; + +/** + * Represents the RecipientEncryptedKey structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + */ +export class RecipientEncryptedKey extends PkiObject implements IRecipientEncryptedKey { + + public static override CLASS_NAME = "RecipientEncryptedKey"; + + public rid!: KeyAgreeRecipientIdentifier; + public encryptedKey!: asn1js.OctetString; + + /** + * Initializes a new instance of the {@link RecipientEncryptedKey} class + * @param parameters Initialization parameters + */ + constructor(parameters: RecipientEncryptedKeyParameters = {}) { + super(); + + this.rid = pvutils.getParametersValue(parameters, RID, RecipientEncryptedKey.defaultValues(RID)); + this.encryptedKey = pvutils.getParametersValue(parameters, ENCRYPTED_KEY, RecipientEncryptedKey.defaultValues(ENCRYPTED_KEY)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof RID): KeyAgreeRecipientIdentifier; + public static override defaultValues(memberName: typeof ENCRYPTED_KEY): asn1js.OctetString; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case RID: + return new KeyAgreeRecipientIdentifier(); + case ENCRYPTED_KEY: + return new asn1js.OctetString(); + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case RID: + return ((memberValue.variant === (-1)) && (("value" in memberValue) === false)); + case ENCRYPTED_KEY: + return (memberValue.isEqual(RecipientEncryptedKey.defaultValues(ENCRYPTED_KEY))); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * RecipientEncryptedKey ::= SEQUENCE { + * rid KeyAgreeRecipientIdentifier, + * encryptedKey EncryptedKey } + * + * EncryptedKey ::= OCTET STRING + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + rid?: KeyAgreeRecipientIdentifierSchema; + encryptedKey?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + KeyAgreeRecipientIdentifier.schema(names.rid || {}), + new asn1js.OctetString({ name: (names.encryptedKey || EMPTY_STRING) }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + RecipientEncryptedKey.schema({ + names: { + rid: { + names: { + blockName: RID + } + }, + encryptedKey: ENCRYPTED_KEY + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.rid = new KeyAgreeRecipientIdentifier({ schema: asn1.result.rid }); + this.encryptedKey = asn1.result.encryptedKey; + } + + public toSchema(): asn1js.Sequence { + // Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: [ + this.rid.toSchema(), + this.encryptedKey + ] + })); + //#endregion + } + + public toJSON(): RecipientEncryptedKeyJson { + return { + rid: this.rid.toJSON(), + encryptedKey: this.encryptedKey.toJSON(), + }; + } + +} diff --git a/third_party/js/PKI.js/src/RecipientEncryptedKeys.ts b/third_party/js/PKI.js/src/RecipientEncryptedKeys.ts new file mode 100644 index 0000000000..68a812d9aa --- /dev/null +++ b/third_party/js/PKI.js/src/RecipientEncryptedKeys.ts @@ -0,0 +1,134 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { RecipientEncryptedKey, RecipientEncryptedKeyJson } from "./RecipientEncryptedKey"; +import * as Schema from "./Schema"; + +const ENCRYPTED_KEYS = "encryptedKeys"; +const RECIPIENT_ENCRYPTED_KEYS = "RecipientEncryptedKeys"; +const CLEAR_PROPS = [ + RECIPIENT_ENCRYPTED_KEYS, +]; + +export interface IRecipientEncryptedKeys { + encryptedKeys: RecipientEncryptedKey[]; +} + +export interface RecipientEncryptedKeysJson { + encryptedKeys: RecipientEncryptedKeyJson[]; +} + +export type RecipientEncryptedKeysParameters = PkiObjectParameters & Partial<IRecipientEncryptedKeys>; + +export type RecipientEncryptedKeysSchema = Schema.SchemaParameters<{ + RecipientEncryptedKeys?: string; +}>; + +/** + * Represents the RecipientEncryptedKeys structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + */ +export class RecipientEncryptedKeys extends PkiObject implements IRecipientEncryptedKeys { + + public static override CLASS_NAME = "RecipientEncryptedKeys"; + + public encryptedKeys!: RecipientEncryptedKey[]; + + /** + * Initializes a new instance of the {@link RecipientEncryptedKeys} class + * @param parameters Initialization parameters + */ + constructor(parameters: RecipientEncryptedKeysParameters = {}) { + super(); + + this.encryptedKeys = pvutils.getParametersValue(parameters, ENCRYPTED_KEYS, RecipientEncryptedKeys.defaultValues(ENCRYPTED_KEYS)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof ENCRYPTED_KEYS): RecipientEncryptedKey[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case ENCRYPTED_KEYS: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case ENCRYPTED_KEYS: + return (memberValue.length === 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * RecipientEncryptedKeys ::= SEQUENCE OF RecipientEncryptedKey + *``` + */ + public static override schema(parameters: RecipientEncryptedKeysSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Repeated({ + name: (names.RecipientEncryptedKeys || EMPTY_STRING), + value: RecipientEncryptedKey.schema() + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + RecipientEncryptedKeys.schema({ + names: { + RecipientEncryptedKeys: RECIPIENT_ENCRYPTED_KEYS + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.encryptedKeys = Array.from(asn1.result.RecipientEncryptedKeys, element => new RecipientEncryptedKey({ schema: element })); + } + + public toSchema(): asn1js.Sequence { + // Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: Array.from(this.encryptedKeys, o => o.toSchema()) + })); + } + + public toJSON(): RecipientEncryptedKeysJson { + return { + encryptedKeys: Array.from(this.encryptedKeys, o => o.toJSON()) + }; + } + +} diff --git a/third_party/js/PKI.js/src/RecipientIdentifier.ts b/third_party/js/PKI.js/src/RecipientIdentifier.ts new file mode 100644 index 0000000000..21d99cf450 --- /dev/null +++ b/third_party/js/PKI.js/src/RecipientIdentifier.ts @@ -0,0 +1,184 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { IssuerAndSerialNumber, IssuerAndSerialNumberJson } from "./IssuerAndSerialNumber"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const VARIANT = "variant"; +const VALUE = "value"; +const CLEAR_PROPS = [ + "blockName" +]; + +export interface IRecipientIdentifier { + variant: number; + value: IssuerAndSerialNumber | asn1js.OctetString; +} + +export interface RecipientIdentifierJson { + variant: number; + value?: IssuerAndSerialNumberJson | asn1js.OctetStringJson; +} + +export type RecipientIdentifierParameters = PkiObjectParameters & Partial<IRecipientIdentifier>; + +export type RecipientIdentifierSchema = Schema.SchemaParameters; + +/** + * Represents the RecipientIdentifier structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + */ +export class RecipientIdentifier extends PkiObject implements IRecipientIdentifier { + + public static override CLASS_NAME = "RecipientIdentifier"; + + public variant!: number; + public value!: IssuerAndSerialNumber | asn1js.OctetString; + + /** + * Initializes a new instance of the {@link RecipientIdentifier} class + * @param parameters Initialization parameters + */ + constructor(parameters: RecipientIdentifierParameters = {}) { + super(); + + this.variant = pvutils.getParametersValue(parameters, VARIANT, RecipientIdentifier.defaultValues(VARIANT)); + if (VALUE in parameters) { + this.value = pvutils.getParametersValue(parameters, VALUE, RecipientIdentifier.defaultValues(VALUE)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof VARIANT): number; + public static override defaultValues(memberName: typeof VALUE): any; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case VARIANT: + return (-1); + case VALUE: + return {}; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case VARIANT: + return (memberValue === (-1)); + case VALUE: + return (Object.keys(memberValue).length === 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * RecipientIdentifier ::= CHOICE { + * issuerAndSerialNumber IssuerAndSerialNumber, + * subjectKeyIdentifier [0] SubjectKeyIdentifier } + * + * SubjectKeyIdentifier ::= OCTET STRING + *``` + */ + public static override schema(parameters: RecipientIdentifierSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Choice({ + value: [ + IssuerAndSerialNumber.schema({ + names: { + blockName: (names.blockName || EMPTY_STRING) + } + }), + new asn1js.Primitive({ + name: (names.blockName || EMPTY_STRING), + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + } + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + RecipientIdentifier.schema({ + names: { + blockName: "blockName" + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + if (asn1.result.blockName.idBlock.tagClass === 1) { + this.variant = 1; + this.value = new IssuerAndSerialNumber({ schema: asn1.result.blockName }); + } else { + this.variant = 2; + this.value = new asn1js.OctetString({ valueHex: asn1.result.blockName.valueBlock.valueHex }); + } + } + + public toSchema(): asn1js.BaseBlock<any> { + // Construct and return new ASN.1 schema for this object + switch (this.variant) { + case 1: + if (!(this.value instanceof IssuerAndSerialNumber)) { + throw new Error("Incorrect type of RecipientIdentifier.value. It should be IssuerAndSerialNumber."); + } + return this.value.toSchema(); + case 2: + if (!(this.value instanceof asn1js.OctetString)) { + throw new Error("Incorrect type of RecipientIdentifier.value. It should be ASN.1 OctetString."); + } + return new asn1js.Primitive({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + valueHex: this.value.valueBlock.valueHexView + }); + default: + return new asn1js.Any() as any; + } + } + + public toJSON(): RecipientIdentifierJson { + const res: RecipientIdentifierJson = { + variant: this.variant + }; + + if ((this.variant === 1 || this.variant === 2) && this.value) { + res.value = this.value.toJSON(); + } + + return res; + } + +} + diff --git a/third_party/js/PKI.js/src/RecipientInfo.ts b/third_party/js/PKI.js/src/RecipientInfo.ts new file mode 100644 index 0000000000..3183dbc969 --- /dev/null +++ b/third_party/js/PKI.js/src/RecipientInfo.ts @@ -0,0 +1,234 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { KeyTransRecipientInfo, KeyTransRecipientInfoJson } from "./KeyTransRecipientInfo"; +import { KeyAgreeRecipientInfo, KeyAgreeRecipientInfoJson } from "./KeyAgreeRecipientInfo"; +import { KEKRecipientInfo, KEKRecipientInfoJson } from "./KEKRecipientInfo"; +import { PasswordRecipientinfo, PasswordRecipientInfoJson } from "./PasswordRecipientinfo"; +import { OtherRecipientInfo, OtherRecipientInfoJson } from "./OtherRecipientInfo"; +import * as Schema from "./Schema"; +import { AsnError, ParameterError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { EMPTY_STRING } from "./constants"; + +const VARIANT = "variant"; +const VALUE = "value"; +const CLEAR_PROPS = [ + "blockName" +]; + +export interface IRecipientInfo { + variant: number; + value?: RecipientInfoValue; +} + +export interface RecipientInfoJson { + variant: number; + value?: RecipientInfoValueJson; +} + +export type RecipientInfoValue = KeyTransRecipientInfo | KeyAgreeRecipientInfo | KEKRecipientInfo | PasswordRecipientinfo | OtherRecipientInfo; +export type RecipientInfoValueJson = KeyTransRecipientInfoJson | KeyAgreeRecipientInfoJson | KEKRecipientInfoJson | PasswordRecipientInfoJson | OtherRecipientInfoJson; + +export type RecipientInfoParameters = PkiObjectParameters & Partial<IRecipientInfo>; + +/** + * Represents the RecipientInfo structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + */ +export class RecipientInfo extends PkiObject implements IRecipientInfo { + + public static override CLASS_NAME = "RecipientInfo"; + + public variant!: number; + public value?: RecipientInfoValue; + + /** + * Initializes a new instance of the {@link RecipientInfo} class + * @param parameters Initialization parameters + */ + constructor(parameters: RecipientInfoParameters = {}) { + super(); + + this.variant = pvutils.getParametersValue(parameters, VARIANT, RecipientInfo.defaultValues(VARIANT)); + if (VALUE in parameters) { + this.value = pvutils.getParametersValue(parameters, VALUE, RecipientInfo.defaultValues(VALUE)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof VARIANT): number; + public static override defaultValues(memberName: typeof VALUE): RecipientInfoValue; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case VARIANT: + return (-1); + case VALUE: + return {}; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case VARIANT: + return (memberValue === RecipientInfo.defaultValues(memberName)); + case VALUE: + return (Object.keys(memberValue).length === 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * RecipientInfo ::= CHOICE { + * ktri KeyTransRecipientInfo, + * kari [1] KeyAgreeRecipientInfo, + * kekri [2] KEKRecipientInfo, + * pwri [3] PasswordRecipientinfo, + * ori [4] OtherRecipientInfo } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Choice({ + value: [ + KeyTransRecipientInfo.schema({ + names: { + blockName: (names.blockName || EMPTY_STRING) + } + }), + new asn1js.Constructed({ + name: (names.blockName || EMPTY_STRING), + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: KeyAgreeRecipientInfo.schema().valueBlock.value + }), + new asn1js.Constructed({ + name: (names.blockName || EMPTY_STRING), + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 2 // [2] + }, + value: KEKRecipientInfo.schema().valueBlock.value + }), + new asn1js.Constructed({ + name: (names.blockName || EMPTY_STRING), + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 3 // [3] + }, + value: PasswordRecipientinfo.schema().valueBlock.value + }), + new asn1js.Constructed({ + name: (names.blockName || EMPTY_STRING), + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 4 // [4] + }, + value: OtherRecipientInfo.schema().valueBlock.value + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + RecipientInfo.schema({ + names: { + blockName: "blockName" + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + if (asn1.result.blockName.idBlock.tagClass === 1) { + this.variant = 1; + this.value = new KeyTransRecipientInfo({ schema: asn1.result.blockName }); + } else { + // Create "SEQUENCE" from "ASN1_CONSTRUCTED" + const blockSequence = new asn1js.Sequence({ + value: asn1.result.blockName.valueBlock.value + }); + + switch (asn1.result.blockName.idBlock.tagNumber) { + case 1: + this.variant = 2; + this.value = new KeyAgreeRecipientInfo({ schema: blockSequence }); + break; + case 2: + this.variant = 3; + this.value = new KEKRecipientInfo({ schema: blockSequence }); + break; + case 3: + this.variant = 4; + this.value = new PasswordRecipientinfo({ schema: blockSequence }); + break; + case 4: + this.variant = 5; + this.value = new OtherRecipientInfo({ schema: blockSequence }); + break; + default: + throw new Error("Incorrect structure of RecipientInfo block"); + } + } + } + + public toSchema(): asn1js.BaseBlock<any> { + // Construct and return new ASN.1 schema for this object + ParameterError.assertEmpty(this.value, "value", "RecipientInfo"); + const _schema = this.value.toSchema(); + + switch (this.variant) { + case 1: + return _schema; + case 2: + case 3: + case 4: + // Create "ASN1_CONSTRUCTED" from "SEQUENCE" + _schema.idBlock.tagClass = 3; // CONTEXT-SPECIFIC + _schema.idBlock.tagNumber = (this.variant - 1); + + return _schema; + default: + return new asn1js.Any() as any; + } + } + + public toJSON(): RecipientInfoJson { + const res: RecipientInfoJson = { + variant: this.variant + }; + + if (this.value && (this.variant >= 1) && (this.variant <= 4)) { + res.value = this.value.toJSON(); + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/RecipientKeyIdentifier.ts b/third_party/js/PKI.js/src/RecipientKeyIdentifier.ts new file mode 100644 index 0000000000..1d4a8a9ddc --- /dev/null +++ b/third_party/js/PKI.js/src/RecipientKeyIdentifier.ts @@ -0,0 +1,207 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { OtherKeyAttribute, OtherKeyAttributeJson, OtherKeyAttributeSchema } from "./OtherKeyAttribute"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const SUBJECT_KEY_IDENTIFIER = "subjectKeyIdentifier"; +const DATE = "date"; +const OTHER = "other"; +const CLEAR_PROPS = [ + SUBJECT_KEY_IDENTIFIER, + DATE, + OTHER, +]; + +export interface IRecipientKeyIdentifier { + subjectKeyIdentifier: asn1js.OctetString; + date?: asn1js.GeneralizedTime; + other?: OtherKeyAttribute; +} + +export interface RecipientKeyIdentifierJson { + subjectKeyIdentifier: asn1js.OctetStringJson; + date?: asn1js.BaseBlockJson; + other?: OtherKeyAttributeJson; +} + +export type RecipientKeyIdentifierParameters = PkiObjectParameters & Partial<IRecipientKeyIdentifier>; + +export type RecipientKeyIdentifierSchema = Schema.SchemaParameters<{ + subjectKeyIdentifier?: string; + date?: string; + other?: OtherKeyAttributeSchema; +}>; + +/** + * Represents the RecipientKeyIdentifier structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + */ +export class RecipientKeyIdentifier extends PkiObject implements IRecipientKeyIdentifier { + + public static override CLASS_NAME = "RecipientKeyIdentifier"; + + public subjectKeyIdentifier!: asn1js.OctetString; + public date?: asn1js.GeneralizedTime; + public other?: OtherKeyAttribute; + + /** + * Initializes a new instance of the {@link RecipientKeyIdentifier} class + * @param parameters Initialization parameters + */ + constructor(parameters: RecipientKeyIdentifierParameters = {}) { + super(); + + this.subjectKeyIdentifier = pvutils.getParametersValue(parameters, SUBJECT_KEY_IDENTIFIER, RecipientKeyIdentifier.defaultValues(SUBJECT_KEY_IDENTIFIER)); + if (DATE in parameters) { + this.date = pvutils.getParametersValue(parameters, DATE, RecipientKeyIdentifier.defaultValues(DATE)); + } + if (OTHER in parameters) { + this.other = pvutils.getParametersValue(parameters, OTHER, RecipientKeyIdentifier.defaultValues(OTHER)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof SUBJECT_KEY_IDENTIFIER): asn1js.OctetString; + public static override defaultValues(memberName: typeof DATE): asn1js.GeneralizedTime; + public static override defaultValues(memberName: typeof OTHER): OtherKeyAttribute; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case SUBJECT_KEY_IDENTIFIER: + return new asn1js.OctetString(); + case DATE: + return new asn1js.GeneralizedTime(); + case OTHER: + return new OtherKeyAttribute(); + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case SUBJECT_KEY_IDENTIFIER: + return (memberValue.isEqual(RecipientKeyIdentifier.defaultValues(SUBJECT_KEY_IDENTIFIER))); + case DATE: + return ((memberValue.year === 0) && + (memberValue.month === 0) && + (memberValue.day === 0) && + (memberValue.hour === 0) && + (memberValue.minute === 0) && + (memberValue.second === 0) && + (memberValue.millisecond === 0)); + case OTHER: + return ((memberValue.keyAttrId === EMPTY_STRING) && (("keyAttr" in memberValue) === false)); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * RecipientKeyIdentifier ::= SEQUENCE { + * subjectKeyIdentifier SubjectKeyIdentifier, + * date GeneralizedTime OPTIONAL, + * other OtherKeyAttribute OPTIONAL } + *``` + */ + public static override schema(parameters: RecipientKeyIdentifierSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.OctetString({ name: (names.subjectKeyIdentifier || EMPTY_STRING) }), + new asn1js.GeneralizedTime({ + optional: true, + name: (names.date || EMPTY_STRING) + }), + OtherKeyAttribute.schema(names.other || {}) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + RecipientKeyIdentifier.schema({ + names: { + subjectKeyIdentifier: SUBJECT_KEY_IDENTIFIER, + date: DATE, + other: { + names: { + blockName: OTHER + } + } + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.subjectKeyIdentifier = asn1.result.subjectKeyIdentifier; + + if (DATE in asn1.result) + this.date = asn1.result.date; + + if (OTHER in asn1.result) + this.other = new OtherKeyAttribute({ schema: asn1.result.other }); + } + + public toSchema(): asn1js.Sequence { + // Create array for output sequence + const outputArray = []; + + outputArray.push(this.subjectKeyIdentifier); + + if (this.date) { + outputArray.push(this.date); + } + + if (this.other) { + outputArray.push(this.other.toSchema()); + } + + // Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + } + + public toJSON(): RecipientKeyIdentifierJson { + const res: any = { + subjectKeyIdentifier: this.subjectKeyIdentifier.toJSON() + }; + + if (this.date) { + res.date = this.date.toJSON(); + } + + if (this.other) { + res.other = this.other.toJSON(); + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/RelativeDistinguishedNames.ts b/third_party/js/PKI.js/src/RelativeDistinguishedNames.ts new file mode 100644 index 0000000000..9022cdb6d3 --- /dev/null +++ b/third_party/js/PKI.js/src/RelativeDistinguishedNames.ts @@ -0,0 +1,202 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { AttributeTypeAndValue, AttributeTypeAndValueJson } from "./AttributeTypeAndValue"; +import { EMPTY_BUFFER, EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +export const TYPE_AND_VALUES = "typesAndValues"; +export const VALUE_BEFORE_DECODE = "valueBeforeDecode"; +export const RDN = "RDN"; + +export interface IRelativeDistinguishedNames { + /** + * Array of "type and value" objects + */ + typesAndValues: AttributeTypeAndValue[]; + /** + * Value of the RDN before decoding from schema + */ + valueBeforeDecode: ArrayBuffer; +} + +export type RelativeDistinguishedNamesParameters = PkiObjectParameters & Partial<IRelativeDistinguishedNames>; + +export type RelativeDistinguishedNamesSchema = Schema.SchemaParameters<{ + repeatedSequence?: string; + repeatedSet?: string; + typeAndValue?: Schema.SchemaType; +}>; + +export interface RelativeDistinguishedNamesJson { + typesAndValues: AttributeTypeAndValueJson[]; +} + +/** + * Represents the RelativeDistinguishedNames structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class RelativeDistinguishedNames extends PkiObject implements IRelativeDistinguishedNames { + + public static override CLASS_NAME = "RelativeDistinguishedNames"; + + public typesAndValues!: AttributeTypeAndValue[]; + public valueBeforeDecode!: ArrayBuffer; + + /** + * Initializes a new instance of the {@link RelativeDistinguishedNames} class + * @param parameters Initialization parameters + */ + constructor(parameters: RelativeDistinguishedNamesParameters = {}) { + super(); + + this.typesAndValues = pvutils.getParametersValue(parameters, TYPE_AND_VALUES, RelativeDistinguishedNames.defaultValues(TYPE_AND_VALUES)); + this.valueBeforeDecode = pvutils.getParametersValue(parameters, VALUE_BEFORE_DECODE, RelativeDistinguishedNames.defaultValues(VALUE_BEFORE_DECODE)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof TYPE_AND_VALUES): AttributeTypeAndValue[]; + public static override defaultValues(memberName: typeof VALUE_BEFORE_DECODE): ArrayBuffer; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case TYPE_AND_VALUES: + return []; + case VALUE_BEFORE_DECODE: + return EMPTY_BUFFER; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compares values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case TYPE_AND_VALUES: + return (memberValue.length === 0); + case VALUE_BEFORE_DECODE: + return (memberValue.byteLength === 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * RDNSequence ::= Sequence OF RelativeDistinguishedName + * + * RelativeDistinguishedName ::= + * SET SIZE (1..MAX) OF AttributeTypeAndValue + *``` + */ + static override schema(parameters: RelativeDistinguishedNamesSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Repeated({ + name: (names.repeatedSequence || EMPTY_STRING), + value: new asn1js.Set({ + value: [ + new asn1js.Repeated({ + name: (names.repeatedSet || EMPTY_STRING), + value: AttributeTypeAndValue.schema(names.typeAndValue || {}) + }) + ] + } as any) + } as any) + ] + } as any)); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, [ + RDN, + TYPE_AND_VALUES + ]); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + RelativeDistinguishedNames.schema({ + names: { + blockName: RDN, + repeatedSet: TYPE_AND_VALUES + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + if (TYPE_AND_VALUES in asn1.result) {// Could be a case when there is no "types and values" + this.typesAndValues = Array.from(asn1.result.typesAndValues, element => new AttributeTypeAndValue({ schema: element })); + } + + this.valueBeforeDecode = (asn1.result.RDN as asn1js.BaseBlock).valueBeforeDecodeView.slice().buffer; + } + + public toSchema(): asn1js.Sequence { + if (this.valueBeforeDecode.byteLength === 0) // No stored encoded array, create "from scratch" + { + return (new asn1js.Sequence({ + value: [new asn1js.Set({ + value: Array.from(this.typesAndValues, o => o.toSchema()) + } as any)] + } as any)); + } + + const asn1 = asn1js.fromBER(this.valueBeforeDecode); + AsnError.assert(asn1, "RelativeDistinguishedNames"); + if (!(asn1.result instanceof asn1js.Sequence)) { + throw new Error("ASN.1 result should be SEQUENCE"); + } + + return asn1.result; + } + + public toJSON(): RelativeDistinguishedNamesJson { + return { + typesAndValues: Array.from(this.typesAndValues, o => o.toJSON()) + }; + } + + /** + * Compares two RDN values, or RDN with ArrayBuffer value + * @param compareTo The value compare to current + */ + public isEqual(compareTo: unknown): boolean { + if (compareTo instanceof RelativeDistinguishedNames) { + if (this.typesAndValues.length !== compareTo.typesAndValues.length) + return false; + + for (const [index, typeAndValue] of this.typesAndValues.entries()) { + if (typeAndValue.isEqual(compareTo.typesAndValues[index]) === false) + return false; + } + + return true; + } + + if (compareTo instanceof ArrayBuffer) { + return pvutils.isEqualBuffer(this.valueBeforeDecode, compareTo); + } + + return false; + } + +} diff --git a/third_party/js/PKI.js/src/Request.ts b/third_party/js/PKI.js/src/Request.ts new file mode 100644 index 0000000000..e3397e424f --- /dev/null +++ b/third_party/js/PKI.js/src/Request.ts @@ -0,0 +1,201 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { CertID, CertIDJson, CertIDSchema } from "./CertID"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { Extension, ExtensionJson, ExtensionSchema } from "./Extension"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const REQ_CERT = "reqCert"; +const SINGLE_REQUEST_EXTENSIONS = "singleRequestExtensions"; +const CLEAR_PROPS = [ + REQ_CERT, + SINGLE_REQUEST_EXTENSIONS, +]; + +export interface IRequest { + reqCert: CertID; + singleRequestExtensions?: Extension[]; +} + +export interface RequestJson { + reqCert: CertIDJson; + singleRequestExtensions?: ExtensionJson[]; +} + +export type RequestParameters = PkiObjectParameters & Partial<IRequest>; + +export type RequestSchema = Schema.SchemaParameters<{ + reqCert?: CertIDSchema; + extensions?: ExtensionSchema; + singleRequestExtensions?: string; +}>; + +/** + * Represents an Request described in [RFC6960](https://datatracker.ietf.org/doc/html/rfc6960) + */ +export class Request extends PkiObject implements IRequest { + + public static override CLASS_NAME = "Request"; + + public reqCert!: CertID; + public singleRequestExtensions?: Extension[]; + + /** + * Initializes a new instance of the {@link Request} class + * @param parameters Initialization parameters + */ + constructor(parameters: RequestParameters = {}) { + super(); + + this.reqCert = pvutils.getParametersValue(parameters, REQ_CERT, Request.defaultValues(REQ_CERT)); + if (SINGLE_REQUEST_EXTENSIONS in parameters) { + this.singleRequestExtensions = pvutils.getParametersValue(parameters, SINGLE_REQUEST_EXTENSIONS, Request.defaultValues(SINGLE_REQUEST_EXTENSIONS)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof REQ_CERT): CertID; + public static override defaultValues(memberName: typeof SINGLE_REQUEST_EXTENSIONS): Extension[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case REQ_CERT: + return new CertID(); + case SINGLE_REQUEST_EXTENSIONS: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case REQ_CERT: + return (memberValue.isEqual(Request.defaultValues(memberName))); + case SINGLE_REQUEST_EXTENSIONS: + return (memberValue.length === 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * Request ::= SEQUENCE { + * reqCert CertID, + * singleRequestExtensions [0] EXPLICIT Extensions OPTIONAL } + *``` + */ + public static override schema(parameters: RequestSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + CertID.schema(names.reqCert || {}), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [Extension.schema(names.extensions || { + names: { + blockName: (names.singleRequestExtensions || EMPTY_STRING) + } + })] + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + Request.schema({ + names: { + reqCert: { + names: { + blockName: REQ_CERT + } + }, + extensions: { + names: { + blockName: SINGLE_REQUEST_EXTENSIONS + } + } + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.reqCert = new CertID({ schema: asn1.result.reqCert }); + if (SINGLE_REQUEST_EXTENSIONS in asn1.result) { + this.singleRequestExtensions = Array.from(asn1.result.singleRequestExtensions.valueBlock.value, element => new Extension({ schema: element })); + } + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray = []; + + outputArray.push(this.reqCert.toSchema()); + + if (this.singleRequestExtensions) { + outputArray.push(new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + new asn1js.Sequence({ + value: Array.from(this.singleRequestExtensions, o => o.toSchema()) + }) + ] + })); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): RequestJson { + const res: RequestJson = { + reqCert: this.reqCert.toJSON() + }; + + if (this.singleRequestExtensions) { + res.singleRequestExtensions = Array.from(this.singleRequestExtensions, o => o.toJSON()); + } + + return res; + } + +} + diff --git a/third_party/js/PKI.js/src/ResponseBytes.ts b/third_party/js/PKI.js/src/ResponseBytes.ts new file mode 100644 index 0000000000..cd9e9fd2b1 --- /dev/null +++ b/third_party/js/PKI.js/src/ResponseBytes.ts @@ -0,0 +1,150 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const RESPONSE_TYPE = "responseType"; +const RESPONSE = "response"; +const CLEAR_PROPS = [ + RESPONSE_TYPE, + RESPONSE +]; + +export interface IResponseBytes { + responseType: string; + response: asn1js.OctetString; +} + +export interface ResponseBytesJson { + responseType: string; + response: asn1js.OctetStringJson; +} + +export type ResponseBytesParameters = PkiObjectParameters & Partial<IResponseBytes>; + +export type ResponseBytesSchema = Schema.SchemaParameters<{ + responseType?: string; + response?: string; +}>; + +/** + * Class from RFC6960 + */ +export class ResponseBytes extends PkiObject implements IResponseBytes { + + public static override CLASS_NAME = "ResponseBytes"; + + public responseType!: string; + public response!: asn1js.OctetString; + + /** + * Initializes a new instance of the {@link Request} class + * @param parameters Initialization parameters + */ + constructor(parameters: ResponseBytesParameters = {}) { + super(); + + this.responseType = pvutils.getParametersValue(parameters, RESPONSE_TYPE, ResponseBytes.defaultValues(RESPONSE_TYPE)); + this.response = pvutils.getParametersValue(parameters, RESPONSE, ResponseBytes.defaultValues(RESPONSE)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof RESPONSE_TYPE): string; + public static override defaultValues(memberName: typeof RESPONSE): asn1js.OctetString; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case RESPONSE_TYPE: + return EMPTY_STRING; + case RESPONSE: + return new asn1js.OctetString(); + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case RESPONSE_TYPE: + return (memberValue === EMPTY_STRING); + case RESPONSE: + return (memberValue.isEqual(ResponseBytes.defaultValues(memberName))); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * ResponseBytes ::= SEQUENCE { + * responseType OBJECT IDENTIFIER, + * response OCTET STRING } + *``` + */ + public static override schema(parameters: ResponseBytesSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.ObjectIdentifier({ name: (names.responseType || EMPTY_STRING) }), + new asn1js.OctetString({ name: (names.response || EMPTY_STRING) }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + ResponseBytes.schema({ + names: { + responseType: RESPONSE_TYPE, + response: RESPONSE + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.responseType = asn1.result.responseType.valueBlock.toString(); + this.response = asn1.result.response; + } + + public toSchema(): asn1js.Sequence { + // Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: [ + new asn1js.ObjectIdentifier({ value: this.responseType }), + this.response + ] + })); + } + + public toJSON(): ResponseBytesJson { + return { + responseType: this.responseType, + response: this.response.toJSON(), + }; + } + +} diff --git a/third_party/js/PKI.js/src/ResponseData.ts b/third_party/js/PKI.js/src/ResponseData.ts new file mode 100644 index 0000000000..b2c4c38d31 --- /dev/null +++ b/third_party/js/PKI.js/src/ResponseData.ts @@ -0,0 +1,373 @@ +import * as asn1js from "asn1js"; +import * as pvtsutils from "pvtsutils"; +import * as pvutils from "pvutils"; +import { RelativeDistinguishedNames, RelativeDistinguishedNamesSchema } from "./RelativeDistinguishedNames"; +import { SingleResponse, SingleResponseJson, SingleResponseSchema } from "./SingleResponse"; +import { Extension, ExtensionJson } from "./Extension"; +import { Extensions, ExtensionsSchema } from "./Extensions"; +import * as Schema from "./Schema"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { EMPTY_BUFFER } from "./constants"; + +const TBS = "tbs"; +const VERSION = "version"; +const RESPONDER_ID = "responderID"; +const PRODUCED_AT = "producedAt"; +const RESPONSES = "responses"; +const RESPONSE_EXTENSIONS = "responseExtensions"; +const RESPONSE_DATA = "ResponseData"; +const RESPONSE_DATA_VERSION = `${RESPONSE_DATA}.${VERSION}`; +const RESPONSE_DATA_RESPONDER_ID = `${RESPONSE_DATA}.${RESPONDER_ID}`; +const RESPONSE_DATA_PRODUCED_AT = `${RESPONSE_DATA}.${PRODUCED_AT}`; +const RESPONSE_DATA_RESPONSES = `${RESPONSE_DATA}.${RESPONSES}`; +const RESPONSE_DATA_RESPONSE_EXTENSIONS = `${RESPONSE_DATA}.${RESPONSE_EXTENSIONS}`; +const CLEAR_PROPS = [ + RESPONSE_DATA, + RESPONSE_DATA_VERSION, + RESPONSE_DATA_RESPONDER_ID, + RESPONSE_DATA_PRODUCED_AT, + RESPONSE_DATA_RESPONSES, + RESPONSE_DATA_RESPONSE_EXTENSIONS +]; + +export interface IResponseData { + version?: number; + tbs: ArrayBuffer; + responderID: any; + producedAt: Date; + responses: SingleResponse[]; + responseExtensions?: Extension[]; +} + +export type ResponseDataParameters = PkiObjectParameters & Partial<IResponseData>; + +export type ResponseDataSchema = Schema.SchemaParameters<{ + version?: string; + responderID?: string; + ResponseDataByName?: RelativeDistinguishedNamesSchema; + ResponseDataByKey?: string; + producedAt?: string; + response?: SingleResponseSchema; + extensions?: ExtensionsSchema; +}>; + +export interface ResponseDataJson { + version?: number; + tbs: string; + responderID: any; + producedAt: Date; + responses: SingleResponseJson[]; + responseExtensions?: ExtensionJson[]; +} + +/** + * Represents an ResponseData described in [RFC6960](https://datatracker.ietf.org/doc/html/rfc6960) + */ +export class ResponseData extends PkiObject implements IResponseData { + + public static override CLASS_NAME = "ResponseData"; + + public version?: number; + public tbsView!: Uint8Array; + /** + * @deprecated Since version 3.0.0 + */ + public get tbs(): ArrayBuffer { + return pvtsutils.BufferSourceConverter.toArrayBuffer(this.tbsView); + } + + /** + * @deprecated Since version 3.0.0 + */ + public set tbs(value: ArrayBuffer) { + this.tbsView = new Uint8Array(value); + } + public responderID: any; + public producedAt!: Date; + public responses!: SingleResponse[]; + public responseExtensions?: Extension[]; + + /** + * Initializes a new instance of the {@link ResponseData} class + * @param parameters Initialization parameters + */ + constructor(parameters: ResponseDataParameters = {}) { + super(); + + this.tbsView = new Uint8Array(pvutils.getParametersValue(parameters, TBS, ResponseData.defaultValues(TBS))); + if (VERSION in parameters) { + this.version = pvutils.getParametersValue(parameters, VERSION, ResponseData.defaultValues(VERSION)); + } + this.responderID = pvutils.getParametersValue(parameters, RESPONDER_ID, ResponseData.defaultValues(RESPONDER_ID)); + this.producedAt = pvutils.getParametersValue(parameters, PRODUCED_AT, ResponseData.defaultValues(PRODUCED_AT)); + this.responses = pvutils.getParametersValue(parameters, RESPONSES, ResponseData.defaultValues(RESPONSES)); + if (RESPONSE_EXTENSIONS in parameters) { + this.responseExtensions = pvutils.getParametersValue(parameters, RESPONSE_EXTENSIONS, ResponseData.defaultValues(RESPONSE_EXTENSIONS)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof TBS): ArrayBuffer; + public static override defaultValues(memberName: typeof VERSION): number; + public static override defaultValues(memberName: typeof RESPONDER_ID): any; + public static override defaultValues(memberName: typeof PRODUCED_AT): Date; + public static override defaultValues(memberName: typeof RESPONSES): SingleResponse[]; + public static override defaultValues(memberName: typeof RESPONSE_EXTENSIONS): Extension[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case VERSION: + return 0; + case TBS: + return EMPTY_BUFFER; + case RESPONDER_ID: + return {}; + case PRODUCED_AT: + return new Date(0, 0, 0); + case RESPONSES: + case RESPONSE_EXTENSIONS: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + // TODO version? + case TBS: + return (memberValue.byteLength === 0); + case RESPONDER_ID: + return (Object.keys(memberValue).length === 0); + case PRODUCED_AT: + return (memberValue === ResponseData.defaultValues(memberName)); + case RESPONSES: + case RESPONSE_EXTENSIONS: + return (memberValue.length === 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * ResponseData ::= SEQUENCE { + * version [0] EXPLICIT Version DEFAULT v1, + * responderID ResponderID, + * producedAt GeneralizedTime, + * responses SEQUENCE OF SingleResponse, + * responseExtensions [1] EXPLICIT Extensions OPTIONAL } + *``` + */ + public static override schema(parameters: ResponseDataSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || RESPONSE_DATA), + value: [ + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [new asn1js.Integer({ name: (names.version || RESPONSE_DATA_VERSION) })] + }), + new asn1js.Choice({ + value: [ + new asn1js.Constructed({ + name: (names.responderID || RESPONSE_DATA_RESPONDER_ID), + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [RelativeDistinguishedNames.schema(names.ResponseDataByName || { + names: { + blockName: "ResponseData.byName" + } + })] + }), + new asn1js.Constructed({ + name: (names.responderID || RESPONSE_DATA_RESPONDER_ID), + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 2 // [2] + }, + value: [new asn1js.OctetString({ name: (names.ResponseDataByKey || "ResponseData.byKey") })] + }) + ] + }), + new asn1js.GeneralizedTime({ name: (names.producedAt || RESPONSE_DATA_PRODUCED_AT) }), + new asn1js.Sequence({ + value: [ + new asn1js.Repeated({ + name: RESPONSE_DATA_RESPONSES, + value: SingleResponse.schema(names.response || {}) + }) + ] + }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [Extensions.schema(names.extensions || { + names: { + blockName: RESPONSE_DATA_RESPONSE_EXTENSIONS + } + })] + }) // EXPLICIT SEQUENCE value + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + //#region Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + ResponseData.schema() + ); + + AsnError.assertSchema(asn1, this.className); + //#endregion + + //#region Get internal properties from parsed schema + this.tbsView = (asn1.result.ResponseData as asn1js.Sequence).valueBeforeDecodeView; + + if (RESPONSE_DATA_VERSION in asn1.result) + this.version = asn1.result[RESPONSE_DATA_VERSION].valueBlock.valueDec; + + if (asn1.result[RESPONSE_DATA_RESPONDER_ID].idBlock.tagNumber === 1) + this.responderID = new RelativeDistinguishedNames({ schema: asn1.result[RESPONSE_DATA_RESPONDER_ID].valueBlock.value[0] }); + else + this.responderID = asn1.result[RESPONSE_DATA_RESPONDER_ID].valueBlock.value[0]; // OCTET_STRING + + this.producedAt = asn1.result[RESPONSE_DATA_PRODUCED_AT].toDate(); + this.responses = Array.from(asn1.result[RESPONSE_DATA_RESPONSES], element => new SingleResponse({ schema: element })); + + if (RESPONSE_DATA_RESPONSE_EXTENSIONS in asn1.result) + this.responseExtensions = Array.from(asn1.result[RESPONSE_DATA_RESPONSE_EXTENSIONS].valueBlock.value, element => new Extension({ schema: element })); + //#endregion + } + + public toSchema(encodeFlag = false): Schema.SchemaType { + //#region Decode stored TBS value + let tbsSchema; + + if (encodeFlag === false) { + if (!this.tbsView.byteLength) {// No stored certificate TBS part + return ResponseData.schema(); + } + + const asn1 = asn1js.fromBER(this.tbsView); + AsnError.assert(asn1, "TBS Response Data"); + tbsSchema = asn1.result; + } + //#endregion + //#region Create TBS schema via assembling from TBS parts + else { + const outputArray = []; + + if (VERSION in this) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [new asn1js.Integer({ value: this.version })] + })); + } + + if (this.responderID instanceof RelativeDistinguishedNames) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [this.responderID.toSchema()] + })); + } else { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 2 // [2] + }, + value: [this.responderID] + })); + } + + outputArray.push(new asn1js.GeneralizedTime({ valueDate: this.producedAt })); + + outputArray.push(new asn1js.Sequence({ + value: Array.from(this.responses, o => o.toSchema()) + })); + + if (this.responseExtensions) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [new asn1js.Sequence({ + value: Array.from(this.responseExtensions, o => o.toSchema()) + })] + })); + } + + tbsSchema = new asn1js.Sequence({ + value: outputArray + }); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return tbsSchema; + //#endregion + } + + public toJSON(): ResponseDataJson { + const res = {} as ResponseDataJson; + + if (VERSION in this) { + res.version = this.version; + } + + if (this.responderID) { + res.responderID = this.responderID; + } + + if (this.producedAt) { + res.producedAt = this.producedAt; + } + + if (this.responses) { + res.responses = Array.from(this.responses, o => o.toJSON()); + } + + if (this.responseExtensions) { + res.responseExtensions = Array.from(this.responseExtensions, o => o.toJSON()); + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/RevocationInfoChoices.ts b/third_party/js/PKI.js/src/RevocationInfoChoices.ts new file mode 100644 index 0000000000..b72f16b97e --- /dev/null +++ b/third_party/js/PKI.js/src/RevocationInfoChoices.ts @@ -0,0 +1,170 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { CertificateRevocationList, CertificateRevocationListJson } from "./CertificateRevocationList"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { OtherRevocationInfoFormat, OtherRevocationInfoFormatJson } from "./OtherRevocationInfoFormat"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const CRLS = "crls"; +const OTHER_REVOCATION_INFOS = "otherRevocationInfos"; +const CLEAR_PROPS = [ + CRLS +]; + +export interface IRevocationInfoChoices { + crls: CertificateRevocationList[]; + otherRevocationInfos: OtherRevocationInfoFormat[]; +} + +export interface RevocationInfoChoicesJson { + crls: CertificateRevocationListJson[]; + otherRevocationInfos: OtherRevocationInfoFormatJson[]; +} + +export type RevocationInfoChoicesParameters = PkiObjectParameters & Partial<IRevocationInfoChoices>; + +export type RevocationInfoChoicesSchema = Schema.SchemaParameters<{ + crls?: string; +}>; + +/** + * Represents the RevocationInfoChoices structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + */ +export class RevocationInfoChoices extends PkiObject implements IRevocationInfoChoices { + + public static override CLASS_NAME = "RevocationInfoChoices"; + + public crls!: CertificateRevocationList[]; + public otherRevocationInfos!: OtherRevocationInfoFormat[]; + + /** + * Initializes a new instance of the {@link RevocationInfoChoices} class + * @param parameters Initialization parameters + */ + constructor(parameters: RevocationInfoChoicesParameters = {}) { + super(); + + this.crls = pvutils.getParametersValue(parameters, CRLS, RevocationInfoChoices.defaultValues(CRLS)); + this.otherRevocationInfos = pvutils.getParametersValue(parameters, OTHER_REVOCATION_INFOS, RevocationInfoChoices.defaultValues(OTHER_REVOCATION_INFOS)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof CRLS): CertificateRevocationList[]; + public static override defaultValues(memberName: typeof OTHER_REVOCATION_INFOS): OtherRevocationInfoFormat[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case CRLS: + return []; + case OTHER_REVOCATION_INFOS: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * RevocationInfoChoices ::= SET OF RevocationInfoChoice + * + * RevocationInfoChoice ::= CHOICE { + * crl CertificateList, + * other [1] IMPLICIT OtherRevocationInfoFormat } + *``` + */ + public static override schema(parameters: RevocationInfoChoicesSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Set({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Repeated({ + name: (names.crls || EMPTY_STRING), + value: new asn1js.Choice({ + value: [ + CertificateRevocationList.schema(), + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [ + new asn1js.ObjectIdentifier(), + new asn1js.Any() + ] + }) + ] + }) + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + RevocationInfoChoices.schema({ + names: { + crls: CRLS + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + if (asn1.result.crls) { + for (const element of asn1.result.crls) { + if (element.idBlock.tagClass === 1) + this.crls.push(new CertificateRevocationList({ schema: element })); + else + this.otherRevocationInfos.push(new OtherRevocationInfoFormat({ schema: element })); + } + } + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output set + const outputArray = []; + + outputArray.push(...Array.from(this.crls, o => o.toSchema())); + + outputArray.push(...Array.from(this.otherRevocationInfos, element => { + const schema = element.toSchema(); + + schema.idBlock.tagClass = 3; + schema.idBlock.tagNumber = 1; + + return schema; + })); + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Set({ + value: outputArray + })); + //#endregion + } + + public toJSON(): RevocationInfoChoicesJson { + return { + crls: Array.from(this.crls, o => o.toJSON()), + otherRevocationInfos: Array.from(this.otherRevocationInfos, o => o.toJSON()) + }; + } + +} diff --git a/third_party/js/PKI.js/src/RevokedCertificate.ts b/third_party/js/PKI.js/src/RevokedCertificate.ts new file mode 100644 index 0000000000..9bfa8887c3 --- /dev/null +++ b/third_party/js/PKI.js/src/RevokedCertificate.ts @@ -0,0 +1,169 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { Time, TimeJson } from "./Time"; +import { Extensions, ExtensionsJson } from "./Extensions"; +import * as Schema from "./Schema"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { AsnError } from "./errors"; +import { EMPTY_STRING } from "./constants"; + +const USER_CERTIFICATE = "userCertificate"; +const REVOCATION_DATE = "revocationDate"; +const CRL_ENTRY_EXTENSIONS = "crlEntryExtensions"; +const CLEAR_PROPS = [ + USER_CERTIFICATE, + REVOCATION_DATE, + CRL_ENTRY_EXTENSIONS +]; + +export interface IRevokedCertificate { + userCertificate: asn1js.Integer; + revocationDate: Time; + crlEntryExtensions?: Extensions; +} + +export type RevokedCertificateParameters = PkiObjectParameters & Partial<IRevokedCertificate>; + +export interface RevokedCertificateJson { + userCertificate: asn1js.IntegerJson; + revocationDate: TimeJson; + crlEntryExtensions?: ExtensionsJson; +} + +/** + * Represents the RevokedCertificate structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class RevokedCertificate extends PkiObject implements IRevokedCertificate { + + public static override CLASS_NAME = "RevokedCertificate"; + + public userCertificate!: asn1js.Integer; + public revocationDate!: Time; + public crlEntryExtensions?: Extensions; + + /** + * Initializes a new instance of the {@link RevokedCertificate} class + * @param parameters Initialization parameters + */ + constructor(parameters: RevokedCertificateParameters = {}) { + super(); + + this.userCertificate = pvutils.getParametersValue(parameters, USER_CERTIFICATE, RevokedCertificate.defaultValues(USER_CERTIFICATE)); + this.revocationDate = pvutils.getParametersValue(parameters, REVOCATION_DATE, RevokedCertificate.defaultValues(REVOCATION_DATE)); + if (CRL_ENTRY_EXTENSIONS in parameters) { + this.crlEntryExtensions = pvutils.getParametersValue(parameters, CRL_ENTRY_EXTENSIONS, RevokedCertificate.defaultValues(CRL_ENTRY_EXTENSIONS)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof USER_CERTIFICATE): asn1js.Integer; + public static override defaultValues(memberName: typeof REVOCATION_DATE): Time; + public static override defaultValues(memberName: typeof CRL_ENTRY_EXTENSIONS): Extensions; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case USER_CERTIFICATE: + return new asn1js.Integer(); + case REVOCATION_DATE: + return new Time(); + case CRL_ENTRY_EXTENSIONS: + return new Extensions(); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * revokedCertificates SEQUENCE OF SEQUENCE { + * userCertificate CertificateSerialNumber, + * revocationDate Time, + * crlEntryExtensions Extensions OPTIONAL + * -- if present, version MUST be v2 + * } OPTIONAL, + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + userCertificate?: string; + revocationDate?: string; + crlEntryExtensions?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Integer({ name: (names.userCertificate || USER_CERTIFICATE) }), + Time.schema({ + names: { + utcTimeName: (names.revocationDate || REVOCATION_DATE), + generalTimeName: (names.revocationDate || REVOCATION_DATE) + } + }), + Extensions.schema({ + names: { + blockName: (names.crlEntryExtensions || CRL_ENTRY_EXTENSIONS) + } + }, true) + ] + }); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + RevokedCertificate.schema() + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.userCertificate = asn1.result.userCertificate; + this.revocationDate = new Time({ schema: asn1.result.revocationDate }); + if (CRL_ENTRY_EXTENSIONS in asn1.result) { + this.crlEntryExtensions = new Extensions({ schema: asn1.result.crlEntryExtensions }); + } + } + + public toSchema(): asn1js.Sequence { + // Create array for output sequence + const outputArray: any[] = [ + this.userCertificate, + this.revocationDate.toSchema() + ]; + if (this.crlEntryExtensions) { + outputArray.push(this.crlEntryExtensions.toSchema()); + } + + // Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + } + + public toJSON(): RevokedCertificateJson { + const res: RevokedCertificateJson = { + userCertificate: this.userCertificate.toJSON(), + revocationDate: this.revocationDate.toJSON(), + }; + + if (this.crlEntryExtensions) { + res.crlEntryExtensions = this.crlEntryExtensions.toJSON(); + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/SafeBag.ts b/third_party/js/PKI.js/src/SafeBag.ts new file mode 100644 index 0000000000..f2230f09bf --- /dev/null +++ b/third_party/js/PKI.js/src/SafeBag.ts @@ -0,0 +1,215 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { Attribute, AttributeJson } from "./Attribute"; +import * as Schema from "./Schema"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { AsnError } from "./errors"; + +const BAG_ID = "bagId"; +const BAG_VALUE = "bagValue"; +const BAG_ATTRIBUTES = "bagAttributes"; +const CLEAR_PROPS = [ + BAG_ID, + BAG_VALUE, + BAG_ATTRIBUTES +]; + +export interface ISafeBag<T extends BagType = BagType> { + bagId: string; + bagValue: T; + bagAttributes?: Attribute[]; +} + +export type SafeBagParameters<T extends BagType = BagType> = PkiObjectParameters & Partial<ISafeBag<T>>; + +export interface SafeBagJson { + bagId: string; + bagValue: BagTypeJson; + bagAttributes?: AttributeJson[]; +} + +/** + * Represents the SafeBag structure described in [RFC7292](https://datatracker.ietf.org/doc/html/rfc7292) + */ +export class SafeBag<T extends BagType = BagType> extends PkiObject implements ISafeBag<T> { + + public static override CLASS_NAME = "SafeBag"; + + public bagId!: string; + public bagValue!: T; + public bagAttributes?: Attribute[]; + + /** + * Initializes a new instance of the {@link SafeBag} class + * @param parameters Initialization parameters + */ + constructor(parameters: SafeBagParameters<T> = {}) { + super(); + + this.bagId = pvutils.getParametersValue(parameters, BAG_ID, SafeBag.defaultValues(BAG_ID)); + this.bagValue = pvutils.getParametersValue(parameters, BAG_VALUE, SafeBag.defaultValues(BAG_VALUE)) as unknown as T; + if (BAG_ATTRIBUTES in parameters) { + this.bagAttributes = pvutils.getParametersValue(parameters, BAG_ATTRIBUTES, SafeBag.defaultValues(BAG_ATTRIBUTES)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof BAG_ID): string; + public static override defaultValues(memberName: typeof BAG_VALUE): BagType; + public static override defaultValues(memberName: typeof BAG_ATTRIBUTES): Attribute[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case BAG_ID: + return EMPTY_STRING; + case BAG_VALUE: + return (new asn1js.Any()); + case BAG_ATTRIBUTES: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case BAG_ID: + return (memberValue === EMPTY_STRING); + case BAG_VALUE: + return (memberValue instanceof asn1js.Any); + case BAG_ATTRIBUTES: + return (memberValue.length === 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * SafeBag ::= SEQUENCE { + * bagId BAG-TYPE.&id ({PKCS12BagSet}), + * bagValue [0] EXPLICIT BAG-TYPE.&Type({PKCS12BagSet}{@bagId}), + * bagAttributes SET OF PKCS12Attribute OPTIONAL + * } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + bagId?: string; + bagValue?: string; + bagAttributes?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.ObjectIdentifier({ name: (names.bagId || BAG_ID) }), + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [new asn1js.Any({ name: (names.bagValue || BAG_VALUE) })] // EXPLICIT ANY value + }), + new asn1js.Set({ + optional: true, + value: [ + new asn1js.Repeated({ + name: (names.bagAttributes || BAG_ATTRIBUTES), + value: Attribute.schema() + }) + ] + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + SafeBag.schema({ + names: { + bagId: BAG_ID, + bagValue: BAG_VALUE, + bagAttributes: BAG_ATTRIBUTES + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + //#region Get internal properties from parsed schema + this.bagId = asn1.result.bagId.valueBlock.toString(); + + const bagType = SafeBagValueFactory.find(this.bagId); + if (!bagType) { + throw new Error(`Invalid BAG_ID for SafeBag: ${this.bagId}`); + } + this.bagValue = new bagType({ schema: asn1.result.bagValue }) as unknown as T; + + if (BAG_ATTRIBUTES in asn1.result) { + this.bagAttributes = Array.from(asn1.result.bagAttributes, element => new Attribute({ schema: element })); + } + //#endregion + } + + public toSchema(): asn1js.Sequence { + //#region Construct and return new ASN.1 schema for this object + const outputArray = [ + new asn1js.ObjectIdentifier({ value: this.bagId }), + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [this.bagValue.toSchema()] + }) + ]; + + if (this.bagAttributes) { + outputArray.push(new asn1js.Set({ + value: Array.from(this.bagAttributes, o => o.toSchema()) + })); + } + + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): SafeBagJson { + const output: SafeBagJson = { + bagId: this.bagId, + bagValue: this.bagValue.toJSON() + }; + + if (this.bagAttributes) { + output.bagAttributes = Array.from(this.bagAttributes, o => o.toJSON()); + } + + return output; + } + +} + +import { type BagType, SafeBagValueFactory, BagTypeJson } from "./SafeBagValueFactory"; +import { EMPTY_STRING } from "./constants"; + diff --git a/third_party/js/PKI.js/src/SafeBagValueFactory.ts b/third_party/js/PKI.js/src/SafeBagValueFactory.ts new file mode 100644 index 0000000000..3bbfe558bc --- /dev/null +++ b/third_party/js/PKI.js/src/SafeBagValueFactory.ts @@ -0,0 +1,41 @@ +export type BagType = PrivateKeyInfo | PKCS8ShroudedKeyBag | CertBag | CRLBag | SecretBag | SafeContents; +export type BagTypeJson = PrivateKeyInfoJson | JsonWebKey | PKCS8ShroudedKeyBagJson | CertBagJson | CRLBagJson | SecretBagJson | SafeContentsJson; + +export interface BagTypeConstructor<T extends BagType> { + new(params: { schema: any; }): T; +} + +export class SafeBagValueFactory { + private static items?: Record<string, BagTypeConstructor<BagType>>; + + private static getItems(): Record<string, BagTypeConstructor<BagType>> { + if (!this.items) { + this.items = {}; + + SafeBagValueFactory.register("1.2.840.113549.1.12.10.1.1", PrivateKeyInfo); + SafeBagValueFactory.register("1.2.840.113549.1.12.10.1.2", PKCS8ShroudedKeyBag); + SafeBagValueFactory.register("1.2.840.113549.1.12.10.1.3", CertBag); + SafeBagValueFactory.register("1.2.840.113549.1.12.10.1.4", CRLBag); + SafeBagValueFactory.register("1.2.840.113549.1.12.10.1.5", SecretBag); + SafeBagValueFactory.register("1.2.840.113549.1.12.10.1.6", SafeContents); + } + + return this.items; + } + public static register<T extends BagType = BagType>(id: string, type: BagTypeConstructor<T>): void { + this.getItems()[id] = type; + } + + public static find(id: string): BagTypeConstructor<BagType> | null { + return this.getItems()[id] || null; + } + +} + +//! NOTE Bag type must be imported after the SafeBagValueFactory declaration +import { CertBag, CertBagJson } from "./CertBag"; +import { CRLBag, CRLBagJson } from "./CRLBag"; +import { PKCS8ShroudedKeyBag, PKCS8ShroudedKeyBagJson } from "./PKCS8ShroudedKeyBag"; +import { PrivateKeyInfo, PrivateKeyInfoJson } from "./PrivateKeyInfo"; +import { SafeContents, SafeContentsJson } from "./SafeContents"; +import { SecretBag, SecretBagJson } from "./SecretBag";
\ No newline at end of file diff --git a/third_party/js/PKI.js/src/SafeContents.ts b/third_party/js/PKI.js/src/SafeContents.ts new file mode 100644 index 0000000000..e534b7618a --- /dev/null +++ b/third_party/js/PKI.js/src/SafeContents.ts @@ -0,0 +1,132 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import * as Schema from "./Schema"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { AsnError } from "./errors"; + +const SAFE_BUGS = "safeBags"; + +export interface ISafeContents { + safeBags: SafeBag[]; +} + +export type SafeContentsParameters = PkiObjectParameters & Partial<ISafeContents>; + +export interface SafeContentsJson { + safeBags: SafeBagJson[]; +} + +/** + * Represents the SafeContents structure described in [RFC7292](https://datatracker.ietf.org/doc/html/rfc7292) + */ +export class SafeContents extends PkiObject implements ISafeContents { + + public static override CLASS_NAME = "SafeContents"; + + public safeBags!: SafeBag[]; + + /** + * Initializes a new instance of the {@link SafeContents} class + * @param parameters Initialization parameters + */ + constructor(parameters: SafeContentsParameters = {}) { + super(); + + this.safeBags = pvutils.getParametersValue(parameters, SAFE_BUGS, SafeContents.defaultValues(SAFE_BUGS)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof SAFE_BUGS): SafeBag[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case SAFE_BUGS: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case SAFE_BUGS: + return (memberValue.length === 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * SafeContents ::= SEQUENCE OF SafeBag + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + safeBags?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Repeated({ + name: (names.safeBags || EMPTY_STRING), + value: SafeBag.schema() + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, [ + SAFE_BUGS + ]); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + SafeContents.schema({ + names: { + safeBags: SAFE_BUGS + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.safeBags = Array.from(asn1.result.safeBags, element => new SafeBag({ schema: element })); + } + + public toSchema(): asn1js.Sequence { + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: Array.from(this.safeBags, o => o.toSchema()) + })); + //#endregion + } + + public toJSON(): SafeContentsJson { + return { + safeBags: Array.from(this.safeBags, o => o.toJSON()) + }; + } + +} + +import { SafeBag, SafeBagJson } from "./SafeBag";import { EMPTY_STRING } from "./constants"; + diff --git a/third_party/js/PKI.js/src/Schema.ts b/third_party/js/PKI.js/src/Schema.ts new file mode 100644 index 0000000000..a93004834a --- /dev/null +++ b/third_party/js/PKI.js/src/Schema.ts @@ -0,0 +1,31 @@ +export type SchemaType = any; + +export type SchemaNames = { + blockName?: string; + optional?: boolean; +}; + +export interface SchemaCompatible { + /** + * Converts parsed ASN.1 object into current class + * @param schema + */ + fromSchema(schema: SchemaType): void; + /** + * Convert current object to asn1js object and set correct values + * @returns asn1js object + */ + toSchema(): SchemaType; + toJSON(): any; +} + +export interface SchemaConstructor { + schema?: SchemaType; +} + +/** + * Parameters for schema generation + */ +export interface SchemaParameters<N extends Record<string, any> = { /**/ }> { + names?: SchemaNames & N; +} diff --git a/third_party/js/PKI.js/src/SecretBag.ts b/third_party/js/PKI.js/src/SecretBag.ts new file mode 100644 index 0000000000..998da3889e --- /dev/null +++ b/third_party/js/PKI.js/src/SecretBag.ts @@ -0,0 +1,161 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const SECRET_TYPE_ID = "secretTypeId"; +const SECRET_VALUE = "secretValue"; +const CLEAR_PROPS = [ + SECRET_TYPE_ID, + SECRET_VALUE, +]; + +export interface ISecretBag { + secretTypeId: string; + secretValue: Schema.SchemaCompatible; +} + +export interface SecretBagJson { + secretTypeId: string; + secretValue: asn1js.BaseBlockJson; +} + +export type SecretBagParameters = PkiObjectParameters & Partial<ISecretBag>; + +/** + * Represents the SecretBag structure described in [RFC7292](https://datatracker.ietf.org/doc/html/rfc7292) + */ +export class SecretBag extends PkiObject implements ISecretBag { + + public static override CLASS_NAME = "SecretBag"; + + public secretTypeId!: string; + public secretValue!: Schema.SchemaCompatible; + + /** + * Initializes a new instance of the {@link SecretBag} class + * @param parameters Initialization parameters + */ + constructor(parameters: SecretBagParameters = {}) { + super(); + + this.secretTypeId = pvutils.getParametersValue(parameters, SECRET_TYPE_ID, SecretBag.defaultValues(SECRET_TYPE_ID)); + this.secretValue = pvutils.getParametersValue(parameters, SECRET_VALUE, SecretBag.defaultValues(SECRET_VALUE)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof SECRET_TYPE_ID): string; + public static override defaultValues(memberName: typeof SECRET_VALUE): Schema.SchemaCompatible; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case SECRET_TYPE_ID: + return EMPTY_STRING; + case SECRET_VALUE: + return (new asn1js.Any()); + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case SECRET_TYPE_ID: + return (memberValue === EMPTY_STRING); + case SECRET_VALUE: + return (memberValue instanceof asn1js.Any); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * SecretBag ::= SEQUENCE { + * secretTypeId BAG-TYPE.&id ({SecretTypes}), + * secretValue [0] EXPLICIT BAG-TYPE.&Type ({SecretTypes}{@secretTypeId}) + * } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + id?: string; + value?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.ObjectIdentifier({ name: (names.id || "id") }), + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [new asn1js.Any({ name: (names.value || "value") })] // EXPLICIT ANY value + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + SecretBag.schema({ + names: { + id: SECRET_TYPE_ID, + value: SECRET_VALUE + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.secretTypeId = asn1.result.secretTypeId.valueBlock.toString(); + this.secretValue = asn1.result.secretValue; + } + + public toSchema(): asn1js.Sequence { + // Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: [ + new asn1js.ObjectIdentifier({ value: this.secretTypeId }), + new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [this.secretValue.toSchema()] + }) + ] + })); + } + + public toJSON(): SecretBagJson { + return { + secretTypeId: this.secretTypeId, + secretValue: this.secretValue.toJSON() + }; + } + +} diff --git a/third_party/js/PKI.js/src/Signature.ts b/third_party/js/PKI.js/src/Signature.ts new file mode 100644 index 0000000000..f0375a4cc0 --- /dev/null +++ b/third_party/js/PKI.js/src/Signature.ts @@ -0,0 +1,215 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier"; +import { Certificate, CertificateJson } from "./Certificate"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const SIGNATURE_ALGORITHM = "signatureAlgorithm"; +const SIGNATURE = "signature"; +const CERTS = "certs"; + +export interface ISignature { + signatureAlgorithm: AlgorithmIdentifier; + signature: asn1js.BitString; + certs?: Certificate[]; +} + +export interface SignatureJson { + signatureAlgorithm: AlgorithmIdentifierJson; + signature: asn1js.BitStringJson; + certs?: CertificateJson[]; +} + +export type SignatureParameters = PkiObjectParameters & Partial<ISignature>; + +export type SignatureSchema = Schema.SchemaParameters<{ + signatureAlgorithm?: AlgorithmIdentifierSchema; + signature?: string; + certs?: string; +}>; + +/** + * Represents the Signature structure described in [RFC6960](https://datatracker.ietf.org/doc/html/rfc6960) + */ +export class Signature extends PkiObject implements ISignature { + + public static override CLASS_NAME = "Signature"; + + public signatureAlgorithm!: AlgorithmIdentifier; + public signature!: asn1js.BitString; + public certs?: Certificate[]; + + /** + * Initializes a new instance of the {@link Signature} class + * @param parameters Initialization parameters + */ + constructor(parameters: SignatureParameters = {}) { + super(); + + this.signatureAlgorithm = pvutils.getParametersValue(parameters, SIGNATURE_ALGORITHM, Signature.defaultValues(SIGNATURE_ALGORITHM)); + this.signature = pvutils.getParametersValue(parameters, SIGNATURE, Signature.defaultValues(SIGNATURE)); + if (CERTS in parameters) { + this.certs = pvutils.getParametersValue(parameters, CERTS, Signature.defaultValues(CERTS)); + } + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof SIGNATURE_ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof SIGNATURE): asn1js.BitString; + public static override defaultValues(memberName: typeof CERTS): Certificate[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case SIGNATURE_ALGORITHM: + return new AlgorithmIdentifier(); + case SIGNATURE: + return new asn1js.BitString(); + case CERTS: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case SIGNATURE_ALGORITHM: + return ((memberValue.algorithmId === EMPTY_STRING) && (("algorithmParams" in memberValue) === false)); + case SIGNATURE: + return (memberValue.isEqual(Signature.defaultValues(memberName))); + case CERTS: + return (memberValue.length === 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * Signature ::= SEQUENCE { + * signatureAlgorithm AlgorithmIdentifier, + * signature BIT STRING, + * certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL } + *``` + */ + public static override schema(parameters: SignatureSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + AlgorithmIdentifier.schema(names.signatureAlgorithm || {}), + new asn1js.BitString({ name: (names.signature || EMPTY_STRING) }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + new asn1js.Sequence({ + value: [new asn1js.Repeated({ + name: (names.certs || EMPTY_STRING), + // TODO Double check + // value: Certificate.schema(names.certs || {}) + value: Certificate.schema({}) + })] + }) + ] + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, [ + SIGNATURE_ALGORITHM, + SIGNATURE, + CERTS + ]); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + Signature.schema({ + names: { + signatureAlgorithm: { + names: { + blockName: SIGNATURE_ALGORITHM + } + }, + signature: SIGNATURE, + certs: CERTS + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.signatureAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.signatureAlgorithm }); + this.signature = asn1.result.signature; + if (CERTS in asn1.result) + this.certs = Array.from(asn1.result.certs, element => new Certificate({ schema: element })); + } + + public toSchema(): asn1js.Sequence { + //#region Create array of output sequence + const outputArray = []; + + outputArray.push(this.signatureAlgorithm.toSchema()); + outputArray.push(this.signature); + + if (this.certs) { + outputArray.push(new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + new asn1js.Sequence({ + value: Array.from(this.certs, o => o.toSchema()) + }) + ] + })); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): SignatureJson { + const res: SignatureJson = { + signatureAlgorithm: this.signatureAlgorithm.toJSON(), + signature: this.signature.toJSON(), + }; + + if (this.certs) { + res.certs = Array.from(this.certs, o => o.toJSON()); + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/SignedAndUnsignedAttributes.ts b/third_party/js/PKI.js/src/SignedAndUnsignedAttributes.ts new file mode 100644 index 0000000000..de87e034a1 --- /dev/null +++ b/third_party/js/PKI.js/src/SignedAndUnsignedAttributes.ts @@ -0,0 +1,194 @@ +import * as asn1js from "asn1js"; +import * as pvtsutils from "pvtsutils"; +import * as pvutils from "pvutils"; +import { Attribute, AttributeJson } from "./Attribute"; +import { EMPTY_BUFFER, EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const TYPE = "type"; +const ATTRIBUTES = "attributes"; +const ENCODED_VALUE = "encodedValue"; +const CLEAR_PROPS = [ + ATTRIBUTES +]; + +export interface ISignedAndUnsignedAttributes { + type: number; + attributes: Attribute[]; + /** + * Need to have it in order to successfully process with signature verification + */ + encodedValue: ArrayBuffer; +} + +export interface SignedAndUnsignedAttributesJson { + type: number; + attributes: AttributeJson[]; +} + +export type SignedAndUnsignedAttributesParameters = PkiObjectParameters & Partial<ISignedAndUnsignedAttributes>; + +export type SignedAndUnsignedAttributesSchema = Schema.SchemaParameters<{ + tagNumber?: number; + attributes?: string; +}>; + +/** + * Represents the SignedAndUnsignedAttributes structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + */ +export class SignedAndUnsignedAttributes extends PkiObject implements ISignedAndUnsignedAttributes { + + public static override CLASS_NAME = "SignedAndUnsignedAttributes"; + + public type!: number; + public attributes!: Attribute[]; + public encodedValue!: ArrayBuffer; + + /** + * Initializes a new instance of the {@link SignedAndUnsignedAttributes} class + * @param parameters Initialization parameters + */ + constructor(parameters: SignedAndUnsignedAttributesParameters = {}) { + super(); + + this.type = pvutils.getParametersValue(parameters, TYPE, SignedAndUnsignedAttributes.defaultValues(TYPE)); + this.attributes = pvutils.getParametersValue(parameters, ATTRIBUTES, SignedAndUnsignedAttributes.defaultValues(ATTRIBUTES)); + this.encodedValue = pvutils.getParametersValue(parameters, ENCODED_VALUE, SignedAndUnsignedAttributes.defaultValues(ENCODED_VALUE)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof TYPE): number; + public static override defaultValues(memberName: typeof ATTRIBUTES): Attribute[]; + public static override defaultValues(memberName: typeof ENCODED_VALUE): ArrayBuffer; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case TYPE: + return (-1); + case ATTRIBUTES: + return []; + case ENCODED_VALUE: + return EMPTY_BUFFER; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case TYPE: + return (memberValue === SignedAndUnsignedAttributes.defaultValues(TYPE)); + case ATTRIBUTES: + return (memberValue.length === 0); + case ENCODED_VALUE: + return (memberValue.byteLength === 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * SignedAttributes ::= SET SIZE (1..MAX) OF Attribute + * + * UnsignedAttributes ::= SET SIZE (1..MAX) OF Attribute + *``` + */ + public static override schema(parameters: SignedAndUnsignedAttributesSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Constructed({ + name: (names.blockName || EMPTY_STRING), + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: names.tagNumber || 0 // "SignedAttributes" = 0, "UnsignedAttributes" = 1 + }, + value: [ + new asn1js.Repeated({ + name: (names.attributes || EMPTY_STRING), + value: Attribute.schema() + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + SignedAndUnsignedAttributes.schema({ + names: { + tagNumber: this.type, + attributes: ATTRIBUTES + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.type = asn1.result.idBlock.tagNumber; + this.encodedValue = pvtsutils.BufferSourceConverter.toArrayBuffer(asn1.result.valueBeforeDecodeView); + + //#region Change type from "[0]" to "SET" accordingly to standard + const encodedView = new Uint8Array(this.encodedValue); + encodedView[0] = 0x31; + //#endregion + + if ((ATTRIBUTES in asn1.result) === false) { + if (this.type === 0) + throw new Error("Wrong structure of SignedUnsignedAttributes"); + else + return; // Not so important in case of "UnsignedAttributes" + } + + this.attributes = Array.from(asn1.result.attributes, element => new Attribute({ schema: element })); + //#endregion + } + + public toSchema(): asn1js.Sequence { + if (SignedAndUnsignedAttributes.compareWithDefault(TYPE, this.type) || SignedAndUnsignedAttributes.compareWithDefault(ATTRIBUTES, this.attributes)) + throw new Error("Incorrectly initialized \"SignedAndUnsignedAttributes\" class"); + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: this.type // "SignedAttributes" = 0, "UnsignedAttributes" = 1 + }, + value: Array.from(this.attributes, o => o.toSchema()) + })); + //#endregion + } + + public toJSON(): SignedAndUnsignedAttributesJson { + if (SignedAndUnsignedAttributes.compareWithDefault(TYPE, this.type) || SignedAndUnsignedAttributes.compareWithDefault(ATTRIBUTES, this.attributes)) + throw new Error("Incorrectly initialized \"SignedAndUnsignedAttributes\" class"); + + return { + type: this.type, + attributes: Array.from(this.attributes, o => o.toJSON()) + }; + } + +} diff --git a/third_party/js/PKI.js/src/SignedCertificateTimestamp.ts b/third_party/js/PKI.js/src/SignedCertificateTimestamp.ts new file mode 100644 index 0000000000..fa9e01b5d1 --- /dev/null +++ b/third_party/js/PKI.js/src/SignedCertificateTimestamp.ts @@ -0,0 +1,480 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import * as bs from "bytestreamjs"; +import * as common from "./common"; +import { PublicKeyInfo } from "./PublicKeyInfo"; +import * as Schema from "./Schema"; +import { AlgorithmIdentifier } from "./AlgorithmIdentifier"; +import { Certificate } from "./Certificate"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { EMPTY_BUFFER, EMPTY_STRING } from "./constants"; +import { SignedCertificateTimestampList } from "./SignedCertificateTimestampList"; +import { id_SignedCertificateTimestampList } from "./ObjectIdentifiers"; + +const VERSION = "version"; +const LOG_ID = "logID"; +const EXTENSIONS = "extensions"; +const TIMESTAMP = "timestamp"; +const HASH_ALGORITHM = "hashAlgorithm"; +const SIGNATURE_ALGORITHM = "signatureAlgorithm"; +const SIGNATURE = "signature"; + +const NONE = "none"; +const MD5 = "md5"; +const SHA1 = "sha1"; +const SHA224 = "sha224"; +const SHA256 = "sha256"; +const SHA384 = "sha384"; +const SHA512 = "sha512"; +const ANONYMOUS = "anonymous"; +const RSA = "rsa"; +const DSA = "dsa"; +const ECDSA = "ecdsa"; + +export interface ISignedCertificateTimestamp { + version: number; + logID: ArrayBuffer; + timestamp: Date; + extensions: ArrayBuffer; + hashAlgorithm: string; + signatureAlgorithm: string; + signature: Schema.SchemaType; +} + +export interface SignedCertificateTimestampJson { + version: number; + logID: string; + timestamp: Date; + extensions: string; + hashAlgorithm: string; + signatureAlgorithm: string; + signature: Schema.SchemaType; +} + +export type SignedCertificateTimestampParameters = PkiObjectParameters & Partial<ISignedCertificateTimestamp> & { stream?: bs.SeqStream; }; + +export interface Log { + /** + * Identifier of the CT Log encoded in BASE-64 format + */ + log_id: string; + /** + * Public key of the CT Log encoded in BASE-64 format + */ + key: string; +} + +export class SignedCertificateTimestamp extends PkiObject implements ISignedCertificateTimestamp { + + public static override CLASS_NAME = "SignedCertificateTimestamp"; + + public version!: number; + public logID!: ArrayBuffer; + public timestamp!: Date; + public extensions!: ArrayBuffer; + public hashAlgorithm!: string; + public signatureAlgorithm!: string; + public signature: asn1js.BaseBlock; + + /** + * Initializes a new instance of the {@link SignedCertificateTimestamp} class + * @param parameters Initialization parameters + */ + constructor(parameters: SignedCertificateTimestampParameters = {}) { + super(); + + this.version = pvutils.getParametersValue(parameters, VERSION, SignedCertificateTimestamp.defaultValues(VERSION)); + this.logID = pvutils.getParametersValue(parameters, LOG_ID, SignedCertificateTimestamp.defaultValues(LOG_ID)); + this.timestamp = pvutils.getParametersValue(parameters, TIMESTAMP, SignedCertificateTimestamp.defaultValues(TIMESTAMP)); + this.extensions = pvutils.getParametersValue(parameters, EXTENSIONS, SignedCertificateTimestamp.defaultValues(EXTENSIONS)); + this.hashAlgorithm = pvutils.getParametersValue(parameters, HASH_ALGORITHM, SignedCertificateTimestamp.defaultValues(HASH_ALGORITHM)); + this.signatureAlgorithm = pvutils.getParametersValue(parameters, SIGNATURE_ALGORITHM, SignedCertificateTimestamp.defaultValues(SIGNATURE_ALGORITHM)); + this.signature = pvutils.getParametersValue(parameters, SIGNATURE, SignedCertificateTimestamp.defaultValues(SIGNATURE)); + + if ("stream" in parameters && parameters.stream) { + this.fromStream(parameters.stream); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof VERSION): number; + public static override defaultValues(memberName: typeof LOG_ID): ArrayBuffer; + public static override defaultValues(memberName: typeof EXTENSIONS): ArrayBuffer; + public static override defaultValues(memberName: typeof TIMESTAMP): Date; + public static override defaultValues(memberName: typeof HASH_ALGORITHM): string; + public static override defaultValues(memberName: typeof SIGNATURE_ALGORITHM): string; + public static override defaultValues(memberName: typeof SIGNATURE): Schema.SchemaType; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case VERSION: + return 0; + case LOG_ID: + case EXTENSIONS: + return EMPTY_BUFFER; + case TIMESTAMP: + return new Date(0); + case HASH_ALGORITHM: + case SIGNATURE_ALGORITHM: + return EMPTY_STRING; + case SIGNATURE: + return new asn1js.Any(); + default: + return super.defaultValues(memberName); + } + } + + public fromSchema(schema: Schema.SchemaType): void { + if ((schema instanceof asn1js.RawData) === false) + throw new Error("Object's schema was not verified against input data for SignedCertificateTimestamp"); + + const seqStream = new bs.SeqStream({ + stream: new bs.ByteStream({ + buffer: schema.data + }) + }); + + this.fromStream(seqStream); + } + + /** + * Converts SeqStream data into current class + * @param stream + */ + public fromStream(stream: bs.SeqStream): void { + const blockLength = stream.getUint16(); + + this.version = (stream.getBlock(1))[0]; + + if (this.version === 0) { + this.logID = (new Uint8Array(stream.getBlock(32))).buffer.slice(0); + this.timestamp = new Date(pvutils.utilFromBase(new Uint8Array(stream.getBlock(8)), 8)); + + //#region Extensions + const extensionsLength = stream.getUint16(); + this.extensions = (new Uint8Array(stream.getBlock(extensionsLength))).buffer.slice(0); + //#endregion + + //#region Hash algorithm + switch ((stream.getBlock(1))[0]) { + case 0: + this.hashAlgorithm = NONE; + break; + case 1: + this.hashAlgorithm = MD5; + break; + case 2: + this.hashAlgorithm = SHA1; + break; + case 3: + this.hashAlgorithm = SHA224; + break; + case 4: + this.hashAlgorithm = SHA256; + break; + case 5: + this.hashAlgorithm = SHA384; + break; + case 6: + this.hashAlgorithm = SHA512; + break; + default: + throw new Error("Object's stream was not correct for SignedCertificateTimestamp"); + } + //#endregion + + //#region Signature algorithm + switch ((stream.getBlock(1))[0]) { + case 0: + this.signatureAlgorithm = ANONYMOUS; + break; + case 1: + this.signatureAlgorithm = RSA; + break; + case 2: + this.signatureAlgorithm = DSA; + break; + case 3: + this.signatureAlgorithm = ECDSA; + break; + default: + throw new Error("Object's stream was not correct for SignedCertificateTimestamp"); + } + //#endregion + + //#region Signature + const signatureLength = stream.getUint16(); + const signatureData = new Uint8Array(stream.getBlock(signatureLength)).buffer.slice(0); + + const asn1 = asn1js.fromBER(signatureData); + AsnError.assert(asn1, "SignedCertificateTimestamp"); + this.signature = asn1.result; + //#endregion + + if (blockLength !== (47 + extensionsLength + signatureLength)) { + throw new Error("Object's stream was not correct for SignedCertificateTimestamp"); + } + } + } + + public toSchema(): asn1js.RawData { + const stream = this.toStream(); + + return new asn1js.RawData({ data: stream.stream.buffer }); + } + + /** + * Converts current object to SeqStream data + * @returns SeqStream object + */ + public toStream(): bs.SeqStream { + const stream = new bs.SeqStream(); + + stream.appendUint16(47 + this.extensions.byteLength + this.signature.valueBeforeDecodeView.byteLength); + stream.appendChar(this.version); + stream.appendView(new Uint8Array(this.logID)); + + const timeBuffer = new ArrayBuffer(8); + const timeView = new Uint8Array(timeBuffer); + + const baseArray = pvutils.utilToBase(this.timestamp.valueOf(), 8); + timeView.set(new Uint8Array(baseArray), 8 - baseArray.byteLength); + + stream.appendView(timeView); + stream.appendUint16(this.extensions.byteLength); + + if (this.extensions.byteLength) + stream.appendView(new Uint8Array(this.extensions)); + + let _hashAlgorithm; + + switch (this.hashAlgorithm.toLowerCase()) { + case NONE: + _hashAlgorithm = 0; + break; + case MD5: + _hashAlgorithm = 1; + break; + case SHA1: + _hashAlgorithm = 2; + break; + case SHA224: + _hashAlgorithm = 3; + break; + case SHA256: + _hashAlgorithm = 4; + break; + case SHA384: + _hashAlgorithm = 5; + break; + case SHA512: + _hashAlgorithm = 6; + break; + default: + throw new Error(`Incorrect data for hashAlgorithm: ${this.hashAlgorithm}`); + } + + stream.appendChar(_hashAlgorithm); + + let _signatureAlgorithm; + + switch (this.signatureAlgorithm.toLowerCase()) { + case ANONYMOUS: + _signatureAlgorithm = 0; + break; + case RSA: + _signatureAlgorithm = 1; + break; + case DSA: + _signatureAlgorithm = 2; + break; + case ECDSA: + _signatureAlgorithm = 3; + break; + default: + throw new Error(`Incorrect data for signatureAlgorithm: ${this.signatureAlgorithm}`); + } + + stream.appendChar(_signatureAlgorithm); + + const _signature = this.signature.toBER(false); + + stream.appendUint16(_signature.byteLength); + stream.appendView(new Uint8Array(_signature)); + + return stream; + } + + public toJSON(): SignedCertificateTimestampJson { + return { + version: this.version, + logID: pvutils.bufferToHexCodes(this.logID), + timestamp: this.timestamp, + extensions: pvutils.bufferToHexCodes(this.extensions), + hashAlgorithm: this.hashAlgorithm, + signatureAlgorithm: this.signatureAlgorithm, + signature: this.signature.toJSON() + }; + } + + /** + * Verify SignedCertificateTimestamp for specific input data + * @param logs Array of objects with information about each CT Log (like here: https://ct.grahamedgecombe.com/logs.json) + * @param data Data to verify signature against. Could be encoded Certificate or encoded PreCert + * @param dataType Type = 0 (data is encoded Certificate), type = 1 (data is encoded PreCert) + * @param crypto Crypto engine + */ + async verify(logs: Log[], data: ArrayBuffer, dataType = 0, crypto = common.getCrypto(true)): Promise<boolean> { + //#region Initial variables + const logId = pvutils.toBase64(pvutils.arrayBufferToString(this.logID)); + + let publicKeyBase64 = null; + + const stream = new bs.SeqStream(); + //#endregion + + //#region Found and init public key + for (const log of logs) { + if (log.log_id === logId) { + publicKeyBase64 = log.key; + break; + } + } + + if (!publicKeyBase64) { + throw new Error(`Public key not found for CT with logId: ${logId}`); + } + + const pki = pvutils.stringToArrayBuffer(pvutils.fromBase64(publicKeyBase64)); + const publicKeyInfo = PublicKeyInfo.fromBER(pki); + //#endregion + + //#region Initialize signed data block + stream.appendChar(0x00); // sct_version + stream.appendChar(0x00); // signature_type = certificate_timestamp + + const timeBuffer = new ArrayBuffer(8); + const timeView = new Uint8Array(timeBuffer); + + const baseArray = pvutils.utilToBase(this.timestamp.valueOf(), 8); + timeView.set(new Uint8Array(baseArray), 8 - baseArray.byteLength); + + stream.appendView(timeView); + + stream.appendUint16(dataType); + + if (dataType === 0) + stream.appendUint24(data.byteLength); + + stream.appendView(new Uint8Array(data)); + + stream.appendUint16(this.extensions.byteLength); + + if (this.extensions.byteLength !== 0) + stream.appendView(new Uint8Array(this.extensions)); + //#endregion + + //#region Perform verification + return crypto.verifyWithPublicKey( + stream.buffer.slice(0, stream.length), + new asn1js.OctetString({ valueHex: this.signature.toBER(false) }), + publicKeyInfo, + { algorithmId: EMPTY_STRING } as AlgorithmIdentifier, + "SHA-256" + ); + //#endregion + } + +} + +export interface Log { + /** + * Identifier of the CT Log encoded in BASE-64 format + */ + log_id: string; + /** + * Public key of the CT Log encoded in BASE-64 format + */ + key: string; +} + +/** + * Verify SignedCertificateTimestamp for specific certificate content + * @param certificate Certificate for which verification would be performed + * @param issuerCertificate Certificate of the issuer of target certificate + * @param logs Array of objects with information about each CT Log (like here: https://ct.grahamedgecombe.com/logs.json) + * @param index Index of SignedCertificateTimestamp inside SignedCertificateTimestampList (for -1 would verify all) + * @param crypto Crypto engine + * @return Array of verification results + */ +export async function verifySCTsForCertificate(certificate: Certificate, issuerCertificate: Certificate, logs: Log[], index = (-1), crypto = common.getCrypto(true)) { + let parsedValue: SignedCertificateTimestampList | null = null; + + const stream = new bs.SeqStream(); + + //#region Remove certificate extension + for (let i = 0; certificate.extensions && i < certificate.extensions.length; i++) { + switch (certificate.extensions[i].extnID) { + case id_SignedCertificateTimestampList: + { + parsedValue = certificate.extensions[i].parsedValue; + + if (!parsedValue || parsedValue.timestamps.length === 0) + throw new Error("Nothing to verify in the certificate"); + + certificate.extensions.splice(i, 1); + } + break; + default: + } + } + //#endregion + + //#region Check we do have what to verify + if (parsedValue === null) + throw new Error("No SignedCertificateTimestampList extension in the specified certificate"); + //#endregion + + //#region Prepare modifier TBS value + const tbs = certificate.encodeTBS().toBER(); + //#endregion + + //#region Initialize "issuer_key_hash" value + const issuerId = await crypto.digest({ name: "SHA-256" }, new Uint8Array(issuerCertificate.subjectPublicKeyInfo.toSchema().toBER(false))); + //#endregion + + //#region Make final "PreCert" value + stream.appendView(new Uint8Array(issuerId)); + stream.appendUint24(tbs.byteLength); + stream.appendView(new Uint8Array(tbs)); + + const preCert = stream.stream.slice(0, stream.length); + //#endregion + + //#region Call verification function for specified index + if (index === (-1)) { + const verifyArray = []; + + for (const timestamp of parsedValue.timestamps) { + const verifyResult = await timestamp.verify(logs, preCert.buffer, 1, crypto); + verifyArray.push(verifyResult); + } + + return verifyArray; + } + + if (index >= parsedValue.timestamps.length) + index = (parsedValue.timestamps.length - 1); + + return [await parsedValue.timestamps[index].verify(logs, preCert.buffer, 1, crypto)]; + //#endregion +} + diff --git a/third_party/js/PKI.js/src/SignedCertificateTimestampList.ts b/third_party/js/PKI.js/src/SignedCertificateTimestampList.ts new file mode 100644 index 0000000000..986b41b9eb --- /dev/null +++ b/third_party/js/PKI.js/src/SignedCertificateTimestampList.ts @@ -0,0 +1,145 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import * as bs from "bytestreamjs"; +import { SignedCertificateTimestamp, SignedCertificateTimestampJson } from "./SignedCertificateTimestamp"; +import * as Schema from "./Schema"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; + +const TIMESTAMPS = "timestamps"; + +export interface ISignedCertificateTimestampList { + timestamps: SignedCertificateTimestamp[]; +} + +export interface SignedCertificateTimestampListJson { + timestamps: SignedCertificateTimestampJson[]; +} + +export type SignedCertificateTimestampListParameters = PkiObjectParameters & Partial<ISignedCertificateTimestampList>; + +/** + * Represents the SignedCertificateTimestampList structure described in [RFC6962](https://datatracker.ietf.org/doc/html/rfc6962) + */ +export class SignedCertificateTimestampList extends PkiObject implements ISignedCertificateTimestampList { + + public static override CLASS_NAME = "SignedCertificateTimestampList"; + + public timestamps!: SignedCertificateTimestamp[]; + + /** + * Initializes a new instance of the {@link SignedCertificateTimestampList} class + * @param parameters Initialization parameters + */ + constructor(parameters: SignedCertificateTimestampListParameters = {}) { + super(); + + this.timestamps = pvutils.getParametersValue(parameters, TIMESTAMPS, SignedCertificateTimestampList.defaultValues(TIMESTAMPS)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof TIMESTAMPS): SignedCertificateTimestamp[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case TIMESTAMPS: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case TIMESTAMPS: + return (memberValue.length === 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * SignedCertificateTimestampList ::= OCTET STRING + *``` + */ + public static override schema(parameters: Schema.SchemaParameters = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + names.optional ??= false; + + return (new asn1js.OctetString({ + name: (names.blockName || "SignedCertificateTimestampList"), + optional: names.optional + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + //#region Check the schema is valid + if ((schema instanceof asn1js.OctetString) === false) { + throw new Error("Object's schema was not verified against input data for SignedCertificateTimestampList"); + } + //#endregion + //#region Get internal properties from parsed schema + const seqStream = new bs.SeqStream({ + stream: new bs.ByteStream({ + buffer: schema.valueBlock.valueHex + }) + }); + + const dataLength = seqStream.getUint16(); + if (dataLength !== seqStream.length) { + throw new Error("Object's schema was not verified against input data for SignedCertificateTimestampList"); + } + + while (seqStream.length) { + this.timestamps.push(new SignedCertificateTimestamp({ stream: seqStream })); + } + //#endregion + } + + public toSchema(): asn1js.OctetString { + //#region Initial variables + const stream = new bs.SeqStream(); + + let overallLength = 0; + + const timestampsData = []; + //#endregion + //#region Get overall length + for (const timestamp of this.timestamps) { + const timestampStream = timestamp.toStream(); + timestampsData.push(timestampStream); + overallLength += timestampStream.stream.buffer.byteLength; + } + //#endregion + stream.appendUint16(overallLength); + + //#region Set data from all timestamps + for (const timestamp of timestampsData) { + stream.appendView(timestamp.stream.view); + } + //#endregion + return new asn1js.OctetString({ valueHex: stream.stream.buffer.slice(0) }); + } + + public toJSON(): SignedCertificateTimestampListJson { + return { + timestamps: Array.from(this.timestamps, o => o.toJSON()) + }; + } + +} diff --git a/third_party/js/PKI.js/src/SignedData.ts b/third_party/js/PKI.js/src/SignedData.ts new file mode 100644 index 0000000000..67731183de --- /dev/null +++ b/third_party/js/PKI.js/src/SignedData.ts @@ -0,0 +1,966 @@ +import * as asn1js from "asn1js"; +import * as pvtsutils from "pvtsutils"; +import * as pvutils from "pvutils"; +import * as common from "./common"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson } from "./AlgorithmIdentifier"; +import { EncapsulatedContentInfo, EncapsulatedContentInfoJson, EncapsulatedContentInfoSchema } from "./EncapsulatedContentInfo"; +import { Certificate, checkCA } from "./Certificate"; +import { CertificateRevocationList, CertificateRevocationListJson } from "./CertificateRevocationList"; +import { OtherRevocationInfoFormat, OtherRevocationInfoFormatJson } from "./OtherRevocationInfoFormat"; +import { SignerInfo, SignerInfoJson } from "./SignerInfo"; +import { CertificateSet, CertificateSetItem, CertificateSetItemJson } from "./CertificateSet"; +import { RevocationInfoChoices, RevocationInfoChoicesSchema } from "./RevocationInfoChoices"; +import { IssuerAndSerialNumber } from "./IssuerAndSerialNumber"; +import { TSTInfo } from "./TSTInfo"; +import { CertificateChainValidationEngine, CertificateChainValidationEngineParameters, FindIssuerCallback, FindOriginCallback } from "./CertificateChainValidationEngine"; +import { BasicOCSPResponse, BasicOCSPResponseJson } from "./BasicOCSPResponse"; +import { OtherCertificateFormat } from "./OtherCertificateFormat"; +import { AttributeCertificateV1 } from "./AttributeCertificateV1"; +import { AttributeCertificateV2 } from "./AttributeCertificateV2"; +import * as Schema from "./Schema"; +import { id_ContentType_Data, id_eContentType_TSTInfo, id_PKIX_OCSP_Basic } from "./ObjectIdentifiers"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { EMPTY_BUFFER, EMPTY_STRING } from "./constants"; +import { ICryptoEngine } from "./CryptoEngine/CryptoEngineInterface"; + +export type SignedDataCRL = CertificateRevocationList | OtherRevocationInfoFormat; +export type SignedDataCRLJson = CertificateRevocationListJson | OtherRevocationInfoFormatJson; + +const VERSION = "version"; +const DIGEST_ALGORITHMS = "digestAlgorithms"; +const ENCAP_CONTENT_INFO = "encapContentInfo"; +const CERTIFICATES = "certificates"; +const CRLS = "crls"; +const SIGNER_INFOS = "signerInfos"; +const OCSPS = "ocsps"; +const SIGNED_DATA = "SignedData"; +const SIGNED_DATA_VERSION = `${SIGNED_DATA}.${VERSION}`; +const SIGNED_DATA_DIGEST_ALGORITHMS = `${SIGNED_DATA}.${DIGEST_ALGORITHMS}`; +const SIGNED_DATA_ENCAP_CONTENT_INFO = `${SIGNED_DATA}.${ENCAP_CONTENT_INFO}`; +const SIGNED_DATA_CERTIFICATES = `${SIGNED_DATA}.${CERTIFICATES}`; +const SIGNED_DATA_CRLS = `${SIGNED_DATA}.${CRLS}`; +const SIGNED_DATA_SIGNER_INFOS = `${SIGNED_DATA}.${SIGNER_INFOS}`; +const CLEAR_PROPS = [ + SIGNED_DATA_VERSION, + SIGNED_DATA_DIGEST_ALGORITHMS, + SIGNED_DATA_ENCAP_CONTENT_INFO, + SIGNED_DATA_CERTIFICATES, + SIGNED_DATA_CRLS, + SIGNED_DATA_SIGNER_INFOS +]; + +export interface ISignedData { + version: number; + digestAlgorithms: AlgorithmIdentifier[]; + encapContentInfo: EncapsulatedContentInfo; + certificates?: CertificateSetItem[]; + crls?: SignedDataCRL[]; + ocsps?: BasicOCSPResponse[]; + signerInfos: SignerInfo[]; +} + +export interface SignedDataJson { + version: number; + digestAlgorithms: AlgorithmIdentifierJson[]; + encapContentInfo: EncapsulatedContentInfoJson; + certificates?: CertificateSetItemJson[]; + crls?: SignedDataCRLJson[]; + ocsps?: BasicOCSPResponseJson[]; + signerInfos: SignerInfoJson[]; +} + +export type SignedDataParameters = PkiObjectParameters & Partial<ISignedData>; + +export interface SignedDataVerifyParams { + signer?: number; + data?: ArrayBuffer; + trustedCerts?: Certificate[]; + checkDate?: Date; + checkChain?: boolean; + passedWhenNotRevValues?: boolean; + extendedMode?: boolean; + findOrigin?: FindOriginCallback | null; + findIssuer?: FindIssuerCallback | null; +} + +export interface SignedDataVerifyErrorParams { + message: string; + date?: Date; + code?: number; + timestampSerial?: ArrayBuffer | null; + signatureVerified?: boolean | null; + signerCertificate?: Certificate | null; + signerCertificateVerified?: boolean | null; + certificatePath?: Certificate[]; +} + +export interface SignedDataVerifyResult { + message: string; + date?: Date; + code?: number; + timestampSerial?: ArrayBuffer | null; + signatureVerified?: boolean | null; + signerCertificate?: Certificate | null; + signerCertificateVerified?: boolean | null; + certificatePath: Certificate[]; +} + +export class SignedDataVerifyError extends Error implements SignedDataVerifyResult { + + public date: Date; + public code: number; + public signatureVerified: boolean | null; + public signerCertificate: Certificate | null; + public signerCertificateVerified: boolean | null; + public timestampSerial: ArrayBuffer | null; + public certificatePath: Certificate[]; + + constructor({ + message, + code = 0, + date = new Date(), + signatureVerified = null, + signerCertificate = null, + signerCertificateVerified = null, + timestampSerial = null, + certificatePath = [], + }: SignedDataVerifyErrorParams) { + super(message); + this.name = "SignedDataVerifyError"; + + this.date = date; + this.code = code; + this.timestampSerial = timestampSerial; + this.signatureVerified = signatureVerified; + this.signerCertificate = signerCertificate; + this.signerCertificateVerified = signerCertificateVerified; + this.certificatePath = certificatePath; + + } +} + +/** + * Represents the SignedData structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + * + * @example The following example demonstrates how to create and sign CMS Signed Data + * ```js + * // Create a new CMS Signed Data + * const cmsSigned = new pkijs.SignedData({ + * encapContentInfo: new pkijs.EncapsulatedContentInfo({ + * eContentType: pkijs.ContentInfo.DATA,, // "data" content type + * eContent: new asn1js.OctetString({ valueHex: buffer }) + * }), + * signerInfos: [ + * new pkijs.SignerInfo({ + * sid: new pkijs.IssuerAndSerialNumber({ + * issuer: cert.issuer, + * serialNumber: cert.serialNumber + * }) + * }) + * ], + * // Signer certificate for chain validation + * certificates: [cert] + * }); + * + * await cmsSigned.sign(keys.privateKey, 0, "SHA-256"); + * + * // Add Signed Data to Content Info + * const cms = new pkijs.ContentInfo({ + * contentType: pkijs.ContentInfo.SIGNED_DATA,, + * content: cmsSigned.toSchema(true), + * }); + * + * // Encode CMS to ASN.1 + * const cmsRaw = cms.toSchema().toBER(); + * ``` + * + * @example The following example demonstrates how to verify CMS Signed Data + * ```js + * // Parse CMS and detect it's Signed Data + * const cms = pkijs.ContentInfo.fromBER(cmsRaw); + * if (cms.contentType !== pkijs.ContentInfo.SIGNED_DATA) { + * throw new Error("CMS is not Signed Data"); + * } + * + * // Read Signed Data + * const signedData = new pkijs.SignedData({ schema: cms.content }); + * + * // Verify Signed Data signature + * const ok = await signedData.verify({ + * signer: 0, + * checkChain: true, + * trustedCerts: [trustedCert], + * }); + * + * if (!ok) { + * throw new Error("CMS signature is invalid") + * } + * ``` + */ +export class SignedData extends PkiObject implements ISignedData { + + public static override CLASS_NAME = "SignedData"; + + public static ID_DATA: typeof id_ContentType_Data = id_ContentType_Data; + + public version!: number; + public digestAlgorithms!: AlgorithmIdentifier[]; + public encapContentInfo!: EncapsulatedContentInfo; + public certificates?: CertificateSetItem[]; + public crls?: SignedDataCRL[]; + public ocsps?: BasicOCSPResponse[]; + public signerInfos!: SignerInfo[]; + + /** + * Initializes a new instance of the {@link SignedData} class + * @param parameters Initialization parameters + */ + constructor(parameters: SignedDataParameters = {}) { + super(); + + this.version = pvutils.getParametersValue(parameters, VERSION, SignedData.defaultValues(VERSION)); + this.digestAlgorithms = pvutils.getParametersValue(parameters, DIGEST_ALGORITHMS, SignedData.defaultValues(DIGEST_ALGORITHMS)); + this.encapContentInfo = pvutils.getParametersValue(parameters, ENCAP_CONTENT_INFO, SignedData.defaultValues(ENCAP_CONTENT_INFO)); + if (CERTIFICATES in parameters) { + this.certificates = pvutils.getParametersValue(parameters, CERTIFICATES, SignedData.defaultValues(CERTIFICATES)); + } + if (CRLS in parameters) { + this.crls = pvutils.getParametersValue(parameters, CRLS, SignedData.defaultValues(CRLS)); + } + if (OCSPS in parameters) { + this.ocsps = pvutils.getParametersValue(parameters, OCSPS, SignedData.defaultValues(OCSPS)); + } + this.signerInfos = pvutils.getParametersValue(parameters, SIGNER_INFOS, SignedData.defaultValues(SIGNER_INFOS)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof VERSION): number; + public static override defaultValues(memberName: typeof DIGEST_ALGORITHMS): AlgorithmIdentifier[]; + public static override defaultValues(memberName: typeof ENCAP_CONTENT_INFO): EncapsulatedContentInfo; + public static override defaultValues(memberName: typeof CERTIFICATES): CertificateSetItem[]; + public static override defaultValues(memberName: typeof CRLS): SignedDataCRL[]; + public static override defaultValues(memberName: typeof OCSPS): BasicOCSPResponse[]; + public static override defaultValues(memberName: typeof SIGNER_INFOS): SignerInfo[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case VERSION: + return 0; + case DIGEST_ALGORITHMS: + return []; + case ENCAP_CONTENT_INFO: + return new EncapsulatedContentInfo(); + case CERTIFICATES: + return []; + case CRLS: + return []; + case OCSPS: + return []; + case SIGNER_INFOS: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case VERSION: + return (memberValue === SignedData.defaultValues(VERSION)); + case ENCAP_CONTENT_INFO: + return EncapsulatedContentInfo.compareWithDefault("eContentType", memberValue.eContentType) && + EncapsulatedContentInfo.compareWithDefault("eContent", memberValue.eContent); + case DIGEST_ALGORITHMS: + case CERTIFICATES: + case CRLS: + case OCSPS: + case SIGNER_INFOS: + return (memberValue.length === 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * SignedData ::= SEQUENCE { + * version CMSVersion, + * digestAlgorithms DigestAlgorithmIdentifiers, + * encapContentInfo EncapsulatedContentInfo, + * certificates [0] IMPLICIT CertificateSet OPTIONAL, + * crls [1] IMPLICIT RevocationInfoChoices OPTIONAL, + * signerInfos SignerInfos } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + version?: string; + digestAlgorithms?: string; + encapContentInfo?: EncapsulatedContentInfoSchema; + certificates?: string; + crls?: RevocationInfoChoicesSchema; + signerInfos?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + if (names.optional === undefined) { + names.optional = false; + } + + return (new asn1js.Sequence({ + name: (names.blockName || SIGNED_DATA), + optional: names.optional, + value: [ + new asn1js.Integer({ name: (names.version || SIGNED_DATA_VERSION) }), + new asn1js.Set({ + value: [ + new asn1js.Repeated({ + name: (names.digestAlgorithms || SIGNED_DATA_DIGEST_ALGORITHMS), + value: AlgorithmIdentifier.schema() + }) + ] + }), + EncapsulatedContentInfo.schema(names.encapContentInfo || { + names: { + blockName: SIGNED_DATA_ENCAP_CONTENT_INFO + } + }), + new asn1js.Constructed({ + name: (names.certificates || SIGNED_DATA_CERTIFICATES), + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: CertificateSet.schema().valueBlock.value + }), // IMPLICIT CertificateSet + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: RevocationInfoChoices.schema(names.crls || { + names: { + crls: SIGNED_DATA_CRLS + } + }).valueBlock.value + }), // IMPLICIT RevocationInfoChoices + new asn1js.Set({ + value: [ + new asn1js.Repeated({ + name: (names.signerInfos || SIGNED_DATA_SIGNER_INFOS), + value: SignerInfo.schema() + }) + ] + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + //#region Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + SignedData.schema() + ); + AsnError.assertSchema(asn1, this.className); + //#endregion + + //#region Get internal properties from parsed schema + this.version = asn1.result[SIGNED_DATA_VERSION].valueBlock.valueDec; + + if (SIGNED_DATA_DIGEST_ALGORITHMS in asn1.result) // Could be empty SET of digest algorithms + this.digestAlgorithms = Array.from(asn1.result[SIGNED_DATA_DIGEST_ALGORITHMS], algorithm => new AlgorithmIdentifier({ schema: algorithm })); + + this.encapContentInfo = new EncapsulatedContentInfo({ schema: asn1.result[SIGNED_DATA_ENCAP_CONTENT_INFO] }); + + if (SIGNED_DATA_CERTIFICATES in asn1.result) { + const certificateSet = new CertificateSet({ + schema: new asn1js.Set({ + value: asn1.result[SIGNED_DATA_CERTIFICATES].valueBlock.value + }) + }); + this.certificates = certificateSet.certificates.slice(0); // Copy all just for making comfortable access + } + + if (SIGNED_DATA_CRLS in asn1.result) { + this.crls = Array.from(asn1.result[SIGNED_DATA_CRLS], (crl: Schema.SchemaType) => { + if (crl.idBlock.tagClass === 1) + return new CertificateRevocationList({ schema: crl }); + + //#region Create SEQUENCE from [1] + crl.idBlock.tagClass = 1; // UNIVERSAL + crl.idBlock.tagNumber = 16; // SEQUENCE + //#endregion + + return new OtherRevocationInfoFormat({ schema: crl }); + }); + } + + if (SIGNED_DATA_SIGNER_INFOS in asn1.result) // Could be empty SET SignerInfos + this.signerInfos = Array.from(asn1.result[SIGNED_DATA_SIGNER_INFOS], signerInfoSchema => new SignerInfo({ schema: signerInfoSchema })); + //#endregion + } + + public toSchema(encodeFlag = false): Schema.SchemaType { + //#region Create array for output sequence + const outputArray = []; + + // IF ((certificates is present) AND + // (any certificates with a type of other are present)) OR + // ((crls is present) AND + // (any crls with a type of other are present)) + // THEN version MUST be 5 + // ELSE + // IF (certificates is present) AND + // (any version 2 attribute certificates are present) + // THEN version MUST be 4 + // ELSE + // IF ((certificates is present) AND + // (any version 1 attribute certificates are present)) OR + // (any SignerInfo structures are version 3) OR + // (encapContentInfo eContentType is other than id-data) + // THEN version MUST be 3 + // ELSE version MUST be 1 + if ((this.certificates && this.certificates.length && this.certificates.some(o => o instanceof OtherCertificateFormat)) + || (this.crls && this.crls.length && this.crls.some(o => o instanceof OtherRevocationInfoFormat))) { + this.version = 5; + } else if (this.certificates && this.certificates.length && this.certificates.some(o => o instanceof AttributeCertificateV2)) { + this.version = 4; + } else if ((this.certificates && this.certificates.length && this.certificates.some(o => o instanceof AttributeCertificateV1)) + || this.signerInfos.some(o => o.version === 3) + || this.encapContentInfo.eContentType !== SignedData.ID_DATA) { + this.version = 3; + } else { + this.version = 1; + } + + outputArray.push(new asn1js.Integer({ value: this.version })); + + //#region Create array of digest algorithms + outputArray.push(new asn1js.Set({ + value: Array.from(this.digestAlgorithms, algorithm => algorithm.toSchema()) + })); + //#endregion + + outputArray.push(this.encapContentInfo.toSchema()); + + if (this.certificates) { + const certificateSet = new CertificateSet({ certificates: this.certificates }); + const certificateSetSchema = certificateSet.toSchema(); + + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, + tagNumber: 0 + }, + value: certificateSetSchema.valueBlock.value + })); + } + + if (this.crls) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: Array.from(this.crls, crl => { + if (crl instanceof OtherRevocationInfoFormat) { + const crlSchema = crl.toSchema(); + + crlSchema.idBlock.tagClass = 3; + crlSchema.idBlock.tagNumber = 1; + + return crlSchema; + } + + return crl.toSchema(encodeFlag); + }) + })); + } + + //#region Create array of signer infos + outputArray.push(new asn1js.Set({ + value: Array.from(this.signerInfos, signerInfo => signerInfo.toSchema()) + })); + //#endregion + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): SignedDataJson { + const res: SignedDataJson = { + version: this.version, + digestAlgorithms: Array.from(this.digestAlgorithms, algorithm => algorithm.toJSON()), + encapContentInfo: this.encapContentInfo.toJSON(), + signerInfos: Array.from(this.signerInfos, signerInfo => signerInfo.toJSON()), + }; + + if (this.certificates) { + res.certificates = Array.from(this.certificates, certificate => certificate.toJSON()); + } + + if (this.crls) { + res.crls = Array.from(this.crls, crl => crl.toJSON()); + } + + + return res; + } + + public verify(params?: SignedDataVerifyParams & { extendedMode?: false; }, crypto?: ICryptoEngine): Promise<boolean>; + public verify(params: SignedDataVerifyParams & { extendedMode: true; }, crypto?: ICryptoEngine): Promise<SignedDataVerifyResult>; + public async verify({ + signer = (-1), + data = (EMPTY_BUFFER), + trustedCerts = [], + checkDate = (new Date()), + checkChain = false, + passedWhenNotRevValues = false, + extendedMode = false, + findOrigin = null, + findIssuer = null + }: SignedDataVerifyParams = {}, crypto = common.getCrypto(true)): Promise<boolean | SignedDataVerifyResult> { + let signerCert: Certificate | null = null; + let timestampSerial: ArrayBuffer | null = null; + try { + //#region Global variables + let messageDigestValue = EMPTY_BUFFER; + let shaAlgorithm = EMPTY_STRING; + let certificatePath: Certificate[] = []; + //#endregion + + //#region Get a signer number + const signerInfo = this.signerInfos[signer]; + if (!signerInfo) { + throw new SignedDataVerifyError({ + date: checkDate, + code: 1, + message: "Unable to get signer by supplied index", + }); + } + //#endregion + + //#region Check that certificates field was included in signed data + if (!this.certificates) { + throw new SignedDataVerifyError({ + date: checkDate, + code: 2, + message: "No certificates attached to this signed data", + }); + } + //#endregion + + //#region Find a certificate for specified signer + + if (signerInfo.sid instanceof IssuerAndSerialNumber) { + for (const certificate of this.certificates) { + if (!(certificate instanceof Certificate)) + continue; + + if ((certificate.issuer.isEqual(signerInfo.sid.issuer)) && + (certificate.serialNumber.isEqual(signerInfo.sid.serialNumber))) { + signerCert = certificate; + break; + } + } + } else { // Find by SubjectKeyIdentifier + const sid = signerInfo.sid; + const keyId = sid.idBlock.isConstructed + ? sid.valueBlock.value[0].valueBlock.valueHex // EXPLICIT OCTET STRING + : sid.valueBlock.valueHex; // IMPLICIT OCTET STRING + + for (const certificate of this.certificates) { + if (!(certificate instanceof Certificate)) { + continue; + } + + const digest = await crypto.digest({ name: "sha-1" }, certificate.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHexView); + if (pvutils.isEqualBuffer(digest, keyId)) { + signerCert = certificate; + break; + } + } + } + + if (!signerCert) { + throw new SignedDataVerifyError({ + date: checkDate, + code: 3, + message: "Unable to find signer certificate", + }); + } + //#endregion + + //#region Verify internal digest in case of "tSTInfo" content type + if (this.encapContentInfo.eContentType === id_eContentType_TSTInfo) { + //#region Check "eContent" presence + if (!this.encapContentInfo.eContent) { + throw new SignedDataVerifyError({ + date: checkDate, + code: 15, + message: "Error during verification: TSTInfo eContent is empty", + signatureVerified: null, + signerCertificate: signerCert, + timestampSerial, + signerCertificateVerified: true + }); + } + //#endregion + + //#region Initialize TST_INFO value + let tstInfo: TSTInfo; + + try { + tstInfo = TSTInfo.fromBER(this.encapContentInfo.eContent.valueBlock.valueHexView); + } + catch (ex) { + throw new SignedDataVerifyError({ + date: checkDate, + code: 15, + message: "Error during verification: TSTInfo wrong ASN.1 schema ", + signatureVerified: null, + signerCertificate: signerCert, + timestampSerial, + signerCertificateVerified: true + }); + } + //#endregion + + //#region Change "checkDate" and append "timestampSerial" + checkDate = tstInfo.genTime; + timestampSerial = tstInfo.serialNumber.valueBlock.valueHexView.slice(); + //#endregion + + //#region Check that we do have detached data content + if (data.byteLength === 0) { + throw new SignedDataVerifyError({ + date: checkDate, + code: 4, + message: "Missed detached data input array", + }); + } + //#endregion + + if (!(await tstInfo.verify({ data }, crypto))) { + throw new SignedDataVerifyError({ + date: checkDate, + code: 15, + message: "Error during verification: TSTInfo verification is failed", + signatureVerified: false, + signerCertificate: signerCert, + timestampSerial, + signerCertificateVerified: true + }); + } + } + + //#endregion + + if (checkChain) { + const certs = this.certificates.filter(certificate => (certificate instanceof Certificate && !!checkCA(certificate, signerCert))) as Certificate[]; + const chainParams: CertificateChainValidationEngineParameters = { + checkDate, + certs, + trustedCerts, + }; + + if (findIssuer) { + chainParams.findIssuer = findIssuer; + } + if (findOrigin) { + chainParams.findOrigin = findOrigin; + } + + const chainEngine = new CertificateChainValidationEngine(chainParams); + chainEngine.certs.push(signerCert); + + if (this.crls) { + for (const crl of this.crls) { + if ("thisUpdate" in crl) + chainEngine.crls.push(crl); + else // Assumed "revocation value" has "OtherRevocationInfoFormat" + { + if (crl.otherRevInfoFormat === id_PKIX_OCSP_Basic) // Basic OCSP response + chainEngine.ocsps.push(new BasicOCSPResponse({ schema: crl.otherRevInfo })); + } + } + } + + if (this.ocsps) { + chainEngine.ocsps.push(...(this.ocsps)); + } + + const verificationResult = await chainEngine.verify({ passedWhenNotRevValues }, crypto) + .catch(e => { + throw new SignedDataVerifyError({ + date: checkDate, + code: 5, + message: `Validation of signer's certificate failed with error: ${((e instanceof Object) ? e.resultMessage : e)}`, + signerCertificate: signerCert, + signerCertificateVerified: false + }); + }); + if (verificationResult.certificatePath) { + certificatePath = verificationResult.certificatePath; + } + + if (!verificationResult.result) + throw new SignedDataVerifyError({ + date: checkDate, + code: 5, + message: `Validation of signer's certificate failed: ${verificationResult.resultMessage}`, + signerCertificate: signerCert, + signerCertificateVerified: false + }); + } + //#endregion + + //#region Find signer's hashing algorithm + + const signerInfoHashAlgorithm = crypto.getAlgorithmByOID(signerInfo.digestAlgorithm.algorithmId); + if (!("name" in signerInfoHashAlgorithm)) { + throw new SignedDataVerifyError({ + date: checkDate, + code: 7, + message: `Unsupported signature algorithm: ${signerInfo.digestAlgorithm.algorithmId}`, + signerCertificate: signerCert, + signerCertificateVerified: true + }); + } + + shaAlgorithm = signerInfoHashAlgorithm.name; + //#endregion + + //#region Create correct data block for verification + + const eContent = this.encapContentInfo.eContent; + if (eContent) // Attached data + { + if ((eContent.idBlock.tagClass === 1) && + (eContent.idBlock.tagNumber === 4)) { + data = eContent.getValue(); + } + else + data = eContent.valueBlock.valueBeforeDecodeView; + } + else // Detached data + { + if (data.byteLength === 0) // Check that "data" already provided by function parameter + { + throw new SignedDataVerifyError({ + date: checkDate, + code: 8, + message: "Missed detached data input array", + signerCertificate: signerCert, + signerCertificateVerified: true + }); + } + } + + if (signerInfo.signedAttrs) { + //#region Check mandatory attributes + let foundContentType = false; + let foundMessageDigest = false; + + for (const attribute of signerInfo.signedAttrs.attributes) { + //#region Check that "content-type" attribute exists + if (attribute.type === "1.2.840.113549.1.9.3") + foundContentType = true; + //#endregion + + //#region Check that "message-digest" attribute exists + if (attribute.type === "1.2.840.113549.1.9.4") { + foundMessageDigest = true; + messageDigestValue = attribute.values[0].valueBlock.valueHex; + } + //#endregion + + //#region Speed-up searching + if (foundContentType && foundMessageDigest) + break; + //#endregion + } + + if (foundContentType === false) { + throw new SignedDataVerifyError({ + date: checkDate, + code: 9, + message: "Attribute \"content-type\" is a mandatory attribute for \"signed attributes\"", + signerCertificate: signerCert, + signerCertificateVerified: true + }); + } + + if (foundMessageDigest === false) { + throw new SignedDataVerifyError({ + date: checkDate, + code: 10, + message: "Attribute \"message-digest\" is a mandatory attribute for \"signed attributes\"", + signatureVerified: null, + signerCertificate: signerCert, + signerCertificateVerified: true + }); + } + //#endregion + } + //#endregion + + //#region Verify "message-digest" attribute in case of "signedAttrs" + if (signerInfo.signedAttrs) { + const messageDigest = await crypto.digest(shaAlgorithm, new Uint8Array(data)); + if (!pvutils.isEqualBuffer(messageDigest, messageDigestValue)) { + throw new SignedDataVerifyError({ + date: checkDate, + code: 15, + message: "Error during verification: Message digest doesn't match", + signatureVerified: null, + signerCertificate: signerCert, + timestampSerial, + signerCertificateVerified: true + }); + } + data = signerInfo.signedAttrs.encodedValue; + } + //#endregion + + const verifyResult = await crypto.verifyWithPublicKey(data, signerInfo.signature, signerCert.subjectPublicKeyInfo, signerCert.signatureAlgorithm, shaAlgorithm); + + //#region Make a final result + + if (extendedMode) { + return { + date: checkDate, + code: 14, + message: EMPTY_STRING, + signatureVerified: verifyResult, + signerCertificate: signerCert, + timestampSerial, + signerCertificateVerified: true, + certificatePath + }; + } else { + return verifyResult; + } + } catch (e) { + if (e instanceof SignedDataVerifyError) { + throw e; + } + throw new SignedDataVerifyError({ + date: checkDate, + code: 15, + message: `Error during verification: ${e instanceof Error ? e.message : e}`, + signatureVerified: null, + signerCertificate: signerCert, + timestampSerial, + signerCertificateVerified: true + }); + } + } + + /** + * Signing current SignedData + * @param privateKey Private key for "subjectPublicKeyInfo" structure + * @param signerIndex Index number (starting from 0) of signer index to make signature for + * @param hashAlgorithm Hashing algorithm. Default SHA-1 + * @param data Detached data + * @param crypto Crypto engine + */ + public async sign(privateKey: CryptoKey, signerIndex: number, hashAlgorithm = "SHA-1", data: BufferSource = (EMPTY_BUFFER), crypto = common.getCrypto(true)): Promise<void> { + //#region Initial checking + if (!privateKey) + throw new Error("Need to provide a private key for signing"); + //#endregion + + //#region Simple check for supported algorithm + const hashAlgorithmOID = crypto.getOIDByAlgorithm({ name: hashAlgorithm }, true, "hashAlgorithm"); + //#endregion + + //#region Append information about hash algorithm + if ((this.digestAlgorithms.filter(algorithm => algorithm.algorithmId === hashAlgorithmOID)).length === 0) { + this.digestAlgorithms.push(new AlgorithmIdentifier({ + algorithmId: hashAlgorithmOID, + algorithmParams: new asn1js.Null() + })); + } + + const signerInfo = this.signerInfos[signerIndex]; + if (!signerInfo) { + throw new RangeError("SignerInfo index is out of range"); + } + signerInfo.digestAlgorithm = new AlgorithmIdentifier({ + algorithmId: hashAlgorithmOID, + algorithmParams: new asn1js.Null() + }); + //#endregion + + //#region Get a "default parameters" for current algorithm and set correct signature algorithm + const signatureParams = await crypto.getSignatureParameters(privateKey, hashAlgorithm); + const parameters = signatureParams.parameters; + signerInfo.signatureAlgorithm = signatureParams.signatureAlgorithm; + //#endregion + + //#region Create TBS data for signing + if (signerInfo.signedAttrs) { + if (signerInfo.signedAttrs.encodedValue.byteLength !== 0) + data = signerInfo.signedAttrs.encodedValue; + else { + data = signerInfo.signedAttrs.toSchema().toBER(); + + //#region Change type from "[0]" to "SET" accordingly to standard + const view = pvtsutils.BufferSourceConverter.toUint8Array(data); + view[0] = 0x31; + //#endregion + } + } + else { + const eContent = this.encapContentInfo.eContent; + if (eContent) // Attached data + { + if ((eContent.idBlock.tagClass === 1) && + (eContent.idBlock.tagNumber === 4)) { + data = eContent.getValue(); + } + else + data = eContent.valueBlock.valueBeforeDecodeView; + } + else // Detached data + { + if (data.byteLength === 0) // Check that "data" already provided by function parameter + throw new Error("Missed detached data input array"); + } + } + //#endregion + + //#region Signing TBS data on provided private key + const signature = await crypto.signWithPrivateKey(data, privateKey, parameters as any); + signerInfo.signature = new asn1js.OctetString({ valueHex: signature }); + //#endregion + } + +} + + diff --git a/third_party/js/PKI.js/src/SignerInfo.ts b/third_party/js/PKI.js/src/SignerInfo.ts new file mode 100644 index 0000000000..a6f59805dd --- /dev/null +++ b/third_party/js/PKI.js/src/SignerInfo.ts @@ -0,0 +1,352 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier"; +import { SignedAndUnsignedAttributes, SignedAndUnsignedAttributesJson, SignedAndUnsignedAttributesSchema } from "./SignedAndUnsignedAttributes"; +import { IssuerAndSerialNumber, IssuerAndSerialNumberSchema } from "./IssuerAndSerialNumber"; +import * as Schema from "./Schema"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { AsnError } from "./errors"; + +const VERSION = "version"; +const SID = "sid"; +const DIGEST_ALGORITHM = "digestAlgorithm"; +const SIGNED_ATTRS = "signedAttrs"; +const SIGNATURE_ALGORITHM = "signatureAlgorithm"; +const SIGNATURE = "signature"; +const UNSIGNED_ATTRS = "unsignedAttrs"; +const SIGNER_INFO = "SignerInfo"; +const SIGNER_INFO_VERSION = `${SIGNER_INFO}.${VERSION}`; +const SIGNER_INFO_SID = `${SIGNER_INFO}.${SID}`; +const SIGNER_INFO_DIGEST_ALGORITHM = `${SIGNER_INFO}.${DIGEST_ALGORITHM}`; +const SIGNER_INFO_SIGNED_ATTRS = `${SIGNER_INFO}.${SIGNED_ATTRS}`; +const SIGNER_INFO_SIGNATURE_ALGORITHM = `${SIGNER_INFO}.${SIGNATURE_ALGORITHM}`; +const SIGNER_INFO_SIGNATURE = `${SIGNER_INFO}.${SIGNATURE}`; +const SIGNER_INFO_UNSIGNED_ATTRS = `${SIGNER_INFO}.${UNSIGNED_ATTRS}`; +const CLEAR_PROPS = [ + SIGNER_INFO_VERSION, + SIGNER_INFO_SID, + SIGNER_INFO_DIGEST_ALGORITHM, + SIGNER_INFO_SIGNED_ATTRS, + SIGNER_INFO_SIGNATURE_ALGORITHM, + SIGNER_INFO_SIGNATURE, + SIGNER_INFO_UNSIGNED_ATTRS +]; + +export interface ISignerInfo { + version: number; + sid: Schema.SchemaType; + digestAlgorithm: AlgorithmIdentifier; + signedAttrs?: SignedAndUnsignedAttributes; + signatureAlgorithm: AlgorithmIdentifier; + signature: asn1js.OctetString; + unsignedAttrs?: SignedAndUnsignedAttributes; +} + +export interface SignerInfoJson { + version: number; + sid?: Schema.SchemaType; + digestAlgorithm: AlgorithmIdentifierJson; + signedAttrs?: SignedAndUnsignedAttributesJson; + signatureAlgorithm: AlgorithmIdentifierJson; + signature: asn1js.OctetStringJson; + unsignedAttrs?: SignedAndUnsignedAttributesJson; +} + +export type SignerInfoParameters = PkiObjectParameters & Partial<ISignerInfo>; + +/** + * Represents the SignerInfo structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) + */ +export class SignerInfo extends PkiObject implements ISignerInfo { + + public static override CLASS_NAME = "SignerInfo"; + + public version!: number; + public sid: Schema.SchemaType; + public digestAlgorithm!: AlgorithmIdentifier; + public signedAttrs?: SignedAndUnsignedAttributes; + public signatureAlgorithm!: AlgorithmIdentifier; + public signature!: asn1js.OctetString; + public unsignedAttrs?: SignedAndUnsignedAttributes; + + /** + * Initializes a new instance of the {@link SignerInfo} class + * @param parameters Initialization parameters + */ + constructor(parameters: SignerInfoParameters = {}) { + super(); + + this.version = pvutils.getParametersValue(parameters, VERSION, SignerInfo.defaultValues(VERSION)); + this.sid = pvutils.getParametersValue(parameters, SID, SignerInfo.defaultValues(SID)); + this.digestAlgorithm = pvutils.getParametersValue(parameters, DIGEST_ALGORITHM, SignerInfo.defaultValues(DIGEST_ALGORITHM)); + if (SIGNED_ATTRS in parameters) { + this.signedAttrs = pvutils.getParametersValue(parameters, SIGNED_ATTRS, SignerInfo.defaultValues(SIGNED_ATTRS)); + } + this.signatureAlgorithm = pvutils.getParametersValue(parameters, SIGNATURE_ALGORITHM, SignerInfo.defaultValues(SIGNATURE_ALGORITHM)); + this.signature = pvutils.getParametersValue(parameters, SIGNATURE, SignerInfo.defaultValues(SIGNATURE)); + if (UNSIGNED_ATTRS in parameters) { + this.unsignedAttrs = pvutils.getParametersValue(parameters, UNSIGNED_ATTRS, SignerInfo.defaultValues(UNSIGNED_ATTRS)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof VERSION): number; + public static override defaultValues(memberName: typeof SID): Schema.SchemaType; + public static override defaultValues(memberName: typeof DIGEST_ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof SIGNED_ATTRS): SignedAndUnsignedAttributes; + public static override defaultValues(memberName: typeof SIGNATURE_ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof SIGNATURE): asn1js.OctetString; + public static override defaultValues(memberName: typeof UNSIGNED_ATTRS): SignedAndUnsignedAttributes; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case VERSION: + return 0; + case SID: + return new asn1js.Any(); + case DIGEST_ALGORITHM: + return new AlgorithmIdentifier(); + case SIGNED_ATTRS: + return new SignedAndUnsignedAttributes({ type: 0 }); + case SIGNATURE_ALGORITHM: + return new AlgorithmIdentifier(); + case SIGNATURE: + return new asn1js.OctetString(); + case UNSIGNED_ATTRS: + return new SignedAndUnsignedAttributes({ type: 1 }); + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case VERSION: + return (SignerInfo.defaultValues(VERSION) === memberValue); + case SID: + return (memberValue instanceof asn1js.Any); + case DIGEST_ALGORITHM: + if ((memberValue instanceof AlgorithmIdentifier) === false) + return false; + + return memberValue.isEqual(SignerInfo.defaultValues(DIGEST_ALGORITHM)); + case SIGNED_ATTRS: + return ((SignedAndUnsignedAttributes.compareWithDefault("type", memberValue.type)) + && (SignedAndUnsignedAttributes.compareWithDefault("attributes", memberValue.attributes)) + && (SignedAndUnsignedAttributes.compareWithDefault("encodedValue", memberValue.encodedValue))); + case SIGNATURE_ALGORITHM: + if ((memberValue instanceof AlgorithmIdentifier) === false) + return false; + + return memberValue.isEqual(SignerInfo.defaultValues(SIGNATURE_ALGORITHM)); + case SIGNATURE: + case UNSIGNED_ATTRS: + return ((SignedAndUnsignedAttributes.compareWithDefault("type", memberValue.type)) + && (SignedAndUnsignedAttributes.compareWithDefault("attributes", memberValue.attributes)) + && (SignedAndUnsignedAttributes.compareWithDefault("encodedValue", memberValue.encodedValue))); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * SignerInfo ::= SEQUENCE { + * version CMSVersion, + * sid SignerIdentifier, + * digestAlgorithm DigestAlgorithmIdentifier, + * signedAttrs [0] IMPLICIT SignedAttributes OPTIONAL, + * signatureAlgorithm SignatureAlgorithmIdentifier, + * signature SignatureValue, + * unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL } + * + * SignerIdentifier ::= CHOICE { + * issuerAndSerialNumber IssuerAndSerialNumber, + * subjectKeyIdentifier [0] SubjectKeyIdentifier } + * + * SubjectKeyIdentifier ::= OCTET STRING + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + version?: string; + sidSchema?: IssuerAndSerialNumberSchema; + sid?: string; + digestAlgorithm?: AlgorithmIdentifierSchema; + signedAttrs?: SignedAndUnsignedAttributesSchema; + signatureAlgorithm?: AlgorithmIdentifierSchema; + signature?: string; + unsignedAttrs?: SignedAndUnsignedAttributesSchema; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return ( + new asn1js.Sequence({ + name: SIGNER_INFO, + value: [ + new asn1js.Integer({ name: (names.version || SIGNER_INFO_VERSION) }), + new asn1js.Choice({ + value: [ + IssuerAndSerialNumber.schema(names.sidSchema || { + names: { + blockName: SIGNER_INFO_SID + } + }), + new asn1js.Choice({ + value: [ + new asn1js.Constructed({ + optional: true, + name: (names.sid || SIGNER_INFO_SID), + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [new asn1js.OctetString()] + }), + new asn1js.Primitive({ + optional: true, + name: (names.sid || SIGNER_INFO_SID), + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + } + }), + ] + }), + ] + }), + AlgorithmIdentifier.schema(names.digestAlgorithm || { + names: { + blockName: SIGNER_INFO_DIGEST_ALGORITHM + } + }), + SignedAndUnsignedAttributes.schema(names.signedAttrs || { + names: { + blockName: SIGNER_INFO_SIGNED_ATTRS, + tagNumber: 0 + } + }), + AlgorithmIdentifier.schema(names.signatureAlgorithm || { + names: { + blockName: SIGNER_INFO_SIGNATURE_ALGORITHM + } + }), + new asn1js.OctetString({ name: (names.signature || SIGNER_INFO_SIGNATURE) }), + SignedAndUnsignedAttributes.schema(names.unsignedAttrs || { + names: { + blockName: SIGNER_INFO_UNSIGNED_ATTRS, + tagNumber: 1 + } + }) + ] + }) + ); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + SignerInfo.schema() + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.version = asn1.result[SIGNER_INFO_VERSION].valueBlock.valueDec; + + const currentSid = asn1.result[SIGNER_INFO_SID]; + if (currentSid.idBlock.tagClass === 1) + this.sid = new IssuerAndSerialNumber({ schema: currentSid }); + else + this.sid = currentSid; + + this.digestAlgorithm = new AlgorithmIdentifier({ schema: asn1.result[SIGNER_INFO_DIGEST_ALGORITHM] }); + if (SIGNER_INFO_SIGNED_ATTRS in asn1.result) + this.signedAttrs = new SignedAndUnsignedAttributes({ type: 0, schema: asn1.result[SIGNER_INFO_SIGNED_ATTRS] }); + + this.signatureAlgorithm = new AlgorithmIdentifier({ schema: asn1.result[SIGNER_INFO_SIGNATURE_ALGORITHM] }); + this.signature = asn1.result[SIGNER_INFO_SIGNATURE]; + if (SIGNER_INFO_UNSIGNED_ATTRS in asn1.result) + this.unsignedAttrs = new SignedAndUnsignedAttributes({ type: 1, schema: asn1.result[SIGNER_INFO_UNSIGNED_ATTRS] }); + } + + public toSchema(): asn1js.Sequence { + if (SignerInfo.compareWithDefault(SID, this.sid)) + throw new Error("Incorrectly initialized \"SignerInfo\" class"); + + //#region Create array for output sequence + const outputArray = []; + + outputArray.push(new asn1js.Integer({ value: this.version })); + + if (this.sid instanceof IssuerAndSerialNumber) + outputArray.push(this.sid.toSchema()); + else + outputArray.push(this.sid); + + outputArray.push(this.digestAlgorithm.toSchema()); + + if (this.signedAttrs) { + if (SignerInfo.compareWithDefault(SIGNED_ATTRS, this.signedAttrs) === false) + outputArray.push(this.signedAttrs.toSchema()); + } + + outputArray.push(this.signatureAlgorithm.toSchema()); + outputArray.push(this.signature); + + if (this.unsignedAttrs) { + if (SignerInfo.compareWithDefault(UNSIGNED_ATTRS, this.unsignedAttrs) === false) + outputArray.push(this.unsignedAttrs.toSchema()); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): SignerInfoJson { + if (SignerInfo.compareWithDefault(SID, this.sid)) { + throw new Error("Incorrectly initialized \"SignerInfo\" class"); + } + + const res: SignerInfoJson = { + version: this.version, + digestAlgorithm: this.digestAlgorithm.toJSON(), + signatureAlgorithm: this.signatureAlgorithm.toJSON(), + signature: this.signature.toJSON(), + }; + + if (!(this.sid instanceof asn1js.Any)) + res.sid = this.sid.toJSON(); + + if (this.signedAttrs && SignerInfo.compareWithDefault(SIGNED_ATTRS, this.signedAttrs) === false) { + res.signedAttrs = this.signedAttrs.toJSON(); + } + + if (this.unsignedAttrs && SignerInfo.compareWithDefault(UNSIGNED_ATTRS, this.unsignedAttrs) === false) { + res.unsignedAttrs = this.unsignedAttrs.toJSON(); + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/SingleResponse.ts b/third_party/js/PKI.js/src/SingleResponse.ts new file mode 100644 index 0000000000..0d3c82696a --- /dev/null +++ b/third_party/js/PKI.js/src/SingleResponse.ts @@ -0,0 +1,305 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { CertID, CertIDJson, CertIDSchema } from "./CertID"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { Extension, ExtensionJson } from "./Extension"; +import { Extensions, ExtensionsSchema } from "./Extensions"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const CERT_ID = "certID"; +const CERT_STATUS = "certStatus"; +const THIS_UPDATE = "thisUpdate"; +const NEXT_UPDATE = "nextUpdate"; +const SINGLE_EXTENSIONS = "singleExtensions"; +const CLEAR_PROPS = [ + CERT_ID, + CERT_STATUS, + THIS_UPDATE, + NEXT_UPDATE, + SINGLE_EXTENSIONS, +]; + +export interface ISingleResponse { + certID: CertID; + certStatus: any; + thisUpdate: Date; + nextUpdate?: Date; + singleExtensions?: Extension[]; +} + +export type SingleResponseParameters = PkiObjectParameters & Partial<ISingleResponse>; + +export type SingleResponseSchema = Schema.SchemaParameters<{ + certID?: CertIDSchema; + certStatus?: string; + thisUpdate?: string; + nextUpdate?: string; + singleExtensions?: ExtensionsSchema; +}>; + +export interface SingleResponseJson { + certID: CertIDJson; + certStatus: any; + thisUpdate: Date; + nextUpdate?: Date; + singleExtensions?: ExtensionJson[]; +} + +/** + * Represents an SingleResponse described in [RFC6960](https://datatracker.ietf.org/doc/html/rfc6960) + */ +export class SingleResponse extends PkiObject implements ISingleResponse { + + public static override CLASS_NAME = "SingleResponse"; + + certID!: CertID; + certStatus: any; + thisUpdate!: Date; + nextUpdate?: Date; + singleExtensions?: Extension[]; + + /** + * Initializes a new instance of the {@link SingleResponse} class + * @param parameters Initialization parameters + */ + constructor(parameters: SingleResponseParameters = {}) { + super(); + + this.certID = pvutils.getParametersValue(parameters, CERT_ID, SingleResponse.defaultValues(CERT_ID)); + this.certStatus = pvutils.getParametersValue(parameters, CERT_STATUS, SingleResponse.defaultValues(CERT_STATUS)); + this.thisUpdate = pvutils.getParametersValue(parameters, THIS_UPDATE, SingleResponse.defaultValues(THIS_UPDATE)); + if (NEXT_UPDATE in parameters) { + this.nextUpdate = pvutils.getParametersValue(parameters, NEXT_UPDATE, SingleResponse.defaultValues(NEXT_UPDATE)); + } + if (SINGLE_EXTENSIONS in parameters) { + this.singleExtensions = pvutils.getParametersValue(parameters, SINGLE_EXTENSIONS, SingleResponse.defaultValues(SINGLE_EXTENSIONS)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof CERT_ID): CertID; + public static override defaultValues(memberName: typeof CERT_STATUS): any; + public static override defaultValues(memberName: typeof THIS_UPDATE): Date; + public static override defaultValues(memberName: typeof NEXT_UPDATE): Date; + public static override defaultValues(memberName: typeof SINGLE_EXTENSIONS): Extension[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case CERT_ID: + return new CertID(); + case CERT_STATUS: + return {}; + case THIS_UPDATE: + case NEXT_UPDATE: + return new Date(0, 0, 0); + case SINGLE_EXTENSIONS: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case CERT_ID: + return ((CertID.compareWithDefault("hashAlgorithm", memberValue.hashAlgorithm)) && + (CertID.compareWithDefault("issuerNameHash", memberValue.issuerNameHash)) && + (CertID.compareWithDefault("issuerKeyHash", memberValue.issuerKeyHash)) && + (CertID.compareWithDefault("serialNumber", memberValue.serialNumber))); + case CERT_STATUS: + return (Object.keys(memberValue).length === 0); + case THIS_UPDATE: + case NEXT_UPDATE: + return (memberValue === SingleResponse.defaultValues(memberName as typeof NEXT_UPDATE)); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * SingleResponse ::= SEQUENCE { + * certID CertID, + * certStatus CertStatus, + * thisUpdate GeneralizedTime, + * nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL, + * singleExtensions [1] EXPLICIT Extensions OPTIONAL } + * + * CertStatus ::= CHOICE { + * good [0] IMPLICIT NULL, + * revoked [1] IMPLICIT RevokedInfo, + * unknown [2] IMPLICIT UnknownInfo } + * + * RevokedInfo ::= SEQUENCE { + * revocationTime GeneralizedTime, + * revocationReason [0] EXPLICIT CRLReason OPTIONAL } + * + * UnknownInfo ::= NULL + *``` + */ + public static override schema(parameters: SingleResponseSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + CertID.schema(names.certID || {}), + new asn1js.Choice({ + value: [ + new asn1js.Primitive({ + name: (names.certStatus || EMPTY_STRING), + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + }), // IMPLICIT NULL (no "valueBlock") + new asn1js.Constructed({ + name: (names.certStatus || EMPTY_STRING), + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [ + new asn1js.GeneralizedTime(), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [new asn1js.Enumerated()] + }) + ] + }), + new asn1js.Primitive({ + name: (names.certStatus || EMPTY_STRING), + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 2 // [2] + }, + lenBlock: { length: 1 } + }) // IMPLICIT NULL (no "valueBlock") + ] + }), + new asn1js.GeneralizedTime({ name: (names.thisUpdate || EMPTY_STRING) }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [new asn1js.GeneralizedTime({ name: (names.nextUpdate || EMPTY_STRING) })] + }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [Extensions.schema(names.singleExtensions || {})] + }) // EXPLICIT SEQUENCE value + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + SingleResponse.schema({ + names: { + certID: { + names: { + blockName: CERT_ID + } + }, + certStatus: CERT_STATUS, + thisUpdate: THIS_UPDATE, + nextUpdate: NEXT_UPDATE, + singleExtensions: { + names: { + blockName: + SINGLE_EXTENSIONS + } + } + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.certID = new CertID({ schema: asn1.result.certID }); + this.certStatus = asn1.result.certStatus; + this.thisUpdate = asn1.result.thisUpdate.toDate(); + if (NEXT_UPDATE in asn1.result) + this.nextUpdate = asn1.result.nextUpdate.toDate(); + if (SINGLE_EXTENSIONS in asn1.result) + this.singleExtensions = Array.from(asn1.result.singleExtensions.valueBlock.value, element => new Extension({ schema: element })); + } + + public toSchema(): asn1js.Sequence { + // Create value array for output sequence + const outputArray = []; + + outputArray.push(this.certID.toSchema()); + outputArray.push(this.certStatus); + outputArray.push(new asn1js.GeneralizedTime({ valueDate: this.thisUpdate })); + if (this.nextUpdate) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [new asn1js.GeneralizedTime({ valueDate: this.nextUpdate })] + })); + } + + if (this.singleExtensions) { + outputArray.push(new asn1js.Sequence({ + value: Array.from(this.singleExtensions, o => o.toSchema()) + })); + } + + return (new asn1js.Sequence({ + value: outputArray + })); + } + + public toJSON(): SingleResponseJson { + const res: SingleResponseJson = { + certID: this.certID.toJSON(), + certStatus: this.certStatus.toJSON(), + thisUpdate: this.thisUpdate + }; + + if (this.nextUpdate) { + res.nextUpdate = this.nextUpdate; + } + + if (this.singleExtensions) { + res.singleExtensions = Array.from(this.singleExtensions, o => o.toJSON()); + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/SubjectDirectoryAttributes.ts b/third_party/js/PKI.js/src/SubjectDirectoryAttributes.ts new file mode 100644 index 0000000000..83c1b58a6a --- /dev/null +++ b/third_party/js/PKI.js/src/SubjectDirectoryAttributes.ts @@ -0,0 +1,117 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { Attribute, AttributeJson } from "./Attribute"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const ATTRIBUTES = "attributes"; +const CLEAR_PROPS = [ + ATTRIBUTES +]; + +export interface ISubjectDirectoryAttributes { + attributes: Attribute[]; +} + +export interface SubjectDirectoryAttributesJson { + attributes: AttributeJson[]; +} + +export type SubjectDirectoryAttributesParameters = PkiObjectParameters & Partial<ISubjectDirectoryAttributes>; + +/** + * Represents the SubjectDirectoryAttributes structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class SubjectDirectoryAttributes extends PkiObject implements ISubjectDirectoryAttributes { + + public static override CLASS_NAME = "SubjectDirectoryAttributes"; + + public attributes!: Attribute[]; + + /** + * Initializes a new instance of the {@link SubjectDirectoryAttributes} class + * @param parameters Initialization parameters + */ + constructor(parameters: SubjectDirectoryAttributesParameters = {}) { + super(); + + this.attributes = pvutils.getParametersValue(parameters, ATTRIBUTES, SubjectDirectoryAttributes.defaultValues(ATTRIBUTES)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof ATTRIBUTES): Attribute[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case ATTRIBUTES: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * SubjectDirectoryAttributes ::= SEQUENCE SIZE (1..MAX) OF Attribute + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + attributes?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Repeated({ + name: (names.attributes || EMPTY_STRING), + value: Attribute.schema() + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + SubjectDirectoryAttributes.schema({ + names: { + attributes: ATTRIBUTES + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.attributes = Array.from(asn1.result.attributes, element => new Attribute({ schema: element })); + } + + public toSchema(): asn1js.Sequence { + // Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: Array.from(this.attributes, o => o.toSchema()) + })); + } + + public toJSON(): SubjectDirectoryAttributesJson { + return { + attributes: Array.from(this.attributes, o => o.toJSON()) + }; + } + +} diff --git a/third_party/js/PKI.js/src/TBSRequest.ts b/third_party/js/PKI.js/src/TBSRequest.ts new file mode 100644 index 0000000000..cca0b47150 --- /dev/null +++ b/third_party/js/PKI.js/src/TBSRequest.ts @@ -0,0 +1,337 @@ +import * as asn1js from "asn1js"; +import * as pvtsutils from "pvtsutils"; +import * as pvutils from "pvutils"; +import { GeneralName, GeneralNameJson, GeneralNameSchema } from "./GeneralName"; +import { Request, RequestJson, RequestSchema } from "./Request"; +import { Extension, ExtensionJson } from "./Extension"; +import { Extensions, ExtensionsSchema } from "./Extensions"; +import * as Schema from "./Schema"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { EMPTY_BUFFER } from "./constants"; + +const TBS = "tbs"; +const VERSION = "version"; +const REQUESTOR_NAME = "requestorName"; +const REQUEST_LIST = "requestList"; +const REQUEST_EXTENSIONS = "requestExtensions"; +const TBS_REQUEST = "TBSRequest"; +const TBS_REQUEST_VERSION = `${TBS_REQUEST}.${VERSION}`; +const TBS_REQUEST_REQUESTOR_NAME = `${TBS_REQUEST}.${REQUESTOR_NAME}`; +const TBS_REQUEST_REQUESTS = `${TBS_REQUEST}.requests`; +const TBS_REQUEST_REQUEST_EXTENSIONS = `${TBS_REQUEST}.${REQUEST_EXTENSIONS}`; +const CLEAR_PROPS = [ + TBS_REQUEST, + TBS_REQUEST_VERSION, + TBS_REQUEST_REQUESTOR_NAME, + TBS_REQUEST_REQUESTS, + TBS_REQUEST_REQUEST_EXTENSIONS +]; + +export interface ITBSRequest { + tbs: ArrayBuffer; + version?: number; + requestorName?: GeneralName; + requestList: Request[]; + requestExtensions?: Extension[]; +} + +export interface TBSRequestJson { + tbs: string; + version?: number; + requestorName?: GeneralNameJson; + requestList: RequestJson[]; + requestExtensions?: ExtensionJson[]; +} + +export type TBSRequestParameters = PkiObjectParameters & Partial<ITBSRequest>; + +export type TBSRequestSchema = Schema.SchemaParameters<{ + TBSRequestVersion?: string; + requestorName?: GeneralNameSchema; + requestList?: string; + requests?: string; + requestNames?: RequestSchema; + extensions?: ExtensionsSchema; + requestExtensions?: string; +}>; + +/** + * Represents the TBSRequest structure described in [RFC6960](https://datatracker.ietf.org/doc/html/rfc6960) + */ +export class TBSRequest extends PkiObject implements ITBSRequest { + + public static override CLASS_NAME = "TBSRequest"; + + public tbsView!: Uint8Array; + /** + * @deprecated Since version 3.0.0 + */ + public get tbs(): ArrayBuffer { + return pvtsutils.BufferSourceConverter.toArrayBuffer(this.tbsView); + } + + /** + * @deprecated Since version 3.0.0 + */ + public set tbs(value: ArrayBuffer) { + this.tbsView = new Uint8Array(value); + } + public version?: number; + public requestorName?: GeneralName; + public requestList!: Request[]; + public requestExtensions?: Extension[]; + + /** + * Initializes a new instance of the {@link TBSRequest} class + * @param parameters Initialization parameters + */ + constructor(parameters: TBSRequestParameters = {}) { + super(); + + this.tbsView = new Uint8Array(pvutils.getParametersValue(parameters, TBS, TBSRequest.defaultValues(TBS))); + if (VERSION in parameters) { + this.version = pvutils.getParametersValue(parameters, VERSION, TBSRequest.defaultValues(VERSION)); + } + if (REQUESTOR_NAME in parameters) { + this.requestorName = pvutils.getParametersValue(parameters, REQUESTOR_NAME, TBSRequest.defaultValues(REQUESTOR_NAME)); + } + this.requestList = pvutils.getParametersValue(parameters, REQUEST_LIST, TBSRequest.defaultValues(REQUEST_LIST)); + if (REQUEST_EXTENSIONS in parameters) { + this.requestExtensions = pvutils.getParametersValue(parameters, REQUEST_EXTENSIONS, TBSRequest.defaultValues(REQUEST_EXTENSIONS)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof TBS): ArrayBuffer; + public static override defaultValues(memberName: typeof VERSION): number; + public static override defaultValues(memberName: typeof REQUESTOR_NAME): GeneralName; + public static override defaultValues(memberName: typeof REQUEST_LIST): Request[]; + public static override defaultValues(memberName: typeof REQUEST_EXTENSIONS): Extension[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case TBS: + return EMPTY_BUFFER; + case VERSION: + return 0; + case REQUESTOR_NAME: + return new GeneralName(); + case REQUEST_LIST: + case REQUEST_EXTENSIONS: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case TBS: + return (memberValue.byteLength === 0); + case VERSION: + return (memberValue === TBSRequest.defaultValues(memberName)); + case REQUESTOR_NAME: + return ((memberValue.type === GeneralName.defaultValues("type")) && (Object.keys(memberValue.value).length === 0)); + case REQUEST_LIST: + case REQUEST_EXTENSIONS: + return (memberValue.length === 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * TBSRequest ::= SEQUENCE { + * version [0] EXPLICIT Version DEFAULT v1, + * requestorName [1] EXPLICIT GeneralName OPTIONAL, + * requestList SEQUENCE OF Request, + * requestExtensions [2] EXPLICIT Extensions OPTIONAL } + *``` + */ + public static override schema(parameters: TBSRequestSchema = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || TBS_REQUEST), + value: [ + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [new asn1js.Integer({ name: (names.TBSRequestVersion || TBS_REQUEST_VERSION) })] + }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [GeneralName.schema(names.requestorName || { + names: { + blockName: TBS_REQUEST_REQUESTOR_NAME + } + })] + }), + new asn1js.Sequence({ + name: (names.requestList || "TBSRequest.requestList"), + value: [ + new asn1js.Repeated({ + name: (names.requests || TBS_REQUEST_REQUESTS), + value: Request.schema(names.requestNames || {}) + }) + ] + }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 2 // [2] + }, + value: [Extensions.schema(names.extensions || { + names: { + blockName: (names.requestExtensions || TBS_REQUEST_REQUEST_EXTENSIONS) + } + })] + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + TBSRequest.schema() + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.tbsView = asn1.result.TBSRequest.valueBeforeDecodeView; + + if (TBS_REQUEST_VERSION in asn1.result) + this.version = asn1.result[TBS_REQUEST_VERSION].valueBlock.valueDec; + if (TBS_REQUEST_REQUESTOR_NAME in asn1.result) + this.requestorName = new GeneralName({ schema: asn1.result[TBS_REQUEST_REQUESTOR_NAME] }); + + this.requestList = Array.from(asn1.result[TBS_REQUEST_REQUESTS], element => new Request({ schema: element })); + + if (TBS_REQUEST_REQUEST_EXTENSIONS in asn1.result) + this.requestExtensions = Array.from(asn1.result[TBS_REQUEST_REQUEST_EXTENSIONS].valueBlock.value, element => new Extension({ schema: element })); + } + + /** + * Convert current object to asn1js object and set correct values + * @param encodeFlag If param equal to false then create TBS schema via decoding stored value. In othe case create TBS schema via assembling from TBS parts. + * @returns asn1js object + */ + public toSchema(encodeFlag = false): asn1js.Sequence { + //#region Decode stored TBS value + let tbsSchema; + + if (encodeFlag === false) { + if (this.tbsView.byteLength === 0) // No stored TBS part + return TBSRequest.schema(); + + const asn1 = asn1js.fromBER(this.tbsView); + AsnError.assert(asn1, "TBS Request"); + if (!(asn1.result instanceof asn1js.Sequence)) { + throw new Error("ASN.1 result should be SEQUENCE"); + } + + tbsSchema = asn1.result; + } + //#endregion + //#region Create TBS schema via assembling from TBS parts + else { + const outputArray = []; + + if (this.version !== undefined) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [new asn1js.Integer({ value: this.version })] + })); + } + + if (this.requestorName) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [this.requestorName.toSchema()] + })); + } + + outputArray.push(new asn1js.Sequence({ + value: Array.from(this.requestList, o => o.toSchema()) + })); + + if (this.requestExtensions) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 2 // [2] + }, + value: [ + new asn1js.Sequence({ + value: Array.from(this.requestExtensions, o => o.toSchema()) + }) + ] + })); + } + + tbsSchema = new asn1js.Sequence({ + value: outputArray + }); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return tbsSchema; + //#endregion + } + + public toJSON(): TBSRequestJson { + const res: any = {}; + + if (this.version != undefined) + res.version = this.version; + + if (this.requestorName) { + res.requestorName = this.requestorName.toJSON(); + } + + res.requestList = Array.from(this.requestList, o => o.toJSON()); + + if (this.requestExtensions) { + res.requestExtensions = Array.from(this.requestExtensions, o => o.toJSON()); + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/TSTInfo.ts b/third_party/js/PKI.js/src/TSTInfo.ts new file mode 100644 index 0000000000..b369382367 --- /dev/null +++ b/third_party/js/PKI.js/src/TSTInfo.ts @@ -0,0 +1,489 @@ +import * as asn1js from "asn1js"; +import * as pvtsutils from "pvtsutils"; +import * as pvutils from "pvutils"; +import * as common from "./common"; +import { MessageImprint, HASHED_MESSAGE, HASH_ALGORITHM, MessageImprintSchema, MessageImprintJson } from "./MessageImprint"; +import { Accuracy, AccuracyJson, AccuracySchema, MICROS, MILLIS, SECONDS } from "./Accuracy"; +import { GeneralName, GeneralNameJson, GeneralNameSchema, TYPE, VALUE } from "./GeneralName"; +import { Extension, ExtensionJson, ExtensionSchema } from "./Extension"; +import * as Schema from "./Schema"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { AsnError } from "./errors"; +import { EMPTY_STRING } from "./constants"; + +const VERSION = "version"; +const POLICY = "policy"; +const MESSAGE_IMPRINT = "messageImprint"; +const SERIAL_NUMBER = "serialNumber"; +const GEN_TIME = "genTime"; +const ORDERING = "ordering"; +const NONCE = "nonce"; +const ACCURACY = "accuracy"; +const TSA = "tsa"; +const EXTENSIONS = "extensions"; +const TST_INFO = "TSTInfo"; +const TST_INFO_VERSION = `${TST_INFO}.${VERSION}`; +const TST_INFO_POLICY = `${TST_INFO}.${POLICY}`; +const TST_INFO_MESSAGE_IMPRINT = `${TST_INFO}.${MESSAGE_IMPRINT}`; +const TST_INFO_SERIAL_NUMBER = `${TST_INFO}.${SERIAL_NUMBER}`; +const TST_INFO_GEN_TIME = `${TST_INFO}.${GEN_TIME}`; +const TST_INFO_ACCURACY = `${TST_INFO}.${ACCURACY}`; +const TST_INFO_ORDERING = `${TST_INFO}.${ORDERING}`; +const TST_INFO_NONCE = `${TST_INFO}.${NONCE}`; +const TST_INFO_TSA = `${TST_INFO}.${TSA}`; +const TST_INFO_EXTENSIONS = `${TST_INFO}.${EXTENSIONS}`; +const CLEAR_PROPS = [ + TST_INFO_VERSION, + TST_INFO_POLICY, + TST_INFO_MESSAGE_IMPRINT, + TST_INFO_SERIAL_NUMBER, + TST_INFO_GEN_TIME, + TST_INFO_ACCURACY, + TST_INFO_ORDERING, + TST_INFO_NONCE, + TST_INFO_TSA, + TST_INFO_EXTENSIONS +]; + +export interface ITSTInfo { + /** + * Version of the time-stamp token. + * + * Conforming time-stamping servers MUST be able to provide version 1 time-stamp tokens. + */ + version: number; + /** + * TSA's policy under which the response was produced. + * + * If a similar field was present in the TimeStampReq, then it MUST have the same value, + * otherwise an error (unacceptedPolicy) MUST be returned + */ + policy: string; + /** + * The messageImprint MUST have the same value as the similar field in + * TimeStampReq, provided that the size of the hash value matches the + * expected size of the hash algorithm identified in hashAlgorithm. + */ + messageImprint: MessageImprint; + /** + * Integer assigned by the TSA to each TimeStampToken. + * + * It MUST be unique for each TimeStampToken issued by a given TSA. + */ + serialNumber: asn1js.Integer; + /** + * Time at which the time-stamp token has been created by the TSA + */ + genTime: Date; + /** + * Represents the time deviation around the UTC time contained in GeneralizedTime + */ + accuracy?: Accuracy; + /** + * If the ordering field is missing, or if the ordering field is present + * and set to false, then the genTime field only indicates the time at + * which the time-stamp token has been created by the TSA.In such a + * case, the ordering of time-stamp tokens issued by the same TSA or + * different TSAs is only possible when the difference between the + * genTime of the first time-stamp token and the genTime of the second + * time-stamp token is greater than the sum of the accuracies of the + * genTime for each time-stamp token. + * + * If the ordering field is present and set to true, every time-stamp + * token from the same TSA can always be ordered based on the genTime + * field, regardless of the genTime accuracy. + */ + ordering?: boolean; + /** + * Field MUST be present if it was present in the TimeStampReq. + * In such a case it MUST equal the value provided in the TimeStampReq structure. + */ + nonce?: asn1js.Integer; + /** + * `tsa` field is to give a hint in identifying the name of the TSA. + * If present, it MUST correspond to one of the subject names included + * in the certificate that is to be used to verify the token. + */ + tsa?: GeneralName; + /** + * Additional information in the future. Extensions is defined in [RFC2459](https://datatracker.ietf.org/doc/html/rfc2459) + */ + extensions?: Extension[]; +} + +export interface TSTInfoJson { + version: number; + policy: string; + messageImprint: MessageImprintJson; + serialNumber: asn1js.IntegerJson; + genTime: Date; + accuracy?: AccuracyJson; + ordering?: boolean; + nonce?: asn1js.IntegerJson; + tsa?: GeneralNameJson; + extensions?: ExtensionJson[]; +} + +export type TSTInfoParameters = PkiObjectParameters & Partial<ITSTInfo>; + +export interface TSTInfoVerifyParams { + data: ArrayBuffer; + notBefore?: Date; + notAfter?: Date; +} + +/** + * Represents the TSTInfo structure described in [RFC3161](https://www.ietf.org/rfc/rfc3161.txt) + */ +export class TSTInfo extends PkiObject implements ITSTInfo { + + public static override CLASS_NAME = "TSTInfo"; + + public version!: number; + public policy!: string; + public messageImprint!: MessageImprint; + public serialNumber!: asn1js.Integer; + public genTime!: Date; + public accuracy?: Accuracy; + public ordering?: boolean; + public nonce?: asn1js.Integer; + public tsa?: GeneralName; + public extensions?: Extension[]; + + /** + * Initializes a new instance of the {@link TSTInfo} class + * @param parameters Initialization parameters + */ + constructor(parameters: TSTInfoParameters = {}) { + super(); + + this.version = pvutils.getParametersValue(parameters, VERSION, TSTInfo.defaultValues(VERSION)); + this.policy = pvutils.getParametersValue(parameters, POLICY, TSTInfo.defaultValues(POLICY)); + this.messageImprint = pvutils.getParametersValue(parameters, MESSAGE_IMPRINT, TSTInfo.defaultValues(MESSAGE_IMPRINT)); + this.serialNumber = pvutils.getParametersValue(parameters, SERIAL_NUMBER, TSTInfo.defaultValues(SERIAL_NUMBER)); + this.genTime = pvutils.getParametersValue(parameters, GEN_TIME, TSTInfo.defaultValues(GEN_TIME)); + + if (ACCURACY in parameters) { + this.accuracy = pvutils.getParametersValue(parameters, ACCURACY, TSTInfo.defaultValues(ACCURACY)); + } + + if (ORDERING in parameters) { + this.ordering = pvutils.getParametersValue(parameters, ORDERING, TSTInfo.defaultValues(ORDERING)); + } + + if (NONCE in parameters) { + this.nonce = pvutils.getParametersValue(parameters, NONCE, TSTInfo.defaultValues(NONCE)); + } + + if (TSA in parameters) { + this.tsa = pvutils.getParametersValue(parameters, TSA, TSTInfo.defaultValues(TSA)); + } + + if (EXTENSIONS in parameters) { + this.extensions = pvutils.getParametersValue(parameters, EXTENSIONS, TSTInfo.defaultValues(EXTENSIONS)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof VERSION): number; + public static override defaultValues(memberName: typeof POLICY): string; + public static override defaultValues(memberName: typeof MESSAGE_IMPRINT): MessageImprint; + public static override defaultValues(memberName: typeof SERIAL_NUMBER): asn1js.Integer; + public static override defaultValues(memberName: typeof GEN_TIME): Date; + public static override defaultValues(memberName: typeof ACCURACY): Accuracy; + public static override defaultValues(memberName: typeof ORDERING): boolean; + public static override defaultValues(memberName: typeof NONCE): asn1js.Integer; + public static override defaultValues(memberName: typeof TSA): GeneralName; + public static override defaultValues(memberName: typeof EXTENSIONS): Extension[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case VERSION: + return 0; + case POLICY: + return EMPTY_STRING; + case MESSAGE_IMPRINT: + return new MessageImprint(); + case SERIAL_NUMBER: + return new asn1js.Integer(); + case GEN_TIME: + return new Date(0, 0, 0); + case ACCURACY: + return new Accuracy(); + case ORDERING: + return false; + case NONCE: + return new asn1js.Integer(); + case TSA: + return new GeneralName(); + case EXTENSIONS: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case VERSION: + case POLICY: + case GEN_TIME: + case ORDERING: + return (memberValue === TSTInfo.defaultValues(ORDERING)); + case MESSAGE_IMPRINT: + return ((MessageImprint.compareWithDefault(HASH_ALGORITHM, memberValue.hashAlgorithm)) && + (MessageImprint.compareWithDefault(HASHED_MESSAGE, memberValue.hashedMessage))); + case SERIAL_NUMBER: + case NONCE: + return (memberValue.isEqual(TSTInfo.defaultValues(NONCE))); + case ACCURACY: + return ((Accuracy.compareWithDefault(SECONDS, memberValue.seconds)) && + (Accuracy.compareWithDefault(MILLIS, memberValue.millis)) && + (Accuracy.compareWithDefault(MICROS, memberValue.micros))); + case TSA: + return ((GeneralName.compareWithDefault(TYPE, memberValue.type)) && + (GeneralName.compareWithDefault(VALUE, memberValue.value))); + case EXTENSIONS: + return (memberValue.length === 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * TSTInfo ::= SEQUENCE { + * version INTEGER { v1(1) }, + * policy TSAPolicyId, + * messageImprint MessageImprint, + * serialNumber INTEGER, + * genTime GeneralizedTime, + * accuracy Accuracy OPTIONAL, + * ordering BOOLEAN DEFAULT FALSE, + * nonce INTEGER OPTIONAL, + * tsa [0] GeneralName OPTIONAL, + * extensions [1] IMPLICIT Extensions OPTIONAL } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + version?: string; + policy?: string; + messageImprint?: MessageImprintSchema; + serialNumber?: string; + genTime?: string; + accuracy?: AccuracySchema; + ordering?: string; + nonce?: string; + tsa?: GeneralNameSchema; + extensions?: string; + extension?: ExtensionSchema; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || TST_INFO), + value: [ + new asn1js.Integer({ name: (names.version || TST_INFO_VERSION) }), + new asn1js.ObjectIdentifier({ name: (names.policy || TST_INFO_POLICY) }), + MessageImprint.schema(names.messageImprint || { + names: { + blockName: TST_INFO_MESSAGE_IMPRINT + } + }), + new asn1js.Integer({ name: (names.serialNumber || TST_INFO_SERIAL_NUMBER) }), + new asn1js.GeneralizedTime({ name: (names.genTime || TST_INFO_GEN_TIME) }), + Accuracy.schema(names.accuracy || { + names: { + blockName: TST_INFO_ACCURACY + } + }), + new asn1js.Boolean({ + name: (names.ordering || TST_INFO_ORDERING), + optional: true + }), + new asn1js.Integer({ + name: (names.nonce || TST_INFO_NONCE), + optional: true + }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [GeneralName.schema(names.tsa || { + names: { + blockName: TST_INFO_TSA + } + })] + }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: [ + new asn1js.Repeated({ + name: (names.extensions || TST_INFO_EXTENSIONS), + value: Extension.schema(names.extension || {}) + }) + ] + }) // IMPLICIT Extensions + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + TSTInfo.schema() + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.version = asn1.result[TST_INFO_VERSION].valueBlock.valueDec; + this.policy = asn1.result[TST_INFO_POLICY].valueBlock.toString(); + this.messageImprint = new MessageImprint({ schema: asn1.result[TST_INFO_MESSAGE_IMPRINT] }); + this.serialNumber = asn1.result[TST_INFO_SERIAL_NUMBER]; + this.genTime = asn1.result[TST_INFO_GEN_TIME].toDate(); + if (TST_INFO_ACCURACY in asn1.result) + this.accuracy = new Accuracy({ schema: asn1.result[TST_INFO_ACCURACY] }); + if (TST_INFO_ORDERING in asn1.result) + this.ordering = asn1.result[TST_INFO_ORDERING].valueBlock.value; + if (TST_INFO_NONCE in asn1.result) + this.nonce = asn1.result[TST_INFO_NONCE]; + if (TST_INFO_TSA in asn1.result) + this.tsa = new GeneralName({ schema: asn1.result[TST_INFO_TSA] }); + if (TST_INFO_EXTENSIONS in asn1.result) + this.extensions = Array.from(asn1.result[TST_INFO_EXTENSIONS], element => new Extension({ schema: element })); + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray = []; + + outputArray.push(new asn1js.Integer({ value: this.version })); + outputArray.push(new asn1js.ObjectIdentifier({ value: this.policy })); + outputArray.push(this.messageImprint.toSchema()); + outputArray.push(this.serialNumber); + outputArray.push(new asn1js.GeneralizedTime({ valueDate: this.genTime })); + if (this.accuracy) + outputArray.push(this.accuracy.toSchema()); + if (this.ordering !== undefined) + outputArray.push(new asn1js.Boolean({ value: this.ordering })); + if (this.nonce) + outputArray.push(this.nonce); + if (this.tsa) { + outputArray.push(new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [this.tsa.toSchema()] + })); + } + + //#region Create array of extensions + if (this.extensions) { + outputArray.push(new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 1 // [1] + }, + value: Array.from(this.extensions, o => o.toSchema()) + })); + } + //#endregion + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): TSTInfoJson { + const res: TSTInfoJson = { + version: this.version, + policy: this.policy, + messageImprint: this.messageImprint.toJSON(), + serialNumber: this.serialNumber.toJSON(), + genTime: this.genTime + }; + + if (this.accuracy) + res.accuracy = this.accuracy.toJSON(); + + if (this.ordering !== undefined) + res.ordering = this.ordering; + + if (this.nonce) + res.nonce = this.nonce.toJSON(); + + if (this.tsa) + res.tsa = this.tsa.toJSON(); + + if (this.extensions) + res.extensions = Array.from(this.extensions, o => o.toJSON()); + + return res; + } + + /** + * Verify current TST Info value + * @param params Input parameters + * @param crypto Crypto engine + */ + public async verify(params: TSTInfoVerifyParams, crypto = common.getCrypto(true)): Promise<boolean> { + + //#region Get initial parameters + if (!params.data) { + throw new Error("\"data\" is a mandatory attribute for TST_INFO verification"); + } + const data = params.data; + //#endregion + + //#region Check date + if (params.notBefore) { + if (this.genTime < params.notBefore) + throw new Error("Generation time for TSTInfo object is less than notBefore value"); + } + + if (params.notAfter) { + if (this.genTime > params.notAfter) + throw new Error("Generation time for TSTInfo object is more than notAfter value"); + } + //#endregion + + // Find hashing algorithm + const shaAlgorithm = crypto.getAlgorithmByOID(this.messageImprint.hashAlgorithm.algorithmId, true, "MessageImprint.hashAlgorithm"); + + // Calculate message digest for input "data" buffer + const hash = await crypto.digest(shaAlgorithm.name, new Uint8Array(data)); + return pvtsutils.BufferSourceConverter.isEqual(hash, this.messageImprint.hashedMessage.valueBlock.valueHexView); + } + +} + diff --git a/third_party/js/PKI.js/src/Time.ts b/third_party/js/PKI.js/src/Time.ts new file mode 100644 index 0000000000..347eb1f435 --- /dev/null +++ b/third_party/js/PKI.js/src/Time.ts @@ -0,0 +1,152 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { EMPTY_STRING } from "./constants"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as Schema from "./Schema"; + +const TYPE = "type"; +const VALUE = "value"; +const UTC_TIME_NAME = "utcTimeName"; +const GENERAL_TIME_NAME = "generalTimeName"; +const CLEAR_PROPS = [UTC_TIME_NAME, GENERAL_TIME_NAME]; + +export enum TimeType { + UTCTime, + GeneralizedTime, + empty, +} + +export interface ITime { + /** + * 0 - UTCTime; 1 - GeneralizedTime; 2 - empty value + */ + type: TimeType; + /** + * Value of the TIME class + */ + value: Date; +} + +export type TimeParameters = PkiObjectParameters & Partial<ITime>; + +export type TimeSchema = Schema.SchemaParameters<{ + utcTimeName?: string; + generalTimeName?: string; +}>; + +export interface TimeJson { + type: TimeType; + value: Date; +} + +/** + * Represents the Time structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class Time extends PkiObject implements ITime { + + public static override CLASS_NAME = "Time"; + + public type!: TimeType; + public value!: Date; + + /** + * Initializes a new instance of the {@link Time} class + * @param parameters Initialization parameters + */ + constructor(parameters: TimeParameters = {}) { + super(); + + this.type = pvutils.getParametersValue(parameters, TYPE, Time.defaultValues(TYPE)); + this.value = pvutils.getParametersValue(parameters, VALUE, Time.defaultValues(VALUE)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof TYPE): TimeType; + public static override defaultValues(memberName: typeof VALUE): Date; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case TYPE: + return 0; + case VALUE: + return new Date(0, 0, 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * Time ::= CHOICE { + * utcTime UTCTime, + * generalTime GeneralizedTime } + * ``` + * + * @param parameters Input parameters for the schema + * @param optional Flag that current schema should be optional + * @returns ASN.1 schema object + */ + static override schema(parameters: TimeSchema = {}, optional = false): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Choice({ + optional, + value: [ + new asn1js.UTCTime({ name: (names.utcTimeName || EMPTY_STRING) }), + new asn1js.GeneralizedTime({ name: (names.generalTimeName || EMPTY_STRING) }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, schema, Time.schema({ + names: { + utcTimeName: UTC_TIME_NAME, + generalTimeName: GENERAL_TIME_NAME + } + })); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + if (UTC_TIME_NAME in asn1.result) { + this.type = 0; + this.value = asn1.result.utcTimeName.toDate(); + } + if (GENERAL_TIME_NAME in asn1.result) { + this.type = 1; + this.value = asn1.result.generalTimeName.toDate(); + } + } + + public toSchema(): asn1js.UTCTime | asn1js.GeneralizedTime { + if (this.type === 0) { + return new asn1js.UTCTime({ valueDate: this.value }); + } else if (this.type === 1) { + return new asn1js.GeneralizedTime({ valueDate: this.value }); + } + + return {} as any; + } + + public toJSON(): TimeJson { + return { + type: this.type, + value: this.value + }; + } + +} diff --git a/third_party/js/PKI.js/src/TimeStampReq.ts b/third_party/js/PKI.js/src/TimeStampReq.ts new file mode 100644 index 0000000000..37497957e5 --- /dev/null +++ b/third_party/js/PKI.js/src/TimeStampReq.ts @@ -0,0 +1,331 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { MessageImprint, MessageImprintJson, MessageImprintSchema } from "./MessageImprint"; +import { Extension, ExtensionJson } from "./Extension"; +import * as Schema from "./Schema"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { AsnError } from "./errors"; +import { EMPTY_STRING } from "./constants"; + +const VERSION = "version"; +const MESSAGE_IMPRINT = "messageImprint"; +const REQ_POLICY = "reqPolicy"; +const NONCE = "nonce"; +const CERT_REQ = "certReq"; +const EXTENSIONS = "extensions"; +const TIME_STAMP_REQ = "TimeStampReq"; +const TIME_STAMP_REQ_VERSION = `${TIME_STAMP_REQ}.${VERSION}`; +const TIME_STAMP_REQ_MESSAGE_IMPRINT = `${TIME_STAMP_REQ}.${MESSAGE_IMPRINT}`; +const TIME_STAMP_REQ_POLICY = `${TIME_STAMP_REQ}.${REQ_POLICY}`; +const TIME_STAMP_REQ_NONCE = `${TIME_STAMP_REQ}.${NONCE}`; +const TIME_STAMP_REQ_CERT_REQ = `${TIME_STAMP_REQ}.${CERT_REQ}`; +const TIME_STAMP_REQ_EXTENSIONS = `${TIME_STAMP_REQ}.${EXTENSIONS}`; +const CLEAR_PROPS = [ + TIME_STAMP_REQ_VERSION, + TIME_STAMP_REQ_MESSAGE_IMPRINT, + TIME_STAMP_REQ_POLICY, + TIME_STAMP_REQ_NONCE, + TIME_STAMP_REQ_CERT_REQ, + TIME_STAMP_REQ_EXTENSIONS, +]; + +export interface ITimeStampReq { + /** + * Version of the Time-Stamp request. Should be version 1. + */ + version: number; + /** + * Contains the hash of the datum to be time-stamped + */ + messageImprint: MessageImprint; + /** + * Indicates the TSA policy under which the TimeStampToken SHOULD be provided. + */ + reqPolicy?: string; + /** + * The nonce, if included, allows the client to verify the timeliness of + * the response when no local clock is available. The nonce is a large + * random number with a high probability that the client generates it + * only once. + */ + nonce?: asn1js.Integer; + /** + * If the certReq field is present and set to true, the TSA's public key + * certificate that is referenced by the ESSCertID identifier inside a + * SigningCertificate attribute in the response MUST be provided by the + * TSA in the certificates field from the SignedData structure in that + * response. That field may also contain other certificates. + * + * If the certReq field is missing or if the certReq field is present + * and set to false then the certificates field from the SignedData + * structure MUST not be present in the response. + */ + certReq?: boolean; + /** + * The extensions field is a generic way to add additional information + * to the request in the future. + */ + extensions?: Extension[]; +} + +export interface TimeStampReqJson { + version: number; + messageImprint: MessageImprintJson; + reqPolicy?: string; + nonce?: asn1js.IntegerJson; + certReq?: boolean; + extensions?: ExtensionJson[]; +} + +export type TimeStampReqParameters = PkiObjectParameters & Partial<ITimeStampReq>; + +/** + * Represents the TimeStampReq structure described in [RFC3161](https://www.ietf.org/rfc/rfc3161.txt) + * + * @example The following example demonstrates how to create Time-Stamp Request + * ```js + * const nonce = pkijs.getRandomValues(new Uint8Array(10)).buffer; + * + * const tspReq = new pkijs.TimeStampReq({ + * version: 1, + * messageImprint: await pkijs.MessageImprint.create("SHA-256", message), + * reqPolicy: "1.2.3.4.5.6", + * certReq: true, + * nonce: new asn1js.Integer({ valueHex: nonce }), + * }); + * + * const tspReqRaw = tspReq.toSchema().toBER(); + */ +export class TimeStampReq extends PkiObject implements ITimeStampReq { + + public static override CLASS_NAME = "TimeStampReq"; + + public version!: number; + public messageImprint!: MessageImprint; + public reqPolicy?: string; + public nonce?: asn1js.Integer; + public certReq?: boolean; + public extensions?: Extension[]; + + /** + * Initializes a new instance of the {@link TimeStampReq} class + * @param parameters Initialization parameters + */ + constructor(parameters: TimeStampReqParameters = {}) { + super(); + + this.version = pvutils.getParametersValue(parameters, VERSION, TimeStampReq.defaultValues(VERSION)); + this.messageImprint = pvutils.getParametersValue(parameters, MESSAGE_IMPRINT, TimeStampReq.defaultValues(MESSAGE_IMPRINT)); + if (REQ_POLICY in parameters) { + this.reqPolicy = pvutils.getParametersValue(parameters, REQ_POLICY, TimeStampReq.defaultValues(REQ_POLICY)); + } + if (NONCE in parameters) { + this.nonce = pvutils.getParametersValue(parameters, NONCE, TimeStampReq.defaultValues(NONCE)); + } + if (CERT_REQ in parameters) { + this.certReq = pvutils.getParametersValue(parameters, CERT_REQ, TimeStampReq.defaultValues(CERT_REQ)); + } + if (EXTENSIONS in parameters) { + this.extensions = pvutils.getParametersValue(parameters, EXTENSIONS, TimeStampReq.defaultValues(EXTENSIONS)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof VERSION): number; + public static override defaultValues(memberName: typeof MESSAGE_IMPRINT): MessageImprint; + public static override defaultValues(memberName: typeof REQ_POLICY): string; + public static override defaultValues(memberName: typeof NONCE): asn1js.Integer; + public static override defaultValues(memberName: typeof CERT_REQ): boolean; + public static override defaultValues(memberName: typeof EXTENSIONS): Extension[]; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case VERSION: + return 0; + case MESSAGE_IMPRINT: + return new MessageImprint(); + case REQ_POLICY: + return EMPTY_STRING; + case NONCE: + return new asn1js.Integer(); + case CERT_REQ: + return false; + case EXTENSIONS: + return []; + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case VERSION: + case REQ_POLICY: + case CERT_REQ: + return (memberValue === TimeStampReq.defaultValues(memberName as typeof CERT_REQ)); + case MESSAGE_IMPRINT: + return ((MessageImprint.compareWithDefault("hashAlgorithm", memberValue.hashAlgorithm)) && + (MessageImprint.compareWithDefault("hashedMessage", memberValue.hashedMessage))); + case NONCE: + return (memberValue.isEqual(TimeStampReq.defaultValues(memberName))); + case EXTENSIONS: + return (memberValue.length === 0); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * TimeStampReq ::= SEQUENCE { + * version INTEGER { v1(1) }, + * messageImprint MessageImprint, + * reqPolicy TSAPolicyId OPTIONAL, + * nonce INTEGER OPTIONAL, + * certReq BOOLEAN DEFAULT FALSE, + * extensions [0] IMPLICIT Extensions OPTIONAL } + * + * TSAPolicyId ::= OBJECT IDENTIFIER + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + version?: string; + messageImprint?: MessageImprintSchema; + reqPolicy?: string; + nonce?: string; + certReq?: string; + extensions?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || TIME_STAMP_REQ), + value: [ + new asn1js.Integer({ name: (names.version || TIME_STAMP_REQ_VERSION) }), + MessageImprint.schema(names.messageImprint || { + names: { + blockName: TIME_STAMP_REQ_MESSAGE_IMPRINT + } + }), + new asn1js.ObjectIdentifier({ + name: (names.reqPolicy || TIME_STAMP_REQ_POLICY), + optional: true + }), + new asn1js.Integer({ + name: (names.nonce || TIME_STAMP_REQ_NONCE), + optional: true + }), + new asn1js.Boolean({ + name: (names.certReq || TIME_STAMP_REQ_CERT_REQ), + optional: true + }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [new asn1js.Repeated({ + name: (names.extensions || TIME_STAMP_REQ_EXTENSIONS), + value: Extension.schema() + })] + }) // IMPLICIT SEQUENCE value + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + TimeStampReq.schema() + ); + + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.version = asn1.result[TIME_STAMP_REQ_VERSION].valueBlock.valueDec; + this.messageImprint = new MessageImprint({ schema: asn1.result[TIME_STAMP_REQ_MESSAGE_IMPRINT] }); + if (TIME_STAMP_REQ_POLICY in asn1.result) + this.reqPolicy = asn1.result[TIME_STAMP_REQ_POLICY].valueBlock.toString(); + if (TIME_STAMP_REQ_NONCE in asn1.result) + this.nonce = asn1.result[TIME_STAMP_REQ_NONCE]; + if (TIME_STAMP_REQ_CERT_REQ in asn1.result) + this.certReq = asn1.result[TIME_STAMP_REQ_CERT_REQ].valueBlock.value; + if (TIME_STAMP_REQ_EXTENSIONS in asn1.result) + this.extensions = Array.from(asn1.result[TIME_STAMP_REQ_EXTENSIONS], element => new Extension({ schema: element })); + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray = []; + + outputArray.push(new asn1js.Integer({ value: this.version })); + outputArray.push(this.messageImprint.toSchema()); + if (this.reqPolicy) + outputArray.push(new asn1js.ObjectIdentifier({ value: this.reqPolicy })); + if (this.nonce) + outputArray.push(this.nonce); + if ((CERT_REQ in this) && (TimeStampReq.compareWithDefault(CERT_REQ, this.certReq) === false)) + outputArray.push(new asn1js.Boolean({ value: this.certReq })); + + //#region Create array of extensions + if (this.extensions) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: Array.from(this.extensions, o => o.toSchema()) + })); + } + //#endregion + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): TimeStampReqJson { + const res: TimeStampReqJson = { + version: this.version, + messageImprint: this.messageImprint.toJSON() + }; + + if (this.reqPolicy !== undefined) + res.reqPolicy = this.reqPolicy; + + if (this.nonce !== undefined) + res.nonce = this.nonce.toJSON(); + + if ((this.certReq !== undefined) && (TimeStampReq.compareWithDefault(CERT_REQ, this.certReq) === false)) + res.certReq = this.certReq; + + if (this.extensions) { + res.extensions = Array.from(this.extensions, o => o.toJSON()); + } + + return res; + } + +} diff --git a/third_party/js/PKI.js/src/TimeStampResp.ts b/third_party/js/PKI.js/src/TimeStampResp.ts new file mode 100644 index 0000000000..bb400f9f7a --- /dev/null +++ b/third_party/js/PKI.js/src/TimeStampResp.ts @@ -0,0 +1,286 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { PKIStatusInfo, PKIStatusInfoJson, PKIStatusInfoSchema } from "./PKIStatusInfo"; +import { ContentInfo, ContentInfoJson, ContentInfoSchema } from "./ContentInfo"; +import { SignedData } from "./SignedData"; +import * as Schema from "./Schema"; +import { id_ContentType_SignedData } from "./ObjectIdentifiers"; +import { Certificate } from "./Certificate"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { AsnError } from "./errors"; +import { EMPTY_BUFFER, EMPTY_STRING } from "./constants"; +import * as common from "./common"; + +const STATUS = "status"; +const TIME_STAMP_TOKEN = "timeStampToken"; +const TIME_STAMP_RESP = "TimeStampResp"; +const TIME_STAMP_RESP_STATUS = `${TIME_STAMP_RESP}.${STATUS}`; +const TIME_STAMP_RESP_TOKEN = `${TIME_STAMP_RESP}.${TIME_STAMP_TOKEN}`; +const CLEAR_PROPS = [ + TIME_STAMP_RESP_STATUS, + TIME_STAMP_RESP_TOKEN +]; + +export interface ITimeStampResp { + /** + * Time-Stamp status + */ + status: PKIStatusInfo; + /** + * Time-Stamp token + */ + timeStampToken?: ContentInfo; +} + +export interface TimeStampRespJson { + status: PKIStatusInfoJson; + timeStampToken?: ContentInfoJson; +} + +export interface TimeStampRespVerifyParams { + signer?: number; + trustedCerts?: Certificate[]; + data?: ArrayBuffer; +} + +export type TimeStampRespParameters = PkiObjectParameters & Partial<ITimeStampResp>; + +/** + * Represents the TimeStampResp structure described in [RFC3161](https://www.ietf.org/rfc/rfc3161.txt) + * + * @example The following example demonstrates how to create and sign Time-Stamp Response + * ```js + * // Generate random serial number + * const serialNumber = pkijs.getRandomValues(new Uint8Array(10)).buffer; + * + * // Create specific TST info structure to sign + * const tstInfo = new pkijs.TSTInfo({ + * version: 1, + * policy: tspReq.reqPolicy, + * messageImprint: tspReq.messageImprint, + * serialNumber: new asn1js.Integer({ valueHex: serialNumber }), + * genTime: new Date(), + * ordering: true, + * accuracy: new pkijs.Accuracy({ + * seconds: 1, + * millis: 1, + * micros: 10 + * }), + * nonce: tspReq.nonce, + * }); + * + * // Create and sign CMS Signed Data with TSTInfo + * const cmsSigned = new pkijs.SignedData({ + * version: 3, + * encapContentInfo: new pkijs.EncapsulatedContentInfo({ + * eContentType: "1.2.840.113549.1.9.16.1.4", // "tSTInfo" content type + * eContent: new asn1js.OctetString({ valueHex: tstInfo.toSchema().toBER() }), + * }), + * signerInfos: [ + * new pkijs.SignerInfo({ + * version: 1, + * sid: new pkijs.IssuerAndSerialNumber({ + * issuer: cert.issuer, + * serialNumber: cert.serialNumber + * }) + * }) + * ], + * certificates: [cert] + * }); + * + * await cmsSigned.sign(keys.privateKey, 0, "SHA-256"); + * + * // Create CMS Content Info + * const cmsContent = new pkijs.ContentInfo({ + * contentType: pkijs.ContentInfo.SIGNED_DATA, + * content: cmsSigned.toSchema(true) + * }); + * + * // Finally create completed TSP response structure + * const tspResp = new pkijs.TimeStampResp({ + * status: new pkijs.PKIStatusInfo({ status: pkijs.PKIStatus.granted }), + * timeStampToken: new pkijs.ContentInfo({ schema: cmsContent.toSchema() }) + * }); + * + * const tspRespRaw = tspResp.toSchema().toBER(); + * ``` + */ +export class TimeStampResp extends PkiObject implements ITimeStampResp { + + public static override CLASS_NAME = "TimeStampResp"; + + public status!: PKIStatusInfo; + public timeStampToken?: ContentInfo; + + /** + * Initializes a new instance of the {@link TimeStampResp} class + * @param parameters Initialization parameters + */ + constructor(parameters: TimeStampRespParameters = {}) { + super(); + + this.status = pvutils.getParametersValue(parameters, STATUS, TimeStampResp.defaultValues(STATUS)); + if (TIME_STAMP_TOKEN in parameters) { + this.timeStampToken = pvutils.getParametersValue(parameters, TIME_STAMP_TOKEN, TimeStampResp.defaultValues(TIME_STAMP_TOKEN)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof STATUS): PKIStatusInfo; + public static override defaultValues(memberName: typeof TIME_STAMP_TOKEN): ContentInfo; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case STATUS: + return new PKIStatusInfo(); + case TIME_STAMP_TOKEN: + return new ContentInfo(); + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case STATUS: + return ((PKIStatusInfo.compareWithDefault(STATUS, memberValue.status)) && + (("statusStrings" in memberValue) === false) && + (("failInfo" in memberValue) === false)); + case TIME_STAMP_TOKEN: + return ((memberValue.contentType === EMPTY_STRING) && + (memberValue.content instanceof asn1js.Any)); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * TimeStampResp ::= SEQUENCE { + * status PKIStatusInfo, + * timeStampToken TimeStampToken OPTIONAL } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + status?: PKIStatusInfoSchema, + timeStampToken?: ContentInfoSchema, + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || TIME_STAMP_RESP), + value: [ + PKIStatusInfo.schema(names.status || { + names: { + blockName: TIME_STAMP_RESP_STATUS + } + }), + ContentInfo.schema(names.timeStampToken || { + names: { + blockName: TIME_STAMP_RESP_TOKEN, + optional: true + } + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + TimeStampResp.schema() + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.status = new PKIStatusInfo({ schema: asn1.result[TIME_STAMP_RESP_STATUS] }); + if (TIME_STAMP_RESP_TOKEN in asn1.result) + this.timeStampToken = new ContentInfo({ schema: asn1.result[TIME_STAMP_RESP_TOKEN] }); + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray = []; + + outputArray.push(this.status.toSchema()); + if (this.timeStampToken) { + outputArray.push(this.timeStampToken.toSchema()); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): TimeStampRespJson { + const res: TimeStampRespJson = { + status: this.status.toJSON() + }; + + if (this.timeStampToken) { + res.timeStampToken = this.timeStampToken.toJSON(); + } + + return res; + } + + /** + * Sign current TSP Response + * @param privateKey Private key for "subjectPublicKeyInfo" structure + * @param hashAlgorithm Hashing algorithm. Default SHA-1 + * @param crypto Crypto engine + */ + public async sign(privateKey: CryptoKey, hashAlgorithm?: string, crypto = common.getCrypto(true)) { + this.assertContentType(); + + // Sign internal signed data value + const signed = new SignedData({ schema: this.timeStampToken.content }); + + return signed.sign(privateKey, 0, hashAlgorithm, undefined, crypto); + } + + /** + * Verify current TSP Response + * @param verificationParameters Input parameters for verification + * @param crypto Crypto engine + */ + public async verify(verificationParameters: TimeStampRespVerifyParams = { signer: 0, trustedCerts: [], data: EMPTY_BUFFER }, crypto = common.getCrypto(true)): Promise<boolean> { + this.assertContentType(); + + // Verify internal signed data value + const signed = new SignedData({ schema: this.timeStampToken.content }); + + return signed.verify(verificationParameters, crypto); + } + + private assertContentType(): asserts this is { timeStampToken: ContentInfo; } { + if (!this.timeStampToken) { + throw new Error("timeStampToken is absent in TSP response"); + } + if (this.timeStampToken.contentType !== id_ContentType_SignedData) { // Must be a CMS signed data + throw new Error(`Wrong format of timeStampToken: ${this.timeStampToken.contentType}`); + } + } +} + diff --git a/third_party/js/PKI.js/src/common.ts b/third_party/js/PKI.js/src/common.ts new file mode 100644 index 0000000000..e95772f4e7 --- /dev/null +++ b/third_party/js/PKI.js/src/common.ts @@ -0,0 +1,411 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { AlgorithmIdentifier } from "./AlgorithmIdentifier"; +import { EMPTY_BUFFER } from "./constants"; +import type { CryptoEngineAlgorithmOperation, CryptoEngineAlgorithmParams, ICryptoEngine } from "./CryptoEngine/CryptoEngineInterface"; +import { ArgumentError } from "./errors"; + +//#region Crypto engine related function +export { ICryptoEngine } from "./CryptoEngine/CryptoEngineInterface"; + +export interface GlobalCryptoEngine { + name: string; + crypto: ICryptoEngine | null; +} +export let engine: GlobalCryptoEngine = { + name: "none", + crypto: null, +}; + +function isCryptoEngine(engine: unknown): engine is ICryptoEngine { + return engine + && typeof engine === "object" + && "crypto" in engine + ? true + : false; +} + +/** + * Sets global crypto engine + * @param name Name of the crypto engine + * @param crypto + * @param subtle + * @deprecated Since version 3.0.0 + */ +export function setEngine(name: string, crypto: ICryptoEngine | Crypto, subtle: ICryptoEngine | SubtleCrypto): void; +/** + * Sets global crypto engine + * @param name Name of the crypto engine + * @param crypto Crypto engine. If the parameter is omitted, `CryptoEngine` with `self.crypto` are used + * @since 3.0.0 + */ +export function setEngine(name: string, crypto?: ICryptoEngine): void; +export function setEngine(name: string, ...args: any[]): void { + let crypto: ICryptoEngine | null = null; + if (args.length < 2) { + // v3.0.0 implementation + if (args.length) { + crypto = args[0]; + } else { + // crypto param is omitted, use CryptoEngine(self.crypto) + crypto = typeof self !== "undefined" && self.crypto ? new CryptoEngine({ name: "browser", crypto: self.crypto }) : null; + } + } else { + // prev implementation + const cryptoArg = args[0]; + const subtleArg = args[1]; + if (isCryptoEngine(subtleArg)) { + crypto = subtleArg; + } else if (isCryptoEngine(cryptoArg)) { + crypto = cryptoArg; + } else if ("subtle" in cryptoArg && "getRandomValues" in cryptoArg) { + crypto = new CryptoEngine({ + crypto: cryptoArg, + }); + } + } + + if ((typeof process !== "undefined") && ("pid" in process) && (typeof global !== "undefined") && (typeof window === "undefined")) { + // We are in Node + if (typeof (global as any)[process.pid] === "undefined") { + (global as any)[process.pid] = {}; + } + else { + if (typeof (global as any)[process.pid] !== "object") { + throw new Error(`Name global.${process.pid} already exists and it is not an object`); + } + } + + if (typeof (global as any)[process.pid].pkijs === "undefined") { + (global as any)[process.pid].pkijs = {}; + } + else { + if (typeof (global as any)[process.pid].pkijs !== "object") { + throw new Error(`Name global.${process.pid}.pkijs already exists and it is not an object`); + } + } + + (global as any)[process.pid].pkijs.engine = { + name: name, + crypto, + }; + } else { + // We are in browser + engine = { + name: name, + crypto, + }; + } +} + +export function getEngine(): GlobalCryptoEngine { + //#region We are in Node + if ((typeof process !== "undefined") && ("pid" in process) && (typeof global !== "undefined") && (typeof window === "undefined")) { + let _engine; + + try { + _engine = (global as any)[process.pid].pkijs.engine; + } + catch (ex) { + throw new Error("Please call 'setEngine' before call to 'getEngine'"); + } + + return _engine; + } + //#endregion + + return engine; +} +//#endregion + +//#region Declaration of common functions + +/** + * Gets crypto subtle from the current "crypto engine" + * @param safety + * @returns Reruns {@link ICryptoEngine} or `null` + */ +export function getCrypto(safety?: boolean): ICryptoEngine | null; +/** + * Gets crypto subtle from the current "crypto engine" + * @param safety + * @returns Reruns {@link ICryptoEngine} or throws en exception + * @throws Throws {@link Error} if `subtle` is empty + */ +export function getCrypto(safety: true): ICryptoEngine; +export function getCrypto(safety = false): ICryptoEngine | null { + const _engine = getEngine(); + + if (!_engine.crypto && safety) { + throw new Error("Unable to create WebCrypto object"); + } + + return _engine.crypto; +} + +/** + * Initialize input Uint8Array by random values (with help from current "crypto engine") + * @param view + */ +export function getRandomValues(view: Uint8Array) { + return getCrypto(true).getRandomValues(view); +} + +/** + * Get OID for each specific algorithm + * @param algorithm WebCrypto Algorithm + * @param safety if `true` throws exception on unknown algorithm + * @param target name of the target + * @throws Throws {@link Error} exception if unknown WebCrypto algorithm + */ +export function getOIDByAlgorithm(algorithm: Algorithm, safety?: boolean, target?: string) { + return getCrypto(true).getOIDByAlgorithm(algorithm, safety, target); +} + +/** + * Get default algorithm parameters for each kind of operation + * @param algorithmName Algorithm name to get common parameters for + * @param operation Kind of operation: "sign", "encrypt", "generateKey", "importKey", "exportKey", "verify" + */ +// TODO Add safety +export function getAlgorithmParameters(algorithmName: string, operation: CryptoEngineAlgorithmOperation): CryptoEngineAlgorithmParams { + return getCrypto(true).getAlgorithmParameters(algorithmName, operation); +} + +/** + * Create CMS ECDSA signature from WebCrypto ECDSA signature + * @param signatureBuffer WebCrypto result of "sign" function + */ +export function createCMSECDSASignature(signatureBuffer: ArrayBuffer): ArrayBuffer { + //#region Initial check for correct length + if ((signatureBuffer.byteLength % 2) !== 0) + return EMPTY_BUFFER; + //#endregion + + //#region Initial variables + const length = signatureBuffer.byteLength / 2; // There are two equal parts inside incoming ArrayBuffer + + const rBuffer = new ArrayBuffer(length); + const rView = new Uint8Array(rBuffer); + rView.set(new Uint8Array(signatureBuffer, 0, length)); + + const rInteger = new asn1js.Integer({ valueHex: rBuffer }); + + const sBuffer = new ArrayBuffer(length); + const sView = new Uint8Array(sBuffer); + sView.set(new Uint8Array(signatureBuffer, length, length)); + + const sInteger = new asn1js.Integer({ valueHex: sBuffer }); + //#endregion + + return (new asn1js.Sequence({ + value: [ + rInteger.convertToDER(), + sInteger.convertToDER() + ] + })).toBER(false); +} + +/** + * Create a single ArrayBuffer from CMS ECDSA signature + * @param cmsSignature ASN.1 SEQUENCE contains CMS ECDSA signature + * @param pointSize Size of EC point. Use {@link ECNamedCurves.find} to get correct point size + * @returns WebCrypto signature + */ +export function createECDSASignatureFromCMS(cmsSignature: asn1js.AsnType, pointSize: number): ArrayBuffer { + // Check input variables + if (!(cmsSignature instanceof asn1js.Sequence + && cmsSignature.valueBlock.value.length === 2 + && cmsSignature.valueBlock.value[0] instanceof asn1js.Integer + && cmsSignature.valueBlock.value[1] instanceof asn1js.Integer)) + return EMPTY_BUFFER; + + const rValueView = cmsSignature.valueBlock.value[0].convertFromDER().valueBlock.valueHexView; + const sValueView = cmsSignature.valueBlock.value[1].convertFromDER().valueBlock.valueHexView; + + const res = new Uint8Array(pointSize * 2); + + res.set(rValueView, pointSize - rValueView.byteLength); + res.set(sValueView, (2 * pointSize) - sValueView.byteLength); + + return res.buffer; +} + +/** + * Gets WebCrypto algorithm by well-known OID + * @param oid algorithm identifier + * @param safety if true throws exception on unknown algorithm identifier + * @param target name of the target + * @returns WebCrypto algorithm or an empty object + */ +export function getAlgorithmByOID<T extends Algorithm = Algorithm>(oid: string, safety?: boolean, target?: string): T | object; +export function getAlgorithmByOID<T extends Algorithm = Algorithm>(oid: string, safety: true, target?: string): T; +export function getAlgorithmByOID(oid: string, safety = false, target?: string): any { + return getCrypto(true).getAlgorithmByOID(oid, safety, target); +} + +/** + * Getting hash algorithm by signature algorithm + * @param signatureAlgorithm Signature algorithm + */ +export function getHashAlgorithm(signatureAlgorithm: AlgorithmIdentifier): string { + return getCrypto(true).getHashAlgorithm(signatureAlgorithm); +} + +/** + * ANS X9.63 Key Derivation Function having a "Counter" as a parameter + * @param hashFunction Used hash function + * @param zBuffer ArrayBuffer containing ECDH shared secret to derive from + * @param Counter + * @param SharedInfo Usually DER encoded "ECC_CMS_SharedInfo" structure + * @param crypto Crypto engine + */ +async function kdfWithCounter(hashFunction: string, zBuffer: ArrayBuffer, Counter: number, SharedInfo: ArrayBuffer, crypto: ICryptoEngine): Promise<{ counter: number; result: ArrayBuffer; }> { + //#region Check of input parameters + switch (hashFunction.toUpperCase()) { + case "SHA-1": + case "SHA-256": + case "SHA-384": + case "SHA-512": + break; + default: + throw new ArgumentError(`Unknown hash function: ${hashFunction}`); + } + + ArgumentError.assert(zBuffer, "zBuffer", "ArrayBuffer"); + if (zBuffer.byteLength === 0) + throw new ArgumentError("'zBuffer' has zero length, error"); + + ArgumentError.assert(SharedInfo, "SharedInfo", "ArrayBuffer"); + if (Counter > 255) + throw new ArgumentError("Please set 'Counter' argument to value less or equal to 255"); + //#endregion + + //#region Initial variables + const counterBuffer = new ArrayBuffer(4); + const counterView = new Uint8Array(counterBuffer); + counterView[0] = 0x00; + counterView[1] = 0x00; + counterView[2] = 0x00; + counterView[3] = Counter; + + let combinedBuffer = EMPTY_BUFFER; + //#endregion + + //#region Create a combined ArrayBuffer for digesting + combinedBuffer = pvutils.utilConcatBuf(combinedBuffer, zBuffer); + combinedBuffer = pvutils.utilConcatBuf(combinedBuffer, counterBuffer); + combinedBuffer = pvutils.utilConcatBuf(combinedBuffer, SharedInfo); + //#endregion + + //#region Return digest of combined ArrayBuffer and information about current counter + const result = await crypto.digest( + { name: hashFunction }, + combinedBuffer); + + return { + counter: Counter, + result + }; + //#endregion +} + +/** + * ANS X9.63 Key Derivation Function + * @param hashFunction Used hash function + * @param Zbuffer ArrayBuffer containing ECDH shared secret to derive from + * @param keydatalen Length (!!! in BITS !!!) of used kew derivation function + * @param SharedInfo Usually DER encoded "ECC_CMS_SharedInfo" structure + * @param crypto Crypto engine + */ +export async function kdf(hashFunction: string, Zbuffer: ArrayBuffer, keydatalen: number, SharedInfo: ArrayBuffer, crypto = getCrypto(true)) { + //#region Initial variables + let hashLength = 0; + let maxCounter = 1; + //#endregion + + //#region Check of input parameters + switch (hashFunction.toUpperCase()) { + case "SHA-1": + hashLength = 160; // In bits + break; + case "SHA-256": + hashLength = 256; // In bits + break; + case "SHA-384": + hashLength = 384; // In bits + break; + case "SHA-512": + hashLength = 512; // In bits + break; + default: + throw new ArgumentError(`Unknown hash function: ${hashFunction}`); + } + + ArgumentError.assert(Zbuffer, "Zbuffer", "ArrayBuffer"); + if (Zbuffer.byteLength === 0) + throw new ArgumentError("'Zbuffer' has zero length, error"); + ArgumentError.assert(SharedInfo, "SharedInfo", "ArrayBuffer"); + //#endregion + + //#region Calculated maximum value of "Counter" variable + const quotient = keydatalen / hashLength; + + if (Math.floor(quotient) > 0) { + maxCounter = Math.floor(quotient); + + if ((quotient - maxCounter) > 0) + maxCounter++; + } + //#endregion + + //#region Create an array of "kdfWithCounter" + const incomingResult = []; + for (let i = 1; i <= maxCounter; i++) + incomingResult.push(await kdfWithCounter(hashFunction, Zbuffer, i, SharedInfo, crypto)); + //#endregion + + //#region Return combined digest with specified length + //#region Initial variables + let combinedBuffer = EMPTY_BUFFER; + let currentCounter = 1; + let found = true; + //#endregion + + //#region Combine all buffer together + while (found) { + found = false; + + for (const result of incomingResult) { + if (result.counter === currentCounter) { + combinedBuffer = pvutils.utilConcatBuf(combinedBuffer, result.result); + found = true; + break; + } + } + + currentCounter++; + } + //#endregion + + //#region Create output buffer with specified length + keydatalen >>= 3; // Divide by 8 since "keydatalen" is in bits + + if (combinedBuffer.byteLength > keydatalen) { + const newBuffer = new ArrayBuffer(keydatalen); + const newView = new Uint8Array(newBuffer); + const combinedView = new Uint8Array(combinedBuffer); + + for (let i = 0; i < keydatalen; i++) + newView[i] = combinedView[i]; + + return newBuffer; + } + + return combinedBuffer; // Since the situation when "combinedBuffer.byteLength < keydatalen" here we have only "combinedBuffer.byteLength === keydatalen" + //#endregion + //#endregion +} +//#endregion + +import { CryptoEngine } from "./CryptoEngine/CryptoEngine";
\ No newline at end of file diff --git a/third_party/js/PKI.js/src/constants.ts b/third_party/js/PKI.js/src/constants.ts new file mode 100644 index 0000000000..7107c3aef6 --- /dev/null +++ b/third_party/js/PKI.js/src/constants.ts @@ -0,0 +1,3 @@ +export const EMPTY_BUFFER = new ArrayBuffer(0); +export const EMPTY_VIEW = new Uint8Array(0); +export const EMPTY_STRING = "";
\ No newline at end of file diff --git a/third_party/js/PKI.js/src/errors/ArgumentError.ts b/third_party/js/PKI.js/src/errors/ArgumentError.ts new file mode 100644 index 0000000000..c3852f6b93 --- /dev/null +++ b/third_party/js/PKI.js/src/errors/ArgumentError.ts @@ -0,0 +1,76 @@ + +export interface AnyConstructor { + new(args: any): any; +} + +export type ArgumentType = + | "undefined" + | "null" + | "boolean" + | "number" + | "string" + | "object" + | "Array" + | "ArrayBuffer" + | "ArrayBufferView" + | AnyConstructor; + +export class ArgumentError extends TypeError { + + public static readonly NAME = "ArgumentError"; + + public static isType(value: any, type: "undefined"): value is undefined; + public static isType(value: any, type: "null"): value is null; + public static isType(value: any, type: "boolean"): value is boolean; + public static isType(value: any, type: "number"): value is number; + public static isType(value: any, type: "object"): value is object; + public static isType(value: any, type: "string"): value is string; + public static isType(value: any, type: "Array"): value is any[]; + public static isType(value: any, type: "ArrayBuffer"): value is ArrayBuffer; + public static isType(value: any, type: "ArrayBufferView"): value is ArrayBufferView; + public static isType<T>(value: any, type: new (...args: any[]) => T): value is T; + // @internal + public static isType(value: any, type: ArgumentType): boolean; + public static isType(value: any, type: ArgumentType): boolean { + if (typeof type === "string") { + if (type === "Array" && Array.isArray(value)) { + return true; + } else if (type === "ArrayBuffer" && value instanceof ArrayBuffer) { + return true; + } else if (type === "ArrayBufferView" && ArrayBuffer.isView(value)) { + return true; + } else if (typeof value === type) { + return true; + } + } else if (value instanceof type) { + return true; + } + + return false; + } + + public static assert(value: any, name: string, type: "undefined"): asserts value is undefined; + public static assert(value: any, name: string, type: "null"): asserts value is null; + public static assert(value: any, name: string, type: "boolean"): asserts value is boolean; + public static assert(value: any, name: string, type: "number"): asserts value is number; + public static assert(value: any, name: string, type: "object"): asserts value is { [key: string]: any; }; + public static assert(value: any, name: string, type: "string"): asserts value is string; + public static assert(value: any, name: string, type: "Array"): asserts value is any[]; + public static assert(value: any, name: string, type: "ArrayBuffer"): asserts value is ArrayBuffer; + public static assert(value: any, name: string, type: "ArrayBufferView"): asserts value is ArrayBufferView; + public static assert<T>(value: any, name: string, type: new (...args: any[]) => T): asserts value is T; + public static assert(value: any, name: string, type: ArgumentType, ...types: ArgumentType[]): void; + public static assert(value: any, name: string, ...types: ArgumentType[]): void { + for (const type of types) { + if (this.isType(value, type)) { + return; + } + } + + const typeNames = types.map(o => o instanceof Function && "name" in o ? o.name : `${o}`); + throw new ArgumentError(`Parameter '${name}' is not of type ${typeNames.length > 1 ? `(${typeNames.join(" or ")})` : typeNames[0]}`); + } + + public override name: typeof ArgumentError.NAME = ArgumentError.NAME; + +} diff --git a/third_party/js/PKI.js/src/errors/AsnError.ts b/third_party/js/PKI.js/src/errors/AsnError.ts new file mode 100644 index 0000000000..60f159695b --- /dev/null +++ b/third_party/js/PKI.js/src/errors/AsnError.ts @@ -0,0 +1,31 @@ +export interface AsnFromBerResult { + offset: number; + result: any; +} + +export interface AsnCompareSchemaResult { + verified: boolean; + result?: any; +} + +export class AsnError extends Error { + + static assertSchema(asn1: AsnCompareSchemaResult, target: string): asserts asn1 is { verified: true, result: any; } { + if (!asn1.verified) { + throw new Error(`Object's schema was not verified against input data for ${target}`); + } + } + + public static assert(asn: AsnFromBerResult, target: string): void { + if (asn.offset === -1) { + throw new AsnError(`Error during parsing of ASN.1 data. Data is not correct for '${target}'.`); + } + } + + constructor(message: string) { + super(message); + + this.name = "AsnError"; + } + +}
\ No newline at end of file diff --git a/third_party/js/PKI.js/src/errors/ParameterError.ts b/third_party/js/PKI.js/src/errors/ParameterError.ts new file mode 100644 index 0000000000..cf32fbd56a --- /dev/null +++ b/third_party/js/PKI.js/src/errors/ParameterError.ts @@ -0,0 +1,57 @@ +import { EMPTY_STRING } from "../constants"; +import { ArgumentError } from "./ArgumentError"; + +export class ParameterError extends TypeError { + + public static readonly NAME = "ParameterError"; + + public static assert(target: string, params: any, ...fields: string[]): void; + public static assert(params: any, ...fields: string[]): void; + public static assert(...args: any[]): void { + let target: string | null = null; + let params: any; + let fields: string[]; + if (typeof args[0] === "string") { + target = args[0]; + params = args[1]; + fields = args.slice(2); + } else { + params = args[0]; + fields = args.slice(1); + } + ArgumentError.assert(params, "parameters", "object"); + for (const field of fields) { + const value = params[field]; + if (value === undefined || value === null) { + throw new ParameterError(field, target); + } + } + } + + public static assertEmpty(value: unknown, name: string, target?: string): asserts value { + if (value === undefined || value === null) { + throw new ParameterError(name, target); + } + } + + public override name: typeof ParameterError.NAME = ParameterError.NAME; + + public field: string; + public target?: string; + + constructor(field: string, target: string | null = null, message?: string) { + super(); + + this.field = field; + if (target) { + this.target = target; + } + + if (message) { + this.message = message; + } else { + this.message = `Absent mandatory parameter '${field}' ${target ? ` in '${target}'` : EMPTY_STRING}`; + } + } + +}
\ No newline at end of file diff --git a/third_party/js/PKI.js/src/errors/index.ts b/third_party/js/PKI.js/src/errors/index.ts new file mode 100644 index 0000000000..2e40b3f8f7 --- /dev/null +++ b/third_party/js/PKI.js/src/errors/index.ts @@ -0,0 +1,3 @@ +export * from "./ParameterError"; +export * from "./ArgumentError"; +export * from "./AsnError"; diff --git a/third_party/js/PKI.js/src/index.ts b/third_party/js/PKI.js/src/index.ts new file mode 100644 index 0000000000..5d1838d2b3 --- /dev/null +++ b/third_party/js/PKI.js/src/index.ts @@ -0,0 +1,124 @@ +export * from "./PkiObject"; +export * from "./AccessDescription"; +export * from "./Accuracy"; +export * from "./AlgorithmIdentifier"; +export * from "./AltName"; +export * from "./Attribute"; +export * from "./AttributeCertificateV1"; +export * from "./AttributeCertificateV2"; +export * from "./AttributeTypeAndValue"; +export * from "./AuthenticatedSafe"; +export * from "./AuthorityKeyIdentifier"; +export * from "./BasicConstraints"; +export * from "./BasicOCSPResponse"; +export * from "./CAVersion"; +export * from "./CRLBag"; +export * from "./CRLDistributionPoints"; +export * from "./CertBag"; +export * from "./CertID"; +export * from "./Certificate"; +export * from "./CertificateChainValidationEngine"; +export * from "./CertificatePolicies"; +export * from "./CertificateRevocationList"; +export * from "./CertificateSet"; +export * from "./CertificateTemplate"; +export * from "./CertificationRequest"; +export * from "./ContentInfo"; +export * from "./CryptoEngine/AbstractCryptoEngine"; +export * from "./CryptoEngine/CryptoEngine"; +export * from "./CryptoEngine/CryptoEngineInterface"; +export * from "./DigestInfo"; +export * from "./DistributionPoint"; +export * from "./ECCCMSSharedInfo"; +export * from "./ECNamedCurves"; +export * from "./ECPrivateKey"; +export * from "./ECPublicKey"; +export * from "./EncapsulatedContentInfo"; +export * from "./EncryptedContentInfo"; +export * from "./EncryptedData"; +export * from "./EnvelopedData"; +export * from "./ExtKeyUsage"; +export * from "./Extension"; +export * from "./ExtensionValueFactory"; +export * from "./Extensions"; +export * from "./GeneralName"; +export * from "./GeneralNames"; +export * from "./GeneralSubtree"; +export * from "./Helpers"; +export * from "./InfoAccess"; +export * from "./IssuerAndSerialNumber"; +export * from "./IssuingDistributionPoint"; +export * from "./KEKIdentifier"; +export * from "./KEKRecipientInfo"; +export * from "./KeyAgreeRecipientIdentifier"; +export * from "./KeyAgreeRecipientInfo"; +export * from "./KeyBag"; +export * from "./KeyTransRecipientInfo"; +export * from "./MacData"; +export * from "./MessageImprint"; +export * from "./NameConstraints"; +export * from "./OCSPRequest"; +export * from "./OCSPResponse"; +export * from "./ObjectIdentifiers"; +export * from "./OriginatorIdentifierOrKey"; +export * from "./OriginatorInfo"; +export * from "./OriginatorPublicKey"; +export * from "./OtherCertificateFormat"; +export * from "./OtherKeyAttribute"; +export * from "./OtherPrimeInfo"; +export * from "./OtherRecipientInfo"; +export * from "./OtherRevocationInfoFormat"; +export * from "./PBES2Params"; +export * from "./PBKDF2Params"; +export * from "./PFX"; +export * from "./PKCS8ShroudedKeyBag"; +export * from "./PKIStatusInfo"; +export * from "./PasswordRecipientinfo"; +export * from "./PolicyConstraints"; +export * from "./PolicyInformation"; +export * from "./PolicyMapping"; +export * from "./PolicyMappings"; +export * from "./PolicyQualifierInfo"; +export * from "./PrivateKeyInfo"; +export * from "./PrivateKeyUsagePeriod"; +export * from "./PublicKeyInfo"; +export * from "./QCStatements"; +export * from "./RSAESOAEPParams"; +export * from "./RSAPrivateKey"; +export * from "./RSAPublicKey"; +export * from "./RSASSAPSSParams"; +export * from "./RecipientEncryptedKey"; +export * from "./RecipientEncryptedKeys"; +export * from "./RecipientIdentifier"; +export * from "./RecipientInfo"; +export * from "./RecipientKeyIdentifier"; +export * from "./RelativeDistinguishedNames"; +export * from "./Request"; +export * from "./ResponseBytes"; +export * from "./ResponseData"; +export * from "./RevocationInfoChoices"; +export * from "./RevokedCertificate"; +export * from "./SafeBag"; +export * from "./SafeBagValueFactory"; +export * from "./SafeContents"; +export * from "./Schema"; +export * from "./SecretBag"; +export * from "./Signature"; +export * from "./SignedAndUnsignedAttributes"; +export * from "./SignedCertificateTimestamp"; +export * from "./SignedCertificateTimestampList"; +export * from "./SignedData"; +export * from "./SignerInfo"; +export * from "./SingleResponse"; +export * from "./SubjectDirectoryAttributes"; +export * from "./TBSRequest"; +export * from "./TSTInfo"; +export * from "./Time"; +export * from "./TimeStampReq"; +export * from "./TimeStampResp"; +export * from "./common"; +export * from "./errors"; + +import { initCryptoEngine } from "./CryptoEngine/CryptoEngineInit"; + +initCryptoEngine();
\ No newline at end of file diff --git a/third_party/js/PKI.js/tsconfig.json b/third_party/js/PKI.js/tsconfig.json new file mode 100644 index 0000000000..e72bdb66f9 --- /dev/null +++ b/third_party/js/PKI.js/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES6", + "module": "ES6", + "moduleResolution": "node", + "strict": true, + "importHelpers": true, + "noImplicitOverride": true, + "noErrorTruncation": true, + "experimentalDecorators": true + }, + "exclude": [ + "build/*.ts" + ] +} diff --git a/third_party/js/PKI.js/yarn.lock b/third_party/js/PKI.js/yarn.lock new file mode 100644 index 0000000000..d7a4989bbd --- /dev/null +++ b/third_party/js/PKI.js/yarn.lock @@ -0,0 +1,2877 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.1.0": + "integrity" "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==" + "resolved" "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz" + "version" "2.1.2" + dependencies: + "@jridgewell/trace-mapping" "^0.3.0" + +"@babel/code-frame@^7.16.7": + "integrity" "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==" + "resolved" "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz" + "version" "7.16.7" + dependencies: + "@babel/highlight" "^7.16.7" + +"@babel/compat-data@^7.17.7": + "integrity" "sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==" + "resolved" "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz" + "version" "7.17.7" + +"@babel/core@^7.0.0", "@babel/core@^7.7.5": + "integrity" "sha512-djHlEfFHnSnTAcPb7dATbiM5HxGOP98+3JLBZtjRb5I7RXrw7kFRoG2dXM8cm3H+o11A8IFH/uprmJpwFynRNQ==" + "resolved" "https://registry.npmjs.org/@babel/core/-/core-7.17.7.tgz" + "version" "7.17.7" + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.17.7" + "@babel/helper-compilation-targets" "^7.17.7" + "@babel/helper-module-transforms" "^7.17.7" + "@babel/helpers" "^7.17.7" + "@babel/parser" "^7.17.7" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.3" + "@babel/types" "^7.17.0" + "convert-source-map" "^1.7.0" + "debug" "^4.1.0" + "gensync" "^1.0.0-beta.2" + "json5" "^2.1.2" + "semver" "^6.3.0" + +"@babel/generator@^7.17.3", "@babel/generator@^7.17.7": + "integrity" "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==" + "resolved" "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz" + "version" "7.17.7" + dependencies: + "@babel/types" "^7.17.0" + "jsesc" "^2.5.1" + "source-map" "^0.5.0" + +"@babel/helper-compilation-targets@^7.17.7": + "integrity" "sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==" + "resolved" "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz" + "version" "7.17.7" + dependencies: + "@babel/compat-data" "^7.17.7" + "@babel/helper-validator-option" "^7.16.7" + "browserslist" "^4.17.5" + "semver" "^6.3.0" + +"@babel/helper-environment-visitor@^7.16.7": + "integrity" "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==" + "resolved" "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz" + "version" "7.16.7" + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-function-name@^7.16.7": + "integrity" "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==" + "resolved" "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz" + "version" "7.16.7" + dependencies: + "@babel/helper-get-function-arity" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/types" "^7.16.7" + +"@babel/helper-get-function-arity@^7.16.7": + "integrity" "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==" + "resolved" "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz" + "version" "7.16.7" + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-hoist-variables@^7.16.7": + "integrity" "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==" + "resolved" "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz" + "version" "7.16.7" + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-module-imports@^7.16.7": + "integrity" "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==" + "resolved" "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz" + "version" "7.16.7" + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-module-transforms@^7.17.7": + "integrity" "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==" + "resolved" "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz" + "version" "7.17.7" + dependencies: + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-simple-access" "^7.17.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/helper-validator-identifier" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.3" + "@babel/types" "^7.17.0" + +"@babel/helper-simple-access@^7.17.7": + "integrity" "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==" + "resolved" "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz" + "version" "7.17.7" + dependencies: + "@babel/types" "^7.17.0" + +"@babel/helper-split-export-declaration@^7.16.7": + "integrity" "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==" + "resolved" "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz" + "version" "7.16.7" + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-validator-identifier@^7.16.7": + "integrity" "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==" + "resolved" "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz" + "version" "7.16.7" + +"@babel/helper-validator-option@^7.16.7": + "integrity" "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==" + "resolved" "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz" + "version" "7.16.7" + +"@babel/helpers@^7.17.7": + "integrity" "sha512-TKsj9NkjJfTBxM7Phfy7kv6yYc4ZcOo+AaWGqQOKTPDOmcGkIFb5xNA746eKisQkm4yavUYh4InYM9S+VnO01w==" + "resolved" "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.7.tgz" + "version" "7.17.7" + dependencies: + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.3" + "@babel/types" "^7.17.0" + +"@babel/highlight@^7.16.7": + "integrity" "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==" + "resolved" "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz" + "version" "7.16.10" + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + "chalk" "^2.0.0" + "js-tokens" "^4.0.0" + +"@babel/parser@^7.16.7", "@babel/parser@^7.17.3", "@babel/parser@^7.17.7": + "integrity" "sha512-bm3AQf45vR4gKggRfvJdYJ0gFLoCbsPxiFLSH6hTVYABptNHY6l9NrhnucVjQ/X+SPtLANT9lc0fFhikj+VBRA==" + "resolved" "https://registry.npmjs.org/@babel/parser/-/parser-7.17.7.tgz" + "version" "7.17.7" + +"@babel/template@^7.16.7": + "integrity" "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==" + "resolved" "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz" + "version" "7.16.7" + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/parser" "^7.16.7" + "@babel/types" "^7.16.7" + +"@babel/traverse@^7.17.3": + "integrity" "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==" + "resolved" "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz" + "version" "7.17.3" + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.17.3" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-function-name" "^7.16.7" + "@babel/helper-hoist-variables" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/parser" "^7.17.3" + "@babel/types" "^7.17.0" + "debug" "^4.1.0" + "globals" "^11.1.0" + +"@babel/types@^7.16.7", "@babel/types@^7.17.0": + "integrity" "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==" + "resolved" "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz" + "version" "7.17.0" + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + "to-fast-properties" "^2.0.0" + +"@cspotcode/source-map-support@^0.8.0": + "integrity" "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==" + "resolved" "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz" + "version" "0.8.1" + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@eslint/eslintrc@^1.3.0": + "integrity" "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==" + "resolved" "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz" + "version" "1.3.0" + dependencies: + "ajv" "^6.12.4" + "debug" "^4.3.2" + "espree" "^9.3.2" + "globals" "^13.15.0" + "ignore" "^5.2.0" + "import-fresh" "^3.2.1" + "js-yaml" "^4.1.0" + "minimatch" "^3.1.2" + "strip-json-comments" "^3.1.1" + +"@humanwhocodes/config-array@^0.10.4": + "integrity" "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==" + "resolved" "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz" + "version" "0.10.4" + dependencies: + "@humanwhocodes/object-schema" "^1.2.1" + "debug" "^4.1.1" + "minimatch" "^3.0.4" + +"@humanwhocodes/gitignore-to-minimatch@^1.0.2": + "integrity" "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==" + "resolved" "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz" + "version" "1.0.2" + +"@humanwhocodes/object-schema@^1.2.1": + "integrity" "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" + "resolved" "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz" + "version" "1.2.1" + +"@istanbuljs/load-nyc-config@^1.0.0": + "integrity" "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==" + "resolved" "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz" + "version" "1.1.0" + dependencies: + "camelcase" "^5.3.1" + "find-up" "^4.1.0" + "get-package-type" "^0.1.0" + "js-yaml" "^3.13.1" + "resolve-from" "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + "integrity" "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==" + "resolved" "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz" + "version" "0.1.3" + +"@jridgewell/resolve-uri@^3.0.3": + "integrity" "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==" + "resolved" "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz" + "version" "3.0.5" + +"@jridgewell/sourcemap-codec@^1.4.10": + "integrity" "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==" + "resolved" "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz" + "version" "1.4.11" + +"@jridgewell/trace-mapping@^0.3.0", "@jridgewell/trace-mapping@0.3.9": + "integrity" "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==" + "resolved" "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" + "version" "0.3.9" + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@nodelib/fs.scandir@2.1.5": + "integrity" "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==" + "resolved" "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" + "version" "2.1.5" + dependencies: + "@nodelib/fs.stat" "2.0.5" + "run-parallel" "^1.1.9" + +"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": + "integrity" "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" + "resolved" "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" + "version" "2.0.5" + +"@nodelib/fs.walk@^1.2.3": + "integrity" "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==" + "resolved" "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" + "version" "1.2.8" + dependencies: + "@nodelib/fs.scandir" "2.1.5" + "fastq" "^1.6.0" + +"@peculiar/asn1-schema@^2.1.6": + "integrity" "sha512-6ewdxD32vsjxbI/MvTef7PQxZIfLyV4w/5XzjOkosIGNt726zj8evLitCbjTeMHSZ/VeU4ZFSnrPXdCBHI6NWQ==" + "resolved" "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.1.6.tgz" + "version" "2.1.6" + dependencies: + "asn1js" "^3.0.0" + "pvtsutils" "^1.3.2" + "tslib" "^2.4.0" + +"@peculiar/json-schema@^1.1.12": + "integrity" "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==" + "resolved" "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz" + "version" "1.1.12" + dependencies: + "tslib" "^2.0.0" + +"@peculiar/webcrypto@^1.4.0": + "integrity" "sha512-U58N44b2m3OuTgpmKgf0LPDOmP3bhwNz01vAnj1mBwxBASRhptWYK+M3zG+HBkDqGQM+bFsoIihTW8MdmPXEqg==" + "resolved" "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.4.0.tgz" + "version" "1.4.0" + dependencies: + "@peculiar/asn1-schema" "^2.1.6" + "@peculiar/json-schema" "^1.1.12" + "pvtsutils" "^1.3.2" + "tslib" "^2.4.0" + "webcrypto-core" "^1.7.4" + +"@rollup/plugin-alias@^3.1.9": + "integrity" "sha512-QI5fsEvm9bDzt32k39wpOwZhVzRcL5ydcffUHMyLVaVaLeC70I8TJZ17F1z1eMoLu4E/UOcH9BWVkKpIKdrfiw==" + "resolved" "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-3.1.9.tgz" + "version" "3.1.9" + dependencies: + "slash" "^3.0.0" + +"@rollup/plugin-commonjs@^22.0.1": + "integrity" "sha512-dGfEZvdjDHObBiP5IvwTKMVeq/tBZGMBHZFMdIV1ClMM/YoWS34xrHFGfag9SN2ZtMgNZRFruqvxZQEa70O6nQ==" + "resolved" "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-22.0.1.tgz" + "version" "22.0.1" + dependencies: + "@rollup/pluginutils" "^3.1.0" + "commondir" "^1.0.1" + "estree-walker" "^2.0.1" + "glob" "^7.1.6" + "is-reference" "^1.2.1" + "magic-string" "^0.25.7" + "resolve" "^1.17.0" + +"@rollup/plugin-node-resolve@^13.3.0": + "integrity" "sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw==" + "resolved" "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz" + "version" "13.3.0" + dependencies: + "@rollup/pluginutils" "^3.1.0" + "@types/resolve" "1.17.1" + "deepmerge" "^4.2.2" + "is-builtin-module" "^3.1.0" + "is-module" "^1.0.0" + "resolve" "^1.19.0" + +"@rollup/pluginutils@^3.1.0": + "integrity" "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==" + "resolved" "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz" + "version" "3.1.0" + dependencies: + "@types/estree" "0.0.39" + "estree-walker" "^1.0.1" + "picomatch" "^2.2.2" + +"@rollup/pluginutils@^4.1.2": + "integrity" "sha512-2WUyJNRkyH5p487pGnn4tWAsxhEFKN/pT8CMgHshd5H+IXkOnKvKZwsz5ZWz+YCXkleZRAU5kwbfgF8CPfDRqA==" + "resolved" "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.0.tgz" + "version" "4.2.0" + dependencies: + "estree-walker" "^2.0.1" + "picomatch" "^2.2.2" + +"@tsconfig/node10@^1.0.7": + "integrity" "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==" + "resolved" "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz" + "version" "1.0.8" + +"@tsconfig/node12@^1.0.7": + "integrity" "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==" + "resolved" "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz" + "version" "1.0.9" + +"@tsconfig/node14@^1.0.0": + "integrity" "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==" + "resolved" "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz" + "version" "1.0.1" + +"@tsconfig/node16@^1.0.2": + "integrity" "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==" + "resolved" "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz" + "version" "1.0.2" + +"@types/estree@*", "@types/estree@0.0.39": + "integrity" "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" + "resolved" "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz" + "version" "0.0.39" + +"@types/json-schema@^7.0.9": + "integrity" "sha512-BLO9bBq59vW3fxCpD4o0N4U+DXsvwvIcl+jofw0frQo/GrBFC+/jRZj1E7kgp6dvTyNmA4y6JCV5Id/r3mNP5A==" + "resolved" "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.10.tgz" + "version" "7.0.10" + +"@types/mocha@^9.1.1": + "integrity" "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==" + "resolved" "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz" + "version" "9.1.1" + +"@types/node@*", "@types/node@^18.6.3": + "integrity" "sha512-6qKpDtoaYLM+5+AFChLhHermMQxc3TOEFIDzrZLPRGHPrLEwqFkkT5Kx3ju05g6X7uDPazz3jHbKPX0KzCjntg==" + "resolved" "https://registry.npmjs.org/@types/node/-/node-18.6.3.tgz" + "version" "18.6.3" + +"@types/resolve@1.17.1": + "integrity" "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==" + "resolved" "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz" + "version" "1.17.1" + dependencies: + "@types/node" "*" + +"@typescript-eslint/eslint-plugin@^5.32.0": + "integrity" "sha512-CHLuz5Uz7bHP2WgVlvoZGhf0BvFakBJKAD/43Ty0emn4wXWv5k01ND0C0fHcl/Im8Td2y/7h44E9pca9qAu2ew==" + "resolved" "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.32.0.tgz" + "version" "5.32.0" + dependencies: + "@typescript-eslint/scope-manager" "5.32.0" + "@typescript-eslint/type-utils" "5.32.0" + "@typescript-eslint/utils" "5.32.0" + "debug" "^4.3.4" + "functional-red-black-tree" "^1.0.1" + "ignore" "^5.2.0" + "regexpp" "^3.2.0" + "semver" "^7.3.7" + "tsutils" "^3.21.0" + +"@typescript-eslint/experimental-utils@^5.0.0": + "integrity" "sha512-rKxoCUtAHwEH6IcAoVpqipY6Th+YKW7WFspAKu0IFdbdKZpveFBeqxxE9Xn+GWikhq1o03V3VXbxIe+GdhggiQ==" + "resolved" "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.22.0.tgz" + "version" "5.22.0" + dependencies: + "@typescript-eslint/utils" "5.22.0" + +"@typescript-eslint/parser@^5.0.0", "@typescript-eslint/parser@^5.32.0": + "integrity" "sha512-IxRtsehdGV9GFQ35IGm5oKKR2OGcazUoiNBxhRV160iF9FoyuXxjY+rIqs1gfnd+4eL98OjeGnMpE7RF/NBb3A==" + "resolved" "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.32.0.tgz" + "version" "5.32.0" + dependencies: + "@typescript-eslint/scope-manager" "5.32.0" + "@typescript-eslint/types" "5.32.0" + "@typescript-eslint/typescript-estree" "5.32.0" + "debug" "^4.3.4" + +"@typescript-eslint/scope-manager@5.22.0": + "integrity" "sha512-yA9G5NJgV5esANJCO0oF15MkBO20mIskbZ8ijfmlKIvQKg0ynVKfHZ15/nhAJN5m8Jn3X5qkwriQCiUntC9AbA==" + "resolved" "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.22.0.tgz" + "version" "5.22.0" + dependencies: + "@typescript-eslint/types" "5.22.0" + "@typescript-eslint/visitor-keys" "5.22.0" + +"@typescript-eslint/scope-manager@5.32.0": + "integrity" "sha512-KyAE+tUON0D7tNz92p1uetRqVJiiAkeluvwvZOqBmW9z2XApmk5WSMV9FrzOroAcVxJZB3GfUwVKr98Dr/OjOg==" + "resolved" "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.32.0.tgz" + "version" "5.32.0" + dependencies: + "@typescript-eslint/types" "5.32.0" + "@typescript-eslint/visitor-keys" "5.32.0" + +"@typescript-eslint/type-utils@5.32.0": + "integrity" "sha512-0gSsIhFDduBz3QcHJIp3qRCvVYbqzHg8D6bHFsDMrm0rURYDj+skBK2zmYebdCp+4nrd9VWd13egvhYFJj/wZg==" + "resolved" "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.32.0.tgz" + "version" "5.32.0" + dependencies: + "@typescript-eslint/utils" "5.32.0" + "debug" "^4.3.4" + "tsutils" "^3.21.0" + +"@typescript-eslint/types@5.22.0": + "integrity" "sha512-T7owcXW4l0v7NTijmjGWwWf/1JqdlWiBzPqzAWhobxft0SiEvMJB56QXmeCQjrPuM8zEfGUKyPQr/L8+cFUBLw==" + "resolved" "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.22.0.tgz" + "version" "5.22.0" + +"@typescript-eslint/types@5.32.0": + "integrity" "sha512-EBUKs68DOcT/EjGfzywp+f8wG9Zw6gj6BjWu7KV/IYllqKJFPlZlLSYw/PTvVyiRw50t6wVbgv4p9uE2h6sZrQ==" + "resolved" "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.32.0.tgz" + "version" "5.32.0" + +"@typescript-eslint/typescript-estree@5.22.0": + "integrity" "sha512-EyBEQxvNjg80yinGE2xdhpDYm41so/1kOItl0qrjIiJ1kX/L/L8WWGmJg8ni6eG3DwqmOzDqOhe6763bF92nOw==" + "resolved" "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.22.0.tgz" + "version" "5.22.0" + dependencies: + "@typescript-eslint/types" "5.22.0" + "@typescript-eslint/visitor-keys" "5.22.0" + "debug" "^4.3.2" + "globby" "^11.0.4" + "is-glob" "^4.0.3" + "semver" "^7.3.5" + "tsutils" "^3.21.0" + +"@typescript-eslint/typescript-estree@5.32.0": + "integrity" "sha512-ZVAUkvPk3ITGtCLU5J4atCw9RTxK+SRc6hXqLtllC2sGSeMFWN+YwbiJR9CFrSFJ3w4SJfcWtDwNb/DmUIHdhg==" + "resolved" "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.32.0.tgz" + "version" "5.32.0" + dependencies: + "@typescript-eslint/types" "5.32.0" + "@typescript-eslint/visitor-keys" "5.32.0" + "debug" "^4.3.4" + "globby" "^11.1.0" + "is-glob" "^4.0.3" + "semver" "^7.3.7" + "tsutils" "^3.21.0" + +"@typescript-eslint/utils@5.22.0": + "integrity" "sha512-HodsGb037iobrWSUMS7QH6Hl1kppikjA1ELiJlNSTYf/UdMEwzgj0WIp+lBNb6WZ3zTwb0tEz51j0Wee3iJ3wQ==" + "resolved" "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.22.0.tgz" + "version" "5.22.0" + dependencies: + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.22.0" + "@typescript-eslint/types" "5.22.0" + "@typescript-eslint/typescript-estree" "5.22.0" + "eslint-scope" "^5.1.1" + "eslint-utils" "^3.0.0" + +"@typescript-eslint/utils@5.32.0": + "integrity" "sha512-W7lYIAI5Zlc5K082dGR27Fczjb3Q57ECcXefKU/f0ajM5ToM0P+N9NmJWip8GmGu/g6QISNT+K6KYB+iSHjXCQ==" + "resolved" "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.32.0.tgz" + "version" "5.32.0" + dependencies: + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.32.0" + "@typescript-eslint/types" "5.32.0" + "@typescript-eslint/typescript-estree" "5.32.0" + "eslint-scope" "^5.1.1" + "eslint-utils" "^3.0.0" + +"@typescript-eslint/visitor-keys@5.22.0": + "integrity" "sha512-DbgTqn2Dv5RFWluG88tn0pP6Ex0ROF+dpDO1TNNZdRtLjUr6bdznjA6f/qNqJLjd2PgguAES2Zgxh/JzwzETDg==" + "resolved" "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.22.0.tgz" + "version" "5.22.0" + dependencies: + "@typescript-eslint/types" "5.22.0" + "eslint-visitor-keys" "^3.0.0" + +"@typescript-eslint/visitor-keys@5.32.0": + "integrity" "sha512-S54xOHZgfThiZ38/ZGTgB2rqx51CMJ5MCfVT2IplK4Q7hgzGfe0nLzLCcenDnc/cSjP568hdeKfeDcBgqNHD/g==" + "resolved" "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.32.0.tgz" + "version" "5.32.0" + dependencies: + "@typescript-eslint/types" "5.32.0" + "eslint-visitor-keys" "^3.3.0" + +"@ungap/promise-all-settled@1.1.2": + "integrity" "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==" + "resolved" "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz" + "version" "1.1.2" + +"acorn-jsx@^5.3.2": + "integrity" "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==" + "resolved" "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" + "version" "5.3.2" + +"acorn-walk@^8.1.1": + "integrity" "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==" + "resolved" "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz" + "version" "8.2.0" + +"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", "acorn@^8.4.1", "acorn@^8.8.0": + "integrity" "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==" + "resolved" "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz" + "version" "8.8.0" + +"aggregate-error@^3.0.0": + "integrity" "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==" + "resolved" "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz" + "version" "3.1.0" + dependencies: + "clean-stack" "^2.0.0" + "indent-string" "^4.0.0" + +"ajv@^6.10.0", "ajv@^6.12.4": + "integrity" "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==" + "resolved" "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" + "version" "6.12.6" + dependencies: + "fast-deep-equal" "^3.1.1" + "fast-json-stable-stringify" "^2.0.0" + "json-schema-traverse" "^0.4.1" + "uri-js" "^4.2.2" + +"ansi-colors@4.1.1": + "integrity" "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==" + "resolved" "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz" + "version" "4.1.1" + +"ansi-regex@^5.0.1": + "integrity" "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + "resolved" "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" + "version" "5.0.1" + +"ansi-styles@^3.2.1": + "integrity" "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==" + "resolved" "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" + "version" "3.2.1" + dependencies: + "color-convert" "^1.9.0" + +"ansi-styles@^4.0.0", "ansi-styles@^4.1.0": + "integrity" "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==" + "resolved" "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" + "version" "4.3.0" + dependencies: + "color-convert" "^2.0.1" + +"anymatch@~3.1.2": + "integrity" "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==" + "resolved" "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz" + "version" "3.1.2" + dependencies: + "normalize-path" "^3.0.0" + "picomatch" "^2.0.4" + +"append-transform@^2.0.0": + "integrity" "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==" + "resolved" "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz" + "version" "2.0.0" + dependencies: + "default-require-extensions" "^3.0.0" + +"archy@^1.0.0": + "integrity" "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA= sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==" + "resolved" "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz" + "version" "1.0.0" + +"arg@^4.1.0": + "integrity" "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + "resolved" "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz" + "version" "4.1.3" + +"argparse@^1.0.7": + "integrity" "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==" + "resolved" "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" + "version" "1.0.10" + dependencies: + "sprintf-js" "~1.0.2" + +"argparse@^2.0.1": + "integrity" "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + "resolved" "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" + "version" "2.0.1" + +"array-union@^2.1.0": + "integrity" "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" + "resolved" "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" + "version" "2.1.0" + +"asn1js@^3.0.0", "asn1js@^3.0.1", "asn1js@^3.0.5": + "integrity" "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==" + "resolved" "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz" + "version" "3.0.5" + dependencies: + "pvtsutils" "^1.3.2" + "pvutils" "^1.1.3" + "tslib" "^2.4.0" + +"assert@^2.0.0": + "integrity" "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==" + "resolved" "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz" + "version" "2.0.0" + dependencies: + "es6-object-assign" "^1.1.0" + "is-nan" "^1.2.1" + "object-is" "^1.0.1" + "util" "^0.12.0" + +"available-typed-arrays@^1.0.5": + "integrity" "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" + "resolved" "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz" + "version" "1.0.5" + +"balanced-match@^1.0.0": + "integrity" "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "resolved" "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" + "version" "1.0.2" + +"binary-extensions@^2.0.0": + "integrity" "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" + "resolved" "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz" + "version" "2.2.0" + +"brace-expansion@^1.1.7": + "integrity" "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==" + "resolved" "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" + "version" "1.1.11" + dependencies: + "balanced-match" "^1.0.0" + "concat-map" "0.0.1" + +"brace-expansion@^2.0.1": + "integrity" "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==" + "resolved" "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz" + "version" "2.0.1" + dependencies: + "balanced-match" "^1.0.0" + +"braces@^3.0.1", "braces@~3.0.2": + "integrity" "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==" + "resolved" "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz" + "version" "3.0.2" + dependencies: + "fill-range" "^7.0.1" + +"browser-stdout@1.3.1": + "integrity" "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" + "resolved" "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz" + "version" "1.3.1" + +"browserslist@^4.17.5": + "integrity" "sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==" + "resolved" "https://registry.npmjs.org/browserslist/-/browserslist-4.20.2.tgz" + "version" "4.20.2" + dependencies: + "caniuse-lite" "^1.0.30001317" + "electron-to-chromium" "^1.4.84" + "escalade" "^3.1.1" + "node-releases" "^2.0.2" + "picocolors" "^1.0.0" + +"builtin-modules@^3.0.0": + "integrity" "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==" + "resolved" "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz" + "version" "3.2.0" + +"bytestreamjs@^2.0.0": + "integrity" "sha512-TyOlxeS92FcMOaJwAVq5gwqW0vfkWUv5W+ErwdbBzolcUN/9XYpCKWvCV21jjzXU550D9Wt4GgE8Pr1vVbR+wQ==" + "resolved" "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.0.tgz" + "version" "2.0.0" + +"caching-transform@^4.0.0": + "integrity" "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==" + "resolved" "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz" + "version" "4.0.0" + dependencies: + "hasha" "^5.0.0" + "make-dir" "^3.0.0" + "package-hash" "^4.0.0" + "write-file-atomic" "^3.0.0" + +"call-bind@^1.0.0", "call-bind@^1.0.2": + "integrity" "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==" + "resolved" "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz" + "version" "1.0.2" + dependencies: + "function-bind" "^1.1.1" + "get-intrinsic" "^1.0.2" + +"callsites@^3.0.0": + "integrity" "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + "resolved" "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" + "version" "3.1.0" + +"camelcase@^5.0.0", "camelcase@^5.3.1": + "integrity" "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + "resolved" "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz" + "version" "5.3.1" + +"camelcase@^6.0.0": + "integrity" "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==" + "resolved" "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" + "version" "6.3.0" + +"caniuse-lite@^1.0.30001317": + "integrity" "sha512-xIZLh8gBm4dqNX0gkzrBeyI86J2eCjWzYAs40q88smG844YIrN4tVQl/RhquHvKEKImWWFIVh1Lxe5n1G/N+GQ==" + "resolved" "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001317.tgz" + "version" "1.0.30001317" + +"chalk@^2.0.0": + "integrity" "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==" + "resolved" "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" + "version" "2.4.2" + dependencies: + "ansi-styles" "^3.2.1" + "escape-string-regexp" "^1.0.5" + "supports-color" "^5.3.0" + +"chalk@^4.0.0", "chalk@^4.1.0": + "integrity" "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==" + "resolved" "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" + "version" "4.1.2" + dependencies: + "ansi-styles" "^4.1.0" + "supports-color" "^7.1.0" + +"chokidar@3.5.3": + "integrity" "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==" + "resolved" "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz" + "version" "3.5.3" + dependencies: + "anymatch" "~3.1.2" + "braces" "~3.0.2" + "glob-parent" "~5.1.2" + "is-binary-path" "~2.1.0" + "is-glob" "~4.0.1" + "normalize-path" "~3.0.0" + "readdirp" "~3.6.0" + optionalDependencies: + "fsevents" "~2.3.2" + +"clean-stack@^2.0.0": + "integrity" "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" + "resolved" "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz" + "version" "2.2.0" + +"cliui@^6.0.0": + "integrity" "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==" + "resolved" "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz" + "version" "6.0.0" + dependencies: + "string-width" "^4.2.0" + "strip-ansi" "^6.0.0" + "wrap-ansi" "^6.2.0" + +"cliui@^7.0.2": + "integrity" "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==" + "resolved" "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz" + "version" "7.0.4" + dependencies: + "string-width" "^4.2.0" + "strip-ansi" "^6.0.0" + "wrap-ansi" "^7.0.0" + +"color-convert@^1.9.0": + "integrity" "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==" + "resolved" "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" + "version" "1.9.3" + dependencies: + "color-name" "1.1.3" + +"color-convert@^2.0.1": + "integrity" "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==" + "resolved" "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" + "version" "2.0.1" + dependencies: + "color-name" "~1.1.4" + +"color-name@~1.1.4": + "integrity" "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "resolved" "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + "version" "1.1.4" + +"color-name@1.1.3": + "integrity" "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "resolved" "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" + "version" "1.1.3" + +"commondir@^1.0.1": + "integrity" "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" + "resolved" "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz" + "version" "1.0.1" + +"concat-map@0.0.1": + "integrity" "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "resolved" "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + "version" "0.0.1" + +"convert-source-map@^1.7.0": + "integrity" "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==" + "resolved" "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz" + "version" "1.8.0" + dependencies: + "safe-buffer" "~5.1.1" + +"create-require@^1.1.0": + "integrity" "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + "resolved" "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz" + "version" "1.1.1" + +"cross-spawn@^7.0.0", "cross-spawn@^7.0.2": + "integrity" "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==" + "resolved" "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" + "version" "7.0.3" + dependencies: + "path-key" "^3.1.0" + "shebang-command" "^2.0.0" + "which" "^2.0.1" + +"debug@^4.1.0", "debug@^4.1.1", "debug@^4.3.2", "debug@^4.3.4", "debug@4.3.4": + "integrity" "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==" + "resolved" "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" + "version" "4.3.4" + dependencies: + "ms" "2.1.2" + +"decamelize@^1.2.0": + "integrity" "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==" + "resolved" "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" + "version" "1.2.0" + +"decamelize@^4.0.0": + "integrity" "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==" + "resolved" "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz" + "version" "4.0.0" + +"deep-is@^0.1.3": + "integrity" "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + "resolved" "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" + "version" "0.1.4" + +"deepmerge@^4.2.2": + "integrity" "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" + "resolved" "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz" + "version" "4.2.2" + +"default-require-extensions@^3.0.0": + "integrity" "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==" + "resolved" "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "strip-bom" "^4.0.0" + +"define-properties@^1.1.3": + "integrity" "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==" + "resolved" "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz" + "version" "1.1.3" + dependencies: + "object-keys" "^1.0.12" + +"diff@^4.0.1": + "integrity" "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" + "resolved" "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" + "version" "4.0.2" + +"diff@5.0.0": + "integrity" "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==" + "resolved" "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz" + "version" "5.0.0" + +"dir-glob@^3.0.1": + "integrity" "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==" + "resolved" "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" + "version" "3.0.1" + dependencies: + "path-type" "^4.0.0" + +"doctrine@^3.0.0": + "integrity" "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==" + "resolved" "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "esutils" "^2.0.2" + +"electron-to-chromium@^1.4.84": + "integrity" "sha512-EXXTtDHFUKdFVkCnhauU7Xp8wmFC1ZG6GK9a1BeI2vvNhy61IwfNPo/CRexhf7mh4ajxAHJPind62BzpzVUeuQ==" + "resolved" "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.87.tgz" + "version" "1.4.87" + +"emailjs-addressparser@^2.0.1", "emailjs-addressparser@^2.0.2": + "integrity" "sha512-E6ztFf1ePtZEwqfN3aWsvZNcpBeDqgJbVL4D32dckHkcwdhJ1bhlE4MLvHBf/IoXQmJBgkKlwGP5/5l11EE5OQ==" + "resolved" "https://registry.npmjs.org/emailjs-addressparser/-/emailjs-addressparser-2.0.2.tgz" + "version" "2.0.2" + +"emailjs-base64@^1.1.4": + "integrity" "sha512-4h0xp1jgVTnIQBHxSJWXWanNnmuc5o+k4aHEpcLXSToN8asjB5qbXAexs7+PEsUKcEyBteNYsSvXUndYT2CGGA==" + "resolved" "https://registry.npmjs.org/emailjs-base64/-/emailjs-base64-1.1.4.tgz" + "version" "1.1.4" + +"emailjs-mime-builder@^2.0.5": + "integrity" "sha512-fkaUn6IA4onX3SmVpqvVTlkdrusM9lq+xnrA24euyMUga2B44Ue0ftE48aSNveEWPgGLtHfSd04lGWhT2/AOpw==" + "resolved" "https://registry.npmjs.org/emailjs-mime-builder/-/emailjs-mime-builder-2.0.5.tgz" + "version" "2.0.5" + dependencies: + "emailjs-addressparser" "^2.0.2" + "emailjs-mime-codec" "^2.0.5" + "emailjs-mime-types" "^2.0.0" + "punycode" "1.4.1" + "ramda" "^0.25.0" + +"emailjs-mime-codec@^2.0.5", "emailjs-mime-codec@^2.0.8": + "integrity" "sha512-7qJo4pFGcKlWh/kCeNjmcgj34YoJWY0ekZXEHYtluWg4MVBnXqGM4CRMtZQkfYwitOhUgaKN5EQktJddi/YIDQ==" + "resolved" "https://registry.npmjs.org/emailjs-mime-codec/-/emailjs-mime-codec-2.0.9.tgz" + "version" "2.0.9" + dependencies: + "emailjs-base64" "^1.1.4" + "ramda" "^0.26.1" + "text-encoding" "^0.7.0" + +"emailjs-mime-parser@^2.0.7": + "integrity" "sha512-rOrRtAzq0OVLrxbTkRLyrtoY/YQldPgIzAk6lcD3LfXR0Gw3+PzsN2rO1QENY+cIQD94vYr2t2Ri0Zxlc9aeew==" + "resolved" "https://registry.npmjs.org/emailjs-mime-parser/-/emailjs-mime-parser-2.0.7.tgz" + "version" "2.0.7" + dependencies: + "emailjs-addressparser" "^2.0.1" + "emailjs-mime-codec" "^2.0.8" + "ramda" "^0.26.1" + +"emailjs-mime-types@^2.0.0": + "integrity" "sha512-vlmgTAE3cya0GHoqHYgvmzG342sKGsHhddJhOSqXIZM0V7kjUAKxXmol3NTyeuHltggCp6HJT3NRqJuDkchCrw==" + "resolved" "https://registry.npmjs.org/emailjs-mime-types/-/emailjs-mime-types-2.1.0.tgz" + "version" "2.1.0" + +"emoji-regex@^8.0.0": + "integrity" "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "resolved" "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" + "version" "8.0.0" + +"es-abstract@^1.18.5": + "integrity" "sha512-gfSBJoZdlL2xRiOCy0g8gLMryhoe1TlimjzU99L/31Z8QEGIhVQI+EWwt5lT+AuU9SnorVupXFqqOGqGfsyO6w==" + "resolved" "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.2.tgz" + "version" "1.19.2" + dependencies: + "call-bind" "^1.0.2" + "es-to-primitive" "^1.2.1" + "function-bind" "^1.1.1" + "get-intrinsic" "^1.1.1" + "get-symbol-description" "^1.0.0" + "has" "^1.0.3" + "has-symbols" "^1.0.3" + "internal-slot" "^1.0.3" + "is-callable" "^1.2.4" + "is-negative-zero" "^2.0.2" + "is-regex" "^1.1.4" + "is-shared-array-buffer" "^1.0.1" + "is-string" "^1.0.7" + "is-weakref" "^1.0.2" + "object-inspect" "^1.12.0" + "object-keys" "^1.1.1" + "object.assign" "^4.1.2" + "string.prototype.trimend" "^1.0.4" + "string.prototype.trimstart" "^1.0.4" + "unbox-primitive" "^1.0.1" + +"es-to-primitive@^1.2.1": + "integrity" "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==" + "resolved" "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz" + "version" "1.2.1" + dependencies: + "is-callable" "^1.1.4" + "is-date-object" "^1.0.1" + "is-symbol" "^1.0.2" + +"es6-error@^4.0.1": + "integrity" "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" + "resolved" "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz" + "version" "4.1.1" + +"es6-object-assign@^1.1.0": + "integrity" "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw= sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==" + "resolved" "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz" + "version" "1.1.0" + +"escalade@^3.1.1": + "integrity" "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + "resolved" "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" + "version" "3.1.1" + +"escape-string-regexp@^1.0.5": + "integrity" "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + "resolved" "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + "version" "1.0.5" + +"escape-string-regexp@^4.0.0", "escape-string-regexp@4.0.0": + "integrity" "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + "resolved" "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" + "version" "4.0.0" + +"eslint-plugin-deprecation@^1.3.2": + "integrity" "sha512-z93wbx9w7H/E3ogPw6AZMkkNJ6m51fTZRNZPNQqxQLmx+KKt7aLkMU9wN67s71i+VVHN4tLOZ3zT3QLbnlC0Mg==" + "resolved" "https://registry.npmjs.org/eslint-plugin-deprecation/-/eslint-plugin-deprecation-1.3.2.tgz" + "version" "1.3.2" + dependencies: + "@typescript-eslint/experimental-utils" "^5.0.0" + "tslib" "^2.3.1" + "tsutils" "^3.21.0" + +"eslint-scope@^5.1.1": + "integrity" "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==" + "resolved" "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz" + "version" "5.1.1" + dependencies: + "esrecurse" "^4.3.0" + "estraverse" "^4.1.1" + +"eslint-scope@^7.1.1": + "integrity" "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==" + "resolved" "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz" + "version" "7.1.1" + dependencies: + "esrecurse" "^4.3.0" + "estraverse" "^5.2.0" + +"eslint-utils@^3.0.0": + "integrity" "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==" + "resolved" "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "eslint-visitor-keys" "^2.0.0" + +"eslint-visitor-keys@^2.0.0": + "integrity" "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==" + "resolved" "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz" + "version" "2.1.0" + +"eslint-visitor-keys@^3.0.0", "eslint-visitor-keys@^3.3.0": + "integrity" "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==" + "resolved" "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz" + "version" "3.3.0" + +"eslint@*", "eslint@^6.0.0 || ^7.0.0 || ^8.0.0", "eslint@^8.21.0", "eslint@>=5": + "integrity" "sha512-/XJ1+Qurf1T9G2M5IHrsjp+xrGT73RZf23xA1z5wB1ZzzEAWSZKvRwhWxTFp1rvkvCfwcvAUNAP31bhKTTGfDA==" + "resolved" "https://registry.npmjs.org/eslint/-/eslint-8.21.0.tgz" + "version" "8.21.0" + dependencies: + "@eslint/eslintrc" "^1.3.0" + "@humanwhocodes/config-array" "^0.10.4" + "@humanwhocodes/gitignore-to-minimatch" "^1.0.2" + "ajv" "^6.10.0" + "chalk" "^4.0.0" + "cross-spawn" "^7.0.2" + "debug" "^4.3.2" + "doctrine" "^3.0.0" + "escape-string-regexp" "^4.0.0" + "eslint-scope" "^7.1.1" + "eslint-utils" "^3.0.0" + "eslint-visitor-keys" "^3.3.0" + "espree" "^9.3.3" + "esquery" "^1.4.0" + "esutils" "^2.0.2" + "fast-deep-equal" "^3.1.3" + "file-entry-cache" "^6.0.1" + "find-up" "^5.0.0" + "functional-red-black-tree" "^1.0.1" + "glob-parent" "^6.0.1" + "globals" "^13.15.0" + "globby" "^11.1.0" + "grapheme-splitter" "^1.0.4" + "ignore" "^5.2.0" + "import-fresh" "^3.0.0" + "imurmurhash" "^0.1.4" + "is-glob" "^4.0.0" + "js-yaml" "^4.1.0" + "json-stable-stringify-without-jsonify" "^1.0.1" + "levn" "^0.4.1" + "lodash.merge" "^4.6.2" + "minimatch" "^3.1.2" + "natural-compare" "^1.4.0" + "optionator" "^0.9.1" + "regexpp" "^3.2.0" + "strip-ansi" "^6.0.1" + "strip-json-comments" "^3.1.0" + "text-table" "^0.2.0" + "v8-compile-cache" "^2.0.3" + +"espree@^9.3.2", "espree@^9.3.3": + "integrity" "sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==" + "resolved" "https://registry.npmjs.org/espree/-/espree-9.3.3.tgz" + "version" "9.3.3" + dependencies: + "acorn" "^8.8.0" + "acorn-jsx" "^5.3.2" + "eslint-visitor-keys" "^3.3.0" + +"esprima@^4.0.0": + "integrity" "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + "resolved" "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" + "version" "4.0.1" + +"esquery@^1.4.0": + "integrity" "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==" + "resolved" "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz" + "version" "1.4.0" + dependencies: + "estraverse" "^5.1.0" + +"esrecurse@^4.3.0": + "integrity" "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==" + "resolved" "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" + "version" "4.3.0" + dependencies: + "estraverse" "^5.2.0" + +"estraverse@^4.1.1": + "integrity" "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + "resolved" "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz" + "version" "4.3.0" + +"estraverse@^5.1.0": + "integrity" "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + "resolved" "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" + "version" "5.3.0" + +"estraverse@^5.2.0": + "integrity" "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + "resolved" "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" + "version" "5.3.0" + +"estree-walker@^1.0.1": + "integrity" "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==" + "resolved" "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz" + "version" "1.0.1" + +"estree-walker@^2.0.1": + "integrity" "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + "resolved" "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz" + "version" "2.0.2" + +"esutils@^2.0.2": + "integrity" "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + "resolved" "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" + "version" "2.0.3" + +"fast-deep-equal@^3.1.1", "fast-deep-equal@^3.1.3": + "integrity" "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + "resolved" "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" + "version" "3.1.3" + +"fast-glob@^3.2.9": + "integrity" "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==" + "resolved" "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz" + "version" "3.2.11" + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + "glob-parent" "^5.1.2" + "merge2" "^1.3.0" + "micromatch" "^4.0.4" + +"fast-json-stable-stringify@^2.0.0": + "integrity" "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + "resolved" "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" + "version" "2.1.0" + +"fast-levenshtein@^2.0.6": + "integrity" "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + "resolved" "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" + "version" "2.0.6" + +"fastq@^1.6.0": + "integrity" "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==" + "resolved" "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz" + "version" "1.13.0" + dependencies: + "reusify" "^1.0.4" + +"file-entry-cache@^6.0.1": + "integrity" "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==" + "resolved" "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz" + "version" "6.0.1" + dependencies: + "flat-cache" "^3.0.4" + +"fill-range@^7.0.1": + "integrity" "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==" + "resolved" "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" + "version" "7.0.1" + dependencies: + "to-regex-range" "^5.0.1" + +"find-cache-dir@^3.2.0", "find-cache-dir@^3.3.2": + "integrity" "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==" + "resolved" "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz" + "version" "3.3.2" + dependencies: + "commondir" "^1.0.1" + "make-dir" "^3.0.2" + "pkg-dir" "^4.1.0" + +"find-up@^4.0.0": + "integrity" "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==" + "resolved" "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" + "version" "4.1.0" + dependencies: + "locate-path" "^5.0.0" + "path-exists" "^4.0.0" + +"find-up@^4.1.0": + "integrity" "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==" + "resolved" "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" + "version" "4.1.0" + dependencies: + "locate-path" "^5.0.0" + "path-exists" "^4.0.0" + +"find-up@^5.0.0", "find-up@5.0.0": + "integrity" "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==" + "resolved" "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" + "version" "5.0.0" + dependencies: + "locate-path" "^6.0.0" + "path-exists" "^4.0.0" + +"flat-cache@^3.0.4": + "integrity" "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==" + "resolved" "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz" + "version" "3.0.4" + dependencies: + "flatted" "^3.1.0" + "rimraf" "^3.0.2" + +"flat@^5.0.2": + "integrity" "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==" + "resolved" "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz" + "version" "5.0.2" + +"flatted@^3.1.0": + "integrity" "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==" + "resolved" "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz" + "version" "3.2.5" + +"foreach@^2.0.5": + "integrity" "sha1-C+4AUBiusmDQo6865ljdATbsG5k= sha512-ZBbtRiapkZYLsqoPyZOR+uPfto0GRMNQN1GwzZtZt7iZvPPbDDQV0JF5Hx4o/QFQ5c0vyuoZ98T8RSBbopzWtA==" + "resolved" "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz" + "version" "2.0.5" + +"foreground-child@^2.0.0": + "integrity" "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==" + "resolved" "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz" + "version" "2.0.0" + dependencies: + "cross-spawn" "^7.0.0" + "signal-exit" "^3.0.2" + +"fromentries@^1.2.0": + "integrity" "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==" + "resolved" "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz" + "version" "1.3.2" + +"fs-extra@^10.0.0": + "integrity" "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==" + "resolved" "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz" + "version" "10.0.1" + dependencies: + "graceful-fs" "^4.2.0" + "jsonfile" "^6.0.1" + "universalify" "^2.0.0" + +"fs.realpath@^1.0.0": + "integrity" "sha1-FQStJSMVjKpA20onh8sBQRmU6k8= sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "resolved" "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" + "version" "1.0.0" + +"fsevents@~2.3.2": + "integrity" "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==" + "resolved" "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" + "version" "2.3.2" + +"function-bind@^1.1.1": + "integrity" "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "resolved" "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" + "version" "1.1.1" + +"functional-red-black-tree@^1.0.1": + "integrity" "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==" + "resolved" "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz" + "version" "1.0.1" + +"gensync@^1.0.0-beta.2": + "integrity" "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" + "resolved" "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" + "version" "1.0.0-beta.2" + +"get-caller-file@^2.0.1", "get-caller-file@^2.0.5": + "integrity" "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + "resolved" "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" + "version" "2.0.5" + +"get-intrinsic@^1.0.2", "get-intrinsic@^1.1.0", "get-intrinsic@^1.1.1": + "integrity" "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==" + "resolved" "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz" + "version" "1.1.1" + dependencies: + "function-bind" "^1.1.1" + "has" "^1.0.3" + "has-symbols" "^1.0.1" + +"get-package-type@^0.1.0": + "integrity" "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==" + "resolved" "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" + "version" "0.1.0" + +"get-symbol-description@^1.0.0": + "integrity" "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==" + "resolved" "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz" + "version" "1.0.0" + dependencies: + "call-bind" "^1.0.2" + "get-intrinsic" "^1.1.1" + +"glob-parent@^5.1.2": + "integrity" "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==" + "resolved" "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" + "version" "5.1.2" + dependencies: + "is-glob" "^4.0.1" + +"glob-parent@^6.0.1": + "integrity" "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==" + "resolved" "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz" + "version" "6.0.2" + dependencies: + "is-glob" "^4.0.3" + +"glob-parent@~5.1.2": + "integrity" "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==" + "resolved" "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" + "version" "5.1.2" + dependencies: + "is-glob" "^4.0.1" + +"glob@^7.1.3", "glob@^7.1.4", "glob@^7.1.6", "glob@7.2.0": + "integrity" "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==" + "resolved" "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz" + "version" "7.2.0" + dependencies: + "fs.realpath" "^1.0.0" + "inflight" "^1.0.4" + "inherits" "2" + "minimatch" "^3.0.4" + "once" "^1.3.0" + "path-is-absolute" "^1.0.0" + +"globals@^11.1.0": + "integrity" "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + "resolved" "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" + "version" "11.12.0" + +"globals@^13.15.0": + "integrity" "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==" + "resolved" "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz" + "version" "13.15.0" + dependencies: + "type-fest" "^0.20.2" + +"globby@^11.0.4", "globby@^11.1.0": + "integrity" "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==" + "resolved" "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" + "version" "11.1.0" + dependencies: + "array-union" "^2.1.0" + "dir-glob" "^3.0.1" + "fast-glob" "^3.2.9" + "ignore" "^5.2.0" + "merge2" "^1.4.1" + "slash" "^3.0.0" + +"graceful-fs@^4.1.15", "graceful-fs@^4.1.6", "graceful-fs@^4.2.0": + "integrity" "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" + "resolved" "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz" + "version" "4.2.9" + +"grapheme-splitter@^1.0.4": + "integrity" "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" + "resolved" "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz" + "version" "1.0.4" + +"has-bigints@^1.0.1": + "integrity" "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==" + "resolved" "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz" + "version" "1.0.1" + +"has-flag@^3.0.0": + "integrity" "sha1-tdRU3CGZriJWmfNGfloH87lVuv0= sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + "resolved" "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" + "version" "3.0.0" + +"has-flag@^4.0.0": + "integrity" "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + "resolved" "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" + "version" "4.0.0" + +"has-symbols@^1.0.1", "has-symbols@^1.0.2", "has-symbols@^1.0.3": + "integrity" "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + "resolved" "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" + "version" "1.0.3" + +"has-tostringtag@^1.0.0": + "integrity" "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==" + "resolved" "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz" + "version" "1.0.0" + dependencies: + "has-symbols" "^1.0.2" + +"has@^1.0.3": + "integrity" "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==" + "resolved" "https://registry.npmjs.org/has/-/has-1.0.3.tgz" + "version" "1.0.3" + dependencies: + "function-bind" "^1.1.1" + +"hasha@^5.0.0": + "integrity" "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==" + "resolved" "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz" + "version" "5.2.2" + dependencies: + "is-stream" "^2.0.0" + "type-fest" "^0.8.0" + +"he@1.2.0": + "integrity" "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" + "resolved" "https://registry.npmjs.org/he/-/he-1.2.0.tgz" + "version" "1.2.0" + +"html-escaper@^2.0.0": + "integrity" "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" + "resolved" "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz" + "version" "2.0.2" + +"ignore@^5.2.0": + "integrity" "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==" + "resolved" "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz" + "version" "5.2.0" + +"import-fresh@^3.0.0", "import-fresh@^3.2.1": + "integrity" "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==" + "resolved" "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" + "version" "3.3.0" + dependencies: + "parent-module" "^1.0.0" + "resolve-from" "^4.0.0" + +"imurmurhash@^0.1.4": + "integrity" "sha1-khi5srkoojixPcT7a21XbyMUU+o= sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" + "resolved" "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" + "version" "0.1.4" + +"indent-string@^4.0.0": + "integrity" "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" + "resolved" "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz" + "version" "4.0.0" + +"inflight@^1.0.4": + "integrity" "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==" + "resolved" "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" + "version" "1.0.6" + dependencies: + "once" "^1.3.0" + "wrappy" "1" + +"inherits@^2.0.3", "inherits@2": + "integrity" "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "resolved" "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + "version" "2.0.4" + +"internal-slot@^1.0.3": + "integrity" "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==" + "resolved" "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz" + "version" "1.0.3" + dependencies: + "get-intrinsic" "^1.1.0" + "has" "^1.0.3" + "side-channel" "^1.0.4" + +"is-arguments@^1.0.4": + "integrity" "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==" + "resolved" "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz" + "version" "1.1.1" + dependencies: + "call-bind" "^1.0.2" + "has-tostringtag" "^1.0.0" + +"is-bigint@^1.0.1": + "integrity" "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==" + "resolved" "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz" + "version" "1.0.4" + dependencies: + "has-bigints" "^1.0.1" + +"is-binary-path@~2.1.0": + "integrity" "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==" + "resolved" "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" + "version" "2.1.0" + dependencies: + "binary-extensions" "^2.0.0" + +"is-boolean-object@^1.1.0": + "integrity" "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==" + "resolved" "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz" + "version" "1.1.2" + dependencies: + "call-bind" "^1.0.2" + "has-tostringtag" "^1.0.0" + +"is-builtin-module@^3.1.0": + "integrity" "sha512-OV7JjAgOTfAFJmHZLvpSTb4qi0nIILDV1gWPYDnDJUTNFM5aGlRAhk4QcT8i7TuAleeEV5Fdkqn3t4mS+Q11fg==" + "resolved" "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.1.0.tgz" + "version" "3.1.0" + dependencies: + "builtin-modules" "^3.0.0" + +"is-callable@^1.1.4", "is-callable@^1.2.4": + "integrity" "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==" + "resolved" "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz" + "version" "1.2.4" + +"is-core-module@^2.8.1": + "integrity" "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==" + "resolved" "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz" + "version" "2.8.1" + dependencies: + "has" "^1.0.3" + +"is-date-object@^1.0.1": + "integrity" "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==" + "resolved" "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz" + "version" "1.0.5" + dependencies: + "has-tostringtag" "^1.0.0" + +"is-extglob@^2.1.1": + "integrity" "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + "resolved" "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" + "version" "2.1.1" + +"is-fullwidth-code-point@^3.0.0": + "integrity" "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + "resolved" "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" + "version" "3.0.0" + +"is-generator-function@^1.0.7": + "integrity" "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==" + "resolved" "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz" + "version" "1.0.10" + dependencies: + "has-tostringtag" "^1.0.0" + +"is-glob@^4.0.0", "is-glob@^4.0.1", "is-glob@^4.0.3", "is-glob@~4.0.1": + "integrity" "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==" + "resolved" "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" + "version" "4.0.3" + dependencies: + "is-extglob" "^2.1.1" + +"is-module@^1.0.0": + "integrity" "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==" + "resolved" "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz" + "version" "1.0.0" + +"is-nan@^1.2.1": + "integrity" "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==" + "resolved" "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz" + "version" "1.3.2" + dependencies: + "call-bind" "^1.0.0" + "define-properties" "^1.1.3" + +"is-negative-zero@^2.0.2": + "integrity" "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==" + "resolved" "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz" + "version" "2.0.2" + +"is-number-object@^1.0.4": + "integrity" "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==" + "resolved" "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz" + "version" "1.0.7" + dependencies: + "has-tostringtag" "^1.0.0" + +"is-number@^7.0.0": + "integrity" "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + "resolved" "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" + "version" "7.0.0" + +"is-plain-obj@^2.1.0": + "integrity" "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==" + "resolved" "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz" + "version" "2.1.0" + +"is-reference@^1.2.1": + "integrity" "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==" + "resolved" "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz" + "version" "1.2.1" + dependencies: + "@types/estree" "*" + +"is-regex@^1.1.4": + "integrity" "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==" + "resolved" "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz" + "version" "1.1.4" + dependencies: + "call-bind" "^1.0.2" + "has-tostringtag" "^1.0.0" + +"is-shared-array-buffer@^1.0.1": + "integrity" "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==" + "resolved" "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz" + "version" "1.0.2" + dependencies: + "call-bind" "^1.0.2" + +"is-stream@^2.0.0": + "integrity" "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" + "resolved" "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" + "version" "2.0.1" + +"is-string@^1.0.5", "is-string@^1.0.7": + "integrity" "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==" + "resolved" "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz" + "version" "1.0.7" + dependencies: + "has-tostringtag" "^1.0.0" + +"is-symbol@^1.0.2", "is-symbol@^1.0.3": + "integrity" "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==" + "resolved" "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz" + "version" "1.0.4" + dependencies: + "has-symbols" "^1.0.2" + +"is-typed-array@^1.1.3", "is-typed-array@^1.1.7": + "integrity" "sha512-HqH41TNZq2fgtGT8WHVFVJhBVGuY3AnP3Q36K8JKXUxSxRgk/d+7NjmwG2vo2mYmXK8UYZKu0qH8bVP5gEisjA==" + "resolved" "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.8.tgz" + "version" "1.1.8" + dependencies: + "available-typed-arrays" "^1.0.5" + "call-bind" "^1.0.2" + "es-abstract" "^1.18.5" + "foreach" "^2.0.5" + "has-tostringtag" "^1.0.0" + +"is-typedarray@^1.0.0": + "integrity" "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + "resolved" "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" + "version" "1.0.0" + +"is-unicode-supported@^0.1.0": + "integrity" "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==" + "resolved" "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz" + "version" "0.1.0" + +"is-weakref@^1.0.2": + "integrity" "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==" + "resolved" "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz" + "version" "1.0.2" + dependencies: + "call-bind" "^1.0.2" + +"is-windows@^1.0.2": + "integrity" "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + "resolved" "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz" + "version" "1.0.2" + +"isexe@^2.0.0": + "integrity" "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + "resolved" "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" + "version" "2.0.0" + +"istanbul-lib-coverage@^3.0.0", "istanbul-lib-coverage@^3.0.0-alpha.1": + "integrity" "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==" + "resolved" "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz" + "version" "3.2.0" + +"istanbul-lib-hook@^3.0.0": + "integrity" "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==" + "resolved" "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "append-transform" "^2.0.0" + +"istanbul-lib-instrument@^4.0.0": + "integrity" "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==" + "resolved" "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz" + "version" "4.0.3" + dependencies: + "@babel/core" "^7.7.5" + "@istanbuljs/schema" "^0.1.2" + "istanbul-lib-coverage" "^3.0.0" + "semver" "^6.3.0" + +"istanbul-lib-processinfo@^2.0.2": + "integrity" "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==" + "resolved" "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz" + "version" "2.0.2" + dependencies: + "archy" "^1.0.0" + "cross-spawn" "^7.0.0" + "istanbul-lib-coverage" "^3.0.0-alpha.1" + "make-dir" "^3.0.0" + "p-map" "^3.0.0" + "rimraf" "^3.0.0" + "uuid" "^3.3.3" + +"istanbul-lib-report@^3.0.0": + "integrity" "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==" + "resolved" "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "istanbul-lib-coverage" "^3.0.0" + "make-dir" "^3.0.0" + "supports-color" "^7.1.0" + +"istanbul-lib-source-maps@^4.0.0": + "integrity" "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==" + "resolved" "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz" + "version" "4.0.1" + dependencies: + "debug" "^4.1.1" + "istanbul-lib-coverage" "^3.0.0" + "source-map" "^0.6.1" + +"istanbul-reports@^3.0.2": + "integrity" "sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==" + "resolved" "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz" + "version" "3.1.4" + dependencies: + "html-escaper" "^2.0.0" + "istanbul-lib-report" "^3.0.0" + +"js-tokens@^4.0.0": + "integrity" "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "resolved" "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" + "version" "4.0.0" + +"js-yaml@^3.13.1": + "integrity" "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==" + "resolved" "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" + "version" "3.14.1" + dependencies: + "argparse" "^1.0.7" + "esprima" "^4.0.0" + +"js-yaml@^4.1.0", "js-yaml@4.1.0": + "integrity" "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==" + "resolved" "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" + "version" "4.1.0" + dependencies: + "argparse" "^2.0.1" + +"jsesc@^2.5.1": + "integrity" "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + "resolved" "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" + "version" "2.5.2" + +"json-schema-traverse@^0.4.1": + "integrity" "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "resolved" "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" + "version" "0.4.1" + +"json-stable-stringify-without-jsonify@^1.0.1": + "integrity" "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + "resolved" "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" + "version" "1.0.1" + +"json5@^2.1.2": + "integrity" "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==" + "resolved" "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz" + "version" "2.2.0" + dependencies: + "minimist" "^1.2.5" + +"jsonc-parser@^3.0.0": + "integrity" "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==" + "resolved" "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz" + "version" "3.0.0" + +"jsonfile@^6.0.1": + "integrity" "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==" + "resolved" "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz" + "version" "6.1.0" + dependencies: + "universalify" "^2.0.0" + optionalDependencies: + "graceful-fs" "^4.1.6" + +"levn@^0.4.1": + "integrity" "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==" + "resolved" "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" + "version" "0.4.1" + dependencies: + "prelude-ls" "^1.2.1" + "type-check" "~0.4.0" + +"locate-path@^5.0.0": + "integrity" "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==" + "resolved" "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz" + "version" "5.0.0" + dependencies: + "p-locate" "^4.1.0" + +"locate-path@^6.0.0": + "integrity" "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==" + "resolved" "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" + "version" "6.0.0" + dependencies: + "p-locate" "^5.0.0" + +"lodash.flattendeep@^4.4.0": + "integrity" "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==" + "resolved" "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz" + "version" "4.4.0" + +"lodash.merge@^4.6.2": + "integrity" "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + "resolved" "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" + "version" "4.6.2" + +"log-symbols@4.1.0": + "integrity" "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==" + "resolved" "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz" + "version" "4.1.0" + dependencies: + "chalk" "^4.1.0" + "is-unicode-supported" "^0.1.0" + +"lru-cache@^6.0.0": + "integrity" "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==" + "resolved" "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" + "version" "6.0.0" + dependencies: + "yallist" "^4.0.0" + +"lunr@^2.3.9": + "integrity" "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==" + "resolved" "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz" + "version" "2.3.9" + +"magic-string@^0.25.7": + "integrity" "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==" + "resolved" "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz" + "version" "0.25.9" + dependencies: + "sourcemap-codec" "^1.4.8" + +"magic-string@^0.26.1": + "integrity" "sha512-ndThHmvgtieXe8J/VGPjG+Apu7v7ItcD5mhEIvOscWjPF/ccOiLxHaSuCAS2G+3x4GKsAbT8u7zdyamupui8Tg==" + "resolved" "https://registry.npmjs.org/magic-string/-/magic-string-0.26.1.tgz" + "version" "0.26.1" + dependencies: + "sourcemap-codec" "^1.4.8" + +"make-dir@^3.0.0", "make-dir@^3.0.2": + "integrity" "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==" + "resolved" "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz" + "version" "3.1.0" + dependencies: + "semver" "^6.0.0" + +"make-error@^1.1.1": + "integrity" "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + "resolved" "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" + "version" "1.3.6" + +"marked@^4.0.18": + "integrity" "sha512-wbLDJ7Zh0sqA0Vdg6aqlbT+yPxqLblpAZh1mK2+AO2twQkPywvvqQNfEPVwSSRjZ7dZcdeVBIAgiO7MMp3Dszw==" + "resolved" "https://registry.npmjs.org/marked/-/marked-4.0.18.tgz" + "version" "4.0.18" + +"merge2@^1.3.0", "merge2@^1.4.1": + "integrity" "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" + "resolved" "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" + "version" "1.4.1" + +"micromatch@^4.0.4": + "integrity" "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==" + "resolved" "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz" + "version" "4.0.4" + dependencies: + "braces" "^3.0.1" + "picomatch" "^2.2.3" + +"minimatch@^3.0.4", "minimatch@^3.1.2": + "integrity" "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==" + "resolved" "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + "version" "3.1.2" + dependencies: + "brace-expansion" "^1.1.7" + +"minimatch@^5.1.0": + "integrity" "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==" + "resolved" "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz" + "version" "5.1.0" + dependencies: + "brace-expansion" "^2.0.1" + +"minimatch@5.0.1": + "integrity" "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==" + "resolved" "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz" + "version" "5.0.1" + dependencies: + "brace-expansion" "^2.0.1" + +"minimist@^1.2.5": + "integrity" "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + "resolved" "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz" + "version" "1.2.6" + +"mocha@^10.0.0": + "integrity" "sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==" + "resolved" "https://registry.npmjs.org/mocha/-/mocha-10.0.0.tgz" + "version" "10.0.0" + dependencies: + "@ungap/promise-all-settled" "1.1.2" + "ansi-colors" "4.1.1" + "browser-stdout" "1.3.1" + "chokidar" "3.5.3" + "debug" "4.3.4" + "diff" "5.0.0" + "escape-string-regexp" "4.0.0" + "find-up" "5.0.0" + "glob" "7.2.0" + "he" "1.2.0" + "js-yaml" "4.1.0" + "log-symbols" "4.1.0" + "minimatch" "5.0.1" + "ms" "2.1.3" + "nanoid" "3.3.3" + "serialize-javascript" "6.0.0" + "strip-json-comments" "3.1.1" + "supports-color" "8.1.1" + "workerpool" "6.2.1" + "yargs" "16.2.0" + "yargs-parser" "20.2.4" + "yargs-unparser" "2.0.0" + +"ms@2.1.2": + "integrity" "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "resolved" "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" + "version" "2.1.2" + +"ms@2.1.3": + "integrity" "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "resolved" "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + "version" "2.1.3" + +"nanoid@3.3.3": + "integrity" "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==" + "resolved" "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz" + "version" "3.3.3" + +"natural-compare@^1.4.0": + "integrity" "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + "resolved" "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" + "version" "1.4.0" + +"node-preload@^0.2.1": + "integrity" "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==" + "resolved" "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz" + "version" "0.2.1" + dependencies: + "process-on-spawn" "^1.0.0" + +"node-releases@^2.0.2": + "integrity" "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==" + "resolved" "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz" + "version" "2.0.2" + +"normalize-path@^3.0.0", "normalize-path@~3.0.0": + "integrity" "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + "resolved" "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" + "version" "3.0.0" + +"nyc@^15.1.0": + "integrity" "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==" + "resolved" "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz" + "version" "15.1.0" + dependencies: + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + "caching-transform" "^4.0.0" + "convert-source-map" "^1.7.0" + "decamelize" "^1.2.0" + "find-cache-dir" "^3.2.0" + "find-up" "^4.1.0" + "foreground-child" "^2.0.0" + "get-package-type" "^0.1.0" + "glob" "^7.1.6" + "istanbul-lib-coverage" "^3.0.0" + "istanbul-lib-hook" "^3.0.0" + "istanbul-lib-instrument" "^4.0.0" + "istanbul-lib-processinfo" "^2.0.2" + "istanbul-lib-report" "^3.0.0" + "istanbul-lib-source-maps" "^4.0.0" + "istanbul-reports" "^3.0.2" + "make-dir" "^3.0.0" + "node-preload" "^0.2.1" + "p-map" "^3.0.0" + "process-on-spawn" "^1.0.0" + "resolve-from" "^5.0.0" + "rimraf" "^3.0.0" + "signal-exit" "^3.0.2" + "spawn-wrap" "^2.0.0" + "test-exclude" "^6.0.0" + "yargs" "^15.0.2" + +"object-inspect@^1.12.0", "object-inspect@^1.9.0": + "integrity" "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==" + "resolved" "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz" + "version" "1.12.0" + +"object-is@^1.0.1": + "integrity" "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==" + "resolved" "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz" + "version" "1.1.5" + dependencies: + "call-bind" "^1.0.2" + "define-properties" "^1.1.3" + +"object-keys@^1.0.12", "object-keys@^1.1.1": + "integrity" "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + "resolved" "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" + "version" "1.1.1" + +"object.assign@^4.1.2": + "integrity" "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==" + "resolved" "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz" + "version" "4.1.2" + dependencies: + "call-bind" "^1.0.0" + "define-properties" "^1.1.3" + "has-symbols" "^1.0.1" + "object-keys" "^1.1.1" + +"once@^1.3.0": + "integrity" "sha1-WDsap3WWHUsROsF9nFC6753Xa9E= sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==" + "resolved" "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + "version" "1.4.0" + dependencies: + "wrappy" "1" + +"optionator@^0.9.1": + "integrity" "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==" + "resolved" "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz" + "version" "0.9.1" + dependencies: + "deep-is" "^0.1.3" + "fast-levenshtein" "^2.0.6" + "levn" "^0.4.1" + "prelude-ls" "^1.2.1" + "type-check" "^0.4.0" + "word-wrap" "^1.2.3" + +"p-limit@^2.2.0": + "integrity" "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==" + "resolved" "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" + "version" "2.3.0" + dependencies: + "p-try" "^2.0.0" + +"p-limit@^3.0.2": + "integrity" "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==" + "resolved" "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" + "version" "3.1.0" + dependencies: + "yocto-queue" "^0.1.0" + +"p-locate@^4.1.0": + "integrity" "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==" + "resolved" "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz" + "version" "4.1.0" + dependencies: + "p-limit" "^2.2.0" + +"p-locate@^5.0.0": + "integrity" "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==" + "resolved" "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz" + "version" "5.0.0" + dependencies: + "p-limit" "^3.0.2" + +"p-map@^3.0.0": + "integrity" "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==" + "resolved" "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "aggregate-error" "^3.0.0" + +"p-try@^2.0.0": + "integrity" "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + "resolved" "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" + "version" "2.2.0" + +"package-hash@^4.0.0": + "integrity" "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==" + "resolved" "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz" + "version" "4.0.0" + dependencies: + "graceful-fs" "^4.1.15" + "hasha" "^5.0.0" + "lodash.flattendeep" "^4.4.0" + "release-zalgo" "^1.0.0" + +"parent-module@^1.0.0": + "integrity" "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==" + "resolved" "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" + "version" "1.0.1" + dependencies: + "callsites" "^3.0.0" + +"path-exists@^4.0.0": + "integrity" "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + "resolved" "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" + "version" "4.0.0" + +"path-is-absolute@^1.0.0": + "integrity" "sha1-F0uSaHNVNP+8es5r9TpanhtcX18= sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" + "resolved" "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + "version" "1.0.1" + +"path-key@^3.1.0": + "integrity" "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + "resolved" "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" + "version" "3.1.1" + +"path-parse@^1.0.7": + "integrity" "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + "resolved" "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" + "version" "1.0.7" + +"path-type@^4.0.0": + "integrity" "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" + "resolved" "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" + "version" "4.0.0" + +"picocolors@^1.0.0": + "integrity" "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "resolved" "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" + "version" "1.0.0" + +"picomatch@^2.0.4", "picomatch@^2.2.1", "picomatch@^2.2.2", "picomatch@^2.2.3": + "integrity" "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + "resolved" "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" + "version" "2.3.1" + +"pkg-dir@^4.1.0": + "integrity" "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==" + "resolved" "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz" + "version" "4.2.0" + dependencies: + "find-up" "^4.0.0" + +"prelude-ls@^1.2.1": + "integrity" "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" + "resolved" "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" + "version" "1.2.1" + +"process-on-spawn@^1.0.0": + "integrity" "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==" + "resolved" "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz" + "version" "1.0.0" + dependencies: + "fromentries" "^1.2.0" + +"punycode@^2.1.0": + "integrity" "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + "resolved" "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" + "version" "2.1.1" + +"punycode@1.4.1": + "integrity" "sha1-wNWmOycYgArY4esPpSachN1BhF4= sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" + "resolved" "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz" + "version" "1.4.1" + +"pvtsutils@^1.3.2": + "integrity" "sha512-+Ipe2iNUyrZz+8K/2IOo+kKikdtfhRKzNpQbruF2URmqPtoqAs8g3xS7TJvFF2GcPXjh7DkqMnpVveRFq4PgEQ==" + "resolved" "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.2.tgz" + "version" "1.3.2" + dependencies: + "tslib" "^2.4.0" + +"pvutils@^1.1.3": + "integrity" "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==" + "resolved" "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz" + "version" "1.1.3" + +"queue-microtask@^1.2.2": + "integrity" "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" + "resolved" "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" + "version" "1.2.3" + +"ramda@^0.25.0": + "integrity" "sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ==" + "resolved" "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz" + "version" "0.25.0" + +"ramda@^0.26.1": + "integrity" "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==" + "resolved" "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz" + "version" "0.26.1" + +"randombytes@^2.1.0": + "integrity" "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==" + "resolved" "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz" + "version" "2.1.0" + dependencies: + "safe-buffer" "^5.1.0" + +"readdirp@~3.6.0": + "integrity" "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==" + "resolved" "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" + "version" "3.6.0" + dependencies: + "picomatch" "^2.2.1" + +"regexpp@^3.2.0": + "integrity" "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==" + "resolved" "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz" + "version" "3.2.0" + +"release-zalgo@^1.0.0": + "integrity" "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA= sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==" + "resolved" "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz" + "version" "1.0.0" + dependencies: + "es6-error" "^4.0.1" + +"require-directory@^2.1.1": + "integrity" "sha1-jGStX9MNqxyXbiNE/+f3kqam30I= sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" + "resolved" "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" + "version" "2.1.1" + +"require-main-filename@^2.0.0": + "integrity" "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + "resolved" "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz" + "version" "2.0.0" + +"resolve-from@^4.0.0": + "integrity" "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + "resolved" "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" + "version" "4.0.0" + +"resolve-from@^5.0.0": + "integrity" "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" + "resolved" "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz" + "version" "5.0.0" + +"resolve@^1.17.0", "resolve@^1.19.0", "resolve@^1.20.0": + "integrity" "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==" + "resolved" "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz" + "version" "1.22.0" + dependencies: + "is-core-module" "^2.8.1" + "path-parse" "^1.0.7" + "supports-preserve-symlinks-flag" "^1.0.0" + +"reusify@^1.0.4": + "integrity" "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + "resolved" "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" + "version" "1.0.4" + +"rimraf@^3.0.0", "rimraf@^3.0.2": + "integrity" "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==" + "resolved" "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" + "version" "3.0.2" + dependencies: + "glob" "^7.1.3" + +"rollup-plugin-dts@^4.2.2": + "integrity" "sha512-A3g6Rogyko/PXeKoUlkjxkP++8UDVpgA7C+Tdl77Xj4fgEaIjPSnxRmR53EzvoYy97VMVwLAOcWJudaVAuxneQ==" + "resolved" "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-4.2.2.tgz" + "version" "4.2.2" + dependencies: + "magic-string" "^0.26.1" + optionalDependencies: + "@babel/code-frame" "^7.16.7" + +"rollup-plugin-typescript2@^0.32.1": + "integrity" "sha512-RanO8bp1WbeMv0bVlgcbsFNCn+Y3rX7wF97SQLDxf0fMLsg0B/QFF005t4AsGUcDgF3aKJHoqt4JF2xVaABeKw==" + "resolved" "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.32.1.tgz" + "version" "0.32.1" + dependencies: + "@rollup/pluginutils" "^4.1.2" + "find-cache-dir" "^3.3.2" + "fs-extra" "^10.0.0" + "resolve" "^1.20.0" + "tslib" "^2.4.0" + +"rollup@^1.20.0||^2.0.0", "rollup@^2.42.0", "rollup@^2.55", "rollup@^2.68.0", "rollup@^2.77.2", "rollup@>=1.26.3": + "integrity" "sha512-m/4YzYgLcpMQbxX3NmAqDvwLATZzxt8bIegO78FZLl+lAgKJBd1DRAOeEiZcKOIOPjxE6ewHWHNgGEalFXuz1g==" + "resolved" "https://registry.npmjs.org/rollup/-/rollup-2.77.2.tgz" + "version" "2.77.2" + optionalDependencies: + "fsevents" "~2.3.2" + +"run-parallel@^1.1.9": + "integrity" "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==" + "resolved" "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" + "version" "1.2.0" + dependencies: + "queue-microtask" "^1.2.2" + +"safe-buffer@^5.1.0", "safe-buffer@^5.1.2", "safe-buffer@~5.1.1": + "integrity" "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "resolved" "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" + "version" "5.1.2" + +"semver@^6.0.0": + "integrity" "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "resolved" "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" + "version" "6.3.0" + +"semver@^6.3.0": + "integrity" "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "resolved" "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" + "version" "6.3.0" + +"semver@^7.3.5", "semver@^7.3.7": + "integrity" "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==" + "resolved" "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz" + "version" "7.3.7" + dependencies: + "lru-cache" "^6.0.0" + +"serialize-javascript@6.0.0": + "integrity" "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==" + "resolved" "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz" + "version" "6.0.0" + dependencies: + "randombytes" "^2.1.0" + +"set-blocking@^2.0.0": + "integrity" "sha1-BF+XgtARrppoA93TgrJDkrPYkPc= sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + "resolved" "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" + "version" "2.0.0" + +"shebang-command@^2.0.0": + "integrity" "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==" + "resolved" "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" + "version" "2.0.0" + dependencies: + "shebang-regex" "^3.0.0" + +"shebang-regex@^3.0.0": + "integrity" "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + "resolved" "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" + "version" "3.0.0" + +"shiki@^0.10.1": + "integrity" "sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng==" + "resolved" "https://registry.npmjs.org/shiki/-/shiki-0.10.1.tgz" + "version" "0.10.1" + dependencies: + "jsonc-parser" "^3.0.0" + "vscode-oniguruma" "^1.6.1" + "vscode-textmate" "5.2.0" + +"side-channel@^1.0.4": + "integrity" "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==" + "resolved" "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz" + "version" "1.0.4" + dependencies: + "call-bind" "^1.0.0" + "get-intrinsic" "^1.0.2" + "object-inspect" "^1.9.0" + +"signal-exit@^3.0.2": + "integrity" "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + "resolved" "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" + "version" "3.0.7" + +"slash@^3.0.0": + "integrity" "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" + "resolved" "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" + "version" "3.0.0" + +"source-map@^0.5.0": + "integrity" "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" + "resolved" "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz" + "version" "0.5.7" + +"source-map@^0.6.1": + "integrity" "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "resolved" "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" + "version" "0.6.1" + +"sourcemap-codec@^1.4.8": + "integrity" "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" + "resolved" "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz" + "version" "1.4.8" + +"spawn-wrap@^2.0.0": + "integrity" "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==" + "resolved" "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz" + "version" "2.0.0" + dependencies: + "foreground-child" "^2.0.0" + "is-windows" "^1.0.2" + "make-dir" "^3.0.0" + "rimraf" "^3.0.0" + "signal-exit" "^3.0.2" + "which" "^2.0.1" + +"sprintf-js@~1.0.2": + "integrity" "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + "resolved" "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" + "version" "1.0.3" + +"string-width@^4.1.0", "string-width@^4.2.0": + "integrity" "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==" + "resolved" "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + "version" "4.2.3" + dependencies: + "emoji-regex" "^8.0.0" + "is-fullwidth-code-point" "^3.0.0" + "strip-ansi" "^6.0.1" + +"string.prototype.trimend@^1.0.4": + "integrity" "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==" + "resolved" "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz" + "version" "1.0.4" + dependencies: + "call-bind" "^1.0.2" + "define-properties" "^1.1.3" + +"string.prototype.trimstart@^1.0.4": + "integrity" "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==" + "resolved" "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz" + "version" "1.0.4" + dependencies: + "call-bind" "^1.0.2" + "define-properties" "^1.1.3" + +"strip-ansi@^6.0.0", "strip-ansi@^6.0.1": + "integrity" "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==" + "resolved" "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + "version" "6.0.1" + dependencies: + "ansi-regex" "^5.0.1" + +"strip-bom@^4.0.0": + "integrity" "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==" + "resolved" "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz" + "version" "4.0.0" + +"strip-json-comments@^3.1.0", "strip-json-comments@^3.1.1", "strip-json-comments@3.1.1": + "integrity" "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" + "resolved" "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" + "version" "3.1.1" + +"supports-color@^5.3.0": + "integrity" "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==" + "resolved" "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" + "version" "5.5.0" + dependencies: + "has-flag" "^3.0.0" + +"supports-color@^7.1.0": + "integrity" "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==" + "resolved" "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" + "version" "7.2.0" + dependencies: + "has-flag" "^4.0.0" + +"supports-color@8.1.1": + "integrity" "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==" + "resolved" "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" + "version" "8.1.1" + dependencies: + "has-flag" "^4.0.0" + +"supports-preserve-symlinks-flag@^1.0.0": + "integrity" "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + "resolved" "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" + "version" "1.0.0" + +"test-exclude@^6.0.0": + "integrity" "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==" + "resolved" "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz" + "version" "6.0.0" + dependencies: + "@istanbuljs/schema" "^0.1.2" + "glob" "^7.1.4" + "minimatch" "^3.0.4" + +"text-encoding@^0.7.0": + "integrity" "sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA==" + "resolved" "https://registry.npmjs.org/text-encoding/-/text-encoding-0.7.0.tgz" + "version" "0.7.0" + +"text-table@^0.2.0": + "integrity" "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + "resolved" "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" + "version" "0.2.0" + +"to-fast-properties@^2.0.0": + "integrity" "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" + "resolved" "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz" + "version" "2.0.0" + +"to-regex-range@^5.0.1": + "integrity" "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==" + "resolved" "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" + "version" "5.0.1" + dependencies: + "is-number" "^7.0.0" + +"ts-node@^10.9.1": + "integrity" "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==" + "resolved" "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz" + "version" "10.9.1" + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + "acorn" "^8.4.1" + "acorn-walk" "^8.1.1" + "arg" "^4.1.0" + "create-require" "^1.1.0" + "diff" "^4.0.1" + "make-error" "^1.1.1" + "v8-compile-cache-lib" "^3.0.1" + "yn" "3.1.1" + +"tslib@^1.8.1": + "integrity" "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "resolved" "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" + "version" "1.14.1" + +"tslib@^2.0.0", "tslib@^2.3.1", "tslib@^2.4.0": + "integrity" "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + "resolved" "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz" + "version" "2.4.0" + +"tsutils@^3.21.0": + "integrity" "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==" + "resolved" "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz" + "version" "3.21.0" + dependencies: + "tslib" "^1.8.1" + +"type-check@^0.4.0", "type-check@~0.4.0": + "integrity" "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==" + "resolved" "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" + "version" "0.4.0" + dependencies: + "prelude-ls" "^1.2.1" + +"type-fest@^0.20.2": + "integrity" "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" + "resolved" "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" + "version" "0.20.2" + +"type-fest@^0.8.0": + "integrity" "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" + "resolved" "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz" + "version" "0.8.1" + +"typedarray-to-buffer@^3.1.5": + "integrity" "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==" + "resolved" "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz" + "version" "3.1.5" + dependencies: + "is-typedarray" "^1.0.0" + +"typedoc@^0.23.10": + "integrity" "sha512-03EUiu/ZuScUBMnY6p0lY+HTH8SwhzvRE3gImoemdPDWXPXlks83UGTx++lyquWeB1MTwm9D9Ca8RIjkK3AFfQ==" + "resolved" "https://registry.npmjs.org/typedoc/-/typedoc-0.23.10.tgz" + "version" "0.23.10" + dependencies: + "lunr" "^2.3.9" + "marked" "^4.0.18" + "minimatch" "^5.1.0" + "shiki" "^0.10.1" + +"typescript@^3.7.5 || ^4.0.0", "typescript@^4.1", "typescript@^4.7.4", "typescript@>=2.4.0", "typescript@>=2.7", "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta", "typescript@4.6.x || 4.7.x": + "integrity" "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==" + "resolved" "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz" + "version" "4.7.4" + +"unbox-primitive@^1.0.1": + "integrity" "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==" + "resolved" "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz" + "version" "1.0.1" + dependencies: + "function-bind" "^1.1.1" + "has-bigints" "^1.0.1" + "has-symbols" "^1.0.2" + "which-boxed-primitive" "^1.0.2" + +"universalify@^2.0.0": + "integrity" "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" + "resolved" "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz" + "version" "2.0.0" + +"uri-js@^4.2.2": + "integrity" "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==" + "resolved" "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" + "version" "4.4.1" + dependencies: + "punycode" "^2.1.0" + +"util@^0.12.0": + "integrity" "sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==" + "resolved" "https://registry.npmjs.org/util/-/util-0.12.4.tgz" + "version" "0.12.4" + dependencies: + "inherits" "^2.0.3" + "is-arguments" "^1.0.4" + "is-generator-function" "^1.0.7" + "is-typed-array" "^1.1.3" + "safe-buffer" "^5.1.2" + "which-typed-array" "^1.1.2" + +"uuid@^3.3.3": + "integrity" "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + "resolved" "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz" + "version" "3.4.0" + +"v8-compile-cache-lib@^3.0.1": + "integrity" "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" + "resolved" "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz" + "version" "3.0.1" + +"v8-compile-cache@^2.0.3": + "integrity" "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==" + "resolved" "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz" + "version" "2.3.0" + +"vscode-oniguruma@^1.6.1": + "integrity" "sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA==" + "resolved" "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.2.tgz" + "version" "1.6.2" + +"vscode-textmate@5.2.0": + "integrity" "sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==" + "resolved" "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-5.2.0.tgz" + "version" "5.2.0" + +"webcrypto-core@^1.7.4": + "integrity" "sha512-gaExY2/3EHQlRNNNVSrbG2Cg94Rutl7fAaKILS1w8ZDhGxdFOaw6EbCfHIxPy9vt/xwp5o0VQAx9aySPF6hU1A==" + "resolved" "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.7.5.tgz" + "version" "1.7.5" + dependencies: + "@peculiar/asn1-schema" "^2.1.6" + "@peculiar/json-schema" "^1.1.12" + "asn1js" "^3.0.1" + "pvtsutils" "^1.3.2" + "tslib" "^2.4.0" + +"which-boxed-primitive@^1.0.2": + "integrity" "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==" + "resolved" "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz" + "version" "1.0.2" + dependencies: + "is-bigint" "^1.0.1" + "is-boolean-object" "^1.1.0" + "is-number-object" "^1.0.4" + "is-string" "^1.0.5" + "is-symbol" "^1.0.3" + +"which-module@^2.0.0": + "integrity" "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==" + "resolved" "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz" + "version" "2.0.0" + +"which-typed-array@^1.1.2": + "integrity" "sha512-vjxaB4nfDqwKI0ws7wZpxIlde1XrLX5uB0ZjpfshgmapJMD7jJWhZI+yToJTqaFByF0eNBcYxbjmCzoRP7CfEw==" + "resolved" "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.7.tgz" + "version" "1.1.7" + dependencies: + "available-typed-arrays" "^1.0.5" + "call-bind" "^1.0.2" + "es-abstract" "^1.18.5" + "foreach" "^2.0.5" + "has-tostringtag" "^1.0.0" + "is-typed-array" "^1.1.7" + +"which@^2.0.1": + "integrity" "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==" + "resolved" "https://registry.npmjs.org/which/-/which-2.0.2.tgz" + "version" "2.0.2" + dependencies: + "isexe" "^2.0.0" + +"word-wrap@^1.2.3": + "integrity" "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" + "resolved" "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz" + "version" "1.2.3" + +"workerpool@6.2.1": + "integrity" "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==" + "resolved" "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz" + "version" "6.2.1" + +"wrap-ansi@^6.2.0": + "integrity" "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==" + "resolved" "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz" + "version" "6.2.0" + dependencies: + "ansi-styles" "^4.0.0" + "string-width" "^4.1.0" + "strip-ansi" "^6.0.0" + +"wrap-ansi@^7.0.0": + "integrity" "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==" + "resolved" "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + "version" "7.0.0" + dependencies: + "ansi-styles" "^4.0.0" + "string-width" "^4.1.0" + "strip-ansi" "^6.0.0" + +"wrappy@1": + "integrity" "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "resolved" "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + "version" "1.0.2" + +"write-file-atomic@^3.0.0": + "integrity" "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==" + "resolved" "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz" + "version" "3.0.3" + dependencies: + "imurmurhash" "^0.1.4" + "is-typedarray" "^1.0.0" + "signal-exit" "^3.0.2" + "typedarray-to-buffer" "^3.1.5" + +"y18n@^4.0.0": + "integrity" "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + "resolved" "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz" + "version" "4.0.3" + +"y18n@^5.0.5": + "integrity" "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + "resolved" "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" + "version" "5.0.8" + +"yallist@^4.0.0": + "integrity" "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "resolved" "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" + "version" "4.0.0" + +"yargs-parser@^18.1.2": + "integrity" "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==" + "resolved" "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz" + "version" "18.1.3" + dependencies: + "camelcase" "^5.0.0" + "decamelize" "^1.2.0" + +"yargs-parser@^20.2.2", "yargs-parser@20.2.4": + "integrity" "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==" + "resolved" "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz" + "version" "20.2.4" + +"yargs-unparser@2.0.0": + "integrity" "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==" + "resolved" "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz" + "version" "2.0.0" + dependencies: + "camelcase" "^6.0.0" + "decamelize" "^4.0.0" + "flat" "^5.0.2" + "is-plain-obj" "^2.1.0" + +"yargs@^15.0.2": + "integrity" "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==" + "resolved" "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz" + "version" "15.4.1" + dependencies: + "cliui" "^6.0.0" + "decamelize" "^1.2.0" + "find-up" "^4.1.0" + "get-caller-file" "^2.0.1" + "require-directory" "^2.1.1" + "require-main-filename" "^2.0.0" + "set-blocking" "^2.0.0" + "string-width" "^4.2.0" + "which-module" "^2.0.0" + "y18n" "^4.0.0" + "yargs-parser" "^18.1.2" + +"yargs@16.2.0": + "integrity" "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==" + "resolved" "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz" + "version" "16.2.0" + dependencies: + "cliui" "^7.0.2" + "escalade" "^3.1.1" + "get-caller-file" "^2.0.5" + "require-directory" "^2.1.1" + "string-width" "^4.2.0" + "y18n" "^5.0.5" + "yargs-parser" "^20.2.2" + +"yn@3.1.1": + "integrity" "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" + "resolved" "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz" + "version" "3.1.1" + +"yocto-queue@^0.1.0": + "integrity" "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + "resolved" "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" + "version" "0.1.0" diff --git a/third_party/js/cfworker/LICENSE.md b/third_party/js/cfworker/LICENSE.md new file mode 100644 index 0000000000..85bcd787b5 --- /dev/null +++ b/third_party/js/cfworker/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Jeremy Danyow + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third_party/js/cfworker/build.sh b/third_party/js/cfworker/build.sh new file mode 100644 index 0000000000..a11d4356de --- /dev/null +++ b/third_party/js/cfworker/build.sh @@ -0,0 +1,42 @@ +#!/bin/sh +set -euo pipefail + +# Path to mach relative to /third_party/js/cfworker +MACH=$(realpath "../../../mach") + +if [[ $(uname -a) == *MSYS* ]]; then + MACH="python ${MACH}" +fi + +NODE="${MACH} node" +NPM="${MACH} npm" + +# Delete empty vestigial directories. +rm -rf .changeset/ .github/ .vscode/ + +# Patch typescript config to ensure we have LF endings. +patch tsconfig-base.json tsconfig-base.json.patch + +cd packages/json-schema + +# Install dependencies. +${NPM} install --also=dev + +# Compile TypeScript into JavaScript. +${NPM} run build + +# Install rollup and bundle into a single module. +${NPM} install rollup@~2.67.x +${NODE} node_modules/rollup/dist/bin/rollup \ + dist/index.js \ + --file json-schema.js \ + --format cjs + +cd ../.. + +# Patch the CommonJS module into a regular JS file and include a copyright notice. +patch packages/json-schema/json-schema.js json-schema.js.patch +awk -f exports.awk packages/json-schema/json-schema.js >json-schema.js + +# Remove source files we no longer need. +rm -rf packages/ tsconfig-base.json diff --git a/third_party/js/cfworker/exports.awk b/third_party/js/cfworker/exports.awk new file mode 100644 index 0000000000..17f63d7bbf --- /dev/null +++ b/third_party/js/cfworker/exports.awk @@ -0,0 +1,20 @@ +#!/bin/awk -f + +BEGIN { + n_exports = 0; +} + +{ + if ($0 ~ /^exports.*=/) { + exports[n_exports] = substr($3, 0, length($3) - 1); + n_exports++; + } else { + print; + } +} + +END { + for (i = 0; i < n_exports; i++) { + print "this." exports[i] " = " exports[i] ";"; + } +} diff --git a/third_party/js/cfworker/json-schema.js b/third_party/js/cfworker/json-schema.js new file mode 100644 index 0000000000..c9d6b68a34 --- /dev/null +++ b/third_party/js/cfworker/json-schema.js @@ -0,0 +1,1180 @@ +/* + * Copyright (c) 2020 Jeremy Danyow + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +'use strict'; + +function deepCompareStrict(a, b) { + const typeofa = typeof a; + if (typeofa !== typeof b) { + return false; + } + if (Array.isArray(a)) { + if (!Array.isArray(b)) { + return false; + } + const length = a.length; + if (length !== b.length) { + return false; + } + for (let i = 0; i < length; i++) { + if (!deepCompareStrict(a[i], b[i])) { + return false; + } + } + return true; + } + if (typeofa === 'object') { + if (!a || !b) { + return a === b; + } + const aKeys = Object.keys(a); + const bKeys = Object.keys(b); + const length = aKeys.length; + if (length !== bKeys.length) { + return false; + } + for (const k of aKeys) { + if (!deepCompareStrict(a[k], b[k])) { + return false; + } + } + return true; + } + return a === b; +} + +function encodePointer(p) { + return encodeURI(escapePointer(p)); +} +function escapePointer(p) { + return p.replace(/~/g, '~0').replace(/\//g, '~1'); +} + +const schemaKeyword = { + additionalItems: true, + unevaluatedItems: true, + items: true, + contains: true, + additionalProperties: true, + unevaluatedProperties: true, + propertyNames: true, + not: true, + if: true, + then: true, + else: true +}; +const schemaArrayKeyword = { + prefixItems: true, + items: true, + allOf: true, + anyOf: true, + oneOf: true +}; +const schemaMapKeyword = { + $defs: true, + definitions: true, + properties: true, + patternProperties: true, + dependentSchemas: true +}; +const ignoredKeyword = { + id: true, + $id: true, + $ref: true, + $schema: true, + $anchor: true, + $vocabulary: true, + $comment: true, + default: true, + enum: true, + const: true, + required: true, + type: true, + maximum: true, + minimum: true, + exclusiveMaximum: true, + exclusiveMinimum: true, + multipleOf: true, + maxLength: true, + minLength: true, + pattern: true, + format: true, + maxItems: true, + minItems: true, + uniqueItems: true, + maxProperties: true, + minProperties: true +}; +let initialBaseURI = typeof self !== 'undefined' && self.location + ? + new URL(self.location.origin + self.location.pathname + location.search) + : new URL('https://github.com/cfworker'); +function dereference(schema, lookup = Object.create(null), baseURI = initialBaseURI, basePointer = '') { + if (schema && typeof schema === 'object' && !Array.isArray(schema)) { + const id = schema.$id || schema.id; + if (id) { + const url = new URL(id, baseURI.href); + if (url.hash.length > 1) { + lookup[url.href] = schema; + } + else { + url.hash = ''; + if (basePointer === '') { + baseURI = url; + } + else { + dereference(schema, lookup, baseURI); + } + } + } + } + else if (schema !== true && schema !== false) { + return lookup; + } + const schemaURI = baseURI.href + (basePointer ? '#' + basePointer : ''); + if (lookup[schemaURI] !== undefined) { + throw new Error(`Duplicate schema URI "${schemaURI}".`); + } + lookup[schemaURI] = schema; + if (schema === true || schema === false) { + return lookup; + } + if (schema.__absolute_uri__ === undefined) { + Object.defineProperty(schema, '__absolute_uri__', { + enumerable: false, + value: schemaURI + }); + } + if (schema.$ref && schema.__absolute_ref__ === undefined) { + const url = new URL(schema.$ref, baseURI.href); + url.hash = url.hash; + Object.defineProperty(schema, '__absolute_ref__', { + enumerable: false, + value: url.href + }); + } + if (schema.$recursiveRef && schema.__absolute_recursive_ref__ === undefined) { + const url = new URL(schema.$recursiveRef, baseURI.href); + url.hash = url.hash; + Object.defineProperty(schema, '__absolute_recursive_ref__', { + enumerable: false, + value: url.href + }); + } + if (schema.$anchor) { + const url = new URL('#' + schema.$anchor, baseURI.href); + lookup[url.href] = schema; + } + for (let key in schema) { + if (ignoredKeyword[key]) { + continue; + } + const keyBase = `${basePointer}/${encodePointer(key)}`; + const subSchema = schema[key]; + if (Array.isArray(subSchema)) { + if (schemaArrayKeyword[key]) { + const length = subSchema.length; + for (let i = 0; i < length; i++) { + dereference(subSchema[i], lookup, baseURI, `${keyBase}/${i}`); + } + } + } + else if (schemaMapKeyword[key]) { + for (let subKey in subSchema) { + dereference(subSchema[subKey], lookup, baseURI, `${keyBase}/${encodePointer(subKey)}`); + } + } + else { + dereference(subSchema, lookup, baseURI, keyBase); + } + } + return lookup; +} + +const DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/; +const DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; +const TIME = /^(\d\d):(\d\d):(\d\d)(\.\d+)?(z|[+-]\d\d(?::?\d\d)?)?$/i; +const HOSTNAME = /^(?=.{1,253}\.?$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*\.?$/i; +const URIREF = /^(?:[a-z][a-z0-9+\-.]*:)?(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'"()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?(?:\?(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i; +const URITEMPLATE = /^(?:(?:[^\x00-\x20"'<>%\\^`{|}]|%[0-9a-f]{2})|\{[+#./;?&=,!@|]?(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?(?:,(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?)*\})*$/i; +const URL_ = /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u{00a1}-\u{ffff}0-9]+-?)*[a-z\u{00a1}-\u{ffff}0-9]+)(?:\.(?:[a-z\u{00a1}-\u{ffff}0-9]+-?)*[a-z\u{00a1}-\u{ffff}0-9]+)*(?:\.(?:[a-z\u{00a1}-\u{ffff}]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$/iu; +const UUID = /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i; +const JSON_POINTER = /^(?:\/(?:[^~/]|~0|~1)*)*$/; +const JSON_POINTER_URI_FRAGMENT = /^#(?:\/(?:[a-z0-9_\-.!$&'()*+,;:=@]|%[0-9a-f]{2}|~0|~1)*)*$/i; +const RELATIVE_JSON_POINTER = /^(?:0|[1-9][0-9]*)(?:#|(?:\/(?:[^~/]|~0|~1)*)*)$/; +const FASTDATE = /^\d\d\d\d-[0-1]\d-[0-3]\d$/; +const FASTTIME = /^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)?$/i; +const FASTDATETIME = /^\d\d\d\d-[0-1]\d-[0-3]\d[t\s](?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)$/i; +const FASTURIREFERENCE = /^(?:(?:[a-z][a-z0-9+-.]*:)?\/?\/)?(?:[^\\\s#][^\s#]*)?(?:#[^\\\s]*)?$/i; +const EMAIL = (input) => { + if (input[0] === '"') + return false; + const [name, host, ...rest] = input.split('@'); + if (!name || + !host || + rest.length !== 0 || + name.length > 64 || + host.length > 253) + return false; + if (name[0] === '.' || name.endsWith('.') || name.includes('..')) + return false; + if (!/^[a-z0-9.-]+$/i.test(host) || + !/^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+$/i.test(name)) + return false; + return host + .split('.') + .every(part => /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/i.test(part)); +}; +const IPV4 = /^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/; +const IPV6 = /^((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))$/i; +const DURATION = (input) => input.length > 1 && + input.length < 80 && + (/^P\d+([.,]\d+)?W$/.test(input) || + (/^P[\dYMDTHS]*(\d[.,]\d+)?[YMDHS]$/.test(input) && + /^P([.,\d]+Y)?([.,\d]+M)?([.,\d]+D)?(T([.,\d]+H)?([.,\d]+M)?([.,\d]+S)?)?$/.test(input))); +function bind(r) { + return r.test.bind(r); +} +const fullFormat = { + date, + time: time.bind(undefined, false), + 'date-time': date_time, + duration: DURATION, + uri, + 'uri-reference': bind(URIREF), + 'uri-template': bind(URITEMPLATE), + url: bind(URL_), + email: EMAIL, + hostname: bind(HOSTNAME), + ipv4: bind(IPV4), + ipv6: bind(IPV6), + regex: regex, + uuid: bind(UUID), + 'json-pointer': bind(JSON_POINTER), + 'json-pointer-uri-fragment': bind(JSON_POINTER_URI_FRAGMENT), + 'relative-json-pointer': bind(RELATIVE_JSON_POINTER) +}; +const fastFormat = { + ...fullFormat, + date: bind(FASTDATE), + time: bind(FASTTIME), + 'date-time': bind(FASTDATETIME), + 'uri-reference': bind(FASTURIREFERENCE) +}; +function isLeapYear(year) { + return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0); +} +function date(str) { + const matches = str.match(DATE); + if (!matches) + return false; + const year = +matches[1]; + const month = +matches[2]; + const day = +matches[3]; + return (month >= 1 && + month <= 12 && + day >= 1 && + day <= (month == 2 && isLeapYear(year) ? 29 : DAYS[month])); +} +function time(full, str) { + const matches = str.match(TIME); + if (!matches) + return false; + const hour = +matches[1]; + const minute = +matches[2]; + const second = +matches[3]; + const timeZone = !!matches[5]; + return (((hour <= 23 && minute <= 59 && second <= 59) || + (hour == 23 && minute == 59 && second == 60)) && + (!full || timeZone)); +} +const DATE_TIME_SEPARATOR = /t|\s/i; +function date_time(str) { + const dateTime = str.split(DATE_TIME_SEPARATOR); + return dateTime.length == 2 && date(dateTime[0]) && time(true, dateTime[1]); +} +const NOT_URI_FRAGMENT = /\/|:/; +const URI_PATTERN = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i; +function uri(str) { + return NOT_URI_FRAGMENT.test(str) && URI_PATTERN.test(str); +} +const Z_ANCHOR = /[^\\]\\Z/; +function regex(str) { + if (Z_ANCHOR.test(str)) + return false; + try { + new RegExp(str); + return true; + } + catch (e) { + return false; + } +} + +function ucs2length(s) { + let result = 0; + let length = s.length; + let index = 0; + let charCode; + while (index < length) { + result++; + charCode = s.charCodeAt(index++); + if (charCode >= 0xd800 && charCode <= 0xdbff && index < length) { + charCode = s.charCodeAt(index); + if ((charCode & 0xfc00) == 0xdc00) { + index++; + } + } + } + return result; +} + +function validate(instance, schema, draft = '2019-09', lookup = dereference(schema), shortCircuit = true, recursiveAnchor = null, instanceLocation = '#', schemaLocation = '#', evaluated = Object.create(null)) { + if (schema === true) { + return { valid: true, errors: [] }; + } + if (schema === false) { + return { + valid: false, + errors: [ + { + instanceLocation, + keyword: 'false', + keywordLocation: instanceLocation, + error: 'False boolean schema.' + } + ] + }; + } + const rawInstanceType = typeof instance; + let instanceType; + switch (rawInstanceType) { + case 'boolean': + case 'number': + case 'string': + instanceType = rawInstanceType; + break; + case 'object': + if (instance === null) { + instanceType = 'null'; + } + else if (Array.isArray(instance)) { + instanceType = 'array'; + } + else { + instanceType = 'object'; + } + break; + default: + throw new Error(`Instances of "${rawInstanceType}" type are not supported.`); + } + const { $ref, $recursiveRef, $recursiveAnchor, type: $type, const: $const, enum: $enum, required: $required, not: $not, anyOf: $anyOf, allOf: $allOf, oneOf: $oneOf, if: $if, then: $then, else: $else, format: $format, properties: $properties, patternProperties: $patternProperties, additionalProperties: $additionalProperties, unevaluatedProperties: $unevaluatedProperties, minProperties: $minProperties, maxProperties: $maxProperties, propertyNames: $propertyNames, dependentRequired: $dependentRequired, dependentSchemas: $dependentSchemas, dependencies: $dependencies, prefixItems: $prefixItems, items: $items, additionalItems: $additionalItems, unevaluatedItems: $unevaluatedItems, contains: $contains, minContains: $minContains, maxContains: $maxContains, minItems: $minItems, maxItems: $maxItems, uniqueItems: $uniqueItems, minimum: $minimum, maximum: $maximum, exclusiveMinimum: $exclusiveMinimum, exclusiveMaximum: $exclusiveMaximum, multipleOf: $multipleOf, minLength: $minLength, maxLength: $maxLength, pattern: $pattern, __absolute_ref__, __absolute_recursive_ref__ } = schema; + const errors = []; + if ($recursiveAnchor === true && recursiveAnchor === null) { + recursiveAnchor = schema; + } + if ($recursiveRef === '#') { + const refSchema = recursiveAnchor === null + ? lookup[__absolute_recursive_ref__] + : recursiveAnchor; + const keywordLocation = `${schemaLocation}/$recursiveRef`; + const result = validate(instance, recursiveAnchor === null ? schema : recursiveAnchor, draft, lookup, shortCircuit, refSchema, instanceLocation, keywordLocation, evaluated); + if (!result.valid) { + errors.push({ + instanceLocation, + keyword: '$recursiveRef', + keywordLocation, + error: 'A subschema had errors.' + }, ...result.errors); + } + } + if ($ref !== undefined) { + const uri = __absolute_ref__ || $ref; + const refSchema = lookup[uri]; + if (refSchema === undefined) { + let message = `Unresolved $ref "${$ref}".`; + if (__absolute_ref__ && __absolute_ref__ !== $ref) { + message += ` Absolute URI "${__absolute_ref__}".`; + } + message += `\nKnown schemas:\n- ${Object.keys(lookup).join('\n- ')}`; + throw new Error(message); + } + const keywordLocation = `${schemaLocation}/$ref`; + const result = validate(instance, refSchema, draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, keywordLocation, evaluated); + if (!result.valid) { + errors.push({ + instanceLocation, + keyword: '$ref', + keywordLocation, + error: 'A subschema had errors.' + }, ...result.errors); + } + if (draft === '4' || draft === '7') { + return { valid: errors.length === 0, errors }; + } + } + if (Array.isArray($type)) { + let length = $type.length; + let valid = false; + for (let i = 0; i < length; i++) { + if (instanceType === $type[i] || + ($type[i] === 'integer' && + instanceType === 'number' && + instance % 1 === 0 && + instance === instance)) { + valid = true; + break; + } + } + if (!valid) { + errors.push({ + instanceLocation, + keyword: 'type', + keywordLocation: `${schemaLocation}/type`, + error: `Instance type "${instanceType}" is invalid. Expected "${$type.join('", "')}".` + }); + } + } + else if ($type === 'integer') { + if (instanceType !== 'number' || instance % 1 || instance !== instance) { + errors.push({ + instanceLocation, + keyword: 'type', + keywordLocation: `${schemaLocation}/type`, + error: `Instance type "${instanceType}" is invalid. Expected "${$type}".` + }); + } + } + else if ($type !== undefined && instanceType !== $type) { + errors.push({ + instanceLocation, + keyword: 'type', + keywordLocation: `${schemaLocation}/type`, + error: `Instance type "${instanceType}" is invalid. Expected "${$type}".` + }); + } + if ($const !== undefined) { + if (instanceType === 'object' || instanceType === 'array') { + if (!deepCompareStrict(instance, $const)) { + errors.push({ + instanceLocation, + keyword: 'const', + keywordLocation: `${schemaLocation}/const`, + error: `Instance does not match ${JSON.stringify($const)}.` + }); + } + } + else if (instance !== $const) { + errors.push({ + instanceLocation, + keyword: 'const', + keywordLocation: `${schemaLocation}/const`, + error: `Instance does not match ${JSON.stringify($const)}.` + }); + } + } + if ($enum !== undefined) { + if (instanceType === 'object' || instanceType === 'array') { + if (!$enum.some(value => deepCompareStrict(instance, value))) { + errors.push({ + instanceLocation, + keyword: 'enum', + keywordLocation: `${schemaLocation}/enum`, + error: `Instance does not match any of ${JSON.stringify($enum)}.` + }); + } + } + else if (!$enum.some(value => instance === value)) { + errors.push({ + instanceLocation, + keyword: 'enum', + keywordLocation: `${schemaLocation}/enum`, + error: `Instance does not match any of ${JSON.stringify($enum)}.` + }); + } + } + if ($not !== undefined) { + const keywordLocation = `${schemaLocation}/not`; + const result = validate(instance, $not, draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, keywordLocation); + if (result.valid) { + errors.push({ + instanceLocation, + keyword: 'not', + keywordLocation, + error: 'Instance matched "not" schema.' + }); + } + } + let subEvaluateds = []; + if ($anyOf !== undefined) { + const keywordLocation = `${schemaLocation}/anyOf`; + const errorsLength = errors.length; + let anyValid = false; + for (let i = 0; i < $anyOf.length; i++) { + const subSchema = $anyOf[i]; + const subEvaluated = Object.create(evaluated); + const result = validate(instance, subSchema, draft, lookup, shortCircuit, $recursiveAnchor === true ? recursiveAnchor : null, instanceLocation, `${keywordLocation}/${i}`, subEvaluated); + errors.push(...result.errors); + anyValid = anyValid || result.valid; + if (result.valid) { + subEvaluateds.push(subEvaluated); + } + } + if (anyValid) { + errors.length = errorsLength; + } + else { + errors.splice(errorsLength, 0, { + instanceLocation, + keyword: 'anyOf', + keywordLocation, + error: 'Instance does not match any subschemas.' + }); + } + } + if ($allOf !== undefined) { + const keywordLocation = `${schemaLocation}/allOf`; + const errorsLength = errors.length; + let allValid = true; + for (let i = 0; i < $allOf.length; i++) { + const subSchema = $allOf[i]; + const subEvaluated = Object.create(evaluated); + const result = validate(instance, subSchema, draft, lookup, shortCircuit, $recursiveAnchor === true ? recursiveAnchor : null, instanceLocation, `${keywordLocation}/${i}`, subEvaluated); + errors.push(...result.errors); + allValid = allValid && result.valid; + if (result.valid) { + subEvaluateds.push(subEvaluated); + } + } + if (allValid) { + errors.length = errorsLength; + } + else { + errors.splice(errorsLength, 0, { + instanceLocation, + keyword: 'allOf', + keywordLocation, + error: `Instance does not match every subschema.` + }); + } + } + if ($oneOf !== undefined) { + const keywordLocation = `${schemaLocation}/oneOf`; + const errorsLength = errors.length; + const matches = $oneOf.filter((subSchema, i) => { + const subEvaluated = Object.create(evaluated); + const result = validate(instance, subSchema, draft, lookup, shortCircuit, $recursiveAnchor === true ? recursiveAnchor : null, instanceLocation, `${keywordLocation}/${i}`, subEvaluated); + errors.push(...result.errors); + if (result.valid) { + subEvaluateds.push(subEvaluated); + } + return result.valid; + }).length; + if (matches === 1) { + errors.length = errorsLength; + } + else { + errors.splice(errorsLength, 0, { + instanceLocation, + keyword: 'oneOf', + keywordLocation, + error: `Instance does not match exactly one subschema (${matches} matches).` + }); + } + } + if (instanceType === 'object' || instanceType === 'array') { + Object.assign(evaluated, ...subEvaluateds); + } + if ($if !== undefined) { + const keywordLocation = `${schemaLocation}/if`; + const conditionResult = validate(instance, $if, draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, keywordLocation, evaluated).valid; + if (conditionResult) { + if ($then !== undefined) { + const thenResult = validate(instance, $then, draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, `${schemaLocation}/then`, evaluated); + if (!thenResult.valid) { + errors.push({ + instanceLocation, + keyword: 'if', + keywordLocation, + error: `Instance does not match "then" schema.` + }, ...thenResult.errors); + } + } + } + else if ($else !== undefined) { + const elseResult = validate(instance, $else, draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, `${schemaLocation}/else`, evaluated); + if (!elseResult.valid) { + errors.push({ + instanceLocation, + keyword: 'if', + keywordLocation, + error: `Instance does not match "else" schema.` + }, ...elseResult.errors); + } + } + } + if (instanceType === 'object') { + if ($required !== undefined) { + for (const key of $required) { + if (!(key in instance)) { + errors.push({ + instanceLocation, + keyword: 'required', + keywordLocation: `${schemaLocation}/required`, + error: `Instance does not have required property "${key}".` + }); + } + } + } + const keys = Object.keys(instance); + if ($minProperties !== undefined && keys.length < $minProperties) { + errors.push({ + instanceLocation, + keyword: 'minProperties', + keywordLocation: `${schemaLocation}/minProperties`, + error: `Instance does not have at least ${$minProperties} properties.` + }); + } + if ($maxProperties !== undefined && keys.length > $maxProperties) { + errors.push({ + instanceLocation, + keyword: 'maxProperties', + keywordLocation: `${schemaLocation}/maxProperties`, + error: `Instance does not have at least ${$maxProperties} properties.` + }); + } + if ($propertyNames !== undefined) { + const keywordLocation = `${schemaLocation}/propertyNames`; + for (const key in instance) { + const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`; + const result = validate(key, $propertyNames, draft, lookup, shortCircuit, recursiveAnchor, subInstancePointer, keywordLocation); + if (!result.valid) { + errors.push({ + instanceLocation, + keyword: 'propertyNames', + keywordLocation, + error: `Property name "${key}" does not match schema.` + }, ...result.errors); + } + } + } + if ($dependentRequired !== undefined) { + const keywordLocation = `${schemaLocation}/dependantRequired`; + for (const key in $dependentRequired) { + if (key in instance) { + const required = $dependentRequired[key]; + for (const dependantKey of required) { + if (!(dependantKey in instance)) { + errors.push({ + instanceLocation, + keyword: 'dependentRequired', + keywordLocation, + error: `Instance has "${key}" but does not have "${dependantKey}".` + }); + } + } + } + } + } + if ($dependentSchemas !== undefined) { + for (const key in $dependentSchemas) { + const keywordLocation = `${schemaLocation}/dependentSchemas`; + if (key in instance) { + const result = validate(instance, $dependentSchemas[key], draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, `${keywordLocation}/${encodePointer(key)}`, evaluated); + if (!result.valid) { + errors.push({ + instanceLocation, + keyword: 'dependentSchemas', + keywordLocation, + error: `Instance has "${key}" but does not match dependant schema.` + }, ...result.errors); + } + } + } + } + if ($dependencies !== undefined) { + const keywordLocation = `${schemaLocation}/dependencies`; + for (const key in $dependencies) { + if (key in instance) { + const propsOrSchema = $dependencies[key]; + if (Array.isArray(propsOrSchema)) { + for (const dependantKey of propsOrSchema) { + if (!(dependantKey in instance)) { + errors.push({ + instanceLocation, + keyword: 'dependencies', + keywordLocation, + error: `Instance has "${key}" but does not have "${dependantKey}".` + }); + } + } + } + else { + const result = validate(instance, propsOrSchema, draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, `${keywordLocation}/${encodePointer(key)}`); + if (!result.valid) { + errors.push({ + instanceLocation, + keyword: 'dependencies', + keywordLocation, + error: `Instance has "${key}" but does not match dependant schema.` + }, ...result.errors); + } + } + } + } + } + const thisEvaluated = Object.create(null); + let stop = false; + if ($properties !== undefined) { + const keywordLocation = `${schemaLocation}/properties`; + for (const key in $properties) { + if (!(key in instance)) { + continue; + } + const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`; + const result = validate(instance[key], $properties[key], draft, lookup, shortCircuit, recursiveAnchor, subInstancePointer, `${keywordLocation}/${encodePointer(key)}`); + if (result.valid) { + evaluated[key] = thisEvaluated[key] = true; + } + else { + stop = shortCircuit; + errors.push({ + instanceLocation, + keyword: 'properties', + keywordLocation, + error: `Property "${key}" does not match schema.` + }, ...result.errors); + if (stop) + break; + } + } + } + if (!stop && $patternProperties !== undefined) { + const keywordLocation = `${schemaLocation}/patternProperties`; + for (const pattern in $patternProperties) { + const regex = new RegExp(pattern); + const subSchema = $patternProperties[pattern]; + for (const key in instance) { + if (!regex.test(key)) { + continue; + } + const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`; + const result = validate(instance[key], subSchema, draft, lookup, shortCircuit, recursiveAnchor, subInstancePointer, `${keywordLocation}/${encodePointer(pattern)}`); + if (result.valid) { + evaluated[key] = thisEvaluated[key] = true; + } + else { + stop = shortCircuit; + errors.push({ + instanceLocation, + keyword: 'patternProperties', + keywordLocation, + error: `Property "${key}" matches pattern "${pattern}" but does not match associated schema.` + }, ...result.errors); + } + } + } + } + if (!stop && $additionalProperties !== undefined) { + const keywordLocation = `${schemaLocation}/additionalProperties`; + for (const key in instance) { + if (thisEvaluated[key]) { + continue; + } + const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`; + const result = validate(instance[key], $additionalProperties, draft, lookup, shortCircuit, recursiveAnchor, subInstancePointer, keywordLocation); + if (result.valid) { + evaluated[key] = true; + } + else { + stop = shortCircuit; + errors.push({ + instanceLocation, + keyword: 'additionalProperties', + keywordLocation, + error: `Property "${key}" does not match additional properties schema.` + }, ...result.errors); + } + } + } + else if (!stop && $unevaluatedProperties !== undefined) { + const keywordLocation = `${schemaLocation}/unevaluatedProperties`; + for (const key in instance) { + if (!evaluated[key]) { + const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`; + const result = validate(instance[key], $unevaluatedProperties, draft, lookup, shortCircuit, recursiveAnchor, subInstancePointer, keywordLocation); + if (result.valid) { + evaluated[key] = true; + } + else { + errors.push({ + instanceLocation, + keyword: 'unevaluatedProperties', + keywordLocation, + error: `Property "${key}" does not match unevaluated properties schema.` + }, ...result.errors); + } + } + } + } + } + else if (instanceType === 'array') { + if ($maxItems !== undefined && instance.length > $maxItems) { + errors.push({ + instanceLocation, + keyword: 'maxItems', + keywordLocation: `${schemaLocation}/maxItems`, + error: `Array has too many items (${instance.length} > ${$maxItems}).` + }); + } + if ($minItems !== undefined && instance.length < $minItems) { + errors.push({ + instanceLocation, + keyword: 'minItems', + keywordLocation: `${schemaLocation}/minItems`, + error: `Array has too few items (${instance.length} < ${$minItems}).` + }); + } + const length = instance.length; + let i = 0; + let stop = false; + if ($prefixItems !== undefined) { + const keywordLocation = `${schemaLocation}/prefixItems`; + const length2 = Math.min($prefixItems.length, length); + for (; i < length2; i++) { + const result = validate(instance[i], $prefixItems[i], draft, lookup, shortCircuit, recursiveAnchor, `${instanceLocation}/${i}`, `${keywordLocation}/${i}`); + evaluated[i] = true; + if (!result.valid) { + stop = shortCircuit; + errors.push({ + instanceLocation, + keyword: 'prefixItems', + keywordLocation, + error: `Items did not match schema.` + }, ...result.errors); + if (stop) + break; + } + } + } + if ($items !== undefined) { + const keywordLocation = `${schemaLocation}/items`; + if (Array.isArray($items)) { + const length2 = Math.min($items.length, length); + for (; i < length2; i++) { + const result = validate(instance[i], $items[i], draft, lookup, shortCircuit, recursiveAnchor, `${instanceLocation}/${i}`, `${keywordLocation}/${i}`); + evaluated[i] = true; + if (!result.valid) { + stop = shortCircuit; + errors.push({ + instanceLocation, + keyword: 'items', + keywordLocation, + error: `Items did not match schema.` + }, ...result.errors); + if (stop) + break; + } + } + } + else { + for (; i < length; i++) { + const result = validate(instance[i], $items, draft, lookup, shortCircuit, recursiveAnchor, `${instanceLocation}/${i}`, keywordLocation); + evaluated[i] = true; + if (!result.valid) { + stop = shortCircuit; + errors.push({ + instanceLocation, + keyword: 'items', + keywordLocation, + error: `Items did not match schema.` + }, ...result.errors); + if (stop) + break; + } + } + } + if (!stop && $additionalItems !== undefined) { + const keywordLocation = `${schemaLocation}/additionalItems`; + for (; i < length; i++) { + const result = validate(instance[i], $additionalItems, draft, lookup, shortCircuit, recursiveAnchor, `${instanceLocation}/${i}`, keywordLocation); + evaluated[i] = true; + if (!result.valid) { + stop = shortCircuit; + errors.push({ + instanceLocation, + keyword: 'additionalItems', + keywordLocation, + error: `Items did not match additional items schema.` + }, ...result.errors); + } + } + } + } + if ($contains !== undefined) { + if (length === 0 && $minContains === undefined) { + errors.push({ + instanceLocation, + keyword: 'contains', + keywordLocation: `${schemaLocation}/contains`, + error: `Array is empty. It must contain at least one item matching the schema.` + }); + } + else if ($minContains !== undefined && length < $minContains) { + errors.push({ + instanceLocation, + keyword: 'minContains', + keywordLocation: `${schemaLocation}/minContains`, + error: `Array has less items (${length}) than minContains (${$minContains}).` + }); + } + else { + const keywordLocation = `${schemaLocation}/contains`; + const errorsLength = errors.length; + let contained = 0; + for (let j = 0; j < length; j++) { + const result = validate(instance[j], $contains, draft, lookup, shortCircuit, recursiveAnchor, `${instanceLocation}/${j}`, keywordLocation); + if (result.valid) { + evaluated[j] = true; + contained++; + } + else { + errors.push(...result.errors); + } + } + if (contained >= ($minContains || 0)) { + errors.length = errorsLength; + } + if ($minContains === undefined && + $maxContains === undefined && + contained === 0) { + errors.splice(errorsLength, 0, { + instanceLocation, + keyword: 'contains', + keywordLocation, + error: `Array does not contain item matching schema.` + }); + } + else if ($minContains !== undefined && contained < $minContains) { + errors.push({ + instanceLocation, + keyword: 'minContains', + keywordLocation: `${schemaLocation}/minContains`, + error: `Array must contain at least ${$minContains} items matching schema. Only ${contained} items were found.` + }); + } + else if ($maxContains !== undefined && contained > $maxContains) { + errors.push({ + instanceLocation, + keyword: 'maxContains', + keywordLocation: `${schemaLocation}/maxContains`, + error: `Array may contain at most ${$maxContains} items matching schema. ${contained} items were found.` + }); + } + } + } + if (!stop && $unevaluatedItems !== undefined) { + const keywordLocation = `${schemaLocation}/unevaluatedItems`; + for (i; i < length; i++) { + if (evaluated[i]) { + continue; + } + const result = validate(instance[i], $unevaluatedItems, draft, lookup, shortCircuit, recursiveAnchor, `${instanceLocation}/${i}`, keywordLocation); + evaluated[i] = true; + if (!result.valid) { + errors.push({ + instanceLocation, + keyword: 'unevaluatedItems', + keywordLocation, + error: `Items did not match unevaluated items schema.` + }, ...result.errors); + } + } + } + if ($uniqueItems) { + for (let j = 0; j < length; j++) { + const a = instance[j]; + const ao = typeof a === 'object' && a !== null; + for (let k = 0; k < length; k++) { + if (j === k) { + continue; + } + const b = instance[k]; + const bo = typeof b === 'object' && b !== null; + if (a === b || (ao && bo && deepCompareStrict(a, b))) { + errors.push({ + instanceLocation, + keyword: 'uniqueItems', + keywordLocation: `${schemaLocation}/uniqueItems`, + error: `Duplicate items at indexes ${j} and ${k}.` + }); + j = Number.MAX_SAFE_INTEGER; + k = Number.MAX_SAFE_INTEGER; + } + } + } + } + } + else if (instanceType === 'number') { + if (draft === '4') { + if ($minimum !== undefined && + (($exclusiveMinimum === true && instance <= $minimum) || + instance < $minimum)) { + errors.push({ + instanceLocation, + keyword: 'minimum', + keywordLocation: `${schemaLocation}/minimum`, + error: `${instance} is less than ${$exclusiveMinimum ? 'or equal to ' : ''} ${$minimum}.` + }); + } + if ($maximum !== undefined && + (($exclusiveMaximum === true && instance >= $maximum) || + instance > $maximum)) { + errors.push({ + instanceLocation, + keyword: 'maximum', + keywordLocation: `${schemaLocation}/maximum`, + error: `${instance} is greater than ${$exclusiveMaximum ? 'or equal to ' : ''} ${$maximum}.` + }); + } + } + else { + if ($minimum !== undefined && instance < $minimum) { + errors.push({ + instanceLocation, + keyword: 'minimum', + keywordLocation: `${schemaLocation}/minimum`, + error: `${instance} is less than ${$minimum}.` + }); + } + if ($maximum !== undefined && instance > $maximum) { + errors.push({ + instanceLocation, + keyword: 'maximum', + keywordLocation: `${schemaLocation}/maximum`, + error: `${instance} is greater than ${$maximum}.` + }); + } + if ($exclusiveMinimum !== undefined && instance <= $exclusiveMinimum) { + errors.push({ + instanceLocation, + keyword: 'exclusiveMinimum', + keywordLocation: `${schemaLocation}/exclusiveMinimum`, + error: `${instance} is less than ${$exclusiveMinimum}.` + }); + } + if ($exclusiveMaximum !== undefined && instance >= $exclusiveMaximum) { + errors.push({ + instanceLocation, + keyword: 'exclusiveMaximum', + keywordLocation: `${schemaLocation}/exclusiveMaximum`, + error: `${instance} is greater than or equal to ${$exclusiveMaximum}.` + }); + } + } + if ($multipleOf !== undefined) { + const remainder = instance % $multipleOf; + if (Math.abs(0 - remainder) >= 1.1920929e-7 && + Math.abs($multipleOf - remainder) >= 1.1920929e-7) { + errors.push({ + instanceLocation, + keyword: 'multipleOf', + keywordLocation: `${schemaLocation}/multipleOf`, + error: `${instance} is not a multiple of ${$multipleOf}.` + }); + } + } + } + else if (instanceType === 'string') { + const length = $minLength === undefined && $maxLength === undefined + ? 0 + : ucs2length(instance); + if ($minLength !== undefined && length < $minLength) { + errors.push({ + instanceLocation, + keyword: 'minLength', + keywordLocation: `${schemaLocation}/minLength`, + error: `String is too short (${length} < ${$minLength}).` + }); + } + if ($maxLength !== undefined && length > $maxLength) { + errors.push({ + instanceLocation, + keyword: 'maxLength', + keywordLocation: `${schemaLocation}/maxLength`, + error: `String is too long (${length} > ${$maxLength}).` + }); + } + if ($pattern !== undefined && !new RegExp($pattern).test(instance)) { + errors.push({ + instanceLocation, + keyword: 'pattern', + keywordLocation: `${schemaLocation}/pattern`, + error: `String does not match pattern.` + }); + } + if ($format !== undefined && + fastFormat[$format] && + !fastFormat[$format](instance)) { + errors.push({ + instanceLocation, + keyword: 'format', + keywordLocation: `${schemaLocation}/format`, + error: `String does not match format "${$format}".` + }); + } + } + return { valid: errors.length === 0, errors }; +} + +class Validator { + constructor(schema, draft = '2019-09', shortCircuit = true) { + this.schema = schema; + this.draft = draft; + this.shortCircuit = shortCircuit; + this.lookup = dereference(schema); + } + validate(instance) { + return validate(instance, this.schema, this.draft, this.lookup, this.shortCircuit); + } + addSchema(schema, id) { + if (id) { + schema = { ...schema, $id: id }; + } + dereference(schema, this.lookup); + } +} + +this.Validator = Validator; +this.deepCompareStrict = deepCompareStrict; +this.dereference = dereference; +this.encodePointer = encodePointer; +this.escapePointer = escapePointer; +this.fastFormat = fastFormat; +this.fullFormat = fullFormat; +this.ignoredKeyword = ignoredKeyword; +this.initialBaseURI = initialBaseURI; +this.schemaArrayKeyword = schemaArrayKeyword; +this.schemaKeyword = schemaKeyword; +this.schemaMapKeyword = schemaMapKeyword; +this.ucs2length = ucs2length; +this.validate = validate; diff --git a/third_party/js/cfworker/json-schema.js.patch b/third_party/js/cfworker/json-schema.js.patch new file mode 100644 index 0000000000..82a0614394 --- /dev/null +++ b/third_party/js/cfworker/json-schema.js.patch @@ -0,0 +1,31 @@ +--- json-schema.js ++++ json-schema.js +@@ -1,6 +1,26 @@ +-'use strict'; ++/* ++ * Copyright (c) 2020 Jeremy Danyow ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining a copy ++ * of this software and associated documentation files (the "Software"), to deal ++ * in the Software without restriction, including without limitation the rights ++ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ++ * copies of the Software, and to permit persons to whom the Software is ++ * furnished to do so, subject to the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be included in ++ * all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ++ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ++ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE ++ * SOFTWARE. ++ */ + +-Object.defineProperty(exports, '__esModule', { value: true }); ++'use strict'; + + function deepCompareStrict(a, b) { + const typeofa = typeof a; diff --git a/third_party/js/cfworker/moz.yaml b/third_party/js/cfworker/moz.yaml new file mode 100644 index 0000000000..1796417e7c --- /dev/null +++ b/third_party/js/cfworker/moz.yaml @@ -0,0 +1,40 @@ +schema: 1 + +bugzilla: + product: Toolkit + component: General + +origin: + name: "cfworker" + description: A JSON schema validator + url: https://github.com/cfworker/cfworker/tree/main/packages/json-schema + license: MIT + release: commit @cfworker/dev@1.13.2 (2022-01-23T22:05:24+00:00). + revision: "v1.10.1" + +vendoring: + url: https://github.com/cfworker/cfworker + source-hosting: github + tracking: tag + skip-vendoring-steps: ["update-moz-build"] + + keep: + - build.sh + - exports.awk + - json-schema.jsm.patch + - tsconfig-base.json.patch + + exclude: + - "**" + + include: + - LICENSE.md + - packages/json-schema/src + - packages/json-schema/package.json + - packages/json-schema/tsconfig.json + - tsconfig-base.json + + update-actions: + - action: run-script + script: 'build.sh' + cwd: '{yaml_dir}' diff --git a/third_party/js/cfworker/tsconfig-base.json.patch b/third_party/js/cfworker/tsconfig-base.json.patch new file mode 100644 index 0000000000..64b921d49f --- /dev/null +++ b/third_party/js/cfworker/tsconfig-base.json.patch @@ -0,0 +1,9 @@ +--- tsconfig-base.json ++++ tsconfig-base.json +@@ -1,5 +1,6 @@ + { + "compilerOptions": { ++ "newLine": "lf", + "moduleResolution": "node", + "esModuleInterop": true, + "strict": true, diff --git a/third_party/js/d3/LICENSE b/third_party/js/d3/LICENSE new file mode 100644 index 0000000000..ff3f2e5419 --- /dev/null +++ b/third_party/js/d3/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2010-2016, Michael Bostock +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* The name Michael Bostock may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/third_party/js/d3/d3.js b/third_party/js/d3/d3.js new file mode 100644 index 0000000000..aded45c440 --- /dev/null +++ b/third_party/js/d3/d3.js @@ -0,0 +1,9554 @@ +!function() { + var d3 = { + version: "3.5.17" + }; + var d3_arraySlice = [].slice, d3_array = function(list) { + return d3_arraySlice.call(list); + }; + var d3_document = this.document; + function d3_documentElement(node) { + return node && (node.ownerDocument || node.document || node).documentElement; + } + function d3_window(node) { + return node && (node.ownerDocument && node.ownerDocument.defaultView || node.document && node || node.defaultView); + } + if (d3_document) { + try { + d3_array(d3_document.documentElement.childNodes)[0].nodeType; + } catch (e) { + d3_array = function(list) { + var i = list.length, array = new Array(i); + while (i--) array[i] = list[i]; + return array; + }; + } + } + if (!Date.now) Date.now = function() { + return +new Date(); + }; + if (d3_document) { + try { + d3_document.createElement("DIV").style.setProperty("opacity", 0, ""); + } catch (error) { + var d3_element_prototype = this.Element.prototype, d3_element_setAttribute = d3_element_prototype.setAttribute, d3_element_setAttributeNS = d3_element_prototype.setAttributeNS, d3_style_prototype = this.CSSStyleDeclaration.prototype, d3_style_setProperty = d3_style_prototype.setProperty; + d3_element_prototype.setAttribute = function(name, value) { + d3_element_setAttribute.call(this, name, value + ""); + }; + d3_element_prototype.setAttributeNS = function(space, local, value) { + d3_element_setAttributeNS.call(this, space, local, value + ""); + }; + d3_style_prototype.setProperty = function(name, value, priority) { + d3_style_setProperty.call(this, name, value + "", priority); + }; + } + } + d3.ascending = d3_ascending; + function d3_ascending(a, b) { + return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; + } + d3.descending = function(a, b) { + return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN; + }; + d3.min = function(array, f) { + var i = -1, n = array.length, a, b; + if (arguments.length === 1) { + while (++i < n) if ((b = array[i]) != null && b >= b) { + a = b; + break; + } + while (++i < n) if ((b = array[i]) != null && a > b) a = b; + } else { + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) { + a = b; + break; + } + while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b; + } + return a; + }; + d3.max = function(array, f) { + var i = -1, n = array.length, a, b; + if (arguments.length === 1) { + while (++i < n) if ((b = array[i]) != null && b >= b) { + a = b; + break; + } + while (++i < n) if ((b = array[i]) != null && b > a) a = b; + } else { + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) { + a = b; + break; + } + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b; + } + return a; + }; + d3.extent = function(array, f) { + var i = -1, n = array.length, a, b, c; + if (arguments.length === 1) { + while (++i < n) if ((b = array[i]) != null && b >= b) { + a = c = b; + break; + } + while (++i < n) if ((b = array[i]) != null) { + if (a > b) a = b; + if (c < b) c = b; + } + } else { + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) { + a = c = b; + break; + } + while (++i < n) if ((b = f.call(array, array[i], i)) != null) { + if (a > b) a = b; + if (c < b) c = b; + } + } + return [ a, c ]; + }; + function d3_number(x) { + return x === null ? NaN : +x; + } + function d3_numeric(x) { + return !isNaN(x); + } + d3.sum = function(array, f) { + var s = 0, n = array.length, a, i = -1; + if (arguments.length === 1) { + while (++i < n) if (d3_numeric(a = +array[i])) s += a; + } else { + while (++i < n) if (d3_numeric(a = +f.call(array, array[i], i))) s += a; + } + return s; + }; + d3.mean = function(array, f) { + var s = 0, n = array.length, a, i = -1, j = n; + if (arguments.length === 1) { + while (++i < n) if (d3_numeric(a = d3_number(array[i]))) s += a; else --j; + } else { + while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) s += a; else --j; + } + if (j) return s / j; + }; + d3.quantile = function(values, p) { + var H = (values.length - 1) * p + 1, h = Math.floor(H), v = +values[h - 1], e = H - h; + return e ? v + e * (values[h] - v) : v; + }; + d3.median = function(array, f) { + var numbers = [], n = array.length, a, i = -1; + if (arguments.length === 1) { + while (++i < n) if (d3_numeric(a = d3_number(array[i]))) numbers.push(a); + } else { + while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) numbers.push(a); + } + if (numbers.length) return d3.quantile(numbers.sort(d3_ascending), .5); + }; + d3.variance = function(array, f) { + var n = array.length, m = 0, a, d, s = 0, i = -1, j = 0; + if (arguments.length === 1) { + while (++i < n) { + if (d3_numeric(a = d3_number(array[i]))) { + d = a - m; + m += d / ++j; + s += d * (a - m); + } + } + } else { + while (++i < n) { + if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) { + d = a - m; + m += d / ++j; + s += d * (a - m); + } + } + } + if (j > 1) return s / (j - 1); + }; + d3.deviation = function() { + var v = d3.variance.apply(this, arguments); + return v ? Math.sqrt(v) : v; + }; + function d3_bisector(compare) { + return { + left: function(a, x, lo, hi) { + if (arguments.length < 3) lo = 0; + if (arguments.length < 4) hi = a.length; + while (lo < hi) { + var mid = lo + hi >>> 1; + if (compare(a[mid], x) < 0) lo = mid + 1; else hi = mid; + } + return lo; + }, + right: function(a, x, lo, hi) { + if (arguments.length < 3) lo = 0; + if (arguments.length < 4) hi = a.length; + while (lo < hi) { + var mid = lo + hi >>> 1; + if (compare(a[mid], x) > 0) hi = mid; else lo = mid + 1; + } + return lo; + } + }; + } + var d3_bisect = d3_bisector(d3_ascending); + d3.bisectLeft = d3_bisect.left; + d3.bisect = d3.bisectRight = d3_bisect.right; + d3.bisector = function(f) { + return d3_bisector(f.length === 1 ? function(d, x) { + return d3_ascending(f(d), x); + } : f); + }; + d3.shuffle = function(array, i0, i1) { + if ((m = arguments.length) < 3) { + i1 = array.length; + if (m < 2) i0 = 0; + } + var m = i1 - i0, t, i; + while (m) { + i = Math.random() * m-- | 0; + t = array[m + i0], array[m + i0] = array[i + i0], array[i + i0] = t; + } + return array; + }; + d3.permute = function(array, indexes) { + var i = indexes.length, permutes = new Array(i); + while (i--) permutes[i] = array[indexes[i]]; + return permutes; + }; + d3.pairs = function(array) { + var i = 0, n = array.length - 1, p0, p1 = array[0], pairs = new Array(n < 0 ? 0 : n); + while (i < n) pairs[i] = [ p0 = p1, p1 = array[++i] ]; + return pairs; + }; + d3.transpose = function(matrix) { + if (!(n = matrix.length)) return []; + for (var i = -1, m = d3.min(matrix, d3_transposeLength), transpose = new Array(m); ++i < m; ) { + for (var j = -1, n, row = transpose[i] = new Array(n); ++j < n; ) { + row[j] = matrix[j][i]; + } + } + return transpose; + }; + function d3_transposeLength(d) { + return d.length; + } + d3.zip = function() { + return d3.transpose(arguments); + }; + d3.keys = function(map) { + var keys = []; + for (var key in map) keys.push(key); + return keys; + }; + d3.values = function(map) { + var values = []; + for (var key in map) values.push(map[key]); + return values; + }; + d3.entries = function(map) { + var entries = []; + for (var key in map) entries.push({ + key: key, + value: map[key] + }); + return entries; + }; + d3.merge = function(arrays) { + var n = arrays.length, m, i = -1, j = 0, merged, array; + while (++i < n) j += arrays[i].length; + merged = new Array(j); + while (--n >= 0) { + array = arrays[n]; + m = array.length; + while (--m >= 0) { + merged[--j] = array[m]; + } + } + return merged; + }; + var abs = Math.abs; + d3.range = function(start, stop, step) { + if (arguments.length < 3) { + step = 1; + if (arguments.length < 2) { + stop = start; + start = 0; + } + } + if ((stop - start) / step === Infinity) throw new Error("infinite range"); + var range = [], k = d3_range_integerScale(abs(step)), i = -1, j; + start *= k, stop *= k, step *= k; + if (step < 0) while ((j = start + step * ++i) > stop) range.push(j / k); else while ((j = start + step * ++i) < stop) range.push(j / k); + return range; + }; + function d3_range_integerScale(x) { + var k = 1; + while (x * k % 1) k *= 10; + return k; + } + function d3_class(ctor, properties) { + for (var key in properties) { + Object.defineProperty(ctor.prototype, key, { + value: properties[key], + enumerable: false + }); + } + } + d3.map = function(object, f) { + var map = new d3_Map(); + if (object instanceof d3_Map) { + object.forEach(function(key, value) { + map.set(key, value); + }); + } else if (Array.isArray(object)) { + var i = -1, n = object.length, o; + if (arguments.length === 1) while (++i < n) map.set(i, object[i]); else while (++i < n) map.set(f.call(object, o = object[i], i), o); + } else { + for (var key in object) map.set(key, object[key]); + } + return map; + }; + function d3_Map() { + this._ = Object.create(null); + } + var d3_map_proto = "__proto__", d3_map_zero = "\x00"; + d3_class(d3_Map, { + has: d3_map_has, + get: function(key) { + return this._[d3_map_escape(key)]; + }, + set: function(key, value) { + return this._[d3_map_escape(key)] = value; + }, + remove: d3_map_remove, + keys: d3_map_keys, + values: function() { + var values = []; + for (var key in this._) values.push(this._[key]); + return values; + }, + entries: function() { + var entries = []; + for (var key in this._) entries.push({ + key: d3_map_unescape(key), + value: this._[key] + }); + return entries; + }, + size: d3_map_size, + empty: d3_map_empty, + forEach: function(f) { + for (var key in this._) f.call(this, d3_map_unescape(key), this._[key]); + } + }); + function d3_map_escape(key) { + return (key += "") === d3_map_proto || key[0] === d3_map_zero ? d3_map_zero + key : key; + } + function d3_map_unescape(key) { + return (key += "")[0] === d3_map_zero ? key.slice(1) : key; + } + function d3_map_has(key) { + return d3_map_escape(key) in this._; + } + function d3_map_remove(key) { + return (key = d3_map_escape(key)) in this._ && delete this._[key]; + } + function d3_map_keys() { + var keys = []; + for (var key in this._) keys.push(d3_map_unescape(key)); + return keys; + } + function d3_map_size() { + var size = 0; + for (var key in this._) ++size; + return size; + } + function d3_map_empty() { + for (var key in this._) return false; + return true; + } + d3.nest = function() { + var nest = {}, keys = [], sortKeys = [], sortValues, rollup; + function map(mapType, array, depth) { + if (depth >= keys.length) return rollup ? rollup.call(nest, array) : sortValues ? array.sort(sortValues) : array; + var i = -1, n = array.length, key = keys[depth++], keyValue, object, setter, valuesByKey = new d3_Map(), values; + while (++i < n) { + if (values = valuesByKey.get(keyValue = key(object = array[i]))) { + values.push(object); + } else { + valuesByKey.set(keyValue, [ object ]); + } + } + if (mapType) { + object = mapType(); + setter = function(keyValue, values) { + object.set(keyValue, map(mapType, values, depth)); + }; + } else { + object = {}; + setter = function(keyValue, values) { + object[keyValue] = map(mapType, values, depth); + }; + } + valuesByKey.forEach(setter); + return object; + } + function entries(map, depth) { + if (depth >= keys.length) return map; + var array = [], sortKey = sortKeys[depth++]; + map.forEach(function(key, keyMap) { + array.push({ + key: key, + values: entries(keyMap, depth) + }); + }); + return sortKey ? array.sort(function(a, b) { + return sortKey(a.key, b.key); + }) : array; + } + nest.map = function(array, mapType) { + return map(mapType, array, 0); + }; + nest.entries = function(array) { + return entries(map(d3.map, array, 0), 0); + }; + nest.key = function(d) { + keys.push(d); + return nest; + }; + nest.sortKeys = function(order) { + sortKeys[keys.length - 1] = order; + return nest; + }; + nest.sortValues = function(order) { + sortValues = order; + return nest; + }; + nest.rollup = function(f) { + rollup = f; + return nest; + }; + return nest; + }; + d3.set = function(array) { + var set = new d3_Set(); + if (array) for (var i = 0, n = array.length; i < n; ++i) set.add(array[i]); + return set; + }; + function d3_Set() { + this._ = Object.create(null); + } + d3_class(d3_Set, { + has: d3_map_has, + add: function(key) { + this._[d3_map_escape(key += "")] = true; + return key; + }, + remove: d3_map_remove, + values: d3_map_keys, + size: d3_map_size, + empty: d3_map_empty, + forEach: function(f) { + for (var key in this._) f.call(this, d3_map_unescape(key)); + } + }); + d3.behavior = {}; + function d3_identity(d) { + return d; + } + d3.rebind = function(target, source) { + var i = 1, n = arguments.length, method; + while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]); + return target; + }; + function d3_rebind(target, source, method) { + return function() { + var value = method.apply(source, arguments); + return value === source ? target : value; + }; + } + function d3_vendorSymbol(object, name) { + if (name in object) return name; + name = name.charAt(0).toUpperCase() + name.slice(1); + for (var i = 0, n = d3_vendorPrefixes.length; i < n; ++i) { + var prefixName = d3_vendorPrefixes[i] + name; + if (prefixName in object) return prefixName; + } + } + var d3_vendorPrefixes = [ "webkit", "ms", "moz", "Moz", "o", "O" ]; + function d3_noop() {} + d3.dispatch = function() { + var dispatch = new d3_dispatch(), i = -1, n = arguments.length; + while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); + return dispatch; + }; + function d3_dispatch() {} + d3_dispatch.prototype.on = function(type, listener) { + var i = type.indexOf("."), name = ""; + if (i >= 0) { + name = type.slice(i + 1); + type = type.slice(0, i); + } + if (type) return arguments.length < 2 ? this[type].on(name) : this[type].on(name, listener); + if (arguments.length === 2) { + if (listener == null) for (type in this) { + if (this.hasOwnProperty(type)) this[type].on(name, null); + } + return this; + } + }; + function d3_dispatch_event(dispatch) { + var listeners = [], listenerByName = new d3_Map(); + function event() { + var z = listeners, i = -1, n = z.length, l; + while (++i < n) if (l = z[i].on) l.apply(this, arguments); + return dispatch; + } + event.on = function(name, listener) { + var l = listenerByName.get(name), i; + if (arguments.length < 2) return l && l.on; + if (l) { + l.on = null; + listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1)); + listenerByName.remove(name); + } + if (listener) listeners.push(listenerByName.set(name, { + on: listener + })); + return dispatch; + }; + return event; + } + d3.event = null; + function d3_eventPreventDefault() { + d3.event.preventDefault(); + } + function d3_eventSource() { + var e = d3.event, s; + while (s = e.sourceEvent) e = s; + return e; + } + function d3_eventDispatch(target) { + var dispatch = new d3_dispatch(), i = 0, n = arguments.length; + while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); + dispatch.of = function(thiz, argumentz) { + return function(e1) { + try { + var e0 = e1.sourceEvent = d3.event; + e1.target = target; + d3.event = e1; + dispatch[e1.type].apply(thiz, argumentz); + } finally { + d3.event = e0; + } + }; + }; + return dispatch; + } + d3.requote = function(s) { + return s.replace(d3_requote_re, "\\$&"); + }; + var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; + var d3_subclass = {}.__proto__ ? function(object, prototype) { + object.__proto__ = prototype; + } : function(object, prototype) { + for (var property in prototype) object[property] = prototype[property]; + }; + function d3_selection(groups) { + d3_subclass(groups, d3_selectionPrototype); + return groups; + } + var d3_select = function(s, n) { + return n.querySelector(s); + }, d3_selectAll = function(s, n) { + return n.querySelectorAll(s); + }, d3_selectMatches = function(n, s) { + var d3_selectMatcher = n.matches || n[d3_vendorSymbol(n, "matchesSelector")]; + d3_selectMatches = function(n, s) { + return d3_selectMatcher.call(n, s); + }; + return d3_selectMatches(n, s); + }; + if (typeof Sizzle === "function") { + d3_select = function(s, n) { + return Sizzle(s, n)[0] || null; + }; + d3_selectAll = Sizzle; + d3_selectMatches = Sizzle.matchesSelector; + } + d3.selection = function() { + return d3.select(d3_document.documentElement); + }; + var d3_selectionPrototype = d3.selection.prototype = []; + d3_selectionPrototype.select = function(selector) { + var subgroups = [], subgroup, subnode, group, node; + selector = d3_selection_selector(selector); + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + subgroup.parentNode = (group = this[j]).parentNode; + for (var i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroup.push(subnode = selector.call(node, node.__data__, i, j)); + if (subnode && "__data__" in node) subnode.__data__ = node.__data__; + } else { + subgroup.push(null); + } + } + } + return d3_selection(subgroups); + }; + function d3_selection_selector(selector) { + return typeof selector === "function" ? selector : function() { + return d3_select(selector, this); + }; + } + d3_selectionPrototype.selectAll = function(selector) { + var subgroups = [], subgroup, node; + selector = d3_selection_selectorAll(selector); + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i, j))); + subgroup.parentNode = node; + } + } + } + return d3_selection(subgroups); + }; + function d3_selection_selectorAll(selector) { + return typeof selector === "function" ? selector : function() { + return d3_selectAll(selector, this); + }; + } + var d3_nsXhtml = "http://www.w3.org/1999/xhtml"; + var d3_nsPrefix = { + svg: "http://www.w3.org/2000/svg", + xhtml: d3_nsXhtml, + xlink: "http://www.w3.org/1999/xlink", + xml: "http://www.w3.org/XML/1998/namespace", + xmlns: "http://www.w3.org/2000/xmlns/" + }; + d3.ns = { + prefix: d3_nsPrefix, + qualify: function(name) { + var i = name.indexOf(":"), prefix = name; + if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns") name = name.slice(i + 1); + return d3_nsPrefix.hasOwnProperty(prefix) ? { + space: d3_nsPrefix[prefix], + local: name + } : name; + } + }; + d3_selectionPrototype.attr = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") { + var node = this.node(); + name = d3.ns.qualify(name); + return name.local ? node.getAttributeNS(name.space, name.local) : node.getAttribute(name); + } + for (value in name) this.each(d3_selection_attr(value, name[value])); + return this; + } + return this.each(d3_selection_attr(name, value)); + }; + function d3_selection_attr(name, value) { + name = d3.ns.qualify(name); + function attrNull() { + this.removeAttribute(name); + } + function attrNullNS() { + this.removeAttributeNS(name.space, name.local); + } + function attrConstant() { + this.setAttribute(name, value); + } + function attrConstantNS() { + this.setAttributeNS(name.space, name.local, value); + } + function attrFunction() { + var x = value.apply(this, arguments); + if (x == null) this.removeAttribute(name); else this.setAttribute(name, x); + } + function attrFunctionNS() { + var x = value.apply(this, arguments); + if (x == null) this.removeAttributeNS(name.space, name.local); else this.setAttributeNS(name.space, name.local, x); + } + return value == null ? name.local ? attrNullNS : attrNull : typeof value === "function" ? name.local ? attrFunctionNS : attrFunction : name.local ? attrConstantNS : attrConstant; + } + function d3_collapse(s) { + return s.trim().replace(/\s+/g, " "); + } + d3_selectionPrototype.classed = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") { + var node = this.node(), n = (name = d3_selection_classes(name)).length, i = -1; + if (value = node.classList) { + while (++i < n) if (!value.contains(name[i])) return false; + } else { + value = node.getAttribute("class"); + while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false; + } + return true; + } + for (value in name) this.each(d3_selection_classed(value, name[value])); + return this; + } + return this.each(d3_selection_classed(name, value)); + }; + function d3_selection_classedRe(name) { + return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g"); + } + function d3_selection_classes(name) { + return (name + "").trim().split(/^|\s+/); + } + function d3_selection_classed(name, value) { + name = d3_selection_classes(name).map(d3_selection_classedName); + var n = name.length; + function classedConstant() { + var i = -1; + while (++i < n) name[i](this, value); + } + function classedFunction() { + var i = -1, x = value.apply(this, arguments); + while (++i < n) name[i](this, x); + } + return typeof value === "function" ? classedFunction : classedConstant; + } + function d3_selection_classedName(name) { + var re = d3_selection_classedRe(name); + return function(node, value) { + if (c = node.classList) return value ? c.add(name) : c.remove(name); + var c = node.getAttribute("class") || ""; + if (value) { + re.lastIndex = 0; + if (!re.test(c)) node.setAttribute("class", d3_collapse(c + " " + name)); + } else { + node.setAttribute("class", d3_collapse(c.replace(re, " "))); + } + }; + } + d3_selectionPrototype.style = function(name, value, priority) { + var n = arguments.length; + if (n < 3) { + if (typeof name !== "string") { + if (n < 2) value = ""; + for (priority in name) this.each(d3_selection_style(priority, name[priority], value)); + return this; + } + if (n < 2) { + var node = this.node(); + return d3_window(node).getComputedStyle(node, null).getPropertyValue(name); + } + priority = ""; + } + return this.each(d3_selection_style(name, value, priority)); + }; + function d3_selection_style(name, value, priority) { + function styleNull() { + this.style.removeProperty(name); + } + function styleConstant() { + this.style.setProperty(name, value, priority); + } + function styleFunction() { + var x = value.apply(this, arguments); + if (x == null) this.style.removeProperty(name); else this.style.setProperty(name, x, priority); + } + return value == null ? styleNull : typeof value === "function" ? styleFunction : styleConstant; + } + d3_selectionPrototype.property = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") return this.node()[name]; + for (value in name) this.each(d3_selection_property(value, name[value])); + return this; + } + return this.each(d3_selection_property(name, value)); + }; + function d3_selection_property(name, value) { + function propertyNull() { + delete this[name]; + } + function propertyConstant() { + this[name] = value; + } + function propertyFunction() { + var x = value.apply(this, arguments); + if (x == null) delete this[name]; else this[name] = x; + } + return value == null ? propertyNull : typeof value === "function" ? propertyFunction : propertyConstant; + } + d3_selectionPrototype.text = function(value) { + return arguments.length ? this.each(typeof value === "function" ? function() { + var v = value.apply(this, arguments); + this.textContent = v == null ? "" : v; + } : value == null ? function() { + this.textContent = ""; + } : function() { + this.textContent = value; + }) : this.node().textContent; + }; + d3_selectionPrototype.html = function(value) { + return arguments.length ? this.each(typeof value === "function" ? function() { + var v = value.apply(this, arguments); + this.innerHTML = v == null ? "" : v; + } : value == null ? function() { + this.innerHTML = ""; + } : function() { + this.innerHTML = value; + }) : this.node().innerHTML; + }; + d3_selectionPrototype.append = function(name) { + name = d3_selection_creator(name); + return this.select(function() { + return this.appendChild(name.apply(this, arguments)); + }); + }; + function d3_selection_creator(name) { + function create() { + var document = this.ownerDocument, namespace = this.namespaceURI; + return namespace === d3_nsXhtml && document.documentElement.namespaceURI === d3_nsXhtml ? document.createElement(name) : document.createElementNS(namespace, name); + } + function createNS() { + return this.ownerDocument.createElementNS(name.space, name.local); + } + return typeof name === "function" ? name : (name = d3.ns.qualify(name)).local ? createNS : create; + } + d3_selectionPrototype.insert = function(name, before) { + name = d3_selection_creator(name); + before = d3_selection_selector(before); + return this.select(function() { + return this.insertBefore(name.apply(this, arguments), before.apply(this, arguments) || null); + }); + }; + d3_selectionPrototype.remove = function() { + return this.each(d3_selectionRemove); + }; + function d3_selectionRemove() { + var parent = this.parentNode; + if (parent) parent.removeChild(this); + } + d3_selectionPrototype.data = function(value, key) { + var i = -1, n = this.length, group, node; + if (!arguments.length) { + value = new Array(n = (group = this[0]).length); + while (++i < n) { + if (node = group[i]) { + value[i] = node.__data__; + } + } + return value; + } + function bind(group, groupData) { + var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), updateNodes = new Array(m), enterNodes = new Array(m), exitNodes = new Array(n), node, nodeData; + if (key) { + var nodeByKeyValue = new d3_Map(), keyValues = new Array(n), keyValue; + for (i = -1; ++i < n; ) { + if (node = group[i]) { + if (nodeByKeyValue.has(keyValue = key.call(node, node.__data__, i))) { + exitNodes[i] = node; + } else { + nodeByKeyValue.set(keyValue, node); + } + keyValues[i] = keyValue; + } + } + for (i = -1; ++i < m; ) { + if (!(node = nodeByKeyValue.get(keyValue = key.call(groupData, nodeData = groupData[i], i)))) { + enterNodes[i] = d3_selection_dataNode(nodeData); + } else if (node !== true) { + updateNodes[i] = node; + node.__data__ = nodeData; + } + nodeByKeyValue.set(keyValue, true); + } + for (i = -1; ++i < n; ) { + if (i in keyValues && nodeByKeyValue.get(keyValues[i]) !== true) { + exitNodes[i] = group[i]; + } + } + } else { + for (i = -1; ++i < n0; ) { + node = group[i]; + nodeData = groupData[i]; + if (node) { + node.__data__ = nodeData; + updateNodes[i] = node; + } else { + enterNodes[i] = d3_selection_dataNode(nodeData); + } + } + for (;i < m; ++i) { + enterNodes[i] = d3_selection_dataNode(groupData[i]); + } + for (;i < n; ++i) { + exitNodes[i] = group[i]; + } + } + enterNodes.update = updateNodes; + enterNodes.parentNode = updateNodes.parentNode = exitNodes.parentNode = group.parentNode; + enter.push(enterNodes); + update.push(updateNodes); + exit.push(exitNodes); + } + var enter = d3_selection_enter([]), update = d3_selection([]), exit = d3_selection([]); + if (typeof value === "function") { + while (++i < n) { + bind(group = this[i], value.call(group, group.parentNode.__data__, i)); + } + } else { + while (++i < n) { + bind(group = this[i], value); + } + } + update.enter = function() { + return enter; + }; + update.exit = function() { + return exit; + }; + return update; + }; + function d3_selection_dataNode(data) { + return { + __data__: data + }; + } + d3_selectionPrototype.datum = function(value) { + return arguments.length ? this.property("__data__", value) : this.property("__data__"); + }; + d3_selectionPrototype.filter = function(filter) { + var subgroups = [], subgroup, group, node; + if (typeof filter !== "function") filter = d3_selection_filter(filter); + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + subgroup.parentNode = (group = this[j]).parentNode; + for (var i = 0, n = group.length; i < n; i++) { + if ((node = group[i]) && filter.call(node, node.__data__, i, j)) { + subgroup.push(node); + } + } + } + return d3_selection(subgroups); + }; + function d3_selection_filter(selector) { + return function() { + return d3_selectMatches(this, selector); + }; + } + d3_selectionPrototype.order = function() { + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = group.length - 1, next = group[i], node; --i >= 0; ) { + if (node = group[i]) { + if (next && next !== node.nextSibling) next.parentNode.insertBefore(node, next); + next = node; + } + } + } + return this; + }; + d3_selectionPrototype.sort = function(comparator) { + comparator = d3_selection_sortComparator.apply(this, arguments); + for (var j = -1, m = this.length; ++j < m; ) this[j].sort(comparator); + return this.order(); + }; + function d3_selection_sortComparator(comparator) { + if (!arguments.length) comparator = d3_ascending; + return function(a, b) { + return a && b ? comparator(a.__data__, b.__data__) : !a - !b; + }; + } + d3_selectionPrototype.each = function(callback) { + return d3_selection_each(this, function(node, i, j) { + callback.call(node, node.__data__, i, j); + }); + }; + function d3_selection_each(groups, callback) { + for (var j = 0, m = groups.length; j < m; j++) { + for (var group = groups[j], i = 0, n = group.length, node; i < n; i++) { + if (node = group[i]) callback(node, i, j); + } + } + return groups; + } + d3_selectionPrototype.call = function(callback) { + var args = d3_array(arguments); + callback.apply(args[0] = this, args); + return this; + }; + d3_selectionPrototype.empty = function() { + return !this.node(); + }; + d3_selectionPrototype.node = function() { + for (var j = 0, m = this.length; j < m; j++) { + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + var node = group[i]; + if (node) return node; + } + } + return null; + }; + d3_selectionPrototype.size = function() { + var n = 0; + d3_selection_each(this, function() { + ++n; + }); + return n; + }; + function d3_selection_enter(selection) { + d3_subclass(selection, d3_selection_enterPrototype); + return selection; + } + var d3_selection_enterPrototype = []; + d3.selection.enter = d3_selection_enter; + d3.selection.enter.prototype = d3_selection_enterPrototype; + d3_selection_enterPrototype.append = d3_selectionPrototype.append; + d3_selection_enterPrototype.empty = d3_selectionPrototype.empty; + d3_selection_enterPrototype.node = d3_selectionPrototype.node; + d3_selection_enterPrototype.call = d3_selectionPrototype.call; + d3_selection_enterPrototype.size = d3_selectionPrototype.size; + d3_selection_enterPrototype.select = function(selector) { + var subgroups = [], subgroup, subnode, upgroup, group, node; + for (var j = -1, m = this.length; ++j < m; ) { + upgroup = (group = this[j]).update; + subgroups.push(subgroup = []); + subgroup.parentNode = group.parentNode; + for (var i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroup.push(upgroup[i] = subnode = selector.call(group.parentNode, node.__data__, i, j)); + subnode.__data__ = node.__data__; + } else { + subgroup.push(null); + } + } + } + return d3_selection(subgroups); + }; + d3_selection_enterPrototype.insert = function(name, before) { + if (arguments.length < 2) before = d3_selection_enterInsertBefore(this); + return d3_selectionPrototype.insert.call(this, name, before); + }; + function d3_selection_enterInsertBefore(enter) { + var i0, j0; + return function(d, i, j) { + var group = enter[j].update, n = group.length, node; + if (j != j0) j0 = j, i0 = 0; + if (i >= i0) i0 = i + 1; + while (!(node = group[i0]) && ++i0 < n) ; + return node; + }; + } + d3.select = function(node) { + var group; + if (typeof node === "string") { + group = [ d3_select(node, d3_document) ]; + group.parentNode = d3_document.documentElement; + } else { + group = [ node ]; + group.parentNode = d3_documentElement(node); + } + return d3_selection([ group ]); + }; + d3.selectAll = function(nodes) { + var group; + if (typeof nodes === "string") { + group = d3_array(d3_selectAll(nodes, d3_document)); + group.parentNode = d3_document.documentElement; + } else { + group = d3_array(nodes); + group.parentNode = null; + } + return d3_selection([ group ]); + }; + d3_selectionPrototype.on = function(type, listener, capture) { + var n = arguments.length; + if (n < 3) { + if (typeof type !== "string") { + if (n < 2) listener = false; + for (capture in type) this.each(d3_selection_on(capture, type[capture], listener)); + return this; + } + if (n < 2) return (n = this.node()["__on" + type]) && n._; + capture = false; + } + return this.each(d3_selection_on(type, listener, capture)); + }; + function d3_selection_on(type, listener, capture) { + var name = "__on" + type, i = type.indexOf("."), wrap = d3_selection_onListener; + if (i > 0) type = type.slice(0, i); + var filter = d3_selection_onFilters.get(type); + if (filter) type = filter, wrap = d3_selection_onFilter; + function onRemove() { + var l = this[name]; + if (l) { + this.removeEventListener(type, l, l.$); + delete this[name]; + } + } + function onAdd() { + var l = wrap(listener, d3_array(arguments)); + onRemove.call(this); + this.addEventListener(type, this[name] = l, l.$ = capture); + l._ = listener; + } + function removeAll() { + var re = new RegExp("^__on([^.]+)" + d3.requote(type) + "$"), match; + for (var name in this) { + if (match = name.match(re)) { + var l = this[name]; + this.removeEventListener(match[1], l, l.$); + delete this[name]; + } + } + } + return i ? listener ? onAdd : onRemove : listener ? d3_noop : removeAll; + } + var d3_selection_onFilters = d3.map({ + mouseenter: "mouseover", + mouseleave: "mouseout" + }); + if (d3_document) { + d3_selection_onFilters.forEach(function(k) { + if ("on" + k in d3_document) d3_selection_onFilters.remove(k); + }); + } + function d3_selection_onListener(listener, argumentz) { + return function(e) { + var o = d3.event; + d3.event = e; + argumentz[0] = this.__data__; + try { + listener.apply(this, argumentz); + } finally { + d3.event = o; + } + }; + } + function d3_selection_onFilter(listener, argumentz) { + var l = d3_selection_onListener(listener, argumentz); + return function(e) { + var target = this, related = e.relatedTarget; + if (!related || related !== target && !(related.compareDocumentPosition(target) & 8)) { + l.call(target, e); + } + }; + } + var d3_event_dragSelect, d3_event_dragId = 0; + function d3_event_dragSuppress(node) { + var name = ".dragsuppress-" + ++d3_event_dragId, click = "click" + name, w = d3.select(d3_window(node)).on("touchmove" + name, d3_eventPreventDefault).on("dragstart" + name, d3_eventPreventDefault).on("selectstart" + name, d3_eventPreventDefault); + if (d3_event_dragSelect == null) { + d3_event_dragSelect = "onselectstart" in node ? false : d3_vendorSymbol(node.style, "userSelect"); + } + if (d3_event_dragSelect) { + var style = d3_documentElement(node).style, select = style[d3_event_dragSelect]; + style[d3_event_dragSelect] = "none"; + } + return function(suppressClick) { + w.on(name, null); + if (d3_event_dragSelect) style[d3_event_dragSelect] = select; + if (suppressClick) { + var off = function() { + w.on(click, null); + }; + w.on(click, function() { + d3_eventPreventDefault(); + off(); + }, true); + setTimeout(off, 0); + } + }; + } + d3.mouse = function(container) { + return d3_mousePoint(container, d3_eventSource()); + }; + var d3_mouse_bug44083 = this.navigator && /WebKit/.test(this.navigator.userAgent) ? -1 : 0; + function d3_mousePoint(container, e) { + if (e.changedTouches) e = e.changedTouches[0]; + var svg = container.ownerSVGElement || container; + if (svg.createSVGPoint) { + var point = svg.createSVGPoint(); + if (d3_mouse_bug44083 < 0) { + var window = d3_window(container); + if (window.scrollX || window.scrollY) { + svg = d3.select("body").append("svg").style({ + position: "absolute", + top: 0, + left: 0, + margin: 0, + padding: 0, + border: "none" + }, "important"); + var ctm = svg[0][0].getScreenCTM(); + d3_mouse_bug44083 = !(ctm.f || ctm.e); + svg.remove(); + } + } + if (d3_mouse_bug44083) point.x = e.pageX, point.y = e.pageY; else point.x = e.clientX, + point.y = e.clientY; + point = point.matrixTransform(container.getScreenCTM().inverse()); + return [ point.x, point.y ]; + } + var rect = container.getBoundingClientRect(); + return [ e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop ]; + } + d3.touch = function(container, touches, identifier) { + if (arguments.length < 3) identifier = touches, touches = d3_eventSource().changedTouches; + if (touches) for (var i = 0, n = touches.length, touch; i < n; ++i) { + if ((touch = touches[i]).identifier === identifier) { + return d3_mousePoint(container, touch); + } + } + }; + d3.behavior.drag = function() { + var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null, mousedown = dragstart(d3_noop, d3.mouse, d3_window, "mousemove", "mouseup"), touchstart = dragstart(d3_behavior_dragTouchId, d3.touch, d3_identity, "touchmove", "touchend"); + function drag() { + this.on("mousedown.drag", mousedown).on("touchstart.drag", touchstart); + } + function dragstart(id, position, subject, move, end) { + return function() { + var that = this, target = d3.event.target.correspondingElement || d3.event.target, parent = that.parentNode, dispatch = event.of(that, arguments), dragged = 0, dragId = id(), dragName = ".drag" + (dragId == null ? "" : "-" + dragId), dragOffset, dragSubject = d3.select(subject(target)).on(move + dragName, moved).on(end + dragName, ended), dragRestore = d3_event_dragSuppress(target), position0 = position(parent, dragId); + if (origin) { + dragOffset = origin.apply(that, arguments); + dragOffset = [ dragOffset.x - position0[0], dragOffset.y - position0[1] ]; + } else { + dragOffset = [ 0, 0 ]; + } + dispatch({ + type: "dragstart" + }); + function moved() { + var position1 = position(parent, dragId), dx, dy; + if (!position1) return; + dx = position1[0] - position0[0]; + dy = position1[1] - position0[1]; + dragged |= dx | dy; + position0 = position1; + dispatch({ + type: "drag", + x: position1[0] + dragOffset[0], + y: position1[1] + dragOffset[1], + dx: dx, + dy: dy + }); + } + function ended() { + if (!position(parent, dragId)) return; + dragSubject.on(move + dragName, null).on(end + dragName, null); + dragRestore(dragged); + dispatch({ + type: "dragend" + }); + } + }; + } + drag.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + return drag; + }; + return d3.rebind(drag, event, "on"); + }; + function d3_behavior_dragTouchId() { + return d3.event.changedTouches[0].identifier; + } + d3.touches = function(container, touches) { + if (arguments.length < 2) touches = d3_eventSource().touches; + return touches ? d3_array(touches).map(function(touch) { + var point = d3_mousePoint(container, touch); + point.identifier = touch.identifier; + return point; + }) : []; + }; + var ε = 1e-6, ε2 = ε * ε, π = Math.PI, τ = 2 * π, τε = τ - ε, halfπ = π / 2, d3_radians = π / 180, d3_degrees = 180 / π; + function d3_sgn(x) { + return x > 0 ? 1 : x < 0 ? -1 : 0; + } + function d3_cross2d(a, b, c) { + return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]); + } + function d3_acos(x) { + return x > 1 ? 0 : x < -1 ? π : Math.acos(x); + } + function d3_asin(x) { + return x > 1 ? halfπ : x < -1 ? -halfπ : Math.asin(x); + } + function d3_sinh(x) { + return ((x = Math.exp(x)) - 1 / x) / 2; + } + function d3_cosh(x) { + return ((x = Math.exp(x)) + 1 / x) / 2; + } + function d3_tanh(x) { + return ((x = Math.exp(2 * x)) - 1) / (x + 1); + } + function d3_haversin(x) { + return (x = Math.sin(x / 2)) * x; + } + var ρ = Math.SQRT2, ρ2 = 2, ρ4 = 4; + d3.interpolateZoom = function(p0, p1) { + var ux0 = p0[0], uy0 = p0[1], w0 = p0[2], ux1 = p1[0], uy1 = p1[1], w1 = p1[2], dx = ux1 - ux0, dy = uy1 - uy0, d2 = dx * dx + dy * dy, i, S; + if (d2 < ε2) { + S = Math.log(w1 / w0) / ρ; + i = function(t) { + return [ ux0 + t * dx, uy0 + t * dy, w0 * Math.exp(ρ * t * S) ]; + }; + } else { + var d1 = Math.sqrt(d2), b0 = (w1 * w1 - w0 * w0 + ρ4 * d2) / (2 * w0 * ρ2 * d1), b1 = (w1 * w1 - w0 * w0 - ρ4 * d2) / (2 * w1 * ρ2 * d1), r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0), r1 = Math.log(Math.sqrt(b1 * b1 + 1) - b1); + S = (r1 - r0) / ρ; + i = function(t) { + var s = t * S, coshr0 = d3_cosh(r0), u = w0 / (ρ2 * d1) * (coshr0 * d3_tanh(ρ * s + r0) - d3_sinh(r0)); + return [ ux0 + u * dx, uy0 + u * dy, w0 * coshr0 / d3_cosh(ρ * s + r0) ]; + }; + } + i.duration = S * 1e3; + return i; + }; + d3.behavior.zoom = function() { + var view = { + x: 0, + y: 0, + k: 1 + }, translate0, center0, center, size = [ 960, 500 ], scaleExtent = d3_behavior_zoomInfinity, duration = 250, zooming = 0, mousedown = "mousedown.zoom", mousemove = "mousemove.zoom", mouseup = "mouseup.zoom", mousewheelTimer, touchstart = "touchstart.zoom", touchtime, event = d3_eventDispatch(zoom, "zoomstart", "zoom", "zoomend"), x0, x1, y0, y1; + if (!d3_behavior_zoomWheel) { + d3_behavior_zoomWheel = "onwheel" in d3_document ? (d3_behavior_zoomDelta = function() { + return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1); + }, "wheel") : "onmousewheel" in d3_document ? (d3_behavior_zoomDelta = function() { + return d3.event.wheelDelta; + }, "mousewheel") : (d3_behavior_zoomDelta = function() { + return -d3.event.detail; + }, "MozMousePixelScroll"); + } + function zoom(g) { + g.on(mousedown, mousedowned).on(d3_behavior_zoomWheel + ".zoom", mousewheeled).on("dblclick.zoom", dblclicked).on(touchstart, touchstarted); + } + zoom.event = function(g) { + g.each(function() { + var dispatch = event.of(this, arguments), view1 = view; + if (d3_transitionInheritId) { + d3.select(this).transition().each("start.zoom", function() { + view = this.__chart__ || { + x: 0, + y: 0, + k: 1 + }; + zoomstarted(dispatch); + }).tween("zoom:zoom", function() { + var dx = size[0], dy = size[1], cx = center0 ? center0[0] : dx / 2, cy = center0 ? center0[1] : dy / 2, i = d3.interpolateZoom([ (cx - view.x) / view.k, (cy - view.y) / view.k, dx / view.k ], [ (cx - view1.x) / view1.k, (cy - view1.y) / view1.k, dx / view1.k ]); + return function(t) { + var l = i(t), k = dx / l[2]; + this.__chart__ = view = { + x: cx - l[0] * k, + y: cy - l[1] * k, + k: k + }; + zoomed(dispatch); + }; + }).each("interrupt.zoom", function() { + zoomended(dispatch); + }).each("end.zoom", function() { + zoomended(dispatch); + }); + } else { + this.__chart__ = view; + zoomstarted(dispatch); + zoomed(dispatch); + zoomended(dispatch); + } + }); + }; + zoom.translate = function(_) { + if (!arguments.length) return [ view.x, view.y ]; + view = { + x: +_[0], + y: +_[1], + k: view.k + }; + rescale(); + return zoom; + }; + zoom.scale = function(_) { + if (!arguments.length) return view.k; + view = { + x: view.x, + y: view.y, + k: null + }; + scaleTo(+_); + rescale(); + return zoom; + }; + zoom.scaleExtent = function(_) { + if (!arguments.length) return scaleExtent; + scaleExtent = _ == null ? d3_behavior_zoomInfinity : [ +_[0], +_[1] ]; + return zoom; + }; + zoom.center = function(_) { + if (!arguments.length) return center; + center = _ && [ +_[0], +_[1] ]; + return zoom; + }; + zoom.size = function(_) { + if (!arguments.length) return size; + size = _ && [ +_[0], +_[1] ]; + return zoom; + }; + zoom.duration = function(_) { + if (!arguments.length) return duration; + duration = +_; + return zoom; + }; + zoom.x = function(z) { + if (!arguments.length) return x1; + x1 = z; + x0 = z.copy(); + view = { + x: 0, + y: 0, + k: 1 + }; + return zoom; + }; + zoom.y = function(z) { + if (!arguments.length) return y1; + y1 = z; + y0 = z.copy(); + view = { + x: 0, + y: 0, + k: 1 + }; + return zoom; + }; + function location(p) { + return [ (p[0] - view.x) / view.k, (p[1] - view.y) / view.k ]; + } + function point(l) { + return [ l[0] * view.k + view.x, l[1] * view.k + view.y ]; + } + function scaleTo(s) { + view.k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s)); + } + function translateTo(p, l) { + l = point(l); + view.x += p[0] - l[0]; + view.y += p[1] - l[1]; + } + function zoomTo(that, p, l, k) { + that.__chart__ = { + x: view.x, + y: view.y, + k: view.k + }; + scaleTo(Math.pow(2, k)); + translateTo(center0 = p, l); + that = d3.select(that); + if (duration > 0) that = that.transition().duration(duration); + that.call(zoom.event); + } + function rescale() { + if (x1) x1.domain(x0.range().map(function(x) { + return (x - view.x) / view.k; + }).map(x0.invert)); + if (y1) y1.domain(y0.range().map(function(y) { + return (y - view.y) / view.k; + }).map(y0.invert)); + } + function zoomstarted(dispatch) { + if (!zooming++) dispatch({ + type: "zoomstart" + }); + } + function zoomed(dispatch) { + rescale(); + dispatch({ + type: "zoom", + scale: view.k, + translate: [ view.x, view.y ] + }); + } + function zoomended(dispatch) { + if (!--zooming) dispatch({ + type: "zoomend" + }), center0 = null; + } + function mousedowned() { + var that = this, dispatch = event.of(that, arguments), dragged = 0, subject = d3.select(d3_window(that)).on(mousemove, moved).on(mouseup, ended), location0 = location(d3.mouse(that)), dragRestore = d3_event_dragSuppress(that); + d3_selection_interrupt.call(that); + zoomstarted(dispatch); + function moved() { + dragged = 1; + translateTo(d3.mouse(that), location0); + zoomed(dispatch); + } + function ended() { + subject.on(mousemove, null).on(mouseup, null); + dragRestore(dragged); + zoomended(dispatch); + } + } + function touchstarted() { + var that = this, dispatch = event.of(that, arguments), locations0 = {}, distance0 = 0, scale0, zoomName = ".zoom-" + d3.event.changedTouches[0].identifier, touchmove = "touchmove" + zoomName, touchend = "touchend" + zoomName, targets = [], subject = d3.select(that), dragRestore = d3_event_dragSuppress(that); + started(); + zoomstarted(dispatch); + subject.on(mousedown, null).on(touchstart, started); + function relocate() { + var touches = d3.touches(that); + scale0 = view.k; + touches.forEach(function(t) { + if (t.identifier in locations0) locations0[t.identifier] = location(t); + }); + return touches; + } + function started() { + var target = d3.event.target; + d3.select(target).on(touchmove, moved).on(touchend, ended); + targets.push(target); + var changed = d3.event.changedTouches; + for (var i = 0, n = changed.length; i < n; ++i) { + locations0[changed[i].identifier] = null; + } + var touches = relocate(), now = Date.now(); + if (touches.length === 1) { + if (now - touchtime < 500) { + var p = touches[0]; + zoomTo(that, p, locations0[p.identifier], Math.floor(Math.log(view.k) / Math.LN2) + 1); + d3_eventPreventDefault(); + } + touchtime = now; + } else if (touches.length > 1) { + var p = touches[0], q = touches[1], dx = p[0] - q[0], dy = p[1] - q[1]; + distance0 = dx * dx + dy * dy; + } + } + function moved() { + var touches = d3.touches(that), p0, l0, p1, l1; + d3_selection_interrupt.call(that); + for (var i = 0, n = touches.length; i < n; ++i, l1 = null) { + p1 = touches[i]; + if (l1 = locations0[p1.identifier]) { + if (l0) break; + p0 = p1, l0 = l1; + } + } + if (l1) { + var distance1 = (distance1 = p1[0] - p0[0]) * distance1 + (distance1 = p1[1] - p0[1]) * distance1, scale1 = distance0 && Math.sqrt(distance1 / distance0); + p0 = [ (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2 ]; + l0 = [ (l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2 ]; + scaleTo(scale1 * scale0); + } + touchtime = null; + translateTo(p0, l0); + zoomed(dispatch); + } + function ended() { + if (d3.event.touches.length) { + var changed = d3.event.changedTouches; + for (var i = 0, n = changed.length; i < n; ++i) { + delete locations0[changed[i].identifier]; + } + for (var identifier in locations0) { + return void relocate(); + } + } + d3.selectAll(targets).on(zoomName, null); + subject.on(mousedown, mousedowned).on(touchstart, touchstarted); + dragRestore(); + zoomended(dispatch); + } + } + function mousewheeled() { + var dispatch = event.of(this, arguments); + if (mousewheelTimer) clearTimeout(mousewheelTimer); else d3_selection_interrupt.call(this), + translate0 = location(center0 = center || d3.mouse(this)), zoomstarted(dispatch); + mousewheelTimer = setTimeout(function() { + mousewheelTimer = null; + zoomended(dispatch); + }, 50); + d3_eventPreventDefault(); + scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * view.k); + translateTo(center0, translate0); + zoomed(dispatch); + } + function dblclicked() { + var p = d3.mouse(this), k = Math.log(view.k) / Math.LN2; + zoomTo(this, p, location(p), d3.event.shiftKey ? Math.ceil(k) - 1 : Math.floor(k) + 1); + } + return d3.rebind(zoom, event, "on"); + }; + var d3_behavior_zoomInfinity = [ 0, Infinity ], d3_behavior_zoomDelta, d3_behavior_zoomWheel; + d3.color = d3_color; + function d3_color() {} + d3_color.prototype.toString = function() { + return this.rgb() + ""; + }; + d3.hsl = d3_hsl; + function d3_hsl(h, s, l) { + return this instanceof d3_hsl ? void (this.h = +h, this.s = +s, this.l = +l) : arguments.length < 2 ? h instanceof d3_hsl ? new d3_hsl(h.h, h.s, h.l) : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl) : new d3_hsl(h, s, l); + } + var d3_hslPrototype = d3_hsl.prototype = new d3_color(); + d3_hslPrototype.brighter = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return new d3_hsl(this.h, this.s, this.l / k); + }; + d3_hslPrototype.darker = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return new d3_hsl(this.h, this.s, k * this.l); + }; + d3_hslPrototype.rgb = function() { + return d3_hsl_rgb(this.h, this.s, this.l); + }; + function d3_hsl_rgb(h, s, l) { + var m1, m2; + h = isNaN(h) ? 0 : (h %= 360) < 0 ? h + 360 : h; + s = isNaN(s) ? 0 : s < 0 ? 0 : s > 1 ? 1 : s; + l = l < 0 ? 0 : l > 1 ? 1 : l; + m2 = l <= .5 ? l * (1 + s) : l + s - l * s; + m1 = 2 * l - m2; + function v(h) { + if (h > 360) h -= 360; else if (h < 0) h += 360; + if (h < 60) return m1 + (m2 - m1) * h / 60; + if (h < 180) return m2; + if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60; + return m1; + } + function vv(h) { + return Math.round(v(h) * 255); + } + return new d3_rgb(vv(h + 120), vv(h), vv(h - 120)); + } + d3.hcl = d3_hcl; + function d3_hcl(h, c, l) { + return this instanceof d3_hcl ? void (this.h = +h, this.c = +c, this.l = +l) : arguments.length < 2 ? h instanceof d3_hcl ? new d3_hcl(h.h, h.c, h.l) : h instanceof d3_lab ? d3_lab_hcl(h.l, h.a, h.b) : d3_lab_hcl((h = d3_rgb_lab((h = d3.rgb(h)).r, h.g, h.b)).l, h.a, h.b) : new d3_hcl(h, c, l); + } + var d3_hclPrototype = d3_hcl.prototype = new d3_color(); + d3_hclPrototype.brighter = function(k) { + return new d3_hcl(this.h, this.c, Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1))); + }; + d3_hclPrototype.darker = function(k) { + return new d3_hcl(this.h, this.c, Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1))); + }; + d3_hclPrototype.rgb = function() { + return d3_hcl_lab(this.h, this.c, this.l).rgb(); + }; + function d3_hcl_lab(h, c, l) { + if (isNaN(h)) h = 0; + if (isNaN(c)) c = 0; + return new d3_lab(l, Math.cos(h *= d3_radians) * c, Math.sin(h) * c); + } + d3.lab = d3_lab; + function d3_lab(l, a, b) { + return this instanceof d3_lab ? void (this.l = +l, this.a = +a, this.b = +b) : arguments.length < 2 ? l instanceof d3_lab ? new d3_lab(l.l, l.a, l.b) : l instanceof d3_hcl ? d3_hcl_lab(l.h, l.c, l.l) : d3_rgb_lab((l = d3_rgb(l)).r, l.g, l.b) : new d3_lab(l, a, b); + } + var d3_lab_K = 18; + var d3_lab_X = .95047, d3_lab_Y = 1, d3_lab_Z = 1.08883; + var d3_labPrototype = d3_lab.prototype = new d3_color(); + d3_labPrototype.brighter = function(k) { + return new d3_lab(Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); + }; + d3_labPrototype.darker = function(k) { + return new d3_lab(Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); + }; + d3_labPrototype.rgb = function() { + return d3_lab_rgb(this.l, this.a, this.b); + }; + function d3_lab_rgb(l, a, b) { + var y = (l + 16) / 116, x = y + a / 500, z = y - b / 200; + x = d3_lab_xyz(x) * d3_lab_X; + y = d3_lab_xyz(y) * d3_lab_Y; + z = d3_lab_xyz(z) * d3_lab_Z; + return new d3_rgb(d3_xyz_rgb(3.2404542 * x - 1.5371385 * y - .4985314 * z), d3_xyz_rgb(-.969266 * x + 1.8760108 * y + .041556 * z), d3_xyz_rgb(.0556434 * x - .2040259 * y + 1.0572252 * z)); + } + function d3_lab_hcl(l, a, b) { + return l > 0 ? new d3_hcl(Math.atan2(b, a) * d3_degrees, Math.sqrt(a * a + b * b), l) : new d3_hcl(NaN, NaN, l); + } + function d3_lab_xyz(x) { + return x > .206893034 ? x * x * x : (x - 4 / 29) / 7.787037; + } + function d3_xyz_lab(x) { + return x > .008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29; + } + function d3_xyz_rgb(r) { + return Math.round(255 * (r <= .00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - .055)); + } + d3.rgb = d3_rgb; + function d3_rgb(r, g, b) { + return this instanceof d3_rgb ? void (this.r = ~~r, this.g = ~~g, this.b = ~~b) : arguments.length < 2 ? r instanceof d3_rgb ? new d3_rgb(r.r, r.g, r.b) : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb) : new d3_rgb(r, g, b); + } + function d3_rgbNumber(value) { + return new d3_rgb(value >> 16, value >> 8 & 255, value & 255); + } + function d3_rgbString(value) { + return d3_rgbNumber(value) + ""; + } + var d3_rgbPrototype = d3_rgb.prototype = new d3_color(); + d3_rgbPrototype.brighter = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + var r = this.r, g = this.g, b = this.b, i = 30; + if (!r && !g && !b) return new d3_rgb(i, i, i); + if (r && r < i) r = i; + if (g && g < i) g = i; + if (b && b < i) b = i; + return new d3_rgb(Math.min(255, r / k), Math.min(255, g / k), Math.min(255, b / k)); + }; + d3_rgbPrototype.darker = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return new d3_rgb(k * this.r, k * this.g, k * this.b); + }; + d3_rgbPrototype.hsl = function() { + return d3_rgb_hsl(this.r, this.g, this.b); + }; + d3_rgbPrototype.toString = function() { + return "#" + d3_rgb_hex(this.r) + d3_rgb_hex(this.g) + d3_rgb_hex(this.b); + }; + function d3_rgb_hex(v) { + return v < 16 ? "0" + Math.max(0, v).toString(16) : Math.min(255, v).toString(16); + } + function d3_rgb_parse(format, rgb, hsl) { + var r = 0, g = 0, b = 0, m1, m2, color; + m1 = /([a-z]+)\((.*)\)/.exec(format = format.toLowerCase()); + if (m1) { + m2 = m1[2].split(","); + switch (m1[1]) { + case "hsl": + { + return hsl(parseFloat(m2[0]), parseFloat(m2[1]) / 100, parseFloat(m2[2]) / 100); + } + + case "rgb": + { + return rgb(d3_rgb_parseNumber(m2[0]), d3_rgb_parseNumber(m2[1]), d3_rgb_parseNumber(m2[2])); + } + } + } + if (color = d3_rgb_names.get(format)) { + return rgb(color.r, color.g, color.b); + } + if (format != null && format.charAt(0) === "#" && !isNaN(color = parseInt(format.slice(1), 16))) { + if (format.length === 4) { + r = (color & 3840) >> 4; + r = r >> 4 | r; + g = color & 240; + g = g >> 4 | g; + b = color & 15; + b = b << 4 | b; + } else if (format.length === 7) { + r = (color & 16711680) >> 16; + g = (color & 65280) >> 8; + b = color & 255; + } + } + return rgb(r, g, b); + } + function d3_rgb_hsl(r, g, b) { + var min = Math.min(r /= 255, g /= 255, b /= 255), max = Math.max(r, g, b), d = max - min, h, s, l = (max + min) / 2; + if (d) { + s = l < .5 ? d / (max + min) : d / (2 - max - min); + if (r == max) h = (g - b) / d + (g < b ? 6 : 0); else if (g == max) h = (b - r) / d + 2; else h = (r - g) / d + 4; + h *= 60; + } else { + h = NaN; + s = l > 0 && l < 1 ? 0 : h; + } + return new d3_hsl(h, s, l); + } + function d3_rgb_lab(r, g, b) { + r = d3_rgb_xyz(r); + g = d3_rgb_xyz(g); + b = d3_rgb_xyz(b); + var x = d3_xyz_lab((.4124564 * r + .3575761 * g + .1804375 * b) / d3_lab_X), y = d3_xyz_lab((.2126729 * r + .7151522 * g + .072175 * b) / d3_lab_Y), z = d3_xyz_lab((.0193339 * r + .119192 * g + .9503041 * b) / d3_lab_Z); + return d3_lab(116 * y - 16, 500 * (x - y), 200 * (y - z)); + } + function d3_rgb_xyz(r) { + return (r /= 255) <= .04045 ? r / 12.92 : Math.pow((r + .055) / 1.055, 2.4); + } + function d3_rgb_parseNumber(c) { + var f = parseFloat(c); + return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f; + } + var d3_rgb_names = d3.map({ + aliceblue: 15792383, + antiquewhite: 16444375, + aqua: 65535, + aquamarine: 8388564, + azure: 15794175, + beige: 16119260, + bisque: 16770244, + black: 0, + blanchedalmond: 16772045, + blue: 255, + blueviolet: 9055202, + brown: 10824234, + burlywood: 14596231, + cadetblue: 6266528, + chartreuse: 8388352, + chocolate: 13789470, + coral: 16744272, + cornflowerblue: 6591981, + cornsilk: 16775388, + crimson: 14423100, + cyan: 65535, + darkblue: 139, + darkcyan: 35723, + darkgoldenrod: 12092939, + darkgray: 11119017, + darkgreen: 25600, + darkgrey: 11119017, + darkkhaki: 12433259, + darkmagenta: 9109643, + darkolivegreen: 5597999, + darkorange: 16747520, + darkorchid: 10040012, + darkred: 9109504, + darksalmon: 15308410, + darkseagreen: 9419919, + darkslateblue: 4734347, + darkslategray: 3100495, + darkslategrey: 3100495, + darkturquoise: 52945, + darkviolet: 9699539, + deeppink: 16716947, + deepskyblue: 49151, + dimgray: 6908265, + dimgrey: 6908265, + dodgerblue: 2003199, + firebrick: 11674146, + floralwhite: 16775920, + forestgreen: 2263842, + fuchsia: 16711935, + gainsboro: 14474460, + ghostwhite: 16316671, + gold: 16766720, + goldenrod: 14329120, + gray: 8421504, + green: 32768, + greenyellow: 11403055, + grey: 8421504, + honeydew: 15794160, + hotpink: 16738740, + indianred: 13458524, + indigo: 4915330, + ivory: 16777200, + khaki: 15787660, + lavender: 15132410, + lavenderblush: 16773365, + lawngreen: 8190976, + lemonchiffon: 16775885, + lightblue: 11393254, + lightcoral: 15761536, + lightcyan: 14745599, + lightgoldenrodyellow: 16448210, + lightgray: 13882323, + lightgreen: 9498256, + lightgrey: 13882323, + lightpink: 16758465, + lightsalmon: 16752762, + lightseagreen: 2142890, + lightskyblue: 8900346, + lightslategray: 7833753, + lightslategrey: 7833753, + lightsteelblue: 11584734, + lightyellow: 16777184, + lime: 65280, + limegreen: 3329330, + linen: 16445670, + magenta: 16711935, + maroon: 8388608, + mediumaquamarine: 6737322, + mediumblue: 205, + mediumorchid: 12211667, + mediumpurple: 9662683, + mediumseagreen: 3978097, + mediumslateblue: 8087790, + mediumspringgreen: 64154, + mediumturquoise: 4772300, + mediumvioletred: 13047173, + midnightblue: 1644912, + mintcream: 16121850, + mistyrose: 16770273, + moccasin: 16770229, + navajowhite: 16768685, + navy: 128, + oldlace: 16643558, + olive: 8421376, + olivedrab: 7048739, + orange: 16753920, + orangered: 16729344, + orchid: 14315734, + palegoldenrod: 15657130, + palegreen: 10025880, + paleturquoise: 11529966, + palevioletred: 14381203, + papayawhip: 16773077, + peachpuff: 16767673, + peru: 13468991, + pink: 16761035, + plum: 14524637, + powderblue: 11591910, + purple: 8388736, + rebeccapurple: 6697881, + red: 16711680, + rosybrown: 12357519, + royalblue: 4286945, + saddlebrown: 9127187, + salmon: 16416882, + sandybrown: 16032864, + seagreen: 3050327, + seashell: 16774638, + sienna: 10506797, + silver: 12632256, + skyblue: 8900331, + slateblue: 6970061, + slategray: 7372944, + slategrey: 7372944, + snow: 16775930, + springgreen: 65407, + steelblue: 4620980, + tan: 13808780, + teal: 32896, + thistle: 14204888, + tomato: 16737095, + turquoise: 4251856, + violet: 15631086, + wheat: 16113331, + white: 16777215, + whitesmoke: 16119285, + yellow: 16776960, + yellowgreen: 10145074 + }); + d3_rgb_names.forEach(function(key, value) { + d3_rgb_names.set(key, d3_rgbNumber(value)); + }); + function d3_functor(v) { + return typeof v === "function" ? v : function() { + return v; + }; + } + d3.functor = d3_functor; + d3.xhr = d3_xhrType(d3_identity); + function d3_xhrType(response) { + return function(url, mimeType, callback) { + if (arguments.length === 2 && typeof mimeType === "function") callback = mimeType, + mimeType = null; + return d3_xhr(url, mimeType, response, callback); + }; + } + function d3_xhr(url, mimeType, response, callback) { + var xhr = {}, dispatch = d3.dispatch("beforesend", "progress", "load", "error"), headers = {}, request = new XMLHttpRequest(), responseType = null; + if (this.XDomainRequest && !("withCredentials" in request) && /^(http(s)?:)?\/\//.test(url)) request = new XDomainRequest(); + "onload" in request ? request.onload = request.onerror = respond : request.onreadystatechange = function() { + request.readyState > 3 && respond(); + }; + function respond() { + var status = request.status, result; + if (!status && d3_xhrHasResponse(request) || status >= 200 && status < 300 || status === 304) { + try { + result = response.call(xhr, request); + } catch (e) { + dispatch.error.call(xhr, e); + return; + } + dispatch.load.call(xhr, result); + } else { + dispatch.error.call(xhr, request); + } + } + request.onprogress = function(event) { + var o = d3.event; + d3.event = event; + try { + dispatch.progress.call(xhr, request); + } finally { + d3.event = o; + } + }; + xhr.header = function(name, value) { + name = (name + "").toLowerCase(); + if (arguments.length < 2) return headers[name]; + if (value == null) delete headers[name]; else headers[name] = value + ""; + return xhr; + }; + xhr.mimeType = function(value) { + if (!arguments.length) return mimeType; + mimeType = value == null ? null : value + ""; + return xhr; + }; + xhr.responseType = function(value) { + if (!arguments.length) return responseType; + responseType = value; + return xhr; + }; + xhr.response = function(value) { + response = value; + return xhr; + }; + [ "get", "post" ].forEach(function(method) { + xhr[method] = function() { + return xhr.send.apply(xhr, [ method ].concat(d3_array(arguments))); + }; + }); + xhr.send = function(method, data, callback) { + if (arguments.length === 2 && typeof data === "function") callback = data, data = null; + request.open(method, url, true); + if (mimeType != null && !("accept" in headers)) headers["accept"] = mimeType + ",*/*"; + if (request.setRequestHeader) for (var name in headers) request.setRequestHeader(name, headers[name]); + if (mimeType != null && request.overrideMimeType) request.overrideMimeType(mimeType); + if (responseType != null) request.responseType = responseType; + if (callback != null) xhr.on("error", callback).on("load", function(request) { + callback(null, request); + }); + dispatch.beforesend.call(xhr, request); + request.send(data == null ? null : data); + return xhr; + }; + xhr.abort = function() { + request.abort(); + return xhr; + }; + d3.rebind(xhr, dispatch, "on"); + return callback == null ? xhr : xhr.get(d3_xhr_fixCallback(callback)); + } + function d3_xhr_fixCallback(callback) { + return callback.length === 1 ? function(error, request) { + callback(error == null ? request : null); + } : callback; + } + function d3_xhrHasResponse(request) { + var type = request.responseType; + return type && type !== "text" ? request.response : request.responseText; + } + d3.dsv = function(delimiter, mimeType) { + var reFormat = new RegExp('["' + delimiter + "\n]"), delimiterCode = delimiter.charCodeAt(0); + function dsv(url, row, callback) { + if (arguments.length < 3) callback = row, row = null; + var xhr = d3_xhr(url, mimeType, row == null ? response : typedResponse(row), callback); + xhr.row = function(_) { + return arguments.length ? xhr.response((row = _) == null ? response : typedResponse(_)) : row; + }; + return xhr; + } + function response(request) { + return dsv.parse(request.responseText); + } + function typedResponse(f) { + return function(request) { + return dsv.parse(request.responseText, f); + }; + } + dsv.parse = function(text, f) { + var o; + return dsv.parseRows(text, function(row, i) { + if (o) return o(row, i - 1); + var a = new Function("d", "return {" + row.map(function(name, i) { + return JSON.stringify(name) + ": d[" + i + "]"; + }).join(",") + "}"); + o = f ? function(row, i) { + return f(a(row), i); + } : a; + }); + }; + dsv.parseRows = function(text, f) { + var EOL = {}, EOF = {}, rows = [], N = text.length, I = 0, n = 0, t, eol; + function token() { + if (I >= N) return EOF; + if (eol) return eol = false, EOL; + var j = I; + if (text.charCodeAt(j) === 34) { + var i = j; + while (i++ < N) { + if (text.charCodeAt(i) === 34) { + if (text.charCodeAt(i + 1) !== 34) break; + ++i; + } + } + I = i + 2; + var c = text.charCodeAt(i + 1); + if (c === 13) { + eol = true; + if (text.charCodeAt(i + 2) === 10) ++I; + } else if (c === 10) { + eol = true; + } + return text.slice(j + 1, i).replace(/""/g, '"'); + } + while (I < N) { + var c = text.charCodeAt(I++), k = 1; + if (c === 10) eol = true; else if (c === 13) { + eol = true; + if (text.charCodeAt(I) === 10) ++I, ++k; + } else if (c !== delimiterCode) continue; + return text.slice(j, I - k); + } + return text.slice(j); + } + while ((t = token()) !== EOF) { + var a = []; + while (t !== EOL && t !== EOF) { + a.push(t); + t = token(); + } + if (f && (a = f(a, n++)) == null) continue; + rows.push(a); + } + return rows; + }; + dsv.format = function(rows) { + if (Array.isArray(rows[0])) return dsv.formatRows(rows); + var fieldSet = new d3_Set(), fields = []; + rows.forEach(function(row) { + for (var field in row) { + if (!fieldSet.has(field)) { + fields.push(fieldSet.add(field)); + } + } + }); + return [ fields.map(formatValue).join(delimiter) ].concat(rows.map(function(row) { + return fields.map(function(field) { + return formatValue(row[field]); + }).join(delimiter); + })).join("\n"); + }; + dsv.formatRows = function(rows) { + return rows.map(formatRow).join("\n"); + }; + function formatRow(row) { + return row.map(formatValue).join(delimiter); + } + function formatValue(text) { + return reFormat.test(text) ? '"' + text.replace(/\"/g, '""') + '"' : text; + } + return dsv; + }; + d3.csv = d3.dsv(",", "text/csv"); + d3.tsv = d3.dsv(" ", "text/tab-separated-values"); + var d3_timer_queueHead, d3_timer_queueTail, d3_timer_interval, d3_timer_timeout, d3_timer_frame = this[d3_vendorSymbol(this, "requestAnimationFrame")] || function(callback) { + setTimeout(callback, 17); + }; + d3.timer = function() { + d3_timer.apply(this, arguments); + }; + function d3_timer(callback, delay, then) { + var n = arguments.length; + if (n < 2) delay = 0; + if (n < 3) then = Date.now(); + var time = then + delay, timer = { + c: callback, + t: time, + n: null + }; + if (d3_timer_queueTail) d3_timer_queueTail.n = timer; else d3_timer_queueHead = timer; + d3_timer_queueTail = timer; + if (!d3_timer_interval) { + d3_timer_timeout = clearTimeout(d3_timer_timeout); + d3_timer_interval = 1; + d3_timer_frame(d3_timer_step); + } + return timer; + } + function d3_timer_step() { + var now = d3_timer_mark(), delay = d3_timer_sweep() - now; + if (delay > 24) { + if (isFinite(delay)) { + clearTimeout(d3_timer_timeout); + d3_timer_timeout = setTimeout(d3_timer_step, delay); + } + d3_timer_interval = 0; + } else { + d3_timer_interval = 1; + d3_timer_frame(d3_timer_step); + } + } + d3.timer.flush = function() { + d3_timer_mark(); + d3_timer_sweep(); + }; + function d3_timer_mark() { + var now = Date.now(), timer = d3_timer_queueHead; + while (timer) { + if (now >= timer.t && timer.c(now - timer.t)) timer.c = null; + timer = timer.n; + } + return now; + } + function d3_timer_sweep() { + var t0, t1 = d3_timer_queueHead, time = Infinity; + while (t1) { + if (t1.c) { + if (t1.t < time) time = t1.t; + t1 = (t0 = t1).n; + } else { + t1 = t0 ? t0.n = t1.n : d3_timer_queueHead = t1.n; + } + } + d3_timer_queueTail = t0; + return time; + } + function d3_format_precision(x, p) { + return p - (x ? Math.ceil(Math.log(x) / Math.LN10) : 1); + } + d3.round = function(x, n) { + return n ? Math.round(x * (n = Math.pow(10, n))) / n : Math.round(x); + }; + var d3_formatPrefixes = [ "y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" ].map(d3_formatPrefix); + d3.formatPrefix = function(value, precision) { + var i = 0; + if (value = +value) { + if (value < 0) value *= -1; + if (precision) value = d3.round(value, d3_format_precision(value, precision)); + i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10); + i = Math.max(-24, Math.min(24, Math.floor((i - 1) / 3) * 3)); + } + return d3_formatPrefixes[8 + i / 3]; + }; + function d3_formatPrefix(d, i) { + var k = Math.pow(10, abs(8 - i) * 3); + return { + scale: i > 8 ? function(d) { + return d / k; + } : function(d) { + return d * k; + }, + symbol: d + }; + } + function d3_locale_numberFormat(locale) { + var locale_decimal = locale.decimal, locale_thousands = locale.thousands, locale_grouping = locale.grouping, locale_currency = locale.currency, formatGroup = locale_grouping && locale_thousands ? function(value, width) { + var i = value.length, t = [], j = 0, g = locale_grouping[0], length = 0; + while (i > 0 && g > 0) { + if (length + g + 1 > width) g = Math.max(1, width - length); + t.push(value.substring(i -= g, i + g)); + if ((length += g + 1) > width) break; + g = locale_grouping[j = (j + 1) % locale_grouping.length]; + } + return t.reverse().join(locale_thousands); + } : d3_identity; + return function(specifier) { + var match = d3_format_re.exec(specifier), fill = match[1] || " ", align = match[2] || ">", sign = match[3] || "-", symbol = match[4] || "", zfill = match[5], width = +match[6], comma = match[7], precision = match[8], type = match[9], scale = 1, prefix = "", suffix = "", integer = false, exponent = true; + if (precision) precision = +precision.substring(1); + if (zfill || fill === "0" && align === "=") { + zfill = fill = "0"; + align = "="; + } + switch (type) { + case "n": + comma = true; + type = "g"; + break; + + case "%": + scale = 100; + suffix = "%"; + type = "f"; + break; + + case "p": + scale = 100; + suffix = "%"; + type = "r"; + break; + + case "b": + case "o": + case "x": + case "X": + if (symbol === "#") prefix = "0" + type.toLowerCase(); + + case "c": + exponent = false; + + case "d": + integer = true; + precision = 0; + break; + + case "s": + scale = -1; + type = "r"; + break; + } + if (symbol === "$") prefix = locale_currency[0], suffix = locale_currency[1]; + if (type == "r" && !precision) type = "g"; + if (precision != null) { + if (type == "g") precision = Math.max(1, Math.min(21, precision)); else if (type == "e" || type == "f") precision = Math.max(0, Math.min(20, precision)); + } + type = d3_format_types.get(type) || d3_format_typeDefault; + var zcomma = zfill && comma; + return function(value) { + var fullSuffix = suffix; + if (integer && value % 1) return ""; + var negative = value < 0 || value === 0 && 1 / value < 0 ? (value = -value, "-") : sign === "-" ? "" : sign; + if (scale < 0) { + var unit = d3.formatPrefix(value, precision); + value = unit.scale(value); + fullSuffix = unit.symbol + suffix; + } else { + value *= scale; + } + value = type(value, precision); + var i = value.lastIndexOf("."), before, after; + if (i < 0) { + var j = exponent ? value.lastIndexOf("e") : -1; + if (j < 0) before = value, after = ""; else before = value.substring(0, j), after = value.substring(j); + } else { + before = value.substring(0, i); + after = locale_decimal + value.substring(i + 1); + } + if (!zfill && comma) before = formatGroup(before, Infinity); + var length = prefix.length + before.length + after.length + (zcomma ? 0 : negative.length), padding = length < width ? new Array(length = width - length + 1).join(fill) : ""; + if (zcomma) before = formatGroup(padding + before, padding.length ? width - after.length : Infinity); + negative += prefix; + value = before + after; + return (align === "<" ? negative + value + padding : align === ">" ? padding + negative + value : align === "^" ? padding.substring(0, length >>= 1) + negative + value + padding.substring(length) : negative + (zcomma ? value : padding + value)) + fullSuffix; + }; + }; + } + var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i; + var d3_format_types = d3.map({ + b: function(x) { + return x.toString(2); + }, + c: function(x) { + return String.fromCharCode(x); + }, + o: function(x) { + return x.toString(8); + }, + x: function(x) { + return x.toString(16); + }, + X: function(x) { + return x.toString(16).toUpperCase(); + }, + g: function(x, p) { + return x.toPrecision(p); + }, + e: function(x, p) { + return x.toExponential(p); + }, + f: function(x, p) { + return x.toFixed(p); + }, + r: function(x, p) { + return (x = d3.round(x, d3_format_precision(x, p))).toFixed(Math.max(0, Math.min(20, d3_format_precision(x * (1 + 1e-15), p)))); + } + }); + function d3_format_typeDefault(x) { + return x + ""; + } + var d3_time = d3.time = {}, d3_date = Date; + function d3_date_utc() { + this._ = new Date(arguments.length > 1 ? Date.UTC.apply(this, arguments) : arguments[0]); + } + d3_date_utc.prototype = { + getDate: function() { + return this._.getUTCDate(); + }, + getDay: function() { + return this._.getUTCDay(); + }, + getFullYear: function() { + return this._.getUTCFullYear(); + }, + getHours: function() { + return this._.getUTCHours(); + }, + getMilliseconds: function() { + return this._.getUTCMilliseconds(); + }, + getMinutes: function() { + return this._.getUTCMinutes(); + }, + getMonth: function() { + return this._.getUTCMonth(); + }, + getSeconds: function() { + return this._.getUTCSeconds(); + }, + getTime: function() { + return this._.getTime(); + }, + getTimezoneOffset: function() { + return 0; + }, + valueOf: function() { + return this._.valueOf(); + }, + setDate: function() { + d3_time_prototype.setUTCDate.apply(this._, arguments); + }, + setDay: function() { + d3_time_prototype.setUTCDay.apply(this._, arguments); + }, + setFullYear: function() { + d3_time_prototype.setUTCFullYear.apply(this._, arguments); + }, + setHours: function() { + d3_time_prototype.setUTCHours.apply(this._, arguments); + }, + setMilliseconds: function() { + d3_time_prototype.setUTCMilliseconds.apply(this._, arguments); + }, + setMinutes: function() { + d3_time_prototype.setUTCMinutes.apply(this._, arguments); + }, + setMonth: function() { + d3_time_prototype.setUTCMonth.apply(this._, arguments); + }, + setSeconds: function() { + d3_time_prototype.setUTCSeconds.apply(this._, arguments); + }, + setTime: function() { + d3_time_prototype.setTime.apply(this._, arguments); + } + }; + var d3_time_prototype = Date.prototype; + function d3_time_interval(local, step, number) { + function round(date) { + var d0 = local(date), d1 = offset(d0, 1); + return date - d0 < d1 - date ? d0 : d1; + } + function ceil(date) { + step(date = local(new d3_date(date - 1)), 1); + return date; + } + function offset(date, k) { + step(date = new d3_date(+date), k); + return date; + } + function range(t0, t1, dt) { + var time = ceil(t0), times = []; + if (dt > 1) { + while (time < t1) { + if (!(number(time) % dt)) times.push(new Date(+time)); + step(time, 1); + } + } else { + while (time < t1) times.push(new Date(+time)), step(time, 1); + } + return times; + } + function range_utc(t0, t1, dt) { + try { + d3_date = d3_date_utc; + var utc = new d3_date_utc(); + utc._ = t0; + return range(utc, t1, dt); + } finally { + d3_date = Date; + } + } + local.floor = local; + local.round = round; + local.ceil = ceil; + local.offset = offset; + local.range = range; + var utc = local.utc = d3_time_interval_utc(local); + utc.floor = utc; + utc.round = d3_time_interval_utc(round); + utc.ceil = d3_time_interval_utc(ceil); + utc.offset = d3_time_interval_utc(offset); + utc.range = range_utc; + return local; + } + function d3_time_interval_utc(method) { + return function(date, k) { + try { + d3_date = d3_date_utc; + var utc = new d3_date_utc(); + utc._ = date; + return method(utc, k)._; + } finally { + d3_date = Date; + } + }; + } + d3_time.year = d3_time_interval(function(date) { + date = d3_time.day(date); + date.setMonth(0, 1); + return date; + }, function(date, offset) { + date.setFullYear(date.getFullYear() + offset); + }, function(date) { + return date.getFullYear(); + }); + d3_time.years = d3_time.year.range; + d3_time.years.utc = d3_time.year.utc.range; + d3_time.day = d3_time_interval(function(date) { + var day = new d3_date(2e3, 0); + day.setFullYear(date.getFullYear(), date.getMonth(), date.getDate()); + return day; + }, function(date, offset) { + date.setDate(date.getDate() + offset); + }, function(date) { + return date.getDate() - 1; + }); + d3_time.days = d3_time.day.range; + d3_time.days.utc = d3_time.day.utc.range; + d3_time.dayOfYear = function(date) { + var year = d3_time.year(date); + return Math.floor((date - year - (date.getTimezoneOffset() - year.getTimezoneOffset()) * 6e4) / 864e5); + }; + [ "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday" ].forEach(function(day, i) { + i = 7 - i; + var interval = d3_time[day] = d3_time_interval(function(date) { + (date = d3_time.day(date)).setDate(date.getDate() - (date.getDay() + i) % 7); + return date; + }, function(date, offset) { + date.setDate(date.getDate() + Math.floor(offset) * 7); + }, function(date) { + var day = d3_time.year(date).getDay(); + return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7) - (day !== i); + }); + d3_time[day + "s"] = interval.range; + d3_time[day + "s"].utc = interval.utc.range; + d3_time[day + "OfYear"] = function(date) { + var day = d3_time.year(date).getDay(); + return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7); + }; + }); + d3_time.week = d3_time.sunday; + d3_time.weeks = d3_time.sunday.range; + d3_time.weeks.utc = d3_time.sunday.utc.range; + d3_time.weekOfYear = d3_time.sundayOfYear; + function d3_locale_timeFormat(locale) { + var locale_dateTime = locale.dateTime, locale_date = locale.date, locale_time = locale.time, locale_periods = locale.periods, locale_days = locale.days, locale_shortDays = locale.shortDays, locale_months = locale.months, locale_shortMonths = locale.shortMonths; + function d3_time_format(template) { + var n = template.length; + function format(date) { + var string = [], i = -1, j = 0, c, p, f; + while (++i < n) { + if (template.charCodeAt(i) === 37) { + string.push(template.slice(j, i)); + if ((p = d3_time_formatPads[c = template.charAt(++i)]) != null) c = template.charAt(++i); + if (f = d3_time_formats[c]) c = f(date, p == null ? c === "e" ? " " : "0" : p); + string.push(c); + j = i + 1; + } + } + string.push(template.slice(j, i)); + return string.join(""); + } + format.parse = function(string) { + var d = { + y: 1900, + m: 0, + d: 1, + H: 0, + M: 0, + S: 0, + L: 0, + Z: null + }, i = d3_time_parse(d, template, string, 0); + if (i != string.length) return null; + if ("p" in d) d.H = d.H % 12 + d.p * 12; + var localZ = d.Z != null && d3_date !== d3_date_utc, date = new (localZ ? d3_date_utc : d3_date)(); + if ("j" in d) date.setFullYear(d.y, 0, d.j); else if ("W" in d || "U" in d) { + if (!("w" in d)) d.w = "W" in d ? 1 : 0; + date.setFullYear(d.y, 0, 1); + date.setFullYear(d.y, 0, "W" in d ? (d.w + 6) % 7 + d.W * 7 - (date.getDay() + 5) % 7 : d.w + d.U * 7 - (date.getDay() + 6) % 7); + } else date.setFullYear(d.y, d.m, d.d); + date.setHours(d.H + (d.Z / 100 | 0), d.M + d.Z % 100, d.S, d.L); + return localZ ? date._ : date; + }; + format.toString = function() { + return template; + }; + return format; + } + function d3_time_parse(date, template, string, j) { + var c, p, t, i = 0, n = template.length, m = string.length; + while (i < n) { + if (j >= m) return -1; + c = template.charCodeAt(i++); + if (c === 37) { + t = template.charAt(i++); + p = d3_time_parsers[t in d3_time_formatPads ? template.charAt(i++) : t]; + if (!p || (j = p(date, string, j)) < 0) return -1; + } else if (c != string.charCodeAt(j++)) { + return -1; + } + } + return j; + } + d3_time_format.utc = function(template) { + var local = d3_time_format(template); + function format(date) { + try { + d3_date = d3_date_utc; + var utc = new d3_date(); + utc._ = date; + return local(utc); + } finally { + d3_date = Date; + } + } + format.parse = function(string) { + try { + d3_date = d3_date_utc; + var date = local.parse(string); + return date && date._; + } finally { + d3_date = Date; + } + }; + format.toString = local.toString; + return format; + }; + d3_time_format.multi = d3_time_format.utc.multi = d3_time_formatMulti; + var d3_time_periodLookup = d3.map(), d3_time_dayRe = d3_time_formatRe(locale_days), d3_time_dayLookup = d3_time_formatLookup(locale_days), d3_time_dayAbbrevRe = d3_time_formatRe(locale_shortDays), d3_time_dayAbbrevLookup = d3_time_formatLookup(locale_shortDays), d3_time_monthRe = d3_time_formatRe(locale_months), d3_time_monthLookup = d3_time_formatLookup(locale_months), d3_time_monthAbbrevRe = d3_time_formatRe(locale_shortMonths), d3_time_monthAbbrevLookup = d3_time_formatLookup(locale_shortMonths); + locale_periods.forEach(function(p, i) { + d3_time_periodLookup.set(p.toLowerCase(), i); + }); + var d3_time_formats = { + a: function(d) { + return locale_shortDays[d.getDay()]; + }, + A: function(d) { + return locale_days[d.getDay()]; + }, + b: function(d) { + return locale_shortMonths[d.getMonth()]; + }, + B: function(d) { + return locale_months[d.getMonth()]; + }, + c: d3_time_format(locale_dateTime), + d: function(d, p) { + return d3_time_formatPad(d.getDate(), p, 2); + }, + e: function(d, p) { + return d3_time_formatPad(d.getDate(), p, 2); + }, + H: function(d, p) { + return d3_time_formatPad(d.getHours(), p, 2); + }, + I: function(d, p) { + return d3_time_formatPad(d.getHours() % 12 || 12, p, 2); + }, + j: function(d, p) { + return d3_time_formatPad(1 + d3_time.dayOfYear(d), p, 3); + }, + L: function(d, p) { + return d3_time_formatPad(d.getMilliseconds(), p, 3); + }, + m: function(d, p) { + return d3_time_formatPad(d.getMonth() + 1, p, 2); + }, + M: function(d, p) { + return d3_time_formatPad(d.getMinutes(), p, 2); + }, + p: function(d) { + return locale_periods[+(d.getHours() >= 12)]; + }, + S: function(d, p) { + return d3_time_formatPad(d.getSeconds(), p, 2); + }, + U: function(d, p) { + return d3_time_formatPad(d3_time.sundayOfYear(d), p, 2); + }, + w: function(d) { + return d.getDay(); + }, + W: function(d, p) { + return d3_time_formatPad(d3_time.mondayOfYear(d), p, 2); + }, + x: d3_time_format(locale_date), + X: d3_time_format(locale_time), + y: function(d, p) { + return d3_time_formatPad(d.getFullYear() % 100, p, 2); + }, + Y: function(d, p) { + return d3_time_formatPad(d.getFullYear() % 1e4, p, 4); + }, + Z: d3_time_zone, + "%": function() { + return "%"; + } + }; + var d3_time_parsers = { + a: d3_time_parseWeekdayAbbrev, + A: d3_time_parseWeekday, + b: d3_time_parseMonthAbbrev, + B: d3_time_parseMonth, + c: d3_time_parseLocaleFull, + d: d3_time_parseDay, + e: d3_time_parseDay, + H: d3_time_parseHour24, + I: d3_time_parseHour24, + j: d3_time_parseDayOfYear, + L: d3_time_parseMilliseconds, + m: d3_time_parseMonthNumber, + M: d3_time_parseMinutes, + p: d3_time_parseAmPm, + S: d3_time_parseSeconds, + U: d3_time_parseWeekNumberSunday, + w: d3_time_parseWeekdayNumber, + W: d3_time_parseWeekNumberMonday, + x: d3_time_parseLocaleDate, + X: d3_time_parseLocaleTime, + y: d3_time_parseYear, + Y: d3_time_parseFullYear, + Z: d3_time_parseZone, + "%": d3_time_parseLiteralPercent + }; + function d3_time_parseWeekdayAbbrev(date, string, i) { + d3_time_dayAbbrevRe.lastIndex = 0; + var n = d3_time_dayAbbrevRe.exec(string.slice(i)); + return n ? (date.w = d3_time_dayAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseWeekday(date, string, i) { + d3_time_dayRe.lastIndex = 0; + var n = d3_time_dayRe.exec(string.slice(i)); + return n ? (date.w = d3_time_dayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseMonthAbbrev(date, string, i) { + d3_time_monthAbbrevRe.lastIndex = 0; + var n = d3_time_monthAbbrevRe.exec(string.slice(i)); + return n ? (date.m = d3_time_monthAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseMonth(date, string, i) { + d3_time_monthRe.lastIndex = 0; + var n = d3_time_monthRe.exec(string.slice(i)); + return n ? (date.m = d3_time_monthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseLocaleFull(date, string, i) { + return d3_time_parse(date, d3_time_formats.c.toString(), string, i); + } + function d3_time_parseLocaleDate(date, string, i) { + return d3_time_parse(date, d3_time_formats.x.toString(), string, i); + } + function d3_time_parseLocaleTime(date, string, i) { + return d3_time_parse(date, d3_time_formats.X.toString(), string, i); + } + function d3_time_parseAmPm(date, string, i) { + var n = d3_time_periodLookup.get(string.slice(i, i += 2).toLowerCase()); + return n == null ? -1 : (date.p = n, i); + } + return d3_time_format; + } + var d3_time_formatPads = { + "-": "", + _: " ", + "0": "0" + }, d3_time_numberRe = /^\s*\d+/, d3_time_percentRe = /^%/; + function d3_time_formatPad(value, fill, width) { + var sign = value < 0 ? "-" : "", string = (sign ? -value : value) + "", length = string.length; + return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string); + } + function d3_time_formatRe(names) { + return new RegExp("^(?:" + names.map(d3.requote).join("|") + ")", "i"); + } + function d3_time_formatLookup(names) { + var map = new d3_Map(), i = -1, n = names.length; + while (++i < n) map.set(names[i].toLowerCase(), i); + return map; + } + function d3_time_parseWeekdayNumber(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 1)); + return n ? (date.w = +n[0], i + n[0].length) : -1; + } + function d3_time_parseWeekNumberSunday(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i)); + return n ? (date.U = +n[0], i + n[0].length) : -1; + } + function d3_time_parseWeekNumberMonday(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i)); + return n ? (date.W = +n[0], i + n[0].length) : -1; + } + function d3_time_parseFullYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 4)); + return n ? (date.y = +n[0], i + n[0].length) : -1; + } + function d3_time_parseYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.y = d3_time_expandYear(+n[0]), i + n[0].length) : -1; + } + function d3_time_parseZone(date, string, i) { + return /^[+-]\d{4}$/.test(string = string.slice(i, i + 5)) ? (date.Z = -string, + i + 5) : -1; + } + function d3_time_expandYear(d) { + return d + (d > 68 ? 1900 : 2e3); + } + function d3_time_parseMonthNumber(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.m = n[0] - 1, i + n[0].length) : -1; + } + function d3_time_parseDay(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.d = +n[0], i + n[0].length) : -1; + } + function d3_time_parseDayOfYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 3)); + return n ? (date.j = +n[0], i + n[0].length) : -1; + } + function d3_time_parseHour24(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.H = +n[0], i + n[0].length) : -1; + } + function d3_time_parseMinutes(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.M = +n[0], i + n[0].length) : -1; + } + function d3_time_parseSeconds(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.S = +n[0], i + n[0].length) : -1; + } + function d3_time_parseMilliseconds(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 3)); + return n ? (date.L = +n[0], i + n[0].length) : -1; + } + function d3_time_zone(d) { + var z = d.getTimezoneOffset(), zs = z > 0 ? "-" : "+", zh = abs(z) / 60 | 0, zm = abs(z) % 60; + return zs + d3_time_formatPad(zh, "0", 2) + d3_time_formatPad(zm, "0", 2); + } + function d3_time_parseLiteralPercent(date, string, i) { + d3_time_percentRe.lastIndex = 0; + var n = d3_time_percentRe.exec(string.slice(i, i + 1)); + return n ? i + n[0].length : -1; + } + function d3_time_formatMulti(formats) { + var n = formats.length, i = -1; + while (++i < n) formats[i][0] = this(formats[i][0]); + return function(date) { + var i = 0, f = formats[i]; + while (!f[1](date)) f = formats[++i]; + return f[0](date); + }; + } + d3.locale = function(locale) { + return { + numberFormat: d3_locale_numberFormat(locale), + timeFormat: d3_locale_timeFormat(locale) + }; + }; + var d3_locale_enUS = d3.locale({ + decimal: ".", + thousands: ",", + grouping: [ 3 ], + currency: [ "$", "" ], + dateTime: "%a %b %e %X %Y", + date: "%m/%d/%Y", + time: "%H:%M:%S", + periods: [ "AM", "PM" ], + days: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], + shortDays: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], + months: [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ], + shortMonths: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ] + }); + d3.format = d3_locale_enUS.numberFormat; + d3.geo = {}; + function d3_adder() {} + d3_adder.prototype = { + s: 0, + t: 0, + add: function(y) { + d3_adderSum(y, this.t, d3_adderTemp); + d3_adderSum(d3_adderTemp.s, this.s, this); + if (this.s) this.t += d3_adderTemp.t; else this.s = d3_adderTemp.t; + }, + reset: function() { + this.s = this.t = 0; + }, + valueOf: function() { + return this.s; + } + }; + var d3_adderTemp = new d3_adder(); + function d3_adderSum(a, b, o) { + var x = o.s = a + b, bv = x - a, av = x - bv; + o.t = a - av + (b - bv); + } + d3.geo.stream = function(object, listener) { + if (object && d3_geo_streamObjectType.hasOwnProperty(object.type)) { + d3_geo_streamObjectType[object.type](object, listener); + } else { + d3_geo_streamGeometry(object, listener); + } + }; + function d3_geo_streamGeometry(geometry, listener) { + if (geometry && d3_geo_streamGeometryType.hasOwnProperty(geometry.type)) { + d3_geo_streamGeometryType[geometry.type](geometry, listener); + } + } + var d3_geo_streamObjectType = { + Feature: function(feature, listener) { + d3_geo_streamGeometry(feature.geometry, listener); + }, + FeatureCollection: function(object, listener) { + var features = object.features, i = -1, n = features.length; + while (++i < n) d3_geo_streamGeometry(features[i].geometry, listener); + } + }; + var d3_geo_streamGeometryType = { + Sphere: function(object, listener) { + listener.sphere(); + }, + Point: function(object, listener) { + object = object.coordinates; + listener.point(object[0], object[1], object[2]); + }, + MultiPoint: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) object = coordinates[i], listener.point(object[0], object[1], object[2]); + }, + LineString: function(object, listener) { + d3_geo_streamLine(object.coordinates, listener, 0); + }, + MultiLineString: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) d3_geo_streamLine(coordinates[i], listener, 0); + }, + Polygon: function(object, listener) { + d3_geo_streamPolygon(object.coordinates, listener); + }, + MultiPolygon: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) d3_geo_streamPolygon(coordinates[i], listener); + }, + GeometryCollection: function(object, listener) { + var geometries = object.geometries, i = -1, n = geometries.length; + while (++i < n) d3_geo_streamGeometry(geometries[i], listener); + } + }; + function d3_geo_streamLine(coordinates, listener, closed) { + var i = -1, n = coordinates.length - closed, coordinate; + listener.lineStart(); + while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1], coordinate[2]); + listener.lineEnd(); + } + function d3_geo_streamPolygon(coordinates, listener) { + var i = -1, n = coordinates.length; + listener.polygonStart(); + while (++i < n) d3_geo_streamLine(coordinates[i], listener, 1); + listener.polygonEnd(); + } + d3.geo.area = function(object) { + d3_geo_areaSum = 0; + d3.geo.stream(object, d3_geo_area); + return d3_geo_areaSum; + }; + var d3_geo_areaSum, d3_geo_areaRingSum = new d3_adder(); + var d3_geo_area = { + sphere: function() { + d3_geo_areaSum += 4 * π; + }, + point: d3_noop, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: function() { + d3_geo_areaRingSum.reset(); + d3_geo_area.lineStart = d3_geo_areaRingStart; + }, + polygonEnd: function() { + var area = 2 * d3_geo_areaRingSum; + d3_geo_areaSum += area < 0 ? 4 * π + area : area; + d3_geo_area.lineStart = d3_geo_area.lineEnd = d3_geo_area.point = d3_noop; + } + }; + function d3_geo_areaRingStart() { + var λ00, φ00, λ0, cosφ0, sinφ0; + d3_geo_area.point = function(λ, φ) { + d3_geo_area.point = nextPoint; + λ0 = (λ00 = λ) * d3_radians, cosφ0 = Math.cos(φ = (φ00 = φ) * d3_radians / 2 + π / 4), + sinφ0 = Math.sin(φ); + }; + function nextPoint(λ, φ) { + λ *= d3_radians; + φ = φ * d3_radians / 2 + π / 4; + var dλ = λ - λ0, sdλ = dλ >= 0 ? 1 : -1, adλ = sdλ * dλ, cosφ = Math.cos(φ), sinφ = Math.sin(φ), k = sinφ0 * sinφ, u = cosφ0 * cosφ + k * Math.cos(adλ), v = k * sdλ * Math.sin(adλ); + d3_geo_areaRingSum.add(Math.atan2(v, u)); + λ0 = λ, cosφ0 = cosφ, sinφ0 = sinφ; + } + d3_geo_area.lineEnd = function() { + nextPoint(λ00, φ00); + }; + } + function d3_geo_cartesian(spherical) { + var λ = spherical[0], φ = spherical[1], cosφ = Math.cos(φ); + return [ cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ) ]; + } + function d3_geo_cartesianDot(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + } + function d3_geo_cartesianCross(a, b) { + return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] ]; + } + function d3_geo_cartesianAdd(a, b) { + a[0] += b[0]; + a[1] += b[1]; + a[2] += b[2]; + } + function d3_geo_cartesianScale(vector, k) { + return [ vector[0] * k, vector[1] * k, vector[2] * k ]; + } + function d3_geo_cartesianNormalize(d) { + var l = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); + d[0] /= l; + d[1] /= l; + d[2] /= l; + } + function d3_geo_spherical(cartesian) { + return [ Math.atan2(cartesian[1], cartesian[0]), d3_asin(cartesian[2]) ]; + } + function d3_geo_sphericalEqual(a, b) { + return abs(a[0] - b[0]) < ε && abs(a[1] - b[1]) < ε; + } + d3.geo.bounds = function() { + var λ0, φ0, λ1, φ1, λ_, λ__, φ__, p0, dλSum, ranges, range; + var bound = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + bound.point = ringPoint; + bound.lineStart = ringStart; + bound.lineEnd = ringEnd; + dλSum = 0; + d3_geo_area.polygonStart(); + }, + polygonEnd: function() { + d3_geo_area.polygonEnd(); + bound.point = point; + bound.lineStart = lineStart; + bound.lineEnd = lineEnd; + if (d3_geo_areaRingSum < 0) λ0 = -(λ1 = 180), φ0 = -(φ1 = 90); else if (dλSum > ε) φ1 = 90; else if (dλSum < -ε) φ0 = -90; + range[0] = λ0, range[1] = λ1; + } + }; + function point(λ, φ) { + ranges.push(range = [ λ0 = λ, λ1 = λ ]); + if (φ < φ0) φ0 = φ; + if (φ > φ1) φ1 = φ; + } + function linePoint(λ, φ) { + var p = d3_geo_cartesian([ λ * d3_radians, φ * d3_radians ]); + if (p0) { + var normal = d3_geo_cartesianCross(p0, p), equatorial = [ normal[1], -normal[0], 0 ], inflection = d3_geo_cartesianCross(equatorial, normal); + d3_geo_cartesianNormalize(inflection); + inflection = d3_geo_spherical(inflection); + var dλ = λ - λ_, s = dλ > 0 ? 1 : -1, λi = inflection[0] * d3_degrees * s, antimeridian = abs(dλ) > 180; + if (antimeridian ^ (s * λ_ < λi && λi < s * λ)) { + var φi = inflection[1] * d3_degrees; + if (φi > φ1) φ1 = φi; + } else if (λi = (λi + 360) % 360 - 180, antimeridian ^ (s * λ_ < λi && λi < s * λ)) { + var φi = -inflection[1] * d3_degrees; + if (φi < φ0) φ0 = φi; + } else { + if (φ < φ0) φ0 = φ; + if (φ > φ1) φ1 = φ; + } + if (antimeridian) { + if (λ < λ_) { + if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ; + } else { + if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ; + } + } else { + if (λ1 >= λ0) { + if (λ < λ0) λ0 = λ; + if (λ > λ1) λ1 = λ; + } else { + if (λ > λ_) { + if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ; + } else { + if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ; + } + } + } + } else { + point(λ, φ); + } + p0 = p, λ_ = λ; + } + function lineStart() { + bound.point = linePoint; + } + function lineEnd() { + range[0] = λ0, range[1] = λ1; + bound.point = point; + p0 = null; + } + function ringPoint(λ, φ) { + if (p0) { + var dλ = λ - λ_; + dλSum += abs(dλ) > 180 ? dλ + (dλ > 0 ? 360 : -360) : dλ; + } else λ__ = λ, φ__ = φ; + d3_geo_area.point(λ, φ); + linePoint(λ, φ); + } + function ringStart() { + d3_geo_area.lineStart(); + } + function ringEnd() { + ringPoint(λ__, φ__); + d3_geo_area.lineEnd(); + if (abs(dλSum) > ε) λ0 = -(λ1 = 180); + range[0] = λ0, range[1] = λ1; + p0 = null; + } + function angle(λ0, λ1) { + return (λ1 -= λ0) < 0 ? λ1 + 360 : λ1; + } + function compareRanges(a, b) { + return a[0] - b[0]; + } + function withinRange(x, range) { + return range[0] <= range[1] ? range[0] <= x && x <= range[1] : x < range[0] || range[1] < x; + } + return function(feature) { + φ1 = λ1 = -(λ0 = φ0 = Infinity); + ranges = []; + d3.geo.stream(feature, bound); + var n = ranges.length; + if (n) { + ranges.sort(compareRanges); + for (var i = 1, a = ranges[0], b, merged = [ a ]; i < n; ++i) { + b = ranges[i]; + if (withinRange(b[0], a) || withinRange(b[1], a)) { + if (angle(a[0], b[1]) > angle(a[0], a[1])) a[1] = b[1]; + if (angle(b[0], a[1]) > angle(a[0], a[1])) a[0] = b[0]; + } else { + merged.push(a = b); + } + } + var best = -Infinity, dλ; + for (var n = merged.length - 1, i = 0, a = merged[n], b; i <= n; a = b, ++i) { + b = merged[i]; + if ((dλ = angle(a[1], b[0])) > best) best = dλ, λ0 = b[0], λ1 = a[1]; + } + } + ranges = range = null; + return λ0 === Infinity || φ0 === Infinity ? [ [ NaN, NaN ], [ NaN, NaN ] ] : [ [ λ0, φ0 ], [ λ1, φ1 ] ]; + }; + }(); + d3.geo.centroid = function(object) { + d3_geo_centroidW0 = d3_geo_centroidW1 = d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0; + d3.geo.stream(object, d3_geo_centroid); + var x = d3_geo_centroidX2, y = d3_geo_centroidY2, z = d3_geo_centroidZ2, m = x * x + y * y + z * z; + if (m < ε2) { + x = d3_geo_centroidX1, y = d3_geo_centroidY1, z = d3_geo_centroidZ1; + if (d3_geo_centroidW1 < ε) x = d3_geo_centroidX0, y = d3_geo_centroidY0, z = d3_geo_centroidZ0; + m = x * x + y * y + z * z; + if (m < ε2) return [ NaN, NaN ]; + } + return [ Math.atan2(y, x) * d3_degrees, d3_asin(z / Math.sqrt(m)) * d3_degrees ]; + }; + var d3_geo_centroidW0, d3_geo_centroidW1, d3_geo_centroidX0, d3_geo_centroidY0, d3_geo_centroidZ0, d3_geo_centroidX1, d3_geo_centroidY1, d3_geo_centroidZ1, d3_geo_centroidX2, d3_geo_centroidY2, d3_geo_centroidZ2; + var d3_geo_centroid = { + sphere: d3_noop, + point: d3_geo_centroidPoint, + lineStart: d3_geo_centroidLineStart, + lineEnd: d3_geo_centroidLineEnd, + polygonStart: function() { + d3_geo_centroid.lineStart = d3_geo_centroidRingStart; + }, + polygonEnd: function() { + d3_geo_centroid.lineStart = d3_geo_centroidLineStart; + } + }; + function d3_geo_centroidPoint(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + d3_geo_centroidPointXYZ(cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ)); + } + function d3_geo_centroidPointXYZ(x, y, z) { + ++d3_geo_centroidW0; + d3_geo_centroidX0 += (x - d3_geo_centroidX0) / d3_geo_centroidW0; + d3_geo_centroidY0 += (y - d3_geo_centroidY0) / d3_geo_centroidW0; + d3_geo_centroidZ0 += (z - d3_geo_centroidZ0) / d3_geo_centroidW0; + } + function d3_geo_centroidLineStart() { + var x0, y0, z0; + d3_geo_centroid.point = function(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + x0 = cosφ * Math.cos(λ); + y0 = cosφ * Math.sin(λ); + z0 = Math.sin(φ); + d3_geo_centroid.point = nextPoint; + d3_geo_centroidPointXYZ(x0, y0, z0); + }; + function nextPoint(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), w = Math.atan2(Math.sqrt((w = y0 * z - z0 * y) * w + (w = z0 * x - x0 * z) * w + (w = x0 * y - y0 * x) * w), x0 * x + y0 * y + z0 * z); + d3_geo_centroidW1 += w; + d3_geo_centroidX1 += w * (x0 + (x0 = x)); + d3_geo_centroidY1 += w * (y0 + (y0 = y)); + d3_geo_centroidZ1 += w * (z0 + (z0 = z)); + d3_geo_centroidPointXYZ(x0, y0, z0); + } + } + function d3_geo_centroidLineEnd() { + d3_geo_centroid.point = d3_geo_centroidPoint; + } + function d3_geo_centroidRingStart() { + var λ00, φ00, x0, y0, z0; + d3_geo_centroid.point = function(λ, φ) { + λ00 = λ, φ00 = φ; + d3_geo_centroid.point = nextPoint; + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + x0 = cosφ * Math.cos(λ); + y0 = cosφ * Math.sin(λ); + z0 = Math.sin(φ); + d3_geo_centroidPointXYZ(x0, y0, z0); + }; + d3_geo_centroid.lineEnd = function() { + nextPoint(λ00, φ00); + d3_geo_centroid.lineEnd = d3_geo_centroidLineEnd; + d3_geo_centroid.point = d3_geo_centroidPoint; + }; + function nextPoint(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), cx = y0 * z - z0 * y, cy = z0 * x - x0 * z, cz = x0 * y - y0 * x, m = Math.sqrt(cx * cx + cy * cy + cz * cz), u = x0 * x + y0 * y + z0 * z, v = m && -d3_acos(u) / m, w = Math.atan2(m, u); + d3_geo_centroidX2 += v * cx; + d3_geo_centroidY2 += v * cy; + d3_geo_centroidZ2 += v * cz; + d3_geo_centroidW1 += w; + d3_geo_centroidX1 += w * (x0 + (x0 = x)); + d3_geo_centroidY1 += w * (y0 + (y0 = y)); + d3_geo_centroidZ1 += w * (z0 + (z0 = z)); + d3_geo_centroidPointXYZ(x0, y0, z0); + } + } + function d3_geo_compose(a, b) { + function compose(x, y) { + return x = a(x, y), b(x[0], x[1]); + } + if (a.invert && b.invert) compose.invert = function(x, y) { + return x = b.invert(x, y), x && a.invert(x[0], x[1]); + }; + return compose; + } + function d3_true() { + return true; + } + function d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener) { + var subject = [], clip = []; + segments.forEach(function(segment) { + if ((n = segment.length - 1) <= 0) return; + var n, p0 = segment[0], p1 = segment[n]; + if (d3_geo_sphericalEqual(p0, p1)) { + listener.lineStart(); + for (var i = 0; i < n; ++i) listener.point((p0 = segment[i])[0], p0[1]); + listener.lineEnd(); + return; + } + var a = new d3_geo_clipPolygonIntersection(p0, segment, null, true), b = new d3_geo_clipPolygonIntersection(p0, null, a, false); + a.o = b; + subject.push(a); + clip.push(b); + a = new d3_geo_clipPolygonIntersection(p1, segment, null, false); + b = new d3_geo_clipPolygonIntersection(p1, null, a, true); + a.o = b; + subject.push(a); + clip.push(b); + }); + clip.sort(compare); + d3_geo_clipPolygonLinkCircular(subject); + d3_geo_clipPolygonLinkCircular(clip); + if (!subject.length) return; + for (var i = 0, entry = clipStartInside, n = clip.length; i < n; ++i) { + clip[i].e = entry = !entry; + } + var start = subject[0], points, point; + while (1) { + var current = start, isSubject = true; + while (current.v) if ((current = current.n) === start) return; + points = current.z; + listener.lineStart(); + do { + current.v = current.o.v = true; + if (current.e) { + if (isSubject) { + for (var i = 0, n = points.length; i < n; ++i) listener.point((point = points[i])[0], point[1]); + } else { + interpolate(current.x, current.n.x, 1, listener); + } + current = current.n; + } else { + if (isSubject) { + points = current.p.z; + for (var i = points.length - 1; i >= 0; --i) listener.point((point = points[i])[0], point[1]); + } else { + interpolate(current.x, current.p.x, -1, listener); + } + current = current.p; + } + current = current.o; + points = current.z; + isSubject = !isSubject; + } while (!current.v); + listener.lineEnd(); + } + } + function d3_geo_clipPolygonLinkCircular(array) { + if (!(n = array.length)) return; + var n, i = 0, a = array[0], b; + while (++i < n) { + a.n = b = array[i]; + b.p = a; + a = b; + } + a.n = b = array[0]; + b.p = a; + } + function d3_geo_clipPolygonIntersection(point, points, other, entry) { + this.x = point; + this.z = points; + this.o = other; + this.e = entry; + this.v = false; + this.n = this.p = null; + } + function d3_geo_clip(pointVisible, clipLine, interpolate, clipStart) { + return function(rotate, listener) { + var line = clipLine(listener), rotatedClipStart = rotate.invert(clipStart[0], clipStart[1]); + var clip = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + clip.point = pointRing; + clip.lineStart = ringStart; + clip.lineEnd = ringEnd; + segments = []; + polygon = []; + }, + polygonEnd: function() { + clip.point = point; + clip.lineStart = lineStart; + clip.lineEnd = lineEnd; + segments = d3.merge(segments); + var clipStartInside = d3_geo_pointInPolygon(rotatedClipStart, polygon); + if (segments.length) { + if (!polygonStarted) listener.polygonStart(), polygonStarted = true; + d3_geo_clipPolygon(segments, d3_geo_clipSort, clipStartInside, interpolate, listener); + } else if (clipStartInside) { + if (!polygonStarted) listener.polygonStart(), polygonStarted = true; + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + } + if (polygonStarted) listener.polygonEnd(), polygonStarted = false; + segments = polygon = null; + }, + sphere: function() { + listener.polygonStart(); + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + listener.polygonEnd(); + } + }; + function point(λ, φ) { + var point = rotate(λ, φ); + if (pointVisible(λ = point[0], φ = point[1])) listener.point(λ, φ); + } + function pointLine(λ, φ) { + var point = rotate(λ, φ); + line.point(point[0], point[1]); + } + function lineStart() { + clip.point = pointLine; + line.lineStart(); + } + function lineEnd() { + clip.point = point; + line.lineEnd(); + } + var segments; + var buffer = d3_geo_clipBufferListener(), ringListener = clipLine(buffer), polygonStarted = false, polygon, ring; + function pointRing(λ, φ) { + ring.push([ λ, φ ]); + var point = rotate(λ, φ); + ringListener.point(point[0], point[1]); + } + function ringStart() { + ringListener.lineStart(); + ring = []; + } + function ringEnd() { + pointRing(ring[0][0], ring[0][1]); + ringListener.lineEnd(); + var clean = ringListener.clean(), ringSegments = buffer.buffer(), segment, n = ringSegments.length; + ring.pop(); + polygon.push(ring); + ring = null; + if (!n) return; + if (clean & 1) { + segment = ringSegments[0]; + var n = segment.length - 1, i = -1, point; + if (n > 0) { + if (!polygonStarted) listener.polygonStart(), polygonStarted = true; + listener.lineStart(); + while (++i < n) listener.point((point = segment[i])[0], point[1]); + listener.lineEnd(); + } + return; + } + if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift())); + segments.push(ringSegments.filter(d3_geo_clipSegmentLength1)); + } + return clip; + }; + } + function d3_geo_clipSegmentLength1(segment) { + return segment.length > 1; + } + function d3_geo_clipBufferListener() { + var lines = [], line; + return { + lineStart: function() { + lines.push(line = []); + }, + point: function(λ, φ) { + line.push([ λ, φ ]); + }, + lineEnd: d3_noop, + buffer: function() { + var buffer = lines; + lines = []; + line = null; + return buffer; + }, + rejoin: function() { + if (lines.length > 1) lines.push(lines.pop().concat(lines.shift())); + } + }; + } + function d3_geo_clipSort(a, b) { + return ((a = a.x)[0] < 0 ? a[1] - halfπ - ε : halfπ - a[1]) - ((b = b.x)[0] < 0 ? b[1] - halfπ - ε : halfπ - b[1]); + } + var d3_geo_clipAntimeridian = d3_geo_clip(d3_true, d3_geo_clipAntimeridianLine, d3_geo_clipAntimeridianInterpolate, [ -π, -π / 2 ]); + function d3_geo_clipAntimeridianLine(listener) { + var λ0 = NaN, φ0 = NaN, sλ0 = NaN, clean; + return { + lineStart: function() { + listener.lineStart(); + clean = 1; + }, + point: function(λ1, φ1) { + var sλ1 = λ1 > 0 ? π : -π, dλ = abs(λ1 - λ0); + if (abs(dλ - π) < ε) { + listener.point(λ0, φ0 = (φ0 + φ1) / 2 > 0 ? halfπ : -halfπ); + listener.point(sλ0, φ0); + listener.lineEnd(); + listener.lineStart(); + listener.point(sλ1, φ0); + listener.point(λ1, φ0); + clean = 0; + } else if (sλ0 !== sλ1 && dλ >= π) { + if (abs(λ0 - sλ0) < ε) λ0 -= sλ0 * ε; + if (abs(λ1 - sλ1) < ε) λ1 -= sλ1 * ε; + φ0 = d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1); + listener.point(sλ0, φ0); + listener.lineEnd(); + listener.lineStart(); + listener.point(sλ1, φ0); + clean = 0; + } + listener.point(λ0 = λ1, φ0 = φ1); + sλ0 = sλ1; + }, + lineEnd: function() { + listener.lineEnd(); + λ0 = φ0 = NaN; + }, + clean: function() { + return 2 - clean; + } + }; + } + function d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1) { + var cosφ0, cosφ1, sinλ0_λ1 = Math.sin(λ0 - λ1); + return abs(sinλ0_λ1) > ε ? Math.atan((Math.sin(φ0) * (cosφ1 = Math.cos(φ1)) * Math.sin(λ1) - Math.sin(φ1) * (cosφ0 = Math.cos(φ0)) * Math.sin(λ0)) / (cosφ0 * cosφ1 * sinλ0_λ1)) : (φ0 + φ1) / 2; + } + function d3_geo_clipAntimeridianInterpolate(from, to, direction, listener) { + var φ; + if (from == null) { + φ = direction * halfπ; + listener.point(-π, φ); + listener.point(0, φ); + listener.point(π, φ); + listener.point(π, 0); + listener.point(π, -φ); + listener.point(0, -φ); + listener.point(-π, -φ); + listener.point(-π, 0); + listener.point(-π, φ); + } else if (abs(from[0] - to[0]) > ε) { + var s = from[0] < to[0] ? π : -π; + φ = direction * s / 2; + listener.point(-s, φ); + listener.point(0, φ); + listener.point(s, φ); + } else { + listener.point(to[0], to[1]); + } + } + function d3_geo_pointInPolygon(point, polygon) { + var meridian = point[0], parallel = point[1], meridianNormal = [ Math.sin(meridian), -Math.cos(meridian), 0 ], polarAngle = 0, winding = 0; + d3_geo_areaRingSum.reset(); + for (var i = 0, n = polygon.length; i < n; ++i) { + var ring = polygon[i], m = ring.length; + if (!m) continue; + var point0 = ring[0], λ0 = point0[0], φ0 = point0[1] / 2 + π / 4, sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), j = 1; + while (true) { + if (j === m) j = 0; + point = ring[j]; + var λ = point[0], φ = point[1] / 2 + π / 4, sinφ = Math.sin(φ), cosφ = Math.cos(φ), dλ = λ - λ0, sdλ = dλ >= 0 ? 1 : -1, adλ = sdλ * dλ, antimeridian = adλ > π, k = sinφ0 * sinφ; + d3_geo_areaRingSum.add(Math.atan2(k * sdλ * Math.sin(adλ), cosφ0 * cosφ + k * Math.cos(adλ))); + polarAngle += antimeridian ? dλ + sdλ * τ : dλ; + if (antimeridian ^ λ0 >= meridian ^ λ >= meridian) { + var arc = d3_geo_cartesianCross(d3_geo_cartesian(point0), d3_geo_cartesian(point)); + d3_geo_cartesianNormalize(arc); + var intersection = d3_geo_cartesianCross(meridianNormal, arc); + d3_geo_cartesianNormalize(intersection); + var φarc = (antimeridian ^ dλ >= 0 ? -1 : 1) * d3_asin(intersection[2]); + if (parallel > φarc || parallel === φarc && (arc[0] || arc[1])) { + winding += antimeridian ^ dλ >= 0 ? 1 : -1; + } + } + if (!j++) break; + λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ, point0 = point; + } + } + return (polarAngle < -ε || polarAngle < ε && d3_geo_areaRingSum < -ε) ^ winding & 1; + } + function d3_geo_clipCircle(radius) { + var cr = Math.cos(radius), smallRadius = cr > 0, notHemisphere = abs(cr) > ε, interpolate = d3_geo_circleInterpolate(radius, 6 * d3_radians); + return d3_geo_clip(visible, clipLine, interpolate, smallRadius ? [ 0, -radius ] : [ -π, radius - π ]); + function visible(λ, φ) { + return Math.cos(λ) * Math.cos(φ) > cr; + } + function clipLine(listener) { + var point0, c0, v0, v00, clean; + return { + lineStart: function() { + v00 = v0 = false; + clean = 1; + }, + point: function(λ, φ) { + var point1 = [ λ, φ ], point2, v = visible(λ, φ), c = smallRadius ? v ? 0 : code(λ, φ) : v ? code(λ + (λ < 0 ? π : -π), φ) : 0; + if (!point0 && (v00 = v0 = v)) listener.lineStart(); + if (v !== v0) { + point2 = intersect(point0, point1); + if (d3_geo_sphericalEqual(point0, point2) || d3_geo_sphericalEqual(point1, point2)) { + point1[0] += ε; + point1[1] += ε; + v = visible(point1[0], point1[1]); + } + } + if (v !== v0) { + clean = 0; + if (v) { + listener.lineStart(); + point2 = intersect(point1, point0); + listener.point(point2[0], point2[1]); + } else { + point2 = intersect(point0, point1); + listener.point(point2[0], point2[1]); + listener.lineEnd(); + } + point0 = point2; + } else if (notHemisphere && point0 && smallRadius ^ v) { + var t; + if (!(c & c0) && (t = intersect(point1, point0, true))) { + clean = 0; + if (smallRadius) { + listener.lineStart(); + listener.point(t[0][0], t[0][1]); + listener.point(t[1][0], t[1][1]); + listener.lineEnd(); + } else { + listener.point(t[1][0], t[1][1]); + listener.lineEnd(); + listener.lineStart(); + listener.point(t[0][0], t[0][1]); + } + } + } + if (v && (!point0 || !d3_geo_sphericalEqual(point0, point1))) { + listener.point(point1[0], point1[1]); + } + point0 = point1, v0 = v, c0 = c; + }, + lineEnd: function() { + if (v0) listener.lineEnd(); + point0 = null; + }, + clean: function() { + return clean | (v00 && v0) << 1; + } + }; + } + function intersect(a, b, two) { + var pa = d3_geo_cartesian(a), pb = d3_geo_cartesian(b); + var n1 = [ 1, 0, 0 ], n2 = d3_geo_cartesianCross(pa, pb), n2n2 = d3_geo_cartesianDot(n2, n2), n1n2 = n2[0], determinant = n2n2 - n1n2 * n1n2; + if (!determinant) return !two && a; + var c1 = cr * n2n2 / determinant, c2 = -cr * n1n2 / determinant, n1xn2 = d3_geo_cartesianCross(n1, n2), A = d3_geo_cartesianScale(n1, c1), B = d3_geo_cartesianScale(n2, c2); + d3_geo_cartesianAdd(A, B); + var u = n1xn2, w = d3_geo_cartesianDot(A, u), uu = d3_geo_cartesianDot(u, u), t2 = w * w - uu * (d3_geo_cartesianDot(A, A) - 1); + if (t2 < 0) return; + var t = Math.sqrt(t2), q = d3_geo_cartesianScale(u, (-w - t) / uu); + d3_geo_cartesianAdd(q, A); + q = d3_geo_spherical(q); + if (!two) return q; + var λ0 = a[0], λ1 = b[0], φ0 = a[1], φ1 = b[1], z; + if (λ1 < λ0) z = λ0, λ0 = λ1, λ1 = z; + var δλ = λ1 - λ0, polar = abs(δλ - π) < ε, meridian = polar || δλ < ε; + if (!polar && φ1 < φ0) z = φ0, φ0 = φ1, φ1 = z; + if (meridian ? polar ? φ0 + φ1 > 0 ^ q[1] < (abs(q[0] - λ0) < ε ? φ0 : φ1) : φ0 <= q[1] && q[1] <= φ1 : δλ > π ^ (λ0 <= q[0] && q[0] <= λ1)) { + var q1 = d3_geo_cartesianScale(u, (-w + t) / uu); + d3_geo_cartesianAdd(q1, A); + return [ q, d3_geo_spherical(q1) ]; + } + } + function code(λ, φ) { + var r = smallRadius ? radius : π - radius, code = 0; + if (λ < -r) code |= 1; else if (λ > r) code |= 2; + if (φ < -r) code |= 4; else if (φ > r) code |= 8; + return code; + } + } + function d3_geom_clipLine(x0, y0, x1, y1) { + return function(line) { + var a = line.a, b = line.b, ax = a.x, ay = a.y, bx = b.x, by = b.y, t0 = 0, t1 = 1, dx = bx - ax, dy = by - ay, r; + r = x0 - ax; + if (!dx && r > 0) return; + r /= dx; + if (dx < 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } else if (dx > 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } + r = x1 - ax; + if (!dx && r < 0) return; + r /= dx; + if (dx < 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } else if (dx > 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } + r = y0 - ay; + if (!dy && r > 0) return; + r /= dy; + if (dy < 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } else if (dy > 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } + r = y1 - ay; + if (!dy && r < 0) return; + r /= dy; + if (dy < 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } else if (dy > 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } + if (t0 > 0) line.a = { + x: ax + t0 * dx, + y: ay + t0 * dy + }; + if (t1 < 1) line.b = { + x: ax + t1 * dx, + y: ay + t1 * dy + }; + return line; + }; + } + var d3_geo_clipExtentMAX = 1e9; + d3.geo.clipExtent = function() { + var x0, y0, x1, y1, stream, clip, clipExtent = { + stream: function(output) { + if (stream) stream.valid = false; + stream = clip(output); + stream.valid = true; + return stream; + }, + extent: function(_) { + if (!arguments.length) return [ [ x0, y0 ], [ x1, y1 ] ]; + clip = d3_geo_clipExtent(x0 = +_[0][0], y0 = +_[0][1], x1 = +_[1][0], y1 = +_[1][1]); + if (stream) stream.valid = false, stream = null; + return clipExtent; + } + }; + return clipExtent.extent([ [ 0, 0 ], [ 960, 500 ] ]); + }; + function d3_geo_clipExtent(x0, y0, x1, y1) { + return function(listener) { + var listener_ = listener, bufferListener = d3_geo_clipBufferListener(), clipLine = d3_geom_clipLine(x0, y0, x1, y1), segments, polygon, ring; + var clip = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + listener = bufferListener; + segments = []; + polygon = []; + clean = true; + }, + polygonEnd: function() { + listener = listener_; + segments = d3.merge(segments); + var clipStartInside = insidePolygon([ x0, y1 ]), inside = clean && clipStartInside, visible = segments.length; + if (inside || visible) { + listener.polygonStart(); + if (inside) { + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + } + if (visible) { + d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener); + } + listener.polygonEnd(); + } + segments = polygon = ring = null; + } + }; + function insidePolygon(p) { + var wn = 0, n = polygon.length, y = p[1]; + for (var i = 0; i < n; ++i) { + for (var j = 1, v = polygon[i], m = v.length, a = v[0], b; j < m; ++j) { + b = v[j]; + if (a[1] <= y) { + if (b[1] > y && d3_cross2d(a, b, p) > 0) ++wn; + } else { + if (b[1] <= y && d3_cross2d(a, b, p) < 0) --wn; + } + a = b; + } + } + return wn !== 0; + } + function interpolate(from, to, direction, listener) { + var a = 0, a1 = 0; + if (from == null || (a = corner(from, direction)) !== (a1 = corner(to, direction)) || comparePoints(from, to) < 0 ^ direction > 0) { + do { + listener.point(a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0); + } while ((a = (a + direction + 4) % 4) !== a1); + } else { + listener.point(to[0], to[1]); + } + } + function pointVisible(x, y) { + return x0 <= x && x <= x1 && y0 <= y && y <= y1; + } + function point(x, y) { + if (pointVisible(x, y)) listener.point(x, y); + } + var x__, y__, v__, x_, y_, v_, first, clean; + function lineStart() { + clip.point = linePoint; + if (polygon) polygon.push(ring = []); + first = true; + v_ = false; + x_ = y_ = NaN; + } + function lineEnd() { + if (segments) { + linePoint(x__, y__); + if (v__ && v_) bufferListener.rejoin(); + segments.push(bufferListener.buffer()); + } + clip.point = point; + if (v_) listener.lineEnd(); + } + function linePoint(x, y) { + x = Math.max(-d3_geo_clipExtentMAX, Math.min(d3_geo_clipExtentMAX, x)); + y = Math.max(-d3_geo_clipExtentMAX, Math.min(d3_geo_clipExtentMAX, y)); + var v = pointVisible(x, y); + if (polygon) ring.push([ x, y ]); + if (first) { + x__ = x, y__ = y, v__ = v; + first = false; + if (v) { + listener.lineStart(); + listener.point(x, y); + } + } else { + if (v && v_) listener.point(x, y); else { + var l = { + a: { + x: x_, + y: y_ + }, + b: { + x: x, + y: y + } + }; + if (clipLine(l)) { + if (!v_) { + listener.lineStart(); + listener.point(l.a.x, l.a.y); + } + listener.point(l.b.x, l.b.y); + if (!v) listener.lineEnd(); + clean = false; + } else if (v) { + listener.lineStart(); + listener.point(x, y); + clean = false; + } + } + } + x_ = x, y_ = y, v_ = v; + } + return clip; + }; + function corner(p, direction) { + return abs(p[0] - x0) < ε ? direction > 0 ? 0 : 3 : abs(p[0] - x1) < ε ? direction > 0 ? 2 : 1 : abs(p[1] - y0) < ε ? direction > 0 ? 1 : 0 : direction > 0 ? 3 : 2; + } + function compare(a, b) { + return comparePoints(a.x, b.x); + } + function comparePoints(a, b) { + var ca = corner(a, 1), cb = corner(b, 1); + return ca !== cb ? ca - cb : ca === 0 ? b[1] - a[1] : ca === 1 ? a[0] - b[0] : ca === 2 ? a[1] - b[1] : b[0] - a[0]; + } + } + function d3_geo_conic(projectAt) { + var φ0 = 0, φ1 = π / 3, m = d3_geo_projectionMutator(projectAt), p = m(φ0, φ1); + p.parallels = function(_) { + if (!arguments.length) return [ φ0 / π * 180, φ1 / π * 180 ]; + return m(φ0 = _[0] * π / 180, φ1 = _[1] * π / 180); + }; + return p; + } + function d3_geo_conicEqualArea(φ0, φ1) { + var sinφ0 = Math.sin(φ0), n = (sinφ0 + Math.sin(φ1)) / 2, C = 1 + sinφ0 * (2 * n - sinφ0), ρ0 = Math.sqrt(C) / n; + function forward(λ, φ) { + var ρ = Math.sqrt(C - 2 * n * Math.sin(φ)) / n; + return [ ρ * Math.sin(λ *= n), ρ0 - ρ * Math.cos(λ) ]; + } + forward.invert = function(x, y) { + var ρ0_y = ρ0 - y; + return [ Math.atan2(x, ρ0_y) / n, d3_asin((C - (x * x + ρ0_y * ρ0_y) * n * n) / (2 * n)) ]; + }; + return forward; + } + (d3.geo.conicEqualArea = function() { + return d3_geo_conic(d3_geo_conicEqualArea); + }).raw = d3_geo_conicEqualArea; + d3.geo.albers = function() { + return d3.geo.conicEqualArea().rotate([ 96, 0 ]).center([ -.6, 38.7 ]).parallels([ 29.5, 45.5 ]).scale(1070); + }; + d3.geo.albersUsa = function() { + var lower48 = d3.geo.albers(); + var alaska = d3.geo.conicEqualArea().rotate([ 154, 0 ]).center([ -2, 58.5 ]).parallels([ 55, 65 ]); + var hawaii = d3.geo.conicEqualArea().rotate([ 157, 0 ]).center([ -3, 19.9 ]).parallels([ 8, 18 ]); + var point, pointStream = { + point: function(x, y) { + point = [ x, y ]; + } + }, lower48Point, alaskaPoint, hawaiiPoint; + function albersUsa(coordinates) { + var x = coordinates[0], y = coordinates[1]; + point = null; + (lower48Point(x, y), point) || (alaskaPoint(x, y), point) || hawaiiPoint(x, y); + return point; + } + albersUsa.invert = function(coordinates) { + var k = lower48.scale(), t = lower48.translate(), x = (coordinates[0] - t[0]) / k, y = (coordinates[1] - t[1]) / k; + return (y >= .12 && y < .234 && x >= -.425 && x < -.214 ? alaska : y >= .166 && y < .234 && x >= -.214 && x < -.115 ? hawaii : lower48).invert(coordinates); + }; + albersUsa.stream = function(stream) { + var lower48Stream = lower48.stream(stream), alaskaStream = alaska.stream(stream), hawaiiStream = hawaii.stream(stream); + return { + point: function(x, y) { + lower48Stream.point(x, y); + alaskaStream.point(x, y); + hawaiiStream.point(x, y); + }, + sphere: function() { + lower48Stream.sphere(); + alaskaStream.sphere(); + hawaiiStream.sphere(); + }, + lineStart: function() { + lower48Stream.lineStart(); + alaskaStream.lineStart(); + hawaiiStream.lineStart(); + }, + lineEnd: function() { + lower48Stream.lineEnd(); + alaskaStream.lineEnd(); + hawaiiStream.lineEnd(); + }, + polygonStart: function() { + lower48Stream.polygonStart(); + alaskaStream.polygonStart(); + hawaiiStream.polygonStart(); + }, + polygonEnd: function() { + lower48Stream.polygonEnd(); + alaskaStream.polygonEnd(); + hawaiiStream.polygonEnd(); + } + }; + }; + albersUsa.precision = function(_) { + if (!arguments.length) return lower48.precision(); + lower48.precision(_); + alaska.precision(_); + hawaii.precision(_); + return albersUsa; + }; + albersUsa.scale = function(_) { + if (!arguments.length) return lower48.scale(); + lower48.scale(_); + alaska.scale(_ * .35); + hawaii.scale(_); + return albersUsa.translate(lower48.translate()); + }; + albersUsa.translate = function(_) { + if (!arguments.length) return lower48.translate(); + var k = lower48.scale(), x = +_[0], y = +_[1]; + lower48Point = lower48.translate(_).clipExtent([ [ x - .455 * k, y - .238 * k ], [ x + .455 * k, y + .238 * k ] ]).stream(pointStream).point; + alaskaPoint = alaska.translate([ x - .307 * k, y + .201 * k ]).clipExtent([ [ x - .425 * k + ε, y + .12 * k + ε ], [ x - .214 * k - ε, y + .234 * k - ε ] ]).stream(pointStream).point; + hawaiiPoint = hawaii.translate([ x - .205 * k, y + .212 * k ]).clipExtent([ [ x - .214 * k + ε, y + .166 * k + ε ], [ x - .115 * k - ε, y + .234 * k - ε ] ]).stream(pointStream).point; + return albersUsa; + }; + return albersUsa.scale(1070); + }; + var d3_geo_pathAreaSum, d3_geo_pathAreaPolygon, d3_geo_pathArea = { + point: d3_noop, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: function() { + d3_geo_pathAreaPolygon = 0; + d3_geo_pathArea.lineStart = d3_geo_pathAreaRingStart; + }, + polygonEnd: function() { + d3_geo_pathArea.lineStart = d3_geo_pathArea.lineEnd = d3_geo_pathArea.point = d3_noop; + d3_geo_pathAreaSum += abs(d3_geo_pathAreaPolygon / 2); + } + }; + function d3_geo_pathAreaRingStart() { + var x00, y00, x0, y0; + d3_geo_pathArea.point = function(x, y) { + d3_geo_pathArea.point = nextPoint; + x00 = x0 = x, y00 = y0 = y; + }; + function nextPoint(x, y) { + d3_geo_pathAreaPolygon += y0 * x - x0 * y; + x0 = x, y0 = y; + } + d3_geo_pathArea.lineEnd = function() { + nextPoint(x00, y00); + }; + } + var d3_geo_pathBoundsX0, d3_geo_pathBoundsY0, d3_geo_pathBoundsX1, d3_geo_pathBoundsY1; + var d3_geo_pathBounds = { + point: d3_geo_pathBoundsPoint, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: d3_noop, + polygonEnd: d3_noop + }; + function d3_geo_pathBoundsPoint(x, y) { + if (x < d3_geo_pathBoundsX0) d3_geo_pathBoundsX0 = x; + if (x > d3_geo_pathBoundsX1) d3_geo_pathBoundsX1 = x; + if (y < d3_geo_pathBoundsY0) d3_geo_pathBoundsY0 = y; + if (y > d3_geo_pathBoundsY1) d3_geo_pathBoundsY1 = y; + } + function d3_geo_pathBuffer() { + var pointCircle = d3_geo_pathBufferCircle(4.5), buffer = []; + var stream = { + point: point, + lineStart: function() { + stream.point = pointLineStart; + }, + lineEnd: lineEnd, + polygonStart: function() { + stream.lineEnd = lineEndPolygon; + }, + polygonEnd: function() { + stream.lineEnd = lineEnd; + stream.point = point; + }, + pointRadius: function(_) { + pointCircle = d3_geo_pathBufferCircle(_); + return stream; + }, + result: function() { + if (buffer.length) { + var result = buffer.join(""); + buffer = []; + return result; + } + } + }; + function point(x, y) { + buffer.push("M", x, ",", y, pointCircle); + } + function pointLineStart(x, y) { + buffer.push("M", x, ",", y); + stream.point = pointLine; + } + function pointLine(x, y) { + buffer.push("L", x, ",", y); + } + function lineEnd() { + stream.point = point; + } + function lineEndPolygon() { + buffer.push("Z"); + } + return stream; + } + function d3_geo_pathBufferCircle(radius) { + return "m0," + radius + "a" + radius + "," + radius + " 0 1,1 0," + -2 * radius + "a" + radius + "," + radius + " 0 1,1 0," + 2 * radius + "z"; + } + var d3_geo_pathCentroid = { + point: d3_geo_pathCentroidPoint, + lineStart: d3_geo_pathCentroidLineStart, + lineEnd: d3_geo_pathCentroidLineEnd, + polygonStart: function() { + d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidRingStart; + }, + polygonEnd: function() { + d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint; + d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidLineStart; + d3_geo_pathCentroid.lineEnd = d3_geo_pathCentroidLineEnd; + } + }; + function d3_geo_pathCentroidPoint(x, y) { + d3_geo_centroidX0 += x; + d3_geo_centroidY0 += y; + ++d3_geo_centroidZ0; + } + function d3_geo_pathCentroidLineStart() { + var x0, y0; + d3_geo_pathCentroid.point = function(x, y) { + d3_geo_pathCentroid.point = nextPoint; + d3_geo_pathCentroidPoint(x0 = x, y0 = y); + }; + function nextPoint(x, y) { + var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy); + d3_geo_centroidX1 += z * (x0 + x) / 2; + d3_geo_centroidY1 += z * (y0 + y) / 2; + d3_geo_centroidZ1 += z; + d3_geo_pathCentroidPoint(x0 = x, y0 = y); + } + } + function d3_geo_pathCentroidLineEnd() { + d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint; + } + function d3_geo_pathCentroidRingStart() { + var x00, y00, x0, y0; + d3_geo_pathCentroid.point = function(x, y) { + d3_geo_pathCentroid.point = nextPoint; + d3_geo_pathCentroidPoint(x00 = x0 = x, y00 = y0 = y); + }; + function nextPoint(x, y) { + var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy); + d3_geo_centroidX1 += z * (x0 + x) / 2; + d3_geo_centroidY1 += z * (y0 + y) / 2; + d3_geo_centroidZ1 += z; + z = y0 * x - x0 * y; + d3_geo_centroidX2 += z * (x0 + x); + d3_geo_centroidY2 += z * (y0 + y); + d3_geo_centroidZ2 += z * 3; + d3_geo_pathCentroidPoint(x0 = x, y0 = y); + } + d3_geo_pathCentroid.lineEnd = function() { + nextPoint(x00, y00); + }; + } + function d3_geo_pathContext(context) { + var pointRadius = 4.5; + var stream = { + point: point, + lineStart: function() { + stream.point = pointLineStart; + }, + lineEnd: lineEnd, + polygonStart: function() { + stream.lineEnd = lineEndPolygon; + }, + polygonEnd: function() { + stream.lineEnd = lineEnd; + stream.point = point; + }, + pointRadius: function(_) { + pointRadius = _; + return stream; + }, + result: d3_noop + }; + function point(x, y) { + context.moveTo(x + pointRadius, y); + context.arc(x, y, pointRadius, 0, τ); + } + function pointLineStart(x, y) { + context.moveTo(x, y); + stream.point = pointLine; + } + function pointLine(x, y) { + context.lineTo(x, y); + } + function lineEnd() { + stream.point = point; + } + function lineEndPolygon() { + context.closePath(); + } + return stream; + } + function d3_geo_resample(project) { + var δ2 = .5, cosMinDistance = Math.cos(30 * d3_radians), maxDepth = 16; + function resample(stream) { + return (maxDepth ? resampleRecursive : resampleNone)(stream); + } + function resampleNone(stream) { + return d3_geo_transformPoint(stream, function(x, y) { + x = project(x, y); + stream.point(x[0], x[1]); + }); + } + function resampleRecursive(stream) { + var λ00, φ00, x00, y00, a00, b00, c00, λ0, x0, y0, a0, b0, c0; + var resample = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + stream.polygonStart(); + resample.lineStart = ringStart; + }, + polygonEnd: function() { + stream.polygonEnd(); + resample.lineStart = lineStart; + } + }; + function point(x, y) { + x = project(x, y); + stream.point(x[0], x[1]); + } + function lineStart() { + x0 = NaN; + resample.point = linePoint; + stream.lineStart(); + } + function linePoint(λ, φ) { + var c = d3_geo_cartesian([ λ, φ ]), p = project(λ, φ); + resampleLineTo(x0, y0, λ0, a0, b0, c0, x0 = p[0], y0 = p[1], λ0 = λ, a0 = c[0], b0 = c[1], c0 = c[2], maxDepth, stream); + stream.point(x0, y0); + } + function lineEnd() { + resample.point = point; + stream.lineEnd(); + } + function ringStart() { + lineStart(); + resample.point = ringPoint; + resample.lineEnd = ringEnd; + } + function ringPoint(λ, φ) { + linePoint(λ00 = λ, φ00 = φ), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0; + resample.point = linePoint; + } + function ringEnd() { + resampleLineTo(x0, y0, λ0, a0, b0, c0, x00, y00, λ00, a00, b00, c00, maxDepth, stream); + resample.lineEnd = lineEnd; + lineEnd(); + } + return resample; + } + function resampleLineTo(x0, y0, λ0, a0, b0, c0, x1, y1, λ1, a1, b1, c1, depth, stream) { + var dx = x1 - x0, dy = y1 - y0, d2 = dx * dx + dy * dy; + if (d2 > 4 * δ2 && depth--) { + var a = a0 + a1, b = b0 + b1, c = c0 + c1, m = Math.sqrt(a * a + b * b + c * c), φ2 = Math.asin(c /= m), λ2 = abs(abs(c) - 1) < ε || abs(λ0 - λ1) < ε ? (λ0 + λ1) / 2 : Math.atan2(b, a), p = project(λ2, φ2), x2 = p[0], y2 = p[1], dx2 = x2 - x0, dy2 = y2 - y0, dz = dy * dx2 - dx * dy2; + if (dz * dz / d2 > δ2 || abs((dx * dx2 + dy * dy2) / d2 - .5) > .3 || a0 * a1 + b0 * b1 + c0 * c1 < cosMinDistance) { + resampleLineTo(x0, y0, λ0, a0, b0, c0, x2, y2, λ2, a /= m, b /= m, c, depth, stream); + stream.point(x2, y2); + resampleLineTo(x2, y2, λ2, a, b, c, x1, y1, λ1, a1, b1, c1, depth, stream); + } + } + } + resample.precision = function(_) { + if (!arguments.length) return Math.sqrt(δ2); + maxDepth = (δ2 = _ * _) > 0 && 16; + return resample; + }; + return resample; + } + d3.geo.path = function() { + var pointRadius = 4.5, projection, context, projectStream, contextStream, cacheStream; + function path(object) { + if (object) { + if (typeof pointRadius === "function") contextStream.pointRadius(+pointRadius.apply(this, arguments)); + if (!cacheStream || !cacheStream.valid) cacheStream = projectStream(contextStream); + d3.geo.stream(object, cacheStream); + } + return contextStream.result(); + } + path.area = function(object) { + d3_geo_pathAreaSum = 0; + d3.geo.stream(object, projectStream(d3_geo_pathArea)); + return d3_geo_pathAreaSum; + }; + path.centroid = function(object) { + d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0; + d3.geo.stream(object, projectStream(d3_geo_pathCentroid)); + return d3_geo_centroidZ2 ? [ d3_geo_centroidX2 / d3_geo_centroidZ2, d3_geo_centroidY2 / d3_geo_centroidZ2 ] : d3_geo_centroidZ1 ? [ d3_geo_centroidX1 / d3_geo_centroidZ1, d3_geo_centroidY1 / d3_geo_centroidZ1 ] : d3_geo_centroidZ0 ? [ d3_geo_centroidX0 / d3_geo_centroidZ0, d3_geo_centroidY0 / d3_geo_centroidZ0 ] : [ NaN, NaN ]; + }; + path.bounds = function(object) { + d3_geo_pathBoundsX1 = d3_geo_pathBoundsY1 = -(d3_geo_pathBoundsX0 = d3_geo_pathBoundsY0 = Infinity); + d3.geo.stream(object, projectStream(d3_geo_pathBounds)); + return [ [ d3_geo_pathBoundsX0, d3_geo_pathBoundsY0 ], [ d3_geo_pathBoundsX1, d3_geo_pathBoundsY1 ] ]; + }; + path.projection = function(_) { + if (!arguments.length) return projection; + projectStream = (projection = _) ? _.stream || d3_geo_pathProjectStream(_) : d3_identity; + return reset(); + }; + path.context = function(_) { + if (!arguments.length) return context; + contextStream = (context = _) == null ? new d3_geo_pathBuffer() : new d3_geo_pathContext(_); + if (typeof pointRadius !== "function") contextStream.pointRadius(pointRadius); + return reset(); + }; + path.pointRadius = function(_) { + if (!arguments.length) return pointRadius; + pointRadius = typeof _ === "function" ? _ : (contextStream.pointRadius(+_), +_); + return path; + }; + function reset() { + cacheStream = null; + return path; + } + return path.projection(d3.geo.albersUsa()).context(null); + }; + function d3_geo_pathProjectStream(project) { + var resample = d3_geo_resample(function(x, y) { + return project([ x * d3_degrees, y * d3_degrees ]); + }); + return function(stream) { + return d3_geo_projectionRadians(resample(stream)); + }; + } + d3.geo.transform = function(methods) { + return { + stream: function(stream) { + var transform = new d3_geo_transform(stream); + for (var k in methods) transform[k] = methods[k]; + return transform; + } + }; + }; + function d3_geo_transform(stream) { + this.stream = stream; + } + d3_geo_transform.prototype = { + point: function(x, y) { + this.stream.point(x, y); + }, + sphere: function() { + this.stream.sphere(); + }, + lineStart: function() { + this.stream.lineStart(); + }, + lineEnd: function() { + this.stream.lineEnd(); + }, + polygonStart: function() { + this.stream.polygonStart(); + }, + polygonEnd: function() { + this.stream.polygonEnd(); + } + }; + function d3_geo_transformPoint(stream, point) { + return { + point: point, + sphere: function() { + stream.sphere(); + }, + lineStart: function() { + stream.lineStart(); + }, + lineEnd: function() { + stream.lineEnd(); + }, + polygonStart: function() { + stream.polygonStart(); + }, + polygonEnd: function() { + stream.polygonEnd(); + } + }; + } + d3.geo.projection = d3_geo_projection; + d3.geo.projectionMutator = d3_geo_projectionMutator; + function d3_geo_projection(project) { + return d3_geo_projectionMutator(function() { + return project; + })(); + } + function d3_geo_projectionMutator(projectAt) { + var project, rotate, projectRotate, projectResample = d3_geo_resample(function(x, y) { + x = project(x, y); + return [ x[0] * k + δx, δy - x[1] * k ]; + }), k = 150, x = 480, y = 250, λ = 0, φ = 0, δλ = 0, δφ = 0, δγ = 0, δx, δy, preclip = d3_geo_clipAntimeridian, postclip = d3_identity, clipAngle = null, clipExtent = null, stream; + function projection(point) { + point = projectRotate(point[0] * d3_radians, point[1] * d3_radians); + return [ point[0] * k + δx, δy - point[1] * k ]; + } + function invert(point) { + point = projectRotate.invert((point[0] - δx) / k, (δy - point[1]) / k); + return point && [ point[0] * d3_degrees, point[1] * d3_degrees ]; + } + projection.stream = function(output) { + if (stream) stream.valid = false; + stream = d3_geo_projectionRadians(preclip(rotate, projectResample(postclip(output)))); + stream.valid = true; + return stream; + }; + projection.clipAngle = function(_) { + if (!arguments.length) return clipAngle; + preclip = _ == null ? (clipAngle = _, d3_geo_clipAntimeridian) : d3_geo_clipCircle((clipAngle = +_) * d3_radians); + return invalidate(); + }; + projection.clipExtent = function(_) { + if (!arguments.length) return clipExtent; + clipExtent = _; + postclip = _ ? d3_geo_clipExtent(_[0][0], _[0][1], _[1][0], _[1][1]) : d3_identity; + return invalidate(); + }; + projection.scale = function(_) { + if (!arguments.length) return k; + k = +_; + return reset(); + }; + projection.translate = function(_) { + if (!arguments.length) return [ x, y ]; + x = +_[0]; + y = +_[1]; + return reset(); + }; + projection.center = function(_) { + if (!arguments.length) return [ λ * d3_degrees, φ * d3_degrees ]; + λ = _[0] % 360 * d3_radians; + φ = _[1] % 360 * d3_radians; + return reset(); + }; + projection.rotate = function(_) { + if (!arguments.length) return [ δλ * d3_degrees, δφ * d3_degrees, δγ * d3_degrees ]; + δλ = _[0] % 360 * d3_radians; + δφ = _[1] % 360 * d3_radians; + δγ = _.length > 2 ? _[2] % 360 * d3_radians : 0; + return reset(); + }; + d3.rebind(projection, projectResample, "precision"); + function reset() { + projectRotate = d3_geo_compose(rotate = d3_geo_rotation(δλ, δφ, δγ), project); + var center = project(λ, φ); + δx = x - center[0] * k; + δy = y + center[1] * k; + return invalidate(); + } + function invalidate() { + if (stream) stream.valid = false, stream = null; + return projection; + } + return function() { + project = projectAt.apply(this, arguments); + projection.invert = project.invert && invert; + return reset(); + }; + } + function d3_geo_projectionRadians(stream) { + return d3_geo_transformPoint(stream, function(x, y) { + stream.point(x * d3_radians, y * d3_radians); + }); + } + function d3_geo_equirectangular(λ, φ) { + return [ λ, φ ]; + } + (d3.geo.equirectangular = function() { + return d3_geo_projection(d3_geo_equirectangular); + }).raw = d3_geo_equirectangular.invert = d3_geo_equirectangular; + d3.geo.rotation = function(rotate) { + rotate = d3_geo_rotation(rotate[0] % 360 * d3_radians, rotate[1] * d3_radians, rotate.length > 2 ? rotate[2] * d3_radians : 0); + function forward(coordinates) { + coordinates = rotate(coordinates[0] * d3_radians, coordinates[1] * d3_radians); + return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates; + } + forward.invert = function(coordinates) { + coordinates = rotate.invert(coordinates[0] * d3_radians, coordinates[1] * d3_radians); + return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates; + }; + return forward; + }; + function d3_geo_identityRotation(λ, φ) { + return [ λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ ]; + } + d3_geo_identityRotation.invert = d3_geo_equirectangular; + function d3_geo_rotation(δλ, δφ, δγ) { + return δλ ? δφ || δγ ? d3_geo_compose(d3_geo_rotationλ(δλ), d3_geo_rotationφγ(δφ, δγ)) : d3_geo_rotationλ(δλ) : δφ || δγ ? d3_geo_rotationφγ(δφ, δγ) : d3_geo_identityRotation; + } + function d3_geo_forwardRotationλ(δλ) { + return function(λ, φ) { + return λ += δλ, [ λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ ]; + }; + } + function d3_geo_rotationλ(δλ) { + var rotation = d3_geo_forwardRotationλ(δλ); + rotation.invert = d3_geo_forwardRotationλ(-δλ); + return rotation; + } + function d3_geo_rotationφγ(δφ, δγ) { + var cosδφ = Math.cos(δφ), sinδφ = Math.sin(δφ), cosδγ = Math.cos(δγ), sinδγ = Math.sin(δγ); + function rotation(λ, φ) { + var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδφ + x * sinδφ; + return [ Math.atan2(y * cosδγ - k * sinδγ, x * cosδφ - z * sinδφ), d3_asin(k * cosδγ + y * sinδγ) ]; + } + rotation.invert = function(λ, φ) { + var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδγ - y * sinδγ; + return [ Math.atan2(y * cosδγ + z * sinδγ, x * cosδφ + k * sinδφ), d3_asin(k * cosδφ - x * sinδφ) ]; + }; + return rotation; + } + d3.geo.circle = function() { + var origin = [ 0, 0 ], angle, precision = 6, interpolate; + function circle() { + var center = typeof origin === "function" ? origin.apply(this, arguments) : origin, rotate = d3_geo_rotation(-center[0] * d3_radians, -center[1] * d3_radians, 0).invert, ring = []; + interpolate(null, null, 1, { + point: function(x, y) { + ring.push(x = rotate(x, y)); + x[0] *= d3_degrees, x[1] *= d3_degrees; + } + }); + return { + type: "Polygon", + coordinates: [ ring ] + }; + } + circle.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + return circle; + }; + circle.angle = function(x) { + if (!arguments.length) return angle; + interpolate = d3_geo_circleInterpolate((angle = +x) * d3_radians, precision * d3_radians); + return circle; + }; + circle.precision = function(_) { + if (!arguments.length) return precision; + interpolate = d3_geo_circleInterpolate(angle * d3_radians, (precision = +_) * d3_radians); + return circle; + }; + return circle.angle(90); + }; + function d3_geo_circleInterpolate(radius, precision) { + var cr = Math.cos(radius), sr = Math.sin(radius); + return function(from, to, direction, listener) { + var step = direction * precision; + if (from != null) { + from = d3_geo_circleAngle(cr, from); + to = d3_geo_circleAngle(cr, to); + if (direction > 0 ? from < to : from > to) from += direction * τ; + } else { + from = radius + direction * τ; + to = radius - .5 * step; + } + for (var point, t = from; direction > 0 ? t > to : t < to; t -= step) { + listener.point((point = d3_geo_spherical([ cr, -sr * Math.cos(t), -sr * Math.sin(t) ]))[0], point[1]); + } + }; + } + function d3_geo_circleAngle(cr, point) { + var a = d3_geo_cartesian(point); + a[0] -= cr; + d3_geo_cartesianNormalize(a); + var angle = d3_acos(-a[1]); + return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI - ε) % (2 * Math.PI); + } + d3.geo.distance = function(a, b) { + var Δλ = (b[0] - a[0]) * d3_radians, φ0 = a[1] * d3_radians, φ1 = b[1] * d3_radians, sinΔλ = Math.sin(Δλ), cosΔλ = Math.cos(Δλ), sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), sinφ1 = Math.sin(φ1), cosφ1 = Math.cos(φ1), t; + return Math.atan2(Math.sqrt((t = cosφ1 * sinΔλ) * t + (t = cosφ0 * sinφ1 - sinφ0 * cosφ1 * cosΔλ) * t), sinφ0 * sinφ1 + cosφ0 * cosφ1 * cosΔλ); + }; + d3.geo.graticule = function() { + var x1, x0, X1, X0, y1, y0, Y1, Y0, dx = 10, dy = dx, DX = 90, DY = 360, x, y, X, Y, precision = 2.5; + function graticule() { + return { + type: "MultiLineString", + coordinates: lines() + }; + } + function lines() { + return d3.range(Math.ceil(X0 / DX) * DX, X1, DX).map(X).concat(d3.range(Math.ceil(Y0 / DY) * DY, Y1, DY).map(Y)).concat(d3.range(Math.ceil(x0 / dx) * dx, x1, dx).filter(function(x) { + return abs(x % DX) > ε; + }).map(x)).concat(d3.range(Math.ceil(y0 / dy) * dy, y1, dy).filter(function(y) { + return abs(y % DY) > ε; + }).map(y)); + } + graticule.lines = function() { + return lines().map(function(coordinates) { + return { + type: "LineString", + coordinates: coordinates + }; + }); + }; + graticule.outline = function() { + return { + type: "Polygon", + coordinates: [ X(X0).concat(Y(Y1).slice(1), X(X1).reverse().slice(1), Y(Y0).reverse().slice(1)) ] + }; + }; + graticule.extent = function(_) { + if (!arguments.length) return graticule.minorExtent(); + return graticule.majorExtent(_).minorExtent(_); + }; + graticule.majorExtent = function(_) { + if (!arguments.length) return [ [ X0, Y0 ], [ X1, Y1 ] ]; + X0 = +_[0][0], X1 = +_[1][0]; + Y0 = +_[0][1], Y1 = +_[1][1]; + if (X0 > X1) _ = X0, X0 = X1, X1 = _; + if (Y0 > Y1) _ = Y0, Y0 = Y1, Y1 = _; + return graticule.precision(precision); + }; + graticule.minorExtent = function(_) { + if (!arguments.length) return [ [ x0, y0 ], [ x1, y1 ] ]; + x0 = +_[0][0], x1 = +_[1][0]; + y0 = +_[0][1], y1 = +_[1][1]; + if (x0 > x1) _ = x0, x0 = x1, x1 = _; + if (y0 > y1) _ = y0, y0 = y1, y1 = _; + return graticule.precision(precision); + }; + graticule.step = function(_) { + if (!arguments.length) return graticule.minorStep(); + return graticule.majorStep(_).minorStep(_); + }; + graticule.majorStep = function(_) { + if (!arguments.length) return [ DX, DY ]; + DX = +_[0], DY = +_[1]; + return graticule; + }; + graticule.minorStep = function(_) { + if (!arguments.length) return [ dx, dy ]; + dx = +_[0], dy = +_[1]; + return graticule; + }; + graticule.precision = function(_) { + if (!arguments.length) return precision; + precision = +_; + x = d3_geo_graticuleX(y0, y1, 90); + y = d3_geo_graticuleY(x0, x1, precision); + X = d3_geo_graticuleX(Y0, Y1, 90); + Y = d3_geo_graticuleY(X0, X1, precision); + return graticule; + }; + return graticule.majorExtent([ [ -180, -90 + ε ], [ 180, 90 - ε ] ]).minorExtent([ [ -180, -80 - ε ], [ 180, 80 + ε ] ]); + }; + function d3_geo_graticuleX(y0, y1, dy) { + var y = d3.range(y0, y1 - ε, dy).concat(y1); + return function(x) { + return y.map(function(y) { + return [ x, y ]; + }); + }; + } + function d3_geo_graticuleY(x0, x1, dx) { + var x = d3.range(x0, x1 - ε, dx).concat(x1); + return function(y) { + return x.map(function(x) { + return [ x, y ]; + }); + }; + } + function d3_source(d) { + return d.source; + } + function d3_target(d) { + return d.target; + } + d3.geo.greatArc = function() { + var source = d3_source, source_, target = d3_target, target_; + function greatArc() { + return { + type: "LineString", + coordinates: [ source_ || source.apply(this, arguments), target_ || target.apply(this, arguments) ] + }; + } + greatArc.distance = function() { + return d3.geo.distance(source_ || source.apply(this, arguments), target_ || target.apply(this, arguments)); + }; + greatArc.source = function(_) { + if (!arguments.length) return source; + source = _, source_ = typeof _ === "function" ? null : _; + return greatArc; + }; + greatArc.target = function(_) { + if (!arguments.length) return target; + target = _, target_ = typeof _ === "function" ? null : _; + return greatArc; + }; + greatArc.precision = function() { + return arguments.length ? greatArc : 0; + }; + return greatArc; + }; + d3.geo.interpolate = function(source, target) { + return d3_geo_interpolate(source[0] * d3_radians, source[1] * d3_radians, target[0] * d3_radians, target[1] * d3_radians); + }; + function d3_geo_interpolate(x0, y0, x1, y1) { + var cy0 = Math.cos(y0), sy0 = Math.sin(y0), cy1 = Math.cos(y1), sy1 = Math.sin(y1), kx0 = cy0 * Math.cos(x0), ky0 = cy0 * Math.sin(x0), kx1 = cy1 * Math.cos(x1), ky1 = cy1 * Math.sin(x1), d = 2 * Math.asin(Math.sqrt(d3_haversin(y1 - y0) + cy0 * cy1 * d3_haversin(x1 - x0))), k = 1 / Math.sin(d); + var interpolate = d ? function(t) { + var B = Math.sin(t *= d) * k, A = Math.sin(d - t) * k, x = A * kx0 + B * kx1, y = A * ky0 + B * ky1, z = A * sy0 + B * sy1; + return [ Math.atan2(y, x) * d3_degrees, Math.atan2(z, Math.sqrt(x * x + y * y)) * d3_degrees ]; + } : function() { + return [ x0 * d3_degrees, y0 * d3_degrees ]; + }; + interpolate.distance = d; + return interpolate; + } + d3.geo.length = function(object) { + d3_geo_lengthSum = 0; + d3.geo.stream(object, d3_geo_length); + return d3_geo_lengthSum; + }; + var d3_geo_lengthSum; + var d3_geo_length = { + sphere: d3_noop, + point: d3_noop, + lineStart: d3_geo_lengthLineStart, + lineEnd: d3_noop, + polygonStart: d3_noop, + polygonEnd: d3_noop + }; + function d3_geo_lengthLineStart() { + var λ0, sinφ0, cosφ0; + d3_geo_length.point = function(λ, φ) { + λ0 = λ * d3_radians, sinφ0 = Math.sin(φ *= d3_radians), cosφ0 = Math.cos(φ); + d3_geo_length.point = nextPoint; + }; + d3_geo_length.lineEnd = function() { + d3_geo_length.point = d3_geo_length.lineEnd = d3_noop; + }; + function nextPoint(λ, φ) { + var sinφ = Math.sin(φ *= d3_radians), cosφ = Math.cos(φ), t = abs((λ *= d3_radians) - λ0), cosΔλ = Math.cos(t); + d3_geo_lengthSum += Math.atan2(Math.sqrt((t = cosφ * Math.sin(t)) * t + (t = cosφ0 * sinφ - sinφ0 * cosφ * cosΔλ) * t), sinφ0 * sinφ + cosφ0 * cosφ * cosΔλ); + λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ; + } + } + function d3_geo_azimuthal(scale, angle) { + function azimuthal(λ, φ) { + var cosλ = Math.cos(λ), cosφ = Math.cos(φ), k = scale(cosλ * cosφ); + return [ k * cosφ * Math.sin(λ), k * Math.sin(φ) ]; + } + azimuthal.invert = function(x, y) { + var ρ = Math.sqrt(x * x + y * y), c = angle(ρ), sinc = Math.sin(c), cosc = Math.cos(c); + return [ Math.atan2(x * sinc, ρ * cosc), Math.asin(ρ && y * sinc / ρ) ]; + }; + return azimuthal; + } + var d3_geo_azimuthalEqualArea = d3_geo_azimuthal(function(cosλcosφ) { + return Math.sqrt(2 / (1 + cosλcosφ)); + }, function(ρ) { + return 2 * Math.asin(ρ / 2); + }); + (d3.geo.azimuthalEqualArea = function() { + return d3_geo_projection(d3_geo_azimuthalEqualArea); + }).raw = d3_geo_azimuthalEqualArea; + var d3_geo_azimuthalEquidistant = d3_geo_azimuthal(function(cosλcosφ) { + var c = Math.acos(cosλcosφ); + return c && c / Math.sin(c); + }, d3_identity); + (d3.geo.azimuthalEquidistant = function() { + return d3_geo_projection(d3_geo_azimuthalEquidistant); + }).raw = d3_geo_azimuthalEquidistant; + function d3_geo_conicConformal(φ0, φ1) { + var cosφ0 = Math.cos(φ0), t = function(φ) { + return Math.tan(π / 4 + φ / 2); + }, n = φ0 === φ1 ? Math.sin(φ0) : Math.log(cosφ0 / Math.cos(φ1)) / Math.log(t(φ1) / t(φ0)), F = cosφ0 * Math.pow(t(φ0), n) / n; + if (!n) return d3_geo_mercator; + function forward(λ, φ) { + if (F > 0) { + if (φ < -halfπ + ε) φ = -halfπ + ε; + } else { + if (φ > halfπ - ε) φ = halfπ - ε; + } + var ρ = F / Math.pow(t(φ), n); + return [ ρ * Math.sin(n * λ), F - ρ * Math.cos(n * λ) ]; + } + forward.invert = function(x, y) { + var ρ0_y = F - y, ρ = d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y); + return [ Math.atan2(x, ρ0_y) / n, 2 * Math.atan(Math.pow(F / ρ, 1 / n)) - halfπ ]; + }; + return forward; + } + (d3.geo.conicConformal = function() { + return d3_geo_conic(d3_geo_conicConformal); + }).raw = d3_geo_conicConformal; + function d3_geo_conicEquidistant(φ0, φ1) { + var cosφ0 = Math.cos(φ0), n = φ0 === φ1 ? Math.sin(φ0) : (cosφ0 - Math.cos(φ1)) / (φ1 - φ0), G = cosφ0 / n + φ0; + if (abs(n) < ε) return d3_geo_equirectangular; + function forward(λ, φ) { + var ρ = G - φ; + return [ ρ * Math.sin(n * λ), G - ρ * Math.cos(n * λ) ]; + } + forward.invert = function(x, y) { + var ρ0_y = G - y; + return [ Math.atan2(x, ρ0_y) / n, G - d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y) ]; + }; + return forward; + } + (d3.geo.conicEquidistant = function() { + return d3_geo_conic(d3_geo_conicEquidistant); + }).raw = d3_geo_conicEquidistant; + var d3_geo_gnomonic = d3_geo_azimuthal(function(cosλcosφ) { + return 1 / cosλcosφ; + }, Math.atan); + (d3.geo.gnomonic = function() { + return d3_geo_projection(d3_geo_gnomonic); + }).raw = d3_geo_gnomonic; + function d3_geo_mercator(λ, φ) { + return [ λ, Math.log(Math.tan(π / 4 + φ / 2)) ]; + } + d3_geo_mercator.invert = function(x, y) { + return [ x, 2 * Math.atan(Math.exp(y)) - halfπ ]; + }; + function d3_geo_mercatorProjection(project) { + var m = d3_geo_projection(project), scale = m.scale, translate = m.translate, clipExtent = m.clipExtent, clipAuto; + m.scale = function() { + var v = scale.apply(m, arguments); + return v === m ? clipAuto ? m.clipExtent(null) : m : v; + }; + m.translate = function() { + var v = translate.apply(m, arguments); + return v === m ? clipAuto ? m.clipExtent(null) : m : v; + }; + m.clipExtent = function(_) { + var v = clipExtent.apply(m, arguments); + if (v === m) { + if (clipAuto = _ == null) { + var k = π * scale(), t = translate(); + clipExtent([ [ t[0] - k, t[1] - k ], [ t[0] + k, t[1] + k ] ]); + } + } else if (clipAuto) { + v = null; + } + return v; + }; + return m.clipExtent(null); + } + (d3.geo.mercator = function() { + return d3_geo_mercatorProjection(d3_geo_mercator); + }).raw = d3_geo_mercator; + var d3_geo_orthographic = d3_geo_azimuthal(function() { + return 1; + }, Math.asin); + (d3.geo.orthographic = function() { + return d3_geo_projection(d3_geo_orthographic); + }).raw = d3_geo_orthographic; + var d3_geo_stereographic = d3_geo_azimuthal(function(cosλcosφ) { + return 1 / (1 + cosλcosφ); + }, function(ρ) { + return 2 * Math.atan(ρ); + }); + (d3.geo.stereographic = function() { + return d3_geo_projection(d3_geo_stereographic); + }).raw = d3_geo_stereographic; + function d3_geo_transverseMercator(λ, φ) { + return [ Math.log(Math.tan(π / 4 + φ / 2)), -λ ]; + } + d3_geo_transverseMercator.invert = function(x, y) { + return [ -y, 2 * Math.atan(Math.exp(x)) - halfπ ]; + }; + (d3.geo.transverseMercator = function() { + var projection = d3_geo_mercatorProjection(d3_geo_transverseMercator), center = projection.center, rotate = projection.rotate; + projection.center = function(_) { + return _ ? center([ -_[1], _[0] ]) : (_ = center(), [ _[1], -_[0] ]); + }; + projection.rotate = function(_) { + return _ ? rotate([ _[0], _[1], _.length > 2 ? _[2] + 90 : 90 ]) : (_ = rotate(), + [ _[0], _[1], _[2] - 90 ]); + }; + return rotate([ 0, 0, 90 ]); + }).raw = d3_geo_transverseMercator; + d3.geom = {}; + function d3_geom_pointX(d) { + return d[0]; + } + function d3_geom_pointY(d) { + return d[1]; + } + d3.geom.hull = function(vertices) { + var x = d3_geom_pointX, y = d3_geom_pointY; + if (arguments.length) return hull(vertices); + function hull(data) { + if (data.length < 3) return []; + var fx = d3_functor(x), fy = d3_functor(y), i, n = data.length, points = [], flippedPoints = []; + for (i = 0; i < n; i++) { + points.push([ +fx.call(this, data[i], i), +fy.call(this, data[i], i), i ]); + } + points.sort(d3_geom_hullOrder); + for (i = 0; i < n; i++) flippedPoints.push([ points[i][0], -points[i][1] ]); + var upper = d3_geom_hullUpper(points), lower = d3_geom_hullUpper(flippedPoints); + var skipLeft = lower[0] === upper[0], skipRight = lower[lower.length - 1] === upper[upper.length - 1], polygon = []; + for (i = upper.length - 1; i >= 0; --i) polygon.push(data[points[upper[i]][2]]); + for (i = +skipLeft; i < lower.length - skipRight; ++i) polygon.push(data[points[lower[i]][2]]); + return polygon; + } + hull.x = function(_) { + return arguments.length ? (x = _, hull) : x; + }; + hull.y = function(_) { + return arguments.length ? (y = _, hull) : y; + }; + return hull; + }; + function d3_geom_hullUpper(points) { + var n = points.length, hull = [ 0, 1 ], hs = 2; + for (var i = 2; i < n; i++) { + while (hs > 1 && d3_cross2d(points[hull[hs - 2]], points[hull[hs - 1]], points[i]) <= 0) --hs; + hull[hs++] = i; + } + return hull.slice(0, hs); + } + function d3_geom_hullOrder(a, b) { + return a[0] - b[0] || a[1] - b[1]; + } + d3.geom.polygon = function(coordinates) { + d3_subclass(coordinates, d3_geom_polygonPrototype); + return coordinates; + }; + var d3_geom_polygonPrototype = d3.geom.polygon.prototype = []; + d3_geom_polygonPrototype.area = function() { + var i = -1, n = this.length, a, b = this[n - 1], area = 0; + while (++i < n) { + a = b; + b = this[i]; + area += a[1] * b[0] - a[0] * b[1]; + } + return area * .5; + }; + d3_geom_polygonPrototype.centroid = function(k) { + var i = -1, n = this.length, x = 0, y = 0, a, b = this[n - 1], c; + if (!arguments.length) k = -1 / (6 * this.area()); + while (++i < n) { + a = b; + b = this[i]; + c = a[0] * b[1] - b[0] * a[1]; + x += (a[0] + b[0]) * c; + y += (a[1] + b[1]) * c; + } + return [ x * k, y * k ]; + }; + d3_geom_polygonPrototype.clip = function(subject) { + var input, closed = d3_geom_polygonClosed(subject), i = -1, n = this.length - d3_geom_polygonClosed(this), j, m, a = this[n - 1], b, c, d; + while (++i < n) { + input = subject.slice(); + subject.length = 0; + b = this[i]; + c = input[(m = input.length - closed) - 1]; + j = -1; + while (++j < m) { + d = input[j]; + if (d3_geom_polygonInside(d, a, b)) { + if (!d3_geom_polygonInside(c, a, b)) { + subject.push(d3_geom_polygonIntersect(c, d, a, b)); + } + subject.push(d); + } else if (d3_geom_polygonInside(c, a, b)) { + subject.push(d3_geom_polygonIntersect(c, d, a, b)); + } + c = d; + } + if (closed) subject.push(subject[0]); + a = b; + } + return subject; + }; + function d3_geom_polygonInside(p, a, b) { + return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]); + } + function d3_geom_polygonIntersect(c, d, a, b) { + var x1 = c[0], x3 = a[0], x21 = d[0] - x1, x43 = b[0] - x3, y1 = c[1], y3 = a[1], y21 = d[1] - y1, y43 = b[1] - y3, ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21); + return [ x1 + ua * x21, y1 + ua * y21 ]; + } + function d3_geom_polygonClosed(coordinates) { + var a = coordinates[0], b = coordinates[coordinates.length - 1]; + return !(a[0] - b[0] || a[1] - b[1]); + } + var d3_geom_voronoiEdges, d3_geom_voronoiCells, d3_geom_voronoiBeaches, d3_geom_voronoiBeachPool = [], d3_geom_voronoiFirstCircle, d3_geom_voronoiCircles, d3_geom_voronoiCirclePool = []; + function d3_geom_voronoiBeach() { + d3_geom_voronoiRedBlackNode(this); + this.edge = this.site = this.circle = null; + } + function d3_geom_voronoiCreateBeach(site) { + var beach = d3_geom_voronoiBeachPool.pop() || new d3_geom_voronoiBeach(); + beach.site = site; + return beach; + } + function d3_geom_voronoiDetachBeach(beach) { + d3_geom_voronoiDetachCircle(beach); + d3_geom_voronoiBeaches.remove(beach); + d3_geom_voronoiBeachPool.push(beach); + d3_geom_voronoiRedBlackNode(beach); + } + function d3_geom_voronoiRemoveBeach(beach) { + var circle = beach.circle, x = circle.x, y = circle.cy, vertex = { + x: x, + y: y + }, previous = beach.P, next = beach.N, disappearing = [ beach ]; + d3_geom_voronoiDetachBeach(beach); + var lArc = previous; + while (lArc.circle && abs(x - lArc.circle.x) < ε && abs(y - lArc.circle.cy) < ε) { + previous = lArc.P; + disappearing.unshift(lArc); + d3_geom_voronoiDetachBeach(lArc); + lArc = previous; + } + disappearing.unshift(lArc); + d3_geom_voronoiDetachCircle(lArc); + var rArc = next; + while (rArc.circle && abs(x - rArc.circle.x) < ε && abs(y - rArc.circle.cy) < ε) { + next = rArc.N; + disappearing.push(rArc); + d3_geom_voronoiDetachBeach(rArc); + rArc = next; + } + disappearing.push(rArc); + d3_geom_voronoiDetachCircle(rArc); + var nArcs = disappearing.length, iArc; + for (iArc = 1; iArc < nArcs; ++iArc) { + rArc = disappearing[iArc]; + lArc = disappearing[iArc - 1]; + d3_geom_voronoiSetEdgeEnd(rArc.edge, lArc.site, rArc.site, vertex); + } + lArc = disappearing[0]; + rArc = disappearing[nArcs - 1]; + rArc.edge = d3_geom_voronoiCreateEdge(lArc.site, rArc.site, null, vertex); + d3_geom_voronoiAttachCircle(lArc); + d3_geom_voronoiAttachCircle(rArc); + } + function d3_geom_voronoiAddBeach(site) { + var x = site.x, directrix = site.y, lArc, rArc, dxl, dxr, node = d3_geom_voronoiBeaches._; + while (node) { + dxl = d3_geom_voronoiLeftBreakPoint(node, directrix) - x; + if (dxl > ε) node = node.L; else { + dxr = x - d3_geom_voronoiRightBreakPoint(node, directrix); + if (dxr > ε) { + if (!node.R) { + lArc = node; + break; + } + node = node.R; + } else { + if (dxl > -ε) { + lArc = node.P; + rArc = node; + } else if (dxr > -ε) { + lArc = node; + rArc = node.N; + } else { + lArc = rArc = node; + } + break; + } + } + } + var newArc = d3_geom_voronoiCreateBeach(site); + d3_geom_voronoiBeaches.insert(lArc, newArc); + if (!lArc && !rArc) return; + if (lArc === rArc) { + d3_geom_voronoiDetachCircle(lArc); + rArc = d3_geom_voronoiCreateBeach(lArc.site); + d3_geom_voronoiBeaches.insert(newArc, rArc); + newArc.edge = rArc.edge = d3_geom_voronoiCreateEdge(lArc.site, newArc.site); + d3_geom_voronoiAttachCircle(lArc); + d3_geom_voronoiAttachCircle(rArc); + return; + } + if (!rArc) { + newArc.edge = d3_geom_voronoiCreateEdge(lArc.site, newArc.site); + return; + } + d3_geom_voronoiDetachCircle(lArc); + d3_geom_voronoiDetachCircle(rArc); + var lSite = lArc.site, ax = lSite.x, ay = lSite.y, bx = site.x - ax, by = site.y - ay, rSite = rArc.site, cx = rSite.x - ax, cy = rSite.y - ay, d = 2 * (bx * cy - by * cx), hb = bx * bx + by * by, hc = cx * cx + cy * cy, vertex = { + x: (cy * hb - by * hc) / d + ax, + y: (bx * hc - cx * hb) / d + ay + }; + d3_geom_voronoiSetEdgeEnd(rArc.edge, lSite, rSite, vertex); + newArc.edge = d3_geom_voronoiCreateEdge(lSite, site, null, vertex); + rArc.edge = d3_geom_voronoiCreateEdge(site, rSite, null, vertex); + d3_geom_voronoiAttachCircle(lArc); + d3_geom_voronoiAttachCircle(rArc); + } + function d3_geom_voronoiLeftBreakPoint(arc, directrix) { + var site = arc.site, rfocx = site.x, rfocy = site.y, pby2 = rfocy - directrix; + if (!pby2) return rfocx; + var lArc = arc.P; + if (!lArc) return -Infinity; + site = lArc.site; + var lfocx = site.x, lfocy = site.y, plby2 = lfocy - directrix; + if (!plby2) return lfocx; + var hl = lfocx - rfocx, aby2 = 1 / pby2 - 1 / plby2, b = hl / plby2; + if (aby2) return (-b + Math.sqrt(b * b - 2 * aby2 * (hl * hl / (-2 * plby2) - lfocy + plby2 / 2 + rfocy - pby2 / 2))) / aby2 + rfocx; + return (rfocx + lfocx) / 2; + } + function d3_geom_voronoiRightBreakPoint(arc, directrix) { + var rArc = arc.N; + if (rArc) return d3_geom_voronoiLeftBreakPoint(rArc, directrix); + var site = arc.site; + return site.y === directrix ? site.x : Infinity; + } + function d3_geom_voronoiCell(site) { + this.site = site; + this.edges = []; + } + d3_geom_voronoiCell.prototype.prepare = function() { + var halfEdges = this.edges, iHalfEdge = halfEdges.length, edge; + while (iHalfEdge--) { + edge = halfEdges[iHalfEdge].edge; + if (!edge.b || !edge.a) halfEdges.splice(iHalfEdge, 1); + } + halfEdges.sort(d3_geom_voronoiHalfEdgeOrder); + return halfEdges.length; + }; + function d3_geom_voronoiCloseCells(extent) { + var x0 = extent[0][0], x1 = extent[1][0], y0 = extent[0][1], y1 = extent[1][1], x2, y2, x3, y3, cells = d3_geom_voronoiCells, iCell = cells.length, cell, iHalfEdge, halfEdges, nHalfEdges, start, end; + while (iCell--) { + cell = cells[iCell]; + if (!cell || !cell.prepare()) continue; + halfEdges = cell.edges; + nHalfEdges = halfEdges.length; + iHalfEdge = 0; + while (iHalfEdge < nHalfEdges) { + end = halfEdges[iHalfEdge].end(), x3 = end.x, y3 = end.y; + start = halfEdges[++iHalfEdge % nHalfEdges].start(), x2 = start.x, y2 = start.y; + if (abs(x3 - x2) > ε || abs(y3 - y2) > ε) { + halfEdges.splice(iHalfEdge, 0, new d3_geom_voronoiHalfEdge(d3_geom_voronoiCreateBorderEdge(cell.site, end, abs(x3 - x0) < ε && y1 - y3 > ε ? { + x: x0, + y: abs(x2 - x0) < ε ? y2 : y1 + } : abs(y3 - y1) < ε && x1 - x3 > ε ? { + x: abs(y2 - y1) < ε ? x2 : x1, + y: y1 + } : abs(x3 - x1) < ε && y3 - y0 > ε ? { + x: x1, + y: abs(x2 - x1) < ε ? y2 : y0 + } : abs(y3 - y0) < ε && x3 - x0 > ε ? { + x: abs(y2 - y0) < ε ? x2 : x0, + y: y0 + } : null), cell.site, null)); + ++nHalfEdges; + } + } + } + } + function d3_geom_voronoiHalfEdgeOrder(a, b) { + return b.angle - a.angle; + } + function d3_geom_voronoiCircle() { + d3_geom_voronoiRedBlackNode(this); + this.x = this.y = this.arc = this.site = this.cy = null; + } + function d3_geom_voronoiAttachCircle(arc) { + var lArc = arc.P, rArc = arc.N; + if (!lArc || !rArc) return; + var lSite = lArc.site, cSite = arc.site, rSite = rArc.site; + if (lSite === rSite) return; + var bx = cSite.x, by = cSite.y, ax = lSite.x - bx, ay = lSite.y - by, cx = rSite.x - bx, cy = rSite.y - by; + var d = 2 * (ax * cy - ay * cx); + if (d >= -ε2) return; + var ha = ax * ax + ay * ay, hc = cx * cx + cy * cy, x = (cy * ha - ay * hc) / d, y = (ax * hc - cx * ha) / d, cy = y + by; + var circle = d3_geom_voronoiCirclePool.pop() || new d3_geom_voronoiCircle(); + circle.arc = arc; + circle.site = cSite; + circle.x = x + bx; + circle.y = cy + Math.sqrt(x * x + y * y); + circle.cy = cy; + arc.circle = circle; + var before = null, node = d3_geom_voronoiCircles._; + while (node) { + if (circle.y < node.y || circle.y === node.y && circle.x <= node.x) { + if (node.L) node = node.L; else { + before = node.P; + break; + } + } else { + if (node.R) node = node.R; else { + before = node; + break; + } + } + } + d3_geom_voronoiCircles.insert(before, circle); + if (!before) d3_geom_voronoiFirstCircle = circle; + } + function d3_geom_voronoiDetachCircle(arc) { + var circle = arc.circle; + if (circle) { + if (!circle.P) d3_geom_voronoiFirstCircle = circle.N; + d3_geom_voronoiCircles.remove(circle); + d3_geom_voronoiCirclePool.push(circle); + d3_geom_voronoiRedBlackNode(circle); + arc.circle = null; + } + } + function d3_geom_voronoiClipEdges(extent) { + var edges = d3_geom_voronoiEdges, clip = d3_geom_clipLine(extent[0][0], extent[0][1], extent[1][0], extent[1][1]), i = edges.length, e; + while (i--) { + e = edges[i]; + if (!d3_geom_voronoiConnectEdge(e, extent) || !clip(e) || abs(e.a.x - e.b.x) < ε && abs(e.a.y - e.b.y) < ε) { + e.a = e.b = null; + edges.splice(i, 1); + } + } + } + function d3_geom_voronoiConnectEdge(edge, extent) { + var vb = edge.b; + if (vb) return true; + var va = edge.a, x0 = extent[0][0], x1 = extent[1][0], y0 = extent[0][1], y1 = extent[1][1], lSite = edge.l, rSite = edge.r, lx = lSite.x, ly = lSite.y, rx = rSite.x, ry = rSite.y, fx = (lx + rx) / 2, fy = (ly + ry) / 2, fm, fb; + if (ry === ly) { + if (fx < x0 || fx >= x1) return; + if (lx > rx) { + if (!va) va = { + x: fx, + y: y0 + }; else if (va.y >= y1) return; + vb = { + x: fx, + y: y1 + }; + } else { + if (!va) va = { + x: fx, + y: y1 + }; else if (va.y < y0) return; + vb = { + x: fx, + y: y0 + }; + } + } else { + fm = (lx - rx) / (ry - ly); + fb = fy - fm * fx; + if (fm < -1 || fm > 1) { + if (lx > rx) { + if (!va) va = { + x: (y0 - fb) / fm, + y: y0 + }; else if (va.y >= y1) return; + vb = { + x: (y1 - fb) / fm, + y: y1 + }; + } else { + if (!va) va = { + x: (y1 - fb) / fm, + y: y1 + }; else if (va.y < y0) return; + vb = { + x: (y0 - fb) / fm, + y: y0 + }; + } + } else { + if (ly < ry) { + if (!va) va = { + x: x0, + y: fm * x0 + fb + }; else if (va.x >= x1) return; + vb = { + x: x1, + y: fm * x1 + fb + }; + } else { + if (!va) va = { + x: x1, + y: fm * x1 + fb + }; else if (va.x < x0) return; + vb = { + x: x0, + y: fm * x0 + fb + }; + } + } + } + edge.a = va; + edge.b = vb; + return true; + } + function d3_geom_voronoiEdge(lSite, rSite) { + this.l = lSite; + this.r = rSite; + this.a = this.b = null; + } + function d3_geom_voronoiCreateEdge(lSite, rSite, va, vb) { + var edge = new d3_geom_voronoiEdge(lSite, rSite); + d3_geom_voronoiEdges.push(edge); + if (va) d3_geom_voronoiSetEdgeEnd(edge, lSite, rSite, va); + if (vb) d3_geom_voronoiSetEdgeEnd(edge, rSite, lSite, vb); + d3_geom_voronoiCells[lSite.i].edges.push(new d3_geom_voronoiHalfEdge(edge, lSite, rSite)); + d3_geom_voronoiCells[rSite.i].edges.push(new d3_geom_voronoiHalfEdge(edge, rSite, lSite)); + return edge; + } + function d3_geom_voronoiCreateBorderEdge(lSite, va, vb) { + var edge = new d3_geom_voronoiEdge(lSite, null); + edge.a = va; + edge.b = vb; + d3_geom_voronoiEdges.push(edge); + return edge; + } + function d3_geom_voronoiSetEdgeEnd(edge, lSite, rSite, vertex) { + if (!edge.a && !edge.b) { + edge.a = vertex; + edge.l = lSite; + edge.r = rSite; + } else if (edge.l === rSite) { + edge.b = vertex; + } else { + edge.a = vertex; + } + } + function d3_geom_voronoiHalfEdge(edge, lSite, rSite) { + var va = edge.a, vb = edge.b; + this.edge = edge; + this.site = lSite; + this.angle = rSite ? Math.atan2(rSite.y - lSite.y, rSite.x - lSite.x) : edge.l === lSite ? Math.atan2(vb.x - va.x, va.y - vb.y) : Math.atan2(va.x - vb.x, vb.y - va.y); + } + d3_geom_voronoiHalfEdge.prototype = { + start: function() { + return this.edge.l === this.site ? this.edge.a : this.edge.b; + }, + end: function() { + return this.edge.l === this.site ? this.edge.b : this.edge.a; + } + }; + function d3_geom_voronoiRedBlackTree() { + this._ = null; + } + function d3_geom_voronoiRedBlackNode(node) { + node.U = node.C = node.L = node.R = node.P = node.N = null; + } + d3_geom_voronoiRedBlackTree.prototype = { + insert: function(after, node) { + var parent, grandpa, uncle; + if (after) { + node.P = after; + node.N = after.N; + if (after.N) after.N.P = node; + after.N = node; + if (after.R) { + after = after.R; + while (after.L) after = after.L; + after.L = node; + } else { + after.R = node; + } + parent = after; + } else if (this._) { + after = d3_geom_voronoiRedBlackFirst(this._); + node.P = null; + node.N = after; + after.P = after.L = node; + parent = after; + } else { + node.P = node.N = null; + this._ = node; + parent = null; + } + node.L = node.R = null; + node.U = parent; + node.C = true; + after = node; + while (parent && parent.C) { + grandpa = parent.U; + if (parent === grandpa.L) { + uncle = grandpa.R; + if (uncle && uncle.C) { + parent.C = uncle.C = false; + grandpa.C = true; + after = grandpa; + } else { + if (after === parent.R) { + d3_geom_voronoiRedBlackRotateLeft(this, parent); + after = parent; + parent = after.U; + } + parent.C = false; + grandpa.C = true; + d3_geom_voronoiRedBlackRotateRight(this, grandpa); + } + } else { + uncle = grandpa.L; + if (uncle && uncle.C) { + parent.C = uncle.C = false; + grandpa.C = true; + after = grandpa; + } else { + if (after === parent.L) { + d3_geom_voronoiRedBlackRotateRight(this, parent); + after = parent; + parent = after.U; + } + parent.C = false; + grandpa.C = true; + d3_geom_voronoiRedBlackRotateLeft(this, grandpa); + } + } + parent = after.U; + } + this._.C = false; + }, + remove: function(node) { + if (node.N) node.N.P = node.P; + if (node.P) node.P.N = node.N; + node.N = node.P = null; + var parent = node.U, sibling, left = node.L, right = node.R, next, red; + if (!left) next = right; else if (!right) next = left; else next = d3_geom_voronoiRedBlackFirst(right); + if (parent) { + if (parent.L === node) parent.L = next; else parent.R = next; + } else { + this._ = next; + } + if (left && right) { + red = next.C; + next.C = node.C; + next.L = left; + left.U = next; + if (next !== right) { + parent = next.U; + next.U = node.U; + node = next.R; + parent.L = node; + next.R = right; + right.U = next; + } else { + next.U = parent; + parent = next; + node = next.R; + } + } else { + red = node.C; + node = next; + } + if (node) node.U = parent; + if (red) return; + if (node && node.C) { + node.C = false; + return; + } + do { + if (node === this._) break; + if (node === parent.L) { + sibling = parent.R; + if (sibling.C) { + sibling.C = false; + parent.C = true; + d3_geom_voronoiRedBlackRotateLeft(this, parent); + sibling = parent.R; + } + if (sibling.L && sibling.L.C || sibling.R && sibling.R.C) { + if (!sibling.R || !sibling.R.C) { + sibling.L.C = false; + sibling.C = true; + d3_geom_voronoiRedBlackRotateRight(this, sibling); + sibling = parent.R; + } + sibling.C = parent.C; + parent.C = sibling.R.C = false; + d3_geom_voronoiRedBlackRotateLeft(this, parent); + node = this._; + break; + } + } else { + sibling = parent.L; + if (sibling.C) { + sibling.C = false; + parent.C = true; + d3_geom_voronoiRedBlackRotateRight(this, parent); + sibling = parent.L; + } + if (sibling.L && sibling.L.C || sibling.R && sibling.R.C) { + if (!sibling.L || !sibling.L.C) { + sibling.R.C = false; + sibling.C = true; + d3_geom_voronoiRedBlackRotateLeft(this, sibling); + sibling = parent.L; + } + sibling.C = parent.C; + parent.C = sibling.L.C = false; + d3_geom_voronoiRedBlackRotateRight(this, parent); + node = this._; + break; + } + } + sibling.C = true; + node = parent; + parent = parent.U; + } while (!node.C); + if (node) node.C = false; + } + }; + function d3_geom_voronoiRedBlackRotateLeft(tree, node) { + var p = node, q = node.R, parent = p.U; + if (parent) { + if (parent.L === p) parent.L = q; else parent.R = q; + } else { + tree._ = q; + } + q.U = parent; + p.U = q; + p.R = q.L; + if (p.R) p.R.U = p; + q.L = p; + } + function d3_geom_voronoiRedBlackRotateRight(tree, node) { + var p = node, q = node.L, parent = p.U; + if (parent) { + if (parent.L === p) parent.L = q; else parent.R = q; + } else { + tree._ = q; + } + q.U = parent; + p.U = q; + p.L = q.R; + if (p.L) p.L.U = p; + q.R = p; + } + function d3_geom_voronoiRedBlackFirst(node) { + while (node.L) node = node.L; + return node; + } + function d3_geom_voronoi(sites, bbox) { + var site = sites.sort(d3_geom_voronoiVertexOrder).pop(), x0, y0, circle; + d3_geom_voronoiEdges = []; + d3_geom_voronoiCells = new Array(sites.length); + d3_geom_voronoiBeaches = new d3_geom_voronoiRedBlackTree(); + d3_geom_voronoiCircles = new d3_geom_voronoiRedBlackTree(); + while (true) { + circle = d3_geom_voronoiFirstCircle; + if (site && (!circle || site.y < circle.y || site.y === circle.y && site.x < circle.x)) { + if (site.x !== x0 || site.y !== y0) { + d3_geom_voronoiCells[site.i] = new d3_geom_voronoiCell(site); + d3_geom_voronoiAddBeach(site); + x0 = site.x, y0 = site.y; + } + site = sites.pop(); + } else if (circle) { + d3_geom_voronoiRemoveBeach(circle.arc); + } else { + break; + } + } + if (bbox) d3_geom_voronoiClipEdges(bbox), d3_geom_voronoiCloseCells(bbox); + var diagram = { + cells: d3_geom_voronoiCells, + edges: d3_geom_voronoiEdges + }; + d3_geom_voronoiBeaches = d3_geom_voronoiCircles = d3_geom_voronoiEdges = d3_geom_voronoiCells = null; + return diagram; + } + function d3_geom_voronoiVertexOrder(a, b) { + return b.y - a.y || b.x - a.x; + } + d3.geom.voronoi = function(points) { + var x = d3_geom_pointX, y = d3_geom_pointY, fx = x, fy = y, clipExtent = d3_geom_voronoiClipExtent; + if (points) return voronoi(points); + function voronoi(data) { + var polygons = new Array(data.length), x0 = clipExtent[0][0], y0 = clipExtent[0][1], x1 = clipExtent[1][0], y1 = clipExtent[1][1]; + d3_geom_voronoi(sites(data), clipExtent).cells.forEach(function(cell, i) { + var edges = cell.edges, site = cell.site, polygon = polygons[i] = edges.length ? edges.map(function(e) { + var s = e.start(); + return [ s.x, s.y ]; + }) : site.x >= x0 && site.x <= x1 && site.y >= y0 && site.y <= y1 ? [ [ x0, y1 ], [ x1, y1 ], [ x1, y0 ], [ x0, y0 ] ] : []; + polygon.point = data[i]; + }); + return polygons; + } + function sites(data) { + return data.map(function(d, i) { + return { + x: Math.round(fx(d, i) / ε) * ε, + y: Math.round(fy(d, i) / ε) * ε, + i: i + }; + }); + } + voronoi.links = function(data) { + return d3_geom_voronoi(sites(data)).edges.filter(function(edge) { + return edge.l && edge.r; + }).map(function(edge) { + return { + source: data[edge.l.i], + target: data[edge.r.i] + }; + }); + }; + voronoi.triangles = function(data) { + var triangles = []; + d3_geom_voronoi(sites(data)).cells.forEach(function(cell, i) { + var site = cell.site, edges = cell.edges.sort(d3_geom_voronoiHalfEdgeOrder), j = -1, m = edges.length, e0, s0, e1 = edges[m - 1].edge, s1 = e1.l === site ? e1.r : e1.l; + while (++j < m) { + e0 = e1; + s0 = s1; + e1 = edges[j].edge; + s1 = e1.l === site ? e1.r : e1.l; + if (i < s0.i && i < s1.i && d3_geom_voronoiTriangleArea(site, s0, s1) < 0) { + triangles.push([ data[i], data[s0.i], data[s1.i] ]); + } + } + }); + return triangles; + }; + voronoi.x = function(_) { + return arguments.length ? (fx = d3_functor(x = _), voronoi) : x; + }; + voronoi.y = function(_) { + return arguments.length ? (fy = d3_functor(y = _), voronoi) : y; + }; + voronoi.clipExtent = function(_) { + if (!arguments.length) return clipExtent === d3_geom_voronoiClipExtent ? null : clipExtent; + clipExtent = _ == null ? d3_geom_voronoiClipExtent : _; + return voronoi; + }; + voronoi.size = function(_) { + if (!arguments.length) return clipExtent === d3_geom_voronoiClipExtent ? null : clipExtent && clipExtent[1]; + return voronoi.clipExtent(_ && [ [ 0, 0 ], _ ]); + }; + return voronoi; + }; + var d3_geom_voronoiClipExtent = [ [ -1e6, -1e6 ], [ 1e6, 1e6 ] ]; + function d3_geom_voronoiTriangleArea(a, b, c) { + return (a.x - c.x) * (b.y - a.y) - (a.x - b.x) * (c.y - a.y); + } + d3.geom.delaunay = function(vertices) { + return d3.geom.voronoi().triangles(vertices); + }; + d3.geom.quadtree = function(points, x1, y1, x2, y2) { + var x = d3_geom_pointX, y = d3_geom_pointY, compat; + if (compat = arguments.length) { + x = d3_geom_quadtreeCompatX; + y = d3_geom_quadtreeCompatY; + if (compat === 3) { + y2 = y1; + x2 = x1; + y1 = x1 = 0; + } + return quadtree(points); + } + function quadtree(data) { + var d, fx = d3_functor(x), fy = d3_functor(y), xs, ys, i, n, x1_, y1_, x2_, y2_; + if (x1 != null) { + x1_ = x1, y1_ = y1, x2_ = x2, y2_ = y2; + } else { + x2_ = y2_ = -(x1_ = y1_ = Infinity); + xs = [], ys = []; + n = data.length; + if (compat) for (i = 0; i < n; ++i) { + d = data[i]; + if (d.x < x1_) x1_ = d.x; + if (d.y < y1_) y1_ = d.y; + if (d.x > x2_) x2_ = d.x; + if (d.y > y2_) y2_ = d.y; + xs.push(d.x); + ys.push(d.y); + } else for (i = 0; i < n; ++i) { + var x_ = +fx(d = data[i], i), y_ = +fy(d, i); + if (x_ < x1_) x1_ = x_; + if (y_ < y1_) y1_ = y_; + if (x_ > x2_) x2_ = x_; + if (y_ > y2_) y2_ = y_; + xs.push(x_); + ys.push(y_); + } + } + var dx = x2_ - x1_, dy = y2_ - y1_; + if (dx > dy) y2_ = y1_ + dx; else x2_ = x1_ + dy; + function insert(n, d, x, y, x1, y1, x2, y2) { + if (isNaN(x) || isNaN(y)) return; + if (n.leaf) { + var nx = n.x, ny = n.y; + if (nx != null) { + if (abs(nx - x) + abs(ny - y) < .01) { + insertChild(n, d, x, y, x1, y1, x2, y2); + } else { + var nPoint = n.point; + n.x = n.y = n.point = null; + insertChild(n, nPoint, nx, ny, x1, y1, x2, y2); + insertChild(n, d, x, y, x1, y1, x2, y2); + } + } else { + n.x = x, n.y = y, n.point = d; + } + } else { + insertChild(n, d, x, y, x1, y1, x2, y2); + } + } + function insertChild(n, d, x, y, x1, y1, x2, y2) { + var xm = (x1 + x2) * .5, ym = (y1 + y2) * .5, right = x >= xm, below = y >= ym, i = below << 1 | right; + n.leaf = false; + n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode()); + if (right) x1 = xm; else x2 = xm; + if (below) y1 = ym; else y2 = ym; + insert(n, d, x, y, x1, y1, x2, y2); + } + var root = d3_geom_quadtreeNode(); + root.add = function(d) { + insert(root, d, +fx(d, ++i), +fy(d, i), x1_, y1_, x2_, y2_); + }; + root.visit = function(f) { + d3_geom_quadtreeVisit(f, root, x1_, y1_, x2_, y2_); + }; + root.find = function(point) { + return d3_geom_quadtreeFind(root, point[0], point[1], x1_, y1_, x2_, y2_); + }; + i = -1; + if (x1 == null) { + while (++i < n) { + insert(root, data[i], xs[i], ys[i], x1_, y1_, x2_, y2_); + } + --i; + } else data.forEach(root.add); + xs = ys = data = d = null; + return root; + } + quadtree.x = function(_) { + return arguments.length ? (x = _, quadtree) : x; + }; + quadtree.y = function(_) { + return arguments.length ? (y = _, quadtree) : y; + }; + quadtree.extent = function(_) { + if (!arguments.length) return x1 == null ? null : [ [ x1, y1 ], [ x2, y2 ] ]; + if (_ == null) x1 = y1 = x2 = y2 = null; else x1 = +_[0][0], y1 = +_[0][1], x2 = +_[1][0], + y2 = +_[1][1]; + return quadtree; + }; + quadtree.size = function(_) { + if (!arguments.length) return x1 == null ? null : [ x2 - x1, y2 - y1 ]; + if (_ == null) x1 = y1 = x2 = y2 = null; else x1 = y1 = 0, x2 = +_[0], y2 = +_[1]; + return quadtree; + }; + return quadtree; + }; + function d3_geom_quadtreeCompatX(d) { + return d.x; + } + function d3_geom_quadtreeCompatY(d) { + return d.y; + } + function d3_geom_quadtreeNode() { + return { + leaf: true, + nodes: [], + point: null, + x: null, + y: null + }; + } + function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) { + if (!f(node, x1, y1, x2, y2)) { + var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, children = node.nodes; + if (children[0]) d3_geom_quadtreeVisit(f, children[0], x1, y1, sx, sy); + if (children[1]) d3_geom_quadtreeVisit(f, children[1], sx, y1, x2, sy); + if (children[2]) d3_geom_quadtreeVisit(f, children[2], x1, sy, sx, y2); + if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2); + } + } + function d3_geom_quadtreeFind(root, x, y, x0, y0, x3, y3) { + var minDistance2 = Infinity, closestPoint; + (function find(node, x1, y1, x2, y2) { + if (x1 > x3 || y1 > y3 || x2 < x0 || y2 < y0) return; + if (point = node.point) { + var point, dx = x - node.x, dy = y - node.y, distance2 = dx * dx + dy * dy; + if (distance2 < minDistance2) { + var distance = Math.sqrt(minDistance2 = distance2); + x0 = x - distance, y0 = y - distance; + x3 = x + distance, y3 = y + distance; + closestPoint = point; + } + } + var children = node.nodes, xm = (x1 + x2) * .5, ym = (y1 + y2) * .5, right = x >= xm, below = y >= ym; + for (var i = below << 1 | right, j = i + 4; i < j; ++i) { + if (node = children[i & 3]) switch (i & 3) { + case 0: + find(node, x1, y1, xm, ym); + break; + + case 1: + find(node, xm, y1, x2, ym); + break; + + case 2: + find(node, x1, ym, xm, y2); + break; + + case 3: + find(node, xm, ym, x2, y2); + break; + } + } + })(root, x0, y0, x3, y3); + return closestPoint; + } + d3.interpolateRgb = d3_interpolateRgb; + function d3_interpolateRgb(a, b) { + a = d3.rgb(a); + b = d3.rgb(b); + var ar = a.r, ag = a.g, ab = a.b, br = b.r - ar, bg = b.g - ag, bb = b.b - ab; + return function(t) { + return "#" + d3_rgb_hex(Math.round(ar + br * t)) + d3_rgb_hex(Math.round(ag + bg * t)) + d3_rgb_hex(Math.round(ab + bb * t)); + }; + } + d3.interpolateObject = d3_interpolateObject; + function d3_interpolateObject(a, b) { + var i = {}, c = {}, k; + for (k in a) { + if (k in b) { + i[k] = d3_interpolate(a[k], b[k]); + } else { + c[k] = a[k]; + } + } + for (k in b) { + if (!(k in a)) { + c[k] = b[k]; + } + } + return function(t) { + for (k in i) c[k] = i[k](t); + return c; + }; + } + d3.interpolateNumber = d3_interpolateNumber; + function d3_interpolateNumber(a, b) { + a = +a, b = +b; + return function(t) { + return a * (1 - t) + b * t; + }; + } + d3.interpolateString = d3_interpolateString; + function d3_interpolateString(a, b) { + var bi = d3_interpolate_numberA.lastIndex = d3_interpolate_numberB.lastIndex = 0, am, bm, bs, i = -1, s = [], q = []; + a = a + "", b = b + ""; + while ((am = d3_interpolate_numberA.exec(a)) && (bm = d3_interpolate_numberB.exec(b))) { + if ((bs = bm.index) > bi) { + bs = b.slice(bi, bs); + if (s[i]) s[i] += bs; else s[++i] = bs; + } + if ((am = am[0]) === (bm = bm[0])) { + if (s[i]) s[i] += bm; else s[++i] = bm; + } else { + s[++i] = null; + q.push({ + i: i, + x: d3_interpolateNumber(am, bm) + }); + } + bi = d3_interpolate_numberB.lastIndex; + } + if (bi < b.length) { + bs = b.slice(bi); + if (s[i]) s[i] += bs; else s[++i] = bs; + } + return s.length < 2 ? q[0] ? (b = q[0].x, function(t) { + return b(t) + ""; + }) : function() { + return b; + } : (b = q.length, function(t) { + for (var i = 0, o; i < b; ++i) s[(o = q[i]).i] = o.x(t); + return s.join(""); + }); + } + var d3_interpolate_numberA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g, d3_interpolate_numberB = new RegExp(d3_interpolate_numberA.source, "g"); + d3.interpolate = d3_interpolate; + function d3_interpolate(a, b) { + var i = d3.interpolators.length, f; + while (--i >= 0 && !(f = d3.interpolators[i](a, b))) ; + return f; + } + d3.interpolators = [ function(a, b) { + var t = typeof b; + return (t === "string" ? d3_rgb_names.has(b.toLowerCase()) || /^(#|rgb\(|hsl\()/i.test(b) ? d3_interpolateRgb : d3_interpolateString : b instanceof d3_color ? d3_interpolateRgb : Array.isArray(b) ? d3_interpolateArray : t === "object" && isNaN(b) ? d3_interpolateObject : d3_interpolateNumber)(a, b); + } ]; + d3.interpolateArray = d3_interpolateArray; + function d3_interpolateArray(a, b) { + var x = [], c = [], na = a.length, nb = b.length, n0 = Math.min(a.length, b.length), i; + for (i = 0; i < n0; ++i) x.push(d3_interpolate(a[i], b[i])); + for (;i < na; ++i) c[i] = a[i]; + for (;i < nb; ++i) c[i] = b[i]; + return function(t) { + for (i = 0; i < n0; ++i) c[i] = x[i](t); + return c; + }; + } + var d3_ease_default = function() { + return d3_identity; + }; + var d3_ease = d3.map({ + linear: d3_ease_default, + poly: d3_ease_poly, + quad: function() { + return d3_ease_quad; + }, + cubic: function() { + return d3_ease_cubic; + }, + sin: function() { + return d3_ease_sin; + }, + exp: function() { + return d3_ease_exp; + }, + circle: function() { + return d3_ease_circle; + }, + elastic: d3_ease_elastic, + back: d3_ease_back, + bounce: function() { + return d3_ease_bounce; + } + }); + var d3_ease_mode = d3.map({ + "in": d3_identity, + out: d3_ease_reverse, + "in-out": d3_ease_reflect, + "out-in": function(f) { + return d3_ease_reflect(d3_ease_reverse(f)); + } + }); + d3.ease = function(name) { + var i = name.indexOf("-"), t = i >= 0 ? name.slice(0, i) : name, m = i >= 0 ? name.slice(i + 1) : "in"; + t = d3_ease.get(t) || d3_ease_default; + m = d3_ease_mode.get(m) || d3_identity; + return d3_ease_clamp(m(t.apply(null, d3_arraySlice.call(arguments, 1)))); + }; + function d3_ease_clamp(f) { + return function(t) { + return t <= 0 ? 0 : t >= 1 ? 1 : f(t); + }; + } + function d3_ease_reverse(f) { + return function(t) { + return 1 - f(1 - t); + }; + } + function d3_ease_reflect(f) { + return function(t) { + return .5 * (t < .5 ? f(2 * t) : 2 - f(2 - 2 * t)); + }; + } + function d3_ease_quad(t) { + return t * t; + } + function d3_ease_cubic(t) { + return t * t * t; + } + function d3_ease_cubicInOut(t) { + if (t <= 0) return 0; + if (t >= 1) return 1; + var t2 = t * t, t3 = t2 * t; + return 4 * (t < .5 ? t3 : 3 * (t - t2) + t3 - .75); + } + function d3_ease_poly(e) { + return function(t) { + return Math.pow(t, e); + }; + } + function d3_ease_sin(t) { + return 1 - Math.cos(t * halfπ); + } + function d3_ease_exp(t) { + return Math.pow(2, 10 * (t - 1)); + } + function d3_ease_circle(t) { + return 1 - Math.sqrt(1 - t * t); + } + function d3_ease_elastic(a, p) { + var s; + if (arguments.length < 2) p = .45; + if (arguments.length) s = p / τ * Math.asin(1 / a); else a = 1, s = p / 4; + return function(t) { + return 1 + a * Math.pow(2, -10 * t) * Math.sin((t - s) * τ / p); + }; + } + function d3_ease_back(s) { + if (!s) s = 1.70158; + return function(t) { + return t * t * ((s + 1) * t - s); + }; + } + function d3_ease_bounce(t) { + return t < 1 / 2.75 ? 7.5625 * t * t : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75 : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375 : 7.5625 * (t -= 2.625 / 2.75) * t + .984375; + } + d3.interpolateHcl = d3_interpolateHcl; + function d3_interpolateHcl(a, b) { + a = d3.hcl(a); + b = d3.hcl(b); + var ah = a.h, ac = a.c, al = a.l, bh = b.h - ah, bc = b.c - ac, bl = b.l - al; + if (isNaN(bc)) bc = 0, ac = isNaN(ac) ? b.c : ac; + if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah; else if (bh > 180) bh -= 360; else if (bh < -180) bh += 360; + return function(t) { + return d3_hcl_lab(ah + bh * t, ac + bc * t, al + bl * t) + ""; + }; + } + d3.interpolateHsl = d3_interpolateHsl; + function d3_interpolateHsl(a, b) { + a = d3.hsl(a); + b = d3.hsl(b); + var ah = a.h, as = a.s, al = a.l, bh = b.h - ah, bs = b.s - as, bl = b.l - al; + if (isNaN(bs)) bs = 0, as = isNaN(as) ? b.s : as; + if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah; else if (bh > 180) bh -= 360; else if (bh < -180) bh += 360; + return function(t) { + return d3_hsl_rgb(ah + bh * t, as + bs * t, al + bl * t) + ""; + }; + } + d3.interpolateLab = d3_interpolateLab; + function d3_interpolateLab(a, b) { + a = d3.lab(a); + b = d3.lab(b); + var al = a.l, aa = a.a, ab = a.b, bl = b.l - al, ba = b.a - aa, bb = b.b - ab; + return function(t) { + return d3_lab_rgb(al + bl * t, aa + ba * t, ab + bb * t) + ""; + }; + } + d3.interpolateRound = d3_interpolateRound; + function d3_interpolateRound(a, b) { + b -= a; + return function(t) { + return Math.round(a + b * t); + }; + } + d3.transform = function(string) { + var g = d3_document.createElementNS(d3.ns.prefix.svg, "g"); + return (d3.transform = function(string) { + if (string != null) { + g.setAttribute("transform", string); + var t = g.transform.baseVal.consolidate(); + } + return new d3_transform(t ? t.matrix : d3_transformIdentity); + })(string); + }; + function d3_transform(m) { + var r0 = [ m.a, m.b ], r1 = [ m.c, m.d ], kx = d3_transformNormalize(r0), kz = d3_transformDot(r0, r1), ky = d3_transformNormalize(d3_transformCombine(r1, r0, -kz)) || 0; + if (r0[0] * r1[1] < r1[0] * r0[1]) { + r0[0] *= -1; + r0[1] *= -1; + kx *= -1; + kz *= -1; + } + this.rotate = (kx ? Math.atan2(r0[1], r0[0]) : Math.atan2(-r1[0], r1[1])) * d3_degrees; + this.translate = [ m.e, m.f ]; + this.scale = [ kx, ky ]; + this.skew = ky ? Math.atan2(kz, ky) * d3_degrees : 0; + } + d3_transform.prototype.toString = function() { + return "translate(" + this.translate + ")rotate(" + this.rotate + ")skewX(" + this.skew + ")scale(" + this.scale + ")"; + }; + function d3_transformDot(a, b) { + return a[0] * b[0] + a[1] * b[1]; + } + function d3_transformNormalize(a) { + var k = Math.sqrt(d3_transformDot(a, a)); + if (k) { + a[0] /= k; + a[1] /= k; + } + return k; + } + function d3_transformCombine(a, b, k) { + a[0] += k * b[0]; + a[1] += k * b[1]; + return a; + } + var d3_transformIdentity = { + a: 1, + b: 0, + c: 0, + d: 1, + e: 0, + f: 0 + }; + d3.interpolateTransform = d3_interpolateTransform; + function d3_interpolateTransformPop(s) { + return s.length ? s.pop() + "," : ""; + } + function d3_interpolateTranslate(ta, tb, s, q) { + if (ta[0] !== tb[0] || ta[1] !== tb[1]) { + var i = s.push("translate(", null, ",", null, ")"); + q.push({ + i: i - 4, + x: d3_interpolateNumber(ta[0], tb[0]) + }, { + i: i - 2, + x: d3_interpolateNumber(ta[1], tb[1]) + }); + } else if (tb[0] || tb[1]) { + s.push("translate(" + tb + ")"); + } + } + function d3_interpolateRotate(ra, rb, s, q) { + if (ra !== rb) { + if (ra - rb > 180) rb += 360; else if (rb - ra > 180) ra += 360; + q.push({ + i: s.push(d3_interpolateTransformPop(s) + "rotate(", null, ")") - 2, + x: d3_interpolateNumber(ra, rb) + }); + } else if (rb) { + s.push(d3_interpolateTransformPop(s) + "rotate(" + rb + ")"); + } + } + function d3_interpolateSkew(wa, wb, s, q) { + if (wa !== wb) { + q.push({ + i: s.push(d3_interpolateTransformPop(s) + "skewX(", null, ")") - 2, + x: d3_interpolateNumber(wa, wb) + }); + } else if (wb) { + s.push(d3_interpolateTransformPop(s) + "skewX(" + wb + ")"); + } + } + function d3_interpolateScale(ka, kb, s, q) { + if (ka[0] !== kb[0] || ka[1] !== kb[1]) { + var i = s.push(d3_interpolateTransformPop(s) + "scale(", null, ",", null, ")"); + q.push({ + i: i - 4, + x: d3_interpolateNumber(ka[0], kb[0]) + }, { + i: i - 2, + x: d3_interpolateNumber(ka[1], kb[1]) + }); + } else if (kb[0] !== 1 || kb[1] !== 1) { + s.push(d3_interpolateTransformPop(s) + "scale(" + kb + ")"); + } + } + function d3_interpolateTransform(a, b) { + var s = [], q = []; + a = d3.transform(a), b = d3.transform(b); + d3_interpolateTranslate(a.translate, b.translate, s, q); + d3_interpolateRotate(a.rotate, b.rotate, s, q); + d3_interpolateSkew(a.skew, b.skew, s, q); + d3_interpolateScale(a.scale, b.scale, s, q); + a = b = null; + return function(t) { + var i = -1, n = q.length, o; + while (++i < n) s[(o = q[i]).i] = o.x(t); + return s.join(""); + }; + } + function d3_uninterpolateNumber(a, b) { + b = (b -= a = +a) || 1 / b; + return function(x) { + return (x - a) / b; + }; + } + function d3_uninterpolateClamp(a, b) { + b = (b -= a = +a) || 1 / b; + return function(x) { + return Math.max(0, Math.min(1, (x - a) / b)); + }; + } + d3.layout = {}; + d3.layout.bundle = function() { + return function(links) { + var paths = [], i = -1, n = links.length; + while (++i < n) paths.push(d3_layout_bundlePath(links[i])); + return paths; + }; + }; + function d3_layout_bundlePath(link) { + var start = link.source, end = link.target, lca = d3_layout_bundleLeastCommonAncestor(start, end), points = [ start ]; + while (start !== lca) { + start = start.parent; + points.push(start); + } + var k = points.length; + while (end !== lca) { + points.splice(k, 0, end); + end = end.parent; + } + return points; + } + function d3_layout_bundleAncestors(node) { + var ancestors = [], parent = node.parent; + while (parent != null) { + ancestors.push(node); + node = parent; + parent = parent.parent; + } + ancestors.push(node); + return ancestors; + } + function d3_layout_bundleLeastCommonAncestor(a, b) { + if (a === b) return a; + var aNodes = d3_layout_bundleAncestors(a), bNodes = d3_layout_bundleAncestors(b), aNode = aNodes.pop(), bNode = bNodes.pop(), sharedNode = null; + while (aNode === bNode) { + sharedNode = aNode; + aNode = aNodes.pop(); + bNode = bNodes.pop(); + } + return sharedNode; + } + d3.layout.chord = function() { + var chord = {}, chords, groups, matrix, n, padding = 0, sortGroups, sortSubgroups, sortChords; + function relayout() { + var subgroups = {}, groupSums = [], groupIndex = d3.range(n), subgroupIndex = [], k, x, x0, i, j; + chords = []; + groups = []; + k = 0, i = -1; + while (++i < n) { + x = 0, j = -1; + while (++j < n) { + x += matrix[i][j]; + } + groupSums.push(x); + subgroupIndex.push(d3.range(n)); + k += x; + } + if (sortGroups) { + groupIndex.sort(function(a, b) { + return sortGroups(groupSums[a], groupSums[b]); + }); + } + if (sortSubgroups) { + subgroupIndex.forEach(function(d, i) { + d.sort(function(a, b) { + return sortSubgroups(matrix[i][a], matrix[i][b]); + }); + }); + } + k = (τ - padding * n) / k; + x = 0, i = -1; + while (++i < n) { + x0 = x, j = -1; + while (++j < n) { + var di = groupIndex[i], dj = subgroupIndex[di][j], v = matrix[di][dj], a0 = x, a1 = x += v * k; + subgroups[di + "-" + dj] = { + index: di, + subindex: dj, + startAngle: a0, + endAngle: a1, + value: v + }; + } + groups[di] = { + index: di, + startAngle: x0, + endAngle: x, + value: groupSums[di] + }; + x += padding; + } + i = -1; + while (++i < n) { + j = i - 1; + while (++j < n) { + var source = subgroups[i + "-" + j], target = subgroups[j + "-" + i]; + if (source.value || target.value) { + chords.push(source.value < target.value ? { + source: target, + target: source + } : { + source: source, + target: target + }); + } + } + } + if (sortChords) resort(); + } + function resort() { + chords.sort(function(a, b) { + return sortChords((a.source.value + a.target.value) / 2, (b.source.value + b.target.value) / 2); + }); + } + chord.matrix = function(x) { + if (!arguments.length) return matrix; + n = (matrix = x) && matrix.length; + chords = groups = null; + return chord; + }; + chord.padding = function(x) { + if (!arguments.length) return padding; + padding = x; + chords = groups = null; + return chord; + }; + chord.sortGroups = function(x) { + if (!arguments.length) return sortGroups; + sortGroups = x; + chords = groups = null; + return chord; + }; + chord.sortSubgroups = function(x) { + if (!arguments.length) return sortSubgroups; + sortSubgroups = x; + chords = null; + return chord; + }; + chord.sortChords = function(x) { + if (!arguments.length) return sortChords; + sortChords = x; + if (chords) resort(); + return chord; + }; + chord.chords = function() { + if (!chords) relayout(); + return chords; + }; + chord.groups = function() { + if (!groups) relayout(); + return groups; + }; + return chord; + }; + d3.layout.force = function() { + var force = {}, event = d3.dispatch("start", "tick", "end"), timer, size = [ 1, 1 ], drag, alpha, friction = .9, linkDistance = d3_layout_forceLinkDistance, linkStrength = d3_layout_forceLinkStrength, charge = -30, chargeDistance2 = d3_layout_forceChargeDistance2, gravity = .1, theta2 = .64, nodes = [], links = [], distances, strengths, charges; + function repulse(node) { + return function(quad, x1, _, x2) { + if (quad.point !== node) { + var dx = quad.cx - node.x, dy = quad.cy - node.y, dw = x2 - x1, dn = dx * dx + dy * dy; + if (dw * dw / theta2 < dn) { + if (dn < chargeDistance2) { + var k = quad.charge / dn; + node.px -= dx * k; + node.py -= dy * k; + } + return true; + } + if (quad.point && dn && dn < chargeDistance2) { + var k = quad.pointCharge / dn; + node.px -= dx * k; + node.py -= dy * k; + } + } + return !quad.charge; + }; + } + force.tick = function() { + if ((alpha *= .99) < .005) { + timer = null; + event.end({ + type: "end", + alpha: alpha = 0 + }); + return true; + } + var n = nodes.length, m = links.length, q, i, o, s, t, l, k, x, y; + for (i = 0; i < m; ++i) { + o = links[i]; + s = o.source; + t = o.target; + x = t.x - s.x; + y = t.y - s.y; + if (l = x * x + y * y) { + l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l; + x *= l; + y *= l; + t.x -= x * (k = s.weight + t.weight ? s.weight / (s.weight + t.weight) : .5); + t.y -= y * k; + s.x += x * (k = 1 - k); + s.y += y * k; + } + } + if (k = alpha * gravity) { + x = size[0] / 2; + y = size[1] / 2; + i = -1; + if (k) while (++i < n) { + o = nodes[i]; + o.x += (x - o.x) * k; + o.y += (y - o.y) * k; + } + } + if (charge) { + d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges); + i = -1; + while (++i < n) { + if (!(o = nodes[i]).fixed) { + q.visit(repulse(o)); + } + } + } + i = -1; + while (++i < n) { + o = nodes[i]; + if (o.fixed) { + o.x = o.px; + o.y = o.py; + } else { + o.x -= (o.px - (o.px = o.x)) * friction; + o.y -= (o.py - (o.py = o.y)) * friction; + } + } + event.tick({ + type: "tick", + alpha: alpha + }); + }; + force.nodes = function(x) { + if (!arguments.length) return nodes; + nodes = x; + return force; + }; + force.links = function(x) { + if (!arguments.length) return links; + links = x; + return force; + }; + force.size = function(x) { + if (!arguments.length) return size; + size = x; + return force; + }; + force.linkDistance = function(x) { + if (!arguments.length) return linkDistance; + linkDistance = typeof x === "function" ? x : +x; + return force; + }; + force.distance = force.linkDistance; + force.linkStrength = function(x) { + if (!arguments.length) return linkStrength; + linkStrength = typeof x === "function" ? x : +x; + return force; + }; + force.friction = function(x) { + if (!arguments.length) return friction; + friction = +x; + return force; + }; + force.charge = function(x) { + if (!arguments.length) return charge; + charge = typeof x === "function" ? x : +x; + return force; + }; + force.chargeDistance = function(x) { + if (!arguments.length) return Math.sqrt(chargeDistance2); + chargeDistance2 = x * x; + return force; + }; + force.gravity = function(x) { + if (!arguments.length) return gravity; + gravity = +x; + return force; + }; + force.theta = function(x) { + if (!arguments.length) return Math.sqrt(theta2); + theta2 = x * x; + return force; + }; + force.alpha = function(x) { + if (!arguments.length) return alpha; + x = +x; + if (alpha) { + if (x > 0) { + alpha = x; + } else { + timer.c = null, timer.t = NaN, timer = null; + event.end({ + type: "end", + alpha: alpha = 0 + }); + } + } else if (x > 0) { + event.start({ + type: "start", + alpha: alpha = x + }); + timer = d3_timer(force.tick); + } + return force; + }; + force.start = function() { + var i, n = nodes.length, m = links.length, w = size[0], h = size[1], neighbors, o; + for (i = 0; i < n; ++i) { + (o = nodes[i]).index = i; + o.weight = 0; + } + for (i = 0; i < m; ++i) { + o = links[i]; + if (typeof o.source == "number") o.source = nodes[o.source]; + if (typeof o.target == "number") o.target = nodes[o.target]; + ++o.source.weight; + ++o.target.weight; + } + for (i = 0; i < n; ++i) { + o = nodes[i]; + if (isNaN(o.x)) o.x = position("x", w); + if (isNaN(o.y)) o.y = position("y", h); + if (isNaN(o.px)) o.px = o.x; + if (isNaN(o.py)) o.py = o.y; + } + distances = []; + if (typeof linkDistance === "function") for (i = 0; i < m; ++i) distances[i] = +linkDistance.call(this, links[i], i); else for (i = 0; i < m; ++i) distances[i] = linkDistance; + strengths = []; + if (typeof linkStrength === "function") for (i = 0; i < m; ++i) strengths[i] = +linkStrength.call(this, links[i], i); else for (i = 0; i < m; ++i) strengths[i] = linkStrength; + charges = []; + if (typeof charge === "function") for (i = 0; i < n; ++i) charges[i] = +charge.call(this, nodes[i], i); else for (i = 0; i < n; ++i) charges[i] = charge; + function position(dimension, size) { + if (!neighbors) { + neighbors = new Array(n); + for (j = 0; j < n; ++j) { + neighbors[j] = []; + } + for (j = 0; j < m; ++j) { + var o = links[j]; + neighbors[o.source.index].push(o.target); + neighbors[o.target.index].push(o.source); + } + } + var candidates = neighbors[i], j = -1, l = candidates.length, x; + while (++j < l) if (!isNaN(x = candidates[j][dimension])) return x; + return Math.random() * size; + } + return force.resume(); + }; + force.resume = function() { + return force.alpha(.1); + }; + force.stop = function() { + return force.alpha(0); + }; + force.drag = function() { + if (!drag) drag = d3.behavior.drag().origin(d3_identity).on("dragstart.force", d3_layout_forceDragstart).on("drag.force", dragmove).on("dragend.force", d3_layout_forceDragend); + if (!arguments.length) return drag; + this.on("mouseover.force", d3_layout_forceMouseover).on("mouseout.force", d3_layout_forceMouseout).call(drag); + }; + function dragmove(d) { + d.px = d3.event.x, d.py = d3.event.y; + force.resume(); + } + return d3.rebind(force, event, "on"); + }; + function d3_layout_forceDragstart(d) { + d.fixed |= 2; + } + function d3_layout_forceDragend(d) { + d.fixed &= ~6; + } + function d3_layout_forceMouseover(d) { + d.fixed |= 4; + d.px = d.x, d.py = d.y; + } + function d3_layout_forceMouseout(d) { + d.fixed &= ~4; + } + function d3_layout_forceAccumulate(quad, alpha, charges) { + var cx = 0, cy = 0; + quad.charge = 0; + if (!quad.leaf) { + var nodes = quad.nodes, n = nodes.length, i = -1, c; + while (++i < n) { + c = nodes[i]; + if (c == null) continue; + d3_layout_forceAccumulate(c, alpha, charges); + quad.charge += c.charge; + cx += c.charge * c.cx; + cy += c.charge * c.cy; + } + } + if (quad.point) { + if (!quad.leaf) { + quad.point.x += Math.random() - .5; + quad.point.y += Math.random() - .5; + } + var k = alpha * charges[quad.point.index]; + quad.charge += quad.pointCharge = k; + cx += k * quad.point.x; + cy += k * quad.point.y; + } + quad.cx = cx / quad.charge; + quad.cy = cy / quad.charge; + } + var d3_layout_forceLinkDistance = 20, d3_layout_forceLinkStrength = 1, d3_layout_forceChargeDistance2 = Infinity; + d3.layout.hierarchy = function() { + var sort = d3_layout_hierarchySort, children = d3_layout_hierarchyChildren, value = d3_layout_hierarchyValue; + function hierarchy(root) { + var stack = [ root ], nodes = [], node; + root.depth = 0; + while ((node = stack.pop()) != null) { + nodes.push(node); + if ((childs = children.call(hierarchy, node, node.depth)) && (n = childs.length)) { + var n, childs, child; + while (--n >= 0) { + stack.push(child = childs[n]); + child.parent = node; + child.depth = node.depth + 1; + } + if (value) node.value = 0; + node.children = childs; + } else { + if (value) node.value = +value.call(hierarchy, node, node.depth) || 0; + delete node.children; + } + } + d3_layout_hierarchyVisitAfter(root, function(node) { + var childs, parent; + if (sort && (childs = node.children)) childs.sort(sort); + if (value && (parent = node.parent)) parent.value += node.value; + }); + return nodes; + } + hierarchy.sort = function(x) { + if (!arguments.length) return sort; + sort = x; + return hierarchy; + }; + hierarchy.children = function(x) { + if (!arguments.length) return children; + children = x; + return hierarchy; + }; + hierarchy.value = function(x) { + if (!arguments.length) return value; + value = x; + return hierarchy; + }; + hierarchy.revalue = function(root) { + if (value) { + d3_layout_hierarchyVisitBefore(root, function(node) { + if (node.children) node.value = 0; + }); + d3_layout_hierarchyVisitAfter(root, function(node) { + var parent; + if (!node.children) node.value = +value.call(hierarchy, node, node.depth) || 0; + if (parent = node.parent) parent.value += node.value; + }); + } + return root; + }; + return hierarchy; + }; + function d3_layout_hierarchyRebind(object, hierarchy) { + d3.rebind(object, hierarchy, "sort", "children", "value"); + object.nodes = object; + object.links = d3_layout_hierarchyLinks; + return object; + } + function d3_layout_hierarchyVisitBefore(node, callback) { + var nodes = [ node ]; + while ((node = nodes.pop()) != null) { + callback(node); + if ((children = node.children) && (n = children.length)) { + var n, children; + while (--n >= 0) nodes.push(children[n]); + } + } + } + function d3_layout_hierarchyVisitAfter(node, callback) { + var nodes = [ node ], nodes2 = []; + while ((node = nodes.pop()) != null) { + nodes2.push(node); + if ((children = node.children) && (n = children.length)) { + var i = -1, n, children; + while (++i < n) nodes.push(children[i]); + } + } + while ((node = nodes2.pop()) != null) { + callback(node); + } + } + function d3_layout_hierarchyChildren(d) { + return d.children; + } + function d3_layout_hierarchyValue(d) { + return d.value; + } + function d3_layout_hierarchySort(a, b) { + return b.value - a.value; + } + function d3_layout_hierarchyLinks(nodes) { + return d3.merge(nodes.map(function(parent) { + return (parent.children || []).map(function(child) { + return { + source: parent, + target: child + }; + }); + })); + } + d3.layout.partition = function() { + var hierarchy = d3.layout.hierarchy(), size = [ 1, 1 ]; + function position(node, x, dx, dy) { + var children = node.children; + node.x = x; + node.y = node.depth * dy; + node.dx = dx; + node.dy = dy; + if (children && (n = children.length)) { + var i = -1, n, c, d; + dx = node.value ? dx / node.value : 0; + while (++i < n) { + position(c = children[i], x, d = c.value * dx, dy); + x += d; + } + } + } + function depth(node) { + var children = node.children, d = 0; + if (children && (n = children.length)) { + var i = -1, n; + while (++i < n) d = Math.max(d, depth(children[i])); + } + return 1 + d; + } + function partition(d, i) { + var nodes = hierarchy.call(this, d, i); + position(nodes[0], 0, size[0], size[1] / depth(nodes[0])); + return nodes; + } + partition.size = function(x) { + if (!arguments.length) return size; + size = x; + return partition; + }; + return d3_layout_hierarchyRebind(partition, hierarchy); + }; + d3.layout.pie = function() { + var value = Number, sort = d3_layout_pieSortByValue, startAngle = 0, endAngle = τ, padAngle = 0; + function pie(data) { + var n = data.length, values = data.map(function(d, i) { + return +value.call(pie, d, i); + }), a = +(typeof startAngle === "function" ? startAngle.apply(this, arguments) : startAngle), da = (typeof endAngle === "function" ? endAngle.apply(this, arguments) : endAngle) - a, p = Math.min(Math.abs(da) / n, +(typeof padAngle === "function" ? padAngle.apply(this, arguments) : padAngle)), pa = p * (da < 0 ? -1 : 1), sum = d3.sum(values), k = sum ? (da - n * pa) / sum : 0, index = d3.range(n), arcs = [], v; + if (sort != null) index.sort(sort === d3_layout_pieSortByValue ? function(i, j) { + return values[j] - values[i]; + } : function(i, j) { + return sort(data[i], data[j]); + }); + index.forEach(function(i) { + arcs[i] = { + data: data[i], + value: v = values[i], + startAngle: a, + endAngle: a += v * k + pa, + padAngle: p + }; + }); + return arcs; + } + pie.value = function(_) { + if (!arguments.length) return value; + value = _; + return pie; + }; + pie.sort = function(_) { + if (!arguments.length) return sort; + sort = _; + return pie; + }; + pie.startAngle = function(_) { + if (!arguments.length) return startAngle; + startAngle = _; + return pie; + }; + pie.endAngle = function(_) { + if (!arguments.length) return endAngle; + endAngle = _; + return pie; + }; + pie.padAngle = function(_) { + if (!arguments.length) return padAngle; + padAngle = _; + return pie; + }; + return pie; + }; + var d3_layout_pieSortByValue = {}; + d3.layout.stack = function() { + var values = d3_identity, order = d3_layout_stackOrderDefault, offset = d3_layout_stackOffsetZero, out = d3_layout_stackOut, x = d3_layout_stackX, y = d3_layout_stackY; + function stack(data, index) { + if (!(n = data.length)) return data; + var series = data.map(function(d, i) { + return values.call(stack, d, i); + }); + var points = series.map(function(d) { + return d.map(function(v, i) { + return [ x.call(stack, v, i), y.call(stack, v, i) ]; + }); + }); + var orders = order.call(stack, points, index); + series = d3.permute(series, orders); + points = d3.permute(points, orders); + var offsets = offset.call(stack, points, index); + var m = series[0].length, n, i, j, o; + for (j = 0; j < m; ++j) { + out.call(stack, series[0][j], o = offsets[j], points[0][j][1]); + for (i = 1; i < n; ++i) { + out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]); + } + } + return data; + } + stack.values = function(x) { + if (!arguments.length) return values; + values = x; + return stack; + }; + stack.order = function(x) { + if (!arguments.length) return order; + order = typeof x === "function" ? x : d3_layout_stackOrders.get(x) || d3_layout_stackOrderDefault; + return stack; + }; + stack.offset = function(x) { + if (!arguments.length) return offset; + offset = typeof x === "function" ? x : d3_layout_stackOffsets.get(x) || d3_layout_stackOffsetZero; + return stack; + }; + stack.x = function(z) { + if (!arguments.length) return x; + x = z; + return stack; + }; + stack.y = function(z) { + if (!arguments.length) return y; + y = z; + return stack; + }; + stack.out = function(z) { + if (!arguments.length) return out; + out = z; + return stack; + }; + return stack; + }; + function d3_layout_stackX(d) { + return d.x; + } + function d3_layout_stackY(d) { + return d.y; + } + function d3_layout_stackOut(d, y0, y) { + d.y0 = y0; + d.y = y; + } + var d3_layout_stackOrders = d3.map({ + "inside-out": function(data) { + var n = data.length, i, j, max = data.map(d3_layout_stackMaxIndex), sums = data.map(d3_layout_stackReduceSum), index = d3.range(n).sort(function(a, b) { + return max[a] - max[b]; + }), top = 0, bottom = 0, tops = [], bottoms = []; + for (i = 0; i < n; ++i) { + j = index[i]; + if (top < bottom) { + top += sums[j]; + tops.push(j); + } else { + bottom += sums[j]; + bottoms.push(j); + } + } + return bottoms.reverse().concat(tops); + }, + reverse: function(data) { + return d3.range(data.length).reverse(); + }, + "default": d3_layout_stackOrderDefault + }); + var d3_layout_stackOffsets = d3.map({ + silhouette: function(data) { + var n = data.length, m = data[0].length, sums = [], max = 0, i, j, o, y0 = []; + for (j = 0; j < m; ++j) { + for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; + if (o > max) max = o; + sums.push(o); + } + for (j = 0; j < m; ++j) { + y0[j] = (max - sums[j]) / 2; + } + return y0; + }, + wiggle: function(data) { + var n = data.length, x = data[0], m = x.length, i, j, k, s1, s2, s3, dx, o, o0, y0 = []; + y0[0] = o = o0 = 0; + for (j = 1; j < m; ++j) { + for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1]; + for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) { + for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) { + s3 += (data[k][j][1] - data[k][j - 1][1]) / dx; + } + s2 += s3 * data[i][j][1]; + } + y0[j] = o -= s1 ? s2 / s1 * dx : 0; + if (o < o0) o0 = o; + } + for (j = 0; j < m; ++j) y0[j] -= o0; + return y0; + }, + expand: function(data) { + var n = data.length, m = data[0].length, k = 1 / n, i, j, o, y0 = []; + for (j = 0; j < m; ++j) { + for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; + if (o) for (i = 0; i < n; i++) data[i][j][1] /= o; else for (i = 0; i < n; i++) data[i][j][1] = k; + } + for (j = 0; j < m; ++j) y0[j] = 0; + return y0; + }, + zero: d3_layout_stackOffsetZero + }); + function d3_layout_stackOrderDefault(data) { + return d3.range(data.length); + } + function d3_layout_stackOffsetZero(data) { + var j = -1, m = data[0].length, y0 = []; + while (++j < m) y0[j] = 0; + return y0; + } + function d3_layout_stackMaxIndex(array) { + var i = 1, j = 0, v = array[0][1], k, n = array.length; + for (;i < n; ++i) { + if ((k = array[i][1]) > v) { + j = i; + v = k; + } + } + return j; + } + function d3_layout_stackReduceSum(d) { + return d.reduce(d3_layout_stackSum, 0); + } + function d3_layout_stackSum(p, d) { + return p + d[1]; + } + d3.layout.histogram = function() { + var frequency = true, valuer = Number, ranger = d3_layout_histogramRange, binner = d3_layout_histogramBinSturges; + function histogram(data, i) { + var bins = [], values = data.map(valuer, this), range = ranger.call(this, values, i), thresholds = binner.call(this, range, values, i), bin, i = -1, n = values.length, m = thresholds.length - 1, k = frequency ? 1 : 1 / n, x; + while (++i < m) { + bin = bins[i] = []; + bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]); + bin.y = 0; + } + if (m > 0) { + i = -1; + while (++i < n) { + x = values[i]; + if (x >= range[0] && x <= range[1]) { + bin = bins[d3.bisect(thresholds, x, 1, m) - 1]; + bin.y += k; + bin.push(data[i]); + } + } + } + return bins; + } + histogram.value = function(x) { + if (!arguments.length) return valuer; + valuer = x; + return histogram; + }; + histogram.range = function(x) { + if (!arguments.length) return ranger; + ranger = d3_functor(x); + return histogram; + }; + histogram.bins = function(x) { + if (!arguments.length) return binner; + binner = typeof x === "number" ? function(range) { + return d3_layout_histogramBinFixed(range, x); + } : d3_functor(x); + return histogram; + }; + histogram.frequency = function(x) { + if (!arguments.length) return frequency; + frequency = !!x; + return histogram; + }; + return histogram; + }; + function d3_layout_histogramBinSturges(range, values) { + return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1)); + } + function d3_layout_histogramBinFixed(range, n) { + var x = -1, b = +range[0], m = (range[1] - b) / n, f = []; + while (++x <= n) f[x] = m * x + b; + return f; + } + function d3_layout_histogramRange(values) { + return [ d3.min(values), d3.max(values) ]; + } + d3.layout.pack = function() { + var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), padding = 0, size = [ 1, 1 ], radius; + function pack(d, i) { + var nodes = hierarchy.call(this, d, i), root = nodes[0], w = size[0], h = size[1], r = radius == null ? Math.sqrt : typeof radius === "function" ? radius : function() { + return radius; + }; + root.x = root.y = 0; + d3_layout_hierarchyVisitAfter(root, function(d) { + d.r = +r(d.value); + }); + d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings); + if (padding) { + var dr = padding * (radius ? 1 : Math.max(2 * root.r / w, 2 * root.r / h)) / 2; + d3_layout_hierarchyVisitAfter(root, function(d) { + d.r += dr; + }); + d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings); + d3_layout_hierarchyVisitAfter(root, function(d) { + d.r -= dr; + }); + } + d3_layout_packTransform(root, w / 2, h / 2, radius ? 1 : 1 / Math.max(2 * root.r / w, 2 * root.r / h)); + return nodes; + } + pack.size = function(_) { + if (!arguments.length) return size; + size = _; + return pack; + }; + pack.radius = function(_) { + if (!arguments.length) return radius; + radius = _ == null || typeof _ === "function" ? _ : +_; + return pack; + }; + pack.padding = function(_) { + if (!arguments.length) return padding; + padding = +_; + return pack; + }; + return d3_layout_hierarchyRebind(pack, hierarchy); + }; + function d3_layout_packSort(a, b) { + return a.value - b.value; + } + function d3_layout_packInsert(a, b) { + var c = a._pack_next; + a._pack_next = b; + b._pack_prev = a; + b._pack_next = c; + c._pack_prev = b; + } + function d3_layout_packSplice(a, b) { + a._pack_next = b; + b._pack_prev = a; + } + function d3_layout_packIntersects(a, b) { + var dx = b.x - a.x, dy = b.y - a.y, dr = a.r + b.r; + return .999 * dr * dr > dx * dx + dy * dy; + } + function d3_layout_packSiblings(node) { + if (!(nodes = node.children) || !(n = nodes.length)) return; + var nodes, xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, a, b, c, i, j, k, n; + function bound(node) { + xMin = Math.min(node.x - node.r, xMin); + xMax = Math.max(node.x + node.r, xMax); + yMin = Math.min(node.y - node.r, yMin); + yMax = Math.max(node.y + node.r, yMax); + } + nodes.forEach(d3_layout_packLink); + a = nodes[0]; + a.x = -a.r; + a.y = 0; + bound(a); + if (n > 1) { + b = nodes[1]; + b.x = b.r; + b.y = 0; + bound(b); + if (n > 2) { + c = nodes[2]; + d3_layout_packPlace(a, b, c); + bound(c); + d3_layout_packInsert(a, c); + a._pack_prev = c; + d3_layout_packInsert(c, b); + b = a._pack_next; + for (i = 3; i < n; i++) { + d3_layout_packPlace(a, b, c = nodes[i]); + var isect = 0, s1 = 1, s2 = 1; + for (j = b._pack_next; j !== b; j = j._pack_next, s1++) { + if (d3_layout_packIntersects(j, c)) { + isect = 1; + break; + } + } + if (isect == 1) { + for (k = a._pack_prev; k !== j._pack_prev; k = k._pack_prev, s2++) { + if (d3_layout_packIntersects(k, c)) { + break; + } + } + } + if (isect) { + if (s1 < s2 || s1 == s2 && b.r < a.r) d3_layout_packSplice(a, b = j); else d3_layout_packSplice(a = k, b); + i--; + } else { + d3_layout_packInsert(a, c); + b = c; + bound(c); + } + } + } + } + var cx = (xMin + xMax) / 2, cy = (yMin + yMax) / 2, cr = 0; + for (i = 0; i < n; i++) { + c = nodes[i]; + c.x -= cx; + c.y -= cy; + cr = Math.max(cr, c.r + Math.sqrt(c.x * c.x + c.y * c.y)); + } + node.r = cr; + nodes.forEach(d3_layout_packUnlink); + } + function d3_layout_packLink(node) { + node._pack_next = node._pack_prev = node; + } + function d3_layout_packUnlink(node) { + delete node._pack_next; + delete node._pack_prev; + } + function d3_layout_packTransform(node, x, y, k) { + var children = node.children; + node.x = x += k * node.x; + node.y = y += k * node.y; + node.r *= k; + if (children) { + var i = -1, n = children.length; + while (++i < n) d3_layout_packTransform(children[i], x, y, k); + } + } + function d3_layout_packPlace(a, b, c) { + var db = a.r + c.r, dx = b.x - a.x, dy = b.y - a.y; + if (db && (dx || dy)) { + var da = b.r + c.r, dc = dx * dx + dy * dy; + da *= da; + db *= db; + var x = .5 + (db - da) / (2 * dc), y = Math.sqrt(Math.max(0, 2 * da * (db + dc) - (db -= dc) * db - da * da)) / (2 * dc); + c.x = a.x + x * dx + y * dy; + c.y = a.y + x * dy - y * dx; + } else { + c.x = a.x + db; + c.y = a.y; + } + } + d3.layout.tree = function() { + var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = null; + function tree(d, i) { + var nodes = hierarchy.call(this, d, i), root0 = nodes[0], root1 = wrapTree(root0); + d3_layout_hierarchyVisitAfter(root1, firstWalk), root1.parent.m = -root1.z; + d3_layout_hierarchyVisitBefore(root1, secondWalk); + if (nodeSize) d3_layout_hierarchyVisitBefore(root0, sizeNode); else { + var left = root0, right = root0, bottom = root0; + d3_layout_hierarchyVisitBefore(root0, function(node) { + if (node.x < left.x) left = node; + if (node.x > right.x) right = node; + if (node.depth > bottom.depth) bottom = node; + }); + var tx = separation(left, right) / 2 - left.x, kx = size[0] / (right.x + separation(right, left) / 2 + tx), ky = size[1] / (bottom.depth || 1); + d3_layout_hierarchyVisitBefore(root0, function(node) { + node.x = (node.x + tx) * kx; + node.y = node.depth * ky; + }); + } + return nodes; + } + function wrapTree(root0) { + var root1 = { + A: null, + children: [ root0 ] + }, queue = [ root1 ], node1; + while ((node1 = queue.pop()) != null) { + for (var children = node1.children, child, i = 0, n = children.length; i < n; ++i) { + queue.push((children[i] = child = { + _: children[i], + parent: node1, + children: (child = children[i].children) && child.slice() || [], + A: null, + a: null, + z: 0, + m: 0, + c: 0, + s: 0, + t: null, + i: i + }).a = child); + } + } + return root1.children[0]; + } + function firstWalk(v) { + var children = v.children, siblings = v.parent.children, w = v.i ? siblings[v.i - 1] : null; + if (children.length) { + d3_layout_treeShift(v); + var midpoint = (children[0].z + children[children.length - 1].z) / 2; + if (w) { + v.z = w.z + separation(v._, w._); + v.m = v.z - midpoint; + } else { + v.z = midpoint; + } + } else if (w) { + v.z = w.z + separation(v._, w._); + } + v.parent.A = apportion(v, w, v.parent.A || siblings[0]); + } + function secondWalk(v) { + v._.x = v.z + v.parent.m; + v.m += v.parent.m; + } + function apportion(v, w, ancestor) { + if (w) { + var vip = v, vop = v, vim = w, vom = vip.parent.children[0], sip = vip.m, sop = vop.m, sim = vim.m, som = vom.m, shift; + while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) { + vom = d3_layout_treeLeft(vom); + vop = d3_layout_treeRight(vop); + vop.a = v; + shift = vim.z + sim - vip.z - sip + separation(vim._, vip._); + if (shift > 0) { + d3_layout_treeMove(d3_layout_treeAncestor(vim, v, ancestor), v, shift); + sip += shift; + sop += shift; + } + sim += vim.m; + sip += vip.m; + som += vom.m; + sop += vop.m; + } + if (vim && !d3_layout_treeRight(vop)) { + vop.t = vim; + vop.m += sim - sop; + } + if (vip && !d3_layout_treeLeft(vom)) { + vom.t = vip; + vom.m += sip - som; + ancestor = v; + } + } + return ancestor; + } + function sizeNode(node) { + node.x *= size[0]; + node.y = node.depth * size[1]; + } + tree.separation = function(x) { + if (!arguments.length) return separation; + separation = x; + return tree; + }; + tree.size = function(x) { + if (!arguments.length) return nodeSize ? null : size; + nodeSize = (size = x) == null ? sizeNode : null; + return tree; + }; + tree.nodeSize = function(x) { + if (!arguments.length) return nodeSize ? size : null; + nodeSize = (size = x) == null ? null : sizeNode; + return tree; + }; + return d3_layout_hierarchyRebind(tree, hierarchy); + }; + function d3_layout_treeSeparation(a, b) { + return a.parent == b.parent ? 1 : 2; + } + function d3_layout_treeLeft(v) { + var children = v.children; + return children.length ? children[0] : v.t; + } + function d3_layout_treeRight(v) { + var children = v.children, n; + return (n = children.length) ? children[n - 1] : v.t; + } + function d3_layout_treeMove(wm, wp, shift) { + var change = shift / (wp.i - wm.i); + wp.c -= change; + wp.s += shift; + wm.c += change; + wp.z += shift; + wp.m += shift; + } + function d3_layout_treeShift(v) { + var shift = 0, change = 0, children = v.children, i = children.length, w; + while (--i >= 0) { + w = children[i]; + w.z += shift; + w.m += shift; + shift += w.s + (change += w.c); + } + } + function d3_layout_treeAncestor(vim, v, ancestor) { + return vim.a.parent === v.parent ? vim.a : ancestor; + } + d3.layout.cluster = function() { + var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = false; + function cluster(d, i) { + var nodes = hierarchy.call(this, d, i), root = nodes[0], previousNode, x = 0; + d3_layout_hierarchyVisitAfter(root, function(node) { + var children = node.children; + if (children && children.length) { + node.x = d3_layout_clusterX(children); + node.y = d3_layout_clusterY(children); + } else { + node.x = previousNode ? x += separation(node, previousNode) : 0; + node.y = 0; + previousNode = node; + } + }); + var left = d3_layout_clusterLeft(root), right = d3_layout_clusterRight(root), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2; + d3_layout_hierarchyVisitAfter(root, nodeSize ? function(node) { + node.x = (node.x - root.x) * size[0]; + node.y = (root.y - node.y) * size[1]; + } : function(node) { + node.x = (node.x - x0) / (x1 - x0) * size[0]; + node.y = (1 - (root.y ? node.y / root.y : 1)) * size[1]; + }); + return nodes; + } + cluster.separation = function(x) { + if (!arguments.length) return separation; + separation = x; + return cluster; + }; + cluster.size = function(x) { + if (!arguments.length) return nodeSize ? null : size; + nodeSize = (size = x) == null; + return cluster; + }; + cluster.nodeSize = function(x) { + if (!arguments.length) return nodeSize ? size : null; + nodeSize = (size = x) != null; + return cluster; + }; + return d3_layout_hierarchyRebind(cluster, hierarchy); + }; + function d3_layout_clusterY(children) { + return 1 + d3.max(children, function(child) { + return child.y; + }); + } + function d3_layout_clusterX(children) { + return children.reduce(function(x, child) { + return x + child.x; + }, 0) / children.length; + } + function d3_layout_clusterLeft(node) { + var children = node.children; + return children && children.length ? d3_layout_clusterLeft(children[0]) : node; + } + function d3_layout_clusterRight(node) { + var children = node.children, n; + return children && (n = children.length) ? d3_layout_clusterRight(children[n - 1]) : node; + } + d3.layout.treemap = function() { + var hierarchy = d3.layout.hierarchy(), round = Math.round, size = [ 1, 1 ], padding = null, pad = d3_layout_treemapPadNull, sticky = false, stickies, mode = "squarify", ratio = .5 * (1 + Math.sqrt(5)); + function scale(children, k) { + var i = -1, n = children.length, child, area; + while (++i < n) { + area = (child = children[i]).value * (k < 0 ? 0 : k); + child.area = isNaN(area) || area <= 0 ? 0 : area; + } + } + function squarify(node) { + var children = node.children; + if (children && children.length) { + var rect = pad(node), row = [], remaining = children.slice(), child, best = Infinity, score, u = mode === "slice" ? rect.dx : mode === "dice" ? rect.dy : mode === "slice-dice" ? node.depth & 1 ? rect.dy : rect.dx : Math.min(rect.dx, rect.dy), n; + scale(remaining, rect.dx * rect.dy / node.value); + row.area = 0; + while ((n = remaining.length) > 0) { + row.push(child = remaining[n - 1]); + row.area += child.area; + if (mode !== "squarify" || (score = worst(row, u)) <= best) { + remaining.pop(); + best = score; + } else { + row.area -= row.pop().area; + position(row, u, rect, false); + u = Math.min(rect.dx, rect.dy); + row.length = row.area = 0; + best = Infinity; + } + } + if (row.length) { + position(row, u, rect, true); + row.length = row.area = 0; + } + children.forEach(squarify); + } + } + function stickify(node) { + var children = node.children; + if (children && children.length) { + var rect = pad(node), remaining = children.slice(), child, row = []; + scale(remaining, rect.dx * rect.dy / node.value); + row.area = 0; + while (child = remaining.pop()) { + row.push(child); + row.area += child.area; + if (child.z != null) { + position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length); + row.length = row.area = 0; + } + } + children.forEach(stickify); + } + } + function worst(row, u) { + var s = row.area, r, rmax = 0, rmin = Infinity, i = -1, n = row.length; + while (++i < n) { + if (!(r = row[i].area)) continue; + if (r < rmin) rmin = r; + if (r > rmax) rmax = r; + } + s *= s; + u *= u; + return s ? Math.max(u * rmax * ratio / s, s / (u * rmin * ratio)) : Infinity; + } + function position(row, u, rect, flush) { + var i = -1, n = row.length, x = rect.x, y = rect.y, v = u ? round(row.area / u) : 0, o; + if (u == rect.dx) { + if (flush || v > rect.dy) v = rect.dy; + while (++i < n) { + o = row[i]; + o.x = x; + o.y = y; + o.dy = v; + x += o.dx = Math.min(rect.x + rect.dx - x, v ? round(o.area / v) : 0); + } + o.z = true; + o.dx += rect.x + rect.dx - x; + rect.y += v; + rect.dy -= v; + } else { + if (flush || v > rect.dx) v = rect.dx; + while (++i < n) { + o = row[i]; + o.x = x; + o.y = y; + o.dx = v; + y += o.dy = Math.min(rect.y + rect.dy - y, v ? round(o.area / v) : 0); + } + o.z = false; + o.dy += rect.y + rect.dy - y; + rect.x += v; + rect.dx -= v; + } + } + function treemap(d) { + var nodes = stickies || hierarchy(d), root = nodes[0]; + root.x = root.y = 0; + if (root.value) root.dx = size[0], root.dy = size[1]; else root.dx = root.dy = 0; + if (stickies) hierarchy.revalue(root); + scale([ root ], root.dx * root.dy / root.value); + (stickies ? stickify : squarify)(root); + if (sticky) stickies = nodes; + return nodes; + } + treemap.size = function(x) { + if (!arguments.length) return size; + size = x; + return treemap; + }; + treemap.padding = function(x) { + if (!arguments.length) return padding; + function padFunction(node) { + var p = x.call(treemap, node, node.depth); + return p == null ? d3_layout_treemapPadNull(node) : d3_layout_treemapPad(node, typeof p === "number" ? [ p, p, p, p ] : p); + } + function padConstant(node) { + return d3_layout_treemapPad(node, x); + } + var type; + pad = (padding = x) == null ? d3_layout_treemapPadNull : (type = typeof x) === "function" ? padFunction : type === "number" ? (x = [ x, x, x, x ], + padConstant) : padConstant; + return treemap; + }; + treemap.round = function(x) { + if (!arguments.length) return round != Number; + round = x ? Math.round : Number; + return treemap; + }; + treemap.sticky = function(x) { + if (!arguments.length) return sticky; + sticky = x; + stickies = null; + return treemap; + }; + treemap.ratio = function(x) { + if (!arguments.length) return ratio; + ratio = x; + return treemap; + }; + treemap.mode = function(x) { + if (!arguments.length) return mode; + mode = x + ""; + return treemap; + }; + return d3_layout_hierarchyRebind(treemap, hierarchy); + }; + function d3_layout_treemapPadNull(node) { + return { + x: node.x, + y: node.y, + dx: node.dx, + dy: node.dy + }; + } + function d3_layout_treemapPad(node, padding) { + var x = node.x + padding[3], y = node.y + padding[0], dx = node.dx - padding[1] - padding[3], dy = node.dy - padding[0] - padding[2]; + if (dx < 0) { + x += dx / 2; + dx = 0; + } + if (dy < 0) { + y += dy / 2; + dy = 0; + } + return { + x: x, + y: y, + dx: dx, + dy: dy + }; + } + d3.random = { + normal: function(µ, σ) { + var n = arguments.length; + if (n < 2) σ = 1; + if (n < 1) µ = 0; + return function() { + var x, y, r; + do { + x = Math.random() * 2 - 1; + y = Math.random() * 2 - 1; + r = x * x + y * y; + } while (!r || r > 1); + return µ + σ * x * Math.sqrt(-2 * Math.log(r) / r); + }; + }, + logNormal: function() { + var random = d3.random.normal.apply(d3, arguments); + return function() { + return Math.exp(random()); + }; + }, + bates: function(m) { + var random = d3.random.irwinHall(m); + return function() { + return random() / m; + }; + }, + irwinHall: function(m) { + return function() { + for (var s = 0, j = 0; j < m; j++) s += Math.random(); + return s; + }; + } + }; + d3.scale = {}; + function d3_scaleExtent(domain) { + var start = domain[0], stop = domain[domain.length - 1]; + return start < stop ? [ start, stop ] : [ stop, start ]; + } + function d3_scaleRange(scale) { + return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range()); + } + function d3_scale_bilinear(domain, range, uninterpolate, interpolate) { + var u = uninterpolate(domain[0], domain[1]), i = interpolate(range[0], range[1]); + return function(x) { + return i(u(x)); + }; + } + function d3_scale_nice(domain, nice) { + var i0 = 0, i1 = domain.length - 1, x0 = domain[i0], x1 = domain[i1], dx; + if (x1 < x0) { + dx = i0, i0 = i1, i1 = dx; + dx = x0, x0 = x1, x1 = dx; + } + domain[i0] = nice.floor(x0); + domain[i1] = nice.ceil(x1); + return domain; + } + function d3_scale_niceStep(step) { + return step ? { + floor: function(x) { + return Math.floor(x / step) * step; + }, + ceil: function(x) { + return Math.ceil(x / step) * step; + } + } : d3_scale_niceIdentity; + } + var d3_scale_niceIdentity = { + floor: d3_identity, + ceil: d3_identity + }; + function d3_scale_polylinear(domain, range, uninterpolate, interpolate) { + var u = [], i = [], j = 0, k = Math.min(domain.length, range.length) - 1; + if (domain[k] < domain[0]) { + domain = domain.slice().reverse(); + range = range.slice().reverse(); + } + while (++j <= k) { + u.push(uninterpolate(domain[j - 1], domain[j])); + i.push(interpolate(range[j - 1], range[j])); + } + return function(x) { + var j = d3.bisect(domain, x, 1, k) - 1; + return i[j](u[j](x)); + }; + } + d3.scale.linear = function() { + return d3_scale_linear([ 0, 1 ], [ 0, 1 ], d3_interpolate, false); + }; + function d3_scale_linear(domain, range, interpolate, clamp) { + var output, input; + function rescale() { + var linear = Math.min(domain.length, range.length) > 2 ? d3_scale_polylinear : d3_scale_bilinear, uninterpolate = clamp ? d3_uninterpolateClamp : d3_uninterpolateNumber; + output = linear(domain, range, uninterpolate, interpolate); + input = linear(range, domain, uninterpolate, d3_interpolate); + return scale; + } + function scale(x) { + return output(x); + } + scale.invert = function(y) { + return input(y); + }; + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = x.map(Number); + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.rangeRound = function(x) { + return scale.range(x).interpolate(d3_interpolateRound); + }; + scale.clamp = function(x) { + if (!arguments.length) return clamp; + clamp = x; + return rescale(); + }; + scale.interpolate = function(x) { + if (!arguments.length) return interpolate; + interpolate = x; + return rescale(); + }; + scale.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + scale.tickFormat = function(m, format) { + return d3_scale_linearTickFormat(domain, m, format); + }; + scale.nice = function(m) { + d3_scale_linearNice(domain, m); + return rescale(); + }; + scale.copy = function() { + return d3_scale_linear(domain, range, interpolate, clamp); + }; + return rescale(); + } + function d3_scale_linearRebind(scale, linear) { + return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp"); + } + function d3_scale_linearNice(domain, m) { + d3_scale_nice(domain, d3_scale_niceStep(d3_scale_linearTickRange(domain, m)[2])); + d3_scale_nice(domain, d3_scale_niceStep(d3_scale_linearTickRange(domain, m)[2])); + return domain; + } + function d3_scale_linearTickRange(domain, m) { + if (m == null) m = 10; + var extent = d3_scaleExtent(domain), span = extent[1] - extent[0], step = Math.pow(10, Math.floor(Math.log(span / m) / Math.LN10)), err = m / span * step; + if (err <= .15) step *= 10; else if (err <= .35) step *= 5; else if (err <= .75) step *= 2; + extent[0] = Math.ceil(extent[0] / step) * step; + extent[1] = Math.floor(extent[1] / step) * step + step * .5; + extent[2] = step; + return extent; + } + function d3_scale_linearTicks(domain, m) { + return d3.range.apply(d3, d3_scale_linearTickRange(domain, m)); + } + function d3_scale_linearTickFormat(domain, m, format) { + var range = d3_scale_linearTickRange(domain, m); + if (format) { + var match = d3_format_re.exec(format); + match.shift(); + if (match[8] === "s") { + var prefix = d3.formatPrefix(Math.max(abs(range[0]), abs(range[1]))); + if (!match[7]) match[7] = "." + d3_scale_linearPrecision(prefix.scale(range[2])); + match[8] = "f"; + format = d3.format(match.join("")); + return function(d) { + return format(prefix.scale(d)) + prefix.symbol; + }; + } + if (!match[7]) match[7] = "." + d3_scale_linearFormatPrecision(match[8], range); + format = match.join(""); + } else { + format = ",." + d3_scale_linearPrecision(range[2]) + "f"; + } + return d3.format(format); + } + var d3_scale_linearFormatSignificant = { + s: 1, + g: 1, + p: 1, + r: 1, + e: 1 + }; + function d3_scale_linearPrecision(value) { + return -Math.floor(Math.log(value) / Math.LN10 + .01); + } + function d3_scale_linearFormatPrecision(type, range) { + var p = d3_scale_linearPrecision(range[2]); + return type in d3_scale_linearFormatSignificant ? Math.abs(p - d3_scale_linearPrecision(Math.max(abs(range[0]), abs(range[1])))) + +(type !== "e") : p - (type === "%") * 2; + } + d3.scale.log = function() { + return d3_scale_log(d3.scale.linear().domain([ 0, 1 ]), 10, true, [ 1, 10 ]); + }; + function d3_scale_log(linear, base, positive, domain) { + function log(x) { + return (positive ? Math.log(x < 0 ? 0 : x) : -Math.log(x > 0 ? 0 : -x)) / Math.log(base); + } + function pow(x) { + return positive ? Math.pow(base, x) : -Math.pow(base, -x); + } + function scale(x) { + return linear(log(x)); + } + scale.invert = function(x) { + return pow(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return domain; + positive = x[0] >= 0; + linear.domain((domain = x.map(Number)).map(log)); + return scale; + }; + scale.base = function(_) { + if (!arguments.length) return base; + base = +_; + linear.domain(domain.map(log)); + return scale; + }; + scale.nice = function() { + var niced = d3_scale_nice(domain.map(log), positive ? Math : d3_scale_logNiceNegative); + linear.domain(niced); + domain = niced.map(pow); + return scale; + }; + scale.ticks = function() { + var extent = d3_scaleExtent(domain), ticks = [], u = extent[0], v = extent[1], i = Math.floor(log(u)), j = Math.ceil(log(v)), n = base % 1 ? 2 : base; + if (isFinite(j - i)) { + if (positive) { + for (;i < j; i++) for (var k = 1; k < n; k++) ticks.push(pow(i) * k); + ticks.push(pow(i)); + } else { + ticks.push(pow(i)); + for (;i++ < j; ) for (var k = n - 1; k > 0; k--) ticks.push(pow(i) * k); + } + for (i = 0; ticks[i] < u; i++) {} + for (j = ticks.length; ticks[j - 1] > v; j--) {} + ticks = ticks.slice(i, j); + } + return ticks; + }; + scale.tickFormat = function(n, format) { + if (!arguments.length) return d3_scale_logFormat; + if (arguments.length < 2) format = d3_scale_logFormat; else if (typeof format !== "function") format = d3.format(format); + var k = Math.max(1, base * n / scale.ticks().length); + return function(d) { + var i = d / pow(Math.round(log(d))); + if (i * base < base - .5) i *= base; + return i <= k ? format(d) : ""; + }; + }; + scale.copy = function() { + return d3_scale_log(linear.copy(), base, positive, domain); + }; + return d3_scale_linearRebind(scale, linear); + } + var d3_scale_logFormat = d3.format(".0e"), d3_scale_logNiceNegative = { + floor: function(x) { + return -Math.ceil(-x); + }, + ceil: function(x) { + return -Math.floor(-x); + } + }; + d3.scale.pow = function() { + return d3_scale_pow(d3.scale.linear(), 1, [ 0, 1 ]); + }; + function d3_scale_pow(linear, exponent, domain) { + var powp = d3_scale_powPow(exponent), powb = d3_scale_powPow(1 / exponent); + function scale(x) { + return linear(powp(x)); + } + scale.invert = function(x) { + return powb(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return domain; + linear.domain((domain = x.map(Number)).map(powp)); + return scale; + }; + scale.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + scale.tickFormat = function(m, format) { + return d3_scale_linearTickFormat(domain, m, format); + }; + scale.nice = function(m) { + return scale.domain(d3_scale_linearNice(domain, m)); + }; + scale.exponent = function(x) { + if (!arguments.length) return exponent; + powp = d3_scale_powPow(exponent = x); + powb = d3_scale_powPow(1 / exponent); + linear.domain(domain.map(powp)); + return scale; + }; + scale.copy = function() { + return d3_scale_pow(linear.copy(), exponent, domain); + }; + return d3_scale_linearRebind(scale, linear); + } + function d3_scale_powPow(e) { + return function(x) { + return x < 0 ? -Math.pow(-x, e) : Math.pow(x, e); + }; + } + d3.scale.sqrt = function() { + return d3.scale.pow().exponent(.5); + }; + d3.scale.ordinal = function() { + return d3_scale_ordinal([], { + t: "range", + a: [ [] ] + }); + }; + function d3_scale_ordinal(domain, ranger) { + var index, range, rangeBand; + function scale(x) { + return range[((index.get(x) || (ranger.t === "range" ? index.set(x, domain.push(x)) : NaN)) - 1) % range.length]; + } + function steps(start, step) { + return d3.range(domain.length).map(function(i) { + return start + step * i; + }); + } + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = []; + index = new d3_Map(); + var i = -1, n = x.length, xi; + while (++i < n) if (!index.has(xi = x[i])) index.set(xi, domain.push(xi)); + return scale[ranger.t].apply(scale, ranger.a); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + rangeBand = 0; + ranger = { + t: "range", + a: arguments + }; + return scale; + }; + scale.rangePoints = function(x, padding) { + if (arguments.length < 2) padding = 0; + var start = x[0], stop = x[1], step = domain.length < 2 ? (start = (start + stop) / 2, + 0) : (stop - start) / (domain.length - 1 + padding); + range = steps(start + step * padding / 2, step); + rangeBand = 0; + ranger = { + t: "rangePoints", + a: arguments + }; + return scale; + }; + scale.rangeRoundPoints = function(x, padding) { + if (arguments.length < 2) padding = 0; + var start = x[0], stop = x[1], step = domain.length < 2 ? (start = stop = Math.round((start + stop) / 2), + 0) : (stop - start) / (domain.length - 1 + padding) | 0; + range = steps(start + Math.round(step * padding / 2 + (stop - start - (domain.length - 1 + padding) * step) / 2), step); + rangeBand = 0; + ranger = { + t: "rangeRoundPoints", + a: arguments + }; + return scale; + }; + scale.rangeBands = function(x, padding, outerPadding) { + if (arguments.length < 2) padding = 0; + if (arguments.length < 3) outerPadding = padding; + var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = (stop - start) / (domain.length - padding + 2 * outerPadding); + range = steps(start + step * outerPadding, step); + if (reverse) range.reverse(); + rangeBand = step * (1 - padding); + ranger = { + t: "rangeBands", + a: arguments + }; + return scale; + }; + scale.rangeRoundBands = function(x, padding, outerPadding) { + if (arguments.length < 2) padding = 0; + if (arguments.length < 3) outerPadding = padding; + var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = Math.floor((stop - start) / (domain.length - padding + 2 * outerPadding)); + range = steps(start + Math.round((stop - start - (domain.length - padding) * step) / 2), step); + if (reverse) range.reverse(); + rangeBand = Math.round(step * (1 - padding)); + ranger = { + t: "rangeRoundBands", + a: arguments + }; + return scale; + }; + scale.rangeBand = function() { + return rangeBand; + }; + scale.rangeExtent = function() { + return d3_scaleExtent(ranger.a[0]); + }; + scale.copy = function() { + return d3_scale_ordinal(domain, ranger); + }; + return scale.domain(domain); + } + d3.scale.category10 = function() { + return d3.scale.ordinal().range(d3_category10); + }; + d3.scale.category20 = function() { + return d3.scale.ordinal().range(d3_category20); + }; + d3.scale.category20b = function() { + return d3.scale.ordinal().range(d3_category20b); + }; + d3.scale.category20c = function() { + return d3.scale.ordinal().range(d3_category20c); + }; + var d3_category10 = [ 2062260, 16744206, 2924588, 14034728, 9725885, 9197131, 14907330, 8355711, 12369186, 1556175 ].map(d3_rgbString); + var d3_category20 = [ 2062260, 11454440, 16744206, 16759672, 2924588, 10018698, 14034728, 16750742, 9725885, 12955861, 9197131, 12885140, 14907330, 16234194, 8355711, 13092807, 12369186, 14408589, 1556175, 10410725 ].map(d3_rgbString); + var d3_category20b = [ 3750777, 5395619, 7040719, 10264286, 6519097, 9216594, 11915115, 13556636, 9202993, 12426809, 15186514, 15190932, 8666169, 11356490, 14049643, 15177372, 8077683, 10834324, 13528509, 14589654 ].map(d3_rgbString); + var d3_category20c = [ 3244733, 7057110, 10406625, 13032431, 15095053, 16616764, 16625259, 16634018, 3253076, 7652470, 10607003, 13101504, 7695281, 10394312, 12369372, 14342891, 6513507, 9868950, 12434877, 14277081 ].map(d3_rgbString); + d3.scale.quantile = function() { + return d3_scale_quantile([], []); + }; + function d3_scale_quantile(domain, range) { + var thresholds; + function rescale() { + var k = 0, q = range.length; + thresholds = []; + while (++k < q) thresholds[k - 1] = d3.quantile(domain, k / q); + return scale; + } + function scale(x) { + if (!isNaN(x = +x)) return range[d3.bisect(thresholds, x)]; + } + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = x.map(d3_number).filter(d3_numeric).sort(d3_ascending); + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.quantiles = function() { + return thresholds; + }; + scale.invertExtent = function(y) { + y = range.indexOf(y); + return y < 0 ? [ NaN, NaN ] : [ y > 0 ? thresholds[y - 1] : domain[0], y < thresholds.length ? thresholds[y] : domain[domain.length - 1] ]; + }; + scale.copy = function() { + return d3_scale_quantile(domain, range); + }; + return rescale(); + } + d3.scale.quantize = function() { + return d3_scale_quantize(0, 1, [ 0, 1 ]); + }; + function d3_scale_quantize(x0, x1, range) { + var kx, i; + function scale(x) { + return range[Math.max(0, Math.min(i, Math.floor(kx * (x - x0))))]; + } + function rescale() { + kx = range.length / (x1 - x0); + i = range.length - 1; + return scale; + } + scale.domain = function(x) { + if (!arguments.length) return [ x0, x1 ]; + x0 = +x[0]; + x1 = +x[x.length - 1]; + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.invertExtent = function(y) { + y = range.indexOf(y); + y = y < 0 ? NaN : y / kx + x0; + return [ y, y + 1 / kx ]; + }; + scale.copy = function() { + return d3_scale_quantize(x0, x1, range); + }; + return rescale(); + } + d3.scale.threshold = function() { + return d3_scale_threshold([ .5 ], [ 0, 1 ]); + }; + function d3_scale_threshold(domain, range) { + function scale(x) { + if (x <= x) return range[d3.bisect(domain, x)]; + } + scale.domain = function(_) { + if (!arguments.length) return domain; + domain = _; + return scale; + }; + scale.range = function(_) { + if (!arguments.length) return range; + range = _; + return scale; + }; + scale.invertExtent = function(y) { + y = range.indexOf(y); + return [ domain[y - 1], domain[y] ]; + }; + scale.copy = function() { + return d3_scale_threshold(domain, range); + }; + return scale; + } + d3.scale.identity = function() { + return d3_scale_identity([ 0, 1 ]); + }; + function d3_scale_identity(domain) { + function identity(x) { + return +x; + } + identity.invert = identity; + identity.domain = identity.range = function(x) { + if (!arguments.length) return domain; + domain = x.map(identity); + return identity; + }; + identity.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + identity.tickFormat = function(m, format) { + return d3_scale_linearTickFormat(domain, m, format); + }; + identity.copy = function() { + return d3_scale_identity(domain); + }; + return identity; + } + d3.svg = {}; + function d3_zero() { + return 0; + } + d3.svg.arc = function() { + var innerRadius = d3_svg_arcInnerRadius, outerRadius = d3_svg_arcOuterRadius, cornerRadius = d3_zero, padRadius = d3_svg_arcAuto, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle, padAngle = d3_svg_arcPadAngle; + function arc() { + var r0 = Math.max(0, +innerRadius.apply(this, arguments)), r1 = Math.max(0, +outerRadius.apply(this, arguments)), a0 = startAngle.apply(this, arguments) - halfπ, a1 = endAngle.apply(this, arguments) - halfπ, da = Math.abs(a1 - a0), cw = a0 > a1 ? 0 : 1; + if (r1 < r0) rc = r1, r1 = r0, r0 = rc; + if (da >= τε) return circleSegment(r1, cw) + (r0 ? circleSegment(r0, 1 - cw) : "") + "Z"; + var rc, cr, rp, ap, p0 = 0, p1 = 0, x0, y0, x1, y1, x2, y2, x3, y3, path = []; + if (ap = (+padAngle.apply(this, arguments) || 0) / 2) { + rp = padRadius === d3_svg_arcAuto ? Math.sqrt(r0 * r0 + r1 * r1) : +padRadius.apply(this, arguments); + if (!cw) p1 *= -1; + if (r1) p1 = d3_asin(rp / r1 * Math.sin(ap)); + if (r0) p0 = d3_asin(rp / r0 * Math.sin(ap)); + } + if (r1) { + x0 = r1 * Math.cos(a0 + p1); + y0 = r1 * Math.sin(a0 + p1); + x1 = r1 * Math.cos(a1 - p1); + y1 = r1 * Math.sin(a1 - p1); + var l1 = Math.abs(a1 - a0 - 2 * p1) <= π ? 0 : 1; + if (p1 && d3_svg_arcSweep(x0, y0, x1, y1) === cw ^ l1) { + var h1 = (a0 + a1) / 2; + x0 = r1 * Math.cos(h1); + y0 = r1 * Math.sin(h1); + x1 = y1 = null; + } + } else { + x0 = y0 = 0; + } + if (r0) { + x2 = r0 * Math.cos(a1 - p0); + y2 = r0 * Math.sin(a1 - p0); + x3 = r0 * Math.cos(a0 + p0); + y3 = r0 * Math.sin(a0 + p0); + var l0 = Math.abs(a0 - a1 + 2 * p0) <= π ? 0 : 1; + if (p0 && d3_svg_arcSweep(x2, y2, x3, y3) === 1 - cw ^ l0) { + var h0 = (a0 + a1) / 2; + x2 = r0 * Math.cos(h0); + y2 = r0 * Math.sin(h0); + x3 = y3 = null; + } + } else { + x2 = y2 = 0; + } + if (da > ε && (rc = Math.min(Math.abs(r1 - r0) / 2, +cornerRadius.apply(this, arguments))) > .001) { + cr = r0 < r1 ^ cw ? 0 : 1; + var rc1 = rc, rc0 = rc; + if (da < π) { + var oc = x3 == null ? [ x2, y2 ] : x1 == null ? [ x0, y0 ] : d3_geom_polygonIntersect([ x0, y0 ], [ x3, y3 ], [ x1, y1 ], [ x2, y2 ]), ax = x0 - oc[0], ay = y0 - oc[1], bx = x1 - oc[0], by = y1 - oc[1], kc = 1 / Math.sin(Math.acos((ax * bx + ay * by) / (Math.sqrt(ax * ax + ay * ay) * Math.sqrt(bx * bx + by * by))) / 2), lc = Math.sqrt(oc[0] * oc[0] + oc[1] * oc[1]); + rc0 = Math.min(rc, (r0 - lc) / (kc - 1)); + rc1 = Math.min(rc, (r1 - lc) / (kc + 1)); + } + if (x1 != null) { + var t30 = d3_svg_arcCornerTangents(x3 == null ? [ x2, y2 ] : [ x3, y3 ], [ x0, y0 ], r1, rc1, cw), t12 = d3_svg_arcCornerTangents([ x1, y1 ], [ x2, y2 ], r1, rc1, cw); + if (rc === rc1) { + path.push("M", t30[0], "A", rc1, ",", rc1, " 0 0,", cr, " ", t30[1], "A", r1, ",", r1, " 0 ", 1 - cw ^ d3_svg_arcSweep(t30[1][0], t30[1][1], t12[1][0], t12[1][1]), ",", cw, " ", t12[1], "A", rc1, ",", rc1, " 0 0,", cr, " ", t12[0]); + } else { + path.push("M", t30[0], "A", rc1, ",", rc1, " 0 1,", cr, " ", t12[0]); + } + } else { + path.push("M", x0, ",", y0); + } + if (x3 != null) { + var t03 = d3_svg_arcCornerTangents([ x0, y0 ], [ x3, y3 ], r0, -rc0, cw), t21 = d3_svg_arcCornerTangents([ x2, y2 ], x1 == null ? [ x0, y0 ] : [ x1, y1 ], r0, -rc0, cw); + if (rc === rc0) { + path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t21[1], "A", r0, ",", r0, " 0 ", cw ^ d3_svg_arcSweep(t21[1][0], t21[1][1], t03[1][0], t03[1][1]), ",", 1 - cw, " ", t03[1], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]); + } else { + path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]); + } + } else { + path.push("L", x2, ",", y2); + } + } else { + path.push("M", x0, ",", y0); + if (x1 != null) path.push("A", r1, ",", r1, " 0 ", l1, ",", cw, " ", x1, ",", y1); + path.push("L", x2, ",", y2); + if (x3 != null) path.push("A", r0, ",", r0, " 0 ", l0, ",", 1 - cw, " ", x3, ",", y3); + } + path.push("Z"); + return path.join(""); + } + function circleSegment(r1, cw) { + return "M0," + r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + -r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + r1; + } + arc.innerRadius = function(v) { + if (!arguments.length) return innerRadius; + innerRadius = d3_functor(v); + return arc; + }; + arc.outerRadius = function(v) { + if (!arguments.length) return outerRadius; + outerRadius = d3_functor(v); + return arc; + }; + arc.cornerRadius = function(v) { + if (!arguments.length) return cornerRadius; + cornerRadius = d3_functor(v); + return arc; + }; + arc.padRadius = function(v) { + if (!arguments.length) return padRadius; + padRadius = v == d3_svg_arcAuto ? d3_svg_arcAuto : d3_functor(v); + return arc; + }; + arc.startAngle = function(v) { + if (!arguments.length) return startAngle; + startAngle = d3_functor(v); + return arc; + }; + arc.endAngle = function(v) { + if (!arguments.length) return endAngle; + endAngle = d3_functor(v); + return arc; + }; + arc.padAngle = function(v) { + if (!arguments.length) return padAngle; + padAngle = d3_functor(v); + return arc; + }; + arc.centroid = function() { + var r = (+innerRadius.apply(this, arguments) + +outerRadius.apply(this, arguments)) / 2, a = (+startAngle.apply(this, arguments) + +endAngle.apply(this, arguments)) / 2 - halfπ; + return [ Math.cos(a) * r, Math.sin(a) * r ]; + }; + return arc; + }; + var d3_svg_arcAuto = "auto"; + function d3_svg_arcInnerRadius(d) { + return d.innerRadius; + } + function d3_svg_arcOuterRadius(d) { + return d.outerRadius; + } + function d3_svg_arcStartAngle(d) { + return d.startAngle; + } + function d3_svg_arcEndAngle(d) { + return d.endAngle; + } + function d3_svg_arcPadAngle(d) { + return d && d.padAngle; + } + function d3_svg_arcSweep(x0, y0, x1, y1) { + return (x0 - x1) * y0 - (y0 - y1) * x0 > 0 ? 0 : 1; + } + function d3_svg_arcCornerTangents(p0, p1, r1, rc, cw) { + var x01 = p0[0] - p1[0], y01 = p0[1] - p1[1], lo = (cw ? rc : -rc) / Math.sqrt(x01 * x01 + y01 * y01), ox = lo * y01, oy = -lo * x01, x1 = p0[0] + ox, y1 = p0[1] + oy, x2 = p1[0] + ox, y2 = p1[1] + oy, x3 = (x1 + x2) / 2, y3 = (y1 + y2) / 2, dx = x2 - x1, dy = y2 - y1, d2 = dx * dx + dy * dy, r = r1 - rc, D = x1 * y2 - x2 * y1, d = (dy < 0 ? -1 : 1) * Math.sqrt(Math.max(0, r * r * d2 - D * D)), cx0 = (D * dy - dx * d) / d2, cy0 = (-D * dx - dy * d) / d2, cx1 = (D * dy + dx * d) / d2, cy1 = (-D * dx + dy * d) / d2, dx0 = cx0 - x3, dy0 = cy0 - y3, dx1 = cx1 - x3, dy1 = cy1 - y3; + if (dx0 * dx0 + dy0 * dy0 > dx1 * dx1 + dy1 * dy1) cx0 = cx1, cy0 = cy1; + return [ [ cx0 - ox, cy0 - oy ], [ cx0 * r1 / r, cy0 * r1 / r ] ]; + } + function d3_svg_line(projection) { + var x = d3_geom_pointX, y = d3_geom_pointY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, tension = .7; + function line(data) { + var segments = [], points = [], i = -1, n = data.length, d, fx = d3_functor(x), fy = d3_functor(y); + function segment() { + segments.push("M", interpolate(projection(points), tension)); + } + while (++i < n) { + if (defined.call(this, d = data[i], i)) { + points.push([ +fx.call(this, d, i), +fy.call(this, d, i) ]); + } else if (points.length) { + segment(); + points = []; + } + } + if (points.length) segment(); + return segments.length ? segments.join("") : null; + } + line.x = function(_) { + if (!arguments.length) return x; + x = _; + return line; + }; + line.y = function(_) { + if (!arguments.length) return y; + y = _; + return line; + }; + line.defined = function(_) { + if (!arguments.length) return defined; + defined = _; + return line; + }; + line.interpolate = function(_) { + if (!arguments.length) return interpolateKey; + if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key; + return line; + }; + line.tension = function(_) { + if (!arguments.length) return tension; + tension = _; + return line; + }; + return line; + } + d3.svg.line = function() { + return d3_svg_line(d3_identity); + }; + var d3_svg_lineInterpolators = d3.map({ + linear: d3_svg_lineLinear, + "linear-closed": d3_svg_lineLinearClosed, + step: d3_svg_lineStep, + "step-before": d3_svg_lineStepBefore, + "step-after": d3_svg_lineStepAfter, + basis: d3_svg_lineBasis, + "basis-open": d3_svg_lineBasisOpen, + "basis-closed": d3_svg_lineBasisClosed, + bundle: d3_svg_lineBundle, + cardinal: d3_svg_lineCardinal, + "cardinal-open": d3_svg_lineCardinalOpen, + "cardinal-closed": d3_svg_lineCardinalClosed, + monotone: d3_svg_lineMonotone + }); + d3_svg_lineInterpolators.forEach(function(key, value) { + value.key = key; + value.closed = /-closed$/.test(key); + }); + function d3_svg_lineLinear(points) { + return points.length > 1 ? points.join("L") : points + "Z"; + } + function d3_svg_lineLinearClosed(points) { + return points.join("L") + "Z"; + } + function d3_svg_lineStep(points) { + var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; + while (++i < n) path.push("H", (p[0] + (p = points[i])[0]) / 2, "V", p[1]); + if (n > 1) path.push("H", p[0]); + return path.join(""); + } + function d3_svg_lineStepBefore(points) { + var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; + while (++i < n) path.push("V", (p = points[i])[1], "H", p[0]); + return path.join(""); + } + function d3_svg_lineStepAfter(points) { + var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; + while (++i < n) path.push("H", (p = points[i])[0], "V", p[1]); + return path.join(""); + } + function d3_svg_lineCardinalOpen(points, tension) { + return points.length < 4 ? d3_svg_lineLinear(points) : points[1] + d3_svg_lineHermite(points.slice(1, -1), d3_svg_lineCardinalTangents(points, tension)); + } + function d3_svg_lineCardinalClosed(points, tension) { + return points.length < 3 ? d3_svg_lineLinearClosed(points) : points[0] + d3_svg_lineHermite((points.push(points[0]), + points), d3_svg_lineCardinalTangents([ points[points.length - 2] ].concat(points, [ points[1] ]), tension)); + } + function d3_svg_lineCardinal(points, tension) { + return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineCardinalTangents(points, tension)); + } + function d3_svg_lineHermite(points, tangents) { + if (tangents.length < 1 || points.length != tangents.length && points.length != tangents.length + 2) { + return d3_svg_lineLinear(points); + } + var quad = points.length != tangents.length, path = "", p0 = points[0], p = points[1], t0 = tangents[0], t = t0, pi = 1; + if (quad) { + path += "Q" + (p[0] - t0[0] * 2 / 3) + "," + (p[1] - t0[1] * 2 / 3) + "," + p[0] + "," + p[1]; + p0 = points[1]; + pi = 2; + } + if (tangents.length > 1) { + t = tangents[1]; + p = points[pi]; + pi++; + path += "C" + (p0[0] + t0[0]) + "," + (p0[1] + t0[1]) + "," + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1]; + for (var i = 2; i < tangents.length; i++, pi++) { + p = points[pi]; + t = tangents[i]; + path += "S" + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1]; + } + } + if (quad) { + var lp = points[pi]; + path += "Q" + (p[0] + t[0] * 2 / 3) + "," + (p[1] + t[1] * 2 / 3) + "," + lp[0] + "," + lp[1]; + } + return path; + } + function d3_svg_lineCardinalTangents(points, tension) { + var tangents = [], a = (1 - tension) / 2, p0, p1 = points[0], p2 = points[1], i = 1, n = points.length; + while (++i < n) { + p0 = p1; + p1 = p2; + p2 = points[i]; + tangents.push([ a * (p2[0] - p0[0]), a * (p2[1] - p0[1]) ]); + } + return tangents; + } + function d3_svg_lineBasis(points) { + if (points.length < 3) return d3_svg_lineLinear(points); + var i = 1, n = points.length, pi = points[0], x0 = pi[0], y0 = pi[1], px = [ x0, x0, x0, (pi = points[1])[0] ], py = [ y0, y0, y0, pi[1] ], path = [ x0, ",", y0, "L", d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ]; + points.push(points[n - 1]); + while (++i <= n) { + pi = points[i]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + points.pop(); + path.push("L", pi); + return path.join(""); + } + function d3_svg_lineBasisOpen(points) { + if (points.length < 4) return d3_svg_lineLinear(points); + var path = [], i = -1, n = points.length, pi, px = [ 0 ], py = [ 0 ]; + while (++i < 3) { + pi = points[i]; + px.push(pi[0]); + py.push(pi[1]); + } + path.push(d3_svg_lineDot4(d3_svg_lineBasisBezier3, px) + "," + d3_svg_lineDot4(d3_svg_lineBasisBezier3, py)); + --i; + while (++i < n) { + pi = points[i]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + return path.join(""); + } + function d3_svg_lineBasisClosed(points) { + var path, i = -1, n = points.length, m = n + 4, pi, px = [], py = []; + while (++i < 4) { + pi = points[i % n]; + px.push(pi[0]); + py.push(pi[1]); + } + path = [ d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ]; + --i; + while (++i < m) { + pi = points[i % n]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + return path.join(""); + } + function d3_svg_lineBundle(points, tension) { + var n = points.length - 1; + if (n) { + var x0 = points[0][0], y0 = points[0][1], dx = points[n][0] - x0, dy = points[n][1] - y0, i = -1, p, t; + while (++i <= n) { + p = points[i]; + t = i / n; + p[0] = tension * p[0] + (1 - tension) * (x0 + t * dx); + p[1] = tension * p[1] + (1 - tension) * (y0 + t * dy); + } + } + return d3_svg_lineBasis(points); + } + function d3_svg_lineDot4(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]; + } + var d3_svg_lineBasisBezier1 = [ 0, 2 / 3, 1 / 3, 0 ], d3_svg_lineBasisBezier2 = [ 0, 1 / 3, 2 / 3, 0 ], d3_svg_lineBasisBezier3 = [ 0, 1 / 6, 2 / 3, 1 / 6 ]; + function d3_svg_lineBasisBezier(path, x, y) { + path.push("C", d3_svg_lineDot4(d3_svg_lineBasisBezier1, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier1, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, y)); + } + function d3_svg_lineSlope(p0, p1) { + return (p1[1] - p0[1]) / (p1[0] - p0[0]); + } + function d3_svg_lineFiniteDifferences(points) { + var i = 0, j = points.length - 1, m = [], p0 = points[0], p1 = points[1], d = m[0] = d3_svg_lineSlope(p0, p1); + while (++i < j) { + m[i] = (d + (d = d3_svg_lineSlope(p0 = p1, p1 = points[i + 1]))) / 2; + } + m[i] = d; + return m; + } + function d3_svg_lineMonotoneTangents(points) { + var tangents = [], d, a, b, s, m = d3_svg_lineFiniteDifferences(points), i = -1, j = points.length - 1; + while (++i < j) { + d = d3_svg_lineSlope(points[i], points[i + 1]); + if (abs(d) < ε) { + m[i] = m[i + 1] = 0; + } else { + a = m[i] / d; + b = m[i + 1] / d; + s = a * a + b * b; + if (s > 9) { + s = d * 3 / Math.sqrt(s); + m[i] = s * a; + m[i + 1] = s * b; + } + } + } + i = -1; + while (++i <= j) { + s = (points[Math.min(j, i + 1)][0] - points[Math.max(0, i - 1)][0]) / (6 * (1 + m[i] * m[i])); + tangents.push([ s || 0, m[i] * s || 0 ]); + } + return tangents; + } + function d3_svg_lineMonotone(points) { + return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineMonotoneTangents(points)); + } + d3.svg.line.radial = function() { + var line = d3_svg_line(d3_svg_lineRadial); + line.radius = line.x, delete line.x; + line.angle = line.y, delete line.y; + return line; + }; + function d3_svg_lineRadial(points) { + var point, i = -1, n = points.length, r, a; + while (++i < n) { + point = points[i]; + r = point[0]; + a = point[1] - halfπ; + point[0] = r * Math.cos(a); + point[1] = r * Math.sin(a); + } + return points; + } + function d3_svg_area(projection) { + var x0 = d3_geom_pointX, x1 = d3_geom_pointX, y0 = 0, y1 = d3_geom_pointY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, interpolateReverse = interpolate, L = "L", tension = .7; + function area(data) { + var segments = [], points0 = [], points1 = [], i = -1, n = data.length, d, fx0 = d3_functor(x0), fy0 = d3_functor(y0), fx1 = x0 === x1 ? function() { + return x; + } : d3_functor(x1), fy1 = y0 === y1 ? function() { + return y; + } : d3_functor(y1), x, y; + function segment() { + segments.push("M", interpolate(projection(points1), tension), L, interpolateReverse(projection(points0.reverse()), tension), "Z"); + } + while (++i < n) { + if (defined.call(this, d = data[i], i)) { + points0.push([ x = +fx0.call(this, d, i), y = +fy0.call(this, d, i) ]); + points1.push([ +fx1.call(this, d, i), +fy1.call(this, d, i) ]); + } else if (points0.length) { + segment(); + points0 = []; + points1 = []; + } + } + if (points0.length) segment(); + return segments.length ? segments.join("") : null; + } + area.x = function(_) { + if (!arguments.length) return x1; + x0 = x1 = _; + return area; + }; + area.x0 = function(_) { + if (!arguments.length) return x0; + x0 = _; + return area; + }; + area.x1 = function(_) { + if (!arguments.length) return x1; + x1 = _; + return area; + }; + area.y = function(_) { + if (!arguments.length) return y1; + y0 = y1 = _; + return area; + }; + area.y0 = function(_) { + if (!arguments.length) return y0; + y0 = _; + return area; + }; + area.y1 = function(_) { + if (!arguments.length) return y1; + y1 = _; + return area; + }; + area.defined = function(_) { + if (!arguments.length) return defined; + defined = _; + return area; + }; + area.interpolate = function(_) { + if (!arguments.length) return interpolateKey; + if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key; + interpolateReverse = interpolate.reverse || interpolate; + L = interpolate.closed ? "M" : "L"; + return area; + }; + area.tension = function(_) { + if (!arguments.length) return tension; + tension = _; + return area; + }; + return area; + } + d3_svg_lineStepBefore.reverse = d3_svg_lineStepAfter; + d3_svg_lineStepAfter.reverse = d3_svg_lineStepBefore; + d3.svg.area = function() { + return d3_svg_area(d3_identity); + }; + d3.svg.area.radial = function() { + var area = d3_svg_area(d3_svg_lineRadial); + area.radius = area.x, delete area.x; + area.innerRadius = area.x0, delete area.x0; + area.outerRadius = area.x1, delete area.x1; + area.angle = area.y, delete area.y; + area.startAngle = area.y0, delete area.y0; + area.endAngle = area.y1, delete area.y1; + return area; + }; + d3.svg.chord = function() { + var source = d3_source, target = d3_target, radius = d3_svg_chordRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle; + function chord(d, i) { + var s = subgroup(this, source, d, i), t = subgroup(this, target, d, i); + return "M" + s.p0 + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.r, s.p1, s.r, s.p0) : curve(s.r, s.p1, t.r, t.p0) + arc(t.r, t.p1, t.a1 - t.a0) + curve(t.r, t.p1, s.r, s.p0)) + "Z"; + } + function subgroup(self, f, d, i) { + var subgroup = f.call(self, d, i), r = radius.call(self, subgroup, i), a0 = startAngle.call(self, subgroup, i) - halfπ, a1 = endAngle.call(self, subgroup, i) - halfπ; + return { + r: r, + a0: a0, + a1: a1, + p0: [ r * Math.cos(a0), r * Math.sin(a0) ], + p1: [ r * Math.cos(a1), r * Math.sin(a1) ] + }; + } + function equals(a, b) { + return a.a0 == b.a0 && a.a1 == b.a1; + } + function arc(r, p, a) { + return "A" + r + "," + r + " 0 " + +(a > π) + ",1 " + p; + } + function curve(r0, p0, r1, p1) { + return "Q 0,0 " + p1; + } + chord.radius = function(v) { + if (!arguments.length) return radius; + radius = d3_functor(v); + return chord; + }; + chord.source = function(v) { + if (!arguments.length) return source; + source = d3_functor(v); + return chord; + }; + chord.target = function(v) { + if (!arguments.length) return target; + target = d3_functor(v); + return chord; + }; + chord.startAngle = function(v) { + if (!arguments.length) return startAngle; + startAngle = d3_functor(v); + return chord; + }; + chord.endAngle = function(v) { + if (!arguments.length) return endAngle; + endAngle = d3_functor(v); + return chord; + }; + return chord; + }; + function d3_svg_chordRadius(d) { + return d.radius; + } + d3.svg.diagonal = function() { + var source = d3_source, target = d3_target, projection = d3_svg_diagonalProjection; + function diagonal(d, i) { + var p0 = source.call(this, d, i), p3 = target.call(this, d, i), m = (p0.y + p3.y) / 2, p = [ p0, { + x: p0.x, + y: m + }, { + x: p3.x, + y: m + }, p3 ]; + p = p.map(projection); + return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3]; + } + diagonal.source = function(x) { + if (!arguments.length) return source; + source = d3_functor(x); + return diagonal; + }; + diagonal.target = function(x) { + if (!arguments.length) return target; + target = d3_functor(x); + return diagonal; + }; + diagonal.projection = function(x) { + if (!arguments.length) return projection; + projection = x; + return diagonal; + }; + return diagonal; + }; + function d3_svg_diagonalProjection(d) { + return [ d.x, d.y ]; + } + d3.svg.diagonal.radial = function() { + var diagonal = d3.svg.diagonal(), projection = d3_svg_diagonalProjection, projection_ = diagonal.projection; + diagonal.projection = function(x) { + return arguments.length ? projection_(d3_svg_diagonalRadialProjection(projection = x)) : projection; + }; + return diagonal; + }; + function d3_svg_diagonalRadialProjection(projection) { + return function() { + var d = projection.apply(this, arguments), r = d[0], a = d[1] - halfπ; + return [ r * Math.cos(a), r * Math.sin(a) ]; + }; + } + d3.svg.symbol = function() { + var type = d3_svg_symbolType, size = d3_svg_symbolSize; + function symbol(d, i) { + return (d3_svg_symbols.get(type.call(this, d, i)) || d3_svg_symbolCircle)(size.call(this, d, i)); + } + symbol.type = function(x) { + if (!arguments.length) return type; + type = d3_functor(x); + return symbol; + }; + symbol.size = function(x) { + if (!arguments.length) return size; + size = d3_functor(x); + return symbol; + }; + return symbol; + }; + function d3_svg_symbolSize() { + return 64; + } + function d3_svg_symbolType() { + return "circle"; + } + function d3_svg_symbolCircle(size) { + var r = Math.sqrt(size / π); + return "M0," + r + "A" + r + "," + r + " 0 1,1 0," + -r + "A" + r + "," + r + " 0 1,1 0," + r + "Z"; + } + var d3_svg_symbols = d3.map({ + circle: d3_svg_symbolCircle, + cross: function(size) { + var r = Math.sqrt(size / 5) / 2; + return "M" + -3 * r + "," + -r + "H" + -r + "V" + -3 * r + "H" + r + "V" + -r + "H" + 3 * r + "V" + r + "H" + r + "V" + 3 * r + "H" + -r + "V" + r + "H" + -3 * r + "Z"; + }, + diamond: function(size) { + var ry = Math.sqrt(size / (2 * d3_svg_symbolTan30)), rx = ry * d3_svg_symbolTan30; + return "M0," + -ry + "L" + rx + ",0" + " 0," + ry + " " + -rx + ",0" + "Z"; + }, + square: function(size) { + var r = Math.sqrt(size) / 2; + return "M" + -r + "," + -r + "L" + r + "," + -r + " " + r + "," + r + " " + -r + "," + r + "Z"; + }, + "triangle-down": function(size) { + var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2; + return "M0," + ry + "L" + rx + "," + -ry + " " + -rx + "," + -ry + "Z"; + }, + "triangle-up": function(size) { + var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2; + return "M0," + -ry + "L" + rx + "," + ry + " " + -rx + "," + ry + "Z"; + } + }); + d3.svg.symbolTypes = d3_svg_symbols.keys(); + var d3_svg_symbolSqrt3 = Math.sqrt(3), d3_svg_symbolTan30 = Math.tan(30 * d3_radians); + d3_selectionPrototype.transition = function(name) { + var id = d3_transitionInheritId || ++d3_transitionId, ns = d3_transitionNamespace(name), subgroups = [], subgroup, node, transition = d3_transitionInherit || { + time: Date.now(), + ease: d3_ease_cubicInOut, + delay: 0, + duration: 250 + }; + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) d3_transitionNode(node, i, ns, id, transition); + subgroup.push(node); + } + } + return d3_transition(subgroups, ns, id); + }; + d3_selectionPrototype.interrupt = function(name) { + return this.each(name == null ? d3_selection_interrupt : d3_selection_interruptNS(d3_transitionNamespace(name))); + }; + var d3_selection_interrupt = d3_selection_interruptNS(d3_transitionNamespace()); + function d3_selection_interruptNS(ns) { + return function() { + var lock, activeId, active; + if ((lock = this[ns]) && (active = lock[activeId = lock.active])) { + active.timer.c = null; + active.timer.t = NaN; + if (--lock.count) delete lock[activeId]; else delete this[ns]; + lock.active += .5; + active.event && active.event.interrupt.call(this, this.__data__, active.index); + } + }; + } + function d3_transition(groups, ns, id) { + d3_subclass(groups, d3_transitionPrototype); + groups.namespace = ns; + groups.id = id; + return groups; + } + var d3_transitionPrototype = [], d3_transitionId = 0, d3_transitionInheritId, d3_transitionInherit; + d3_transitionPrototype.call = d3_selectionPrototype.call; + d3_transitionPrototype.empty = d3_selectionPrototype.empty; + d3_transitionPrototype.node = d3_selectionPrototype.node; + d3_transitionPrototype.size = d3_selectionPrototype.size; + d3.transition = function(selection, name) { + return selection && selection.transition ? d3_transitionInheritId ? selection.transition(name) : selection : d3.selection().transition(selection); + }; + d3.transition.prototype = d3_transitionPrototype; + d3_transitionPrototype.select = function(selector) { + var id = this.id, ns = this.namespace, subgroups = [], subgroup, subnode, node; + selector = d3_selection_selector(selector); + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if ((node = group[i]) && (subnode = selector.call(node, node.__data__, i, j))) { + if ("__data__" in node) subnode.__data__ = node.__data__; + d3_transitionNode(subnode, i, ns, id, node[ns][id]); + subgroup.push(subnode); + } else { + subgroup.push(null); + } + } + } + return d3_transition(subgroups, ns, id); + }; + d3_transitionPrototype.selectAll = function(selector) { + var id = this.id, ns = this.namespace, subgroups = [], subgroup, subnodes, node, subnode, transition; + selector = d3_selection_selectorAll(selector); + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + transition = node[ns][id]; + subnodes = selector.call(node, node.__data__, i, j); + subgroups.push(subgroup = []); + for (var k = -1, o = subnodes.length; ++k < o; ) { + if (subnode = subnodes[k]) d3_transitionNode(subnode, k, ns, id, transition); + subgroup.push(subnode); + } + } + } + } + return d3_transition(subgroups, ns, id); + }; + d3_transitionPrototype.filter = function(filter) { + var subgroups = [], subgroup, group, node; + if (typeof filter !== "function") filter = d3_selection_filter(filter); + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + if ((node = group[i]) && filter.call(node, node.__data__, i, j)) { + subgroup.push(node); + } + } + } + return d3_transition(subgroups, this.namespace, this.id); + }; + d3_transitionPrototype.tween = function(name, tween) { + var id = this.id, ns = this.namespace; + if (arguments.length < 2) return this.node()[ns][id].tween.get(name); + return d3_selection_each(this, tween == null ? function(node) { + node[ns][id].tween.remove(name); + } : function(node) { + node[ns][id].tween.set(name, tween); + }); + }; + function d3_transition_tween(groups, name, value, tween) { + var id = groups.id, ns = groups.namespace; + return d3_selection_each(groups, typeof value === "function" ? function(node, i, j) { + node[ns][id].tween.set(name, tween(value.call(node, node.__data__, i, j))); + } : (value = tween(value), function(node) { + node[ns][id].tween.set(name, value); + })); + } + d3_transitionPrototype.attr = function(nameNS, value) { + if (arguments.length < 2) { + for (value in nameNS) this.attr(value, nameNS[value]); + return this; + } + var interpolate = nameNS == "transform" ? d3_interpolateTransform : d3_interpolate, name = d3.ns.qualify(nameNS); + function attrNull() { + this.removeAttribute(name); + } + function attrNullNS() { + this.removeAttributeNS(name.space, name.local); + } + function attrTween(b) { + return b == null ? attrNull : (b += "", function() { + var a = this.getAttribute(name), i; + return a !== b && (i = interpolate(a, b), function(t) { + this.setAttribute(name, i(t)); + }); + }); + } + function attrTweenNS(b) { + return b == null ? attrNullNS : (b += "", function() { + var a = this.getAttributeNS(name.space, name.local), i; + return a !== b && (i = interpolate(a, b), function(t) { + this.setAttributeNS(name.space, name.local, i(t)); + }); + }); + } + return d3_transition_tween(this, "attr." + nameNS, value, name.local ? attrTweenNS : attrTween); + }; + d3_transitionPrototype.attrTween = function(nameNS, tween) { + var name = d3.ns.qualify(nameNS); + function attrTween(d, i) { + var f = tween.call(this, d, i, this.getAttribute(name)); + return f && function(t) { + this.setAttribute(name, f(t)); + }; + } + function attrTweenNS(d, i) { + var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local)); + return f && function(t) { + this.setAttributeNS(name.space, name.local, f(t)); + }; + } + return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween); + }; + d3_transitionPrototype.style = function(name, value, priority) { + var n = arguments.length; + if (n < 3) { + if (typeof name !== "string") { + if (n < 2) value = ""; + for (priority in name) this.style(priority, name[priority], value); + return this; + } + priority = ""; + } + function styleNull() { + this.style.removeProperty(name); + } + function styleString(b) { + return b == null ? styleNull : (b += "", function() { + var a = d3_window(this).getComputedStyle(this, null).getPropertyValue(name), i; + return a !== b && (i = d3_interpolate(a, b), function(t) { + this.style.setProperty(name, i(t), priority); + }); + }); + } + return d3_transition_tween(this, "style." + name, value, styleString); + }; + d3_transitionPrototype.styleTween = function(name, tween, priority) { + if (arguments.length < 3) priority = ""; + function styleTween(d, i) { + var f = tween.call(this, d, i, d3_window(this).getComputedStyle(this, null).getPropertyValue(name)); + return f && function(t) { + this.style.setProperty(name, f(t), priority); + }; + } + return this.tween("style." + name, styleTween); + }; + d3_transitionPrototype.text = function(value) { + return d3_transition_tween(this, "text", value, d3_transition_text); + }; + function d3_transition_text(b) { + if (b == null) b = ""; + return function() { + this.textContent = b; + }; + } + d3_transitionPrototype.remove = function() { + var ns = this.namespace; + return this.each("end.transition", function() { + var p; + if (this[ns].count < 2 && (p = this.parentNode)) p.removeChild(this); + }); + }; + d3_transitionPrototype.ease = function(value) { + var id = this.id, ns = this.namespace; + if (arguments.length < 1) return this.node()[ns][id].ease; + if (typeof value !== "function") value = d3.ease.apply(d3, arguments); + return d3_selection_each(this, function(node) { + node[ns][id].ease = value; + }); + }; + d3_transitionPrototype.delay = function(value) { + var id = this.id, ns = this.namespace; + if (arguments.length < 1) return this.node()[ns][id].delay; + return d3_selection_each(this, typeof value === "function" ? function(node, i, j) { + node[ns][id].delay = +value.call(node, node.__data__, i, j); + } : (value = +value, function(node) { + node[ns][id].delay = value; + })); + }; + d3_transitionPrototype.duration = function(value) { + var id = this.id, ns = this.namespace; + if (arguments.length < 1) return this.node()[ns][id].duration; + return d3_selection_each(this, typeof value === "function" ? function(node, i, j) { + node[ns][id].duration = Math.max(1, value.call(node, node.__data__, i, j)); + } : (value = Math.max(1, value), function(node) { + node[ns][id].duration = value; + })); + }; + d3_transitionPrototype.each = function(type, listener) { + var id = this.id, ns = this.namespace; + if (arguments.length < 2) { + var inherit = d3_transitionInherit, inheritId = d3_transitionInheritId; + try { + d3_transitionInheritId = id; + d3_selection_each(this, function(node, i, j) { + d3_transitionInherit = node[ns][id]; + type.call(node, node.__data__, i, j); + }); + } finally { + d3_transitionInherit = inherit; + d3_transitionInheritId = inheritId; + } + } else { + d3_selection_each(this, function(node) { + var transition = node[ns][id]; + (transition.event || (transition.event = d3.dispatch("start", "end", "interrupt"))).on(type, listener); + }); + } + return this; + }; + d3_transitionPrototype.transition = function() { + var id0 = this.id, id1 = ++d3_transitionId, ns = this.namespace, subgroups = [], subgroup, group, node, transition; + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + if (node = group[i]) { + transition = node[ns][id0]; + d3_transitionNode(node, i, ns, id1, { + time: transition.time, + ease: transition.ease, + delay: transition.delay + transition.duration, + duration: transition.duration + }); + } + subgroup.push(node); + } + } + return d3_transition(subgroups, ns, id1); + }; + function d3_transitionNamespace(name) { + return name == null ? "__transition__" : "__transition_" + name + "__"; + } + function d3_transitionNode(node, i, ns, id, inherit) { + var lock = node[ns] || (node[ns] = { + active: 0, + count: 0 + }), transition = lock[id], time, timer, duration, ease, tweens; + function schedule(elapsed) { + var delay = transition.delay; + timer.t = delay + time; + if (delay <= elapsed) return start(elapsed - delay); + timer.c = start; + } + function start(elapsed) { + var activeId = lock.active, active = lock[activeId]; + if (active) { + active.timer.c = null; + active.timer.t = NaN; + --lock.count; + delete lock[activeId]; + active.event && active.event.interrupt.call(node, node.__data__, active.index); + } + for (var cancelId in lock) { + if (+cancelId < id) { + var cancel = lock[cancelId]; + cancel.timer.c = null; + cancel.timer.t = NaN; + --lock.count; + delete lock[cancelId]; + } + } + timer.c = tick; + d3_timer(function() { + if (timer.c && tick(elapsed || 1)) { + timer.c = null; + timer.t = NaN; + } + return 1; + }, 0, time); + lock.active = id; + transition.event && transition.event.start.call(node, node.__data__, i); + tweens = []; + transition.tween.forEach(function(key, value) { + if (value = value.call(node, node.__data__, i)) { + tweens.push(value); + } + }); + ease = transition.ease; + duration = transition.duration; + } + function tick(elapsed) { + var t = elapsed / duration, e = ease(t), n = tweens.length; + while (n > 0) { + tweens[--n].call(node, e); + } + if (t >= 1) { + transition.event && transition.event.end.call(node, node.__data__, i); + if (--lock.count) delete lock[id]; else delete node[ns]; + return 1; + } + } + if (!transition) { + time = inherit.time; + timer = d3_timer(schedule, 0, time); + transition = lock[id] = { + tween: new d3_Map(), + time: time, + timer: timer, + delay: inherit.delay, + duration: inherit.duration, + ease: inherit.ease, + index: i + }; + inherit = null; + ++lock.count; + } + } + d3.svg.axis = function() { + var scale = d3.scale.linear(), orient = d3_svg_axisDefaultOrient, innerTickSize = 6, outerTickSize = 6, tickPadding = 3, tickArguments_ = [ 10 ], tickValues = null, tickFormat_; + function axis(g) { + g.each(function() { + var g = d3.select(this); + var scale0 = this.__chart__ || scale, scale1 = this.__chart__ = scale.copy(); + var ticks = tickValues == null ? scale1.ticks ? scale1.ticks.apply(scale1, tickArguments_) : scale1.domain() : tickValues, tickFormat = tickFormat_ == null ? scale1.tickFormat ? scale1.tickFormat.apply(scale1, tickArguments_) : d3_identity : tickFormat_, tick = g.selectAll(".tick").data(ticks, scale1), tickEnter = tick.enter().insert("g", ".domain").attr("class", "tick").style("opacity", ε), tickExit = d3.transition(tick.exit()).style("opacity", ε).remove(), tickUpdate = d3.transition(tick.order()).style("opacity", 1), tickSpacing = Math.max(innerTickSize, 0) + tickPadding, tickTransform; + var range = d3_scaleRange(scale1), path = g.selectAll(".domain").data([ 0 ]), pathUpdate = (path.enter().append("path").attr("class", "domain"), + d3.transition(path)); + tickEnter.append("line"); + tickEnter.append("text"); + var lineEnter = tickEnter.select("line"), lineUpdate = tickUpdate.select("line"), text = tick.select("text").text(tickFormat), textEnter = tickEnter.select("text"), textUpdate = tickUpdate.select("text"), sign = orient === "top" || orient === "left" ? -1 : 1, x1, x2, y1, y2; + if (orient === "bottom" || orient === "top") { + tickTransform = d3_svg_axisX, x1 = "x", y1 = "y", x2 = "x2", y2 = "y2"; + text.attr("dy", sign < 0 ? "0em" : ".71em").style("text-anchor", "middle"); + pathUpdate.attr("d", "M" + range[0] + "," + sign * outerTickSize + "V0H" + range[1] + "V" + sign * outerTickSize); + } else { + tickTransform = d3_svg_axisY, x1 = "y", y1 = "x", x2 = "y2", y2 = "x2"; + text.attr("dy", ".32em").style("text-anchor", sign < 0 ? "end" : "start"); + pathUpdate.attr("d", "M" + sign * outerTickSize + "," + range[0] + "H0V" + range[1] + "H" + sign * outerTickSize); + } + lineEnter.attr(y2, sign * innerTickSize); + textEnter.attr(y1, sign * tickSpacing); + lineUpdate.attr(x2, 0).attr(y2, sign * innerTickSize); + textUpdate.attr(x1, 0).attr(y1, sign * tickSpacing); + if (scale1.rangeBand) { + var x = scale1, dx = x.rangeBand() / 2; + scale0 = scale1 = function(d) { + return x(d) + dx; + }; + } else if (scale0.rangeBand) { + scale0 = scale1; + } else { + tickExit.call(tickTransform, scale1, scale0); + } + tickEnter.call(tickTransform, scale0, scale1); + tickUpdate.call(tickTransform, scale1, scale1); + }); + } + axis.scale = function(x) { + if (!arguments.length) return scale; + scale = x; + return axis; + }; + axis.orient = function(x) { + if (!arguments.length) return orient; + orient = x in d3_svg_axisOrients ? x + "" : d3_svg_axisDefaultOrient; + return axis; + }; + axis.ticks = function() { + if (!arguments.length) return tickArguments_; + tickArguments_ = d3_array(arguments); + return axis; + }; + axis.tickValues = function(x) { + if (!arguments.length) return tickValues; + tickValues = x; + return axis; + }; + axis.tickFormat = function(x) { + if (!arguments.length) return tickFormat_; + tickFormat_ = x; + return axis; + }; + axis.tickSize = function(x) { + var n = arguments.length; + if (!n) return innerTickSize; + innerTickSize = +x; + outerTickSize = +arguments[n - 1]; + return axis; + }; + axis.innerTickSize = function(x) { + if (!arguments.length) return innerTickSize; + innerTickSize = +x; + return axis; + }; + axis.outerTickSize = function(x) { + if (!arguments.length) return outerTickSize; + outerTickSize = +x; + return axis; + }; + axis.tickPadding = function(x) { + if (!arguments.length) return tickPadding; + tickPadding = +x; + return axis; + }; + axis.tickSubdivide = function() { + return arguments.length && axis; + }; + return axis; + }; + var d3_svg_axisDefaultOrient = "bottom", d3_svg_axisOrients = { + top: 1, + right: 1, + bottom: 1, + left: 1 + }; + function d3_svg_axisX(selection, x0, x1) { + selection.attr("transform", function(d) { + var v0 = x0(d); + return "translate(" + (isFinite(v0) ? v0 : x1(d)) + ",0)"; + }); + } + function d3_svg_axisY(selection, y0, y1) { + selection.attr("transform", function(d) { + var v0 = y0(d); + return "translate(0," + (isFinite(v0) ? v0 : y1(d)) + ")"; + }); + } + d3.svg.brush = function() { + var event = d3_eventDispatch(brush, "brushstart", "brush", "brushend"), x = null, y = null, xExtent = [ 0, 0 ], yExtent = [ 0, 0 ], xExtentDomain, yExtentDomain, xClamp = true, yClamp = true, resizes = d3_svg_brushResizes[0]; + function brush(g) { + g.each(function() { + var g = d3.select(this).style("pointer-events", "all").style("-webkit-tap-highlight-color", "rgba(0,0,0,0)").on("mousedown.brush", brushstart).on("touchstart.brush", brushstart); + var background = g.selectAll(".background").data([ 0 ]); + background.enter().append("rect").attr("class", "background").style("visibility", "hidden").style("cursor", "crosshair"); + g.selectAll(".extent").data([ 0 ]).enter().append("rect").attr("class", "extent").style("cursor", "move"); + var resize = g.selectAll(".resize").data(resizes, d3_identity); + resize.exit().remove(); + resize.enter().append("g").attr("class", function(d) { + return "resize " + d; + }).style("cursor", function(d) { + return d3_svg_brushCursor[d]; + }).append("rect").attr("x", function(d) { + return /[ew]$/.test(d) ? -3 : null; + }).attr("y", function(d) { + return /^[ns]/.test(d) ? -3 : null; + }).attr("width", 6).attr("height", 6).style("visibility", "hidden"); + resize.style("display", brush.empty() ? "none" : null); + var gUpdate = d3.transition(g), backgroundUpdate = d3.transition(background), range; + if (x) { + range = d3_scaleRange(x); + backgroundUpdate.attr("x", range[0]).attr("width", range[1] - range[0]); + redrawX(gUpdate); + } + if (y) { + range = d3_scaleRange(y); + backgroundUpdate.attr("y", range[0]).attr("height", range[1] - range[0]); + redrawY(gUpdate); + } + redraw(gUpdate); + }); + } + brush.event = function(g) { + g.each(function() { + var event_ = event.of(this, arguments), extent1 = { + x: xExtent, + y: yExtent, + i: xExtentDomain, + j: yExtentDomain + }, extent0 = this.__chart__ || extent1; + this.__chart__ = extent1; + if (d3_transitionInheritId) { + d3.select(this).transition().each("start.brush", function() { + xExtentDomain = extent0.i; + yExtentDomain = extent0.j; + xExtent = extent0.x; + yExtent = extent0.y; + event_({ + type: "brushstart" + }); + }).tween("brush:brush", function() { + var xi = d3_interpolateArray(xExtent, extent1.x), yi = d3_interpolateArray(yExtent, extent1.y); + xExtentDomain = yExtentDomain = null; + return function(t) { + xExtent = extent1.x = xi(t); + yExtent = extent1.y = yi(t); + event_({ + type: "brush", + mode: "resize" + }); + }; + }).each("end.brush", function() { + xExtentDomain = extent1.i; + yExtentDomain = extent1.j; + event_({ + type: "brush", + mode: "resize" + }); + event_({ + type: "brushend" + }); + }); + } else { + event_({ + type: "brushstart" + }); + event_({ + type: "brush", + mode: "resize" + }); + event_({ + type: "brushend" + }); + } + }); + }; + function redraw(g) { + g.selectAll(".resize").attr("transform", function(d) { + return "translate(" + xExtent[+/e$/.test(d)] + "," + yExtent[+/^s/.test(d)] + ")"; + }); + } + function redrawX(g) { + g.select(".extent").attr("x", xExtent[0]); + g.selectAll(".extent,.n>rect,.s>rect").attr("width", xExtent[1] - xExtent[0]); + } + function redrawY(g) { + g.select(".extent").attr("y", yExtent[0]); + g.selectAll(".extent,.e>rect,.w>rect").attr("height", yExtent[1] - yExtent[0]); + } + function brushstart() { + var target = this, eventTarget = d3.select(d3.event.target), event_ = event.of(target, arguments), g = d3.select(target), resizing = eventTarget.datum(), resizingX = !/^(n|s)$/.test(resizing) && x, resizingY = !/^(e|w)$/.test(resizing) && y, dragging = eventTarget.classed("extent"), dragRestore = d3_event_dragSuppress(target), center, origin = d3.mouse(target), offset; + var w = d3.select(d3_window(target)).on("keydown.brush", keydown).on("keyup.brush", keyup); + if (d3.event.changedTouches) { + w.on("touchmove.brush", brushmove).on("touchend.brush", brushend); + } else { + w.on("mousemove.brush", brushmove).on("mouseup.brush", brushend); + } + g.interrupt().selectAll("*").interrupt(); + if (dragging) { + origin[0] = xExtent[0] - origin[0]; + origin[1] = yExtent[0] - origin[1]; + } else if (resizing) { + var ex = +/w$/.test(resizing), ey = +/^n/.test(resizing); + offset = [ xExtent[1 - ex] - origin[0], yExtent[1 - ey] - origin[1] ]; + origin[0] = xExtent[ex]; + origin[1] = yExtent[ey]; + } else if (d3.event.altKey) center = origin.slice(); + g.style("pointer-events", "none").selectAll(".resize").style("display", null); + d3.select("body").style("cursor", eventTarget.style("cursor")); + event_({ + type: "brushstart" + }); + brushmove(); + function keydown() { + if (d3.event.keyCode == 32) { + if (!dragging) { + center = null; + origin[0] -= xExtent[1]; + origin[1] -= yExtent[1]; + dragging = 2; + } + d3_eventPreventDefault(); + } + } + function keyup() { + if (d3.event.keyCode == 32 && dragging == 2) { + origin[0] += xExtent[1]; + origin[1] += yExtent[1]; + dragging = 0; + d3_eventPreventDefault(); + } + } + function brushmove() { + var point = d3.mouse(target), moved = false; + if (offset) { + point[0] += offset[0]; + point[1] += offset[1]; + } + if (!dragging) { + if (d3.event.altKey) { + if (!center) center = [ (xExtent[0] + xExtent[1]) / 2, (yExtent[0] + yExtent[1]) / 2 ]; + origin[0] = xExtent[+(point[0] < center[0])]; + origin[1] = yExtent[+(point[1] < center[1])]; + } else center = null; + } + if (resizingX && move1(point, x, 0)) { + redrawX(g); + moved = true; + } + if (resizingY && move1(point, y, 1)) { + redrawY(g); + moved = true; + } + if (moved) { + redraw(g); + event_({ + type: "brush", + mode: dragging ? "move" : "resize" + }); + } + } + function move1(point, scale, i) { + var range = d3_scaleRange(scale), r0 = range[0], r1 = range[1], position = origin[i], extent = i ? yExtent : xExtent, size = extent[1] - extent[0], min, max; + if (dragging) { + r0 -= position; + r1 -= size + position; + } + min = (i ? yClamp : xClamp) ? Math.max(r0, Math.min(r1, point[i])) : point[i]; + if (dragging) { + max = (min += position) + size; + } else { + if (center) position = Math.max(r0, Math.min(r1, 2 * center[i] - min)); + if (position < min) { + max = min; + min = position; + } else { + max = position; + } + } + if (extent[0] != min || extent[1] != max) { + if (i) yExtentDomain = null; else xExtentDomain = null; + extent[0] = min; + extent[1] = max; + return true; + } + } + function brushend() { + brushmove(); + g.style("pointer-events", "all").selectAll(".resize").style("display", brush.empty() ? "none" : null); + d3.select("body").style("cursor", null); + w.on("mousemove.brush", null).on("mouseup.brush", null).on("touchmove.brush", null).on("touchend.brush", null).on("keydown.brush", null).on("keyup.brush", null); + dragRestore(); + event_({ + type: "brushend" + }); + } + } + brush.x = function(z) { + if (!arguments.length) return x; + x = z; + resizes = d3_svg_brushResizes[!x << 1 | !y]; + return brush; + }; + brush.y = function(z) { + if (!arguments.length) return y; + y = z; + resizes = d3_svg_brushResizes[!x << 1 | !y]; + return brush; + }; + brush.clamp = function(z) { + if (!arguments.length) return x && y ? [ xClamp, yClamp ] : x ? xClamp : y ? yClamp : null; + if (x && y) xClamp = !!z[0], yClamp = !!z[1]; else if (x) xClamp = !!z; else if (y) yClamp = !!z; + return brush; + }; + brush.extent = function(z) { + var x0, x1, y0, y1, t; + if (!arguments.length) { + if (x) { + if (xExtentDomain) { + x0 = xExtentDomain[0], x1 = xExtentDomain[1]; + } else { + x0 = xExtent[0], x1 = xExtent[1]; + if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1); + if (x1 < x0) t = x0, x0 = x1, x1 = t; + } + } + if (y) { + if (yExtentDomain) { + y0 = yExtentDomain[0], y1 = yExtentDomain[1]; + } else { + y0 = yExtent[0], y1 = yExtent[1]; + if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1); + if (y1 < y0) t = y0, y0 = y1, y1 = t; + } + } + return x && y ? [ [ x0, y0 ], [ x1, y1 ] ] : x ? [ x0, x1 ] : y && [ y0, y1 ]; + } + if (x) { + x0 = z[0], x1 = z[1]; + if (y) x0 = x0[0], x1 = x1[0]; + xExtentDomain = [ x0, x1 ]; + if (x.invert) x0 = x(x0), x1 = x(x1); + if (x1 < x0) t = x0, x0 = x1, x1 = t; + if (x0 != xExtent[0] || x1 != xExtent[1]) xExtent = [ x0, x1 ]; + } + if (y) { + y0 = z[0], y1 = z[1]; + if (x) y0 = y0[1], y1 = y1[1]; + yExtentDomain = [ y0, y1 ]; + if (y.invert) y0 = y(y0), y1 = y(y1); + if (y1 < y0) t = y0, y0 = y1, y1 = t; + if (y0 != yExtent[0] || y1 != yExtent[1]) yExtent = [ y0, y1 ]; + } + return brush; + }; + brush.clear = function() { + if (!brush.empty()) { + xExtent = [ 0, 0 ], yExtent = [ 0, 0 ]; + xExtentDomain = yExtentDomain = null; + } + return brush; + }; + brush.empty = function() { + return !!x && xExtent[0] == xExtent[1] || !!y && yExtent[0] == yExtent[1]; + }; + return d3.rebind(brush, event, "on"); + }; + var d3_svg_brushCursor = { + n: "ns-resize", + e: "ew-resize", + s: "ns-resize", + w: "ew-resize", + nw: "nwse-resize", + ne: "nesw-resize", + se: "nwse-resize", + sw: "nesw-resize" + }; + var d3_svg_brushResizes = [ [ "n", "e", "s", "w", "nw", "ne", "se", "sw" ], [ "e", "w" ], [ "n", "s" ], [] ]; + var d3_time_format = d3_time.format = d3_locale_enUS.timeFormat; + var d3_time_formatUtc = d3_time_format.utc; + var d3_time_formatIso = d3_time_formatUtc("%Y-%m-%dT%H:%M:%S.%LZ"); + d3_time_format.iso = Date.prototype.toISOString && +new Date("2000-01-01T00:00:00.000Z") ? d3_time_formatIsoNative : d3_time_formatIso; + function d3_time_formatIsoNative(date) { + return date.toISOString(); + } + d3_time_formatIsoNative.parse = function(string) { + var date = new Date(string); + return isNaN(date) ? null : date; + }; + d3_time_formatIsoNative.toString = d3_time_formatIso.toString; + d3_time.second = d3_time_interval(function(date) { + return new d3_date(Math.floor(date / 1e3) * 1e3); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 1e3); + }, function(date) { + return date.getSeconds(); + }); + d3_time.seconds = d3_time.second.range; + d3_time.seconds.utc = d3_time.second.utc.range; + d3_time.minute = d3_time_interval(function(date) { + return new d3_date(Math.floor(date / 6e4) * 6e4); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 6e4); + }, function(date) { + return date.getMinutes(); + }); + d3_time.minutes = d3_time.minute.range; + d3_time.minutes.utc = d3_time.minute.utc.range; + d3_time.hour = d3_time_interval(function(date) { + var timezone = date.getTimezoneOffset() / 60; + return new d3_date((Math.floor(date / 36e5 - timezone) + timezone) * 36e5); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 36e5); + }, function(date) { + return date.getHours(); + }); + d3_time.hours = d3_time.hour.range; + d3_time.hours.utc = d3_time.hour.utc.range; + d3_time.month = d3_time_interval(function(date) { + date = d3_time.day(date); + date.setDate(1); + return date; + }, function(date, offset) { + date.setMonth(date.getMonth() + offset); + }, function(date) { + return date.getMonth(); + }); + d3_time.months = d3_time.month.range; + d3_time.months.utc = d3_time.month.utc.range; + function d3_time_scale(linear, methods, format) { + function scale(x) { + return linear(x); + } + scale.invert = function(x) { + return d3_time_scaleDate(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return linear.domain().map(d3_time_scaleDate); + linear.domain(x); + return scale; + }; + function tickMethod(extent, count) { + var span = extent[1] - extent[0], target = span / count, i = d3.bisect(d3_time_scaleSteps, target); + return i == d3_time_scaleSteps.length ? [ methods.year, d3_scale_linearTickRange(extent.map(function(d) { + return d / 31536e6; + }), count)[2] ] : !i ? [ d3_time_scaleMilliseconds, d3_scale_linearTickRange(extent, count)[2] ] : methods[target / d3_time_scaleSteps[i - 1] < d3_time_scaleSteps[i] / target ? i - 1 : i]; + } + scale.nice = function(interval, skip) { + var domain = scale.domain(), extent = d3_scaleExtent(domain), method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" && tickMethod(extent, interval); + if (method) interval = method[0], skip = method[1]; + function skipped(date) { + return !isNaN(date) && !interval.range(date, d3_time_scaleDate(+date + 1), skip).length; + } + return scale.domain(d3_scale_nice(domain, skip > 1 ? { + floor: function(date) { + while (skipped(date = interval.floor(date))) date = d3_time_scaleDate(date - 1); + return date; + }, + ceil: function(date) { + while (skipped(date = interval.ceil(date))) date = d3_time_scaleDate(+date + 1); + return date; + } + } : interval)); + }; + scale.ticks = function(interval, skip) { + var extent = d3_scaleExtent(scale.domain()), method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" ? tickMethod(extent, interval) : !interval.range && [ { + range: interval + }, skip ]; + if (method) interval = method[0], skip = method[1]; + return interval.range(extent[0], d3_time_scaleDate(+extent[1] + 1), skip < 1 ? 1 : skip); + }; + scale.tickFormat = function() { + return format; + }; + scale.copy = function() { + return d3_time_scale(linear.copy(), methods, format); + }; + return d3_scale_linearRebind(scale, linear); + } + function d3_time_scaleDate(t) { + return new Date(t); + } + var d3_time_scaleSteps = [ 1e3, 5e3, 15e3, 3e4, 6e4, 3e5, 9e5, 18e5, 36e5, 108e5, 216e5, 432e5, 864e5, 1728e5, 6048e5, 2592e6, 7776e6, 31536e6 ]; + var d3_time_scaleLocalMethods = [ [ d3_time.second, 1 ], [ d3_time.second, 5 ], [ d3_time.second, 15 ], [ d3_time.second, 30 ], [ d3_time.minute, 1 ], [ d3_time.minute, 5 ], [ d3_time.minute, 15 ], [ d3_time.minute, 30 ], [ d3_time.hour, 1 ], [ d3_time.hour, 3 ], [ d3_time.hour, 6 ], [ d3_time.hour, 12 ], [ d3_time.day, 1 ], [ d3_time.day, 2 ], [ d3_time.week, 1 ], [ d3_time.month, 1 ], [ d3_time.month, 3 ], [ d3_time.year, 1 ] ]; + var d3_time_scaleLocalFormat = d3_time_format.multi([ [ ".%L", function(d) { + return d.getMilliseconds(); + } ], [ ":%S", function(d) { + return d.getSeconds(); + } ], [ "%I:%M", function(d) { + return d.getMinutes(); + } ], [ "%I %p", function(d) { + return d.getHours(); + } ], [ "%a %d", function(d) { + return d.getDay() && d.getDate() != 1; + } ], [ "%b %d", function(d) { + return d.getDate() != 1; + } ], [ "%B", function(d) { + return d.getMonth(); + } ], [ "%Y", d3_true ] ]); + var d3_time_scaleMilliseconds = { + range: function(start, stop, step) { + return d3.range(Math.ceil(start / step) * step, +stop, step).map(d3_time_scaleDate); + }, + floor: d3_identity, + ceil: d3_identity + }; + d3_time_scaleLocalMethods.year = d3_time.year; + d3_time.scale = function() { + return d3_time_scale(d3.scale.linear(), d3_time_scaleLocalMethods, d3_time_scaleLocalFormat); + }; + var d3_time_scaleUtcMethods = d3_time_scaleLocalMethods.map(function(m) { + return [ m[0].utc, m[1] ]; + }); + var d3_time_scaleUtcFormat = d3_time_formatUtc.multi([ [ ".%L", function(d) { + return d.getUTCMilliseconds(); + } ], [ ":%S", function(d) { + return d.getUTCSeconds(); + } ], [ "%I:%M", function(d) { + return d.getUTCMinutes(); + } ], [ "%I %p", function(d) { + return d.getUTCHours(); + } ], [ "%a %d", function(d) { + return d.getUTCDay() && d.getUTCDate() != 1; + } ], [ "%b %d", function(d) { + return d.getUTCDate() != 1; + } ], [ "%B", function(d) { + return d.getUTCMonth(); + } ], [ "%Y", d3_true ] ]); + d3_time_scaleUtcMethods.year = d3_time.year.utc; + d3_time.scale.utc = function() { + return d3_time_scale(d3.scale.linear(), d3_time_scaleUtcMethods, d3_time_scaleUtcFormat); + }; + d3.text = d3_xhrType(function(request) { + return request.responseText; + }); + d3.json = function(url, callback) { + return d3_xhr(url, "application/json", d3_json, callback); + }; + function d3_json(request) { + return JSON.parse(request.responseText); + } + d3.html = function(url, callback) { + return d3_xhr(url, "text/html", d3_html, callback); + }; + function d3_html(request) { + var range = d3_document.createRange(); + range.selectNode(d3_document.body); + return range.createContextualFragment(request.responseText); + } + d3.xml = d3_xhrType(function(request) { + return request.responseXML; + }); + if (typeof define === "function" && define.amd) this.d3 = d3, define(d3); else if (typeof module === "object" && module.exports) module.exports = d3; else this.d3 = d3; +}();
\ No newline at end of file diff --git a/third_party/js/d3/moz.yaml b/third_party/js/d3/moz.yaml new file mode 100644 index 0000000000..194e512c91 --- /dev/null +++ b/third_party/js/d3/moz.yaml @@ -0,0 +1,38 @@ +# Version of this schema +schema: 1 + +bugzilla: + # Bugzilla product and component for this directory and subdirectories + product: Toolkit + component: General + +# Document the source of externally hosted code +origin: + + # Short name of the package/library + name: D3.js + + description: JavaScript library for visualizing data using web standards + + # Full URL for the package's homepage/etc + # Usually different from repository url + url: https://d3js.org/ + + # Human-readable identifier for this version/release + # Generally "version NNN", "tag SSS", "bookmark SSS" + release: version 3.4.2 + + # Revision to pull in + # Must be a long or short commit SHA (long preferred) + revision: 04fa5dd3856de768b43b4aac9e34c112f1227a17 + + # The package's license, where possible using the mnemonic from + # https://spdx.org/licenses/ + # Multiple licenses can be specified (as a YAML list) + # A "LICENSE" file must exist containing the full license text + license: BSD-3-Clause + + # If the package's license is specified in a particular file, + # this is the name of the file. + # optional + license-file: LICENSE |