I am building my own .Net Matter Controller.
I’m writing these posts as I go, figuring the stuff out and blogging how and what I’m doing.
In my previous post, I ended with a working CASE exchange. Not complete by any stretch (I don’t perform the necessary validation), but it is working.
New Secure Session
As shown in the previous post, I finished with a shared secret, derived from the Sigma messages.
Just like the PASE, I now needed to turn this shared secret into a pair of keys for encrypting and decrypting. This is done using KDF (Key Derivation Function).
hkdf = new HkdfBytesGenerator(new Sha256Digest());
hkdf.Init(new HkdfParameters(sharedSecret, secureSessionSalt, info));
var caseKeys = new byte[48];
hkdf.GenerateBytes(caseKeys, 0, 48);
encryptKey = caseKeys.AsSpan().Slice(0, 16).ToArray();
decryptKey = caseKeys.AsSpan().Slice(16, 16).ToArray();
I then create an instance of CaseSecureSession, passing in the two keys. At this point I realised that both the PaseSecureSession and CaseSecureSession classes are identical, so flagged them for cleanup.
CommissioningComplete
The CommissioningComplete (0x04) is a Command belonging to the General Commissioning Cluster (0x30). It’s called using the IM InvokeCommand. I did this during the certificates steps to send the trusted certificate and Node Operational Certificate.
Unfortunately, this wasn’t liked.

I repeated my well-worn debugging steps (logs of console.log and Console.WriteLine) and identified my mistake. My secureSessionSalt was wrong. I was using an empty byte[], like in PASE, but this wasn’t enough.
The matter.js library created a hashed salt like this:
const secureSessionSalt = Bytes.concat(
operationalIdentityProtectionKey,
Crypto.hash([sigma1Bytes, sigma2Bytes, sigma3Bytes]),
);
I had no idea why it was doing this. After the PASE, the Specification gave some guidance on how to establish the session under a heading “Session Encryption Keys”. I searched for this and found what I needed!

Adding these changes resulted in this

This was much better. The generated keys now matched.


Unfortunately, matterjs was still giving me this

As I looked at it, I noticed that the incoming message was a StandaloneAck, not the InvokeRequest I thought I was sending.
I reduced the amount of logging in my code and switched some of it to Debug.Writeline instead of Console (faster?) and was surprised by the number of message re-transmissions by matter.js. Even messages that I had ack’d sometimes got resent.
In this case, it appeared that it was trying to decrypt my StandaloneAck using the new CASE session. That message was being sent in the old PASE session. After some looking, I spotted my mistake! I was omitting the ExchangeId in my AcknowledgeMessageAsync method. This must have confused matter.js and made it use the *current* session by default. I fixed that and…the retranmissions all but vanished!
At this point, for some unknown reason, I pulled down the latest matter.js. This caused everything to then *break*.
My Sigma1 message was now being ignored

Breaking Changes to matter.js
After consulting with the Oracles in Discord, it turned out that matter.js was behaving correctly! I was trying to send the CommissioningComplete over a secure channel (the PASE) and that wasn’t correct. I should be using an insecure channel!
This makes sense as the CASE is supposed to happen whenever a new session is being setup. I have all the certificates installed to perform CASE.
I updated my code to use the same UnsecuredSession I used for PASE and that almost worked!

There just appeared to be something wrong with my encryption. I knew the keys were correct, so I double checked the nonce and additionalData. Turns out, the nonce I was generating was incorrect. I was still using the old SourceId of 0x00. Since I had a proper fabric and and the NodeId of the device I was configuring, I used that.
commissioningCompleteMessageFrame.SourceNodeID = BitConverter.ToUInt64(peerNodeId.ToByteArrayUnsigned());
Then I got a real doozie!

The error read “(126) Access to 0/0x30 denied on secure/23694.”
I had to dig into this error. It was obvious this was a security issue. ClusterId 0x30 is for General Commissioning cluster. Was I missing a step during the commissioning flow??
There is a step to adjust the ACL (Access Control Lists), but the matter.js implementation skips that step, so I did the same. Maybe my nodeIds are all wrong??
The ACL got updated when my addNocRequest was accepted.

I knew my time with the chip-tool, that 2 was the NodeId that had the given permission. Of course, there was nothing with a NodeId of 2 in my code. And that lead me to this line of code:
addNocRequest.AddUInt64(3, 2); // CaseAdminSubject - In this case a NodeId of 2.
I promptly put this right:
addNocRequest.AddUInt64(3, fabric.RootNodeId.ToByteArrayUnsigned());
Yee Haw!

My CommissioningComplete request was successfully accepted!!!!
Next Steps
This milestone represents v0.1 in my mind. I have got enough code working to commission a node into a Fabric.
The most immediate thing is actually *persisting* the Fabric credentials that I create on startup. All the certificates and keys are in memory, so launching the Console app again results in a new fabric.
Once my Fabric is saved, I’ll then need a nice way to persist the Node I just added. Then, when the console app is restarted, it can establish a CASE session again. At that point, I’ll shoot for the moon and attempt to send a simple OnOff command.
As ever, all the code is up at https://github.com/tomasmcguinness/dotnet-matter. It’s saved under the tag v0.1 to match my progress so far.




Leave a comment