From 24c687f2ebacb887c608c894ecd480475a76a0c3 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Mon, 10 Jun 2024 21:10:31 +0200 Subject: [PATCH 01/11] Add support for KeyStore.PrivateKeyEntry --- .../Interop.Ssl.cs | 11 +++ .../Interop.X509.cs | 12 +++ .../Pal.Android/SafeDeleteSslContext.cs | 5 ++ .../X509Certificates/AndroidCertificatePal.cs | 30 +++++++- .../android/crypto/DotnetX509KeyManager.java | 73 +++++++++++++++++++ .../pal_jni.c | 12 +++ .../pal_jni.h | 7 ++ .../pal_sslstream.c | 37 ++++++++++ .../pal_sslstream.h | 7 ++ .../pal_x509.c | 25 +++++++ .../pal_x509.h | 10 +++ .../pal_x509store.c | 54 ++++++++++++++ 12 files changed, 281 insertions(+), 2 deletions(-) create mode 100644 src/native/libs/System.Security.Cryptography.Native.Android/net/dot/android/crypto/DotnetX509KeyManager.java diff --git a/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Ssl.cs b/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Ssl.cs index 5f3ee7ac00b1e..d663095cd8cea 100644 --- a/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Ssl.cs +++ b/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Ssl.cs @@ -56,6 +56,17 @@ ref MemoryMarshal.GetReference(pkcs8PrivateKey), certificates.Length); } + [LibraryImport(Interop.Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_SSLStreamCreateWithKeyStorePrivateKeyEntry")] + private static partial SafeSslHandle SSLStreamCreateWithKeyStorePrivateKeyEntry( + IntPtr sslStreamProxyHandle, + IntPtr keyStorePrivateKeyEntryHandle); + internal static SafeSslHandle SSLStreamCreateWithKeyStorePrivateKeyEntry( + SslStream.JavaProxy sslStreamProxy, + IntPtr keyStorePrivateKeyEntryHandle) + { + return SSLStreamCreateWithKeyStorePrivateKeyEntry(sslStreamProxy.Handle, keyStorePrivateKeyEntryHandle); + } + [LibraryImport(Interop.Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_RegisterRemoteCertificateValidationCallback")] internal static unsafe partial void RegisterRemoteCertificateValidationCallback( delegate* unmanaged verifyRemoteCertificate); diff --git a/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.X509.cs b/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.X509.cs index 5d39487b4bf0e..957382d66c877 100644 --- a/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.X509.cs +++ b/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.X509.cs @@ -32,6 +32,18 @@ internal static byte[] X509Encode(SafeX509Handle x) return encoded; } + [LibraryImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_X509IsKeyStorePrivateKeyEntry")] + [return: MarshalAs(UnmanagedType.U1)] + internal static partial bool IsKeyStorePrivateKeyEntry(IntPtr handle); + [LibraryImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_X509GetCertificateForPrivateKeyEntry")] + private static partial IntPtr GetPrivateKeyEntryCertificate(IntPtr privatKeyEntryHandle); + internal static SafeX509Handle GetPrivateKeyEntryCertificate(SafeHandle privatKeyEntryHandle) + { + var certificateHandle = new SafeX509Handle(); + var certificatePtr = GetPrivateKeyEntryCertificate(privatKeyEntryHandle.DangerousGetHandle()); + Marshal.InitHandle(certificateHandle, Interop.JObjectLifetime.NewGlobalReference(certificatePtr)); + return certificateHandle; + } [LibraryImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_X509DecodeCollection")] private static partial int X509DecodeCollection(ref byte buf, int bufLen, IntPtr[]? ptrs, ref int handlesLen); diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/Pal.Android/SafeDeleteSslContext.cs b/src/libraries/System.Net.Security/src/System/Net/Security/Pal.Android/SafeDeleteSslContext.cs index 96302cda3d784..0f141c0812f94 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/Pal.Android/SafeDeleteSslContext.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/Pal.Android/SafeDeleteSslContext.cs @@ -158,6 +158,11 @@ private static SafeSslHandle CreateSslContext(SslStream.JavaProxy sslStreamProxy X509Certificate2 cert = context.TargetCertificate; Debug.Assert(context.TargetCertificate.HasPrivateKey); + if (Interop.AndroidCrypto.IsKeyStorePrivateKeyEntry(cert.Handle)) + { + return Interop.AndroidCrypto.SSLStreamCreateWithKeyStorePrivateKeyEntry(sslStreamProxy, cert.Handle); + } + PAL_KeyAlgorithm algorithm; byte[] keyBytes; using (AsymmetricAlgorithm key = GetPrivateKeyAlgorithm(cert, out algorithm)) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AndroidCertificatePal.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AndroidCertificatePal.cs index 30a6cdbce3c29..3e3d3a4b099d1 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AndroidCertificatePal.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AndroidCertificatePal.cs @@ -17,6 +17,7 @@ internal sealed class AndroidCertificatePal : ICertificatePal { private SafeX509Handle _cert; private SafeKeyHandle? _privateKey; + private Interop.JObjectLifetime.SafeJObjectHandle? _keyStorePrivateKeyEntry; private CertificateData _certData; @@ -25,6 +26,13 @@ public static ICertificatePal FromHandle(IntPtr handle) if (handle == IntPtr.Zero) throw new ArgumentException(SR.Arg_InvalidHandle, nameof(handle)); + if (Interop.AndroidCrypto.IsKeyStorePrivateKeyEntry(handle)) + { + var newPrivateKeyEntryHandle = new Interop.JObjectLifetime.SafeJObjectHandle(); + Marshal.InitHandle(newPrivateKeyEntryHandle, Interop.JObjectLifetime.NewGlobalReference(handle)); + return new AndroidCertificatePal(newPrivateKeyEntryHandle); + } + var newHandle = new SafeX509Handle(); Marshal.InitHandle(newHandle, Interop.JObjectLifetime.NewGlobalReference(handle)); return new AndroidCertificatePal(newHandle); @@ -36,6 +44,13 @@ public static ICertificatePal FromOtherCert(X509Certificate cert) AndroidCertificatePal certPal = (AndroidCertificatePal)cert.Pal; + if (certPal._keyStorePrivateKeyEntry is not null) + { + var jobjectHandle = new Interop.JObjectLifetime.SafeJObjectHandle(); + Marshal.InitHandle(jobjectHandle, Interop.JObjectLifetime.NewGlobalReference(certPal.Handle)); + return new AndroidCertificatePal(jobjectHandle); + } + // Ensure private key is copied if (certPal.PrivateKeyHandle != null) { @@ -134,6 +149,12 @@ private static AndroidCertificatePal ReadPkcs12(ReadOnlySpan rawData, Safe } } + internal AndroidCertificatePal(Interop.JObjectLifetime.SafeJObjectHandle handle) + { + _cert = Interop.AndroidCrypto.GetPrivateKeyEntryCertificate(handle); + _keyStorePrivateKeyEntry = handle; + } + internal AndroidCertificatePal(SafeX509Handle handle) { _cert = handle; @@ -145,9 +166,14 @@ internal AndroidCertificatePal(SafeX509Handle handle, SafeKeyHandle privateKey) _privateKey = privateKey; } - public bool HasPrivateKey => _privateKey != null; + public bool HasPrivateKey => _privateKey is not null || _keyStorePrivateKeyEntry is not null; - public IntPtr Handle => _cert == null ? IntPtr.Zero : _cert.DangerousGetHandle(); + public IntPtr Handle => (_keyStorePrivateKeyEntry, _cert) switch + { + ({} privateKeyEntry, _) => privateKeyEntry.DangerousGetHandle(), + (null, {} cert) => cert.DangerousGetHandle(), + _ => IntPtr.Zero, + }; internal SafeX509Handle SafeHandle => _cert; diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/net/dot/android/crypto/DotnetX509KeyManager.java b/src/native/libs/System.Security.Cryptography.Native.Android/net/dot/android/crypto/DotnetX509KeyManager.java new file mode 100644 index 0000000000000..511f692f8bad2 --- /dev/null +++ b/src/native/libs/System.Security.Cryptography.Native.Android/net/dot/android/crypto/DotnetX509KeyManager.java @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +package net.dot.android.crypto; + +import java.net.Socket; +import java.security.KeyStore; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.ArrayList; + +import javax.net.ssl.X509KeyManager; + +public final class DotnetX509KeyManager implements X509KeyManager { + private static final String CLIENT_CERTIFICATE_ALIAS = "DOTNET_SSLStream_ClientCertificateContext"; + + private final PrivateKey privateKey; + private final X509Certificate[] certificateChain; + + public DotnetX509KeyManager(KeyStore.PrivateKeyEntry privateKeyEntry) { + if (privateKeyEntry == null) { + throw new IllegalArgumentException("PrivateKeyEntry must not be null"); + } + + this.privateKey = privateKeyEntry.getPrivateKey(); + + Certificate[] certificates = privateKeyEntry.getCertificateChain(); + ArrayList x509Certificates = new ArrayList<>(); + for (Certificate certificate : certificates) { + if (certificate instanceof X509Certificate) { + x509Certificates.add((X509Certificate) certificate); + } + } + + if (x509Certificates.size() == 0) { + throw new IllegalArgumentException("No valid X509 certificates found in the chain"); + } + + this.certificateChain = x509Certificates.toArray(new X509Certificate[0]); + } + + @Override + public String[] getClientAliases(String keyType, Principal[] issuers) { + return new String[] { CLIENT_CERTIFICATE_ALIAS }; + } + + @Override + public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) { + return CLIENT_CERTIFICATE_ALIAS; + } + + @Override + public String[] getServerAliases(String keyType, Principal[] issuers) { + return new String[0]; + } + + @Override + public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) { + return null; + } + + @Override + public X509Certificate[] getCertificateChain(String alias) { + return certificateChain; + } + + @Override + public PrivateKey getPrivateKey(String alias) { + return privateKey; + } +} 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 d562451ee54a0..8700708346e25 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 @@ -412,6 +412,9 @@ jmethodID g_HostnameVerifierVerify; jclass g_HttpsURLConnection; jmethodID g_HttpsURLConnectionGetDefaultHostnameVerifier; +// javax/net/ssl/KeyManager +jclass g_KeyManager; + // javax/net/ssl/KeyManagerFactory jclass g_KeyManagerFactory; jmethodID g_KeyManagerFactoryGetInstance; @@ -489,6 +492,10 @@ jclass g_TrustManager; jclass g_DotnetProxyTrustManager; jmethodID g_DotnetProxyTrustManagerCtor; +// net/dot/android/crypto/DotnetX509KeyManager +jclass g_DotnetX509KeyManager; +jmethodID g_DotnetX509KeyManagerCtor; + jobject ToGRef(JNIEnv *env, jobject lref) { if (lref) @@ -1024,6 +1031,8 @@ JNI_OnLoad(JavaVM *vm, void *reserved) g_HttpsURLConnection = GetClassGRef(env, "javax/net/ssl/HttpsURLConnection"); g_HttpsURLConnectionGetDefaultHostnameVerifier = GetMethod(env, true, g_HttpsURLConnection, "getDefaultHostnameVerifier", "()Ljavax/net/ssl/HostnameVerifier;"); + g_KeyManager = GetClassGRef(env, "javax/net/ssl/KeyManager"); + g_KeyManagerFactory = GetClassGRef(env, "javax/net/ssl/KeyManagerFactory"); g_KeyManagerFactoryGetInstance = GetMethod(env, true, g_KeyManagerFactory, "getInstance", "(Ljava/lang/String;)Ljavax/net/ssl/KeyManagerFactory;"); g_KeyManagerFactoryInit = GetMethod(env, false, g_KeyManagerFactory, "init", "(Ljava/security/KeyStore;[C)V"); @@ -1096,5 +1105,8 @@ JNI_OnLoad(JavaVM *vm, void *reserved) g_DotnetProxyTrustManager = GetClassGRef(env, "net/dot/android/crypto/DotnetProxyTrustManager"); g_DotnetProxyTrustManagerCtor = GetMethod(env, false, g_DotnetProxyTrustManager, "", "(J)V"); + g_DotnetX509KeyManager = GetClassGRef(env, "net/dot/android/crypto/DotnetX509KeyManager"); + g_DotnetX509KeyManagerCtor = GetMethod(env, false, g_DotnetX509KeyManager, "", "(Ljava/security/KeyStore$PrivateKeyEntry;)V"); + return JNI_VERSION_1_6; } 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 d1dc577bdf078..f2421c8f3618a 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 @@ -426,6 +426,9 @@ extern jmethodID g_HostnameVerifierVerify; extern jclass g_HttpsURLConnection; extern jmethodID g_HttpsURLConnectionGetDefaultHostnameVerifier; +// javax/net/ssl/KeyManager +extern jclass g_KeyManager; + // javax/net/ssl/KeyManagerFactory extern jclass g_KeyManagerFactory; extern jmethodID g_KeyManagerFactoryGetInstance; @@ -503,6 +506,10 @@ extern jclass g_TrustManager; extern jclass g_DotnetProxyTrustManager; extern jmethodID g_DotnetProxyTrustManagerCtor; +// net/dot/android/crypto/DotnetX509KeyManager +extern jclass g_DotnetX509KeyManager; +extern jmethodID g_DotnetX509KeyManagerCtor; + // Compatibility macros #if !defined (__mallocfunc) #if defined (__clang__) || defined (__GNUC__) diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_sslstream.c b/src/native/libs/System.Security.Cryptography.Native.Android/pal_sslstream.c index f19210fe9e05a..9aa7444e391bc 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Android/pal_sslstream.c +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_sslstream.c @@ -583,6 +583,43 @@ SSLStream* AndroidCryptoNative_SSLStreamCreateWithCertificates(intptr_t sslStrea return sslStream; } +SSLStream* AndroidCryptoNative_SSLStreamCreateWithKeyStorePrivateKeyEntry(intptr_t sslStreamProxyHandle, jobject privateKeyEntry) +{ + abort_unless(sslStreamProxyHandle != 0, "invalid pointer to the .NET SslStream proxy"); + + SSLStream* sslStream = NULL; + JNIEnv* env = GetJNIEnv(); + + INIT_LOCALS(loc, sslContext, dotnetX509KeyManager, keyManagers, trustManagers); + + loc[sslContext] = GetSSLContextInstance(env); + if (!loc[sslContext]) + goto cleanup; + + loc[dotnetX509KeyManager] = (*env)->NewObject(env, g_DotnetX509KeyManager, g_DotnetX509KeyManagerCtor, privateKeyEntry); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); + + loc[keyManagers] = make_java_object_array(env, 1, g_KeyManager, loc[dotnetX509KeyManager]); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); + + // TrustManager[] trustManagers = GetTrustManagers(sslStreamProxyHandle); + loc[trustManagers] = GetTrustManagers(env, sslStreamProxyHandle); + if (!loc[trustManagers]) + goto cleanup; + + // sslContext.init(keyManagers, trustManagers, null); + (*env)->CallVoidMethod(env, loc[sslContext], g_SSLContextInitMethod, loc[keyManagers], loc[trustManagers], NULL); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); + + sslStream = xcalloc(1, sizeof(SSLStream)); + sslStream->sslContext = ToGRef(env, loc[sslContext]); + loc[sslContext] = NULL; + +cleanup: + RELEASE_LOCALS(loc, env); + return sslStream; +} + int32_t AndroidCryptoNative_SSLStreamInitialize( SSLStream* sslStream, bool isServer, ManagedContextHandle managedContextHandle, STREAM_READER streamReader, STREAM_WRITER streamWriter, int32_t appBufferSize, char* peerHost) { diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_sslstream.h b/src/native/libs/System.Security.Cryptography.Native.Android/pal_sslstream.h index fa3a884d68adf..2760a62d1e491 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Android/pal_sslstream.h +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_sslstream.h @@ -58,6 +58,13 @@ PALEXPORT SSLStream* AndroidCryptoNative_SSLStreamCreateWithCertificates(intptr_ jobject* /*X509Certificate[]*/ certs, int32_t certsLen); +/* +Create an SSL context with the specified certificates and private key from KeyChain + +Returns NULL on failure +*/ +PALEXPORT SSLStream* AndroidCryptoNative_SSLStreamCreateWithKeyStorePrivateKeyEntry(intptr_t sslStreamProxyHandle, jobject privateKeyEntry); + /* Initialize an SSL context - isServer : true if the context should be created in server mode diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509.c b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509.c index c179659bdb2ea..3baf304959d63 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509.c +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509.c @@ -346,3 +346,28 @@ static void FindCertStart(const uint8_t** buffer, int32_t* len) } } } + +jobject /*X509Certificate*/ AndroidCryptoNative_X509GetCertificateForPrivateKeyEntry(jobject /*PrivateKeyEntry*/ privateKeyEntry) +{ + abort_if_invalid_pointer_argument (privateKeyEntry); + + JNIEnv* env = GetJNIEnv(); + + jobject cert = (*env)->CallObjectMethod(env, privateKeyEntry, g_PrivateKeyEntryGetCertificate); + if (CheckJNIExceptions(env) || !cert) + { + return NULL; + } + + return ToGRef(env, cert); +} + +bool AndroidCryptoNative_X509IsKeyStorePrivateKeyEntry(jobject entry) +{ + if (!entry) + return false; + + JNIEnv* env = GetJNIEnv(); + + return (*env)->IsInstanceOf(env, entry, g_PrivateKeyEntryClass); +} diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509.h b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509.h index 05a5839ce876b..388da7a76438e 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509.h +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509.h @@ -56,3 +56,13 @@ Gets an opaque handle for a certificate's public key Returns null if the requested algorithm does not match that of the public key. */ PALEXPORT void* AndroidCryptoNative_X509PublicKey(jobject /*X509Certificate*/ cert, PAL_KeyAlgorithm algorithm); + +/* +Returns the certificate belonging to the PrivateKeyEntry +*/ +PALEXPORT jobject /*X509Certificate*/ AndroidCryptoNative_X509GetCertificateForPrivateKeyEntry(jobject /*PrivateKeyEntry*/ privateKeyEntry); + +/* +Checks if the given entry is a private key entry +*/ +PALEXPORT bool AndroidCryptoNative_X509IsKeyStorePrivateKeyEntry(jobject entry); diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.c b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.c index e178c32135b93..3250c45ddb937 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.c +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.c @@ -458,3 +458,57 @@ int32_t AndroidCryptoNative_X509StoreRemoveCertificate(jobject /*KeyStore*/ stor (*env)->DeleteLocalRef(env, alias); return CheckJNIExceptions(env) ? FAIL : SUCCESS; } + +jobject AndroidCryptoNative_X509StoreGetPrivateKeyEntry(jobject /*KeyStore*/ store, const char* hashString) +{ + abort_if_invalid_pointer_argument (store); + + JNIEnv* env = GetJNIEnv(); + INIT_LOCALS(loc, alias); + + jobject privateKeyEntry = NULL; + + loc[alias] = make_java_string(env, hashString); + + privateKeyEntry = (*env)->CallObjectMethod(env, store, g_KeyStoreGetEntry, loc[alias], NULL); + if (CheckJNIExceptions(env)) + { + ReleaseLRef(env, privateKeyEntry); + goto cleanup; + } + + bool isPrivateKeyEntry = (*env)->IsInstanceOf(env, privateKeyEntry, g_PrivateKeyEntryClass); + if (!isPrivateKeyEntry) + { + ReleaseLRef(env, privateKeyEntry); + privateKeyEntry = NULL; + goto cleanup; + } + + privateKeyEntry = ToGRef(env, privateKeyEntry); + +cleanup: + RELEASE_LOCALS(loc, env); + return privateKeyEntry; +} + +bool AndroidCryptoNative_X509StoreDeleteEntry(jobject /*KeyStore*/ store, const char* hashString) +{ + int32_t ret = FAIL; + + abort_if_invalid_pointer_argument (store); + + JNIEnv* env = GetJNIEnv(); + INIT_LOCALS(loc, alias); + + loc[alias] = make_java_string(env, hashString); + + (*env)->CallVoidMethod(env, store, g_KeyStoreDeleteEntry, loc[alias]); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); + + ret = SUCCESS; + +cleanup: + RELEASE_LOCALS(loc, env); + return ret; +} From fef9203faa26d8079bdd74f53f917bb313ed32e7 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 12 Jun 2024 12:32:10 +0200 Subject: [PATCH 02/11] Add test --- .../Interop.X509Store.cs | 6 +++ ...ttpClientHandlerTest.ClientCertificates.cs | 40 +++++++++++++++++++ .../System/AndroidKeyStoreHelper.cs | 30 ++++++++++++++ .../tests/TestUtilities/TestUtilities.csproj | 9 +++++ .../pal_x509store.c | 1 + .../pal_x509store.h | 10 +++++ 6 files changed, 96 insertions(+) create mode 100644 src/libraries/Common/tests/TestUtilities/System/AndroidKeyStoreHelper.cs diff --git a/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.X509Store.cs b/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.X509Store.cs index ec5861ba6e1f4..574b57dac9b25 100644 --- a/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.X509Store.cs +++ b/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.X509Store.cs @@ -57,6 +57,12 @@ internal static unsafe partial bool X509StoreRemoveCertificate( SafeX509StoreHandle store, SafeX509Handle cert, string hashString); + + [LibraryImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_X509StoreGetPrivateKeyEntry", StringMarshalling = StringMarshalling.Utf8)] + internal static partial IntPtr X509StoreGetPrivateKeyEntry(IntPtr store, string hashString); + [LibraryImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_X509StoreDeleteEntry", StringMarshalling = StringMarshalling.Utf8)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool X509StoreDeleteEntry(IntPtr store, string hashString); } } diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ClientCertificates.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ClientCertificates.cs index 373928b90792b..8ffa4f216a52e 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ClientCertificates.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ClientCertificates.cs @@ -208,5 +208,45 @@ await LoopbackServer.CreateServerAsync(async server => }, new LoopbackServer.Options { UseSsl = true }); } } + + [Fact] + [PlatformSpecific(TestPlatforms.Android)] + public async Task Android_GetCertificateFromKeyStoreViaAlias() + { + var options = new LoopbackServer.Options { UseSsl = true }; + + var (store, alias) = AndroidKeyStoreHelper.AddCertificate(Configuration.Certificates.GetClientCertificate()); + try + { + var clientCertificate = AndroidKeyStoreHelper.GetCertificateViaAlias(store, alias); + Assert.True(clientCertificate.HasPrivateKey); + + + await LoopbackServer.CreateServerAsync(async (server, url) => + { + using HttpClient client = CreateHttpClientWithCert(clientCertificate); + + await TestHelper.WhenAllCompletedOrAnyFailed( + client.GetStringAsync(url), + server.AcceptConnectionAsync(async connection => + { + SslStream sslStream = Assert.IsType(connection.Stream); + + _output.WriteLine( + "Client cert: {0}", + new X509Certificate2(sslStream.RemoteCertificate.Export(X509ContentType.Cert)).GetNameInfo(X509NameType.SimpleName, false)); + + Assert.Equal(clientCertificate.GetCertHashString(), sslStream.RemoteCertificate.GetCertHashString()); + + await connection.ReadRequestHeaderAndSendResponseAsync(additionalHeaders: "Connection: close\r\n"); + })); + }, options); + } + finally + { + Assert.True(AndroidKeyStoreHelper.DeleteAlias(store, alias)); + store.Dispose(); + } + } } } diff --git a/src/libraries/Common/tests/TestUtilities/System/AndroidKeyStoreHelper.cs b/src/libraries/Common/tests/TestUtilities/System/AndroidKeyStoreHelper.cs new file mode 100644 index 0000000000000..749125bf2d3cd --- /dev/null +++ b/src/libraries/Common/tests/TestUtilities/System/AndroidKeyStoreHelper.cs @@ -0,0 +1,30 @@ + +using System.Security.Cryptography.X509Certificates; + +namespace System +{ + public static partial class AndroidKeyStoreHelper + { + public static (X509Store, string) AddCertificate(X509Certificate2 cert) + { + // Add the certificate to the Android keystore via X509Store + // the alias is the certificate hash string (sha256) + X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser); + store.Open(OpenFlags.ReadWrite); + store.Add(cert); + string alias = cert.GetCertHashString(System.Security.Cryptography.HashAlgorithmName.SHA256); + return (store, alias); + } + + public static X509Certificate2 GetCertificateViaAlias(X509Store store, string alias) + { + var privateKeyEntry = Interop.AndroidCrypto.X509StoreGetPrivateKeyEntry(store.StoreHandle, alias); + return new X509Certificate2(privateKeyEntry); + } + + public static bool DeleteAlias(X509Store store, string alias) + { + return Interop.AndroidCrypto.X509StoreDeleteEntry(store.StoreHandle, alias); + } + } +} diff --git a/src/libraries/Common/tests/TestUtilities/TestUtilities.csproj b/src/libraries/Common/tests/TestUtilities/TestUtilities.csproj index 1566f163697bc..fdaf180ed1220 100644 --- a/src/libraries/Common/tests/TestUtilities/TestUtilities.csproj +++ b/src/libraries/Common/tests/TestUtilities/TestUtilities.csproj @@ -15,6 +15,7 @@ Condition="'$(EnableAggressiveTrimming)' == 'true'" /> + @@ -100,8 +101,16 @@ + + + + diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.c b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.c index 3250c45ddb937..1440b2463bcf4 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.c +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.c @@ -503,6 +503,7 @@ bool AndroidCryptoNative_X509StoreDeleteEntry(jobject /*KeyStore*/ store, const loc[alias] = make_java_string(env, hashString); + // store.deleteEntry(alias); (*env)->CallVoidMethod(env, store, g_KeyStoreDeleteEntry, loc[alias]); ON_EXCEPTION_PRINT_AND_GOTO(cleanup); diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.h b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.h index 1702f19ca0b45..80b4b0035b891 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.h +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.h @@ -70,3 +70,13 @@ Returns 1 on success, 0 otherwise. PALEXPORT int32_t AndroidCryptoNative_X509StoreRemoveCertificate(jobject /*KeyStore*/ store, jobject /*X509Certificate*/ cert, const char* hashString); + +/* +Looks up priate key and certificate chain based on the alias in the provided keystore +*/ +PALEXPORT jobject AndroidCryptoNative_X509StoreGetPrivateKeyEntry(jobject /*KeyStore*/ store, const char* hashString); + +/* +Looks up priate key and certificate chain based on the alias in the provided keystore +*/ +PALEXPORT bool AndroidCryptoNative_X509StoreDeleteEntry(jobject /*KeyStore*/ store, const char* hashString); From 35690e1d8151513b82054a075d0a2c093842b527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Rozs=C3=ADval?= Date: Fri, 14 Jun 2024 16:56:17 +0200 Subject: [PATCH 03/11] Update src/libraries/Common/tests/TestUtilities/System/AndroidKeyStoreHelper.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alexander Köplinger --- .../Common/tests/TestUtilities/System/AndroidKeyStoreHelper.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libraries/Common/tests/TestUtilities/System/AndroidKeyStoreHelper.cs b/src/libraries/Common/tests/TestUtilities/System/AndroidKeyStoreHelper.cs index 749125bf2d3cd..bb4b16a29331b 100644 --- a/src/libraries/Common/tests/TestUtilities/System/AndroidKeyStoreHelper.cs +++ b/src/libraries/Common/tests/TestUtilities/System/AndroidKeyStoreHelper.cs @@ -1,3 +1,5 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. using System.Security.Cryptography.X509Certificates; From dadc2d5e2b2ad53682d569aab007ea1c9c63e527 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 13 Jun 2024 09:56:14 +0200 Subject: [PATCH 04/11] Fix outdated comment --- .../System.Security.Cryptography.Native.Android/pal_x509store.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.h b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.h index 80b4b0035b891..75b2f83875660 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.h +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.h @@ -77,6 +77,6 @@ Looks up priate key and certificate chain based on the alias in the provided key PALEXPORT jobject AndroidCryptoNative_X509StoreGetPrivateKeyEntry(jobject /*KeyStore*/ store, const char* hashString); /* -Looks up priate key and certificate chain based on the alias in the provided keystore +Removes an entry from the keystore for the given alias */ PALEXPORT bool AndroidCryptoNative_X509StoreDeleteEntry(jobject /*KeyStore*/ store, const char* hashString); From 25b208876019082d3194c3a8244ee4df865f673f Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 14 Jun 2024 22:40:57 +0200 Subject: [PATCH 05/11] Fix tests --- ...ttpClientHandlerTest.ClientCertificates.cs | 8 +++++-- .../tests/TestUtilities/TestUtilities.csproj | 9 -------- .../FunctionalTests}/AndroidKeyStoreHelper.cs | 4 ++-- .../System.Net.Http.Functional.Tests.csproj | 22 ++++++++++++++++++- 4 files changed, 29 insertions(+), 14 deletions(-) rename src/libraries/{Common/tests/TestUtilities/System => System.Net.Http/tests/FunctionalTests}/AndroidKeyStoreHelper.cs (93%) diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ClientCertificates.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ClientCertificates.cs index 8ffa4f216a52e..45f6963190692 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ClientCertificates.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ClientCertificates.cs @@ -209,10 +209,11 @@ await LoopbackServer.CreateServerAsync(async server => } } - [Fact] + [ConditionalFact] [PlatformSpecific(TestPlatforms.Android)] public async Task Android_GetCertificateFromKeyStoreViaAlias() { +#if TARGETS_ANDROID var options = new LoopbackServer.Options { UseSsl = true }; var (store, alias) = AndroidKeyStoreHelper.AddCertificate(Configuration.Certificates.GetClientCertificate()); @@ -221,7 +222,6 @@ public async Task Android_GetCertificateFromKeyStoreViaAlias() var clientCertificate = AndroidKeyStoreHelper.GetCertificateViaAlias(store, alias); Assert.True(clientCertificate.HasPrivateKey); - await LoopbackServer.CreateServerAsync(async (server, url) => { using HttpClient client = CreateHttpClientWithCert(clientCertificate); @@ -247,6 +247,10 @@ await TestHelper.WhenAllCompletedOrAnyFailed( Assert.True(AndroidKeyStoreHelper.DeleteAlias(store, alias)); store.Dispose(); } +#else + await Task.CompletedTask; + throw new SkipTestException("Android-specific test"); +#endif } } } diff --git a/src/libraries/Common/tests/TestUtilities/TestUtilities.csproj b/src/libraries/Common/tests/TestUtilities/TestUtilities.csproj index fdaf180ed1220..1566f163697bc 100644 --- a/src/libraries/Common/tests/TestUtilities/TestUtilities.csproj +++ b/src/libraries/Common/tests/TestUtilities/TestUtilities.csproj @@ -15,7 +15,6 @@ Condition="'$(EnableAggressiveTrimming)' == 'true'" /> - @@ -101,16 +100,8 @@ - - - - diff --git a/src/libraries/Common/tests/TestUtilities/System/AndroidKeyStoreHelper.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/AndroidKeyStoreHelper.cs similarity index 93% rename from src/libraries/Common/tests/TestUtilities/System/AndroidKeyStoreHelper.cs rename to src/libraries/System.Net.Http/tests/FunctionalTests/AndroidKeyStoreHelper.cs index bb4b16a29331b..d3ab3df784563 100644 --- a/src/libraries/Common/tests/TestUtilities/System/AndroidKeyStoreHelper.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/AndroidKeyStoreHelper.cs @@ -3,9 +3,9 @@ using System.Security.Cryptography.X509Certificates; -namespace System +namespace System.Net.Http.Functional.Tests { - public static partial class AndroidKeyStoreHelper + public static class AndroidKeyStoreHelper { public static (X509Store, string) AddCertificate(X509Certificate2 cert) { diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj b/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj index bdcdf4ada7c92..87c90349a7b6b 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj @@ -29,6 +29,10 @@ 01:15:00 + + $(DefineConstants);TARGETS_ANDROID + + @@ -233,7 +237,7 @@ - + @@ -242,6 +246,22 @@ + + + + + + + + + + From 4ccb2538aa3dd375b21615ef32126d3b40dc15ef Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 19 Jun 2024 17:05:56 +0200 Subject: [PATCH 06/11] Fix typo --- .../System.Security.Cryptography.Native.Android/pal_x509store.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.h b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.h index 75b2f83875660..65555e73c2dfd 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.h +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.h @@ -72,7 +72,7 @@ PALEXPORT int32_t AndroidCryptoNative_X509StoreRemoveCertificate(jobject /*KeySt const char* hashString); /* -Looks up priate key and certificate chain based on the alias in the provided keystore +Looks up private key and certificate chain based on the alias in the provided keystore */ PALEXPORT jobject AndroidCryptoNative_X509StoreGetPrivateKeyEntry(jobject /*KeyStore*/ store, const char* hashString); From b9e78ebb09b7fc553940d2cec3c2dbf0c25c0509 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 19 Jun 2024 18:05:24 +0200 Subject: [PATCH 07/11] Add helper method --- .../Common/src/Interop/Android/Interop.JObjectLifetime.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/libraries/Common/src/Interop/Android/Interop.JObjectLifetime.cs b/src/libraries/Common/src/Interop/Android/Interop.JObjectLifetime.cs index ff83cf7bb1b87..006396eacd0b8 100644 --- a/src/libraries/Common/src/Interop/Android/Interop.JObjectLifetime.cs +++ b/src/libraries/Common/src/Interop/Android/Interop.JObjectLifetime.cs @@ -42,6 +42,13 @@ protected override bool ReleaseHandle() return true; } + internal static SafeJObjectHandle CreateGlobalReferenceFromHandle(IntPtr handle) + { + var jObjectHandle = new SafeJObjectHandle(); + Marshal.InitHandle(jObjectHandle, NewGlobalReference(handle)); + return jObjectHandle; + } + public override bool IsInvalid => handle == IntPtr.Zero; } } From d93c08ed869ca0888555aae9eb275b5557cc0057 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 19 Jun 2024 18:07:12 +0200 Subject: [PATCH 08/11] Address lifetime concerns --- .../Interop.X509.cs | 21 +++++++++++++++---- .../X509Certificates/AndroidCertificatePal.cs | 19 +++++++++++++---- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.X509.cs b/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.X509.cs index 957382d66c877..21a448899b2f9 100644 --- a/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.X509.cs +++ b/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.X509.cs @@ -39,10 +39,23 @@ internal static byte[] X509Encode(SafeX509Handle x) private static partial IntPtr GetPrivateKeyEntryCertificate(IntPtr privatKeyEntryHandle); internal static SafeX509Handle GetPrivateKeyEntryCertificate(SafeHandle privatKeyEntryHandle) { - var certificateHandle = new SafeX509Handle(); - var certificatePtr = GetPrivateKeyEntryCertificate(privatKeyEntryHandle.DangerousGetHandle()); - Marshal.InitHandle(certificateHandle, Interop.JObjectLifetime.NewGlobalReference(certificatePtr)); - return certificateHandle; + bool addedRef = false; + try + { + privatKeyEntryHandle.DangerousAddRef(ref addedRef); + IntPtr certificatePtr = GetPrivateKeyEntryCertificate(privatKeyEntryHandle.DangerousGetHandle()); + + SafeX509Handle certificateHandle = new(); + Marshal.InitHandle(certificateHandle, certificatePtr); + return certificateHandle; + } + finally + { + if (addedRef) + { + privatKeyEntryHandle.DangerousRelease(); + } + } } [LibraryImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_X509DecodeCollection")] diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AndroidCertificatePal.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AndroidCertificatePal.cs index 3e3d3a4b099d1..60c7a347fd898 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AndroidCertificatePal.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AndroidCertificatePal.cs @@ -44,11 +44,22 @@ public static ICertificatePal FromOtherCert(X509Certificate cert) AndroidCertificatePal certPal = (AndroidCertificatePal)cert.Pal; - if (certPal._keyStorePrivateKeyEntry is not null) + if (certPal._keyStorePrivateKeyEntry is SafeJObjectHandle privateKeyEntry) { - var jobjectHandle = new Interop.JObjectLifetime.SafeJObjectHandle(); - Marshal.InitHandle(jobjectHandle, Interop.JObjectLifetime.NewGlobalReference(certPal.Handle)); - return new AndroidCertificatePal(jobjectHandle); + bool addedRef = false; + try + { + privateKeyEntry.DangerousAddRef(ref addedRef); + SafeJObjectHandle newSafeHandle = SafeJObjectHandle.CreateGlobalReferenceFromHandle(privateKeyEntry.DangerousGetHandle()); + return new AndroidCertificatePal(newSafeHandle); + } + finally + { + if (addedRef) + { + privateKeyEntry.DangerousRelease(); + } + } } // Ensure private key is copied From a62afead6e8dfabb80c9dbeafc5b3b1edbc5aa80 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 19 Jun 2024 18:08:11 +0200 Subject: [PATCH 09/11] Fix bool marshalling --- .../System.Security.Cryptography.Native.Android/pal_x509.c | 6 +++--- .../System.Security.Cryptography.Native.Android/pal_x509.h | 2 +- .../pal_x509store.c | 2 +- .../pal_x509store.h | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509.c b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509.c index 3baf304959d63..99c6e522a203c 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509.c +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509.c @@ -362,12 +362,12 @@ jobject /*X509Certificate*/ AndroidCryptoNative_X509GetCertificateForPrivateKeyE return ToGRef(env, cert); } -bool AndroidCryptoNative_X509IsKeyStorePrivateKeyEntry(jobject entry) +int32_t AndroidCryptoNative_X509IsKeyStorePrivateKeyEntry(jobject entry) { if (!entry) - return false; + return 0; JNIEnv* env = GetJNIEnv(); - return (*env)->IsInstanceOf(env, entry, g_PrivateKeyEntryClass); + return (*env)->IsInstanceOf(env, entry, g_PrivateKeyEntryClass) ? 1 : 0; } diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509.h b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509.h index 388da7a76438e..e840087624cd0 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509.h +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509.h @@ -65,4 +65,4 @@ PALEXPORT jobject /*X509Certificate*/ AndroidCryptoNative_X509GetCertificateForP /* Checks if the given entry is a private key entry */ -PALEXPORT bool AndroidCryptoNative_X509IsKeyStorePrivateKeyEntry(jobject entry); +PALEXPORT int32_t AndroidCryptoNative_X509IsKeyStorePrivateKeyEntry(jobject entry); diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.c b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.c index 1440b2463bcf4..d66617f2d64ae 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.c +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.c @@ -492,7 +492,7 @@ jobject AndroidCryptoNative_X509StoreGetPrivateKeyEntry(jobject /*KeyStore*/ sto return privateKeyEntry; } -bool AndroidCryptoNative_X509StoreDeleteEntry(jobject /*KeyStore*/ store, const char* hashString) +int32_t AndroidCryptoNative_X509StoreDeleteEntry(jobject /*KeyStore*/ store, const char* hashString) { int32_t ret = FAIL; diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.h b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.h index 65555e73c2dfd..b76d7e78cb94a 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.h +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_x509store.h @@ -79,4 +79,4 @@ PALEXPORT jobject AndroidCryptoNative_X509StoreGetPrivateKeyEntry(jobject /*KeyS /* Removes an entry from the keystore for the given alias */ -PALEXPORT bool AndroidCryptoNative_X509StoreDeleteEntry(jobject /*KeyStore*/ store, const char* hashString); +PALEXPORT int32_t AndroidCryptoNative_X509StoreDeleteEntry(jobject /*KeyStore*/ store, const char* hashString); From 86327ef9fa97047597e25a58aa9101972c43baa4 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 19 Jun 2024 18:08:42 +0200 Subject: [PATCH 10/11] Improve code quality --- .../X509Certificates/AndroidCertificatePal.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AndroidCertificatePal.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AndroidCertificatePal.cs index 60c7a347fd898..42a1da0fa9d79 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AndroidCertificatePal.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/AndroidCertificatePal.cs @@ -11,13 +11,15 @@ using System.Text; using Microsoft.Win32.SafeHandles; +using SafeJObjectHandle = Interop.JObjectLifetime.SafeJObjectHandle; + namespace System.Security.Cryptography.X509Certificates { internal sealed class AndroidCertificatePal : ICertificatePal { private SafeX509Handle _cert; private SafeKeyHandle? _privateKey; - private Interop.JObjectLifetime.SafeJObjectHandle? _keyStorePrivateKeyEntry; + private SafeJObjectHandle? _keyStorePrivateKeyEntry; private CertificateData _certData; @@ -28,8 +30,7 @@ public static ICertificatePal FromHandle(IntPtr handle) if (Interop.AndroidCrypto.IsKeyStorePrivateKeyEntry(handle)) { - var newPrivateKeyEntryHandle = new Interop.JObjectLifetime.SafeJObjectHandle(); - Marshal.InitHandle(newPrivateKeyEntryHandle, Interop.JObjectLifetime.NewGlobalReference(handle)); + SafeJObjectHandle newPrivateKeyEntryHandle = SafeJObjectHandle.CreateGlobalReferenceFromHandle(handle); return new AndroidCertificatePal(newPrivateKeyEntryHandle); } @@ -160,7 +161,7 @@ private static AndroidCertificatePal ReadPkcs12(ReadOnlySpan rawData, Safe } } - internal AndroidCertificatePal(Interop.JObjectLifetime.SafeJObjectHandle handle) + internal AndroidCertificatePal(SafeJObjectHandle handle) { _cert = Interop.AndroidCrypto.GetPrivateKeyEntryCertificate(handle); _keyStorePrivateKeyEntry = handle; @@ -179,12 +180,9 @@ internal AndroidCertificatePal(SafeX509Handle handle, SafeKeyHandle privateKey) public bool HasPrivateKey => _privateKey is not null || _keyStorePrivateKeyEntry is not null; - public IntPtr Handle => (_keyStorePrivateKeyEntry, _cert) switch - { - ({} privateKeyEntry, _) => privateKeyEntry.DangerousGetHandle(), - (null, {} cert) => cert.DangerousGetHandle(), - _ => IntPtr.Zero, - }; + public IntPtr Handle => _keyStorePrivateKeyEntry?.DangerousGetHandle() + ?? _cert?.DangerousGetHandle() + ?? IntPtr.Zero; internal SafeX509Handle SafeHandle => _cert; From 49663a68186010f0e52db3f031431b63db4866e6 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 19 Jun 2024 18:09:41 +0200 Subject: [PATCH 11/11] Improve tests --- .../HttpClientHandlerTest.ClientCertificates.cs | 14 +++++--------- .../tests/FunctionalTests/AndroidKeyStoreHelper.cs | 2 +- .../System.Net.Http.Functional.Tests.csproj | 8 ++++---- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ClientCertificates.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ClientCertificates.cs index 45f6963190692..e5bd399a977ba 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ClientCertificates.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ClientCertificates.cs @@ -209,17 +209,16 @@ await LoopbackServer.CreateServerAsync(async server => } } - [ConditionalFact] - [PlatformSpecific(TestPlatforms.Android)] +#if TARGETS_ANDROID + [Fact] public async Task Android_GetCertificateFromKeyStoreViaAlias() { -#if TARGETS_ANDROID var options = new LoopbackServer.Options { UseSsl = true }; - var (store, alias) = AndroidKeyStoreHelper.AddCertificate(Configuration.Certificates.GetClientCertificate()); + (X509Store store, string alias) = AndroidKeyStoreHelper.AddCertificate(Configuration.Certificates.GetClientCertificate()); try { - var clientCertificate = AndroidKeyStoreHelper.GetCertificateViaAlias(store, alias); + X509Certificate2 clientCertificate = AndroidKeyStoreHelper.GetCertificateViaAlias(store, alias); Assert.True(clientCertificate.HasPrivateKey); await LoopbackServer.CreateServerAsync(async (server, url) => @@ -247,10 +246,7 @@ await TestHelper.WhenAllCompletedOrAnyFailed( Assert.True(AndroidKeyStoreHelper.DeleteAlias(store, alias)); store.Dispose(); } -#else - await Task.CompletedTask; - throw new SkipTestException("Android-specific test"); -#endif } +#endif } } diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/AndroidKeyStoreHelper.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/AndroidKeyStoreHelper.cs index d3ab3df784563..21c0cb1c2fae1 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/AndroidKeyStoreHelper.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/AndroidKeyStoreHelper.cs @@ -20,7 +20,7 @@ public static (X509Store, string) AddCertificate(X509Certificate2 cert) public static X509Certificate2 GetCertificateViaAlias(X509Store store, string alias) { - var privateKeyEntry = Interop.AndroidCrypto.X509StoreGetPrivateKeyEntry(store.StoreHandle, alias); + IntPtr privateKeyEntry = Interop.AndroidCrypto.X509StoreGetPrivateKeyEntry(store.StoreHandle, alias); return new X509Certificate2(privateKeyEntry); } diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj b/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj index 87c90349a7b6b..add7df7ccd731 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj @@ -6,7 +6,7 @@ $(DefineConstants);SYSNETHTTP_NO_OPENSSL;HTTP3 true true - $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-linux;$(NetCoreAppCurrent)-browser;$(NetCoreAppCurrent)-osx + $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-linux;$(NetCoreAppCurrent)-android;$(NetCoreAppCurrent)-browser;$(NetCoreAppCurrent)-osx true true @@ -29,7 +29,7 @@ 01:15:00 - + $(DefineConstants);TARGETS_ANDROID @@ -237,7 +237,7 @@ - + @@ -247,7 +247,7 @@ Link="Common\Interop\OSX\Interop.Libraries.cs" /> - +