tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

EncryptedData.ts (9882B)


      1 import * as asn1js from "asn1js";
      2 import * as pvutils from "pvutils";
      3 import * as common from "./common";
      4 import { EncryptedContentInfo, EncryptedContentInfoJson, EncryptedContentInfoSchema } from "./EncryptedContentInfo";
      5 import { Attribute, AttributeJson } from "./Attribute";
      6 import * as Schema from "./Schema";
      7 import { ArgumentError, AsnError } from "./errors";
      8 import { CryptoEngineEncryptParams } from "./CryptoEngine/CryptoEngineInterface";
      9 import { PkiObject, PkiObjectParameters } from "./PkiObject";
     10 import { EMPTY_STRING } from "./constants";
     11 
     12 const VERSION = "version";
     13 const ENCRYPTED_CONTENT_INFO = "encryptedContentInfo";
     14 const UNPROTECTED_ATTRS = "unprotectedAttrs";
     15 const CLEAR_PROPS = [
     16  VERSION,
     17  ENCRYPTED_CONTENT_INFO,
     18  UNPROTECTED_ATTRS,
     19 ];
     20 
     21 export interface IEncryptedData {
     22  /**
     23   * Version number.
     24   *
     25   * If `unprotectedAttrs` is present, then the version MUST be 2. If `unprotectedAttrs` is absent, then version MUST be 0.
     26   */
     27  version: number;
     28  /**
     29   * Encrypted content information
     30   */
     31  encryptedContentInfo: EncryptedContentInfo;
     32  /**
     33   * Collection of attributes that are not encrypted
     34   */
     35  unprotectedAttrs?: Attribute[];
     36 }
     37 
     38 export interface EncryptedDataJson {
     39  version: number;
     40  encryptedContentInfo: EncryptedContentInfoJson;
     41  unprotectedAttrs?: AttributeJson[];
     42 }
     43 
     44 export type EncryptedDataParameters = PkiObjectParameters & Partial<IEncryptedData>;
     45 
     46 export type EncryptedDataEncryptParams = Omit<CryptoEngineEncryptParams, "contentType">;
     47 
     48 /**
     49 * Represents the EncryptedData structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652)
     50 *
     51 * @example The following example demonstrates how to create and encrypt CMS Encrypted Data
     52 * ```js
     53 * const cmsEncrypted = new pkijs.EncryptedData();
     54 *
     55 * await cmsEncrypted.encrypt({
     56 *   contentEncryptionAlgorithm: {
     57 *     name: "AES-GCM",
     58 *     length: 256,
     59 *   },
     60 *   hmacHashAlgorithm: "SHA-256",
     61 *   iterationCount: 1000,
     62 *   password: password,
     63 *   contentToEncrypt: dataToEncrypt,
     64 * });
     65 *
     66 * // Add Encrypted Data into CMS Content Info
     67 * const cmsContent = new pkijs.ContentInfo();
     68 * cmsContent.contentType = pkijs.ContentInfo.ENCRYPTED_DATA;
     69 * cmsContent.content = cmsEncrypted.toSchema();
     70 *
     71 * const cmsContentRaw = cmsContent.toSchema().toBER();
     72 * ```
     73 *
     74 * @example The following example demonstrates how to decrypt CMS Encrypted Data
     75 * ```js
     76 * // Parse CMS Content Info
     77 * const cmsContent = pkijs.ContentInfo.fromBER(cmsContentRaw);
     78 * if (cmsContent.contentType !== pkijs.ContentInfo.ENCRYPTED_DATA) {
     79 *   throw new Error("CMS is not Encrypted Data");
     80 * }
     81 * // Parse CMS Encrypted Data
     82 * const cmsEncrypted = new pkijs.EncryptedData({ schema: cmsContent.content });
     83 *
     84 * // Decrypt data
     85 * const decryptedData = await cmsEncrypted.decrypt({
     86 *   password: password,
     87 * });
     88 * ```
     89 */
     90 export class EncryptedData extends PkiObject implements IEncryptedData {
     91 
     92  public static override CLASS_NAME = "EncryptedData";
     93 
     94  public version!: number;
     95  public encryptedContentInfo!: EncryptedContentInfo;
     96  public unprotectedAttrs?: Attribute[];
     97 
     98  /**
     99   * Initializes a new instance of the {@link EncryptedData} class
    100   * @param parameters Initialization parameters
    101   */
    102  constructor(parameters: EncryptedDataParameters = {}) {
    103    super();
    104 
    105    this.version = pvutils.getParametersValue(parameters, VERSION, EncryptedData.defaultValues(VERSION));
    106    this.encryptedContentInfo = pvutils.getParametersValue(parameters, ENCRYPTED_CONTENT_INFO, EncryptedData.defaultValues(ENCRYPTED_CONTENT_INFO));
    107    if (UNPROTECTED_ATTRS in parameters) {
    108      this.unprotectedAttrs = pvutils.getParametersValue(parameters, UNPROTECTED_ATTRS, EncryptedData.defaultValues(UNPROTECTED_ATTRS));
    109    }
    110 
    111    if (parameters.schema) {
    112      this.fromSchema(parameters.schema);
    113    }
    114  }
    115 
    116  /**
    117   * Returns default values for all class members
    118   * @param memberName String name for a class member
    119   * @returns Default value
    120   */
    121  public static override defaultValues(memberName: typeof VERSION): number;
    122  public static override defaultValues(memberName: typeof ENCRYPTED_CONTENT_INFO): EncryptedContentInfo;
    123  public static override defaultValues(memberName: typeof UNPROTECTED_ATTRS): Attribute[];
    124  public static override defaultValues(memberName: string): any {
    125    switch (memberName) {
    126      case VERSION:
    127        return 0;
    128      case ENCRYPTED_CONTENT_INFO:
    129        return new EncryptedContentInfo();
    130      case UNPROTECTED_ATTRS:
    131        return [];
    132      default:
    133        return super.defaultValues(memberName);
    134    }
    135  }
    136 
    137  /**
    138   * Compare values with default values for all class members
    139   * @param memberName String name for a class member
    140   * @param memberValue Value to compare with default value
    141   */
    142  public static compareWithDefault(memberName: string, memberValue: any): boolean {
    143    switch (memberName) {
    144      case VERSION:
    145        return (memberValue === 0);
    146      case ENCRYPTED_CONTENT_INFO:
    147        // TODO move to isEmpty method
    148        return ((EncryptedContentInfo.compareWithDefault("contentType", memberValue.contentType)) &&
    149          (EncryptedContentInfo.compareWithDefault("contentEncryptionAlgorithm", memberValue.contentEncryptionAlgorithm)) &&
    150          (EncryptedContentInfo.compareWithDefault("encryptedContent", memberValue.encryptedContent)));
    151      case UNPROTECTED_ATTRS:
    152        return (memberValue.length === 0);
    153      default:
    154        return super.defaultValues(memberName);
    155    }
    156  }
    157 
    158  /**
    159   * @inheritdoc
    160   * @asn ASN.1 schema
    161   * ```asn
    162   * EncryptedData ::= SEQUENCE {
    163   *    version CMSVersion,
    164   *    encryptedContentInfo EncryptedContentInfo,
    165   *    unprotectedAttrs [1] IMPLICIT UnprotectedAttributes OPTIONAL }
    166   *```
    167   */
    168  public static override schema(parameters: Schema.SchemaParameters<{
    169    version?: string;
    170    encryptedContentInfo?: EncryptedContentInfoSchema;
    171    unprotectedAttrs?: string;
    172  }> = {}): Schema.SchemaType {
    173    const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {});
    174 
    175    return (new asn1js.Sequence({
    176      name: (names.blockName || EMPTY_STRING),
    177      value: [
    178        new asn1js.Integer({ name: (names.version || EMPTY_STRING) }),
    179        EncryptedContentInfo.schema(names.encryptedContentInfo || {}),
    180        new asn1js.Constructed({
    181          optional: true,
    182          idBlock: {
    183            tagClass: 3, // CONTEXT-SPECIFIC
    184            tagNumber: 1 // [1]
    185          },
    186          value: [
    187            new asn1js.Repeated({
    188              name: (names.unprotectedAttrs || EMPTY_STRING),
    189              value: Attribute.schema()
    190            })
    191          ]
    192        })
    193      ]
    194    }));
    195  }
    196 
    197  public fromSchema(schema: Schema.SchemaType): void {
    198    // Clear input data first
    199    pvutils.clearProps(schema, CLEAR_PROPS);
    200 
    201    // Check the schema is valid
    202    const asn1 = asn1js.compareSchema(schema,
    203      schema,
    204      EncryptedData.schema({
    205        names: {
    206          version: VERSION,
    207          encryptedContentInfo: {
    208            names: {
    209              blockName: ENCRYPTED_CONTENT_INFO
    210            }
    211          },
    212          unprotectedAttrs: UNPROTECTED_ATTRS
    213        }
    214      })
    215    );
    216    AsnError.assertSchema(asn1, this.className);
    217 
    218    // Get internal properties from parsed schema
    219    this.version = asn1.result.version.valueBlock.valueDec;
    220    this.encryptedContentInfo = new EncryptedContentInfo({ schema: asn1.result.encryptedContentInfo });
    221    if (UNPROTECTED_ATTRS in asn1.result)
    222      this.unprotectedAttrs = Array.from(asn1.result.unprotectedAttrs, element => new Attribute({ schema: element }));
    223  }
    224 
    225  public toSchema(): asn1js.Sequence {
    226    //#region Create array for output sequence
    227    const outputArray = [];
    228 
    229    outputArray.push(new asn1js.Integer({ value: this.version }));
    230    outputArray.push(this.encryptedContentInfo.toSchema());
    231 
    232    if (this.unprotectedAttrs) {
    233      outputArray.push(new asn1js.Constructed({
    234        optional: true,
    235        idBlock: {
    236          tagClass: 3, // CONTEXT-SPECIFIC
    237          tagNumber: 1 // [1]
    238        },
    239        value: Array.from(this.unprotectedAttrs, o => o.toSchema())
    240      }));
    241    }
    242    //#endregion
    243 
    244    //#region Construct and return new ASN.1 schema for this object
    245    return (new asn1js.Sequence({
    246      value: outputArray
    247    }));
    248    //#endregion
    249  }
    250 
    251  public toJSON(): EncryptedDataJson {
    252    const res: EncryptedDataJson = {
    253      version: this.version,
    254      encryptedContentInfo: this.encryptedContentInfo.toJSON()
    255    };
    256 
    257    if (this.unprotectedAttrs)
    258      res.unprotectedAttrs = Array.from(this.unprotectedAttrs, o => o.toJSON());
    259 
    260    return res;
    261  }
    262 
    263  /**
    264   * Creates a new CMS Encrypted Data content
    265   * @param parameters Parameters necessary for encryption
    266   */
    267  public async encrypt(parameters: EncryptedDataEncryptParams, crypto = common.getCrypto(true)): Promise<void> {
    268    //#region Check for input parameters
    269    ArgumentError.assert(parameters, "parameters", "object");
    270    //#endregion
    271 
    272    //#region Set "contentType" parameter
    273    const encryptParams: CryptoEngineEncryptParams = {
    274      ...parameters,
    275      contentType: "1.2.840.113549.1.7.1",
    276    };
    277    //#endregion
    278 
    279    this.encryptedContentInfo = await crypto.encryptEncryptedContentInfo(encryptParams);
    280  }
    281 
    282  /**
    283   * Creates a new CMS Encrypted Data content
    284   * @param parameters Parameters necessary for encryption
    285   * @param crypto Crypto engine
    286   * @returns Returns decrypted raw data
    287   */
    288  async decrypt(parameters: {
    289    password: ArrayBuffer;
    290  }, crypto = common.getCrypto(true)): Promise<ArrayBuffer> {
    291    // Check for input parameters
    292    ArgumentError.assert(parameters, "parameters", "object");
    293 
    294    // Set ENCRYPTED_CONTENT_INFO value
    295    const decryptParams = {
    296      ...parameters,
    297      encryptedContentInfo: this.encryptedContentInfo,
    298    };
    299 
    300    return crypto.decryptEncryptedContentInfo(decryptParams);
    301  }
    302 
    303 }