@matter/protocol 0.16.0-alpha.0-20251106-4e10fd77b → 0.16.0-alpha.0-20251107-6bcb5baf4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dist/cjs/advertisement/PairingHintBitmap.d.ts +2 -2
  2. package/dist/cjs/advertisement/PairingHintBitmap.js +1 -1
  3. package/dist/cjs/advertisement/PairingHintBitmap.js.map +1 -1
  4. package/dist/cjs/advertisement/mdns/CommissionableMdnsAdvertisement.d.ts.map +1 -1
  5. package/dist/cjs/advertisement/mdns/CommissionableMdnsAdvertisement.js +22 -4
  6. package/dist/cjs/advertisement/mdns/CommissionableMdnsAdvertisement.js.map +1 -1
  7. package/dist/cjs/certificate/kinds/Icac.d.ts +2 -0
  8. package/dist/cjs/certificate/kinds/Icac.d.ts.map +1 -1
  9. package/dist/cjs/certificate/kinds/Icac.js +6 -0
  10. package/dist/cjs/certificate/kinds/Icac.js.map +1 -1
  11. package/dist/cjs/certificate/kinds/Noc.d.ts +2 -0
  12. package/dist/cjs/certificate/kinds/Noc.d.ts.map +1 -1
  13. package/dist/cjs/certificate/kinds/Noc.js +6 -0
  14. package/dist/cjs/certificate/kinds/Noc.js.map +1 -1
  15. package/dist/cjs/certificate/kinds/OperationalBase.d.ts +6 -1
  16. package/dist/cjs/certificate/kinds/OperationalBase.d.ts.map +1 -1
  17. package/dist/cjs/certificate/kinds/OperationalBase.js +14 -0
  18. package/dist/cjs/certificate/kinds/OperationalBase.js.map +1 -1
  19. package/dist/cjs/certificate/kinds/Rcac.d.ts +2 -0
  20. package/dist/cjs/certificate/kinds/Rcac.d.ts.map +1 -1
  21. package/dist/cjs/certificate/kinds/Rcac.js +6 -0
  22. package/dist/cjs/certificate/kinds/Rcac.js.map +1 -1
  23. package/dist/cjs/certificate/kinds/X509Base.d.ts +10 -3
  24. package/dist/cjs/certificate/kinds/X509Base.d.ts.map +1 -1
  25. package/dist/cjs/certificate/kinds/X509Base.js +301 -13
  26. package/dist/cjs/certificate/kinds/X509Base.js.map +2 -2
  27. package/dist/cjs/certificate/kinds/definitions/asn.d.ts +13 -0
  28. package/dist/cjs/certificate/kinds/definitions/asn.d.ts.map +1 -1
  29. package/dist/cjs/certificate/kinds/definitions/asn.js +32 -22
  30. package/dist/cjs/certificate/kinds/definitions/asn.js.map +2 -2
  31. package/dist/cjs/certificate/kinds/definitions/operational.d.ts +16 -16
  32. package/dist/cjs/certificate/kinds/definitions/operational.js +2 -2
  33. package/dist/cjs/certificate/kinds/definitions/operational.js.map +1 -1
  34. package/dist/cjs/mdns/MdnsConsts.d.ts +1 -1
  35. package/dist/cjs/mdns/MdnsConsts.d.ts.map +1 -1
  36. package/dist/cjs/mdns/MdnsConsts.js +1 -1
  37. package/dist/esm/advertisement/PairingHintBitmap.d.ts +2 -2
  38. package/dist/esm/advertisement/PairingHintBitmap.js +1 -1
  39. package/dist/esm/advertisement/PairingHintBitmap.js.map +1 -1
  40. package/dist/esm/advertisement/mdns/CommissionableMdnsAdvertisement.d.ts.map +1 -1
  41. package/dist/esm/advertisement/mdns/CommissionableMdnsAdvertisement.js +22 -4
  42. package/dist/esm/advertisement/mdns/CommissionableMdnsAdvertisement.js.map +1 -1
  43. package/dist/esm/certificate/kinds/Icac.d.ts +2 -0
  44. package/dist/esm/certificate/kinds/Icac.d.ts.map +1 -1
  45. package/dist/esm/certificate/kinds/Icac.js +6 -0
  46. package/dist/esm/certificate/kinds/Icac.js.map +1 -1
  47. package/dist/esm/certificate/kinds/Noc.d.ts +2 -0
  48. package/dist/esm/certificate/kinds/Noc.d.ts.map +1 -1
  49. package/dist/esm/certificate/kinds/Noc.js +6 -0
  50. package/dist/esm/certificate/kinds/Noc.js.map +1 -1
  51. package/dist/esm/certificate/kinds/OperationalBase.d.ts +6 -1
  52. package/dist/esm/certificate/kinds/OperationalBase.d.ts.map +1 -1
  53. package/dist/esm/certificate/kinds/OperationalBase.js +16 -2
  54. package/dist/esm/certificate/kinds/OperationalBase.js.map +1 -1
  55. package/dist/esm/certificate/kinds/Rcac.d.ts +2 -0
  56. package/dist/esm/certificate/kinds/Rcac.d.ts.map +1 -1
  57. package/dist/esm/certificate/kinds/Rcac.js +6 -0
  58. package/dist/esm/certificate/kinds/Rcac.js.map +1 -1
  59. package/dist/esm/certificate/kinds/X509Base.d.ts +10 -3
  60. package/dist/esm/certificate/kinds/X509Base.d.ts.map +1 -1
  61. package/dist/esm/certificate/kinds/X509Base.js +303 -13
  62. package/dist/esm/certificate/kinds/X509Base.js.map +2 -2
  63. package/dist/esm/certificate/kinds/definitions/asn.d.ts +13 -0
  64. package/dist/esm/certificate/kinds/definitions/asn.d.ts.map +1 -1
  65. package/dist/esm/certificate/kinds/definitions/asn.js +32 -22
  66. package/dist/esm/certificate/kinds/definitions/asn.js.map +1 -1
  67. package/dist/esm/certificate/kinds/definitions/operational.d.ts +16 -16
  68. package/dist/esm/certificate/kinds/definitions/operational.js +2 -2
  69. package/dist/esm/certificate/kinds/definitions/operational.js.map +1 -1
  70. package/dist/esm/mdns/MdnsConsts.d.ts +1 -1
  71. package/dist/esm/mdns/MdnsConsts.d.ts.map +1 -1
  72. package/dist/esm/mdns/MdnsConsts.js +1 -1
  73. package/package.json +6 -6
  74. package/src/advertisement/PairingHintBitmap.ts +1 -1
  75. package/src/advertisement/mdns/CommissionableMdnsAdvertisement.ts +23 -6
  76. package/src/certificate/kinds/Icac.ts +7 -0
  77. package/src/certificate/kinds/Noc.ts +7 -0
  78. package/src/certificate/kinds/OperationalBase.ts +18 -3
  79. package/src/certificate/kinds/Rcac.ts +7 -0
  80. package/src/certificate/kinds/X509Base.ts +422 -7
  81. package/src/certificate/kinds/definitions/asn.ts +48 -25
  82. package/src/certificate/kinds/definitions/operational.ts +2 -2
  83. package/src/mdns/MdnsConsts.ts +1 -1
@@ -12,6 +12,7 @@ import {
12
12
  DerBitString,
13
13
  DerCodec,
14
14
  DerKey,
15
+ DerNode,
15
16
  DerType,
16
17
  Key,
17
18
  PublicKey,
@@ -26,6 +27,8 @@ import {
26
27
  FabricId_Matter,
27
28
  FirmwareSigningId_Matter,
28
29
  IcacId_Matter,
30
+ MATTER_EPOCH_OFFSET_S,
31
+ MATTER_OID_TO_FIELD_MAP,
29
32
  matterToJsDate,
30
33
  NocCat_Matter,
31
34
  NodeId_Matter,
@@ -114,8 +117,8 @@ export abstract class X509Base<CT extends X509Certificate> {
114
117
  case "commonName":
115
118
  asn.commonName = X520.CommonName(value as string);
116
119
  break;
117
- case "sureName":
118
- asn.sureName = X520.SurName(value as string);
120
+ case "surName":
121
+ asn.surName = X520.SurName(value as string);
119
122
  break;
120
123
  case "serialNum":
121
124
  asn.serialNum = X520.SerialNumber(value as string);
@@ -206,8 +209,8 @@ export abstract class X509Base<CT extends X509Certificate> {
206
209
  case "commonNamePs":
207
210
  asn.commonNamePs = X520.CommonName(value as string, true);
208
211
  break;
209
- case "sureNamePs":
210
- asn.sureNamePs = X520.SurName(value as string, true);
212
+ case "surNamePs":
213
+ asn.surNamePs = X520.SurName(value as string, true);
211
214
  break;
212
215
  case "serialNumPs":
213
216
  asn.serialNumPs = X520.SerialNumber(value as string, true);
@@ -293,7 +296,7 @@ export abstract class X509Base<CT extends X509Certificate> {
293
296
  /**
294
297
  * Build the ASN.1 DER structure for the certificate.
295
298
  */
296
- protected genericBuildAsn1Structure({
299
+ genericBuildAsn1Structure({
297
300
  serialNumber,
298
301
  notBefore,
299
302
  notAfter,
@@ -322,11 +325,13 @@ export abstract class X509Base<CT extends X509Certificate> {
322
325
  extensions: ContextTagged(3, this.#extensionsToAsn1(extensions)),
323
326
  };
324
327
  }
328
+ }
325
329
 
330
+ export namespace X509Base {
326
331
  /**
327
332
  * Create a Certificate Signing Request (CSR) in ASN.1 DER format.
328
333
  */
329
- static async createCertificateSigningRequest(crypto: Crypto, key: Key) {
334
+ export async function createCertificateSigningRequest(crypto: Crypto, key: Key) {
330
335
  const request = {
331
336
  version: 0,
332
337
  subject: { organization: X520.OrganisationName("CSR") },
@@ -341,10 +346,420 @@ export abstract class X509Base<CT extends X509Certificate> {
341
346
  });
342
347
  }
343
348
 
349
+ /**
350
+ * X.509 certificate extension OIDs
351
+ */
352
+ namespace ExtensionOid {
353
+ export const BASIC_CONSTRAINTS = 0x551d13n;
354
+ export const KEY_USAGE = 0x551d0fn;
355
+ export const EXTENDED_KEY_USAGE = 0x551d25n;
356
+ export const SUBJECT_KEY_IDENTIFIER = 0x551d0en;
357
+ export const AUTHORITY_KEY_IDENTIFIER = 0x551d23n;
358
+ }
359
+
360
+ /**
361
+ * Extended Key Usage OIDs
362
+ */
363
+ namespace ExtendedKeyUsageOid {
364
+ export const SERVER_AUTH = 0x2b06010505070301n;
365
+ export const CLIENT_AUTH = 0x2b06010505070302n;
366
+ export const CODE_SIGNING = 0x2b06010505070303n;
367
+ export const EMAIL_PROTECTION = 0x2b06010505070304n;
368
+ export const TIME_STAMPING = 0x2b06010505070308n;
369
+ export const OCSP_SIGNING = 0x2b06010505070309n;
370
+ }
371
+
372
+ /**
373
+ * Map an OID to a subject/issuer field name.
374
+ * Uses auto-generated lookup maps from X520 and Matter OID definitions.
375
+ * Returns the field name and whether the value is a PrintableString variant.
376
+ */
377
+ function oidToSubjectField(oid: Bytes) {
378
+ const oidHex = Bytes.toHex(oid);
379
+
380
+ const field = X520.OID_TO_FIELD_MAP[oidHex] ?? MATTER_OID_TO_FIELD_MAP[oidHex];
381
+ if (field !== undefined) {
382
+ return { field, isPrintable: false };
383
+ }
384
+ }
385
+
386
+ /**
387
+ * Parse a subject or issuer field from ASN.1 DER format.
388
+ */
389
+ function parseSubjectOrIssuer(node: DerNode) {
390
+ const result: { [field: string]: unknown } = {};
391
+
392
+ const { [DerKey.Elements]: rdnSequence } = node;
393
+ if (!rdnSequence) {
394
+ throw new CertificateError("Invalid subject/issuer structure");
395
+ }
396
+
397
+ // Iterate through RDN SEQUENCEs
398
+ for (const rdnSet of rdnSequence) {
399
+ const { [DerKey.Elements]: attributeSets } = rdnSet;
400
+ if (!attributeSets) continue;
401
+
402
+ for (const attributeSet of attributeSets) {
403
+ const { [DerKey.Elements]: attrElements } = attributeSet;
404
+ if (!attrElements || attrElements.length !== 2) continue;
405
+
406
+ const [oidNode, valueNode] = attrElements;
407
+ const oid = oidNode[DerKey.Bytes];
408
+ const fieldInfo = oidToSubjectField(oid);
409
+
410
+ if (fieldInfo === undefined) continue;
411
+
412
+ let { field } = fieldInfo;
413
+ let value;
414
+
415
+ // Parse the value based on the field type
416
+ const valueBytes = Bytes.of(valueNode[DerKey.Bytes]);
417
+ const valueTag = valueNode[DerKey.TagId];
418
+
419
+ // Matter-specific fields are encoded as UTF8 strings containing hex values
420
+ switch (field) {
421
+ case "nodeId":
422
+ case "fabricId": {
423
+ // 16-byte hex string -> BigInt
424
+ const hexString = Bytes.toString(valueBytes);
425
+ value = BigInt("0x" + hexString);
426
+ break;
427
+ }
428
+ case "icacId":
429
+ case "rcacId":
430
+ case "vvsId": {
431
+ // 8-byte hex string -> BigInt, but convert to number if it fits
432
+ const hexString = Bytes.toString(valueBytes);
433
+ const bigIntValue = BigInt("0x" + hexString);
434
+ // Convert to number if it fits in Number.MAX_SAFE_INTEGER
435
+ value = bigIntValue <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(bigIntValue) : bigIntValue;
436
+ break;
437
+ }
438
+ case "firmwareSigningId":
439
+ case "productId":
440
+ case "vendorId": {
441
+ // 4-byte or 2-byte hex string -> number
442
+ const hexString = Bytes.toString(valueBytes);
443
+ value = parseInt(hexString, 16);
444
+ break;
445
+ }
446
+ case "caseAuthenticatedTag": {
447
+ // CAT tags - 4-byte hex string -> number
448
+ const hexString = Bytes.toString(valueBytes);
449
+ const catValue = parseInt(hexString, 16);
450
+ if (result.caseAuthenticatedTags !== undefined) {
451
+ (result.caseAuthenticatedTags as number[]).push(catValue);
452
+ continue;
453
+ }
454
+ field = "caseAuthenticatedTags";
455
+ value = [catValue];
456
+ break;
457
+ }
458
+ default: {
459
+ // String fields
460
+ value = Bytes.toString(valueBytes);
461
+
462
+ // Handle PrintableString variants
463
+ if (valueTag === DerType.PrintableString) {
464
+ field += "Ps";
465
+ }
466
+ }
467
+ }
468
+ result[field] = value;
469
+ }
470
+ }
471
+
472
+ return result;
473
+ }
474
+
475
+ /**
476
+ * Parse extensions from ASN.1 DER format.
477
+ */
478
+ function parseExtensions(extensionsNode: DerNode): X509Certificate["extensions"] & {
479
+ [oid: string]: unknown; // For unrecognized extensions
480
+ } {
481
+ const result = {
482
+ basicConstraints: { isCa: false },
483
+ } as X509Certificate["extensions"] & { [oid: string]: unknown };
484
+
485
+ const { [DerKey.Elements]: extensions } = extensionsNode;
486
+ if (!extensions) {
487
+ throw new CertificateError("Invalid extensions structure");
488
+ }
489
+
490
+ for (const ext of extensions) {
491
+ const { [DerKey.Elements]: extElements } = ext;
492
+ if (!extElements || extElements.length < 2) continue;
493
+
494
+ const oid = extElements[0][DerKey.Bytes];
495
+ const oidValue = Bytes.asBigInt(oid);
496
+
497
+ // Find the value - it might be after a critical flag
498
+ let valueIndex = 1;
499
+ if (extElements.length > 2 && extElements[1][DerKey.TagId] === DerType.Boolean) {
500
+ valueIndex = 2; // Skip critical flag
501
+ }
502
+
503
+ const valueOctetString = extElements[valueIndex][DerKey.Bytes];
504
+ const valueNode = DerCodec.decode(valueOctetString);
505
+
506
+ switch (oidValue) {
507
+ case ExtensionOid.BASIC_CONSTRAINTS:
508
+ {
509
+ const { [DerKey.Elements]: bcElements } = valueNode;
510
+ // Always initialize basicConstraints when extension is present
511
+ if (bcElements && bcElements.length > 0) {
512
+ // First element is isCa boolean
513
+ if (bcElements[0][DerKey.TagId] === DerType.Boolean) {
514
+ const bcBytes = Bytes.of(bcElements[0][DerKey.Bytes]);
515
+ result.basicConstraints.isCa = bcBytes[0] !== 0;
516
+ }
517
+ // Second element (if present) is pathLen integer
518
+ if (bcElements.length > 1 && bcElements[1][DerKey.TagId] === DerType.Integer) {
519
+ const pathLenBytes = Bytes.of(bcElements[1][DerKey.Bytes]);
520
+ result.basicConstraints.pathLen = pathLenBytes[0];
521
+ }
522
+ }
523
+ }
524
+ break;
525
+
526
+ case ExtensionOid.KEY_USAGE:
527
+ {
528
+ // Note: DerKey.Bytes for BIT STRING returns data without the padding byte
529
+ const bitString = Bytes.of(valueNode[DerKey.Bytes]);
530
+ if (bitString.length >= 1) {
531
+ // The keyUsage flags are in the first byte
532
+ const usageByte = bitString[0];
533
+
534
+ // Set all flags based on the bit values
535
+ result.keyUsage = {
536
+ digitalSignature: (usageByte & 0x80) !== 0,
537
+ nonRepudiation: (usageByte & 0x40) !== 0,
538
+ keyEncipherment: (usageByte & 0x20) !== 0,
539
+ dataEncipherment: (usageByte & 0x10) !== 0,
540
+ keyAgreement: (usageByte & 0x08) !== 0,
541
+ keyCertSign: (usageByte & 0x04) !== 0,
542
+ cRLSign: (usageByte & 0x02) !== 0,
543
+ encipherOnly: (usageByte & 0x01) !== 0,
544
+ decipherOnly: bitString.length > 1 ? (bitString[1] & 0x80) !== 0 : false,
545
+ };
546
+ }
547
+ }
548
+ break;
549
+
550
+ case ExtensionOid.EXTENDED_KEY_USAGE:
551
+ {
552
+ const { [DerKey.Elements]: ekuElements } = valueNode;
553
+ if (ekuElements) {
554
+ const ekuValues: number[] = [];
555
+ for (const eku of ekuElements) {
556
+ const ekuOidValue = Bytes.asBigInt(eku[DerKey.Bytes]);
557
+ switch (ekuOidValue) {
558
+ case ExtendedKeyUsageOid.SERVER_AUTH:
559
+ ekuValues.push(1);
560
+ break;
561
+ case ExtendedKeyUsageOid.CLIENT_AUTH:
562
+ ekuValues.push(2);
563
+ break;
564
+ case ExtendedKeyUsageOid.CODE_SIGNING:
565
+ ekuValues.push(3);
566
+ break;
567
+ case ExtendedKeyUsageOid.EMAIL_PROTECTION:
568
+ ekuValues.push(4);
569
+ break;
570
+ case ExtendedKeyUsageOid.TIME_STAMPING:
571
+ ekuValues.push(5);
572
+ break;
573
+ case ExtendedKeyUsageOid.OCSP_SIGNING:
574
+ ekuValues.push(6);
575
+ break;
576
+ }
577
+ }
578
+ if (ekuValues.length > 0) {
579
+ result.extendedKeyUsage = ekuValues;
580
+ }
581
+ }
582
+ }
583
+ break;
584
+
585
+ case ExtensionOid.SUBJECT_KEY_IDENTIFIER:
586
+ result.subjectKeyIdentifier = valueNode[DerKey.Bytes];
587
+ break;
588
+
589
+ case ExtensionOid.AUTHORITY_KEY_IDENTIFIER:
590
+ {
591
+ const { [DerKey.Elements]: akiElements } = valueNode;
592
+ if (akiElements && akiElements.length > 0) {
593
+ // The keyIdentifier is context-tagged with [0]
594
+ result.authorityKeyIdentifier = akiElements[0][DerKey.Bytes];
595
+ }
596
+ }
597
+ break;
598
+ }
599
+ }
600
+
601
+ if (
602
+ result.basicConstraints === undefined ||
603
+ result.keyUsage === undefined ||
604
+ result.subjectKeyIdentifier === undefined ||
605
+ result.authorityKeyIdentifier === undefined
606
+ ) {
607
+ throw new CertificateError("Missing required extensions in certificate");
608
+ }
609
+
610
+ return result;
611
+ }
612
+
613
+ /**
614
+ * Parse a date from ASN.1 DER format (UTCTime or GeneralizedTime).
615
+ */
616
+ function parseDate(node: DerNode): number {
617
+ const dateBytes = node[DerKey.Bytes];
618
+ const dateString = Bytes.toString(dateBytes);
619
+ const tag = node[DerKey.TagId];
620
+
621
+ let year: number, month: number, day: number, hour: number, minute: number, second: number;
622
+
623
+ if (tag === DerType.UtcDate) {
624
+ // UTCTime format: YYMMDDHHMMSSZ
625
+ year = parseInt(dateString.substring(0, 2));
626
+ year += year >= 50 ? 1900 : 2000;
627
+ month = parseInt(dateString.substring(2, 4));
628
+ day = parseInt(dateString.substring(4, 6));
629
+ hour = parseInt(dateString.substring(6, 8));
630
+ minute = parseInt(dateString.substring(8, 10));
631
+ second = parseInt(dateString.substring(10, 12));
632
+ } else if (tag === DerType.GeneralizedTime) {
633
+ // GeneralizedTime format: YYYYMMDDHHMMSSZ
634
+ year = parseInt(dateString.substring(0, 4));
635
+ month = parseInt(dateString.substring(4, 6));
636
+ day = parseInt(dateString.substring(6, 8));
637
+ hour = parseInt(dateString.substring(8, 10));
638
+ minute = parseInt(dateString.substring(10, 12));
639
+ second = parseInt(dateString.substring(12, 14));
640
+ } else {
641
+ throw new CertificateError(`Unsupported date type: ${tag}`);
642
+ }
643
+
644
+ const date = new Date(Date.UTC(year, month - 1, day, hour, minute, second));
645
+
646
+ // Check if this is the special NON_WELL_DEFINED_DATE (9999-12-31 23:59:59Z)
647
+ // which should be represented as 0 in Matter epoch
648
+ if (date.getTime() === X520.NON_WELL_DEFINED_DATE.getTime()) {
649
+ return 0;
650
+ }
651
+
652
+ // Convert to Matter epoch (seconds since 2000-01-01 00:00:00 UTC)
653
+ return Math.floor(date.getTime() / 1000) - MATTER_EPOCH_OFFSET_S;
654
+ }
655
+
656
+ /**
657
+ * Parse an ASN.1/DER encoded certificate into the internal format.
658
+ * This extracts the certificate data without the signature.
659
+ */
660
+ export function parseAsn1Certificate(encodedCert: Bytes): X509Certificate {
661
+ const { [DerKey.Elements]: rootElements } = DerCodec.decode(encodedCert);
662
+
663
+ if (!rootElements || rootElements.length !== 3) {
664
+ throw new CertificateError(
665
+ `Invalid certificate structure - expected 3 root elements, got ${rootElements?.length ?? 0}`,
666
+ );
667
+ }
668
+
669
+ const [certificateNode, , signatureNode] = rootElements;
670
+
671
+ // Parse TBSCertificate
672
+ const { [DerKey.Elements]: certElements } = certificateNode;
673
+ if (!certElements || certElements.length < 7) {
674
+ throw new CertificateError("Invalid TBSCertificate structure");
675
+ }
676
+
677
+ let idx = 0;
678
+
679
+ // Version (optional, context-tagged [0])
680
+ if (certElements[idx][DerKey.TagId] === 0xa0) {
681
+ // Skip version - we don't need it for the internal representation
682
+ idx++;
683
+ }
684
+
685
+ // Serial number
686
+ const serialNumber = certElements[idx++][DerKey.Bytes];
687
+
688
+ // Signature algorithm
689
+ const signatureAlgorithmOid = certElements[idx][DerKey.Elements]?.[0]?.[DerKey.Bytes];
690
+ if (!signatureAlgorithmOid) {
691
+ throw new CertificateError("Invalid signature algorithm structure");
692
+ }
693
+ const signatureAlgorithm = Bytes.toHex(signatureAlgorithmOid) === "2a8648ce3d040302" ? 1 : 0;
694
+ idx++;
695
+
696
+ // Issuer
697
+ const issuer = parseSubjectOrIssuer(certElements[idx++]);
698
+
699
+ // Validity
700
+ const { [DerKey.Elements]: validityElements } = certElements[idx++];
701
+ if (!validityElements || validityElements.length !== 2) {
702
+ throw new CertificateError("Invalid validity structure");
703
+ }
704
+ const notBefore = parseDate(validityElements[0]);
705
+ const notAfter = parseDate(validityElements[1]);
706
+
707
+ // Subject
708
+ const subject = parseSubjectOrIssuer(certElements[idx++]);
709
+
710
+ // Public key
711
+ const { [DerKey.Elements]: publicKeyElements } = certElements[idx++];
712
+ if (!publicKeyElements || publicKeyElements.length !== 2) {
713
+ throw new CertificateError("Invalid public key structure");
714
+ }
715
+
716
+ const { [DerKey.Elements]: algorithmElements } = publicKeyElements[0];
717
+ if (!algorithmElements || algorithmElements.length !== 2) {
718
+ throw new CertificateError("Invalid public key algorithm structure");
719
+ }
720
+
721
+ const publicKeyAlgorithmOid = Bytes.toHex(algorithmElements[0][DerKey.Bytes]);
722
+ const publicKeyAlgorithm = publicKeyAlgorithmOid === "2a8648ce3d0201" ? 1 : 0;
723
+
724
+ const ellipticCurveOid = Bytes.toHex(algorithmElements[1][DerKey.Bytes]);
725
+ const ellipticCurveIdentifier = ellipticCurveOid === "2a8648ce3d030107" ? 1 : 0;
726
+
727
+ // Note: DerKey.Bytes for BIT STRING returns data without the padding byte
728
+ // EC public keys in Matter format include the 0x04 uncompressed point format byte
729
+ // followed by 64 bytes (32 bytes X + 32 bytes Y), totaling 65 bytes
730
+ const ellipticCurvePublicKey = Bytes.of(publicKeyElements[1][DerKey.Bytes]);
731
+
732
+ // Extensions (required, context-tagged [3])
733
+ if (idx >= certElements.length || certElements[idx][DerKey.TagId] !== 0xa3) {
734
+ throw new CertificateError("Missing required extensions in certificate");
735
+ }
736
+ const extensionsBytes = certElements[idx][DerKey.Bytes];
737
+ const extensionsSequence = DerCodec.decode(extensionsBytes);
738
+ const extensions = parseExtensions(extensionsSequence);
739
+
740
+ // Extract signature from BIT STRING
741
+ // Note: DerKey.Bytes for BIT STRING returns data without the padding byte
742
+ const signature = Bytes.of(signatureNode[DerKey.Bytes]);
743
+
744
+ return {
745
+ serialNumber,
746
+ signatureAlgorithm,
747
+ issuer,
748
+ notBefore,
749
+ notAfter,
750
+ subject,
751
+ publicKeyAlgorithm,
752
+ ellipticCurveIdentifier,
753
+ ellipticCurvePublicKey,
754
+ extensions,
755
+ signature,
756
+ };
757
+ }
758
+
344
759
  /**
345
760
  * Extract the public key from a Certificate Signing Request (CSR) in ASN.1 DER format.
346
761
  */
347
- static async getPublicKeyFromCsr(crypto: Crypto, encodedCsr: Bytes) {
762
+ export async function getPublicKeyFromCsr(crypto: Crypto, encodedCsr: Bytes) {
348
763
  const { [DerKey.Elements]: rootElements } = DerCodec.decode(encodedCsr);
349
764
  if (rootElements?.length !== 3) {
350
765
  throw new CertificateError("Invalid CSR data");
@@ -6,20 +6,22 @@
6
6
 
7
7
  import { Bytes, DerObject, X520 } from "#general";
8
8
  import { FabricId, NodeId, VendorId } from "#types";
9
+ import { InternalError } from "@matter/general";
9
10
 
10
11
  const YEAR_S = 365 * 24 * 60 * 60;
11
- const EPOCH_OFFSET_S = 10957 * 24 * 60 * 60;
12
+ /** Seconds from Unix epoch (1970-01-01) to Matter epoch (2000-01-01) */
13
+ export const MATTER_EPOCH_OFFSET_S = 10957 * 24 * 60 * 60;
12
14
 
13
15
  // TODO replace usage of Date by abstraction
14
16
 
15
17
  export function matterToJsDate(date: number) {
16
- return date === 0 ? X520.NON_WELL_DEFINED_DATE : new Date((date + EPOCH_OFFSET_S) * 1000);
18
+ return date === 0 ? X520.NON_WELL_DEFINED_DATE : new Date((date + MATTER_EPOCH_OFFSET_S) * 1000);
17
19
  }
18
20
 
19
21
  export function jsToMatterDate(date: Date, addYears = 0) {
20
22
  return date.getTime() === X520.NON_WELL_DEFINED_DATE.getTime()
21
23
  ? 0
22
- : Math.floor(date.getTime() / 1000) - EPOCH_OFFSET_S + addYears * YEAR_S;
24
+ : Math.floor(date.getTime() / 1000) - MATTER_EPOCH_OFFSET_S + addYears * YEAR_S;
23
25
  }
24
26
 
25
27
  function intTo16Chars(value: bigint | number) {
@@ -49,52 +51,73 @@ function uInt16To4Chars(value: number) {
49
51
  */
50
52
 
51
53
  /**
52
- * Generator function to create a specific ASN field for a Matter OpCert DN with the OID base 1.3.6.1.4.1.37244.1.*.
53
- * The returned function takes the value and returns the ASN.1 DER object.
54
+ * Reverse lookup map from OID hex to field name for all Matter-specific attributes
55
+ * (both operational and attestation certificates)
54
56
  */
55
- const GenericMatterOpCertObject =
56
- <T>(id: number, valueConverter?: (value: T) => string) =>
57
- (value: T) => [
58
- DerObject(`2b0601040182a27c01${id.toString(16).padStart(2, "0")}`, {
57
+ export const MATTER_OID_TO_FIELD_MAP: { [oidHex: string]: string } = {};
58
+
59
+ /**
60
+ * Generic generator function for Matter-specific ASN.1 OID fields.
61
+ * Registers the OID in the reverse lookup map immediately at function creation time.
62
+ */
63
+ const GenericMatterObject = <T>(
64
+ oidBase: string,
65
+ id: number,
66
+ fieldName: string,
67
+ valueConverter?: (value: T) => string,
68
+ ) => {
69
+ const oidHex = `${oidBase}${id.toString(16).padStart(2, "0")}`;
70
+ // Register in reverse lookup map immediately at function creation time
71
+ if (MATTER_OID_TO_FIELD_MAP[oidHex] !== undefined && MATTER_OID_TO_FIELD_MAP[oidHex] !== fieldName) {
72
+ throw new InternalError(
73
+ `ASN.1 Matter OID mapping for ${oidHex} already exists with a different field name: "${MATTER_OID_TO_FIELD_MAP[oidHex]}" vs "${fieldName}"`,
74
+ );
75
+ }
76
+ MATTER_OID_TO_FIELD_MAP[oidHex] = fieldName;
77
+ return (value: T) => [
78
+ DerObject(oidHex, {
59
79
  value: (valueConverter ?? intTo16Chars)(value as any),
60
80
  }),
61
81
  ];
82
+ };
83
+
84
+ /**
85
+ * Generator function to create a specific ASN field for a Matter OpCert DN with the OID base 1.3.6.1.4.1.37244.1.*.
86
+ * The returned function takes the value and returns the ASN.1 DER object.
87
+ */
88
+ const GenericMatterOpCertObject = <T>(id: number, fieldName: string, valueConverter?: (value: T) => string) =>
89
+ GenericMatterObject<T>("2b0601040182a27c01", id, fieldName, valueConverter);
62
90
 
63
91
  /**
64
92
  * Generator function to create a specific ASN field for a Matter AttCert DN with the OID base 1.3.6.1.4.1.37244.2.*.
65
93
  * The returned function takes the value and returns the ASN.1 DER object.
66
94
  */
67
- const GenericMatterAttCertObject =
68
- <T>(id: number, valueConverter?: (value: T) => string) =>
69
- (value: T) => [
70
- DerObject(`2b0601040182a27c02${id.toString(16).padStart(2, "0")}`, {
71
- value: (valueConverter ?? intTo16Chars)(value as any),
72
- }),
73
- ];
95
+ const GenericMatterAttCertObject = <T>(id: number, fieldName: string, valueConverter?: (value: T) => string) =>
96
+ GenericMatterObject<T>("2b0601040182a27c02", id, fieldName, valueConverter);
74
97
 
75
98
  /** matter-node-id = ASN.1 OID 1.3.6.1.4.1.37244.1.1 */
76
- export const NodeId_Matter = GenericMatterOpCertObject<NodeId>(1);
99
+ export const NodeId_Matter = GenericMatterOpCertObject<NodeId>(1, "nodeId");
77
100
 
78
101
  /** matter-firmware-signing-id = ASN.1 OID 1.3.6.1.4.1.37244.1.2 */
79
- export const FirmwareSigningId_Matter = GenericMatterOpCertObject<number>(2);
102
+ export const FirmwareSigningId_Matter = GenericMatterOpCertObject<number>(2, "firmwareSigningId");
80
103
 
81
104
  /** matter-icac-id = ASN.1 OID 1.3.6.1.4.1.37244.1.3 */
82
- export const IcacId_Matter = GenericMatterOpCertObject<bigint | number>(3);
105
+ export const IcacId_Matter = GenericMatterOpCertObject<bigint | number>(3, "icacId");
83
106
 
84
107
  /** matter-rcac-id = ASN.1 OID 1.3.6.1.4.1.37244.1.4 */
85
- export const RcacId_Matter = GenericMatterOpCertObject<bigint | number>(4);
108
+ export const RcacId_Matter = GenericMatterOpCertObject<bigint | number>(4, "rcacId");
86
109
 
87
110
  /** matter-fabric-id = ASN.1 OID 1.3.6.1.4.1.37244.1.5 */
88
- export const FabricId_Matter = GenericMatterOpCertObject<FabricId>(5);
111
+ export const FabricId_Matter = GenericMatterOpCertObject<FabricId>(5, "fabricId");
89
112
 
90
113
  /** matter-noc-cat = ASN.1 OID 1.3.6.1.4.1.37244.1.6 */
91
- export const NocCat_Matter = GenericMatterOpCertObject<number>(6, uInt16To8Chars);
114
+ export const NocCat_Matter = GenericMatterOpCertObject<number>(6, "caseAuthenticatedTag", uInt16To8Chars);
92
115
 
93
116
  /** matter-vvs-id = ASN.1 OID 1.3.6.1.4.1.37244.1.7 */
94
- export const VvsId_Matter = GenericMatterOpCertObject<bigint | number>(7);
117
+ export const VvsId_Matter = GenericMatterOpCertObject<bigint | number>(7, "vvsId");
95
118
 
96
119
  /** matter-oid-vid = ASN.1 OID 1.3.6.1.4.1.37244.2.1 */
97
- export const VendorId_Matter = GenericMatterAttCertObject<VendorId>(1, uInt16To4Chars);
120
+ export const VendorId_Matter = GenericMatterAttCertObject<VendorId>(1, "vendorId", uInt16To4Chars);
98
121
 
99
122
  /** matter-oid-pid = ASN.1 OID 1.3.6.1.4.1.37244.2.2 */
100
- export const ProductId_Matter = GenericMatterAttCertObject<number>(2, uInt16To4Chars);
123
+ export const ProductId_Matter = GenericMatterAttCertObject<number>(2, "productId", uInt16To4Chars);
@@ -69,7 +69,7 @@ export namespace OperationalCertificate {
69
69
  const fields = {
70
70
  // Standard DNs
71
71
  commonName: TlvOptionalField(1, TlvString),
72
- sureName: TlvOptionalField(2, TlvString),
72
+ surName: TlvOptionalField(2, TlvString),
73
73
  serialNum: TlvOptionalField(3, TlvString),
74
74
  countryName: TlvOptionalField(4, TlvString),
75
75
  localityName: TlvOptionalField(5, TlvString),
@@ -90,7 +90,7 @@ export namespace OperationalCertificate {
90
90
 
91
91
  // Standard DNs when encoded as Printable String
92
92
  commonNamePs: TlvOptionalField(129, TlvString),
93
- sureNamePs: TlvOptionalField(130, TlvString),
93
+ surNamePs: TlvOptionalField(130, TlvString),
94
94
  serialNumPs: TlvOptionalField(131, TlvString),
95
95
  countryNamePs: TlvOptionalField(132, TlvString),
96
96
  localityNamePs: TlvOptionalField(133, TlvString),
@@ -23,7 +23,7 @@ export const DEFAULT_PAIRING_HINT = {
23
23
 
24
24
  export const PAIRING_HINTS_REQUIRING_INSTRUCTION = Array<keyof typeof PairingHintBitmap>(
25
25
  "customInstruction",
26
- "pressRestButtonForNumberOfSeconds",
26
+ "pressResetButtonForNumberOfSeconds",
27
27
  "pressResetButtonUntilLightBlinks",
28
28
  "pressResetButtonForNumberOfSecondsWithApplicationOfPower",
29
29
  "pressResetButtonUntilLightBlinksWithApplicationOfPower",