tor-browser

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

SignedData.ts (34557B)


      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 { AlgorithmIdentifier, AlgorithmIdentifierJson } from "./AlgorithmIdentifier";
      6 import { EncapsulatedContentInfo, EncapsulatedContentInfoJson, EncapsulatedContentInfoSchema } from "./EncapsulatedContentInfo";
      7 import { Certificate, checkCA } from "./Certificate";
      8 import { CertificateRevocationList, CertificateRevocationListJson } from "./CertificateRevocationList";
      9 import { OtherRevocationInfoFormat, OtherRevocationInfoFormatJson } from "./OtherRevocationInfoFormat";
     10 import { SignerInfo, SignerInfoJson } from "./SignerInfo";
     11 import { CertificateSet, CertificateSetItem, CertificateSetItemJson } from "./CertificateSet";
     12 import { RevocationInfoChoices, RevocationInfoChoicesSchema } from "./RevocationInfoChoices";
     13 import { IssuerAndSerialNumber } from "./IssuerAndSerialNumber";
     14 import { TSTInfo } from "./TSTInfo";
     15 import { CertificateChainValidationEngine, CertificateChainValidationEngineParameters, FindIssuerCallback, FindOriginCallback } from "./CertificateChainValidationEngine";
     16 import { BasicOCSPResponse, BasicOCSPResponseJson } from "./BasicOCSPResponse";
     17 import { OtherCertificateFormat } from "./OtherCertificateFormat";
     18 import { AttributeCertificateV1 } from "./AttributeCertificateV1";
     19 import { AttributeCertificateV2 } from "./AttributeCertificateV2";
     20 import * as Schema from "./Schema";
     21 import { id_ContentType_Data, id_eContentType_TSTInfo, id_PKIX_OCSP_Basic } from "./ObjectIdentifiers";
     22 import { AsnError } from "./errors";
     23 import { PkiObject, PkiObjectParameters } from "./PkiObject";
     24 import { EMPTY_BUFFER, EMPTY_STRING } from "./constants";
     25 import { ICryptoEngine } from "./CryptoEngine/CryptoEngineInterface";
     26 
     27 export type SignedDataCRL = CertificateRevocationList | OtherRevocationInfoFormat;
     28 export type SignedDataCRLJson = CertificateRevocationListJson | OtherRevocationInfoFormatJson;
     29 
     30 const VERSION = "version";
     31 const DIGEST_ALGORITHMS = "digestAlgorithms";
     32 const ENCAP_CONTENT_INFO = "encapContentInfo";
     33 const CERTIFICATES = "certificates";
     34 const CRLS = "crls";
     35 const SIGNER_INFOS = "signerInfos";
     36 const OCSPS = "ocsps";
     37 const SIGNED_DATA = "SignedData";
     38 const SIGNED_DATA_VERSION = `${SIGNED_DATA}.${VERSION}`;
     39 const SIGNED_DATA_DIGEST_ALGORITHMS = `${SIGNED_DATA}.${DIGEST_ALGORITHMS}`;
     40 const SIGNED_DATA_ENCAP_CONTENT_INFO = `${SIGNED_DATA}.${ENCAP_CONTENT_INFO}`;
     41 const SIGNED_DATA_CERTIFICATES = `${SIGNED_DATA}.${CERTIFICATES}`;
     42 const SIGNED_DATA_CRLS = `${SIGNED_DATA}.${CRLS}`;
     43 const SIGNED_DATA_SIGNER_INFOS = `${SIGNED_DATA}.${SIGNER_INFOS}`;
     44 const CLEAR_PROPS = [
     45  SIGNED_DATA_VERSION,
     46  SIGNED_DATA_DIGEST_ALGORITHMS,
     47  SIGNED_DATA_ENCAP_CONTENT_INFO,
     48  SIGNED_DATA_CERTIFICATES,
     49  SIGNED_DATA_CRLS,
     50  SIGNED_DATA_SIGNER_INFOS
     51 ];
     52 
     53 export interface ISignedData {
     54  version: number;
     55  digestAlgorithms: AlgorithmIdentifier[];
     56  encapContentInfo: EncapsulatedContentInfo;
     57  certificates?: CertificateSetItem[];
     58  crls?: SignedDataCRL[];
     59  ocsps?: BasicOCSPResponse[];
     60  signerInfos: SignerInfo[];
     61 }
     62 
     63 export interface SignedDataJson {
     64  version: number;
     65  digestAlgorithms: AlgorithmIdentifierJson[];
     66  encapContentInfo: EncapsulatedContentInfoJson;
     67  certificates?: CertificateSetItemJson[];
     68  crls?: SignedDataCRLJson[];
     69  ocsps?: BasicOCSPResponseJson[];
     70  signerInfos: SignerInfoJson[];
     71 }
     72 
     73 export type SignedDataParameters = PkiObjectParameters & Partial<ISignedData>;
     74 
     75 export interface SignedDataVerifyParams {
     76  signer?: number;
     77  data?: ArrayBuffer;
     78  trustedCerts?: Certificate[];
     79  checkDate?: Date;
     80  checkChain?: boolean;
     81  passedWhenNotRevValues?: boolean;
     82  extendedMode?: boolean;
     83  findOrigin?: FindOriginCallback | null;
     84  findIssuer?: FindIssuerCallback | null;
     85 }
     86 
     87 export interface SignedDataVerifyErrorParams {
     88  message: string;
     89  date?: Date;
     90  code?: number;
     91  timestampSerial?: ArrayBuffer | null;
     92  signatureVerified?: boolean | null;
     93  signerCertificate?: Certificate | null;
     94  signerCertificateVerified?: boolean | null;
     95  certificatePath?: Certificate[];
     96 }
     97 
     98 export interface SignedDataVerifyResult {
     99  message: string;
    100  date?: Date;
    101  code?: number;
    102  timestampSerial?: ArrayBuffer | null;
    103  signatureVerified?: boolean | null;
    104  signerCertificate?: Certificate | null;
    105  signerCertificateVerified?: boolean | null;
    106  certificatePath: Certificate[];
    107 }
    108 
    109 export class SignedDataVerifyError extends Error implements SignedDataVerifyResult {
    110 
    111  public date: Date;
    112  public code: number;
    113  public signatureVerified: boolean | null;
    114  public signerCertificate: Certificate | null;
    115  public signerCertificateVerified: boolean | null;
    116  public timestampSerial: ArrayBuffer | null;
    117  public certificatePath: Certificate[];
    118 
    119  constructor({
    120    message,
    121    code = 0,
    122    date = new Date(),
    123    signatureVerified = null,
    124    signerCertificate = null,
    125    signerCertificateVerified = null,
    126    timestampSerial = null,
    127    certificatePath = [],
    128  }: SignedDataVerifyErrorParams) {
    129    super(message);
    130    this.name = "SignedDataVerifyError";
    131 
    132    this.date = date;
    133    this.code = code;
    134    this.timestampSerial = timestampSerial;
    135    this.signatureVerified = signatureVerified;
    136    this.signerCertificate = signerCertificate;
    137    this.signerCertificateVerified = signerCertificateVerified;
    138    this.certificatePath = certificatePath;
    139 
    140  }
    141 }
    142 
    143 /**
    144 * Represents the SignedData structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652)
    145 *
    146 * @example The following example demonstrates how to create and sign CMS Signed Data
    147 * ```js
    148 * // Create a new CMS Signed Data
    149 * const cmsSigned = new pkijs.SignedData({
    150 *   encapContentInfo: new pkijs.EncapsulatedContentInfo({
    151 *     eContentType: pkijs.ContentInfo.DATA,, // "data" content type
    152 *     eContent: new asn1js.OctetString({ valueHex: buffer })
    153 *   }),
    154 *   signerInfos: [
    155 *     new pkijs.SignerInfo({
    156 *       sid: new pkijs.IssuerAndSerialNumber({
    157 *         issuer: cert.issuer,
    158 *         serialNumber: cert.serialNumber
    159 *       })
    160 *     })
    161 *   ],
    162 *   // Signer certificate for chain validation
    163 *   certificates: [cert]
    164 * });
    165 *
    166 * await cmsSigned.sign(keys.privateKey, 0, "SHA-256");
    167 *
    168 * // Add Signed Data to Content Info
    169 * const cms = new pkijs.ContentInfo({
    170 *   contentType: pkijs.ContentInfo.SIGNED_DATA,,
    171 *   content: cmsSigned.toSchema(true),
    172 * });
    173 *
    174 * // Encode CMS to ASN.1
    175 * const cmsRaw = cms.toSchema().toBER();
    176 * ```
    177 *
    178 * @example The following example demonstrates how to verify CMS Signed Data
    179 * ```js
    180 * // Parse CMS and detect it's Signed Data
    181 * const cms = pkijs.ContentInfo.fromBER(cmsRaw);
    182 * if (cms.contentType !== pkijs.ContentInfo.SIGNED_DATA) {
    183 *   throw new Error("CMS is not Signed Data");
    184 * }
    185 *
    186 * // Read Signed Data
    187 * const signedData = new pkijs.SignedData({ schema: cms.content });
    188 *
    189 * // Verify Signed Data signature
    190 * const ok = await signedData.verify({
    191 *   signer: 0,
    192 *   checkChain: true,
    193 *   trustedCerts: [trustedCert],
    194 * });
    195 *
    196 * if (!ok) {
    197 *   throw new Error("CMS signature is invalid")
    198 * }
    199 * ```
    200 */
    201 export class SignedData extends PkiObject implements ISignedData {
    202 
    203  public static override CLASS_NAME = "SignedData";
    204 
    205  public static ID_DATA: typeof id_ContentType_Data = id_ContentType_Data;
    206 
    207  public version!: number;
    208  public digestAlgorithms!: AlgorithmIdentifier[];
    209  public encapContentInfo!: EncapsulatedContentInfo;
    210  public certificates?: CertificateSetItem[];
    211  public crls?: SignedDataCRL[];
    212  public ocsps?: BasicOCSPResponse[];
    213  public signerInfos!: SignerInfo[];
    214 
    215  /**
    216   * Initializes a new instance of the {@link SignedData} class
    217   * @param parameters Initialization parameters
    218   */
    219  constructor(parameters: SignedDataParameters = {}) {
    220    super();
    221 
    222    this.version = pvutils.getParametersValue(parameters, VERSION, SignedData.defaultValues(VERSION));
    223    this.digestAlgorithms = pvutils.getParametersValue(parameters, DIGEST_ALGORITHMS, SignedData.defaultValues(DIGEST_ALGORITHMS));
    224    this.encapContentInfo = pvutils.getParametersValue(parameters, ENCAP_CONTENT_INFO, SignedData.defaultValues(ENCAP_CONTENT_INFO));
    225    if (CERTIFICATES in parameters) {
    226      this.certificates = pvutils.getParametersValue(parameters, CERTIFICATES, SignedData.defaultValues(CERTIFICATES));
    227    }
    228    if (CRLS in parameters) {
    229      this.crls = pvutils.getParametersValue(parameters, CRLS, SignedData.defaultValues(CRLS));
    230    }
    231    if (OCSPS in parameters) {
    232      this.ocsps = pvutils.getParametersValue(parameters, OCSPS, SignedData.defaultValues(OCSPS));
    233    }
    234    this.signerInfos = pvutils.getParametersValue(parameters, SIGNER_INFOS, SignedData.defaultValues(SIGNER_INFOS));
    235 
    236    if (parameters.schema) {
    237      this.fromSchema(parameters.schema);
    238    }
    239  }
    240 
    241  /**
    242   * Returns default values for all class members
    243   * @param memberName String name for a class member
    244   * @returns Default value
    245   */
    246  public static override defaultValues(memberName: typeof VERSION): number;
    247  public static override defaultValues(memberName: typeof DIGEST_ALGORITHMS): AlgorithmIdentifier[];
    248  public static override defaultValues(memberName: typeof ENCAP_CONTENT_INFO): EncapsulatedContentInfo;
    249  public static override defaultValues(memberName: typeof CERTIFICATES): CertificateSetItem[];
    250  public static override defaultValues(memberName: typeof CRLS): SignedDataCRL[];
    251  public static override defaultValues(memberName: typeof OCSPS): BasicOCSPResponse[];
    252  public static override defaultValues(memberName: typeof SIGNER_INFOS): SignerInfo[];
    253  public static override defaultValues(memberName: string): any {
    254    switch (memberName) {
    255      case VERSION:
    256        return 0;
    257      case DIGEST_ALGORITHMS:
    258        return [];
    259      case ENCAP_CONTENT_INFO:
    260        return new EncapsulatedContentInfo();
    261      case CERTIFICATES:
    262        return [];
    263      case CRLS:
    264        return [];
    265      case OCSPS:
    266        return [];
    267      case SIGNER_INFOS:
    268        return [];
    269      default:
    270        return super.defaultValues(memberName);
    271    }
    272  }
    273 
    274  /**
    275   * Compare values with default values for all class members
    276   * @param memberName String name for a class member
    277   * @param memberValue Value to compare with default value
    278   */
    279  public static compareWithDefault(memberName: string, memberValue: any): boolean {
    280    switch (memberName) {
    281      case VERSION:
    282        return (memberValue === SignedData.defaultValues(VERSION));
    283      case ENCAP_CONTENT_INFO:
    284        return EncapsulatedContentInfo.compareWithDefault("eContentType", memberValue.eContentType) &&
    285          EncapsulatedContentInfo.compareWithDefault("eContent", memberValue.eContent);
    286      case DIGEST_ALGORITHMS:
    287      case CERTIFICATES:
    288      case CRLS:
    289      case OCSPS:
    290      case SIGNER_INFOS:
    291        return (memberValue.length === 0);
    292      default:
    293        return super.defaultValues(memberName);
    294    }
    295  }
    296 
    297  /**
    298   * @inheritdoc
    299   * @asn ASN.1 schema
    300   * ```asn
    301   * SignedData ::= SEQUENCE {
    302   *    version CMSVersion,
    303   *    digestAlgorithms DigestAlgorithmIdentifiers,
    304   *    encapContentInfo EncapsulatedContentInfo,
    305   *    certificates [0] IMPLICIT CertificateSet OPTIONAL,
    306   *    crls [1] IMPLICIT RevocationInfoChoices OPTIONAL,
    307   *    signerInfos SignerInfos }
    308   *```
    309   */
    310  public static override schema(parameters: Schema.SchemaParameters<{
    311    version?: string;
    312    digestAlgorithms?: string;
    313    encapContentInfo?: EncapsulatedContentInfoSchema;
    314    certificates?: string;
    315    crls?: RevocationInfoChoicesSchema;
    316    signerInfos?: string;
    317  }> = {}): Schema.SchemaType {
    318    const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {});
    319 
    320    if (names.optional === undefined) {
    321      names.optional = false;
    322    }
    323 
    324    return (new asn1js.Sequence({
    325      name: (names.blockName || SIGNED_DATA),
    326      optional: names.optional,
    327      value: [
    328        new asn1js.Integer({ name: (names.version || SIGNED_DATA_VERSION) }),
    329        new asn1js.Set({
    330          value: [
    331            new asn1js.Repeated({
    332              name: (names.digestAlgorithms || SIGNED_DATA_DIGEST_ALGORITHMS),
    333              value: AlgorithmIdentifier.schema()
    334            })
    335          ]
    336        }),
    337        EncapsulatedContentInfo.schema(names.encapContentInfo || {
    338          names: {
    339            blockName: SIGNED_DATA_ENCAP_CONTENT_INFO
    340          }
    341        }),
    342        new asn1js.Constructed({
    343          name: (names.certificates || SIGNED_DATA_CERTIFICATES),
    344          optional: true,
    345          idBlock: {
    346            tagClass: 3, // CONTEXT-SPECIFIC
    347            tagNumber: 0 // [0]
    348          },
    349          value: CertificateSet.schema().valueBlock.value
    350        }), // IMPLICIT CertificateSet
    351        new asn1js.Constructed({
    352          optional: true,
    353          idBlock: {
    354            tagClass: 3, // CONTEXT-SPECIFIC
    355            tagNumber: 1 // [1]
    356          },
    357          value: RevocationInfoChoices.schema(names.crls || {
    358            names: {
    359              crls: SIGNED_DATA_CRLS
    360            }
    361          }).valueBlock.value
    362        }), // IMPLICIT RevocationInfoChoices
    363        new asn1js.Set({
    364          value: [
    365            new asn1js.Repeated({
    366              name: (names.signerInfos || SIGNED_DATA_SIGNER_INFOS),
    367              value: SignerInfo.schema()
    368            })
    369          ]
    370        })
    371      ]
    372    }));
    373  }
    374 
    375  public fromSchema(schema: Schema.SchemaType): void {
    376    // Clear input data first
    377    pvutils.clearProps(schema, CLEAR_PROPS);
    378 
    379    //#region Check the schema is valid
    380    const asn1 = asn1js.compareSchema(schema,
    381      schema,
    382      SignedData.schema()
    383    );
    384    AsnError.assertSchema(asn1, this.className);
    385    //#endregion
    386 
    387    //#region Get internal properties from parsed schema
    388    this.version = asn1.result[SIGNED_DATA_VERSION].valueBlock.valueDec;
    389 
    390    if (SIGNED_DATA_DIGEST_ALGORITHMS in asn1.result) // Could be empty SET of digest algorithms
    391      this.digestAlgorithms = Array.from(asn1.result[SIGNED_DATA_DIGEST_ALGORITHMS], algorithm => new AlgorithmIdentifier({ schema: algorithm }));
    392 
    393    this.encapContentInfo = new EncapsulatedContentInfo({ schema: asn1.result[SIGNED_DATA_ENCAP_CONTENT_INFO] });
    394 
    395    if (SIGNED_DATA_CERTIFICATES in asn1.result) {
    396      const certificateSet = new CertificateSet({
    397        schema: new asn1js.Set({
    398          value: asn1.result[SIGNED_DATA_CERTIFICATES].valueBlock.value
    399        })
    400      });
    401      this.certificates = certificateSet.certificates.slice(0); // Copy all just for making comfortable access
    402    }
    403 
    404    if (SIGNED_DATA_CRLS in asn1.result) {
    405      this.crls = Array.from(asn1.result[SIGNED_DATA_CRLS], (crl: Schema.SchemaType) => {
    406        if (crl.idBlock.tagClass === 1)
    407          return new CertificateRevocationList({ schema: crl });
    408 
    409        //#region Create SEQUENCE from [1]
    410        crl.idBlock.tagClass = 1; // UNIVERSAL
    411        crl.idBlock.tagNumber = 16; // SEQUENCE
    412        //#endregion
    413 
    414        return new OtherRevocationInfoFormat({ schema: crl });
    415      });
    416    }
    417 
    418    if (SIGNED_DATA_SIGNER_INFOS in asn1.result) // Could be empty SET SignerInfos
    419      this.signerInfos = Array.from(asn1.result[SIGNED_DATA_SIGNER_INFOS], signerInfoSchema => new SignerInfo({ schema: signerInfoSchema }));
    420    //#endregion
    421  }
    422 
    423  public toSchema(encodeFlag = false): Schema.SchemaType {
    424    //#region Create array for output sequence
    425    const outputArray = [];
    426 
    427    // IF ((certificates is present) AND
    428    //  (any certificates with a type of other are present)) OR
    429    //  ((crls is present) AND
    430    //  (any crls with a type of other are present))
    431    // THEN version MUST be 5
    432    // ELSE
    433    //  IF (certificates is present) AND
    434    //    (any version 2 attribute certificates are present)
    435    //  THEN version MUST be 4
    436    //  ELSE
    437    //    IF ((certificates is present) AND
    438    //      (any version 1 attribute certificates are present)) OR
    439    //      (any SignerInfo structures are version 3) OR
    440    //      (encapContentInfo eContentType is other than id-data)
    441    //    THEN version MUST be 3
    442    //    ELSE version MUST be 1
    443    if ((this.certificates && this.certificates.length && this.certificates.some(o => o instanceof OtherCertificateFormat))
    444      || (this.crls && this.crls.length && this.crls.some(o => o instanceof OtherRevocationInfoFormat))) {
    445      this.version = 5;
    446    } else if (this.certificates && this.certificates.length && this.certificates.some(o => o instanceof AttributeCertificateV2)) {
    447      this.version = 4;
    448    } else if ((this.certificates && this.certificates.length && this.certificates.some(o => o instanceof AttributeCertificateV1))
    449      || this.signerInfos.some(o => o.version === 3)
    450      || this.encapContentInfo.eContentType !== SignedData.ID_DATA) {
    451      this.version = 3;
    452    } else {
    453      this.version = 1;
    454    }
    455 
    456    outputArray.push(new asn1js.Integer({ value: this.version }));
    457 
    458    //#region Create array of digest algorithms
    459    outputArray.push(new asn1js.Set({
    460      value: Array.from(this.digestAlgorithms, algorithm => algorithm.toSchema())
    461    }));
    462    //#endregion
    463 
    464    outputArray.push(this.encapContentInfo.toSchema());
    465 
    466    if (this.certificates) {
    467      const certificateSet = new CertificateSet({ certificates: this.certificates });
    468      const certificateSetSchema = certificateSet.toSchema();
    469 
    470      outputArray.push(new asn1js.Constructed({
    471        idBlock: {
    472          tagClass: 3,
    473          tagNumber: 0
    474        },
    475        value: certificateSetSchema.valueBlock.value
    476      }));
    477    }
    478 
    479    if (this.crls) {
    480      outputArray.push(new asn1js.Constructed({
    481        idBlock: {
    482          tagClass: 3, // CONTEXT-SPECIFIC
    483          tagNumber: 1 // [1]
    484        },
    485        value: Array.from(this.crls, crl => {
    486          if (crl instanceof OtherRevocationInfoFormat) {
    487            const crlSchema = crl.toSchema();
    488 
    489            crlSchema.idBlock.tagClass = 3;
    490            crlSchema.idBlock.tagNumber = 1;
    491 
    492            return crlSchema;
    493          }
    494 
    495          return crl.toSchema(encodeFlag);
    496        })
    497      }));
    498    }
    499 
    500    //#region Create array of signer infos
    501    outputArray.push(new asn1js.Set({
    502      value: Array.from(this.signerInfos, signerInfo => signerInfo.toSchema())
    503    }));
    504    //#endregion
    505    //#endregion
    506 
    507    //#region Construct and return new ASN.1 schema for this object
    508    return (new asn1js.Sequence({
    509      value: outputArray
    510    }));
    511    //#endregion
    512  }
    513 
    514  public toJSON(): SignedDataJson {
    515    const res: SignedDataJson = {
    516      version: this.version,
    517      digestAlgorithms: Array.from(this.digestAlgorithms, algorithm => algorithm.toJSON()),
    518      encapContentInfo: this.encapContentInfo.toJSON(),
    519      signerInfos: Array.from(this.signerInfos, signerInfo => signerInfo.toJSON()),
    520    };
    521 
    522    if (this.certificates) {
    523      res.certificates = Array.from(this.certificates, certificate => certificate.toJSON());
    524    }
    525 
    526    if (this.crls) {
    527      res.crls = Array.from(this.crls, crl => crl.toJSON());
    528    }
    529 
    530 
    531    return res;
    532  }
    533 
    534  public verify(params?: SignedDataVerifyParams & { extendedMode?: false; }, crypto?: ICryptoEngine): Promise<boolean>;
    535  public verify(params: SignedDataVerifyParams & { extendedMode: true; }, crypto?: ICryptoEngine): Promise<SignedDataVerifyResult>;
    536  public async verify({
    537    signer = (-1),
    538    data = (EMPTY_BUFFER),
    539    trustedCerts = [],
    540    checkDate = (new Date()),
    541    checkChain = false,
    542    passedWhenNotRevValues = false,
    543    extendedMode = false,
    544    findOrigin = null,
    545    findIssuer = null
    546  }: SignedDataVerifyParams = {}, crypto = common.getCrypto(true)): Promise<boolean | SignedDataVerifyResult> {
    547    let signerCert: Certificate | null = null;
    548    let timestampSerial: ArrayBuffer | null = null;
    549    try {
    550      //#region Global variables
    551      let messageDigestValue = EMPTY_BUFFER;
    552      let shaAlgorithm = EMPTY_STRING;
    553      let certificatePath: Certificate[] = [];
    554      //#endregion
    555 
    556      //#region Get a signer number
    557      const signerInfo = this.signerInfos[signer];
    558      if (!signerInfo) {
    559        throw new SignedDataVerifyError({
    560          date: checkDate,
    561          code: 1,
    562          message: "Unable to get signer by supplied index",
    563        });
    564      }
    565      //#endregion
    566 
    567      //#region Check that certificates field was included in signed data
    568      if (!this.certificates) {
    569        throw new SignedDataVerifyError({
    570          date: checkDate,
    571          code: 2,
    572          message: "No certificates attached to this signed data",
    573        });
    574      }
    575      //#endregion
    576 
    577      //#region Find a certificate for specified signer
    578 
    579      if (signerInfo.sid instanceof IssuerAndSerialNumber) {
    580        for (const certificate of this.certificates) {
    581          if (!(certificate instanceof Certificate))
    582            continue;
    583 
    584          if ((certificate.issuer.isEqual(signerInfo.sid.issuer)) &&
    585            (certificate.serialNumber.isEqual(signerInfo.sid.serialNumber))) {
    586            signerCert = certificate;
    587            break;
    588          }
    589        }
    590      } else { // Find by SubjectKeyIdentifier
    591        const sid = signerInfo.sid;
    592        const keyId = sid.idBlock.isConstructed
    593          ? sid.valueBlock.value[0].valueBlock.valueHex // EXPLICIT OCTET STRING
    594          : sid.valueBlock.valueHex; // IMPLICIT OCTET STRING
    595 
    596        for (const certificate of this.certificates) {
    597          if (!(certificate instanceof Certificate)) {
    598            continue;
    599          }
    600 
    601          const digest = await crypto.digest({ name: "sha-1" }, certificate.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHexView as BufferSource);
    602          if (pvutils.isEqualBuffer(digest, keyId)) {
    603            signerCert = certificate;
    604            break;
    605          }
    606        }
    607      }
    608 
    609      if (!signerCert) {
    610        throw new SignedDataVerifyError({
    611          date: checkDate,
    612          code: 3,
    613          message: "Unable to find signer certificate",
    614        });
    615      }
    616      //#endregion
    617 
    618      //#region Verify internal digest in case of "tSTInfo" content type
    619      if (this.encapContentInfo.eContentType === id_eContentType_TSTInfo) {
    620        //#region Check "eContent" presence
    621        if (!this.encapContentInfo.eContent) {
    622          throw new SignedDataVerifyError({
    623            date: checkDate,
    624            code: 15,
    625            message: "Error during verification: TSTInfo eContent is empty",
    626            signatureVerified: null,
    627            signerCertificate: signerCert,
    628            timestampSerial,
    629            signerCertificateVerified: true
    630          });
    631        }
    632        //#endregion
    633 
    634        //#region Initialize TST_INFO value
    635        let tstInfo: TSTInfo;
    636 
    637        try {
    638          tstInfo = TSTInfo.fromBER(this.encapContentInfo.eContent.valueBlock.valueHexView as BufferSource);
    639        }
    640        catch {
    641          throw new SignedDataVerifyError({
    642            date: checkDate,
    643            code: 15,
    644            message: "Error during verification: TSTInfo wrong ASN.1 schema ",
    645            signatureVerified: null,
    646            signerCertificate: signerCert,
    647            timestampSerial,
    648            signerCertificateVerified: true
    649          });
    650        }
    651        //#endregion
    652 
    653        //#region Change "checkDate" and append "timestampSerial"
    654        checkDate = tstInfo.genTime;
    655        timestampSerial = tstInfo.serialNumber.valueBlock.valueHexView.slice().buffer;
    656        //#endregion
    657 
    658        //#region Check that we do have detached data content
    659        if (data.byteLength === 0) {
    660          throw new SignedDataVerifyError({
    661            date: checkDate,
    662            code: 4,
    663            message: "Missed detached data input array",
    664          });
    665        }
    666        //#endregion
    667 
    668        if (!(await tstInfo.verify({ data }, crypto))) {
    669          throw new SignedDataVerifyError({
    670            date: checkDate,
    671            code: 15,
    672            message: "Error during verification: TSTInfo verification is failed",
    673            signatureVerified: false,
    674            signerCertificate: signerCert,
    675            timestampSerial,
    676            signerCertificateVerified: true
    677          });
    678        }
    679      }
    680 
    681      //#endregion
    682 
    683      if (checkChain) {
    684        const certs = this.certificates.filter(certificate => (certificate instanceof Certificate && !!checkCA(certificate, signerCert))) as Certificate[];
    685        const chainParams: CertificateChainValidationEngineParameters = {
    686          checkDate,
    687          certs,
    688          trustedCerts,
    689        };
    690 
    691        if (findIssuer) {
    692          chainParams.findIssuer = findIssuer;
    693        }
    694        if (findOrigin) {
    695          chainParams.findOrigin = findOrigin;
    696        }
    697 
    698        const chainEngine = new CertificateChainValidationEngine(chainParams);
    699        chainEngine.certs.push(signerCert);
    700 
    701        if (this.crls) {
    702          for (const crl of this.crls) {
    703            if ("thisUpdate" in crl)
    704              chainEngine.crls.push(crl);
    705            else // Assumed "revocation value" has "OtherRevocationInfoFormat"
    706            {
    707              if (crl.otherRevInfoFormat === id_PKIX_OCSP_Basic) // Basic OCSP response
    708                chainEngine.ocsps.push(new BasicOCSPResponse({ schema: crl.otherRevInfo }));
    709            }
    710          }
    711        }
    712 
    713        if (this.ocsps) {
    714          chainEngine.ocsps.push(...(this.ocsps));
    715        }
    716 
    717        const verificationResult = await chainEngine.verify({ passedWhenNotRevValues }, crypto)
    718          .catch(e => {
    719            throw new SignedDataVerifyError({
    720              date: checkDate,
    721              code: 5,
    722              message: `Validation of signer's certificate failed with error: ${((e instanceof Object) ? e.resultMessage : e)}`,
    723              signerCertificate: signerCert,
    724              signerCertificateVerified: false
    725            });
    726          });
    727        if (verificationResult.certificatePath) {
    728          certificatePath = verificationResult.certificatePath;
    729        }
    730 
    731        if (!verificationResult.result)
    732          throw new SignedDataVerifyError({
    733            date: checkDate,
    734            code: 5,
    735            message: `Validation of signer's certificate failed: ${verificationResult.resultMessage}`,
    736            signerCertificate: signerCert,
    737            signerCertificateVerified: false
    738          });
    739      }
    740      //#endregion
    741 
    742      //#region Find signer's hashing algorithm
    743 
    744      const signerInfoHashAlgorithm = crypto.getAlgorithmByOID(signerInfo.digestAlgorithm.algorithmId);
    745      if (!("name" in signerInfoHashAlgorithm)) {
    746        throw new SignedDataVerifyError({
    747          date: checkDate,
    748          code: 7,
    749          message: `Unsupported signature algorithm: ${signerInfo.digestAlgorithm.algorithmId}`,
    750          signerCertificate: signerCert,
    751          signerCertificateVerified: true
    752        });
    753      }
    754 
    755      shaAlgorithm = signerInfoHashAlgorithm.name;
    756      //#endregion
    757 
    758      //#region Create correct data block for verification
    759 
    760      const eContent = this.encapContentInfo.eContent;
    761      if (eContent) // Attached data
    762      {
    763        if ((eContent.idBlock.tagClass === 1) &&
    764          (eContent.idBlock.tagNumber === 4)) {
    765          data = eContent.getValue();
    766        }
    767        else
    768          data = eContent.valueBlock.valueBeforeDecodeView.slice().buffer;
    769      }
    770      else // Detached data
    771      {
    772        if (data.byteLength === 0) // Check that "data" already provided by function parameter
    773        {
    774          throw new SignedDataVerifyError({
    775            date: checkDate,
    776            code: 8,
    777            message: "Missed detached data input array",
    778            signerCertificate: signerCert,
    779            signerCertificateVerified: true
    780          });
    781        }
    782      }
    783 
    784      if (signerInfo.signedAttrs) {
    785        //#region Check mandatory attributes
    786        let foundContentType = false;
    787        let foundMessageDigest = false;
    788 
    789        for (const attribute of signerInfo.signedAttrs.attributes) {
    790          //#region Check that "content-type" attribute exists
    791          if (attribute.type === "1.2.840.113549.1.9.3")
    792            foundContentType = true;
    793          //#endregion
    794 
    795          //#region Check that "message-digest" attribute exists
    796          if (attribute.type === "1.2.840.113549.1.9.4") {
    797            foundMessageDigest = true;
    798            messageDigestValue = attribute.values[0].valueBlock.valueHex;
    799          }
    800          //#endregion
    801 
    802          //#region Speed-up searching
    803          if (foundContentType && foundMessageDigest)
    804            break;
    805          //#endregion
    806        }
    807 
    808        if (foundContentType === false) {
    809          throw new SignedDataVerifyError({
    810            date: checkDate,
    811            code: 9,
    812            message: "Attribute \"content-type\" is a mandatory attribute for \"signed attributes\"",
    813            signerCertificate: signerCert,
    814            signerCertificateVerified: true
    815          });
    816        }
    817 
    818        if (foundMessageDigest === false) {
    819          throw new SignedDataVerifyError({
    820            date: checkDate,
    821            code: 10,
    822            message: "Attribute \"message-digest\" is a mandatory attribute for \"signed attributes\"",
    823            signatureVerified: null,
    824            signerCertificate: signerCert,
    825            signerCertificateVerified: true
    826          });
    827        }
    828        //#endregion
    829      }
    830      //#endregion
    831 
    832      //#region Verify "message-digest" attribute in case of "signedAttrs"
    833      if (signerInfo.signedAttrs) {
    834        const messageDigest = await crypto.digest(shaAlgorithm, new Uint8Array(data));
    835        if (!pvutils.isEqualBuffer(messageDigest, messageDigestValue)) {
    836          throw new SignedDataVerifyError({
    837            date: checkDate,
    838            code: 15,
    839            message: "Error during verification: Message digest doesn't match",
    840            signatureVerified: null,
    841            signerCertificate: signerCert,
    842            timestampSerial,
    843            signerCertificateVerified: true
    844          });
    845        }
    846        data = signerInfo.signedAttrs.encodedValue;
    847      }
    848      //#endregion
    849 
    850      // This adjustment is specifically for cases where the signature algorithm is rsaEncryption.
    851      // In such cases, we rely on the hash mechanism defined in signerInfo.digestAlgorithm for verification.
    852      const verifyResult = signerInfo.signatureAlgorithm.algorithmId === "1.2.840.113549.1.1.1"
    853        ? await crypto.verifyWithPublicKey(data, signerInfo.signature, signerCert.subjectPublicKeyInfo, signerInfo.signatureAlgorithm, shaAlgorithm)
    854        : await crypto.verifyWithPublicKey(data, signerInfo.signature, signerCert.subjectPublicKeyInfo, signerInfo.signatureAlgorithm);
    855 
    856      //#region Make a final result
    857 
    858      if (extendedMode) {
    859        return {
    860          date: checkDate,
    861          code: 14,
    862          message: EMPTY_STRING,
    863          signatureVerified: verifyResult,
    864          signerCertificate: signerCert,
    865          timestampSerial,
    866          signerCertificateVerified: true,
    867          certificatePath
    868        };
    869      } else {
    870        return verifyResult;
    871      }
    872    } catch (e) {
    873      if (e instanceof SignedDataVerifyError) {
    874        throw e;
    875      }
    876      throw new SignedDataVerifyError({
    877        date: checkDate,
    878        code: 15,
    879        message: `Error during verification: ${e instanceof Error ? e.message : e}`,
    880        signatureVerified: null,
    881        signerCertificate: signerCert,
    882        timestampSerial,
    883        signerCertificateVerified: true
    884      });
    885    }
    886  }
    887 
    888  /**
    889   * Signing current SignedData
    890   * @param privateKey Private key for "subjectPublicKeyInfo" structure
    891   * @param signerIndex Index number (starting from 0) of signer index to make signature for
    892   * @param hashAlgorithm Hashing algorithm. Default SHA-1
    893   * @param data Detached data
    894   * @param crypto Crypto engine
    895   */
    896  public async sign(privateKey: CryptoKey, signerIndex: number, hashAlgorithm = "SHA-1", data: BufferSource = (EMPTY_BUFFER), crypto = common.getCrypto(true)): Promise<void> {
    897    //#region Initial checking
    898    if (!privateKey)
    899      throw new Error("Need to provide a private key for signing");
    900 
    901    const signerInfo = this.signerInfos[signerIndex];
    902    if (!signerInfo) {
    903      throw new RangeError("SignerInfo index is out of range");
    904    }
    905 
    906    //#endregion
    907 
    908    //#region Adjust hashAlgorithm based on privateKey if signedAttrs are missing
    909    if (!signerInfo.signedAttrs?.attributes.length && "hash" in privateKey.algorithm && "hash" in privateKey.algorithm && privateKey.algorithm.hash) {
    910      hashAlgorithm = (privateKey.algorithm.hash as Algorithm).name;
    911    }
    912    const hashAlgorithmOID = crypto.getOIDByAlgorithm({ name: hashAlgorithm }, true, "hashAlgorithm");
    913    //#endregion
    914 
    915    //#region Append information about hash algorithm
    916    if ((this.digestAlgorithms.filter(algorithm => algorithm.algorithmId === hashAlgorithmOID)).length === 0) {
    917      this.digestAlgorithms.push(new AlgorithmIdentifier({
    918        algorithmId: hashAlgorithmOID,
    919        algorithmParams: new asn1js.Null()
    920      }));
    921    }
    922 
    923    signerInfo.digestAlgorithm = new AlgorithmIdentifier({
    924      algorithmId: hashAlgorithmOID,
    925      algorithmParams: new asn1js.Null()
    926    });
    927    //#endregion
    928 
    929    //#region Get a "default parameters" for current algorithm and set correct signature algorithm
    930    const signatureParams = await crypto.getSignatureParameters(privateKey, hashAlgorithm);
    931    const parameters = signatureParams.parameters;
    932    signerInfo.signatureAlgorithm = signatureParams.signatureAlgorithm;
    933    //#endregion
    934 
    935    //#region Create TBS data for signing
    936    if (signerInfo.signedAttrs) {
    937      if (signerInfo.signedAttrs.encodedValue.byteLength !== 0)
    938        data = signerInfo.signedAttrs.encodedValue;
    939      else {
    940        data = signerInfo.signedAttrs.toSchema().toBER();
    941 
    942        //#region Change type from "[0]" to "SET" accordingly to standard
    943        const view = pvtsutils.BufferSourceConverter.toUint8Array(data);
    944        view[0] = 0x31;
    945        //#endregion
    946      }
    947    }
    948    else {
    949      const eContent = this.encapContentInfo.eContent;
    950      if (eContent) // Attached data
    951      {
    952        if ((eContent.idBlock.tagClass === 1) &&
    953          (eContent.idBlock.tagNumber === 4)) {
    954          data = eContent.getValue();
    955        }
    956        else
    957          data = eContent.valueBlock.valueBeforeDecodeView.slice().buffer;
    958      }
    959      else // Detached data
    960      {
    961        if (data.byteLength === 0) // Check that "data" already provided by function parameter
    962          throw new Error("Missed detached data input array");
    963      }
    964    }
    965    //#endregion
    966 
    967    //#region Signing TBS data on provided private key
    968    const signature = await crypto.signWithPrivateKey(data, privateKey, parameters as any);
    969    signerInfo.signature = new asn1js.OctetString({ valueHex: signature });
    970    //#endregion
    971  }
    972 
    973 }