diff --git a/src/DtlsSrtpClientSample/Program.cs b/src/DtlsSrtpClientSample/Program.cs index 1c17e5e..8d3a843 100644 --- a/src/DtlsSrtpClientSample/Program.cs +++ b/src/DtlsSrtpClientSample/Program.cs @@ -19,20 +19,20 @@ client.OnSessionStarted += (sender, e) => { isSrtpSessionRunning = true; - socket.ReceiveTimeout = 1000; + socket.ReceiveTimeout = 1000; var context = e.Context; var srtpSession = Task.Run(async () => { var protectionProfile = context.DecodeRtpContext.ProtectionProfile; Console.WriteLine($"SRTP cipher: {protectionProfile.Cipher}, auth: {protectionProfile.Auth}"); - + byte[] receiveBuffer = new byte[2048]; int timeoutCounter = 0; while (!isShutdown) { - int receivedLen = 0; + int receivedLen = 0; try { receivedLen = socket.Receive(receiveBuffer); @@ -40,24 +40,29 @@ } catch (SocketException ex) { - if(ex.SocketErrorCode == SocketError.TimedOut) + if (ex.SocketErrorCode == SocketError.TimedOut) { timeoutCounter++; } } - + if (receivedLen != 0) { Console.WriteLine($"SRTP: {Convert.ToHexString(receiveBuffer.Take(receivedLen).ToArray())}"); - if (context.UnprotectRtp(receiveBuffer, receivedLen, out int length) == 0) + try { + var length = context.UnprotectRtp(receiveBuffer, receiveBuffer); byte[] rtp = receiveBuffer.Take(length).ToArray(); Console.WriteLine($"RTP: {Convert.ToHexString(rtp)}"); } + catch (System.Security.Cryptography.CryptographicException ex) + { + Console.WriteLine($"SRTP error: {ex.HResult}"); + } } - if(timeoutCounter > 30) + if (timeoutCounter > 30) { isSrtpSessionRunning = false; break; @@ -74,7 +79,7 @@ { Console.WriteLine($"DTLS connected"); - while(isSrtpSessionRunning) + while (isSrtpSessionRunning) { Thread.Sleep(1000); } diff --git a/src/DtlsSrtpServerSample/Program.cs b/src/DtlsSrtpServerSample/Program.cs index 309bac1..9cb3798 100644 --- a/src/DtlsSrtpServerSample/Program.cs +++ b/src/DtlsSrtpServerSample/Program.cs @@ -52,12 +52,17 @@ Buffer.BlockCopy(rtpPacket, 0, rtpBuffer, 0, rtpPacket.Length); Console.WriteLine($"RTP: {Convert.ToHexString(rtpPacket)}"); - if (context.ProtectRtp(rtpBuffer, rtpPacket.Length, out int length) == 0) + try { + var length = context.ProtectRtp(rtpBuffer, rtpBuffer); byte[] srtp = rtpBuffer.Take(length).ToArray(); Console.WriteLine($"SRTP: {Convert.ToHexString(srtp)}"); listenSocket.SendTo(srtp, remoteEndpoint); } + catch (System.Security.Cryptography.CryptographicException ex) + { + Console.WriteLine($"SRTP error: {ex.HResult}"); + } sequenceNumber++; Thread.Sleep(1000); diff --git a/src/SharpSRTP.Tests/Convert.cs b/src/SharpSRTP.Tests/Convert.cs new file mode 100644 index 0000000..3a48fff --- /dev/null +++ b/src/SharpSRTP.Tests/Convert.cs @@ -0,0 +1,52 @@ +#if !NET5_0_OR_GREATER +using System; + +namespace SharpSRTP.Tests +{ + internal static partial class Convert + { + internal static byte[] FromHexString(string s) + { + if (s == null) throw new ArgumentNullException(nameof(s)); + if ((s.Length & 1) != 0) throw new FormatException("Hex string must have even length."); + + int len = s.Length >> 1; + var bytes = new byte[len]; + for (int i = 0, bi = 0; bi < len; i += 2, bi++) + { + int hi = ParseNibble(s[i]); + int lo = ParseNibble(s[i + 1]); + bytes[bi] = (byte)((hi << 4) | lo); + } + return bytes; + } + + internal static string ToHexString(ReadOnlySpan bytes) + { + if (bytes == null) throw new ArgumentNullException(nameof(bytes)); + char[] c = new char[bytes.Length * 2]; + int ci = 0; + for (int i = 0; i < bytes.Length; i++) + { + byte b = bytes[i]; + c[ci++] = NibbleToHex((b >> 4) & 0xF); + c[ci++] = NibbleToHex(b & 0xF); + } + return new string(c); + } + + private static int ParseNibble(char c) + { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + throw new FormatException("Invalid hex character."); + } + + private static char NibbleToHex(int value) + { + return (char)(value < 10 ? ('0' + value) : ('A' + (value - 10))); + } + } +} +#endif diff --git a/src/SharpSRTP.Tests/SharpSRTP.Tests.csproj b/src/SharpSRTP.Tests/SharpSRTP.Tests.csproj index f819fd8..9b47cc0 100644 --- a/src/SharpSRTP.Tests/SharpSRTP.Tests.csproj +++ b/src/SharpSRTP.Tests/SharpSRTP.Tests.csproj @@ -1,7 +1,7 @@  - net10.0 + net462;net8.0 latest disable disable diff --git a/src/SharpSRTP.Tests/TestProtectUnprotect.cs b/src/SharpSRTP.Tests/TestProtectUnprotect.cs index c033052..a2cfa7a 100644 --- a/src/SharpSRTP.Tests/TestProtectUnprotect.cs +++ b/src/SharpSRTP.Tests/TestProtectUnprotect.cs @@ -22,33 +22,65 @@ using SharpSRTP.SRTP; using System; using System.Data; -using System.Linq; namespace SharpSRTP.Tests { [TestClass] public class TestProtectUnprotect { - [DataRow("n7z9GgmnJ4Bc2hC0prEf8KFCKv8EyG+4WrUOg7oi", "80e1000103cb6bc84218a6a3001006c801123318f6882d06086141a9c44dfbfb7e9f1cf997eb257b77c732bcf779ae750b6493aff001815dcfc814a4fb96089153b0becc4e091f2632584ee88fc01701a0dc5111f3d7b201b0a5496972275d00e503d921370ecbdebc5ac4e54572e59ca65c29ce246b438659df04633d5d0452da1b9ce729670a616b4f5050df2c7de897ca16f5762d6df93da0134d6c3d2fedb178be2fbbfa3c702673c231d5af4f1c9b2fa791a19ef3a23aee2325dc633f19ebde33f0eeec8351cfa62bbbf9339d6b7e322ba3bb5e1d31a3956475cf450984d4a274d2583d1b80e0", "80e1000103cb6bc84218a6a3cf77c0bc864411afc82ac978b1087b699bf51892b46152bcf95963dbc69f7efbb776c79a0daa3e2e7ae8a3ceda005fb29b068d099d0b0a103ae0bc9ae62b55c0c8dca25583478377f2bb310f0371a2ada32a119e96a84c796b9376a093409e21a7b16bafedbc4fffadabe5f770e895ec36b8de959819aac706aba8788ba9da2fd3f58bd43796fd51124e92117d98575cc82d302741a8be3c9234bafeb42d2c52ebd9e6edfcb1e7e01fb40131758c9d1181525b1c02e35cc34b46e0aaf1df4dc931036aaf4f9044b47058d22008395596e8000b4a7def6aa97a989e76f0c88ba939313459373a6f")] + public static System.Collections.Generic.IEnumerable Test_Srtp_Protect_Unprotect_TestData() + { + var masterKeySalt = "n7z9GgmnJ4Bc2hC0prEf8KFCKv8EyG+4WrUOg7oi"; + var rtp = "80e1000103cb6bc84218a6a3001006c801123318f6882d06086141a9c44dfbfb7e9f1cf997eb257b77c732bcf779ae750b6493aff001815dcfc814a4fb96089153b0becc4e091f2632584ee88fc01701a0dc5111f3d7b201b0a5496972275d00e503d921370ecbdebc5ac4e54572e59ca65c29ce246b438659df04633d5d0452da1b9ce729670a616b4f5050df2c7de897ca16f5762d6df93da0134d6c3d2fedb178be2fbbfa3c702673c231d5af4f1c9b2fa791a19ef3a23aee2325dc633f19ebde33f0eeec8351cfa62bbbf9339d6b7e322ba3bb5e1d31a3956475cf450984d4a274d2583d1b80e0"; + var srtp = "80e1000103cb6bc84218a6a3cf77c0bc864411afc82ac978b1087b699bf51892b46152bcf95963dbc69f7efbb776c79a0daa3e2e7ae8a3ceda005fb29b068d099d0b0a103ae0bc9ae62b55c0c8dca25583478377f2bb310f0371a2ada32a119e96a84c796b9376a093409e21a7b16bafedbc4fffadabe5f770e895ec36b8de959819aac706aba8788ba9da2fd3f58bd43796fd51124e92117d98575cc82d302741a8be3c9234bafeb42d2c52ebd9e6edfcb1e7e01fb40131758c9d1181525b1c02e35cc34b46e0aaf1df4dc931036aaf4f9044b47058d22008395596e8000b4a7def6aa97a989e76f0c88ba939313459373a6f"; + + yield return new object[] { masterKeySalt, rtp, srtp, true }; + yield return new object[] { masterKeySalt, rtp, srtp, false }; + } + + [DynamicData(nameof(Test_Srtp_Protect_Unprotect_TestData))] [TestMethod] - public void Test_Srtp_Protect_Unprotect(string masterKeySalt, string rtp, string srtp) + public void Test_Srtp_Protect_Unprotect(string masterKeySalt, string rtp, string srtp, bool useSharedBuffer) { - byte[] masterKeySaltBytes = Convert.FromBase64String(masterKeySalt); + byte[] masterKeySaltBytes = System.Convert.FromBase64String(masterKeySalt); byte[] rtpBytes = Convert.FromHexString(rtp); - byte[] srtpBytes = rtpBytes.Concat(new byte[10]).ToArray(); + byte[] srtpProtectedBytes = new byte[rtpBytes.Length + 10]; + ReadOnlySpan srtpUnprotectedBytes; + + if (useSharedBuffer) + { + srtpUnprotectedBytes = srtpProtectedBytes.AsSpan(0, rtpBytes.Length); + rtpBytes.AsSpan().CopyTo(srtpProtectedBytes); + } + else + { + srtpUnprotectedBytes = rtpBytes; + } byte[] MKI = null; var keys = SrtpProtocol.CreateMasterKeys(SrtpCryptoSuites.AES_CM_128_HMAC_SHA1_80, MKI, masterKeySaltBytes); var context = SrtpProtocol.CreateSrtpSessionContext(keys); - int ret = context.ProtectRtp(srtpBytes, rtpBytes.Length, out int len); - - string srtpString = Convert.ToHexString(srtpBytes.Take(len).ToArray()).ToLowerInvariant(); - Assert.AreEqual(srtp, srtpString); - context.UnprotectRtp(srtpBytes, srtpBytes.Length, out int olen); + int len = context.ProtectRtp(srtpProtectedBytes.AsSpan(), srtpUnprotectedBytes); + var expectedSrtpBytes = Convert.FromHexString(srtp); + var actualSrtpBytes = srtpProtectedBytes.AsSpan(0, len).ToArray(); + Assert.IsTrue(expectedSrtpBytes.SequenceEqual(actualSrtpBytes), + $"SRTP protect mismatch.\nExpected: {BitConverter.ToString(expectedSrtpBytes)}\nActual: {BitConverter.ToString(actualSrtpBytes)}"); - string rtpString = Convert.ToHexString(srtpBytes.Take(olen).ToArray()).ToLowerInvariant(); - Assert.AreEqual(rtp, rtpString); + var decodeContext = SrtpProtocol.CreateSrtpSessionContext(keys); + Span srtpUnprotectedOut; + if (useSharedBuffer) + { + srtpUnprotectedOut = srtpProtectedBytes; + } + else + { + srtpUnprotectedOut = new byte[srtpProtectedBytes.Length]; + } + int olen = decodeContext.UnprotectRtp(srtpUnprotectedOut, srtpProtectedBytes.AsSpan(0, len)); + var actualUnprotected = srtpUnprotectedOut.Slice(0, olen).ToArray(); + Assert.IsTrue(rtpBytes.SequenceEqual(actualUnprotected), + $"SRTP unprotect mismatch.\nExpected: {BitConverter.ToString(rtpBytes)}\nActual: {BitConverter.ToString(actualUnprotected)}"); } } } diff --git a/src/SharpSRTP.Tests/TestRFC3711.cs b/src/SharpSRTP.Tests/TestRFC3711.cs index d0712c8..d63c4da 100644 --- a/src/SharpSRTP.Tests/TestRFC3711.cs +++ b/src/SharpSRTP.Tests/TestRFC3711.cs @@ -28,7 +28,6 @@ using SharpSRTP.SRTP.Encryption; using SharpSRTP.SRTP.Readers; using System; -using System.Linq; namespace SharpSRTP.Tests { @@ -37,7 +36,7 @@ namespace SharpSRTP.Tests /// [TestClass] public sealed class TestRFC3711 - { + { [DataRow("2B7E151628AED2A6ABF7158809CF4F3C", "F0F1F2F3F4F5F6F7F8F9FAFBFCFD0000", "E03EAD0935C95E80E166B16DD92B4EB4", 0u, (ushort)0, 0u, 0)] [DataRow("2B7E151628AED2A6ABF7158809CF4F3C", "F0F1F2F3F4F5F6F7F8F9FAFBFCFD0000", "D23513162B02D0F72A43A2FE4A5F97AB", 0u, (ushort)0, 0u, 1)] [DataRow("2B7E151628AED2A6ABF7158809CF4F3C", "F0F1F2F3F4F5F6F7F8F9FAFBFCFD0000", "41E95B3BB0A2E8DD477901E4FCA894C0", 0u, (ushort)0, 0u, 2)] @@ -52,17 +51,19 @@ public void Test_Encrypt_AESCM(string sk_e, string sk_s, string keystream, uint ulong index = SrtpContext.GenerateRtpIndex(roc, sequenceNumber); AesEngine aes = new AesEngine(); - byte[] iv = CTR.GenerateMessageKeyIV(k_s, ssrc, index); + Span iv = stackalloc byte[CTR.BLOCK_SIZE]; + CTR.GenerateMessageKeyIV(iv, k_s, ssrc, index); aes.Init(true, new Org.BouncyCastle.Crypto.Parameters.KeyParameter(k_e)); iv[14] = (byte)((i >> 8) & 0xff); iv[15] = (byte)(i & 0xff); byte[] cipher = new byte[k_s.Length]; - aes.ProcessBlock(iv, 0, cipher, 0); + aes.ProcessBlock(iv.ToArray(), 0, cipher, 0); - string payloadString = Convert.ToHexString(cipher); - Assert.AreEqual(keystream, payloadString); + var expectedKeystream = Convert.FromHexString(keystream); + Assert.IsTrue(cipher.AsSpan().SequenceEqual(expectedKeystream), + $"AESCM keystream mismatch.\nExpected: {BitConverter.ToString(expectedKeystream)}\nActual: {BitConverter.ToString(cipher)}"); } [DataRow(ExtendedSrtpProtectionProfile.SRTP_AES128_CM_HMAC_SHA1_80, SrtpContextType.RTP, "72db0646db1e98b02a0056ef700c6cb2", "45111565691aa9d235afee475b12", "57dca73c834ac313d7fdbe58f4df5d93", "a0482b8914db8219f0ec4e54c2f32c4f854eeacf", "8666bc4b1ec16deb3e28fed64da3")] @@ -77,13 +78,12 @@ public void Test_Session_Keys_AESCM(int dtlsProtectionProfile, SrtpContextType s var protectionProfile = DtlsSrtpProtocol.DtlsProtectionProfiles[dtlsProtectionProfile]; var context = new SrtpContext(strpContextType, protectionProfile, masterKeyBytes, masterSaltBytes, null); - string sgk_e = Convert.ToHexString(context.K_e).ToLowerInvariant(); - string sgk_a = Convert.ToHexString(context.K_a).ToLowerInvariant(); - string sgk_s = Convert.ToHexString(context.K_s).ToLowerInvariant(); - - Assert.AreEqual(sk_e, sgk_e); - Assert.AreEqual(sk_a, sgk_a); - Assert.AreEqual(sk_s, sgk_s); + var expected_e = Convert.FromHexString(sk_e); + var expected_a = Convert.FromHexString(sk_a); + var expected_s = Convert.FromHexString(sk_s); + Assert.IsTrue(context.K_e.Span.SequenceEqual(expected_e), "K_e mismatch"); + Assert.IsTrue(context.K_a.Span.SequenceEqual(expected_a), "K_a mismatch"); + Assert.IsTrue(context.K_s.Span.SequenceEqual(expected_s), "K_s mismatch"); } [DataRow(ExtendedSrtpProtectionProfile.SRTP_AES128_CM_HMAC_SHA1_80, SrtpContextType.RTP, "503073e919570c4be07872a22f6f682e", "f635137879c3dbf0d3b422aee13b", "45bd0c56f87f8013721257c9322c1fbf", "fc958d05c608be655f16c1f2b423223de9a9cdc2", "c5029308e55be19e3911ce68a1af", "8061eb7f8b1f6f186dc9803d67640028acb402802dc8", "8061eb7f8b1f6f186dc9803df7be778106050cb06813102c2ff82d9f51f2c52c")] @@ -100,13 +100,12 @@ public void Test_Session_Keys_AESCM_RTP(int dtlsProtectionProfile, SrtpContextTy var protectionProfile = DtlsSrtpProtocol.DtlsProtectionProfiles[dtlsProtectionProfile]; var context = new SrtpContext(strpContextType, protectionProfile, masterKeyBytes, masterSaltBytes, null); - string sgk_e = Convert.ToHexString(context.K_e).ToLowerInvariant(); - string sgk_a = Convert.ToHexString(context.K_a).ToLowerInvariant(); - string sgk_s = Convert.ToHexString(context.K_s).ToLowerInvariant(); - - Assert.AreEqual(sk_e, sgk_e); - Assert.AreEqual(sk_a, sgk_a); - Assert.AreEqual(sk_s, sgk_s); + var expected_e = Convert.FromHexString(sk_e); + var expected_a = Convert.FromHexString(sk_a); + var expected_s = Convert.FromHexString(sk_s); + Assert.IsTrue(context.K_e.Span.SequenceEqual(expected_e), "K_e mismatch"); + Assert.IsTrue(context.K_a.Span.SequenceEqual(expected_a), "K_a mismatch"); + Assert.IsTrue(context.K_s.Span.SequenceEqual(expected_s), "K_s mismatch"); byte[] payloadRaw = Convert.FromHexString(rtp); int length = payloadRaw.Length; @@ -119,15 +118,16 @@ public void Test_Session_Keys_AESCM_RTP(int dtlsProtectionProfile, SrtpContextTy uint roc = 0; ulong index = SrtpContext.GenerateRtpIndex(roc, sequenceNumber); - byte[] iv = CTR.GenerateMessageKeyIV(context.K_s, ssrc, index); + Span iv = stackalloc byte[CTR.BLOCK_SIZE]; + CTR.GenerateMessageKeyIV(iv, context.K_s.Span, ssrc, index); var aes = new AesEngine(); - aes.Init(true, new Org.BouncyCastle.Crypto.Parameters.KeyParameter(context.K_e)); + aes.Init(true, new Org.BouncyCastle.Crypto.Parameters.KeyParameter(context.K_e.ToArray())); var hmac = new HMac(new Sha1Digest()); - hmac.Init(new Org.BouncyCastle.Crypto.Parameters.KeyParameter(context.K_a)); + hmac.Init(new Org.BouncyCastle.Crypto.Parameters.KeyParameter(context.K_a.ToArray())); - CTR.Encrypt(aes, payload, offset, length, iv); + CTR.Encrypt(payload.AsSpan(offset, length - offset), aes, payload.AsSpan(offset, length - offset), iv); payload[length + 0] = (byte)(roc >> 24); payload[length + 1] = (byte)(roc >> 16); @@ -135,12 +135,14 @@ public void Test_Session_Keys_AESCM_RTP(int dtlsProtectionProfile, SrtpContextTy payload[length + 3] = (byte)roc; int n_tag = protectionProfile.AuthTagLength >> 3; - byte[] auth = HMAC.GenerateAuthTag(hmac, payload, 0, length + 4); + var auth = new byte[hmac.GetMacSize()]; + HMAC.GenerateAuthTag(hmac, payload.AsSpan(0, length + 4), auth); System.Buffer.BlockCopy(auth, 0, payload, length, n_tag); // we don't append ROC in SRTP - var result = payload.Take(length + n_tag).ToArray(); + var result = payload.AsSpan(0, length + n_tag); - string srtpResult = Convert.ToHexString(result).ToLowerInvariant(); - Assert.AreEqual(srtp, srtpResult); + var expectedSrtpBytes = Convert.FromHexString(srtp); + Assert.IsTrue(result.SequenceEqual(expectedSrtpBytes), + $"SRTP RTP mismatch.\nExpected: {BitConverter.ToString(expectedSrtpBytes)}\nActual: {BitConverter.ToString(result.ToArray())}"); } [DataRow(ExtendedSrtpProtectionProfile.SRTP_AES128_CM_HMAC_SHA1_80, SrtpContextType.RTCP, "7c70686d319cdb521a6b71d883f9ce08", "68d48dc36f7f29c860eae2e3be5b", "36db45fe23d42378ffc1df8f8241f26b", "a5a171c0a334826513056abe124e22417fa21a86", "fad326f5f9a17147157f82602566", 0u, "80c8000667160dd7ecf549a36fdf3b6499f322dc00000083000288e781ca000267160dd701000000", "80c8000667160dd794c8324dbed8d36fe8e0b6afa47cc7e05f436ec763e8a1e9081aefa22e084c1d800000002af3b0da27475b47bee1")] @@ -155,13 +157,12 @@ public void Test_Session_Keys_AESCM_RTCP(int dtlsProtectionProfile, SrtpContextT var protectionProfile = DtlsSrtpProtocol.DtlsProtectionProfiles[dtlsProtectionProfile]; var context = new SrtpContext(strpContextType, protectionProfile, masterKeyBytes, masterSaltBytes, null); - string sgk_e = Convert.ToHexString(context.K_e); - string sgk_a = Convert.ToHexString(context.K_a); - string sgk_s = Convert.ToHexString(context.K_s); - - Assert.AreEqual(sk_e.ToUpperInvariant(), sgk_e); - Assert.AreEqual(sk_a.ToUpperInvariant(), sgk_a); - Assert.AreEqual(sk_s.ToUpperInvariant(), sgk_s); + var expected_e = Convert.FromHexString(sk_e); + var expected_a = Convert.FromHexString(sk_a); + var expected_s = Convert.FromHexString(sk_s); + Assert.IsTrue(context.K_e.Span.SequenceEqual(expected_e), "K_e mismatch"); + Assert.IsTrue(context.K_a.Span.SequenceEqual(expected_a), "K_a mismatch"); + Assert.IsTrue(context.K_s.Span.SequenceEqual(expected_s), "K_s mismatch"); byte[] payloadRaw = Convert.FromHexString(rtcp); int length = payloadRaw.Length; @@ -172,15 +173,16 @@ public void Test_Session_Keys_AESCM_RTCP(int dtlsProtectionProfile, SrtpContextT uint index = S_l | SrtpContext.E_FLAG; int offset = RtcpReader.GetHeaderLen(); - byte[] iv = CTR.GenerateMessageKeyIV(context.K_s, ssrc, S_l); + Span iv = stackalloc byte[CTR.BLOCK_SIZE]; + CTR.GenerateMessageKeyIV(iv, context.K_s.Span, ssrc, S_l); var aes = new AesEngine(); - aes.Init(true, new Org.BouncyCastle.Crypto.Parameters.KeyParameter(context.K_e)); + aes.Init(true, new Org.BouncyCastle.Crypto.Parameters.KeyParameter(context.K_e.ToArray())); var hmac = new HMac(new Sha1Digest()); - hmac.Init(new Org.BouncyCastle.Crypto.Parameters.KeyParameter(context.K_a)); + hmac.Init(new Org.BouncyCastle.Crypto.Parameters.KeyParameter(context.K_a.ToArray())); - CTR.Encrypt(aes, payload, offset, length, iv); + CTR.Encrypt(payload.AsSpan(offset, length - offset), aes, payload.AsSpan(offset, length - offset), iv); payload[length + 0] = (byte)(index >> 24); payload[length + 1] = (byte)(index >> 16); @@ -188,12 +190,13 @@ public void Test_Session_Keys_AESCM_RTCP(int dtlsProtectionProfile, SrtpContextT payload[length + 3] = (byte)index; int n_tag = protectionProfile.AuthTagLength >> 3; - byte[] auth = HMAC.GenerateAuthTag(hmac, payload, 0, length + 4); + var auth = new byte[hmac.GetMacSize()]; + HMAC.GenerateAuthTag(hmac, payload.AsSpan(0, length + 4), auth); System.Buffer.BlockCopy(auth, 0, payload, length + 4, n_tag); // we don't append ROC in SRTP - var result = payload.Take(length + 4 + n_tag).ToArray(); - - string srtpResult = Convert.ToHexString(result).ToLowerInvariant(); - Assert.AreEqual(expectedSrtcp, srtpResult); + var result = payload.AsSpan(0, length + 4 + n_tag); + var expectedSrtcpBytes = Convert.FromHexString(expectedSrtcp); + Assert.IsTrue(result.SequenceEqual(expectedSrtcpBytes), + $"SRTP RTCP mismatch.\nExpected: {BitConverter.ToString(expectedSrtcpBytes)}\nActual: {BitConverter.ToString(result.ToArray())}"); } [DataRow("806e5cba50681de55c62159970736575646f72616e646f6d6e65737320697320746865206e6578742062657374207468696e67", 0xd462564a, "234829008467be186c3de14aae72d62c", "32f2870d", "806e5cba50681de55c621599019ce7a26e7854014a6366aa95d4eefd1ad4172a14f9faf455b7f1d4b62bd08f562c0eef7c4802")] @@ -210,13 +213,15 @@ public void Test_AESF8(string rtp, uint roc, string sk_e, string sk_s, string ex ulong index = ((ulong)roc << 16) | sequenceNumber; AesEngine aes = new AesEngine(); - byte[] iv = F8.GenerateRtpMessageKeyIV(aes, k_e, k_s, rtpBytes, roc); + var iv = new byte[CTR.BLOCK_SIZE]; + F8.GenerateRtpMessageKeyIV(iv, aes, k_e, k_s, rtpBytes, roc); aes.Init(true, new Org.BouncyCastle.Crypto.Parameters.KeyParameter(k_e)); - F8.Encrypt(aes, rtpBytes, offset, rtpBytes.Length, iv); + F8.Encrypt(rtpBytes.AsSpan(offset, rtpBytes.Length - offset), aes, rtpBytes.AsSpan(offset, rtpBytes.Length - offset), iv); - string payloadString = Convert.ToHexString(rtpBytes).ToLowerInvariant(); - Assert.AreEqual(expectedSrtp, payloadString); + var expectedSrtpBytes = Convert.FromHexString(expectedSrtp); + Assert.IsTrue(rtpBytes.AsSpan().SequenceEqual(expectedSrtpBytes), + $"AESF8 mismatch.\nExpected: {BitConverter.ToString(expectedSrtpBytes)}\nActual: {BitConverter.ToString(rtpBytes)}"); } } } diff --git a/src/SharpSRTP.Tests/TestRFC5669.cs b/src/SharpSRTP.Tests/TestRFC5669.cs index 58299fd..538de4f 100644 --- a/src/SharpSRTP.Tests/TestRFC5669.cs +++ b/src/SharpSRTP.Tests/TestRFC5669.cs @@ -28,7 +28,6 @@ using SharpSRTP.SRTP.Encryption; using SharpSRTP.SRTP.Readers; using System; -using System.Linq; namespace SharpSRTP.Tests { @@ -62,7 +61,8 @@ public void Test_Encrypt_RTP_Seed_CTR(string cryptoSuite, string key, string aut uint roc = 0; ulong index = SrtpContext.GenerateRtpIndex(roc, sequenceNumber); - byte[] iv = CTR.GenerateMessageKeyIV(k_s, ssrc, index); + Span iv = stackalloc byte[CTR.BLOCK_SIZE]; + CTR.GenerateMessageKeyIV(iv, k_s, ssrc, index); var seed = new SeedEngine(); seed.Init(true, new Org.BouncyCastle.Crypto.Parameters.KeyParameter(k_e)); @@ -70,7 +70,8 @@ public void Test_Encrypt_RTP_Seed_CTR(string cryptoSuite, string key, string aut var hmac = new HMac(new Sha1Digest()); hmac.Init(new Org.BouncyCastle.Crypto.Parameters.KeyParameter(k_a)); - CTR.Encrypt(seed, payload, offset, length, iv); + var slice = payload.AsSpan(offset, length - offset); + SharpSRTP.SRTP.Encryption.CTR.Encrypt(slice, seed, slice, iv); payload[length + 0] = (byte)(roc >> 24); payload[length + 1] = (byte)(roc >> 16); @@ -85,12 +86,13 @@ public void Test_Encrypt_RTP_Seed_CTR(string cryptoSuite, string key, string aut byte[] auth = HMAC.GenerateAuthTag(hmac, payloadRaw, 0, length); */ // However, it makes little sense to do it that way, so it's likely a bug and I've updated the test data with a different authTag produced by the standard algorithm - byte[] auth = HMAC.GenerateAuthTag(hmac, payload, 0, length + 4); - System.Buffer.BlockCopy(auth, 0, payload, length, n_tag); // we don't append ROC in SRTP - var result = payload.Take(length + n_tag).ToArray(); - - string srtpResult = Convert.ToHexString(result).ToLowerInvariant(); - Assert.AreEqual(srtp, srtpResult); + var auth = new byte[hmac.GetMacSize()]; + HMAC.GenerateAuthTag(hmac, payload.AsSpan(0, length + 4), auth); + System.Buffer.BlockCopy(auth, 0, payload, length, n_tag); + var result = payload.AsSpan(0, length + n_tag); + var expectedSrtpBytes = Convert.FromHexString(srtp); + Assert.IsTrue(result.SequenceEqual(expectedSrtpBytes), + $"SEED-CTR SRTP mismatch.\nExpected: {BitConverter.ToString(expectedSrtpBytes)}\nActual: {BitConverter.ToString(result.ToArray())}"); } [DataRow(SrtpCryptoSuites.SEED_128_GCM_96, "e91e5e75da65554a48181f3846349562", "0000000000000000000000000000", "8008315ebf2e6fe020e8f5ebf57af5fd4ae19562976ec57a5a7ad55a5af5c5e5c5fdf5c55ad57a4a7272d57262e9729566ed66e97ac54a4a5a7ad5e15ae5fdd5fd5ac5d56ae56ad5c572d54ae54ac55a956afd6aed5a4ac562957a9516991691d572fd14e97ae962ed7a9f4a955af572e162f57a956666e17ae1f54a95f566d54a66e16e4afd6a9f7ae1c5c55ae5d56afde916c5e94a6ec56695e14afde1148416e94ad57ac5146ed59d1cc5", "8008315ebf2e6fe020e8f5eb8a5363682c6b1bbf13c0b09cf747a5512543cb2f129b8bd0e92dfadf735cda8f88c4bbf90288f5e58d20c4f1bb0d58446ea009103ee57ba99cdeabaaa18d4a9a05ddb46e7e5290a5a2284fe50b1f6fe9ad3f1348c354181e85b24f1a552a1193cf0e13eed5ab95ae854fb4f5b0edb2d3ee5eb238c8f4bfb136b2eb6cd78760420680ce1879100014f140a15e07e70133ed9cbb6d57b75d574acb0087eefbac9936cd9ae602be3ee2cd8d5d9d")] @@ -110,17 +112,23 @@ public void Test_Encrypt_RTP_Seed_GCM(string cryptoSuite, string key, string sal int offset = RtpReader.ReadHeaderLen(rtpBytes); - byte[] iv = AEAD.GenerateMessageKeyIV(k_s, ssrc, index); + var iv = new byte[AEAD.BLOCK_SIZE]; + AEAD.GenerateMessageKeyIV(iv, k_s, ssrc, index); byte[] result = new byte[rtpBytes.Length + n_tag]; Buffer.BlockCopy(rtpBytes, 0, result, 0, rtpBytes.Length); var cipher = new GcmBlockCipher(new SeedEngine()); - byte[] associatedData = result.Take(offset).ToArray(); - AEAD.Encrypt(cipher, true, result, offset, rtpBytes.Length, iv, k_e, n_tag, associatedData); + byte[] associatedData = new byte[offset]; + Buffer.BlockCopy(result, 0, associatedData, 0, offset); + + var payloadSpan = result.AsSpan(offset, rtpBytes.Length - offset); + var outputSpan = result.AsSpan(offset, (rtpBytes.Length - offset) + n_tag); + AEAD.Encrypt(outputSpan, cipher, true, payloadSpan, iv, k_e, n_tag, associatedData); - string encryptedRTP = Convert.ToHexString(result).ToLowerInvariant(); - Assert.AreEqual(expectedEncryptedRTP, encryptedRTP); + var expectedEncryptedBytes = Convert.FromHexString(expectedEncryptedRTP); + Assert.IsTrue(result.AsSpan().SequenceEqual(expectedEncryptedBytes), + $"SEED-GCM SRTP mismatch.\nExpected: {BitConverter.ToString(expectedEncryptedBytes)}\nActual: {BitConverter.ToString(result)}"); } [DataRow(SrtpCryptoSuites.SEED_128_CCM_80, "974bee725d44fc3992267b284c3c6750", "0000000000000000000000000000", "8008315ebf2e6fe020e8f5ebf57af5fd4ae19562976ec57a5a7ad55a5af5c5e5c5fdf5c55ad57a4a7272d57262e9729566ed66e97ac54a4a5a7ad5e15ae5fdd5fd5ac5d56ae56ad5c572d54ae54ac55a956afd6aed5a4ac562957a9516991691d572fd14e97ae962ed7a9f4a955af572e162f57a956666e17ae1f54a95f566d54a66e16e4afd6a9f7ae1c5c55ae5d56afde916c5e94a6ec56695e14afde1148416e94ad57ac5146ed59d1cc5", "8008315ebf2e6fe020e8f5eb486843a881df215a8574650ddabf5dbb2650f06f51252bccaeb4012899d6d71e30c64dad5ead5d8ba65ffe9d79aaf30dc9e6334490c07e7533d704114a9006ecb3b3bff59ecf585485bc0bd286ed434cfd684d19a1ad514ca5f37b71d93288c07cf4d5e9b83db8becc8c692a7279b6a9ac62ba970fc54f46dcc926d434c0b5ad8678fbf0e7a03037924dae342ef64fa65b8eaea260fecb477a57e3919c5dab82b0a8274cf6a8bb6cc466")] @@ -140,17 +148,23 @@ public void Test_Encrypt_RTP_Seed_CCM(string cryptoSuite, string key, string sal int offset = RtpReader.ReadHeaderLen(rtpBytes); - byte[] iv = AEAD.GenerateMessageKeyIV(k_s, ssrc, index); + var iv = new byte[AEAD.BLOCK_SIZE]; + AEAD.GenerateMessageKeyIV(iv, k_s, ssrc, index); byte[] result = new byte[rtpBytes.Length + n_tag]; Buffer.BlockCopy(rtpBytes, 0, result, 0, rtpBytes.Length); var cipher = new CcmBlockCipher(new SeedEngine()); - byte[] associatedData = result.Take(offset).ToArray(); - AEAD.Encrypt(cipher, true, result, offset, rtpBytes.Length, iv, k_e, n_tag, associatedData); + byte[] associatedData = new byte[offset]; + Buffer.BlockCopy(result, 0, associatedData, 0, offset); + + var payloadSpan = result.AsSpan(offset, rtpBytes.Length - offset); + var outputSpan = result.AsSpan(offset, (rtpBytes.Length - offset) + n_tag); + AEAD.Encrypt(outputSpan, cipher, true, payloadSpan, iv, k_e, n_tag, associatedData); - string encryptedRTP = Convert.ToHexString(result).ToLowerInvariant(); - Assert.AreEqual(expectedEncryptedRTP, encryptedRTP); + var expectedEncryptedBytes = Convert.FromHexString(expectedEncryptedRTP); + Assert.IsTrue(result.AsSpan().SequenceEqual(expectedEncryptedBytes), + $"SEED-CCM SRTP mismatch.\nExpected: {BitConverter.ToString(expectedEncryptedBytes)}\nActual: {BitConverter.ToString(result)}"); } } } diff --git a/src/SharpSRTP.Tests/TestRFC6904.cs b/src/SharpSRTP.Tests/TestRFC6904.cs index 37e75a6..9634c8c 100644 --- a/src/SharpSRTP.Tests/TestRFC6904.cs +++ b/src/SharpSRTP.Tests/TestRFC6904.cs @@ -22,7 +22,6 @@ using SharpSRTP.SRTP; using System; using System.Data; -using System.Linq; namespace SharpSRTP.Tests { @@ -38,22 +37,28 @@ public void Test_Extension_Encryption(string cryptoSuite, SrtpContextType strpCo { byte[] masterKeyBytes = Convert.FromHexString(masterKey); byte[] masterSaltBytes = Convert.FromHexString(masterSalt); - byte[] masterKeySalt = masterKeyBytes.Concat(masterSaltBytes).ToArray(); + byte[] masterKeySalt = new byte[masterKeyBytes.Length + masterSaltBytes.Length]; + Buffer.BlockCopy(masterKeyBytes, 0, masterKeySalt, 0, masterKeyBytes.Length); + Buffer.BlockCopy(masterSaltBytes, 0, masterKeySalt, masterKeyBytes.Length, masterSaltBytes.Length); SrtpKeys keys = SrtpProtocol.CreateMasterKeys(cryptoSuite, null, masterKeySalt); SrtpSessionContext context = SrtpProtocol.CreateSrtpSessionContext(keys); - Assert.AreEqual(sk_he, Convert.ToHexString(context.EncodeRtpContext.K_he).ToUpperInvariant()); - Assert.AreEqual(sk_hs, Convert.ToHexString(context.EncodeRtpContext.K_hs).ToUpperInvariant()); + var expected_he = Convert.FromHexString(sk_he); + var expected_hs = Convert.FromHexString(sk_hs); + Assert.IsTrue(context.EncodeRtpContext.K_he.Span.SequenceEqual(expected_he), "K_he mismatch"); + Assert.IsTrue(context.EncodeRtpContext.K_hs.Span.SequenceEqual(expected_hs), "K_hs mismatch"); byte[] rtpExtensionsBytes = Convert.FromHexString(rtpExtensions.Replace(" ", "")); byte[] rtpExtensionsMaskBytes = Convert.FromHexString(rtpExtensionsMask.Replace(" ", "")); + var encryptedExtensions = new byte[rtpExtensionsBytes.Length]; + // null payload won't work for F8 cipher - int ret = context.EncodeRtpContext.ProtectUnprotectRtpHeaderExtensions(null, rtpExtensionsBytes, rtpExtensionsMaskBytes, ssrc, roc, SrtpContext.GenerateRtpIndex(roc, sequenceNumber)); - Assert.AreEqual(0, ret); + context.EncodeRtpContext.ProtectUnprotectRtpHeaderExtensions(encryptedExtensions, default, rtpExtensionsBytes, rtpExtensionsMaskBytes, ssrc, roc, SrtpContext.GenerateRtpIndex(roc, sequenceNumber)); - string encryptedExtensions = Convert.ToHexString(rtpExtensionsBytes).ToUpperInvariant(); - Assert.AreEqual(expectedEncryptedExtensions, encryptedExtensions); + var expectedEncrypted = Convert.FromHexString(expectedEncryptedExtensions); + Assert.IsTrue(encryptedExtensions.AsSpan().SequenceEqual(expectedEncrypted), + $"Encrypted RTP header extensions mismatch.\nExpected: {BitConverter.ToString(expectedEncrypted)}\nActual: {BitConverter.ToString(encryptedExtensions)}"); } } } diff --git a/src/SharpSRTP.Tests/TestRFC7714.cs b/src/SharpSRTP.Tests/TestRFC7714.cs index 69b6d17..44b1646 100644 --- a/src/SharpSRTP.Tests/TestRFC7714.cs +++ b/src/SharpSRTP.Tests/TestRFC7714.cs @@ -26,7 +26,7 @@ using SharpSRTP.SRTP.Encryption; using SharpSRTP.SRTP.Readers; using System; -using System.Linq; +using System.Buffers.Binary; namespace SharpSRTP.Tests { @@ -47,10 +47,12 @@ public void Test_IV_RTP(string rtp, string sk_s, string expectedIv) ushort sequenceNumber = RtpReader.ReadSequenceNumber(rtpBytes); ulong index = SrtpContext.GenerateRtpIndex(0, sequenceNumber); - byte[] iv = AEAD.GenerateMessageKeyIV(k_s, ssrc, index); + Span iv = stackalloc byte[AEAD.BLOCK_SIZE]; + AEAD.GenerateMessageKeyIV(iv, k_s, ssrc, index); - string ivString = Convert.ToHexString(iv).ToLowerInvariant(); - Assert.AreEqual(expectedIv, ivString); + var expectedIvBytes = Convert.FromHexString(expectedIv); + Assert.IsTrue(iv.SequenceEqual(expectedIvBytes), + $"IV mismatch.\nExpected: {BitConverter.ToString(expectedIvBytes)}\nActual: {BitConverter.ToString(iv.ToArray())}"); } [DataRow(ExtendedSrtpProtectionProfile.SRTP_AEAD_AES_128_GCM, "8040f17b8041f8d35501a0b247616c6c696120657374206f6d6e69732064697669736120696e207061727465732074726573", "000102030405060708090a0b0c0d0e0f", "517569642070726f2071756f", "8040f17b8041f8d35501a0b2f24de3a3fb34de6cacba861c9d7e4bcabe633bd50d294e6f42a5f47a51c7d19b36de3adf8833899d7f27beb16a9152cf765ee4390cce")] @@ -70,17 +72,24 @@ public void Test_Encrypt_RTP(int dtlsProtectionProfile, string rtp, string sk_e, int n_tag = protectionProfile.AuthTagLength >> 3; int offset = RtpReader.ReadHeaderLen(rtpBytes); - byte[] iv = AEAD.GenerateMessageKeyIV(k_s, ssrc, index); + var iv = new byte[AEAD.BLOCK_SIZE]; + AEAD.GenerateMessageKeyIV(iv, k_s, ssrc, index); byte[] result = new byte[rtpBytes.Length + n_tag]; Buffer.BlockCopy(rtpBytes, 0, result, 0, rtpBytes.Length); var cipher = new GcmBlockCipher(new AesEngine()); - byte[] associatedData = result.Take(offset).ToArray(); - AEAD.Encrypt(cipher, true, result, offset, rtpBytes.Length, iv, k_e, n_tag, associatedData); + byte[] associatedData = new byte[offset]; + Buffer.BlockCopy(result, 0, associatedData, 0, offset); - string encryptedRTP = Convert.ToHexString(result).ToLowerInvariant(); - Assert.AreEqual(expectedSrtp, encryptedRTP); + // Encrypt payload -> payload+tag slice + var payloadSpan = result.AsSpan(offset, rtpBytes.Length - offset); + var outputSpan = result.AsSpan(offset, (rtpBytes.Length - offset) + n_tag); + AEAD.Encrypt(outputSpan, cipher, true, payloadSpan, iv, k_e, n_tag, associatedData); + + var expectedSrtpBytes = Convert.FromHexString(expectedSrtp); + Assert.IsTrue(result.AsSpan().SequenceEqual(expectedSrtpBytes), + $"SRTP RTP mismatch.\nExpected: {BitConverter.ToString(expectedSrtpBytes)}\nActual: {BitConverter.ToString(result)}"); } [DataRow(ExtendedSrtpProtectionProfile.SRTP_AEAD_AES_128_GCM, "8040f17b8041f8d35501a0b2f24de3a3fb34de6cacba861c9d7e4bcabe633bd50d294e6f42a5f47a51c7d19b36de3adf8833899d7f27beb16a9152cf765ee4390cce", "000102030405060708090a0b0c0d0e0f", "517569642070726f2071756f", "8040f17b8041f8d35501a0b247616c6c696120657374206f6d6e69732064697669736120696e207061727465732074726573")] @@ -100,14 +109,22 @@ public void Test_Decrypt_RTP(int dtlsProtectionProfile, string srtp, string sk_e int n_tag = protectionProfile.AuthTagLength >> 3; int offset = RtpReader.ReadHeaderLen(srtpBytes); - byte[] iv = AEAD.GenerateMessageKeyIV(k_s, ssrc, index); + var iv = new byte[AEAD.BLOCK_SIZE]; + AEAD.GenerateMessageKeyIV(iv, k_s, ssrc, index); var cipher = new GcmBlockCipher(new AesEngine()); - byte[] associatedData = srtpBytes.Take(offset).ToArray(); - AEAD.Encrypt(cipher, false, srtpBytes, offset, srtpBytes.Length, iv, k_e, n_tag, associatedData); - - string result = Convert.ToHexString(srtpBytes.Take(srtpBytes.Length - n_tag).ToArray()).ToLowerInvariant(); - Assert.AreEqual(expectedRtp, result); + byte[] associatedData = new byte[offset]; + Buffer.BlockCopy(srtpBytes, 0, associatedData, 0, offset); + + // Decrypt payload+tag -> payload slice + var encSpan = srtpBytes.AsSpan(offset, srtpBytes.Length - offset); + var outSpan = srtpBytes.AsSpan(offset, srtpBytes.Length - offset); + AEAD.Encrypt(outSpan, cipher, false, encSpan, iv, k_e, n_tag, associatedData); + + var expectedRtpBytes = Convert.FromHexString(expectedRtp); + var actualRtpSpan = srtpBytes.AsSpan(0, srtpBytes.Length - n_tag); + Assert.IsTrue(actualRtpSpan.SequenceEqual(expectedRtpBytes), + $"SRTP Decrypt RTP mismatch.\nExpected: {BitConverter.ToString(expectedRtpBytes)}\nActual: {BitConverter.ToString(actualRtpSpan.ToArray())}"); } [DataRow("81c8000e4d6172734e5450314e545031525450200000042a0000eb984c756e61deadbeefdeadbeefdeadbeefdeadbeefdeadbeef", "517569642070726f2071756f", (uint)0x000005d4, "517524055203726f207170bb")] @@ -118,9 +135,11 @@ public void Test_IV_RTCP(string rtcp, string sk_s, uint index, string expectedIv byte[] k_s = Convert.FromHexString(sk_s); uint ssrc = RtcpReader.ReadSsrc(rtpBytes); - byte[] iv = AEAD.GenerateMessageKeyIV(k_s, ssrc, index); - string ivString = Convert.ToHexString(iv).ToLowerInvariant(); - Assert.AreEqual(expectedIv, ivString); + Span iv = stackalloc byte[AEAD.BLOCK_SIZE]; + AEAD.GenerateMessageKeyIV(iv, k_s, ssrc, index); + var expectedIvBytes = Convert.FromHexString(expectedIv); + Assert.IsTrue(iv.SequenceEqual(expectedIvBytes), + $"IV RTCP mismatch.\nExpected: {BitConverter.ToString(expectedIvBytes)}\nActual: {BitConverter.ToString(iv.ToArray())}"); } [DataRow(ExtendedSrtpProtectionProfile.SRTP_AEAD_AES_128_GCM, "81c8000d4d6172734e5450314e545032525450200000042a0000e9304c756e61deadbeefdeadbeefdeadbeefdeadbeefdeadbeef", "000102030405060708090a0b0c0d0e0f", "517569642070726f2071756f", (uint)0x000005d4, "81c8000d4d61727363e94885dcdab67ca727d7662f6b7e997ff5c0f76c06f32dc676a5f1730d6fda4ce09b4686303ded0bb9275bc84aa45896cf4d2fc5abf87245d9eade800005d4")] @@ -135,7 +154,8 @@ public void Test_Encrypt_RTCP(int dtlsProtectionProfile, string rtcp, string sk_ uint ssrc = RtcpReader.ReadSsrc(rtcpBytes); int offset = RtcpReader.GetHeaderLen(); - byte[] iv = AEAD.GenerateMessageKeyIV(k_s, ssrc, idx); + var iv = new byte[AEAD.BLOCK_SIZE]; + AEAD.GenerateMessageKeyIV(iv, k_s, ssrc, idx); int n_tag = protectionProfile.AuthTagLength >> 3; byte[] srtcp = new byte[rtcpBytes.Length + n_tag + 4]; @@ -143,16 +163,21 @@ public void Test_Encrypt_RTCP(int dtlsProtectionProfile, string rtcp, string sk_ var cipher = new GcmBlockCipher(new AesEngine()); uint index = idx | SrtpContext.E_FLAG; - byte[] associatedData = srtcp.Take(offset).Concat(new byte[] { (byte)(index >> 24), (byte)(index >> 16), (byte)(index >> 8), (byte)index }).ToArray(); // associatedData include also index - AEAD.Encrypt(cipher, true, srtcp, offset, rtcpBytes.Length, iv, k_e, n_tag, associatedData); - srtcp[rtcpBytes.Length + n_tag + 0] = (byte)(index >> 24); - srtcp[rtcpBytes.Length + n_tag + 1] = (byte)(index >> 16); - srtcp[rtcpBytes.Length + n_tag + 2] = (byte)(index >> 8); - srtcp[rtcpBytes.Length + n_tag + 3] = (byte)index; + // associatedData include also index + byte[] associatedData = new byte[offset + 4]; + Array.Copy(srtcp, 0, associatedData, 0, offset); + BinaryPrimitives.WriteUInt32BigEndian(associatedData.AsSpan(offset, 4), index); + + var payloadSpan = srtcp.AsSpan(offset, rtcpBytes.Length - offset); + var outputSpan = srtcp.AsSpan(offset, rtcpBytes.Length - offset + n_tag); + AEAD.Encrypt(outputSpan, cipher, true, payloadSpan, iv, k_e, n_tag, associatedData); - string result = Convert.ToHexString(srtcp).ToLowerInvariant(); - Assert.AreEqual(expectedSrtcp, result); + BinaryPrimitives.WriteUInt32BigEndian(srtcp.AsSpan(rtcpBytes.Length + n_tag, 4), index); + + var expectedSrtcpBytes = Convert.FromHexString(expectedSrtcp); + Assert.IsTrue(srtcp.AsSpan().SequenceEqual(expectedSrtcpBytes), + $"SRTP RTCP mismatch.\nExpected: {BitConverter.ToString(expectedSrtcpBytes)}\nActual: {BitConverter.ToString(srtcp)}"); } [DataRow(ExtendedSrtpProtectionProfile.SRTP_AEAD_AES_128_GCM, "81c8000d4d617273d50ae4d1f5ce5d304ba297e47d470c282c3ece5dbffe0a50a2eaa5c1110555be8415f658c61de0476f1b6fad1d1eb30c4446839f57ff6f6cb26ac3be800005d4", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", "517569642070726f2071756f", "81c8000d4d6172734e5450314e545032525450200000042a0000e9304c756e61deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")] @@ -168,18 +193,27 @@ public void Test_Decrypt_RTCP(int dtlsProtectionProfile, string srtcp, string sk int n_tag = protectionProfile.AuthTagLength >> 3; uint ssrc = RtcpReader.ReadSsrc(srtcpBytes); uint idx = RtcpReader.SrtcpReadIndex(srtcpBytes, 0); - + uint index = idx & ~SrtpContext.E_FLAG; int offset = RtcpReader.GetHeaderLen(); - byte[] iv = AEAD.GenerateMessageKeyIV(k_s, ssrc, index); + var iv = new byte[AEAD.BLOCK_SIZE]; + AEAD.GenerateMessageKeyIV(iv, k_s, ssrc, index); var cipher = new GcmBlockCipher(new AesEngine()); - byte[] associatedData = srtcpBytes.Take(offset).Concat(srtcpBytes.Skip(srtcpBytes.Length - 4).Take(4)).ToArray(); // associatedData include also index - AEAD.Encrypt(cipher, false, srtcpBytes, offset, srtcpBytes.Length - 4, iv, k_e, n_tag, associatedData); - - string result = Convert.ToHexString(srtcpBytes.Take(srtcpBytes.Length - 4 - n_tag).ToArray()).ToLowerInvariant(); - Assert.AreEqual(expectedRtcp, result); + byte[] associatedData = new byte[offset + 4]; + Array.Copy(srtcpBytes, 0, associatedData, 0, offset); + BinaryPrimitives.WriteUInt32BigEndian(associatedData.AsSpan(offset, 4), + BinaryPrimitives.ReadUInt32BigEndian(srtcpBytes.AsSpan(srtcpBytes.Length - 4, 4))); + + var encSpan = srtcpBytes.AsSpan(offset, srtcpBytes.Length - offset - 4); + var outSpan = srtcpBytes.AsSpan(offset, srtcpBytes.Length - offset); + AEAD.Encrypt(outSpan, cipher, false, encSpan, iv, k_e, n_tag, associatedData); + + var expectedRtcpBytes = Convert.FromHexString(expectedRtcp); + var actualRtcpSpan = srtcpBytes.AsSpan(0, srtcpBytes.Length - 4 - n_tag); + Assert.IsTrue(actualRtcpSpan.SequenceEqual(expectedRtcpBytes), + $"SRTP Decrypt RTCP mismatch.\nExpected: {BitConverter.ToString(expectedRtcpBytes)}\nActual: {BitConverter.ToString(actualRtcpSpan.ToArray())}"); } } } diff --git a/src/SharpSRTP.Tests/TestRFC8269.cs b/src/SharpSRTP.Tests/TestRFC8269.cs index f5ed68c..9031e6f 100644 --- a/src/SharpSRTP.Tests/TestRFC8269.cs +++ b/src/SharpSRTP.Tests/TestRFC8269.cs @@ -29,7 +29,6 @@ using SharpSRTP.SRTP.Encryption; using SharpSRTP.SRTP.Readers; using System; -using System.Linq; namespace SharpSRTP.Tests { @@ -63,7 +62,8 @@ public void Test_Encrypt_ARIACTR_RTP(int dtlsProtectionProfile, string sk_e, str uint roc = 0; ulong index = SrtpContext.GenerateRtpIndex(roc, sequenceNumber); - byte[] iv = CTR.GenerateMessageKeyIV(bk_s, ssrc, index); + Span iv = stackalloc byte[CTR.BLOCK_SIZE]; + CTR.GenerateMessageKeyIV(iv, bk_s, ssrc, index); var aria = new AriaEngine(); aria.Init(true, new Org.BouncyCastle.Crypto.Parameters.KeyParameter(bk_e)); @@ -71,7 +71,7 @@ public void Test_Encrypt_ARIACTR_RTP(int dtlsProtectionProfile, string sk_e, str var hmac = new HMac(new Sha1Digest()); hmac.Init(new Org.BouncyCastle.Crypto.Parameters.KeyParameter(bk_a)); - CTR.Encrypt(aria, payload, offset, length, iv); + CTR.Encrypt(payload.AsSpan(offset, length - offset), aria, payload.AsSpan(offset, length - offset), iv); payload[length + 0] = (byte)(roc >> 24); payload[length + 1] = (byte)(roc >> 16); @@ -79,12 +79,13 @@ public void Test_Encrypt_ARIACTR_RTP(int dtlsProtectionProfile, string sk_e, str payload[length + 3] = (byte)roc; int n_tag = protectionProfile.AuthTagLength >> 3; - byte[] auth = HMAC.GenerateAuthTag(hmac, payload, 0, length + 4); + var auth = new byte[hmac.GetMacSize()]; + HMAC.GenerateAuthTag(hmac, payload.AsSpan(0, length + 4), auth); System.Buffer.BlockCopy(auth, 0, payload, length, n_tag); // we don't append ROC in SRTP - var result = payload.Take(length + n_tag).ToArray(); - - string srtpResult = Convert.ToHexString(result).ToLowerInvariant(); - Assert.AreEqual(expectedSrtp, srtpResult); + var result = payload.AsSpan(0, length + n_tag); + var expectedSrtpBytes = Convert.FromHexString(expectedSrtp); + Assert.IsTrue(result.SequenceEqual(expectedSrtpBytes), + $"SRTP ARIA-CTR output does not match expected value.\nExpected: {BitConverter.ToString(expectedSrtpBytes)}\nActual: {BitConverter.ToString(result.ToArray())}"); } [DataRow(ExtendedSrtpProtectionProfile.SRTP_AEAD_ARIA_128_GCM, "e91e5e75da65554a48181f3846349562", "000000000000000000000000", "8008315ebf2e6fe020e8f5ebf57af5fd4ae19562976ec57a5a7ad55a5af5c5e5c5fdf5c55ad57a4a7272d57262e9729566ed66e97ac54a4a5a7ad5e15ae5fdd5fd5ac5d56ae56ad5c572d54ae54ac55a956afd6aed5a4ac562957a9516991691d572fd14e97ae962ed7a9f4a955af572e162f57a956666e17ae1f54a95f566d54a66e16e4afd6a9f7ae1c5c55ae5d56afde916c5e94a6ec56695e14afde1148416e94ad57ac5146ed59d1cc5", "8008315ebf2e6fe020e8f5eb4d8a9a0675550c704b17d8c9ddc81a5cd6f7da34f2fe1b3db7cb3dfb9697102ea0f3c1fc2dbc873d44bceeae8e4442974ba21ff6789d3272613fb9631a7cf3f14bacbeb421633a90ffbe58c2fa6bdca534f10d0de0502ce1d531b6336e58878278531e5c22bc6c85bbd784d78d9e680aa19031aaf89101d669d7a3965c1f7e16229d7463e0535f4e253f5d18187d40b8ae0f564bd970b5e7e2adfb211e89a9535abace3f37f5a736f4be984bbffbedc1")] @@ -104,17 +105,22 @@ public void Test_Encrypt_ARIAGCM_RTP(int dtlsProtectionProfile, string sk_e, str int n_tag = protectionProfile.AuthTagLength >> 3; int offset = RtpReader.ReadHeaderLen(rtpBytes); - byte[] iv = AEAD.GenerateMessageKeyIV(bk_s, ssrc, index); + var iv = new byte[AEAD.BLOCK_SIZE]; + AEAD.GenerateMessageKeyIV(iv, bk_s, ssrc, index); byte[] result = new byte[rtpBytes.Length + n_tag]; Buffer.BlockCopy(rtpBytes, 0, result, 0, rtpBytes.Length); var cipher = new GcmBlockCipher(new AriaEngine()); - byte[] associatedData = result.Take(offset).ToArray(); - AEAD.Encrypt(cipher, true, result, offset, rtpBytes.Length, iv, bk_e, n_tag, associatedData); - - string srtpResult = Convert.ToHexString(result).ToLowerInvariant(); - Assert.AreEqual(expectedSrtp, srtpResult); + byte[] associatedData = new byte[offset]; + Buffer.BlockCopy(result, 0, associatedData, 0, offset); + var payloadSpan = result.AsSpan(offset, rtpBytes.Length - offset); + var outputSpan = result.AsSpan(offset, (rtpBytes.Length - offset) + n_tag); + AEAD.Encrypt(outputSpan, cipher, true, payloadSpan, iv, bk_e, n_tag, associatedData); + + var expectedSrtpBytes = Convert.FromHexString(expectedSrtp); + Assert.IsTrue(result.AsSpan().SequenceEqual(expectedSrtpBytes), + $"SRTP ARIA-GCM output does not match expected value.\nExpected: {BitConverter.ToString(expectedSrtpBytes)}\nActual: {BitConverter.ToString(result)}"); } [DataRow(ExtendedSrtpProtectionProfile.SRTP_ARIA_128_CTR_HMAC_SHA1_80, SrtpContextType.RTP, "e1f97a0d3e018be0d64fa32c06de4139", "0ec675ad498afeebb6960b3aabe6", "dbd85a3c4d9219b3e81f7d942e299de4", "d021877bd3eaf92d581ed70ddc050e03f1125703", "9700657f5f34161830d7d85f5dc8")] @@ -129,13 +135,12 @@ public void Test_Session_Keys_ARIACTR(int dtlsProtectionProfile, SrtpContextType var context = new SrtpContext(srtpContextType, protectionProfile, masterKeyBytes, masterSaltBytes, null); - string sgk_e = Convert.ToHexString(context.K_e).ToLowerInvariant(); - string sgk_a = Convert.ToHexString(context.K_a).ToLowerInvariant(); - string sgk_s = Convert.ToHexString(context.K_s).ToLowerInvariant(); - - Assert.AreEqual(sk_e, sgk_e); - Assert.AreEqual(sk_a, sgk_a); // TODO: RFC shows 96 bytes auth key, not sure why - Assert.AreEqual(sk_s, sgk_s); + var expected_e = Convert.FromHexString(sk_e); + var expected_a = Convert.FromHexString(sk_a); + var expected_s = Convert.FromHexString(sk_s); + Assert.IsTrue(context.K_e.Span.SequenceEqual(expected_e), "K_e mismatch"); + Assert.IsTrue(context.K_a.Span.SequenceEqual(expected_a), "K_a mismatch"); + Assert.IsTrue(context.K_s.Span.SequenceEqual(expected_s), "K_s mismatch"); } } } diff --git a/src/SharpSRTP.Tests/TestRFC8723.cs b/src/SharpSRTP.Tests/TestRFC8723.cs index c0ed706..880fb16 100644 --- a/src/SharpSRTP.Tests/TestRFC8723.cs +++ b/src/SharpSRTP.Tests/TestRFC8723.cs @@ -21,7 +21,7 @@ using SharpSRTP.DTLSSRTP; using System; -using System.Linq; +using System.Collections.Generic; namespace SharpSRTP.Tests { @@ -31,30 +31,73 @@ namespace SharpSRTP.Tests [TestClass] public class TestRFC8723 { + public static IEnumerable Test_Srtp_Protect_Unprotect_TestData() + { + var dtlsSrtpProfile = ExtendedSrtpProtectionProfile.DOUBLE_AEAD_AES_128_GCM_AEAD_AES_128_GCM; + var masterKeySalt = "f7d54b1f77018d00a48438d4be6d1b59be683885ec77dde0d18a1e1e566044aa00fb3c11179f6a224763350e26634b952e00d0e34f6ef57b41aecbc8216832bbf18389d0f58f861065f337b8ffdb115c585cfef1019b19d71579a1792d0a6ac7467efa2e39ec36a4a75c1ff9c1f599e2"; + var rtp = "80e1000103cb6bc84218a6a3001006c801123318f6882d06086141a9c44dfbfb7e9f1cf997eb257b77c732bcf779ae750b6493aff001815dcfc814a4fb96089153b0becc4e091f2632584ee88fc01701a0dc5111f3d7b201b0a5496972275d00e503d921370ecbdebc5ac4e54572e59ca65c29ce246b438659df04633d5d0452da1b9ce729670a616b4f5050df2c7de897ca16f5762d6df93da0134d6c3d2fedb178be2fbbfa3c702673c231d5af4f1c9b2fa791a19ef3a23aee2325dc633f19ebde33f0eeec8351cfa62bbbf9339d6b7e322ba3bb5e1d31a3956475cf450984d4a274d2583d1b80e0"; + var expectedSrtp = "80e1000103cb6bc84218a6a341c2f1daae18bf8e657088e360e382cbbfd74b34c90987d06e28d3f693221b99d1f22c8cd50c01d74fe473e95a71cf65505fc05c7384a6f522835a5c7f21a3ce9a24d0293bb06f3f5b2b7e9cfa151f0ae5caef93f074d70d3eaf54d2c09f0309b1e751523f15a2c621fa182a8cb23d68edb35acee7d7ec6d142486c0a0c464118c2bd56cee57847703bcea3904a0da3e6cf240f7e295250d721977fb56005d3ec36d917055853f1cc98b3cd0e3a9b0c5e24f88533df6f238516c248815b1308c23dc72cf9c07f0db436968b57d51ece116e6d832ad85f8e3c1a962554fe90c2982f3b462a10f61f5503651bc6cd26d51ffc86cc3b101bf28991e8dd34968"; + + yield return [dtlsSrtpProfile, masterKeySalt, rtp, expectedSrtp, true]; + yield return [dtlsSrtpProfile, masterKeySalt, rtp, expectedSrtp, false]; + } + // These test vectors were generated by SharpSRTP, they are not official and can be wrong! - [DataRow(ExtendedSrtpProtectionProfile.DOUBLE_AEAD_AES_128_GCM_AEAD_AES_128_GCM, "f7d54b1f77018d00a48438d4be6d1b59be683885ec77dde0d18a1e1e566044aa00fb3c11179f6a224763350e26634b952e00d0e34f6ef57b41aecbc8216832bbf18389d0f58f861065f337b8ffdb115c585cfef1019b19d71579a1792d0a6ac7467efa2e39ec36a4a75c1ff9c1f599e2", "80e1000103cb6bc84218a6a3001006c801123318f6882d06086141a9c44dfbfb7e9f1cf997eb257b77c732bcf779ae750b6493aff001815dcfc814a4fb96089153b0becc4e091f2632584ee88fc01701a0dc5111f3d7b201b0a5496972275d00e503d921370ecbdebc5ac4e54572e59ca65c29ce246b438659df04633d5d0452da1b9ce729670a616b4f5050df2c7de897ca16f5762d6df93da0134d6c3d2fedb178be2fbbfa3c702673c231d5af4f1c9b2fa791a19ef3a23aee2325dc633f19ebde33f0eeec8351cfa62bbbf9339d6b7e322ba3bb5e1d31a3956475cf450984d4a274d2583d1b80e0", "80e1000103cb6bc84218a6a341c2f1daae18bf8e657088e360e382cbbfd74b34c90987d06e28d3f693221b99d1f22c8cd50c01d74fe473e95a71cf65505fc05c7384a6f522835a5c7f21a3ce9a24d0293bb06f3f5b2b7e9cfa151f0ae5caef93f074d70d3eaf54d2c09f0309b1e751523f15a2c621fa182a8cb23d68edb35acee7d7ec6d142486c0a0c464118c2bd56cee57847703bcea3904a0da3e6cf240f7e295250d721977fb56005d3ec36d917055853f1cc98b3cd0e3a9b0c5e24f88533df6f238516c248815b1308c23dc72cf9c07f0db436968b57d51ece116e6d832ad85f8e3c1a962554fe90c2982f3b462a10f61f5503651bc6cd26d51ffc86cc3b101bf28991e8dd34968")] + [DynamicData(nameof(Test_Srtp_Protect_Unprotect_TestData))] [TestMethod] - public void Test_Srtp_Protect_Unprotect(int dtlsSrtpProfile, string masterKeySalt, string rtp, string expectedSrtp) + public void Test_Srtp_Protect_Unprotect(int dtlsSrtpProfile, string masterKeySalt, string rtp, string expectedSrtp, bool useSharedBuffer) { - byte[] masterKeySaltBytes = Convert.FromBase64String(masterKeySalt); - byte[] rtpBytes = Convert.FromHexString(rtp); + var masterKeySaltBytes = System.Convert.FromBase64String(masterKeySalt); + var rtpBytes = Convert.FromHexString(rtp); - byte[] MKI = null; - var keys = DtlsSrtpProtocol.CreateMasterKeys(dtlsSrtpProfile, MKI, masterKeySaltBytes); + var keys = DtlsSrtpProtocol.CreateMasterKeys(dtlsSrtpProfile, null, masterKeySaltBytes); var context = DtlsSrtpProtocol.CreateSrtpClientSessionContext(keys); - byte[] srtpBytes = new byte[context.CalculateRequiredSrtpPayloadLength(rtpBytes.Length)]; - Buffer.BlockCopy(rtpBytes, 0, srtpBytes, 0, rtpBytes.Length); - int ret = context.ProtectRtp(srtpBytes, rtpBytes.Length, out int len); - string srtpString = Convert.ToHexString(srtpBytes.Take(len).ToArray()).ToLowerInvariant(); - Assert.AreEqual(expectedSrtp, srtpString); + Span srtpProtectedBytes; + + { + srtpProtectedBytes = new byte[context.CalculateRequiredSrtpPayloadLength(rtpBytes.Length)]; + ReadOnlySpan srtpUnprotectedBytes; + + if (useSharedBuffer) + { + srtpUnprotectedBytes = srtpProtectedBytes.Slice(0, rtpBytes.Length); + rtpBytes.AsSpan(0, rtpBytes.Length).CopyTo(srtpProtectedBytes); + } + else + { + srtpUnprotectedBytes = rtpBytes; + } + + var len = context.ProtectRtp(srtpProtectedBytes, srtpUnprotectedBytes); + + var expectedBytes = Convert.FromHexString(expectedSrtp); + var actualBytes = srtpProtectedBytes.Slice(0, len).ToArray(); + Assert.IsTrue(expectedBytes.SequenceEqual(actualBytes), + $"ProtectRtp output does not match the expected value.\nExpected: {BitConverter.ToString(expectedBytes)}\nActual: {BitConverter.ToString(actualBytes)}"); + } var decodeContext = DtlsSrtpProtocol.CreateSrtpServerSessionContext(keys); - ret = decodeContext.UnprotectRtp(srtpBytes, srtpBytes.Length, out int olen); - string rtpString = Convert.ToHexString(srtpBytes.Take(olen).ToArray()).ToLowerInvariant(); - Assert.AreEqual(rtp, rtpString); + { + Span srtpUnprotectedBytes; + + if (useSharedBuffer) + { + srtpUnprotectedBytes = srtpProtectedBytes; + } + else + { + srtpUnprotectedBytes = new byte[srtpProtectedBytes.Length]; + } + + var olen = decodeContext.UnprotectRtp(srtpUnprotectedBytes, srtpProtectedBytes); + + var actualUnprotected = srtpUnprotectedBytes.Slice(0, olen).ToArray(); + Assert.IsTrue(rtpBytes.SequenceEqual(actualUnprotected), + $"UnprotectRtp output does not match the expected value.\nExpected: {BitConverter.ToString(rtpBytes)}\nActual: {BitConverter.ToString(actualUnprotected)}"); + } } } } diff --git a/src/SharpSRTP/BouncyCastleExtensions.cs b/src/SharpSRTP/BouncyCastleExtensions.cs new file mode 100644 index 0000000..de5d6c8 --- /dev/null +++ b/src/SharpSRTP/BouncyCastleExtensions.cs @@ -0,0 +1,33 @@ +using Org.BouncyCastle.Crypto.Parameters; +using System; +using System.Runtime.CompilerServices; + +namespace SharpSRTP +{ + public static class BouncyCastleExtensions + { +#if NET8_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static KeyParameter ToKeyParameter(this ReadOnlyMemory memory) + { + return new KeyParameter(memory.Span); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static KeyParameter ToKeyParameter(this ReadOnlySpan span) + { + return new KeyParameter(span); + } +#else + public static KeyParameter ToKeyParameter(this ReadOnlyMemory memory) + { + if (System.Runtime.InteropServices.MemoryMarshal.TryGetArray(memory, out ArraySegment segment)) + { + return new KeyParameter(segment.Array, segment.Offset, segment.Count); + } + // Fallback for non-array-backed memory + return new KeyParameter(memory.ToArray()); + } +#endif + } +} diff --git a/src/SharpSRTP/BytesExtensions.cs b/src/SharpSRTP/BytesExtensions.cs new file mode 100644 index 0000000..a17cad4 --- /dev/null +++ b/src/SharpSRTP/BytesExtensions.cs @@ -0,0 +1,35 @@ +using System; +using System.Runtime.CompilerServices; + +namespace SharpSRTP; + +internal static class BytesExtensions +{ +#if NET8_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span AsBytes(this Span bytes) => bytes; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span AsSpan(this Span bytes) => bytes; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span AsSpan(this Span bytes, int start) => bytes.Slice(start); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span AsSpan(this Span bytes, int start, int length) => bytes.Slice(start, length); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan AsBytes(this ReadOnlySpan bytes) => bytes; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan AsSpan(this ReadOnlySpan bytes) => bytes; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan AsSpan(this ReadOnlySpan bytes, int start) => bytes.Slice(start); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan AsSpan(this ReadOnlySpan bytes, int start, int length) => bytes.Slice(start, length); +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte[] AsBytes(this ReadOnlySpan bytes) => bytes.ToArray(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte[] AsBytes(this Span bytes) => bytes.ToArray(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span Slice(this byte[] bytes, int start, int length) => bytes.AsSpan(start, length); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span Slice(this byte[] bytes, int start) => bytes.AsSpan(start, bytes.Length - start); +#endif +} diff --git a/src/SharpSRTP/DTLS/DtlsCertificateUtils.cs b/src/SharpSRTP/DTLS/DtlsCertificateUtils.cs index 8a05bcd..9bbc6bc 100644 --- a/src/SharpSRTP/DTLS/DtlsCertificateUtils.cs +++ b/src/SharpSRTP/DTLS/DtlsCertificateUtils.cs @@ -195,10 +195,7 @@ public static string Fingerprint(X509CertificateStructure c, string algorithm = public static bool IsHashSupported(string algStr) { - if (string.IsNullOrEmpty(algStr)) - { - throw new ArgumentNullException(nameof(algStr)); - } + Throw.IfNullOrEmpty(algStr); IDigest digest = null; diff --git a/src/SharpSRTP/DTLS/DtlsClient.cs b/src/SharpSRTP/DTLS/DtlsClient.cs index 7442be0..0e4bdc5 100644 --- a/src/SharpSRTP/DTLS/DtlsClient.cs +++ b/src/SharpSRTP/DTLS/DtlsClient.cs @@ -52,7 +52,7 @@ public class DtlsClient : DefaultTlsClient, IDtlsPeer public event EventHandler OnHandshakeCompleted; public event EventHandler OnAlert; - public DtlsClient(TlsSession session = null, Certificate certificate = null, AsymmetricKeyParameter privateKey = null, short certificateSignatureAlgorithm = SignatureAlgorithm.ecdsa, short certificateHashAlgorithm = HashAlgorithm.sha256) + public DtlsClient(TlsSession session = null, Certificate certificate = null, AsymmetricKeyParameter privateKey = null, short certificateSignatureAlgorithm = SignatureAlgorithm.ecdsa, short certificateHashAlgorithm = HashAlgorithm.sha256) : this(new BcTlsCrypto(), session, certificate, privateKey, certificateSignatureAlgorithm, certificateHashAlgorithm) { } @@ -112,7 +112,7 @@ protected override int[] GetSupportedCipherSuites() CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, }; } - else if(CertificateSignatureAlgorithm == SignatureAlgorithm.ecdsa) + else if (CertificateSignatureAlgorithm == SignatureAlgorithm.ecdsa) { // ECDSA certificates require matching cipher suites return new int[] @@ -130,10 +130,9 @@ protected override int[] GetSupportedCipherSuites() CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, }; } - else - { - throw new NotSupportedException(); - } + + Throw.NotSupportedException(); + return null!; } public virtual DtlsTransport DoHandshake(out string handshakeError, DatagramTransport datagramTransport, DtlsRequest request = null) @@ -288,7 +287,7 @@ public override IDictionary GetClientExtensions() { if (m_context.SecurityParameters.ClientRandom == null) { - throw new TlsFatalAlert(AlertDescription.internal_error); + Throw.TlsFatalAlert(AlertDescription.internal_error); } return base.GetClientExtensions(); @@ -298,7 +297,7 @@ public override void ProcessServerExtensions(IDictionary serverExte { if (m_context.SecurityParameters.ServerRandom == null) { - throw new TlsFatalAlert(AlertDescription.internal_error); + Throw.TlsFatalAlert(AlertDescription.internal_error); } base.ProcessServerExtensions(serverExtensions); @@ -316,7 +315,9 @@ internal class DTlsAuthentication : TlsAuthentication public DTlsAuthentication(TlsContext context, DtlsClient client) { - this._client = client ?? throw new ArgumentNullException(nameof(client)); + Throw.IfNull(client); + + this._client = client; this._context = context; } @@ -341,7 +342,7 @@ public void NotifyServerCertificate(TlsServerCertificate serverCertificate) if (isEmpty) { - throw new TlsFatalAlert(AlertDescription.bad_certificate); + Throw.TlsFatalAlert(AlertDescription.bad_certificate); } TlsCertificate[] certPath = chain; @@ -360,7 +361,7 @@ public TlsCredentials GetClientCredentials(CertificateRequest certificateRequest return null; } - if(_client.Certificate == null || _client.CertificatePrivateKey == null) + if (_client.Certificate == null || _client.CertificatePrivateKey == null) { if (_client.AutogenerateCertificate) { @@ -387,9 +388,9 @@ public TlsCredentials GetClientCredentials(CertificateRequest certificateRequest } } - if(signatureAndHashAlgorithm == null) + if (signatureAndHashAlgorithm == null) { - throw new InvalidOperationException("DTLS Client does not support the selected certificate algorithm!"); + Throw.InvalidOperationException("DTLS Client does not support the selected certificate algorithm!"); } return new BcDefaultTlsCredentialedSigner(new TlsCryptoParameters(_context), (BcTlsCrypto)_context.Crypto, _client.CertificatePrivateKey, _client.Certificate, signatureAndHashAlgorithm); @@ -398,15 +399,14 @@ public TlsCredentials GetClientCredentials(CertificateRequest certificateRequest public static bool IsServerCertificateRsa(TlsServerCertificate serverCertificate) { - if (serverCertificate == null || serverCertificate.Certificate == null || serverCertificate.Certificate.IsEmpty) - { - throw new ArgumentNullException(nameof(serverCertificate)); - } + Throw.IfNull(serverCertificate); + Throw.IfNull(serverCertificate.Certificate); + Throw.IfFalse(serverCertificate.Certificate.IsEmpty); var certList = serverCertificate.Certificate.GetCertificateList(); if (certList == null || certList.Length == 0) { - throw new ArgumentException("Server certificate chain is empty.", nameof(serverCertificate)); + Throw.ArgumentException("Server certificate chain is empty.", nameof(serverCertificate)); } var firstCertificate = X509CertificateStructure.GetInstance(certList[0].GetEncoded()); @@ -423,7 +423,7 @@ public static bool IsServerCertificateRsa(TlsServerCertificate serverCertificate { return true; } - + return false; } } diff --git a/src/SharpSRTP/DTLS/DtlsServer.cs b/src/SharpSRTP/DTLS/DtlsServer.cs index ce4094e..c6fe242 100644 --- a/src/SharpSRTP/DTLS/DtlsServer.cs +++ b/src/SharpSRTP/DTLS/DtlsServer.cs @@ -46,9 +46,9 @@ public class DtlsServer : DefaultTlsServer, IDtlsPeer public event EventHandler OnHandshakeCompleted; public event EventHandler OnAlert; - public DtlsServer(Certificate certificate = null, AsymmetricKeyParameter privateKey = null, short certificateSignatureAlgorithm = SignatureAlgorithm.ecdsa, short certificateHashAlgorithm = HashAlgorithm.sha256) : + public DtlsServer(Certificate certificate = null, AsymmetricKeyParameter privateKey = null, short certificateSignatureAlgorithm = SignatureAlgorithm.ecdsa, short certificateHashAlgorithm = HashAlgorithm.sha256) : this(new BcTlsCrypto(), certificate, privateKey, certificateSignatureAlgorithm, certificateHashAlgorithm) - { } + { } public DtlsServer(TlsCrypto crypto, Certificate certificate = null, AsymmetricKeyParameter privateKey = null, short certificateSignatureAlgorithm = SignatureAlgorithm.ecdsa, short certificateHashAlgorithm = HashAlgorithm.sha256) : base(crypto) { @@ -125,20 +125,16 @@ protected override int[] GetSupportedCipherSuites() CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, }; } - else - { - throw new InvalidOperationException($"DTLS server certificate algorithm {CertificateSignatureAlgorithm} not supported!"); - } + + Throw.InvalidOperationException($"DTLS server certificate algorithm {CertificateSignatureAlgorithm} not supported!"); + return null!; } public virtual DtlsTransport DoHandshake(out string handshakeError, DatagramTransport datagramTransport, DtlsRequest request = null) { lock (_syncRoot) { - if (datagramTransport == null) - { - throw new ArgumentNullException(nameof(datagramTransport)); - } + Throw.IfNull(datagramTransport); DtlsTransport transport = null; @@ -222,7 +218,7 @@ public override int GetHandshakeTimeoutMillis() public override CertificateRequest GetCertificateRequest() { - short[] certificateTypes = new short[]{ ClientCertificateType.ecdsa_sign, ClientCertificateType.rsa_sign }; + short[] certificateTypes = new short[] { ClientCertificateType.ecdsa_sign, ClientCertificateType.rsa_sign }; IList serverSigAlgs = null; if (TlsUtilities.IsSignatureAlgorithmsExtensionAllowed(m_context.ServerVersion)) @@ -284,7 +280,7 @@ public override void ProcessClientExtensions(IDictionary clientExte { if (m_context.SecurityParameters.ClientRandom == null) { - throw new TlsFatalAlert(AlertDescription.internal_error); + Throw.TlsFatalAlert(AlertDescription.internal_error); } base.ProcessClientExtensions(clientExtensions); @@ -294,7 +290,7 @@ public override IDictionary GetServerExtensions() { if (m_context.SecurityParameters.ServerRandom == null) { - throw new TlsFatalAlert(AlertDescription.internal_error); + Throw.TlsFatalAlert(AlertDescription.internal_error); } return base.GetServerExtensions(); @@ -304,7 +300,7 @@ public override void GetServerExtensionsForConnection(IDictionary s { if (m_context.SecurityParameters.ServerRandom == null) { - throw new TlsFatalAlert(AlertDescription.internal_error); + Throw.TlsFatalAlert(AlertDescription.internal_error); } base.GetServerExtensionsForConnection(serverExtensions); @@ -327,7 +323,7 @@ protected override TlsCredentialedSigner GetECDsaSignerCredentials() if (Certificate == null || CertificatePrivateKey == null) { - throw new InvalidOperationException("DTLS server ECDsa certificate not set!"); + Throw.InvalidOperationException("DTLS server ECDsa certificate not set!"); } foreach (SignatureAndHashAlgorithm alg in clientSigAlgs) @@ -341,7 +337,7 @@ protected override TlsCredentialedSigner GetECDsaSignerCredentials() if (signatureAndHashAlgorithm == null) { - throw new InvalidOperationException("DTLS Client does not support the selected certificate algorithm!"); + Throw.InvalidOperationException("DTLS Client does not support the selected certificate algorithm!"); } return new BcDefaultTlsCredentialedSigner(new TlsCryptoParameters(m_context), (BcTlsCrypto)m_context.Crypto, CertificatePrivateKey, Certificate, signatureAndHashAlgorithm); @@ -354,7 +350,7 @@ protected override TlsCredentialedSigner GetRsaSignerCredentials() if (Certificate == null || CertificatePrivateKey == null) { - throw new InvalidOperationException("DTLS server RSA certificate not set!"); + Throw.InvalidOperationException("DTLS server RSA certificate not set!"); } foreach (SignatureAndHashAlgorithm alg in clientSigAlgs) @@ -366,9 +362,9 @@ protected override TlsCredentialedSigner GetRsaSignerCredentials() } } - if(signatureAndHashAlgorithm == null) + if (signatureAndHashAlgorithm == null) { - throw new InvalidOperationException("DTLS Client does not support the selected certificate algorithm!"); + Throw.InvalidOperationException("DTLS Client does not support the selected certificate algorithm!"); } return new BcDefaultTlsCredentialedSigner(new TlsCryptoParameters(m_context), (BcTlsCrypto)m_context.Crypto, CertificatePrivateKey, Certificate, signatureAndHashAlgorithm); diff --git a/src/SharpSRTP/DTLSSRTP/DtlsSrtpClient.cs b/src/SharpSRTP/DTLSSRTP/DtlsSrtpClient.cs index 97d46e3..8c174b2 100644 --- a/src/SharpSRTP/DTLSSRTP/DtlsSrtpClient.cs +++ b/src/SharpSRTP/DTLSSRTP/DtlsSrtpClient.cs @@ -69,10 +69,7 @@ public void SetMKI(byte[] mki) } else { - if (mki.Length > 255) - { - throw new ArgumentOutOfRangeException(nameof(mki)); - } + Throw.IfGreaterThan(mki.Length, 255); MkiLength = mki.Length; } @@ -119,20 +116,20 @@ public override void ProcessServerExtensions(IDictionary serverExte int[] clientSupportedProfiles = GetSupportedProtectionProfiles(); if (serverSrtpExtension.ProtectionProfiles.Length != 1) { - throw new TlsFatalAlert(AlertDescription.internal_error); + Throw.TlsFatalAlert(AlertDescription.internal_error); } // verify that the server has selected a profile we support int selectedProfile = serverSrtpExtension.ProtectionProfiles[0]; if (!clientSupportedProfiles.Contains(selectedProfile)) { - throw new TlsFatalAlert(AlertDescription.internal_error); + Throw.TlsFatalAlert(AlertDescription.internal_error); } // verify the mki sent by the server matches our mki if (_srtpData.Mki != null && serverSrtpExtension.Mki != null && !Enumerable.SequenceEqual(_srtpData.Mki, serverSrtpExtension.Mki)) { - throw new TlsFatalAlert(AlertDescription.illegal_parameter); + Throw.TlsFatalAlert(AlertDescription.illegal_parameter); } // store the server extension as it contains the selected profile @@ -151,7 +148,7 @@ public virtual SrtpSessionContext CreateSessionContext(SecurityParameters securi // this should only be called from OnHandshakeCompleted so we should still have _srtpData from the connection if (m_context == null) { - throw new InvalidOperationException(); + Throw.InvalidOperationException(); } int selectedProtectionProfile = _srtpData.ProtectionProfiles[0]; diff --git a/src/SharpSRTP/DTLSSRTP/DtlsSrtpKeys.cs b/src/SharpSRTP/DTLSSRTP/DtlsSrtpKeys.cs index 77928fc..beab040 100644 --- a/src/SharpSRTP/DTLSSRTP/DtlsSrtpKeys.cs +++ b/src/SharpSRTP/DTLSSRTP/DtlsSrtpKeys.cs @@ -27,25 +27,37 @@ namespace SharpSRTP.DTLSSRTP public class DtlsSrtpKeys { public SrtpProtectionProfileConfiguration ProtectionProfile { get; } - public byte[] Mki { get; } + public ReadOnlyMemory Mki { get; } - public byte[] ClientWriteMasterKey { get; } - public byte[] ClientWriteMasterSalt { get; } - public byte[] ServerWriteMasterKey { get; } - public byte[] ServerWriteMasterSalt { get; } + public ReadOnlyMemory ClientWriteMasterKey { get; } + public ReadOnlyMemory ClientWriteMasterSalt { get; } + public ReadOnlyMemory ServerWriteMasterKey { get; } + public ReadOnlyMemory ServerWriteMasterSalt { get; } - public DtlsSrtpKeys(SrtpProtectionProfileConfiguration protectionProfile, byte[] mki = null) + public DtlsSrtpKeys( + SrtpProtectionProfileConfiguration protectionProfile, + ReadOnlyMemory clientWriteMasterKey, + ReadOnlyMemory clientWriteMasterSalt, + ReadOnlyMemory serverWriteMasterKey, + ReadOnlyMemory serverWriteMasterSalt, + ReadOnlyMemory mki = default) { - this.ProtectionProfile = protectionProfile ?? throw new ArgumentNullException(nameof(protectionProfile)); - this.Mki = mki; + Throw.IfNull(protectionProfile); int cipherKeyLen = protectionProfile.CipherKeyLength >> 3; int cipherSaltLen = protectionProfile.CipherSaltLength >> 3; - this.ClientWriteMasterKey = new byte[cipherKeyLen]; - this.ClientWriteMasterSalt = new byte[cipherSaltLen]; - this.ServerWriteMasterKey = new byte[cipherKeyLen]; - this.ServerWriteMasterSalt = new byte[cipherSaltLen]; + Throw.ThrowIfNotEqual(clientWriteMasterKey.Length, cipherKeyLen); + Throw.ThrowIfNotEqual(serverWriteMasterKey.Length, cipherKeyLen); + Throw.ThrowIfNotEqual(clientWriteMasterSalt.Length, cipherSaltLen); + Throw.ThrowIfNotEqual(serverWriteMasterSalt.Length, cipherSaltLen); + + this.ProtectionProfile = protectionProfile; + this.Mki = mki; + this.ClientWriteMasterKey = clientWriteMasterKey; + this.ClientWriteMasterSalt = clientWriteMasterSalt; + this.ServerWriteMasterKey = serverWriteMasterKey; + this.ServerWriteMasterSalt = serverWriteMasterSalt; } } } diff --git a/src/SharpSRTP/DTLSSRTP/DtlsSrtpProtocol.cs b/src/SharpSRTP/DTLSSRTP/DtlsSrtpProtocol.cs index 9b087f7..86c4138 100644 --- a/src/SharpSRTP/DTLSSRTP/DtlsSrtpProtocol.cs +++ b/src/SharpSRTP/DTLSSRTP/DtlsSrtpProtocol.cs @@ -93,7 +93,7 @@ public static DtlsSrtpKeys CreateMasterKeys(int protectionProfile, byte[] mki, S // verify that we have extended master secret before computing the keys if (!dtlsSecurityParameters.IsExtendedMasterSecret && requireExtendedMasterSecret) { - throw new InvalidOperationException(); + Throw.InvalidOperationException(); } // SRTP key derivation as described here https://datatracker.ietf.org/doc/html/rfc5764 @@ -135,42 +135,50 @@ public static DtlsSrtpKeys CreateMasterKeys(int protectionProfile, byte[] mki, b { var srtpSecurityParams = DtlsProtectionProfiles[protectionProfile]; - if(sharedSecret == null) - { - throw new ArgumentNullException(nameof(sharedSecret)); - } + Throw.IfNull(sharedSecret); int sharedSecretLength = (2 * (srtpSecurityParams.CipherKeyLength + srtpSecurityParams.CipherSaltLength)) >> 3; - if(sharedSecret.Length < sharedSecretLength) - { - throw new ArgumentException("Invalid shared secret length.", nameof(sharedSecret)); - } - DtlsSrtpKeys keys = new DtlsSrtpKeys(srtpSecurityParams, mki); + Throw.IfLessThan(sharedSecret.Length, sharedSecretLength); + + var cipherKeyLen = srtpSecurityParams.CipherKeyLength >> 3; + var cipherSaltLen = srtpSecurityParams.CipherSaltLength >> 3; if (srtpSecurityParams.Cipher >= SrtpCiphers.DOUBLE_AEAD_AES_128_GCM_AEAD_AES_128_GCM) { + var ClientWriteMasterKey = GC.AllocateUninitializedArray(cipherKeyLen); + var ClientWriteMasterSalt = GC.AllocateUninitializedArray(cipherSaltLen); + var ServerWriteMasterKey = GC.AllocateUninitializedArray(cipherKeyLen); + var ServerWriteMasterSalt = GC.AllocateUninitializedArray(cipherSaltLen); + // we have to maintain separation of the inner and outer keys according to RFC8723 // | - Buffer.BlockCopy(sharedSecret, 0, keys.ClientWriteMasterKey, 0, keys.ClientWriteMasterKey.Length / 2); // inner - Buffer.BlockCopy(sharedSecret, sharedSecretLength / 2, keys.ClientWriteMasterKey, keys.ClientWriteMasterKey.Length / 2, keys.ClientWriteMasterKey.Length / 2); // outer - Buffer.BlockCopy(sharedSecret, keys.ClientWriteMasterKey.Length / 2, keys.ServerWriteMasterKey, 0, keys.ServerWriteMasterKey.Length / 2); // inner - Buffer.BlockCopy(sharedSecret, sharedSecretLength / 2 + keys.ClientWriteMasterKey.Length / 2, keys.ServerWriteMasterKey, keys.ServerWriteMasterKey.Length / 2, keys.ServerWriteMasterKey.Length / 2); // outer - Buffer.BlockCopy(sharedSecret, keys.ClientWriteMasterKey.Length / 2 + keys.ServerWriteMasterKey.Length / 2, keys.ClientWriteMasterSalt, 0, keys.ClientWriteMasterSalt.Length / 2); // inner - Buffer.BlockCopy(sharedSecret, sharedSecretLength / 2 + keys.ClientWriteMasterKey.Length / 2 + keys.ServerWriteMasterKey.Length / 2, keys.ClientWriteMasterSalt, keys.ClientWriteMasterSalt.Length / 2, keys.ClientWriteMasterSalt.Length / 2); // outer - Buffer.BlockCopy(sharedSecret, keys.ClientWriteMasterKey.Length / 2 + keys.ServerWriteMasterKey.Length / 2 + keys.ClientWriteMasterSalt.Length / 2, keys.ServerWriteMasterSalt, 0, keys.ServerWriteMasterSalt.Length / 2); // inner - Buffer.BlockCopy(sharedSecret, sharedSecretLength / 2 + keys.ClientWriteMasterKey.Length / 2 + keys.ServerWriteMasterKey.Length / 2 + keys.ClientWriteMasterSalt.Length / 2, keys.ServerWriteMasterSalt, keys.ServerWriteMasterSalt.Length / 2, keys.ServerWriteMasterSalt.Length / 2); // outer + Buffer.BlockCopy(sharedSecret, 0, ClientWriteMasterKey, 0, ClientWriteMasterKey.Length / 2); // inner + Buffer.BlockCopy(sharedSecret, sharedSecretLength / 2, ClientWriteMasterKey, ClientWriteMasterKey.Length / 2, ClientWriteMasterKey.Length / 2); // outer + Buffer.BlockCopy(sharedSecret, ClientWriteMasterKey.Length / 2, ServerWriteMasterKey, 0, ServerWriteMasterKey.Length / 2); // inner + Buffer.BlockCopy(sharedSecret, sharedSecretLength / 2 + ClientWriteMasterKey.Length / 2, ServerWriteMasterKey, ServerWriteMasterKey.Length / 2, ServerWriteMasterKey.Length / 2); // outer + Buffer.BlockCopy(sharedSecret, ClientWriteMasterKey.Length / 2 + ServerWriteMasterKey.Length / 2, ClientWriteMasterSalt, 0, ClientWriteMasterSalt.Length / 2); // inner + Buffer.BlockCopy(sharedSecret, sharedSecretLength / 2 + ClientWriteMasterKey.Length / 2 + ServerWriteMasterKey.Length / 2, ClientWriteMasterSalt, ClientWriteMasterSalt.Length / 2, ClientWriteMasterSalt.Length / 2); // outer + Buffer.BlockCopy(sharedSecret, ClientWriteMasterKey.Length / 2 + ServerWriteMasterKey.Length / 2 + ClientWriteMasterSalt.Length / 2, ServerWriteMasterSalt, 0, ServerWriteMasterSalt.Length / 2); // inner + Buffer.BlockCopy(sharedSecret, sharedSecretLength / 2 + ClientWriteMasterKey.Length / 2 + ServerWriteMasterKey.Length / 2 + ClientWriteMasterSalt.Length / 2, ServerWriteMasterSalt, ServerWriteMasterSalt.Length / 2, ServerWriteMasterSalt.Length / 2); // outer + + return new DtlsSrtpKeys(srtpSecurityParams, ClientWriteMasterKey, ClientWriteMasterSalt, ServerWriteMasterKey, ServerWriteMasterSalt, mki); } else { + var ClientWriteMasterKey = GC.AllocateUninitializedArray(cipherKeyLen); + var ClientWriteMasterSalt = GC.AllocateUninitializedArray(cipherSaltLen); + var ServerWriteMasterKey = GC.AllocateUninitializedArray(cipherKeyLen); + var ServerWriteMasterSalt = GC.AllocateUninitializedArray(cipherSaltLen); + // - Buffer.BlockCopy(sharedSecret, 0, keys.ClientWriteMasterKey, 0, keys.ClientWriteMasterKey.Length); - Buffer.BlockCopy(sharedSecret, keys.ClientWriteMasterKey.Length, keys.ServerWriteMasterKey, 0, keys.ServerWriteMasterKey.Length); - Buffer.BlockCopy(sharedSecret, keys.ClientWriteMasterKey.Length + keys.ServerWriteMasterKey.Length, keys.ClientWriteMasterSalt, 0, keys.ClientWriteMasterSalt.Length); - Buffer.BlockCopy(sharedSecret, keys.ClientWriteMasterKey.Length + keys.ServerWriteMasterKey.Length + keys.ClientWriteMasterSalt.Length, keys.ServerWriteMasterSalt, 0, keys.ServerWriteMasterSalt.Length); - } + Buffer.BlockCopy(sharedSecret, 0, ClientWriteMasterKey, 0, ClientWriteMasterKey.Length); + Buffer.BlockCopy(sharedSecret, ClientWriteMasterKey.Length, ServerWriteMasterKey, 0, ServerWriteMasterKey.Length); + Buffer.BlockCopy(sharedSecret, ClientWriteMasterKey.Length + ServerWriteMasterKey.Length, ClientWriteMasterSalt, 0, ClientWriteMasterSalt.Length); + Buffer.BlockCopy(sharedSecret, ClientWriteMasterKey.Length + ServerWriteMasterKey.Length + ClientWriteMasterSalt.Length, ServerWriteMasterSalt, 0, ServerWriteMasterSalt.Length); - return keys; + return new DtlsSrtpKeys(srtpSecurityParams, ClientWriteMasterKey, ClientWriteMasterSalt, ServerWriteMasterKey, ServerWriteMasterSalt, mki); + } } public static byte[] GenerateMki(int length) diff --git a/src/SharpSRTP/DTLSSRTP/DtlsSrtpServer.cs b/src/SharpSRTP/DTLSSRTP/DtlsSrtpServer.cs index 68ddd1b..50a3858 100644 --- a/src/SharpSRTP/DTLSSRTP/DtlsSrtpServer.cs +++ b/src/SharpSRTP/DTLSSRTP/DtlsSrtpServer.cs @@ -101,7 +101,7 @@ public override void ProcessClientExtensions(IDictionary clientExte int[] mutuallySupportedProfiles = clientSrtpExtension.ProtectionProfiles.Where(x => serverSupportedProfiles.Contains(x)).ToArray(); if (mutuallySupportedProfiles.Length == 0) { - throw new TlsFatalAlert(AlertDescription.internal_error); + Throw.TlsFatalAlert(AlertDescription.internal_error); } int selectedProfile = mutuallySupportedProfiles.OrderBy(x => Array.IndexOf(serverSupportedProfiles, x)).First(); // Choose the highest priority profile supported by the server @@ -120,7 +120,7 @@ public virtual SrtpSessionContext CreateSessionContext(SecurityParameters securi // this should only be called from OnHandshakeCompleted so we should still have _srtpData from the connection if (m_context == null) { - throw new InvalidOperationException(); + Throw.InvalidOperationException(); } int selectedProtectionProfile = _srtpData.ProtectionProfiles[0]; diff --git a/src/SharpSRTP/DTLSSRTP/IDtlsSrtpPeer.cs b/src/SharpSRTP/DTLSSRTP/IDtlsSrtpPeer.cs index 355cd65..8a6da60 100644 --- a/src/SharpSRTP/DTLSSRTP/IDtlsSrtpPeer.cs +++ b/src/SharpSRTP/DTLSSRTP/IDtlsSrtpPeer.cs @@ -29,14 +29,18 @@ namespace SharpSRTP.DTLSSRTP public class DtlsSessionStartedEventArgs : EventArgs { public SrtpSessionContext Context { get; private set; } - public Certificate PeerCertificate { get; private set; } + public Certificate PeerCertificate { get; private set; } public DatagramTransport ClientDatagramTransport { get; private set; } public DtlsSessionStartedEventArgs(SrtpSessionContext context, Certificate peerCertificate, DatagramTransport clientDatagramTransport) { - this.Context = context ?? throw new ArgumentNullException(nameof(context)); - this.PeerCertificate = peerCertificate ?? throw new ArgumentNullException(nameof(peerCertificate)); - this.ClientDatagramTransport = clientDatagramTransport ?? throw new ArgumentNullException(nameof(clientDatagramTransport)); + Throw.IfNull(context); + Throw.IfNull(peerCertificate); + Throw.IfNull(clientDatagramTransport); + + this.Context = context; + this.PeerCertificate = peerCertificate; + this.ClientDatagramTransport = clientDatagramTransport; } } diff --git a/src/SharpSRTP/PolyfillExtensions.cs b/src/SharpSRTP/PolyfillExtensions.cs new file mode 100644 index 0000000..1814a62 --- /dev/null +++ b/src/SharpSRTP/PolyfillExtensions.cs @@ -0,0 +1,44 @@ +using System; +using System.Runtime.CompilerServices; + +#if !NET8_0_OR_GREATER +internal sealed class SkipLocalsInitAttribute : Attribute { } + +internal sealed class DoesNotReturnAttribute : Attribute { } + +internal sealed class NotNullAttribute : Attribute { } + +[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] +internal sealed class CallerArgumentExpressionAttribute : Attribute +{ + public CallerArgumentExpressionAttribute(string parameterName) + { + ParameterName = parameterName; + } + + public string ParameterName { get; } +} + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Struct, Inherited = false)] +public sealed class StackTraceHiddenAttribute : Attribute +{ + /// + /// Initializes a new instance of the class. + /// + public StackTraceHiddenAttribute() { } +} + +internal static partial class GC +{ + /// + /// Allocate an array while skipping zero-initialization if possible. + /// + /// Specifies the type of the array element. + /// Specifies the length of the array. + [MethodImpl(MethodImplOptions.AggressiveInlining)] // forced to ensure no perf drop for small memory buffers (hot path) + public static T[] AllocateUninitializedArray(int length) // T[] rather than T?[] to match `new T[length]` behavior + { + return new T[length]; + } +} +#endif diff --git a/src/SharpSRTP/SRTP/Authentication/HMAC.cs b/src/SharpSRTP/SRTP/Authentication/HMAC.cs index a8a277f..d2a3c17 100644 --- a/src/SharpSRTP/SRTP/Authentication/HMAC.cs +++ b/src/SharpSRTP/SRTP/Authentication/HMAC.cs @@ -20,19 +20,40 @@ // SOFTWARE. using Org.BouncyCastle.Crypto.Macs; +using System; +using System.Buffers; +#if NET8_0_OR_GREATER +using Bytes = System.Span; +#else +using Bytes = byte[]; +#endif namespace SharpSRTP.SRTP.Authentication { public static class HMAC { - public static byte[] GenerateAuthTag(HMac hmac, byte[] payload, int offset, int length) + public static void GenerateAuthTag(HMac hmac, ReadOnlySpan payload, Bytes output) { - hmac.BlockUpdate(payload, offset, length); + Throw.IfLessThan(output.Length, hmac.GetMacSize()); - byte[] output = new byte[hmac.GetMacSize()]; - hmac.DoFinal(output, 0); +#if NET8_0_OR_GREATER + hmac.BlockUpdate(payload); + + hmac.DoFinal(output); +#else + var buffer = ArrayPool.Shared.Rent(payload.Length); + payload.CopyTo(buffer); + try + { + hmac.BlockUpdate(buffer, 0, payload.Length); + } + finally + { + ArrayPool.Shared.Return(buffer); + } - return output; + hmac.DoFinal(output, 0); +#endif } } } diff --git a/src/SharpSRTP/SRTP/Encryption/AEAD.cs b/src/SharpSRTP/SRTP/Encryption/AEAD.cs index 39cc92a..5ac89c8 100644 --- a/src/SharpSRTP/SRTP/Encryption/AEAD.cs +++ b/src/SharpSRTP/SRTP/Encryption/AEAD.cs @@ -22,47 +22,86 @@ using Org.BouncyCastle.Crypto.Modes; using Org.BouncyCastle.Crypto.Parameters; using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Runtime.CompilerServices; + +#if NET8_0_OR_GREATER +using ReadOnlyBytes = System.ReadOnlySpan; +using Bytes = System.Span; +#else +using ReadOnlyBytes = byte[]; +using Bytes = byte[]; +#endif namespace SharpSRTP.SRTP.Encryption { public static class AEAD { - public static void Encrypt(IAeadBlockCipher engine, bool encrypt, byte[] payload, int offset, int length, byte[] iv, byte[] K_e, int N_tag, byte[] associatedData) + public const int BLOCK_SIZE = 12; + + [SkipLocalsInit] + public static void Encrypt(Span output, IAeadBlockCipher engine, bool encrypt, ReadOnlySpan payload, byte[] iv, ReadOnlyMemory K_e, int N_tag, byte[] associatedData) { - int payloadSize = length - offset; + var payloadSize = payload.Length; + var expectedLength = engine.GetOutputSize(payloadSize); - int expectedLength = engine.GetOutputSize(payloadSize); - if (offset + expectedLength > payload.Length) - { - throw new ArgumentOutOfRangeException("Payload is too small!"); - } + Throw.IfLessThan(output.Length, expectedLength); - var parameters = new AeadParameters(new KeyParameter(K_e), N_tag << 3, iv, associatedData); + var parameters = new AeadParameters(K_e.ToKeyParameter(), N_tag << 3, iv, associatedData); engine.Init(encrypt, parameters); - int len = engine.ProcessBytes(payload, offset, payloadSize, payload, offset); - +#if NET8_0_OR_GREATER + var len = engine.ProcessBytes(payload, output); + // throws when the MAC fails to match - engine.DoFinal(payload, offset + len); + len += engine.DoFinal(output.Slice(len)); +#else + var bytes = ArrayPool.Shared.Rent(Math.Max(payloadSize, output.Length)); + try + { + payload.CopyTo(bytes); + + var len = engine.ProcessBytes(bytes, 0, payloadSize, bytes, 0); + + // throws when the MAC fails to match + len += engine.DoFinal(bytes, len); + + // Copy result to output span + bytes.AsSpan(0, len).CopyTo(output); + } + finally + { + ArrayPool.Shared.Return(bytes); + } +#endif } - public static byte[] GenerateMessageKeyIV(byte[] k_s, uint ssrc, ulong index) + public static void GenerateMessageKeyIV(Span iv, ReadOnlySpan k_s, uint ssrc, ulong index) { - byte[] iv = new byte[12]; - Buffer.BlockCopy(k_s, 0, iv, 0, 12); - - iv[2] ^= (byte)((ssrc >> 24) & 0xFF); - iv[3] ^= (byte)((ssrc >> 16) & 0xFF); - iv[4] ^= (byte)((ssrc >> 8) & 0xFF); - iv[5] ^= (byte)(ssrc & 0xFF); - iv[6] ^= (byte)((index >> 40) & 0xFF); - iv[7] ^= (byte)((index >> 32) & 0xFF); - iv[8] ^= (byte)((index >> 24) & 0xFF); - iv[9] ^= (byte)((index >> 16) & 0xFF); - iv[10] ^= (byte)((index >> 8) & 0xFF); - iv[11] ^= (byte)(index & 0xFF); - - return iv; + if (iv.Length != BLOCK_SIZE) + { + Throw.ArgumentException($"IV Bytes must be exactly {BLOCK_SIZE} bytes", nameof(iv)); + } + + k_s.Slice(0, 12).CopyTo(iv); + + // XOR in SSRC (big-endian) + var ssrcSpan = iv.Slice(2, 4); + var ssrcVal = BinaryPrimitives.ReadUInt32BigEndian(ssrcSpan); + ssrcVal ^= ssrc; + BinaryPrimitives.WriteUInt32BigEndian(ssrcSpan, ssrcVal); + + // XOR in index high 48bits using big-endian 32-bit and 16-bit segments + var hiSpan = iv.Slice(6, 4); + var hi = BinaryPrimitives.ReadUInt32BigEndian(hiSpan); + hi ^= (uint)(index >> 16); + BinaryPrimitives.WriteUInt32BigEndian(hiSpan, hi); + + var loSpan = iv.Slice(10, 2); + var lo = BinaryPrimitives.ReadUInt16BigEndian(loSpan); + lo ^= (ushort)(index & 0xFFFF); + BinaryPrimitives.WriteUInt16BigEndian(loSpan, lo); } } } diff --git a/src/SharpSRTP/SRTP/Encryption/CTR.cs b/src/SharpSRTP/SRTP/Encryption/CTR.cs index 93a0b4d..8d0da90 100644 --- a/src/SharpSRTP/SRTP/Encryption/CTR.cs +++ b/src/SharpSRTP/SRTP/Encryption/CTR.cs @@ -21,6 +21,9 @@ using Org.BouncyCastle.Crypto; using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Numerics; namespace SharpSRTP.SRTP.Encryption { @@ -28,9 +31,10 @@ public static class CTR { public const int BLOCK_SIZE = 16; - public static byte[] GenerateSessionKeyIV(byte[] masterSalt, ulong index, ulong kdr, byte label) + public static void GenerateSessionKeyIV(Span iv, ReadOnlySpan masterSalt, ulong index, ulong kdr, byte label) { - byte[] iv = new byte[BLOCK_SIZE]; + if (iv.Length != BLOCK_SIZE) + Throw.ArgumentException($"IV buffer must be exactly {BLOCK_SIZE} bytes", nameof(iv)); // RFC 3711 - 4.3.1 // Key derivation SHALL be defined as follows in terms of