diff --git a/MatterDotNet/Security/AesCtr.cs b/MatterDotNet/Security/AesCtr.cs new file mode 100644 index 0000000..d134a2b --- /dev/null +++ b/MatterDotNet/Security/AesCtr.cs @@ -0,0 +1,83 @@ +// 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 . + +using System.Security.Cryptography; + +namespace MatterDotNet.Security +{ + internal class AesCtr : IDisposable + { + private const int BLOCK_SIZE = 16; + private readonly byte[] counter = new byte[BLOCK_SIZE]; + + private readonly ICryptoTransform counterEncryptor; + private bool isDisposed; + + public AesCtr(byte[] key, byte[] initialCounter) + { + this.isDisposed = false; + + SymmetricAlgorithm aes = Aes.Create(); + aes.Mode = CipherMode.ECB; + aes.Padding = PaddingMode.None; + + Buffer.BlockCopy(initialCounter, 0, this.counter, 0, BLOCK_SIZE); + + var zeroIv = new byte[BLOCK_SIZE]; + this.counterEncryptor = aes.CreateEncryptor(key, zeroIv); + } + + public Span EncryptDecrypt(Span input) + { + if (isDisposed) + throw new ObjectDisposedException("AES CTR disposed"); + + Span output = new byte[input.Length]; + int offset = 0, numBytes = input.Length; + byte[] block = new byte[BLOCK_SIZE]; + + while (numBytes > 0) + { + this.counterEncryptor.TransformBlock(counter, 0, BLOCK_SIZE, block, 0); + + for (int i = 0; i < BLOCK_SIZE; i++) + { + if (++counter[i] != 0) + break; + } + + if (numBytes <= BLOCK_SIZE) + { + for (int i = 0; i < numBytes; i++) + output[i + offset] = (byte)(input[i + offset] ^ block[i]); + return output; + } + + for (int i = 0; i < BLOCK_SIZE; i++) + output[i + offset] = (byte)(input[i + offset] ^ block[i]); + + numBytes -= BLOCK_SIZE; + offset += BLOCK_SIZE; + } + return output; + } + + public void Dispose() + { + if (!isDisposed) + this.counterEncryptor?.Dispose(); + + isDisposed = true; + GC.SuppressFinalize(this); + } + } +} diff --git a/MatterDotNet/Security/Crypto.cs b/MatterDotNet/Security/Crypto.cs index 3475c5f..04757c3 100644 --- a/MatterDotNet/Security/Crypto.cs +++ b/MatterDotNet/Security/Crypto.cs @@ -11,6 +11,7 @@ // along with this program. If not, see . +using System.Buffers.Binary; using System.Security.Cryptography; namespace MatterDotNet.Security @@ -56,10 +57,93 @@ public static bool Crypto_AEAD_DecryptVerify(byte[] key, Span payload, Rea } } - public static ReadOnlySpan Crypto_Privacy_Encrypt(byte[] key, Span message, byte[] nonce) + /// + /// Performs the decryption of message using the key and a nonce; the output is the decryption of message + /// + /// + /// + /// + /// + public static Span Crypto_Privacy_Decrypt(byte[] key, Span message, byte[] nonce) { - // TODO - throw new NotImplementedException(); + AesCtr ctr = new AesCtr(key, nonce); + return ctr.EncryptDecrypt(message); + } + + /// + /// Performs the Encryption of message using the key and a nonce; the output is the decryption of message + /// + /// + /// + /// + /// + public static Span Crypto_Privacy_Encrypt(byte[] key, Span message, byte[] nonce) + { + AesCtr ctr = new AesCtr(key, nonce); + return ctr.EncryptDecrypt(message); + } + + /// + /// Returns the key of len bits derived from inputKey using the salt and the info; len SHALL be a multiple of 8. + /// + /// + /// + /// + /// + /// + public static Span Crypto_KDF(byte[] inputKey, byte[] salt, byte[] info, int len) + { + if (len > 256) + throw new ArgumentException("This wasn't designed for multiple blocks"); + int L = len / 8; + const int HashLen = 32; + byte N = (byte)Math.Ceiling((double)(L / HashLen)); + byte[] PRK = HKDF_Extract(salt, inputKey); + return HMAC_Hash(N, PRK, info); + } + + private static byte[] HMAC_Hash(byte T, byte[] PRK, byte[] info) + { + if (T == 0) + return []; + byte[] bytes = SpanUtil.Combine(HMAC_Hash((byte)(T - 1), PRK, info), info, [T]); + return HMACSHA256.HashData(PRK, bytes); + } + + public static byte[] HKDF_Extract(byte[] salt, byte[] IKM) + { + return HMACSHA256.HashData(salt, IKM); + } + + /// + /// Password-based key derivation function to compute a derived key from a cryptographically weak password + /// + /// + /// + /// + /// Length in bits + /// + /// + public static byte[] Crypto_PBKDF(byte[] input, byte[] salt, int iterations, int len) + { + 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++) + { + byte[] IntI = new byte[4]; + BinaryPrimitives.WriteUInt32BigEndian(IntI, (uint)i); + Span T = new byte[32]; + byte[] U = SpanUtil.Combine(salt, IntI); + for (int j = 1; j < iterations; j++) + { + U = HMACSHA256.HashData(input, U); + T = SpanUtil.XOR(T, U); + } + T.CopyTo(ret.AsSpan(i * 32, 32)); + } + return ret; } } } diff --git a/MatterDotNet/Security/MemoryUtil.cs b/MatterDotNet/Security/SpanUtil.cs similarity index 70% rename from MatterDotNet/Security/MemoryUtil.cs rename to MatterDotNet/Security/SpanUtil.cs index 058bfc8..4274b4d 100644 --- a/MatterDotNet/Security/MemoryUtil.cs +++ b/MatterDotNet/Security/SpanUtil.cs @@ -15,8 +15,20 @@ namespace MatterDotNet.Security { - internal static class MemoryUtil + internal static class SpanUtil { + public static byte[] Combine(params byte[][] arrays) + { + byte[] rv = new byte[arrays.Sum(a => a.Length)]; + int offset = 0; + foreach (byte[] array in arrays) + { + System.Buffer.BlockCopy(array, 0, rv, offset, array.Length); + offset += array.Length; + } + return rv; + } + public static Memory Fill(byte val, int count) { Memory ret = new byte[count]; @@ -25,38 +37,38 @@ public static Memory Fill(byte val, int count) return ret; } - public static Memory PadZeros(Memory val, int count) + public static Span PadZeros(Span val, int count) { if (count <= 0) return val; Memory ret = new byte[val.Length + count]; if (val.Length > 0) - val.CopyTo(ret); - return ret; + val.CopyTo(ret.Span); + return ret.Span; } - public static Memory LeftShift1(Memory array) + public static Span LeftShift1(Span array) { Memory ret = new byte[array.Length]; for (int i = 0; i < ret.Length - 1; i++) - ret.Span[i] = (byte)(array.Span[i] << 1 | (array.Span[i + 1] >> 7)); - ret.Span[ret.Length - 1] = (byte)(array.Span[array.Length - 1] << 1); - return ret; + ret.Span[i] = (byte)(array[i] << 1 | (array[i + 1] >> 7)); + ret.Span[ret.Length - 1] = (byte)(array[array.Length - 1] << 1); + return ret.Span; } - public static Memory XOR(Memory a, Memory b) + public static Span XOR(Span a, Span b) { if (a.Length != b.Length) throw new ArgumentException("Invalid Byte Array Sizes"); Memory ret = new byte[a.Length]; for (int i = 0; i < a.Length; i++) - ret.Span[i] = (byte)(a.Span[i] ^ b.Span[i]); - return ret; + ret.Span[i] = (byte)(a[i] ^ b[i]); + return ret.Span; } - public static byte XOR(Memory a, byte start) + public static byte XOR(Span a, byte start) { - foreach (byte b in a.Span) + foreach (byte b in a) start ^= b; return start; } @@ -71,11 +83,6 @@ public static void Increment(Span mem) } } - public static string Print(Memory mem) - { - return Print(mem.Span); - } - public static string Print(ReadOnlySpan span) { StringBuilder ret = new StringBuilder(span.Length * 3); @@ -88,7 +95,7 @@ public static string Print(ReadOnlySpan span) return ret.ToString(); } - public static Memory From(string hexString) + public static Span From(string hexString) { if (hexString.Length % 2 != 0) throw new ArgumentException("Not a hex string"); @@ -97,7 +104,7 @@ public static Memory From(string hexString) for (int index = 0; index < data.Length; index++) data.Span[index] = byte.Parse(hexString.AsSpan(index * 2, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); - return data; + return data.Span; } } } diff --git a/README.md b/README.md index df960a5..fe2ccf5 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ +[![Build](https://github.com/SmartHomeOS/MatterDotNet/actions/workflows/dotnet.yml/badge.svg)](https://github.com/SmartHomeOS/MatterDotNet/actions/workflows/dotnet.yml) # MatterDotNet **Coming Soon** - New implementation in progress #### What's Working -* Nothing Yet +* Some Crypto functions, message framing and QR/PIN parsing #### Coming Soon * Matter 1.3 Standard Implementation