EncryptedContentInfo.ts (10280B)
1 import * as asn1js from "asn1js"; 2 import * as pvutils from "pvutils"; 3 import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier"; 4 import { EMPTY_STRING } from "./constants"; 5 import { AsnError } from "./errors"; 6 import { PkiObject, PkiObjectParameters } from "./PkiObject"; 7 import * as Schema from "./Schema"; 8 9 const CONTENT_TYPE = "contentType"; 10 const CONTENT_ENCRYPTION_ALGORITHM = "contentEncryptionAlgorithm"; 11 const ENCRYPTED_CONTENT = "encryptedContent"; 12 const CLEAR_PROPS = [ 13 CONTENT_TYPE, 14 CONTENT_ENCRYPTION_ALGORITHM, 15 ENCRYPTED_CONTENT, 16 ]; 17 18 export interface IEncryptedContentInfo { 19 contentType: string; 20 contentEncryptionAlgorithm: AlgorithmIdentifier; 21 encryptedContent?: asn1js.OctetString; 22 } 23 24 export interface EncryptedContentInfoJson { 25 contentType: string; 26 contentEncryptionAlgorithm: AlgorithmIdentifierJson; 27 encryptedContent?: asn1js.OctetStringJson; 28 } 29 30 export interface EncryptedContentInfoSplit { 31 /** 32 * Disables OctetString splitting for encryptedContent. 33 */ 34 disableSplit?: boolean; 35 } 36 37 export type EncryptedContentParameters = PkiObjectParameters & Partial<IEncryptedContentInfo> & EncryptedContentInfoSplit; 38 39 export type EncryptedContentInfoSchema = Schema.SchemaParameters<{ 40 contentType?: string; 41 contentEncryptionAlgorithm?: AlgorithmIdentifierSchema; 42 encryptedContent?: string; 43 }>; 44 45 const PIECE_SIZE = 1024; 46 47 /** 48 * Represents the EncryptedContentInfo structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) 49 */ 50 export class EncryptedContentInfo extends PkiObject implements IEncryptedContentInfo { 51 52 public static override CLASS_NAME = "EncryptedContentInfo"; 53 54 public contentType!: string; 55 public contentEncryptionAlgorithm!: AlgorithmIdentifier; 56 public encryptedContent?: asn1js.OctetString; 57 58 /** 59 * Initializes a new instance of the {@link EncryptedContentInfo} class 60 * @param parameters Initialization parameters 61 */ 62 constructor(parameters: EncryptedContentParameters = {}) { 63 super(); 64 65 this.contentType = pvutils.getParametersValue(parameters, CONTENT_TYPE, EncryptedContentInfo.defaultValues(CONTENT_TYPE)); 66 this.contentEncryptionAlgorithm = pvutils.getParametersValue(parameters, CONTENT_ENCRYPTION_ALGORITHM, EncryptedContentInfo.defaultValues(CONTENT_ENCRYPTION_ALGORITHM)); 67 68 if (ENCRYPTED_CONTENT in parameters && parameters.encryptedContent) { 69 // encryptedContent (!!!) could be constructive or primitive value (!!!) 70 this.encryptedContent = parameters.encryptedContent; 71 72 if ((this.encryptedContent.idBlock.tagClass === 1) && 73 (this.encryptedContent.idBlock.tagNumber === 4)) { 74 //#region Divide OCTET STRING value down to small pieces 75 // NOTE: Acrobat cannot decrypt the content, if constructed OctetString is used 76 if (this.encryptedContent.idBlock.isConstructed === false && !parameters.disableSplit) { 77 const constrString = new asn1js.OctetString({ 78 idBlock: { isConstructed: true }, 79 isConstructed: true 80 }); 81 82 let offset = 0; 83 const valueHex = this.encryptedContent.valueBlock.valueHexView.slice().buffer; 84 let length = valueHex.byteLength; 85 86 while (length > 0) { 87 const pieceView = new Uint8Array(valueHex, offset, ((offset + PIECE_SIZE) > valueHex.byteLength) ? (valueHex.byteLength - offset) : PIECE_SIZE); 88 const _array = new ArrayBuffer(pieceView.length); 89 const _view = new Uint8Array(_array); 90 91 for (let i = 0; i < _view.length; i++) 92 _view[i] = pieceView[i]; 93 94 constrString.valueBlock.value.push(new asn1js.OctetString({ valueHex: _array })); 95 96 length -= pieceView.length; 97 offset += pieceView.length; 98 } 99 100 this.encryptedContent = constrString; 101 } 102 //#endregion 103 } 104 } 105 106 if (parameters.schema) { 107 this.fromSchema(parameters.schema); 108 } 109 } 110 111 /** 112 * Returns default values for all class members 113 * @param memberName String name for a class member 114 * @returns Default value 115 */ 116 public static override defaultValues(memberName: typeof CONTENT_TYPE): string; 117 public static override defaultValues(memberName: typeof CONTENT_ENCRYPTION_ALGORITHM): AlgorithmIdentifier; 118 public static override defaultValues(memberName: typeof ENCRYPTED_CONTENT): asn1js.OctetString; 119 public static override defaultValues(memberName: string): any { 120 switch (memberName) { 121 case CONTENT_TYPE: 122 return EMPTY_STRING; 123 case CONTENT_ENCRYPTION_ALGORITHM: 124 return new AlgorithmIdentifier(); 125 case ENCRYPTED_CONTENT: 126 return new asn1js.OctetString(); 127 default: 128 return super.defaultValues(memberName); 129 } 130 } 131 132 /** 133 * Compare values with default values for all class members 134 * @param memberName String name for a class member 135 * @param memberValue Value to compare with default value 136 */ 137 public static compareWithDefault(memberName: string, memberValue: any): boolean { 138 switch (memberName) { 139 case CONTENT_TYPE: 140 return (memberValue === EMPTY_STRING); 141 case CONTENT_ENCRYPTION_ALGORITHM: 142 return ((memberValue.algorithmId === EMPTY_STRING) && (("algorithmParams" in memberValue) === false)); 143 case ENCRYPTED_CONTENT: 144 return (memberValue.isEqual(EncryptedContentInfo.defaultValues(ENCRYPTED_CONTENT))); 145 default: 146 return super.defaultValues(memberName); 147 } 148 } 149 150 /** 151 * @inheritdoc 152 * @asn ASN.1 schema 153 * ```asn 154 * EncryptedContentInfo ::= SEQUENCE { 155 * contentType ContentType, 156 * contentEncryptionAlgorithm ContentEncryptionAlgorithmIdentifier, 157 * encryptedContent [0] IMPLICIT EncryptedContent OPTIONAL } 158 * 159 * Comment: Strange, but modern crypto engines create ENCRYPTED_CONTENT as "[0] EXPLICIT EncryptedContent" 160 * 161 * EncryptedContent ::= OCTET STRING 162 *``` 163 */ 164 public static override schema(parameters: EncryptedContentInfoSchema = {}): Schema.SchemaType { 165 const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); 166 167 return (new asn1js.Sequence({ 168 name: (names.blockName || EMPTY_STRING), 169 value: [ 170 new asn1js.ObjectIdentifier({ name: (names.contentType || EMPTY_STRING) }), 171 AlgorithmIdentifier.schema(names.contentEncryptionAlgorithm || {}), 172 // The CHOICE we need because ENCRYPTED_CONTENT could have either "constructive" 173 // or "primitive" form of encoding and we need to handle both variants 174 new asn1js.Choice({ 175 value: [ 176 new asn1js.Constructed({ 177 name: (names.encryptedContent || EMPTY_STRING), 178 idBlock: { 179 tagClass: 3, // CONTEXT-SPECIFIC 180 tagNumber: 0 // [0] 181 }, 182 value: [ 183 new asn1js.Repeated({ 184 value: new asn1js.OctetString() 185 }) 186 ] 187 }), 188 new asn1js.Primitive({ 189 name: (names.encryptedContent || EMPTY_STRING), 190 idBlock: { 191 tagClass: 3, // CONTEXT-SPECIFIC 192 tagNumber: 0 // [0] 193 } 194 }) 195 ] 196 }) 197 ] 198 })); 199 } 200 201 public fromSchema(schema: Schema.SchemaType): void { 202 // Clear input data first 203 pvutils.clearProps(schema, CLEAR_PROPS); 204 205 // Check the schema is valid 206 const asn1 = asn1js.compareSchema(schema, 207 schema, 208 EncryptedContentInfo.schema({ 209 names: { 210 contentType: CONTENT_TYPE, 211 contentEncryptionAlgorithm: { 212 names: { 213 blockName: CONTENT_ENCRYPTION_ALGORITHM 214 } 215 }, 216 encryptedContent: ENCRYPTED_CONTENT 217 } 218 }) 219 ); 220 AsnError.assertSchema(asn1, this.className); 221 222 // Get internal properties from parsed schema 223 this.contentType = asn1.result.contentType.valueBlock.toString(); 224 this.contentEncryptionAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.contentEncryptionAlgorithm }); 225 if (ENCRYPTED_CONTENT in asn1.result) { 226 this.encryptedContent = asn1.result.encryptedContent as asn1js.OctetString; 227 228 this.encryptedContent.idBlock.tagClass = 1; // UNIVERSAL 229 this.encryptedContent.idBlock.tagNumber = 4; // OCTET STRING (!!!) The value still has instance of "in_window.org.pkijs.asn1.ASN1_CONSTRUCTED / ASN1_PRIMITIVE" 230 } 231 } 232 233 public toSchema(): asn1js.Sequence { 234 //#region Create array for output sequence 235 const sequenceLengthBlock = { 236 isIndefiniteForm: false 237 }; 238 239 const outputArray = []; 240 241 outputArray.push(new asn1js.ObjectIdentifier({ value: this.contentType })); 242 outputArray.push(this.contentEncryptionAlgorithm.toSchema()); 243 244 if (this.encryptedContent) { 245 sequenceLengthBlock.isIndefiniteForm = this.encryptedContent.idBlock.isConstructed; 246 247 const encryptedValue = this.encryptedContent; 248 249 encryptedValue.idBlock.tagClass = 3; // CONTEXT-SPECIFIC 250 encryptedValue.idBlock.tagNumber = 0; // [0] 251 252 encryptedValue.lenBlock.isIndefiniteForm = this.encryptedContent.idBlock.isConstructed; 253 254 outputArray.push(encryptedValue); 255 } 256 //#endregion 257 258 //#region Construct and return new ASN.1 schema for this object 259 return (new asn1js.Sequence({ 260 lenBlock: sequenceLengthBlock, 261 value: outputArray 262 })); 263 //#endregion 264 } 265 266 public toJSON(): EncryptedContentInfoJson { 267 const res: EncryptedContentInfoJson = { 268 contentType: this.contentType, 269 contentEncryptionAlgorithm: this.contentEncryptionAlgorithm.toJSON() 270 }; 271 272 if (this.encryptedContent) { 273 res.encryptedContent = this.encryptedContent.toJSON(); 274 } 275 276 return res; 277 } 278 279 /** 280 * Returns concatenated buffer from `encryptedContent` field. 281 * @returns Array buffer 282 * @since 3.0.0 283 * @throws Throws Error if `encryptedContent` is undefined 284 */ 285 public getEncryptedContent(): ArrayBuffer { 286 if (!this.encryptedContent) { 287 throw new Error("Parameter 'encryptedContent' is undefined"); 288 } 289 // NOTE encryptedContent can be CONSTRUCTED/PRIMITIVE 290 return asn1js.OctetString.prototype.getValue.call(this.encryptedContent); 291 } 292 293 }