Skip to content

Commit

Permalink
Add support for enhanced commissioning
Browse files Browse the repository at this point in the history
  • Loading branch information
jdomnitz committed Jan 13, 2025
1 parent 0e43aed commit 7f38e03
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 28 deletions.
35 changes: 30 additions & 5 deletions MatterDotNet/Entities/Node.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
using MatterDotNet.OperationalDiscovery;
using MatterDotNet.PKI;
using MatterDotNet.Protocol.Connection;
using MatterDotNet.Protocol.Cryptography;
using MatterDotNet.Protocol.Sessions;
using MatterDotNet.Protocol.Subprotocols;
using System.Net;
using System.Security.Cryptography;
using System.Text;

namespace MatterDotNet.Entities
Expand Down Expand Up @@ -57,22 +59,45 @@ private Node(ODNode connection, Fabric fabric, OperationalCertificate noc)
/// <exception cref="IOException"></exception>
public async Task<SecureSession> GetCASESession()
{
if (connection.IP6Address != null)
if (connection.IP4Address != null)
{
using (SessionContext session = SessionManager.GetUnsecureSession(new IPEndPoint(connection.IP6Address!, connection.Port), true))
using (SessionContext session = SessionManager.GetUnsecureSession(new IPEndPoint(connection.IP4Address!, connection.Port), true))
return await GetCASESession(session);
}
else if(connection.IP4Address != null)
else if (connection.IP6Address != null)
{
using (SessionContext session = SessionManager.GetUnsecureSession(new IPEndPoint(connection.IP4Address!, connection.Port), true))
using (SessionContext session = SessionManager.GetUnsecureSession(new IPEndPoint(connection.IP6Address!, connection.Port), true))
return await GetCASESession(session);
}
else
{
using (SessionContext session = SessionManager.GetUnsecureSession(new BLEEndPoint(connection.BTAddress), true))
using (SessionContext session = SessionManager.GetUnsecureSession(new BLEEndPoint(connection.BTAddress!), true))
return await GetCASESession(session);
}
}

/// <summary>
/// Open a commissioning window with a new randomly generateed PIN Code
/// </summary>
/// <param name="session"></param>
/// <param name="timeoutSec"></param>
/// <returns></returns>
/// <exception cref="PlatformNotSupportedException"></exception>
public async Task<string?> OpenEnhancedCommissioning(SecureSession session, ushort timeoutSec = 300)
{
if (!root.HasCluster<AdministratorCommissioningCluster>())
throw new PlatformNotSupportedException("Admin commissioning cluster not found");
uint passcode = Crypto.GeneratePasscode();
ushort discriminator = (ushort)(Random.Shared.Next() & 0xFFF);
string pin = CommissioningPayload.GeneratePIN(discriminator, passcode);
SPAKE2Plus spake = new SPAKE2Plus();
AdministratorCommissioningCluster com = root.GetCluster<AdministratorCommissioningCluster>();
byte[] salt = RandomNumberGenerator.GetBytes(32);
if (!await com.OpenCommissioningWindow(session, 10000, timeoutSec, spake.CommissioneePakeInput(passcode, 10000, salt), discriminator, 10000, salt))
return null;
return pin;
}

/// <summary>
/// Get a secure session for the node
/// </summary>
Expand Down
17 changes: 17 additions & 0 deletions MatterDotNet/OperationalDiscovery/CommissioningPayload.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,23 @@ public static CommissioningPayload FromPIN(string pin)
return ret;
}

/// <summary>
/// Generate a pairing code from a 12-bit discriminator and passcode
/// </summary>
/// <param name="descriminator"></param>
/// <param name="passcode"></param>
/// <returns></returns>
public static string GeneratePIN(ushort descriminator, uint passcode)
{
descriminator >>= 8;
byte b1 = (byte)((descriminator & 0xF) >> 2);
ushort group1 = (ushort)((descriminator & 0x3) << 14);
group1 |= (ushort)(passcode & 0x3FFF);
uint group2 = passcode >> 14;
string ret = string.Concat(b1, group1.ToString("00000"), group2.ToString("0000"));
return ret.Substring(0, 4) + '-' + ret.Substring(4, 3) + '-' + ret.Substring(7) + Checksum.GenerateVerhoeff(ret);
}

private static byte[] Decode(string str)
{
List<byte> data = new List<byte>();
Expand Down
44 changes: 21 additions & 23 deletions MatterDotNet/Protocol/Cryptography/Crypto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using MatterDotNet.Security;
using System.Buffers.Binary;
using System.Security.Cryptography;
using System.Text;

namespace MatterDotNet.Protocol.Cryptography
{
Expand Down Expand Up @@ -214,29 +215,26 @@ public static (byte[] Public, byte[] Private) GenerateKeypair()
return (pub, p.D!);
}

//public static byte[] Sign(byte[] privateKey, byte[] message)
//{
// ArgumentNullException.ThrowIfNull(privateKey, nameof(privateKey));
// ArgumentNullException.ThrowIfNull(message, nameof(message));
// ECParameters ecp = new ECParameters();
// ecp.Curve = ECCurve.NamedCurves.nistP256;
// ecp.D = privateKey;
// ECDsa ec = ECDsa.Create(ecp);
// return ec.SignData(message, HashAlgorithmName.SHA256);
//}

//public static bool Verify(byte[] publicKey, byte[] message, byte[] signature)
//{
// ArgumentNullException.ThrowIfNull(publicKey, nameof(publicKey));
// ArgumentNullException.ThrowIfNull(message, nameof(message));
// ArgumentNullException.ThrowIfNull(signature, nameof(signature));
// ECParameters ecp = new ECParameters();
// ecp.Curve = ECCurve.NamedCurves.nistP256;
// ecp.Q = new BigIntegerPoint(publicKey).ToECPoint();
// ecp.Validate();
// ECDsa ec = ECDsa.Create(ecp);
// return ec.VerifyData(message, signature, HashAlgorithmName.SHA256);
//}
public static uint GeneratePasscode()
{
StringBuilder pin = new StringBuilder(10);
for (int i = 0; i < 8; i++)
pin.Append((char)Random.Shared.Next(48, 57));
string ret = pin.ToString();
bool allSame = true;
char check = ret[0];
foreach (char c in ret)
{
if (check != c)
{
allSame = false;
break;
}
}
if (allSame || ret == "12345678" || ret == "87654321")
return GeneratePasscode();
return uint.Parse(ret);
}

public static byte[] ECDH(byte[] myPrivateKey, byte[] theirPublicKey)
{
Expand Down
14 changes: 14 additions & 0 deletions MatterDotNet/Protocol/Cryptography/SPAKE2Plus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

using MatterDotNet.Messages.PASE;
using MatterDotNet.Protocol.Payloads;
using MatterDotNet.Security;
using System.Buffers.Binary;
using System.Numerics;
using System.Security.Cryptography;
Expand Down Expand Up @@ -68,6 +69,19 @@ public BigIntegerPoint PAKEValues_Responder(uint passcode, int iterations, byte[
return Y;
}

public byte[] CommissioneePakeInput(uint passcode, int iterations, byte[] salt)
{
byte[] pinBytes = new byte[4];
BinaryPrimitives.WriteUInt32LittleEndian(pinBytes, passcode);
byte[] w = Crypto.PBKDF(pinBytes, salt, iterations, Crypto.W_SIZE_BITS * 2);
BigInteger w0s = new BigInteger(w.AsSpan().Slice(0, Crypto.W_SIZE_BYTES), true, true);
BigInteger w1s = new BigInteger(w.AsSpan().Slice(Crypto.W_SIZE_BYTES, Crypto.W_SIZE_BYTES), true, true);
w0 = w0s % SecP256.n;
w1 = w1s % SecP256.n;
L = SecP256.Multiply(w1, SecP256.GeneratorP);
return SpanUtil.Combine(w0.ToByteArray(true, true), L.ToBytes(false));
}

public (BigIntegerPoint Z, BigIntegerPoint V) InitiatorValidate(BigIntegerPoint pB)
{
if (!SecP256.IsOnCurve(pB))
Expand Down

0 comments on commit 7f38e03

Please sign in to comment.