Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


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.31 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge