From 764f444494b97baead8733929fffd408b164e5e0 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Fri, 19 Jun 2026 17:21:33 -0400 Subject: [PATCH 1/6] Add X25519 to Microsoft.Bcl.Cryptography Share the X25519DiffieHellman implementation and tests through Common so Microsoft.Bcl.Cryptography can build the API for downlevel targets while forwarding to System.Security.Cryptography on .NET 11 and later. Wire the Microsoft.Bcl.Cryptography project and tests for X25519, add the required downlevel shims/resources, and deduplicate CNG key duplication helpers used by the CNG-backed cryptography types. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../NCrypt/Interop.NCryptDeriveKeyMaterial.cs | 15 +- .../Windows/NCrypt/Interop.Properties.cs | 4 - .../Security/Cryptography/CngHelpers.cs | 14 ++ .../Security/Cryptography/CompositeMLDsa.cs | 4 +- .../Security/Cryptography/KeyFormatHelper.cs | 7 + .../src/System/Security/Cryptography/MLDsa.cs | 1 - .../Security/Cryptography/MLDsaCng.Windows.cs | 10 -- .../src/System/Security/Cryptography/MLKem.cs | 6 - .../Security/Cryptography/MLKemCng.Windows.cs | 10 -- .../System/Security/Cryptography/SlhDsa.cs | 1 - .../Cryptography/X25519DiffieHellman.cs | 22 ++- .../X25519DiffieHellmanCng.Windows.cs | 51 +++++-- .../Cryptography/X25519DiffieHellmanCng.cs | 0 ...iffieHellmanImplementation.NotSupported.cs | 2 +- ...5519DiffieHellmanImplementation.Windows.cs | 8 +- .../X25519DiffieHellmanBaseTests.cs | 0 .../X25519DiffieHellmanCngTests.Windows.cs} | 4 + .../X25519DiffieHellmanContractTests.cs | 0 .../X25519DiffieHellmanImplementationTests.cs | 0 .../X25519DiffieHellmanKeyTests.cs | 0 .../X25519DiffieHellmanNotSupportedTests.cs | 0 .../X25519DiffieHellmanTestData.cs | 0 .../Cryptography}/X25519DiffieHellmanTests.cs | 0 .../Microsoft.Bcl.Cryptography.Forwards.cs | 4 + .../src/Microsoft.Bcl.Cryptography.csproj | 144 ++++++++++++------ .../src/Resources/Strings.resx | 6 + .../Security/Cryptography/CngExtensions.cs | 10 -- .../Security/Cryptography/NetStandardShims.cs | 70 ++++++++- .../Microsoft.Bcl.Cryptography.Tests.csproj | 43 ++++++ .../src/System.Security.Cryptography.csproj | 15 +- .../System.Security.Cryptography.Tests.csproj | 24 ++- 31 files changed, 347 insertions(+), 128 deletions(-) rename src/libraries/{System.Security.Cryptography => Common}/src/System/Security/Cryptography/X25519DiffieHellman.cs (99%) rename src/libraries/{System.Security.Cryptography => Common}/src/System/Security/Cryptography/X25519DiffieHellmanCng.Windows.cs (86%) rename src/libraries/{System.Security.Cryptography => Common}/src/System/Security/Cryptography/X25519DiffieHellmanCng.cs (100%) rename src/libraries/{System.Security.Cryptography => Common}/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.NotSupported.cs (96%) rename src/libraries/{System.Security.Cryptography => Common}/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Windows.cs (97%) rename src/libraries/{System.Security.Cryptography/tests => Common/tests/System/Security/Cryptography}/X25519DiffieHellmanBaseTests.cs (100%) rename src/libraries/{System.Security.Cryptography/tests/X25519DiffieHellmanCngTests.cs => Common/tests/System/Security/Cryptography/X25519DiffieHellmanCngTests.Windows.cs} (97%) rename src/libraries/{System.Security.Cryptography/tests => Common/tests/System/Security/Cryptography}/X25519DiffieHellmanContractTests.cs (100%) rename src/libraries/{System.Security.Cryptography/tests => Common/tests/System/Security/Cryptography}/X25519DiffieHellmanImplementationTests.cs (100%) rename src/libraries/{System.Security.Cryptography/tests => Common/tests/System/Security/Cryptography}/X25519DiffieHellmanKeyTests.cs (100%) rename src/libraries/{System.Security.Cryptography/tests => Common/tests/System/Security/Cryptography}/X25519DiffieHellmanNotSupportedTests.cs (100%) rename src/libraries/{System.Security.Cryptography/tests => Common/tests/System/Security/Cryptography}/X25519DiffieHellmanTestData.cs (100%) rename src/libraries/{System.Security.Cryptography/tests => Common/tests/System/Security/Cryptography}/X25519DiffieHellmanTests.cs (100%) diff --git a/src/libraries/Common/src/Interop/Windows/NCrypt/Interop.NCryptDeriveKeyMaterial.cs b/src/libraries/Common/src/Interop/Windows/NCrypt/Interop.NCryptDeriveKeyMaterial.cs index e0c1ada8a4c9f7..942ab50783b231 100644 --- a/src/libraries/Common/src/Interop/Windows/NCrypt/Interop.NCryptDeriveKeyMaterial.cs +++ b/src/libraries/Common/src/Interop/Windows/NCrypt/Interop.NCryptDeriveKeyMaterial.cs @@ -252,7 +252,7 @@ internal static byte[] DeriveKeyMaterialTruncate( SafeNCryptSecretHandle secretAgreement, SecretAgreementFlags flags) { - if (!OperatingSystem.IsWindowsVersionAtLeast(10)) + if (!IsWindows10OrGreater()) { throw new PlatformNotSupportedException(); } @@ -268,6 +268,19 @@ internal static byte[] DeriveKeyMaterialTruncate( return result; } + private static bool IsWindows10OrGreater() + { +#if NET + return OperatingSystem.IsWindowsVersionAtLeast(10); +#elif NETSTANDARD + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Environment.OSVersion.Version.Major >= 10; +#elif NETFRAMEWORK + return Environment.OSVersion.Version.Major >= 10; +#else +#error Unhandled platform target +#endif + } + internal static bool TryDeriveKeyMaterialTruncate( SafeNCryptSecretHandle secretAgreement, SecretAgreementFlags flags, diff --git a/src/libraries/Common/src/Interop/Windows/NCrypt/Interop.Properties.cs b/src/libraries/Common/src/Interop/Windows/NCrypt/Interop.Properties.cs index 377924f386948f..22bb2a145a914d 100644 --- a/src/libraries/Common/src/Interop/Windows/NCrypt/Interop.Properties.cs +++ b/src/libraries/Common/src/Interop/Windows/NCrypt/Interop.Properties.cs @@ -5,7 +5,6 @@ using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security.Cryptography; -using Internal.Cryptography; using Microsoft.Win32.SafeHandles; internal static partial class Interop @@ -29,7 +28,6 @@ internal static unsafe partial ErrorCode NCryptSetProperty( int cbInput, CngPropertyOptions dwFlags); - [SupportedOSPlatform("windows")] internal static unsafe ErrorCode NCryptGetByteProperty(SafeNCryptHandle hObject, string pszProperty, ref byte result, CngPropertyOptions options = CngPropertyOptions.None) { fixed (byte* pResult = &result) @@ -55,8 +53,6 @@ internal static unsafe ErrorCode NCryptGetIntProperty(SafeNCryptHandle hObject, { fixed (int* pResult = &result) { - Debug.Assert(Helpers.IsOSPlatformWindows); - ErrorCode errorCode = Interop.NCrypt.NCryptGetProperty( hObject, pszProperty, diff --git a/src/libraries/Common/src/System/Security/Cryptography/CngHelpers.cs b/src/libraries/Common/src/System/Security/Cryptography/CngHelpers.cs index 1c781f653ce425..aa89d567e0852f 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/CngHelpers.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/CngHelpers.cs @@ -343,5 +343,19 @@ internal static CngKey Duplicate(this SafeNCryptKeyHandle keyHandle, bool isEphe { return CngKey.Open(keyHandle, isEphemeral ? CngKeyHandleOpenOptions.EphemeralKey : CngKeyHandleOpenOptions.None); } + + internal static CngKey Duplicate(this CngKey key) + { +#if SYSTEM_SECURITY_CRYPTOGRAPHY + return Duplicate(key.HandleNoDuplicate, key.IsEphemeral); +#else +#pragma warning disable CA1416 // only supported on: 'windows' + using (SafeNCryptKeyHandle handle = key.Handle) + { + return Duplicate(handle, key.IsEphemeral); + } +#pragma warning restore CA1416 // only supported on: 'windows' +#endif + } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsa.cs b/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsa.cs index 8145cdcdcd6e4a..1c90be70113613 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsa.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsa.cs @@ -1877,9 +1877,7 @@ private AsnWriter WriteSubjectPublicKeyToAsnWriter() return writer; } - private delegate TResult ProcessExportedContent(ReadOnlySpan exportedContent); - - private TResult ExportPkcs8PrivateKeyCallback(ProcessExportedContent func) + private TResult ExportPkcs8PrivateKeyCallback(ExportPkcs8PrivateKeyFunc func) { int size = Algorithm.MaxPrivateKeySizeInBytes; byte[] buffer = CryptoPool.Rent(size); diff --git a/src/libraries/Common/src/System/Security/Cryptography/KeyFormatHelper.cs b/src/libraries/Common/src/System/Security/Cryptography/KeyFormatHelper.cs index 31f1a3ffbbc205..7104308a4f1492 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/KeyFormatHelper.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/KeyFormatHelper.cs @@ -9,6 +9,13 @@ namespace System.Security.Cryptography { + internal delegate TResult ExportPkcs8PrivateKeyFunc(ReadOnlySpan pkcs8); + + internal delegate AsnWriter WriteEncryptedPkcs8Func( + ReadOnlySpan password, + AsnWriter writer, + PbeParameters pbeParameters); + internal static partial class KeyFormatHelper { internal delegate void KeyReader(ReadOnlySpan key, in ValueAlgorithmIdentifierAsn algId, out TRet ret); diff --git a/src/libraries/Common/src/System/Security/Cryptography/MLDsa.cs b/src/libraries/Common/src/System/Security/Cryptography/MLDsa.cs index ccf270bf419833..b51046d7dd279b 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/MLDsa.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/MLDsa.cs @@ -2335,6 +2335,5 @@ internal static void ThrowIfNotSupported() return hashAlgorithmIdentifier; } - private delegate TResult ExportPkcs8PrivateKeyFunc(ReadOnlySpan pkcs8); } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/MLDsaCng.Windows.cs b/src/libraries/Common/src/System/Security/Cryptography/MLDsaCng.Windows.cs index a2ec3aaf4dab37..1addab31a360be 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/MLDsaCng.Windows.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/MLDsaCng.Windows.cs @@ -49,11 +49,7 @@ private static partial MLDsaAlgorithm AlgorithmFromHandle(CngKey key, out CngKey MLDsaAlgorithm algorithm = AlgorithmFromHandleImpl(key); -#if SYSTEM_SECURITY_CRYPTOGRAPHY - duplicateKey = CngHelpers.Duplicate(key.HandleNoDuplicate, key.IsEphemeral); -#else duplicateKey = key.Duplicate(); -#endif return algorithm; } @@ -98,13 +94,7 @@ public partial CngKey GetKey() { ThrowIfDisposed(); -#if SYSTEM_SECURITY_CRYPTOGRAPHY - return CngHelpers.Duplicate(_key.HandleNoDuplicate, _key.IsEphemeral); -#else -#pragma warning disable CA1416 // only supported on: 'windows' return _key.Duplicate(); -#pragma warning restore CA1416 // only supported on: 'windows' -#endif } internal CngKey KeyNoDuplicate => _key; diff --git a/src/libraries/Common/src/System/Security/Cryptography/MLKem.cs b/src/libraries/Common/src/System/Security/Cryptography/MLKem.cs index 7a8c377cfb77cc..a83ea5ddd71576 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/MLKem.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/MLKem.cs @@ -1838,11 +1838,5 @@ private protected static void ThrowIfNoDecapsulationKey(bool hasDecapsulationKey } } - private delegate TResult ExportPkcs8PrivateKeyFunc(ReadOnlySpan pkcs8); - - private delegate AsnWriter WriteEncryptedPkcs8Func( - ReadOnlySpan password, - AsnWriter writer, - PbeParameters pbeParameters); } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/MLKemCng.Windows.cs b/src/libraries/Common/src/System/Security/Cryptography/MLKemCng.Windows.cs index df238d5c28ab17..64a84cf0ee5bd4 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/MLKemCng.Windows.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/MLKemCng.Windows.cs @@ -41,11 +41,7 @@ private static partial MLKemAlgorithm AlgorithmFromHandle(CngKey key, out CngKey MLKemAlgorithm algorithm = AlgorithmFromHandleImpl(key); -#if SYSTEM_SECURITY_CRYPTOGRAPHY - duplicateKey = CngHelpers.Duplicate(key.HandleNoDuplicate, key.IsEphemeral); -#else duplicateKey = key.Duplicate(); -#endif return algorithm; } @@ -90,13 +86,7 @@ public partial CngKey GetKey() { ThrowIfDisposed(); -#if SYSTEM_SECURITY_CRYPTOGRAPHY - return CngHelpers.Duplicate(_key.HandleNoDuplicate, _key.IsEphemeral); -#else -#pragma warning disable CA1416 // only supported on: 'windows' return _key.Duplicate(); -#pragma warning restore CA1416 // only supported on: 'windows' -#endif } /// diff --git a/src/libraries/Common/src/System/Security/Cryptography/SlhDsa.cs b/src/libraries/Common/src/System/Security/Cryptography/SlhDsa.cs index c22659afdb3cc3..bcc2ee4d13a0a9 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/SlhDsa.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/SlhDsa.cs @@ -2003,6 +2003,5 @@ private static void ThrowIfNotSupported() } } - private delegate TResult ExportPkcs8PrivateKeyFunc(ReadOnlySpan pkcs8); } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellman.cs b/src/libraries/Common/src/System/Security/Cryptography/X25519DiffieHellman.cs similarity index 99% rename from src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellman.cs rename to src/libraries/Common/src/System/Security/Cryptography/X25519DiffieHellman.cs index 32fa23d7d46df9..390fb964a1b832 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellman.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/X25519DiffieHellman.cs @@ -20,7 +20,7 @@ namespace System.Security.Cryptography /// cryptographic libraries. /// /// - public abstract class X25519DiffieHellman : IDisposable + public abstract partial class X25519DiffieHellman : IDisposable { private protected static readonly string[] s_knownOids = [Oids.X25519]; @@ -1433,9 +1433,11 @@ protected virtual void Dispose(bool disposing) private protected static bool TryWriteSubjectPublicKeyInfo( Span destination, TState state, - Action> writer, + WriteKeyFunc writer, out int bytesWritten) +#if NET where TState : allows ref struct +#endif { // Pre-encoded SubjectPublicKeyInfo for X25519 (RFC 8410): ReadOnlySpan spkiPreamble = @@ -1468,7 +1470,7 @@ private bool TryExportSubjectPublicKeyInfoCore(Span destination, out int b out bytesWritten); } - private TResult ExportPkcs8PrivateKeyCallback(Func, TResult> func) + private TResult ExportPkcs8PrivateKeyCallback(ExportPkcs8PrivateKeyFunc func) { // A PKCS#8 X25519 PrivateKeyInfo has an ASN.1 overhead of 16 bytes, assuming no attributes. // Make it an even 32 and that should give a good starting point for a buffer size. @@ -1507,9 +1509,11 @@ private protected bool TryExportPkcs8PrivateKeyImpl(Span destination, out private protected static bool TryWritePkcs8PrivateKey( Span destination, TState state, - Action> writer, + WriteKeyFunc writer, out int bytesWritten) +#if NET where TState : allows ref struct +#endif { // Pre-encoded PKCS#8 PrivateKeyInfo for X25519 (RFC 8410): ReadOnlySpan pkcs8Preamble = @@ -1548,7 +1552,7 @@ private protected static bool TryWritePkcs8PrivateKey( private AsnWriter ExportEncryptedPkcs8PrivateKeyCore( ReadOnlySpan password, PbeParameters pbeParameters, - Func, AsnWriter, PbeParameters, AsnWriter> encryptor) + WriteEncryptedPkcs8Func encryptor) { // A PKCS#8 X25519 PrivateKeyInfo has an ASN.1 overhead of 16 bytes, assuming no attributes. // Make it an even 32 and that should give a good starting point for a buffer size. @@ -1618,5 +1622,13 @@ private protected static void ThrowIfNotSupported() nameof(X25519DiffieHellman))); } } + + private protected delegate void WriteKeyFunc(TState state, Span destination) +#if NET + where TState : allows ref struct; +#else + ; +#endif + } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanCng.Windows.cs b/src/libraries/Common/src/System/Security/Cryptography/X25519DiffieHellmanCng.Windows.cs similarity index 86% rename from src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanCng.Windows.cs rename to src/libraries/Common/src/System/Security/Cryptography/X25519DiffieHellmanCng.Windows.cs index 398c6a5e92c3e9..4bdeafdbfdac0c 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanCng.Windows.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/X25519DiffieHellmanCng.Windows.cs @@ -4,8 +4,8 @@ using System.Diagnostics; using System.Formats.Asn1; using System.Security.Cryptography.Asn1; -using Microsoft.Win32.SafeHandles; using Internal.Cryptography; +using Microsoft.Win32.SafeHandles; using ErrorCode = Interop.NCrypt.ErrorCode; @@ -18,23 +18,26 @@ public sealed partial class X25519DiffieHellmanCng : X25519DiffieHellman public partial X25519DiffieHellmanCng(CngKey key) { - Debug.Assert(Helpers.IsOSPlatformWindows); + if (!Helpers.IsOSPlatformWindows) + { + throw new PlatformNotSupportedException(); + } + ArgumentNullException.ThrowIfNull(key); ThrowIfNotSupported(); - if (key.AlgorithmGroup != CngAlgorithmGroup.ECDiffieHellman || - key.GetCurveName(out _) != X25519WindowsHelpers.BCRYPT_ECC_CURVE_25519) + if (key.AlgorithmGroup != CngAlgorithmGroup.ECDiffieHellman || !IsX25519Key(key)) { throw new ArgumentException(SR.Cryptography_ArgX25519RequiresX25519Key, nameof(key)); } - _key = CngHelpers.Duplicate(key.HandleNoDuplicate, key.IsEphemeral); + _key = key.Duplicate(); } public partial CngKey GetKey() { ThrowIfDisposed(); - return CngHelpers.Duplicate(_key.HandleNoDuplicate, _key.IsEphemeral); + return _key.Duplicate(); } protected override unsafe partial void DeriveRawSecretAgreementCore(X25519DiffieHellman otherParty, Span destination) @@ -87,9 +90,16 @@ private void DeriveRawSecretAgreementWithPublicKey(ReadOnlySpan otherParty flags); using (keyHandle) +#if SYSTEM_SECURITY_CRYPTOGRAPHY using (SafeNCryptSecretHandle secretAgreement = Interop.NCrypt.DeriveSecretAgreement( _key.HandleNoDuplicate, keyHandle)) +#else + using (SafeNCryptKeyHandle currentKeyHandle = _key.Handle) + using (SafeNCryptSecretHandle secretAgreement = Interop.NCrypt.DeriveSecretAgreement( + currentKeyHandle, + keyHandle)) +#endif { bool success = Interop.NCrypt.TryDeriveKeyMaterialTruncate( secretAgreement, @@ -163,8 +173,14 @@ private static void ExportKeyFromBlob(CngKey key, bool privateKey, Span de CngKeyBlobFormat.EccPrivateBlob.Format : CngKeyBlobFormat.EccPublicBlob.Format; +#if SYSTEM_SECURITY_CRYPTOGRAPHY + SafeNCryptKeyHandle keyHandle = key.HandleNoDuplicate; +#else + using SafeNCryptKeyHandle keyHandle = key.Handle; +#endif + ErrorCode errorCode = Interop.NCrypt.NCryptExportKey( - key.HandleNoDuplicate, + keyHandle, IntPtr.Zero, format, IntPtr.Zero, @@ -181,7 +197,7 @@ private static void ExportKeyFromBlob(CngKey key, bool privateKey, Span de using (CryptoPoolLease lease = CryptoPoolLease.Rent(numBytesNeeded, skipClear: !privateKey)) { errorCode = Interop.NCrypt.NCryptExportKey( - key.HandleNoDuplicate, + keyHandle, IntPtr.Zero, format, IntPtr.Zero, @@ -199,19 +215,30 @@ private static void ExportKeyFromBlob(CngKey key, bool privateKey, Span de } } + private static bool IsX25519Key(CngKey key) + { +#if SYSTEM_SECURITY_CRYPTOGRAPHY + return key.GetCurveName(out _) == X25519WindowsHelpers.BCRYPT_ECC_CURVE_25519; +#else + return key.GetPropertyAsString(KeyPropertyName.ECCCurveName) == X25519WindowsHelpers.BCRYPT_ECC_CURVE_25519; +#endif + } + private void ExportPrivateKeyFromEncryptedPkcs8(Span destination) { const string TemporaryExportPassword = "DotnetExportPhrase"; byte[] exported = _key.ExportPkcs8KeyBlob(TemporaryExportPassword, 1); + byte[] privateKey = new byte[PrivateKeySizeInBytes]; using (PinAndClear.Track(exported)) + using (PinAndClear.Track(privateKey)) { KeyFormatHelper.ReadEncryptedPkcs8( s_eccKeyOid, exported, TemporaryExportPassword, - destination, - static (ReadOnlySpan key, Span destination, in ValueAlgorithmIdentifierAsn algId, out object? ret) => + privateKey, + static (ReadOnlySpan key, byte[] privateKey, in ValueAlgorithmIdentifierAsn algId, out object? ret) => { if (algId.Algorithm != Oids.EcPublicKey) { @@ -228,11 +255,13 @@ private void ExportPrivateKeyFromEncryptedPkcs8(Span destination) throw new CryptographicException(SR.Cryptography_NotValidPrivateKey); } - ecKey.PrivateKey.CopyTo(destination); + ecKey.PrivateKey.CopyTo(privateKey); ret = (object?)null; }, out _, out _); + + privateKey.CopyTo(destination); } } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanCng.cs b/src/libraries/Common/src/System/Security/Cryptography/X25519DiffieHellmanCng.cs similarity index 100% rename from src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanCng.cs rename to src/libraries/Common/src/System/Security/Cryptography/X25519DiffieHellmanCng.cs diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.NotSupported.cs b/src/libraries/Common/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.NotSupported.cs similarity index 96% rename from src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.NotSupported.cs rename to src/libraries/Common/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.NotSupported.cs index 067db596082deb..67c2751e654952 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.NotSupported.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.NotSupported.cs @@ -5,7 +5,7 @@ namespace System.Security.Cryptography { - internal sealed class X25519DiffieHellmanImplementation : X25519DiffieHellman + internal sealed partial class X25519DiffieHellmanImplementation : X25519DiffieHellman { internal static new bool IsSupported => false; diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Windows.cs b/src/libraries/Common/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Windows.cs similarity index 97% rename from src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Windows.cs rename to src/libraries/Common/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Windows.cs index 4dcb4357b4b6f1..13fdbe1abe8edd 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Windows.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Windows.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using Internal.Cryptography; using Internal.NativeCrypto; using Microsoft.Win32.SafeHandles; @@ -11,7 +12,7 @@ namespace System.Security.Cryptography { - internal sealed class X25519DiffieHellmanImplementation : X25519DiffieHellman + internal sealed partial class X25519DiffieHellmanImplementation : X25519DiffieHellman { private static readonly SafeBCryptAlgorithmHandle? s_algHandle = OpenAlgorithmHandle(); @@ -234,6 +235,11 @@ private static SafeBCryptKeyHandle ImportKey(bool privateKey, ReadOnlySpan private static SafeBCryptAlgorithmHandle? OpenAlgorithmHandle() { + if (!Helpers.IsOSPlatformWindows) + { + return null; + } + NTSTATUS status = Interop.BCrypt.BCryptOpenAlgorithmProvider( out SafeBCryptAlgorithmHandle hAlgorithm, BCryptNative.AlgorithmName.ECDH, diff --git a/src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanBaseTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanBaseTests.cs similarity index 100% rename from src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanBaseTests.cs rename to src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanBaseTests.cs diff --git a/src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanCngTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanCngTests.Windows.cs similarity index 97% rename from src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanCngTests.cs rename to src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanCngTests.Windows.cs index b50adbbffac1b0..a897373077c7ae 100644 --- a/src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanCngTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanCngTests.Windows.cs @@ -9,12 +9,14 @@ namespace System.Security.Cryptography.Tests { [ConditionalClass(typeof(X25519DiffieHellman), nameof(X25519DiffieHellman.IsSupported))] + [PlatformSpecific(TestPlatforms.Windows)] public sealed class X25519DiffieHellmanExportableCngTests : X25519DiffieHellmanCngTests { protected override CngExportPolicies ExportPolicy => CngExportPolicies.AllowExport; } [ConditionalClass(typeof(X25519DiffieHellman), nameof(X25519DiffieHellman.IsSupported))] + [PlatformSpecific(TestPlatforms.Windows)] public sealed class X25519DiffieHellmanPlaintextExportableCngTests : X25519DiffieHellmanCngTests { protected override CngExportPolicies ExportPolicy => @@ -23,6 +25,7 @@ public sealed class X25519DiffieHellmanPlaintextExportableCngTests : X25519Diffi } [ConditionalClass(typeof(X25519DiffieHellman), nameof(X25519DiffieHellman.IsSupported))] + [PlatformSpecific(TestPlatforms.Windows)] public static class X25519DiffieHellmanCngContractTests { [Fact] @@ -75,6 +78,7 @@ public static void GetKey_Disposed() } [ConditionalClass(typeof(X25519DiffieHellman), nameof(X25519DiffieHellman.IsSupported))] + [PlatformSpecific(TestPlatforms.Windows)] public static class X25519DiffieHellmanCngNonExportableTests { [Fact] diff --git a/src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanContractTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanContractTests.cs similarity index 100% rename from src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanContractTests.cs rename to src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanContractTests.cs diff --git a/src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanImplementationTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanImplementationTests.cs similarity index 100% rename from src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanImplementationTests.cs rename to src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanImplementationTests.cs diff --git a/src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanKeyTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanKeyTests.cs similarity index 100% rename from src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanKeyTests.cs rename to src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanKeyTests.cs diff --git a/src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanNotSupportedTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanNotSupportedTests.cs similarity index 100% rename from src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanNotSupportedTests.cs rename to src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanNotSupportedTests.cs diff --git a/src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanTestData.cs b/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanTestData.cs similarity index 100% rename from src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanTestData.cs rename to src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanTestData.cs diff --git a/src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanTests.cs similarity index 100% rename from src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanTests.cs rename to src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanTests.cs diff --git a/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.Forwards.cs b/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.Forwards.cs index 85c7ddfdc2e14e..395d2aa62dc5b2 100644 --- a/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.Forwards.cs +++ b/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.Forwards.cs @@ -20,6 +20,10 @@ [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Security.Cryptography.SlhDsa))] [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Security.Cryptography.SlhDsaAlgorithm))] [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Security.Cryptography.SlhDsaCng))] +#if NET11_0_OR_GREATER +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Security.Cryptography.X25519DiffieHellman))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Security.Cryptography.X25519DiffieHellmanCng))] +#endif #endif #if NET || NETSTANDARD2_1_OR_GREATER [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Security.Cryptography.PbeEncryptionAlgorithm))] diff --git a/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.csproj b/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.csproj index 6670adf751a160..997ff87a57e39b 100644 --- a/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.csproj +++ b/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.csproj @@ -17,11 +17,13 @@ true true - true + + true + true - - + + - + - + + + + @@ -90,7 +95,7 @@ Link="Common\System\Security\Cryptography\PbeParameters.cs" /> - + Common\System\Security\Cryptography\Asn1\AlgorithmIdentifierAsn.xml @@ -348,7 +353,7 @@ - + @@ -373,11 +378,56 @@ Link="Common\System\Security\Cryptography\ECCng.ImportExport.NamedCurve.cs" /> - + + + + + + + + + + + + + + + + + + + + + + + + + + + System\Security\Cryptography\Asn1\MLKemPrivateKeyAsn.xml @@ -393,28 +443,10 @@ System\Security\Cryptography\Asn1\MLKemPrivateKeyBothAsn.xml.cs System\Security\Cryptography\Asn1\MLKemPrivateKeyBothAsn.xml - - - - - - - - - - - + - + + + + + + + + + + + + + + + + + @@ -488,20 +544,12 @@ Link="Common\Microsoft\Win32\SafeHandles\SafeCryptMsgHandle.cs" /> - - - - - - - - - - + + + + + diff --git a/src/libraries/Microsoft.Bcl.Cryptography/src/Resources/Strings.resx b/src/libraries/Microsoft.Bcl.Cryptography/src/Resources/Strings.resx index 065d4467e23e9e..e8dba523496f6e 100644 --- a/src/libraries/Microsoft.Bcl.Cryptography/src/Resources/Strings.resx +++ b/src/libraries/Microsoft.Bcl.Cryptography/src/Resources/Strings.resx @@ -147,6 +147,9 @@ Keys used with the MLKemCng algorithm must have an algorithm group of MLKem. + + Keys used with the X25519DiffieHellmanCng algorithm must have an algorithm group of ECDiffieHellman using curve25519. + The computed authentication tag did not match the input authentication tag. @@ -156,6 +159,9 @@ Composite signature generation failed due to an error in one or both of the components. + + Object contains only the public half of a key pair. A private key must also be provided. + The specified curve '{0}' or its parameters are not valid for this platform. diff --git a/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/CngExtensions.cs b/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/CngExtensions.cs index 8a2f2c1ded858b..5b42c1a3b3eeed 100644 --- a/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/CngExtensions.cs +++ b/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/CngExtensions.cs @@ -2,22 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using System.Runtime.Versioning; using Microsoft.Win32.SafeHandles; namespace System.Security.Cryptography { internal static class CngKeyExtensions { - [SupportedOSPlatform("windows")] - internal static CngKey Duplicate(this CngKey key) - { - using (SafeNCryptKeyHandle handle = key.Handle) - { - return CngHelpers.Duplicate(handle, key.IsEphemeral); - } - } - internal static void SetExportPolicy(this CngKey key, CngExportPolicies exportPolicy) { using (SafeNCryptKeyHandle keyHandle = key.Handle) diff --git a/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/NetStandardShims.cs b/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/NetStandardShims.cs index 344ff4878ca9eb..4f8c96ceacae60 100644 --- a/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/NetStandardShims.cs +++ b/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/NetStandardShims.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; @@ -8,6 +9,25 @@ namespace System.Security.Cryptography { internal static class NetStandardShims { +#if !NET + extension(ReadOnlySpan source) where T : IEquatable? + { + internal int IndexOfAnyExcept(T value) + { + for (int i = 0; i < source.Length; i++) + { + if (!EqualityComparer.Default.Equals(source[i], value)) + { + return i; + } + } + + return -1; + } + } + +#endif + internal static void ReadExactly(this Stream stream, Span buffer) => ReadAtLeast(stream, buffer, buffer.Length, throwOnEndOfStream: true); @@ -86,7 +106,34 @@ internal static bool TryGetHashAndReset( } } -#if !NETSTANDARD2_1_OR_GREATER +#if !NET11_0_OR_GREATER && (NET || NETSTANDARD) + internal static class CryptographicOperationsExtensions + { + extension(CryptographicOperations) + { + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + internal static bool FixedTimeEquals(ReadOnlySpan left, byte right) + { + // NoOptimization because we want this method to be exactly as non-short-circuiting + // as written. + // + // NoInlining because the NoOptimization would get lost if the method got inlined. + + int length = left.Length; + int accum = 0; + + for (int i = 0; i < length; i++) + { + accum |= left[i] - right; + } + + return accum == 0; + } + } + } +#endif + +#if NETFRAMEWORK || NETSTANDARD2_0 internal static class CryptographicOperations { [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] @@ -118,6 +165,27 @@ internal static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan return accum == 0; } + +#if NETFRAMEWORK + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + internal static bool FixedTimeEquals(ReadOnlySpan left, byte right) + { + // NoOptimization because we want this method to be exactly as non-short-circuiting + // as written. + // + // NoInlining because the NoOptimization would get lost if the method got inlined. + + int length = left.Length; + int accum = 0; + + for (int i = 0; i < length; i++) + { + accum |= left[i] - right; + } + + return accum == 0; + } +#endif } #endif } diff --git a/src/libraries/Microsoft.Bcl.Cryptography/tests/Microsoft.Bcl.Cryptography.Tests.csproj b/src/libraries/Microsoft.Bcl.Cryptography/tests/Microsoft.Bcl.Cryptography.Tests.csproj index 616f3521063d97..021673e2d2a7be 100644 --- a/src/libraries/Microsoft.Bcl.Cryptography/tests/Microsoft.Bcl.Cryptography.Tests.csproj +++ b/src/libraries/Microsoft.Bcl.Cryptography/tests/Microsoft.Bcl.Cryptography.Tests.csproj @@ -147,12 +147,55 @@ Link="CommonTest\System\Security\Cryptography\MLKemCngTests.Windows.cs" /> + + + + + + + + + + + + + + + + + + + + + + - - + + @@ -887,7 +889,8 @@ - + @@ -2040,8 +2043,10 @@ - - + + diff --git a/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj b/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj index 56ed338c3f9b1a..a0cb9b7393e670 100644 --- a/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj +++ b/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj @@ -619,13 +619,20 @@ - - - - - - - + + + + + + + @@ -726,7 +733,8 @@ Link="Common\System\Security\Cryptography\CryptoThrowHelper.Windows.cs" /> - + From 1f26292b95872ff52138d6a36fcbb03477425cb1 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sat, 20 Jun 2026 00:16:43 -0400 Subject: [PATCH 2/6] Fix .NET Framework build --- .../Security/Cryptography/CngHelpers.cs | 26 +- .../X25519DiffieHellmanBaseTests.cs | 260 +++++++++--------- .../X25519DiffieHellmanCngTests.Windows.cs | 6 +- .../X25519DiffieHellmanContractTests.cs | 5 +- .../X25519DiffieHellmanImplementationTests.cs | 2 + .../X25519DiffieHellmanNotSupportedTests.cs | 13 +- 6 files changed, 169 insertions(+), 143 deletions(-) diff --git a/src/libraries/Common/src/System/Security/Cryptography/CngHelpers.cs b/src/libraries/Common/src/System/Security/Cryptography/CngHelpers.cs index aa89d567e0852f..43ff0f39eada36 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/CngHelpers.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/CngHelpers.cs @@ -242,7 +242,7 @@ internal static unsafe bool ExportPkcs8KeyBlob( Interop.NCrypt.PBE_PARAMS pbeParams = default; Span salt = new Span(pbeParams.rgbSalt, Interop.NCrypt.PBE_PARAMS.RgbSaltSize); - RandomNumberGenerator.Fill(salt); + RngFill(salt); pbeParams.Params.cbSalt = salt.Length; pbeParams.Params.iIterations = kdfCount; @@ -338,7 +338,6 @@ ref MemoryMarshal.GetReference(destination), } } - [SupportedOSPlatform("windows")] internal static CngKey Duplicate(this SafeNCryptKeyHandle keyHandle, bool isEphemeral) { return CngKey.Open(keyHandle, isEphemeral ? CngKeyHandleOpenOptions.EphemeralKey : CngKeyHandleOpenOptions.None); @@ -355,6 +354,29 @@ internal static CngKey Duplicate(this CngKey key) return Duplicate(handle, key.IsEphemeral); } #pragma warning restore CA1416 // only supported on: 'windows' +#endif + } + + private static void RngFill(Span destination) + { +#if NET + RandomNumberGenerator.Fill(destination); +#else + byte[] tmp = new byte[destination.Length]; + + try + { + using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(tmp); + } + + tmp.AsSpan().CopyTo(destination); + } + finally + { + CryptographicOperations.ZeroMemory(tmp); + } #endif } } diff --git a/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanBaseTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanBaseTests.cs index 34b326624458e3..15e46dd0a244ed 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanBaseTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanBaseTests.cs @@ -264,8 +264,8 @@ public void DeriveRawSecretAgreement_ZeroSharedSecret_Throws() { // Wycheproof tcId 64: peer public key is a low-order point on Curve25519. // This low-order point produces a shared secret that is all zeros. - byte[] privateKey = Convert.FromHexString("387355d995616090503aafad49da01fb3dc3eda962704eaee6b86f9e20c92579"); - byte[] peerPublicKey = Convert.FromHexString("5f9c95bca3508c24b1d0b1559c83ef5b04445cc4581c8e86d8224eddd09f1157"); + byte[] privateKey = "387355d995616090503aafad49da01fb3dc3eda962704eaee6b86f9e20c92579".HexToByteArray(); + byte[] peerPublicKey = "5f9c95bca3508c24b1d0b1559c83ef5b04445cc4581c8e86d8224eddd09f1157".HexToByteArray(); using X25519DiffieHellman key = ImportPrivateKey(privateKey); using X25519DiffieHellman peer = ImportPublicKey(peerPublicKey); @@ -278,8 +278,8 @@ public void DeriveRawSecretAgreement_ExactBuffers_ZeroSharedSecret_Throws() { // Wycheproof tcId 64: peer public key is a low-order point on Curve25519. // This low-order point produces a shared secret that is all zeros. - byte[] privateKey = Convert.FromHexString("387355d995616090503aafad49da01fb3dc3eda962704eaee6b86f9e20c92579"); - byte[] peerPublicKey = Convert.FromHexString("5f9c95bca3508c24b1d0b1559c83ef5b04445cc4581c8e86d8224eddd09f1157"); + byte[] privateKey = "387355d995616090503aafad49da01fb3dc3eda962704eaee6b86f9e20c92579".HexToByteArray(); + byte[] peerPublicKey = "5f9c95bca3508c24b1d0b1559c83ef5b04445cc4581c8e86d8224eddd09f1157".HexToByteArray(); using X25519DiffieHellman key = ImportPrivateKey(privateKey); using X25519DiffieHellman peer = ImportPublicKey(peerPublicKey); @@ -293,8 +293,8 @@ public void DeriveRawSecretAgreement_Bytes_ZeroSharedSecret_Throws() { // Wycheproof tcId 64: peer public key is a low-order point on Curve25519. // This low-order point produces a shared secret that is all zeros. - byte[] privateKey = Convert.FromHexString("387355d995616090503aafad49da01fb3dc3eda962704eaee6b86f9e20c92579"); - byte[] peerPublicKey = Convert.FromHexString("5f9c95bca3508c24b1d0b1559c83ef5b04445cc4581c8e86d8224eddd09f1157"); + byte[] privateKey = "387355d995616090503aafad49da01fb3dc3eda962704eaee6b86f9e20c92579".HexToByteArray(); + byte[] peerPublicKey = "5f9c95bca3508c24b1d0b1559c83ef5b04445cc4581c8e86d8224eddd09f1157".HexToByteArray(); using X25519DiffieHellman key = ImportPrivateKey(privateKey); @@ -309,8 +309,8 @@ public void DeriveRawSecretAgreement_Bytes_ExactBuffers_ZeroSharedSecret_Throws( { // Wycheproof tcId 64: peer public key is a low-order point on Curve25519. // This low-order point produces a shared secret that is all zeros. - byte[] privateKey = Convert.FromHexString("387355d995616090503aafad49da01fb3dc3eda962704eaee6b86f9e20c92579"); - byte[] peerPublicKey = Convert.FromHexString("5f9c95bca3508c24b1d0b1559c83ef5b04445cc4581c8e86d8224eddd09f1157"); + byte[] privateKey = "387355d995616090503aafad49da01fb3dc3eda962704eaee6b86f9e20c92579".HexToByteArray(); + byte[] peerPublicKey = "5f9c95bca3508c24b1d0b1559c83ef5b04445cc4581c8e86d8224eddd09f1157".HexToByteArray(); using X25519DiffieHellman key = ImportPrivateKey(privateKey); @@ -733,7 +733,7 @@ public static IEnumerable DeriveSecretAgreementVect { // Wycheproof Cases 2-4: low-order shared secret results (same private key) byte[] wycheproofPrivate2 = - Convert.FromHexString("a0a4f130b98a5be4b1cedb7cb85584a3520e142d474dc9ccb909a073a976bf63"); + "a0a4f130b98a5be4b1cedb7cb85584a3520e142d474dc9ccb909a073a976bf63".HexToByteArray(); // RFC 7748 Section 6.1 yield return new("RFC7748_Alice", @@ -764,212 +764,212 @@ public static IEnumerable DeriveSecretAgreementVect // platforms reject the non-canonical encoding. yield return new("NonCanonical_BasePoint_PPlus9", X25519DiffieHellmanTestData.AlicePrivateKey, - Convert.FromHexString("f6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"), + "f6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f".HexToByteArray(), X25519DiffieHellmanTestData.AlicePublicKey, RequiresLenientValidation: true); // Wycheproof Case 0: near-max public key (0xF0FF...FF7F) yield return new("Wycheproof_0", - Convert.FromHexString("288796bc5aff4b81a37501757bc0753a3c21964790d38699308debc17a6eaf8d"), - Convert.FromHexString("f0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"), - Convert.FromHexString("b4e0dd76da7b071728b61f856771aa356e57eda78a5b1655cc3820fb5f854c5c"), + "288796bc5aff4b81a37501757bc0753a3c21964790d38699308debc17a6eaf8d".HexToByteArray(), + "f0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f".HexToByteArray(), + "b4e0dd76da7b071728b61f856771aa356e57eda78a5b1655cc3820fb5f854c5c".HexToByteArray(), RequiresLenientValidation: true); // Wycheproof Case 1: all-bits-set public key (0xF0FF...FFFF) yield return new("Wycheproof_1", - Convert.FromHexString("60887b3dc72443026ebedbbbb70665f42b87add1440e7768fbd7e8e2ce5f639d"), - Convert.FromHexString("f0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), - Convert.FromHexString("38d6304c4a7e6d9f7959334fb5245bd2c754525d4c91db950206926234c1f633"), + "60887b3dc72443026ebedbbbb70665f42b87add1440e7768fbd7e8e2ce5f639d".HexToByteArray(), + "f0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".HexToByteArray(), + "38d6304c4a7e6d9f7959334fb5245bd2c754525d4c91db950206926234c1f633".HexToByteArray(), RequiresLenientValidation: true); yield return new("Wycheproof_2_LowOrder", wycheproofPrivate2, - Convert.FromHexString("0ab4e76380d84dde4f6833c58f2a9fb8f83bb0169b172be4b6e0592887741a36"), - Convert.FromHexString("0200000000000000000000000000000000000000000000000000000000000000"), + "0ab4e76380d84dde4f6833c58f2a9fb8f83bb0169b172be4b6e0592887741a36".HexToByteArray(), + "0200000000000000000000000000000000000000000000000000000000000000".HexToByteArray(), RequiresLenientValidation: true); yield return new("Wycheproof_3_LowOrder", wycheproofPrivate2, - Convert.FromHexString("89e10d5701b4337d2d032181538b1064bd4084401ceca1fd12663a1959388000"), - Convert.FromHexString("0900000000000000000000000000000000000000000000000000000000000000")); + "89e10d5701b4337d2d032181538b1064bd4084401ceca1fd12663a1959388000".HexToByteArray(), + "0900000000000000000000000000000000000000000000000000000000000000".HexToByteArray()); yield return new("Wycheproof_4_LowOrder", wycheproofPrivate2, - Convert.FromHexString("2b55d3aa4a8f80c8c0b2ae5f933e85af49beac36c2fa7394bab76c8933f8f81d"), - Convert.FromHexString("1000000000000000000000000000000000000000000000000000000000000000")); + "2b55d3aa4a8f80c8c0b2ae5f933e85af49beac36c2fa7394bab76c8933f8f81d".HexToByteArray(), + "1000000000000000000000000000000000000000000000000000000000000000".HexToByteArray()); // Select Wycheproof X25519 test vectors for edge cases. yield return new("WycheproofTcId_1", // Normal - Convert.FromHexString("c8a9d5a91091ad851c668b0736c1c9a02936c0d3ad62670858088047ba057475"), - Convert.FromHexString("504a36999f489cd2fdbc08baff3d88fa00569ba986cba22548ffde80f9806829"), - Convert.FromHexString("436a2c040cf45fea9b29a0cb81b1f41458f863d0d61b453d0a982720d6d61320")); + "c8a9d5a91091ad851c668b0736c1c9a02936c0d3ad62670858088047ba057475".HexToByteArray(), + "504a36999f489cd2fdbc08baff3d88fa00569ba986cba22548ffde80f9806829".HexToByteArray(), + "436a2c040cf45fea9b29a0cb81b1f41458f863d0d61b453d0a982720d6d61320".HexToByteArray()); yield return new("WycheproofTcId_2", // Twist - Convert.FromHexString("d85d8c061a50804ac488ad774ac716c3f5ba714b2712e048491379a500211958"), - Convert.FromHexString("63aa40c6e38346c5caf23a6df0a5e6c80889a08647e551b3563449befcfc9733"), - Convert.FromHexString("279df67a7c4611db4708a0e8282b195e5ac0ed6f4b2f292c6fbd0acac30d1332"), + "d85d8c061a50804ac488ad774ac716c3f5ba714b2712e048491379a500211958".HexToByteArray(), + "63aa40c6e38346c5caf23a6df0a5e6c80889a08647e551b3563449befcfc9733".HexToByteArray(), + "279df67a7c4611db4708a0e8282b195e5ac0ed6f4b2f292c6fbd0acac30d1332".HexToByteArray(), RequiresLenientValidation: true); yield return new("WycheproofTcId_4", // Twist - Convert.FromHexString("f876e34bcbe1f47fbc0fddfd7c1e1aa53d57bfe0f66d243067b424bb6210be51"), - Convert.FromHexString("0b8211a2b6049097f6871c6c052d3c5fc1ba17da9e32ae458403b05bb283092a"), - Convert.FromHexString("119d37ed4b109cbd6418b1f28dea83c836c844715cdf98a3a8c362191debd514"), + "f876e34bcbe1f47fbc0fddfd7c1e1aa53d57bfe0f66d243067b424bb6210be51".HexToByteArray(), + "0b8211a2b6049097f6871c6c052d3c5fc1ba17da9e32ae458403b05bb283092a".HexToByteArray(), + "119d37ed4b109cbd6418b1f28dea83c836c844715cdf98a3a8c362191debd514".HexToByteArray(), RequiresLenientValidation: true); yield return new("WycheproofTcId_7", // SpecialPublicKey,Twist - Convert.FromHexString("d03edde9f3e7b799045f9ac3793d4a9277dadeadc41bec0290f81f744f73775f"), - Convert.FromHexString("0200000000000000000000000000000000000000000000000000000000000000"), - Convert.FromHexString("b87a1722cc6c1e2feecb54e97abd5a22acc27616f78f6e315fd2b73d9f221e57"), + "d03edde9f3e7b799045f9ac3793d4a9277dadeadc41bec0290f81f744f73775f".HexToByteArray(), + "0200000000000000000000000000000000000000000000000000000000000000".HexToByteArray(), + "b87a1722cc6c1e2feecb54e97abd5a22acc27616f78f6e315fd2b73d9f221e57".HexToByteArray(), RequiresLenientValidation: true); yield return new("WycheproofTcId_19", // SpecialPublicKey,Twist - Convert.FromHexString("105d621e1ef339c3d99245cfb77cd3a5bd0c4427a0e4d8752c3b51f045889b4f"), - Convert.FromHexString("ffffff030000f8ffff1f0000c0ffffff000000feffff070000f0ffff3f000000"), - Convert.FromHexString("61eace52da5f5ecefafa4f199b077ff64f2e3d2a6ece6f8ec0497826b212ef5f"), + "105d621e1ef339c3d99245cfb77cd3a5bd0c4427a0e4d8752c3b51f045889b4f".HexToByteArray(), + "ffffff030000f8ffff1f0000c0ffffff000000feffff070000f0ffff3f000000".HexToByteArray(), + "61eace52da5f5ecefafa4f199b077ff64f2e3d2a6ece6f8ec0497826b212ef5f".HexToByteArray(), RequiresLenientValidation: true); yield return new("WycheproofTcId_31", // SpecialPublicKey,Twist - Convert.FromHexString("d02456e456911d3c6cd054933199807732dfdc958642ad1aebe900c793bef24a"), - Convert.FromHexString("eaffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"), - Convert.FromHexString("07ba5fcbda21a9a17845c401492b10e6de0a168d5c94b606694c11bac39bea41"), + "d02456e456911d3c6cd054933199807732dfdc958642ad1aebe900c793bef24a".HexToByteArray(), + "eaffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f".HexToByteArray(), + "07ba5fcbda21a9a17845c401492b10e6de0a168d5c94b606694c11bac39bea41".HexToByteArray(), RequiresLenientValidation: true); yield return new("WycheproofTcId_34", // SpecialPublicKey - Convert.FromHexString("a8386f7f16c50731d64f82e6a170b142a4e34f31fd7768fcb8902925e7d1e25a"), - Convert.FromHexString("0400000000000000000000000000000000000000000000000000000000000000"), - Convert.FromHexString("34b7e4fa53264420d9f943d15513902342b386b172a0b0b7c8b8f2dd3d669f59"), + "a8386f7f16c50731d64f82e6a170b142a4e34f31fd7768fcb8902925e7d1e25a".HexToByteArray(), + "0400000000000000000000000000000000000000000000000000000000000000".HexToByteArray(), + "34b7e4fa53264420d9f943d15513902342b386b172a0b0b7c8b8f2dd3d669f59".HexToByteArray(), RequiresLenientValidation: true); yield return new("WycheproofTcId_48", // SpecialPublicKey - Convert.FromHexString("182191b7052e9cd630ef08007fc6b43bc7652913be6774e2fd271b71b962a641"), - Convert.FromHexString("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03"), - Convert.FromHexString("a0e0315175788362d4ebe05e6ac76d52d40187bd687492af05abc7ba7c70197d")); + "182191b7052e9cd630ef08007fc6b43bc7652913be6774e2fd271b71b962a641".HexToByteArray(), + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03".HexToByteArray(), + "a0e0315175788362d4ebe05e6ac76d52d40187bd687492af05abc7ba7c70197d".HexToByteArray()); yield return new("WycheproofTcId_62", // SpecialPublicKey - Convert.FromHexString("40bd4e1caf39d9def7663823502dad3e7d30eb6eb01e9b89516d4f2f45b7cd7f"), - Convert.FromHexString("ebffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"), - Convert.FromHexString("2cf6974b0c070e3707bf92e721d3ea9de3db6f61ed810e0a23d72d433365f631"), + "40bd4e1caf39d9def7663823502dad3e7d30eb6eb01e9b89516d4f2f45b7cd7f".HexToByteArray(), + "ebffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f".HexToByteArray(), + "2cf6974b0c070e3707bf92e721d3ea9de3db6f61ed810e0a23d72d433365f631".HexToByteArray(), RequiresLenientValidation: true); yield return new("WycheproofTcId_87", // NonCanonicalPublic,SpecialPublicKey,Twist - Convert.FromHexString("0016b62af5cabde8c40938ebf2108e05d27fa0533ed85d70015ad4ad39762d54"), - Convert.FromHexString("efffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"), - Convert.FromHexString("b4d10e832714972f96bd3382e4d082a21a8333a16315b3ffb536061d2482360d"), + "0016b62af5cabde8c40938ebf2108e05d27fa0533ed85d70015ad4ad39762d54".HexToByteArray(), + "efffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f".HexToByteArray(), + "b4d10e832714972f96bd3382e4d082a21a8333a16315b3ffb536061d2482360d".HexToByteArray(), RequiresLenientValidation: true); yield return new("WycheproofTcId_89", // NonCanonicalPublic,SpecialPublicKey - Convert.FromHexString("88dd14e2711ebd0b0026c651264ca965e7e3da5082789fbab7e24425e7b4377e"), - Convert.FromHexString("f1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"), - Convert.FromHexString("6919992d6a591e77b3f2bacbd74caf3aea4be4802b18b2bc07eb09ade3ad6662"), + "88dd14e2711ebd0b0026c651264ca965e7e3da5082789fbab7e24425e7b4377e".HexToByteArray(), + "f1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f".HexToByteArray(), + "6919992d6a591e77b3f2bacbd74caf3aea4be4802b18b2bc07eb09ade3ad6662".HexToByteArray(), RequiresLenientValidation: true); yield return new("WycheproofTcId_91", // NonCanonicalPublic,SpecialPublicKey,Twist - Convert.FromHexString("c0697b6f05e0f3433b44ea352f20508eb0623098a7770853af5ca09727340c4e"), - Convert.FromHexString("0200000000000000000000000000000000000000000000000000000000000080"), - Convert.FromHexString("ed18b06da512cab63f22d2d51d77d99facd3c4502e4abf4e97b094c20a9ddf10"), + "c0697b6f05e0f3433b44ea352f20508eb0623098a7770853af5ca09727340c4e".HexToByteArray(), + "0200000000000000000000000000000000000000000000000000000000000080".HexToByteArray(), + "ed18b06da512cab63f22d2d51d77d99facd3c4502e4abf4e97b094c20a9ddf10".HexToByteArray(), RequiresLenientValidation: true); yield return new("WycheproofTcId_94", // NonCanonicalPublic,SpecialPublicKey - Convert.FromHexString("285a6a7ceeb7122f2c78d99c53b2a902b490892f7dff326f89d12673c3101b53"), - Convert.FromHexString("daffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), - Convert.FromHexString("9b01287717d72f4cfb583ec85f8f936849b17d978dbae7b837db56a62f100a68"), + "285a6a7ceeb7122f2c78d99c53b2a902b490892f7dff326f89d12673c3101b53".HexToByteArray(), + "daffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".HexToByteArray(), + "9b01287717d72f4cfb583ec85f8f936849b17d978dbae7b837db56a62f100a68".HexToByteArray(), RequiresLenientValidation: true); yield return new("WycheproofTcId_97", // NonCanonicalPublic,SpecialPublicKey,Twist - Convert.FromHexString("9041c6e044a277df8466275ca8b5ee0da7bc028648054ade5c592add3057474e"), - Convert.FromHexString("eaffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), - Convert.FromHexString("13da5695a4c206115409b5277a934782fe985fa050bc902cba5616f9156fe277"), + "9041c6e044a277df8466275ca8b5ee0da7bc028648054ade5c592add3057474e".HexToByteArray(), + "eaffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".HexToByteArray(), + "13da5695a4c206115409b5277a934782fe985fa050bc902cba5616f9156fe277".HexToByteArray(), RequiresLenientValidation: true); yield return new("WycheproofTcId_99", // NonCanonicalPublic,SpecialPublicKey - Convert.FromHexString("c85f08e60c845f82099141a66dc4583d2b1040462c544d33d0453b20b1a6377e"), - Convert.FromHexString("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), - Convert.FromHexString("e9db74bc88d0d9bf046ddd13f943bccbe6dbb47d49323f8dfeedc4a694991a3c"), + "c85f08e60c845f82099141a66dc4583d2b1040462c544d33d0453b20b1a6377e".HexToByteArray(), + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".HexToByteArray(), + "e9db74bc88d0d9bf046ddd13f943bccbe6dbb47d49323f8dfeedc4a694991a3c".HexToByteArray(), RequiresLenientValidation: true); yield return new("WycheproofTcId_100", // Ktv - Convert.FromHexString("a046e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449a44"), - Convert.FromHexString("e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c"), - Convert.FromHexString("c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552"), + "a046e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449a44".HexToByteArray(), + "e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c".HexToByteArray(), + "c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552".HexToByteArray(), RequiresLenientValidation: true); yield return new("WycheproofTcId_101", // Ktv,Twist - Convert.FromHexString("4866e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba4d"), - Convert.FromHexString("e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a413"), - Convert.FromHexString("95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957"), + "4866e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba4d".HexToByteArray(), + "e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a413".HexToByteArray(), + "95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957".HexToByteArray(), RequiresLenientValidation: true); yield return new("WycheproofTcId_102", // Ktv - Convert.FromHexString("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a"), - Convert.FromHexString("de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f"), - Convert.FromHexString("4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742")); + "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a".HexToByteArray(), + "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f".HexToByteArray(), + "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742".HexToByteArray()); yield return new("WycheproofTcId_103", // EdgeCaseShared,Twist - Convert.FromHexString("60a3a4f130b98a5be4b1cedb7cb85584a3520e142d474dc9ccb909a073a9767f"), - Convert.FromHexString("b7b6d39c765cb60c0c8542f4f3952ffb51d3002d4aeb9f8ff988b192043e6d0a"), - Convert.FromHexString("0200000000000000000000000000000000000000000000000000000000000000"), + "60a3a4f130b98a5be4b1cedb7cb85584a3520e142d474dc9ccb909a073a9767f".HexToByteArray(), + "b7b6d39c765cb60c0c8542f4f3952ffb51d3002d4aeb9f8ff988b192043e6d0a".HexToByteArray(), + "0200000000000000000000000000000000000000000000000000000000000000".HexToByteArray(), RequiresLenientValidation: true); yield return new("WycheproofTcId_104", // EdgeCaseShared - Convert.FromHexString("60a3a4f130b98a5be4b1cedb7cb85584a3520e142d474dc9ccb909a073a9767f"), - Convert.FromHexString("3b18df1e50b899ebd588c3161cbd3bf98ebcc2c1f7df53b811bd0e91b4d5153d"), - Convert.FromHexString("0900000000000000000000000000000000000000000000000000000000000000")); + "60a3a4f130b98a5be4b1cedb7cb85584a3520e142d474dc9ccb909a073a9767f".HexToByteArray(), + "3b18df1e50b899ebd588c3161cbd3bf98ebcc2c1f7df53b811bd0e91b4d5153d".HexToByteArray(), + "0900000000000000000000000000000000000000000000000000000000000000".HexToByteArray()); yield return new("WycheproofTcId_107", // EdgeCaseShared - Convert.FromHexString("60a3a4f130b98a5be4b1cedb7cb85584a3520e142d474dc9ccb909a073a9767f"), - Convert.FromHexString("98730bc03e29e8b057fb1d20ef8c0bffc822485d3db7f45f4e3cc2c3c6d1d14c"), - Convert.FromHexString("fcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f")); + "60a3a4f130b98a5be4b1cedb7cb85584a3520e142d474dc9ccb909a073a9767f".HexToByteArray(), + "98730bc03e29e8b057fb1d20ef8c0bffc822485d3db7f45f4e3cc2c3c6d1d14c".HexToByteArray(), + "fcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f".HexToByteArray()); yield return new("WycheproofTcId_112", // EdgeCaseShared,Twist - Convert.FromHexString("60a3a4f130b98a5be4b1cedb7cb85584a3520e142d474dc9ccb909a073a9767f"), - Convert.FromHexString("8d612c5831aa64b057300e7e310f3aa332af34066fefcab2b089c9592878f832"), - Convert.FromHexString("e3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"), + "60a3a4f130b98a5be4b1cedb7cb85584a3520e142d474dc9ccb909a073a9767f".HexToByteArray(), + "8d612c5831aa64b057300e7e310f3aa332af34066fefcab2b089c9592878f832".HexToByteArray(), + "e3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f".HexToByteArray(), RequiresLenientValidation: true); yield return new("WycheproofTcId_113", // EdgeCaseShared - Convert.FromHexString("60a3a4f130b98a5be4b1cedb7cb85584a3520e142d474dc9ccb909a073a9767f"), - Convert.FromHexString("8d44108d05d940d3dfe5647ea7a87be24d0d036c9f0a95a2386b839e7b7bf145"), - Convert.FromHexString("ddffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f")); + "60a3a4f130b98a5be4b1cedb7cb85584a3520e142d474dc9ccb909a073a9767f".HexToByteArray(), + "8d44108d05d940d3dfe5647ea7a87be24d0d036c9f0a95a2386b839e7b7bf145".HexToByteArray(), + "ddffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f".HexToByteArray()); yield return new("WycheproofTcId_116", // EdgeCaseShared,Twist - Convert.FromHexString("60a3a4f130b98a5be4b1cedb7cb85584a3520e142d474dc9ccb909a073a9767f"), - Convert.FromHexString("8e41f05ea3c76572be104ad8788e970863c6e2ca3daae64d1c2f46decfffa571"), - Convert.FromHexString("0000000000000000000000000000000000000000000000000000000000008000"), + "60a3a4f130b98a5be4b1cedb7cb85584a3520e142d474dc9ccb909a073a9767f".HexToByteArray(), + "8e41f05ea3c76572be104ad8788e970863c6e2ca3daae64d1c2f46decfffa571".HexToByteArray(), + "0000000000000000000000000000000000000000000000000000000000008000".HexToByteArray(), RequiresLenientValidation: true); yield return new("WycheproofTcId_119", // EdgeCaseMultiplication,Twist - Convert.FromHexString("e0a8be63315c4f0f0a3fee607f44d30a55be63f09561d9af93e0a1c9cf0ed751"), - Convert.FromHexString("0200000000000000000000000000000000000000000000000000000000000000"), - Convert.FromHexString("0c50ac2bfb6815b47d0734c5981379882a24a2de6166853c735329d978baee4d"), + "e0a8be63315c4f0f0a3fee607f44d30a55be63f09561d9af93e0a1c9cf0ed751".HexToByteArray(), + "0200000000000000000000000000000000000000000000000000000000000000".HexToByteArray(), + "0c50ac2bfb6815b47d0734c5981379882a24a2de6166853c735329d978baee4d".HexToByteArray(), RequiresLenientValidation: true); yield return new("WycheproofTcId_120", // EdgeCaseMultiplication - Convert.FromHexString("0840a8af5bc4c48da8850e973d7e14220f45c192cea4020d377eecd25c7c3643"), - Convert.FromHexString("1200000000000000000000000000000000000000000000000000000000000000"), - Convert.FromHexString("77557137a2a2a651c49627a9b239ac1f2bf78b8a3e72168ccecc10a51fc5ae66"), + "0840a8af5bc4c48da8850e973d7e14220f45c192cea4020d377eecd25c7c3643".HexToByteArray(), + "1200000000000000000000000000000000000000000000000000000000000000".HexToByteArray(), + "77557137a2a2a651c49627a9b239ac1f2bf78b8a3e72168ccecc10a51fc5ae66".HexToByteArray(), RequiresLenientValidation: true); yield return new("WycheproofTcId_249", // EdgeCaseMultiplication - Convert.FromHexString("4028802030d8a8221a7160eebbf1846116c1c253abc467d6e43cb850f1459860"), - Convert.FromHexString("0f13955978b93d7b9f9a2e70d96df922850a8ffd8412e236fb074aef99d37d54"), - Convert.FromHexString("e23d63a46be67c7443c07b9371ff6a06afcd7a5794bf2537926074b88190307a"), + "4028802030d8a8221a7160eebbf1846116c1c253abc467d6e43cb850f1459860".HexToByteArray(), + "0f13955978b93d7b9f9a2e70d96df922850a8ffd8412e236fb074aef99d37d54".HexToByteArray(), + "e23d63a46be67c7443c07b9371ff6a06afcd7a5794bf2537926074b88190307a".HexToByteArray(), RequiresLenientValidation: true); yield return new("WycheproofTcId_256", // EdgeCaseMultiplication,Twist - Convert.FromHexString("d0e67f68183a4c1aed9c56864b36278bb7bb75d57a78321bc7c24ff61636607a"), - Convert.FromHexString("d8c8e2c6f33a98525df3767d1d04430dab0bda41f1f904c95bc61cc122caca74"), - Convert.FromHexString("ef7612c156078dae3a81e50ef33951cab661fb07731d8f419bc0105c4d6d6050"), + "d0e67f68183a4c1aed9c56864b36278bb7bb75d57a78321bc7c24ff61636607a".HexToByteArray(), + "d8c8e2c6f33a98525df3767d1d04430dab0bda41f1f904c95bc61cc122caca74".HexToByteArray(), + "ef7612c156078dae3a81e50ef33951cab661fb07731d8f419bc0105c4d6d6050".HexToByteArray(), RequiresLenientValidation: true); yield return new("WycheproofTcId_377", // EdgeCaseMultiplication - Convert.FromHexString("c01f66cb094289d728421dd46c6f9718412e1c546dad70e586851be4da58bf67"), - Convert.FromHexString("4e056b317a31dd96f8ec14b48474af587d195efcc2a70f01f052ef882d7b3a45"), - Convert.FromHexString("bad9f7b27dac64b0fc980a41f1cefa50c5ca40c714296c0c4042095c2db60e11"), + "c01f66cb094289d728421dd46c6f9718412e1c546dad70e586851be4da58bf67".HexToByteArray(), + "4e056b317a31dd96f8ec14b48474af587d195efcc2a70f01f052ef882d7b3a45".HexToByteArray(), + "bad9f7b27dac64b0fc980a41f1cefa50c5ca40c714296c0c4042095c2db60e11".HexToByteArray(), RequiresLenientValidation: true); yield return new("WycheproofTcId_387", // EdgeCaseMultiplication,Twist - Convert.FromHexString("204a3b5652854ff48e25cd385cabe6360f64ce44fea5621db1fa2f6e219f3063"), - Convert.FromHexString("ed1c82082b74cc2aaebf3dc772ba09557c0fc14139a8814fc5f9370bb8e98858"), - Convert.FromHexString("e0a82f313046024b3cea93b98e2f8ecf228cbfab8ae10b10292c32feccff1603"), + "204a3b5652854ff48e25cd385cabe6360f64ce44fea5621db1fa2f6e219f3063".HexToByteArray(), + "ed1c82082b74cc2aaebf3dc772ba09557c0fc14139a8814fc5f9370bb8e98858".HexToByteArray(), + "e0a82f313046024b3cea93b98e2f8ecf228cbfab8ae10b10292c32feccff1603".HexToByteArray(), RequiresLenientValidation: true); yield return new("WycheproofTcId_506", // EdgeCaseMultiplication,Twist - Convert.FromHexString("707ee81f113a244c9d87608b12158c50f9ac1f2c8948d170ad16ab0ad866d74b"), - Convert.FromHexString("dcffc4c1e1fba5fda9d5c98421d99c257afa90921bc212a046d90f6683e8a467"), - Convert.FromHexString("7ecdd54c5e15f7b4061be2c30b5a4884a0256581f87df60d579a3345653eb641"), + "707ee81f113a244c9d87608b12158c50f9ac1f2c8948d170ad16ab0ad866d74b".HexToByteArray(), + "dcffc4c1e1fba5fda9d5c98421d99c257afa90921bc212a046d90f6683e8a467".HexToByteArray(), + "7ecdd54c5e15f7b4061be2c30b5a4884a0256581f87df60d579a3345653eb641".HexToByteArray(), RequiresLenientValidation: true); yield return new("WycheproofTcId_510", // EdgeCaseMultiplication - Convert.FromHexString("78ed4c9bf9f44db8d93388985191ecf59226b9c1205fe7e762c327581c75884e"), - Convert.FromHexString("ce7295d1227c9062aab9cf02fc5671fb81632e725367f131d4122824a6132d68"), - Convert.FromHexString("3740de297ff0122067951e8985247123440e0f27171da99e263d5b4450f59f3d"), + "78ed4c9bf9f44db8d93388985191ecf59226b9c1205fe7e762c327581c75884e".HexToByteArray(), + "ce7295d1227c9062aab9cf02fc5671fb81632e725367f131d4122824a6132d68".HexToByteArray(), + "3740de297ff0122067951e8985247123440e0f27171da99e263d5b4450f59f3d".HexToByteArray(), RequiresLenientValidation: true); yield return new("WycheproofTcId_511", // EdgeCasePrivateKey - Convert.FromHexString("a023cdd083ef5bb82f10d62e59e15a6800000000000000000000000000000050"), - Convert.FromHexString("6c05871352a451dbe182ed5e6ba554f2034456ffe041a054ff9cc56b8e946376"), - Convert.FromHexString("6c05871352a451dbe182ed5e6ba554f2034456ffe041a054ff9cc56b8e946376")); + "a023cdd083ef5bb82f10d62e59e15a6800000000000000000000000000000050".HexToByteArray(), + "6c05871352a451dbe182ed5e6ba554f2034456ffe041a054ff9cc56b8e946376".HexToByteArray(), + "6c05871352a451dbe182ed5e6ba554f2034456ffe041a054ff9cc56b8e946376".HexToByteArray()); yield return new("WycheproofTcId_512", // Twist - Convert.FromHexString("58083dd261ad91eff952322ec824c682ffffffffffffffffffffffffffffff5f"), - Convert.FromHexString("2eae5ec3dd494e9f2d37d258f873a8e6e9d0dbd1e383ef64d98bb91b3e0be035"), - Convert.FromHexString("2eae5ec3dd494e9f2d37d258f873a8e6e9d0dbd1e383ef64d98bb91b3e0be035"), + "58083dd261ad91eff952322ec824c682ffffffffffffffffffffffffffffff5f".HexToByteArray(), + "2eae5ec3dd494e9f2d37d258f873a8e6e9d0dbd1e383ef64d98bb91b3e0be035".HexToByteArray(), + "2eae5ec3dd494e9f2d37d258f873a8e6e9d0dbd1e383ef64d98bb91b3e0be035".HexToByteArray(), RequiresLenientValidation: true); yield return new("WycheproofTcId_515", // EdgeCasePrivateKey - Convert.FromHexString("4855555555555555555555555555555555555555555555555555555555555555"), - Convert.FromHexString("be3b3edeffaf83c54ae526379b23dd79f1cb41446e3687fef347eb9b5f0dc308"), - Convert.FromHexString("cfa83e098829fe82fd4c14355f70829015219942c01e2b85bdd9ac4889ec2921")); + "4855555555555555555555555555555555555555555555555555555555555555".HexToByteArray(), + "be3b3edeffaf83c54ae526379b23dd79f1cb41446e3687fef347eb9b5f0dc308".HexToByteArray(), + "cfa83e098829fe82fd4c14355f70829015219942c01e2b85bdd9ac4889ec2921".HexToByteArray()); yield return new("WycheproofTcId_518", // EdgeCasePrivateKey - Convert.FromHexString("b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa6a"), - Convert.FromHexString("be3b3edeffaf83c54ae526379b23dd79f1cb41446e3687fef347eb9b5f0dc308"), - Convert.FromHexString("e3c649beae7cc4a0698d519a0a61932ee5493cbb590dbe14db0274cc8611f914")); + "b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa6a".HexToByteArray(), + "be3b3edeffaf83c54ae526379b23dd79f1cb41446e3687fef347eb9b5f0dc308".HexToByteArray(), + "e3c649beae7cc4a0698d519a0a61932ee5493cbb590dbe14db0274cc8611f914".HexToByteArray()); } } diff --git a/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanCngTests.Windows.cs b/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanCngTests.Windows.cs index a897373077c7ae..8a2fec8180111c 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanCngTests.Windows.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanCngTests.Windows.cs @@ -113,13 +113,13 @@ public abstract class X25519DiffieHellmanCngTests : X25519DiffieHellmanBaseTests protected abstract CngExportPolicies ExportPolicy { get; } - public override X25519DiffieHellmanCng GenerateKey() + public override X25519DiffieHellman GenerateKey() { using CngKey key = GenerateCngKey(exportPolicy: ExportPolicy); return new X25519DiffieHellmanCng(key); } - public override X25519DiffieHellmanCng ImportPrivateKey(ReadOnlySpan source) + public override X25519DiffieHellman ImportPrivateKey(ReadOnlySpan source) { using CryptoPoolLease lease = X25519WindowsHelpers.CreateCngBlob(source, true, out _); using SafeNCryptKeyHandle keyHandle = ECCng.ImportKeyBlob( @@ -140,7 +140,7 @@ public override X25519DiffieHellmanCng ImportPrivateKey(ReadOnlySpan sourc return new X25519DiffieHellmanCng(cngKey); } - public override X25519DiffieHellmanCng ImportPublicKey(ReadOnlySpan source) + public override X25519DiffieHellman ImportPublicKey(ReadOnlySpan source) { Span reducedPublicKey = stackalloc byte[X25519DiffieHellman.PublicKeySizeInBytes]; X25519WindowsHelpers.ReducePublicKey(source, reducedPublicKey); diff --git a/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanContractTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanContractTests.cs index 74a20a5f780021..dae20fc024c2d9 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanContractTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanContractTests.cs @@ -463,8 +463,9 @@ public static void TryExportPkcs8PrivateKey() // Test with various inputs of different sizes from TryExportPkcs8PrivateKeyCore that it reports // as-is to the public APIs. Invalid behavior like reporting more byte written than possible is handled // elsewhere. - int bufferSize = Random.Shared.Next(50, 1024); - int writtenSize = Random.Shared.Next(48, bufferSize); + Random random = new(); + int bufferSize = random.Next(50, 1024); + int writtenSize = random.Next(48, bufferSize); bool success = (writtenSize & 1) == 1; byte[] buffer = new byte[bufferSize]; X25519DiffieHellmanContract xdh = new() diff --git a/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanImplementationTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanImplementationTests.cs index b0cac751d4367e..147e4d973143fe 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanImplementationTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanImplementationTests.cs @@ -25,7 +25,9 @@ public static void IsSupported_AgreesWithPlatform() bool expectedSupported = PlatformDetection.IsWindows10OrLater || PlatformDetection.IsApplePlatform || +#if NET OperatingSystem.IsAndroidVersionAtLeast(33) || +#endif PlatformDetection.IsOpenSslSupported; // X25519 is in OpenSSL 1.1.0 and .NET's floor is 1.1.1. Assert.Equal(expectedSupported, X25519DiffieHellman.IsSupported); diff --git a/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanNotSupportedTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanNotSupportedTests.cs index 03e2711dbb0a8a..88a638093063d7 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanNotSupportedTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanNotSupportedTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Xunit; +using Test.Cryptography; namespace System.Security.Cryptography.Tests { @@ -40,9 +41,9 @@ public static void ImportPublicKey_NotSupported() public static void ImportSubjectPublicKeyInfo_NotSupported() { // A minimal valid SPKI for X25519 - byte[] spki = Convert.FromHexString( + byte[] spki = ( "302a300506032b656e032100" + - "0000000000000000000000000000000000000000000000000000000000000000"); + "0000000000000000000000000000000000000000000000000000000000000000").HexToByteArray(); Assert.Throws(() => X25519DiffieHellman.ImportSubjectPublicKeyInfo(spki)); @@ -55,9 +56,9 @@ public static void ImportSubjectPublicKeyInfo_NotSupported() public static void ImportPkcs8PrivateKey_NotSupported() { // A minimal valid PKCS#8 for X25519 - byte[] pkcs8 = Convert.FromHexString( + byte[] pkcs8 = ( "302e020100300506032b656e04220420" + - "0000000000000000000000000000000000000000000000000000000000000000"); + "0000000000000000000000000000000000000000000000000000000000000000").HexToByteArray(); Assert.Throws(() => X25519DiffieHellman.ImportPkcs8PrivateKey(pkcs8)); @@ -71,9 +72,9 @@ public static void ImportEncryptedPkcs8PrivateKey_NotSupported() { // Use an encrypted PKCS#8 blob. The implementation should throw PlatformNotSupportedException // before attempting decryption. - byte[] pkcs8 = Convert.FromHexString( + byte[] pkcs8 = ( "302e020100300506032b656e04220420" + - "0000000000000000000000000000000000000000000000000000000000000000"); + "0000000000000000000000000000000000000000000000000000000000000000").HexToByteArray(); Assert.Throws(() => X25519DiffieHellman.ImportEncryptedPkcs8PrivateKey("password", pkcs8)); From 7a3c862054f255815d679806f36524d70df3333b Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sat, 20 Jun 2026 11:07:45 -0400 Subject: [PATCH 3/6] Fix build and p/invokes for downlevel --- .../Windows/BCrypt/Interop.BCryptDeriveKey.cs | 31 ++++++++++--------- .../Interop/Windows/NCrypt/Interop.Keys.cs | 10 +++++- .../NCrypt/Interop.NCryptDeriveKeyMaterial.cs | 27 ++++++++++------ .../Security/Cryptography/CngHelpers.cs | 2 ++ 4 files changed, 46 insertions(+), 24 deletions(-) diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptDeriveKey.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptDeriveKey.cs index 0ac167f36debaf..01b8b0a989df3e 100644 --- a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptDeriveKey.cs +++ b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptDeriveKey.cs @@ -15,7 +15,7 @@ private static unsafe partial NTSTATUS BCryptDeriveKey( SafeBCryptSecretHandle hSharedSecret, string pwszKDF, ref readonly BCryptBufferDesc pParameterList, - Span pbDerivedKey, + byte* pbDerivedKey, uint cbDerivedKey, out uint pcbResult, uint dwFlags); @@ -27,21 +27,24 @@ internal static unsafe void BCryptDeriveKey( Span destination, out int written) { - NTSTATUS status = BCryptDeriveKey( - hSharedSecret, - pwszKDF, - in parameterList, - destination, - (uint)destination.Length, - out uint pcbResult, - 0); - - if (status != NTSTATUS.STATUS_SUCCESS) + fixed (byte* pDestination = destination) { - throw CreateCryptographicException(status); - } + NTSTATUS status = BCryptDeriveKey( + hSharedSecret, + pwszKDF, + in parameterList, + pDestination, + (uint)destination.Length, + out uint pcbResult, + 0); - written = checked((int)pcbResult); + if (status != NTSTATUS.STATUS_SUCCESS) + { + throw CreateCryptographicException(status); + } + + written = checked((int)pcbResult); + } } } } diff --git a/src/libraries/Common/src/Interop/Windows/NCrypt/Interop.Keys.cs b/src/libraries/Common/src/Interop/Windows/NCrypt/Interop.Keys.cs index 4cd779d627aeba..a6eca535c0fade 100644 --- a/src/libraries/Common/src/Interop/Windows/NCrypt/Interop.Keys.cs +++ b/src/libraries/Common/src/Interop/Windows/NCrypt/Interop.Keys.cs @@ -32,7 +32,15 @@ internal static partial class NCrypt internal static partial ErrorCode NCryptExportKey(SafeNCryptKeyHandle hKey, IntPtr hExportKey, string pszBlobType, IntPtr pParameterList, ref byte pbOutput, int cbOutput, out int pcbResult, int dwFlags); [LibraryImport(Interop.Libraries.NCrypt, StringMarshalling = StringMarshalling.Utf16)] - internal static partial ErrorCode NCryptExportKey(SafeNCryptKeyHandle hKey, IntPtr hExportKey, string pszBlobType, IntPtr pParameterList, Span pbOutput, int cbOutput, out int pcbResult, int dwFlags); + private static unsafe partial ErrorCode NCryptExportKey(SafeNCryptKeyHandle hKey, IntPtr hExportKey, string pszBlobType, IntPtr pParameterList, byte* pbOutput, int cbOutput, out int pcbResult, int dwFlags); + + internal static unsafe ErrorCode NCryptExportKey(SafeNCryptKeyHandle hKey, IntPtr hExportKey, string pszBlobType, IntPtr pParameterList, Span pbOutput, int cbOutput, out int pcbResult, int dwFlags) + { + fixed (byte* pOutput = pbOutput) + { + return NCryptExportKey(hKey, hExportKey, pszBlobType, pParameterList, pOutput, cbOutput, out pcbResult, dwFlags); + } + } [LibraryImport(Interop.Libraries.NCrypt, StringMarshalling = StringMarshalling.Utf16)] internal static partial ErrorCode NCryptExportKey(SafeNCryptKeyHandle hKey, IntPtr hExportKey, string pszBlobType, ref NCryptBufferDesc pParameterList, ref byte pbOutput, int cbOutput, out int pcbResult, int dwFlags); diff --git a/src/libraries/Common/src/Interop/Windows/NCrypt/Interop.NCryptDeriveKeyMaterial.cs b/src/libraries/Common/src/Interop/Windows/NCrypt/Interop.NCryptDeriveKeyMaterial.cs index 942ab50783b231..36ab5473f932e5 100644 --- a/src/libraries/Common/src/Interop/Windows/NCrypt/Interop.NCryptDeriveKeyMaterial.cs +++ b/src/libraries/Common/src/Interop/Windows/NCrypt/Interop.NCryptDeriveKeyMaterial.cs @@ -30,7 +30,7 @@ private static partial ErrorCode NCryptDeriveKey( SafeNCryptSecretHandle hSharedSecret, string pwszKDF, IntPtr pParameterList, - Span pbDerivedKey, + byte* pbDerivedKey, int cbDerivedKey, out int pcbResult, SecretAgreementFlags dwFlags); @@ -287,14 +287,23 @@ internal static bool TryDeriveKeyMaterialTruncate( Span destination, out int bytesWritten) { - ErrorCode error = NCryptDeriveKey( - secretAgreement, - BCryptNative.KeyDerivationFunction.Raw, - IntPtr.Zero, - destination, - destination.Length, - out int localWritten, - flags); + ErrorCode error; + int localWritten; + + unsafe + { + fixed (byte* pDestination = destination) + { + error = NCryptDeriveKey( + secretAgreement, + BCryptNative.KeyDerivationFunction.Raw, + IntPtr.Zero, + pDestination, + destination.Length, + out localWritten, + flags); + } + } switch (error) { diff --git a/src/libraries/Common/src/System/Security/Cryptography/CngHelpers.cs b/src/libraries/Common/src/System/Security/Cryptography/CngHelpers.cs index 43ff0f39eada36..cf89fa27bc73d5 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/CngHelpers.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/CngHelpers.cs @@ -340,7 +340,9 @@ ref MemoryMarshal.GetReference(destination), internal static CngKey Duplicate(this SafeNCryptKeyHandle keyHandle, bool isEphemeral) { +#pragma warning disable CA1416 // only supported on: 'windows' return CngKey.Open(keyHandle, isEphemeral ? CngKeyHandleOpenOptions.EphemeralKey : CngKeyHandleOpenOptions.None); +#pragma warning restore CA1416 // only supported on: 'windows' } internal static CngKey Duplicate(this CngKey key) From e9135298f8611ad6e2f13a2a4cfeaa7bf22e04f1 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sat, 20 Jun 2026 11:21:53 -0400 Subject: [PATCH 4/6] Address code review feedback --- .../System/Security/Cryptography/Helpers.cs | 15 ---------- .../src/System.Security.Cryptography.csproj | 8 +++--- .../System.Security.Cryptography.Tests.csproj | 28 +++++++++---------- 3 files changed, 18 insertions(+), 33 deletions(-) diff --git a/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/Helpers.cs b/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/Helpers.cs index 2d50b4bd4b445c..3ed31067629db3 100644 --- a/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/Helpers.cs +++ b/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/Helpers.cs @@ -99,21 +99,6 @@ internal static int GetHashLengthInBytes(this IncrementalHash hash) } } - extension (RandomNumberGenerator) - { - internal static unsafe void Fill(Span buffer) - { - if (buffer.Length > 0) - { - fixed (byte* pbBuffer = buffer) - { - Interop.BCrypt.NTSTATUS status = Interop.BCrypt.BCryptGenRandom(IntPtr.Zero, pbBuffer, buffer.Length, Interop.BCrypt.BCRYPT_USE_SYSTEM_PREFERRED_RNG); - if (status != Interop.BCrypt.NTSTATUS.STATUS_SUCCESS) - throw Interop.BCrypt.CreateCryptographicException(status); - } - } - } - } #endif } } diff --git a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj index b8a6eab91a2fbb..26a9a073d41714 100644 --- a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj +++ b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj @@ -428,6 +428,10 @@ Link="Common\System\Security\Cryptography\MLKemCng.cs" /> + + - - diff --git a/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj b/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj index a0cb9b7393e670..901cc08e329c4d 100644 --- a/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj +++ b/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj @@ -246,6 +246,20 @@ Link="CommonTest\System\Security\Cryptography\MLKemTests.cs" /> + + + + + + + - - - - - - - From 487bf9b478be8e8375f037bfb0f6178db63f5be7 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sat, 20 Jun 2026 14:49:32 -0400 Subject: [PATCH 5/6] Relax asserts --- .../X25519DiffieHellmanBaseTests.cs | 24 +++++++++---------- .../X25519DiffieHellmanCngTests.Windows.cs | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanBaseTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanBaseTests.cs index 15e46dd0a244ed..ac939e83c58bb1 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanBaseTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanBaseTests.cs @@ -84,8 +84,8 @@ public void ExportPrivateKey_PublicKeyOnly_Throws() using X25519DiffieHellman xdh = GenerateKey(); using X25519DiffieHellman publicOnly = ImportPublicKey(xdh.ExportPublicKey()); - Assert.Throws(() => publicOnly.ExportPrivateKey()); - Assert.Throws(() => publicOnly.ExportPrivateKey(new byte[X25519DiffieHellman.PrivateKeySizeInBytes])); + Assert.ThrowsAny(() => publicOnly.ExportPrivateKey()); + Assert.ThrowsAny(() => publicOnly.ExportPrivateKey(new byte[X25519DiffieHellman.PrivateKeySizeInBytes])); } [Fact] @@ -381,8 +381,8 @@ public void ExportPkcs8PrivateKey_Roundtrip() public void ExportPkcs8PrivateKey_PublicKeyOnly_Fails() { using X25519DiffieHellman xdh = ImportPublicKey(X25519DiffieHellmanTestData.AlicePublicKey); - Assert.Throws(() => DoTryUntilDone(xdh.TryExportPkcs8PrivateKey)); - Assert.Throws(() => xdh.ExportPkcs8PrivateKey()); + Assert.ThrowsAny(() => DoTryUntilDone(xdh.TryExportPkcs8PrivateKey)); + Assert.ThrowsAny(() => xdh.ExportPkcs8PrivateKey()); } [Fact] @@ -410,36 +410,36 @@ public void ExportEncryptedPkcs8PrivateKey_PublicKeyOnly_Fails() { using X25519DiffieHellman xdh = ImportPublicKey(X25519DiffieHellmanTestData.AlicePublicKey); - Assert.Throws(() => DoTryUntilDone((Span destination, out int bytesWritten) => + Assert.ThrowsAny(() => DoTryUntilDone((Span destination, out int bytesWritten) => xdh.TryExportEncryptedPkcs8PrivateKey( X25519DiffieHellmanTestData.EncryptedPrivateKeyPassword.AsSpan(), s_aes128Pbe, destination, out bytesWritten))); - Assert.Throws(() => DoTryUntilDone((Span destination, out int bytesWritten) => + Assert.ThrowsAny(() => DoTryUntilDone((Span destination, out int bytesWritten) => xdh.TryExportEncryptedPkcs8PrivateKey( X25519DiffieHellmanTestData.EncryptedPrivateKeyPasswordBytes, s_aes128Pbe, destination, out bytesWritten))); - Assert.Throws(() => xdh.ExportEncryptedPkcs8PrivateKey( + Assert.ThrowsAny(() => xdh.ExportEncryptedPkcs8PrivateKey( X25519DiffieHellmanTestData.EncryptedPrivateKeyPassword, s_aes128Pbe)); - Assert.Throws(() => xdh.ExportEncryptedPkcs8PrivateKey( + Assert.ThrowsAny(() => xdh.ExportEncryptedPkcs8PrivateKey( X25519DiffieHellmanTestData.EncryptedPrivateKeyPassword.AsSpan(), s_aes128Pbe)); - Assert.Throws(() => xdh.ExportEncryptedPkcs8PrivateKey( + Assert.ThrowsAny(() => xdh.ExportEncryptedPkcs8PrivateKey( X25519DiffieHellmanTestData.EncryptedPrivateKeyPasswordBytes, s_aes128Pbe)); - Assert.Throws(() => xdh.ExportEncryptedPkcs8PrivateKeyPem( + Assert.ThrowsAny(() => xdh.ExportEncryptedPkcs8PrivateKeyPem( X25519DiffieHellmanTestData.EncryptedPrivateKeyPasswordBytes, s_aes128Pbe)); - Assert.Throws(() => xdh.ExportEncryptedPkcs8PrivateKeyPem( + Assert.ThrowsAny(() => xdh.ExportEncryptedPkcs8PrivateKeyPem( X25519DiffieHellmanTestData.EncryptedPrivateKeyPassword, s_aes128Pbe)); - Assert.Throws(() => xdh.ExportEncryptedPkcs8PrivateKeyPem( + Assert.ThrowsAny(() => xdh.ExportEncryptedPkcs8PrivateKeyPem( X25519DiffieHellmanTestData.EncryptedPrivateKeyPassword.AsSpan(), s_aes128Pbe)); } diff --git a/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanCngTests.Windows.cs b/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanCngTests.Windows.cs index 8a2fec8180111c..7863ccfa341b7f 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanCngTests.Windows.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/X25519DiffieHellmanCngTests.Windows.cs @@ -86,7 +86,7 @@ public static void X25519DiffieHellmanCng_NonExportable_ExportPrivateKeyThrows() { using CngKey key = X25519DiffieHellmanCngTests.GenerateCngKey(exportPolicy: CngExportPolicies.None); using X25519DiffieHellmanCng xdh = new(key); - Assert.Throws(() => xdh.ExportPrivateKey()); + Assert.ThrowsAny(() => xdh.ExportPrivateKey()); } [Fact] From ea7cb7e7cf4ce63c5e78a5882870e22a63da8329 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sat, 20 Jun 2026 17:50:35 -0400 Subject: [PATCH 6/6] Fix exception for Windows internal implementation throwing an unfriendly exception --- .../Cryptography/X25519DiffieHellmanImplementation.Windows.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/Common/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Windows.cs b/src/libraries/Common/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Windows.cs index 13fdbe1abe8edd..9a9e8f95a7b172 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Windows.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Windows.cs @@ -107,6 +107,7 @@ private void DeriveRawSecretAgreementWithKey(SafeBCryptKeyHandle otherPartyKey, protected override void ExportPrivateKeyCore(Span destination) { + ThrowIfPrivateNeeded(); ExportKey(true, destination); X25519WindowsHelpers.RefixPrivateScalar(destination, _privatePreservation); }