Browse Source

Unify the instantiation of cryptographic algorithms

pull/1557/head
Kévin Chalet 3 years ago
parent
commit
d7fea891f0
  1. 12
      Directory.Build.targets
  2. 370
      shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs
  3. 5
      src/OpenIddict.Abstractions/OpenIddictResources.resx
  4. 4
      src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs
  5. 3
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationBuilder.cs
  6. 108
      src/OpenIddict.Client/OpenIddictClientBuilder.cs
  7. 12
      src/OpenIddict.Client/OpenIddictClientHandlers.Protection.cs
  8. 164
      src/OpenIddict.Client/OpenIddictClientHandlers.cs
  9. 47
      src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
  10. 5
      src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
  11. 4
      src/OpenIddict.Server.AspNetCore/OpenIddict.Server.AspNetCore.csproj
  12. 13
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Authentication.cs
  13. 13
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Session.cs
  14. 4
      src/OpenIddict.Server.Owin/OpenIddict.Server.Owin.csproj
  15. 8
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Authentication.cs
  16. 8
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Session.cs
  17. 108
      src/OpenIddict.Server/OpenIddictServerBuilder.cs
  18. 60
      src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs
  19. 36
      src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
  20. 28
      src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs
  21. 93
      src/OpenIddict.Server/OpenIddictServerHandlers.cs
  22. 4
      src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs
  23. 2
      src/OpenIddict.Validation/OpenIddictValidationBuilder.cs
  24. 2
      test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTestServer.cs
  25. 2
      test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs
  26. 2
      test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTestServer.cs
  27. 2
      test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs
  28. 2
      test/OpenIddict.Validation.AspNetCore.IntegrationTests/OpenIddictValidationAspNetCoreIntegrationTestServer.cs
  29. 2
      test/OpenIddict.Validation.AspNetCore.IntegrationTests/OpenIddictValidationAspNetCoreIntegrationTests.cs
  30. 2
      test/OpenIddict.Validation.Owin.IntegrationTests/OpenIddictValidationOwinIntegrationTestServer.cs
  31. 2
      test/OpenIddict.Validation.Owin.IntegrationTests/OpenIddictValidationOwinIntegrationTests.cs

12
Directory.Build.targets

@ -41,22 +41,14 @@
('$(TargetFrameworkIdentifier)' == '.NETFramework' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '4.7.2'))) Or ('$(TargetFrameworkIdentifier)' == '.NETFramework' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '4.7.2'))) Or
('$(TargetFrameworkIdentifier)' == '.NETStandard' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '2.1'))) "> ('$(TargetFrameworkIdentifier)' == '.NETStandard' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '2.1'))) ">
<DefineConstants>$(DefineConstants);SUPPORTS_CERTIFICATE_GENERATION</DefineConstants> <DefineConstants>$(DefineConstants);SUPPORTS_CERTIFICATE_GENERATION</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_DIRECT_KEY_CREATION_WITH_SPECIFIED_SIZE</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_EPHEMERAL_KEY_SETS</DefineConstants> <DefineConstants>$(DefineConstants);SUPPORTS_EPHEMERAL_KEY_SETS</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_KEY_DERIVATION_WITH_SPECIFIED_HASH_ALGORITHM</DefineConstants> <DefineConstants>$(DefineConstants);SUPPORTS_KEY_DERIVATION_WITH_SPECIFIED_HASH_ALGORITHM</DefineConstants>
</PropertyGroup> <DefineConstants>$(DefineConstants);SUPPORTS_RSA_KEY_CREATION_WITH_SPECIFIED_SIZE</DefineConstants>
<PropertyGroup
Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '2.1'))) Or
('$(TargetFrameworkIdentifier)' == '.NETFramework' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '4.8'))) Or
('$(TargetFrameworkIdentifier)' == '.NETStandard' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '2.1'))) ">
<DefineConstants>$(DefineConstants);SUPPORTS_CERTIFICATE_HASHING_WITH_SPECIFIED_ALGORITHM</DefineConstants>
</PropertyGroup> </PropertyGroup>
<PropertyGroup <PropertyGroup
Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '2.1'))) Or Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '2.1'))) Or
('$(TargetFrameworkIdentifier)' == '.NETStandard' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '2.1'))) "> ('$(TargetFrameworkIdentifier)' == '.NETStandard' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '2.1'))) ">
<DefineConstants>$(DefineConstants);SUPPORTS_BASE64_SPAN_CONVERSION</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_BROTLI_COMPRESSION</DefineConstants> <DefineConstants>$(DefineConstants);SUPPORTS_BROTLI_COMPRESSION</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS</DefineConstants> <DefineConstants>$(DefineConstants);SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_TIME_CONSTANT_COMPARISONS</DefineConstants> <DefineConstants>$(DefineConstants);SUPPORTS_TIME_CONSTANT_COMPARISONS</DefineConstants>
@ -84,6 +76,7 @@
Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '5.0'))) "> Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '5.0'))) ">
<DefineConstants>$(DefineConstants);SUPPORTS_MULTIPLE_VALUES_IN_QUERYHELPERS</DefineConstants> <DefineConstants>$(DefineConstants);SUPPORTS_MULTIPLE_VALUES_IN_QUERYHELPERS</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_HTTP_CLIENT_DEFAULT_REQUEST_VERSION_POLICY</DefineConstants> <DefineConstants>$(DefineConstants);SUPPORTS_HTTP_CLIENT_DEFAULT_REQUEST_VERSION_POLICY</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_ONE_SHOT_HASHING_METHODS</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_PEM_ENCODED_KEY_IMPORT</DefineConstants> <DefineConstants>$(DefineConstants);SUPPORTS_PEM_ENCODED_KEY_IMPORT</DefineConstants>
</PropertyGroup> </PropertyGroup>
@ -91,6 +84,7 @@
Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '6.0'))) "> Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '6.0'))) ">
<DefineConstants>$(DefineConstants);SUPPORTS_DIRECT_JSON_ELEMENT_SERIALIZATION</DefineConstants> <DefineConstants>$(DefineConstants);SUPPORTS_DIRECT_JSON_ELEMENT_SERIALIZATION</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_JSON_NODES</DefineConstants> <DefineConstants>$(DefineConstants);SUPPORTS_JSON_NODES</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_ONE_SHOT_RANDOM_NUMBER_GENERATOR_METHODS</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_ZLIB_COMPRESSION</DefineConstants> <DefineConstants>$(DefineConstants);SUPPORTS_ZLIB_COMPRESSION</DefineConstants>
</PropertyGroup> </PropertyGroup>

370
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 System.Text;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
@ -239,4 +243,368 @@ internal static class OpenIddictHelpers
return new ClaimsPrincipal(identity); return new ClaimsPrincipal(identity);
} }
#if SUPPORTS_ECDSA
/// <summary>
/// Creates a new <see cref="ECDsa"/> key.
/// </summary>
/// <returns>A new <see cref="ECDsa"/> key.</returns>
/// <exception cref="CryptographicException">
/// The implementation resolved from <see cref="CryptoConfig.CreateFromName(string)"/> is not valid.
/// </exception>
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))
};
/// <summary>
/// Creates a new <see cref="ECDsa"/> key.
/// </summary>
/// <param name="curve">The EC curve to use to create the key.</param>
/// <returns>A new <see cref="ECDsa"/> key.</returns>
/// <exception cref="CryptographicException">
/// The implementation resolved from <see cref="CryptoConfig.CreateFromName(string)"/> is not valid.
/// </exception>
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
/// <summary>
/// Creates a new <see cref="RSA"/> key.
/// </summary>
/// <param name="size">The key size to use to create the key.</param>
/// <returns>A new <see cref="RSA"/> key.</returns>
/// <exception cref="CryptographicException">
/// The implementation resolved from <see cref="CryptoConfig.CreateFromName(string)"/> is not valid.
/// </exception>
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;
}
/// <summary>
/// Computes the SHA-256 hash of the specified <paramref name="data"/> array.
/// </summary>
/// <param name="data">The data to hash.</param>
/// <returns>The SHA-256 hash of the specified <paramref name="data"/> array.</returns>
/// <exception cref="CryptographicException">
/// The implementation resolved from <see cref="CryptoConfig.CreateFromName(string)"/> is not valid.
/// </exception>
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();
}
}
/// <summary>
/// Computes the SHA-384 hash of the specified <paramref name="data"/> array.
/// </summary>
/// <param name="data">The data to hash.</param>
/// <returns>The SHA-384 hash of the specified <paramref name="data"/> array.</returns>
/// <exception cref="CryptographicException">
/// The implementation resolved from <see cref="CryptoConfig.CreateFromName(string)"/> is not valid.
/// </exception>
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();
}
}
/// <summary>
/// Computes the SHA-512 hash of the specified <paramref name="data"/> array.
/// </summary>
/// <param name="data">The data to hash.</param>
/// <returns>The SHA-512 hash of the specified <paramref name="data"/> array.</returns>
/// <exception cref="CryptographicException">
/// The implementation resolved from <see cref="CryptoConfig.CreateFromName(string)"/> is not valid.
/// </exception>
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();
}
}
/// <summary>
/// Creates a new array of <see cref="byte"/> containing random data.
/// </summary>
/// <param name="size">The desired entropy, in bits.</param>
/// <returns>A new array of <see cref="byte"/> containing random data.</returns>
/// <exception cref="CryptographicException">
/// The implementation resolved from <see cref="CryptoConfig.CreateFromName(string)"/> is not valid.
/// </exception>
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
/// <summary>
/// Creates a derived key based on the specified <paramref name="secret"/> using PBKDF2.
/// </summary>
/// <param name="secret">The secret from which the derived key is created.</param>
/// <param name="salt">The salt.</param>
/// <param name="algorithm">The hash algorithm to use.</param>
/// <param name="iterations">The number of iterations to use.</param>
/// <param name="length">The desired length of the derived key.</param>
/// <returns>A derived key based on the specified <paramref name="secret"/>.</returns>
/// <exception cref="CryptographicException">
/// The implementation resolved from <see cref="CryptoConfig.CreateFromName(string)"/> is not valid.
/// </exception>
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
/// <summary>
/// Determines whether the specified <paramref name="parameters"/> represent a specific EC curve.
/// </summary>
/// <param name="parameters">The <see cref="ECParameters"/>.</param>
/// <param name="curve">The <see cref="ECCurve"/>.</param>
/// <returns>
/// <see langword="true"/> if <see cref="ECParameters.Curve"/> is identical to
/// the specified <paramref name="curve"/>, <see langword="false"/> otherwise.
/// </returns>
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
} }

5
src/OpenIddict.Abstractions/OpenIddictResources.resx

@ -330,7 +330,7 @@ Consider using 'options.AddEncryptionCredentials(EncryptingCredentials)' instead
<value>The specified algorithm is not supported.</value> <value>The specified algorithm is not supported.</value>
</data> </data>
<data name="ID0059" xml:space="preserve"> <data name="ID0059" xml:space="preserve">
<value>RSA key generation failed.</value> <value>An unspecified error occurred while trying to change the key size of a System.Security.Cryptography.RSA instance of type '{0}'.</value>
</data> </data>
<data name="ID0060" xml:space="preserve"> <data name="ID0060" xml:space="preserve">
<value>The specified certificate is not a key encryption certificate.</value> <value>The specified certificate is not a key encryption certificate.</value>
@ -1343,6 +1343,9 @@ Alternatively, you can disable the token storage feature by calling 'services.Ad
<data name="ID0350" xml:space="preserve"> <data name="ID0350" xml:space="preserve">
<value>The '{0}' setting required by the {1} provider integration must be a valid absolute URI.</value> <value>The '{0}' setting required by the {1} provider integration must be a valid absolute URI.</value>
</data> </data>
<data name="ID0351" xml:space="preserve">
<value>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).</value>
</data>
<data name="ID2000" xml:space="preserve"> <data name="ID2000" xml:space="preserve">
<value>The security token is missing.</value> <value>The security token is missing.</value>
</data> </data>

4
src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs

@ -44,8 +44,6 @@ public static partial class OpenIddictClientSystemNetHttpHandlers
.Build(); .Build();
/// <inheritdoc/> /// <inheritdoc/>
[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) public ValueTask HandleAsync(TContext context)
{ {
if (context is null) if (context is null)
@ -78,8 +76,6 @@ public static partial class OpenIddictClientSystemNetHttpHandlers
.Build(); .Build();
/// <inheritdoc/> /// <inheritdoc/>
[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) public ValueTask HandleAsync(TContext context)
{ {
if (context is null) if (context is null)

3
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationBuilder.cs

@ -6,6 +6,7 @@
using System.ComponentModel; using System.ComponentModel;
using OpenIddict.Client.WebIntegration; using OpenIddict.Client.WebIntegration;
using OpenIddict.Extensions;
#if SUPPORTS_PEM_ENCODED_KEY_IMPORT #if SUPPORTS_PEM_ENCODED_KEY_IMPORT
using System.Security.Cryptography; using System.Security.Cryptography;
@ -96,7 +97,7 @@ public partial class OpenIddictClientWebIntegrationBuilder
throw new ArgumentException(SR.GetResourceString(SR.ID0346), nameof(key)); throw new ArgumentException(SR.GetResourceString(SR.ID0346), nameof(key));
} }
var algorithm = ECDsa.Create(); var algorithm = OpenIddictHelpers.CreateEcdsaKey();
try try
{ {

108
src/OpenIddict.Client/OpenIddictClientBuilder.cs

@ -13,6 +13,7 @@ using System.Security.Cryptography.X509Certificates;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using OpenIddict.Client; using OpenIddict.Client;
using OpenIddict.Extensions;
namespace Microsoft.Extensions.DependencyInjection; namespace Microsoft.Extensions.DependencyInjection;
@ -191,8 +192,6 @@ public class OpenIddictClientBuilder
/// </summary> /// </summary>
/// <param name="subject">The subject name associated with the certificate.</param> /// <param name="subject">The subject name associated with the certificate.</param>
/// <returns>The <see cref="OpenIddictClientBuilder"/> instance.</returns> /// <returns>The <see cref="OpenIddictClientBuilder"/> instance.</returns>
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope",
Justification = "The X.509 certificate is attached to the client options.")]
public OpenIddictClientBuilder AddDevelopmentEncryptionCertificate(X500DistinguishedName subject) public OpenIddictClientBuilder AddDevelopmentEncryptionCertificate(X500DistinguishedName subject)
{ {
if (subject is null) if (subject is null)
@ -212,7 +211,7 @@ public class OpenIddictClientBuilder
if (!certificates.Any(certificate => certificate.NotBefore < DateTime.Now && certificate.NotAfter > DateTime.Now)) if (!certificates.Any(certificate => certificate.NotBefore < DateTime.Now && certificate.NotAfter > DateTime.Now))
{ {
#if SUPPORTS_CERTIFICATE_GENERATION #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); var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment, critical: true)); request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment, critical: true));
@ -291,62 +290,18 @@ public class OpenIddictClientBuilder
return algorithm switch return algorithm switch
{ {
SecurityAlgorithms.Aes256KW SecurityAlgorithms.Aes256KW
=> AddEncryptionCredentials(new EncryptingCredentials(CreateSymmetricSecurityKey(256), => AddEncryptionCredentials(new EncryptingCredentials(
new SymmetricSecurityKey(OpenIddictHelpers.CreateRandomArray(size: 256)),
algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)), algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)),
SecurityAlgorithms.RsaOAEP or SecurityAlgorithms.RsaOAEP or
SecurityAlgorithms.RsaOaepKeyWrap SecurityAlgorithms.RsaOaepKeyWrap
=> AddEncryptionCredentials(new EncryptingCredentials(CreateRsaSecurityKey(2048), => AddEncryptionCredentials(new EncryptingCredentials(
new RsaSecurityKey(OpenIddictHelpers.CreateRsaKey(size: 2048)),
algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)), algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)),
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0058)) _ => 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
}
} }
/// <summary> /// <summary>
@ -451,8 +406,6 @@ public class OpenIddictClientBuilder
/// to store the private key of the certificate. /// to store the private key of the certificate.
/// </param> /// </param>
/// <returns>The <see cref="OpenIddictClientBuilder"/> instance.</returns> /// <returns>The <see cref="OpenIddictClientBuilder"/> instance.</returns>
[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) public OpenIddictClientBuilder AddEncryptionCertificate(Stream stream, string? password, X509KeyStorageFlags flags)
{ {
if (stream is null) if (stream is null)
@ -601,8 +554,6 @@ public class OpenIddictClientBuilder
/// </summary> /// </summary>
/// <param name="subject">The subject name associated with the certificate.</param> /// <param name="subject">The subject name associated with the certificate.</param>
/// <returns>The <see cref="OpenIddictClientBuilder"/> instance.</returns> /// <returns>The <see cref="OpenIddictClientBuilder"/> instance.</returns>
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope",
Justification = "The X.509 certificate is attached to the client options.")]
public OpenIddictClientBuilder AddDevelopmentSigningCertificate(X500DistinguishedName subject) public OpenIddictClientBuilder AddDevelopmentSigningCertificate(X500DistinguishedName subject)
{ {
if (subject is null) if (subject is null)
@ -622,7 +573,7 @@ public class OpenIddictClientBuilder
if (!certificates.Any(certificate => certificate.NotBefore < DateTime.Now && certificate.NotAfter > DateTime.Now)) if (!certificates.Any(certificate => certificate.NotBefore < DateTime.Now && certificate.NotAfter > DateTime.Now))
{ {
#if SUPPORTS_CERTIFICATE_GENERATION #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); var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, critical: true)); request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, critical: true));
@ -691,8 +642,6 @@ public class OpenIddictClientBuilder
/// </summary> /// </summary>
/// <param name="algorithm">The algorithm associated with the signing key.</param> /// <param name="algorithm">The algorithm associated with the signing key.</param>
/// <returns>The <see cref="OpenIddictClientBuilder"/> instance.</returns> /// <returns>The <see cref="OpenIddictClientBuilder"/> instance.</returns>
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope",
Justification = "The X.509 certificate is attached to the client options.")]
public OpenIddictClientBuilder AddEphemeralSigningKey(string algorithm) public OpenIddictClientBuilder AddEphemeralSigningKey(string algorithm)
{ {
if (string.IsNullOrEmpty(algorithm)) if (string.IsNullOrEmpty(algorithm))
@ -714,23 +663,24 @@ public class OpenIddictClientBuilder
SecurityAlgorithms.RsaSsaPssSha256Signature or SecurityAlgorithms.RsaSsaPssSha256Signature or
SecurityAlgorithms.RsaSsaPssSha384Signature or SecurityAlgorithms.RsaSsaPssSha384Signature or
SecurityAlgorithms.RsaSsaPssSha512Signature SecurityAlgorithms.RsaSsaPssSha512Signature
=> AddSigningCredentials(new SigningCredentials(CreateRsaSecurityKey(2048), algorithm)), => AddSigningCredentials(new SigningCredentials(new RsaSecurityKey(
OpenIddictHelpers.CreateRsaKey(size: 2048)), algorithm)),
#if SUPPORTS_ECDSA #if SUPPORTS_ECDSA
SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha256 or
SecurityAlgorithms.EcdsaSha256Signature SecurityAlgorithms.EcdsaSha256Signature
=> AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey( => AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey(
ECDsa.Create(ECCurve.NamedCurves.nistP256)), algorithm)), OpenIddictHelpers.CreateEcdsaKey(ECCurve.NamedCurves.nistP256)), algorithm)),
SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.EcdsaSha384 or
SecurityAlgorithms.EcdsaSha384Signature SecurityAlgorithms.EcdsaSha384Signature
=> AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey( => AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey(
ECDsa.Create(ECCurve.NamedCurves.nistP384)), algorithm)), OpenIddictHelpers.CreateEcdsaKey(ECCurve.NamedCurves.nistP384)), algorithm)),
SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.EcdsaSha512 or
SecurityAlgorithms.EcdsaSha512Signature SecurityAlgorithms.EcdsaSha512Signature
=> AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey( => AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey(
ECDsa.Create(ECCurve.NamedCurves.nistP521)), algorithm)), OpenIddictHelpers.CreateEcdsaKey(ECCurve.NamedCurves.nistP521)), algorithm)),
#else #else
SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha256 or
SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.EcdsaSha384 or
@ -743,38 +693,6 @@ public class OpenIddictClientBuilder
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0058)) _ => 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
}
} }
/// <summary> /// <summary>
@ -879,8 +797,6 @@ public class OpenIddictClientBuilder
/// to store the private key of the certificate. /// to store the private key of the certificate.
/// </param> /// </param>
/// <returns>The <see cref="OpenIddictClientBuilder"/> instance.</returns> /// <returns>The <see cref="OpenIddictClientBuilder"/> instance.</returns>
[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) public OpenIddictClientBuilder AddSigningCertificate(Stream stream, string? password, X509KeyStorageFlags flags)
{ {
if (stream is null) if (stream is null)

12
src/OpenIddict.Client/OpenIddictClientHandlers.Protection.cs

@ -12,6 +12,7 @@ using System.Security.Cryptography;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using OpenIddict.Extensions;
namespace OpenIddict.Client; namespace OpenIddict.Client;
@ -883,16 +884,7 @@ public static partial class OpenIddictClientHandlers
// Attach the generated token to the token entry. // Attach the generated token to the token entry.
descriptor.Payload = context.Token; descriptor.Payload = context.Token;
descriptor.Principal = context.Principal; descriptor.Principal = context.Principal;
descriptor.ReferenceId = Base64UrlEncoder.Encode(OpenIddictHelpers.CreateRandomArray(size: 256));
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);
await _tokenManager.UpdateAsync(token, descriptor); await _tokenManager.UpdateAsync(token, descriptor);

164
src/OpenIddict.Client/OpenIddictClientHandlers.cs

@ -12,6 +12,7 @@ using System.Security.Cryptography;
using System.Text; using System.Text;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using OpenIddict.Extensions;
using static OpenIddict.Abstractions.OpenIddictExceptions; using static OpenIddict.Abstractions.OpenIddictExceptions;
#if !SUPPORTS_TIME_CONSTANT_COMPARISONS #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 // 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, // malformed but the token was still considered valid. While highly unlikly,
// an exception is thrown in this case to abort the authentication demand. // an exception is thrown in this case to abort the authentication demand.
var name = context.FrontchannelIdentityTokenPrincipal.GetClaim(Claims.Private.SigningAlgorithm); var algorithm = context.FrontchannelIdentityTokenPrincipal.GetClaim(Claims.Private.SigningAlgorithm);
if (string.IsNullOrEmpty(name)) if (string.IsNullOrEmpty(algorithm))
{ {
throw new InvalidOperationException(SR.GetResourceString(SR.ID0293)); 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, // If a frontchannel access token was returned in the authorization response,
// ensure the at_hash claim matches the hash of the actual access token. // ensure the at_hash claim matches the hash of the actual access token.
if (!string.IsNullOrEmpty(context.FrontchannelAccessToken)) 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. // Resolve the hash algorithm associated with the signing algorithm and compute the token
// See http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken for more information. // hash. If an instance of the BCL hash algorithm cannot be resolved, throw an exception.
var digest = algorithm.ComputeHash(Encoding.ASCII.GetBytes(token)); 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 #if SUPPORTS_TIME_CONSTANT_COMPARISONS
CryptographicOperations.FixedTimeEquals( CryptographicOperations.FixedTimeEquals(
left: Encoding.ASCII.GetBytes(hash), left: Encoding.ASCII.GetBytes(hash),
right: ComputeTokenHash(algorithm, token)); right: ComputeTokenHash(algorithm, Encoding.ASCII.GetBytes(token)));
#else #else
Arrays.ConstantTimeAreEqual( Arrays.ConstantTimeAreEqual(
a: Encoding.ASCII.GetBytes(hash), a: Encoding.ASCII.GetBytes(hash),
b: ComputeTokenHash(algorithm, token)); b: ComputeTokenHash(algorithm, Encoding.ASCII.GetBytes(token)));
#endif #endif
return default; 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 // 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, // malformed but the token was still considered valid. While highly unlikly,
// an exception is thrown in this case to abort the authentication demand. // an exception is thrown in this case to abort the authentication demand.
var name = context.BackchannelIdentityTokenPrincipal.GetClaim(Claims.Private.SigningAlgorithm); var algorithm = context.BackchannelIdentityTokenPrincipal.GetClaim(Claims.Private.SigningAlgorithm);
if (string.IsNullOrEmpty(name)) if (string.IsNullOrEmpty(algorithm))
{ {
throw new InvalidOperationException(SR.GetResourceString(SR.ID0295)); 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. // 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. // 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. // 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 // 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. // 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. // Resolve the hash algorithm associated with the signing algorithm and compute the token
// See http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken for more information. // hash. If an instance of the BCL hash algorithm cannot be resolved, throw an exception.
var digest = algorithm.ComputeHash(Encoding.ASCII.GetBytes(token)); 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 #if SUPPORTS_TIME_CONSTANT_COMPARISONS
CryptographicOperations.FixedTimeEquals( CryptographicOperations.FixedTimeEquals(
left: Encoding.ASCII.GetBytes(hash), left: Encoding.ASCII.GetBytes(hash),
right: ComputeTokenHash(algorithm, token)); right: ComputeTokenHash(algorithm, Encoding.ASCII.GetBytes(token)));
#else #else
Arrays.ConstantTimeAreEqual( Arrays.ConstantTimeAreEqual(
a: Encoding.ASCII.GetBytes(hash), a: Encoding.ASCII.GetBytes(hash),
b: ComputeTokenHash(algorithm, token)); b: ComputeTokenHash(algorithm, Encoding.ASCII.GetBytes(token)));
#endif #endif
return default; 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 // Generate a new crypto-secure random identifier that will
// be used as the non-guessable part of the state token. // be used as the non-guessable part of the state token.
var data = new byte[256 / 8]; context.RequestForgeryProtection = Base64UrlEncoder.Encode(
#if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS OpenIddictHelpers.CreateRandomArray(size: 256));
RandomNumberGenerator.Fill(data);
#else
using var generator = RandomNumberGenerator.Create();
generator.GetBytes(data);
#endif
context.RequestForgeryProtection = Base64UrlEncoder.Encode(data);
return default; 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. // Generate a new crypto-secure random identifier that will be used as the nonce.
var data = new byte[256 / 8]; context.Nonce = Base64UrlEncoder.Encode(OpenIddictHelpers.CreateRandomArray(size: 256));
#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);
return default; 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. // Generate a new crypto-secure random identifier that will be used as the code challenge.
var data = new byte[256 / 8]; context.CodeVerifier = Base64UrlEncoder.Encode(OpenIddictHelpers.CreateRandomArray(size: 256));
#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);
if (context.CodeChallengeMethod is CodeChallengeMethods.Plain) if (context.CodeChallengeMethod is CodeChallengeMethods.Plain)
{ {
@ -4163,9 +4132,8 @@ public static partial class OpenIddictClientHandlers
else if (context.CodeChallengeMethod is CodeChallengeMethods.Sha256) else if (context.CodeChallengeMethod is CodeChallengeMethods.Sha256)
{ {
// Compute of the SHA-256 hash of the code verifier and use it as the code challenge. // Compute the SHA-256 hash of the code verifier and use it as the code challenge.
using var algorithm = SHA256.Create(); context.CodeChallenge = Base64UrlEncoder.Encode(OpenIddictHelpers.ComputeSha256Hash(
context.CodeChallenge = Base64UrlEncoder.Encode(algorithm.ComputeHash(
Encoding.ASCII.GetBytes(context.CodeVerifier))); Encoding.ASCII.GetBytes(context.CodeVerifier)));
} }
@ -4725,14 +4693,8 @@ public static partial class OpenIddictClientHandlers
// Generate a new crypto-secure random identifier that will // Generate a new crypto-secure random identifier that will
// be used as the non-guessable part of the state token. // be used as the non-guessable part of the state token.
var data = new byte[256 / 8]; context.RequestForgeryProtection = Base64UrlEncoder.Encode(
#if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS OpenIddictHelpers.CreateRandomArray(size: 256));
RandomNumberGenerator.Fill(data);
#else
using var generator = RandomNumberGenerator.Create();
generator.GetBytes(data);
#endif
context.RequestForgeryProtection = Base64UrlEncoder.Encode(data);
return default; return default;
} }

47
src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs

@ -1384,40 +1384,25 @@ public class OpenIddictApplicationManager<TApplication> : IOpenIddictApplication
// Note: the PRF, iteration count, salt length and key length currently all match the default values // 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. // used by CryptoHelper and ASP.NET Core Identity but this may change in the future, if necessary.
var salt = new byte[128 / 8]; var salt = OpenIddictHelpers.CreateRandomArray(size: 128);
#if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS
RandomNumberGenerator.Fill(salt);
#else
using var generator = RandomNumberGenerator.Create();
generator.GetBytes(salt);
#endif
var hash = HashSecret(secret, salt, HashAlgorithmName.SHA256, iterations: 10_000, length: 256 / 8); var hash = HashSecret(secret, salt, HashAlgorithmName.SHA256, iterations: 10_000, length: 256 / 8);
return new( return new(Convert.ToBase64String(hash));
#if SUPPORTS_BASE64_SPAN_CONVERSION
Convert.ToBase64String(hash)
#else
Convert.ToBase64String(hash.ToArray())
#endif
);
// Note: the following logic deliberately uses the same format as CryptoHelper (used in OpenIddict 1.x/2.x), // 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 // 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). // secrets hashed using a recent OpenIddict version can still be read by older packages (and vice versa).
static ReadOnlySpan<byte> HashSecret(string secret, ReadOnlySpan<byte> salt, static byte[] HashSecret(string secret, byte[] salt, HashAlgorithmName algorithm, int iterations, int length)
HashAlgorithmName algorithm, int iterations, int length)
{ {
var key = DeriveKey(secret, salt, algorithm, iterations, length); var key = DeriveKey(secret, salt, algorithm, iterations, length);
var payload = new Span<byte>(new byte[13 + salt.Length + key.Length]); var payload = new byte[13 + salt.Length + key.Length];
// Write the format marker. // Write the format marker.
payload[0] = 0x01; payload[0] = 0x01;
// Write the hashing algorithm version. // 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.SHA1 => 0,
var name when name == HashAlgorithmName.SHA256 => 1, var name when name == HashAlgorithmName.SHA256 => 1,
@ -1427,16 +1412,16 @@ public class OpenIddictApplicationManager<TApplication> : IOpenIddictApplication
}); });
// Write the iteration count of the algorithm. // 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. // 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. // Write the salt.
salt.CopyTo(payload[13..]); salt.CopyTo(payload.AsSpan(13));
// Write the subkey. // Write the subkey.
key.CopyTo(payload[(13 + salt.Length)..]); key.CopyTo(payload.AsSpan(13 + salt.Length));
return payload; return payload;
} }
@ -1529,23 +1514,19 @@ public class OpenIddictApplicationManager<TApplication> : IOpenIddictApplication
#if SUPPORTS_TIME_CONSTANT_COMPARISONS #if SUPPORTS_TIME_CONSTANT_COMPARISONS
return CryptographicOperations.FixedTimeEquals( return CryptographicOperations.FixedTimeEquals(
left: payload.Slice(13 + salt.Length, keyLength), left: payload.Slice(13 + salt.Length, keyLength),
right: DeriveKey(secret, salt, algorithm, iterations, keyLength)); right: DeriveKey(secret, salt.ToArray(), algorithm, iterations, keyLength));
#else #else
return Arrays.ConstantTimeAreEqual( return Arrays.ConstantTimeAreEqual(
a: payload.Slice(13 + salt.Length, keyLength).ToArray(), a: payload.Slice(13 + salt.Length, keyLength).ToArray(),
b: DeriveKey(secret, salt, algorithm, iterations, keyLength)); b: DeriveKey(secret, salt.ToArray(), algorithm, iterations, keyLength));
#endif #endif
} }
} }
[SuppressMessage("Security", "CA5379:Do not use weak key derivation function algorithm", private static byte[] DeriveKey(string secret, byte[] salt, HashAlgorithmName algorithm, int iterations, int length)
Justification = "The SHA-1 digest algorithm is still supported for backward compatibility.")]
private static byte[] DeriveKey(string secret, ReadOnlySpan<byte> salt,
HashAlgorithmName algorithm, int iterations, int length)
{ {
#if SUPPORTS_KEY_DERIVATION_WITH_SPECIFIED_HASH_ALGORITHM #if SUPPORTS_KEY_DERIVATION_WITH_SPECIFIED_HASH_ALGORITHM
using var generator = new Rfc2898DeriveBytes(secret, salt.ToArray(), iterations, algorithm); return OpenIddictHelpers.DeriveKey(secret, salt, algorithm, iterations, length);
return generator.GetBytes(length);
#else #else
var generator = new Pkcs5S2ParametersGenerator(algorithm switch var generator = new Pkcs5S2ParametersGenerator(algorithm switch
{ {
@ -1556,7 +1537,7 @@ public class OpenIddictApplicationManager<TApplication> : IOpenIddictApplication
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0217)) _ => 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); var key = (KeyParameter) generator.GenerateDerivedMacParameters(length * 8);
return key.GetKey(); return key.GetKey();

5
src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs

@ -7,11 +7,11 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using OpenIddict.Extensions;
using static OpenIddict.Abstractions.OpenIddictExceptions; using static OpenIddict.Abstractions.OpenIddictExceptions;
using ValidationException = OpenIddict.Abstractions.OpenIddictExceptions.ValidationException; using ValidationException = OpenIddict.Abstractions.OpenIddictExceptions.ValidationException;
@ -1331,8 +1331,7 @@ public class OpenIddictTokenManager<TToken> : IOpenIddictTokenManager where TTok
// Compute the digest of the generated identifier and use it as the hashed identifier of the reference token. // 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. // 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(OpenIddictHelpers.ComputeSha256Hash(Encoding.UTF8.GetBytes(identifier))));
return new(Convert.ToBase64String(algorithm.ComputeHash(Encoding.UTF8.GetBytes(identifier))));
} }
/// <inheritdoc/> /// <inheritdoc/>

4
src/OpenIddict.Server.AspNetCore/OpenIddict.Server.AspNetCore.csproj

@ -27,6 +27,10 @@
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" /> <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Include="..\..\shared\OpenIddict.Extensions\*\*.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Using Include="Microsoft.AspNetCore.Authentication" /> <Using Include="Microsoft.AspNetCore.Authentication" />
<Using Include="Microsoft.AspNetCore.Http" /> <Using Include="Microsoft.AspNetCore.Http" />

13
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Authentication.cs

@ -7,7 +7,6 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Security.Claims; using System.Security.Claims;
using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using System.Text.Json; using System.Text.Json;
@ -19,6 +18,7 @@ using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
using OpenIddict.Extensions;
using static OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreConstants; using static OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreConstants;
using JsonWebTokenTypes = OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreConstants.JsonWebTokenTypes; 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. // Generate a 256-bit request identifier using a crypto-secure random number generator.
var data = new byte[256 / 8]; context.Request.RequestId = Base64UrlEncoder.Encode(OpenIddictHelpers.CreateRandomArray(size: 256));
#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);
// Build a list of claims matching the parameters extracted from the request. // Build a list of claims matching the parameters extracted from the request.
// //

13
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Session.cs

@ -7,7 +7,6 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Security.Claims; using System.Security.Claims;
using System.Security.Cryptography;
using System.Text.Json; using System.Text.Json;
using Microsoft.AspNetCore; using Microsoft.AspNetCore;
using Microsoft.AspNetCore.WebUtilities; using Microsoft.AspNetCore.WebUtilities;
@ -16,6 +15,7 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using OpenIddict.Extensions;
using static OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreConstants; using static OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreConstants;
using JsonWebTokenTypes = OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreConstants.JsonWebTokenTypes; 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. // Generate a 256-bit request identifier using a crypto-secure random number generator.
var data = new byte[256 / 8]; context.Request.RequestId = Base64UrlEncoder.Encode(OpenIddictHelpers.CreateRandomArray(size: 256));
#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);
// Build a list of claims matching the parameters extracted from the request. // Build a list of claims matching the parameters extracted from the request.
// //

4
src/OpenIddict.Server.Owin/OpenIddict.Server.Owin.csproj

@ -19,6 +19,10 @@
<PackageReference Include="Microsoft.Owin.Security" /> <PackageReference Include="Microsoft.Owin.Security" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Include="..\..\shared\OpenIddict.Extensions\*\*.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Using Include="Microsoft.Owin" /> <Using Include="Microsoft.Owin" />
<Using Include="Microsoft.Owin.Infrastructure" /> <Using Include="Microsoft.Owin.Infrastructure" />

8
src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Authentication.cs

@ -7,7 +7,6 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Security.Claims; using System.Security.Claims;
using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using System.Text.Json; using System.Text.Json;
@ -16,6 +15,7 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using OpenIddict.Extensions;
using Owin; using Owin;
using static OpenIddict.Server.Owin.OpenIddictServerOwinConstants; using static OpenIddict.Server.Owin.OpenIddictServerOwinConstants;
using JsonWebTokenTypes = OpenIddict.Server.Owin.OpenIddictServerOwinConstants.JsonWebTokenTypes; 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. // Generate a 256-bit request identifier using a crypto-secure random number generator.
var data = new byte[256 / 8]; context.Request.RequestId = Base64UrlEncoder.Encode(OpenIddictHelpers.CreateRandomArray(size: 256));
using var generator = RandomNumberGenerator.Create();
generator.GetBytes(data);
context.Request.RequestId = Base64UrlEncoder.Encode(data);
// Build a list of claims matching the parameters extracted from the request. // Build a list of claims matching the parameters extracted from the request.
// //

8
src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Session.cs

@ -7,13 +7,13 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Security.Claims; using System.Security.Claims;
using System.Security.Cryptography;
using System.Text.Json; using System.Text.Json;
using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using OpenIddict.Extensions;
using Owin; using Owin;
using static OpenIddict.Server.Owin.OpenIddictServerOwinConstants; using static OpenIddict.Server.Owin.OpenIddictServerOwinConstants;
using JsonWebTokenTypes = OpenIddict.Server.Owin.OpenIddictServerOwinConstants.JsonWebTokenTypes; 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. // Generate a 256-bit request identifier using a crypto-secure random number generator.
var data = new byte[256 / 8]; context.Request.RequestId = Base64UrlEncoder.Encode(OpenIddictHelpers.CreateRandomArray(size: 256));
using var generator = RandomNumberGenerator.Create();
generator.GetBytes(data);
context.Request.RequestId = Base64UrlEncoder.Encode(data);
// Build a list of claims matching the parameters extracted from the request. // Build a list of claims matching the parameters extracted from the request.
// //

108
src/OpenIddict.Server/OpenIddictServerBuilder.cs

@ -12,6 +12,7 @@ using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using OpenIddict.Extensions;
using OpenIddict.Server; using OpenIddict.Server;
namespace Microsoft.Extensions.DependencyInjection; namespace Microsoft.Extensions.DependencyInjection;
@ -200,8 +201,6 @@ public class OpenIddictServerBuilder
/// </summary> /// </summary>
/// <param name="subject">The subject name associated with the certificate.</param> /// <param name="subject">The subject name associated with the certificate.</param>
/// <returns>The <see cref="OpenIddictServerBuilder"/> instance.</returns> /// <returns>The <see cref="OpenIddictServerBuilder"/> instance.</returns>
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope",
Justification = "The X.509 certificate is attached to the server options.")]
public OpenIddictServerBuilder AddDevelopmentEncryptionCertificate(X500DistinguishedName subject) public OpenIddictServerBuilder AddDevelopmentEncryptionCertificate(X500DistinguishedName subject)
{ {
if (subject is null) if (subject is null)
@ -221,7 +220,7 @@ public class OpenIddictServerBuilder
if (!certificates.Any(certificate => certificate.NotBefore < DateTime.Now && certificate.NotAfter > DateTime.Now)) if (!certificates.Any(certificate => certificate.NotBefore < DateTime.Now && certificate.NotAfter > DateTime.Now))
{ {
#if SUPPORTS_CERTIFICATE_GENERATION #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); var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment, critical: true)); request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment, critical: true));
@ -300,62 +299,18 @@ public class OpenIddictServerBuilder
return algorithm switch return algorithm switch
{ {
SecurityAlgorithms.Aes256KW SecurityAlgorithms.Aes256KW
=> AddEncryptionCredentials(new EncryptingCredentials(CreateSymmetricSecurityKey(256), => AddEncryptionCredentials(new EncryptingCredentials(
new SymmetricSecurityKey(OpenIddictHelpers.CreateRandomArray(size: 256)),
algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)), algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)),
SecurityAlgorithms.RsaOAEP or SecurityAlgorithms.RsaOAEP or
SecurityAlgorithms.RsaOaepKeyWrap SecurityAlgorithms.RsaOaepKeyWrap
=> AddEncryptionCredentials(new EncryptingCredentials(CreateRsaSecurityKey(2048), => AddEncryptionCredentials(new EncryptingCredentials(
new RsaSecurityKey(OpenIddictHelpers.CreateRsaKey(size: 2048)),
algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)), algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)),
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0058)) _ => 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
}
} }
/// <summary> /// <summary>
@ -460,8 +415,6 @@ public class OpenIddictServerBuilder
/// to store the private key of the certificate. /// to store the private key of the certificate.
/// </param> /// </param>
/// <returns>The <see cref="OpenIddictServerBuilder"/> instance.</returns> /// <returns>The <see cref="OpenIddictServerBuilder"/> instance.</returns>
[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) public OpenIddictServerBuilder AddEncryptionCertificate(Stream stream, string? password, X509KeyStorageFlags flags)
{ {
if (stream is null) if (stream is null)
@ -610,8 +563,6 @@ public class OpenIddictServerBuilder
/// </summary> /// </summary>
/// <param name="subject">The subject name associated with the certificate.</param> /// <param name="subject">The subject name associated with the certificate.</param>
/// <returns>The <see cref="OpenIddictServerBuilder"/> instance.</returns> /// <returns>The <see cref="OpenIddictServerBuilder"/> instance.</returns>
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope",
Justification = "The X.509 certificate is attached to the server options.")]
public OpenIddictServerBuilder AddDevelopmentSigningCertificate(X500DistinguishedName subject) public OpenIddictServerBuilder AddDevelopmentSigningCertificate(X500DistinguishedName subject)
{ {
if (subject is null) if (subject is null)
@ -631,7 +582,7 @@ public class OpenIddictServerBuilder
if (!certificates.Any(certificate => certificate.NotBefore < DateTime.Now && certificate.NotAfter > DateTime.Now)) if (!certificates.Any(certificate => certificate.NotBefore < DateTime.Now && certificate.NotAfter > DateTime.Now))
{ {
#if SUPPORTS_CERTIFICATE_GENERATION #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); var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, critical: true)); request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, critical: true));
@ -700,8 +651,6 @@ public class OpenIddictServerBuilder
/// </summary> /// </summary>
/// <param name="algorithm">The algorithm associated with the signing key.</param> /// <param name="algorithm">The algorithm associated with the signing key.</param>
/// <returns>The <see cref="OpenIddictServerBuilder"/> instance.</returns> /// <returns>The <see cref="OpenIddictServerBuilder"/> instance.</returns>
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope",
Justification = "The X.509 certificate is attached to the server options.")]
public OpenIddictServerBuilder AddEphemeralSigningKey(string algorithm) public OpenIddictServerBuilder AddEphemeralSigningKey(string algorithm)
{ {
if (string.IsNullOrEmpty(algorithm)) if (string.IsNullOrEmpty(algorithm))
@ -723,23 +672,24 @@ public class OpenIddictServerBuilder
SecurityAlgorithms.RsaSsaPssSha256Signature or SecurityAlgorithms.RsaSsaPssSha256Signature or
SecurityAlgorithms.RsaSsaPssSha384Signature or SecurityAlgorithms.RsaSsaPssSha384Signature or
SecurityAlgorithms.RsaSsaPssSha512Signature SecurityAlgorithms.RsaSsaPssSha512Signature
=> AddSigningCredentials(new SigningCredentials(CreateRsaSecurityKey(2048), algorithm)), => AddSigningCredentials(new SigningCredentials(new RsaSecurityKey(
OpenIddictHelpers.CreateRsaKey(size: 2048)), algorithm)),
#if SUPPORTS_ECDSA #if SUPPORTS_ECDSA
SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha256 or
SecurityAlgorithms.EcdsaSha256Signature SecurityAlgorithms.EcdsaSha256Signature
=> AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey( => AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey(
ECDsa.Create(ECCurve.NamedCurves.nistP256)), algorithm)), OpenIddictHelpers.CreateEcdsaKey(ECCurve.NamedCurves.nistP256)), algorithm)),
SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.EcdsaSha384 or
SecurityAlgorithms.EcdsaSha384Signature SecurityAlgorithms.EcdsaSha384Signature
=> AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey( => AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey(
ECDsa.Create(ECCurve.NamedCurves.nistP384)), algorithm)), OpenIddictHelpers.CreateEcdsaKey(ECCurve.NamedCurves.nistP384)), algorithm)),
SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.EcdsaSha512 or
SecurityAlgorithms.EcdsaSha512Signature SecurityAlgorithms.EcdsaSha512Signature
=> AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey( => AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey(
ECDsa.Create(ECCurve.NamedCurves.nistP521)), algorithm)), OpenIddictHelpers.CreateEcdsaKey(ECCurve.NamedCurves.nistP521)), algorithm)),
#else #else
SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha256 or
SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.EcdsaSha384 or
@ -752,38 +702,6 @@ public class OpenIddictServerBuilder
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0058)) _ => 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
}
} }
/// <summary> /// <summary>
@ -888,8 +806,6 @@ public class OpenIddictServerBuilder
/// to store the private key of the certificate. /// to store the private key of the certificate.
/// </param> /// </param>
/// <returns>The <see cref="OpenIddictServerBuilder"/> instance.</returns> /// <returns>The <see cref="OpenIddictServerBuilder"/> instance.</returns>
[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) public OpenIddictServerBuilder AddSigningCertificate(Stream stream, string? password, X509KeyStorageFlags flags)
{ {
if (stream is null) if (stream is null)

60
src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs

@ -7,10 +7,10 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text.Json; using System.Text.Json;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using OpenIddict.Extensions;
namespace OpenIddict.Server; namespace OpenIddict.Server;
@ -1163,7 +1163,7 @@ public static partial class OpenIddictServerHandlers
key.Kty = JsonWebAlgorithmsKeyTypes.RSA; key.Kty = JsonWebAlgorithmsKeyTypes.RSA;
// Note: both E and N must be base64url-encoded. // 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.E = Base64UrlEncoder.Encode(parameters.Value.Exponent);
key.N = Base64UrlEncoder.Encode(parameters.Value.Modulus); key.N = Base64UrlEncoder.Encode(parameters.Value.Modulus);
} }
@ -1189,9 +1189,10 @@ public static partial class OpenIddictServerHandlers
continue; continue;
} }
var curve = IsCurve(parameters.Value, ECCurve.NamedCurves.nistP256) ? JsonWebKeyECTypes.P256 : var curve =
IsCurve(parameters.Value, ECCurve.NamedCurves.nistP384) ? JsonWebKeyECTypes.P384 : OpenIddictHelpers.IsEcCurve(parameters.Value, ECCurve.NamedCurves.nistP256) ? JsonWebKeyECTypes.P256 :
IsCurve(parameters.Value, ECCurve.NamedCurves.nistP521) ? JsonWebKeyECTypes.P521 : null; OpenIddictHelpers.IsEcCurve(parameters.Value, ECCurve.NamedCurves.nistP384) ? JsonWebKeyECTypes.P384 :
OpenIddictHelpers.IsEcCurve(parameters.Value, ECCurve.NamedCurves.nistP521) ? JsonWebKeyECTypes.P521 : null;
if (string.IsNullOrEmpty(curve)) if (string.IsNullOrEmpty(curve))
{ {
@ -1210,7 +1211,7 @@ public static partial class OpenIddictServerHandlers
key.Crv = curve; key.Crv = curve;
// Note: both X and Y must be base64url-encoded. // 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.X = Base64UrlEncoder.Encode(parameters.Value.Q.X);
key.Y = Base64UrlEncoder.Encode(parameters.Value.Q.Y); key.Y = Base64UrlEncoder.Encode(parameters.Value.Q.Y);
} }
@ -1222,16 +1223,16 @@ public static partial class OpenIddictServerHandlers
if (certificate is not null) if (certificate is not null)
{ {
// x5t must be base64url-encoded. // 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()); key.X5t = Base64UrlEncoder.Encode(certificate.GetCertHash());
// x5t#S256 must be base64url-encoded. // x5t#S256 must be base64url-encoded.
// See https://tools.ietf.org/html/rfc7517#section-4.9 // See https://tools.ietf.org/html/rfc7517#section-4.9.
key.X5tS256 = Base64UrlEncoder.Encode(GetCertificateHash(certificate, HashAlgorithmName.SHA256)); key.X5tS256 = Base64UrlEncoder.Encode(OpenIddictHelpers.ComputeSha256Hash(certificate.RawData));
// Unlike E or N, the certificates contained in x5c // Unlike E or N, the certificates contained in x5c
// must be base64-encoded and not base64url-encoded. // 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)); key.X5c.Add(Convert.ToBase64String(certificate.RawData));
} }
@ -1239,45 +1240,6 @@ public static partial class OpenIddictServerHandlers
} }
return default; 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
}
} }
} }
} }

36
src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs

@ -13,6 +13,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using OpenIddict.Extensions;
#if !SUPPORTS_TIME_CONSTANT_COMPARISONS #if !SUPPORTS_TIME_CONSTANT_COMPARISONS
using Org.BouncyCastle.Utilities; using Org.BouncyCastle.Utilities;
@ -1544,39 +1545,26 @@ public static partial class OpenIddictServerHandlers
return default; return default;
} }
// If no code challenge method was specified, default to S256. var comparand = context.Principal.GetClaim(Claims.Private.CodeChallengeMethod) switch
var method = context.Principal.GetClaim(Claims.Private.CodeChallengeMethod);
if (string.IsNullOrEmpty(method))
{ {
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. CodeChallengeMethods.Sha256 => Encoding.ASCII.GetBytes(Base64UrlEncoder.Encode(
// In this case, the raw ASCII bytes of the verifier are directly compared to the challenge. OpenIddictHelpers.ComputeSha256Hash(Encoding.ASCII.GetBytes(context.Request.CodeVerifier)))),
byte[] data;
if (string.Equals(method, CodeChallengeMethods.Plain, StringComparison.Ordinal))
{
data = Encoding.ASCII.GetBytes(context.Request.CodeVerifier);
}
else if (string.Equals(method, CodeChallengeMethods.Sha256, StringComparison.Ordinal)) null or { Length: 0 } => throw new InvalidOperationException(SR.GetResourceString(SR.ID0268)),
{
using var algorithm = SHA256.Create();
data = Encoding.ASCII.GetBytes(Base64UrlEncoder.Encode(
algorithm.ComputeHash(Encoding.ASCII.GetBytes(context.Request.CodeVerifier))));
}
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. // 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. // Note: to prevent timing attacks, a time-constant comparer is always used.
#if SUPPORTS_TIME_CONSTANT_COMPARISONS #if SUPPORTS_TIME_CONSTANT_COMPARISONS
if (!CryptographicOperations.FixedTimeEquals(data, Encoding.ASCII.GetBytes(challenge))) if (!CryptographicOperations.FixedTimeEquals(comparand, Encoding.ASCII.GetBytes(challenge)))
#else #else
if (!Arrays.ConstantTimeAreEqual(data, Encoding.ASCII.GetBytes(challenge))) if (!Arrays.ConstantTimeAreEqual(comparand, Encoding.ASCII.GetBytes(challenge)))
#endif #endif
{ {
context.Logger.LogInformation(SR.GetResourceString(SR.ID6092), Parameters.CodeVerifier); context.Logger.LogInformation(SR.GetResourceString(SR.ID6092), Parameters.CodeVerifier);

28
src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs

@ -8,11 +8,11 @@ using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Security.Claims; using System.Security.Claims;
using System.Security.Cryptography;
using System.Text; using System.Text;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using OpenIddict.Extensions;
namespace OpenIddict.Server; namespace OpenIddict.Server;
@ -1327,18 +1327,12 @@ public static partial class OpenIddictServerHandlers
{ {
do do
{ {
var data = new byte[12]; var array = OpenIddictHelpers.CreateRandomArray(size: 12);
#if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS var builder = new StringBuilder(array.Length);
RandomNumberGenerator.Fill(data);
#else for (var index = 0; index < array.Length; index += 4)
using var generator = RandomNumberGenerator.Create();
generator.GetBytes(data);
#endif
var builder = new StringBuilder(data.Length);
for (var index = 0; index < data.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(); descriptor.ReferenceId = builder.ToString();
@ -1352,15 +1346,7 @@ public static partial class OpenIddictServerHandlers
// For other tokens, generate a base64url-encoded 256-bit random identifier. // For other tokens, generate a base64url-encoded 256-bit random identifier.
else else
{ {
var data = new byte[256 / 8]; descriptor.ReferenceId = Base64UrlEncoder.Encode(OpenIddictHelpers.CreateRandomArray(size: 256));
#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);
} }
await _tokenManager.UpdateAsync(token, descriptor); await _tokenManager.UpdateAsync(token, descriptor);

93
src/OpenIddict.Server/OpenIddictServerHandlers.cs

@ -12,6 +12,7 @@ using System.Security.Cryptography;
using System.Text; using System.Text;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using OpenIddict.Extensions;
namespace OpenIddict.Server; namespace OpenIddict.Server;
@ -2707,15 +2708,9 @@ public static partial class OpenIddictServerHandlers
credentials => credentials.Key is AsymmetricSecurityKey) ?? credentials => credentials.Key is AsymmetricSecurityKey) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0266)); 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)) 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. // Note: only the left-most half of the hash is used.
// See http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken // 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)) 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. // Note: only the left-most half of the hash is used.
// See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken
@ -2733,63 +2728,31 @@ public static partial class OpenIddictServerHandlers
return default; return default;
static HashAlgorithm? GetHashAlgorithm(SigningCredentials credentials) static byte[] ComputeHash(SigningCredentials credentials, byte[] data) => credentials switch
{ {
HashAlgorithm? hash = null; { Digest: SecurityAlgorithms.Sha256 or SecurityAlgorithms.Sha256Digest } or
{ Algorithm: SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha256Signature } or
if (!string.IsNullOrEmpty(credentials.Digest)) { Algorithm: SecurityAlgorithms.HmacSha256 or SecurityAlgorithms.HmacSha256Signature } or
{ { Algorithm: SecurityAlgorithms.RsaSha256 or SecurityAlgorithms.RsaSha256Signature } or
hash = CryptoConfig.CreateFromName(credentials.Digest) as HashAlgorithm; { Algorithm: SecurityAlgorithms.RsaSsaPssSha256 or SecurityAlgorithms.RsaSsaPssSha256Signature }
} => OpenIddictHelpers.ComputeSha256Hash(data),
if (hash is null) { Digest: SecurityAlgorithms.Sha384 or SecurityAlgorithms.Sha384Digest } or
{ { Algorithm: SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.EcdsaSha384Signature } or
var algorithm = credentials.Digest switch { Algorithm: SecurityAlgorithms.HmacSha384 or SecurityAlgorithms.HmacSha384Signature } or
{ { Algorithm: SecurityAlgorithms.RsaSha384 or SecurityAlgorithms.RsaSha384Signature } or
SecurityAlgorithms.Sha256 or SecurityAlgorithms.Sha256Digest => HashAlgorithmName.SHA256, { Algorithm: SecurityAlgorithms.RsaSsaPssSha384 or SecurityAlgorithms.RsaSsaPssSha384Signature }
SecurityAlgorithms.Sha384 or SecurityAlgorithms.Sha384Digest => HashAlgorithmName.SHA384, => OpenIddictHelpers.ComputeSha384Hash(data),
SecurityAlgorithms.Sha512 or SecurityAlgorithms.Sha512Digest => HashAlgorithmName.SHA512,
{ Digest: SecurityAlgorithms.Sha512 or SecurityAlgorithms.Sha512Digest } or
_ => credentials.Algorithm switch { Algorithm: SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.EcdsaSha512Signature } or
{ { Algorithm: SecurityAlgorithms.HmacSha512 or SecurityAlgorithms.HmacSha512Signature } or
#if SUPPORTS_ECDSA { Algorithm: SecurityAlgorithms.RsaSha512 or SecurityAlgorithms.RsaSha512Signature } or
SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha256Signature { Algorithm: SecurityAlgorithms.RsaSsaPssSha512 or SecurityAlgorithms.RsaSsaPssSha512Signature }
=> HashAlgorithmName.SHA256, => OpenIddictHelpers.ComputeSha512Hash(data),
SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.EcdsaSha384Signature
=> HashAlgorithmName.SHA384, _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0267))
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;
}
} }
} }

4
src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs

@ -43,8 +43,6 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers
.Build(); .Build();
/// <inheritdoc/> /// <inheritdoc/>
[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) public ValueTask HandleAsync(TContext context)
{ {
if (context is null) if (context is null)
@ -77,8 +75,6 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers
.Build(); .Build();
/// <inheritdoc/> /// <inheritdoc/>
[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) public ValueTask HandleAsync(TContext context)
{ {
if (context is null) if (context is null)

2
src/OpenIddict.Validation/OpenIddictValidationBuilder.cs

@ -276,8 +276,6 @@ public class OpenIddictValidationBuilder
/// to store the private key of the certificate. /// to store the private key of the certificate.
/// </param> /// </param>
/// <returns>The <see cref="OpenIddictValidationBuilder"/> instance.</returns> /// <returns>The <see cref="OpenIddictValidationBuilder"/> instance.</returns>
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope",
Justification = "The X.509 certificate is attached to the server options.")]
public OpenIddictValidationBuilder AddEncryptionCertificate( public OpenIddictValidationBuilder AddEncryptionCertificate(
Stream stream, string? password, X509KeyStorageFlags flags) Stream stream, string? password, X509KeyStorageFlags flags)
{ {

2
test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTestServer.cs

@ -40,8 +40,6 @@ public class OpenIddictServerAspNetCoreIntegrationTestServer : OpenIddictServerI
/// </summary> /// </summary>
public TestServer Server { get; } 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<OpenIddictServerIntegrationTestClient> CreateClientAsync() public override ValueTask<OpenIddictServerIntegrationTestClient> CreateClientAsync()
=> new(new OpenIddictServerIntegrationTestClient(Server.CreateClient())); => new(new OpenIddictServerIntegrationTestClient(Server.CreateClient()));

2
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"]); 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 protected override
#if SUPPORTS_GENERIC_HOST #if SUPPORTS_GENERIC_HOST
async async

2
test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTestServer.cs

@ -23,8 +23,6 @@ public class OpenIddictServerOwinIntegrationTestServer : OpenIddictServerIntegra
/// </summary> /// </summary>
public TestServer Server { get; } 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<OpenIddictServerIntegrationTestClient> CreateClientAsync() public override ValueTask<OpenIddictServerIntegrationTestClient> CreateClientAsync()
=> new(new OpenIddictServerIntegrationTestClient(Server.HttpClient)); => new(new OpenIddictServerIntegrationTestClient(Server.HttpClient));

2
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"]); 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<OpenIddictServerIntegrationTestServer> CreateServerAsync(Action<OpenIddictServerBuilder>? configuration = null) protected override ValueTask<OpenIddictServerIntegrationTestServer> CreateServerAsync(Action<OpenIddictServerBuilder>? configuration = null)
{ {
var services = new ServiceCollection(); var services = new ServiceCollection();

2
test/OpenIddict.Validation.AspNetCore.IntegrationTests/OpenIddictValidationAspNetCoreIntegrationTestServer.cs

@ -40,8 +40,6 @@ public class OpenIddictValidationAspNetCoreIntegrationTestServer : OpenIddictVal
/// </summary> /// </summary>
public TestServer Server { get; } 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<OpenIddictValidationIntegrationTestClient> CreateClientAsync() public override ValueTask<OpenIddictValidationIntegrationTestClient> CreateClientAsync()
=> new(new OpenIddictValidationIntegrationTestClient(Server.CreateClient())); => new(new OpenIddictValidationIntegrationTestClient(Server.CreateClient()));

2
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); 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 protected override
#if SUPPORTS_GENERIC_HOST #if SUPPORTS_GENERIC_HOST
async async

2
test/OpenIddict.Validation.Owin.IntegrationTests/OpenIddictValidationOwinIntegrationTestServer.cs

@ -23,8 +23,6 @@ public class OpenIddictValidationOwinIntegrationTestValidation : OpenIddictValid
/// </summary> /// </summary>
public TestServer Server { get; } 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<OpenIddictValidationIntegrationTestClient> CreateClientAsync() public override ValueTask<OpenIddictValidationIntegrationTestClient> CreateClientAsync()
=> new(new OpenIddictValidationIntegrationTestClient(Server.HttpClient)); => new(new OpenIddictValidationIntegrationTestClient(Server.HttpClient));

2
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); 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<OpenIddictValidationIntegrationTestServer> CreateServerAsync(Action<OpenIddictValidationBuilder>? configuration = null) protected override ValueTask<OpenIddictValidationIntegrationTestServer> CreateServerAsync(Action<OpenIddictValidationBuilder>? configuration = null)
{ {
var services = new ServiceCollection(); var services = new ServiceCollection();

Loading…
Cancel
Save