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 }