tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }