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();