summaryrefslogtreecommitdiffstats
path: root/third_party/js/PKI.js/src/Certificate.ts
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/js/PKI.js/src/Certificate.ts')
-rw-r--r--third_party/js/PKI.js/src/Certificate.ts828
1 files changed, 828 insertions, 0 deletions
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;
+}