CertificationRequest.ts (16909B)
1 import * as asn1js from "asn1js"; 2 import * as pvtsutils from "pvtsutils"; 3 import * as pvutils from "pvutils"; 4 import * as common from "./common"; 5 import { PublicKeyInfo, PublicKeyInfoJson } from "./PublicKeyInfo"; 6 import { RelativeDistinguishedNames, RelativeDistinguishedNamesJson, RelativeDistinguishedNamesSchema } from "./RelativeDistinguishedNames"; 7 import { AlgorithmIdentifier, AlgorithmIdentifierJson } from "./AlgorithmIdentifier"; 8 import { Attribute, AttributeJson, AttributeSchema } from "./Attribute"; 9 import * as Schema from "./Schema"; 10 import { CryptoEnginePublicKeyParams } from "./CryptoEngine/CryptoEngineInterface"; 11 import { AsnError } from "./errors"; 12 import { PkiObject, PkiObjectParameters } from "./PkiObject"; 13 import { EMPTY_BUFFER } from "./constants"; 14 15 const TBS = "tbs"; 16 const VERSION = "version"; 17 const SUBJECT = "subject"; 18 const SPKI = "subjectPublicKeyInfo"; 19 const ATTRIBUTES = "attributes"; 20 const SIGNATURE_ALGORITHM = "signatureAlgorithm"; 21 const SIGNATURE_VALUE = "signatureValue"; 22 const CSR_INFO = "CertificationRequestInfo"; 23 const CSR_INFO_VERSION = `${CSR_INFO}.version`; 24 const CSR_INFO_SUBJECT = `${CSR_INFO}.subject`; 25 const CSR_INFO_SPKI = `${CSR_INFO}.subjectPublicKeyInfo`; 26 const CSR_INFO_ATTRS = `${CSR_INFO}.attributes`; 27 const CLEAR_PROPS = [ 28 CSR_INFO, 29 CSR_INFO_VERSION, 30 CSR_INFO_SUBJECT, 31 CSR_INFO_SPKI, 32 CSR_INFO_ATTRS, 33 SIGNATURE_ALGORITHM, 34 SIGNATURE_VALUE 35 ]; 36 37 export interface ICertificationRequest { 38 /** 39 * Value being signed 40 */ 41 tbs: ArrayBuffer; 42 /** 43 * Version number. It should be 0 44 */ 45 version: number; 46 /** 47 * Distinguished name of the certificate subject 48 */ 49 subject: RelativeDistinguishedNames; 50 /** 51 * Information about the public key being certified 52 */ 53 subjectPublicKeyInfo: PublicKeyInfo; 54 /** 55 * Collection of attributes providing additional information about the subject of the certificate 56 */ 57 attributes?: Attribute[]; 58 59 /** 60 * signature algorithm (and any associated parameters) under which the certification-request information is signed 61 */ 62 signatureAlgorithm: AlgorithmIdentifier; 63 /** 64 * result of signing the certification request information with the certification request subject's private key 65 */ 66 signatureValue: asn1js.BitString; 67 } 68 69 /** 70 * JSON representation of {@link CertificationRequest} 71 */ 72 export interface CertificationRequestJson { 73 tbs: string; 74 version: number; 75 subject: RelativeDistinguishedNamesJson; 76 subjectPublicKeyInfo: PublicKeyInfoJson | JsonWebKey; 77 attributes?: AttributeJson[]; 78 signatureAlgorithm: AlgorithmIdentifierJson; 79 signatureValue: asn1js.BitStringJson; 80 } 81 82 export interface CertificationRequestInfoParameters { 83 names?: { 84 blockName?: string; 85 CertificationRequestInfo?: string; 86 CertificationRequestInfoVersion?: string; 87 subject?: RelativeDistinguishedNamesSchema; 88 CertificationRequestInfoAttributes?: string; 89 attributes?: AttributeSchema; 90 }; 91 } 92 93 function CertificationRequestInfo(parameters: CertificationRequestInfoParameters = {}) { 94 //CertificationRequestInfo ::= SEQUENCE { 95 // version INTEGER { v1(0) } (v1,...), 96 // subject Name, 97 // subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }}, 98 // attributes [0] Attributes{{ CRIAttributes }} 99 //} 100 101 const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); 102 103 return (new asn1js.Sequence({ 104 name: (names.CertificationRequestInfo || CSR_INFO), 105 value: [ 106 new asn1js.Integer({ name: (names.CertificationRequestInfoVersion || CSR_INFO_VERSION) }), 107 RelativeDistinguishedNames.schema(names.subject || { 108 names: { 109 blockName: CSR_INFO_SUBJECT 110 } 111 }), 112 PublicKeyInfo.schema({ 113 names: { 114 blockName: CSR_INFO_SPKI 115 } 116 }), 117 new asn1js.Constructed({ 118 optional: true, 119 idBlock: { 120 tagClass: 3, // CONTEXT-SPECIFIC 121 tagNumber: 0 // [0] 122 }, 123 value: [ 124 new asn1js.Repeated({ 125 optional: true, // Because OpenSSL makes wrong ATTRIBUTES field 126 name: (names.CertificationRequestInfoAttributes || CSR_INFO_ATTRS), 127 value: Attribute.schema(names.attributes || {}) 128 }) 129 ] 130 }) 131 ] 132 })); 133 } 134 135 export type CertificationRequestParameters = PkiObjectParameters & Partial<ICertificationRequest>; 136 137 /** 138 * Represents the CertificationRequest structure described in [RFC2986](https://datatracker.ietf.org/doc/html/rfc2986) 139 * 140 * @example The following example demonstrates how to parse PKCS#11 certification request 141 * and verify its challenge password extension and signature value 142 * ```js 143 * const pkcs10 = pkijs.CertificationRequest.fromBER(pkcs10Raw); 144 * 145 * // Get and validate challenge password extension 146 * if (pkcs10.attributes) { 147 * const attrExtensions = pkcs10.attributes.find(o => o.type === "1.2.840.113549.1.9.14"); // pkcs-9-at-extensionRequest 148 * if (attrExtensions) { 149 * const extensions = new pkijs.Extensions({ schema: attrExtensions.values[0] }); 150 * for (const extension of extensions.extensions) { 151 * if (extension.extnID === "1.2.840.113549.1.9.7") { // pkcs-9-at-challengePassword 152 * const asn = asn1js.fromBER(extension.extnValue.valueBlock.valueHex); 153 * if (asn.result.valueBlock.value !== "passwordChallenge") { 154 * throw new Error("PKCS#11 certification request is invalid. Challenge password is incorrect"); 155 * } 156 * } 157 * } 158 * } 159 * } 160 * 161 * // Verify signature value 162 * const ok = await pkcs10.verify(); 163 * if (!ok) { 164 * throw Error("PKCS#11 certification request is invalid. Signature is wrong") 165 * } 166 * ``` 167 * 168 * @example The following example demonstrates how to create PKCS#11 certification request 169 * ```js 170 * // Get a "crypto" extension 171 * const crypto = pkijs.getCrypto(true); 172 * 173 * const pkcs10 = new pkijs.CertificationRequest(); 174 * 175 * pkcs10.subject.typesAndValues.push(new pkijs.AttributeTypeAndValue({ 176 * type: "2.5.4.3", 177 * value: new asn1js.Utf8String({ value: "Test" }) 178 * })); 179 * 180 * 181 * await pkcs10.subjectPublicKeyInfo.importKey(keys.publicKey); 182 * 183 * pkcs10.attributes = []; 184 * 185 * // Subject Alternative Name 186 * const altNames = new pkijs.GeneralNames({ 187 * names: [ 188 * new pkijs.GeneralName({ // email 189 * type: 1, 190 * value: "email@address.com" 191 * }), 192 * new pkijs.GeneralName({ // domain 193 * type: 2, 194 * value: "www.domain.com" 195 * }), 196 * ] 197 * }); 198 * 199 * // SubjectKeyIdentifier 200 * const subjectKeyIdentifier = await crypto.digest({ name: "SHA-1" }, pkcs10.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHex); 201 * 202 * pkcs10.attributes.push(new pkijs.Attribute({ 203 * type: "1.2.840.113549.1.9.14", // pkcs-9-at-extensionRequest 204 * values: [(new pkijs.Extensions({ 205 * extensions: [ 206 * new pkijs.Extension({ 207 * extnID: "2.5.29.14", // id-ce-subjectKeyIdentifier 208 * critical: false, 209 * extnValue: (new asn1js.OctetString({ valueHex: subjectKeyIdentifier })).toBER(false) 210 * }), 211 * new pkijs.Extension({ 212 * extnID: "2.5.29.17", // id-ce-subjectAltName 213 * critical: false, 214 * extnValue: altNames.toSchema().toBER(false) 215 * }), 216 * new pkijs.Extension({ 217 * extnID: "1.2.840.113549.1.9.7", // pkcs-9-at-challengePassword 218 * critical: false, 219 * extnValue: (new asn1js.PrintableString({ value: "passwordChallenge" })).toBER(false) 220 * }) 221 * ] 222 * })).toSchema()] 223 * })); 224 * 225 * // Signing final PKCS#10 request 226 * await pkcs10.sign(keys.privateKey, "SHA-256"); 227 * 228 * const pkcs10Raw = pkcs10.toSchema(true).toBER(); 229 * ``` 230 */ 231 export class CertificationRequest extends PkiObject implements ICertificationRequest { 232 233 public static override CLASS_NAME = "CertificationRequest"; 234 235 public tbsView!: Uint8Array; 236 /** 237 * @deprecated Since version 3.0.0 238 */ 239 public get tbs(): ArrayBuffer { 240 return pvtsutils.BufferSourceConverter.toArrayBuffer(this.tbsView); 241 } 242 243 /** 244 * @deprecated Since version 3.0.0 245 */ 246 public set tbs(value: ArrayBuffer) { 247 this.tbsView = new Uint8Array(value); 248 } 249 public version!: number; 250 public subject!: RelativeDistinguishedNames; 251 public subjectPublicKeyInfo!: PublicKeyInfo; 252 public attributes?: Attribute[]; 253 public signatureAlgorithm!: AlgorithmIdentifier; 254 public signatureValue!: asn1js.BitString; 255 256 /** 257 * Initializes a new instance of the {@link CertificationRequest} class 258 * @param parameters Initialization parameters 259 */ 260 constructor(parameters: CertificationRequestParameters = {}) { 261 super(); 262 263 this.tbsView = new Uint8Array(pvutils.getParametersValue(parameters, TBS, CertificationRequest.defaultValues(TBS))); 264 this.version = pvutils.getParametersValue(parameters, VERSION, CertificationRequest.defaultValues(VERSION)); 265 this.subject = pvutils.getParametersValue(parameters, SUBJECT, CertificationRequest.defaultValues(SUBJECT)); 266 this.subjectPublicKeyInfo = pvutils.getParametersValue(parameters, SPKI, CertificationRequest.defaultValues(SPKI)); 267 if (ATTRIBUTES in parameters) { 268 this.attributes = pvutils.getParametersValue(parameters, ATTRIBUTES, CertificationRequest.defaultValues(ATTRIBUTES)); 269 } 270 this.signatureAlgorithm = pvutils.getParametersValue(parameters, SIGNATURE_ALGORITHM, CertificationRequest.defaultValues(SIGNATURE_ALGORITHM)); 271 this.signatureValue = pvutils.getParametersValue(parameters, SIGNATURE_VALUE, CertificationRequest.defaultValues(SIGNATURE_VALUE)); 272 273 if (parameters.schema) { 274 this.fromSchema(parameters.schema); 275 } 276 } 277 278 /** 279 * Returns default values for all class members 280 * @param memberName String name for a class member 281 * @returns Default value 282 */ 283 public static override defaultValues(memberName: typeof TBS): ArrayBuffer; 284 public static override defaultValues(memberName: typeof VERSION): number; 285 public static override defaultValues(memberName: typeof SUBJECT): RelativeDistinguishedNames; 286 public static override defaultValues(memberName: typeof SPKI): PublicKeyInfo; 287 public static override defaultValues(memberName: typeof ATTRIBUTES): Attribute[]; 288 public static override defaultValues(memberName: typeof SIGNATURE_ALGORITHM): AlgorithmIdentifier; 289 public static override defaultValues(memberName: typeof SIGNATURE_VALUE): asn1js.BitString; 290 public static override defaultValues(memberName: string): any { 291 switch (memberName) { 292 case TBS: 293 return EMPTY_BUFFER; 294 case VERSION: 295 return 0; 296 case SUBJECT: 297 return new RelativeDistinguishedNames(); 298 case SPKI: 299 return new PublicKeyInfo(); 300 case ATTRIBUTES: 301 return []; 302 case SIGNATURE_ALGORITHM: 303 return new AlgorithmIdentifier(); 304 case SIGNATURE_VALUE: 305 return new asn1js.BitString(); 306 default: 307 return super.defaultValues(memberName); 308 } 309 } 310 311 /** 312 * @inheritdoc 313 * @asn ASN.1 schema 314 * ```asn 315 * CertificationRequest ::= SEQUENCE { 316 * certificationRequestInfo CertificationRequestInfo, 317 * signatureAlgorithm AlgorithmIdentifier{{ SignatureAlgorithms }}, 318 * signature BIT STRING 319 * } 320 *``` 321 */ 322 static override schema(parameters: Schema.SchemaParameters<{ 323 certificationRequestInfo?: CertificationRequestInfoParameters; 324 signatureAlgorithm?: string; 325 signatureValue?: string; 326 }> = {}): Schema.SchemaType { 327 const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); 328 329 return (new asn1js.Sequence({ 330 value: [ 331 CertificationRequestInfo(names.certificationRequestInfo || {}), 332 new asn1js.Sequence({ 333 name: (names.signatureAlgorithm || SIGNATURE_ALGORITHM), 334 value: [ 335 new asn1js.ObjectIdentifier(), 336 new asn1js.Any({ optional: true }) 337 ] 338 }), 339 new asn1js.BitString({ name: (names.signatureValue || SIGNATURE_VALUE) }) 340 ] 341 })); 342 } 343 344 public fromSchema(schema: Schema.SchemaType): void { 345 // Clear input data first 346 pvutils.clearProps(schema, CLEAR_PROPS); 347 348 // Check the schema is valid 349 const asn1 = asn1js.compareSchema(schema, 350 schema, 351 CertificationRequest.schema() 352 ); 353 AsnError.assertSchema(asn1, this.className); 354 355 // Get internal properties from parsed schema 356 this.tbsView = (asn1.result.CertificationRequestInfo as asn1js.Sequence).valueBeforeDecodeView; 357 this.version = asn1.result[CSR_INFO_VERSION].valueBlock.valueDec; 358 this.subject = new RelativeDistinguishedNames({ schema: asn1.result[CSR_INFO_SUBJECT] }); 359 this.subjectPublicKeyInfo = new PublicKeyInfo({ schema: asn1.result[CSR_INFO_SPKI] }); 360 if (CSR_INFO_ATTRS in asn1.result) { 361 this.attributes = Array.from(asn1.result[CSR_INFO_ATTRS], element => new Attribute({ schema: element })); 362 } 363 this.signatureAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.signatureAlgorithm }); 364 this.signatureValue = asn1.result.signatureValue; 365 } 366 367 /** 368 * Aux function making ASN1js Sequence from current TBS 369 * @returns 370 */ 371 protected encodeTBS(): asn1js.Sequence { 372 //#region Create array for output sequence 373 const outputArray = [ 374 new asn1js.Integer({ value: this.version }), 375 this.subject.toSchema(), 376 this.subjectPublicKeyInfo.toSchema() 377 ]; 378 379 if (ATTRIBUTES in this) { 380 outputArray.push(new asn1js.Constructed({ 381 idBlock: { 382 tagClass: 3, // CONTEXT-SPECIFIC 383 tagNumber: 0 // [0] 384 }, 385 value: Array.from(this.attributes || [], o => o.toSchema()) 386 })); 387 } 388 //#endregion 389 390 return (new asn1js.Sequence({ 391 value: outputArray 392 })); 393 } 394 395 public toSchema(encodeFlag = false): asn1js.Sequence { 396 let tbsSchema; 397 398 if (encodeFlag === false) { 399 if (this.tbsView.byteLength === 0) { // No stored TBS part 400 return CertificationRequest.schema(); 401 } 402 403 const asn1 = asn1js.fromBER(this.tbsView); 404 AsnError.assert(asn1, "PKCS#10 Certificate Request"); 405 406 tbsSchema = asn1.result; 407 } else { 408 tbsSchema = this.encodeTBS(); 409 } 410 411 //#region Construct and return new ASN.1 schema for this object 412 return (new asn1js.Sequence({ 413 value: [ 414 tbsSchema, 415 this.signatureAlgorithm.toSchema(), 416 this.signatureValue 417 ] 418 })); 419 //#endregion 420 } 421 422 public toJSON(): CertificationRequestJson { 423 const object: CertificationRequestJson = { 424 tbs: pvtsutils.Convert.ToHex(this.tbsView), 425 version: this.version, 426 subject: this.subject.toJSON(), 427 subjectPublicKeyInfo: this.subjectPublicKeyInfo.toJSON(), 428 signatureAlgorithm: this.signatureAlgorithm.toJSON(), 429 signatureValue: this.signatureValue.toJSON(), 430 }; 431 432 if (ATTRIBUTES in this) { 433 object.attributes = Array.from(this.attributes || [], o => o.toJSON()); 434 } 435 436 return object; 437 } 438 439 /** 440 * Makes signature for current certification request 441 * @param privateKey WebCrypto private key 442 * @param hashAlgorithm String representing current hashing algorithm 443 * @param crypto Crypto engine 444 */ 445 async sign(privateKey: CryptoKey, hashAlgorithm = "SHA-1", crypto = common.getCrypto(true)): Promise<void> { 446 // Initial checking 447 if (!privateKey) { 448 throw new Error("Need to provide a private key for signing"); 449 } 450 451 //#region Get a "default parameters" for current algorithm and set correct signature algorithm 452 const signatureParams = await crypto.getSignatureParameters(privateKey, hashAlgorithm); 453 const parameters = signatureParams.parameters; 454 this.signatureAlgorithm = signatureParams.signatureAlgorithm; 455 //#endregion 456 457 //#region Create TBS data for signing 458 this.tbsView = new Uint8Array(this.encodeTBS().toBER()); 459 //#endregion 460 461 //#region Signing TBS data on provided private key 462 const signature = await crypto.signWithPrivateKey(this.tbsView as BufferSource, privateKey, parameters as any); 463 this.signatureValue = new asn1js.BitString({ valueHex: signature }); 464 //#endregion 465 } 466 467 /** 468 * Verify existing certification request signature 469 * @param crypto Crypto engine 470 * @returns Returns `true` if signature value is valid, otherwise `false` 471 */ 472 public async verify(crypto = common.getCrypto(true)): Promise<boolean> { 473 return crypto.verifyWithPublicKey(this.tbsView as BufferSource, this.signatureValue, this.subjectPublicKeyInfo, this.signatureAlgorithm); 474 } 475 476 /** 477 * Importing public key for current certificate request 478 * @param parameters 479 * @param crypto Crypto engine 480 * @returns WebCrypt public key 481 */ 482 public async getPublicKey(parameters?: CryptoEnginePublicKeyParams, crypto = common.getCrypto(true)): Promise<CryptoKey> { 483 return crypto.getPublicKey(this.subjectPublicKeyInfo, this.signatureAlgorithm, parameters); 484 } 485 486 }