/* * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) * See https://github.com/openiddict/openiddict-core for more information concerning * the license and the contributors participating to this project. */ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; namespace OpenIddict.Extensions; /// /// Exposes common polyfills used by the OpenIddict assemblies. /// internal static class OpenIddictPolyfills { extension(CryptographicOperations) { #if !NET /// /// Determine 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. /// /// true if and have the same /// values for and the same contents, false /// otherwise. /// /// /// This method compares two buffers' contents for equality in a manner which does not /// leak timing information, making it ideal for use within cryptographic routines. /// This method will short-circuit and return false only if /// and have different lengths. /// /// Fixed-time behavior is guaranteed in all other cases, including if /// and reference the same address. /// [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] public static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) { // 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 } extension(HMACSHA256) { #if !NET /// /// Computes the HMAC of data using the SHA256 algorithm. /// /// The HMAC key. /// The data to HMAC. /// The HMAC of the data. /// /// or is . /// public static byte[] HashData(byte[] key, byte[] source) { ArgumentNullException.ThrowIfNull(key); ArgumentNullException.ThrowIfNull(source); using var algorithm = new HMACSHA256(key); return algorithm.ComputeHash(source); } #endif } extension(OperatingSystem) { #if !NET /// /// Indicates whether the current application is running on Android. /// public static bool IsAndroid() => RuntimeInformation.IsOSPlatform(OSPlatform.Create("ANDROID")); /// /// Check for the Android API level (returned by 'ro.build.version.sdk') with a >= /// version comparison. Used to guard APIs that were added in the given Android release. /// public static bool IsAndroidVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0) => IsAndroid() && IsOSVersionAtLeast(major, minor, build, revision); /// /// Indicates whether the current application is running on iOS or MacCatalyst. /// [SupportedOSPlatformGuard("maccatalyst")] public static bool IsIOS() => RuntimeInformation.IsOSPlatform(OSPlatform.Create("IOS")); /// /// Check for the iOS/MacCatalyst version (returned by 'libobjc.get_operatingSystemVersion') /// with a >= version comparison. Used to guard APIs that were added in the given iOS release. /// [SupportedOSPlatformGuard("maccatalyst")] public static bool IsIOSVersionAtLeast(int major, int minor = 0, int build = 0) => IsIOS() && IsOSVersionAtLeast(major, minor, build, 0); /// /// Indicates whether the current application is running on Linux. /// public static bool IsLinux() => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); /// /// Indicates whether the current application is running on Mac Catalyst. /// public static bool IsMacCatalyst() => RuntimeInformation.IsOSPlatform(OSPlatform.Create("MACCATALYST")); /// /// Check for the Mac Catalyst version (iOS version as presented in Apple documentation) with a >= /// version comparison. Used to guard APIs that were added in the given Mac Catalyst release. /// public static bool IsMacCatalystVersionAtLeast(int major, int minor = 0, int build = 0) => IsMacCatalyst() && IsOSVersionAtLeast(major, minor, build, 0); /// /// Indicates whether the current application is running on macOS. /// public static bool IsMacOS() => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); /// /// Check for the macOS version (returned by 'libobjc.get_operatingSystemVersion') with a >= /// version comparison. Used to guard APIs that were added in the given macOS release. /// public static bool IsMacOSVersionAtLeast(int major, int minor = 0, int build = 0) => IsMacOS() && IsOSVersionAtLeast(major, minor, build, 0); /// /// Indicates whether the current application is running on Windows. /// public static bool IsWindows() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); /// /// Check for the Windows version (returned by 'RtlGetVersion') with a >= version /// comparison. Used to guard APIs that were added in the given Windows release. /// public static bool IsWindowsVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0) { if (Environment.OSVersion.Platform is PlatformID.Win32NT && Environment.OSVersion.Version >= new Version(major, minor, build, revision)) { return true; } // Note: on older versions of .NET, Environment.OSVersion.Version is known to be affected by // the compatibility shims used by Windows 10+ when the application doesn't have a manifest // that explicitly indicates it's compatible with Windows 10 and higher. To avoid that, a // second pass using RuntimeInformation.OSDescription (that calls NtDll.RtlGetVersion() under // the hood) is made. Note: no version is returned on UWP due to the missing Win32 API. return RuntimeInformation.OSDescription.StartsWith("Microsoft Windows ", StringComparison.OrdinalIgnoreCase) && RuntimeInformation.OSDescription["Microsoft Windows ".Length..] is string value && Version.TryParse(value, out Version? version) && version >= new Version(major, minor, build, revision); } #endif } extension(Rfc2898DeriveBytes) { #if !NET /// /// Creates a PBKDF2 derived key from a password. /// /// The password used to derive the key. /// The key salt used to derive the key. /// The number of iterations for the operation. /// The hash algorithm to use to derive the key. /// The size of key to derive. /// /// is not zero or a positive value. /// -or- /// is not a positive value. /// /// /// has a /// that is empty or . /// /// /// is an unsupported hash algorithm. Supported algorithms /// are , , /// , and . /// public static byte[] Pbkdf2( ReadOnlySpan password, ReadOnlySpan salt, int iterations, HashAlgorithmName hashAlgorithm, int outputLength) { ArgumentOutOfRangeException.ThrowIfNegative(outputLength); ArgumentOutOfRangeException.ThrowIfNegativeOrZero(iterations); using var algorithm = new Rfc2898DeriveBytes(password.ToString(), salt.ToArray(), iterations, hashAlgorithm); return algorithm.GetBytes(outputLength); } #endif } extension(ValueTask) { #if !NET /// /// Gets a task that has already completed successfully. /// public static ValueTask CompletedTask => default; #endif } #if !NET static bool IsOSVersionAtLeast(int major, int minor, int build, int revision) { Version current = Environment.OSVersion.Version; if (current.Major != major) { return current.Major > major; } if (current.Minor != minor) { return current.Minor > minor; } int currentBuild = current.Build < 0 ? 0 : current.Build; build = build < 0 ? 0 : build; if (currentBuild != build) { return currentBuild > build; } int currentRevision = current.Revision < 0 ? 0 : current.Revision; revision = revision < 0 ? 0 : revision; return currentRevision >= revision; } #endif extension(X509ChainPolicy policy) { #if !NET public X509ChainPolicy Clone() { var clone = new X509ChainPolicy { RevocationMode = policy.RevocationMode, RevocationFlag = policy.RevocationFlag, UrlRetrievalTimeout = policy.UrlRetrievalTimeout, VerificationFlags = policy.VerificationFlags, VerificationTime = policy.VerificationTime, }; if (policy.ApplicationPolicy.Count is > 0) { for (var index = 0; index < policy.ApplicationPolicy.Count; index++) { clone.ApplicationPolicy.Add(policy.ApplicationPolicy[index]); } } if (policy.CertificatePolicy.Count is > 0) { for (var index = 0; index < policy.CertificatePolicy.Count; index++) { clone.CertificatePolicy.Add(policy.CertificatePolicy[index]); } } clone.ExtraStore.AddRange(policy.ExtraStore); return clone; } #endif } }