Source code

Revision control

Copy as Markdown

Other Tools

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