import * as asn1js from "asn1js"; import * as pvutils from "pvutils"; import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier"; import { EncryptedData, EncryptedDataEncryptParams } from "./EncryptedData"; import { EncryptedContentInfo } from "./EncryptedContentInfo"; import { PrivateKeyInfo } from "./PrivateKeyInfo"; import * as Schema from "./Schema"; import { AsnError } from "./errors"; import { PkiObject, PkiObjectParameters } from "./PkiObject"; import { EMPTY_STRING } from "./constants"; import * as common from "./common"; const ENCRYPTION_ALGORITHM = "encryptionAlgorithm"; const ENCRYPTED_DATA = "encryptedData"; const PARSED_VALUE = "parsedValue"; const CLEAR_PROPS = [ ENCRYPTION_ALGORITHM, ENCRYPTED_DATA, ]; export interface IPKCS8ShroudedKeyBag { encryptionAlgorithm: AlgorithmIdentifier; encryptedData: asn1js.OctetString; parsedValue?: PrivateKeyInfo; } export type PKCS8ShroudedKeyBagParameters = PkiObjectParameters & Partial; export interface PKCS8ShroudedKeyBagJson { encryptionAlgorithm: AlgorithmIdentifierJson; encryptedData: asn1js.OctetStringJson; } type PKCS8ShroudedKeyBagMakeInternalValuesParams = Omit; /** * Represents the PKCS8ShroudedKeyBag structure described in [RFC7292](https://datatracker.ietf.org/doc/html/rfc7292) */ export class PKCS8ShroudedKeyBag extends PkiObject implements IPKCS8ShroudedKeyBag { public static override CLASS_NAME = "PKCS8ShroudedKeyBag"; public encryptionAlgorithm!: AlgorithmIdentifier; public encryptedData!: asn1js.OctetString; public parsedValue?: PrivateKeyInfo; /** * Initializes a new instance of the {@link PKCS8ShroudedKeyBag} class * @param parameters Initialization parameters */ constructor(parameters: PKCS8ShroudedKeyBagParameters = {}) { super(); this.encryptionAlgorithm = pvutils.getParametersValue(parameters, ENCRYPTION_ALGORITHM, PKCS8ShroudedKeyBag.defaultValues(ENCRYPTION_ALGORITHM)); this.encryptedData = pvutils.getParametersValue(parameters, ENCRYPTED_DATA, PKCS8ShroudedKeyBag.defaultValues(ENCRYPTED_DATA)); if (PARSED_VALUE in parameters) { this.parsedValue = pvutils.getParametersValue(parameters, PARSED_VALUE, PKCS8ShroudedKeyBag.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 ENCRYPTION_ALGORITHM): AlgorithmIdentifier; public static override defaultValues(memberName: typeof ENCRYPTED_DATA): asn1js.OctetString; public static override defaultValues(memberName: typeof PARSED_VALUE): PrivateKeyInfo; public static override defaultValues(memberName: string): any { switch (memberName) { case ENCRYPTION_ALGORITHM: return (new AlgorithmIdentifier()); case ENCRYPTED_DATA: return (new asn1js.OctetString()); 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 ENCRYPTION_ALGORITHM: return ((AlgorithmIdentifier.compareWithDefault("algorithmId", memberValue.algorithmId)) && (("algorithmParams" in memberValue) === false)); case ENCRYPTED_DATA: return (memberValue.isEqual(PKCS8ShroudedKeyBag.defaultValues(memberName))); case PARSED_VALUE: return ((memberValue instanceof Object) && (Object.keys(memberValue).length === 0)); default: return super.defaultValues(memberName); } } /** * @inheritdoc * @asn ASN.1 schema * ```asn * PKCS8ShroudedKeyBag ::= EncryptedPrivateKeyInfo * * EncryptedPrivateKeyInfo ::= SEQUENCE { * encryptionAlgorithm AlgorithmIdentifier {{KeyEncryptionAlgorithms}}, * encryptedData EncryptedData * } * * EncryptedData ::= OCTET STRING *``` */ public static override schema(parameters: Schema.SchemaParameters<{ encryptionAlgorithm?: AlgorithmIdentifierSchema; encryptedData?: string; }> = {}): Schema.SchemaType { /** * @type {Object} * @property {string} [blockName] * @property {string} [encryptionAlgorithm] * @property {string} [encryptedData] */ const names = pvutils.getParametersValue>(parameters, "names", {}); return (new asn1js.Sequence({ name: (names.blockName || EMPTY_STRING), value: [ AlgorithmIdentifier.schema(names.encryptionAlgorithm || { names: { blockName: ENCRYPTION_ALGORITHM } }), new asn1js.Choice({ value: [ new asn1js.OctetString({ name: (names.encryptedData || ENCRYPTED_DATA) }), new asn1js.OctetString({ idBlock: { isConstructed: true }, name: (names.encryptedData || ENCRYPTED_DATA) }) ] }) ] })); } public fromSchema(schema: Schema.SchemaType): void { // Clear input data first pvutils.clearProps(schema, CLEAR_PROPS); // Check the schema is valid const asn1 = asn1js.compareSchema(schema, schema, PKCS8ShroudedKeyBag.schema({ names: { encryptionAlgorithm: { names: { blockName: ENCRYPTION_ALGORITHM } }, encryptedData: ENCRYPTED_DATA } }) ); AsnError.assertSchema(asn1, this.className); // Get internal properties from parsed schema this.encryptionAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.encryptionAlgorithm }); this.encryptedData = asn1.result.encryptedData; } public toSchema(): asn1js.Sequence { return (new asn1js.Sequence({ value: [ this.encryptionAlgorithm.toSchema(), this.encryptedData ] })); } public toJSON(): PKCS8ShroudedKeyBagJson { return { encryptionAlgorithm: this.encryptionAlgorithm.toJSON(), encryptedData: this.encryptedData.toJSON(), }; } protected async parseInternalValues(parameters: { password: ArrayBuffer; }, crypto = common.getCrypto(true)) { //#region Initial variables const cmsEncrypted = new EncryptedData({ encryptedContentInfo: new EncryptedContentInfo({ contentEncryptionAlgorithm: this.encryptionAlgorithm, encryptedContent: this.encryptedData }) }); //#endregion //#region Decrypt internal data const decryptedData = await cmsEncrypted.decrypt(parameters, crypto); //#endregion //#region Initialize PARSED_VALUE with decrypted PKCS#8 private key this.parsedValue = PrivateKeyInfo.fromBER(decryptedData); //#endregion } public async makeInternalValues(parameters: PKCS8ShroudedKeyBagMakeInternalValuesParams): Promise { //#region Check that we do have PARSED_VALUE if (!this.parsedValue) { throw new Error("Please initialize \"parsedValue\" first"); } //#endregion //#region Initial variables const cmsEncrypted = new EncryptedData(); //#endregion //#region Encrypt internal data const encryptParams: EncryptedDataEncryptParams = { ...parameters, contentToEncrypt: this.parsedValue.toSchema().toBER(false), }; await cmsEncrypted.encrypt(encryptParams); if (!cmsEncrypted.encryptedContentInfo.encryptedContent) { throw new Error("The filed `encryptedContent` in EncryptedContentInfo is empty"); } //#endregion //#region Initialize internal values this.encryptionAlgorithm = cmsEncrypted.encryptedContentInfo.contentEncryptionAlgorithm; this.encryptedData = cmsEncrypted.encryptedContentInfo.encryptedContent; //#endregion } }