GeneralName.ts (18270B)
1 import * as asn1js from "asn1js"; 2 import * as pvutils from "pvutils"; 3 import { EMPTY_STRING } from "./constants"; 4 import { AsnError } from "./errors"; 5 import { PkiObject, PkiObjectParameters } from "./PkiObject"; 6 import { RelativeDistinguishedNames } from "./RelativeDistinguishedNames"; 7 import * as Schema from "./Schema"; 8 9 export const TYPE = "type"; 10 export const VALUE = "value"; 11 12 //#region Additional asn1js schema elements existing inside GeneralName schema 13 14 /** 15 * Schema for "builtInStandardAttributes" of "ORAddress" 16 * @param parameters 17 * @property names 18 * @param optional 19 * @returns 20 */ 21 function builtInStandardAttributes(parameters: Schema.SchemaParameters<{ 22 country_name?: string; 23 administration_domain_name?: string; 24 network_address?: string; 25 terminal_identifier?: string; 26 private_domain_name?: string; 27 organization_name?: string; 28 numeric_user_identifier?: string; 29 personal_name?: string; 30 organizational_unit_names?: string; 31 }> = {}, optional = false) { 32 //builtInStandardAttributes ::= Sequence { 33 // country-name CountryName OPTIONAL, 34 // administration-domain-name AdministrationDomainName OPTIONAL, 35 // network-address [0] IMPLICIT NetworkAddress OPTIONAL, 36 // terminal-identifier [1] IMPLICIT TerminalIdentifier OPTIONAL, 37 // private-domain-name [2] PrivateDomainName OPTIONAL, 38 // organization-name [3] IMPLICIT OrganizationName OPTIONAL, 39 // numeric-user-identifier [4] IMPLICIT NumericUserIdentifier OPTIONAL, 40 // personal-name [5] IMPLICIT PersonalName OPTIONAL, 41 // organizational-unit-names [6] IMPLICIT OrganizationalUnitNames OPTIONAL } 42 const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); 43 44 return (new asn1js.Sequence({ 45 optional, 46 value: [ 47 new asn1js.Constructed({ 48 optional: true, 49 idBlock: { 50 tagClass: 2, // APPLICATION-SPECIFIC 51 tagNumber: 1 // [1] 52 }, 53 name: (names.country_name || EMPTY_STRING), 54 value: [ 55 new asn1js.Choice({ 56 value: [ 57 new asn1js.NumericString(), 58 new asn1js.PrintableString() 59 ] 60 }) 61 ] 62 }), 63 new asn1js.Constructed({ 64 optional: true, 65 idBlock: { 66 tagClass: 2, // APPLICATION-SPECIFIC 67 tagNumber: 2 // [2] 68 }, 69 name: (names.administration_domain_name || EMPTY_STRING), 70 value: [ 71 new asn1js.Choice({ 72 value: [ 73 new asn1js.NumericString(), 74 new asn1js.PrintableString() 75 ] 76 }) 77 ] 78 }), 79 new asn1js.Primitive({ 80 optional: true, 81 idBlock: { 82 tagClass: 3, // CONTEXT-SPECIFIC 83 tagNumber: 0 // [0] 84 }, 85 name: (names.network_address || EMPTY_STRING), 86 isHexOnly: true 87 }), 88 new asn1js.Primitive({ 89 optional: true, 90 idBlock: { 91 tagClass: 3, // CONTEXT-SPECIFIC 92 tagNumber: 1 // [1] 93 }, 94 name: (names.terminal_identifier || EMPTY_STRING), 95 isHexOnly: true 96 }), 97 new asn1js.Constructed({ 98 optional: true, 99 idBlock: { 100 tagClass: 3, // CONTEXT-SPECIFIC 101 tagNumber: 2 // [2] 102 }, 103 name: (names.private_domain_name || EMPTY_STRING), 104 value: [ 105 new asn1js.Choice({ 106 value: [ 107 new asn1js.NumericString(), 108 new asn1js.PrintableString() 109 ] 110 }) 111 ] 112 }), 113 new asn1js.Primitive({ 114 optional: true, 115 idBlock: { 116 tagClass: 3, // CONTEXT-SPECIFIC 117 tagNumber: 3 // [3] 118 }, 119 name: (names.organization_name || EMPTY_STRING), 120 isHexOnly: true 121 }), 122 new asn1js.Primitive({ 123 optional: true, 124 name: (names.numeric_user_identifier || EMPTY_STRING), 125 idBlock: { 126 tagClass: 3, // CONTEXT-SPECIFIC 127 tagNumber: 4 // [4] 128 }, 129 isHexOnly: true 130 }), 131 new asn1js.Constructed({ 132 optional: true, 133 name: (names.personal_name || EMPTY_STRING), 134 idBlock: { 135 tagClass: 3, // CONTEXT-SPECIFIC 136 tagNumber: 5 // [5] 137 }, 138 value: [ 139 new asn1js.Primitive({ 140 idBlock: { 141 tagClass: 3, // CONTEXT-SPECIFIC 142 tagNumber: 0 // [0] 143 }, 144 isHexOnly: true 145 }), 146 new asn1js.Primitive({ 147 optional: true, 148 idBlock: { 149 tagClass: 3, // CONTEXT-SPECIFIC 150 tagNumber: 1 // [1] 151 }, 152 isHexOnly: true 153 }), 154 new asn1js.Primitive({ 155 optional: true, 156 idBlock: { 157 tagClass: 3, // CONTEXT-SPECIFIC 158 tagNumber: 2 // [2] 159 }, 160 isHexOnly: true 161 }), 162 new asn1js.Primitive({ 163 optional: true, 164 idBlock: { 165 tagClass: 3, // CONTEXT-SPECIFIC 166 tagNumber: 3 // [3] 167 }, 168 isHexOnly: true 169 }) 170 ] 171 }), 172 new asn1js.Constructed({ 173 optional: true, 174 name: (names.organizational_unit_names || EMPTY_STRING), 175 idBlock: { 176 tagClass: 3, // CONTEXT-SPECIFIC 177 tagNumber: 6 // [6] 178 }, 179 value: [ 180 new asn1js.Repeated({ 181 value: new asn1js.PrintableString() 182 }) 183 ] 184 }) 185 ] 186 })); 187 } 188 189 /** 190 * Schema for "builtInDomainDefinedAttributes" of "ORAddress" 191 * @param optional 192 */ 193 function builtInDomainDefinedAttributes(optional = false): Schema.SchemaType { 194 return (new asn1js.Sequence({ 195 optional, 196 value: [ 197 new asn1js.PrintableString(), 198 new asn1js.PrintableString() 199 ] 200 })); 201 } 202 203 /** 204 * Schema for "builtInDomainDefinedAttributes" of "ORAddress" 205 * @param optional 206 */ 207 function extensionAttributes(optional = false): Schema.SchemaType { 208 return (new asn1js.Set({ 209 optional, 210 value: [ 211 new asn1js.Primitive({ 212 optional: true, 213 idBlock: { 214 tagClass: 3, // CONTEXT-SPECIFIC 215 tagNumber: 0 // [0] 216 }, 217 isHexOnly: true 218 }), 219 new asn1js.Constructed({ 220 optional: true, 221 idBlock: { 222 tagClass: 3, // CONTEXT-SPECIFIC 223 tagNumber: 1 // [1] 224 }, 225 value: [new asn1js.Any()] 226 }) 227 ] 228 })); 229 } 230 231 //#endregion 232 233 export interface IGeneralName { 234 /** 235 * value type - from a tagged value (0 for "otherName", 1 for "rfc822Name" etc.) 236 */ 237 type: number; 238 /** 239 * ASN.1 object having GeneralName value (type depends on TYPE value) 240 */ 241 value: any; 242 } 243 244 export type GeneralNameParameters = PkiObjectParameters & Partial<{ type: 1 | 2 | 6; value: string; } | { type: 0 | 3 | 4 | 7 | 8; value: any; }>; 245 246 export interface GeneralNameSchema { 247 names?: { 248 blockName?: string; 249 directoryName?: object; 250 builtInStandardAttributes?: object; 251 otherName?: string; 252 rfc822Name?: string; 253 dNSName?: string; 254 x400Address?: string; 255 ediPartyName?: string; 256 uniformResourceIdentifier?: string; 257 iPAddress?: string; 258 registeredID?: string; 259 }; 260 } 261 262 export interface GeneralNameJson { 263 type: number; 264 value: string; 265 } 266 267 /** 268 * Represents the GeneralName structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) 269 */ 270 export class GeneralName extends PkiObject implements IGeneralName { 271 272 public static override CLASS_NAME = "GeneralName"; 273 274 public type!: number; 275 public value: any; 276 277 /** 278 * Initializes a new instance of the {@link GeneralName} class 279 * @param parameters Initialization parameters 280 */ 281 constructor(parameters: GeneralNameParameters = {}) { 282 super(); 283 284 this.type = pvutils.getParametersValue(parameters, TYPE, GeneralName.defaultValues(TYPE)); 285 this.value = pvutils.getParametersValue(parameters, VALUE, GeneralName.defaultValues(VALUE)); 286 287 if (parameters.schema) { 288 this.fromSchema(parameters.schema); 289 } 290 } 291 292 /** 293 * Returns default values for all class members 294 * @param memberName String name for a class member 295 * @returns Default value 296 */ 297 public static override defaultValues(memberName: typeof TYPE): number; 298 public static override defaultValues(memberName: typeof VALUE): any; 299 public static override defaultValues(memberName: string): any { 300 switch (memberName) { 301 case TYPE: 302 return 9; 303 case VALUE: 304 return {}; 305 default: 306 return super.defaultValues(memberName); 307 } 308 } 309 310 /** 311 * Compares values with default values for all class members 312 * @param memberName String name for a class member 313 * @param memberValue Value to compare with default value 314 */ 315 public static compareWithDefault(memberName: string, memberValue: any): boolean { 316 switch (memberName) { 317 case TYPE: 318 return (memberValue === GeneralName.defaultValues(memberName)); 319 case VALUE: 320 return (Object.keys(memberValue).length === 0); 321 default: 322 return super.defaultValues(memberName); 323 } 324 } 325 326 /** 327 * @inheritdoc 328 * @asn ASN.1 schema 329 * ```asn 330 * GeneralName ::= Choice { 331 * otherName [0] OtherName, 332 * rfc822Name [1] IA5String, 333 * dNSName [2] IA5String, 334 * x400Address [3] ORAddress, 335 * directoryName [4] value, 336 * ediPartyName [5] EDIPartyName, 337 * uniformResourceIdentifier [6] IA5String, 338 * iPAddress [7] OCTET STRING, 339 * registeredID [8] OBJECT IDENTIFIER } 340 *``` 341 */ 342 static override schema(parameters: GeneralNameSchema = {}): asn1js.Choice { 343 const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); 344 345 return (new asn1js.Choice({ 346 value: [ 347 new asn1js.Constructed({ 348 idBlock: { 349 tagClass: 3, // CONTEXT-SPECIFIC 350 tagNumber: 0 // [0] 351 }, 352 name: (names.blockName || EMPTY_STRING), 353 value: [ 354 new asn1js.ObjectIdentifier(), 355 new asn1js.Constructed({ 356 idBlock: { 357 tagClass: 3, // CONTEXT-SPECIFIC 358 tagNumber: 0 // [0] 359 }, 360 value: [new asn1js.Any()] 361 }) 362 ] 363 }), 364 new asn1js.Primitive({ 365 name: (names.blockName || EMPTY_STRING), 366 idBlock: { 367 tagClass: 3, // CONTEXT-SPECIFIC 368 tagNumber: 1 // [1] 369 } 370 }), 371 new asn1js.Primitive({ 372 name: (names.blockName || EMPTY_STRING), 373 idBlock: { 374 tagClass: 3, // CONTEXT-SPECIFIC 375 tagNumber: 2 // [2] 376 } 377 }), 378 new asn1js.Constructed({ 379 idBlock: { 380 tagClass: 3, // CONTEXT-SPECIFIC 381 tagNumber: 3 // [3] 382 }, 383 name: (names.blockName || EMPTY_STRING), 384 value: [ 385 builtInStandardAttributes((names.builtInStandardAttributes || {}), false), 386 builtInDomainDefinedAttributes(true), 387 extensionAttributes(true) 388 ] 389 }), 390 new asn1js.Constructed({ 391 idBlock: { 392 tagClass: 3, // CONTEXT-SPECIFIC 393 tagNumber: 4 // [4] 394 }, 395 name: (names.blockName || EMPTY_STRING), 396 value: [RelativeDistinguishedNames.schema(names.directoryName || {})] 397 }), 398 new asn1js.Constructed({ 399 idBlock: { 400 tagClass: 3, // CONTEXT-SPECIFIC 401 tagNumber: 5 // [5] 402 }, 403 name: (names.blockName || EMPTY_STRING), 404 value: [ 405 new asn1js.Constructed({ 406 optional: true, 407 idBlock: { 408 tagClass: 3, // CONTEXT-SPECIFIC 409 tagNumber: 0 // [0] 410 }, 411 value: [ 412 new asn1js.Choice({ 413 value: [ 414 new asn1js.TeletexString(), 415 new asn1js.PrintableString(), 416 new asn1js.UniversalString(), 417 new asn1js.Utf8String(), 418 new asn1js.BmpString() 419 ] 420 }) 421 ] 422 }), 423 new asn1js.Constructed({ 424 idBlock: { 425 tagClass: 3, // CONTEXT-SPECIFIC 426 tagNumber: 1 // [1] 427 }, 428 value: [ 429 new asn1js.Choice({ 430 value: [ 431 new asn1js.TeletexString(), 432 new asn1js.PrintableString(), 433 new asn1js.UniversalString(), 434 new asn1js.Utf8String(), 435 new asn1js.BmpString() 436 ] 437 }) 438 ] 439 }) 440 ] 441 }), 442 new asn1js.Primitive({ 443 name: (names.blockName || EMPTY_STRING), 444 idBlock: { 445 tagClass: 3, // CONTEXT-SPECIFIC 446 tagNumber: 6 // [6] 447 } 448 }), 449 new asn1js.Primitive({ 450 name: (names.blockName || EMPTY_STRING), 451 idBlock: { 452 tagClass: 3, // CONTEXT-SPECIFIC 453 tagNumber: 7 // [7] 454 } 455 }), 456 new asn1js.Primitive({ 457 name: (names.blockName || EMPTY_STRING), 458 idBlock: { 459 tagClass: 3, // CONTEXT-SPECIFIC 460 tagNumber: 8 // [8] 461 } 462 }) 463 ] 464 })); 465 } 466 467 public fromSchema(schema: Schema.SchemaType): void { 468 //#region Clear input data first 469 pvutils.clearProps(schema, [ 470 "blockName", 471 "otherName", 472 "rfc822Name", 473 "dNSName", 474 "x400Address", 475 "directoryName", 476 "ediPartyName", 477 "uniformResourceIdentifier", 478 "iPAddress", 479 "registeredID" 480 ]); 481 //#endregion 482 483 //#region Check the schema is valid 484 const asn1 = asn1js.compareSchema(schema, 485 schema, 486 GeneralName.schema({ 487 names: { 488 blockName: "blockName", 489 otherName: "otherName", 490 rfc822Name: "rfc822Name", 491 dNSName: "dNSName", 492 x400Address: "x400Address", 493 directoryName: { 494 names: { 495 blockName: "directoryName" 496 } 497 }, 498 ediPartyName: "ediPartyName", 499 uniformResourceIdentifier: "uniformResourceIdentifier", 500 iPAddress: "iPAddress", 501 registeredID: "registeredID" 502 } 503 }) 504 ); 505 506 AsnError.assertSchema(asn1, this.className); 507 //#endregion 508 509 //#region Get internal properties from parsed schema 510 this.type = asn1.result.blockName.idBlock.tagNumber; 511 512 switch (this.type) { 513 case 0: // otherName 514 this.value = asn1.result.blockName; 515 break; 516 case 1: // rfc822Name + dNSName + uniformResourceIdentifier 517 case 2: 518 case 6: 519 { 520 const value = asn1.result.blockName; 521 522 value.idBlock.tagClass = 1; // UNIVERSAL 523 value.idBlock.tagNumber = 22; // IA5STRING 524 525 const valueBER = value.toBER(false); 526 527 const asnValue = asn1js.fromBER(valueBER); 528 AsnError.assert(asnValue, "GeneralName value"); 529 530 this.value = (asnValue.result as asn1js.BaseStringBlock).valueBlock.value; 531 } 532 break; 533 case 3: // x400Address 534 this.value = asn1.result.blockName; 535 break; 536 case 4: // directoryName 537 this.value = new RelativeDistinguishedNames({ schema: asn1.result.directoryName }); 538 break; 539 case 5: // ediPartyName 540 this.value = asn1.result.ediPartyName; 541 break; 542 case 7: // iPAddress 543 this.value = new asn1js.OctetString({ valueHex: asn1.result.blockName.valueBlock.valueHex }); 544 break; 545 case 8: // registeredID 546 { 547 const value = asn1.result.blockName; 548 549 value.idBlock.tagClass = 1; // UNIVERSAL 550 value.idBlock.tagNumber = 6; // ObjectIdentifier 551 552 const valueBER = value.toBER(false); 553 554 const asnValue = asn1js.fromBER(valueBER); 555 AsnError.assert(asnValue, "GeneralName registeredID"); 556 this.value = asnValue.result.valueBlock.toString(); // Getting a string representation of the ObjectIdentifier 557 } 558 break; 559 default: 560 } 561 //#endregion 562 } 563 564 public toSchema(): asn1js.Constructed | asn1js.IA5String | asn1js.ObjectIdentifier | asn1js.Choice { 565 //#region Construct and return new ASN.1 schema for this object 566 switch (this.type) { 567 case 0: 568 case 3: 569 case 5: 570 return new asn1js.Constructed({ 571 idBlock: { 572 tagClass: 3, // CONTEXT-SPECIFIC 573 tagNumber: this.type 574 }, 575 value: [ 576 this.value 577 ] 578 }); 579 case 1: 580 case 2: 581 case 6: 582 { 583 const value = new asn1js.IA5String({ value: this.value }); 584 585 value.idBlock.tagClass = 3; 586 value.idBlock.tagNumber = this.type; 587 588 return value; 589 } 590 case 4: 591 return new asn1js.Constructed({ 592 idBlock: { 593 tagClass: 3, // CONTEXT-SPECIFIC 594 tagNumber: 4 595 }, 596 value: [this.value.toSchema()] 597 }); 598 case 7: 599 { 600 const value = this.value; 601 602 value.idBlock.tagClass = 3; 603 value.idBlock.tagNumber = this.type; 604 605 return value; 606 } 607 case 8: 608 { 609 const value = new asn1js.ObjectIdentifier({ value: this.value }); 610 611 value.idBlock.tagClass = 3; 612 value.idBlock.tagNumber = this.type; 613 614 return value; 615 } 616 default: 617 return GeneralName.schema(); 618 } 619 //#endregion 620 } 621 622 public toJSON(): GeneralNameJson { 623 const _object = { 624 type: this.type, 625 value: EMPTY_STRING 626 } as GeneralNameJson; 627 628 if ((typeof this.value) === "string") 629 _object.value = this.value; 630 else { 631 try { 632 _object.value = this.value.toJSON(); 633 } 634 catch { 635 // nothing 636 } 637 } 638 639 return _object; 640 } 641 642 }