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