Skip to content

Commit 25d1749

Browse files
committed
Frame encryption, decryption and privacy
1 parent 1cd15c7 commit 25d1749

File tree

5 files changed

+254
-49
lines changed

5 files changed

+254
-49
lines changed

MatterDotNet/Protocol/Frame.cs

Lines changed: 109 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,51 +10,141 @@
1010
// You should have received a copy of the GNU Affero General Public License
1111
// along with this program. If not, see <http://www.gnu.org/licenses/>.
1212

13+
using MatterDotNet.Security;
14+
using System.Buffers;
1315
using System.Buffers.Binary;
16+
using System.Text;
1417

1518
namespace MatterDotNet.Protocol
1619
{
17-
internal class Frame
20+
internal class Frame : IPayload
1821
{
1922
internal const int MAX_SIZE = 1280;
23+
internal static readonly byte[] PRIVACY_INFO = Encoding.UTF8.GetBytes("PrivacyKey");
2024

21-
public MessageFlags Flags { get; init; }
22-
public ushort SessionID { get; init; }
23-
public SecurityFlags Security { get; init; }
24-
public uint Counter { get; init; }
25-
public ulong SourceNodeID { get; init; }
26-
public ulong DestinationNodeID { get; init; }
27-
public Version1Payload Message { get; init; }
28-
public bool Valid { get; init; }
25+
public MessageFlags Flags { get; set; }
26+
public ushort SessionID { get; set; }
27+
public SecurityFlags Security { get; set; }
28+
public uint Counter { get; set; }
29+
public ulong SourceNodeID { get; set; }
30+
public ulong DestinationNodeID { get; set; }
31+
public Version1Payload Message { get; set; }
32+
public bool Valid { get; set; }
2933

30-
public Frame(ReadOnlySpan<byte> payload)
34+
public bool Serialize(PayloadWriter stream)
35+
{
36+
stream.Write((byte)Flags);
37+
stream.Write(SessionID);
38+
stream.Write((byte)Security);
39+
stream.Write(Counter);
40+
if ((Flags & MessageFlags.SourceNodeID) == MessageFlags.SourceNodeID)
41+
stream.Write(SourceNodeID);
42+
if ((Flags & MessageFlags.DestinationGroupID) == MessageFlags.DestinationNodeID)
43+
stream.Write(DestinationNodeID);
44+
else if ((Flags & MessageFlags.DestinationGroupID) == MessageFlags.DestinationGroupID)
45+
stream.Write(DestinationNodeID);
46+
47+
//Extensions not supported
48+
byte[] temp = ArrayPool<byte>.Shared.Rent(MAX_SIZE);
49+
try
50+
{
51+
PayloadWriter secureStream = new PayloadWriter(temp);
52+
if (!Message.Serialize(secureStream))
53+
return false;
54+
//TODO - Fetch Encryption key
55+
byte[] key = new byte[1];
56+
Span<byte> nonce = new byte[Crypto.NONCE_LENGTH_BYTES];
57+
stream.GetPayload().Slice(3, 5).CopyTo(nonce);
58+
if ((Security & SecurityFlags.GroupSession) == SecurityFlags.GroupSession)
59+
BinaryPrimitives.WriteUInt64LittleEndian(nonce.Slice(5, 8), SourceNodeID);
60+
//TODO: For a CASE session, the Nonce Source Node ID SHALL be determined via the Secure Session Context associated with the Session Identifier.
61+
62+
ReadOnlySpan<byte> mic = Crypto.AEAD_GenerateEncrypt(key, secureStream.GetPayload(), stream.GetPayload(), nonce);
63+
stream.Write(secureStream);
64+
stream.Write(mic);
65+
if ((Security & SecurityFlags.Privacy) == SecurityFlags.Privacy)
66+
{
67+
byte[] privacyKey = Crypto.KDF(key, [], PRIVACY_INFO, Crypto.SYMMETRIC_KEY_LENGTH_BITS);
68+
Span<byte> ptr = stream.GetPayload();
69+
byte[] privacyNonce = new byte[Crypto.NONCE_LENGTH_BYTES];
70+
BinaryPrimitives.WriteUInt16BigEndian(privacyNonce, SessionID);
71+
mic.Slice(5, Crypto.AEAD_MIC_LENGTH_BYTES - 5).CopyTo(privacyNonce.AsSpan().Slice(2));
72+
Crypto.Privacy_Encrypt(privacyKey, ptr.Slice(4, PrivacyBlockSize()), privacyNonce);
73+
}
74+
return true;
75+
}
76+
finally
77+
{
78+
ArrayPool<byte>.Shared.Return(temp);
79+
}
80+
}
81+
82+
public Frame(Span<byte> payload)
3183
{
3284
Flags = (MessageFlags)payload[0];
3385
SessionID = BinaryPrimitives.ReadUInt16LittleEndian(payload.Slice(1, 2));
3486
Security = (SecurityFlags)payload[3];
87+
88+
//TODO - Get Encryption Key
89+
byte[] key = new byte[1];
90+
91+
if ((Security & SecurityFlags.Privacy) == SecurityFlags.Privacy)
92+
{
93+
// Remove Privacy Encryption
94+
byte[] privacyKey = Crypto.KDF(key, [], PRIVACY_INFO, Crypto.SYMMETRIC_KEY_LENGTH_BITS);
95+
byte[] privacyNonce = new byte[Crypto.NONCE_LENGTH_BYTES];
96+
BinaryPrimitives.WriteUInt16BigEndian(privacyNonce, SessionID);
97+
payload.Slice(payload.Length - Crypto.AEAD_MIC_LENGTH_BYTES + 5).CopyTo(privacyNonce.AsSpan().Slice(2));
98+
Crypto.Privacy_Decrypt(privacyKey, payload.Slice(4, PrivacyBlockSize()), privacyNonce).CopyTo(payload.Slice(4, PrivacyBlockSize()));
99+
}
35100
Counter = BinaryPrimitives.ReadUInt32LittleEndian(payload.Slice(4, 4));
101+
Span<byte> slice = payload.Slice(0);
36102
if ((Flags & MessageFlags.SourceNodeID) == MessageFlags.SourceNodeID)
37103
{
38-
SourceNodeID = BinaryPrimitives.ReadUInt64LittleEndian(payload.Slice(8, 8));
39-
payload = payload.Slice(8);
104+
SourceNodeID = BinaryPrimitives.ReadUInt64LittleEndian(slice.Slice(8, 8));
105+
slice = slice.Slice(8);
40106
}
41107
if ((Flags & MessageFlags.DestinationGroupID) == MessageFlags.DestinationNodeID)
42108
{
43-
DestinationNodeID = BinaryPrimitives.ReadUInt64LittleEndian(payload.Slice(8, 8));
44-
payload = payload.Slice(8);
109+
DestinationNodeID = BinaryPrimitives.ReadUInt64LittleEndian(slice.Slice(8, 8));
110+
slice = slice.Slice(8);
45111
}
46112
else if ((Flags & MessageFlags.DestinationGroupID) == MessageFlags.DestinationGroupID)
47113
{
48-
DestinationNodeID = BinaryPrimitives.ReadUInt16LittleEndian(payload.Slice(8, 2));
49-
payload = payload.Slice(2);
114+
DestinationNodeID = BinaryPrimitives.ReadUInt16LittleEndian(slice.Slice(8, 2));
115+
slice = slice.Slice(2);
50116
}
51117
if ((Security & SecurityFlags.MessageExtensions) == SecurityFlags.MessageExtensions)
52118
{
53-
ushort len = BinaryPrimitives.ReadUInt16LittleEndian(payload.Slice(8, 2));
54-
payload = payload.Slice(2 + len);
119+
ushort len = BinaryPrimitives.ReadUInt16LittleEndian(slice.Slice(8, 2));
120+
slice = slice.Slice(2 + len);
55121
}
56122

57-
//TODO - Decryption
123+
Span<byte> nonce = new byte[Crypto.NONCE_LENGTH_BYTES];
124+
nonce[0] = (byte)Security;
125+
BinaryPrimitives.WriteUInt32LittleEndian(nonce.Slice(1, 4), Counter);
126+
if ((Security & SecurityFlags.GroupSession) == SecurityFlags.GroupSession)
127+
BinaryPrimitives.WriteUInt64LittleEndian(nonce.Slice(5, 8), SourceNodeID);
128+
//TODO: For a CASE session, the Nonce Source Node ID SHALL be determined via the Secure Session Context associated with the Session Identifier.
129+
130+
Crypto.AEAD_DecryptVerify(key,
131+
slice.Slice(0, slice.Length - Crypto.AEAD_MIC_LENGTH_BYTES),
132+
slice.Slice(slice.Length - Crypto.AEAD_MIC_LENGTH_BYTES, Crypto.AEAD_MIC_LENGTH_BYTES),
133+
payload.Slice(0, payload.Length - slice.Length),
134+
nonce);
135+
Message = new Version1Payload(payload.Slice(0, slice.Length - Crypto.AEAD_MIC_LENGTH_BYTES));
136+
}
137+
138+
private int PrivacyBlockSize()
139+
{
140+
int ret = 4;
141+
if ((Flags & MessageFlags.SourceNodeID) == MessageFlags.SourceNodeID)
142+
ret += 8;
143+
if ((Flags & MessageFlags.DestinationGroupID) == MessageFlags.DestinationNodeID)
144+
ret += 8;
145+
else if ((Flags & MessageFlags.DestinationGroupID) == MessageFlags.DestinationGroupID)
146+
ret += 2;
147+
return ret;
58148
}
59149
}
60150
}

MatterDotNet/Protocol/IPayload.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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+
namespace MatterDotNet.Protocol
14+
{
15+
internal interface IPayload
16+
{
17+
public bool Serialize(PayloadWriter stream);
18+
}
19+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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 System.Buffers.Binary;
14+
15+
namespace MatterDotNet.Protocol
16+
{
17+
internal class PayloadWriter
18+
{
19+
private readonly Memory<byte> data;
20+
private int pos;
21+
22+
public PayloadWriter(Memory<byte> data, int pos = 0)
23+
{
24+
this.data = data;
25+
this.pos = pos;
26+
}
27+
28+
public PayloadWriter(int capacity)
29+
{
30+
this.data = new byte[capacity];
31+
pos = 0;
32+
}
33+
34+
public void Write(byte value)
35+
{
36+
data.Span[pos++] = value;
37+
}
38+
39+
public void Write(ReadOnlySpan<byte> bytes)
40+
{
41+
bytes.CopyTo(data.Slice(pos).Span);
42+
pos += bytes.Length;
43+
}
44+
45+
public void Write(PayloadWriter payload)
46+
{
47+
payload.CopyTo(data.Slice(pos));
48+
pos += payload.Length;
49+
}
50+
51+
public void Write(Memory<byte> bytes)
52+
{
53+
bytes.CopyTo(data.Slice(pos));
54+
pos += bytes.Length;
55+
}
56+
57+
public void Write(int value)
58+
{
59+
BinaryPrimitives.WriteInt32LittleEndian(data.Span.Slice(pos, 4), value);
60+
pos += 4;
61+
}
62+
63+
public void Write(uint value)
64+
{
65+
BinaryPrimitives.WriteUInt32LittleEndian(data.Span.Slice(pos, 4), value);
66+
pos += 4;
67+
}
68+
69+
public void Write(ulong value)
70+
{
71+
BinaryPrimitives.WriteUInt64LittleEndian(data.Span.Slice(pos, 8), value);
72+
pos += 8;
73+
}
74+
75+
public void Write(short value)
76+
{
77+
BinaryPrimitives.WriteInt16LittleEndian(data.Span.Slice(pos, 2), value);
78+
pos += 2;
79+
}
80+
81+
public void Write(ushort value)
82+
{
83+
BinaryPrimitives.WriteUInt16LittleEndian(data.Span.Slice(pos, 2), value);
84+
pos += 2;
85+
}
86+
87+
public void Seek(int offset)
88+
{
89+
pos += offset;
90+
}
91+
92+
public int Length { get { return pos; } }
93+
94+
public Span<byte> GetPayload()
95+
{
96+
return data.Slice(0, pos).Span;
97+
}
98+
99+
private void CopyTo(Memory<byte> slice)
100+
{
101+
data.CopyTo(slice);
102+
}
103+
}
104+
}

MatterDotNet/Protocol/Version1Payload.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414

1515
namespace MatterDotNet.Protocol
1616
{
17-
internal class Version1Payload
17+
internal class Version1Payload : IPayload
1818
{
1919
public ExchangeFlags Flags { get; init; }
20-
public byte OpCode { get; init; }
20+
public byte OpCode { get; init; } //Depends on Protocol
2121
public ushort ExchangeID { get; init; }
2222
public ushort VendorID { get; init; }
2323
public ProtocolType Protocol { get; init; }
@@ -47,5 +47,10 @@ public Version1Payload(ReadOnlySpan<byte> payload)
4747
}
4848
Payload = payload.Slice(6).ToArray();
4949
}
50+
51+
public bool Serialize(PayloadWriter stream)
52+
{
53+
throw new NotImplementedException();
54+
}
5055
}
5156
}

0 commit comments

Comments
 (0)