Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/third_party/js/PKI.js/src/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 17 kB image not shown  

Quelle  PFX.ts   Sprache: unbekannt

 
import * as asn1js from "asn1js";
import * as pvutils from "pvutils";
import * as common from "./common";
import { ContentInfo, ContentInfoJson, ContentInfoSchema } from "./ContentInfo";
import { MacData, MacDataJson, MacDataSchema } from "./MacData";
import { DigestInfo } from "./DigestInfo";
import { AlgorithmIdentifier } from "./AlgorithmIdentifier";
import { SignedData } from "./SignedData";
import { EncapsulatedContentInfo } from "./EncapsulatedContentInfo";
import { Attribute } from "./Attribute";
import { SignerInfo } from "./SignerInfo";
import { IssuerAndSerialNumber } from "./IssuerAndSerialNumber";
import { SignedAndUnsignedAttributes } from "./SignedAndUnsignedAttributes";
import { AuthenticatedSafe } from "./AuthenticatedSafe";
import * as Schema from "./Schema";
import { Certificate } from "./Certificate";
import { ArgumentError, AsnError, ParameterError } from "./errors";
import { PkiObject, PkiObjectParameters } from "./PkiObject";
import { BufferSourceConverter } from "pvtsutils";
import { EMPTY_STRING } from "./constants";

const VERSION = "version";
const AUTH_SAFE = "authSafe";
const MAC_DATA = "macData";
const PARSED_VALUE = "parsedValue";
const CLERA_PROPS = [
  VERSION,
  AUTH_SAFE,
  MAC_DATA
];

export interface IPFX {
  version: number;
  authSafe: ContentInfo;
  macData?: MacData;
  parsedValue?: PFXParsedValue;
}

export interface PFXJson {
  version: number;
  authSafe: ContentInfoJson;
  macData?: MacDataJson;
}

export type PFXParameters = PkiObjectParameters & Partial<IPFX>;

export interface PFXParsedValue {
  authenticatedSafe?: AuthenticatedSafe;
  integrityMode?: number;
}

export type MakeInternalValuesParams =
  {
    // empty
  }
  |
  {
    iterations: number;
    pbkdf2HashAlgorithm: Algorithm;
    hmacHashAlgorithm: string;
    password: ArrayBuffer;
  }
  |
  {
    signingCertificate: Certificate;
    privateKey: CryptoKey;
    hashAlgorithm: string;
  };

/**
 * Represents the PFX structure described in [RFC7292](https://datatracker.ietf.org/doc/html/rfc7292)
 */
export class PFX extends PkiObject implements IPFX {

  public static override CLASS_NAME = "PFX";

  public version!: number;
  public authSafe!: ContentInfo;
  public macData?: MacData;
  public parsedValue?: PFXParsedValue;

  /**
   * Initializes a new instance of the {@link PFX} class
   * @param parameters Initialization parameters
   */
  constructor(parameters: PFXParameters = {}) {
    super();

    this.version = pvutils.getParametersValue(parameters, VERSION, PFX.defaultValues(VERSION));
    this.authSafe = pvutils.getParametersValue(parameters, AUTH_SAFE, PFX.defaultValues(AUTH_SAFE));
    if (MAC_DATA in parameters) {
      this.macData = pvutils.getParametersValue(parameters, MAC_DATA, PFX.defaultValues(MAC_DATA));
    }
    if (PARSED_VALUE in parameters) {
      this.parsedValue = pvutils.getParametersValue(parameters, PARSED_VALUE, PFX.defaultValues(PARSED_VALUE));
    }

    if (parameters.schema) {
      this.fromSchema(parameters.schema);
    }
  }

  /**
   * Returns default values for all class members
   * @param memberName String name for a class member
   * @returns Default value
   */
  public static override defaultValues(memberName: typeof VERSION): number;
  public static override defaultValues(memberName: typeof AUTH_SAFE): ContentInfo;
  public static override defaultValues(memberName: typeof MAC_DATA): MacData;
  public static override defaultValues(memberName: typeof PARSED_VALUE): PFXParsedValue;
  public static override defaultValues(memberName: string): any {
    switch (memberName) {
      case VERSION:
        return 3;
      case AUTH_SAFE:
        return (new ContentInfo());
      case MAC_DATA:
        return (new MacData());
      case PARSED_VALUE:
        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 === PFX.defaultValues(memberName));
      case AUTH_SAFE:
        return ((ContentInfo.compareWithDefault("contentType", memberValue.contentType)) &&
          (ContentInfo.compareWithDefault("content", memberValue.content)));
      case MAC_DATA:
        return ((MacData.compareWithDefault("mac", memberValue.mac)) &&
          (MacData.compareWithDefault("macSalt", memberValue.macSalt)) &&
          (MacData.compareWithDefault("iterations", memberValue.iterations)));
      case PARSED_VALUE:
        return ((memberValue instanceof Object) && (Object.keys(memberValue).length === 0));
      default:
        return super.defaultValues(memberName);
    }
  }

  /**
   * @inheritdoc
   * @asn ASN.1 schema
   * ```asn
   * PFX ::= SEQUENCE {
   *    version     INTEGER {v3(3)}(v3,...),
   *    authSafe    ContentInfo,
   *    macData     MacData OPTIONAL
   * }
   *```
   */
  public static override schema(parameters: Schema.SchemaParameters<{
    version?: string;
    authSafe?: ContentInfoSchema;
    macData?: MacDataSchema;
  }> = {}): Schema.SchemaType {
    const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {});

    return (new asn1js.Sequence({
      name: (names.blockName || EMPTY_STRING),
      value: [
        new asn1js.Integer({ name: (names.version || VERSION) }),
        ContentInfo.schema(names.authSafe || {
          names: {
            blockName: AUTH_SAFE
          }
        }),
        MacData.schema(names.macData || {
          names: {
            blockName: MAC_DATA,
            optional: true
          }
        })
      ]
    }));
  }

  public fromSchema(schema: Schema.SchemaType): void {
    // Clear input data first
    pvutils.clearProps(schema, CLERA_PROPS);

    // Check the schema is valid
    const asn1 = asn1js.compareSchema(schema,
      schema,
      PFX.schema({
        names: {
          version: VERSION,
          authSafe: {
            names: {
              blockName: AUTH_SAFE
            }
          },
          macData: {
            names: {
              blockName: MAC_DATA
            }
          }
        }
      })
    );
    AsnError.assertSchema(asn1, this.className);

    // Get internal properties from parsed schema
    this.version = asn1.result.version.valueBlock.valueDec;
    this.authSafe = new ContentInfo({ schema: asn1.result.authSafe });
    if (MAC_DATA in asn1.result)
      this.macData = new MacData({ schema: asn1.result.macData });
  }

  public toSchema(): asn1js.Sequence {
    //#region Construct and return new ASN.1 schema for this object
    const outputArray = [
      new asn1js.Integer({ value: this.version }),
      this.authSafe.toSchema()
    ];

    if (this.macData) {
      outputArray.push(this.macData.toSchema());
    }

    return (new asn1js.Sequence({
      value: outputArray
    }));
    //#endregion
  }

  public toJSON(): PFXJson {
    const output: PFXJson = {
      version: this.version,
      authSafe: this.authSafe.toJSON()
    };

    if (this.macData) {
      output.macData = this.macData.toJSON();
    }

    return output;
  }

  /**
   * Making ContentInfo from PARSED_VALUE object
   * @param parameters Parameters, specific to each "integrity mode"
   * @param crypto Crypto engine
   */
  public async makeInternalValues(parameters: MakeInternalValuesParams = {}, crypto = common.getCrypto(true)) {
    //#region Check mandatory parameter
    ArgumentError.assert(parameters, "parameters", "object");
    if (!this.parsedValue) {
      throw new Error("Please call \"parseValues\" function first in order to make \"parsedValue\" data");
    }
    ParameterError.assertEmpty(this.parsedValue.integrityMode, "integrityMode", "parsedValue");
    ParameterError.assertEmpty(this.parsedValue.authenticatedSafe, "authenticatedSafe", "parsedValue");
    //#endregion

    //#region Makes values for each particular integrity mode
    switch (this.parsedValue.integrityMode) {
      //#region HMAC-based integrity
      case 0:
        {
          //#region Check additional mandatory parameters
          if (!("iterations" in parameters))
            throw new ParameterError("iterations");
          ParameterError.assertEmpty(parameters.pbkdf2HashAlgorithm, "pbkdf2HashAlgorithm");
          ParameterError.assertEmpty(parameters.hmacHashAlgorithm, "hmacHashAlgorithm");
          ParameterError.assertEmpty(parameters.password, "password");
          //#endregion

          //#region Initial variables
          const saltBuffer = new ArrayBuffer(64);
          const saltView = new Uint8Array(saltBuffer);

          crypto.getRandomValues(saltView);

          const data = this.parsedValue.authenticatedSafe.toSchema().toBER(false);

          this.authSafe = new ContentInfo({
            contentType: ContentInfo.DATA,
            content: new asn1js.OctetString({ valueHex: data })
          });
          //#endregion

          //#region Call current crypto engine for making HMAC-based data stamp
          const result = await crypto.stampDataWithPassword({
            password: parameters.password,
            hashAlgorithm: parameters.hmacHashAlgorithm,
            salt: saltBuffer,
            iterationCount: parameters.iterations,
            contentToStamp: data
          });
          //#endregion

          //#region Make MAC_DATA values
          this.macData = new MacData({
            mac: new DigestInfo({
              digestAlgorithm: new AlgorithmIdentifier({
                algorithmId: crypto.getOIDByAlgorithm({ name: parameters.hmacHashAlgorithm }, true, "hmacHashAlgorithm"),
              }),
              digest: new asn1js.OctetString({ valueHex: result })
            }),
            macSalt: new asn1js.OctetString({ valueHex: saltBuffer }),
            iterations: parameters.iterations
          });
          //#endregion
          //#endregion
        }
        break;
      //#endregion
      //#region publicKey-based integrity
      case 1:
        {
          //#region Check additional mandatory parameters
          if (!("signingCertificate" in parameters)) {
            throw new ParameterError("signingCertificate");
          }
          ParameterError.assertEmpty(parameters.privateKey, "privateKey");
          ParameterError.assertEmpty(parameters.hashAlgorithm, "hashAlgorithm");
          //#endregion

          //#region Making data to be signed
          // NOTE: all internal data for "authenticatedSafe" must be already prepared.
          // Thus user must call "makeValues" for all internal "SafeContent" value with appropriate parameters.
          // Or user can choose to use values from initial parsing of existing PKCS#12 data.

          const toBeSigned = this.parsedValue.authenticatedSafe.toSchema().toBER(false);
          //#endregion

          //#region Initial variables
          const cmsSigned = new SignedData({
            version: 1,
            encapContentInfo: new EncapsulatedContentInfo({
              eContentType: "1.2.840.113549.1.7.1", // "data" content type
              eContent: new asn1js.OctetString({ valueHex: toBeSigned })
            }),
            certificates: [parameters.signingCertificate]
          });
          //#endregion

          //#region Making additional attributes for CMS Signed Data
          //#region Create a message digest
          const result = await crypto.digest({ name: parameters.hashAlgorithm }, new Uint8Array(toBeSigned));
          //#endregion

          //#region Combine all signed extensions
          //#region Initial variables
          const signedAttr: Attribute[] = [];
          //#endregion

          //#region contentType
          signedAttr.push(new Attribute({
            type: "1.2.840.113549.1.9.3",
            values: [
              new asn1js.ObjectIdentifier({ value: "1.2.840.113549.1.7.1" })
            ]
          }));
          //#endregion
          //#region signingTime
          signedAttr.push(new Attribute({
            type: "1.2.840.113549.1.9.5",
            values: [
              new asn1js.UTCTime({ valueDate: new Date() })
            ]
          }));
          //#endregion
          //#region messageDigest
          signedAttr.push(new Attribute({
            type: "1.2.840.113549.1.9.4",
            values: [
              new asn1js.OctetString({ valueHex: result })
            ]
          }));
          //#endregion

          //#region Making final value for "SignerInfo" type
          cmsSigned.signerInfos.push(new SignerInfo({
            version: 1,
            sid: new IssuerAndSerialNumber({
              issuer: parameters.signingCertificate.issuer,
              serialNumber: parameters.signingCertificate.serialNumber
            }),
            signedAttrs: new SignedAndUnsignedAttributes({
              type: 0,
              attributes: signedAttr
            })
          }));
          //#endregion
          //#endregion
          //#endregion

          //#region Signing CMS Signed Data
          await cmsSigned.sign(parameters.privateKey, 0, parameters.hashAlgorithm, undefined, crypto);
          //#endregion

          //#region Making final CMS_CONTENT_INFO type
          this.authSafe = new ContentInfo({
            contentType: "1.2.840.113549.1.7.2",
            content: cmsSigned.toSchema(true)
          });
          //#endregion
        }
        break;
      //#endregion
      //#region default
      default:
        throw new Error(`Parameter "integrityMode" has unknown value: ${this.parsedValue.integrityMode}`);
      //#endregion
    }
    //#endregion
  }

  public async parseInternalValues(parameters: {
    checkIntegrity?: boolean;
    password?: ArrayBuffer;
  }, crypto = common.getCrypto(true)) {
    //#region Check input data from "parameters"
    ArgumentError.assert(parameters, "parameters", "object");

    if (parameters.checkIntegrity === undefined) {
      parameters.checkIntegrity = true;
    }
    //#endregion

    //#region Create value for "this.parsedValue.authenticatedSafe" and check integrity
    this.parsedValue = {};

    switch (this.authSafe.contentType) {
      //#region data
      case ContentInfo.DATA:
        {
          //#region Check additional mandatory parameters
          ParameterError.assertEmpty(parameters.password, "password");
          //#endregion

          //#region Integrity based on HMAC
          this.parsedValue.integrityMode = 0;
          //#endregion

          //#region Check that we do have OCTETSTRING as "content"
          ArgumentError.assert(this.authSafe.content, "authSafe.content", asn1js.OctetString);
          //#endregion

          //#region Check we have "constructive encoding" for AuthSafe content
          const authSafeContent = this.authSafe.content.getValue();
          //#endregion

          //#region Set "authenticatedSafe" value
          this.parsedValue.authenticatedSafe = AuthenticatedSafe.fromBER(authSafeContent);
          //#endregion

          //#region Check integrity
          if (parameters.checkIntegrity) {
            //#region Check that MAC_DATA exists
            if (!this.macData) {
              throw new Error("Absent \"macData\" value, can not check PKCS#12 data integrity");
            }
            //#endregion

            //#region Initial variables
            const hashAlgorithm = crypto.getAlgorithmByOID(this.macData.mac.digestAlgorithm.algorithmId, true, "digestAlgorithm");
            //#endregion

            //#region Call current crypto engine for verifying HMAC-based data stamp
            const result = await crypto.verifyDataStampedWithPassword({
              password: parameters.password,
              hashAlgorithm: hashAlgorithm.name,
              salt: BufferSourceConverter.toArrayBuffer(this.macData.macSalt.valueBlock.valueHexView),
              iterationCount: this.macData.iterations || 1,
              contentToVerify: authSafeContent,
              signatureToVerify: BufferSourceConverter.toArrayBuffer(this.macData.mac.digest.valueBlock.valueHexView),
            });
            //#endregion

            //#region Verify HMAC signature
            if (!result) {
              throw new Error("Integrity for the PKCS#12 data is broken!");
            }
            //#endregion
          }
          //#endregion
        }
        break;
      //#endregion
      //#region signedData
      case ContentInfo.SIGNED_DATA:
        {
          //#region Integrity based on signature using public key
          this.parsedValue.integrityMode = 1;
          //#endregion

          //#region Parse CMS Signed Data
          const cmsSigned = new SignedData({ schema: this.authSafe.content });
          //#endregion

          //#region Check that we do have OCTET STRING as "content"
          const eContent = cmsSigned.encapContentInfo.eContent;
          ParameterError.assert(eContent, "eContent", "cmsSigned.encapContentInfo");
          ArgumentError.assert(eContent, "eContent", asn1js.OctetString);
          //#endregion

          //#region Create correct data block for verification
          const data = eContent.getValue();
          //#endregion

          //#region Set "authenticatedSafe" value
          this.parsedValue.authenticatedSafe = AuthenticatedSafe.fromBER(data);
          //#endregion

          //#region Check integrity
          const ok = await cmsSigned.verify({ signer: 0, checkChain: false }, crypto);
          if (!ok) {
            throw new Error("Integrity for the PKCS#12 data is broken!");
          }
          //#endregion
        }
        break;
      //#endregion
      //#region default
      default:
        throw new Error(`Incorrect value for "this.authSafe.contentType": ${this.authSafe.contentType}`);
      //#endregion
    }
    //#endregion
  }

}

[ Dauer der Verarbeitung: 0.35 Sekunden  (vorverarbeitet)  ]