From 79928797dd92430c7da90c952c98ede09f062e29 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Fri, 5 Jun 2026 20:05:15 +0000 Subject: [PATCH 01/11] Implement Android X25519 --- .../Interop.X25519.cs | 193 +++++++++++ .../src/System.Security.Cryptography.csproj | 4 +- ...5519DiffieHellmanImplementation.Android.cs | 290 ++++++++++++++++ .../X25519DiffieHellmanImplementationTests.cs | 1 + .../CMakeLists.txt | 1 + .../pal_jni.c | 7 + .../pal_jni.h | 4 + .../pal_x25519.c | 312 ++++++++++++++++++ .../pal_x25519.h | 35 ++ 9 files changed, 846 insertions(+), 1 deletion(-) create mode 100644 src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.X25519.cs create mode 100644 src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs create mode 100644 src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.c create mode 100644 src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.h diff --git a/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.X25519.cs b/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.X25519.cs new file mode 100644 index 00000000000000..7212605410cf76 --- /dev/null +++ b/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.X25519.cs @@ -0,0 +1,193 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; +using System.Security.Cryptography; + +internal static partial class Interop +{ + internal static partial class AndroidCrypto + { + [LibraryImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_X25519IsSupported")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool X25519IsSupported(); + + [LibraryImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_X25519DestroyKey")] + internal static partial void X25519DestroyKey(IntPtr key); + + [LibraryImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_X25519GenerateKey")] + private static partial int X25519GenerateKeyNative( + out SafeX25519PublicKeyHandle publicKey, + out SafeX25519PrivateKeyHandle privateKey); + + internal static void X25519GenerateKey( + out SafeX25519PublicKeyHandle publicKey, + out SafeX25519PrivateKeyHandle privateKey) + { + const int Success = 1; + + int result = X25519GenerateKeyNative(out publicKey, out privateKey); + + if (result != Success) + { + publicKey.Dispose(); + privateKey.Dispose(); + throw new CryptographicException(); + } + } + + [LibraryImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_X25519ExportSubjectPublicKeyInfo")] + private static partial int X25519ExportSubjectPublicKeyInfoNative( + SafeX25519PublicKeyHandle publicKey, + Span buffer, + int bufferLength, + out int bytesWritten); + + internal static bool X25519TryExportSubjectPublicKeyInfo( + SafeX25519PublicKeyHandle publicKey, + Span buffer, + out int bytesWritten) + { + const int Success = 1; + const int InsufficientBuffer = -1; + + int result = X25519ExportSubjectPublicKeyInfoNative( + publicKey, + buffer, + buffer.Length, + out bytesWritten); + + return result switch + { + Success => true, + InsufficientBuffer => false, + _ => throw new CryptographicException(), + }; + } + + [LibraryImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_X25519ExportPkcs8PrivateKey")] + private static partial int X25519ExportPkcs8PrivateKeyNative( + SafeX25519PrivateKeyHandle privateKey, + Span buffer, + int bufferLength, + out int bytesWritten); + + internal static bool X25519TryExportPkcs8PrivateKey( + SafeX25519PrivateKeyHandle privateKey, + Span buffer, + out int bytesWritten) + { + const int Success = 1; + const int InsufficientBuffer = -1; + + int result = X25519ExportPkcs8PrivateKeyNative( + privateKey, + buffer, + buffer.Length, + out bytesWritten); + + return result switch + { + Success => true, + InsufficientBuffer => false, + _ => throw new CryptographicException(), + }; + } + + [LibraryImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_X25519ImportSubjectPublicKeyInfo")] + private static partial SafeX25519PublicKeyHandle X25519ImportSubjectPublicKeyInfoNative( + ReadOnlySpan buffer, + int bufferLength); + + internal static SafeX25519PublicKeyHandle X25519ImportSubjectPublicKeyInfo(ReadOnlySpan spki) + { + SafeX25519PublicKeyHandle handle = X25519ImportSubjectPublicKeyInfoNative(spki, spki.Length); + + if (handle.IsInvalid) + { + handle.Dispose(); + throw new CryptographicException(SR.Cryptography_NotValidPublicOrPrivateKey); + } + + return handle; + } + + [LibraryImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_X25519ImportPkcs8PrivateKey")] + private static partial SafeX25519PrivateKeyHandle X25519ImportPkcs8PrivateKeyNative( + ReadOnlySpan buffer, + int bufferLength); + + internal static SafeX25519PrivateKeyHandle X25519ImportPkcs8PrivateKey(ReadOnlySpan pkcs8) + { + SafeX25519PrivateKeyHandle handle = X25519ImportPkcs8PrivateKeyNative(pkcs8, pkcs8.Length); + + if (handle.IsInvalid) + { + handle.Dispose(); + throw new CryptographicException(SR.Cryptography_NotValidPublicOrPrivateKey); + } + + return handle; + } + + [LibraryImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_X25519DeriveSecret")] + private static partial int X25519DeriveSecretNative( + SafeX25519PrivateKeyHandle privateKey, + SafeX25519PublicKeyHandle publicKey, + Span destination, + int destinationLength); + + internal static void X25519DeriveSecret( + SafeX25519PrivateKeyHandle privateKey, + SafeX25519PublicKeyHandle publicKey, + Span destination) + { + const int Success = 1; + + int result = X25519DeriveSecretNative(privateKey, publicKey, destination, destination.Length); + + if (result != Success) + { + throw new CryptographicException(); + } + } + } +} + +namespace System.Security.Cryptography +{ + internal sealed class SafeX25519PublicKeyHandle : SafeHandle + { + public SafeX25519PublicKeyHandle() + : base(IntPtr.Zero, ownsHandle: true) + { + } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected override bool ReleaseHandle() + { + Interop.AndroidCrypto.X25519DestroyKey(handle); + SetHandle(IntPtr.Zero); + return true; + } + } + + internal sealed class SafeX25519PrivateKeyHandle : SafeHandle + { + public SafeX25519PrivateKeyHandle() + : base(IntPtr.Zero, ownsHandle: true) + { + } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected override bool ReleaseHandle() + { + Interop.AndroidCrypto.X25519DestroyKey(handle); + SetHandle(IntPtr.Zero); + return true; + } + } +} 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 344b9f60f008ed..360895a759854f 100644 --- a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj +++ b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj @@ -1208,6 +1208,8 @@ Link="Common\Interop\Android\System.Security.Cryptography.Native.Android\Interop.Random.cs" /> + - + diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs new file mode 100644 index 00000000000000..5f39450bd668af --- /dev/null +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs @@ -0,0 +1,290 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace System.Security.Cryptography +{ + internal sealed class X25519DiffieHellmanImplementation : X25519DiffieHellman + { + private static readonly Lazy s_basePointHandle = new(static () => + { + ReadOnlySpan basePoint = + [ + 9, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + ]; + + return ImportPublicKeyAsHandle(basePoint); + }); + + private readonly SafeX25519PublicKeyHandle _publicKey; + private readonly SafeX25519PrivateKeyHandle? _privateKey; + + internal static new bool IsSupported { get; } = Interop.AndroidCrypto.X25519IsSupported(); + + private X25519DiffieHellmanImplementation( + SafeX25519PublicKeyHandle publicKey, + SafeX25519PrivateKeyHandle? privateKey) + { + _publicKey = publicKey; + _privateKey = privateKey; + } + + protected override void DeriveRawSecretAgreementCore(X25519DiffieHellman otherParty, Span destination) + { + Debug.Assert(destination.Length == SecretAgreementSizeInBytes); + ThrowIfPrivateNeeded(); + + if (otherParty is X25519DiffieHellmanImplementation otherImpl) + { + DeriveRawSecretAgreementCore(_privateKey, otherImpl._publicKey, destination); + } + else + { + unsafe + { + Span otherPublicKey = stackalloc byte[PublicKeySizeInBytes]; + otherParty.ExportPublicKey(otherPublicKey); + DeriveRawSecretAgreementCore(otherPublicKey, destination); + } + } + } + + protected override void DeriveRawSecretAgreementCore(ReadOnlySpan otherPartyPublicKey, Span destination) + { + Debug.Assert(otherPartyPublicKey.Length == PublicKeySizeInBytes); + Debug.Assert(destination.Length == SecretAgreementSizeInBytes); + ThrowIfPrivateNeeded(); + + using (SafeX25519PublicKeyHandle importedPublicKey = ImportPublicKeyAsHandle(otherPartyPublicKey)) + { + DeriveRawSecretAgreementCore(_privateKey, importedPublicKey, destination); + } + } + + protected override void ExportPrivateKeyCore(Span destination) + { + Debug.Assert(destination.Length == PrivateKeySizeInBytes); + ThrowIfPrivateNeeded(); + + byte[] pkcs8Buffer = new byte[128]; + + if (!Interop.AndroidCrypto.X25519TryExportPkcs8PrivateKey(_privateKey, pkcs8Buffer, out int written)) + { + Debug.Fail($"X25519 PKCS#8 PrivateKeyInfo did not fit in {pkcs8Buffer.Length} bytes."); + throw new CryptographicException(SR.Argument_DestinationTooShort); + } + + try + { + ExtractPrivateKeyFromPkcs8(pkcs8Buffer.AsSpan(0, written)).CopyTo(destination); + } + finally + { + CryptographicOperations.ZeroMemory(pkcs8Buffer); + } + } + + protected override void ExportPublicKeyCore(Span destination) + { + Debug.Assert(destination.Length == PublicKeySizeInBytes); + + byte[] spkiBuffer = new byte[64]; + + if (!Interop.AndroidCrypto.X25519TryExportSubjectPublicKeyInfo(_publicKey, spkiBuffer, out int written)) + { + Debug.Fail($"X25519 SubjectPublicKeyInfo did not fit in {spkiBuffer.Length} bytes."); + throw new CryptographicException(SR.Argument_DestinationTooShort); + } + + ExtractPublicKeyFromSubjectPublicKeyInfo(spkiBuffer.AsSpan(0, written)).CopyTo(destination); + } + + protected override bool TryExportPkcs8PrivateKeyCore(Span destination, out int bytesWritten) + { + ThrowIfPrivateNeeded(); + return TryExportPkcs8PrivateKeyImpl(destination, out bytesWritten); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _publicKey.Dispose(); + _privateKey?.Dispose(); + } + + base.Dispose(disposing); + } + + internal static X25519DiffieHellmanImplementation GenerateKeyImpl() + { + Interop.AndroidCrypto.X25519GenerateKey( + out SafeX25519PublicKeyHandle publicKey, + out SafeX25519PrivateKeyHandle privateKey); + + return new X25519DiffieHellmanImplementation(publicKey, privateKey); + } + + internal static X25519DiffieHellmanImplementation ImportPrivateKeyImpl(ReadOnlySpan source) + { + Debug.Assert(source.Length == PrivateKeySizeInBytes); + + unsafe + { + Span pkcs8 = stackalloc byte[48]; + bool encoded = TryEncodePrivateKey(source, pkcs8, out int written); + Debug.Assert(encoded); + + SafeX25519PrivateKeyHandle privateKey = Interop.AndroidCrypto.X25519ImportPkcs8PrivateKey(pkcs8.Slice(0, written)); + + try + { + Span publicKeyBytes = stackalloc byte[PublicKeySizeInBytes]; + DeriveRawSecretAgreementCore(privateKey, s_basePointHandle.Value, publicKeyBytes); + SafeX25519PublicKeyHandle publicKey = ImportPublicKeyAsHandle(publicKeyBytes); + return new X25519DiffieHellmanImplementation(publicKey, privateKey); + } + catch + { + privateKey.Dispose(); + throw; + } + finally + { + CryptographicOperations.ZeroMemory(pkcs8); + } + } + } + + internal static X25519DiffieHellmanImplementation ImportPublicKeyImpl(ReadOnlySpan source) + { + Debug.Assert(source.Length == PublicKeySizeInBytes); + return new X25519DiffieHellmanImplementation(ImportPublicKeyAsHandle(source), privateKey: null); + } + + [MemberNotNull(nameof(_privateKey))] + private void ThrowIfPrivateNeeded() + { + if (_privateKey is null) + { + throw new CryptographicException(SR.Cryptography_CSP_NoPrivateKey); + } + } + + private static void DeriveRawSecretAgreementCore( + SafeX25519PrivateKeyHandle currentParty, + SafeX25519PublicKeyHandle otherParty, + Span destination) + { + Debug.Assert(destination.Length == SecretAgreementSizeInBytes); + Interop.AndroidCrypto.X25519DeriveSecret(currentParty, otherParty, destination); + } + + private static SafeX25519PublicKeyHandle ImportPublicKeyAsHandle(ReadOnlySpan source) + { + unsafe + { + Span spki = stackalloc byte[44]; + bool encoded = TryEncodePublicKey(source, spki, out int written); + Debug.Assert(encoded); + + return Interop.AndroidCrypto.X25519ImportSubjectPublicKeyInfo(spki.Slice(0, written)); + } + } + + private static ReadOnlySpan ExtractPrivateKeyFromPkcs8(ReadOnlySpan pkcs8) + { + ReadOnlySpan pkcs8Preamble = + [ + 0x30, 0x2e, // SEQUENCE (46 bytes) + 0x02, 0x01, 0x00, // INTEGER 0 + 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, // SEQUENCE { OID 1.3.101.110 } + 0x04, 0x22, // OCTET STRING (34 bytes) + 0x04, 0x20, // OCTET STRING (32 bytes) + ]; + + if (pkcs8.Length != pkcs8Preamble.Length + PrivateKeySizeInBytes || + !pkcs8.StartsWith(pkcs8Preamble)) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + return pkcs8.Slice(pkcs8Preamble.Length); + } + + private static ReadOnlySpan ExtractPublicKeyFromSubjectPublicKeyInfo(ReadOnlySpan spki) + { + ReadOnlySpan spkiPreamble = + [ + 0x30, 0x2a, // SEQUENCE (42 bytes) + 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, // SEQUENCE { OID 1.3.101.110 } + 0x03, 0x21, 0x00, // BIT STRING (33 bytes, 0 unused bits) + ]; + + if (spki.Length != spkiPreamble.Length + PublicKeySizeInBytes || + !spki.StartsWith(spkiPreamble)) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + return spki.Slice(spkiPreamble.Length); + } + + private static bool TryEncodePrivateKey(ReadOnlySpan privateKey, Span destination, out int bytesWritten) + { + Debug.Assert(privateKey.Length == PrivateKeySizeInBytes); + + ReadOnlySpan pkcs8Preamble = + [ + 0x30, 0x2e, // SEQUENCE (46 bytes) + 0x02, 0x01, 0x00, // INTEGER 0 + 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, // SEQUENCE { OID 1.3.101.110 } + 0x04, 0x22, // OCTET STRING (34 bytes) + 0x04, 0x20, // OCTET STRING (32 bytes) + ]; + + int pkcs8Size = pkcs8Preamble.Length + privateKey.Length; + + if (destination.Length < pkcs8Size) + { + bytesWritten = 0; + return false; + } + + pkcs8Preamble.CopyTo(destination); + privateKey.CopyTo(destination.Slice(pkcs8Preamble.Length)); + bytesWritten = pkcs8Size; + return true; + } + + private static bool TryEncodePublicKey(ReadOnlySpan publicKey, Span destination, out int bytesWritten) + { + Debug.Assert(publicKey.Length == PublicKeySizeInBytes); + + ReadOnlySpan spkiPreamble = + [ + 0x30, 0x2a, // SEQUENCE (42 bytes) + 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, // SEQUENCE { OID 1.3.101.110 } + 0x03, 0x21, 0x00, // BIT STRING (33 bytes, 0 unused bits) + ]; + + int spkiSize = spkiPreamble.Length + publicKey.Length; + + if (destination.Length < spkiSize) + { + bytesWritten = 0; + return false; + } + + spkiPreamble.CopyTo(destination); + publicKey.CopyTo(destination.Slice(spkiPreamble.Length)); + bytesWritten = spkiSize; + return true; + } + } +} diff --git a/src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanImplementationTests.cs b/src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanImplementationTests.cs index fa00b438fbc569..b0cac751d4367e 100644 --- a/src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanImplementationTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/X25519DiffieHellmanImplementationTests.cs @@ -25,6 +25,7 @@ public static void IsSupported_AgreesWithPlatform() bool expectedSupported = PlatformDetection.IsWindows10OrLater || PlatformDetection.IsApplePlatform || + OperatingSystem.IsAndroidVersionAtLeast(33) || 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/native/libs/System.Security.Cryptography.Native.Android/CMakeLists.txt b/src/native/libs/System.Security.Cryptography.Native.Android/CMakeLists.txt index 9b30cdeeacd90c..6cf63c72359927 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Android/CMakeLists.txt +++ b/src/native/libs/System.Security.Cryptography.Native.Android/CMakeLists.txt @@ -27,6 +27,7 @@ set(NATIVECRYPTO_SOURCES pal_ssl.c pal_sslstream.c pal_trust_manager.c + pal_x25519.c pal_x509.c pal_x509chain.c pal_x509store.c diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_jni.c b/src/native/libs/System.Security.Cryptography.Native.Android/pal_jni.c index 81f11ab25c2ea7..7f0b4d0e49f620 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Android/pal_jni.c +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_jni.c @@ -155,6 +155,10 @@ jclass g_PrivateKeyEntryClass; jmethodID g_PrivateKeyEntryGetCertificate; jmethodID g_PrivateKeyEntryGetPrivateKey; +// java/security/Key +jclass g_KeyClass; +jmethodID g_KeyGetEncoded; + // java/security/KeyStore$TrustedCertificateEntry jclass g_TrustedCertificateEntryClass; jmethodID g_TrustedCertificateEntryGetTrustedCertificate; @@ -873,6 +877,9 @@ jint AndroidCryptoNative_InitLibraryOnLoad (JavaVM *vm, void *reserved) g_PrivateKeyEntryGetCertificate = GetMethod(env, false, g_PrivateKeyEntryClass, "getCertificate", "()Ljava/security/cert/Certificate;"); g_PrivateKeyEntryGetPrivateKey = GetMethod(env, false, g_PrivateKeyEntryClass, "getPrivateKey", "()Ljava/security/PrivateKey;"); + g_KeyClass = GetClassGRef(env, "java/security/Key"); + g_KeyGetEncoded = GetMethod(env, false, g_KeyClass, "getEncoded", "()[B"); + g_TrustedCertificateEntryClass = GetClassGRef(env, "java/security/KeyStore$TrustedCertificateEntry"); g_TrustedCertificateEntryGetTrustedCertificate = GetMethod(env, false, g_TrustedCertificateEntryClass, "getTrustedCertificate", "()Ljava/security/cert/Certificate;"); diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_jni.h b/src/native/libs/System.Security.Cryptography.Native.Android/pal_jni.h index 9285c123525a27..4b77d0c24367b0 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Android/pal_jni.h +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_jni.h @@ -245,6 +245,10 @@ extern jclass g_PrivateKeyEntryClass; extern jmethodID g_PrivateKeyEntryGetCertificate; extern jmethodID g_PrivateKeyEntryGetPrivateKey; +// java/security/Key +extern jclass g_KeyClass; +extern jmethodID g_KeyGetEncoded; + // java/security/KeyStore$TrustedCertificateEntry extern jclass g_TrustedCertificateEntryClass; extern jmethodID g_TrustedCertificateEntryGetTrustedCertificate; diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.c b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.c new file mode 100644 index 00000000000000..159ae028f2f706 --- /dev/null +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.c @@ -0,0 +1,312 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "pal_x25519.h" +#include "pal_misc.h" + +#include + +static int32_t ExportEncodedKey(jobject key, uint8_t* buffer, int32_t bufferLength, int32_t* bytesWritten); + +int32_t AndroidCryptoNative_X25519IsSupported(void) +{ + JNIEnv* env = GetJNIEnv(); + + jstring algorithmName = make_java_string(env, "X25519"); + jobject keyPairGenerator = (*env)->CallStaticObjectMethod(env, g_keyPairGenClass, g_keyPairGenGetInstanceMethod, algorithmName); + ReleaseLRef(env, algorithmName); + + if (TryClearJNIExceptions(env)) + { + ReleaseLRef(env, keyPairGenerator); + return FAIL; + } + + // Generating a key pair exercises the full provider path, which catches cases where + // getInstance succeeds on a stub provider but actual key generation is not implemented. + jobject keyPair = (*env)->CallObjectMethod(env, keyPairGenerator, g_keyPairGenGenKeyPairMethod); + ReleaseLRef(env, keyPairGenerator); + ReleaseLRef(env, keyPair); + + return TryClearJNIExceptions(env) ? FAIL : SUCCESS; +} + +void AndroidCryptoNative_X25519DestroyKey(jobject key) +{ + if (key) + { + JNIEnv* env = GetJNIEnv(); + + if ((*env)->IsInstanceOf(env, key, g_DestroyableClass)) + { + (*env)->CallVoidMethod(env, key, g_destroy); + (void)TryClearJNIExceptions(env); + } + + ReleaseGRef(env, key); + } +} + +int32_t AndroidCryptoNative_X25519GenerateKey(jobject* publicKey, jobject* privateKey) +{ + abort_if_invalid_pointer_argument(publicKey); + abort_if_invalid_pointer_argument(privateKey); + + *publicKey = NULL; + *privateKey = NULL; + + JNIEnv* env = GetJNIEnv(); + + // Conscrypt's XDH KeyPairGenerator does not support initialize(AlgorithmParameterSpec), + // so use "X25519" directly as the algorithm name instead of "XDH" + NamedParameterSpec. + jstring algorithmName = make_java_string(env, "X25519"); + jobject keyPairGenerator = (*env)->CallStaticObjectMethod(env, g_keyPairGenClass, g_keyPairGenGetInstanceMethod, algorithmName); + ReleaseLRef(env, algorithmName); + + if (CheckJNIExceptions(env)) + { + ReleaseLRef(env, keyPairGenerator); + return FAIL; + } + + jobject keyPair = (*env)->CallObjectMethod(env, keyPairGenerator, g_keyPairGenGenKeyPairMethod); + ReleaseLRef(env, keyPairGenerator); + + if (CheckJNIExceptions(env) || !keyPair) + { + ReleaseLRef(env, keyPair); + return FAIL; + } + + jobject pubKey = (*env)->CallObjectMethod(env, keyPair, g_keyPairGetPublicMethod); + jobject privKey = (*env)->CallObjectMethod(env, keyPair, g_keyPairGetPrivateMethod); + ReleaseLRef(env, keyPair); + + if (CheckJNIExceptions(env) || !pubKey || !privKey) + { + ReleaseLRef(env, pubKey); + ReleaseLRef(env, privKey); + return FAIL; + } + + *publicKey = ToGRef(env, pubKey); + *privateKey = ToGRef(env, privKey); + return SUCCESS; +} + +int32_t AndroidCryptoNative_X25519ExportSubjectPublicKeyInfo( + jobject publicKey, + uint8_t* buffer, + int32_t bufferLength, + int32_t* bytesWritten) +{ + return ExportEncodedKey(publicKey, buffer, bufferLength, bytesWritten); +} + +int32_t AndroidCryptoNative_X25519ExportPkcs8PrivateKey( + jobject privateKey, + uint8_t* buffer, + int32_t bufferLength, + int32_t* bytesWritten) +{ + return ExportEncodedKey(privateKey, buffer, bufferLength, bytesWritten); +} + +jobject AndroidCryptoNative_X25519ImportSubjectPublicKeyInfo(const uint8_t* buffer, int32_t bufferLength) +{ + abort_if_invalid_pointer_argument(buffer); + abort_if_negative_integer_argument(bufferLength); + + JNIEnv* env = GetJNIEnv(); + + jstring algorithmName = make_java_string(env, "X25519"); + jobject keyFactory = (*env)->CallStaticObjectMethod(env, g_KeyFactoryClass, g_KeyFactoryGetInstanceMethod, algorithmName); + ReleaseLRef(env, algorithmName); + + if (CheckJNIExceptions(env)) + { + ReleaseLRef(env, keyFactory); + return NULL; + } + + jbyteArray spkiBytes = make_java_byte_array(env, bufferLength); + (*env)->SetByteArrayRegion(env, spkiBytes, 0, bufferLength, (const jbyte*)buffer); + + jobject keySpec = (*env)->NewObject(env, g_X509EncodedKeySpecClass, g_X509EncodedKeySpecCtor, spkiBytes); + ReleaseLRef(env, spkiBytes); + + if (CheckJNIExceptions(env)) + { + ReleaseLRef(env, keyFactory); + ReleaseLRef(env, keySpec); + return NULL; + } + + jobject publicKey = (*env)->CallObjectMethod(env, keyFactory, g_KeyFactoryGenPublicMethod, keySpec); + ReleaseLRef(env, keyFactory); + ReleaseLRef(env, keySpec); + + if (CheckJNIExceptions(env) || !publicKey) + { + ReleaseLRef(env, publicKey); + return NULL; + } + + return ToGRef(env, publicKey); +} + +jobject AndroidCryptoNative_X25519ImportPkcs8PrivateKey(const uint8_t* buffer, int32_t bufferLength) +{ + abort_if_invalid_pointer_argument(buffer); + abort_if_negative_integer_argument(bufferLength); + + JNIEnv* env = GetJNIEnv(); + + jstring algorithmName = make_java_string(env, "X25519"); + jobject keyFactory = (*env)->CallStaticObjectMethod(env, g_KeyFactoryClass, g_KeyFactoryGetInstanceMethod, algorithmName); + ReleaseLRef(env, algorithmName); + + if (CheckJNIExceptions(env)) + { + ReleaseLRef(env, keyFactory); + return NULL; + } + + jbyteArray pkcs8Bytes = make_java_byte_array(env, bufferLength); + (*env)->SetByteArrayRegion(env, pkcs8Bytes, 0, bufferLength, (const jbyte*)buffer); + + jobject keySpec = (*env)->NewObject(env, g_PKCS8EncodedKeySpec, g_PKCS8EncodedKeySpecCtor, pkcs8Bytes); + + jbyte* pkcs8Elements = (*env)->GetByteArrayElements(env, pkcs8Bytes, NULL); + if (pkcs8Elements != NULL) + { + memset(pkcs8Elements, 0, (size_t)bufferLength); + (*env)->ReleaseByteArrayElements(env, pkcs8Bytes, pkcs8Elements, 0); + } + + ReleaseLRef(env, pkcs8Bytes); + + if (CheckJNIExceptions(env)) + { + ReleaseLRef(env, keyFactory); + ReleaseLRef(env, keySpec); + return NULL; + } + + jobject privateKey = (*env)->CallObjectMethod(env, keyFactory, g_KeyFactoryGenPrivateMethod, keySpec); + ReleaseLRef(env, keyFactory); + ReleaseLRef(env, keySpec); + + if (CheckJNIExceptions(env) || !privateKey) + { + ReleaseLRef(env, privateKey); + return NULL; + } + + return ToGRef(env, privateKey); +} + +int32_t AndroidCryptoNative_X25519DeriveSecret( + jobject privateKey, + jobject publicKey, + uint8_t* destination, + int32_t destinationLength) +{ + abort_if_invalid_pointer_argument(privateKey); + abort_if_invalid_pointer_argument(publicKey); + abort_if_invalid_pointer_argument(destination); + abort_if_negative_integer_argument(destinationLength); + + JNIEnv* env = GetJNIEnv(); + int32_t ret = FAIL; + + jstring algorithmName = make_java_string(env, "XDH"); + jobject keyAgreement = (*env)->CallStaticObjectMethod(env, g_KeyAgreementClass, g_KeyAgreementGetInstance, algorithmName); + ReleaseLRef(env, algorithmName); + + if (CheckJNIExceptions(env)) + { + ReleaseLRef(env, keyAgreement); + return FAIL; + } + + (*env)->CallVoidMethod(env, keyAgreement, g_KeyAgreementInit, privateKey); + + if (CheckJNIExceptions(env)) + { + ReleaseLRef(env, keyAgreement); + return FAIL; + } + + jobject phaseResult = (*env)->CallObjectMethod(env, keyAgreement, g_KeyAgreementDoPhase, publicKey, JNI_TRUE); + ReleaseLRef(env, phaseResult); + + if (CheckJNIExceptions(env)) + { + ReleaseLRef(env, keyAgreement); + return FAIL; + } + + jbyteArray secret = (jbyteArray)(*env)->CallObjectMethod(env, keyAgreement, g_KeyAgreementGenerateSecret); + ReleaseLRef(env, keyAgreement); + + if (CheckJNIExceptions(env) || !secret) + { + ReleaseLRef(env, secret); + return FAIL; + } + + jsize secretLen = (*env)->GetArrayLength(env, secret); + + if (secretLen != destinationLength) + { + ReleaseLRef(env, secret); + return FAIL; + } + + (*env)->GetByteArrayRegion(env, secret, 0, secretLen, (jbyte*)destination); + ReleaseLRef(env, secret); + + ret = CheckJNIExceptions(env) ? FAIL : SUCCESS; + return ret; +} + +static int32_t ExportEncodedKey(jobject key, uint8_t* buffer, int32_t bufferLength, int32_t* bytesWritten) +{ + abort_if_invalid_pointer_argument(key); + abort_if_invalid_pointer_argument(buffer); + abort_if_invalid_pointer_argument(bytesWritten); + abort_if_negative_integer_argument(bufferLength); + + *bytesWritten = 0; + + JNIEnv* env = GetJNIEnv(); + + jbyteArray encoded = (jbyteArray)(*env)->CallObjectMethod(env, key, g_KeyGetEncoded); + + if (CheckJNIExceptions(env) || !encoded) + { + ReleaseLRef(env, encoded); + return FAIL; + } + + jsize encodedLen = (*env)->GetArrayLength(env, encoded); + + if (encodedLen > bufferLength) + { + *bytesWritten = (int32_t)encodedLen; + ReleaseLRef(env, encoded); + return INSUFFICIENT_BUFFER; + } + + (*env)->GetByteArrayRegion(env, encoded, 0, encodedLen, (jbyte*)buffer); + ReleaseLRef(env, encoded); + + if (CheckJNIExceptions(env)) + { + return FAIL; + } + + *bytesWritten = (int32_t)encodedLen; + return SUCCESS; +} diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.h b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.h new file mode 100644 index 00000000000000..ff3506a90c6b38 --- /dev/null +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.h @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma once + +#include "pal_compiler.h" +#include "pal_jni.h" +#include "pal_types.h" + +PALEXPORT int32_t AndroidCryptoNative_X25519IsSupported(void); + +PALEXPORT void AndroidCryptoNative_X25519DestroyKey(jobject key); + +PALEXPORT int32_t AndroidCryptoNative_X25519GenerateKey(jobject* publicKey, jobject* privateKey); + +PALEXPORT int32_t AndroidCryptoNative_X25519ExportSubjectPublicKeyInfo( + jobject publicKey, + uint8_t* buffer, + int32_t bufferLength, + int32_t* bytesWritten); + +PALEXPORT int32_t AndroidCryptoNative_X25519ExportPkcs8PrivateKey( + jobject privateKey, + uint8_t* buffer, + int32_t bufferLength, + int32_t* bytesWritten); + +PALEXPORT jobject AndroidCryptoNative_X25519ImportSubjectPublicKeyInfo(const uint8_t* buffer, int32_t bufferLength); +PALEXPORT jobject AndroidCryptoNative_X25519ImportPkcs8PrivateKey(const uint8_t* buffer, int32_t bufferLength); + +PALEXPORT int32_t AndroidCryptoNative_X25519DeriveSecret( + jobject privateKey, + jobject publicKey, + uint8_t* destination, + int32_t destinationLength); From 599ce17a028db7dff58cbabc760674a2c1189a28 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sat, 6 Jun 2026 10:19:54 -0400 Subject: [PATCH 02/11] Extract SPKI logic out --- .../Cryptography/X25519DiffieHellman.cs | 20 ++++++++++++--- ...5519DiffieHellmanImplementation.Android.cs | 25 +++++++++++-------- 2 files changed, 31 insertions(+), 14 deletions(-) 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 9a32a2191a87dc..f7c927d2ca63f8 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 @@ -42,7 +42,7 @@ public abstract class X25519DiffieHellman : IDisposable public const int PublicKeySizeInBytes = 32; // Pre-encoded SPKI for X25519 is 44 bytes: 12 byte preamble + 32 byte public key. - private const int SpkiSizeInBytes = 12 + PublicKeySizeInBytes; + private protected const int SpkiSizeInBytes = 12 + PublicKeySizeInBytes; /// /// Gets a value that indicates whether the algorithm is supported on the current platform. @@ -1427,7 +1427,12 @@ protected virtual void Dispose(bool disposing) { } - private bool TryExportSubjectPublicKeyInfoCore(Span destination, out int bytesWritten) + private protected static bool TryWriteSubjectPublicKeyInfo( + Span destination, + TState state, + Action> writer, + out int bytesWritten) + where TState : allows ref struct { // Pre-encoded SubjectPublicKeyInfo for X25519 (RFC 8410): ReadOnlySpan spkiPreamble = @@ -1446,11 +1451,20 @@ private bool TryExportSubjectPublicKeyInfoCore(Span destination, out int b } spkiPreamble.CopyTo(destination); - ExportPublicKeyCore(destination.Slice(spkiPreamble.Length, PublicKeySizeInBytes)); + writer(state, destination.Slice(spkiPreamble.Length, PublicKeySizeInBytes)); bytesWritten = SpkiSizeInBytes; return true; } + private bool TryExportSubjectPublicKeyInfoCore(Span destination, out int bytesWritten) + { + return TryWriteSubjectPublicKeyInfo( + destination, + this, + static (self, buffer) => self.ExportPublicKeyCore(buffer), + out bytesWritten); + } + private TResult ExportPkcs8PrivateKeyCallback(Func, TResult> func) { // A PKCS#8 X25519 PrivateKeyInfo has an ASN.1 overhead of 16 bytes, assuming no attributes. diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs index 5f39450bd668af..6de87726049fe4 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs @@ -89,11 +89,11 @@ protected override void ExportPrivateKeyCore(Span destination) } } - protected override void ExportPublicKeyCore(Span destination) + protected override unsafe void ExportPublicKeyCore(Span destination) { Debug.Assert(destination.Length == PublicKeySizeInBytes); - byte[] spkiBuffer = new byte[64]; + Span spkiBuffer = stackalloc byte[SpkiSizeInBytes]; if (!Interop.AndroidCrypto.X25519TryExportSubjectPublicKeyInfo(_publicKey, spkiBuffer, out int written)) { @@ -101,7 +101,7 @@ protected override void ExportPublicKeyCore(Span destination) throw new CryptographicException(SR.Argument_DestinationTooShort); } - ExtractPublicKeyFromSubjectPublicKeyInfo(spkiBuffer.AsSpan(0, written)).CopyTo(destination); + ExtractPublicKeyFromSubjectPublicKeyInfo(spkiBuffer.Slice(0, written)).CopyTo(destination); } protected override bool TryExportPkcs8PrivateKeyCore(Span destination, out int bytesWritten) @@ -185,16 +185,19 @@ private static void DeriveRawSecretAgreementCore( Interop.AndroidCrypto.X25519DeriveSecret(currentParty, otherParty, destination); } - private static SafeX25519PublicKeyHandle ImportPublicKeyAsHandle(ReadOnlySpan source) + private static unsafe SafeX25519PublicKeyHandle ImportPublicKeyAsHandle(ReadOnlySpan source) { - unsafe - { - Span spki = stackalloc byte[44]; - bool encoded = TryEncodePublicKey(source, spki, out int written); - Debug.Assert(encoded); + Span spki = stackalloc byte[SpkiSizeInBytes]; - return Interop.AndroidCrypto.X25519ImportSubjectPublicKeyInfo(spki.Slice(0, written)); - } + bool encoded = TryWriteSubjectPublicKeyInfo( + spki, + source, + static (source, buffer) => source.CopyTo(buffer), + out int written); + + Debug.Assert(encoded); + Debug.Assert(written == SpkiSizeInBytes); + return Interop.AndroidCrypto.X25519ImportSubjectPublicKeyInfo(spki.Slice(0, written)); } private static ReadOnlySpan ExtractPrivateKeyFromPkcs8(ReadOnlySpan pkcs8) From 3f26ad276de43d126d754e9d0c67f13a22a73bf9 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sat, 6 Jun 2026 14:27:08 +0000 Subject: [PATCH 03/11] Make the same change for PKCS#8 --- .../Cryptography/X25519DiffieHellman.cs | 25 ++++++-- ...5519DiffieHellmanImplementation.Android.cs | 62 +++---------------- 2 files changed, 30 insertions(+), 57 deletions(-) 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 f7c927d2ca63f8..52706d6288e56b 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 @@ -41,6 +41,9 @@ public abstract class X25519DiffieHellman : IDisposable /// public const int PublicKeySizeInBytes = 32; + // Pre-encoded PKCS#8 for X25519 is 48 bytes: 16 byte preamble + 32 byte private key. + private protected const int Pkcs8SizeInBytes = 16 + PrivateKeySizeInBytes; + // Pre-encoded SPKI for X25519 is 44 bytes: 12 byte preamble + 32 byte public key. private protected const int SpkiSizeInBytes = 12 + PublicKeySizeInBytes; @@ -1493,6 +1496,20 @@ private TResult ExportPkcs8PrivateKeyCallback(Func, } private protected bool TryExportPkcs8PrivateKeyImpl(Span destination, out int bytesWritten) + { + return TryWritePkcs8PrivateKey( + destination, + this, + static (self, buffer) => self.ExportPrivateKeyCore(buffer), + out bytesWritten); + } + + private protected static bool TryWritePkcs8PrivateKey( + Span destination, + TState state, + Action> writer, + out int bytesWritten) + where TState : allows ref struct { // Pre-encoded PKCS#8 PrivateKeyInfo for X25519 (RFC 8410): ReadOnlySpan pkcs8Preamble = @@ -1504,9 +1521,9 @@ private protected bool TryExportPkcs8PrivateKeyImpl(Span destination, out 0x04, 0x20, // OCTET STRING (32 bytes) ]; - int pkcs8SizeInBytes = pkcs8Preamble.Length + PrivateKeySizeInBytes; + Debug.Assert(pkcs8Preamble.Length + PrivateKeySizeInBytes == Pkcs8SizeInBytes); - if (destination.Length < pkcs8SizeInBytes) + if (destination.Length < Pkcs8SizeInBytes) { bytesWritten = 0; return false; @@ -1517,8 +1534,8 @@ private protected bool TryExportPkcs8PrivateKeyImpl(Span destination, out try { - ExportPrivateKey(privateKeyBuffer); - bytesWritten = pkcs8SizeInBytes; + writer(state, privateKeyBuffer); + bytesWritten = Pkcs8SizeInBytes; return true; } catch diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs index 6de87726049fe4..107d0e828d560e 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs @@ -136,9 +136,16 @@ internal static X25519DiffieHellmanImplementation ImportPrivateKeyImpl(ReadOnlyS unsafe { - Span pkcs8 = stackalloc byte[48]; - bool encoded = TryEncodePrivateKey(source, pkcs8, out int written); + Span pkcs8 = stackalloc byte[Pkcs8SizeInBytes]; + + bool encoded = TryWritePkcs8PrivateKey( + pkcs8, + source, + static (source, buffer) => source.CopyTo(buffer), + out int written); + Debug.Assert(encoded); + Debug.Assert(written == Pkcs8SizeInBytes); SafeX25519PrivateKeyHandle privateKey = Interop.AndroidCrypto.X25519ImportPkcs8PrivateKey(pkcs8.Slice(0, written)); @@ -238,56 +245,5 @@ private static ReadOnlySpan ExtractPublicKeyFromSubjectPublicKeyInfo(ReadO return spki.Slice(spkiPreamble.Length); } - private static bool TryEncodePrivateKey(ReadOnlySpan privateKey, Span destination, out int bytesWritten) - { - Debug.Assert(privateKey.Length == PrivateKeySizeInBytes); - - ReadOnlySpan pkcs8Preamble = - [ - 0x30, 0x2e, // SEQUENCE (46 bytes) - 0x02, 0x01, 0x00, // INTEGER 0 - 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, // SEQUENCE { OID 1.3.101.110 } - 0x04, 0x22, // OCTET STRING (34 bytes) - 0x04, 0x20, // OCTET STRING (32 bytes) - ]; - - int pkcs8Size = pkcs8Preamble.Length + privateKey.Length; - - if (destination.Length < pkcs8Size) - { - bytesWritten = 0; - return false; - } - - pkcs8Preamble.CopyTo(destination); - privateKey.CopyTo(destination.Slice(pkcs8Preamble.Length)); - bytesWritten = pkcs8Size; - return true; - } - - private static bool TryEncodePublicKey(ReadOnlySpan publicKey, Span destination, out int bytesWritten) - { - Debug.Assert(publicKey.Length == PublicKeySizeInBytes); - - ReadOnlySpan spkiPreamble = - [ - 0x30, 0x2a, // SEQUENCE (42 bytes) - 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, // SEQUENCE { OID 1.3.101.110 } - 0x03, 0x21, 0x00, // BIT STRING (33 bytes, 0 unused bits) - ]; - - int spkiSize = spkiPreamble.Length + publicKey.Length; - - if (destination.Length < spkiSize) - { - bytesWritten = 0; - return false; - } - - spkiPreamble.CopyTo(destination); - publicKey.CopyTo(destination.Slice(spkiPreamble.Length)); - bytesWritten = spkiSize; - return true; - } } } From efa27dce6067b7f3b0da66e37ad521018395de63 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sat, 6 Jun 2026 22:00:04 -0400 Subject: [PATCH 04/11] Refactor a bit, descope unsafe --- .../Security/Cryptography/KeyFormatHelper.cs | 12 +- .../Cryptography/X25519DiffieHellman.cs | 2 +- ...5519DiffieHellmanImplementation.Android.cs | 104 +++++++++--------- 3 files changed, 63 insertions(+), 55 deletions(-) diff --git a/src/libraries/Common/src/System/Security/Cryptography/KeyFormatHelper.cs b/src/libraries/Common/src/System/Security/Cryptography/KeyFormatHelper.cs index 109f25afc5096c..31f1a3ffbbc205 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/KeyFormatHelper.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/KeyFormatHelper.cs @@ -53,7 +53,8 @@ internal static void ReadSubjectPublicKeyInfo( internal static ReadOnlySpan ReadSubjectPublicKeyInfo( string[] validOids, ReadOnlySpan source, - out int bytesRead) + out int bytesRead, + bool permitParameters = true) { ValueSubjectPublicKeyInfoAsn spki; int read; @@ -70,7 +71,8 @@ internal static ReadOnlySpan ReadSubjectPublicKeyInfo( throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); } - if (Array.IndexOf(validOids, spki.Algorithm.Algorithm) < 0) + if (Array.IndexOf(validOids, spki.Algorithm.Algorithm) < 0 || + (!permitParameters && spki.Algorithm.HasParameters)) { throw new CryptographicException(SR.Cryptography_NotValidPublicOrPrivateKey); } @@ -130,7 +132,8 @@ internal static void ReadPkcs8( internal static ReadOnlySpan ReadPkcs8( string[] validOids, ReadOnlySpan source, - out int bytesRead) + out int bytesRead, + bool permitParameters = true) { try { @@ -138,7 +141,8 @@ internal static ReadOnlySpan ReadPkcs8( int read = reader.PeekEncodedValue().Length; ValuePrivateKeyInfoAsn.Decode(ref reader, out ValuePrivateKeyInfoAsn privateKeyInfo); - if (Array.IndexOf(validOids, privateKeyInfo.PrivateKeyAlgorithm.Algorithm) < 0) + if (Array.IndexOf(validOids, privateKeyInfo.PrivateKeyAlgorithm.Algorithm) < 0 || + (!permitParameters && privateKeyInfo.PrivateKeyAlgorithm.HasParameters)) { throw new CryptographicException(SR.Cryptography_NotValidPublicOrPrivateKey); } 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 52706d6288e56b..32fa23d7d46df9 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 @@ -22,7 +22,7 @@ namespace System.Security.Cryptography /// public abstract class X25519DiffieHellman : IDisposable { - private static readonly string[] s_knownOids = [Oids.X25519]; + private protected static readonly string[] s_knownOids = [Oids.X25519]; private bool _disposed; diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs index 107d0e828d560e..31eb3e3061d7b2 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs @@ -71,7 +71,16 @@ protected override void ExportPrivateKeyCore(Span destination) Debug.Assert(destination.Length == PrivateKeySizeInBytes); ThrowIfPrivateNeeded(); - byte[] pkcs8Buffer = new byte[128]; + + // PKCS#8 keys are not strictly deterministic in size because they could have attributes as "metadata" + // attached. A minimally encoded PKCS#8 private key is going to be 48 bytes. 512 bytes is 10x more space + // than needed, but anything larger than that we won't attempt to process. + scoped Span pkcs8Buffer; + + unsafe + { + pkcs8Buffer = stackalloc byte[512]; + } if (!Interop.AndroidCrypto.X25519TryExportPkcs8PrivateKey(_privateKey, pkcs8Buffer, out int written)) { @@ -81,27 +90,50 @@ protected override void ExportPrivateKeyCore(Span destination) try { - ExtractPrivateKeyFromPkcs8(pkcs8Buffer.AsSpan(0, written)).CopyTo(destination); + ReadOnlySpan privateKey = KeyFormatHelper.ReadPkcs8( + s_knownOids, + pkcs8Buffer.Slice(0, written), + out int bytesRead, + permitParameters: false); + + Debug.Assert(bytesRead == written); + privateKey.CopyTo(destination); } finally { - CryptographicOperations.ZeroMemory(pkcs8Buffer); + CryptographicOperations.ZeroMemory(pkcs8Buffer.Slice(0, written)); } } - protected override unsafe void ExportPublicKeyCore(Span destination) + protected override void ExportPublicKeyCore(Span destination) { Debug.Assert(destination.Length == PublicKeySizeInBytes); - Span spkiBuffer = stackalloc byte[SpkiSizeInBytes]; - if (!Interop.AndroidCrypto.X25519TryExportSubjectPublicKeyInfo(_publicKey, spkiBuffer, out int written)) + scoped Span spkiBuffer; + + unsafe { - Debug.Fail($"X25519 SubjectPublicKeyInfo did not fit in {spkiBuffer.Length} bytes."); - throw new CryptographicException(SR.Argument_DestinationTooShort); + spkiBuffer = stackalloc byte[SpkiSizeInBytes]; } - ExtractPublicKeyFromSubjectPublicKeyInfo(spkiBuffer.Slice(0, written)).CopyTo(destination); + // A SPKI has no wiggle room - they are DER, we expect no algorithm parameters, etc. Either it is exactly + // the right size, or it's wrong. + if (!Interop.AndroidCrypto.X25519TryExportSubjectPublicKeyInfo(_publicKey, spkiBuffer, out int written) + || written != SpkiSizeInBytes) + { + Debug.Fail($"X25519 SubjectPublicKeyInfo did not fit in {spkiBuffer.Length} bytes or wrote the incorrect amount."); + throw new CryptographicException(); + } + + ReadOnlySpan key = KeyFormatHelper.ReadSubjectPublicKeyInfo( + s_knownOids, + spkiBuffer, + out int read, + permitParameters: false); + + Debug.Assert(read == SpkiSizeInBytes); + key.CopyTo(destination); } protected override bool TryExportPkcs8PrivateKeyCore(Span destination, out int bytesWritten) @@ -151,6 +183,7 @@ internal static X25519DiffieHellmanImplementation ImportPrivateKeyImpl(ReadOnlyS try { + // Android requires reconsistuting the public key from the private key. Span publicKeyBytes = stackalloc byte[PublicKeySizeInBytes]; DeriveRawSecretAgreementCore(privateKey, s_basePointHandle.Value, publicKeyBytes); SafeX25519PublicKeyHandle publicKey = ImportPublicKeyAsHandle(publicKeyBytes); @@ -192,9 +225,14 @@ private static void DeriveRawSecretAgreementCore( Interop.AndroidCrypto.X25519DeriveSecret(currentParty, otherParty, destination); } - private static unsafe SafeX25519PublicKeyHandle ImportPublicKeyAsHandle(ReadOnlySpan source) + private static SafeX25519PublicKeyHandle ImportPublicKeyAsHandle(ReadOnlySpan source) { - Span spki = stackalloc byte[SpkiSizeInBytes]; + scoped Span spki; + + unsafe + { + spki = stackalloc byte[SpkiSizeInBytes]; + } bool encoded = TryWriteSubjectPublicKeyInfo( spki, @@ -202,48 +240,14 @@ private static unsafe SafeX25519PublicKeyHandle ImportPublicKeyAsHandle(ReadOnly static (source, buffer) => source.CopyTo(buffer), out int written); - Debug.Assert(encoded); - Debug.Assert(written == SpkiSizeInBytes); - return Interop.AndroidCrypto.X25519ImportSubjectPublicKeyInfo(spki.Slice(0, written)); - } - - private static ReadOnlySpan ExtractPrivateKeyFromPkcs8(ReadOnlySpan pkcs8) - { - ReadOnlySpan pkcs8Preamble = - [ - 0x30, 0x2e, // SEQUENCE (46 bytes) - 0x02, 0x01, 0x00, // INTEGER 0 - 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, // SEQUENCE { OID 1.3.101.110 } - 0x04, 0x22, // OCTET STRING (34 bytes) - 0x04, 0x20, // OCTET STRING (32 bytes) - ]; - - if (pkcs8.Length != pkcs8Preamble.Length + PrivateKeySizeInBytes || - !pkcs8.StartsWith(pkcs8Preamble)) - { - throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); - } - - return pkcs8.Slice(pkcs8Preamble.Length); - } - - private static ReadOnlySpan ExtractPublicKeyFromSubjectPublicKeyInfo(ReadOnlySpan spki) - { - ReadOnlySpan spkiPreamble = - [ - 0x30, 0x2a, // SEQUENCE (42 bytes) - 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, // SEQUENCE { OID 1.3.101.110 } - 0x03, 0x21, 0x00, // BIT STRING (33 bytes, 0 unused bits) - ]; - - if (spki.Length != spkiPreamble.Length + PublicKeySizeInBytes || - !spki.StartsWith(spkiPreamble)) + // SPKI encoding is either right or wrong, there isn't "optional" things that can be written down. So it + // should be precisely sized. + if (!encoded || written != SpkiSizeInBytes) { - throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + throw new CryptographicException(); } - return spki.Slice(spkiPreamble.Length); + return Interop.AndroidCrypto.X25519ImportSubjectPublicKeyInfo(spki); } - } } From b72dbd1856096cb3bbb9b0f7c4a97e8250956a5e Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sun, 7 Jun 2026 02:51:44 +0000 Subject: [PATCH 05/11] Use correct algorithm name --- .../pal_x25519.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.c b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.c index 159ae028f2f706..05d3940502b4f7 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.c +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.c @@ -12,7 +12,7 @@ int32_t AndroidCryptoNative_X25519IsSupported(void) { JNIEnv* env = GetJNIEnv(); - jstring algorithmName = make_java_string(env, "X25519"); + jstring algorithmName = make_java_string(env, "XDH"); jobject keyPairGenerator = (*env)->CallStaticObjectMethod(env, g_keyPairGenClass, g_keyPairGenGetInstanceMethod, algorithmName); ReleaseLRef(env, algorithmName); @@ -57,9 +57,7 @@ int32_t AndroidCryptoNative_X25519GenerateKey(jobject* publicKey, jobject* priva JNIEnv* env = GetJNIEnv(); - // Conscrypt's XDH KeyPairGenerator does not support initialize(AlgorithmParameterSpec), - // so use "X25519" directly as the algorithm name instead of "XDH" + NamedParameterSpec. - jstring algorithmName = make_java_string(env, "X25519"); + jstring algorithmName = make_java_string(env, "XDH"); jobject keyPairGenerator = (*env)->CallStaticObjectMethod(env, g_keyPairGenClass, g_keyPairGenGetInstanceMethod, algorithmName); ReleaseLRef(env, algorithmName); @@ -119,7 +117,7 @@ jobject AndroidCryptoNative_X25519ImportSubjectPublicKeyInfo(const uint8_t* buff JNIEnv* env = GetJNIEnv(); - jstring algorithmName = make_java_string(env, "X25519"); + jstring algorithmName = make_java_string(env, "XDH"); jobject keyFactory = (*env)->CallStaticObjectMethod(env, g_KeyFactoryClass, g_KeyFactoryGetInstanceMethod, algorithmName); ReleaseLRef(env, algorithmName); @@ -162,7 +160,7 @@ jobject AndroidCryptoNative_X25519ImportPkcs8PrivateKey(const uint8_t* buffer, i JNIEnv* env = GetJNIEnv(); - jstring algorithmName = make_java_string(env, "X25519"); + jstring algorithmName = make_java_string(env, "XDH"); jobject keyFactory = (*env)->CallStaticObjectMethod(env, g_KeyFactoryClass, g_KeyFactoryGetInstanceMethod, algorithmName); ReleaseLRef(env, algorithmName); From 97346da19ec1b71d2e0580ea8a3f9444dfc4afd2 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sun, 7 Jun 2026 15:46:22 +0000 Subject: [PATCH 06/11] Fix private key decoding. --- ...5519DiffieHellmanImplementation.Android.cs | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs index 31eb3e3061d7b2..df242f4b6152ce 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Formats.Asn1; namespace System.Security.Cryptography { @@ -90,14 +91,49 @@ protected override void ExportPrivateKeyCore(Span destination) try { - ReadOnlySpan privateKey = KeyFormatHelper.ReadPkcs8( + ReadOnlySpan privateKeyContents = KeyFormatHelper.ReadPkcs8( s_knownOids, pkcs8Buffer.Slice(0, written), out int bytesRead, permitParameters: false); Debug.Assert(bytesRead == written); - privateKey.CopyTo(destination); + + ValueAsnReader reader = new(privateKeyContents, AsnEncodingRules.BER); + + if (reader.TryReadPrimitiveOctetString(out ReadOnlySpan privateKey)) + { + if (privateKey.Length != PrivateKeySizeInBytes) + { + throw new CryptographicException(SR.Argument_PrivateKeyWrongSizeForAlgorithm); + } + + privateKey.CopyTo(destination); + } + else + { + byte[] privateKey = reader.ReadOctetString(); + + try + { + if (privateKey.Length != PrivateKeySizeInBytes) + { + throw new CryptographicException(SR.Argument_PrivateKeyWrongSizeForAlgorithm); + } + + privateKey.CopyTo(destination); + } + finally + { + CryptographicOperations.ZeroMemory(privateKey); + } + } + + reader.ThrowIfNotEmpty(); + } + catch (AsnContentException e) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); } finally { From e81a6706e4905080db55c4576a2ec8ef91f49c12 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sun, 7 Jun 2026 16:12:52 +0000 Subject: [PATCH 07/11] fix variable name --- .../X25519DiffieHellmanImplementation.Android.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs index df242f4b6152ce..025aef1d0b1fc8 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs @@ -112,20 +112,20 @@ protected override void ExportPrivateKeyCore(Span destination) } else { - byte[] privateKey = reader.ReadOctetString(); + byte[] allocatedPrivateKey = reader.ReadOctetString(); try { - if (privateKey.Length != PrivateKeySizeInBytes) + if (allocatedPrivateKey.Length != PrivateKeySizeInBytes) { throw new CryptographicException(SR.Argument_PrivateKeyWrongSizeForAlgorithm); } - privateKey.CopyTo(destination); + allocatedPrivateKey.CopyTo(destination); } finally { - CryptographicOperations.ZeroMemory(privateKey); + CryptographicOperations.ZeroMemory(allocatedPrivateKey); } } From ba9c15e46095d7024ab39950de6a56aa58729385 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sun, 7 Jun 2026 16:37:20 +0000 Subject: [PATCH 08/11] More robust error handling --- .../pal_x25519.c | 263 +++++++++--------- 1 file changed, 128 insertions(+), 135 deletions(-) diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.c b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.c index 05d3940502b4f7..73c28714b28448 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.c +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.c @@ -11,24 +11,35 @@ static int32_t ExportEncodedKey(jobject key, uint8_t* buffer, int32_t bufferLeng int32_t AndroidCryptoNative_X25519IsSupported(void) { JNIEnv* env = GetJNIEnv(); + int32_t ret = FAIL; + + INIT_LOCALS(loc, algorithmName, keyPairGenerator, keyPair); - jstring algorithmName = make_java_string(env, "XDH"); - jobject keyPairGenerator = (*env)->CallStaticObjectMethod(env, g_keyPairGenClass, g_keyPairGenGetInstanceMethod, algorithmName); - ReleaseLRef(env, algorithmName); + loc[algorithmName] = make_java_string(env, "XDH"); + loc[keyPairGenerator] = (*env)->CallStaticObjectMethod(env, g_keyPairGenClass, g_keyPairGenGetInstanceMethod, loc[algorithmName]); if (TryClearJNIExceptions(env)) { - ReleaseLRef(env, keyPairGenerator); - return FAIL; + goto cleanup; } // Generating a key pair exercises the full provider path, which catches cases where // getInstance succeeds on a stub provider but actual key generation is not implemented. - jobject keyPair = (*env)->CallObjectMethod(env, keyPairGenerator, g_keyPairGenGenKeyPairMethod); - ReleaseLRef(env, keyPairGenerator); - ReleaseLRef(env, keyPair); + loc[keyPair] = (*env)->CallObjectMethod(env, loc[keyPairGenerator], g_keyPairGenGenKeyPairMethod); + + if (TryClearJNIExceptions(env)) + { + goto cleanup; + } - return TryClearJNIExceptions(env) ? FAIL : SUCCESS; + if (loc[keyPair] != NULL) + { + ret = SUCCESS; + } + +cleanup: + RELEASE_LOCALS(loc, env); + return ret; } void AndroidCryptoNative_X25519DestroyKey(jobject key) @@ -56,40 +67,42 @@ int32_t AndroidCryptoNative_X25519GenerateKey(jobject* publicKey, jobject* priva *privateKey = NULL; JNIEnv* env = GetJNIEnv(); + int32_t ret = FAIL; - jstring algorithmName = make_java_string(env, "XDH"); - jobject keyPairGenerator = (*env)->CallStaticObjectMethod(env, g_keyPairGenClass, g_keyPairGenGetInstanceMethod, algorithmName); - ReleaseLRef(env, algorithmName); + INIT_LOCALS(loc, algorithmName, keyPairGenerator, keyPair, pubKey, privKey); - if (CheckJNIExceptions(env)) - { - ReleaseLRef(env, keyPairGenerator); - return FAIL; - } + loc[algorithmName] = make_java_string(env, "XDH"); + loc[keyPairGenerator] = (*env)->CallStaticObjectMethod(env, g_keyPairGenClass, g_keyPairGenGetInstanceMethod, loc[algorithmName]); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); - jobject keyPair = (*env)->CallObjectMethod(env, keyPairGenerator, g_keyPairGenGenKeyPairMethod); - ReleaseLRef(env, keyPairGenerator); + loc[keyPair] = (*env)->CallObjectMethod(env, loc[keyPairGenerator], g_keyPairGenGenKeyPairMethod); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); - if (CheckJNIExceptions(env) || !keyPair) + if (loc[keyPair] == NULL) { - ReleaseLRef(env, keyPair); - return FAIL; + goto cleanup; } - jobject pubKey = (*env)->CallObjectMethod(env, keyPair, g_keyPairGetPublicMethod); - jobject privKey = (*env)->CallObjectMethod(env, keyPair, g_keyPairGetPrivateMethod); - ReleaseLRef(env, keyPair); + loc[pubKey] = (*env)->CallObjectMethod(env, loc[keyPair], g_keyPairGetPublicMethod); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); + + loc[privKey] = (*env)->CallObjectMethod(env, loc[keyPair], g_keyPairGetPrivateMethod); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); - if (CheckJNIExceptions(env) || !pubKey || !privKey) + if (loc[pubKey] == NULL || loc[privKey] == NULL) { - ReleaseLRef(env, pubKey); - ReleaseLRef(env, privKey); - return FAIL; + goto cleanup; } - *publicKey = ToGRef(env, pubKey); - *privateKey = ToGRef(env, privKey); - return SUCCESS; + *publicKey = ToGRef(env, loc[pubKey]); + loc[pubKey] = NULL; + *privateKey = ToGRef(env, loc[privKey]); + loc[privKey] = NULL; + ret = SUCCESS; + +cleanup: + RELEASE_LOCALS(loc, env); + return ret; } int32_t AndroidCryptoNative_X25519ExportSubjectPublicKeyInfo( @@ -116,41 +129,35 @@ jobject AndroidCryptoNative_X25519ImportSubjectPublicKeyInfo(const uint8_t* buff abort_if_negative_integer_argument(bufferLength); JNIEnv* env = GetJNIEnv(); + jobject ret = NULL; - jstring algorithmName = make_java_string(env, "XDH"); - jobject keyFactory = (*env)->CallStaticObjectMethod(env, g_KeyFactoryClass, g_KeyFactoryGetInstanceMethod, algorithmName); - ReleaseLRef(env, algorithmName); + INIT_LOCALS(loc, algorithmName, keyFactory, spkiBytes, keySpec, publicKey); - if (CheckJNIExceptions(env)) - { - ReleaseLRef(env, keyFactory); - return NULL; - } + loc[algorithmName] = make_java_string(env, "XDH"); + loc[keyFactory] = (*env)->CallStaticObjectMethod(env, g_KeyFactoryClass, g_KeyFactoryGetInstanceMethod, loc[algorithmName]); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); - jbyteArray spkiBytes = make_java_byte_array(env, bufferLength); - (*env)->SetByteArrayRegion(env, spkiBytes, 0, bufferLength, (const jbyte*)buffer); + loc[spkiBytes] = make_java_byte_array(env, bufferLength); + (*env)->SetByteArrayRegion(env, loc[spkiBytes], 0, bufferLength, (const jbyte*)buffer); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); - jobject keySpec = (*env)->NewObject(env, g_X509EncodedKeySpecClass, g_X509EncodedKeySpecCtor, spkiBytes); - ReleaseLRef(env, spkiBytes); + loc[keySpec] = (*env)->NewObject(env, g_X509EncodedKeySpecClass, g_X509EncodedKeySpecCtor, loc[spkiBytes]); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); - if (CheckJNIExceptions(env)) - { - ReleaseLRef(env, keyFactory); - ReleaseLRef(env, keySpec); - return NULL; - } - - jobject publicKey = (*env)->CallObjectMethod(env, keyFactory, g_KeyFactoryGenPublicMethod, keySpec); - ReleaseLRef(env, keyFactory); - ReleaseLRef(env, keySpec); + loc[publicKey] = (*env)->CallObjectMethod(env, loc[keyFactory], g_KeyFactoryGenPublicMethod, loc[keySpec]); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); - if (CheckJNIExceptions(env) || !publicKey) + if (loc[publicKey] == NULL) { - ReleaseLRef(env, publicKey); - return NULL; + goto cleanup; } - return ToGRef(env, publicKey); + ret = ToGRef(env, loc[publicKey]); + loc[publicKey] = NULL; + +cleanup: + RELEASE_LOCALS(loc, env); + return ret; } jobject AndroidCryptoNative_X25519ImportPkcs8PrivateKey(const uint8_t* buffer, int32_t bufferLength) @@ -159,49 +166,45 @@ jobject AndroidCryptoNative_X25519ImportPkcs8PrivateKey(const uint8_t* buffer, i abort_if_negative_integer_argument(bufferLength); JNIEnv* env = GetJNIEnv(); + jobject ret = NULL; - jstring algorithmName = make_java_string(env, "XDH"); - jobject keyFactory = (*env)->CallStaticObjectMethod(env, g_KeyFactoryClass, g_KeyFactoryGetInstanceMethod, algorithmName); - ReleaseLRef(env, algorithmName); + INIT_LOCALS(loc, algorithmName, keyFactory, pkcs8Bytes, keySpec, privateKey); - if (CheckJNIExceptions(env)) - { - ReleaseLRef(env, keyFactory); - return NULL; - } + loc[algorithmName] = make_java_string(env, "XDH"); + loc[keyFactory] = (*env)->CallStaticObjectMethod(env, g_KeyFactoryClass, g_KeyFactoryGetInstanceMethod, loc[algorithmName]); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); + + loc[pkcs8Bytes] = make_java_byte_array(env, bufferLength); + (*env)->SetByteArrayRegion(env, loc[pkcs8Bytes], 0, bufferLength, (const jbyte*)buffer); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); - jbyteArray pkcs8Bytes = make_java_byte_array(env, bufferLength); - (*env)->SetByteArrayRegion(env, pkcs8Bytes, 0, bufferLength, (const jbyte*)buffer); + loc[keySpec] = (*env)->NewObject(env, g_PKCS8EncodedKeySpec, g_PKCS8EncodedKeySpecCtor, loc[pkcs8Bytes]); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); - jobject keySpec = (*env)->NewObject(env, g_PKCS8EncodedKeySpec, g_PKCS8EncodedKeySpecCtor, pkcs8Bytes); + jbyte* pkcs8Elements = (*env)->GetByteArrayElements(env, loc[pkcs8Bytes], NULL); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); - jbyte* pkcs8Elements = (*env)->GetByteArrayElements(env, pkcs8Bytes, NULL); if (pkcs8Elements != NULL) { memset(pkcs8Elements, 0, (size_t)bufferLength); - (*env)->ReleaseByteArrayElements(env, pkcs8Bytes, pkcs8Elements, 0); + (*env)->ReleaseByteArrayElements(env, loc[pkcs8Bytes], pkcs8Elements, 0); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); } - ReleaseLRef(env, pkcs8Bytes); + loc[privateKey] = (*env)->CallObjectMethod(env, loc[keyFactory], g_KeyFactoryGenPrivateMethod, loc[keySpec]); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); - if (CheckJNIExceptions(env)) + if (loc[privateKey] == NULL) { - ReleaseLRef(env, keyFactory); - ReleaseLRef(env, keySpec); - return NULL; + goto cleanup; } - jobject privateKey = (*env)->CallObjectMethod(env, keyFactory, g_KeyFactoryGenPrivateMethod, keySpec); - ReleaseLRef(env, keyFactory); - ReleaseLRef(env, keySpec); - - if (CheckJNIExceptions(env) || !privateKey) - { - ReleaseLRef(env, privateKey); - return NULL; - } + ret = ToGRef(env, loc[privateKey]); + loc[privateKey] = NULL; - return ToGRef(env, privateKey); +cleanup: + RELEASE_LOCALS(loc, env); + return ret; } int32_t AndroidCryptoNative_X25519DeriveSecret( @@ -218,54 +221,41 @@ int32_t AndroidCryptoNative_X25519DeriveSecret( JNIEnv* env = GetJNIEnv(); int32_t ret = FAIL; - jstring algorithmName = make_java_string(env, "XDH"); - jobject keyAgreement = (*env)->CallStaticObjectMethod(env, g_KeyAgreementClass, g_KeyAgreementGetInstance, algorithmName); - ReleaseLRef(env, algorithmName); + INIT_LOCALS(loc, algorithmName, keyAgreement, phaseResult, secret); - if (CheckJNIExceptions(env)) - { - ReleaseLRef(env, keyAgreement); - return FAIL; - } + loc[algorithmName] = make_java_string(env, "XDH"); + loc[keyAgreement] = (*env)->CallStaticObjectMethod(env, g_KeyAgreementClass, g_KeyAgreementGetInstance, loc[algorithmName]); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); - (*env)->CallVoidMethod(env, keyAgreement, g_KeyAgreementInit, privateKey); + (*env)->CallVoidMethod(env, loc[keyAgreement], g_KeyAgreementInit, privateKey); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); - if (CheckJNIExceptions(env)) - { - ReleaseLRef(env, keyAgreement); - return FAIL; - } + loc[phaseResult] = (*env)->CallObjectMethod(env, loc[keyAgreement], g_KeyAgreementDoPhase, publicKey, JNI_TRUE); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); - jobject phaseResult = (*env)->CallObjectMethod(env, keyAgreement, g_KeyAgreementDoPhase, publicKey, JNI_TRUE); - ReleaseLRef(env, phaseResult); + loc[secret] = (jbyteArray)(*env)->CallObjectMethod(env, loc[keyAgreement], g_KeyAgreementGenerateSecret); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); - if (CheckJNIExceptions(env)) + if (loc[secret] == NULL) { - ReleaseLRef(env, keyAgreement); - return FAIL; + goto cleanup; } - jbyteArray secret = (jbyteArray)(*env)->CallObjectMethod(env, keyAgreement, g_KeyAgreementGenerateSecret); - ReleaseLRef(env, keyAgreement); - - if (CheckJNIExceptions(env) || !secret) - { - ReleaseLRef(env, secret); - return FAIL; - } - - jsize secretLen = (*env)->GetArrayLength(env, secret); + jsize secretLen = (*env)->GetArrayLength(env, loc[secret]); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); if (secretLen != destinationLength) { - ReleaseLRef(env, secret); - return FAIL; + goto cleanup; } - (*env)->GetByteArrayRegion(env, secret, 0, secretLen, (jbyte*)destination); - ReleaseLRef(env, secret); + (*env)->GetByteArrayRegion(env, loc[secret], 0, secretLen, (jbyte*)destination); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); - ret = CheckJNIExceptions(env) ? FAIL : SUCCESS; + ret = SUCCESS; + +cleanup: + RELEASE_LOCALS(loc, env); return ret; } @@ -279,32 +269,35 @@ static int32_t ExportEncodedKey(jobject key, uint8_t* buffer, int32_t bufferLeng *bytesWritten = 0; JNIEnv* env = GetJNIEnv(); + int32_t ret = FAIL; + + INIT_LOCALS(loc, encoded); - jbyteArray encoded = (jbyteArray)(*env)->CallObjectMethod(env, key, g_KeyGetEncoded); + loc[encoded] = (jbyteArray)(*env)->CallObjectMethod(env, key, g_KeyGetEncoded); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); - if (CheckJNIExceptions(env) || !encoded) + if (loc[encoded] == NULL) { - ReleaseLRef(env, encoded); - return FAIL; + goto cleanup; } - jsize encodedLen = (*env)->GetArrayLength(env, encoded); + jsize encodedLen = (*env)->GetArrayLength(env, loc[encoded]); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); if (encodedLen > bufferLength) { *bytesWritten = (int32_t)encodedLen; - ReleaseLRef(env, encoded); - return INSUFFICIENT_BUFFER; + ret = INSUFFICIENT_BUFFER; + goto cleanup; } - (*env)->GetByteArrayRegion(env, encoded, 0, encodedLen, (jbyte*)buffer); - ReleaseLRef(env, encoded); - - if (CheckJNIExceptions(env)) - { - return FAIL; - } + (*env)->GetByteArrayRegion(env, loc[encoded], 0, encodedLen, (jbyte*)buffer); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); *bytesWritten = (int32_t)encodedLen; - return SUCCESS; + ret = SUCCESS; + +cleanup: + RELEASE_LOCALS(loc, env); + return ret; } From 020b966982d70a1a995248c561686eede198977b Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sun, 7 Jun 2026 16:57:16 -0400 Subject: [PATCH 09/11] Better comment --- .../X25519DiffieHellmanImplementation.Android.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs index 025aef1d0b1fc8..c0158323ac0c74 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs @@ -219,7 +219,12 @@ internal static X25519DiffieHellmanImplementation ImportPrivateKeyImpl(ReadOnlyS try { - // Android requires reconsistuting the public key from the private key. + // Android's native implementation gives us two handles, one for the private key and one for the + // public key when generating a key pair. When importing a private key, it only gives us a handle + // representing the private key back. This makes it difficult to export the public key out of a private + // key handle since the export on the handle doesn't specify whether you want the public or private key. + // To recover the public key from the private key, we do X25519(9, key). This is how the public key + // is computed per RFC7748, section 6.1. Span publicKeyBytes = stackalloc byte[PublicKeySizeInBytes]; DeriveRawSecretAgreementCore(privateKey, s_basePointHandle.Value, publicKeyBytes); SafeX25519PublicKeyHandle publicKey = ImportPublicKeyAsHandle(publicKeyBytes); From 3c6386b5dfb5ca7341d9e5a25a45545e93c0afa3 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Mon, 8 Jun 2026 01:44:07 +0000 Subject: [PATCH 10/11] Implement proper one-shot key agreement with public key bytes --- .../Interop.X25519.cs | 28 ++++++ ...5519DiffieHellmanImplementation.Android.cs | 19 +++- .../pal_x25519.c | 95 +++++++++++++------ .../pal_x25519.h | 7 ++ 4 files changed, 120 insertions(+), 29 deletions(-) diff --git a/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.X25519.cs b/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.X25519.cs index 7212605410cf76..ae315aef7cf8ec 100644 --- a/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.X25519.cs +++ b/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.X25519.cs @@ -152,6 +152,34 @@ internal static void X25519DeriveSecret( throw new CryptographicException(); } } + + [LibraryImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_X25519DeriveSecretWithSubjectPublicKeyInfo")] + private static partial int X25519DeriveSecretWithSubjectPublicKeyInfoNative( + SafeX25519PrivateKeyHandle privateKey, + ReadOnlySpan subjectPublicKeyInfo, + int subjectPublicKeyInfoLength, + Span destination, + int destinationLength); + + internal static void X25519DeriveSecretWithSubjectPublicKeyInfo( + SafeX25519PrivateKeyHandle privateKey, + ReadOnlySpan subjectPublicKeyInfo, + Span destination) + { + const int Success = 1; + + int result = X25519DeriveSecretWithSubjectPublicKeyInfoNative( + privateKey, + subjectPublicKeyInfo, + subjectPublicKeyInfo.Length, + destination, + destination.Length); + + if (result != Success) + { + throw new CryptographicException(); + } + } } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs index c0158323ac0c74..5194e37080be0d 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs @@ -61,9 +61,24 @@ protected override void DeriveRawSecretAgreementCore(ReadOnlySpan otherPar Debug.Assert(destination.Length == SecretAgreementSizeInBytes); ThrowIfPrivateNeeded(); - using (SafeX25519PublicKeyHandle importedPublicKey = ImportPublicKeyAsHandle(otherPartyPublicKey)) + unsafe { - DeriveRawSecretAgreementCore(_privateKey, importedPublicKey, destination); + Span spki = stackalloc byte[SpkiSizeInBytes]; + + bool encoded = TryWriteSubjectPublicKeyInfo( + spki, + otherPartyPublicKey, + static (source, buffer) => source.CopyTo(buffer), + out int written); + + // SPKI encoding is either right or wrong, there isn't "optional" things that can be written down. So it + // should be precisely sized. + if (!encoded || written != SpkiSizeInBytes) + { + throw new CryptographicException(); + } + + Interop.AndroidCrypto.X25519DeriveSecretWithSubjectPublicKeyInfo(_privateKey, spki, destination); } } diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.c b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.c index 73c28714b28448..1112a53bfef161 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.c +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.c @@ -6,6 +6,7 @@ #include +static jobject ImportSubjectPublicKeyInfo(JNIEnv* env, const uint8_t* buffer, int32_t bufferLength); static int32_t ExportEncodedKey(jobject key, uint8_t* buffer, int32_t bufferLength, int32_t* bytesWritten); int32_t AndroidCryptoNative_X25519IsSupported(void) @@ -129,34 +130,9 @@ jobject AndroidCryptoNative_X25519ImportSubjectPublicKeyInfo(const uint8_t* buff abort_if_negative_integer_argument(bufferLength); JNIEnv* env = GetJNIEnv(); - jobject ret = NULL; - - INIT_LOCALS(loc, algorithmName, keyFactory, spkiBytes, keySpec, publicKey); - - loc[algorithmName] = make_java_string(env, "XDH"); - loc[keyFactory] = (*env)->CallStaticObjectMethod(env, g_KeyFactoryClass, g_KeyFactoryGetInstanceMethod, loc[algorithmName]); - ON_EXCEPTION_PRINT_AND_GOTO(cleanup); - - loc[spkiBytes] = make_java_byte_array(env, bufferLength); - (*env)->SetByteArrayRegion(env, loc[spkiBytes], 0, bufferLength, (const jbyte*)buffer); - ON_EXCEPTION_PRINT_AND_GOTO(cleanup); - - loc[keySpec] = (*env)->NewObject(env, g_X509EncodedKeySpecClass, g_X509EncodedKeySpecCtor, loc[spkiBytes]); - ON_EXCEPTION_PRINT_AND_GOTO(cleanup); - - loc[publicKey] = (*env)->CallObjectMethod(env, loc[keyFactory], g_KeyFactoryGenPublicMethod, loc[keySpec]); - ON_EXCEPTION_PRINT_AND_GOTO(cleanup); - - if (loc[publicKey] == NULL) - { - goto cleanup; - } - - ret = ToGRef(env, loc[publicKey]); - loc[publicKey] = NULL; + jobject publicKey = ImportSubjectPublicKeyInfo(env, buffer, bufferLength); + jobject ret = ToGRef(env, publicKey); -cleanup: - RELEASE_LOCALS(loc, env); return ret; } @@ -259,6 +235,38 @@ int32_t AndroidCryptoNative_X25519DeriveSecret( return ret; } +int32_t AndroidCryptoNative_X25519DeriveSecretWithSubjectPublicKeyInfo( + jobject privateKey, + const uint8_t* buffer, + int32_t bufferLength, + uint8_t* destination, + int32_t destinationLength) +{ + abort_if_invalid_pointer_argument(privateKey); + abort_if_invalid_pointer_argument(buffer); + abort_if_invalid_pointer_argument(destination); + abort_if_negative_integer_argument(bufferLength); + abort_if_negative_integer_argument(destinationLength); + + JNIEnv* env = GetJNIEnv(); + int32_t ret = FAIL; + + INIT_LOCALS(loc, publicKey); + + loc[publicKey] = ImportSubjectPublicKeyInfo(env, buffer, bufferLength); + + if (loc[publicKey] == NULL) + { + goto cleanup; + } + + ret = AndroidCryptoNative_X25519DeriveSecret(privateKey, loc[publicKey], destination, destinationLength); + +cleanup: + RELEASE_LOCALS(loc, env); + return ret; +} + static int32_t ExportEncodedKey(jobject key, uint8_t* buffer, int32_t bufferLength, int32_t* bytesWritten) { abort_if_invalid_pointer_argument(key); @@ -301,3 +309,36 @@ static int32_t ExportEncodedKey(jobject key, uint8_t* buffer, int32_t bufferLeng RELEASE_LOCALS(loc, env); return ret; } + +static jobject ImportSubjectPublicKeyInfo(JNIEnv* env, const uint8_t* buffer, int32_t bufferLength) +{ + jobject ret = NULL; + + INIT_LOCALS(loc, algorithmName, keyFactory, spkiBytes, keySpec, publicKey); + + loc[algorithmName] = make_java_string(env, "XDH"); + loc[keyFactory] = (*env)->CallStaticObjectMethod(env, g_KeyFactoryClass, g_KeyFactoryGetInstanceMethod, loc[algorithmName]); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); + + loc[spkiBytes] = make_java_byte_array(env, bufferLength); + (*env)->SetByteArrayRegion(env, loc[spkiBytes], 0, bufferLength, (const jbyte*)buffer); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); + + loc[keySpec] = (*env)->NewObject(env, g_X509EncodedKeySpecClass, g_X509EncodedKeySpecCtor, loc[spkiBytes]); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); + + loc[publicKey] = (*env)->CallObjectMethod(env, loc[keyFactory], g_KeyFactoryGenPublicMethod, loc[keySpec]); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); + + if (loc[publicKey] == NULL) + { + goto cleanup; + } + + ret = loc[publicKey]; + loc[publicKey] = NULL; + +cleanup: + RELEASE_LOCALS(loc, env); + return ret; +} diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.h b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.h index ff3506a90c6b38..8a38c8931eb402 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.h +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.h @@ -33,3 +33,10 @@ PALEXPORT int32_t AndroidCryptoNative_X25519DeriveSecret( jobject publicKey, uint8_t* destination, int32_t destinationLength); + +PALEXPORT int32_t AndroidCryptoNative_X25519DeriveSecretWithSubjectPublicKeyInfo( + jobject privateKey, + const uint8_t* buffer, + int32_t bufferLength, + uint8_t* destination, + int32_t destinationLength); From 52e34e8a9549e8f01ba179d19f50731c5d609b02 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Mon, 8 Jun 2026 20:55:06 +0000 Subject: [PATCH 11/11] Code review feedback, be more defensive when ToGRef fails --- ...5519DiffieHellmanImplementation.Android.cs | 5 ++-- .../pal_x25519.c | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs index 5194e37080be0d..ffa4dbc8dbb514 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X25519DiffieHellmanImplementation.Android.cs @@ -71,7 +71,7 @@ protected override void DeriveRawSecretAgreementCore(ReadOnlySpan otherPar static (source, buffer) => source.CopyTo(buffer), out int written); - // SPKI encoding is either right or wrong, there isn't "optional" things that can be written down. So it + // SPKI encoding is either right or wrong, there aren't "optional" things that can be written down. So it // should be precisely sized. if (!encoded || written != SpkiSizeInBytes) { @@ -87,7 +87,6 @@ protected override void ExportPrivateKeyCore(Span destination) Debug.Assert(destination.Length == PrivateKeySizeInBytes); ThrowIfPrivateNeeded(); - // PKCS#8 keys are not strictly deterministic in size because they could have attributes as "metadata" // attached. A minimally encoded PKCS#8 private key is going to be 48 bytes. 512 bytes is 10x more space // than needed, but anything larger than that we won't attempt to process. @@ -296,7 +295,7 @@ private static SafeX25519PublicKeyHandle ImportPublicKeyAsHandle(ReadOnlySpan source.CopyTo(buffer), out int written); - // SPKI encoding is either right or wrong, there isn't "optional" things that can be written down. So it + // SPKI encoding is either right or wrong, there aren't "optional" things that can be written down. So it // should be precisely sized. if (!encoded || written != SpkiSizeInBytes) { diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.c b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.c index 1112a53bfef161..c04977b2742951 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.c +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x25519.c @@ -97,8 +97,22 @@ int32_t AndroidCryptoNative_X25519GenerateKey(jobject* publicKey, jobject* priva *publicKey = ToGRef(env, loc[pubKey]); loc[pubKey] = NULL; + + if (CheckJNIExceptions(env) || *publicKey == NULL) + { + goto cleanup; + } + *privateKey = ToGRef(env, loc[privKey]); loc[privKey] = NULL; + + if (CheckJNIExceptions(env) || *privateKey == NULL) + { + AndroidCryptoNative_X25519DestroyKey(*publicKey); + *publicKey = NULL; + goto cleanup; + } + ret = SUCCESS; cleanup: @@ -133,6 +147,11 @@ jobject AndroidCryptoNative_X25519ImportSubjectPublicKeyInfo(const uint8_t* buff jobject publicKey = ImportSubjectPublicKeyInfo(env, buffer, bufferLength); jobject ret = ToGRef(env, publicKey); + if (CheckJNIExceptions(env) || ret == NULL) + { + ret = NULL; + } + return ret; } @@ -178,6 +197,13 @@ jobject AndroidCryptoNative_X25519ImportPkcs8PrivateKey(const uint8_t* buffer, i ret = ToGRef(env, loc[privateKey]); loc[privateKey] = NULL; + if (CheckJNIExceptions(env) || ret == NULL) + { + AndroidCryptoNative_X25519DestroyKey(ret); + ret = NULL; + goto cleanup; + } + cleanup: RELEASE_LOCALS(loc, env); return ret;