-
Notifications
You must be signed in to change notification settings - Fork 533
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Mono.Android] custom validation callback for server certificates in …
…HTTP handlers (#6972) * [Mono.Android] custom validation callback for server certificates in HTTP handlers (#6665) Backport of: #6665 Context: dotnet/runtime#62966 The `AndroidClientHandler` and `AndroidMessageHandler` classes both have the `ServerCertificateCustomValidationCallback` property, which should be useful e.g. to allow running the Android app against a server with a self-signed SSL certificate during development, but the callback is never used. Unfortunatelly since .NET 6 the `System.Net.Http.SocketsHttpHandler` for Android doesn't support the use case anymore. That means that [the recommended way of connecting to local web server][0] won't work in MAUI. This PR introduces an implementation of `IX509TrustManger` which wraps the default Java X509 trust manager and calls the user's callback on top of the default validation. It turns out that `X509Chain` `Build` function doesn't work on Android, so I'm not calling it and I'm passing the chain to the callback directly. Additionally, we need a default proguard rule due to: https://github.com/xamarin/xamarin-android/blob/46002b49d8c0b7b1a17532a8e104b4d31afee7a6/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/GenerateProguardConfiguration.cs#L50-L57 -keep class xamarin.android.net.X509TrustManagerWithValidationCallback { *; <init>(...); } `Mono.Android.dll` is skipped during the `GenerateProguardConfiguration` linker step. It might be worth addressing this in a future PR. [0]: https://docs.microsoft.com/en-us/xamarin/cross-platform/deploy-test/connect-to-local-web-services * Update .apkdesc (probably merge issue) Co-authored-by: Simon Rozsival <simon@rozsival.com>
- Loading branch information
1 parent
aee4383
commit 735e6d9
Showing
11 changed files
with
843 additions
and
633 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
96 changes: 96 additions & 0 deletions
96
src/Mono.Android/Xamarin.Android.Net/X509TrustManagerWithValidationCallback.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Net.Http; | ||
using System.Net.Security; | ||
using System.Security.Cryptography.X509Certificates; | ||
|
||
using Javax.Net.Ssl; | ||
|
||
using JavaCertificateException = Java.Security.Cert.CertificateException; | ||
using JavaX509Certificate = Java.Security.Cert.X509Certificate; | ||
|
||
namespace Xamarin.Android.Net | ||
{ | ||
internal sealed class X509TrustManagerWithValidationCallback : Java.Lang.Object, IX509TrustManager | ||
{ | ||
internal sealed class Helper | ||
{ | ||
public Func<HttpRequestMessage, X509Certificate2?, X509Chain?, SslPolicyErrors, bool> Callback { get; } | ||
|
||
public Helper (Func<HttpRequestMessage, X509Certificate2?, X509Chain?, SslPolicyErrors, bool> callback) | ||
{ | ||
Callback = callback; | ||
} | ||
|
||
public ITrustManager[] Inject ( | ||
ITrustManager[]? trustManagers, | ||
HttpRequestMessage requestMessage) | ||
{ | ||
IX509TrustManager? x509TrustManager = trustManagers?.OfType<IX509TrustManager> ().FirstOrDefault (); | ||
IEnumerable<ITrustManager> otherTrustManagers = trustManagers?.Where (manager => manager != x509TrustManager) ?? Enumerable.Empty<ITrustManager> (); | ||
var trustManagerWithCallback = new X509TrustManagerWithValidationCallback (x509TrustManager, requestMessage, Callback); | ||
return otherTrustManagers.Prepend (trustManagerWithCallback).ToArray (); | ||
} | ||
} | ||
|
||
private readonly IX509TrustManager? _internalTrustManager; | ||
private readonly HttpRequestMessage _request; | ||
private readonly Func<HttpRequestMessage, X509Certificate2?, X509Chain?, SslPolicyErrors, bool> _serverCertificateCustomValidationCallback; | ||
|
||
private X509TrustManagerWithValidationCallback ( | ||
IX509TrustManager? internalTrustManager, | ||
HttpRequestMessage request, | ||
Func<HttpRequestMessage, X509Certificate2?, X509Chain?, SslPolicyErrors, bool> serverCertificateCustomValidationCallback) | ||
{ | ||
_request = request; | ||
_internalTrustManager = internalTrustManager; | ||
_serverCertificateCustomValidationCallback = serverCertificateCustomValidationCallback; | ||
} | ||
|
||
public void CheckServerTrusted (JavaX509Certificate[] javaChain, string authType) | ||
{ | ||
var sslPolicyErrors = SslPolicyErrors.None; | ||
var certificates = ConvertCertificates (javaChain); | ||
|
||
try { | ||
_internalTrustManager?.CheckServerTrusted (javaChain, authType); | ||
} catch (JavaCertificateException) { | ||
sslPolicyErrors |= SslPolicyErrors.RemoteCertificateChainErrors; | ||
} | ||
|
||
X509Certificate2? certificate = certificates.FirstOrDefault (); | ||
using X509Chain chain = CreateChain (certificates); | ||
|
||
if (certificate == null) { | ||
sslPolicyErrors |= SslPolicyErrors.RemoteCertificateNotAvailable; | ||
} | ||
|
||
if (!_serverCertificateCustomValidationCallback (_request, certificate, chain, sslPolicyErrors)) { | ||
throw new JavaCertificateException ("The remote certificate was rejected by the provided RemoteCertificateValidationCallback."); | ||
} | ||
} | ||
|
||
public void CheckClientTrusted (JavaX509Certificate[] chain, string authType) | ||
=> _internalTrustManager?.CheckClientTrusted (chain, authType); | ||
|
||
public JavaX509Certificate[] GetAcceptedIssuers () | ||
=> _internalTrustManager?.GetAcceptedIssuers () ?? Array.Empty<JavaX509Certificate> (); | ||
|
||
private static X509Chain CreateChain (X509Certificate2[] certificates) | ||
{ | ||
// the chain initialization is based on dotnet/runtime implementation in System.Net.Security.SecureChannel | ||
var chain = new X509Chain (); | ||
|
||
chain.ChainPolicy.RevocationMode = X509RevocationMode.Online; | ||
chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot; | ||
|
||
chain.ChainPolicy.ExtraStore.AddRange (certificates); | ||
|
||
return chain; | ||
} | ||
|
||
private static X509Certificate2[] ConvertCertificates (JavaX509Certificate[] certificates) | ||
=> certificates.Select (cert => new X509Certificate2 (cert.GetEncoded ()!)).ToArray (); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.