diff --git a/README.md b/README.md index 900454bc..c98ea55c 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ JWE JSON Serialization cross-tested with [JWCrypto](https://github.com/latchset/ Library is fully FIPS compliant since v2.1 ## Which version? -- v5.0 brings Linux, OSX and FreeBSD compatibility for [ECDH encryption](#ecdh-es-and-ecdh-es-with-aes-key-wrap-key-management-family-of-algorithms) as long as managed `ECDsa` keys support. And fixes cross compatibility issues with encryption over NIST P-384, P-521 curves. +- v5.0 brings Linux, OSX and FreeBSD compatibility for [ECDH encryption](#ecdh-es-and-ecdh-es-with-aes-key-wrap-key-management-family-of-algorithms) as long as managed `ECDsa` keys support. Fixes cross compatibility issues with encryption over NIST P-384, P-521 curves. And introduces new [security fixes and controls](#customizing-compression). - v4.1 added additional capabilities to manage runtime avaliable alg suite, see [Customizing library for security](#customizing-library-for-security). And also introduced default max limits for `PBKDF2` (`PBES2-*`) max iterations according to [OWASP PBKDF2 Recomendations](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2). @@ -32,7 +32,10 @@ Library is fully FIPS compliant since v2.1 - PCLCrypto based experimental project living up here: [jose-pcl](https://github.com/dvsekhvalnov/jose-pcl). ## Important upgrade notes -> :warning: **v4 -> v5 JWK EC keys now bridges to `ECDsa` by default instead of `CngKey` on .net 4.7.2+ and netstandard2.1+** +> :warning: **v4 -> v5**: +> - JWK EC keys now bridges to `ECDsa` by default instead of `CngKey` on .net 4.7.2+ and netstandard2.1+ +> - Deflate decompression is limited to 250Kb by default. Check out [customization section](#customizing-compression) if need more. + > :warning: **v3.0 -> v3.1 stricter argument validation extraHeaders argument** > @@ -1452,8 +1455,20 @@ One can use following methods to deregister any signing, encryption, key managem ```c# JWT.DefaultSettings.DeregisterJws(JwsAlgorithm.none) .DeregisterJwe(JweAlgorithm.RSA1_5) - .DeregisterJwe(JweAlgorithm.DIR); + .DeregisterJwe(JweAlgorithm.DIR) + .DeregisterCompression(JweCompression.DEF); ``` + +### Customizing compression +There were denial-of-service attacks reported on JWT libraries that supports deflate compression by constructing malicious payload that explodes in terms of RAM on decompression. See for details: https://github.com/dvsekhvalnov/jose-jwt/issues/237 + +As of v5 `jose-jwt` limits decompression buffer to 250Kb to limit memory consumption and additionaly provides a way to adjust the limit according to specific scenarios: + +``` cs + // Override compression alg with new limits (10Kb example) + Jose.JWT.DefaultSettings.RegisterCompression(JweCompression.DEF, new DeflateCompression(10 * 1024)); +``` + ### Customizing PBKDF2 As it quite easy to abuse `PBES2` family of algorithms via forging header with extra large `p2c` values, `jose-jwt` library introduced iteration count limits in v4.1 to reduce runtime exposure. @@ -1468,7 +1483,7 @@ By default, `maxIterations` is set according to [OWASP PBKDF2 Recomendations](ht If it is desired to implement different limits, it can be achieved via registering `Pbse2HmacShaKeyManagementWithAesKeyWrap` implementation with different parameters: ```c# - Jost.JWT.DefaultSettings + Jose.JWT.DefaultSettings // Pick your own min/max limits .RegisterJwe(JweAlgorithm.PBES2_HS256_A128KW, new Pbse2HmacShaKeyManagementWithAesKeyWrap(128, new AesKeyWrapManagement(128), 310000, 310000)); .RegisterJwe(JweAlgorithm.PBES2_HS384_A192KW, new Pbse2HmacShaKeyManagementWithAesKeyWrap(192, new AesKeyWrapManagement(192), 250000, 250000)); 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/UnitTests/SecurityVulnerabilitiesTest.cs b/UnitTests/SecurityVulnerabilitiesTest.cs index ec467c82..ec79a90d 100644 --- a/UnitTests/SecurityVulnerabilitiesTest.cs +++ b/UnitTests/SecurityVulnerabilitiesTest.cs @@ -168,5 +168,42 @@ public void BitLengthIntegerOverflow() Console.Out.WriteLine(e.ToString()); } } + + [Fact] + public void DeflateBomb() + { + // given + Jwk privateKey = new Jwk( + e: "AQAB", + n: "qFZv0pea_jn5Mo4qEUmStuhlulso8n1inXbEotd_zTrQp9K0RK0hf7t0K4BjKVhaiqIam4tVVQvkmYeBeYr1MmnO_0N97dMBz_7fmvyv0hgHaBdQ5mR5u3LTlHo8tjRE7-GzZmGs6jMcyj7HbXobDPQJZpqNy6JjliDVXxW8nWJDetxGBlqmTj1E1fr2RCsZLreDOPSDIedG1upz9RraShsIDzeefOcKibcAaKeeVI3rkAU8_mOauLSXv37hlk0h6sStJb3qZQXyOUkVkjXIkhvNu_ve0v7LiLT4G_OxYGzpOQcCnimKdojzNP6GtVDaMPh-QkSJE32UCos9R3wI2Q", + p: "0qaOkT174vRG3E_67gU3lgOgoT6L3pVHuu7wfrIEoxycPa5_mZVG54SgvQUofGUYEGjR0lavUAjClw9tOzcODHX8RAxkuDntAFntBxgRM-IzAy8QzeRl_cbhgVjBTAhBcxg-3VySv5GdxFyrQaIo8Oy_PPI1L4EFKZHmicBd3ts", + q: "zJPqCDKqaJH9TAGfzt6b4aNt9fpirEcdpAF1bCedFfQmUZM0LG3rMtOAIhjEXgADt5GB8ZNK3BQl8BJyMmKs57oKmbVcODERCtPqjECXXsxH-az9nzxatPvcb7imFW8OlWslwr4IIRKdEjzEYs4syQJz7k2ktqOpYI5_UfYnw1s", + d: "lJhwb0pKlB2ivyDFO6thajotClrMA3nxIiSkIUbvVr-TToFtha36gyF6w6e6YNXQXs4HhMRy1_b-nRQDk8G4_f5urd_q-pOn5u4KfmqN3Xw-lYD3ddi9qF0NLeTVUNVFASeP0FFqbPYfdNwD-LyvwjhtT_ggMOAw3mYvU5cBfz6-3uPdhl3CwQFCTgwOud_BA9p2MPMUHG82wMK_sNO1I0TYpjm7TnwNBwiKbMf-i5CKnuohgoYrEDYLeMg3f32eBljlCFNYaoCtT-mr1Ze0OTJND04vbfLotV-BBKulIpbOOSeVpKG7gJxZHmv7in7PE5_WzaxKFVoHW3wR6v_GzQ", + dp: "KTWmTGmf092AA1euOmRQ5IsfIIxQ5qGDn-FgsRh4acSOGE8L7WrTrTU4EOJyciuA0qz-50xIDbs4_j5pWx1BJVTrnhBin9vNLrVo9mtR6jmFS0ko226kOUpwEVLgtdQjobWLjtiuaMW-_Iw4gKWNptxZ6T1lBD8UWHaPiEFW2-M", + dq: "Jn0lqMkvemENEMG1eUw0c601wPOMoPD4SKTlnKWPTlQS6YISbNF5UKSuFLwoJa9HA8BifDrD-Mfpo1M1HPmnoilEWUrfwMqqdCkOlbiJQhKY8AZ16QGH50kDXhmVVa8BRWdVQWBTUzWXS5kXMaeskVzextTgymPcOAhXN-ph7MU", + qi: "sRAPigJpl8S_vsf1zhJTrHM97xRwuB26R6Tm-J8sKRPb7p5xxNlmOBBFvWmWxdto8dBElNlydSZan373yBLxzW-bZgVp-B2RKT1B3WhTYW_Vo5DLhWi84XMncJxH7avtxtF9yksaeKe0e2n3J6TTan53mDg4KF8U0OEO2ciqO9g" + ); + + Jwk publicKey = new Jwk( + e: "AQAB", + n: "qFZv0pea_jn5Mo4qEUmStuhlulso8n1inXbEotd_zTrQp9K0RK0hf7t0K4BjKVhaiqIam4tVVQvkmYeBeYr1MmnO_0N97dMBz_7fmvyv0hgHaBdQ5mR5u3LTlHo8tjRE7-GzZmGs6jMcyj7HbXobDPQJZpqNy6JjliDVXxW8nWJDetxGBlqmTj1E1fr2RCsZLreDOPSDIedG1upz9RraShsIDzeefOcKibcAaKeeVI3rkAU8_mOauLSXv37hlk0h6sStJb3qZQXyOUkVkjXIkhvNu_ve0v7LiLT4G_OxYGzpOQcCnimKdojzNP6GtVDaMPh-QkSJE32UCos9R3wI2Q" + ); + + 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.RSA_OAEP, JweEncryption.A256GCM, JweCompression.DEF); + + // when + try + { + string decoded = Jose.JWT.Decode(bomb, privateKey, JwsAlgorithm.RS256); + Assert.True(false, "Should fail with NotSupportedException"); + } + catch (JoseException e) + { + Console.Out.WriteLine(e.ToString()); + } + } } } \ No newline at end of file diff --git a/UnitTests/SettingsTest.cs b/UnitTests/SettingsTest.cs index 3c9d45d5..46802f25 100644 --- a/UnitTests/SettingsTest.cs +++ b/UnitTests/SettingsTest.cs @@ -426,6 +426,7 @@ private class MockKeyManagement : DirectKeyManagement, IKeyManagement private class MockCompression : DeflateCompression, ICompression { + public MockCompression(): base(250*1024) {} public bool CompressCalled { get; set; } public bool DecompressCalled { get; set; } 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 74fcda3a..3b6692d5 100644 --- a/UnitTestsNet46/SecurityVulnerabilitiesTest.cs +++ b/UnitTestsNet46/SecurityVulnerabilitiesTest.cs @@ -201,5 +201,42 @@ public void BitLengthIntegerOverflow() Console.Out.WriteLine(e.ToString()); } } + + [Fact] + public void DeflateBomb() + { + // given + Jwk privateKey = new Jwk( + e: "AQAB", + n: "qFZv0pea_jn5Mo4qEUmStuhlulso8n1inXbEotd_zTrQp9K0RK0hf7t0K4BjKVhaiqIam4tVVQvkmYeBeYr1MmnO_0N97dMBz_7fmvyv0hgHaBdQ5mR5u3LTlHo8tjRE7-GzZmGs6jMcyj7HbXobDPQJZpqNy6JjliDVXxW8nWJDetxGBlqmTj1E1fr2RCsZLreDOPSDIedG1upz9RraShsIDzeefOcKibcAaKeeVI3rkAU8_mOauLSXv37hlk0h6sStJb3qZQXyOUkVkjXIkhvNu_ve0v7LiLT4G_OxYGzpOQcCnimKdojzNP6GtVDaMPh-QkSJE32UCos9R3wI2Q", + p: "0qaOkT174vRG3E_67gU3lgOgoT6L3pVHuu7wfrIEoxycPa5_mZVG54SgvQUofGUYEGjR0lavUAjClw9tOzcODHX8RAxkuDntAFntBxgRM-IzAy8QzeRl_cbhgVjBTAhBcxg-3VySv5GdxFyrQaIo8Oy_PPI1L4EFKZHmicBd3ts", + q: "zJPqCDKqaJH9TAGfzt6b4aNt9fpirEcdpAF1bCedFfQmUZM0LG3rMtOAIhjEXgADt5GB8ZNK3BQl8BJyMmKs57oKmbVcODERCtPqjECXXsxH-az9nzxatPvcb7imFW8OlWslwr4IIRKdEjzEYs4syQJz7k2ktqOpYI5_UfYnw1s", + d: "lJhwb0pKlB2ivyDFO6thajotClrMA3nxIiSkIUbvVr-TToFtha36gyF6w6e6YNXQXs4HhMRy1_b-nRQDk8G4_f5urd_q-pOn5u4KfmqN3Xw-lYD3ddi9qF0NLeTVUNVFASeP0FFqbPYfdNwD-LyvwjhtT_ggMOAw3mYvU5cBfz6-3uPdhl3CwQFCTgwOud_BA9p2MPMUHG82wMK_sNO1I0TYpjm7TnwNBwiKbMf-i5CKnuohgoYrEDYLeMg3f32eBljlCFNYaoCtT-mr1Ze0OTJND04vbfLotV-BBKulIpbOOSeVpKG7gJxZHmv7in7PE5_WzaxKFVoHW3wR6v_GzQ", + dp: "KTWmTGmf092AA1euOmRQ5IsfIIxQ5qGDn-FgsRh4acSOGE8L7WrTrTU4EOJyciuA0qz-50xIDbs4_j5pWx1BJVTrnhBin9vNLrVo9mtR6jmFS0ko226kOUpwEVLgtdQjobWLjtiuaMW-_Iw4gKWNptxZ6T1lBD8UWHaPiEFW2-M", + dq: "Jn0lqMkvemENEMG1eUw0c601wPOMoPD4SKTlnKWPTlQS6YISbNF5UKSuFLwoJa9HA8BifDrD-Mfpo1M1HPmnoilEWUrfwMqqdCkOlbiJQhKY8AZ16QGH50kDXhmVVa8BRWdVQWBTUzWXS5kXMaeskVzextTgymPcOAhXN-ph7MU", + qi: "sRAPigJpl8S_vsf1zhJTrHM97xRwuB26R6Tm-J8sKRPb7p5xxNlmOBBFvWmWxdto8dBElNlydSZan373yBLxzW-bZgVp-B2RKT1B3WhTYW_Vo5DLhWi84XMncJxH7avtxtF9yksaeKe0e2n3J6TTan53mDg4KF8U0OEO2ciqO9g" + ); + + Jwk publicKey = new Jwk( + e: "AQAB", + n: "qFZv0pea_jn5Mo4qEUmStuhlulso8n1inXbEotd_zTrQp9K0RK0hf7t0K4BjKVhaiqIam4tVVQvkmYeBeYr1MmnO_0N97dMBz_7fmvyv0hgHaBdQ5mR5u3LTlHo8tjRE7-GzZmGs6jMcyj7HbXobDPQJZpqNy6JjliDVXxW8nWJDetxGBlqmTj1E1fr2RCsZLreDOPSDIedG1upz9RraShsIDzeefOcKibcAaKeeVI3rkAU8_mOauLSXv37hlk0h6sStJb3qZQXyOUkVkjXIkhvNu_ve0v7LiLT4G_OxYGzpOQcCnimKdojzNP6GtVDaMPh-QkSJE32UCos9R3wI2Q" + ); + + 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.RSA_OAEP, JweEncryption.A256GCM, JweCompression.DEF); + + // when + try + { + string decoded = Jose.JWT.Decode(bomb, privateKey); + Assert.True(false, "Should fail with NotSupportedException"); + } + catch (JoseException e) + { + Console.Out.WriteLine(e.ToString()); + } + } } } \ No newline at end of file diff --git a/UnitTestsNet46/SettingsTest.cs b/UnitTestsNet46/SettingsTest.cs index 6e65905f..7d5b63d7 100644 --- a/UnitTestsNet46/SettingsTest.cs +++ b/UnitTestsNet46/SettingsTest.cs @@ -425,6 +425,8 @@ 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/jose-jwt/JWTSettings.cs b/jose-jwt/JWTSettings.cs index 7870c822..ddd08b9e 100644 --- a/jose-jwt/JWTSettings.cs +++ b/jose-jwt/JWTSettings.cs @@ -152,7 +152,10 @@ public JwtSettings() private readonly Dictionary compressionAlgorithms = new Dictionary { - { JweCompression.DEF, new DeflateCompression() } + { + // 250Kb limited decompression buffer + JweCompression.DEF, new DeflateCompression(250 * 1024) + } }; private readonly Dictionary jweCompressionHeaderValue = new Dictionary diff --git a/jose-jwt/compression/DeflateCompression.cs b/jose-jwt/compression/DeflateCompression.cs index 4acaf503..ebae6490 100644 --- a/jose-jwt/compression/DeflateCompression.cs +++ b/jose-jwt/compression/DeflateCompression.cs @@ -1,3 +1,4 @@ +using System; using System.IO; using System.IO.Compression; @@ -5,6 +6,13 @@ namespace Jose { public class DeflateCompression : ICompression { + private readonly long maxBufferSizeBytes; + + public DeflateCompression(long maxBufferSizeBytes) + { + this.maxBufferSizeBytes = maxBufferSizeBytes; + } + public byte[] Compress(byte[] plainText) { using (MemoryStream output = new MemoryStream()) @@ -20,17 +28,26 @@ public byte[] Compress(byte[] plainText) public byte[] Decompress(byte[] compressedText) { - using (MemoryStream ms = new MemoryStream()) - { - using (MemoryStream compressedStream = new MemoryStream(compressedText)) - { - using (DeflateStream deflater = new DeflateStream(compressedStream, CompressionMode.Decompress)) - { - deflater.CopyTo(ms); - } - } + byte[] buffer = new byte[maxBufferSizeBytes]; - return ms.ToArray(); + try + { + using (MemoryStream ms = new MemoryStream(buffer)) + { + using (MemoryStream compressedStream = new MemoryStream(compressedText)) + { + using (DeflateStream deflater = new DeflateStream(compressedStream, CompressionMode.Decompress)) + { + deflater.CopyTo(ms); + } + } + + return Arrays.Truncate(ms.ToArray(), ms.Position); + } + } + catch(NotSupportedException e) + { + throw new JoseException("Unable to deflate compressed payload, most likely exceeded decompression buffer size.", 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));