diff --git a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs index b9df145e..e740167e 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs @@ -27,6 +27,10 @@ using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.Parameters; #endif +#if !SUPPORTS_TIME_CONSTANT_COMPARISONS +using Org.BouncyCastle.Utilities; +#endif + namespace OpenIddict.Core { /// @@ -1318,9 +1322,15 @@ namespace OpenIddict.Core return false; } - return FixedTimeEquals( +#if SUPPORTS_TIME_CONSTANT_COMPARISONS + return CryptographicOperations.FixedTimeEquals( left: payload.Slice(13 + salt.Length, keyLength), right: DeriveKey(secret, salt, algorithm, iterations, keyLength)); +#else + return Arrays.ConstantTimeAreEqual( + a: payload.Slice(13 + salt.Length, keyLength).ToArray(), + b: DeriveKey(secret, salt, algorithm, iterations, keyLength)); +#endif } static uint ReadNetworkByteOrder(ReadOnlySpan buffer, int offset) => @@ -1330,7 +1340,7 @@ namespace OpenIddict.Core ((uint) buffer[offset + 3]); } - private static ReadOnlySpan DeriveKey(string secret, ReadOnlySpan salt, + private static byte[] DeriveKey(string secret, ReadOnlySpan salt, HashAlgorithmName algorithm, int iterations, int length) { #if SUPPORTS_KEY_DERIVATION_WITH_SPECIFIED_HASH_ALGORITHM @@ -1352,33 +1362,6 @@ namespace OpenIddict.Core #endif } - [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] - private static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) - { -#if SUPPORTS_TIME_CONSTANT_COMPARISONS - return CryptographicOperations.FixedTimeEquals(left, right); -#else - // Note: these null checks can be theoretically considered as early checks - // (which would defeat the purpose of a time-constant comparison method), - // but the expected string length is the only information an attacker - // could get at this stage, which is not critical where this method is used. - - if (left.Length != right.Length) - { - return false; - } - - var result = true; - - for (var index = 0; index < left.Length; index++) - { - result &= left[index] == right[index]; - } - - return result; -#endif - } - ValueTask IOpenIddictApplicationManager.CountAsync(CancellationToken cancellationToken) => CountAsync(cancellationToken); diff --git a/src/OpenIddict.Core/OpenIddict.Core.csproj b/src/OpenIddict.Core/OpenIddict.Core.csproj index 89b00f84..275109da 100644 --- a/src/OpenIddict.Core/OpenIddict.Core.csproj +++ b/src/OpenIddict.Core/OpenIddict.Core.csproj @@ -21,7 +21,7 @@ - + diff --git a/src/OpenIddict.Server/OpenIddict.Server.csproj b/src/OpenIddict.Server/OpenIddict.Server.csproj index 2122f354..eff20688 100644 --- a/src/OpenIddict.Server/OpenIddict.Server.csproj +++ b/src/OpenIddict.Server/OpenIddict.Server.csproj @@ -19,6 +19,12 @@ + + + + $(DefineConstants);SUPPORTS_ECDSA @@ -29,7 +35,9 @@ $(DefineConstants);SUPPORTS_EPHEMERAL_KEY_SETS - + $(DefineConstants);SUPPORTS_CERTIFICATE_HASHING_WITH_SPECIFIED_ALGORITHM $(DefineConstants);SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS $(DefineConstants);SUPPORTS_TIME_CONSTANT_COMPARISONS diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs index 1a08649b..a5e344d4 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs @@ -7,8 +7,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; -using System.Runtime.CompilerServices; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; @@ -20,6 +18,10 @@ using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Server.OpenIddictServerEvents; using static OpenIddict.Server.OpenIddictServerHandlerFilters; +#if !SUPPORTS_TIME_CONSTANT_COMPARISONS +using Org.BouncyCastle.Utilities; +#endif + namespace OpenIddict.Server { public static partial class OpenIddictServerHandlers @@ -1654,7 +1656,7 @@ namespace OpenIddict.Server // 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. - ReadOnlySpan data; + byte[] data; if (string.Equals(method, CodeChallengeMethods.Plain, StringComparison.Ordinal)) { data = Encoding.ASCII.GetBytes(context.Request.CodeVerifier); @@ -1674,7 +1676,11 @@ namespace OpenIddict.Server // Compare the verifier and the code challenge: if the two don't match, return an error. // Note: to prevent timing attacks, a time-constant comparer is always used. - if (!FixedTimeEquals(data, Encoding.ASCII.GetBytes(challenge))) +#if SUPPORTS_TIME_CONSTANT_COMPARISONS + if (!CryptographicOperations.FixedTimeEquals(data, Encoding.ASCII.GetBytes(challenge))) +#else + if (!Arrays.ConstantTimeAreEqual(data, Encoding.ASCII.GetBytes(challenge))) +#endif { context.Logger.LogError("The token request was rejected because the 'code_verifier' was invalid."); @@ -1687,33 +1693,6 @@ namespace OpenIddict.Server return default; } - - [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] - private static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) - { -#if SUPPORTS_TIME_CONSTANT_COMPARISONS - return CryptographicOperations.FixedTimeEquals(left, right); -#else - // Note: these null checks can be theoretically considered as early checks - // (which would defeat the purpose of a time-constant comparison method), - // but the expected string length is the only information an attacker - // could get at this stage, which is not critical where this method is used. - - if (left.Length != right.Length) - { - return false; - } - - var result = true; - - for (var index = 0; index < left.Length; index++) - { - result &= left[index] == right[index]; - } - - return result; -#endif - } } ///