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