diff options
Diffstat (limited to 'third_party/js/PKI.js/src/CertificationRequest.ts')
-rw-r--r-- | third_party/js/PKI.js/src/CertificationRequest.ts | 487 |
1 files changed, 487 insertions, 0 deletions
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); + } + +} + |