diff options
Diffstat (limited to 'third_party/js/PKI.js/src/AuthenticatedSafe.ts')
-rw-r--r-- | third_party/js/PKI.js/src/AuthenticatedSafe.ts | 379 |
1 files changed, 379 insertions, 0 deletions
diff --git a/third_party/js/PKI.js/src/AuthenticatedSafe.ts b/third_party/js/PKI.js/src/AuthenticatedSafe.ts new file mode 100644 index 0000000000..ea85afbd38 --- /dev/null +++ b/third_party/js/PKI.js/src/AuthenticatedSafe.ts @@ -0,0 +1,379 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { ContentInfo, ContentInfoJson } from "./ContentInfo"; +import { SafeContents } from "./SafeContents"; +import { EnvelopedData } from "./EnvelopedData"; +import { EncryptedData } from "./EncryptedData"; +import * as Schema from "./Schema"; +import { id_ContentType_Data, id_ContentType_EncryptedData, id_ContentType_EnvelopedData } from "./ObjectIdentifiers"; +import { ArgumentError, AsnError, ParameterError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { EMPTY_STRING } from "./constants"; +import * as common from "./common"; + +const SAFE_CONTENTS = "safeContents"; +const PARSED_VALUE = "parsedValue"; +const CONTENT_INFOS = "contentInfos"; + +export interface IAuthenticatedSafe { + safeContents: ContentInfo[]; + parsedValue: any; +} + +export type AuthenticatedSafeParameters = PkiObjectParameters & Partial<IAuthenticatedSafe>; + +export interface AuthenticatedSafeJson { + safeContents: ContentInfoJson[]; +} + +export type SafeContent = ContentInfo | EncryptedData | EnvelopedData | object; + +/** + * Represents the AuthenticatedSafe structure described in [RFC7292](https://datatracker.ietf.org/doc/html/rfc7292) + */ +export class AuthenticatedSafe extends PkiObject implements IAuthenticatedSafe { + + public static override CLASS_NAME = "AuthenticatedSafe"; + + public safeContents!: ContentInfo[]; + public parsedValue: any; + + /** + * Initializes a new instance of the {@link AuthenticatedSafe} class + * @param parameters Initialization parameters + */ + constructor(parameters: AuthenticatedSafeParameters = {}) { + super(); + + this.safeContents = pvutils.getParametersValue(parameters, SAFE_CONTENTS, AuthenticatedSafe.defaultValues(SAFE_CONTENTS)); + if (PARSED_VALUE in parameters) { + this.parsedValue = pvutils.getParametersValue(parameters, PARSED_VALUE, AuthenticatedSafe.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 SAFE_CONTENTS): ContentInfo[]; + public static override defaultValues(memberName: typeof PARSED_VALUE): any; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case SAFE_CONTENTS: + return []; + 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 SAFE_CONTENTS: + return (memberValue.length === 0); + case PARSED_VALUE: + return ((memberValue instanceof Object) && (Object.keys(memberValue).length === 0)); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * AuthenticatedSafe ::= SEQUENCE OF ContentInfo + * -- Data if unencrypted + * -- EncryptedData if password-encrypted + * -- EnvelopedData if public key-encrypted + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + contentInfos?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || EMPTY_STRING), + value: [ + new asn1js.Repeated({ + name: (names.contentInfos || EMPTY_STRING), + value: ContentInfo.schema() + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, [ + CONTENT_INFOS + ]); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + AuthenticatedSafe.schema({ + names: { + contentInfos: CONTENT_INFOS + } + }) + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.safeContents = Array.from(asn1.result.contentInfos, element => new ContentInfo({ schema: element })); + } + + public toSchema(): asn1js.Sequence { + return (new asn1js.Sequence({ + value: Array.from(this.safeContents, o => o.toSchema()) + })); + } + + public toJSON(): AuthenticatedSafeJson { + return { + safeContents: Array.from(this.safeContents, o => o.toJSON()) + }; + } + + public async parseInternalValues(parameters: { safeContents: SafeContent[]; }, crypto = common.getCrypto(true)): Promise<void> { + //#region Check input data from "parameters" + ParameterError.assert(parameters, SAFE_CONTENTS); + ArgumentError.assert(parameters.safeContents, SAFE_CONTENTS, "Array"); + if (parameters.safeContents.length !== this.safeContents.length) { + throw new ArgumentError("Length of \"parameters.safeContents\" must be equal to \"this.safeContents.length\""); + } + //#endregion + + //#region Create value for "this.parsedValue.authenticatedSafe" + this.parsedValue = { + safeContents: [] as any[], + }; + + for (const [index, content] of this.safeContents.entries()) { + const safeContent = parameters.safeContents[index]; + const errorTarget = `parameters.safeContents[${index}]`; + switch (content.contentType) { + //#region data + case id_ContentType_Data: + { + // Check that we do have OCTET STRING as "content" + ArgumentError.assert(content.content, "this.safeContents[j].content", asn1js.OctetString); + + //#region Check we have "constructive encoding" for AuthSafe content + const authSafeContent = content.content.getValue(); + //#endregion + + //#region Finally initialize initial values of SAFE_CONTENTS type + this.parsedValue.safeContents.push({ + privacyMode: 0, // No privacy, clear data + value: SafeContents.fromBER(authSafeContent) + }); + //#endregion + } + break; + //#endregion + //#region envelopedData + case id_ContentType_EnvelopedData: + { + //#region Initial variables + const cmsEnveloped = new EnvelopedData({ schema: content.content }); + //#endregion + + //#region Check mandatory parameters + ParameterError.assert(errorTarget, safeContent, "recipientCertificate", "recipientKey"); + const envelopedData = safeContent as any; + const recipientCertificate = envelopedData.recipientCertificate; + const recipientKey = envelopedData.recipientKey; + //#endregion + + //#region Decrypt CMS EnvelopedData using first recipient information + const decrypted = await cmsEnveloped.decrypt(0, { + recipientCertificate, + recipientPrivateKey: recipientKey + }, crypto); + + this.parsedValue.safeContents.push({ + privacyMode: 2, // Public-key privacy mode + value: SafeContents.fromBER(decrypted), + }); + //#endregion + } + break; + //#endregion + //#region encryptedData + case id_ContentType_EncryptedData: + { + //#region Initial variables + const cmsEncrypted = new EncryptedData({ schema: content.content }); + //#endregion + + //#region Check mandatory parameters + ParameterError.assert(errorTarget, safeContent, "password"); + + const password = (safeContent as any).password; + //#endregion + + //#region Decrypt CMS EncryptedData using password + const decrypted = await cmsEncrypted.decrypt({ + password + }, crypto); + //#endregion + + //#region Initialize internal data + this.parsedValue.safeContents.push({ + privacyMode: 1, // Password-based privacy mode + value: SafeContents.fromBER(decrypted), + }); + //#endregion + } + break; + //#endregion + //#region default + default: + throw new Error(`Unknown "contentType" for AuthenticatedSafe: " ${content.contentType}`); + //#endregion + } + } + //#endregion + } + public async makeInternalValues(parameters: { + safeContents: any[]; + }, crypto = common.getCrypto(true)): Promise<this> { + //#region Check data in PARSED_VALUE + if (!(this.parsedValue)) { + throw new Error("Please run \"parseValues\" first or add \"parsedValue\" manually"); + } + ArgumentError.assert(this.parsedValue, "this.parsedValue", "object"); + ArgumentError.assert(this.parsedValue.safeContents, "this.parsedValue.safeContents", "Array"); + + //#region Check input data from "parameters" + ArgumentError.assert(parameters, "parameters", "object"); + ParameterError.assert(parameters, "safeContents"); + ArgumentError.assert(parameters.safeContents, "parameters.safeContents", "Array"); + if (parameters.safeContents.length !== this.parsedValue.safeContents.length) { + throw new ArgumentError("Length of \"parameters.safeContents\" must be equal to \"this.parsedValue.safeContents\""); + } + //#endregion + + //#region Create internal values from already parsed values + this.safeContents = []; + + for (const [index, content] of this.parsedValue.safeContents.entries()) { + //#region Check current "content" value + ParameterError.assert("content", content, "privacyMode", "value"); + ArgumentError.assert(content.value, "content.value", SafeContents); + //#endregion + + switch (content.privacyMode) { + //#region No privacy + case 0: + { + const contentBuffer = content.value.toSchema().toBER(false); + + this.safeContents.push(new ContentInfo({ + contentType: "1.2.840.113549.1.7.1", + content: new asn1js.OctetString({ valueHex: contentBuffer }) + })); + } + break; + //#endregion + //#region Privacy with password + case 1: + { + //#region Initial variables + const cmsEncrypted = new EncryptedData(); + + const currentParameters = parameters.safeContents[index]; + currentParameters.contentToEncrypt = content.value.toSchema().toBER(false); + //#endregion + + //#region Encrypt CMS EncryptedData using password + await cmsEncrypted.encrypt(currentParameters); + //#endregion + + //#region Store result content in CMS_CONTENT_INFO type + this.safeContents.push(new ContentInfo({ + contentType: "1.2.840.113549.1.7.6", + content: cmsEncrypted.toSchema() + })); + //#endregion + } + break; + //#endregion + //#region Privacy with public key + case 2: + { + //#region Initial variables + const cmsEnveloped = new EnvelopedData(); + const contentToEncrypt = content.value.toSchema().toBER(false); + const safeContent = parameters.safeContents[index]; + //#endregion + + //#region Check mandatory parameters + ParameterError.assert(`parameters.safeContents[${index}]`, safeContent, "encryptingCertificate", "encryptionAlgorithm"); + + switch (true) { + case (safeContent.encryptionAlgorithm.name.toLowerCase() === "aes-cbc"): + case (safeContent.encryptionAlgorithm.name.toLowerCase() === "aes-gcm"): + break; + default: + throw new Error(`Incorrect parameter "encryptionAlgorithm" in "parameters.safeContents[i]": ${safeContent.encryptionAlgorithm}`); + } + + switch (true) { + case (safeContent.encryptionAlgorithm.length === 128): + case (safeContent.encryptionAlgorithm.length === 192): + case (safeContent.encryptionAlgorithm.length === 256): + break; + default: + throw new Error(`Incorrect parameter "encryptionAlgorithm.length" in "parameters.safeContents[i]": ${safeContent.encryptionAlgorithm.length}`); + } + //#endregion + + //#region Making correct "encryptionAlgorithm" variable + const encryptionAlgorithm = safeContent.encryptionAlgorithm; + //#endregion + + //#region Append recipient for enveloped data + cmsEnveloped.addRecipientByCertificate(safeContent.encryptingCertificate, {}, undefined, crypto); + //#endregion + + //#region Making encryption + await cmsEnveloped.encrypt(encryptionAlgorithm, contentToEncrypt, crypto); + + this.safeContents.push(new ContentInfo({ + contentType: "1.2.840.113549.1.7.3", + content: cmsEnveloped.toSchema() + })); + //#endregion + } + break; + //#endregion + //#region default + default: + throw new Error(`Incorrect value for "content.privacyMode": ${content.privacyMode}`); + //#endregion + } + } + //#endregion + + //#region Return result of the function + return this; + //#endregion + } + +} + |