From 97ff1d0ee89768efa90a852adad7e961c0f63a33 Mon Sep 17 00:00:00 2001 From: DV Date: Mon, 18 Mar 2024 21:35:15 +0300 Subject: [PATCH] Truncating decompression buffer, porting unit tests across different targets --- UnitTests/ArraysTest.cs | 14 ++++++++++ UnitTestsNet40/ArraysTest.cs | 13 +++++++++ UnitTestsNet40/SecurityVulnerabilitiesTest.cs | 28 +++++++++++++++++++ UnitTestsNet40/SettingsTest.cs | 4 ++- UnitTestsNet46/ArraysTest.cs | 13 +++++++++ UnitTestsNet46/SecurityVulnerabilitiesTest.cs | 2 +- jose-jwt/compression/DeflateCompression.cs | 2 +- jose-jwt/jose-jwt.net40.csproj | 1 - jose-jwt/util/Arrays.cs | 15 ++++++++++ jose-jwt/util/Ensure.cs | 2 +- 10 files changed, 89 insertions(+), 5 deletions(-) diff --git a/UnitTests/ArraysTest.cs b/UnitTests/ArraysTest.cs index 31171609..f70399cf 100644 --- a/UnitTests/ArraysTest.cs +++ b/UnitTests/ArraysTest.cs @@ -126,5 +126,19 @@ public void RightmostBits() Assert.Equal(new byte[] { 8, 9 }, Arrays.RightmostBits(data, 16)); Assert.Equal(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, Arrays.RightmostBits(data, 72)); } + + [Fact] + public void Truncate() + { + // given + byte[] data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + // then + Assert.Equal(new byte[] {}, Arrays.Truncate(data, 0)); + Assert.Equal(new byte[] { 0 }, Arrays.Truncate(data, 1)); + Assert.Equal(new byte[] { 0, 1, 2, 3, 4 }, Arrays.Truncate(data, 5)); + Assert.Equal(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, Arrays.Truncate(data, 10)); + } + } } \ No newline at end of file diff --git a/UnitTestsNet40/ArraysTest.cs b/UnitTestsNet40/ArraysTest.cs index ee5e1252..cf61f813 100644 --- a/UnitTestsNet40/ArraysTest.cs +++ b/UnitTestsNet40/ArraysTest.cs @@ -128,5 +128,18 @@ public void RightmostBits() Assert.That(Arrays.RightmostBits(data,16), Is.EqualTo(new byte[] { 8, 9 })); Assert.That(Arrays.RightmostBits(data, 72), Is.EqualTo(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 })); } + + [Test] + public void Truncate() + { + // given + byte[] data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + // then + Assert.That(Arrays.Truncate(data, 0), Is.EqualTo(new byte[] {})); + Assert.That(Arrays.Truncate(data, 1), Is.EqualTo(new byte[] { 0 })); + Assert.That(Arrays.Truncate(data, 5), Is.EqualTo(new byte[] { 0, 1, 2, 3, 4 })); + Assert.That(Arrays.Truncate(data, 10), Is.EqualTo(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 })); + } } } \ No newline at end of file diff --git a/UnitTestsNet40/SecurityVulnerabilitiesTest.cs b/UnitTestsNet40/SecurityVulnerabilitiesTest.cs index 6c9490ed..04132b97 100644 --- a/UnitTestsNet40/SecurityVulnerabilitiesTest.cs +++ b/UnitTestsNet40/SecurityVulnerabilitiesTest.cs @@ -128,5 +128,33 @@ public void BitLengthIntegerOverflow() //if we reach that point HMAC check was bypassed although the decrypted data is different Assert.Fail("JoseException should be raised."); } + + [Test] + public void DeflateBomb() + { + // given + byte[] x = Base64Url.Decode("weNJy2HscCSM6AEDTDg04biOvhFhyyWvOHQfeF_PxMQ"); + byte[] y = Base64Url.Decode("e8lnCO-AlStT-NJVX-crhB7QRYhiix03illJOVAOyck"); + byte[] d = Base64Url.Decode("VEmDZpDXXK8p8N0Cndsxs924q6nS1RXFASRl6BfUqdw"); + + var privateKey = EccKey.New(x, y, d, usage: CngKeyUsages.KeyAgreement); + var publicKey = EccKey.New(x, y, usage: CngKeyUsages.KeyAgreement); + + string strU = new string('U', 400000000); + string strUU = new string('U', 100000000); + string payload = $@"{{""U"":""{strU}"", ""UU"":""{strUU}""}}"; + string bomb = Jose.JWT.Encode(payload, publicKey, JweAlgorithm.ECDH_ES, JweEncryption.A128GCM, JweCompression.DEF); + + // when + try + { + string decoded = Jose.JWT.Decode(bomb, privateKey); + Assert.Fail("Should fail with NotSupportedException"); + } + catch (JoseException e) + { + Console.Out.WriteLine(e.ToString()); + } + } } } \ No newline at end of file diff --git a/UnitTestsNet40/SettingsTest.cs b/UnitTestsNet40/SettingsTest.cs index 08a0a93e..13b99093 100644 --- a/UnitTestsNet40/SettingsTest.cs +++ b/UnitTestsNet40/SettingsTest.cs @@ -419,7 +419,9 @@ class MockKeyManagement : DirectKeyManagement, IKeyManagement } class MockCompression : DeflateCompression, ICompression - { + { + public MockCompression(): base(250*1024) {} + public bool CompressCalled { get; set; } public bool DecompressCalled { get; set; } diff --git a/UnitTestsNet46/ArraysTest.cs b/UnitTestsNet46/ArraysTest.cs index 31171609..50e9705c 100644 --- a/UnitTestsNet46/ArraysTest.cs +++ b/UnitTestsNet46/ArraysTest.cs @@ -126,5 +126,18 @@ public void RightmostBits() Assert.Equal(new byte[] { 8, 9 }, Arrays.RightmostBits(data, 16)); Assert.Equal(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, Arrays.RightmostBits(data, 72)); } + + [Fact] + public void Truncate() + { + // given + byte[] data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + // then + Assert.Equal(new byte[] {}, Arrays.Truncate(data, 0)); + Assert.Equal(new byte[] { 0 }, Arrays.Truncate(data, 1)); + Assert.Equal(new byte[] { 0, 1, 2, 3, 4 }, Arrays.Truncate(data, 5)); + Assert.Equal(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, Arrays.Truncate(data, 10)); + } } } \ No newline at end of file diff --git a/UnitTestsNet46/SecurityVulnerabilitiesTest.cs b/UnitTestsNet46/SecurityVulnerabilitiesTest.cs index abb4ecc8..3b6692d5 100644 --- a/UnitTestsNet46/SecurityVulnerabilitiesTest.cs +++ b/UnitTestsNet46/SecurityVulnerabilitiesTest.cs @@ -230,7 +230,7 @@ public void DeflateBomb() // when try { - string decoded = Jose.JWT.Decode(bomb, privateKey, JwsAlgorithm.RS256); + string decoded = Jose.JWT.Decode(bomb, privateKey); Assert.True(false, "Should fail with NotSupportedException"); } catch (JoseException e) diff --git a/jose-jwt/compression/DeflateCompression.cs b/jose-jwt/compression/DeflateCompression.cs index faa19ac8..ebae6490 100644 --- a/jose-jwt/compression/DeflateCompression.cs +++ b/jose-jwt/compression/DeflateCompression.cs @@ -42,7 +42,7 @@ public byte[] Decompress(byte[] compressedText) } } - return ms.ToArray(); + return Arrays.Truncate(ms.ToArray(), ms.Position); } } catch(NotSupportedException e) diff --git a/jose-jwt/jose-jwt.net40.csproj b/jose-jwt/jose-jwt.net40.csproj index e94b988e..13b4caa7 100644 --- a/jose-jwt/jose-jwt.net40.csproj +++ b/jose-jwt/jose-jwt.net40.csproj @@ -80,7 +80,6 @@ - diff --git a/jose-jwt/util/Arrays.cs b/jose-jwt/util/Arrays.cs index 879e41c9..9e3bda6b 100644 --- a/jose-jwt/util/Arrays.cs +++ b/jose-jwt/util/Arrays.cs @@ -199,5 +199,20 @@ public static byte[] RightmostBits(byte[] data, int lengthBits) return result; } + + public static byte[] Truncate(byte[] data, long size) + { + Ensure.MinValue(size, 0, "Truncate() can't go negative size, but was given {0}", size); + Ensure.MaxValue(size, data.Length, "Truncate() can't go beyond array size {0}, but was given {1}", data.Length, size); + Ensure.MaxValue(size, Int32.MaxValue, "Truncate() can't go beyond int32, but was given {0}", size); + + int byteCount = Convert.ToInt32(size); + + var result = new byte[byteCount]; + + Buffer.BlockCopy(data, 0, result, 0, byteCount); + + return result; + } } } diff --git a/jose-jwt/util/Ensure.cs b/jose-jwt/util/Ensure.cs index 9c7256eb..2351c3c6 100644 --- a/jose-jwt/util/Ensure.cs +++ b/jose-jwt/util/Ensure.cs @@ -56,7 +56,7 @@ public static void MinValue(long arg, long min, string msg, params object[] args throw new ArgumentException(string.Format(msg,args)); } - public static void MaxValue(int arg, long max, string msg, params object[] args) + public static void MaxValue(long arg, long max, string msg, params object[] args) { if(arg > max) throw new ArgumentException(string.Format(msg,args));