@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.
- package/dist/cjs/advertisement/PairingHintBitmap.d.ts +2 -2
- package/dist/cjs/advertisement/PairingHintBitmap.js +1 -1
- package/dist/cjs/advertisement/PairingHintBitmap.js.map +1 -1
- package/dist/cjs/advertisement/mdns/CommissionableMdnsAdvertisement.d.ts.map +1 -1
- package/dist/cjs/advertisement/mdns/CommissionableMdnsAdvertisement.js +22 -4
- package/dist/cjs/advertisement/mdns/CommissionableMdnsAdvertisement.js.map +1 -1
- package/dist/cjs/certificate/kinds/Icac.d.ts +2 -0
- package/dist/cjs/certificate/kinds/Icac.d.ts.map +1 -1
- package/dist/cjs/certificate/kinds/Icac.js +6 -0
- package/dist/cjs/certificate/kinds/Icac.js.map +1 -1
- package/dist/cjs/certificate/kinds/Noc.d.ts +2 -0
- package/dist/cjs/certificate/kinds/Noc.d.ts.map +1 -1
- package/dist/cjs/certificate/kinds/Noc.js +6 -0
- package/dist/cjs/certificate/kinds/Noc.js.map +1 -1
- package/dist/cjs/certificate/kinds/OperationalBase.d.ts +6 -1
- package/dist/cjs/certificate/kinds/OperationalBase.d.ts.map +1 -1
- package/dist/cjs/certificate/kinds/OperationalBase.js +14 -0
- package/dist/cjs/certificate/kinds/OperationalBase.js.map +1 -1
- package/dist/cjs/certificate/kinds/Rcac.d.ts +2 -0
- package/dist/cjs/certificate/kinds/Rcac.d.ts.map +1 -1
- package/dist/cjs/certificate/kinds/Rcac.js +6 -0
- package/dist/cjs/certificate/kinds/Rcac.js.map +1 -1
- package/dist/cjs/certificate/kinds/X509Base.d.ts +10 -3
- package/dist/cjs/certificate/kinds/X509Base.d.ts.map +1 -1
- package/dist/cjs/certificate/kinds/X509Base.js +301 -13
- package/dist/cjs/certificate/kinds/X509Base.js.map +2 -2
- package/dist/cjs/certificate/kinds/definitions/asn.d.ts +13 -0
- package/dist/cjs/certificate/kinds/definitions/asn.d.ts.map +1 -1
- package/dist/cjs/certificate/kinds/definitions/asn.js +32 -22
- package/dist/cjs/certificate/kinds/definitions/asn.js.map +2 -2
- package/dist/cjs/certificate/kinds/definitions/operational.d.ts +16 -16
- package/dist/cjs/certificate/kinds/definitions/operational.js +2 -2
- package/dist/cjs/certificate/kinds/definitions/operational.js.map +1 -1
- package/dist/cjs/mdns/MdnsConsts.d.ts +1 -1
- package/dist/cjs/mdns/MdnsConsts.d.ts.map +1 -1
- package/dist/cjs/mdns/MdnsConsts.js +1 -1
- package/dist/esm/advertisement/PairingHintBitmap.d.ts +2 -2
- package/dist/esm/advertisement/PairingHintBitmap.js +1 -1
- package/dist/esm/advertisement/PairingHintBitmap.js.map +1 -1
- package/dist/esm/advertisement/mdns/CommissionableMdnsAdvertisement.d.ts.map +1 -1
- package/dist/esm/advertisement/mdns/CommissionableMdnsAdvertisement.js +22 -4
- package/dist/esm/advertisement/mdns/CommissionableMdnsAdvertisement.js.map +1 -1
- package/dist/esm/certificate/kinds/Icac.d.ts +2 -0
- package/dist/esm/certificate/kinds/Icac.d.ts.map +1 -1
- package/dist/esm/certificate/kinds/Icac.js +6 -0
- package/dist/esm/certificate/kinds/Icac.js.map +1 -1
- package/dist/esm/certificate/kinds/Noc.d.ts +2 -0
- package/dist/esm/certificate/kinds/Noc.d.ts.map +1 -1
- package/dist/esm/certificate/kinds/Noc.js +6 -0
- package/dist/esm/certificate/kinds/Noc.js.map +1 -1
- package/dist/esm/certificate/kinds/OperationalBase.d.ts +6 -1
- package/dist/esm/certificate/kinds/OperationalBase.d.ts.map +1 -1
- package/dist/esm/certificate/kinds/OperationalBase.js +16 -2
- package/dist/esm/certificate/kinds/OperationalBase.js.map +1 -1
- package/dist/esm/certificate/kinds/Rcac.d.ts +2 -0
- package/dist/esm/certificate/kinds/Rcac.d.ts.map +1 -1
- package/dist/esm/certificate/kinds/Rcac.js +6 -0
- package/dist/esm/certificate/kinds/Rcac.js.map +1 -1
- package/dist/esm/certificate/kinds/X509Base.d.ts +10 -3
- package/dist/esm/certificate/kinds/X509Base.d.ts.map +1 -1
- package/dist/esm/certificate/kinds/X509Base.js +303 -13
- package/dist/esm/certificate/kinds/X509Base.js.map +2 -2
- package/dist/esm/certificate/kinds/definitions/asn.d.ts +13 -0
- package/dist/esm/certificate/kinds/definitions/asn.d.ts.map +1 -1
- package/dist/esm/certificate/kinds/definitions/asn.js +32 -22
- package/dist/esm/certificate/kinds/definitions/asn.js.map +1 -1
- package/dist/esm/certificate/kinds/definitions/operational.d.ts +16 -16
- package/dist/esm/certificate/kinds/definitions/operational.js +2 -2
- package/dist/esm/certificate/kinds/definitions/operational.js.map +1 -1
- package/dist/esm/mdns/MdnsConsts.d.ts +1 -1
- package/dist/esm/mdns/MdnsConsts.d.ts.map +1 -1
- package/dist/esm/mdns/MdnsConsts.js +1 -1
- package/package.json +6 -6
- package/src/advertisement/PairingHintBitmap.ts +1 -1
- package/src/advertisement/mdns/CommissionableMdnsAdvertisement.ts +23 -6
- package/src/certificate/kinds/Icac.ts +7 -0
- package/src/certificate/kinds/Noc.ts +7 -0
- package/src/certificate/kinds/OperationalBase.ts +18 -3
- package/src/certificate/kinds/Rcac.ts +7 -0
- package/src/certificate/kinds/X509Base.ts +422 -7
- package/src/certificate/kinds/definitions/asn.ts +48 -25
- package/src/certificate/kinds/definitions/operational.ts +2 -2
- 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 "
|
|
118
|
-
asn.
|
|
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 "
|
|
210
|
-
asn.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 +
|
|
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) -
|
|
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
|
-
*
|
|
53
|
-
*
|
|
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
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
93
|
+
surNamePs: TlvOptionalField(130, TlvString),
|
|
94
94
|
serialNumPs: TlvOptionalField(131, TlvString),
|
|
95
95
|
countryNamePs: TlvOptionalField(132, TlvString),
|
|
96
96
|
localityNamePs: TlvOptionalField(133, TlvString),
|
package/src/mdns/MdnsConsts.ts
CHANGED
|
@@ -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
|
-
"
|
|
26
|
+
"pressResetButtonForNumberOfSeconds",
|
|
27
27
|
"pressResetButtonUntilLightBlinks",
|
|
28
28
|
"pressResetButtonForNumberOfSecondsWithApplicationOfPower",
|
|
29
29
|
"pressResetButtonUntilLightBlinksWithApplicationOfPower",
|