diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X25519.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X25519.cs index 0827a041af2f7b..542e110ebf5781 100644 --- a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X25519.cs +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X25519.cs @@ -23,6 +23,15 @@ private static partial int AppleCryptoNative_X25519DeriveRawSecretAgreement( Span destination, int destinationLength); + [LibraryImport(Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_X25519DeriveRawSecretAgreementWithBytes")] + [UnmanagedCallConv(CallConvs = [ typeof(CallConvSwift) ])] + private static partial int AppleCryptoNative_X25519DeriveRawSecretAgreementWithBytes( + SafeX25519KeyHandle key, + ReadOnlySpan peerKey, + int peerKeyLength, + Span destination, + int destinationLength); + [LibraryImport(Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_X25519ExportPrivateKey")] [UnmanagedCallConv(CallConvs = [ typeof(CallConvSwift) ])] private static partial int AppleCryptoNative_X25519ExportPrivateKey( @@ -71,6 +80,32 @@ internal static void X25519DeriveRawSecretAgreement(SafeX25519KeyHandle key, Saf } } + internal static void X25519DeriveRawSecretAgreementWithBytes( + SafeX25519KeyHandle key, + ReadOnlySpan peerKey, + Span destination) + { + const int Success = 1; + const int KeyDerivationFailed = 0; + int ret = AppleCryptoNative_X25519DeriveRawSecretAgreementWithBytes( + key, + peerKey, + peerKey.Length, + destination, + destination.Length); + + switch (ret) + { + case Success: + return; + case KeyDerivationFailed: + throw new CryptographicException(); + default: + Debug.Fail($"Unexpected result from {nameof(AppleCryptoNative_X25519DeriveRawSecretAgreementWithBytes)}: {ret}."); + throw new CryptographicException(); + } + } + internal static void X25519ExportPrivateKey(SafeX25519KeyHandle key, Span destination) { const int Success = 1; diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.X25519.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.X25519.cs index 3bf002aa12158e..f02dfd3aa58944 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.X25519.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.X25519.cs @@ -30,6 +30,15 @@ private static partial int X25519ExportPublicKey( [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_X25519GenerateKey")] private static partial SafeEvpPKeyHandle CryptoNative_X25519GenerateKey(); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_X25519DeriveSecretAgreementWithPublicKey")] + private static partial int CryptoNative_X25519DeriveSecretAgreementWithPublicKey( + SafeEvpPKeyHandle key, + IntPtr extraHandle, + ReadOnlySpan peerKey, + int peerKeyLength, + Span secret, + uint secretLength); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_X25519IsValidHandle")] private static partial int CryptoNative_X25519IsValidHandle( SafeEvpPKeyHandle key, @@ -127,6 +136,32 @@ internal static SafeEvpPKeyHandle X25519ImportPrivateKey(ReadOnlySpan sour return key; } + internal static int X25519DeriveSecretAgreementWithPublicKey( + SafeEvpPKeyHandle key, + ReadOnlySpan peerKey, + Span destination) + { + Debug.Assert(key != null); + Debug.Assert(peerKey.Length == X25519DiffieHellman.PublicKeySizeInBytes); + Debug.Assert(destination.Length == X25519DiffieHellman.SecretAgreementSizeInBytes); + + int written = CryptoNative_X25519DeriveSecretAgreementWithPublicKey( + key, + GetExtraHandle(key), + peerKey, + peerKey.Length, + destination, + (uint)destination.Length); + + if (written <= 0) + { + Debug.Assert(written == 0); + throw CreateOpenSslCryptographicException(); + } + + return written; + } + internal static SafeEvpPKeyHandle X25519ImportPublicKey(ReadOnlySpan source) { SafeEvpPKeyHandle key = X25519ImportPublicKey(source, source.Length); diff --git a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs index ebd3d646bdbc63..cfd549cabf1ec1 100644 --- a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs +++ b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs @@ -3449,8 +3449,11 @@ public abstract partial class X25519DiffieHellman : System.IDisposable protected X25519DiffieHellman() { } public static bool IsSupported { get { throw null; } } public byte[] DeriveRawSecretAgreement(System.Security.Cryptography.X25519DiffieHellman otherParty) { throw null; } + public byte[] DeriveRawSecretAgreement(byte[] otherPartyPublicKey) { throw null; } public void DeriveRawSecretAgreement(System.Security.Cryptography.X25519DiffieHellman otherParty, System.Span destination) { } + public void DeriveRawSecretAgreement(System.ReadOnlySpan otherPartyPublicKey, System.Span destination) { } protected abstract void DeriveRawSecretAgreementCore(System.Security.Cryptography.X25519DiffieHellman otherParty, System.Span destination); + protected abstract void DeriveRawSecretAgreementCore(System.ReadOnlySpan otherPartyPublicKey, System.Span destination); public void Dispose() { } protected virtual void Dispose(bool disposing) { } public byte[] ExportPrivateKey() { throw null; } @@ -3499,6 +3502,7 @@ public sealed partial class X25519DiffieHellmanCng : System.Security.Cryptograph [System.Runtime.Versioning.SupportedOSPlatformAttribute("windows")] public X25519DiffieHellmanCng(System.Security.Cryptography.CngKey key) { } protected override void DeriveRawSecretAgreementCore(System.Security.Cryptography.X25519DiffieHellman otherParty, System.Span destination) { } + protected override void DeriveRawSecretAgreementCore(System.ReadOnlySpan otherPartyPublicKey, System.Span destination) { } protected override void Dispose(bool disposing) { } protected override void ExportPrivateKeyCore(System.Span destination) { } protected override void ExportPublicKeyCore(System.Span destination) { } @@ -3515,6 +3519,7 @@ public sealed partial class X25519DiffieHellmanOpenSsl : System.Security.Cryptog [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] public X25519DiffieHellmanOpenSsl(System.Security.Cryptography.SafeEvpPKeyHandle pkeyHandle) { } protected override void DeriveRawSecretAgreementCore(System.Security.Cryptography.X25519DiffieHellman otherParty, System.Span destination) { } + protected override void DeriveRawSecretAgreementCore(System.ReadOnlySpan otherPartyPublicKey, System.Span destination) { } protected override void Dispose(bool disposing) { } public System.Security.Cryptography.SafeEvpPKeyHandle DuplicateKeyHandle() { throw null; } protected override void ExportPrivateKeyCore(System.Span destination) { } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Cng.NotSupported.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Cng.NotSupported.cs index 0182251fa124ac..7b74e87c4b143a 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Cng.NotSupported.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Cng.NotSupported.cs @@ -505,6 +505,11 @@ protected override unsafe partial void DeriveRawSecretAgreementCore(X25519Diffie throw new PlatformNotSupportedException(SR.PlatformNotSupported_CryptographyCng); } + protected override partial void DeriveRawSecretAgreementCore(ReadOnlySpan otherPartyPublicKey, Span destination) + { + throw new PlatformNotSupportedException(SR.PlatformNotSupported_CryptographyCng); + } + protected override partial void ExportPrivateKeyCore(Span destination) { throw new PlatformNotSupportedException(SR.PlatformNotSupported_CryptographyCng); diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellman.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellman.cs index 03d8e582b777ce..e2034174e9670d 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellman.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellman.cs @@ -82,6 +82,40 @@ public byte[] DeriveRawSecretAgreement(X25519DiffieHellman otherParty) return buffer; } + /// + /// Derives a raw secret agreement with the other party's public key. + /// + /// + /// The other party's public key. + /// + /// + /// The secret agreement. + /// + /// + /// The raw secret agreement value is expected to be used as input into a Key Derivation Function, + /// and not used directly as key material. + /// + /// + /// is . + /// + /// + /// has a length that is not . + /// + /// + /// An error occurred during the secret agreement derivation. + /// + /// The object has already been disposed. + public byte[] DeriveRawSecretAgreement(byte[] otherPartyPublicKey) + { + ArgumentNullException.ThrowIfNull(otherPartyPublicKey); + ThrowIfPublicKeyWrongSize(otherPartyPublicKey, nameof(otherPartyPublicKey)); + ThrowIfDisposed(); + + byte[] buffer = new byte[SecretAgreementSizeInBytes]; + DeriveRawSecretAgreementCore(otherPartyPublicKey, buffer); + return buffer; + } + /// /// Derives a raw secret agreement with the other party's key, writing it into the provided buffer. /// @@ -120,6 +154,43 @@ public void DeriveRawSecretAgreement(X25519DiffieHellman otherParty, Span DeriveRawSecretAgreementCore(otherParty, destination); } + /// + /// Derives a raw secret agreement with the other party's public key, writing it into the provided buffer. + /// + /// + /// The other party's public key. + /// + /// + /// The buffer to receive the secret agreement. + /// + /// + /// The raw secret agreement value is expected to be used as input into a Key Derivation Function, + /// and not used directly as key material. + /// + /// + /// has a length that is not . + /// -or- + /// is the incorrect length to receive the secret agreement. + /// + /// + /// An error occurred during the secret agreement derivation. + /// + /// The object has already been disposed. + public void DeriveRawSecretAgreement(ReadOnlySpan otherPartyPublicKey, Span destination) + { + ThrowIfPublicKeyWrongSize(otherPartyPublicKey, nameof(otherPartyPublicKey)); + + if (destination.Length != SecretAgreementSizeInBytes) + { + throw new ArgumentException( + SR.Format(SR.Argument_DestinationImprecise, SecretAgreementSizeInBytes), + nameof(destination)); + } + + ThrowIfDisposed(); + DeriveRawSecretAgreementCore(otherPartyPublicKey, destination); + } + /// /// Generates a new X25519 Diffie-Hellman key. /// @@ -736,6 +807,21 @@ public string ExportEncryptedPkcs8PrivateKeyPem(string password, PbeParameters p /// protected abstract void DeriveRawSecretAgreementCore(X25519DiffieHellman otherParty, Span destination); + /// + /// When overridden in a derived class, derives a raw secret agreement with the other party's public key, + /// writing it into the provided buffer. + /// + /// + /// The other party's public key. + /// + /// + /// The buffer to receive the secret agreement. + /// + /// + /// An error occurred during the secret agreement derivation. + /// + protected abstract void DeriveRawSecretAgreementCore(ReadOnlySpan otherPartyPublicKey, Span destination); + /// /// When overridden in a derived class, exports the private key into the provided buffer. /// @@ -1485,6 +1571,12 @@ private protected void ThrowIfDisposed() ObjectDisposedException.ThrowIf(_disposed, typeof(X25519DiffieHellman)); } + private static void ThrowIfPublicKeyWrongSize(ReadOnlySpan publicKey, string paramName) + { + if (publicKey.Length != PublicKeySizeInBytes) + throw new ArgumentException(SR.Argument_PublicKeyWrongSizeForAlgorithm, paramName); + } + private protected static void ThrowIfNotSupported() { if (!IsSupported) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanCng.Windows.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanCng.Windows.cs index 912e6866c644c7..398c6a5e92c3e9 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanCng.Windows.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanCng.Windows.cs @@ -41,17 +41,28 @@ protected override unsafe partial void DeriveRawSecretAgreementCore(X25519Diffie { // We intentionally don't special case otherParty being an instance of X25519DiffieHellmanCng and always // export the public key into the current instance's provider. - scoped Span publicKeyBuffer; + Span publicKeyBytes = stackalloc byte[PublicKeySizeInBytes]; + otherParty.ExportPublicKey(publicKeyBytes); + DeriveRawSecretAgreementWithPublicKey(publicKeyBytes, destination); + } + + protected override partial void DeriveRawSecretAgreementCore(ReadOnlySpan otherPartyPublicKey, Span destination) + { + Debug.Assert(otherPartyPublicKey.Length == PublicKeySizeInBytes); + Debug.Assert(destination.Length == SecretAgreementSizeInBytes); + DeriveRawSecretAgreementWithPublicKey(otherPartyPublicKey, destination); + } + + private void DeriveRawSecretAgreementWithPublicKey(ReadOnlySpan otherPartyPublicKey, Span destination) + { + scoped Span reducedPublicKey; unsafe { - publicKeyBuffer = stackalloc byte[PublicKeySizeInBytes * 2]; + reducedPublicKey = stackalloc byte[PublicKeySizeInBytes]; } - Span publicKeyBytes = publicKeyBuffer.Slice(0, PublicKeySizeInBytes); - Span reducedPublicKey = publicKeyBuffer.Slice(PublicKeySizeInBytes, PublicKeySizeInBytes); - otherParty.ExportPublicKey(publicKeyBytes); - X25519WindowsHelpers.ReducePublicKey(publicKeyBytes, reducedPublicKey); + X25519WindowsHelpers.ReducePublicKey(otherPartyPublicKey, reducedPublicKey); // CNG does not permit cross-provider key agreements. Import the public key in to the same provider // as the current key. diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanCng.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanCng.cs index 588a4597c4deb4..2ac746fd3c7f67 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanCng.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanCng.cs @@ -55,6 +55,9 @@ public sealed partial class X25519DiffieHellmanCng : X25519DiffieHellman /// protected override unsafe partial void DeriveRawSecretAgreementCore(X25519DiffieHellman otherParty, Span destination); + /// + protected override partial void DeriveRawSecretAgreementCore(ReadOnlySpan otherPartyPublicKey, Span destination); + /// protected override partial void ExportPublicKeyCore(Span destination); diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Apple.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Apple.cs index c586fc65887ab1..f5c7b758708173 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Apple.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Apple.cs @@ -19,8 +19,9 @@ private X25519DiffieHellmanImplementation(SafeX25519KeyHandle key, bool hasPriva _hasPrivate = hasPrivate; } - protected override unsafe void DeriveRawSecretAgreementCore(X25519DiffieHellman otherParty, Span destination) + protected override void DeriveRawSecretAgreementCore(X25519DiffieHellman otherParty, Span destination) { + Debug.Assert(destination.Length == SecretAgreementSizeInBytes); ThrowIfPrivateNeeded(); if (otherParty is X25519DiffieHellmanImplementation x25519impl) @@ -29,16 +30,23 @@ protected override unsafe void DeriveRawSecretAgreementCore(X25519DiffieHellman } else { - Span publicKeyBuffer = stackalloc byte[PublicKeySizeInBytes]; - otherParty.ExportPublicKey(publicKeyBuffer); - - using (SafeX25519KeyHandle publicKey = Interop.AppleCrypto.X25519ImportPublicKey(publicKeyBuffer)) + unsafe { - Interop.AppleCrypto.X25519DeriveRawSecretAgreement(_key, publicKey, destination); + Span publicKeyBuffer = stackalloc byte[PublicKeySizeInBytes]; + otherParty.ExportPublicKey(publicKeyBuffer); + Interop.AppleCrypto.X25519DeriveRawSecretAgreementWithBytes(_key, publicKeyBuffer, destination); } } } + protected override void DeriveRawSecretAgreementCore(ReadOnlySpan otherPartyPublicKey, Span destination) + { + Debug.Assert(otherPartyPublicKey.Length == PublicKeySizeInBytes); + Debug.Assert(destination.Length == SecretAgreementSizeInBytes); + ThrowIfPrivateNeeded(); + Interop.AppleCrypto.X25519DeriveRawSecretAgreementWithBytes(_key, otherPartyPublicKey, destination); + } + protected override void ExportPrivateKeyCore(Span destination) { ThrowIfPrivateNeeded(); @@ -92,5 +100,6 @@ private void ThrowIfPrivateNeeded() if (!_hasPrivate) throw new CryptographicException(SR.Cryptography_CSP_NoPrivateKey); } + } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.NotSupported.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.NotSupported.cs index f42527a2d5a67d..067db596082deb 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.NotSupported.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.NotSupported.cs @@ -15,6 +15,12 @@ protected override void DeriveRawSecretAgreementCore(X25519DiffieHellman otherPa throw new PlatformNotSupportedException(); } + protected override void DeriveRawSecretAgreementCore(ReadOnlySpan otherPartyPublicKey, Span destination) + { + Debug.Fail("Caller should have checked platform availability."); + throw new PlatformNotSupportedException(); + } + protected override void ExportPrivateKeyCore(Span destination) { Debug.Fail("Caller should have checked platform availability."); diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.OpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.OpenSsl.cs index 0e4156dba97284..0f92d51d80f1d1 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.OpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.OpenSsl.cs @@ -19,7 +19,7 @@ private X25519DiffieHellmanImplementation(SafeEvpPKeyHandle key, bool hasPrivate _hasPrivate = hasPrivate; } - protected override unsafe void DeriveRawSecretAgreementCore(X25519DiffieHellman otherParty, Span destination) + protected override void DeriveRawSecretAgreementCore(X25519DiffieHellman otherParty, Span destination) { Debug.Assert(destination.Length == SecretAgreementSizeInBytes); ThrowIfPrivateNeeded(); @@ -32,12 +32,11 @@ protected override unsafe void DeriveRawSecretAgreementCore(X25519DiffieHellman } else { - Span publicKey = stackalloc byte[PublicKeySizeInBytes]; - otherParty.ExportPublicKey(publicKey); - - using (SafeEvpPKeyHandle peerKeyHandle = Interop.Crypto.X25519ImportPublicKey(publicKey)) + unsafe { - written = Interop.Crypto.EvpPKeyDeriveSecretAgreement(_key, peerKeyHandle, destination); + Span publicKey = stackalloc byte[PublicKeySizeInBytes]; + otherParty.ExportPublicKey(publicKey); + written = Interop.Crypto.X25519DeriveSecretAgreementWithPublicKey(_key, publicKey, destination); } } @@ -48,6 +47,21 @@ protected override unsafe void DeriveRawSecretAgreementCore(X25519DiffieHellman } } + protected override void DeriveRawSecretAgreementCore(ReadOnlySpan otherPartyPublicKey, Span destination) + { + Debug.Assert(otherPartyPublicKey.Length == PublicKeySizeInBytes); + Debug.Assert(destination.Length == SecretAgreementSizeInBytes); + ThrowIfPrivateNeeded(); + + int written = Interop.Crypto.X25519DeriveSecretAgreementWithPublicKey(_key, otherPartyPublicKey, destination); + + if (written != SecretAgreementSizeInBytes) + { + Debug.Fail($"{nameof(Interop.Crypto.X25519DeriveSecretAgreementWithPublicKey)} wrote an unexpected number of bytes: {written}."); + throw new CryptographicException(); + } + } + protected override void ExportPrivateKeyCore(Span destination) { Debug.Assert(destination.Length == PrivateKeySizeInBytes); diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Windows.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Windows.cs index dcaea940398d3d..81683dd95ee0d9 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Windows.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Windows.cs @@ -33,61 +33,75 @@ private X25519DiffieHellmanImplementation(SafeBCryptKeyHandle key, bool hasPriva [MemberNotNullWhen(true, nameof(s_algHandle))] internal static new bool IsSupported => s_algHandle is not null; - protected override unsafe void DeriveRawSecretAgreementCore(X25519DiffieHellman otherParty, Span destination) + protected override void DeriveRawSecretAgreementCore(X25519DiffieHellman otherParty, Span destination) { Debug.Assert(destination.Length == SecretAgreementSizeInBytes); ThrowIfPrivateNeeded(); - int written; if (otherParty is X25519DiffieHellmanImplementation x25519impl) { - using (SafeBCryptSecretHandle secret = Interop.BCrypt.BCryptSecretAgreement(_key, x25519impl._key)) - { - Interop.BCrypt.BCryptDeriveKey( - secret, - BCryptNative.KeyDerivationFunction.Raw, - in Unsafe.NullRef(), - destination, - out written); - } + DeriveRawSecretAgreementWithKey(x25519impl._key, destination); } else { - Span publicKeyBytes = stackalloc byte[PublicKeySizeInBytes]; - otherParty.ExportPublicKey(publicKeyBytes); - - using (X25519DiffieHellmanImplementation otherPartyImplementation = ImportPublicKeyImpl(publicKeyBytes)) - using (SafeBCryptSecretHandle secret = Interop.BCrypt.BCryptSecretAgreement(_key, otherPartyImplementation._key)) + unsafe { - Interop.BCrypt.BCryptDeriveKey( - secret, - BCryptNative.KeyDerivationFunction.Raw, - in Unsafe.NullRef(), - destination, - out written); + Span publicKeyBytes = stackalloc byte[PublicKeySizeInBytes]; + + otherParty.ExportPublicKey(publicKeyBytes); + + using (SafeBCryptKeyHandle otherPartyKey = ImportPublicKey(publicKeyBytes, out _)) + { + DeriveRawSecretAgreementWithKey(otherPartyKey, destination); + } } } + } - if (written != SecretAgreementSizeInBytes) - { - destination.Clear(); - Debug.Fail($"Unexpected number of bytes written: {written}."); - throw new CryptographicException(); - } + protected override void DeriveRawSecretAgreementCore(ReadOnlySpan otherPartyPublicKey, Span destination) + { + Debug.Assert(otherPartyPublicKey.Length == PublicKeySizeInBytes); + Debug.Assert(destination.Length == SecretAgreementSizeInBytes); + ThrowIfPrivateNeeded(); - // CNG with BCRYPT_NO_KEY_VALIDATION permits low-order public keys, which produce - // an all-zero shared secret. Other platforms reject these at - // derive time per RFC 7748 6.1. - // We still need BCRYPT_NO_KEY_VALIDATION though because there are small subgroup keys that work, which do - // not produce all zero shared secrets. - if (CryptographicOperations.FixedTimeEquals(destination, 0)) + using (SafeBCryptKeyHandle otherPartyKey = ImportPublicKey(otherPartyPublicKey, out _)) { - throw new CryptographicException(); + DeriveRawSecretAgreementWithKey(otherPartyKey, destination); } - else + } + + private void DeriveRawSecretAgreementWithKey(SafeBCryptKeyHandle otherPartyKey, Span destination) + { + using (SafeBCryptSecretHandle secret = Interop.BCrypt.BCryptSecretAgreement(_key, otherPartyKey)) { - // BCryptDeriveKey exports with the wrong endianness. - destination.Reverse(); + Interop.BCrypt.BCryptDeriveKey( + secret, + BCryptNative.KeyDerivationFunction.Raw, + in Unsafe.NullRef(), + destination, + out int written); + + if (written != SecretAgreementSizeInBytes) + { + destination.Clear(); + Debug.Fail($"Unexpected number of bytes written: {written}."); + throw new CryptographicException(); + } + + // CNG with BCRYPT_NO_KEY_VALIDATION permits low-order public keys, which produce + // an all-zero shared secret. Other platforms reject these at + // derive time per RFC 7748 6.1. + // We still need BCRYPT_NO_KEY_VALIDATION though because there are small subgroup keys that work, which do + // not produce all zero shared secrets. + if (CryptographicOperations.FixedTimeEquals(destination, 0)) + { + throw new CryptographicException(); + } + else + { + // BCryptDeriveKey exports with the wrong endianness. + destination.Reverse(); + } } } @@ -150,12 +164,9 @@ internal static X25519DiffieHellmanImplementation ImportPrivateKeyImpl(ReadOnlyS return new X25519DiffieHellmanImplementation(key, hasPrivate: true, privatePreservation: preservation); } - internal static unsafe X25519DiffieHellmanImplementation ImportPublicKeyImpl(ReadOnlySpan source) + internal static X25519DiffieHellmanImplementation ImportPublicKeyImpl(ReadOnlySpan source) { - Span reducedPublicKey = stackalloc byte[PublicKeySizeInBytes]; - bool requiredReduction = X25519WindowsHelpers.ReducePublicKey(source, reducedPublicKey); - - SafeBCryptKeyHandle key = ImportKey(false, reducedPublicKey, out _); + SafeBCryptKeyHandle key = ImportPublicKey(source, out bool requiredReduction); Debug.Assert(!key.IsInvalid); return new X25519DiffieHellmanImplementation( @@ -165,6 +176,20 @@ internal static unsafe X25519DiffieHellmanImplementation ImportPublicKeyImpl(Rea requiredReduction ? source.ToArray() : null); } + private static SafeBCryptKeyHandle ImportPublicKey(ReadOnlySpan source, out bool requiredReduction) + { + scoped Span reducedPublicKey; + + unsafe + { + reducedPublicKey = stackalloc byte[PublicKeySizeInBytes]; + } + + requiredReduction = X25519WindowsHelpers.ReducePublicKey(source, reducedPublicKey); + + return ImportKey(false, reducedPublicKey, out _); + } + private void ExportKey(bool privateKey, Span destination) { string blobType = privateKey ? diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanOpenSsl.NotSupported.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanOpenSsl.NotSupported.cs index 7099fb5f9d7050..728f88b4468e09 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanOpenSsl.NotSupported.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanOpenSsl.NotSupported.cs @@ -25,6 +25,12 @@ protected override void DeriveRawSecretAgreementCore(X25519DiffieHellman otherPa throw new PlatformNotSupportedException(); } + protected override void DeriveRawSecretAgreementCore(ReadOnlySpan otherPartyPublicKey, Span destination) + { + Debug.Fail("Caller should have checked platform availability."); + throw new PlatformNotSupportedException(); + } + protected override void ExportPrivateKeyCore(Span destination) { Debug.Fail("Caller should have checked platform availability."); diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanOpenSsl.OpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanOpenSsl.OpenSsl.cs index 2737692dd999a5..b33ac9a55b22ca 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanOpenSsl.OpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanOpenSsl.OpenSsl.cs @@ -54,11 +54,7 @@ protected override unsafe void DeriveRawSecretAgreementCore(X25519DiffieHellman { Span publicKey = stackalloc byte[PublicKeySizeInBytes]; otherParty.ExportPublicKey(publicKey); - - using (SafeEvpPKeyHandle peerKeyHandle = Interop.Crypto.X25519ImportPublicKey(publicKey)) - { - written = Interop.Crypto.EvpPKeyDeriveSecretAgreement(_key, peerKeyHandle, destination); - } + written = Interop.Crypto.X25519DeriveSecretAgreementWithPublicKey(_key, publicKey, destination); } if (written != SecretAgreementSizeInBytes) @@ -68,6 +64,21 @@ protected override unsafe void DeriveRawSecretAgreementCore(X25519DiffieHellman } } + protected override void DeriveRawSecretAgreementCore(ReadOnlySpan otherPartyPublicKey, Span destination) + { + Debug.Assert(otherPartyPublicKey.Length == PublicKeySizeInBytes); + Debug.Assert(destination.Length == SecretAgreementSizeInBytes); + ThrowIfPrivateNeeded(); + + int written = Interop.Crypto.X25519DeriveSecretAgreementWithPublicKey(_key, otherPartyPublicKey, destination); + + if (written != SecretAgreementSizeInBytes) + { + Debug.Fail($"{nameof(Interop.Crypto.X25519DeriveSecretAgreementWithPublicKey)} wrote an unexpected number of bytes: {written}."); + throw new CryptographicException(); + } + } + protected override void ExportPrivateKeyCore(Span destination) { Debug.Assert(destination.Length == PrivateKeySizeInBytes); diff --git a/src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanBaseTests.cs b/src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanBaseTests.cs index d91bb42514237c..66f4be502c7cb5 100644 --- a/src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanBaseTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanBaseTests.cs @@ -100,6 +100,18 @@ public void DeriveRawSecretAgreement_Symmetric() AssertExtensions.SequenceEqual(secret1, secret2); } + [Fact] + public void DeriveRawSecretAgreement_Bytes_Symmetric() + { + using X25519DiffieHellman key1 = GenerateKey(); + using X25519DiffieHellman key2 = GenerateKey(); + + byte[] secret1 = key1.DeriveRawSecretAgreement(key2.ExportPublicKey()); + byte[] secret2 = key2.DeriveRawSecretAgreement(key1.ExportPublicKey()); + + AssertExtensions.SequenceEqual(secret1, secret2); + } + [Fact] public void DeriveRawSecretAgreement_ExactBuffers() { @@ -114,6 +126,20 @@ public void DeriveRawSecretAgreement_ExactBuffers() AssertExtensions.SequenceEqual(secret1, secret2); } + [Fact] + public void DeriveRawSecretAgreement_Bytes_ExactBuffers() + { + using X25519DiffieHellman key1 = GenerateKey(); + using X25519DiffieHellman key2 = GenerateKey(); + + byte[] secret1 = new byte[X25519DiffieHellman.SecretAgreementSizeInBytes]; + byte[] secret2 = new byte[X25519DiffieHellman.SecretAgreementSizeInBytes]; + key1.DeriveRawSecretAgreement(key2.ExportPublicKey(), secret1); + key2.DeriveRawSecretAgreement(key1.ExportPublicKey(), secret2); + + AssertExtensions.SequenceEqual(secret1, secret2); + } + [Fact] public void DeriveRawSecretAgreement_PublicKeyOnly_Throws() { @@ -122,7 +148,40 @@ public void DeriveRawSecretAgreement_PublicKeyOnly_Throws() using X25519DiffieHellman other = GenerateKey(); Assert.Throws(() => publicOnly.DeriveRawSecretAgreement(other)); - Assert.Throws(() => publicOnly.DeriveRawSecretAgreement(other, new byte[X25519DiffieHellman.SecretAgreementSizeInBytes])); + } + + [Fact] + public void DeriveRawSecretAgreement_ExactBuffers_PublicKeyOnly_Throws() + { + using X25519DiffieHellman xdh = GenerateKey(); + using X25519DiffieHellman publicOnly = ImportPublicKey(xdh.ExportPublicKey()); + using X25519DiffieHellman other = GenerateKey(); + + Assert.Throws(() => + publicOnly.DeriveRawSecretAgreement(other, new byte[X25519DiffieHellman.SecretAgreementSizeInBytes])); + } + + [Fact] + public void DeriveRawSecretAgreement_Bytes_PublicKeyOnly_Throws() + { + using X25519DiffieHellman xdh = GenerateKey(); + using X25519DiffieHellman publicOnly = ImportPublicKey(xdh.ExportPublicKey()); + using X25519DiffieHellman other = GenerateKey(); + + Assert.Throws(() => publicOnly.DeriveRawSecretAgreement(other.ExportPublicKey())); + } + + [Fact] + public void DeriveRawSecretAgreement_Bytes_ExactBuffers_PublicKeyOnly_Throws() + { + using X25519DiffieHellman xdh = GenerateKey(); + using X25519DiffieHellman publicOnly = ImportPublicKey(xdh.ExportPublicKey()); + using X25519DiffieHellman other = GenerateKey(); + + Assert.Throws(() => + publicOnly.DeriveRawSecretAgreement( + other.ExportPublicKey(), + new byte[X25519DiffieHellman.SecretAgreementSizeInBytes])); } [Theory] @@ -134,12 +193,41 @@ public void DeriveRawSecretAgreement_Vectors(DeriveSecretAgreementVector vector) byte[] secret = key.DeriveRawSecretAgreement(peer); AssertExtensions.SequenceEqual(vector.SharedSecret, secret); + } + + [Theory] + [MemberData(nameof(DeriveSecretAgreementVectorsForCurrentPlatform))] + public void DeriveRawSecretAgreement_ExactBuffers_Vectors(DeriveSecretAgreementVector vector) + { + using X25519DiffieHellman key = ImportPrivateKey(vector.PrivateKey); + using X25519DiffieHellman peer = ImportPublicKey(vector.PeerPublicKey); byte[] secretBuffer = new byte[X25519DiffieHellman.SecretAgreementSizeInBytes]; key.DeriveRawSecretAgreement(peer, secretBuffer); AssertExtensions.SequenceEqual(vector.SharedSecret, secretBuffer); } + [Theory] + [MemberData(nameof(DeriveSecretAgreementVectorsForCurrentPlatform))] + public void DeriveRawSecretAgreement_Bytes_Vectors(DeriveSecretAgreementVector vector) + { + using X25519DiffieHellman key = ImportPrivateKey(vector.PrivateKey); + + byte[] secretWithBytes = key.DeriveRawSecretAgreement(vector.PeerPublicKey); + AssertExtensions.SequenceEqual(vector.SharedSecret, secretWithBytes); + } + + [Theory] + [MemberData(nameof(DeriveSecretAgreementVectorsForCurrentPlatform))] + public void DeriveRawSecretAgreement_Bytes_ExactBuffers_Vectors(DeriveSecretAgreementVector vector) + { + using X25519DiffieHellman key = ImportPrivateKey(vector.PrivateKey); + + byte[] secretWithBytesBuffer = new byte[X25519DiffieHellman.SecretAgreementSizeInBytes]; + key.DeriveRawSecretAgreement(vector.PeerPublicKey, secretWithBytesBuffer); + AssertExtensions.SequenceEqual(vector.SharedSecret, secretWithBytesBuffer); + } + [Theory] [MemberData(nameof(DeriveSecretAgreementVectorsForCurrentPlatform))] public void DeriveRawSecretAgreement_CrossImplementation(DeriveSecretAgreementVector vector) @@ -167,10 +255,50 @@ public void DeriveRawSecretAgreement_ZeroSharedSecret_Throws() using X25519DiffieHellman peer = ImportPublicKey(peerPublicKey); Assert.ThrowsAny(() => key.DeriveRawSecretAgreement(peer)); + } + + [ConditionalFact(nameof(IsNotStrictKeyValidatingPlatform))] + 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"); + + using X25519DiffieHellman key = ImportPrivateKey(privateKey); + using X25519DiffieHellman peer = ImportPublicKey(peerPublicKey); + Assert.ThrowsAny( () => key.DeriveRawSecretAgreement(peer, new byte[X25519DiffieHellman.SecretAgreementSizeInBytes])); } + [ConditionalFact(nameof(IsNotStrictKeyValidatingPlatform))] + 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"); + + using X25519DiffieHellman key = ImportPrivateKey(privateKey); + + Assert.ThrowsAny(() => key.DeriveRawSecretAgreement(peerPublicKey)); + } + + [ConditionalFact(nameof(IsNotStrictKeyValidatingPlatform))] + 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"); + + using X25519DiffieHellman key = ImportPrivateKey(privateKey); + + Assert.ThrowsAny( + () => key.DeriveRawSecretAgreement(peerPublicKey, new byte[X25519DiffieHellman.SecretAgreementSizeInBytes])); + } + [Theory] [InlineData(true)] [InlineData(false)] @@ -526,6 +654,11 @@ protected override void DeriveRawSecretAgreementCore(X25519DiffieHellman otherPa _inner.DeriveRawSecretAgreement(otherParty, destination); } + protected override void DeriveRawSecretAgreementCore(ReadOnlySpan otherPartyPublicKey, Span destination) + { + _inner.DeriveRawSecretAgreement(otherPartyPublicKey, destination); + } + protected override void ExportPrivateKeyCore(Span destination) { _inner.ExportPrivateKey(destination); diff --git a/src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanContractTests.cs b/src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanContractTests.cs index 751ab25a9d1cb2..74a20a5f780021 100644 --- a/src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanContractTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanContractTests.cs @@ -58,12 +58,32 @@ public static void DeriveRawSecretAgreement_Allocated_NullOtherParty() xdh.DeriveRawSecretAgreement((X25519DiffieHellman)null)); } + [Fact] + public static void DeriveRawSecretAgreement_Allocated_NullOtherPartyPublicKey() + { + using X25519DiffieHellmanContract xdh = new(); + AssertExtensions.Throws("otherPartyPublicKey", () => + xdh.DeriveRawSecretAgreement((byte[])null)); + } + [Fact] public static void DeriveRawSecretAgreement_Exact_NullOtherParty() { using X25519DiffieHellmanContract xdh = new(); AssertExtensions.Throws("otherParty", () => - xdh.DeriveRawSecretAgreement(null, new byte[X25519DiffieHellman.SecretAgreementSizeInBytes])); + xdh.DeriveRawSecretAgreement( + (X25519DiffieHellman)null, + new byte[X25519DiffieHellman.SecretAgreementSizeInBytes])); + } + + [Theory] + [InlineData(X25519DiffieHellman.PublicKeySizeInBytes - 1)] + [InlineData(X25519DiffieHellman.PublicKeySizeInBytes + 1)] + public static void DeriveRawSecretAgreement_Allocated_WrongOtherPartyPublicKeyLength(int publicKeyLength) + { + using X25519DiffieHellmanContract xdh = new(); + AssertExtensions.Throws("otherPartyPublicKey", () => + xdh.DeriveRawSecretAgreement(new byte[publicKeyLength])); } [Fact] @@ -79,6 +99,31 @@ public static void DeriveRawSecretAgreement_Exact_WrongDestinationLength() xdh.DeriveRawSecretAgreement(other, new byte[X25519DiffieHellman.SecretAgreementSizeInBytes + 1])); } + [Theory] + [InlineData(X25519DiffieHellman.PublicKeySizeInBytes - 1)] + [InlineData(X25519DiffieHellman.PublicKeySizeInBytes + 1)] + public static void DeriveRawSecretAgreement_Exact_WrongOtherPartyPublicKeyLength(int publicKeyLength) + { + using X25519DiffieHellmanContract xdh = new(); + AssertExtensions.Throws("otherPartyPublicKey", () => + xdh.DeriveRawSecretAgreement( + new byte[publicKeyLength], + new byte[X25519DiffieHellman.SecretAgreementSizeInBytes])); + } + + [Fact] + public static void DeriveRawSecretAgreement_Exact_WrongDestinationLength_WithOtherPartyPublicKey() + { + using X25519DiffieHellmanContract xdh = new(); + byte[] publicKey = new byte[X25519DiffieHellman.PublicKeySizeInBytes]; + + AssertExtensions.Throws("destination", () => + xdh.DeriveRawSecretAgreement(publicKey, new byte[X25519DiffieHellman.SecretAgreementSizeInBytes - 1])); + + AssertExtensions.Throws("destination", () => + xdh.DeriveRawSecretAgreement(publicKey, new byte[X25519DiffieHellman.SecretAgreementSizeInBytes + 1])); + } + [Fact] public static void DeriveRawSecretAgreement_Allocated_Disposed() { @@ -88,6 +133,15 @@ public static void DeriveRawSecretAgreement_Allocated_Disposed() Assert.Throws(() => xdh.DeriveRawSecretAgreement(other)); } + [Fact] + public static void DeriveRawSecretAgreement_Allocated_Disposed_WithOtherPartyPublicKey() + { + X25519DiffieHellmanContract xdh = new(); + xdh.Dispose(); + Assert.Throws(() => + xdh.DeriveRawSecretAgreement(new byte[X25519DiffieHellman.PublicKeySizeInBytes])); + } + [Fact] public static void DeriveRawSecretAgreement_Exact_Disposed() { @@ -98,6 +152,17 @@ public static void DeriveRawSecretAgreement_Exact_Disposed() xdh.DeriveRawSecretAgreement(other, new byte[X25519DiffieHellman.SecretAgreementSizeInBytes])); } + [Fact] + public static void DeriveRawSecretAgreement_Exact_Disposed_WithOtherPartyPublicKey() + { + X25519DiffieHellmanContract xdh = new(); + xdh.Dispose(); + Assert.Throws(() => + xdh.DeriveRawSecretAgreement( + new byte[X25519DiffieHellman.PublicKeySizeInBytes], + new byte[X25519DiffieHellman.SecretAgreementSizeInBytes])); + } + [Fact] public static void DeriveRawSecretAgreement_Allocated_Works() { @@ -116,6 +181,25 @@ public static void DeriveRawSecretAgreement_Allocated_Works() AssertExtensions.FilledWith(0xAA, agreement); } + [Fact] + public static void DeriveRawSecretAgreement_Allocated_Works_WithOtherPartyPublicKey() + { + byte[] publicKey = new byte[X25519DiffieHellman.PublicKeySizeInBytes]; + publicKey.AsSpan().Fill(0x42); + using X25519DiffieHellmanContract xdh = new() + { + OnDeriveRawSecretAgreementCoreWithBytes = (ReadOnlySpan otherPartyPublicKey, Span destination) => + { + AssertExtensions.SequenceEqual(publicKey, otherPartyPublicKey); + destination.Fill(0xAA); + } + }; + + byte[] agreement = xdh.DeriveRawSecretAgreement(publicKey); + Assert.Equal(X25519DiffieHellman.SecretAgreementSizeInBytes, agreement.Length); + AssertExtensions.FilledWith(0xAA, agreement); + } + [Fact] public static void DeriveRawSecretAgreement_Exact_Works() { @@ -133,6 +217,24 @@ public static void DeriveRawSecretAgreement_Exact_Works() xdh.DeriveRawSecretAgreement(other, buffer); } + [Fact] + public static void DeriveRawSecretAgreement_Exact_Works_WithOtherPartyPublicKey() + { + byte[] publicKey = new byte[X25519DiffieHellman.PublicKeySizeInBytes]; + publicKey.AsSpan().Fill(0x42); + byte[] buffer = new byte[X25519DiffieHellman.SecretAgreementSizeInBytes]; + using X25519DiffieHellmanContract xdh = new() + { + OnDeriveRawSecretAgreementCoreWithBytes = (ReadOnlySpan otherPartyPublicKey, Span destination) => + { + AssertExtensions.SequenceEqual(publicKey, otherPartyPublicKey); + AssertExtensions.Same(buffer, destination); + } + }; + + xdh.DeriveRawSecretAgreement(publicKey, buffer); + } + [Fact] public static void ExportPrivateKey_Exact_WrongSize() { @@ -822,12 +924,14 @@ public enum TryExportPkcs8PasswordKind internal sealed class X25519DiffieHellmanContract : X25519DiffieHellman { internal DeriveRawSecretAgreementCoreCallback OnDeriveRawSecretAgreementCore { get; set; } + internal DeriveRawSecretAgreementCoreWithBytesCallback OnDeriveRawSecretAgreementCoreWithBytes { get; set; } internal ExportKeyCoreCallback OnExportPrivateKeyCore { get; set; } internal ExportKeyCoreCallback OnExportPublicKeyCore { get; set; } internal TryExportPkcs8PrivateKeyCoreCallback OnTryExportPkcs8PrivateKeyCore { get; set; } internal Action OnDispose { get; set; } = (bool disposing) => { }; internal int DeriveRawSecretAgreementCoreCount { get; set; } + internal int DeriveRawSecretAgreementCoreWithBytesCount { get; set; } internal int ExportPrivateKeyCoreCount { get; set; } internal int ExportPublicKeyCoreCount { get; set; } internal int TryExportPkcs8PrivateKeyCoreCount { get; set; } @@ -840,6 +944,12 @@ protected override void DeriveRawSecretAgreementCore(X25519DiffieHellman otherPa GetCallback(OnDeriveRawSecretAgreementCore)(otherParty, destination); } + protected override void DeriveRawSecretAgreementCore(ReadOnlySpan otherPartyPublicKey, Span destination) + { + DeriveRawSecretAgreementCoreWithBytesCount++; + GetCallback(OnDeriveRawSecretAgreementCoreWithBytes)(otherPartyPublicKey, destination); + } + protected override void ExportPrivateKeyCore(Span destination) { ExportPrivateKeyCoreCount++; @@ -871,6 +981,10 @@ private void VerifyCalledOnDispose() { Assert.Fail($"Expected call to {nameof(DeriveRawSecretAgreementCore)}."); } + if (OnDeriveRawSecretAgreementCoreWithBytes is not null && DeriveRawSecretAgreementCoreWithBytesCount == 0) + { + Assert.Fail($"Expected call to {nameof(DeriveRawSecretAgreementCore)}."); + } if (OnExportPrivateKeyCore is not null && ExportPrivateKeyCoreCount == 0) { Assert.Fail($"Expected call to {nameof(ExportPrivateKeyCore)}."); @@ -886,6 +1000,7 @@ private void VerifyCalledOnDispose() } internal delegate void DeriveRawSecretAgreementCoreCallback(X25519DiffieHellman otherParty, Span destination); + internal delegate void DeriveRawSecretAgreementCoreWithBytesCallback(ReadOnlySpan otherPartyPublicKey, Span destination); internal delegate void ExportKeyCoreCallback(Span destination); internal delegate bool TryExportPkcs8PrivateKeyCoreCallback(Span destination, out int bytesWritten); diff --git a/src/native/libs/System.Security.Cryptography.Native.Apple/entrypoints.c b/src/native/libs/System.Security.Cryptography.Native.Apple/entrypoints.c index 56e8762cdc4fc3..d1b4305dd0f7a3 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Apple/entrypoints.c +++ b/src/native/libs/System.Security.Cryptography.Native.Apple/entrypoints.c @@ -116,6 +116,7 @@ static const Entry s_cryptoAppleNative[] = DllImportEntry(AppleCryptoNative_StoreEnumerateUserDisallowed) DllImportEntry(AppleCryptoNative_StoreEnumerateMachineDisallowed) DllImportEntry(AppleCryptoNative_X25519DeriveRawSecretAgreement) + DllImportEntry(AppleCryptoNative_X25519DeriveRawSecretAgreementWithBytes) DllImportEntry(AppleCryptoNative_X25519ExportPrivateKey) DllImportEntry(AppleCryptoNative_X25519ExportPublicKey) DllImportEntry(AppleCryptoNative_X25519ImportPrivateKey) diff --git a/src/native/libs/System.Security.Cryptography.Native.Apple/pal_swiftbindings.h b/src/native/libs/System.Security.Cryptography.Native.Apple/pal_swiftbindings.h index 93641c5f9f0baf..392d09ab0004a6 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Apple/pal_swiftbindings.h +++ b/src/native/libs/System.Security.Cryptography.Native.Apple/pal_swiftbindings.h @@ -26,6 +26,7 @@ EXTERN_C void* AppleCryptoNative_DigestReset; EXTERN_C void* AppleCryptoNative_DigestClone; EXTERN_C void* AppleCryptoNative_X25519DeriveRawSecretAgreement; +EXTERN_C void* AppleCryptoNative_X25519DeriveRawSecretAgreementWithBytes; EXTERN_C void* AppleCryptoNative_X25519ExportPrivateKey; EXTERN_C void* AppleCryptoNative_X25519ExportPublicKey; EXTERN_C void* AppleCryptoNative_X25519FreeKey; diff --git a/src/native/libs/System.Security.Cryptography.Native.Apple/pal_swiftbindings.swift b/src/native/libs/System.Security.Cryptography.Native.Apple/pal_swiftbindings.swift index 1e002c4154038d..28ee4fed9d2f85 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Apple/pal_swiftbindings.swift +++ b/src/native/libs/System.Security.Cryptography.Native.Apple/pal_swiftbindings.swift @@ -561,6 +561,28 @@ public func AppleCryptoNative_DigestCurrent(ctx: UnsafeMutableRawPointer?, pOutp // 0: key agreement failed (e.g. peer is a low-order point and the shared // secret would be all-zero; CryptoKit raises an error) // -1: invalid arguments or unexpected error +private func deriveRawSecretAgreement( + key: Curve25519.KeyAgreement.PrivateKey, + peerKey: Curve25519.KeyAgreement.PublicKey, + pOutput: UnsafeMutablePointer, + cbOutput: Int32) -> Int32 { + let destination = UnsafeMutableRawBufferPointer(start: pOutput, count: Int(cbOutput)) + + guard let sharedSecret = try? key.sharedSecretFromKeyAgreement(with: peerKey) else { + return 0 + } + + let copied = sharedSecret.withUnsafeBytes { rawSecret in + return rawSecret.copyBytes(to: destination) == rawSecret.count + } + + if (!copied) { + return -1 + } + + return 1 +} + @_silgen_name("AppleCryptoNative_X25519DeriveRawSecretAgreement") public func AppleCryptoNative_X25519DeriveRawSecretAgreement( keyPtr: UnsafeMutableRawPointer?, @@ -579,21 +601,33 @@ public func AppleCryptoNative_X25519DeriveRawSecretAgreement( } let peerKey = peerBox.value.getPublic() - let destination = UnsafeMutableRawBufferPointer(start: pOutput, count: Int(cbOutput)) + return deriveRawSecretAgreement(key: key, peerKey: peerKey, pOutput: pOutput, cbOutput: cbOutput) +} - guard let sharedSecret = try? key.sharedSecretFromKeyAgreement(with: peerKey) else { - return 0 +@_silgen_name("AppleCryptoNative_X25519DeriveRawSecretAgreementWithBytes") +public func AppleCryptoNative_X25519DeriveRawSecretAgreementWithBytes( + keyPtr: UnsafeMutableRawPointer?, + peerKeyPtr: UnsafeMutableRawPointer?, + cbPeerKey: Int32, + pOutput: UnsafeMutablePointer?, + cbOutput: Int32) -> Int32 { + guard let keyPtr, let peerKeyPtr, let pOutput else { + return -1 } - let copied = sharedSecret.withUnsafeBytes { rawSecret in - return rawSecret.copyBytes(to: destination) == rawSecret.count + let keyBox = Unmanaged.fromOpaque(keyPtr).takeUnretainedValue() + + guard case .privateKey(let key) = keyBox.value else { + return -1 } - if (!copied) { + let source = Data(bytesNoCopy: peerKeyPtr, count: Int(cbPeerKey), deallocator: Data.Deallocator.none) + + guard let peerKey = try? Curve25519.KeyAgreement.PublicKey.init(rawRepresentation: source) else { return -1 } - return 1 + return deriveRawSecretAgreement(key: key, peerKey: peerKey, pOutput: pOutput, cbOutput: cbOutput) } @_silgen_name("AppleCryptoNative_X25519FreeKey") diff --git a/src/native/libs/System.Security.Cryptography.Native/entrypoints.c b/src/native/libs/System.Security.Cryptography.Native/entrypoints.c index 59b0a6207f815b..2bb9c3f3441d58 100644 --- a/src/native/libs/System.Security.Cryptography.Native/entrypoints.c +++ b/src/native/libs/System.Security.Cryptography.Native/entrypoints.c @@ -422,6 +422,7 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_SslWrite) DllImportEntry(CryptoNative_Tls13Supported) DllImportEntry(CryptoNative_X25519Available) + DllImportEntry(CryptoNative_X25519DeriveSecretAgreementWithPublicKey) DllImportEntry(CryptoNative_X25519ExportPrivateKey) DllImportEntry(CryptoNative_X25519ExportPublicKey) DllImportEntry(CryptoNative_X25519GenerateKey) diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_x25519.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_x25519.c index 66ab4e4533e30f..3c36d60167db3a 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_x25519.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_x25519.c @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. #include "pal_evp_pkey.h" +#include "pal_evp_pkey_ecdh.h" #include "pal_evp_pkey_x25519.h" #include "pal_utilities.h" #include "openssl.h" @@ -120,6 +121,30 @@ EVP_PKEY* CryptoNative_X25519ImportPublicKey(const uint8_t* source, int32_t sour Int32ToSizeT(sourceLength)); } +int32_t CryptoNative_X25519DeriveSecretAgreementWithPublicKey(EVP_PKEY* pkey, + void* extraHandle, + const uint8_t* peerKey, + int32_t peerKeyLength, + uint8_t* secret, + uint32_t secretLength) +{ + if (pkey == NULL || peerKey == NULL || peerKeyLength <= 0 || secret == NULL || secretLength == 0) + { + return 0; + } + + EVP_PKEY* peerPKey = CryptoNative_X25519ImportPublicKey(peerKey, peerKeyLength); + + if (peerPKey == NULL) + { + return 0; + } + + int32_t ret = CryptoNative_EvpPKeyDeriveSecretAgreement(pkey, extraHandle, peerPKey, secret, secretLength); + EVP_PKEY_free(peerPKey); + return ret; +} + int32_t CryptoNative_X25519IsValidHandle(const EVP_PKEY* key, int32_t* hasPrivateKey) { assert(key != NULL && hasPrivateKey != NULL); diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_x25519.h b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_x25519.h index 53ec6ee9a3d51d..7ffa94e07f6ad6 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_x25519.h +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_x25519.h @@ -40,6 +40,18 @@ Returns the new EVP_PKEY on success, NULL on failure. */ PALEXPORT EVP_PKEY* CryptoNative_X25519ImportPublicKey(const uint8_t* source, int32_t sourceLength); +/* +Derives an X25519 secret agreement with a raw peer public key. + +Returns the number of bytes written on success, 0 on failure. +*/ +PALEXPORT int32_t CryptoNative_X25519DeriveSecretAgreementWithPublicKey(EVP_PKEY* pkey, + void* extraHandle, + const uint8_t* peerKey, + int32_t peerKeyLength, + uint8_t* secret, + uint32_t secretLength); + /* Generates a new X25519 key pair and returns it as an EVP_PKEY.