SignedAndUnsignedAttributes.ts (6493B)
1 import * as asn1js from "asn1js"; 2 import * as pvtsutils from "pvtsutils"; 3 import * as pvutils from "pvutils"; 4 import { Attribute, AttributeJson } from "./Attribute"; 5 import { EMPTY_BUFFER, EMPTY_STRING } from "./constants"; 6 import { AsnError } from "./errors"; 7 import { PkiObject, PkiObjectParameters } from "./PkiObject"; 8 import * as Schema from "./Schema"; 9 10 const TYPE = "type"; 11 const ATTRIBUTES = "attributes"; 12 const ENCODED_VALUE = "encodedValue"; 13 const CLEAR_PROPS = [ 14 ATTRIBUTES 15 ]; 16 17 export interface ISignedAndUnsignedAttributes { 18 type: number; 19 attributes: Attribute[]; 20 /** 21 * Need to have it in order to successfully process with signature verification 22 */ 23 encodedValue: ArrayBuffer; 24 } 25 26 export interface SignedAndUnsignedAttributesJson { 27 type: number; 28 attributes: AttributeJson[]; 29 } 30 31 export type SignedAndUnsignedAttributesParameters = PkiObjectParameters & Partial<ISignedAndUnsignedAttributes>; 32 33 export type SignedAndUnsignedAttributesSchema = Schema.SchemaParameters<{ 34 tagNumber?: number; 35 attributes?: string; 36 }>; 37 38 /** 39 * Represents the SignedAndUnsignedAttributes structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) 40 */ 41 export class SignedAndUnsignedAttributes extends PkiObject implements ISignedAndUnsignedAttributes { 42 43 public static override CLASS_NAME = "SignedAndUnsignedAttributes"; 44 45 public type!: number; 46 public attributes!: Attribute[]; 47 public encodedValue!: ArrayBuffer; 48 49 /** 50 * Initializes a new instance of the {@link SignedAndUnsignedAttributes} class 51 * @param parameters Initialization parameters 52 */ 53 constructor(parameters: SignedAndUnsignedAttributesParameters = {}) { 54 super(); 55 56 this.type = pvutils.getParametersValue(parameters, TYPE, SignedAndUnsignedAttributes.defaultValues(TYPE)); 57 this.attributes = pvutils.getParametersValue(parameters, ATTRIBUTES, SignedAndUnsignedAttributes.defaultValues(ATTRIBUTES)); 58 this.encodedValue = pvutils.getParametersValue(parameters, ENCODED_VALUE, SignedAndUnsignedAttributes.defaultValues(ENCODED_VALUE)); 59 60 if (parameters.schema) { 61 this.fromSchema(parameters.schema); 62 } 63 } 64 65 /** 66 * Returns default values for all class members 67 * @param memberName String name for a class member 68 * @returns Default value 69 */ 70 public static override defaultValues(memberName: typeof TYPE): number; 71 public static override defaultValues(memberName: typeof ATTRIBUTES): Attribute[]; 72 public static override defaultValues(memberName: typeof ENCODED_VALUE): ArrayBuffer; 73 public static override defaultValues(memberName: string): any { 74 switch (memberName) { 75 case TYPE: 76 return (-1); 77 case ATTRIBUTES: 78 return []; 79 case ENCODED_VALUE: 80 return EMPTY_BUFFER; 81 default: 82 return super.defaultValues(memberName); 83 } 84 } 85 86 /** 87 * Compare values with default values for all class members 88 * @param memberName String name for a class member 89 * @param memberValue Value to compare with default value 90 */ 91 public static compareWithDefault(memberName: string, memberValue: any): boolean { 92 switch (memberName) { 93 case TYPE: 94 return (memberValue === SignedAndUnsignedAttributes.defaultValues(TYPE)); 95 case ATTRIBUTES: 96 return (memberValue.length === 0); 97 case ENCODED_VALUE: 98 return (memberValue.byteLength === 0); 99 default: 100 return super.defaultValues(memberName); 101 } 102 } 103 104 /** 105 * @inheritdoc 106 * @asn ASN.1 schema 107 * ```asn 108 * SignedAttributes ::= SET SIZE (1..MAX) OF Attribute 109 * 110 * UnsignedAttributes ::= SET SIZE (1..MAX) OF Attribute 111 *``` 112 */ 113 public static override schema(parameters: SignedAndUnsignedAttributesSchema = {}): Schema.SchemaType { 114 const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); 115 116 return (new asn1js.Constructed({ 117 name: (names.blockName || EMPTY_STRING), 118 optional: true, 119 idBlock: { 120 tagClass: 3, // CONTEXT-SPECIFIC 121 tagNumber: names.tagNumber || 0 // "SignedAttributes" = 0, "UnsignedAttributes" = 1 122 }, 123 value: [ 124 new asn1js.Repeated({ 125 name: (names.attributes || EMPTY_STRING), 126 value: Attribute.schema() 127 }) 128 ] 129 })); 130 } 131 132 public fromSchema(schema: Schema.SchemaType): void { 133 // Clear input data first 134 pvutils.clearProps(schema, CLEAR_PROPS); 135 136 // Check the schema is valid 137 const asn1 = asn1js.compareSchema(schema, 138 schema, 139 SignedAndUnsignedAttributes.schema({ 140 names: { 141 tagNumber: this.type, 142 attributes: ATTRIBUTES 143 } 144 }) 145 ); 146 AsnError.assertSchema(asn1, this.className); 147 148 // Get internal properties from parsed schema 149 this.type = asn1.result.idBlock.tagNumber; 150 this.encodedValue = pvtsutils.BufferSourceConverter.toArrayBuffer(asn1.result.valueBeforeDecodeView); 151 152 //#region Change type from "[0]" to "SET" accordingly to standard 153 const encodedView = new Uint8Array(this.encodedValue); 154 encodedView[0] = 0x31; 155 //#endregion 156 157 if ((ATTRIBUTES in asn1.result) === false) { 158 if (this.type === 0) 159 throw new Error("Wrong structure of SignedUnsignedAttributes"); 160 else 161 return; // Not so important in case of "UnsignedAttributes" 162 } 163 164 this.attributes = Array.from(asn1.result.attributes, element => new Attribute({ schema: element })); 165 //#endregion 166 } 167 168 public toSchema(): asn1js.Sequence { 169 if (SignedAndUnsignedAttributes.compareWithDefault(TYPE, this.type) || SignedAndUnsignedAttributes.compareWithDefault(ATTRIBUTES, this.attributes)) 170 throw new Error("Incorrectly initialized \"SignedAndUnsignedAttributes\" class"); 171 172 //#region Construct and return new ASN.1 schema for this object 173 return (new asn1js.Constructed({ 174 optional: true, 175 idBlock: { 176 tagClass: 3, // CONTEXT-SPECIFIC 177 tagNumber: this.type // "SignedAttributes" = 0, "UnsignedAttributes" = 1 178 }, 179 value: Array.from(this.attributes, o => o.toSchema()) 180 })); 181 //#endregion 182 } 183 184 public toJSON(): SignedAndUnsignedAttributesJson { 185 if (SignedAndUnsignedAttributes.compareWithDefault(TYPE, this.type) || SignedAndUnsignedAttributes.compareWithDefault(ATTRIBUTES, this.attributes)) 186 throw new Error("Incorrectly initialized \"SignedAndUnsignedAttributes\" class"); 187 188 return { 189 type: this.type, 190 attributes: Array.from(this.attributes, o => o.toJSON()) 191 }; 192 } 193 194 }