diff --git a/README.md b/README.md index 57b68114..f16c47db 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ JWE JSON Serialization cross-tested with [JWCrypto](https://github.com/latchset/ Library is fully FIPS compliant since v2.1 ## Which version? +- v5.1 support for experimental algorithms RSA-OAEP-384, RSA-OAEP-512 + - 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). @@ -78,7 +80,7 @@ AES Key Wrap implementation ideas and test data from http://www.cryptofreak.org/ - NONE (unprotected) plain text algorithm without integrity protection **Encryption** -- RSAES OAEP 256 (using SHA-256 and MGF1 with SHA-256) encryption with A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM, A256GCM +- RSAES OAEP 256, 384, 512 (using SHA-256, 384, 512 and MGF1 with SHA-256, 384, 512) encryption with A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM, A256GCM - RSAES OAEP (using SHA-1 and MGF1 with SHA-1) encryption with A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM, A256GCM - RSAES-PKCS1-V1_5 encryption with A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM, A256GCM - Direct symmetric key encryption with pre-shared key A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM and A256GCM @@ -101,7 +103,7 @@ AES Key Wrap implementation ideas and test data from http://www.cryptofreak.org/ - NONE (unprotected) plain text algorithm without integrity protection **Encryption** -- RSAES OAEP 256 (using SHA-256 and MGF1 with SHA-256) encryption with A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM, A256GCM +- RSAES OAEP 256, 384, 512 (using SHA-256, 384, 512 and MGF1 with SHA-256, 384, 512) encryption with A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM, A256GCM - RSAES OAEP (using SHA-1 and MGF1 with SHA-1) encryption with A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM, A256GCM - RSAES-PKCS1-V1_5 encryption with A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM, A256GCM - Direct symmetric key encryption with pre-shared key A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM and A256GCM @@ -302,7 +304,7 @@ Accepts `CngKey`, `ECDsa` and `Jwk` types of keys (see above). **NET40-NET45**: -RSA-OAEP-256, RSA-OAEP and RSA1_5 key management requires `RSACryptoServiceProvider` (usually public) key of corresponding length. +RSA-OAEP-256, RSA-OAEP-384, RSA-OAEP-512, RSA-OAEP and RSA1_5 key management requires `RSACryptoServiceProvider` (usually public) key of corresponding length. ```C# var payload = new Dictionary() @@ -317,7 +319,7 @@ string token = Jose.JWT.Encode(payload, publicKey, JweAlgorithm.RSA_OAEP, JweEnc ``` **NETCORE:** -RSA-OAEP-256, RSA-OAEP and RSA1_5 key management requires `RSA` (usually public) or `Jwk` key of type `RSA` of corresponding length. +RSA-OAEP-256, RSA-OAEP-384, RSA-OAEP-512, RSA-OAEP and RSA1_5 key management requires `RSA` (usually public) or `Jwk` key of type `RSA` of corresponding length. ``` cs var payload = new Dictionary() @@ -587,7 +589,7 @@ string json = Jose.JWT.Decode(token, secretKey); string json = Jose.JWT.Decrypt(token, secretKey); ``` -**RS256, RS384, RS512**, **PS256, PS384, PS512** signatures and **RSA-OAEP-256**, **RSA-OAEP, RSA1_5** key management algorithms expects +**RS256, RS384, RS512**, **PS256, PS384, PS512** signatures and **RSA-OAEP-256**, **RSA-OAEP-384**, **RSA-OAEP-512**, **RSA-OAEP, RSA1_5** key management algorithms expects **NET40-NET45**: `RSACryptoServiceProvider` as a key, public/private is asymmetric to encoding: diff --git a/UnitTests/TestSuite.cs b/UnitTests/TestSuite.cs index 7aa21d3b..d2571169 100644 --- a/UnitTests/TestSuite.cs +++ b/UnitTests/TestSuite.cs @@ -958,6 +958,51 @@ public void Decrypt_RSA_OAEP_256_A256CBC_HS512() Assert.Equal(@"{""exp"":1392553211,""sub"":""alice"",""nbf"":1392552611,""aud"":[""https:\/\/app-one.com"",""https:\/\/app-two.com""],""iss"":""https:\/\/openid.net"",""jti"":""586dd129-a29f-49c8-9de7-454af1155e27"",""iat"":1392552611}", json); } + [Fact] + public void Decrypt_RSA_OAEP_256_A128GCM() + { + //given + const string token = "eyJlbmMiOiJBMTI4R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.Ki1uPWs34nXxE2nGmie94nGkcl1KbJcutGIRL2x7025JXITQ905nGlMqPf4YjgzvJxj51oc0EARPfC6IrFVlefuJzeKbJgZNj0IBkIKL1UbxkP0PrCovuXOnQhfYbcbppC0uNA6SDdGUiCJZH9DNnEBjQcbUWmDAdLSmI0ZkBhYK7fsla7dITP6rcAPM8wQcN6JqcVFZ-s_-uP8B6o2ywkawdtTacsQq9UxJvJ5bKfXJSNT-0xZY5kevgUo7rbJIpzv6BpFsA4EduOVMGu9Y7Xu_4seNIHZQA4NEqTxtbwjuq1lUdkm6Bnkr7GSXJ8tRjoFRf6OD2aX-snFx6Zg-MA.ez2XiQxsD1dvkehk._UCAGhvkeUWHKNa5lHQZvPpvWyH7j4E6wPJFLeNIFe53WCWVBVqFPoBRa5SANLak2COOgF__KBezLp935hROEvmtgFqJ5adl9uF501fCN0Cq-53bZP5MjKP237fOcZYQrGcei2GRRG-vOMt--owVX0Wjyy4Jy0oLqf9-hHoImtyLTpM89nN1s3jKcpsMntIfiqespShHLehZ0zW4v9pzT0ScHmSdjSqsisqcA0Za71GzPRoNIq5BzCvIf0TR6Q.hm0FdUsB0iHd1yrJW4Omzg"; + + //when + string json = Jose.JWT.Decode(token, testSuiteUtils.PrivKey()); + + //then + Console.Out.WriteLine("json = {0}", json); + + Assert.Equal(@"{""sub"":""alice"",""aud"":[""https://app-one.com"",""https://app-two.com""],""nbf"":1730312729,""iss"":""https://openid.net"",""exp"":1730313329,""iat"":1730312729,""jti"":""d8915fdb-8985-4c86-ae8d-28567016f623""}", json); + } + + [Fact] + public void Decrypt_RSA_OAEP_384_A192GCM() + { + //given + const string token = "eyJlbmMiOiJBMTkyR0NNIiwiYWxnIjoiUlNBLU9BRVAtMzg0In0.pHwaRvG-WVUQtofFUwjedVIY_MxZs8V3ulf-46pFSAKqyERjJF0Za-LoDqN_EvMMwJp4TJ2JgEFHk9vLoThh-7EtJ3noaLaDxTXbt38slZ6xw1J-dziSUKXaiJstUiGLuJAK5liKwFWdqNtolBG0plEc2vvuROu7xUNoFuwb286992sKOAQb_1CFomDk5QuaiTN8pDwtQDLt8UsU5Gurml1U9bdU08FnH0PYIZlyVxKDL7a_fAfG-4o9_S__p8xLpYfp0aKX4MMbHSZcnMgqiwbfx_PiWQLhTYWeqf8XNNEYnGdv_QHK11FAd2NnT_RS028WJkf9W8veJTc-UUujjg.yej5UbPyU3A9gQD2.euXP6xG2-cLGJ0rTTjhwbeUX-qUKgT0Rr5JmtwDtGpZG8LyUpTpYhhLLcDTMtiUd0l0GeJPRzeApt5XTy-2RgpS-vcTn0b4t2xH27GPtN5Jvvsc-0rJlSbmptIE3QImrbckEXnwNCbz0qt95wPFSde04UzRWsna-LaSQ6se_pXbfga2kn5TiZ6ZKWh6fcfV6WIY3DtcWY1GCwdX2VW1vrqp-EQbWrOKqZp8Zx6BLwo521SPFg36EnJZWopoQBA.ZAC9GsPtN4P_ZhEKU1Hnpg"; + + //when + string json = Jose.JWT.Decode(token, testSuiteUtils.PrivKey()); + + //then + Console.Out.WriteLine("json = {0}", json); + + Assert.Equal(@"{""sub"":""alice"",""aud"":[""https://app-one.com"",""https://app-two.com""],""nbf"":1729866729,""iss"":""https://openid.net"",""exp"":1729867329,""iat"":1729866729,""jti"":""da2aa440-2923-4501-b169-04fe0904da4d""}", json); + } + + [Fact] + public void Decrypt_RSA_OAEP_512_A256GCM() + { + //given + const string token = "eyJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAtNTEyIn0.Xti41IW1gNT9RqV2Eah4v8ylTP_E1DOjniWw9EJbEHTWzAjXeQ7sWdHLgH_h7G6xNhppLziGbsSZeqBcB_r-jkRXkSpP-kUyKp8QddvXCgUHHgme39LnnNjZXQ6KHBFiM0F0zQp3DJhxBe0lFEezrW3oSjusX43ybsTx3OqKWpp1TwhLyiGtjrKP82b2KcPS8XHT5mOP-_-KFKqOtut3m4jpLO6xYTClP-HZFtTHTz-CrK1y5XisDez-hk9LwzwJu0G-MFL5mNtWL24yeskbD2JWXzOHpkywiZ-pUU_BkfGqCHJnNGlbbX19xEHpd4a4XkPI_IatFX7QNCZOfRoiSw.mftbPhWtNUuxaRO_.GQPlZZruXeXEWeYu6QtCbu0i2bXBWThic-5VjVlRa2hu_ZJm1X4yUmmLdIsc8AE0F28dxbgvAI6HxTwVJkihPT_n5EF8zMdUPsTOMmxQOqfyRWgNX1WOvFgtsf8D1AlQ0Ukf0Pyt57nb-0ha6x5Bj9jMUmGQNfL98bDzNYrR-Y-cHIRfXzbyXw3u5f4qdczmdsNgWJ7ZQSm-4Rc8QSCP70bnISRPehhIOGYrKJenL9GaIUZa0lRrNv6YS17mhQ.6R_c3mLxSQ524Uk8usfNPg"; + + //when + string json = Jose.JWT.Decode(token, testSuiteUtils.PrivKey()); + + //then + Console.Out.WriteLine("json = {0}", json); + + Assert.Equal(@"{""sub"":""alice"",""aud"":[""https://app-one.com"",""https://app-two.com""],""nbf"":1729866729,""iss"":""https://openid.net"",""exp"":1729867329,""iat"":1729866729,""jti"":""b02ef943-92d6-4bf4-b7ac-18d2e122efa5""}", json); + } + [Fact] public void Encrypt_RSA_OAEP_256_A128GCM() { @@ -1007,6 +1052,55 @@ public void Encrypt_RSA_OAEP_256_A128GCM_JsonWebKey() Assert.Equal(json, Jose.JWT.Decode(token, testSuiteUtils.PrivKey())); } + [Fact] + public void Encrypt_RSA_OAEP_384_A192GCM() + { + //given + const string json = @"{""hello"": ""world""}"; + + //when + string token = Jose.JWT.Encode(json, testSuiteUtils.PubKey(), JweAlgorithm.RSA_OAEP_384, JweEncryption.A192GCM); + + //then + Console.Out.WriteLine("RSA_OAEP_384_A192GCM={0}", token); + + string[] parts = token.Split('.'); + + Assert.Equal(5, parts.Length); //Make sure 5 parts + Assert.Equal("eyJhbGciOiJSU0EtT0FFUC0zODQiLCJlbmMiOiJBMTkyR0NNIn0", parts[0]); //Header is non-encrypted and static text + Assert.Equal(342, parts[1].Length); //CEK size + Assert.Equal(16, parts[2].Length); //IV size + Assert.Equal(24, parts[3].Length); //cipher text size + Assert.Equal(22, parts[4].Length); //auth tag size + + Assert.Equal(json, Jose.JWT.Decode(token, testSuiteUtils.PrivKey())); + } + + [Fact] + public void Encrypt_RSA_OAEP_512_A256GCM() + { + //given + const string json = @"{""hello"": ""world""}"; + + //when + Jwk publicKey = new Jwk("AQAB", "qFZv0pea_jn5Mo4qEUmStuhlulso8n1inXbEotd_zTrQp9K0RK0hf7t0K4BjKVhaiqIam4tVVQvkmYeBeYr1MmnO_0N97dMBz_7fmvyv0hgHaBdQ5mR5u3LTlHo8tjRE7-GzZmGs6jMcyj7HbXobDPQJZpqNy6JjliDVXxW8nWJDetxGBlqmTj1E1fr2RCsZLreDOPSDIedG1upz9RraShsIDzeefOcKibcAaKeeVI3rkAU8_mOauLSXv37hlk0h6sStJb3qZQXyOUkVkjXIkhvNu_ve0v7LiLT4G_OxYGzpOQcCnimKdojzNP6GtVDaMPh-QkSJE32UCos9R3wI2Q"); + string token = Jose.JWT.Encode(json, publicKey, JweAlgorithm.RSA_OAEP_512, JweEncryption.A256GCM); + + //then + Console.Out.WriteLine("RSA_OAEP_512_A256GCM={0}", token); + + string[] parts = token.Split('.'); + + Assert.Equal(5, parts.Length); //Make sure 5 parts + Assert.Equal("eyJhbGciOiJSU0EtT0FFUC01MTIiLCJlbmMiOiJBMjU2R0NNIn0", parts[0]); //Header is non-encrypted and static text + Assert.Equal(342, parts[1].Length); //CEK size + Assert.Equal(16, parts[2].Length); //IV size + Assert.Equal(24, parts[3].Length); //cipher text size + Assert.Equal(22, parts[4].Length); //auth tag size + + Assert.Equal(json, Jose.JWT.Decode(token, testSuiteUtils.PrivKey())); + } + [Fact] public void Encrypt_RSA_OAEP_256_A192GCM() { diff --git a/UnitTestsNet40/TestSuite.cs b/UnitTestsNet40/TestSuite.cs index 41434c7b..c3ff688e 100644 --- a/UnitTestsNet40/TestSuite.cs +++ b/UnitTestsNet40/TestSuite.cs @@ -704,6 +704,51 @@ public void Decrypt_RSA_OAEP_256_A256CBC_HS512() Assert.That(json, Is.EqualTo(@"{""exp"":1392553211,""sub"":""alice"",""nbf"":1392552611,""aud"":[""https:\/\/app-one.com"",""https:\/\/app-two.com""],""iss"":""https:\/\/openid.net"",""jti"":""586dd129-a29f-49c8-9de7-454af1155e27"",""iat"":1392552611}")); } + [Test] + public void Decrypt_RSA_OAEP_256_A128GCM() + { + //given + const string token = "eyJlbmMiOiJBMTI4R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.Ki1uPWs34nXxE2nGmie94nGkcl1KbJcutGIRL2x7025JXITQ905nGlMqPf4YjgzvJxj51oc0EARPfC6IrFVlefuJzeKbJgZNj0IBkIKL1UbxkP0PrCovuXOnQhfYbcbppC0uNA6SDdGUiCJZH9DNnEBjQcbUWmDAdLSmI0ZkBhYK7fsla7dITP6rcAPM8wQcN6JqcVFZ-s_-uP8B6o2ywkawdtTacsQq9UxJvJ5bKfXJSNT-0xZY5kevgUo7rbJIpzv6BpFsA4EduOVMGu9Y7Xu_4seNIHZQA4NEqTxtbwjuq1lUdkm6Bnkr7GSXJ8tRjoFRf6OD2aX-snFx6Zg-MA.ez2XiQxsD1dvkehk._UCAGhvkeUWHKNa5lHQZvPpvWyH7j4E6wPJFLeNIFe53WCWVBVqFPoBRa5SANLak2COOgF__KBezLp935hROEvmtgFqJ5adl9uF501fCN0Cq-53bZP5MjKP237fOcZYQrGcei2GRRG-vOMt--owVX0Wjyy4Jy0oLqf9-hHoImtyLTpM89nN1s3jKcpsMntIfiqespShHLehZ0zW4v9pzT0ScHmSdjSqsisqcA0Za71GzPRoNIq5BzCvIf0TR6Q.hm0FdUsB0iHd1yrJW4Omzg"; + + //when + string json = Jose.JWT.Decode(token, RsaKey.New(PrivKey().ExportParameters(true))); + + //then + Console.Out.WriteLine("json = {0}", json); + + Assert.That(json, Is.EqualTo(@"{""sub"":""alice"",""aud"":[""https://app-one.com"",""https://app-two.com""],""nbf"":1730312729,""iss"":""https://openid.net"",""exp"":1730313329,""iat"":1730312729,""jti"":""d8915fdb-8985-4c86-ae8d-28567016f623""}")); + } + + [Test] + public void Decrypt_RSA_OAEP_384_A192GCM() + { + //given + const string token = "eyJlbmMiOiJBMTkyR0NNIiwiYWxnIjoiUlNBLU9BRVAtMzg0In0.pHwaRvG-WVUQtofFUwjedVIY_MxZs8V3ulf-46pFSAKqyERjJF0Za-LoDqN_EvMMwJp4TJ2JgEFHk9vLoThh-7EtJ3noaLaDxTXbt38slZ6xw1J-dziSUKXaiJstUiGLuJAK5liKwFWdqNtolBG0plEc2vvuROu7xUNoFuwb286992sKOAQb_1CFomDk5QuaiTN8pDwtQDLt8UsU5Gurml1U9bdU08FnH0PYIZlyVxKDL7a_fAfG-4o9_S__p8xLpYfp0aKX4MMbHSZcnMgqiwbfx_PiWQLhTYWeqf8XNNEYnGdv_QHK11FAd2NnT_RS028WJkf9W8veJTc-UUujjg.yej5UbPyU3A9gQD2.euXP6xG2-cLGJ0rTTjhwbeUX-qUKgT0Rr5JmtwDtGpZG8LyUpTpYhhLLcDTMtiUd0l0GeJPRzeApt5XTy-2RgpS-vcTn0b4t2xH27GPtN5Jvvsc-0rJlSbmptIE3QImrbckEXnwNCbz0qt95wPFSde04UzRWsna-LaSQ6se_pXbfga2kn5TiZ6ZKWh6fcfV6WIY3DtcWY1GCwdX2VW1vrqp-EQbWrOKqZp8Zx6BLwo521SPFg36EnJZWopoQBA.ZAC9GsPtN4P_ZhEKU1Hnpg"; + + //when + string json = Jose.JWT.Decode(token, PrivKey()); + + //then + Console.Out.WriteLine("json = {0}", json); + + Assert. That(json, Is.EqualTo(@"{""sub"":""alice"",""aud"":[""https://app-one.com"",""https://app-two.com""],""nbf"":1729866729,""iss"":""https://openid.net"",""exp"":1729867329,""iat"":1729866729,""jti"":""da2aa440-2923-4501-b169-04fe0904da4d""}")); + } + + [Test] + public void Decrypt_RSA_OAEP_512_A256GCM() + { + //given + const string token = "eyJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAtNTEyIn0.Xti41IW1gNT9RqV2Eah4v8ylTP_E1DOjniWw9EJbEHTWzAjXeQ7sWdHLgH_h7G6xNhppLziGbsSZeqBcB_r-jkRXkSpP-kUyKp8QddvXCgUHHgme39LnnNjZXQ6KHBFiM0F0zQp3DJhxBe0lFEezrW3oSjusX43ybsTx3OqKWpp1TwhLyiGtjrKP82b2KcPS8XHT5mOP-_-KFKqOtut3m4jpLO6xYTClP-HZFtTHTz-CrK1y5XisDez-hk9LwzwJu0G-MFL5mNtWL24yeskbD2JWXzOHpkywiZ-pUU_BkfGqCHJnNGlbbX19xEHpd4a4XkPI_IatFX7QNCZOfRoiSw.mftbPhWtNUuxaRO_.GQPlZZruXeXEWeYu6QtCbu0i2bXBWThic-5VjVlRa2hu_ZJm1X4yUmmLdIsc8AE0F28dxbgvAI6HxTwVJkihPT_n5EF8zMdUPsTOMmxQOqfyRWgNX1WOvFgtsf8D1AlQ0Ukf0Pyt57nb-0ha6x5Bj9jMUmGQNfL98bDzNYrR-Y-cHIRfXzbyXw3u5f4qdczmdsNgWJ7ZQSm-4Rc8QSCP70bnISRPehhIOGYrKJenL9GaIUZa0lRrNv6YS17mhQ.6R_c3mLxSQ524Uk8usfNPg"; + + //when + string json = Jose.JWT.Decode(token, PrivKey()); + + //then + Console.Out.WriteLine("json = {0}", json); + + Assert.That(json, Is.EqualTo(@"{""sub"":""alice"",""aud"":[""https://app-one.com"",""https://app-two.com""],""nbf"":1729866729,""iss"":""https://openid.net"",""exp"":1729867329,""iat"":1729866729,""jti"":""b02ef943-92d6-4bf4-b7ac-18d2e122efa5""}")); + } + [Test] public void Encrypt_RSA_OAEP_256_A128GCM() { @@ -803,6 +848,54 @@ public void Encrypt_RSA_OAEP_256_A256GCM() Assert.That(Jose.JWT.Decode(token, RsaKey.New(PrivKey().ExportParameters(true))), Is.EqualTo(json), "Make sure we are consistent with ourselfs"); } + [Test] + public void Encrypt_RSA_OAEP_384_A192GCM() + { + //given + const string json = @"{""hello"": ""world""}"; + + //when + string token = Jose.JWT.Encode(json, RsaKey.New(PubKey().ExportParameters(false)), JweAlgorithm.RSA_OAEP_384, JweEncryption.A192GCM); + + //then + Console.Out.WriteLine("RSA_OAEP_384_A192GCM={0}", token); + + string[] parts = token.Split('.'); + + Assert.That(parts.Length, Is.EqualTo(5)); //Make sure 5 parts + Assert.That(parts[0], Is.EqualTo("eyJhbGciOiJSU0EtT0FFUC0zODQiLCJlbmMiOiJBMTkyR0NNIn0")); //Header is non-encrypted and static text + Assert.That(parts[1].Length, Is.EqualTo(342)); //CEK size + Assert.That(parts[2].Length, Is.EqualTo(16)); //IV size + Assert.That(parts[3].Length, Is.EqualTo(24)); //cipher text size + Assert.That(parts[4].Length, Is.EqualTo(22)); //auth tag size + + Assert.That(Jose.JWT.Decode(token, RsaKey.New(PrivKey().ExportParameters(true))), Is.EqualTo(json)); + } + + [Test] + public void Encrypt_RSA_OAEP_512_A256GCM() + { + //given + const string json = @"{""hello"": ""world""}"; + + //when + string token = Jose.JWT.Encode(json, RsaKey.New(PubKey().ExportParameters(false)), JweAlgorithm.RSA_OAEP_512, JweEncryption.A256GCM); + + //then + Console.Out.WriteLine("RSA_OAEP_512_A256GCM={0}", token); + + string[] parts = token.Split('.'); + + Assert.That(parts.Length, Is.EqualTo(5)); //Make sure 5 parts + Assert.That(parts[0], Is.EqualTo("eyJhbGciOiJSU0EtT0FFUC01MTIiLCJlbmMiOiJBMjU2R0NNIn0")); //Header is non-encrypted and static text + Assert.That(parts[1].Length, Is.EqualTo(342)); //CEK size + Assert.That(parts[2].Length, Is.EqualTo(16)); //IV size + Assert.That(parts[3].Length, Is.EqualTo(24)); //cipher text size + Assert.That(parts[4].Length, Is.EqualTo(22)); //auth tag size + + Assert.That(Jose.JWT.Decode(token, RsaKey.New(PrivKey().ExportParameters(true))), Is.EqualTo(json)); + } + [Test] public void Decrypt_RSA_1_5_A128CBC_HS256() { diff --git a/UnitTestsNet46/TestSuite.cs b/UnitTestsNet46/TestSuite.cs index 4ff65529..537a1c81 100644 --- a/UnitTestsNet46/TestSuite.cs +++ b/UnitTestsNet46/TestSuite.cs @@ -1024,6 +1024,51 @@ public void Decrypt_RSA_OAEP_256_A256CBC_HS512() Assert.Equal(@"{""exp"":1392553211,""sub"":""alice"",""nbf"":1392552611,""aud"":[""https:\/\/app-one.com"",""https:\/\/app-two.com""],""iss"":""https:\/\/openid.net"",""jti"":""586dd129-a29f-49c8-9de7-454af1155e27"",""iat"":1392552611}", json); } + [Fact] + public void Decrypt_RSA_OAEP_256_A128GCM() + { + //given + const string token = "eyJlbmMiOiJBMTI4R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.Ki1uPWs34nXxE2nGmie94nGkcl1KbJcutGIRL2x7025JXITQ905nGlMqPf4YjgzvJxj51oc0EARPfC6IrFVlefuJzeKbJgZNj0IBkIKL1UbxkP0PrCovuXOnQhfYbcbppC0uNA6SDdGUiCJZH9DNnEBjQcbUWmDAdLSmI0ZkBhYK7fsla7dITP6rcAPM8wQcN6JqcVFZ-s_-uP8B6o2ywkawdtTacsQq9UxJvJ5bKfXJSNT-0xZY5kevgUo7rbJIpzv6BpFsA4EduOVMGu9Y7Xu_4seNIHZQA4NEqTxtbwjuq1lUdkm6Bnkr7GSXJ8tRjoFRf6OD2aX-snFx6Zg-MA.ez2XiQxsD1dvkehk._UCAGhvkeUWHKNa5lHQZvPpvWyH7j4E6wPJFLeNIFe53WCWVBVqFPoBRa5SANLak2COOgF__KBezLp935hROEvmtgFqJ5adl9uF501fCN0Cq-53bZP5MjKP237fOcZYQrGcei2GRRG-vOMt--owVX0Wjyy4Jy0oLqf9-hHoImtyLTpM89nN1s3jKcpsMntIfiqespShHLehZ0zW4v9pzT0ScHmSdjSqsisqcA0Za71GzPRoNIq5BzCvIf0TR6Q.hm0FdUsB0iHd1yrJW4Omzg"; + + //when + string json = Jose.JWT.Decode(token, RsaKey.New(PrivKey().ExportParameters(true))); + + //then + Console.Out.WriteLine("json = {0}", json); + + Assert.Equal(@"{""sub"":""alice"",""aud"":[""https://app-one.com"",""https://app-two.com""],""nbf"":1730312729,""iss"":""https://openid.net"",""exp"":1730313329,""iat"":1730312729,""jti"":""d8915fdb-8985-4c86-ae8d-28567016f623""}", json); + } + + [Fact] + public void Decrypt_RSA_OAEP_384_A192GCM() + { + //given + const string token = "eyJlbmMiOiJBMTkyR0NNIiwiYWxnIjoiUlNBLU9BRVAtMzg0In0.pHwaRvG-WVUQtofFUwjedVIY_MxZs8V3ulf-46pFSAKqyERjJF0Za-LoDqN_EvMMwJp4TJ2JgEFHk9vLoThh-7EtJ3noaLaDxTXbt38slZ6xw1J-dziSUKXaiJstUiGLuJAK5liKwFWdqNtolBG0plEc2vvuROu7xUNoFuwb286992sKOAQb_1CFomDk5QuaiTN8pDwtQDLt8UsU5Gurml1U9bdU08FnH0PYIZlyVxKDL7a_fAfG-4o9_S__p8xLpYfp0aKX4MMbHSZcnMgqiwbfx_PiWQLhTYWeqf8XNNEYnGdv_QHK11FAd2NnT_RS028WJkf9W8veJTc-UUujjg.yej5UbPyU3A9gQD2.euXP6xG2-cLGJ0rTTjhwbeUX-qUKgT0Rr5JmtwDtGpZG8LyUpTpYhhLLcDTMtiUd0l0GeJPRzeApt5XTy-2RgpS-vcTn0b4t2xH27GPtN5Jvvsc-0rJlSbmptIE3QImrbckEXnwNCbz0qt95wPFSde04UzRWsna-LaSQ6se_pXbfga2kn5TiZ6ZKWh6fcfV6WIY3DtcWY1GCwdX2VW1vrqp-EQbWrOKqZp8Zx6BLwo521SPFg36EnJZWopoQBA.ZAC9GsPtN4P_ZhEKU1Hnpg"; + + //when + string json = Jose.JWT.Decode(token, PrivKey()); + + //then + Console.Out.WriteLine("json = {0}", json); + + Assert.Equal(@"{""sub"":""alice"",""aud"":[""https://app-one.com"",""https://app-two.com""],""nbf"":1729866729,""iss"":""https://openid.net"",""exp"":1729867329,""iat"":1729866729,""jti"":""da2aa440-2923-4501-b169-04fe0904da4d""}", json); + } + + [Fact] + public void Decrypt_RSA_OAEP_512_A256GCM() + { + //given + const string token = "eyJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAtNTEyIn0.Xti41IW1gNT9RqV2Eah4v8ylTP_E1DOjniWw9EJbEHTWzAjXeQ7sWdHLgH_h7G6xNhppLziGbsSZeqBcB_r-jkRXkSpP-kUyKp8QddvXCgUHHgme39LnnNjZXQ6KHBFiM0F0zQp3DJhxBe0lFEezrW3oSjusX43ybsTx3OqKWpp1TwhLyiGtjrKP82b2KcPS8XHT5mOP-_-KFKqOtut3m4jpLO6xYTClP-HZFtTHTz-CrK1y5XisDez-hk9LwzwJu0G-MFL5mNtWL24yeskbD2JWXzOHpkywiZ-pUU_BkfGqCHJnNGlbbX19xEHpd4a4XkPI_IatFX7QNCZOfRoiSw.mftbPhWtNUuxaRO_.GQPlZZruXeXEWeYu6QtCbu0i2bXBWThic-5VjVlRa2hu_ZJm1X4yUmmLdIsc8AE0F28dxbgvAI6HxTwVJkihPT_n5EF8zMdUPsTOMmxQOqfyRWgNX1WOvFgtsf8D1AlQ0Ukf0Pyt57nb-0ha6x5Bj9jMUmGQNfL98bDzNYrR-Y-cHIRfXzbyXw3u5f4qdczmdsNgWJ7ZQSm-4Rc8QSCP70bnISRPehhIOGYrKJenL9GaIUZa0lRrNv6YS17mhQ.6R_c3mLxSQ524Uk8usfNPg"; + + //when + string json = Jose.JWT.Decode(token, PrivRsaKey()); + + //then + Console.Out.WriteLine("json = {0}", json); + + Assert.Equal(@"{""sub"":""alice"",""aud"":[""https://app-one.com"",""https://app-two.com""],""nbf"":1729866729,""iss"":""https://openid.net"",""exp"":1729867329,""iat"":1729866729,""jti"":""b02ef943-92d6-4bf4-b7ac-18d2e122efa5""}", json); + } + [Fact] public void Encrypt_RSA_OAEP_256_A128GCM() { @@ -1121,6 +1166,56 @@ public void Encrypt_RSA_OAEP_256_A128GCM_RsaCSPKey() Assert.Equal(json, Jose.JWT.Decode(token, RsaKey.New(PrivKey().ExportParameters(true)))); } + [Fact] + public void Encrypt_RSA_OAEP_384_A192GCM() + { + //given + const string json = @"{""hello"": ""world""}"; + + //when + string token = Jose.JWT.Encode(json, RsaKey.New(PubKey().ExportParameters(false)), JweAlgorithm.RSA_OAEP_384, JweEncryption.A192GCM); + + //then + Console.Out.WriteLine("RSA_OAEP_384_A192GCM={0}", token); + + string[] parts = token.Split('.'); + + Assert.Equal(5, parts.Length); //Make sure 5 parts + Assert.Equal("eyJhbGciOiJSU0EtT0FFUC0zODQiLCJlbmMiOiJBMTkyR0NNIn0", parts[0]); //Header is non-encrypted and static text + Assert.Equal(342, parts[1].Length); //CEK size + Assert.Equal(16, parts[2].Length); //IV size + Assert.Equal(24, parts[3].Length); //cipher text size + Assert.Equal(22, parts[4].Length); //auth tag size + + Assert.Equal(json, Jose.JWT.Decode(token, RsaKey.New(PrivKey().ExportParameters(true)))); + } + + [Fact] + public void Encrypt_RSA_OAEP_512_A256GCM() + { + //given + const string json = @"{""hello"": ""world""}"; + + //when + Jwk publicKey = new Jwk("AQAB", "qFZv0pea_jn5Mo4qEUmStuhlulso8n1inXbEotd_zTrQp9K0RK0hf7t0K4BjKVhaiqIam4tVVQvkmYeBeYr1MmnO_0N97dMBz_7fmvyv0hgHaBdQ5mR5u3LTlHo8tjRE7-GzZmGs6jMcyj7HbXobDPQJZpqNy6JjliDVXxW8nWJDetxGBlqmTj1E1fr2RCsZLreDOPSDIedG1upz9RraShsIDzeefOcKibcAaKeeVI3rkAU8_mOauLSXv37hlk0h6sStJb3qZQXyOUkVkjXIkhvNu_ve0v7LiLT4G_OxYGzpOQcCnimKdojzNP6GtVDaMPh-QkSJE32UCos9R3wI2Q"); + string token = Jose.JWT.Encode(json, publicKey, JweAlgorithm.RSA_OAEP_512, JweEncryption.A256GCM); + + //then + Console.Out.WriteLine("RSA_OAEP_512_A256GCM={0}", token); + + string[] parts = token.Split('.'); + + Assert.Equal(5, parts.Length); //Make sure 5 parts + Assert.Equal("eyJhbGciOiJSU0EtT0FFUC01MTIiLCJlbmMiOiJBMjU2R0NNIn0", parts[0]); //Header is non-encrypted and static text + Assert.Equal(342, parts[1].Length); //CEK size + Assert.Equal(16, parts[2].Length); //IV size + Assert.Equal(24, parts[3].Length); //cipher text size + Assert.Equal(22, parts[4].Length); //auth tag size + + Assert.Equal(json, Jose.JWT.Decode(token, RsaKey.New(PrivKey().ExportParameters(true)))); + } + + [Fact] public void Encrypt_RSA_OAEP_256_A192GCM() { diff --git a/jose-jwt/JWT.cs b/jose-jwt/JWT.cs index cc03906c..694b6032 100644 --- a/jose-jwt/JWT.cs +++ b/jose-jwt/JWT.cs @@ -28,6 +28,8 @@ public enum JweAlgorithm RSA1_5, //RSAES with PKCS #1 v1.5 padding, RFC 3447 RSA_OAEP, //RSAES using Optimal Asymmetric Jwe Padding, RFC 3447 RSA_OAEP_256, //RSAES with SHA-256 using Optimal Asymmetric Jwe Padding, RFC 3447 + RSA_OAEP_384, //RSAES with SHA-256 using Optimal Asymmetric Jwe Padding, RFC 3447 + RSA_OAEP_512, //RSAES with SHA-256 using Optimal Asymmetric Jwe Padding, RFC 3447 DIR, //Direct use of pre-shared symmetric key A128KW, //AES Key Wrap Algorithm using 128 bit keys, RFC 3394 A192KW, //AES Key Wrap Algorithm using 192 bit keys, RFC 3394 diff --git a/jose-jwt/JWTSettings.cs b/jose-jwt/JWTSettings.cs index ddd08b9e..f9c112d3 100644 --- a/jose-jwt/JWTSettings.cs +++ b/jose-jwt/JWTSettings.cs @@ -110,7 +110,9 @@ public JwtSettings() private readonly Dictionary keyAlgorithms = new Dictionary { { JweAlgorithm.RSA_OAEP, new RsaKeyManagement(true) }, - { JweAlgorithm.RSA_OAEP_256, new RsaOaep256KeyManagement() }, + { JweAlgorithm.RSA_OAEP_256, new RsaOaepKeyManagement(256) }, + { JweAlgorithm.RSA_OAEP_384, new RsaOaepKeyManagement(384) }, + { JweAlgorithm.RSA_OAEP_512, new RsaOaepKeyManagement(512) }, { JweAlgorithm.RSA1_5, new RsaKeyManagement(false) }, { JweAlgorithm.DIR, new DirectKeyManagement() }, { JweAlgorithm.A128KW, new AesKeyWrapManagement(128) }, @@ -132,6 +134,8 @@ public JwtSettings() { JweAlgorithm.RSA1_5, "RSA1_5" }, { JweAlgorithm.RSA_OAEP, "RSA-OAEP" }, { JweAlgorithm.RSA_OAEP_256, "RSA-OAEP-256" }, + { JweAlgorithm.RSA_OAEP_384, "RSA-OAEP-384" }, + { JweAlgorithm.RSA_OAEP_512, "RSA-OAEP-512" }, { JweAlgorithm.DIR, "dir" }, { JweAlgorithm.A128KW, "A128KW" }, { JweAlgorithm.A256KW, "A256KW" }, diff --git a/jose-jwt/jose-jwt.net40.csproj b/jose-jwt/jose-jwt.net40.csproj index 13b4caa7..bd821de5 100644 --- a/jose-jwt/jose-jwt.net40.csproj +++ b/jose-jwt/jose-jwt.net40.csproj @@ -61,7 +61,7 @@ - + diff --git a/jose-jwt/jose-jwt.net46.csproj b/jose-jwt/jose-jwt.net46.csproj index ed0aac0e..2e0ff345 100644 --- a/jose-jwt/jose-jwt.net46.csproj +++ b/jose-jwt/jose-jwt.net46.csproj @@ -62,7 +62,7 @@ - + diff --git a/jose-jwt/jose-jwt.net47.csproj b/jose-jwt/jose-jwt.net47.csproj index 8a51f08f..e5f91fe6 100644 --- a/jose-jwt/jose-jwt.net47.csproj +++ b/jose-jwt/jose-jwt.net47.csproj @@ -68,7 +68,7 @@ - + diff --git a/jose-jwt/jwa/RsaOaep256KeyManagement.cs b/jose-jwt/jwa/RsaOaepKeyManagement.cs similarity index 66% rename from jose-jwt/jwa/RsaOaep256KeyManagement.cs rename to jose-jwt/jwa/RsaOaepKeyManagement.cs index ff18b1f5..413e789d 100644 --- a/jose-jwt/jwa/RsaOaep256KeyManagement.cs +++ b/jose-jwt/jwa/RsaOaepKeyManagement.cs @@ -5,8 +5,15 @@ namespace Jose { - public class RsaOaep256KeyManagement : IKeyManagement + public class RsaOaepKeyManagement : IKeyManagement { + private readonly int hashSizeBits; + + public RsaOaepKeyManagement(int hashSizeBits) + { + this.hashSizeBits = hashSizeBits; + } + public byte[][] WrapNewKey(int cekSizeBits, object key, IDictionary header) { var cek = Arrays.Random(cekSizeBits); @@ -19,7 +26,7 @@ public byte[] WrapKey(byte[] cek, object key, IDictionary header #if NET40 if (key is CngKey cngKey) { - return RsaOaep.Encrypt(cek, cngKey, CngAlgorithm.Sha256); + return RsaOaep.Encrypt(cek, cngKey, CngAlgorithmHash()); } else if (key is RSACryptoServiceProvider rsaKey) { @@ -27,7 +34,7 @@ public byte[] WrapKey(byte[] cek, object key, IDictionary header //To be removed in 3.x var publicKey = RsaKey.New(rsaKey.ExportParameters(false)); - return RsaOaep.Encrypt(cek, publicKey, CngAlgorithm.Sha256); + return RsaOaep.Encrypt(cek, publicKey, CngAlgorithmHash()); } throw new ArgumentException("RsaKeyManagement algorithm expects key to be of CngKey or RSACryptoServiceProvider types."); @@ -35,7 +42,7 @@ public byte[] WrapKey(byte[] cek, object key, IDictionary header #elif NET461 || NET472 if (key is CngKey cngKey) { - return RsaOaep.Encrypt(cek, cngKey, CngAlgorithm.Sha256); + return RsaOaep.Encrypt(cek, cngKey, CngAlgorithmHash()); } else if (key is RSACryptoServiceProvider rsaKey) @@ -44,17 +51,17 @@ public byte[] WrapKey(byte[] cek, object key, IDictionary header //To be removed in 3.x var publicKey = RsaKey.New(rsaKey.ExportParameters(false)); - return RsaOaep.Encrypt(cek, publicKey, CngAlgorithm.Sha256); + return RsaOaep.Encrypt(cek, publicKey, CngAlgorithmHash()); } else if (key is RSA rsa) { - return rsa.Encrypt(cek, RSAEncryptionPadding.OaepSHA256); + return rsa.Encrypt(cek, OaepPadding()); } else if (key is Jwk jwk) { if (jwk.Kty == Jwk.KeyTypes.RSA) { - return jwk.RsaKey().Encrypt(cek, RSAEncryptionPadding.OaepSHA256); + return jwk.RsaKey().Encrypt(cek, OaepPadding()); } } @@ -64,13 +71,13 @@ public byte[] WrapKey(byte[] cek, object key, IDictionary header #elif NETSTANDARD if (key is RSA rsa) { - return rsa.Encrypt(cek, RSAEncryptionPadding.OaepSHA256); + return rsa.Encrypt(cek, OaepPadding()); } else if (key is Jwk jwk) { if (jwk.Kty == Jwk.KeyTypes.RSA) { - return jwk.RsaKey().Encrypt(cek, RSAEncryptionPadding.OaepSHA256); + return jwk.RsaKey().Encrypt(cek, OaepPadding()); } } @@ -84,7 +91,7 @@ public byte[] Unwrap(byte[] encryptedCek, object key, int cekSizeBits, IDictiona #if NET40 if (key is CngKey cngKey) { - return RsaOaep.Decrypt(encryptedCek, cngKey, CngAlgorithm.Sha256); + return RsaOaep.Decrypt(encryptedCek, cngKey, CngAlgorithmHash()); } else if (key is RSACryptoServiceProvider rsaKey) { @@ -92,7 +99,7 @@ public byte[] Unwrap(byte[] encryptedCek, object key, int cekSizeBits, IDictiona //To be removed in 3.x var privateKey = RsaKey.New(rsaKey.ExportParameters(true)); - return RsaOaep.Decrypt(encryptedCek, privateKey, CngAlgorithm.Sha256); + return RsaOaep.Decrypt(encryptedCek, privateKey, CngAlgorithmHash()); } throw new ArgumentException("RsaKeyManagement algorithm expects key to be of CngKey type."); @@ -100,7 +107,7 @@ public byte[] Unwrap(byte[] encryptedCek, object key, int cekSizeBits, IDictiona #elif NET461 || NET472 if (key is CngKey cngKey) { - return RsaOaep.Decrypt(encryptedCek, cngKey, CngAlgorithm.Sha256); + return RsaOaep.Decrypt(encryptedCek, cngKey, CngAlgorithmHash()); } else if (key is RSACryptoServiceProvider rsaKey) { @@ -108,17 +115,17 @@ public byte[] Unwrap(byte[] encryptedCek, object key, int cekSizeBits, IDictiona //To be removed in 3.x var privateKey = RsaKey.New(rsaKey.ExportParameters(true)); - return RsaOaep.Decrypt(encryptedCek, privateKey, CngAlgorithm.Sha256); + return RsaOaep.Decrypt(encryptedCek, privateKey, CngAlgorithmHash()); } else if (key is RSA rsa) { - return rsa.Decrypt(encryptedCek, RSAEncryptionPadding.OaepSHA256); + return rsa.Decrypt(encryptedCek, OaepPadding()); } else if (key is Jwk jwk) { if (jwk.Kty == Jwk.KeyTypes.RSA) { - return jwk.RsaKey().Decrypt(encryptedCek, RSAEncryptionPadding.OaepSHA256); + return jwk.RsaKey().Decrypt(encryptedCek, OaepPadding()); } } @@ -127,18 +134,56 @@ public byte[] Unwrap(byte[] encryptedCek, object key, int cekSizeBits, IDictiona #elif NETSTANDARD if (key is RSA rsa) { - return rsa.Decrypt(encryptedCek, RSAEncryptionPadding.OaepSHA256); + return rsa.Decrypt(encryptedCek, OaepPadding()); } else if (key is Jwk jwk) { if (jwk.Kty == Jwk.KeyTypes.RSA) { - return jwk.RsaKey().Decrypt(encryptedCek, RSAEncryptionPadding.OaepSHA256); + return jwk.RsaKey().Decrypt(encryptedCek, OaepPadding()); } } throw new ArgumentException("RsaKeyManagement algorithm expects key to be of RSA type or Jwk type with kty='rsa'."); #endif } + + private CngAlgorithm CngAlgorithmHash() + { + switch (hashSizeBits) + { + case 256: + return CngAlgorithm.Sha256; + + case 384: + return CngAlgorithm.Sha384; + + case 512: + return CngAlgorithm.Sha512; + + default: + throw new ArgumentException(string.Format("Unsupported hash size: {0} bits.", hashSizeBits)); + } + } +#if NET461 || NET472 || NETSTANDARD + private RSAEncryptionPadding OaepPadding() + { + switch (hashSizeBits) + { + case 256: + return RSAEncryptionPadding.OaepSHA256; + + case 384: + return RSAEncryptionPadding.OaepSHA384; + + case 512: + return RSAEncryptionPadding.OaepSHA512; + + default: + throw new ArgumentException(string.Format("Unsupported hash size: {0} bits.", hashSizeBits)); + } + + } +#endif } } \ No newline at end of file