@matter/protocol 0.16.0-alpha.0-20251220-0bb8d9f89 → 0.16.0-alpha.0-20251221-3dce6fa1b

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.
@@ -295,6 +295,17 @@ export class Fabric {
295
295
  }
296
296
 
297
297
  async verifyCredentials(operationalCert: Bytes, intermediateCACert?: Bytes) {
298
+ if (intermediateCACert !== undefined && intermediateCACert.byteLength === 0) {
299
+ intermediateCACert = undefined;
300
+ }
301
+
302
+ // Workaround for an issue with the Ikea hub where the root certificate was also provided as ICAC
303
+ // see https://github.com/project-chip/connectedhomeip/issues/42479
304
+ if (intermediateCACert !== undefined && Bytes.areEqual(this.rootCert, intermediateCACert)) {
305
+ logger.info("Intermediate CA certificate is identical to root certificate; omitting ICAC");
306
+ intermediateCACert = undefined;
307
+ }
308
+
298
309
  const rootCert = Rcac.fromTlv(this.rootCert);
299
310
  const nocCert = Noc.fromTlv(operationalCert);
300
311
  const icaCert = intermediateCACert !== undefined ? Icac.fromTlv(intermediateCACert) : undefined;
@@ -499,6 +510,13 @@ export class FabricBuilder {
499
510
  throw new MatterFlowError("Root certificate needs to be set first");
500
511
  }
501
512
 
513
+ // Workaround for an issue with the Ikea hub where the root certificate was also provided as ICAC
514
+ // see https://github.com/project-chip/connectedhomeip/issues/42479
515
+ if (intermediateCACert !== undefined && Bytes.areEqual(this.#rootCert, intermediateCACert)) {
516
+ logger.info("Intermediate CA certificate is identical to root certificate; omitting ICAC");
517
+ intermediateCACert = undefined;
518
+ }
519
+
502
520
  const rootCert = Rcac.fromTlv(this.#rootCert);
503
521
  const nocCert = Noc.fromTlv(operationalCert);
504
522
  const icaCert = intermediateCACert !== undefined ? Icac.fromTlv(intermediateCACert) : undefined;
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import { CertificateAuthority } from "#certificate/CertificateAuthority.js";
8
+ import { Noc } from "#certificate/kinds/Noc.js";
8
9
  import {
9
10
  AsyncObservable,
10
11
  Bytes,
@@ -53,6 +54,7 @@ export class FabricAuthority {
53
54
  #ca: CertificateAuthority;
54
55
  #fabrics: FabricManager;
55
56
  #fabricAdded = new AsyncObservable<[Fabric]>();
57
+ #rotatedFabricIndices = new Set<FabricIndex>(); // Remember which we already rotated in this run
56
58
 
57
59
  constructor({ ca, fabrics }: FabricAuthorityContext) {
58
60
  this.#ca = ca;
@@ -76,13 +78,17 @@ export class FabricAuthority {
76
78
  }
77
79
 
78
80
  /**
79
- * Obtain the default fabric for this authority.
81
+ * Get the default fabric for this authority.
82
+ * When rotateNoc is true (the default), the NOC key pair is rotated once per runtime when the fabric already exists.
80
83
  */
81
- async defaultFabric(config: FabricAuthorityConfiguration) {
84
+ async defaultFabric(config: FabricAuthorityConfiguration, rotateNoc = true) {
82
85
  // First search for a fabric associated with the CA's root certificate
83
86
  const caRootCert = this.#ca.rootCert;
84
- const fabric = this.fabrics.find(fabric => Bytes.areEqual(fabric.rootCert, caRootCert));
87
+ let fabric = this.fabrics.find(fabric => Bytes.areEqual(fabric.rootCert, caRootCert));
85
88
  if (fabric !== undefined) {
89
+ if (rotateNoc) {
90
+ fabric = await this.#rotateFabricNocKey(fabric);
91
+ }
86
92
  if (fabric.label !== config.adminFabricLabel) {
87
93
  await fabric.setLabel(config.adminFabricLabel);
88
94
  }
@@ -118,7 +124,7 @@ export class FabricAuthority {
118
124
  }
119
125
 
120
126
  /**
121
- * Create a new fabric under our control.
127
+ * Create new fabric under our control.
122
128
  */
123
129
  async createFabric(config: FabricAuthorityConfiguration) {
124
130
  const rootNodeId = config.adminNodeId ?? NodeId.randomOperationalNodeId(this.#fabrics.crypto);
@@ -162,6 +168,35 @@ export class FabricAuthority {
162
168
  return fabric;
163
169
  }
164
170
 
171
+ async #rotateFabricNocKey(fabric: Fabric) {
172
+ if (this.#rotatedFabricIndices.has(fabric.fabricIndex)) {
173
+ // We only rotate once per runtime
174
+ return fabric;
175
+ }
176
+
177
+ const builder = await FabricBuilder.create(this.#fabrics.crypto);
178
+ builder.initializeFromFabricForUpdate(fabric);
179
+ const {
180
+ subject: { nodeId, fabricId, caseAuthenticatedTags },
181
+ } = Noc.fromTlv(fabric.operationalCert).cert;
182
+ if (nodeId !== fabric.rootNodeId) {
183
+ throw new ImplementationError(
184
+ `Cannot rotate NOC for fabric ${fabric.fabricIndex} because root node ID changed`,
185
+ );
186
+ }
187
+ await builder.setOperationalCert(
188
+ await this.#ca.generateNoc(builder.publicKey, fabricId, nodeId, caseAuthenticatedTags),
189
+ fabric.intermediateCACert,
190
+ );
191
+ const newFabric = await builder.build(fabric.fabricIndex);
192
+ logger.info(`Rotated NOC for fabric ${fabric.fabricIndex}`);
193
+
194
+ await this.#fabrics.replaceFabric(newFabric);
195
+
196
+ this.#rotatedFabricIndices.add(fabric.fabricIndex);
197
+ return newFabric;
198
+ }
199
+
165
200
  static [Environmental.create](env: Environment) {
166
201
  const instance = new FabricAuthority({
167
202
  ca: env.get(CertificateAuthority),
@@ -78,7 +78,7 @@ export class CaseServer implements ProtocolHandler {
78
78
  async #handleSigma1(messenger: CaseServerMessenger) {
79
79
  logger.info("Received pairing request", Mark.INBOUND, Diagnostic.via(messenger.channelName));
80
80
 
81
- // Initialize context with information from peer
81
+ // Initialize context with information from a peer
82
82
  const { sigma1Bytes, sigma1 } = await messenger.readSigma1();
83
83
  const resumptionRecord =
84
84
  sigma1.resumptionId !== undefined && sigma1.initiatorResumeMic !== undefined
@@ -268,7 +268,7 @@ export class CaseServer implements ProtocolHandler {
268
268
 
269
269
  await crypto.verifyEcdsa(PublicKey(peerPublicKey), peerSignatureData, new EcdsaSignature(peerSignature));
270
270
 
271
- // All good! Create secure session
271
+ // All good! Create a secure session
272
272
  const secureSessionSalt = Bytes.concat(
273
273
  operationalIdentityProtectionKey,
274
274
  await crypto.computeHash([cx.bytes, sigma2Bytes, sigma3Bytes]),