From d7fea891f09618cb09e581fa1042bfa56739b462 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Wed, 2 Nov 2022 03:38:43 +0100 Subject: [PATCH] Unify the instantiation of cryptographic algorithms --- Directory.Build.targets | 12 +- .../Helpers/OpenIddictHelpers.cs | 370 +++++++++++++++++- .../OpenIddictResources.resx | 5 +- .../OpenIddictClientSystemNetHttpHandlers.cs | 4 - .../OpenIddictClientWebIntegrationBuilder.cs | 3 +- .../OpenIddictClientBuilder.cs | 108 +---- .../OpenIddictClientHandlers.Protection.cs | 12 +- .../OpenIddictClientHandlers.cs | 164 +++----- .../Managers/OpenIddictApplicationManager.cs | 47 +-- .../Managers/OpenIddictTokenManager.cs | 5 +- .../OpenIddict.Server.AspNetCore.csproj | 4 + ...ServerAspNetCoreHandlers.Authentication.cs | 13 +- ...nIddictServerAspNetCoreHandlers.Session.cs | 13 +- .../OpenIddict.Server.Owin.csproj | 4 + ...IddictServerOwinHandlers.Authentication.cs | 8 +- .../OpenIddictServerOwinHandlers.Session.cs | 8 +- .../OpenIddictServerBuilder.cs | 108 +---- .../OpenIddictServerHandlers.Discovery.cs | 60 +-- .../OpenIddictServerHandlers.Exchange.cs | 36 +- .../OpenIddictServerHandlers.Protection.cs | 28 +- .../OpenIddictServerHandlers.cs | 93 ++--- ...enIddictValidationSystemNetHttpHandlers.cs | 4 - .../OpenIddictValidationBuilder.cs | 2 - ...ctServerAspNetCoreIntegrationTestServer.cs | 2 - ...nIddictServerAspNetCoreIntegrationTests.cs | 2 - ...enIddictServerOwinIntegrationTestServer.cs | 2 - .../OpenIddictServerOwinIntegrationTests.cs | 2 - ...lidationAspNetCoreIntegrationTestServer.cs | 2 - ...ictValidationAspNetCoreIntegrationTests.cs | 2 - ...dictValidationOwinIntegrationTestServer.cs | 2 - ...penIddictValidationOwinIntegrationTests.cs | 2 - 31 files changed, 557 insertions(+), 570 deletions(-) diff --git a/Directory.Build.targets b/Directory.Build.targets index 8baf140f..40527c59 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -41,22 +41,14 @@ ('$(TargetFrameworkIdentifier)' == '.NETFramework' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '4.7.2'))) Or ('$(TargetFrameworkIdentifier)' == '.NETStandard' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '2.1'))) "> $(DefineConstants);SUPPORTS_CERTIFICATE_GENERATION - $(DefineConstants);SUPPORTS_DIRECT_KEY_CREATION_WITH_SPECIFIED_SIZE $(DefineConstants);SUPPORTS_EPHEMERAL_KEY_SETS $(DefineConstants);SUPPORTS_KEY_DERIVATION_WITH_SPECIFIED_HASH_ALGORITHM - - - - $(DefineConstants);SUPPORTS_CERTIFICATE_HASHING_WITH_SPECIFIED_ALGORITHM + $(DefineConstants);SUPPORTS_RSA_KEY_CREATION_WITH_SPECIFIED_SIZE - $(DefineConstants);SUPPORTS_BASE64_SPAN_CONVERSION $(DefineConstants);SUPPORTS_BROTLI_COMPRESSION $(DefineConstants);SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS $(DefineConstants);SUPPORTS_TIME_CONSTANT_COMPARISONS @@ -84,6 +76,7 @@ Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '5.0'))) "> $(DefineConstants);SUPPORTS_MULTIPLE_VALUES_IN_QUERYHELPERS $(DefineConstants);SUPPORTS_HTTP_CLIENT_DEFAULT_REQUEST_VERSION_POLICY + $(DefineConstants);SUPPORTS_ONE_SHOT_HASHING_METHODS $(DefineConstants);SUPPORTS_PEM_ENCODED_KEY_IMPORT @@ -91,6 +84,7 @@ Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '6.0'))) "> $(DefineConstants);SUPPORTS_DIRECT_JSON_ELEMENT_SERIALIZATION $(DefineConstants);SUPPORTS_JSON_NODES + $(DefineConstants);SUPPORTS_ONE_SHOT_RANDOM_NUMBER_GENERATOR_METHODS $(DefineConstants);SUPPORTS_ZLIB_COMPRESSION diff --git a/shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs b/shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs index b1c85eec..e844cd93 100644 --- a/shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs +++ b/shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs @@ -1,4 +1,8 @@ -using System.Security.Claims; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Security.Claims; +using System.Security.Cryptography; using System.Text; using Microsoft.Extensions.Primitives; using Microsoft.IdentityModel.Tokens; @@ -239,4 +243,368 @@ internal static class OpenIddictHelpers return new ClaimsPrincipal(identity); } + +#if SUPPORTS_ECDSA + /// + /// Creates a new key. + /// + /// A new key. + /// + /// The implementation resolved from is not valid. + /// + public static ECDsa CreateEcdsaKey() + => CryptoConfig.CreateFromName("OpenIddict ECDSA Cryptographic Provider") switch + { + ECDsa result => result, + null => ECDsa.Create(), + var result => throw new CryptographicException(SR.FormatID0351(result.GetType().FullName)) + }; + + /// + /// Creates a new key. + /// + /// The EC curve to use to create the key. + /// A new key. + /// + /// The implementation resolved from is not valid. + /// + public static ECDsa CreateEcdsaKey(ECCurve curve) + { + var algorithm = CryptoConfig.CreateFromName("OpenIddict ECDSA Cryptographic Provider") switch + { + ECDsa result => result, + null => null, + var result => throw new CryptographicException(SR.FormatID0351(result.GetType().FullName)) + }; + + // If no custom algorithm was registered, use either the static Create() API + // on platforms that support it or create a default instance provided by the BCL. + if (algorithm is null) + { + return ECDsa.Create(curve); + } + + try + { + algorithm.GenerateKey(curve); + } + + catch + { + algorithm.Dispose(); + + throw; + } + + return algorithm; + } +#endif + + /// + /// Creates a new key. + /// + /// The key size to use to create the key. + /// A new key. + /// + /// The implementation resolved from is not valid. + /// + public static RSA CreateRsaKey(int size) + { + var algorithm = CryptoConfig.CreateFromName("OpenIddict RSA Cryptographic Provider") switch + { + RSA result => result, + +#if SUPPORTS_RSA_KEY_CREATION_WITH_SPECIFIED_SIZE + // Note: on .NET Framework >= 4.7.2, the new RSA.Create(int keySizeInBits) uses + // CryptoConfig.CreateFromName("RSAPSS") internally, which returns by default + // a RSACng instance instead of a RSACryptoServiceProvider based on CryptoAPI. + null => RSA.Create(size), +#else + // Note: while a RSACng object could be manually instantiated and returned on + // .NET Framework < 4.7.2, the static RSA.Create() factory (which returns a + // RSACryptoServiceProvider instance by default) is always preferred to RSACng + // as this type is known to have compatibility issues on .NET Framework < 4.6.2. + // + // Developers who prefer using a CNG-based implementation on .NET Framework 4.6.1 + // can do so by tweaking machine.config or by using CryptoConfig.AddAlgorithm(). + null => RSA.Create(), +#endif + var result => throw new CryptographicException(SR.FormatID0351(result.GetType().FullName)) + }; + + // Note: on .NET Framework, the RSA.Create() overload uses CryptoConfig.CreateFromName() + // and always returns a RSACryptoServiceProvider instance unless the default name mapping was + // explicitly overriden in machine.config or via CryptoConfig.AddAlgorithm(). Unfortunately, + // RSACryptoServiceProvider still uses 1024-bit keys by default and doesn't support changing + // the key size via RSACryptoServiceProvider.KeySize (setting it has no effect on the object). + // + // To ensure the key size matches the requested size, this method replaces the instance by a + // new RSACryptoServiceProvider using the constructor allowing to override the default key size. + try + { + if (algorithm.KeySize != size) + { + if (algorithm is RSACryptoServiceProvider) + { + algorithm.Dispose(); + algorithm = new RSACryptoServiceProvider(size); + } + + else + { + algorithm.KeySize = size; + } + + if (algorithm.KeySize != size) + { + throw new CryptographicException(SR.FormatID0059(algorithm.GetType().FullName)); + } + } + } + + catch + { + algorithm.Dispose(); + + throw; + } + + return algorithm; + } + + /// + /// Computes the SHA-256 hash of the specified array. + /// + /// The data to hash. + /// The SHA-256 hash of the specified array. + /// + /// The implementation resolved from is not valid. + /// + public static byte[] ComputeSha256Hash(byte[] data) + { + var algorithm = CryptoConfig.CreateFromName("OpenIddict SHA-256 Cryptographic Provider") switch + { + SHA256 result => result, + null => null, + var result => throw new CryptographicException(SR.FormatID0351(result.GetType().FullName)) + }; + + // If no custom algorithm was registered, use either the static/one-shot HashData() API + // on platforms that support it or create a default instance provided by the BCL. + if (algorithm is null) + { +#if SUPPORTS_ONE_SHOT_HASHING_METHODS + return SHA256.HashData(data); +#else + algorithm = SHA256.Create(); +#endif + } + + try + { + return algorithm.ComputeHash(data); + } + + finally + { + algorithm.Dispose(); + } + } + + /// + /// Computes the SHA-384 hash of the specified array. + /// + /// The data to hash. + /// The SHA-384 hash of the specified array. + /// + /// The implementation resolved from is not valid. + /// + public static byte[] ComputeSha384Hash(byte[] data) + { + var algorithm = CryptoConfig.CreateFromName("OpenIddict SHA-384 Cryptographic Provider") switch + { + SHA384 result => result, + null => null, + var result => throw new CryptographicException(SR.FormatID0351(result.GetType().FullName)) + }; + + // If no custom algorithm was registered, use either the static/one-shot HashData() API + // on platforms that support it or create a default instance provided by the BCL. + if (algorithm is null) + { +#if SUPPORTS_ONE_SHOT_HASHING_METHODS + return SHA384.HashData(data); +#else + algorithm = SHA384.Create(); +#endif + } + + try + { + return algorithm.ComputeHash(data); + } + + finally + { + algorithm.Dispose(); + } + } + + /// + /// Computes the SHA-512 hash of the specified array. + /// + /// The data to hash. + /// The SHA-512 hash of the specified array. + /// + /// The implementation resolved from is not valid. + /// + public static byte[] ComputeSha512Hash(byte[] data) + { + var algorithm = CryptoConfig.CreateFromName("OpenIddict SHA-512 Cryptographic Provider") switch + { + SHA512 result => result, + null => null, + var result => throw new CryptographicException(SR.FormatID0351(result.GetType().FullName)) + }; + + // If no custom algorithm was registered, use either the static/one-shot HashData() API + // on platforms that support it or create a default instance provided by the BCL. + if (algorithm is null) + { +#if SUPPORTS_ONE_SHOT_HASHING_METHODS + return SHA512.HashData(data); +#else + algorithm = SHA512.Create(); +#endif + } + + try + { + return algorithm.ComputeHash(data); + } + + finally + { + algorithm.Dispose(); + } + } + + /// + /// Creates a new array of containing random data. + /// + /// The desired entropy, in bits. + /// A new array of containing random data. + /// + /// The implementation resolved from is not valid. + /// + public static byte[] CreateRandomArray(int size) + { + var algorithm = CryptoConfig.CreateFromName("OpenIddict RNG Cryptographic Provider") switch + { + RandomNumberGenerator result => result, + null => null, + var result => throw new CryptographicException(SR.FormatID0351(result.GetType().FullName)) + }; + + // If no custom random number generator was registered, use either the static GetBytes() or + // Fill() APIs on platforms that support them or create a default instance provided by the BCL. +#if SUPPORTS_ONE_SHOT_RANDOM_NUMBER_GENERATOR_METHODS + if (algorithm is null) + { + return RandomNumberGenerator.GetBytes(size / 8); + } +#endif + var array = new byte[size / 8]; + +#if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS + if (algorithm is null) + { + RandomNumberGenerator.Fill(array); + return array; + } +#else + algorithm ??= RandomNumberGenerator.Create(); +#endif + try + { + algorithm.GetBytes(array); + } + + finally + { + algorithm.Dispose(); + } + + return array; + } + +#if SUPPORTS_KEY_DERIVATION_WITH_SPECIFIED_HASH_ALGORITHM + /// + /// Creates a derived key based on the specified using PBKDF2. + /// + /// The secret from which the derived key is created. + /// The salt. + /// The hash algorithm to use. + /// The number of iterations to use. + /// The desired length of the derived key. + /// A derived key based on the specified . + /// + /// The implementation resolved from is not valid. + /// + public static byte[] DeriveKey(string secret, byte[] salt, HashAlgorithmName algorithm, int iterations, int length) + { + // Warning: the type and order of the arguments specified here MUST exactly match the parameters used with + // Rfc2898DeriveBytes(string password, byte[] salt, int iterations, HashAlgorithmName hashAlgorithm). + using var generator = CryptoConfig.CreateFromName("OpenIddict PBKDF2 Cryptographic Provider", + args: new object?[] { secret, salt, iterations, algorithm }) switch + { + Rfc2898DeriveBytes result => result, + +#pragma warning disable CA5379 + null => new Rfc2898DeriveBytes(secret, salt, iterations, algorithm), +#pragma warning restore CA5379 + + var result => throw new CryptographicException(SR.FormatID0351(result.GetType().FullName)) + }; + + return generator.GetBytes(length); + } +#endif + +#if SUPPORTS_ECDSA + /// + /// Determines whether the specified represent a specific EC curve. + /// + /// The . + /// The . + /// + /// if is identical to + /// the specified , otherwise. + /// + public static bool IsEcCurve(ECParameters parameters, ECCurve curve) + { + Debug.Assert(parameters.Curve.Oid is not null, SR.GetResourceString(SR.ID4011)); + Debug.Assert(curve.Oid is not null, SR.GetResourceString(SR.ID4011)); + + // Warning: on .NET Framework 4.x and .NET Core 2.1, exported ECParameters generally have + // a null OID value attached. To work around this limitation, both the raw OID values and + // the friendly names are compared to determine whether the curve is of the specified type. + if (!string.IsNullOrEmpty(parameters.Curve.Oid.Value) && + !string.IsNullOrEmpty(curve.Oid.Value)) + { + return string.Equals(parameters.Curve.Oid.Value, + curve.Oid.Value, StringComparison.Ordinal); + } + + if (!string.IsNullOrEmpty(parameters.Curve.Oid.FriendlyName) && + !string.IsNullOrEmpty(curve.Oid.FriendlyName)) + { + return string.Equals(parameters.Curve.Oid.FriendlyName, + curve.Oid.FriendlyName, StringComparison.Ordinal); + } + + Debug.Fail(SR.GetResourceString(SR.ID4012)); + return false; + } +#endif } diff --git a/src/OpenIddict.Abstractions/OpenIddictResources.resx b/src/OpenIddict.Abstractions/OpenIddictResources.resx index e4641e06..a4e19c5a 100644 --- a/src/OpenIddict.Abstractions/OpenIddictResources.resx +++ b/src/OpenIddict.Abstractions/OpenIddictResources.resx @@ -330,7 +330,7 @@ Consider using 'options.AddEncryptionCredentials(EncryptingCredentials)' instead The specified algorithm is not supported. - RSA key generation failed. + An unspecified error occurred while trying to change the key size of a System.Security.Cryptography.RSA instance of type '{0}'. The specified certificate is not a key encryption certificate. @@ -1343,6 +1343,9 @@ Alternatively, you can disable the token storage feature by calling 'services.Ad The '{0}' setting required by the {1} provider integration must be a valid absolute URI. + + The '{0}' instance returned by CryptoConfig.CreateFromName() is not suitable for the requested operation. When registering a custom implementation of a cryptographic algorithm, make sure it inherits from the correct base type and uses the correct name (e.g "OpenIddict RSA Cryptographic Provider" implementations must derive from System.Security.Cryptography.RSA). + The security token is missing. diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs index 5d973d8c..2cd7631d 100644 --- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs +++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs @@ -44,8 +44,6 @@ public static partial class OpenIddictClientSystemNetHttpHandlers .Build(); /// - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The HTTP request message is disposed later by a dedicated handler.")] public ValueTask HandleAsync(TContext context) { if (context is null) @@ -78,8 +76,6 @@ public static partial class OpenIddictClientSystemNetHttpHandlers .Build(); /// - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The HTTP request message is disposed later by a dedicated handler.")] public ValueTask HandleAsync(TContext context) { if (context is null) diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationBuilder.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationBuilder.cs index fbbb21c5..83772560 100644 --- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationBuilder.cs +++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationBuilder.cs @@ -6,6 +6,7 @@ using System.ComponentModel; using OpenIddict.Client.WebIntegration; +using OpenIddict.Extensions; #if SUPPORTS_PEM_ENCODED_KEY_IMPORT using System.Security.Cryptography; @@ -96,7 +97,7 @@ public partial class OpenIddictClientWebIntegrationBuilder throw new ArgumentException(SR.GetResourceString(SR.ID0346), nameof(key)); } - var algorithm = ECDsa.Create(); + var algorithm = OpenIddictHelpers.CreateEcdsaKey(); try { diff --git a/src/OpenIddict.Client/OpenIddictClientBuilder.cs b/src/OpenIddict.Client/OpenIddictClientBuilder.cs index 1c906889..6e0d197f 100644 --- a/src/OpenIddict.Client/OpenIddictClientBuilder.cs +++ b/src/OpenIddict.Client/OpenIddictClientBuilder.cs @@ -13,6 +13,7 @@ using System.Security.Cryptography.X509Certificates; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.IdentityModel.Tokens; using OpenIddict.Client; +using OpenIddict.Extensions; namespace Microsoft.Extensions.DependencyInjection; @@ -191,8 +192,6 @@ public class OpenIddictClientBuilder /// /// The subject name associated with the certificate. /// The instance. - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The X.509 certificate is attached to the client options.")] public OpenIddictClientBuilder AddDevelopmentEncryptionCertificate(X500DistinguishedName subject) { if (subject is null) @@ -212,7 +211,7 @@ public class OpenIddictClientBuilder if (!certificates.Any(certificate => certificate.NotBefore < DateTime.Now && certificate.NotAfter > DateTime.Now)) { #if SUPPORTS_CERTIFICATE_GENERATION - using var algorithm = RSA.Create(keySizeInBits: 2048); + using var algorithm = OpenIddictHelpers.CreateRsaKey(size: 2048); var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment, critical: true)); @@ -291,62 +290,18 @@ public class OpenIddictClientBuilder return algorithm switch { SecurityAlgorithms.Aes256KW - => AddEncryptionCredentials(new EncryptingCredentials(CreateSymmetricSecurityKey(256), + => AddEncryptionCredentials(new EncryptingCredentials( + new SymmetricSecurityKey(OpenIddictHelpers.CreateRandomArray(size: 256)), algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)), SecurityAlgorithms.RsaOAEP or SecurityAlgorithms.RsaOaepKeyWrap - => AddEncryptionCredentials(new EncryptingCredentials(CreateRsaSecurityKey(2048), + => AddEncryptionCredentials(new EncryptingCredentials( + new RsaSecurityKey(OpenIddictHelpers.CreateRsaKey(size: 2048)), algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)), _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0058)) }; - - static SymmetricSecurityKey CreateSymmetricSecurityKey(int size) - { - var data = new byte[size / 8]; - -#if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS - RandomNumberGenerator.Fill(data); -#else - using var generator = RandomNumberGenerator.Create(); - generator.GetBytes(data); -#endif - - return new SymmetricSecurityKey(data); - } - - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The generated RSA key is attached to the client options.")] - static RsaSecurityKey CreateRsaSecurityKey(int size) - { -#if SUPPORTS_DIRECT_KEY_CREATION_WITH_SPECIFIED_SIZE - return new RsaSecurityKey(RSA.Create(size)); -#else - // Note: a 1024-bit key might be returned by RSA.Create() on .NET Desktop/Mono, - // where RSACryptoServiceProvider is still the default implementation and - // where custom implementations can be registered via CryptoConfig. - // To ensure the key size is always acceptable, replace it if necessary. - var algorithm = RSA.Create(); - if (algorithm.KeySize < size) - { - algorithm.KeySize = size; - } - - if (algorithm.KeySize < size && algorithm is RSACryptoServiceProvider) - { - algorithm.Dispose(); - algorithm = new RSACryptoServiceProvider(size); - } - - if (algorithm.KeySize < size) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0059)); - } - - return new RsaSecurityKey(algorithm); -#endif - } } /// @@ -451,8 +406,6 @@ public class OpenIddictClientBuilder /// to store the private key of the certificate. /// /// The instance. - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The X.509 certificate is attached to the client options.")] public OpenIddictClientBuilder AddEncryptionCertificate(Stream stream, string? password, X509KeyStorageFlags flags) { if (stream is null) @@ -601,8 +554,6 @@ public class OpenIddictClientBuilder /// /// The subject name associated with the certificate. /// The instance. - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The X.509 certificate is attached to the client options.")] public OpenIddictClientBuilder AddDevelopmentSigningCertificate(X500DistinguishedName subject) { if (subject is null) @@ -622,7 +573,7 @@ public class OpenIddictClientBuilder if (!certificates.Any(certificate => certificate.NotBefore < DateTime.Now && certificate.NotAfter > DateTime.Now)) { #if SUPPORTS_CERTIFICATE_GENERATION - using var algorithm = RSA.Create(keySizeInBits: 2048); + using var algorithm = OpenIddictHelpers.CreateRsaKey(size: 2048); var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, critical: true)); @@ -691,8 +642,6 @@ public class OpenIddictClientBuilder /// /// The algorithm associated with the signing key. /// The instance. - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The X.509 certificate is attached to the client options.")] public OpenIddictClientBuilder AddEphemeralSigningKey(string algorithm) { if (string.IsNullOrEmpty(algorithm)) @@ -714,23 +663,24 @@ public class OpenIddictClientBuilder SecurityAlgorithms.RsaSsaPssSha256Signature or SecurityAlgorithms.RsaSsaPssSha384Signature or SecurityAlgorithms.RsaSsaPssSha512Signature - => AddSigningCredentials(new SigningCredentials(CreateRsaSecurityKey(2048), algorithm)), + => AddSigningCredentials(new SigningCredentials(new RsaSecurityKey( + OpenIddictHelpers.CreateRsaKey(size: 2048)), algorithm)), #if SUPPORTS_ECDSA SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha256Signature => AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey( - ECDsa.Create(ECCurve.NamedCurves.nistP256)), algorithm)), + OpenIddictHelpers.CreateEcdsaKey(ECCurve.NamedCurves.nistP256)), algorithm)), SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.EcdsaSha384Signature => AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey( - ECDsa.Create(ECCurve.NamedCurves.nistP384)), algorithm)), + OpenIddictHelpers.CreateEcdsaKey(ECCurve.NamedCurves.nistP384)), algorithm)), SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.EcdsaSha512Signature => AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey( - ECDsa.Create(ECCurve.NamedCurves.nistP521)), algorithm)), + OpenIddictHelpers.CreateEcdsaKey(ECCurve.NamedCurves.nistP521)), algorithm)), #else SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha384 or @@ -743,38 +693,6 @@ public class OpenIddictClientBuilder _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0058)) }; - - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The generated RSA key is attached to the client options.")] - static RsaSecurityKey CreateRsaSecurityKey(int size) - { -#if SUPPORTS_DIRECT_KEY_CREATION_WITH_SPECIFIED_SIZE - return new RsaSecurityKey(RSA.Create(size)); -#else - // Note: a 1024-bit key might be returned by RSA.Create() on .NET Desktop/Mono, - // where RSACryptoServiceProvider is still the default implementation and - // where custom implementations can be registered via CryptoConfig. - // To ensure the key size is always acceptable, replace it if necessary. - var algorithm = RSA.Create(); - if (algorithm.KeySize < size) - { - algorithm.KeySize = size; - } - - if (algorithm.KeySize < size && algorithm is RSACryptoServiceProvider) - { - algorithm.Dispose(); - algorithm = new RSACryptoServiceProvider(size); - } - - if (algorithm.KeySize < size) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0059)); - } - - return new RsaSecurityKey(algorithm); -#endif - } } /// @@ -879,8 +797,6 @@ public class OpenIddictClientBuilder /// to store the private key of the certificate. /// /// The instance. - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The X.509 certificate is attached to the client options.")] public OpenIddictClientBuilder AddSigningCertificate(Stream stream, string? password, X509KeyStorageFlags flags) { if (stream is null) diff --git a/src/OpenIddict.Client/OpenIddictClientHandlers.Protection.cs b/src/OpenIddict.Client/OpenIddictClientHandlers.Protection.cs index aa092e24..2f6be112 100644 --- a/src/OpenIddict.Client/OpenIddictClientHandlers.Protection.cs +++ b/src/OpenIddict.Client/OpenIddictClientHandlers.Protection.cs @@ -12,6 +12,7 @@ using System.Security.Cryptography; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; +using OpenIddict.Extensions; namespace OpenIddict.Client; @@ -883,16 +884,7 @@ public static partial class OpenIddictClientHandlers // Attach the generated token to the token entry. descriptor.Payload = context.Token; descriptor.Principal = context.Principal; - - var data = new byte[256 / 8]; -#if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS - RandomNumberGenerator.Fill(data); -#else - using var generator = RandomNumberGenerator.Create(); - generator.GetBytes(data); -#endif - - descriptor.ReferenceId = Base64UrlEncoder.Encode(data); + descriptor.ReferenceId = Base64UrlEncoder.Encode(OpenIddictHelpers.CreateRandomArray(size: 256)); await _tokenManager.UpdateAsync(token, descriptor); diff --git a/src/OpenIddict.Client/OpenIddictClientHandlers.cs b/src/OpenIddict.Client/OpenIddictClientHandlers.cs index d67daac3..7b735cae 100644 --- a/src/OpenIddict.Client/OpenIddictClientHandlers.cs +++ b/src/OpenIddict.Client/OpenIddictClientHandlers.cs @@ -12,6 +12,7 @@ using System.Security.Cryptography; using System.Text; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; +using OpenIddict.Extensions; using static OpenIddict.Abstractions.OpenIddictExceptions; #if !SUPPORTS_TIME_CONSTANT_COMPARISONS @@ -1394,17 +1395,12 @@ public static partial class OpenIddictClientHandlers // claim cannot be found, it means the "alg" header of the identity token was // malformed but the token was still considered valid. While highly unlikly, // an exception is thrown in this case to abort the authentication demand. - var name = context.FrontchannelIdentityTokenPrincipal.GetClaim(Claims.Private.SigningAlgorithm); - if (string.IsNullOrEmpty(name)) + var algorithm = context.FrontchannelIdentityTokenPrincipal.GetClaim(Claims.Private.SigningAlgorithm); + if (string.IsNullOrEmpty(algorithm)) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0293)); } - // Resolve the hash algorithm corresponding to the signing algorithm. If an - // instance of the BCL hash algorithm cannot be resolved, throw an exception. - using var algorithm = GetHashAlgorithm(name) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0293)); - // If a frontchannel access token was returned in the authorization response, // ensure the at_hash claim matches the hash of the actual access token. if (!string.IsNullOrEmpty(context.FrontchannelAccessToken)) @@ -1459,44 +1455,43 @@ public static partial class OpenIddictClientHandlers } } - static byte[] ComputeTokenHash(HashAlgorithm algorithm, string token) + static byte[] ComputeTokenHash(string algorithm, byte[] data) { - // Note: only the left-most half of the access token and authorization code digest is used. - // See http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken for more information. - var digest = algorithm.ComputeHash(Encoding.ASCII.GetBytes(token)); + // Resolve the hash algorithm associated with the signing algorithm and compute the token + // hash. If an instance of the BCL hash algorithm cannot be resolved, throw an exception. + var hash = algorithm switch + { + SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.HmacSha256 or + SecurityAlgorithms.RsaSha256 or SecurityAlgorithms.RsaSsaPssSha256 + => OpenIddictHelpers.ComputeSha256Hash(data), + + SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.HmacSha384 or + SecurityAlgorithms.RsaSha384 or SecurityAlgorithms.RsaSsaPssSha384 + => OpenIddictHelpers.ComputeSha384Hash(data), - return Encoding.ASCII.GetBytes(Base64UrlEncoder.Encode(digest, 0, digest.Length / 2)); + SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.HmacSha384 or + SecurityAlgorithms.RsaSha512 or SecurityAlgorithms.RsaSsaPssSha512 + => OpenIddictHelpers.ComputeSha512Hash(data), + + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0293)) + }; + + // Warning: only the left-most half of the access token and authorization code digest is used. + // See http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken for more information. + return Encoding.ASCII.GetBytes(Base64UrlEncoder.Encode(hash, 0, hash.Length / 2)); } - static bool ValidateTokenHash(HashAlgorithm algorithm, string token, string hash) => + static bool ValidateTokenHash(string algorithm, string token, string hash) => #if SUPPORTS_TIME_CONSTANT_COMPARISONS CryptographicOperations.FixedTimeEquals( left: Encoding.ASCII.GetBytes(hash), - right: ComputeTokenHash(algorithm, token)); + right: ComputeTokenHash(algorithm, Encoding.ASCII.GetBytes(token))); #else Arrays.ConstantTimeAreEqual( a: Encoding.ASCII.GetBytes(hash), - b: ComputeTokenHash(algorithm, token)); + b: ComputeTokenHash(algorithm, Encoding.ASCII.GetBytes(token))); #endif - return default; - - static HashAlgorithm? GetHashAlgorithm(string algorithm) => algorithm switch - { - SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.HmacSha256 or - SecurityAlgorithms.RsaSha256 or SecurityAlgorithms.RsaSsaPssSha256 - => CryptoConfig.CreateFromName(SecurityAlgorithms.Sha256) as HashAlgorithm, - - SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.HmacSha384 or - SecurityAlgorithms.RsaSha384 or SecurityAlgorithms.RsaSsaPssSha384 - => CryptoConfig.CreateFromName(SecurityAlgorithms.Sha384) as HashAlgorithm, - - SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.HmacSha384 or - SecurityAlgorithms.RsaSha512 or SecurityAlgorithms.RsaSsaPssSha512 - => CryptoConfig.CreateFromName(SecurityAlgorithms.Sha512) as HashAlgorithm, - - _ => null - }; } } @@ -2659,17 +2654,12 @@ public static partial class OpenIddictClientHandlers // claim cannot be found, it means the "alg" header of the identity token was // malformed but the token was still considered valid. While highly unlikly, // an exception is thrown in this case to abort the authentication demand. - var name = context.BackchannelIdentityTokenPrincipal.GetClaim(Claims.Private.SigningAlgorithm); - if (string.IsNullOrEmpty(name)) + var algorithm = context.BackchannelIdentityTokenPrincipal.GetClaim(Claims.Private.SigningAlgorithm); + if (string.IsNullOrEmpty(algorithm)) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0295)); } - // Resolve the hash algorithm corresponding to the signing algorithm. If an - // instance of the BCL hash algorithm cannot be resolved, throw an exception. - using var algorithm = GetHashAlgorithm(name) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0295)); - // Note: the at_hash is optional for backchannel identity tokens returned from the token endpoint. // As such, the validation routine is only enforced if the at_hash claim is present in the token. // See https://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken2 for more information. @@ -2687,44 +2677,43 @@ public static partial class OpenIddictClientHandlers // Note: unlike frontchannel identity tokens, backchannel identity tokens are not expected to include // an authorization code hash as no authorization code is normally returned from the token endpoint. - static byte[] ComputeTokenHash(HashAlgorithm algorithm, string token) + static byte[] ComputeTokenHash(string algorithm, byte[] data) { - // Note: only the left-most half of the access token and authorization code digest is used. - // See http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken for more information. - var digest = algorithm.ComputeHash(Encoding.ASCII.GetBytes(token)); + // Resolve the hash algorithm associated with the signing algorithm and compute the token + // hash. If an instance of the BCL hash algorithm cannot be resolved, throw an exception. + var hash = algorithm switch + { + SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.HmacSha256 or + SecurityAlgorithms.RsaSha256 or SecurityAlgorithms.RsaSsaPssSha256 + => OpenIddictHelpers.ComputeSha256Hash(data), - return Encoding.ASCII.GetBytes(Base64UrlEncoder.Encode(digest, 0, digest.Length / 2)); + SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.HmacSha384 or + SecurityAlgorithms.RsaSha384 or SecurityAlgorithms.RsaSsaPssSha384 + => OpenIddictHelpers.ComputeSha384Hash(data), + + SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.HmacSha384 or + SecurityAlgorithms.RsaSha512 or SecurityAlgorithms.RsaSsaPssSha512 + => OpenIddictHelpers.ComputeSha512Hash(data), + + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0293)) + }; + + // Warning: only the left-most half of the access token and authorization code digest is used. + // See http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken for more information. + return Encoding.ASCII.GetBytes(Base64UrlEncoder.Encode(hash, 0, hash.Length / 2)); } - static bool ValidateTokenHash(HashAlgorithm algorithm, string token, string hash) => + static bool ValidateTokenHash(string algorithm, string token, string hash) => #if SUPPORTS_TIME_CONSTANT_COMPARISONS CryptographicOperations.FixedTimeEquals( left: Encoding.ASCII.GetBytes(hash), - right: ComputeTokenHash(algorithm, token)); + right: ComputeTokenHash(algorithm, Encoding.ASCII.GetBytes(token))); #else Arrays.ConstantTimeAreEqual( a: Encoding.ASCII.GetBytes(hash), - b: ComputeTokenHash(algorithm, token)); + b: ComputeTokenHash(algorithm, Encoding.ASCII.GetBytes(token))); #endif - return default; - - static HashAlgorithm? GetHashAlgorithm(string algorithm) => algorithm switch - { - SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.HmacSha256 or - SecurityAlgorithms.RsaSha256 or SecurityAlgorithms.RsaSsaPssSha256 - => CryptoConfig.CreateFromName(SecurityAlgorithms.Sha256) as HashAlgorithm, - - SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.HmacSha384 or - SecurityAlgorithms.RsaSha384 or SecurityAlgorithms.RsaSsaPssSha384 - => CryptoConfig.CreateFromName(SecurityAlgorithms.Sha384) as HashAlgorithm, - - SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.HmacSha384 or - SecurityAlgorithms.RsaSha512 or SecurityAlgorithms.RsaSsaPssSha512 - => CryptoConfig.CreateFromName(SecurityAlgorithms.Sha512) as HashAlgorithm, - - _ => null - }; } } @@ -4019,14 +4008,8 @@ public static partial class OpenIddictClientHandlers // Generate a new crypto-secure random identifier that will // be used as the non-guessable part of the state token. - var data = new byte[256 / 8]; -#if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS - RandomNumberGenerator.Fill(data); -#else - using var generator = RandomNumberGenerator.Create(); - generator.GetBytes(data); -#endif - context.RequestForgeryProtection = Base64UrlEncoder.Encode(data); + context.RequestForgeryProtection = Base64UrlEncoder.Encode( + OpenIddictHelpers.CreateRandomArray(size: 256)); return default; } @@ -4062,14 +4045,7 @@ public static partial class OpenIddictClientHandlers } // Generate a new crypto-secure random identifier that will be used as the nonce. - var data = new byte[256 / 8]; -#if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS - RandomNumberGenerator.Fill(data); -#else - using var generator = RandomNumberGenerator.Create(); - generator.GetBytes(data); -#endif - context.Nonce = Base64UrlEncoder.Encode(data); + context.Nonce = Base64UrlEncoder.Encode(OpenIddictHelpers.CreateRandomArray(size: 256)); return default; } @@ -4146,14 +4122,7 @@ public static partial class OpenIddictClientHandlers } // Generate a new crypto-secure random identifier that will be used as the code challenge. - var data = new byte[256 / 8]; -#if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS - RandomNumberGenerator.Fill(data); -#else - using var generator = RandomNumberGenerator.Create(); - generator.GetBytes(data); -#endif - context.CodeVerifier = Base64UrlEncoder.Encode(data); + context.CodeVerifier = Base64UrlEncoder.Encode(OpenIddictHelpers.CreateRandomArray(size: 256)); if (context.CodeChallengeMethod is CodeChallengeMethods.Plain) { @@ -4163,9 +4132,8 @@ public static partial class OpenIddictClientHandlers else if (context.CodeChallengeMethod is CodeChallengeMethods.Sha256) { - // Compute of the SHA-256 hash of the code verifier and use it as the code challenge. - using var algorithm = SHA256.Create(); - context.CodeChallenge = Base64UrlEncoder.Encode(algorithm.ComputeHash( + // Compute the SHA-256 hash of the code verifier and use it as the code challenge. + context.CodeChallenge = Base64UrlEncoder.Encode(OpenIddictHelpers.ComputeSha256Hash( Encoding.ASCII.GetBytes(context.CodeVerifier))); } @@ -4725,14 +4693,8 @@ public static partial class OpenIddictClientHandlers // Generate a new crypto-secure random identifier that will // be used as the non-guessable part of the state token. - var data = new byte[256 / 8]; -#if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS - RandomNumberGenerator.Fill(data); -#else - using var generator = RandomNumberGenerator.Create(); - generator.GetBytes(data); -#endif - context.RequestForgeryProtection = Base64UrlEncoder.Encode(data); + context.RequestForgeryProtection = Base64UrlEncoder.Encode( + OpenIddictHelpers.CreateRandomArray(size: 256)); return default; } diff --git a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs index dc945ac4..d8ee6b4a 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs @@ -1384,40 +1384,25 @@ public class OpenIddictApplicationManager : IOpenIddictApplication // Note: the PRF, iteration count, salt length and key length currently all match the default values // used by CryptoHelper and ASP.NET Core Identity but this may change in the future, if necessary. - var salt = new byte[128 / 8]; - -#if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS - RandomNumberGenerator.Fill(salt); -#else - using var generator = RandomNumberGenerator.Create(); - generator.GetBytes(salt); -#endif - + var salt = OpenIddictHelpers.CreateRandomArray(size: 128); var hash = HashSecret(secret, salt, HashAlgorithmName.SHA256, iterations: 10_000, length: 256 / 8); - return new( -#if SUPPORTS_BASE64_SPAN_CONVERSION - Convert.ToBase64String(hash) -#else - Convert.ToBase64String(hash.ToArray()) -#endif - ); + return new(Convert.ToBase64String(hash)); // Note: the following logic deliberately uses the same format as CryptoHelper (used in OpenIddict 1.x/2.x), // which was itself based on ASP.NET Core Identity's latest hashed password format. This guarantees that // secrets hashed using a recent OpenIddict version can still be read by older packages (and vice versa). - static ReadOnlySpan HashSecret(string secret, ReadOnlySpan salt, - HashAlgorithmName algorithm, int iterations, int length) + static byte[] HashSecret(string secret, byte[] salt, HashAlgorithmName algorithm, int iterations, int length) { var key = DeriveKey(secret, salt, algorithm, iterations, length); - var payload = new Span(new byte[13 + salt.Length + key.Length]); + var payload = new byte[13 + salt.Length + key.Length]; // Write the format marker. payload[0] = 0x01; // Write the hashing algorithm version. - BinaryPrimitives.WriteUInt32BigEndian(payload.Slice(1, 4), algorithm switch + BinaryPrimitives.WriteUInt32BigEndian(payload.AsSpan(1, 4), algorithm switch { var name when name == HashAlgorithmName.SHA1 => 0, var name when name == HashAlgorithmName.SHA256 => 1, @@ -1427,16 +1412,16 @@ public class OpenIddictApplicationManager : IOpenIddictApplication }); // Write the iteration count of the algorithm. - BinaryPrimitives.WriteUInt32BigEndian(payload.Slice(5, 8), (uint) iterations); + BinaryPrimitives.WriteUInt32BigEndian(payload.AsSpan(5, 8), (uint) iterations); // Write the size of the salt. - BinaryPrimitives.WriteUInt32BigEndian(payload.Slice(9, 12), (uint) salt.Length); + BinaryPrimitives.WriteUInt32BigEndian(payload.AsSpan(9, 12), (uint) salt.Length); // Write the salt. - salt.CopyTo(payload[13..]); + salt.CopyTo(payload.AsSpan(13)); // Write the subkey. - key.CopyTo(payload[(13 + salt.Length)..]); + key.CopyTo(payload.AsSpan(13 + salt.Length)); return payload; } @@ -1529,23 +1514,19 @@ public class OpenIddictApplicationManager : IOpenIddictApplication #if SUPPORTS_TIME_CONSTANT_COMPARISONS return CryptographicOperations.FixedTimeEquals( left: payload.Slice(13 + salt.Length, keyLength), - right: DeriveKey(secret, salt, algorithm, iterations, keyLength)); + right: DeriveKey(secret, salt.ToArray(), algorithm, iterations, keyLength)); #else return Arrays.ConstantTimeAreEqual( a: payload.Slice(13 + salt.Length, keyLength).ToArray(), - b: DeriveKey(secret, salt, algorithm, iterations, keyLength)); + b: DeriveKey(secret, salt.ToArray(), algorithm, iterations, keyLength)); #endif } } - [SuppressMessage("Security", "CA5379:Do not use weak key derivation function algorithm", - Justification = "The SHA-1 digest algorithm is still supported for backward compatibility.")] - private static byte[] DeriveKey(string secret, ReadOnlySpan salt, - HashAlgorithmName algorithm, int iterations, int length) + private static byte[] DeriveKey(string secret, byte[] salt, HashAlgorithmName algorithm, int iterations, int length) { #if SUPPORTS_KEY_DERIVATION_WITH_SPECIFIED_HASH_ALGORITHM - using var generator = new Rfc2898DeriveBytes(secret, salt.ToArray(), iterations, algorithm); - return generator.GetBytes(length); + return OpenIddictHelpers.DeriveKey(secret, salt, algorithm, iterations, length); #else var generator = new Pkcs5S2ParametersGenerator(algorithm switch { @@ -1556,7 +1537,7 @@ public class OpenIddictApplicationManager : IOpenIddictApplication _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0217)) }); - generator.Init(PbeParametersGenerator.Pkcs5PasswordToBytes(secret.ToCharArray()), salt.ToArray(), iterations); + generator.Init(PbeParametersGenerator.Pkcs5PasswordToBytes(secret.ToCharArray()), salt, iterations); var key = (KeyParameter) generator.GenerateDerivedMacParameters(length * 8); return key.GetKey(); diff --git a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs index f9a0a44e..4140b7c8 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs @@ -7,11 +7,11 @@ using System.Collections.Immutable; using System.ComponentModel.DataAnnotations; using System.Runtime.CompilerServices; -using System.Security.Cryptography; using System.Text; using System.Text.Json; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using OpenIddict.Extensions; using static OpenIddict.Abstractions.OpenIddictExceptions; using ValidationException = OpenIddict.Abstractions.OpenIddictExceptions.ValidationException; @@ -1331,8 +1331,7 @@ public class OpenIddictTokenManager : IOpenIddictTokenManager where TTok // Compute the digest of the generated identifier and use it as the hashed identifier of the reference token. // Doing that prevents token identifiers stolen from the database from being used as valid reference tokens. - using var algorithm = SHA256.Create(); - return new(Convert.ToBase64String(algorithm.ComputeHash(Encoding.UTF8.GetBytes(identifier)))); + return new(Convert.ToBase64String(OpenIddictHelpers.ComputeSha256Hash(Encoding.UTF8.GetBytes(identifier)))); } /// diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddict.Server.AspNetCore.csproj b/src/OpenIddict.Server.AspNetCore/OpenIddict.Server.AspNetCore.csproj index 909fec7f..f905d37c 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddict.Server.AspNetCore.csproj +++ b/src/OpenIddict.Server.AspNetCore/OpenIddict.Server.AspNetCore.csproj @@ -27,6 +27,10 @@ + + + + diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Authentication.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Authentication.cs index cf426ad0..94b786f9 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Authentication.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Authentication.cs @@ -7,7 +7,6 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Security.Claims; -using System.Security.Cryptography; using System.Text; using System.Text.Encodings.Web; using System.Text.Json; @@ -19,6 +18,7 @@ using Microsoft.Extensions.Options; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; using Microsoft.Net.Http.Headers; +using OpenIddict.Extensions; using static OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreConstants; using JsonWebTokenTypes = OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreConstants.JsonWebTokenTypes; @@ -206,16 +206,7 @@ public static partial class OpenIddictServerAspNetCoreHandlers } // Generate a 256-bit request identifier using a crypto-secure random number generator. - var data = new byte[256 / 8]; - -#if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS - RandomNumberGenerator.Fill(data); -#else - using var generator = RandomNumberGenerator.Create(); - generator.GetBytes(data); -#endif - - context.Request.RequestId = Base64UrlEncoder.Encode(data); + context.Request.RequestId = Base64UrlEncoder.Encode(OpenIddictHelpers.CreateRandomArray(size: 256)); // Build a list of claims matching the parameters extracted from the request. // diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Session.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Session.cs index 94d74586..3cafecf7 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Session.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Session.cs @@ -7,7 +7,6 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Security.Claims; -using System.Security.Cryptography; using System.Text.Json; using Microsoft.AspNetCore; using Microsoft.AspNetCore.WebUtilities; @@ -16,6 +15,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; +using OpenIddict.Extensions; using static OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreConstants; using JsonWebTokenTypes = OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreConstants.JsonWebTokenTypes; @@ -203,16 +203,7 @@ public static partial class OpenIddictServerAspNetCoreHandlers } // Generate a 256-bit request identifier using a crypto-secure random number generator. - var data = new byte[256 / 8]; - -#if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS - RandomNumberGenerator.Fill(data); -#else - using var generator = RandomNumberGenerator.Create(); - generator.GetBytes(data); -#endif - - context.Request.RequestId = Base64UrlEncoder.Encode(data); + context.Request.RequestId = Base64UrlEncoder.Encode(OpenIddictHelpers.CreateRandomArray(size: 256)); // Build a list of claims matching the parameters extracted from the request. // diff --git a/src/OpenIddict.Server.Owin/OpenIddict.Server.Owin.csproj b/src/OpenIddict.Server.Owin/OpenIddict.Server.Owin.csproj index 5370383f..4a8ebde7 100644 --- a/src/OpenIddict.Server.Owin/OpenIddict.Server.Owin.csproj +++ b/src/OpenIddict.Server.Owin/OpenIddict.Server.Owin.csproj @@ -19,6 +19,10 @@ + + + + diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Authentication.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Authentication.cs index 0da58b5b..f0d1fc56 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Authentication.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Authentication.cs @@ -7,7 +7,6 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Security.Claims; -using System.Security.Cryptography; using System.Text; using System.Text.Encodings.Web; using System.Text.Json; @@ -16,6 +15,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; +using OpenIddict.Extensions; using Owin; using static OpenIddict.Server.Owin.OpenIddictServerOwinConstants; using JsonWebTokenTypes = OpenIddict.Server.Owin.OpenIddictServerOwinConstants.JsonWebTokenTypes; @@ -202,11 +202,7 @@ public static partial class OpenIddictServerOwinHandlers } // Generate a 256-bit request identifier using a crypto-secure random number generator. - var data = new byte[256 / 8]; - using var generator = RandomNumberGenerator.Create(); - generator.GetBytes(data); - - context.Request.RequestId = Base64UrlEncoder.Encode(data); + context.Request.RequestId = Base64UrlEncoder.Encode(OpenIddictHelpers.CreateRandomArray(size: 256)); // Build a list of claims matching the parameters extracted from the request. // diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Session.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Session.cs index c8656256..6c423e2c 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Session.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Session.cs @@ -7,13 +7,13 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Security.Claims; -using System.Security.Cryptography; using System.Text.Json; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; +using OpenIddict.Extensions; using Owin; using static OpenIddict.Server.Owin.OpenIddictServerOwinConstants; using JsonWebTokenTypes = OpenIddict.Server.Owin.OpenIddictServerOwinConstants.JsonWebTokenTypes; @@ -200,11 +200,7 @@ public static partial class OpenIddictServerOwinHandlers } // Generate a 256-bit request identifier using a crypto-secure random number generator. - var data = new byte[256 / 8]; - using var generator = RandomNumberGenerator.Create(); - generator.GetBytes(data); - - context.Request.RequestId = Base64UrlEncoder.Encode(data); + context.Request.RequestId = Base64UrlEncoder.Encode(OpenIddictHelpers.CreateRandomArray(size: 256)); // Build a list of claims matching the parameters extracted from the request. // diff --git a/src/OpenIddict.Server/OpenIddictServerBuilder.cs b/src/OpenIddict.Server/OpenIddictServerBuilder.cs index fec737fe..f18572c2 100644 --- a/src/OpenIddict.Server/OpenIddictServerBuilder.cs +++ b/src/OpenIddict.Server/OpenIddictServerBuilder.cs @@ -12,6 +12,7 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.IdentityModel.Tokens; +using OpenIddict.Extensions; using OpenIddict.Server; namespace Microsoft.Extensions.DependencyInjection; @@ -200,8 +201,6 @@ public class OpenIddictServerBuilder /// /// The subject name associated with the certificate. /// The instance. - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The X.509 certificate is attached to the server options.")] public OpenIddictServerBuilder AddDevelopmentEncryptionCertificate(X500DistinguishedName subject) { if (subject is null) @@ -221,7 +220,7 @@ public class OpenIddictServerBuilder if (!certificates.Any(certificate => certificate.NotBefore < DateTime.Now && certificate.NotAfter > DateTime.Now)) { #if SUPPORTS_CERTIFICATE_GENERATION - using var algorithm = RSA.Create(keySizeInBits: 2048); + using var algorithm = OpenIddictHelpers.CreateRsaKey(size: 2048); var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment, critical: true)); @@ -300,62 +299,18 @@ public class OpenIddictServerBuilder return algorithm switch { SecurityAlgorithms.Aes256KW - => AddEncryptionCredentials(new EncryptingCredentials(CreateSymmetricSecurityKey(256), + => AddEncryptionCredentials(new EncryptingCredentials( + new SymmetricSecurityKey(OpenIddictHelpers.CreateRandomArray(size: 256)), algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)), SecurityAlgorithms.RsaOAEP or SecurityAlgorithms.RsaOaepKeyWrap - => AddEncryptionCredentials(new EncryptingCredentials(CreateRsaSecurityKey(2048), + => AddEncryptionCredentials(new EncryptingCredentials( + new RsaSecurityKey(OpenIddictHelpers.CreateRsaKey(size: 2048)), algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)), _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0058)) }; - - static SymmetricSecurityKey CreateSymmetricSecurityKey(int size) - { - var data = new byte[size / 8]; - -#if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS - RandomNumberGenerator.Fill(data); -#else - using var generator = RandomNumberGenerator.Create(); - generator.GetBytes(data); -#endif - - return new SymmetricSecurityKey(data); - } - - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The generated RSA key is attached to the server options.")] - static RsaSecurityKey CreateRsaSecurityKey(int size) - { -#if SUPPORTS_DIRECT_KEY_CREATION_WITH_SPECIFIED_SIZE - return new RsaSecurityKey(RSA.Create(size)); -#else - // Note: a 1024-bit key might be returned by RSA.Create() on .NET Desktop/Mono, - // where RSACryptoServiceProvider is still the default implementation and - // where custom implementations can be registered via CryptoConfig. - // To ensure the key size is always acceptable, replace it if necessary. - var algorithm = RSA.Create(); - if (algorithm.KeySize < size) - { - algorithm.KeySize = size; - } - - if (algorithm.KeySize < size && algorithm is RSACryptoServiceProvider) - { - algorithm.Dispose(); - algorithm = new RSACryptoServiceProvider(size); - } - - if (algorithm.KeySize < size) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0059)); - } - - return new RsaSecurityKey(algorithm); -#endif - } } /// @@ -460,8 +415,6 @@ public class OpenIddictServerBuilder /// to store the private key of the certificate. /// /// The instance. - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The X.509 certificate is attached to the server options.")] public OpenIddictServerBuilder AddEncryptionCertificate(Stream stream, string? password, X509KeyStorageFlags flags) { if (stream is null) @@ -610,8 +563,6 @@ public class OpenIddictServerBuilder /// /// The subject name associated with the certificate. /// The instance. - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The X.509 certificate is attached to the server options.")] public OpenIddictServerBuilder AddDevelopmentSigningCertificate(X500DistinguishedName subject) { if (subject is null) @@ -631,7 +582,7 @@ public class OpenIddictServerBuilder if (!certificates.Any(certificate => certificate.NotBefore < DateTime.Now && certificate.NotAfter > DateTime.Now)) { #if SUPPORTS_CERTIFICATE_GENERATION - using var algorithm = RSA.Create(keySizeInBits: 2048); + using var algorithm = OpenIddictHelpers.CreateRsaKey(size: 2048); var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, critical: true)); @@ -700,8 +651,6 @@ public class OpenIddictServerBuilder /// /// The algorithm associated with the signing key. /// The instance. - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The X.509 certificate is attached to the server options.")] public OpenIddictServerBuilder AddEphemeralSigningKey(string algorithm) { if (string.IsNullOrEmpty(algorithm)) @@ -723,23 +672,24 @@ public class OpenIddictServerBuilder SecurityAlgorithms.RsaSsaPssSha256Signature or SecurityAlgorithms.RsaSsaPssSha384Signature or SecurityAlgorithms.RsaSsaPssSha512Signature - => AddSigningCredentials(new SigningCredentials(CreateRsaSecurityKey(2048), algorithm)), + => AddSigningCredentials(new SigningCredentials(new RsaSecurityKey( + OpenIddictHelpers.CreateRsaKey(size: 2048)), algorithm)), #if SUPPORTS_ECDSA SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha256Signature => AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey( - ECDsa.Create(ECCurve.NamedCurves.nistP256)), algorithm)), + OpenIddictHelpers.CreateEcdsaKey(ECCurve.NamedCurves.nistP256)), algorithm)), SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.EcdsaSha384Signature => AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey( - ECDsa.Create(ECCurve.NamedCurves.nistP384)), algorithm)), + OpenIddictHelpers.CreateEcdsaKey(ECCurve.NamedCurves.nistP384)), algorithm)), SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.EcdsaSha512Signature => AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey( - ECDsa.Create(ECCurve.NamedCurves.nistP521)), algorithm)), + OpenIddictHelpers.CreateEcdsaKey(ECCurve.NamedCurves.nistP521)), algorithm)), #else SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha384 or @@ -752,38 +702,6 @@ public class OpenIddictServerBuilder _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0058)) }; - - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The generated RSA key is attached to the server options.")] - static RsaSecurityKey CreateRsaSecurityKey(int size) - { -#if SUPPORTS_DIRECT_KEY_CREATION_WITH_SPECIFIED_SIZE - return new RsaSecurityKey(RSA.Create(size)); -#else - // Note: a 1024-bit key might be returned by RSA.Create() on .NET Desktop/Mono, - // where RSACryptoServiceProvider is still the default implementation and - // where custom implementations can be registered via CryptoConfig. - // To ensure the key size is always acceptable, replace it if necessary. - var algorithm = RSA.Create(); - if (algorithm.KeySize < size) - { - algorithm.KeySize = size; - } - - if (algorithm.KeySize < size && algorithm is RSACryptoServiceProvider) - { - algorithm.Dispose(); - algorithm = new RSACryptoServiceProvider(size); - } - - if (algorithm.KeySize < size) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0059)); - } - - return new RsaSecurityKey(algorithm); -#endif - } } /// @@ -888,8 +806,6 @@ public class OpenIddictServerBuilder /// to store the private key of the certificate. /// /// The instance. - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The X.509 certificate is attached to the server options.")] public OpenIddictServerBuilder AddSigningCertificate(Stream stream, string? password, X509KeyStorageFlags flags) { if (stream is null) diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs index fd0d583a..979a2474 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs @@ -7,10 +7,10 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; using System.Text.Json; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; +using OpenIddict.Extensions; namespace OpenIddict.Server; @@ -1163,7 +1163,7 @@ public static partial class OpenIddictServerHandlers key.Kty = JsonWebAlgorithmsKeyTypes.RSA; // Note: both E and N must be base64url-encoded. - // See https://tools.ietf.org/html/rfc7518#section-6.3.1.1 + // See https://tools.ietf.org/html/rfc7518#section-6.3.1.1. key.E = Base64UrlEncoder.Encode(parameters.Value.Exponent); key.N = Base64UrlEncoder.Encode(parameters.Value.Modulus); } @@ -1189,9 +1189,10 @@ public static partial class OpenIddictServerHandlers continue; } - var curve = IsCurve(parameters.Value, ECCurve.NamedCurves.nistP256) ? JsonWebKeyECTypes.P256 : - IsCurve(parameters.Value, ECCurve.NamedCurves.nistP384) ? JsonWebKeyECTypes.P384 : - IsCurve(parameters.Value, ECCurve.NamedCurves.nistP521) ? JsonWebKeyECTypes.P521 : null; + var curve = + OpenIddictHelpers.IsEcCurve(parameters.Value, ECCurve.NamedCurves.nistP256) ? JsonWebKeyECTypes.P256 : + OpenIddictHelpers.IsEcCurve(parameters.Value, ECCurve.NamedCurves.nistP384) ? JsonWebKeyECTypes.P384 : + OpenIddictHelpers.IsEcCurve(parameters.Value, ECCurve.NamedCurves.nistP521) ? JsonWebKeyECTypes.P521 : null; if (string.IsNullOrEmpty(curve)) { @@ -1210,7 +1211,7 @@ public static partial class OpenIddictServerHandlers key.Crv = curve; // Note: both X and Y must be base64url-encoded. - // See https://tools.ietf.org/html/rfc7518#section-6.2.1.2 + // See https://tools.ietf.org/html/rfc7518#section-6.2.1.2. key.X = Base64UrlEncoder.Encode(parameters.Value.Q.X); key.Y = Base64UrlEncoder.Encode(parameters.Value.Q.Y); } @@ -1222,16 +1223,16 @@ public static partial class OpenIddictServerHandlers if (certificate is not null) { // x5t must be base64url-encoded. - // See https://tools.ietf.org/html/rfc7517#section-4.8 + // See https://tools.ietf.org/html/rfc7517#section-4.8. key.X5t = Base64UrlEncoder.Encode(certificate.GetCertHash()); // x5t#S256 must be base64url-encoded. - // See https://tools.ietf.org/html/rfc7517#section-4.9 - key.X5tS256 = Base64UrlEncoder.Encode(GetCertificateHash(certificate, HashAlgorithmName.SHA256)); + // See https://tools.ietf.org/html/rfc7517#section-4.9. + key.X5tS256 = Base64UrlEncoder.Encode(OpenIddictHelpers.ComputeSha256Hash(certificate.RawData)); // Unlike E or N, the certificates contained in x5c // must be base64-encoded and not base64url-encoded. - // See https://tools.ietf.org/html/rfc7517#section-4.7 + // See https://tools.ietf.org/html/rfc7517#section-4.7. key.X5c.Add(Convert.ToBase64String(certificate.RawData)); } @@ -1239,45 +1240,6 @@ public static partial class OpenIddictServerHandlers } return default; - -#if SUPPORTS_ECDSA - static bool IsCurve(ECParameters parameters, ECCurve curve) - { - Debug.Assert(parameters.Curve.Oid is not null, SR.GetResourceString(SR.ID4011)); - Debug.Assert(curve.Oid is not null, SR.GetResourceString(SR.ID4011)); - - // Warning: on .NET Framework 4.x and .NET Core 2.1, exported ECParameters generally have - // a null OID value attached. To work around this limitation, both the raw OID values and - // the friendly names are compared to determine whether the curve is of the specified type. - if (!string.IsNullOrEmpty(parameters.Curve.Oid.Value) && !string.IsNullOrEmpty(curve.Oid.Value)) - { - return string.Equals(parameters.Curve.Oid.Value, curve.Oid.Value, StringComparison.Ordinal); - } - - if (!string.IsNullOrEmpty(parameters.Curve.Oid.FriendlyName) && !string.IsNullOrEmpty(curve.Oid.FriendlyName)) - { - return string.Equals(parameters.Curve.Oid.FriendlyName, curve.Oid.FriendlyName, StringComparison.Ordinal); - } - - Debug.Fail(SR.GetResourceString(SR.ID4012)); - return false; - } -#endif - - static byte[] GetCertificateHash(X509Certificate2 certificate, HashAlgorithmName algorithm) - { -#if SUPPORTS_CERTIFICATE_HASHING_WITH_SPECIFIED_ALGORITHM - return certificate.GetCertHash(algorithm); -#else - using var hash = CryptoConfig.CreateFromName(algorithm.Name!) as HashAlgorithm; - if (hash is null or KeyedHashAlgorithm) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0217)); - } - - return hash.ComputeHash(certificate.RawData); -#endif - } } } } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs index d3086470..ce2e55fb 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs @@ -13,6 +13,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; +using OpenIddict.Extensions; #if !SUPPORTS_TIME_CONSTANT_COMPARISONS using Org.BouncyCastle.Utilities; @@ -1544,39 +1545,26 @@ public static partial class OpenIddictServerHandlers return default; } - // If no code challenge method was specified, default to S256. - var method = context.Principal.GetClaim(Claims.Private.CodeChallengeMethod); - if (string.IsNullOrEmpty(method)) + var comparand = context.Principal.GetClaim(Claims.Private.CodeChallengeMethod) switch { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0268)); - } + // Note: when using the "plain" code challenge method, no hashing is actually performed. + // In this case, the raw ASCII bytes of the verifier are directly compared to the challenge. + CodeChallengeMethods.Plain => Encoding.ASCII.GetBytes(context.Request.CodeVerifier), - // Note: when using the "plain" code challenge method, no hashing is actually performed. - // In this case, the raw ASCII bytes of the verifier are directly compared to the challenge. - byte[] data; - if (string.Equals(method, CodeChallengeMethods.Plain, StringComparison.Ordinal)) - { - data = Encoding.ASCII.GetBytes(context.Request.CodeVerifier); - } + CodeChallengeMethods.Sha256 => Encoding.ASCII.GetBytes(Base64UrlEncoder.Encode( + OpenIddictHelpers.ComputeSha256Hash(Encoding.ASCII.GetBytes(context.Request.CodeVerifier)))), - else if (string.Equals(method, CodeChallengeMethods.Sha256, StringComparison.Ordinal)) - { - using var algorithm = SHA256.Create(); - data = Encoding.ASCII.GetBytes(Base64UrlEncoder.Encode( - algorithm.ComputeHash(Encoding.ASCII.GetBytes(context.Request.CodeVerifier)))); - } + null or { Length: 0 } => throw new InvalidOperationException(SR.GetResourceString(SR.ID0268)), - else - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0045)); - } + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0045)) + }; // Compare the verifier and the code challenge: if the two don't match, return an error. // Note: to prevent timing attacks, a time-constant comparer is always used. #if SUPPORTS_TIME_CONSTANT_COMPARISONS - if (!CryptographicOperations.FixedTimeEquals(data, Encoding.ASCII.GetBytes(challenge))) + if (!CryptographicOperations.FixedTimeEquals(comparand, Encoding.ASCII.GetBytes(challenge))) #else - if (!Arrays.ConstantTimeAreEqual(data, Encoding.ASCII.GetBytes(challenge))) + if (!Arrays.ConstantTimeAreEqual(comparand, Encoding.ASCII.GetBytes(challenge))) #endif { context.Logger.LogInformation(SR.GetResourceString(SR.ID6092), Parameters.CodeVerifier); diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs index 66ce2a72..f717994f 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs @@ -8,11 +8,11 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Globalization; using System.Security.Claims; -using System.Security.Cryptography; using System.Text; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; +using OpenIddict.Extensions; namespace OpenIddict.Server; @@ -1327,18 +1327,12 @@ public static partial class OpenIddictServerHandlers { do { - var data = new byte[12]; -#if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS - RandomNumberGenerator.Fill(data); -#else - using var generator = RandomNumberGenerator.Create(); - generator.GetBytes(data); -#endif - var builder = new StringBuilder(data.Length); - - for (var index = 0; index < data.Length; index += 4) + var array = OpenIddictHelpers.CreateRandomArray(size: 12); + var builder = new StringBuilder(array.Length); + + for (var index = 0; index < array.Length; index += 4) { - builder.AppendFormat(CultureInfo.InvariantCulture, "{0:D4}", BitConverter.ToUInt32(data, index) % 10000); + builder.AppendFormat(CultureInfo.InvariantCulture, "{0:D4}", BitConverter.ToUInt32(array, index) % 10000); } descriptor.ReferenceId = builder.ToString(); @@ -1352,15 +1346,7 @@ public static partial class OpenIddictServerHandlers // For other tokens, generate a base64url-encoded 256-bit random identifier. else { - var data = new byte[256 / 8]; -#if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS - RandomNumberGenerator.Fill(data); -#else - using var generator = RandomNumberGenerator.Create(); - generator.GetBytes(data); -#endif - - descriptor.ReferenceId = Base64UrlEncoder.Encode(data); + descriptor.ReferenceId = Base64UrlEncoder.Encode(OpenIddictHelpers.CreateRandomArray(size: 256)); } await _tokenManager.UpdateAsync(token, descriptor); diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.cs index 6654e7e0..2dae0613 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.cs @@ -12,6 +12,7 @@ using System.Security.Cryptography; using System.Text; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; +using OpenIddict.Extensions; namespace OpenIddict.Server; @@ -2707,15 +2708,9 @@ public static partial class OpenIddictServerHandlers credentials => credentials.Key is AsymmetricSecurityKey) ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0266)); - using var algorithm = GetHashAlgorithm(credentials); - if (algorithm is null or KeyedHashAlgorithm) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0267)); - } - if (!string.IsNullOrEmpty(context.AccessToken)) { - var digest = algorithm.ComputeHash(Encoding.ASCII.GetBytes(context.AccessToken)); + var digest = ComputeHash(credentials, Encoding.ASCII.GetBytes(context.AccessToken)); // Note: only the left-most half of the hash is used. // See http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken @@ -2724,7 +2719,7 @@ public static partial class OpenIddictServerHandlers if (!string.IsNullOrEmpty(context.AuthorizationCode)) { - var digest = algorithm.ComputeHash(Encoding.ASCII.GetBytes(context.AuthorizationCode)); + var digest = ComputeHash(credentials, Encoding.ASCII.GetBytes(context.AuthorizationCode)); // Note: only the left-most half of the hash is used. // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken @@ -2733,63 +2728,31 @@ public static partial class OpenIddictServerHandlers return default; - static HashAlgorithm? GetHashAlgorithm(SigningCredentials credentials) - { - HashAlgorithm? hash = null; - - if (!string.IsNullOrEmpty(credentials.Digest)) - { - hash = CryptoConfig.CreateFromName(credentials.Digest) as HashAlgorithm; - } - - if (hash is null) - { - var algorithm = credentials.Digest switch - { - SecurityAlgorithms.Sha256 or SecurityAlgorithms.Sha256Digest => HashAlgorithmName.SHA256, - SecurityAlgorithms.Sha384 or SecurityAlgorithms.Sha384Digest => HashAlgorithmName.SHA384, - SecurityAlgorithms.Sha512 or SecurityAlgorithms.Sha512Digest => HashAlgorithmName.SHA512, - - _ => credentials.Algorithm switch - { -#if SUPPORTS_ECDSA - SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha256Signature - => HashAlgorithmName.SHA256, - SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.EcdsaSha384Signature - => HashAlgorithmName.SHA384, - SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.EcdsaSha512Signature - => HashAlgorithmName.SHA512, -#endif - SecurityAlgorithms.HmacSha256 or SecurityAlgorithms.HmacSha256Signature - => HashAlgorithmName.SHA256, - SecurityAlgorithms.HmacSha384 or SecurityAlgorithms.HmacSha384Signature - => HashAlgorithmName.SHA384, - SecurityAlgorithms.HmacSha512 or SecurityAlgorithms.HmacSha512Signature - => HashAlgorithmName.SHA512, - - SecurityAlgorithms.RsaSha256 or SecurityAlgorithms.RsaSha256Signature - => HashAlgorithmName.SHA256, - SecurityAlgorithms.RsaSha384 or SecurityAlgorithms.RsaSha384Signature - => HashAlgorithmName.SHA384, - SecurityAlgorithms.RsaSha512 or SecurityAlgorithms.RsaSha512Signature - => HashAlgorithmName.SHA512, - - SecurityAlgorithms.RsaSsaPssSha256 or SecurityAlgorithms.RsaSsaPssSha256Signature - => HashAlgorithmName.SHA256, - SecurityAlgorithms.RsaSsaPssSha384 or SecurityAlgorithms.RsaSsaPssSha384Signature - => HashAlgorithmName.SHA384, - SecurityAlgorithms.RsaSsaPssSha512 or SecurityAlgorithms.RsaSsaPssSha512Signature - => HashAlgorithmName.SHA512, - - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0267)) - } - }; - - hash = CryptoConfig.CreateFromName(algorithm.Name!) as HashAlgorithm; - } - - return hash; - } + static byte[] ComputeHash(SigningCredentials credentials, byte[] data) => credentials switch + { + { Digest: SecurityAlgorithms.Sha256 or SecurityAlgorithms.Sha256Digest } or + { Algorithm: SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha256Signature } or + { Algorithm: SecurityAlgorithms.HmacSha256 or SecurityAlgorithms.HmacSha256Signature } or + { Algorithm: SecurityAlgorithms.RsaSha256 or SecurityAlgorithms.RsaSha256Signature } or + { Algorithm: SecurityAlgorithms.RsaSsaPssSha256 or SecurityAlgorithms.RsaSsaPssSha256Signature } + => OpenIddictHelpers.ComputeSha256Hash(data), + + { Digest: SecurityAlgorithms.Sha384 or SecurityAlgorithms.Sha384Digest } or + { Algorithm: SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.EcdsaSha384Signature } or + { Algorithm: SecurityAlgorithms.HmacSha384 or SecurityAlgorithms.HmacSha384Signature } or + { Algorithm: SecurityAlgorithms.RsaSha384 or SecurityAlgorithms.RsaSha384Signature } or + { Algorithm: SecurityAlgorithms.RsaSsaPssSha384 or SecurityAlgorithms.RsaSsaPssSha384Signature } + => OpenIddictHelpers.ComputeSha384Hash(data), + + { Digest: SecurityAlgorithms.Sha512 or SecurityAlgorithms.Sha512Digest } or + { Algorithm: SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.EcdsaSha512Signature } or + { Algorithm: SecurityAlgorithms.HmacSha512 or SecurityAlgorithms.HmacSha512Signature } or + { Algorithm: SecurityAlgorithms.RsaSha512 or SecurityAlgorithms.RsaSha512Signature } or + { Algorithm: SecurityAlgorithms.RsaSsaPssSha512 or SecurityAlgorithms.RsaSsaPssSha512Signature } + => OpenIddictHelpers.ComputeSha512Hash(data), + + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0267)) + }; } } diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs index b0b045bc..22bda167 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs @@ -43,8 +43,6 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers .Build(); /// - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The HTTP request message is disposed later by a dedicated handler.")] public ValueTask HandleAsync(TContext context) { if (context is null) @@ -77,8 +75,6 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers .Build(); /// - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The HTTP request message is disposed later by a dedicated handler.")] public ValueTask HandleAsync(TContext context) { if (context is null) diff --git a/src/OpenIddict.Validation/OpenIddictValidationBuilder.cs b/src/OpenIddict.Validation/OpenIddictValidationBuilder.cs index 0023700f..f09c547b 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationBuilder.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationBuilder.cs @@ -276,8 +276,6 @@ public class OpenIddictValidationBuilder /// to store the private key of the certificate. /// /// The instance. - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The X.509 certificate is attached to the server options.")] public OpenIddictValidationBuilder AddEncryptionCertificate( Stream stream, string? password, X509KeyStorageFlags flags) { diff --git a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTestServer.cs b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTestServer.cs index a904ece3..393d11af 100644 --- a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTestServer.cs +++ b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTestServer.cs @@ -40,8 +40,6 @@ public class OpenIddictServerAspNetCoreIntegrationTestServer : OpenIddictServerI /// public TestServer Server { get; } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The caller is responsible for disposing the test client.")] public override ValueTask CreateClientAsync() => new(new OpenIddictServerIntegrationTestClient(Server.CreateClient())); diff --git a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs index 0f80c943..27c9f641 100644 --- a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs +++ b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs @@ -936,8 +936,6 @@ public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServ Assert.Equal("Bob l'Eponge", (string?) response["string_parameter"]); } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The caller is responsible for disposing the test server.")] protected override #if SUPPORTS_GENERIC_HOST async diff --git a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTestServer.cs b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTestServer.cs index 879a06b5..efcf25c9 100644 --- a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTestServer.cs +++ b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTestServer.cs @@ -23,8 +23,6 @@ public class OpenIddictServerOwinIntegrationTestServer : OpenIddictServerIntegra /// public TestServer Server { get; } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The caller is responsible for disposing the test client.")] public override ValueTask CreateClientAsync() => new(new OpenIddictServerIntegrationTestClient(Server.HttpClient)); diff --git a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs index 18599e14..f5936d72 100644 --- a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs +++ b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs @@ -912,8 +912,6 @@ public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerInte Assert.Equal("Bob l'Eponge", (string?) response["string_parameter"]); } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The caller is responsible for disposing the test server.")] protected override ValueTask CreateServerAsync(Action? configuration = null) { var services = new ServiceCollection(); diff --git a/test/OpenIddict.Validation.AspNetCore.IntegrationTests/OpenIddictValidationAspNetCoreIntegrationTestServer.cs b/test/OpenIddict.Validation.AspNetCore.IntegrationTests/OpenIddictValidationAspNetCoreIntegrationTestServer.cs index 67ee8e90..377d38ce 100644 --- a/test/OpenIddict.Validation.AspNetCore.IntegrationTests/OpenIddictValidationAspNetCoreIntegrationTestServer.cs +++ b/test/OpenIddict.Validation.AspNetCore.IntegrationTests/OpenIddictValidationAspNetCoreIntegrationTestServer.cs @@ -40,8 +40,6 @@ public class OpenIddictValidationAspNetCoreIntegrationTestServer : OpenIddictVal /// public TestServer Server { get; } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The caller is responsible for disposing the test client.")] public override ValueTask CreateClientAsync() => new(new OpenIddictValidationIntegrationTestClient(Server.CreateClient())); diff --git a/test/OpenIddict.Validation.AspNetCore.IntegrationTests/OpenIddictValidationAspNetCoreIntegrationTests.cs b/test/OpenIddict.Validation.AspNetCore.IntegrationTests/OpenIddictValidationAspNetCoreIntegrationTests.cs index 094c1545..f9c33465 100644 --- a/test/OpenIddict.Validation.AspNetCore.IntegrationTests/OpenIddictValidationAspNetCoreIntegrationTests.cs +++ b/test/OpenIddict.Validation.AspNetCore.IntegrationTests/OpenIddictValidationAspNetCoreIntegrationTests.cs @@ -113,8 +113,6 @@ public partial class OpenIddictValidationAspNetCoreIntegrationTests : OpenIddict Assert.Equal(new DateTimeOffset(2120, 01, 01, 00, 00, 00, TimeSpan.Zero), properties.ExpiresUtc); } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The caller is responsible for disposing the test server.")] protected override #if SUPPORTS_GENERIC_HOST async diff --git a/test/OpenIddict.Validation.Owin.IntegrationTests/OpenIddictValidationOwinIntegrationTestServer.cs b/test/OpenIddict.Validation.Owin.IntegrationTests/OpenIddictValidationOwinIntegrationTestServer.cs index 0e473410..d3e1b084 100644 --- a/test/OpenIddict.Validation.Owin.IntegrationTests/OpenIddictValidationOwinIntegrationTestServer.cs +++ b/test/OpenIddict.Validation.Owin.IntegrationTests/OpenIddictValidationOwinIntegrationTestServer.cs @@ -23,8 +23,6 @@ public class OpenIddictValidationOwinIntegrationTestValidation : OpenIddictValid /// public TestServer Server { get; } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The caller is responsible for disposing the test client.")] public override ValueTask CreateClientAsync() => new(new OpenIddictValidationIntegrationTestClient(Server.HttpClient)); diff --git a/test/OpenIddict.Validation.Owin.IntegrationTests/OpenIddictValidationOwinIntegrationTests.cs b/test/OpenIddict.Validation.Owin.IntegrationTests/OpenIddictValidationOwinIntegrationTests.cs index 4e4c1754..63067ebb 100644 --- a/test/OpenIddict.Validation.Owin.IntegrationTests/OpenIddictValidationOwinIntegrationTests.cs +++ b/test/OpenIddict.Validation.Owin.IntegrationTests/OpenIddictValidationOwinIntegrationTests.cs @@ -107,8 +107,6 @@ public partial class OpenIddictValidationOwinIntegrationTests : OpenIddictValida Assert.Equal(new DateTimeOffset(2120, 01, 01, 00, 00, 00, TimeSpan.Zero), properties.ExpiresUtc); } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The caller is responsible for disposing the test Validation.")] protected override ValueTask CreateServerAsync(Action? configuration = null) { var services = new ServiceCollection();