Skip to content

Commit c435061

Browse files
simonrozsivalelinor-fungakoeplinger
authored
[Android] Improvements to remote certificate verification in SslStream (#77386)
* Extract existing validation code into a separate class * Implement AndroidDexBuilderTask * Implement TrustManager proxy * Integrate the trust manager proxy with SslStream on Android * Update tests * Update System.Net.Http tests * Update System.Net.Security tests * Fix packaging * Propagate caught exceptions * Build and pack .jar * Optimize allocation and deallocation of memory for certificate data * Fix building .jar * Cleanup * Remove complicated certificate copying * Remove unnecessary JNI classes and methods * Simplify and fix the core implementation * Update enabled and disabled tests * Cleanup * Renaming * Remove unnecessary changes * Fix invoking validation even when the Java callbacks aren't called (no peer certificate to validate) * Minor refactoring * Enable more unnecessarily disabled tests * Refactor exception handling * Update disabled tests * Renaming * Remove network security config workarounds * Keep existing active issue * Remove unnecessary changes * Remove unnecessary code * Enable more disabled tests * Fix throwing exception * Fix intptr_t cast to Java * Remove initialization lock * Update naming * Fix type casting * Improve throwing validation exception * Experiment with code structure * Fix repeated calls to beginHandshake * Make SslStream proxy mandatory * Add missing attributes * Free temporary buffer * Update src/native/libs/System.Security.Cryptography.Native.Android/pal_sslstream.c Co-authored-by: Elinor Fung <[email protected]> * Refactor creating array of trust managers * Add comments and clean up pal_sslstream.c * Revert experimental change * Remove special case for IPv6 addresses as hostnames and disable affected tests * Fix duplicate variable after merge * Improve code formatting * Remove the hack with SafeDeleteContextStub * Enable passing test * Remove unnecessary factory * Move clearing selected client certificate out of the remote certificate verification method * Fix typo in comment * Add comment with java equivalent * Move Android specific runtime files into a separate item group * Apply suggestions from code review Co-authored-by: Alexander Köplinger <[email protected]> * Update src/native/libs/build-native.proj Co-authored-by: Alexander Köplinger <[email protected]> * Disable test that fails on Android emualtors Co-authored-by: Elinor Fung <[email protected]> Co-authored-by: Alexander Köplinger <[email protected]>
1 parent 4cf8c07 commit c435061

File tree

50 files changed

+809
-192
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+809
-192
lines changed

eng/liveBuilds.targets

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,11 @@
179179
$(LibrariesNativeArtifactsPath)*.pdb"
180180
IsNative="true"
181181
Exclude="@(ExcludeNativeLibrariesRuntimeFiles)" />
182+
<LibrariesRuntimeFiles Condition="'$(TargetOS)' == 'android'"
183+
Include="
184+
$(LibrariesNativeArtifactsPath)*.dex;
185+
$(LibrariesNativeArtifactsPath)*.jar;"
186+
IsNative="true" />
182187
<LibrariesRuntimeFiles Condition="'$(TargetOS)' == 'browser'"
183188
Include="
184189
$(LibrariesNativeArtifactsPath)dotnet.js;

src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@
9595
<PlatformManifestFileEntry Include="libSystem.Security.Cryptography.Native.Apple.dylib" IsNative="true" />
9696
<PlatformManifestFileEntry Include="libSystem.Security.Cryptography.Native.Android.a" IsNative="true" />
9797
<PlatformManifestFileEntry Include="libSystem.Security.Cryptography.Native.Android.so" IsNative="true" />
98+
<PlatformManifestFileEntry Include="libSystem.Security.Cryptography.Native.Android.dex" IsNative="true" />
99+
<PlatformManifestFileEntry Include="libSystem.Security.Cryptography.Native.Android.jar" IsNative="true" />
98100
<PlatformManifestFileEntry Include="libSystem.Security.Cryptography.Native.OpenSsl.a" IsNative="true" />
99101
<PlatformManifestFileEntry Include="libSystem.Security.Cryptography.Native.OpenSsl.dylib" IsNative="true" />
100102
<PlatformManifestFileEntry Include="libSystem.Security.Cryptography.Native.OpenSsl.so" IsNative="true" />

src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Ssl.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,37 @@ internal enum PAL_SSLStreamStatus
2929
};
3030

3131
[LibraryImport(Interop.Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_SSLStreamCreate")]
32-
internal static partial SafeSslHandle SSLStreamCreate();
32+
private static partial SafeSslHandle SSLStreamCreate(IntPtr sslStreamProxyHandle);
33+
internal static SafeSslHandle SSLStreamCreate(SslStream.JavaProxy sslStreamProxy)
34+
=> SSLStreamCreate(sslStreamProxy.Handle);
3335

3436
[LibraryImport(Interop.Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_SSLStreamCreateWithCertificates")]
3537
private static partial SafeSslHandle SSLStreamCreateWithCertificates(
38+
IntPtr sslStreamProxyHandle,
3639
ref byte pkcs8PrivateKey,
3740
int pkcs8PrivateKeyLen,
3841
PAL_KeyAlgorithm algorithm,
3942
IntPtr[] certs,
4043
int certsLen);
41-
internal static SafeSslHandle SSLStreamCreateWithCertificates(ReadOnlySpan<byte> pkcs8PrivateKey, PAL_KeyAlgorithm algorithm, IntPtr[] certificates)
44+
internal static SafeSslHandle SSLStreamCreateWithCertificates(
45+
SslStream.JavaProxy sslStreamProxy,
46+
ReadOnlySpan<byte> pkcs8PrivateKey,
47+
PAL_KeyAlgorithm algorithm,
48+
IntPtr[] certificates)
4249
{
4350
return SSLStreamCreateWithCertificates(
51+
sslStreamProxy.Handle,
4452
ref MemoryMarshal.GetReference(pkcs8PrivateKey),
4553
pkcs8PrivateKey.Length,
4654
algorithm,
4755
certificates,
4856
certificates.Length);
4957
}
5058

59+
[LibraryImport(Interop.Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_RegisterRemoteCertificateValidationCallback")]
60+
internal static unsafe partial void RegisterRemoteCertificateValidationCallback(
61+
delegate* unmanaged<IntPtr, bool> verifyRemoteCertificate);
62+
5163
[LibraryImport(Interop.Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_SSLStreamInitialize")]
5264
private static unsafe partial int SSLStreamInitializeImpl(
5365
SafeSslHandle sslHandle,

src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.AcceptAllCerts.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ await TestHelper.WhenAllCompletedOrAnyFailed(
9696
[OuterLoop]
9797
[ConditionalTheory(nameof(ClientSupportsDHECipherSuites))]
9898
[MemberData(nameof(InvalidCertificateServers))]
99-
[SkipOnPlatform(TestPlatforms.Android, "Android rejects the certificate, the custom validation callback in .NET cannot override OS behavior in the current implementation")]
10099
public async Task InvalidCertificateServers_CertificateValidationDisabled_Succeeds(string url)
101100
{
102101
using (HttpClientHandler handler = CreateHttpClientHandler(allowAllCertificates: true))

src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,6 @@ private async Task UseCallback_BadCertificate_ExpectedPolicyErrors_Helper(string
286286
[OuterLoop("Uses external servers")]
287287
[Theory]
288288
[MemberData(nameof(CertificateValidationServersAndExpectedPolicies))]
289-
[SkipOnPlatform(TestPlatforms.Android, "Android rejects the certificate, the custom validation callback in .NET cannot override OS behavior in the current implementation")]
290289
public async Task UseCallback_BadCertificate_ExpectedPolicyErrors(string url, SslPolicyErrors expectedErrors)
291290
{
292291
const int SEC_E_BUFFER_TOO_SMALL = unchecked((int)0x80090321);
@@ -310,7 +309,6 @@ public async Task UseCallback_BadCertificate_ExpectedPolicyErrors(string url, Ss
310309
}
311310

312311
[Fact]
313-
[SkipOnPlatform(TestPlatforms.Android, "Android rejects the certificate, the custom validation callback in .NET cannot override OS behavior in the current implementation")]
314312
public async Task UseCallback_SelfSignedCertificate_ExpectedPolicyErrors()
315313
{
316314
using (HttpClientHandler handler = CreateHttpClientHandler())

src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ public void Properties_AddItemToDictionary_ItemPresent()
153153

154154
[ConditionalFact]
155155
[SkipOnPlatform(TestPlatforms.Browser, "ServerCertificateCustomValidationCallback not supported on Browser")]
156-
[SkipOnPlatform(TestPlatforms.Android, "IPv6 loopback with SSL doesn't work on Android")]
156+
[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")]
157157
public async Task GetAsync_IPv6LinkLocalAddressUri_Success()
158158
{
159159
if (IsWinHttpHandler && UseVersion >= HttpVersion20.Value)
@@ -205,7 +205,7 @@ public async Task GetAsync_IPBasedUri_Success(IPAddress address)
205205

206206
if (PlatformDetection.IsAndroid && options.UseSsl && address == IPAddress.IPv6Loopback)
207207
{
208-
throw new SkipTestException("IPv6 loopback with SSL doesn't work on Android");
208+
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");
209209
}
210210

211211
await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
@@ -287,7 +287,7 @@ public async Task GetAsync_SecureAndNonSecureIPBasedUri_CorrectlyFormatted(IPAdd
287287

288288
if (PlatformDetection.IsAndroid && useSsl && address == IPAddress.IPv6Loopback)
289289
{
290-
throw new SkipTestException("IPv6 loopback with SSL doesn't work on Android");
290+
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");
291291
}
292292

293293
var options = new LoopbackServer.Options { Address = address, UseSsl = useSsl };

src/libraries/Common/tests/System/Net/Http/TestHelper.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,6 @@ public static SocketsHttpHandler CreateSocketsHttpHandler(bool allowAllCertifica
173173
// Browser doesn't support ServerCertificateCustomValidationCallback
174174
if (allowAllCertificates && PlatformDetection.IsNotBrowser)
175175
{
176-
// On Android, it is not enough to set the custom validation callback, the certificates also need to be trusted by the OS.
177-
// See HttpClientHandlerTestBase.SocketsHttpHandler.cs:CreateHttpClientHandler for more details.
178-
179176
handler.SslOptions.RemoteCertificateValidationCallback = delegate { return true; };
180177
}
181178

src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.SocketsHttpHandler.cs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,6 @@ protected static HttpClientHandler CreateHttpClientHandler(Version useVersion =
3737
// Browser doesn't support ServerCertificateCustomValidationCallback
3838
if (allowAllCertificates && PlatformDetection.IsNotBrowser)
3939
{
40-
// On Android, it is not enough to set the custom validation callback, the certificates also need to be trusted by the OS.
41-
// The public keys of our self-signed certificates that are used by the loopback server are part of the System.Net.TestData
42-
// package and they can be included in a the Android test apk by adding the following property to the test's .csproj:
43-
//
44-
// <IncludeNetworkSecurityConfig Condition="'$(TargetOS)' == 'android'">true</IncludeNetworkSecurityConfig>
45-
//
46-
4740
handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
4841
}
4942

src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4076,7 +4076,6 @@ public abstract class SocketsHttpHandler_SecurityTest : HttpClientHandlerTestBas
40764076
public SocketsHttpHandler_SecurityTest(ITestOutputHelper output) : base(output) { }
40774077

40784078
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
4079-
[SkipOnPlatform(TestPlatforms.Android, "Self-signed certificates are rejected by Android before the .NET validation is reached")]
40804079
public async Task SslOptions_CustomTrust_Ok()
40814080
{
40824081
X509Certificate2Collection caCerts = new X509Certificate2Collection();
@@ -4113,7 +4112,6 @@ await LoopbackServerFactory.CreateClientAndServerAsync(
41134112
}
41144113

41154114
[Fact]
4116-
[SkipOnPlatform(TestPlatforms.Android, "Self-signed certificates are rejected by Android before the .NET validation is reached")]
41174115
public async Task SslOptions_InvalidName_Throws()
41184116
{
41194117
X509Certificate2Collection caCerts = new X509Certificate2Collection();
@@ -4144,7 +4142,6 @@ await LoopbackServerFactory.CreateClientAndServerAsync(
41444142
}
41454143

41464144
[Fact]
4147-
[SkipOnPlatform(TestPlatforms.Android, "Self-signed certificates are rejected by Android before the .NET validation is reached")]
41484145
public async Task SslOptions_CustomPolicy_IgnoresNameMismatch()
41494146
{
41504147
X509Certificate2Collection caCerts = new X509Certificate2Collection();

src/libraries/System.Net.Http/tests/FunctionalTests/SocksProxyTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public async Task TestLoopbackAsync(string scheme, bool useSsl, bool useAuth, st
3737

3838
if (PlatformDetection.IsAndroid && useSsl && host == "::1")
3939
{
40-
throw new SkipTestException("IPv6 loopback with SSL doesn't work on Android");
40+
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");
4141
}
4242

4343
await LoopbackServerFactory.CreateClientAndServerAsync(

0 commit comments

Comments
 (0)