diff --git a/src/libraries/Common/src/Interop/Android/Interop.JObjectLifetime.cs b/src/libraries/Common/src/Interop/Android/Interop.JObjectLifetime.cs index ff83cf7bb1b875..006396eacd0b8e 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; } } 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 5f3ee7ac00b1e8..d663095cd8cead 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 5d39487b4bf0ea..21a448899b2f90 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,31 @@ 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) + { + 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")] private static partial int X509DecodeCollection(ref byte buf, int bufLen, IntPtr[]? ptrs, ref int handlesLen); 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 ec5861ba6e1f42..574b57dac9b258 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 373928b90792b2..e5bd399a977ba0 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 }); } } + +#if TARGETS_ANDROID + [Fact] + public async Task Android_GetCertificateFromKeyStoreViaAlias() + { + var options = new LoopbackServer.Options { UseSsl = true }; + + (X509Store store, string alias) = AndroidKeyStoreHelper.AddCertificate(Configuration.Certificates.GetClientCertificate()); + try + { + X509Certificate2 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(); + } + } +#endif } } diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/AndroidKeyStoreHelper.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/AndroidKeyStoreHelper.cs new file mode 100644 index 00000000000000..21c0cb1c2fae1b --- /dev/null +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/AndroidKeyStoreHelper.cs @@ -0,0 +1,32 @@ +// 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; + +namespace System.Net.Http.Functional.Tests +{ + public static 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) + { + IntPtr 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/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 bdcdf4ada7c928..add7df7ccd7317 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,6 +29,10 @@ 01:15:00 + + $(DefineConstants);TARGETS_ANDROID + + @@ -242,6 +246,22 @@ + + + + + + + + + + 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 96302cda3d7846..0f141c0812f942 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 30a6cdbce3c291..42a1da0fa9d790 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,12 +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 SafeJObjectHandle? _keyStorePrivateKeyEntry; private CertificateData _certData; @@ -25,6 +28,12 @@ public static ICertificatePal FromHandle(IntPtr handle) if (handle == IntPtr.Zero) throw new ArgumentException(SR.Arg_InvalidHandle, nameof(handle)); + if (Interop.AndroidCrypto.IsKeyStorePrivateKeyEntry(handle)) + { + SafeJObjectHandle newPrivateKeyEntryHandle = SafeJObjectHandle.CreateGlobalReferenceFromHandle(handle); + return new AndroidCertificatePal(newPrivateKeyEntryHandle); + } + var newHandle = new SafeX509Handle(); Marshal.InitHandle(newHandle, Interop.JObjectLifetime.NewGlobalReference(handle)); return new AndroidCertificatePal(newHandle); @@ -36,6 +45,24 @@ public static ICertificatePal FromOtherCert(X509Certificate cert) AndroidCertificatePal certPal = (AndroidCertificatePal)cert.Pal; + if (certPal._keyStorePrivateKeyEntry is SafeJObjectHandle privateKeyEntry) + { + 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 if (certPal.PrivateKeyHandle != null) { @@ -134,6 +161,12 @@ private static AndroidCertificatePal ReadPkcs12(ReadOnlySpan rawData, Safe } } + internal AndroidCertificatePal(SafeJObjectHandle handle) + { + _cert = Interop.AndroidCrypto.GetPrivateKeyEntryCertificate(handle); + _keyStorePrivateKeyEntry = handle; + } + internal AndroidCertificatePal(SafeX509Handle handle) { _cert = handle; @@ -145,9 +178,11 @@ 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?.DangerousGetHandle() + ?? _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 00000000000000..511f692f8bad22 --- /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 e4109c26e2c3d8..243dbd1d9466d1 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; + // net/dot/android/crypto/PalPbkdf2 jclass g_PalPbkdf2; jmethodID g_PalPbkdf2Pbkdf2OneShot; @@ -1028,6 +1035,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"); @@ -1100,6 +1109,9 @@ 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"); + g_PalPbkdf2 = GetClassGRef(env, "net/dot/android/crypto/PalPbkdf2"); g_PalPbkdf2Pbkdf2OneShot = GetMethod(env, true, g_PalPbkdf2, "pbkdf2OneShot", "(Ljava/lang/String;[BLjava/nio/ByteBuffer;ILjava/nio/ByteBuffer;)I"); 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 fa716968cbc6d4..79bc888224629f 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; + // net/dot/android/crypto/PalPbkdf2 extern jclass g_PalPbkdf2; extern jmethodID g_PalPbkdf2Pbkdf2OneShot; 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 f19210fe9e05a6..9aa7444e391bcc 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 fa3a884d68adfa..2760a62d1e491d 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 c179659bdb2ea3..99c6e522a203c6 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); +} + +int32_t AndroidCryptoNative_X509IsKeyStorePrivateKeyEntry(jobject entry) +{ + if (!entry) + return 0; + + JNIEnv* env = GetJNIEnv(); + + 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 05a5839ce876b1..e840087624cd0f 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 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 e178c32135b937..d66617f2d64aef 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,58 @@ 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; +} + +int32_t 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); + + // store.deleteEntry(alias); + (*env)->CallVoidMethod(env, store, g_KeyStoreDeleteEntry, loc[alias]); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); + + ret = SUCCESS; + +cleanup: + RELEASE_LOCALS(loc, env); + return ret; +} 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 1702f19ca0b459..b76d7e78cb94a7 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 private key and certificate chain based on the alias in the provided keystore +*/ +PALEXPORT jobject AndroidCryptoNative_X509StoreGetPrivateKeyEntry(jobject /*KeyStore*/ store, const char* hashString); + +/* +Removes an entry from the keystore for the given alias +*/ +PALEXPORT int32_t AndroidCryptoNative_X509StoreDeleteEntry(jobject /*KeyStore*/ store, const char* hashString);