summaryrefslogtreecommitdiffstats
path: root/third_party/js/PKI.js/src/SignedData.ts
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
commit9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /third_party/js/PKI.js/src/SignedData.ts
parentInitial commit. (diff)
downloadthunderbird-9e3c08db40b8916968b9f30096c7be3f00ce9647.tar.xz
thunderbird-9e3c08db40b8916968b9f30096c7be3f00ce9647.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/js/PKI.js/src/SignedData.ts')
-rw-r--r--third_party/js/PKI.js/src/SignedData.ts966
1 files changed, 966 insertions, 0 deletions
diff --git a/third_party/js/PKI.js/src/SignedData.ts b/third_party/js/PKI.js/src/SignedData.ts
new file mode 100644
index 0000000000..67731183de
--- /dev/null
+++ b/third_party/js/PKI.js/src/SignedData.ts
@@ -0,0 +1,966 @@
+import * as asn1js from "asn1js";
+import * as pvtsutils from "pvtsutils";
+import * as pvutils from "pvutils";
+import * as common from "./common";
+import { AlgorithmIdentifier, AlgorithmIdentifierJson } from "./AlgorithmIdentifier";
+import { EncapsulatedContentInfo, EncapsulatedContentInfoJson, EncapsulatedContentInfoSchema } from "./EncapsulatedContentInfo";
+import { Certificate, checkCA } from "./Certificate";
+import { CertificateRevocationList, CertificateRevocationListJson } from "./CertificateRevocationList";
+import { OtherRevocationInfoFormat, OtherRevocationInfoFormatJson } from "./OtherRevocationInfoFormat";
+import { SignerInfo, SignerInfoJson } from "./SignerInfo";
+import { CertificateSet, CertificateSetItem, CertificateSetItemJson } from "./CertificateSet";
+import { RevocationInfoChoices, RevocationInfoChoicesSchema } from "./RevocationInfoChoices";
+import { IssuerAndSerialNumber } from "./IssuerAndSerialNumber";
+import { TSTInfo } from "./TSTInfo";
+import { CertificateChainValidationEngine, CertificateChainValidationEngineParameters, FindIssuerCallback, FindOriginCallback } from "./CertificateChainValidationEngine";
+import { BasicOCSPResponse, BasicOCSPResponseJson } from "./BasicOCSPResponse";
+import { OtherCertificateFormat } from "./OtherCertificateFormat";
+import { AttributeCertificateV1 } from "./AttributeCertificateV1";
+import { AttributeCertificateV2 } from "./AttributeCertificateV2";
+import * as Schema from "./Schema";
+import { id_ContentType_Data, id_eContentType_TSTInfo, id_PKIX_OCSP_Basic } from "./ObjectIdentifiers";
+import { AsnError } from "./errors";
+import { PkiObject, PkiObjectParameters } from "./PkiObject";
+import { EMPTY_BUFFER, EMPTY_STRING } from "./constants";
+import { ICryptoEngine } from "./CryptoEngine/CryptoEngineInterface";
+
+export type SignedDataCRL = CertificateRevocationList | OtherRevocationInfoFormat;
+export type SignedDataCRLJson = CertificateRevocationListJson | OtherRevocationInfoFormatJson;
+
+const VERSION = "version";
+const DIGEST_ALGORITHMS = "digestAlgorithms";
+const ENCAP_CONTENT_INFO = "encapContentInfo";
+const CERTIFICATES = "certificates";
+const CRLS = "crls";
+const SIGNER_INFOS = "signerInfos";
+const OCSPS = "ocsps";
+const SIGNED_DATA = "SignedData";
+const SIGNED_DATA_VERSION = `${SIGNED_DATA}.${VERSION}`;
+const SIGNED_DATA_DIGEST_ALGORITHMS = `${SIGNED_DATA}.${DIGEST_ALGORITHMS}`;
+const SIGNED_DATA_ENCAP_CONTENT_INFO = `${SIGNED_DATA}.${ENCAP_CONTENT_INFO}`;
+const SIGNED_DATA_CERTIFICATES = `${SIGNED_DATA}.${CERTIFICATES}`;
+const SIGNED_DATA_CRLS = `${SIGNED_DATA}.${CRLS}`;
+const SIGNED_DATA_SIGNER_INFOS = `${SIGNED_DATA}.${SIGNER_INFOS}`;
+const CLEAR_PROPS = [
+ SIGNED_DATA_VERSION,
+ SIGNED_DATA_DIGEST_ALGORITHMS,
+ SIGNED_DATA_ENCAP_CONTENT_INFO,
+ SIGNED_DATA_CERTIFICATES,
+ SIGNED_DATA_CRLS,
+ SIGNED_DATA_SIGNER_INFOS
+];
+
+export interface ISignedData {
+ version: number;
+ digestAlgorithms: AlgorithmIdentifier[];
+ encapContentInfo: EncapsulatedContentInfo;
+ certificates?: CertificateSetItem[];
+ crls?: SignedDataCRL[];
+ ocsps?: BasicOCSPResponse[];
+ signerInfos: SignerInfo[];
+}
+
+export interface SignedDataJson {
+ version: number;
+ digestAlgorithms: AlgorithmIdentifierJson[];
+ encapContentInfo: EncapsulatedContentInfoJson;
+ certificates?: CertificateSetItemJson[];
+ crls?: SignedDataCRLJson[];
+ ocsps?: BasicOCSPResponseJson[];
+ signerInfos: SignerInfoJson[];
+}
+
+export type SignedDataParameters = PkiObjectParameters & Partial<ISignedData>;
+
+export interface SignedDataVerifyParams {
+ signer?: number;
+ data?: ArrayBuffer;
+ trustedCerts?: Certificate[];
+ checkDate?: Date;
+ checkChain?: boolean;
+ passedWhenNotRevValues?: boolean;
+ extendedMode?: boolean;
+ findOrigin?: FindOriginCallback | null;
+ findIssuer?: FindIssuerCallback | null;
+}
+
+export interface SignedDataVerifyErrorParams {
+ message: string;
+ date?: Date;
+ code?: number;
+ timestampSerial?: ArrayBuffer | null;
+ signatureVerified?: boolean | null;
+ signerCertificate?: Certificate | null;
+ signerCertificateVerified?: boolean | null;
+ certificatePath?: Certificate[];
+}
+
+export interface SignedDataVerifyResult {
+ message: string;
+ date?: Date;
+ code?: number;
+ timestampSerial?: ArrayBuffer | null;
+ signatureVerified?: boolean | null;
+ signerCertificate?: Certificate | null;
+ signerCertificateVerified?: boolean | null;
+ certificatePath: Certificate[];
+}
+
+export class SignedDataVerifyError extends Error implements SignedDataVerifyResult {
+
+ public date: Date;
+ public code: number;
+ public signatureVerified: boolean | null;
+ public signerCertificate: Certificate | null;
+ public signerCertificateVerified: boolean | null;
+ public timestampSerial: ArrayBuffer | null;
+ public certificatePath: Certificate[];
+
+ constructor({
+ message,
+ code = 0,
+ date = new Date(),
+ signatureVerified = null,
+ signerCertificate = null,
+ signerCertificateVerified = null,
+ timestampSerial = null,
+ certificatePath = [],
+ }: SignedDataVerifyErrorParams) {
+ super(message);
+ this.name = "SignedDataVerifyError";
+
+ this.date = date;
+ this.code = code;
+ this.timestampSerial = timestampSerial;
+ this.signatureVerified = signatureVerified;
+ this.signerCertificate = signerCertificate;
+ this.signerCertificateVerified = signerCertificateVerified;
+ this.certificatePath = certificatePath;
+
+ }
+}
+
+/**
+ * Represents the SignedData structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652)
+ *
+ * @example The following example demonstrates how to create and sign CMS Signed Data
+ * ```js
+ * // Create a new CMS Signed Data
+ * const cmsSigned = new pkijs.SignedData({
+ * encapContentInfo: new pkijs.EncapsulatedContentInfo({
+ * eContentType: pkijs.ContentInfo.DATA,, // "data" content type
+ * eContent: new asn1js.OctetString({ valueHex: buffer })
+ * }),
+ * signerInfos: [
+ * new pkijs.SignerInfo({
+ * sid: new pkijs.IssuerAndSerialNumber({
+ * issuer: cert.issuer,
+ * serialNumber: cert.serialNumber
+ * })
+ * })
+ * ],
+ * // Signer certificate for chain validation
+ * certificates: [cert]
+ * });
+ *
+ * await cmsSigned.sign(keys.privateKey, 0, "SHA-256");
+ *
+ * // Add Signed Data to Content Info
+ * const cms = new pkijs.ContentInfo({
+ * contentType: pkijs.ContentInfo.SIGNED_DATA,,
+ * content: cmsSigned.toSchema(true),
+ * });
+ *
+ * // Encode CMS to ASN.1
+ * const cmsRaw = cms.toSchema().toBER();
+ * ```
+ *
+ * @example The following example demonstrates how to verify CMS Signed Data
+ * ```js
+ * // Parse CMS and detect it's Signed Data
+ * const cms = pkijs.ContentInfo.fromBER(cmsRaw);
+ * if (cms.contentType !== pkijs.ContentInfo.SIGNED_DATA) {
+ * throw new Error("CMS is not Signed Data");
+ * }
+ *
+ * // Read Signed Data
+ * const signedData = new pkijs.SignedData({ schema: cms.content });
+ *
+ * // Verify Signed Data signature
+ * const ok = await signedData.verify({
+ * signer: 0,
+ * checkChain: true,
+ * trustedCerts: [trustedCert],
+ * });
+ *
+ * if (!ok) {
+ * throw new Error("CMS signature is invalid")
+ * }
+ * ```
+ */
+export class SignedData extends PkiObject implements ISignedData {
+
+ public static override CLASS_NAME = "SignedData";
+
+ public static ID_DATA: typeof id_ContentType_Data = id_ContentType_Data;
+
+ public version!: number;
+ public digestAlgorithms!: AlgorithmIdentifier[];
+ public encapContentInfo!: EncapsulatedContentInfo;
+ public certificates?: CertificateSetItem[];
+ public crls?: SignedDataCRL[];
+ public ocsps?: BasicOCSPResponse[];
+ public signerInfos!: SignerInfo[];
+
+ /**
+ * Initializes a new instance of the {@link SignedData} class
+ * @param parameters Initialization parameters
+ */
+ constructor(parameters: SignedDataParameters = {}) {
+ super();
+
+ this.version = pvutils.getParametersValue(parameters, VERSION, SignedData.defaultValues(VERSION));
+ this.digestAlgorithms = pvutils.getParametersValue(parameters, DIGEST_ALGORITHMS, SignedData.defaultValues(DIGEST_ALGORITHMS));
+ this.encapContentInfo = pvutils.getParametersValue(parameters, ENCAP_CONTENT_INFO, SignedData.defaultValues(ENCAP_CONTENT_INFO));
+ if (CERTIFICATES in parameters) {
+ this.certificates = pvutils.getParametersValue(parameters, CERTIFICATES, SignedData.defaultValues(CERTIFICATES));
+ }
+ if (CRLS in parameters) {
+ this.crls = pvutils.getParametersValue(parameters, CRLS, SignedData.defaultValues(CRLS));
+ }
+ if (OCSPS in parameters) {
+ this.ocsps = pvutils.getParametersValue(parameters, OCSPS, SignedData.defaultValues(OCSPS));
+ }
+ this.signerInfos = pvutils.getParametersValue(parameters, SIGNER_INFOS, SignedData.defaultValues(SIGNER_INFOS));
+
+ 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 VERSION): number;
+ public static override defaultValues(memberName: typeof DIGEST_ALGORITHMS): AlgorithmIdentifier[];
+ public static override defaultValues(memberName: typeof ENCAP_CONTENT_INFO): EncapsulatedContentInfo;
+ public static override defaultValues(memberName: typeof CERTIFICATES): CertificateSetItem[];
+ public static override defaultValues(memberName: typeof CRLS): SignedDataCRL[];
+ public static override defaultValues(memberName: typeof OCSPS): BasicOCSPResponse[];
+ public static override defaultValues(memberName: typeof SIGNER_INFOS): SignerInfo[];
+ public static override defaultValues(memberName: string): any {
+ switch (memberName) {
+ case VERSION:
+ return 0;
+ case DIGEST_ALGORITHMS:
+ return [];
+ case ENCAP_CONTENT_INFO:
+ return new EncapsulatedContentInfo();
+ case CERTIFICATES:
+ return [];
+ case CRLS:
+ return [];
+ case OCSPS:
+ return [];
+ case SIGNER_INFOS:
+ return [];
+ default:
+ return super.defaultValues(memberName);
+ }
+ }
+
+ /**
+ * Compare values with default values for all class members
+ * @param memberName String name for a class member
+ * @param memberValue Value to compare with default value
+ */
+ public static compareWithDefault(memberName: string, memberValue: any): boolean {
+ switch (memberName) {
+ case VERSION:
+ return (memberValue === SignedData.defaultValues(VERSION));
+ case ENCAP_CONTENT_INFO:
+ return EncapsulatedContentInfo.compareWithDefault("eContentType", memberValue.eContentType) &&
+ EncapsulatedContentInfo.compareWithDefault("eContent", memberValue.eContent);
+ case DIGEST_ALGORITHMS:
+ case CERTIFICATES:
+ case CRLS:
+ case OCSPS:
+ case SIGNER_INFOS:
+ return (memberValue.length === 0);
+ default:
+ return super.defaultValues(memberName);
+ }
+ }
+
+ /**
+ * @inheritdoc
+ * @asn ASN.1 schema
+ * ```asn
+ * SignedData ::= SEQUENCE {
+ * version CMSVersion,
+ * digestAlgorithms DigestAlgorithmIdentifiers,
+ * encapContentInfo EncapsulatedContentInfo,
+ * certificates [0] IMPLICIT CertificateSet OPTIONAL,
+ * crls [1] IMPLICIT RevocationInfoChoices OPTIONAL,
+ * signerInfos SignerInfos }
+ *```
+ */
+ public static override schema(parameters: Schema.SchemaParameters<{
+ version?: string;
+ digestAlgorithms?: string;
+ encapContentInfo?: EncapsulatedContentInfoSchema;
+ certificates?: string;
+ crls?: RevocationInfoChoicesSchema;
+ signerInfos?: string;
+ }> = {}): Schema.SchemaType {
+ const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {});
+
+ if (names.optional === undefined) {
+ names.optional = false;
+ }
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || SIGNED_DATA),
+ optional: names.optional,
+ value: [
+ new asn1js.Integer({ name: (names.version || SIGNED_DATA_VERSION) }),
+ new asn1js.Set({
+ value: [
+ new asn1js.Repeated({
+ name: (names.digestAlgorithms || SIGNED_DATA_DIGEST_ALGORITHMS),
+ value: AlgorithmIdentifier.schema()
+ })
+ ]
+ }),
+ EncapsulatedContentInfo.schema(names.encapContentInfo || {
+ names: {
+ blockName: SIGNED_DATA_ENCAP_CONTENT_INFO
+ }
+ }),
+ new asn1js.Constructed({
+ name: (names.certificates || SIGNED_DATA_CERTIFICATES),
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: CertificateSet.schema().valueBlock.value
+ }), // IMPLICIT CertificateSet
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: RevocationInfoChoices.schema(names.crls || {
+ names: {
+ crls: SIGNED_DATA_CRLS
+ }
+ }).valueBlock.value
+ }), // IMPLICIT RevocationInfoChoices
+ new asn1js.Set({
+ value: [
+ new asn1js.Repeated({
+ name: (names.signerInfos || SIGNED_DATA_SIGNER_INFOS),
+ value: SignerInfo.schema()
+ })
+ ]
+ })
+ ]
+ }));
+ }
+
+ public fromSchema(schema: Schema.SchemaType): void {
+ // Clear input data first
+ pvutils.clearProps(schema, CLEAR_PROPS);
+
+ //#region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ SignedData.schema()
+ );
+ AsnError.assertSchema(asn1, this.className);
+ //#endregion
+
+ //#region Get internal properties from parsed schema
+ this.version = asn1.result[SIGNED_DATA_VERSION].valueBlock.valueDec;
+
+ if (SIGNED_DATA_DIGEST_ALGORITHMS in asn1.result) // Could be empty SET of digest algorithms
+ this.digestAlgorithms = Array.from(asn1.result[SIGNED_DATA_DIGEST_ALGORITHMS], algorithm => new AlgorithmIdentifier({ schema: algorithm }));
+
+ this.encapContentInfo = new EncapsulatedContentInfo({ schema: asn1.result[SIGNED_DATA_ENCAP_CONTENT_INFO] });
+
+ if (SIGNED_DATA_CERTIFICATES in asn1.result) {
+ const certificateSet = new CertificateSet({
+ schema: new asn1js.Set({
+ value: asn1.result[SIGNED_DATA_CERTIFICATES].valueBlock.value
+ })
+ });
+ this.certificates = certificateSet.certificates.slice(0); // Copy all just for making comfortable access
+ }
+
+ if (SIGNED_DATA_CRLS in asn1.result) {
+ this.crls = Array.from(asn1.result[SIGNED_DATA_CRLS], (crl: Schema.SchemaType) => {
+ if (crl.idBlock.tagClass === 1)
+ return new CertificateRevocationList({ schema: crl });
+
+ //#region Create SEQUENCE from [1]
+ crl.idBlock.tagClass = 1; // UNIVERSAL
+ crl.idBlock.tagNumber = 16; // SEQUENCE
+ //#endregion
+
+ return new OtherRevocationInfoFormat({ schema: crl });
+ });
+ }
+
+ if (SIGNED_DATA_SIGNER_INFOS in asn1.result) // Could be empty SET SignerInfos
+ this.signerInfos = Array.from(asn1.result[SIGNED_DATA_SIGNER_INFOS], signerInfoSchema => new SignerInfo({ schema: signerInfoSchema }));
+ //#endregion
+ }
+
+ public toSchema(encodeFlag = false): Schema.SchemaType {
+ //#region Create array for output sequence
+ const outputArray = [];
+
+ // IF ((certificates is present) AND
+ // (any certificates with a type of other are present)) OR
+ // ((crls is present) AND
+ // (any crls with a type of other are present))
+ // THEN version MUST be 5
+ // ELSE
+ // IF (certificates is present) AND
+ // (any version 2 attribute certificates are present)
+ // THEN version MUST be 4
+ // ELSE
+ // IF ((certificates is present) AND
+ // (any version 1 attribute certificates are present)) OR
+ // (any SignerInfo structures are version 3) OR
+ // (encapContentInfo eContentType is other than id-data)
+ // THEN version MUST be 3
+ // ELSE version MUST be 1
+ if ((this.certificates && this.certificates.length && this.certificates.some(o => o instanceof OtherCertificateFormat))
+ || (this.crls && this.crls.length && this.crls.some(o => o instanceof OtherRevocationInfoFormat))) {
+ this.version = 5;
+ } else if (this.certificates && this.certificates.length && this.certificates.some(o => o instanceof AttributeCertificateV2)) {
+ this.version = 4;
+ } else if ((this.certificates && this.certificates.length && this.certificates.some(o => o instanceof AttributeCertificateV1))
+ || this.signerInfos.some(o => o.version === 3)
+ || this.encapContentInfo.eContentType !== SignedData.ID_DATA) {
+ this.version = 3;
+ } else {
+ this.version = 1;
+ }
+
+ outputArray.push(new asn1js.Integer({ value: this.version }));
+
+ //#region Create array of digest algorithms
+ outputArray.push(new asn1js.Set({
+ value: Array.from(this.digestAlgorithms, algorithm => algorithm.toSchema())
+ }));
+ //#endregion
+
+ outputArray.push(this.encapContentInfo.toSchema());
+
+ if (this.certificates) {
+ const certificateSet = new CertificateSet({ certificates: this.certificates });
+ const certificateSetSchema = certificateSet.toSchema();
+
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3,
+ tagNumber: 0
+ },
+ value: certificateSetSchema.valueBlock.value
+ }));
+ }
+
+ if (this.crls) {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: Array.from(this.crls, crl => {
+ if (crl instanceof OtherRevocationInfoFormat) {
+ const crlSchema = crl.toSchema();
+
+ crlSchema.idBlock.tagClass = 3;
+ crlSchema.idBlock.tagNumber = 1;
+
+ return crlSchema;
+ }
+
+ return crl.toSchema(encodeFlag);
+ })
+ }));
+ }
+
+ //#region Create array of signer infos
+ outputArray.push(new asn1js.Set({
+ value: Array.from(this.signerInfos, signerInfo => signerInfo.toSchema())
+ }));
+ //#endregion
+ //#endregion
+
+ //#region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: outputArray
+ }));
+ //#endregion
+ }
+
+ public toJSON(): SignedDataJson {
+ const res: SignedDataJson = {
+ version: this.version,
+ digestAlgorithms: Array.from(this.digestAlgorithms, algorithm => algorithm.toJSON()),
+ encapContentInfo: this.encapContentInfo.toJSON(),
+ signerInfos: Array.from(this.signerInfos, signerInfo => signerInfo.toJSON()),
+ };
+
+ if (this.certificates) {
+ res.certificates = Array.from(this.certificates, certificate => certificate.toJSON());
+ }
+
+ if (this.crls) {
+ res.crls = Array.from(this.crls, crl => crl.toJSON());
+ }
+
+
+ return res;
+ }
+
+ public verify(params?: SignedDataVerifyParams & { extendedMode?: false; }, crypto?: ICryptoEngine): Promise<boolean>;
+ public verify(params: SignedDataVerifyParams & { extendedMode: true; }, crypto?: ICryptoEngine): Promise<SignedDataVerifyResult>;
+ public async verify({
+ signer = (-1),
+ data = (EMPTY_BUFFER),
+ trustedCerts = [],
+ checkDate = (new Date()),
+ checkChain = false,
+ passedWhenNotRevValues = false,
+ extendedMode = false,
+ findOrigin = null,
+ findIssuer = null
+ }: SignedDataVerifyParams = {}, crypto = common.getCrypto(true)): Promise<boolean | SignedDataVerifyResult> {
+ let signerCert: Certificate | null = null;
+ let timestampSerial: ArrayBuffer | null = null;
+ try {
+ //#region Global variables
+ let messageDigestValue = EMPTY_BUFFER;
+ let shaAlgorithm = EMPTY_STRING;
+ let certificatePath: Certificate[] = [];
+ //#endregion
+
+ //#region Get a signer number
+ const signerInfo = this.signerInfos[signer];
+ if (!signerInfo) {
+ throw new SignedDataVerifyError({
+ date: checkDate,
+ code: 1,
+ message: "Unable to get signer by supplied index",
+ });
+ }
+ //#endregion
+
+ //#region Check that certificates field was included in signed data
+ if (!this.certificates) {
+ throw new SignedDataVerifyError({
+ date: checkDate,
+ code: 2,
+ message: "No certificates attached to this signed data",
+ });
+ }
+ //#endregion
+
+ //#region Find a certificate for specified signer
+
+ if (signerInfo.sid instanceof IssuerAndSerialNumber) {
+ for (const certificate of this.certificates) {
+ if (!(certificate instanceof Certificate))
+ continue;
+
+ if ((certificate.issuer.isEqual(signerInfo.sid.issuer)) &&
+ (certificate.serialNumber.isEqual(signerInfo.sid.serialNumber))) {
+ signerCert = certificate;
+ break;
+ }
+ }
+ } else { // Find by SubjectKeyIdentifier
+ const sid = signerInfo.sid;
+ const keyId = sid.idBlock.isConstructed
+ ? sid.valueBlock.value[0].valueBlock.valueHex // EXPLICIT OCTET STRING
+ : sid.valueBlock.valueHex; // IMPLICIT OCTET STRING
+
+ for (const certificate of this.certificates) {
+ if (!(certificate instanceof Certificate)) {
+ continue;
+ }
+
+ const digest = await crypto.digest({ name: "sha-1" }, certificate.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHexView);
+ if (pvutils.isEqualBuffer(digest, keyId)) {
+ signerCert = certificate;
+ break;
+ }
+ }
+ }
+
+ if (!signerCert) {
+ throw new SignedDataVerifyError({
+ date: checkDate,
+ code: 3,
+ message: "Unable to find signer certificate",
+ });
+ }
+ //#endregion
+
+ //#region Verify internal digest in case of "tSTInfo" content type
+ if (this.encapContentInfo.eContentType === id_eContentType_TSTInfo) {
+ //#region Check "eContent" presence
+ if (!this.encapContentInfo.eContent) {
+ throw new SignedDataVerifyError({
+ date: checkDate,
+ code: 15,
+ message: "Error during verification: TSTInfo eContent is empty",
+ signatureVerified: null,
+ signerCertificate: signerCert,
+ timestampSerial,
+ signerCertificateVerified: true
+ });
+ }
+ //#endregion
+
+ //#region Initialize TST_INFO value
+ let tstInfo: TSTInfo;
+
+ try {
+ tstInfo = TSTInfo.fromBER(this.encapContentInfo.eContent.valueBlock.valueHexView);
+ }
+ catch (ex) {
+ throw new SignedDataVerifyError({
+ date: checkDate,
+ code: 15,
+ message: "Error during verification: TSTInfo wrong ASN.1 schema ",
+ signatureVerified: null,
+ signerCertificate: signerCert,
+ timestampSerial,
+ signerCertificateVerified: true
+ });
+ }
+ //#endregion
+
+ //#region Change "checkDate" and append "timestampSerial"
+ checkDate = tstInfo.genTime;
+ timestampSerial = tstInfo.serialNumber.valueBlock.valueHexView.slice();
+ //#endregion
+
+ //#region Check that we do have detached data content
+ if (data.byteLength === 0) {
+ throw new SignedDataVerifyError({
+ date: checkDate,
+ code: 4,
+ message: "Missed detached data input array",
+ });
+ }
+ //#endregion
+
+ if (!(await tstInfo.verify({ data }, crypto))) {
+ throw new SignedDataVerifyError({
+ date: checkDate,
+ code: 15,
+ message: "Error during verification: TSTInfo verification is failed",
+ signatureVerified: false,
+ signerCertificate: signerCert,
+ timestampSerial,
+ signerCertificateVerified: true
+ });
+ }
+ }
+
+ //#endregion
+
+ if (checkChain) {
+ const certs = this.certificates.filter(certificate => (certificate instanceof Certificate && !!checkCA(certificate, signerCert))) as Certificate[];
+ const chainParams: CertificateChainValidationEngineParameters = {
+ checkDate,
+ certs,
+ trustedCerts,
+ };
+
+ if (findIssuer) {
+ chainParams.findIssuer = findIssuer;
+ }
+ if (findOrigin) {
+ chainParams.findOrigin = findOrigin;
+ }
+
+ const chainEngine = new CertificateChainValidationEngine(chainParams);
+ chainEngine.certs.push(signerCert);
+
+ if (this.crls) {
+ for (const crl of this.crls) {
+ if ("thisUpdate" in crl)
+ chainEngine.crls.push(crl);
+ else // Assumed "revocation value" has "OtherRevocationInfoFormat"
+ {
+ if (crl.otherRevInfoFormat === id_PKIX_OCSP_Basic) // Basic OCSP response
+ chainEngine.ocsps.push(new BasicOCSPResponse({ schema: crl.otherRevInfo }));
+ }
+ }
+ }
+
+ if (this.ocsps) {
+ chainEngine.ocsps.push(...(this.ocsps));
+ }
+
+ const verificationResult = await chainEngine.verify({ passedWhenNotRevValues }, crypto)
+ .catch(e => {
+ throw new SignedDataVerifyError({
+ date: checkDate,
+ code: 5,
+ message: `Validation of signer's certificate failed with error: ${((e instanceof Object) ? e.resultMessage : e)}`,
+ signerCertificate: signerCert,
+ signerCertificateVerified: false
+ });
+ });
+ if (verificationResult.certificatePath) {
+ certificatePath = verificationResult.certificatePath;
+ }
+
+ if (!verificationResult.result)
+ throw new SignedDataVerifyError({
+ date: checkDate,
+ code: 5,
+ message: `Validation of signer's certificate failed: ${verificationResult.resultMessage}`,
+ signerCertificate: signerCert,
+ signerCertificateVerified: false
+ });
+ }
+ //#endregion
+
+ //#region Find signer's hashing algorithm
+
+ const signerInfoHashAlgorithm = crypto.getAlgorithmByOID(signerInfo.digestAlgorithm.algorithmId);
+ if (!("name" in signerInfoHashAlgorithm)) {
+ throw new SignedDataVerifyError({
+ date: checkDate,
+ code: 7,
+ message: `Unsupported signature algorithm: ${signerInfo.digestAlgorithm.algorithmId}`,
+ signerCertificate: signerCert,
+ signerCertificateVerified: true
+ });
+ }
+
+ shaAlgorithm = signerInfoHashAlgorithm.name;
+ //#endregion
+
+ //#region Create correct data block for verification
+
+ const eContent = this.encapContentInfo.eContent;
+ if (eContent) // Attached data
+ {
+ if ((eContent.idBlock.tagClass === 1) &&
+ (eContent.idBlock.tagNumber === 4)) {
+ data = eContent.getValue();
+ }
+ else
+ data = eContent.valueBlock.valueBeforeDecodeView;
+ }
+ else // Detached data
+ {
+ if (data.byteLength === 0) // Check that "data" already provided by function parameter
+ {
+ throw new SignedDataVerifyError({
+ date: checkDate,
+ code: 8,
+ message: "Missed detached data input array",
+ signerCertificate: signerCert,
+ signerCertificateVerified: true
+ });
+ }
+ }
+
+ if (signerInfo.signedAttrs) {
+ //#region Check mandatory attributes
+ let foundContentType = false;
+ let foundMessageDigest = false;
+
+ for (const attribute of signerInfo.signedAttrs.attributes) {
+ //#region Check that "content-type" attribute exists
+ if (attribute.type === "1.2.840.113549.1.9.3")
+ foundContentType = true;
+ //#endregion
+
+ //#region Check that "message-digest" attribute exists
+ if (attribute.type === "1.2.840.113549.1.9.4") {
+ foundMessageDigest = true;
+ messageDigestValue = attribute.values[0].valueBlock.valueHex;
+ }
+ //#endregion
+
+ //#region Speed-up searching
+ if (foundContentType && foundMessageDigest)
+ break;
+ //#endregion
+ }
+
+ if (foundContentType === false) {
+ throw new SignedDataVerifyError({
+ date: checkDate,
+ code: 9,
+ message: "Attribute \"content-type\" is a mandatory attribute for \"signed attributes\"",
+ signerCertificate: signerCert,
+ signerCertificateVerified: true
+ });
+ }
+
+ if (foundMessageDigest === false) {
+ throw new SignedDataVerifyError({
+ date: checkDate,
+ code: 10,
+ message: "Attribute \"message-digest\" is a mandatory attribute for \"signed attributes\"",
+ signatureVerified: null,
+ signerCertificate: signerCert,
+ signerCertificateVerified: true
+ });
+ }
+ //#endregion
+ }
+ //#endregion
+
+ //#region Verify "message-digest" attribute in case of "signedAttrs"
+ if (signerInfo.signedAttrs) {
+ const messageDigest = await crypto.digest(shaAlgorithm, new Uint8Array(data));
+ if (!pvutils.isEqualBuffer(messageDigest, messageDigestValue)) {
+ throw new SignedDataVerifyError({
+ date: checkDate,
+ code: 15,
+ message: "Error during verification: Message digest doesn't match",
+ signatureVerified: null,
+ signerCertificate: signerCert,
+ timestampSerial,
+ signerCertificateVerified: true
+ });
+ }
+ data = signerInfo.signedAttrs.encodedValue;
+ }
+ //#endregion
+
+ const verifyResult = await crypto.verifyWithPublicKey(data, signerInfo.signature, signerCert.subjectPublicKeyInfo, signerCert.signatureAlgorithm, shaAlgorithm);
+
+ //#region Make a final result
+
+ if (extendedMode) {
+ return {
+ date: checkDate,
+ code: 14,
+ message: EMPTY_STRING,
+ signatureVerified: verifyResult,
+ signerCertificate: signerCert,
+ timestampSerial,
+ signerCertificateVerified: true,
+ certificatePath
+ };
+ } else {
+ return verifyResult;
+ }
+ } catch (e) {
+ if (e instanceof SignedDataVerifyError) {
+ throw e;
+ }
+ throw new SignedDataVerifyError({
+ date: checkDate,
+ code: 15,
+ message: `Error during verification: ${e instanceof Error ? e.message : e}`,
+ signatureVerified: null,
+ signerCertificate: signerCert,
+ timestampSerial,
+ signerCertificateVerified: true
+ });
+ }
+ }
+
+ /**
+ * Signing current SignedData
+ * @param privateKey Private key for "subjectPublicKeyInfo" structure
+ * @param signerIndex Index number (starting from 0) of signer index to make signature for
+ * @param hashAlgorithm Hashing algorithm. Default SHA-1
+ * @param data Detached data
+ * @param crypto Crypto engine
+ */
+ public async sign(privateKey: CryptoKey, signerIndex: number, hashAlgorithm = "SHA-1", data: BufferSource = (EMPTY_BUFFER), crypto = common.getCrypto(true)): Promise<void> {
+ //#region Initial checking
+ if (!privateKey)
+ throw new Error("Need to provide a private key for signing");
+ //#endregion
+
+ //#region Simple check for supported algorithm
+ const hashAlgorithmOID = crypto.getOIDByAlgorithm({ name: hashAlgorithm }, true, "hashAlgorithm");
+ //#endregion
+
+ //#region Append information about hash algorithm
+ if ((this.digestAlgorithms.filter(algorithm => algorithm.algorithmId === hashAlgorithmOID)).length === 0) {
+ this.digestAlgorithms.push(new AlgorithmIdentifier({
+ algorithmId: hashAlgorithmOID,
+ algorithmParams: new asn1js.Null()
+ }));
+ }
+
+ const signerInfo = this.signerInfos[signerIndex];
+ if (!signerInfo) {
+ throw new RangeError("SignerInfo index is out of range");
+ }
+ signerInfo.digestAlgorithm = new AlgorithmIdentifier({
+ algorithmId: hashAlgorithmOID,
+ algorithmParams: new asn1js.Null()
+ });
+ //#endregion
+
+ //#region Get a "default parameters" for current algorithm and set correct signature algorithm
+ const signatureParams = await crypto.getSignatureParameters(privateKey, hashAlgorithm);
+ const parameters = signatureParams.parameters;
+ signerInfo.signatureAlgorithm = signatureParams.signatureAlgorithm;
+ //#endregion
+
+ //#region Create TBS data for signing
+ if (signerInfo.signedAttrs) {
+ if (signerInfo.signedAttrs.encodedValue.byteLength !== 0)
+ data = signerInfo.signedAttrs.encodedValue;
+ else {
+ data = signerInfo.signedAttrs.toSchema().toBER();
+
+ //#region Change type from "[0]" to "SET" accordingly to standard
+ const view = pvtsutils.BufferSourceConverter.toUint8Array(data);
+ view[0] = 0x31;
+ //#endregion
+ }
+ }
+ else {
+ const eContent = this.encapContentInfo.eContent;
+ if (eContent) // Attached data
+ {
+ if ((eContent.idBlock.tagClass === 1) &&
+ (eContent.idBlock.tagNumber === 4)) {
+ data = eContent.getValue();
+ }
+ else
+ data = eContent.valueBlock.valueBeforeDecodeView;
+ }
+ else // Detached data
+ {
+ if (data.byteLength === 0) // Check that "data" already provided by function parameter
+ throw new Error("Missed detached data input array");
+ }
+ }
+ //#endregion
+
+ //#region Signing TBS data on provided private key
+ const signature = await crypto.signWithPrivateKey(data, privateKey, parameters as any);
+ signerInfo.signature = new asn1js.OctetString({ valueHex: signature });
+ //#endregion
+ }
+
+}
+
+