diff --git a/eng/liveBuilds.targets b/eng/liveBuilds.targets
index 15768278534e1b..9f29ab2f4849de 100644
--- a/eng/liveBuilds.targets
+++ b/eng/liveBuilds.targets
@@ -179,6 +179,11 @@
$(LibrariesNativeArtifactsPath)*.pdb"
IsNative="true"
Exclude="@(ExcludeNativeLibrariesRuntimeFiles)" />
+
+
+
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 e0a103c0e13979..5f3ee7ac00b1e8 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
@@ -29,18 +29,26 @@ internal enum PAL_SSLStreamStatus
};
[LibraryImport(Interop.Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_SSLStreamCreate")]
- internal static partial SafeSslHandle SSLStreamCreate();
+ private static partial SafeSslHandle SSLStreamCreate(IntPtr sslStreamProxyHandle);
+ internal static SafeSslHandle SSLStreamCreate(SslStream.JavaProxy sslStreamProxy)
+ => SSLStreamCreate(sslStreamProxy.Handle);
[LibraryImport(Interop.Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_SSLStreamCreateWithCertificates")]
private static partial SafeSslHandle SSLStreamCreateWithCertificates(
+ IntPtr sslStreamProxyHandle,
ref byte pkcs8PrivateKey,
int pkcs8PrivateKeyLen,
PAL_KeyAlgorithm algorithm,
IntPtr[] certs,
int certsLen);
- internal static SafeSslHandle SSLStreamCreateWithCertificates(ReadOnlySpan pkcs8PrivateKey, PAL_KeyAlgorithm algorithm, IntPtr[] certificates)
+ internal static SafeSslHandle SSLStreamCreateWithCertificates(
+ SslStream.JavaProxy sslStreamProxy,
+ ReadOnlySpan pkcs8PrivateKey,
+ PAL_KeyAlgorithm algorithm,
+ IntPtr[] certificates)
{
return SSLStreamCreateWithCertificates(
+ sslStreamProxy.Handle,
ref MemoryMarshal.GetReference(pkcs8PrivateKey),
pkcs8PrivateKey.Length,
algorithm,
@@ -48,6 +56,10 @@ ref MemoryMarshal.GetReference(pkcs8PrivateKey),
certificates.Length);
}
+ [LibraryImport(Interop.Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_RegisterRemoteCertificateValidationCallback")]
+ internal static unsafe partial void RegisterRemoteCertificateValidationCallback(
+ delegate* unmanaged verifyRemoteCertificate);
+
[LibraryImport(Interop.Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_SSLStreamInitialize")]
private static unsafe partial int SSLStreamInitializeImpl(
SafeSslHandle sslHandle,
diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.AcceptAllCerts.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.AcceptAllCerts.cs
index cf9af001342177..b449f5025276dc 100644
--- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.AcceptAllCerts.cs
+++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.AcceptAllCerts.cs
@@ -96,7 +96,6 @@ await TestHelper.WhenAllCompletedOrAnyFailed(
[OuterLoop]
[ConditionalTheory(nameof(ClientSupportsDHECipherSuites))]
[MemberData(nameof(InvalidCertificateServers))]
- [SkipOnPlatform(TestPlatforms.Android, "Android rejects the certificate, the custom validation callback in .NET cannot override OS behavior in the current implementation")]
public async Task InvalidCertificateServers_CertificateValidationDisabled_Succeeds(string url)
{
using (HttpClientHandler handler = CreateHttpClientHandler(allowAllCertificates: true))
diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs
index 2962e25c6cd599..eca2101b78edbb 100644
--- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs
+++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs
@@ -286,7 +286,6 @@ private async Task UseCallback_BadCertificate_ExpectedPolicyErrors_Helper(string
[OuterLoop("Uses external servers")]
[Theory]
[MemberData(nameof(CertificateValidationServersAndExpectedPolicies))]
- [SkipOnPlatform(TestPlatforms.Android, "Android rejects the certificate, the custom validation callback in .NET cannot override OS behavior in the current implementation")]
public async Task UseCallback_BadCertificate_ExpectedPolicyErrors(string url, SslPolicyErrors expectedErrors)
{
const int SEC_E_BUFFER_TOO_SMALL = unchecked((int)0x80090321);
@@ -310,7 +309,6 @@ public async Task UseCallback_BadCertificate_ExpectedPolicyErrors(string url, Ss
}
[Fact]
- [SkipOnPlatform(TestPlatforms.Android, "Android rejects the certificate, the custom validation callback in .NET cannot override OS behavior in the current implementation")]
public async Task UseCallback_SelfSignedCertificate_ExpectedPolicyErrors()
{
using (HttpClientHandler handler = CreateHttpClientHandler())
diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs
index 91ea0ae621bffe..f9f6de4931b9dd 100644
--- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs
+++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs
@@ -153,7 +153,7 @@ public void Properties_AddItemToDictionary_ItemPresent()
[ConditionalFact]
[SkipOnPlatform(TestPlatforms.Browser, "ServerCertificateCustomValidationCallback not supported on Browser")]
- [SkipOnPlatform(TestPlatforms.Android, "IPv6 loopback with SSL doesn't work on Android")]
+ [SkipOnPlatform(TestPlatforms.Android, "TargetHost cannot be set to an IPv6 address on Android because the string doesn't conform to the STD 3 ASCII rules")]
public async Task GetAsync_IPv6LinkLocalAddressUri_Success()
{
if (IsWinHttpHandler && UseVersion >= HttpVersion20.Value)
@@ -205,7 +205,7 @@ public async Task GetAsync_IPBasedUri_Success(IPAddress address)
if (PlatformDetection.IsAndroid && options.UseSsl && address == IPAddress.IPv6Loopback)
{
- throw new SkipTestException("IPv6 loopback with SSL doesn't work on Android");
+ throw new SkipTestException("TargetHost cannot be set to an IPv6 address on Android because the string doesn't conform to the STD 3 ASCII rules");
}
await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
@@ -287,7 +287,7 @@ public async Task GetAsync_SecureAndNonSecureIPBasedUri_CorrectlyFormatted(IPAdd
if (PlatformDetection.IsAndroid && useSsl && address == IPAddress.IPv6Loopback)
{
- throw new SkipTestException("IPv6 loopback with SSL doesn't work on Android");
+ throw new SkipTestException("TargetHost cannot be set to an IPv6 address on Android because the string doesn't conform to the STD 3 ASCII rules");
}
var options = new LoopbackServer.Options { Address = address, UseSsl = useSsl };
diff --git a/src/libraries/Common/tests/System/Net/Http/TestHelper.cs b/src/libraries/Common/tests/System/Net/Http/TestHelper.cs
index 8525ed8c1b2974..fd19ace2704b39 100644
--- a/src/libraries/Common/tests/System/Net/Http/TestHelper.cs
+++ b/src/libraries/Common/tests/System/Net/Http/TestHelper.cs
@@ -173,9 +173,6 @@ public static SocketsHttpHandler CreateSocketsHttpHandler(bool allowAllCertifica
// Browser doesn't support ServerCertificateCustomValidationCallback
if (allowAllCertificates && PlatformDetection.IsNotBrowser)
{
- // On Android, it is not enough to set the custom validation callback, the certificates also need to be trusted by the OS.
- // See HttpClientHandlerTestBase.SocketsHttpHandler.cs:CreateHttpClientHandler for more details.
-
handler.SslOptions.RemoteCertificateValidationCallback = delegate { return true; };
}
diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.SocketsHttpHandler.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.SocketsHttpHandler.cs
index 855dc25acb2d08..602d177f5f1be1 100644
--- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.SocketsHttpHandler.cs
+++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.SocketsHttpHandler.cs
@@ -37,13 +37,6 @@ protected static HttpClientHandler CreateHttpClientHandler(Version useVersion =
// Browser doesn't support ServerCertificateCustomValidationCallback
if (allowAllCertificates && PlatformDetection.IsNotBrowser)
{
- // On Android, it is not enough to set the custom validation callback, the certificates also need to be trusted by the OS.
- // The public keys of our self-signed certificates that are used by the loopback server are part of the System.Net.TestData
- // package and they can be included in a the Android test apk by adding the following property to the test's .csproj:
- //
- // true
- //
-
handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
}
diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs
index bc14a2900f9440..4ad0546969f0c0 100644
--- a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs
+++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs
@@ -4076,7 +4076,6 @@ public abstract class SocketsHttpHandler_SecurityTest : HttpClientHandlerTestBas
public SocketsHttpHandler_SecurityTest(ITestOutputHelper output) : base(output) { }
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
- [SkipOnPlatform(TestPlatforms.Android, "Self-signed certificates are rejected by Android before the .NET validation is reached")]
public async Task SslOptions_CustomTrust_Ok()
{
X509Certificate2Collection caCerts = new X509Certificate2Collection();
@@ -4113,7 +4112,6 @@ await LoopbackServerFactory.CreateClientAndServerAsync(
}
[Fact]
- [SkipOnPlatform(TestPlatforms.Android, "Self-signed certificates are rejected by Android before the .NET validation is reached")]
public async Task SslOptions_InvalidName_Throws()
{
X509Certificate2Collection caCerts = new X509Certificate2Collection();
@@ -4144,7 +4142,6 @@ await LoopbackServerFactory.CreateClientAndServerAsync(
}
[Fact]
- [SkipOnPlatform(TestPlatforms.Android, "Self-signed certificates are rejected by Android before the .NET validation is reached")]
public async Task SslOptions_CustomPolicy_IgnoresNameMismatch()
{
X509Certificate2Collection caCerts = new X509Certificate2Collection();
diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/SocksProxyTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SocksProxyTest.cs
index b7e6995f999809..a70f1c4cb5979c 100644
--- a/src/libraries/System.Net.Http/tests/FunctionalTests/SocksProxyTest.cs
+++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SocksProxyTest.cs
@@ -37,7 +37,7 @@ public async Task TestLoopbackAsync(string scheme, bool useSsl, bool useAuth, st
if (PlatformDetection.IsAndroid && useSsl && host == "::1")
{
- throw new SkipTestException("IPv6 loopback with SSL doesn't work on Android");
+ throw new SkipTestException("TargetHost cannot be set to an IPv6 address on Android because the string doesn't conform to the STD 3 ASCII rules");
}
await LoopbackServerFactory.CreateClientAndServerAsync(
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 f4480104d296d1..90c828e26b56b6 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
@@ -9,8 +9,6 @@
$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-linux;$(NetCoreAppCurrent)-browser;$(NetCoreAppCurrent)-osx
true
true
-
- true
true
diff --git a/src/libraries/System.Net.Requests/tests/System.Net.Requests.Tests.csproj b/src/libraries/System.Net.Requests/tests/System.Net.Requests.Tests.csproj
index c7c5bf5f83cdc8..1a2818addac82b 100644
--- a/src/libraries/System.Net.Requests/tests/System.Net.Requests.Tests.csproj
+++ b/src/libraries/System.Net.Requests/tests/System.Net.Requests.Tests.csproj
@@ -7,8 +7,6 @@
true
$(NoWarn);SYSLIB0014
-
- true
true
true
diff --git a/src/libraries/System.Net.Security/src/System.Net.Security.csproj b/src/libraries/System.Net.Security/src/System.Net.Security.csproj
index af280c25779ed9..27f71a0b0eed99 100644
--- a/src/libraries/System.Net.Security/src/System.Net.Security.csproj
+++ b/src/libraries/System.Net.Security/src/System.Net.Security.csproj
@@ -11,6 +11,7 @@
$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))
SR.SystemNetSecurity_PlatformNotSupported
$(DefineConstants);TARGET_WINDOWS
+ $(DefineConstants);TARGET_ANDROID
true
true
true
@@ -102,7 +103,7 @@
Link="Common\System\Net\SecurityStatusPal.cs" />
-
@@ -382,6 +383,7 @@
+
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 a07f84a1440e01..50b0eff0aab14f 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
@@ -34,14 +34,19 @@ internal sealed class SafeDeleteSslContext : SafeDeleteContext
private ArrayBuffer _inputBuffer = new ArrayBuffer(InitialBufferSize);
private ArrayBuffer _outputBuffer = new ArrayBuffer(InitialBufferSize);
+ public SslStream.JavaProxy SslStreamProxy { get; }
+
public SafeSslHandle SslContext => _sslContext;
public SafeDeleteSslContext(SslAuthenticationOptions authOptions)
: base(IntPtr.Zero)
{
+ SslStreamProxy = authOptions.SslStreamProxy
+ ?? throw new ArgumentNullException(nameof(authOptions.SslStreamProxy));
+
try
{
- _sslContext = CreateSslContext(authOptions);
+ _sslContext = CreateSslContext(SslStreamProxy, authOptions);
InitializeSslContext(_sslContext, authOptions);
}
catch (Exception ex)
@@ -58,8 +63,7 @@ protected override void Dispose(bool disposing)
{
if (disposing)
{
- SafeSslHandle sslContext = _sslContext;
- if (sslContext != null)
+ if (_sslContext is SafeSslHandle sslContext)
{
_inputBuffer.Dispose();
_outputBuffer.Dispose();
@@ -145,11 +149,11 @@ internal int ReadPendingWrites(byte[] buf, int offset, int count)
return limit;
}
- private static SafeSslHandle CreateSslContext(SslAuthenticationOptions authOptions)
+ private static SafeSslHandle CreateSslContext(SslStream.JavaProxy sslStreamProxy, SslAuthenticationOptions authOptions)
{
if (authOptions.CertificateContext == null)
{
- return Interop.AndroidCrypto.SSLStreamCreate();
+ return Interop.AndroidCrypto.SSLStreamCreate(sslStreamProxy);
}
SslStreamCertificateContext context = authOptions.CertificateContext;
@@ -169,7 +173,7 @@ private static SafeSslHandle CreateSslContext(SslAuthenticationOptions authOptio
ptrs[i + 1] = context.IntermediateCertificates[i].Handle;
}
- return Interop.AndroidCrypto.SSLStreamCreateWithCertificates(keyBytes, algorithm, ptrs);
+ return Interop.AndroidCrypto.SSLStreamCreateWithCertificates(sslStreamProxy, keyBytes, algorithm, ptrs);
}
private static AsymmetricAlgorithm GetPrivateKeyAlgorithm(X509Certificate2 cert, out PAL_KeyAlgorithm algorithm)
diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs
index 523cbdd1522bf8..261b356472bcf1 100644
--- a/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs
+++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs
@@ -182,5 +182,9 @@ private static SslProtocols FilterOutIncompatibleSslProtocols(SslProtocols proto
internal object? UserState { get; set; }
internal ServerOptionsSelectionCallback? ServerOptionDelegate { get; set; }
internal X509ChainPolicy? CertificateChainPolicy { get; set; }
+
+#if TARGET_ANDROID
+ internal SslStream.JavaProxy? SslStreamProxy { get; set; }
+#endif
}
}
diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Android.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Android.cs
new file mode 100644
index 00000000000000..f35aafe9a66d29
--- /dev/null
+++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Android.cs
@@ -0,0 +1,110 @@
+// 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.Diagnostics;
+using System.Net.Security;
+using System.Threading;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography.X509Certificates;
+
+namespace System.Net.Security
+{
+ public partial class SslStream
+ {
+ private JavaProxy.RemoteCertificateValidationResult VerifyRemoteCertificate()
+ {
+ ProtocolToken? alertToken = null;
+ var isValid = VerifyRemoteCertificate(
+ _sslAuthenticationOptions.CertValidationDelegate,
+ _sslAuthenticationOptions.CertificateContext?.Trust,
+ ref alertToken,
+ out SslPolicyErrors sslPolicyErrors,
+ out X509ChainStatusFlags chainStatus);
+
+ return new()
+ {
+ IsValid = isValid,
+ SslPolicyErrors = sslPolicyErrors,
+ ChainStatus = chainStatus,
+ AlertToken = alertToken,
+ };
+ }
+
+ private bool TryGetRemoteCertificateValidationResult(out SslPolicyErrors sslPolicyErrors, out X509ChainStatusFlags chainStatus, out ProtocolToken? alertToken, out bool isValid)
+ {
+ JavaProxy.RemoteCertificateValidationResult? validationResult = _securityContext?.SslStreamProxy.ValidationResult;
+ sslPolicyErrors = validationResult?.SslPolicyErrors ?? default;
+ chainStatus = validationResult?.ChainStatus ?? default;
+ isValid = validationResult?.IsValid ?? default;
+ alertToken = validationResult?.AlertToken;
+ return validationResult is not null;
+ }
+
+ internal sealed class JavaProxy : IDisposable
+ {
+ private static bool s_initialized;
+
+ private readonly SslStream _sslStream;
+ private GCHandle? _handle;
+
+ public IntPtr Handle
+ => _handle is GCHandle handle
+ ? GCHandle.ToIntPtr(handle)
+ : throw new ObjectDisposedException(nameof(JavaProxy));
+
+ public Exception? ValidationException { get; private set; }
+ public RemoteCertificateValidationResult? ValidationResult { get; private set; }
+
+ public JavaProxy(SslStream sslStream)
+ {
+ RegisterRemoteCertificateValidationCallback();
+
+ _sslStream = sslStream;
+ _handle = GCHandle.Alloc(this);
+ }
+
+ public void Dispose()
+ {
+ _handle?.Free();
+ _handle = null;
+ }
+
+ private static unsafe void RegisterRemoteCertificateValidationCallback()
+ {
+ if (!s_initialized)
+ {
+ Interop.AndroidCrypto.RegisterRemoteCertificateValidationCallback(&VerifyRemoteCertificate);
+ s_initialized = true;
+ }
+ }
+
+ [UnmanagedCallersOnly]
+ private static unsafe bool VerifyRemoteCertificate(IntPtr sslStreamProxyHandle)
+ {
+ var proxy = (JavaProxy?)GCHandle.FromIntPtr(sslStreamProxyHandle).Target;
+ Debug.Assert(proxy is not null);
+ Debug.Assert(proxy.ValidationResult is null);
+
+ try
+ {
+ proxy.ValidationResult = proxy._sslStream.VerifyRemoteCertificate();
+ return proxy.ValidationResult.IsValid;
+ }
+ catch (Exception exception)
+ {
+ proxy.ValidationException = exception;
+ return false;
+ }
+ }
+
+ internal sealed class RemoteCertificateValidationResult
+ {
+ public bool IsValid { get; init; }
+ public SslPolicyErrors SslPolicyErrors { get; init; }
+ public X509ChainStatusFlags ChainStatus { get; init; }
+ public ProtocolToken? AlertToken { get; init; }
+ }
+ }
+ }
+}
diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.IO.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.IO.cs
index 310ef723c48d0c..e18a73fbb3d919 100644
--- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.IO.cs
+++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.IO.cs
@@ -512,6 +512,28 @@ private bool CompleteHandshake(ref ProtocolToken? alertToken, out SslPolicyError
return true;
}
+ if (_selectedClientCertificate != null && !CertificateValidationPal.IsLocalCertificateUsed(_securityContext!))
+ {
+ // We may select client cert but it may not be used.
+ // This is primarily an issue on Windows with credential caching.
+ _selectedClientCertificate = null;
+ }
+
+#if TARGET_ANDROID
+ // On Android, the remote certificate verification can be invoked from Java TrustManager's callback
+ // during the handshake process. If that has occurred, we shouldn't run the validation again and
+ // return the existing validation result.
+ //
+ // The Java TrustManager callback is called only when the peer has a certificate. It's possible that
+ // the peer didn't provide any certificate (for example when the peer is the client) and the validation
+ // result hasn't been set. In that case we still need to run the verification at this point.
+ if (TryGetRemoteCertificateValidationResult(out sslPolicyErrors, out chainStatus, out alertToken, out bool isValid))
+ {
+ _handshakeCompleted = isValid;
+ return isValid;
+ }
+#endif
+
if (!VerifyRemoteCertificate(_sslAuthenticationOptions.CertValidationDelegate, _sslAuthenticationOptions.CertificateContext?.Trust, ref alertToken, out sslPolicyErrors, out chainStatus))
{
_handshakeCompleted = false;
diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs
index 62edaff0a451a2..cf6e16e9e501d3 100644
--- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs
+++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs
@@ -119,6 +119,10 @@ internal void CloseContext()
_securityContext?.Dispose();
_credentialsHandle?.Dispose();
+
+#if TARGET_ANDROID
+ _sslAuthenticationOptions.SslStreamProxy?.Dispose();
+#endif
}
//
@@ -1009,13 +1013,6 @@ internal bool VerifyRemoteCertificate(RemoteCertificateValidationCallback? remot
}
_remoteCertificate = certificate;
- if (_selectedClientCertificate != null && !CertificateValidationPal.IsLocalCertificateUsed(_securityContext!))
- {
- // We may select client cert but it may not be used.
- // This is primarily issue on Windows with credential caching
- _selectedClientCertificate = null;
- }
-
if (_remoteCertificate == null)
{
if (NetEventSource.Log.IsEnabled() && RemoteCertRequired) NetEventSource.Error(this, $"Remote certificate required, but no remote certificate received");
diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.cs
index e609d3dbdccafd..395ab232576e0d 100644
--- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.cs
+++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.cs
@@ -214,6 +214,10 @@ public SslStream(Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificat
_sslAuthenticationOptions.CertValidationDelegate = userCertificateValidationCallback;
_sslAuthenticationOptions.CertSelectionDelegate = userCertificateSelectionCallback;
+#if TARGET_ANDROID
+ _sslAuthenticationOptions.SslStreamProxy = new SslStream.JavaProxy(sslStream: this);
+#endif
+
if (NetEventSource.Log.IsEnabled()) NetEventSource.Log.SslStreamCtor(this, innerStream);
}
diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Android.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Android.cs
index fe3a22c02b3b50..53b8f5ce77dc34 100644
--- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Android.cs
+++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Android.cs
@@ -189,7 +189,7 @@ private static SecurityStatusPal HandshakeInternal(
{
SafeDeleteSslContext? sslContext = ((SafeDeleteSslContext?)context);
- if ((context == null) || context.IsInvalid)
+ if (context == null || context.IsInvalid)
{
context = new SafeDeleteSslContext(sslAuthenticationOptions);
sslContext = context;
@@ -212,7 +212,8 @@ private static SecurityStatusPal HandshakeInternal(
outputBuffer = sslContext.ReadPendingWrites();
- return new SecurityStatusPal(statusCode);
+ Exception? validationException = sslContext?.SslStreamProxy.ValidationException;
+ return new SecurityStatusPal(statusCode, validationException);
}
catch (Exception exc)
{
diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationRemoteServer.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationRemoteServer.cs
index 1530d5a33b7b10..2d4b1ba09ff08c 100644
--- a/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationRemoteServer.cs
+++ b/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationRemoteServer.cs
@@ -95,7 +95,6 @@ public async Task DefaultConnect_EndToEnd_Ok(string host)
[Theory]
[InlineData(true)]
[InlineData(false)]
- [SkipOnPlatform(TestPlatforms.Android, "The invalid certificate is rejected by Android and the .NET validation code isn't reached")]
[ActiveIssue("https://github.com/dotnet/runtime/issues/70981", TestPlatforms.OSX)]
public Task ConnectWithRevocation_WithCallback(bool checkRevocation)
{
diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCredentialCacheTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCredentialCacheTest.cs
index d8746a5d81afef..f1350954763201 100644
--- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCredentialCacheTest.cs
+++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCredentialCacheTest.cs
@@ -17,7 +17,6 @@ namespace System.Net.Security.Tests
public class SslStreamCredentialCacheTest
{
[Fact]
- [ActiveIssue("https://github.com/dotnet/runtime/issues/68206", TestPlatforms.Android)]
public async Task SslStream_SameCertUsedForClientAndServer_Ok()
{
(Stream stream1, Stream stream2) = TestHelper.GetConnectedStreams();
diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs
index 58d6dfb0ff76b1..0c9a9ee22183f1 100644
--- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs
+++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs
@@ -754,7 +754,7 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout(
[Theory]
[InlineData(true)]
[InlineData(false)]
- [SkipOnPlatform(TestPlatforms.Android, "Self-signed certificates are rejected by Android before the .NET validation is reached")]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/68206", TestPlatforms.Android)]
public async Task SslStream_ServerUntrustedCaWithCustomTrust_OK(bool usePartialChain)
{
int split = Random.Shared.Next(0, _certificates.serverChain.Count - 1);
@@ -854,7 +854,7 @@ private async Task SslStream_ClientSendsChain_Core(SslClientAuthenticationOption
}
[Fact]
- [SkipOnPlatform(TestPlatforms.Android, "Self-signed certificates are rejected by Android before the .NET validation is reached")]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/68206", TestPlatforms.Android)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/73862", TestPlatforms.OSX)]
public async Task SslStream_ClientCertificate_SendsChain()
{
@@ -915,7 +915,7 @@ public async Task SslStream_ClientCertificate_SendsChain()
}
[Fact]
- [SkipOnPlatform(TestPlatforms.Android, "Self-signed certificates are rejected by Android before the .NET validation is reached")]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/68206", TestPlatforms.Android)]
public async Task SslStream_ClientCertificateContext_SendsChain()
{
(X509Certificate2 clientCertificate, X509Certificate2Collection clientChain) = TestHelper.GenerateCertificates(nameof(SslStream_ClientCertificateContext_SendsChain), serverCertificate: false);
diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamSniTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamSniTest.cs
index 9d4aa07ff92991..944ab95699cce6 100644
--- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamSniTest.cs
+++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamSniTest.cs
@@ -18,7 +18,7 @@ public class SslStreamSniTest
{
[Theory]
[MemberData(nameof(HostNameData))]
- [SkipOnPlatform(TestPlatforms.Android, "Host name is not sent on Android")]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/68206", TestPlatforms.Android)]
public async Task SslStream_ClientSendsSNIServerReceives_Ok(string hostName)
{
using X509Certificate serverCert = Configuration.Certificates.GetSelfSignedServerCertificate();
@@ -96,7 +96,7 @@ public async Task SslStream_ServerCallbackAndLocalCertificateSelectionSet_Throws
[Theory]
[MemberData(nameof(HostNameData))]
- [SkipOnPlatform(TestPlatforms.Android, "TODO: this test would work with GetServerCertificate(). Is there something wrong with the PEMs?")]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/68206", TestPlatforms.Android)]
public async Task SslStream_ServerCallbackNotSet_UsesLocalCertificateSelection(string hostName)
{
using X509Certificate serverCert = Configuration.Certificates.GetSelfSignedServerCertificate();
diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj b/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj
index ecee9fc385253f..56e1f32c48f0b1 100644
--- a/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj
+++ b/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj
@@ -5,8 +5,6 @@
$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-unix;$(NetCoreAppCurrent)-browser;$(NetCoreAppCurrent)-osx;$(NetCoreAppCurrent)-ios
true
true
-
- true
true
true
diff --git a/src/libraries/System.Net.WebSockets.Client/tests/System.Net.WebSockets.Client.Tests.csproj b/src/libraries/System.Net.WebSockets.Client/tests/System.Net.WebSockets.Client.Tests.csproj
index d022835499355f..690a3d4584485f 100644
--- a/src/libraries/System.Net.WebSockets.Client/tests/System.Net.WebSockets.Client.Tests.csproj
+++ b/src/libraries/System.Net.WebSockets.Client/tests/System.Net.WebSockets.Client.Tests.csproj
@@ -7,8 +7,6 @@
$(DefineConstants);NETSTANDARD
false
-
- true
diff --git a/src/libraries/native-binplace.proj b/src/libraries/native-binplace.proj
index 82427d8f27b01c..867fadd3c4637e 100644
--- a/src/libraries/native-binplace.proj
+++ b/src/libraries/native-binplace.proj
@@ -22,10 +22,12 @@
+
+
-
\ No newline at end of file
+
diff --git a/src/libraries/pretest.proj b/src/libraries/pretest.proj
index bbe56c8bcd5c1f..27c862055389ab 100644
--- a/src/libraries/pretest.proj
+++ b/src/libraries/pretest.proj
@@ -47,6 +47,8 @@
+
+
diff --git a/src/mono/msbuild/android/build/AndroidApp.targets b/src/mono/msbuild/android/build/AndroidApp.targets
index cafe0ea419cad9..7c4545dac726a7 100644
--- a/src/mono/msbuild/android/build/AndroidApp.targets
+++ b/src/mono/msbuild/android/build/AndroidApp.targets
@@ -1,5 +1,5 @@
-
@@ -45,7 +45,7 @@
-
+
@@ -63,7 +63,7 @@
- <_AotInputAssemblies Include="@(_AndroidAssembliesInternal)"
+ <_AotInputAssemblies Include="@(_AndroidAssembliesInternal)"
Condition="'%(_AndroidAssembliesInternal._InternalForceInterpret)' != 'true'">
$(AotArguments)
$(ProcessArguments)
@@ -72,7 +72,7 @@
<_AOT_InternalForceInterpretAssemblies Include="@(_AndroidAssembliesInternal->WithMetadataValue('_InternalForceInterpret', 'true'))" />
<_AndroidAssembliesInternal Remove="@(_AndroidAssembliesInternal)" />
-
+
@@ -132,7 +132,6 @@
MonoRuntimeHeaders="$(MicrosoftNetCoreAppRuntimePackNativeDir)include\mono-2.0"
Assemblies="@(_AndroidAssembliesInternal)"
MainLibraryFileName="$(MainLibraryFileName)"
- IncludeNetworkSecurityConfig="$(IncludeNetworkSecurityConfig)"
EnvironmentVariables="@(AndroidEnv)"
ForceAOT="$(RunAOTCompilation)"
ForceFullAOT="$(ForceFullAOT)"
@@ -152,7 +151,7 @@
-
+
-
\ No newline at end of file
+
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 c4136a588f8565..36a0827e25d13c 100644
--- a/src/native/libs/System.Security.Cryptography.Native.Android/CMakeLists.txt
+++ b/src/native/libs/System.Security.Cryptography.Native.Android/CMakeLists.txt
@@ -24,6 +24,7 @@ set(NATIVECRYPTO_SOURCES
pal_signature.c
pal_ssl.c
pal_sslstream.c
+ pal_trust_manager.c
pal_x509.c
pal_x509chain.c
pal_x509store.c
diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/net/dot/android/crypto/DotnetProxyTrustManager.java b/src/native/libs/System.Security.Cryptography.Native.Android/net/dot/android/crypto/DotnetProxyTrustManager.java
new file mode 100644
index 00000000000000..38454a3d289d14
--- /dev/null
+++ b/src/native/libs/System.Security.Cryptography.Native.Android/net/dot/android/crypto/DotnetProxyTrustManager.java
@@ -0,0 +1,39 @@
+package net.dot.android.crypto;
+
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * This class is meant to replace the built-in X509TrustManager.
+ * Its sole responsibility is to invoke the C# code in the SslStream
+ * class during TLS handshakes to perform the validation of the remote
+ * peer's certificate.
+ */
+public final class DotnetProxyTrustManager implements X509TrustManager {
+ private final long sslStreamProxyHandle;
+
+ public DotnetProxyTrustManager(long sslStreamProxyHandle) {
+ this.sslStreamProxyHandle = sslStreamProxyHandle;
+ }
+
+ public void checkClientTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException {
+ if (!verifyRemoteCertificate(sslStreamProxyHandle)) {
+ throw new CertificateException();
+ }
+ }
+
+ public void checkServerTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException {
+ if (!verifyRemoteCertificate(sslStreamProxyHandle)) {
+ throw new CertificateException();
+ }
+ }
+
+ public X509Certificate[] getAcceptedIssuers() {
+ return new X509Certificate[0];
+ }
+
+ static native boolean verifyRemoteCertificate(long sslStreamProxyHandle);
+}
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 1fdffac63e034f..4455dd62fd4d38 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
@@ -426,8 +426,8 @@ jclass g_SSLEngine;
jmethodID g_SSLEngineBeginHandshake;
jmethodID g_SSLEngineCloseOutbound;
jmethodID g_SSLEngineGetApplicationProtocol;
-jmethodID g_SSLEngineGetHandshakeStatus;
jmethodID g_SSLEngineGetHandshakeSession;
+jmethodID g_SSLEngineGetHandshakeStatus;
jmethodID g_SSLEngineGetSession;
jmethodID g_SSLEngineGetSSLParameters;
jmethodID g_SSLEngineGetSupportedProtocols;
@@ -481,6 +481,13 @@ jmethodID g_KeyAgreementInit;
jmethodID g_KeyAgreementDoPhase;
jmethodID g_KeyAgreementGenerateSecret;
+// javax/net/ssl/TrustManager
+jclass g_TrustManager;
+
+// net/dot/android/crypto/DotnetProxyTrustManager
+jclass g_DotnetProxyTrustManager;
+jmethodID g_DotnetProxyTrustManagerCtor;
+
jobject ToGRef(JNIEnv *env, jobject lref)
{
if (lref)
@@ -1021,9 +1028,9 @@ JNI_OnLoad(JavaVM *vm, void *reserved)
g_SSLEngineBeginHandshake = GetMethod(env, false, g_SSLEngine, "beginHandshake", "()V");
g_SSLEngineCloseOutbound = GetMethod(env, false, g_SSLEngine, "closeOutbound", "()V");
g_SSLEngineGetApplicationProtocol = GetOptionalMethod(env, false, g_SSLEngine, "getApplicationProtocol", "()Ljava/lang/String;");
+ g_SSLEngineGetHandshakeSession = GetOptionalMethod(env, false, g_SSLEngine, "getHandshakeSession", "()Ljavax/net/ssl/SSLSession;");
g_SSLEngineGetHandshakeStatus = GetMethod(env, false, g_SSLEngine, "getHandshakeStatus", "()Ljavax/net/ssl/SSLEngineResult$HandshakeStatus;");
g_SSLEngineGetSession = GetMethod(env, false, g_SSLEngine, "getSession", "()Ljavax/net/ssl/SSLSession;");
- g_SSLEngineGetHandshakeSession = GetOptionalMethod(env, false, g_SSLEngine, "getHandshakeSession", "()Ljavax/net/ssl/SSLSession;");
g_SSLEngineGetSSLParameters = GetMethod(env, false, g_SSLEngine, "getSSLParameters", "()Ljavax/net/ssl/SSLParameters;");
g_SSLEngineGetSupportedProtocols = GetMethod(env, false, g_SSLEngine, "getSupportedProtocols", "()[Ljava/lang/String;");
g_SSLEngineSetEnabledProtocols = GetMethod(env, false, g_SSLEngine, "setEnabledProtocols", "([Ljava/lang/String;)V");
@@ -1071,5 +1078,10 @@ JNI_OnLoad(JavaVM *vm, void *reserved)
g_KeyAgreementDoPhase = GetMethod(env, false, g_KeyAgreementClass, "doPhase", "(Ljava/security/Key;Z)Ljava/security/Key;");
g_KeyAgreementGenerateSecret = GetMethod(env, false, g_KeyAgreementClass, "generateSecret", "()[B");
+ g_TrustManager = GetClassGRef(env, "javax/net/ssl/TrustManager");
+
+ g_DotnetProxyTrustManager = GetClassGRef(env, "net/dot/android/crypto/DotnetProxyTrustManager");
+ g_DotnetProxyTrustManagerCtor = GetMethod(env, false, g_DotnetProxyTrustManager, "", "(J)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 7933a8eac3fa21..341a274ba110ee 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
@@ -440,8 +440,8 @@ extern jclass g_SSLEngine;
extern jmethodID g_SSLEngineBeginHandshake;
extern jmethodID g_SSLEngineCloseOutbound;
extern jmethodID g_SSLEngineGetApplicationProtocol;
-extern jmethodID g_SSLEngineGetHandshakeStatus;
extern jmethodID g_SSLEngineGetHandshakeSession;
+extern jmethodID g_SSLEngineGetHandshakeStatus;
extern jmethodID g_SSLEngineGetSession;
extern jmethodID g_SSLEngineGetSSLParameters;
extern jmethodID g_SSLEngineGetSupportedProtocols;
@@ -495,6 +495,13 @@ extern jmethodID g_KeyAgreementInit;
extern jmethodID g_KeyAgreementDoPhase;
extern jmethodID g_KeyAgreementGenerateSecret;
+// javax/net/ssl/TrustManager
+extern jclass g_TrustManager;
+
+// net/dot/android/crypto/DotnetProxyTrustManager
+extern jclass g_DotnetProxyTrustManager;
+extern jmethodID g_DotnetProxyTrustManagerCtor;
+
// 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 637fa7fa89fe74..090bf1ea1e9c32 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
@@ -3,6 +3,7 @@
#include "pal_sslstream.h"
#include "pal_ssl.h"
+#include "pal_trust_manager.h"
// javax/net/ssl/SSLEngineResult$HandshakeStatus
enum
@@ -46,14 +47,27 @@ ARGS_NON_NULL_ALL static PAL_SSLStreamStatus DoHandshake(JNIEnv* env, SSLStream*
ARGS_NON_NULL_ALL static PAL_SSLStreamStatus DoWrap(JNIEnv* env, SSLStream* sslStream, int* handshakeStatus);
ARGS_NON_NULL_ALL static PAL_SSLStreamStatus DoUnwrap(JNIEnv* env, SSLStream* sslStream, int* handshakeStatus);
+ARGS_NON_NULL_ALL static int GetHandshakeStatus(JNIEnv* env, SSLStream* sslStream)
+{
+ // int handshakeStatus = sslEngine.getHandshakeStatus().ordinal();
+ int handshakeStatus = GetEnumAsInt(env, (*env)->CallObjectMethod(env, sslStream->sslEngine, g_SSLEngineGetHandshakeStatus));
+ if (CheckJNIExceptions(env))
+ return -1;
+
+ return handshakeStatus;
+}
+
static bool IsHandshaking(int handshakeStatus)
{
return handshakeStatus != HANDSHAKE_STATUS__NOT_HANDSHAKING && handshakeStatus != HANDSHAKE_STATUS__FINISHED;
}
-static jobject GetSslSessionForHandshakeStatus(JNIEnv* env, SSLStream* sslStream, int handshakeStatus)
+ARGS_NON_NULL(1, 2) static jobject GetSslSession(JNIEnv* env, SSLStream* sslStream, int handshakeStatus)
{
- // SSLEngine.getHandshakeSession() is available since API 24
+ // During the initial handshake our sslStream->sslSession doesn't have access to the peer certificates
+ // which we need for hostname verification. Luckily, the SSLEngine has a getter for the handshake SSLSession.
+ // SSLEngine.getHandshakeSession() is available since API 24.
+
jobject sslSession = IsHandshaking(handshakeStatus) && g_SSLEngineGetHandshakeSession != NULL
? (*env)->CallObjectMethod(env, sslStream->sslEngine, g_SSLEngineGetHandshakeSession)
: (*env)->CallObjectMethod(env, sslStream->sslEngine, g_SSLEngineGetSession);
@@ -63,13 +77,13 @@ static jobject GetSslSessionForHandshakeStatus(JNIEnv* env, SSLStream* sslStream
return sslSession;
}
-static jobject GetCurrentSslSession(JNIEnv* env, SSLStream* sslStream)
+ARGS_NON_NULL_ALL static jobject GetCurrentSslSession(JNIEnv* env, SSLStream* sslStream)
{
- int handshakeStatus = GetEnumAsInt(env, (*env)->CallObjectMethod(env, sslStream->sslEngine, g_SSLEngineGetHandshakeStatus));
- if (CheckJNIExceptions(env))
+ int handshakeStatus = GetHandshakeStatus(env, sslStream);
+ if (handshakeStatus == -1)
return NULL;
- return GetSslSessionForHandshakeStatus(env, sslStream, handshakeStatus);
+ return GetSslSession(env, sslStream, handshakeStatus);
}
ARGS_NON_NULL_ALL static PAL_SSLStreamStatus Close(JNIEnv* env, SSLStream* sslStream)
@@ -149,8 +163,10 @@ ARGS_NON_NULL_ALL static jobject EnsureRemaining(JNIEnv* env, jobject oldBuffer,
// There has been a change in the SSLEngineResult.Status enum between API 23 and 24 that changed
// the order/interger values of the enum options.
-static int MapLegacySSLEngineResultStatus(int legacyStatus) {
- switch (legacyStatus) {
+static int MapLegacySSLEngineResultStatus(int legacyStatus)
+{
+ switch (legacyStatus)
+ {
case LEGACY__STATUS__BUFFER_OVERFLOW:
return STATUS__BUFFER_OVERFLOW;
case LEGACY__STATUS__BUFFER_UNDERFLOW:
@@ -185,7 +201,8 @@ ARGS_NON_NULL_ALL static PAL_SSLStreamStatus DoWrap(JNIEnv* env, SSLStream* sslS
int status = GetEnumAsInt(env, (*env)->CallObjectMethod(env, result, g_SSLEngineResultGetStatus));
(*env)->DeleteLocalRef(env, result);
- if (g_SSLEngineResultStatusLegacyOrder) {
+ if (g_SSLEngineResultStatusLegacyOrder)
+ {
status = MapLegacySSLEngineResultStatus(status);
}
@@ -236,6 +253,7 @@ ARGS_NON_NULL_ALL static PAL_SSLStreamStatus DoUnwrap(JNIEnv* env, SSLStream* ss
PAL_SSLStreamStatus status = sslStream->streamReader(sslStream->managedContextHandle, tmpNative, &count);
if (status != SSLStreamStatus_OK)
{
+ free(tmpNative);
(*env)->DeleteLocalRef(env, tmp);
return status;
}
@@ -264,7 +282,8 @@ ARGS_NON_NULL_ALL static PAL_SSLStreamStatus DoUnwrap(JNIEnv* env, SSLStream* ss
int status = GetEnumAsInt(env, (*env)->CallObjectMethod(env, result, g_SSLEngineResultGetStatus));
(*env)->DeleteLocalRef(env, result);
- if (g_SSLEngineResultStatusLegacyOrder) {
+ if (g_SSLEngineResultStatusLegacyOrder)
+ {
status = MapLegacySSLEngineResultStatus(status);
}
@@ -307,8 +326,9 @@ ARGS_NON_NULL_ALL static PAL_SSLStreamStatus DoUnwrap(JNIEnv* env, SSLStream* ss
ARGS_NON_NULL_ALL static PAL_SSLStreamStatus DoHandshake(JNIEnv* env, SSLStream* sslStream)
{
PAL_SSLStreamStatus status = SSLStreamStatus_OK;
- int handshakeStatus =
- GetEnumAsInt(env, (*env)->CallObjectMethod(env, sslStream->sslEngine, g_SSLEngineGetHandshakeStatus));
+ int handshakeStatus = GetHandshakeStatus(env, sslStream);
+ assert(handshakeStatus >= 0);
+
while (IsHandshaking(handshakeStatus) && status == SSLStreamStatus_OK)
{
switch (handshakeStatus)
@@ -343,17 +363,79 @@ ARGS_NON_NULL_ALL static void FreeSSLStream(JNIEnv* env, SSLStream* sslStream)
free(sslStream);
}
-SSLStream* AndroidCryptoNative_SSLStreamCreate(void)
+ARGS_NON_NULL_ALL static jobject GetSSLContextInstance(JNIEnv* env)
+{
+ jobject sslContext = NULL;
+
+ // sslContext = SSLContext.getInstance("TLSv1.3");
+ jstring tls13 = make_java_string(env, "TLSv1.3");
+ sslContext = (*env)->CallStaticObjectMethod(env, g_SSLContext, g_SSLContextGetInstanceMethod, tls13);
+ if (TryClearJNIExceptions(env))
+ {
+ // TLSv1.3 is only supported on API level 29+ - fall back to TLSv1.2 (which is supported on API level 16+)
+ // sslContext = SSLContext.getInstance("TLSv1.2");
+ jstring tls12 = make_java_string(env, "TLSv1.2");
+ sslContext = (*env)->CallStaticObjectMethod(env, g_SSLContext, g_SSLContextGetInstanceMethod, tls12);
+ ReleaseLRef(env, tls12);
+ ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
+ }
+
+cleanup:
+ ReleaseLRef(env, tls13);
+ return sslContext;
+}
+
+ARGS_NON_NULL_ALL static jobject GetKeyStoreInstance(JNIEnv* env)
+{
+ jobject keyStore = NULL;
+ jstring ksType = NULL;
+
+ // String ksType = KeyStore.getDefaultType();
+ // KeyStore keyStore = KeyStore.getInstance(ksType);
+ // keyStore.load(null, null);
+ // return keyStore;
+
+ ksType = (*env)->CallStaticObjectMethod(env, g_KeyStoreClass, g_KeyStoreGetDefaultType);
+ ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
+
+ keyStore = (*env)->CallStaticObjectMethod(env, g_KeyStoreClass, g_KeyStoreGetInstance, ksType);
+ ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
+
+ (*env)->CallVoidMethod(env, keyStore, g_KeyStoreLoad, NULL, NULL);
+ ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
+
+cleanup:
+ ReleaseLRef(env, ksType);
+ return keyStore;
+}
+
+SSLStream* AndroidCryptoNative_SSLStreamCreate(intptr_t sslStreamProxyHandle)
{
+ abort_unless(sslStreamProxyHandle != 0, "invalid pointer to the .NET SslStream proxy");
+
+ SSLStream* sslStream = NULL;
JNIEnv* env = GetJNIEnv();
- // SSLContext sslContext = SSLContext.getDefault();
- jobject sslContext = (*env)->CallStaticObjectMethod(env, g_SSLContext, g_SSLContextGetDefault);
- if (CheckJNIExceptions(env))
- return NULL;
+ INIT_LOCALS(loc, sslContext, trustManagers);
+
+ loc[sslContext] = GetSSLContextInstance(env);
+ if (!loc[sslContext])
+ goto cleanup;
+
+ loc[trustManagers] = GetTrustManagers(env, sslStreamProxyHandle);
+ if (!loc[trustManagers])
+ goto cleanup;
+
+ // sslContext.init(null, trustManagers, null);
+ (*env)->CallVoidMethod(env, loc[sslContext], g_SSLContextInitMethod, NULL, loc[trustManagers], NULL);
+ ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
- SSLStream* sslStream = xcalloc(1, sizeof(SSLStream));
- sslStream->sslContext = ToGRef(env, sslContext);
+ sslStream = xcalloc(1, sizeof(SSLStream));
+ sslStream->sslContext = ToGRef(env, loc[sslContext]);
+ loc[sslContext] = NULL;
+
+cleanup:
+ RELEASE_LOCALS(loc, env);
return sslStream;
}
@@ -420,38 +502,27 @@ static int32_t AddCertChainToStore(JNIEnv* env,
return ret;
}
-SSLStream* AndroidCryptoNative_SSLStreamCreateWithCertificates(uint8_t* pkcs8PrivateKey,
+SSLStream* AndroidCryptoNative_SSLStreamCreateWithCertificates(intptr_t sslStreamProxyHandle,
+ uint8_t* pkcs8PrivateKey,
int32_t pkcs8PrivateKeyLen,
PAL_KeyAlgorithm algorithm,
jobject* /*X509Certificate[]*/ certs,
int32_t certsLen)
{
+ abort_unless(sslStreamProxyHandle != 0, "invalid pointer to the .NET SslStream proxy");
+
SSLStream* sslStream = NULL;
JNIEnv* env = GetJNIEnv();
- INIT_LOCALS(loc, tls13, sslContext, ksType, keyStore, kmfType, kmf, keyManagers);
+ INIT_LOCALS(loc, sslContext, keyStore, kmfType, kmf, keyManagers, trustManagers);
- // SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
- loc[tls13] = make_java_string(env, "TLSv1.3");
- loc[sslContext] = (*env)->CallStaticObjectMethod(env, g_SSLContext, g_SSLContextGetInstanceMethod, loc[tls13]);
- if (TryClearJNIExceptions(env))
- {
- // TLSv1.3 is only supported on API level 29+ - fall back to TLSv1.2 (which is supported on API level 16+)
- // sslContext = SSLContext.getInstance("TLSv1.2");
- jobject tls12 = make_java_string(env, "TLSv1.2");
- loc[sslContext] = (*env)->CallStaticObjectMethod(env, g_SSLContext, g_SSLContextGetInstanceMethod, tls12);
- ReleaseLRef(env, tls12);
- ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
- }
+ loc[sslContext] = GetSSLContextInstance(env);
+ if (!loc[sslContext])
+ goto cleanup;
- // String ksType = KeyStore.getDefaultType();
- // KeyStore keyStore = KeyStore.getInstance(ksType);
- // keyStore.load(null, null);
- loc[ksType] = (*env)->CallStaticObjectMethod(env, g_KeyStoreClass, g_KeyStoreGetDefaultType);
- loc[keyStore] = (*env)->CallStaticObjectMethod(env, g_KeyStoreClass, g_KeyStoreGetInstance, loc[ksType]);
- ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
- (*env)->CallVoidMethod(env, loc[keyStore], g_KeyStoreLoad, NULL, NULL);
- ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
+ loc[keyStore] = GetKeyStoreInstance(env);
+ if (!loc[keyStore])
+ goto cleanup;
int32_t status =
AddCertChainToStore(env, loc[keyStore], pkcs8PrivateKey, pkcs8PrivateKeyLen, algorithm, certs, certsLen);
@@ -469,10 +540,16 @@ SSLStream* AndroidCryptoNative_SSLStreamCreateWithCertificates(uint8_t* pkcs8Pri
ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
// KeyManager[] keyManagers = kmf.getKeyManagers();
- // sslContext.init(keyManagers, null, null);
loc[keyManagers] = (*env)->CallObjectMethod(env, loc[kmf], g_KeyManagerFactoryGetKeyManagers);
ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
- (*env)->CallVoidMethod(env, loc[sslContext], g_SSLContextInitMethod, loc[keyManagers], NULL, NULL);
+
+ // 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));
@@ -521,8 +598,7 @@ int32_t AndroidCryptoNative_SSLStreamInitialize(
// int applicationBufferSize = sslSession.getApplicationBufferSize();
// int packetBufferSize = sslSession.getPacketBufferSize();
- int32_t applicationBufferSize =
- (*env)->CallIntMethod(env, sslStream->sslSession, g_SSLSessionGetApplicationBufferSize);
+ int32_t applicationBufferSize = (*env)->CallIntMethod(env, sslStream->sslSession, g_SSLSessionGetApplicationBufferSize);
int32_t packetBufferSize = (*env)->CallIntMethod(env, sslStream->sslSession, g_SSLSessionGetPacketBufferSize);
// ByteBuffer appInBuffer = ByteBuffer.allocate(Math.max(applicationBufferSize, appBufferSize));
@@ -625,8 +701,12 @@ PAL_SSLStreamStatus AndroidCryptoNative_SSLStreamHandshake(SSLStream* sslStream)
abort_if_invalid_pointer_argument (sslStream);
JNIEnv* env = GetJNIEnv();
- int handshakeStatus = GetEnumAsInt(env, (*env)->CallObjectMethod(env, sslStream->sslEngine, g_SSLEngineGetHandshakeStatus));
- if (!IsHandshaking(handshakeStatus)) {
+ int handshakeStatus = GetHandshakeStatus(env, sslStream);
+ if (handshakeStatus == -1)
+ return SSLStreamStatus_Error;
+
+ if (!IsHandshaking(handshakeStatus))
+ {
// sslEngine.beginHandshake();
(*env)->CallVoidMethod(env, sslStream->sslEngine, g_SSLEngineBeginHandshake);
if (CheckJNIExceptions(env))
@@ -808,18 +888,21 @@ int32_t AndroidCryptoNative_SSLStreamGetCipherSuite(SSLStream* sslStream, uint16
JNIEnv* env = GetJNIEnv();
int32_t ret = FAIL;
*out = NULL;
+ INIT_LOCALS(loc, sslSession, cipherSuite);
+
+ loc[sslSession] = GetCurrentSslSession(env, sslStream);
+ if (loc[sslSession] == NULL)
+ goto cleanup;
// String cipherSuite = sslSession.getCipherSuite();
- jobject sslSession = GetCurrentSslSession(env, sslStream);
- jstring cipherSuite = (*env)->CallObjectMethod(env, sslSession, g_SSLSessionGetCipherSuite);
+ loc[cipherSuite] = (*env)->CallObjectMethod(env, loc[sslSession], g_SSLSessionGetCipherSuite);
ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
- *out = AllocateString(env, cipherSuite);
+ *out = AllocateString(env, loc[cipherSuite]);
ret = SUCCESS;
cleanup:
- ReleaseLRef(env, sslSession);
- ReleaseLRef(env, cipherSuite);
+ RELEASE_LOCALS(loc, env);
return ret;
}
@@ -831,21 +914,43 @@ int32_t AndroidCryptoNative_SSLStreamGetProtocol(SSLStream* sslStream, uint16_t*
JNIEnv* env = GetJNIEnv();
int32_t ret = FAIL;
*out = NULL;
+ INIT_LOCALS(loc, sslSession, protocol);
+
+ loc[sslSession] = GetCurrentSslSession(env, sslStream);
+ if (loc[sslSession] == NULL)
+ goto cleanup;
// String protocol = sslSession.getProtocol();
- jobject sslSession = GetCurrentSslSession(env, sslStream);
- jstring protocol = (*env)->CallObjectMethod(env, sslSession, g_SSLSessionGetProtocol);
+ loc[protocol] = (*env)->CallObjectMethod(env, loc[sslSession], g_SSLSessionGetProtocol);
ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
- *out = AllocateString(env, protocol);
+ *out = AllocateString(env, loc[protocol]);
ret = SUCCESS;
cleanup:
- ReleaseLRef(env, sslSession);
- ReleaseLRef(env, protocol);
+ RELEASE_LOCALS(loc, env);
return ret;
}
+ARGS_NON_NULL_ALL static jobject GetPeerCertificates(JNIEnv* env, SSLStream* sslStream)
+{
+ jobject certificates = NULL;
+ INIT_LOCALS(loc, sslSession);
+
+ loc[sslSession] = GetCurrentSslSession(env, sslStream);
+ if (loc[sslSession] == NULL)
+ goto cleanup;
+
+ // Certificate[] certificates = sslSession.getPeerCertificates();
+ certificates = (*env)->CallObjectMethod(env, loc[sslSession], g_SSLSessionGetPeerCertificates);
+ // If there are no peer certificates, getPeerCertificates will throw. Return null to indicate no certificates.
+ ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
+
+cleanup:
+ RELEASE_LOCALS(loc, env);
+ return certificates;
+}
+
jobject /*X509Certificate*/ AndroidCryptoNative_SSLStreamGetPeerCertificate(SSLStream* sslStream)
{
abort_if_invalid_pointer_argument (sslStream);
@@ -853,15 +958,11 @@ jobject /*X509Certificate*/ AndroidCryptoNative_SSLStreamGetPeerCertificate(SSLS
JNIEnv* env = GetJNIEnv();
jobject ret = NULL;
- // Certificate[] certs = sslSession.getPeerCertificates();
- // out = certs[0];
- jobject sslSession = GetCurrentSslSession(env, sslStream);
- jobjectArray certs = (*env)->CallObjectMethod(env, sslSession, g_SSLSessionGetPeerCertificates);
-
- // If there are no peer certificates, getPeerCertificates will throw. Return null to indicate no certificate.
- if (TryClearJNIExceptions(env))
+ jobject certs = GetPeerCertificates(env, sslStream);
+ if (certs == NULL)
goto cleanup;
+ // out = certs[0];
jsize len = (*env)->GetArrayLength(env, certs);
if (len > 0)
{
@@ -871,7 +972,6 @@ jobject /*X509Certificate*/ AndroidCryptoNative_SSLStreamGetPeerCertificate(SSLS
}
cleanup:
- ReleaseLRef(env, sslSession);
ReleaseLRef(env, certs);
return ret;
}
@@ -886,17 +986,13 @@ void AndroidCryptoNative_SSLStreamGetPeerCertificates(SSLStream* sslStream, jobj
*out = NULL;
*outLen = 0;
- // Certificate[] certs = sslSession.getPeerCertificates();
+ jobjectArray certs = GetPeerCertificates(env, sslStream);
+ if (certs == NULL)
+ goto cleanup;
+
// for (int i = 0; i < certs.length; i++) {
// out[i] = certs[i];
// }
- jobject sslSession = GetCurrentSslSession(env, sslStream);
- jobjectArray certs = (*env)->CallObjectMethod(env, sslSession, g_SSLSessionGetPeerCertificates);
-
- // If there are no peer certificates, getPeerCertificates will throw. Return null and length of zero to indicate no certificates.
- if (TryClearJNIExceptions(env))
- goto cleanup;
-
jsize len = (*env)->GetArrayLength(env, certs);
*outLen = len;
if (len > 0)
@@ -910,7 +1006,6 @@ void AndroidCryptoNative_SSLStreamGetPeerCertificates(SSLStream* sslStream, jobj
}
cleanup:
- ReleaseLRef(env, sslSession);
ReleaseLRef(env, certs);
}
@@ -930,7 +1025,8 @@ int32_t AndroidCryptoNative_SSLStreamSetApplicationProtocols(SSLStream* sslStrea
abort_if_invalid_pointer_argument (sslStream);
abort_if_invalid_pointer_argument (protocolData);
- if (!AndroidCryptoNative_SSLSupportsApplicationProtocolsConfiguration()) {
+ if (!AndroidCryptoNative_SSLSupportsApplicationProtocolsConfiguration())
+ {
LOG_ERROR ("SSL does not support application protocols configuration");
return FAIL;
}
@@ -1027,14 +1123,20 @@ bool AndroidCryptoNative_SSLStreamVerifyHostname(SSLStream* sslStream, char* hos
bool ret = false;
INIT_LOCALS(loc, name, verifier, sslSession);
+ loc[sslSession] = GetCurrentSslSession(env, sslStream);
+ if (loc[sslSession] == NULL)
+ goto cleanup;
+
// HostnameVerifier verifier = HttpsURLConnection.getDefaultHostnameVerifier();
- // return verifier.verify(hostname, sslSession);
loc[name] = make_java_string(env, hostname);
- loc[sslSession] = GetCurrentSslSession(env, sslStream);
- loc[verifier] =
- (*env)->CallStaticObjectMethod(env, g_HttpsURLConnection, g_HttpsURLConnectionGetDefaultHostnameVerifier);
+ loc[verifier] = (*env)->CallStaticObjectMethod(env, g_HttpsURLConnection, g_HttpsURLConnectionGetDefaultHostnameVerifier);
+ ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
+
+ // return verifier.verify(hostname, sslSession);
ret = (*env)->CallBooleanMethod(env, loc[verifier], g_HostnameVerifierVerify, loc[name], loc[sslSession]);
+ ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
+cleanup:
RELEASE_LOCALS(loc, env);
return ret;
}
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 51c692440337e9..fa3a884d68adfa 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
@@ -44,14 +44,15 @@ Create an SSL context
Returns NULL on failure
*/
-PALEXPORT SSLStream* AndroidCryptoNative_SSLStreamCreate(void);
+PALEXPORT SSLStream* AndroidCryptoNative_SSLStreamCreate(intptr_t sslStreamProxyHandle);
/*
Create an SSL context with the specified certificates
Returns NULL on failure
*/
-PALEXPORT SSLStream* AndroidCryptoNative_SSLStreamCreateWithCertificates(uint8_t* pkcs8PrivateKey,
+PALEXPORT SSLStream* AndroidCryptoNative_SSLStreamCreateWithCertificates(intptr_t sslStreamProxyHandle,
+ uint8_t* pkcs8PrivateKey,
int32_t pkcs8PrivateKeyLen,
PAL_KeyAlgorithm algorithm,
jobject* /*X509Certificate[]*/ certs,
diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_trust_manager.c b/src/native/libs/System.Security.Cryptography.Native.Android/pal_trust_manager.c
new file mode 100644
index 00000000000000..c0097c9f3b998a
--- /dev/null
+++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_trust_manager.c
@@ -0,0 +1,36 @@
+#include "pal_trust_manager.h"
+
+static RemoteCertificateValidationCallback verifyRemoteCertificate;
+
+ARGS_NON_NULL_ALL void AndroidCryptoNative_RegisterRemoteCertificateValidationCallback(RemoteCertificateValidationCallback callback)
+{
+ abort_unless(verifyRemoteCertificate == NULL, "AndroidCryptoNative_RegisterRemoteCertificateValidationCallback can only be used once");
+ verifyRemoteCertificate = callback;
+}
+
+ARGS_NON_NULL_ALL jobjectArray GetTrustManagers(JNIEnv* env, intptr_t sslStreamProxyHandle)
+{
+ // X509TrustManager dotnetProxyTrustManager = new DotnetProxyTrustManager(sslStreamProxyHandle);
+ // TrustManager[] trustManagers = new TrustManager[] { dotnetProxyTrustManager };
+ // return trustManagers;
+
+ jobjectArray trustManagers = NULL;
+ INIT_LOCALS(loc, dotnetProxyTrustManager);
+
+ loc[dotnetProxyTrustManager] = (*env)->NewObject(env, g_DotnetProxyTrustManager, g_DotnetProxyTrustManagerCtor, (jlong)sslStreamProxyHandle);
+ ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
+
+ trustManagers = make_java_object_array(env, 1, g_TrustManager, loc[dotnetProxyTrustManager]);
+ ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
+
+cleanup:
+ RELEASE_LOCALS(loc, env);
+ return trustManagers;
+}
+
+ARGS_NON_NULL_ALL jboolean Java_net_dot_android_crypto_DotnetProxyTrustManager_verifyRemoteCertificate(
+ JNIEnv* env, jobject thisHandle, jlong sslStreamProxyHandle)
+{
+ abort_unless(verifyRemoteCertificate, "verifyRemoteCertificate callback has not been registered");
+ return verifyRemoteCertificate((intptr_t)sslStreamProxyHandle);
+}
diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_trust_manager.h b/src/native/libs/System.Security.Cryptography.Native.Android/pal_trust_manager.h
new file mode 100644
index 00000000000000..e4f09118492327
--- /dev/null
+++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_trust_manager.h
@@ -0,0 +1,10 @@
+#include "pal_jni.h"
+
+typedef bool (*RemoteCertificateValidationCallback)(intptr_t);
+
+PALEXPORT void AndroidCryptoNative_RegisterRemoteCertificateValidationCallback(RemoteCertificateValidationCallback callback);
+
+jobjectArray GetTrustManagers(JNIEnv* env, intptr_t sslStreamProxyHandle);
+
+JNIEXPORT jboolean JNICALL Java_net_dot_android_crypto_DotnetProxyTrustManager_verifyRemoteCertificate(
+ JNIEnv *env, jobject thisHandle, jlong sslStreamProxyHandle);
diff --git a/src/native/libs/build-native.proj b/src/native/libs/build-native.proj
index 00ac5897cc1c73..7199673717bc33 100644
--- a/src/native/libs/build-native.proj
+++ b/src/native/libs/build-native.proj
@@ -6,7 +6,8 @@
.NET Runtime
<_BuildNativeTargetOS>$(TargetOS)
<_BuildNativeTargetOS Condition="'$(TargetsLinuxBionic)' == 'true'">linux-bionic
- <_BuildNativeArgs>$(TargetArchitecture) $(Configuration) outconfig $(NetCoreAppCurrent)-$(TargetOS)-$(Configuration)-$(TargetArchitecture) -os $(_BuildNativeTargetOS)
+ <_BuildNativeOutConfig>$(NetCoreAppCurrent)-$(TargetOS)-$(Configuration)-$(TargetArchitecture)
+ <_BuildNativeArgs>$(TargetArchitecture) $(Configuration) outconfig $(_BuildNativeOutConfig) -os $(_BuildNativeTargetOS)
<_BuildNativeArgs Condition="'$(OfficialBuildId)' != ''">$(_BuildNativeArgs) /p:OfficialBuildId="$(OfficialBuildId)"
@@ -53,4 +54,17 @@
+
+
+
+
+
+
diff --git a/src/tasks/AndroidAppBuilder/AndroidAppBuilder.cs b/src/tasks/AndroidAppBuilder/AndroidAppBuilder.cs
index 0f409c1745f7ca..bf10dc962c942b 100644
--- a/src/tasks/AndroidAppBuilder/AndroidAppBuilder.cs
+++ b/src/tasks/AndroidAppBuilder/AndroidAppBuilder.cs
@@ -85,8 +85,6 @@ public class AndroidAppBuilderTask : Task
///
public string? NativeMainSource { get; set; }
- public bool IncludeNetworkSecurityConfig { get; set; }
-
public string? KeyStorePath { get; set; }
public bool ForceInterpreter { get; set; }
@@ -112,7 +110,6 @@ public override bool Execute()
apkBuilder.BuildToolsVersion = BuildToolsVersion;
apkBuilder.StripDebugSymbols = StripDebugSymbols;
apkBuilder.NativeMainSource = NativeMainSource;
- apkBuilder.IncludeNetworkSecurityConfig = IncludeNetworkSecurityConfig;
apkBuilder.KeyStorePath = KeyStorePath;
apkBuilder.ForceInterpreter = ForceInterpreter;
apkBuilder.ForceAOT = ForceAOT;
diff --git a/src/tasks/AndroidAppBuilder/AndroidAppBuilder.csproj b/src/tasks/AndroidAppBuilder/AndroidAppBuilder.csproj
index 139d5672ee01a0..3dd38caa3c8809 100644
--- a/src/tasks/AndroidAppBuilder/AndroidAppBuilder.csproj
+++ b/src/tasks/AndroidAppBuilder/AndroidAppBuilder.csproj
@@ -17,6 +17,11 @@
+
+
+
+
+
diff --git a/src/tasks/AndroidAppBuilder/AndroidLibBuilderTask.cs b/src/tasks/AndroidAppBuilder/AndroidLibBuilderTask.cs
new file mode 100644
index 00000000000000..81d9062c6f0eae
--- /dev/null
+++ b/src/tasks/AndroidAppBuilder/AndroidLibBuilderTask.cs
@@ -0,0 +1,80 @@
+// 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.IO;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+public class AndroidLibBuilderTask : Task
+{
+ [Required]
+ public string JavaSourceDirectory { get; set; } = ""!;
+
+ [Required]
+ public string OutputDir { get; set; } = ""!;
+
+ [Required]
+ public string DexFileName { get; set; } = ""!;
+
+ [Required]
+ public string JarFileName { get; set; } = ""!;
+
+ public string? AndroidSdk { get; set; }
+
+ public string? BuildApiLevel { get; set; }
+
+ public string? BuildToolsVersion { get; set; }
+
+ public override bool Execute()
+ {
+ var androidSdk = new AndroidSdkHelper(
+ androidSdkPath: AndroidSdk,
+ buildApiLevel: BuildApiLevel,
+ buildToolsVersion: BuildToolsVersion);
+
+ var objDir = Path.Combine(OutputDir, "obj");
+ Directory.CreateDirectory(objDir);
+
+ try
+ {
+ CompileJava(objDir, androidSdk);
+ BuildJar(objDir);
+ BuildDex(objDir, androidSdk);
+
+ return true;
+ }
+ finally
+ {
+ Directory.Delete(objDir, recursive: true);
+ }
+ }
+
+ private void CompileJava(string objDir, AndroidSdkHelper androidSdk)
+ {
+ var compiler = new JavaCompiler(Log, androidSdk, workingDir: JavaSourceDirectory);
+ string[] javaFiles = Directory.GetFiles(JavaSourceDirectory, "*.java", SearchOption.AllDirectories);
+ foreach (var file in javaFiles)
+ {
+ compiler.Compile(file, outputDir: objDir);
+ }
+ }
+
+ private void BuildJar(string objDir)
+ {
+ var jarBuilder = new JarBuilder(Log);
+ var jarFilePath = Path.Combine(OutputDir, JarFileName);
+ jarBuilder.Build(inputDir: objDir, outputFileName: jarFilePath);
+
+ Log.LogMessage(MessageImportance.High, $"Built {jarFilePath}");
+ }
+
+ private void BuildDex(string objDir, AndroidSdkHelper androidSdk)
+ {
+ var dexBuilder = new DexBuilder(Log, androidSdk, workingDir: OutputDir);
+ var dexFilePath = Path.Combine(OutputDir, DexFileName);
+ dexBuilder.Build(inputDir: objDir, outputFileName: dexFilePath);
+
+ Log.LogMessage(MessageImportance.High, $"Built {dexFilePath}");
+ }
+}
diff --git a/src/tasks/AndroidAppBuilder/ApkBuilder.cs b/src/tasks/AndroidAppBuilder/ApkBuilder.cs
index 4cfc8d0d1cac9b..ba1b7b3a8cdf0e 100644
--- a/src/tasks/AndroidAppBuilder/ApkBuilder.cs
+++ b/src/tasks/AndroidAppBuilder/ApkBuilder.cs
@@ -24,7 +24,6 @@ public class ApkBuilder
public string OutputDir { get; set; } = ""!;
public bool StripDebugSymbols { get; set; }
public string? NativeMainSource { get; set; }
- public bool IncludeNetworkSecurityConfig { get; set; }
public string? KeyStorePath { get; set; }
public bool ForceInterpreter { get; set; }
public bool ForceAOT { get; set; }
@@ -59,12 +58,6 @@ public ApkBuilder(TaskLoggingHelper logger)
throw new ArgumentException($"MainLibraryFileName='{mainLibraryFileName}' was not found in AppDir='{AppDir}'");
}
- var networkSecurityConfigFilePath = Path.Combine(AppDir, "res", "xml", "network_security_config.xml");
- if (IncludeNetworkSecurityConfig && !File.Exists(networkSecurityConfigFilePath))
- {
- throw new ArgumentException($"IncludeNetworkSecurityConfig is set but the file '{networkSecurityConfigFilePath}' was not found");
- }
-
if (string.IsNullOrEmpty(abi))
{
throw new ArgumentException("abi should not be empty (e.g. x86, x86_64, armeabi-v7a or arm64-v8a");
@@ -180,9 +173,8 @@ public ApkBuilder(TaskLoggingHelper logger)
Directory.CreateDirectory(Path.Combine(OutputDir, "obj"));
Directory.CreateDirectory(Path.Combine(OutputDir, "assets-tozip"));
Directory.CreateDirectory(Path.Combine(OutputDir, "assets"));
- Directory.CreateDirectory(Path.Combine(OutputDir, "res"));
- var extensionsToIgnore = new List { ".so", ".a" };
+ var extensionsToIgnore = new List { ".so", ".a", ".dex", ".jar" };
if (StripDebugSymbols)
{
extensionsToIgnore.Add(".pdb");
@@ -209,20 +201,9 @@ public ApkBuilder(TaskLoggingHelper logger)
// aapt complains on such files
return false;
}
- if (file.Contains("/res/"))
- {
- // exclude everything in the `res` folder
- return false;
- }
return true;
});
- // copy the res directory as is
- if (Directory.Exists(Path.Combine(AppDir, "res")))
- {
- Utils.DirectoryCopy(Path.Combine(AppDir, "res"), Path.Combine(OutputDir, "res"));
- }
-
// add AOT .so libraries
foreach (var aotlib in aotLibraryFiles)
{
@@ -247,7 +228,7 @@ public ApkBuilder(TaskLoggingHelper logger)
if (!File.Exists(androidJar))
throw new ArgumentException($"API level={BuildApiLevel} is not downloaded in Android SDK");
- // 1. Build libmonodroid.so` via cmake
+ // 1. Build libmonodroid.so via cmake
string nativeLibraries = "";
string monoRuntimeLib = "";
@@ -399,11 +380,6 @@ public ApkBuilder(TaskLoggingHelper logger)
if (!string.IsNullOrEmpty(NativeMainSource))
File.Copy(NativeMainSource, javaActivityPath, true);
- string networkSecurityConfigAttribute =
- IncludeNetworkSecurityConfig
- ? "a:networkSecurityConfig=\"@xml/network_security_config\""
- : string.Empty;
-
string envVariables = "";
foreach (ITaskItem item in EnvironmentVariables)
{
@@ -421,7 +397,6 @@ public ApkBuilder(TaskLoggingHelper logger)
File.WriteAllText(Path.Combine(OutputDir, "AndroidManifest.xml"),
Utils.GetEmbeddedResource("AndroidManifest.xml")
.Replace("%PackageName%", packageId)
- .Replace("%NetworkSecurityConfig%", networkSecurityConfigAttribute)
.Replace("%MinSdkLevel%", MinApiLevel));
string javaCompilerArgs = $"-d obj -classpath src -bootclasspath {androidJar} -source 1.8 -target 1.8 ";
@@ -446,8 +421,7 @@ public ApkBuilder(TaskLoggingHelper logger)
string debugModeArg = StripDebugSymbols ? string.Empty : "--debug-mode";
string apkFile = Path.Combine(OutputDir, "bin", $"{ProjectName}.unaligned.apk");
- string resources = IncludeNetworkSecurityConfig ? "-S res" : string.Empty;
- Utils.RunProcess(logger, aapt, $"package -f -m -F {apkFile} -A assets {resources} -M AndroidManifest.xml -I {androidJar} {debugModeArg}", workingDir: OutputDir);
+ Utils.RunProcess(logger, aapt, $"package -f -m -F {apkFile} -A assets -M AndroidManifest.xml -I {androidJar} {debugModeArg}", workingDir: OutputDir);
var dynamicLibs = new List();
dynamicLibs.Add(Path.Combine(OutputDir, "monodroid", "libmonodroid.so"));
@@ -507,6 +481,17 @@ public ApkBuilder(TaskLoggingHelper logger)
}
Utils.RunProcess(logger, aapt, $"add {apkFile} classes.dex", workingDir: OutputDir);
+ // Include prebuilt .dex files
+ int sequence = 2;
+ var dexFiles = Directory.GetFiles(AppDir, "*.dex");
+ foreach (var dexFile in dexFiles)
+ {
+ var classesFileName = $"classes{sequence++}.dex";
+ File.Copy(dexFile, Path.Combine(OutputDir, classesFileName));
+ logger.LogMessage(MessageImportance.High, $"Adding dex file {Path.GetFileName(dexFile)} as {classesFileName}");
+ Utils.RunProcess(logger, aapt, $"add {apkFile} {classesFileName}", workingDir: OutputDir);
+ }
+
// 4. Align APK
string alignedApk = Path.Combine(OutputDir, "bin", $"{ProjectName}.apk");
diff --git a/src/tasks/AndroidAppBuilder/Templates/AndroidManifest.xml b/src/tasks/AndroidAppBuilder/Templates/AndroidManifest.xml
index befd2e446a650e..30ded9e1360480 100644
--- a/src/tasks/AndroidAppBuilder/Templates/AndroidManifest.xml
+++ b/src/tasks/AndroidAppBuilder/Templates/AndroidManifest.xml
@@ -1,5 +1,5 @@
-
@@ -8,7 +8,6 @@
@@ -21,4 +20,4 @@
-
\ No newline at end of file
+
diff --git a/src/tasks/Common/AndroidSdkHelper.cs b/src/tasks/Common/AndroidSdkHelper.cs
new file mode 100644
index 00000000000000..e56e7d65699d2d
--- /dev/null
+++ b/src/tasks/Common/AndroidSdkHelper.cs
@@ -0,0 +1,81 @@
+// 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.IO;
+using System.Linq;
+
+internal sealed class AndroidSdkHelper
+{
+ private readonly string _androidSdkPath;
+ private readonly string _buildToolsPath;
+ private readonly string _buildApiLevel;
+
+ public AndroidSdkHelper(
+ string? androidSdkPath,
+ string? buildApiLevel,
+ string? buildToolsVersion)
+ {
+ if (string.IsNullOrEmpty(androidSdkPath))
+ androidSdkPath = Environment.GetEnvironmentVariable("ANDROID_SDK_ROOT");
+
+ if (string.IsNullOrEmpty(androidSdkPath) || !Directory.Exists(androidSdkPath))
+ throw new ArgumentException($"Android SDK='{androidSdkPath}' was not found or empty (can be set via ANDROID_SDK_ROOT envvar).");
+
+ _androidSdkPath = androidSdkPath;
+
+ // Try to get the latest API level if not specified
+ if (string.IsNullOrEmpty(buildApiLevel))
+ buildApiLevel = GetLatestApiLevel(_androidSdkPath);
+
+ _buildApiLevel = buildApiLevel;
+
+ // Try to get the latest build-tools version if not specified
+ if (string.IsNullOrEmpty(buildToolsVersion))
+ buildToolsVersion = GetLatestBuildTools(_androidSdkPath);
+
+ _buildToolsPath = Path.Combine(_androidSdkPath, "build-tools", buildToolsVersion);
+
+ if (!Directory.Exists(_buildToolsPath))
+ throw new ArgumentException($"{_buildToolsPath} was not found.");
+ }
+
+ public string AndroidJarPath => Path.Combine(_androidSdkPath, "platforms", $"android-{_buildApiLevel}", "android.jar");
+
+ public bool HasD8 => File.Exists(D8Path);
+ public string D8Path => getToolPath("d8");
+ public string DxPath => getToolPath("dx");
+
+ private string getToolPath(string tool)
+ => Path.Combine(_buildToolsPath, tool);
+
+ ///
+ /// Scan android SDK for api levels (ignore preview versions)
+ ///
+ private static string GetLatestApiLevel(string androidSdkDir)
+ {
+ return Directory.GetDirectories(Path.Combine(androidSdkDir, "platforms"))
+ .Select(file => int.TryParse(Path.GetFileName(file).Replace("android-", ""), out int apiLevel) ? apiLevel : -1)
+ .OrderByDescending(v => v)
+ .FirstOrDefault()
+ .ToString();
+ }
+
+ ///
+ /// Scan android SDK for build tools (ignore preview versions)
+ ///
+ private static string GetLatestBuildTools(string androidSdkPath)
+ {
+ string? buildTools = Directory.GetDirectories(Path.Combine(androidSdkPath, "build-tools"))
+ .Select(Path.GetFileName)
+ .Where(file => !file!.Contains('-'))
+ .Select(file => { Version.TryParse(Path.GetFileName(file), out Version? version); return version; })
+ .OrderByDescending(v => v)
+ .FirstOrDefault()?.ToString();
+
+ if (string.IsNullOrEmpty(buildTools))
+ throw new ArgumentException($"Android SDK ({androidSdkPath}) doesn't contain build-tools.");
+
+ return buildTools;
+ }
+}
diff --git a/src/tasks/Common/DexBuilder.cs b/src/tasks/Common/DexBuilder.cs
new file mode 100644
index 00000000000000..a98da4e218e939
--- /dev/null
+++ b/src/tasks/Common/DexBuilder.cs
@@ -0,0 +1,56 @@
+// 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.IO;
+using System.Linq;
+using Microsoft.Build.Utilities;
+
+internal sealed class DexBuilder
+{
+ private readonly string _workingDir;
+ private readonly AndroidSdkHelper _androidSdk;
+ private readonly TaskLoggingHelper _logger;
+
+ public DexBuilder(
+ TaskLoggingHelper logger,
+ AndroidSdkHelper buildTools,
+ string workingDir)
+ {
+ _androidSdk = buildTools;
+ _workingDir = workingDir;
+ _logger = logger;
+ }
+
+ public void Build(string inputDir, string outputFileName)
+ {
+ if (_androidSdk.HasD8)
+ {
+ BuildUsingD8(inputDir, outputFileName);
+ }
+ else
+ {
+ BuildUsingDx(inputDir, outputFileName);
+ }
+ }
+
+ private void BuildUsingD8(string inputDir, string outputFilePath)
+ {
+ string[] classFiles = Directory.GetFiles(inputDir, "*.class", SearchOption.AllDirectories);
+
+ if (!classFiles.Any())
+ throw new InvalidOperationException("Didn't find any .class files");
+
+ Utils.RunProcess(_logger, _androidSdk.D8Path, $"--no-desugaring {string.Join(" ", classFiles)}", workingDir: _workingDir);
+
+ File.Move(
+ sourceFileName: Path.Combine(_workingDir, "classes.dex"),
+ destFileName: outputFilePath,
+ overwrite: true);
+ }
+
+ private void BuildUsingDx(string inputDir, string outputFileName)
+ {
+ Utils.RunProcess(_logger, _androidSdk.DxPath, $"--dex --output={outputFileName} {inputDir}", workingDir: _workingDir);
+ }
+}
diff --git a/src/tasks/Common/JarBuilder.cs b/src/tasks/Common/JarBuilder.cs
new file mode 100644
index 00000000000000..f5e2a03215b3ff
--- /dev/null
+++ b/src/tasks/Common/JarBuilder.cs
@@ -0,0 +1,27 @@
+// 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.IO;
+using System.Linq;
+using System.Collections.Generic;
+using Microsoft.Build.Utilities;
+
+internal sealed class JarBuilder
+{
+ private readonly TaskLoggingHelper _logger;
+
+ public JarBuilder(TaskLoggingHelper logger)
+ {
+ _logger = logger;
+ }
+
+ public void Build(string inputDir, string outputFileName)
+ {
+ IEnumerable classFiles =
+ Directory.GetFiles(inputDir, "*.class", SearchOption.AllDirectories)
+ .Select(classFile => Path.GetRelativePath(inputDir, classFile));
+
+ Utils.RunProcess(_logger, "jar", $"-cf {outputFileName} {string.Join(" ", classFiles)}", workingDir: inputDir);
+ }
+}
diff --git a/src/tasks/Common/JavaCompiler.cs b/src/tasks/Common/JavaCompiler.cs
new file mode 100644
index 00000000000000..abeb7c6fd91556
--- /dev/null
+++ b/src/tasks/Common/JavaCompiler.cs
@@ -0,0 +1,29 @@
+// 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.IO;
+using Microsoft.Build.Utilities;
+
+internal sealed class JavaCompiler
+{
+ private readonly string _javaCompilerArgs;
+ private readonly string _workingDir;
+ private readonly TaskLoggingHelper _logger;
+
+ public JavaCompiler(
+ TaskLoggingHelper logger,
+ AndroidSdkHelper androidSdk,
+ string workingDir,
+ string javaVersion = "1.8")
+ {
+ _javaCompilerArgs = $"-classpath src -bootclasspath {androidSdk.AndroidJarPath} -source {javaVersion} -target {javaVersion}";
+ _workingDir = workingDir;
+ _logger = logger;
+ }
+
+ public void Compile(string javaSourceFile, string outputDir)
+ {
+ Utils.RunProcess(_logger, "javac", $"{_javaCompilerArgs} -d {outputDir} {javaSourceFile}", workingDir: _workingDir);
+ }
+}
diff --git a/src/tests/Directory.Build.targets b/src/tests/Directory.Build.targets
index 29b8159ce3a32d..d5a29562a686b4 100644
--- a/src/tests/Directory.Build.targets
+++ b/src/tests/Directory.Build.targets
@@ -170,12 +170,17 @@
+
+
+
+
+
diff --git a/src/tests/FunctionalTests/Android/Device_Emulator/gRPC/Android.Device_Emulator.gRPC.Test.csproj b/src/tests/FunctionalTests/Android/Device_Emulator/gRPC/Android.Device_Emulator.gRPC.Test.csproj
index 93f5817bc2aad6..12755944bf324a 100644
--- a/src/tests/FunctionalTests/Android/Device_Emulator/gRPC/Android.Device_Emulator.gRPC.Test.csproj
+++ b/src/tests/FunctionalTests/Android/Device_Emulator/gRPC/Android.Device_Emulator.gRPC.Test.csproj
@@ -13,7 +13,7 @@
true
true
-
+
enable
enable
@@ -21,16 +21,8 @@
CS8981;SYSLIB0039
-
- true
-
-
-
-
- PreserveNewest
-
diff --git a/src/tests/build.proj b/src/tests/build.proj
index e156a911555be0..bec907bd2afde5 100644
--- a/src/tests/build.proj
+++ b/src/tests/build.proj
@@ -210,7 +210,7 @@
-
+
@@ -238,7 +238,6 @@
RuntimeIdentifier="$(RuntimeIdentifier)"
ProjectName="$(Category)"
MonoRuntimeHeaders="$(MicrosoftNetCoreAppRuntimePackDir)/native/include/mono-2.0"
- IncludeNetworkSecurityConfig="$(IncludeNetworkSecurityConfig)"
RuntimeComponents="$(RuntimeComponents)"
DiagnosticPorts="$(DiagnosticPorts)"
StripDebugSymbols="$(StripDebugSymbols)"