Skip to content

Commit 346f5e8

Browse files
committed
Implement Fabrics (Setting up NOCs now works correctly)
1 parent c1aa769 commit 346f5e8

File tree

3 files changed

+282
-80
lines changed

3 files changed

+282
-80
lines changed

MatterDotNet/PKI/Fabric.cs

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
// MatterDotNet Copyright (C) 2024
2+
//
3+
// This program is free software: you can redistribute it and/or modify
4+
// it under the terms of the GNU Affero General Public License as published by
5+
// the Free Software Foundation, either version 3 of the License, or any later version.
6+
// This program is distributed in the hope that it will be useful,
7+
// but WITHOUT ANY WARRANTY, without even the implied warranty of
8+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
9+
// See the GNU Affero General Public License for more details.
10+
// You should have received a copy of the GNU Affero General Public License
11+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
12+
13+
using MatterDotNet.Protocol.Cryptography;
14+
using System.Buffers.Binary;
15+
using System.Formats.Asn1;
16+
using System.Globalization;
17+
using System.Security.Cryptography;
18+
using System.Security.Cryptography.X509Certificates;
19+
20+
namespace MatterDotNet.PKI
21+
{
22+
public class Fabric : OperationalCertificate
23+
{
24+
private static readonly byte[] COMPRESSED_FABRIC_INFO = new byte[] {0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x46, 0x61, 0x62, 0x72, 0x69, 0x63};
25+
Dictionary<ulong, OperationalCertificate> nodes = new Dictionary<ulong, OperationalCertificate>();
26+
27+
public Fabric(ulong rcac, ulong fabricId) : base()
28+
{
29+
if (fabricId == 0)
30+
throw new ArgumentException("Invalid Fabric ID");
31+
this.RCAC = rcac;
32+
this.FabricID = fabricId;
33+
X500DistinguishedNameBuilder builder = new X500DistinguishedNameBuilder();
34+
builder.Add(OID_RCAC.Substring(4), $"{RCAC:X16}", UniversalTagNumber.UTF8String);
35+
builder.Add(OID_FabricID.Substring(4), $"{FabricID:X16}", UniversalTagNumber.UTF8String);
36+
PrivateKey = ECDsa.Create(ECCurve.NamedCurves.nistP256);
37+
CertificateRequest req = new CertificateRequest(builder.Build(), PrivateKey, HashAlgorithmName.SHA256);
38+
req.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, true, 0, true));
39+
req.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyCertSign | X509KeyUsageFlags.CrlSign, true));
40+
X509SubjectKeyIdentifierExtension subjectKeyIdentifier = new X509SubjectKeyIdentifierExtension(SHA1.HashData(new BigIntegerPoint(PrivateKey.ExportParameters(false).Q).ToBytes(false)), false);
41+
req.CertificateExtensions.Add(subjectKeyIdentifier);
42+
req.CertificateExtensions.Add(X509AuthorityKeyIdentifierExtension.CreateFromSubjectKeyIdentifier(subjectKeyIdentifier));
43+
this.cert = req.CreateSelfSigned(DateTime.Now.Subtract(TimeSpan.FromSeconds(30)), DateTime.Now.AddYears(10));
44+
byte[] fabricBytes = new byte[8];
45+
BinaryPrimitives.WriteUInt64BigEndian(fabricBytes, FabricID);
46+
CompressedFabricID = Crypto.KDF(PublicKey.AsSpan(1), fabricBytes, COMPRESSED_FABRIC_INFO, 64);
47+
}
48+
49+
protected Fabric(X509Certificate2 cert, ECDsa key)
50+
{
51+
this.cert = cert;
52+
string[] oids = this.cert.Subject.Split(',', StringSplitOptions.TrimEntries);
53+
foreach (string kvp in oids)
54+
{
55+
string[] parts = kvp.Split('=', 2);
56+
if (parts.Length == 2)
57+
{
58+
switch (parts[0].ToUpper())
59+
{
60+
case OID_RCAC:
61+
if (ulong.TryParse(parts[1], NumberStyles.HexNumber, null, out ulong rcac))
62+
RCAC = rcac;
63+
break;
64+
case OID_FabricID:
65+
if (ulong.TryParse(parts[1], NumberStyles.HexNumber, null, out ulong fabric))
66+
FabricID = fabric;
67+
break;
68+
}
69+
}
70+
}
71+
PrivateKey = key;
72+
byte[] fabricBytes = new byte[8];
73+
BinaryPrimitives.WriteUInt64BigEndian(fabricBytes, FabricID);
74+
CompressedFabricID = Crypto.KDF(PublicKey.AsSpan(1), fabricBytes, COMPRESSED_FABRIC_INFO, 64);
75+
}
76+
77+
public OperationalCertificate Sign(CertificateRequest nocsr)
78+
{
79+
ulong nodeId = (ulong)(0xbaddeed2 + nodes.Count);
80+
X500DistinguishedNameBuilder builder = new X500DistinguishedNameBuilder();
81+
builder.Add(OID_NodeId.Substring(4), $"{nodeId:X16}", UniversalTagNumber.UTF8String);
82+
builder.Add(OID_FabricID.Substring(4), $"{FabricID:X16}", UniversalTagNumber.UTF8String);
83+
CertificateRequest signingCSR = new CertificateRequest(builder.Build(), nocsr.PublicKey, HashAlgorithmName.SHA256);
84+
signingCSR.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, true));
85+
signingCSR.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, true));
86+
OidCollection collection = new OidCollection();
87+
collection.Add(new Oid("1.3.6.1.5.5.7.3.1"));
88+
collection.Add(new Oid("1.3.6.1.5.5.7.3.2"));
89+
signingCSR.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(collection, true));
90+
signingCSR.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(nocsr.PublicKey, false));
91+
signingCSR.CertificateExtensions.Add(cert.Extensions.First(e => e is X509AuthorityKeyIdentifierExtension));
92+
byte[] serial = new byte[20];
93+
Random.Shared.NextBytes(serial);
94+
OperationalCertificate ret = new OperationalCertificate(signingCSR.Create(cert, DateTime.Now.Subtract(TimeSpan.FromSeconds(30)), DateTime.Now.AddYears(1), serial));
95+
nodes.Add(ret.NodeID, ret);
96+
return ret;
97+
}
98+
99+
public OperationalCertificate Sign(byte[] publicKey, byte[] privateKey)
100+
{
101+
ulong nodeId = (ulong)(0xbaddeed2 + nodes.Count);
102+
ECDsa key = ECDsa.Create(new ECParameters() { Curve = ECCurve.NamedCurves.nistP256, D = privateKey, Q = new BigIntegerPoint(publicKey).ToECPoint()});
103+
X500DistinguishedNameBuilder builder = new X500DistinguishedNameBuilder();
104+
builder.Add(OID_NodeId.Substring(4), $"{nodeId:X16}", UniversalTagNumber.UTF8String);
105+
builder.Add(OID_FabricID.Substring(4), $"{FabricID:X16}", UniversalTagNumber.UTF8String);
106+
CertificateRequest signingCSR = new CertificateRequest(builder.Build(), key, HashAlgorithmName.SHA256);
107+
signingCSR.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, true));
108+
signingCSR.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, true));
109+
OidCollection collection = new OidCollection();
110+
collection.Add(new Oid("1.3.6.1.5.5.7.3.1"));
111+
collection.Add(new Oid("1.3.6.1.5.5.7.3.2"));
112+
signingCSR.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(collection, true));
113+
signingCSR.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(key.ExportSubjectPublicKeyInfo(), false));
114+
signingCSR.CertificateExtensions.Add(cert.Extensions.First(e => e is X509AuthorityKeyIdentifierExtension));
115+
byte[] serial = new byte[20];
116+
Random.Shared.NextBytes(serial);
117+
OperationalCertificate ret = new OperationalCertificate(signingCSR.Create(cert, DateTime.Now.Subtract(TimeSpan.FromSeconds(30)), DateTime.Now.AddYears(1), serial));
118+
nodes.Add(ret.NodeID, ret);
119+
return ret;
120+
}
121+
122+
public X509Certificate2Collection Export()
123+
{
124+
X509Certificate2Collection collection = new X509Certificate2Collection();
125+
collection.Add(cert);
126+
foreach (var node in nodes)
127+
collection.Add(node.Value.GetRaw());
128+
return collection;
129+
}
130+
131+
public void Export(string certPath, string keyPath)
132+
{
133+
X509Certificate2Collection collection = Export();
134+
string export = collection.ExportCertificatePems();
135+
File.WriteAllText(certPath, export);
136+
File.WriteAllBytes(keyPath, PrivateKey.ExportPkcs8PrivateKey());
137+
}
138+
139+
public static Fabric Import(string certPath, string keyPath)
140+
{
141+
X509Certificate2Collection collection = new X509Certificate2Collection();
142+
collection.ImportFromPemFile(certPath);
143+
ECDsa key = ECDsa.Create();
144+
key.ImportPkcs8PrivateKey(File.ReadAllBytes(keyPath), out _);
145+
Fabric fabric = new Fabric(collection[0], key);
146+
for (int i = 1; i < collection.Count; i++)
147+
{
148+
OperationalCertificate noc = new OperationalCertificate(collection[i]);
149+
fabric.nodes.TryAdd(noc.NodeID, noc);
150+
}
151+
return fabric;
152+
}
153+
154+
public ECDsa PrivateKey { get; init; }
155+
public byte[] CompressedFabricID { get; init; }
156+
}
157+
}

0 commit comments

Comments
 (0)