diff --git a/Directory.Packages.props b/Directory.Packages.props index 885828b9..5144c5b1 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -41,7 +41,7 @@ - + @@ -117,7 +117,6 @@ - @@ -326,7 +325,7 @@ - + diff --git a/shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs b/shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs index a286afcc..9ef45cdc 100644 --- a/shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs +++ b/shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs @@ -1,5 +1,5 @@ -using System; -using System.Diagnostics; +using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Security.Claims; using System.Security.Cryptography; using System.Text; @@ -620,6 +620,51 @@ internal static class OpenIddictHelpers } } + /// + /// Determines the equality of two byte sequences in an amount of time + /// which depends on the length of the sequences, but not the values. + /// + /// The first buffer to compare. + /// The second buffer to compare. + /// + /// if and have the same values + /// for and the same contents, otherwise. + /// +#if !SUPPORTS_TIME_CONSTANT_COMPARISONS + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] +#endif + public static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) + { +#if SUPPORTS_TIME_CONSTANT_COMPARISONS + return CryptographicOperations.FixedTimeEquals(left, right); +#else + // Note: the logic used here is directly taken from the official implementation of + // the CryptographicOperations.FixedTimeEquals() method introduced in .NET Core 2.1. + // + // See https://github.com/dotnet/corefx/pull/27103 for more information. + + // 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 length = left.Length; + var accumulator = 0; + + for (var index = 0; index < length; index++) + { + accumulator |= left[index] - right[index]; + } + + return accumulator is 0; +#endif + } + #if SUPPORTS_KEY_DERIVATION_WITH_SPECIFIED_HASH_ALGORITHM /// /// Creates a derived key based on the specified using PBKDF2. diff --git a/src/OpenIddict.Client/OpenIddict.Client.csproj b/src/OpenIddict.Client/OpenIddict.Client.csproj index 69c1476b..7129b0da 100644 --- a/src/OpenIddict.Client/OpenIddict.Client.csproj +++ b/src/OpenIddict.Client/OpenIddict.Client.csproj @@ -23,12 +23,6 @@ To use the client feature on ASP.NET Core or OWIN/Katana, reference the OpenIddi - - - - diff --git a/src/OpenIddict.Client/OpenIddictClientHandlers.cs b/src/OpenIddict.Client/OpenIddictClientHandlers.cs index a663a72b..a1efcf52 100644 --- a/src/OpenIddict.Client/OpenIddictClientHandlers.cs +++ b/src/OpenIddict.Client/OpenIddictClientHandlers.cs @@ -9,17 +9,12 @@ using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; using System.Security.Claims; -using System.Security.Cryptography; using System.Text; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; using OpenIddict.Extensions; using static OpenIddict.Abstractions.OpenIddictExceptions; -#if !SUPPORTS_TIME_CONSTANT_COMPARISONS -using Org.BouncyCastle.Utilities; -#endif - namespace OpenIddict.Client; [EditorBrowsable(EditorBrowsableState.Never)] @@ -621,16 +616,9 @@ public static partial class OpenIddictClientHandlers // // In any case, the authentication demand MUST be rejected as it's impossible to ensure // it's not an injection or session fixation attack without the correct "rfp" value. - if (string.IsNullOrEmpty(context.RequestForgeryProtection) || -#if SUPPORTS_TIME_CONSTANT_COMPARISONS - !CryptographicOperations.FixedTimeEquals( - left: MemoryMarshal.AsBytes(comparand.AsSpan()), - right: MemoryMarshal.AsBytes(context.RequestForgeryProtection.AsSpan()))) -#else - !Arrays.ConstantTimeAreEqual( - a: MemoryMarshal.AsBytes(comparand.AsSpan()).ToArray(), - b: MemoryMarshal.AsBytes(context.RequestForgeryProtection.AsSpan()).ToArray())) -#endif + if (string.IsNullOrEmpty(context.RequestForgeryProtection) || !OpenIddictHelpers.FixedTimeEquals( + left: MemoryMarshal.AsBytes(comparand.AsSpan()), + right: MemoryMarshal.AsBytes(context.RequestForgeryProtection.AsSpan()))) { context.Logger.LogWarning(SR.GetResourceString(SR.ID6209)); @@ -1517,17 +1505,10 @@ public static partial class OpenIddictClientHandlers // If the two nonces don't match, return an error. case { FrontchannelIdentityTokenNonce: string left, StateTokenNonce: string right } when -#if SUPPORTS_TIME_CONSTANT_COMPARISONS - !CryptographicOperations.FixedTimeEquals( + !OpenIddictHelpers.FixedTimeEquals( left: MemoryMarshal.AsBytes(left.AsSpan()), // The nonce in the identity token is already hashed. right: MemoryMarshal.AsBytes(Base64UrlEncoder.Encode( OpenIddictHelpers.ComputeSha256Hash(Encoding.UTF8.GetBytes(right))).AsSpan())): -#else - !Arrays.ConstantTimeAreEqual( - a: MemoryMarshal.AsBytes(left.AsSpan()).ToArray(), // The nonce in the identity token is already hashed. - b: MemoryMarshal.AsBytes(Base64UrlEncoder.Encode( - OpenIddictHelpers.ComputeSha256Hash(Encoding.UTF8.GetBytes(right))).AsSpan()).ToArray()): -#endif context.Logger.LogWarning(SR.GetResourceString(SR.ID6210)); context.Reject( @@ -1658,15 +1639,10 @@ public static partial class OpenIddictClientHandlers } static bool ValidateTokenHash(string algorithm, string token, string hash) => -#if SUPPORTS_TIME_CONSTANT_COMPARISONS - CryptographicOperations.FixedTimeEquals( + OpenIddictHelpers.FixedTimeEquals( left: MemoryMarshal.AsBytes(hash.AsSpan()), right: MemoryMarshal.AsBytes(ComputeTokenHash(algorithm, token))); -#else - Arrays.ConstantTimeAreEqual( - a: MemoryMarshal.AsBytes(hash.AsSpan()).ToArray(), - b: MemoryMarshal.AsBytes(ComputeTokenHash(algorithm, token)).ToArray()); -#endif + return default; } } @@ -2831,17 +2807,10 @@ public static partial class OpenIddictClientHandlers // If the two nonces don't match, return an error. case { BackchannelIdentityTokenNonce: string left, StateTokenNonce: string right } when -#if SUPPORTS_TIME_CONSTANT_COMPARISONS - !CryptographicOperations.FixedTimeEquals( + !OpenIddictHelpers.FixedTimeEquals( left: MemoryMarshal.AsBytes(left.AsSpan()), // The nonce in the identity token is already hashed. right: MemoryMarshal.AsBytes(Base64UrlEncoder.Encode( OpenIddictHelpers.ComputeSha256Hash(Encoding.UTF8.GetBytes(right))).AsSpan())): -#else - !Arrays.ConstantTimeAreEqual( - a: MemoryMarshal.AsBytes(left.AsSpan()).ToArray(), // The nonce in the identity token is already hashed. - b: MemoryMarshal.AsBytes(Base64UrlEncoder.Encode( - OpenIddictHelpers.ComputeSha256Hash(Encoding.UTF8.GetBytes(right))).AsSpan()).ToArray()): -#endif context.Logger.LogWarning(SR.GetResourceString(SR.ID6211)); context.Reject( @@ -2936,15 +2905,10 @@ public static partial class OpenIddictClientHandlers } static bool ValidateTokenHash(string algorithm, string token, string hash) => -#if SUPPORTS_TIME_CONSTANT_COMPARISONS - CryptographicOperations.FixedTimeEquals( + OpenIddictHelpers.FixedTimeEquals( left: MemoryMarshal.AsBytes(hash.AsSpan()), right: MemoryMarshal.AsBytes(ComputeTokenHash(algorithm, token))); -#else - Arrays.ConstantTimeAreEqual( - a: MemoryMarshal.AsBytes(hash.AsSpan()).ToArray(), - b: MemoryMarshal.AsBytes(ComputeTokenHash(algorithm, token)).ToArray()); -#endif + return default; } } diff --git a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs index 560e16ac..b16596cd 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs @@ -25,10 +25,6 @@ using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.Parameters; #endif -#if !SUPPORTS_TIME_CONSTANT_COMPARISONS -using Org.BouncyCastle.Utilities; -#endif - namespace OpenIddict.Core; /// @@ -1511,15 +1507,9 @@ public class OpenIddictApplicationManager : IOpenIddictApplication return false; } -#if SUPPORTS_TIME_CONSTANT_COMPARISONS - return CryptographicOperations.FixedTimeEquals( - left: payload.Slice(13 + salt.Length, keyLength), + return OpenIddictHelpers.FixedTimeEquals( + left: payload.Slice(13 + salt.Length, keyLength), right: DeriveKey(secret, salt.ToArray(), algorithm, iterations, keyLength)); -#else - return Arrays.ConstantTimeAreEqual( - a: payload.Slice(13 + salt.Length, keyLength).ToArray(), - b: DeriveKey(secret, salt.ToArray(), algorithm, iterations, keyLength)); -#endif } } diff --git a/src/OpenIddict.Core/OpenIddict.Core.csproj b/src/OpenIddict.Core/OpenIddict.Core.csproj index 3aca5fe1..74c47405 100644 --- a/src/OpenIddict.Core/OpenIddict.Core.csproj +++ b/src/OpenIddict.Core/OpenIddict.Core.csproj @@ -24,9 +24,9 @@ - + Condition=" ('$(TargetFrameworkIdentifier)' == '.NETFramework' And $([MSBuild]::VersionLessThan($(TargetFrameworkVersion), '4.7.2'))) Or + ('$(TargetFrameworkIdentifier)' == '.NETStandard' And $([MSBuild]::VersionLessThan($(TargetFrameworkVersion), '2.1'))) "> + diff --git a/src/OpenIddict.Server/OpenIddict.Server.csproj b/src/OpenIddict.Server/OpenIddict.Server.csproj index 92c48f9c..7f0979ac 100644 --- a/src/OpenIddict.Server/OpenIddict.Server.csproj +++ b/src/OpenIddict.Server/OpenIddict.Server.csproj @@ -21,12 +21,6 @@ To use the server feature on ASP.NET Core or OWIN/Katana, reference the OpenIddi - - - - diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs index d06315cc..8d366a61 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs @@ -8,7 +8,6 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Runtime.InteropServices; using System.Security.Claims; -using System.Security.Cryptography; using System.Text; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -16,10 +15,6 @@ using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using OpenIddict.Extensions; -#if !SUPPORTS_TIME_CONSTANT_COMPARISONS -using Org.BouncyCastle.Utilities; -#endif - namespace OpenIddict.Server; public static partial class OpenIddictServerHandlers @@ -1562,15 +1557,9 @@ public static partial class OpenIddictServerHandlers // Compare the verifier and the code challenge: if the two don't match, return an error. // Note: to prevent timing attacks, a time-constant comparer is always used. -#if SUPPORTS_TIME_CONSTANT_COMPARISONS - if (!CryptographicOperations.FixedTimeEquals( + if (!OpenIddictHelpers.FixedTimeEquals( left: MemoryMarshal.AsBytes(comparand.AsSpan()), right: MemoryMarshal.AsBytes(challenge.AsSpan()))) -#else - if (!Arrays.ConstantTimeAreEqual( - a: MemoryMarshal.AsBytes(comparand.AsSpan()).ToArray(), - b: MemoryMarshal.AsBytes(challenge.AsSpan()).ToArray())) -#endif { context.Logger.LogInformation(SR.GetResourceString(SR.ID6092), Parameters.CodeVerifier);