Skip to content

Commit

Permalink
Unit tests, Spake+ implementation, EC Crypto Math
Browse files Browse the repository at this point in the history
  • Loading branch information
jdomnitz committed Nov 27, 2024
1 parent 25d1749 commit dca403c
Show file tree
Hide file tree
Showing 19 changed files with 333 additions and 38 deletions.
18 changes: 17 additions & 1 deletion MatterDotNet/PKI/MatterCertificate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,23 @@ public MatterCertificate(byte[] cert)
}
}
}
}
string[] issuerOIDs = this.cert.Issuer.Split(',', StringSplitOptions.TrimEntries);
foreach (string kvp in issuerOIDs)
{
string[] parts = kvp.Split('=', 2);
if (parts.Length == 2)
{
switch (parts[0].ToUpper())
{
case "CN":
IssuerName = parts[1];
break;
}
}
}
}

public string IssuerName { get; set; } = string.Empty;

public string CommonName { get; set; } = string.Empty;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

using System.Security.Cryptography;

namespace MatterDotNet.Security
namespace MatterDotNet.Protocol.Cryptography
{
internal class AesCtr : IDisposable
{
Expand All @@ -24,16 +24,16 @@ internal class AesCtr : IDisposable

public AesCtr(byte[] key, byte[] initialCounter)
{
this.isDisposed = false;
isDisposed = false;

SymmetricAlgorithm aes = Aes.Create();
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.None;

Buffer.BlockCopy(initialCounter, 0, this.counter, 0, BLOCK_SIZE);
Buffer.BlockCopy(initialCounter, 0, counter, 0, BLOCK_SIZE);

var zeroIv = new byte[BLOCK_SIZE];
this.counterEncryptor = aes.CreateEncryptor(key, zeroIv);
counterEncryptor = aes.CreateEncryptor(key, zeroIv);
}

public Span<byte> EncryptDecrypt(Span<byte> input)
Expand All @@ -47,7 +47,7 @@ public Span<byte> EncryptDecrypt(Span<byte> input)

while (numBytes > 0)
{
this.counterEncryptor.TransformBlock(counter, 0, BLOCK_SIZE, block, 0);
counterEncryptor.TransformBlock(counter, 0, BLOCK_SIZE, block, 0);

for (int i = 0; i < BLOCK_SIZE; i++)
{
Expand All @@ -74,7 +74,7 @@ public Span<byte> EncryptDecrypt(Span<byte> input)
public void Dispose()
{
if (!isDisposed)
this.counterEncryptor?.Dispose();
counterEncryptor?.Dispose();

isDisposed = true;
GC.SuppressFinalize(this);
Expand Down
76 changes: 76 additions & 0 deletions MatterDotNet/Protocol/Cryptography/BigIntegerPoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// MatterDotNet Copyright (C) 2024
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY, without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

using MatterDotNet.Security;
using System.Numerics;

namespace MatterDotNet.Protocol.Cryptography
{
public struct BigIntegerPoint
{
private static readonly BigInteger SIGN_BIT = 1 << 256;
public BigIntegerPoint() { }
public BigIntegerPoint(BigInteger x, BigInteger y)
{
X = x;
Y = y;
}
public BigIntegerPoint(byte[] x, byte[] y)
{
X = new BigInteger(x, true, true);
Y = new BigInteger(y, true, true);
}

public BigIntegerPoint(byte[] point)
{
switch (point[0])
{
case 2:
case 3:
X = new BigInteger(point.AsSpan(1), true, true);
Y = BigIntUtil.ModSqrt((BigInteger.ModPow(X, 3, SecP256.p) + (SecP256.a * X) % SecP256.p + SecP256.b) % SecP256.p, SecP256.p);
if (point[0] == 0x3)
Y &= ~SIGN_BIT;
break;
case 4:
int len = (point.Length - 1) / 2;
X = new BigInteger(point.AsSpan(1, len), true, true);
Y = new BigInteger(point.AsSpan(len + 1, len), true, true);
break;
default:
throw new ArgumentException("Invalid Point Type: " + point[0], nameof(point));
}
}

public BigInteger X { get; set; }
public BigInteger Y { get; set; }

public ReadOnlySpan<byte> ToBytes(bool compressed)
{
if (compressed)
{
Span<byte> ret = new byte[33];
ret[0] = ((Y & SIGN_BIT) == SIGN_BIT) ? (byte)0x3 : (byte)0x2;
X.TryWriteBytes(ret.Slice(1), out _, true, true);
return ret;
}
else
{
Span<byte> ret = new byte[65];
ret[0] = 0x4;
X.TryWriteBytes(ret.Slice(1), out _, true, true);
Y.TryWriteBytes(ret.Slice(33), out _, true, true);
return ret;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.


using MatterDotNet.Security;
using System.Buffers.Binary;
using System.Security.Cryptography;

namespace MatterDotNet.Security
namespace MatterDotNet.Protocol.Cryptography
{
public static class Crypto
{
Expand Down Expand Up @@ -57,7 +57,7 @@ public static bool AEAD_DecryptVerify(byte[] key, Span<byte> payload, ReadOnlySp
aes.Decrypt(nonce, payload, mic, payload, additionalData);
return true;
}
catch(AuthenticationTagMismatchException)
catch (AuthenticationTagMismatchException)
{
return false;
}
Expand Down Expand Up @@ -108,27 +108,31 @@ public static byte[] KDF(byte[] inputKey, byte[] salt, byte[] info, int len)
/// <param name="input"></param>
/// <param name="salt"></param>
/// <param name="iterations"></param>
/// <param name="len">Length in bits</param>
/// <param name="klen">Length in bits</param>
/// <returns></returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static byte[] PBKDF(byte[] input, byte[] salt, int iterations, int len)
public static byte[] PBKDF(byte[] input, byte[] salt, int iterations, int klen)
{
if (iterations < 1000 || iterations > 100000)
throw new ArgumentOutOfRangeException(nameof(iterations));
int numBlocks = (int)Math.Ceiling((double)(len / 256));
byte[] ret = new byte[len / 8];
for (int i = 0; i < numBlocks; i++)
//Is this a hard requirement?
//if (iterations < 1000 || iterations > 100000)
// throw new ArgumentOutOfRangeException(nameof(iterations));

int numBlocks = (int)Math.Ceiling((double)klen / 256);
byte[] ret = new byte[klen / 8];
for (int i = 1; i <= numBlocks; i++)
{
byte[] IntI = new byte[4];
BinaryPrimitives.WriteUInt32BigEndian(IntI, (uint)i);
Span<byte> T = new byte[32];
byte[] U = SpanUtil.Combine(salt, IntI);
for (int j = 1; j < iterations; j++)
for (int j = 1; j <= iterations; j++)
{
U = HMACSHA256.HashData(input, U);
T = SpanUtil.XOR(T, U);
}
T.CopyTo(ret.AsSpan(i * 32, 32));
int pos = (i - 1) * 32;
int useableBytes = Math.Min(32, ret.Length - pos);
T.Slice(0, useableBytes).CopyTo(ret.AsSpan(pos, useableBytes));
}
return ret;
}
Expand Down
92 changes: 92 additions & 0 deletions MatterDotNet/Protocol/Cryptography/SecP256.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// MatterDotNet Copyright (C) 2024
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY, without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

using System.Numerics;

namespace MatterDotNet.Protocol.Cryptography
{
public class SecP256
{
public static BigInteger a = new BigInteger(Convert.FromHexString("ffffffff00000001000000000000000000000000fffffffffffffffffffffffc"), true, true);
public static BigInteger b = new BigInteger(Convert.FromHexString("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b"), true, true);
public static BigInteger p = new BigInteger(Convert.FromHexString("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF"), true, true);
public static BigInteger n = new BigInteger(Convert.FromHexString("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551"), true, true);
static BigIntegerPoint ZERO = new BigIntegerPoint();
public static BigIntegerPoint G = new BigIntegerPoint(
Convert.FromHexString("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296"),
Convert.FromHexString("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5")
);

public static BigInteger Invert(BigInteger number, BigInteger modulo)
{
return BigInteger.ModPow(number, modulo - 2, modulo);
}
public static BigIntegerPoint Double(BigIntegerPoint point)
{
if (point.Y == 0)
return ZERO;
BigInteger slope = (3 * BigInteger.ModPow(point.X, 2, p) + a) * Invert(2 * point.Y, p) % p;
BigInteger x = (BigInteger.ModPow(slope, 2, p) - 2 * point.X) % p;
BigInteger y = (slope * (point.X - x) - point.Y) % p;
if (x < 0)
x += p;
if (y < 0)
y += p;
return new BigIntegerPoint(x, y);
}
public static BigIntegerPoint Add(BigIntegerPoint point1, BigIntegerPoint point2)
{
if (point1.Y == 0)
return point2;
if (point2.Y == 0)
return point1;
if (point1.X == point2.X)
{
if (point1.Y == point2.Y)
return Double(point1);
return ZERO;
}
BigInteger slope = (point2.Y - point1.Y) % p * Invert(point2.X - point1.X, p) % p;
BigInteger x = (BigInteger.ModPow(slope, 2, p) - point1.X - point2.X) % p;
BigInteger y = (slope * (point1.X - x) - point1.Y) % p;
if (x < 0)
x += p;
if (y < 0)
y += p;
return new BigIntegerPoint(x, y);
}

public static BigIntegerPoint Multiply(BigInteger k, BigIntegerPoint point)
{
BigIntegerPoint temp = point;
BigIntegerPoint result = ZERO;
while (k > 0)
{
if ((k & 1) == 1)
result = Add(result, temp);
k >>= 1;
temp = Double(temp);
}
return result;
}
public static bool IsOnCurve(BigIntegerPoint point)
{
BigInteger x = point.X % p;
if (x < 0)
x += p;
BigInteger y = point.Y % p;
if (y < 0)
y += p;
return BigInteger.ModPow(y, 2, p) == (BigInteger.ModPow(x, 3, p) + a * x + b) % p;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

namespace MatterDotNet.Protocol
namespace MatterDotNet.Protocol.Messages
{
[Flags]
internal enum ExchangeFlags : byte
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

using MatterDotNet.Security;
using MatterDotNet.Protocol.Cryptography;
using System.Buffers;
using System.Buffers.Binary;
using System.Text;

namespace MatterDotNet.Protocol
namespace MatterDotNet.Protocol.Messages
{
internal class Frame : IPayload
{
Expand Down Expand Up @@ -58,7 +58,7 @@ public bool Serialize(PayloadWriter stream)
if ((Security & SecurityFlags.GroupSession) == SecurityFlags.GroupSession)
BinaryPrimitives.WriteUInt64LittleEndian(nonce.Slice(5, 8), SourceNodeID);
//TODO: For a CASE session, the Nonce Source Node ID SHALL be determined via the Secure Session Context associated with the Session Identifier.

ReadOnlySpan<byte> mic = Crypto.AEAD_GenerateEncrypt(key, secureStream.GetPayload(), stream.GetPayload(), nonce);
stream.Write(secureStream);
stream.Write(mic);
Expand All @@ -84,7 +84,7 @@ public Frame(Span<byte> payload)
Flags = (MessageFlags)payload[0];
SessionID = BinaryPrimitives.ReadUInt16LittleEndian(payload.Slice(1, 2));
Security = (SecurityFlags)payload[3];

//TODO - Get Encryption Key
byte[] key = new byte[1];

Expand Down Expand Up @@ -127,10 +127,10 @@ public Frame(Span<byte> payload)
BinaryPrimitives.WriteUInt64LittleEndian(nonce.Slice(5, 8), SourceNodeID);
//TODO: For a CASE session, the Nonce Source Node ID SHALL be determined via the Secure Session Context associated with the Session Identifier.

Crypto.AEAD_DecryptVerify(key,
slice.Slice(0, slice.Length - Crypto.AEAD_MIC_LENGTH_BYTES),
slice.Slice(slice.Length - Crypto.AEAD_MIC_LENGTH_BYTES, Crypto.AEAD_MIC_LENGTH_BYTES),
payload.Slice(0, payload.Length - slice.Length),
Crypto.AEAD_DecryptVerify(key,
slice.Slice(0, slice.Length - Crypto.AEAD_MIC_LENGTH_BYTES),
slice.Slice(slice.Length - Crypto.AEAD_MIC_LENGTH_BYTES, Crypto.AEAD_MIC_LENGTH_BYTES),
payload.Slice(0, payload.Length - slice.Length),
nonce);
Message = new Version1Payload(payload.Slice(0, slice.Length - Crypto.AEAD_MIC_LENGTH_BYTES));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

namespace MatterDotNet.Protocol
namespace MatterDotNet.Protocol.Messages
{
internal interface IPayload
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

namespace MatterDotNet.Protocol
namespace MatterDotNet.Protocol.Messages
{
[Flags]
internal enum MessageFlags : byte
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

namespace MatterDotNet.Protocol
namespace MatterDotNet.Protocol.Messages
{
internal enum ProtocolType
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

namespace MatterDotNet.Protocol
namespace MatterDotNet.Protocol.Messages
{
[Flags]
internal enum SecurityFlags : byte
Expand Down
Loading

0 comments on commit dca403c

Please sign in to comment.