Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use custom validation callback for server certificates in HTTP handlers #6665

Merged
Show file tree
Hide file tree
Changes from 56 commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
42c0dc5
Add basic implementaiton of custom X509 Trust Manager
simonrozsival Jan 24, 2022
cf11487
Merge branch 'main' of git://github.com/xamarin/xamarin-android into …
simonrozsival Jan 24, 2022
ad3faad
Update the implementaiton of the legacy http client handler
simonrozsival Jan 25, 2022
3637e2f
Update chain building
simonrozsival Jan 25, 2022
fb47fc2
Add integration tests
simonrozsival Jan 25, 2022
478cc55
Fix typo
simonrozsival Jan 25, 2022
1579766
Fix Func signature
simonrozsival Jan 25, 2022
7976be8
Fix Func signature in tests
simonrozsival Jan 25, 2022
6b466f2
Fix incorrect return type of async tests
simonrozsival Jan 25, 2022
e7f8303
Add missing usings to tests
simonrozsival Jan 25, 2022
2e96d0a
Fix tests
simonrozsival Jan 26, 2022
d19208b
Fix func generic params nullability
simonrozsival Jan 26, 2022
2d102dc
Attempt to fix trust manager instantiation
simonrozsival Jan 26, 2022
f0dedda
Change spaces to tabs
simonrozsival Jan 27, 2022
73ac4b8
Remove Register attribute
simonrozsival Jan 27, 2022
32b5ae3
Remove integration tests
simonrozsival Jan 27, 2022
728d0d8
WIP separate apk for net tests
simonrozsival Jan 27, 2022
077d51a
Finalize tests
simonrozsival Jan 27, 2022
98f13da
Update implementation for both handlers
simonrozsival Jan 27, 2022
d6f4099
Merge branch 'main' of git://github.com/xamarin/xamarin-android into …
simonrozsival Jan 28, 2022
bcf498a
Adjust newlines
simonrozsival Jan 28, 2022
82371c2
Fix project and solution
simonrozsival Jan 28, 2022
6d96d0a
Fix Func return value
simonrozsival Jan 28, 2022
25f81a4
Add missing permission
simonrozsival Jan 28, 2022
f0265ed
Update test project settings
simonrozsival Jan 28, 2022
c2a7454
Update comments
simonrozsival Feb 1, 2022
d3acfd1
Remove TODOs
simonrozsival Feb 1, 2022
31daa4b
Try to add .net6 tests
simonrozsival Feb 3, 2022
f23eaa0
Rename csproj files
simonrozsival Feb 4, 2022
2646442
Update .NET project
simonrozsival Feb 4, 2022
b89c6b6
Try to update nunit reference
simonrozsival Feb 4, 2022
050e8f0
Add test
simonrozsival Feb 4, 2022
78778a0
Fix test
Feb 7, 2022
4ed5bfc
Update legacy project settings
simonrozsival Feb 7, 2022
46f9cd8
Update .NET test project file
simonrozsival Feb 7, 2022
4c7b8a9
Change NUnitLite reference from Android to Legacy
simonrozsival Feb 7, 2022
3afb880
Update error messages
simonrozsival Feb 7, 2022
c4e5816
Add temporary throw for testing
simonrozsival Feb 7, 2022
cad31b7
Fix typo
simonrozsival Feb 7, 2022
ce85c3d
Remove Resources.designer.cs
simonrozsival Feb 7, 2022
4d0575a
Add more exceptions for debugging purposes
simonrozsival Feb 7, 2022
58105c0
Fix condition
simonrozsival Feb 7, 2022
040ab06
Add more exceptions for debugging
simonrozsival Feb 8, 2022
88a1d1f
More debugging information
simonrozsival Feb 8, 2022
e7aa149
Fix build
simonrozsival Feb 8, 2022
3b9d675
Dump trust manager names
simonrozsival Feb 8, 2022
6162112
Try to fix the issue with multiple X509 trust managers
simonrozsival Feb 8, 2022
ea45b2b
Remove broken .NET tests
simonrozsival Feb 9, 2022
70e44bf
Remove TODO
simonrozsival Feb 11, 2022
5d8644c
Merge branch 'main' into simonrozsival/android-http-handlers-use-serv…
simonrozsival Mar 15, 2022
9940bc7
Merge branch 'main' of github.com:xamarin/xamarin-android into simonr…
simonrozsival Apr 12, 2022
3760be0
Merge branch 'main' of github.com:xamarin/xamarin-android into simonr…
simonrozsival Apr 13, 2022
3d4a5d7
Update apk desc files
simonrozsival Apr 13, 2022
8485733
Merge branch 'simonrozsival/android-http-handlers-use-server-certific…
simonrozsival Apr 13, 2022
6dbff42
Merge branch 'main' of github.com:xamarin/xamarin-android into simonr…
simonrozsival Apr 14, 2022
9ef2ab7
Update apk desc
simonrozsival Apr 14, 2022
0164fdd
Improve code formatting to match the code style of the repo
simonrozsival Apr 25, 2022
271d708
Remove new test project and move the test into Mono.Android-Tests
simonrozsival Apr 25, 2022
ce29621
Fix test namespace
simonrozsival Apr 25, 2022
d05ba71
Fix code formatting
simonrozsival Apr 25, 2022
7c6da63
Remove build-tools changes
simonrozsival Apr 25, 2022
765c26e
Allow X509TrustManagerWithValidationCallback to be trimmed away if Se…
akoeplinger Apr 25, 2022
fbb4b0a
Fix formatting
akoeplinger Apr 25, 2022
b71dee3
Merge branch 'main' of github.com:xamarin/xamarin-android into simonr…
simonrozsival Apr 26, 2022
ff7ddb1
Merge branch 'simonrozsival/android-http-handlers-use-server-certific…
simonrozsival Apr 26, 2022
a8392ab
Revert all changes to the legacy client handler
simonrozsival Apr 26, 2022
d5b3ba6
Simplify helper
simonrozsival Apr 26, 2022
d5fbd16
Improve code formatting
simonrozsival Apr 26, 2022
b11c904
Fix overriding trust managers with null
simonrozsival Apr 26, 2022
491d1c1
Add the Register attribute
simonrozsival Apr 26, 2022
266e308
Remove property init
simonrozsival Apr 26, 2022
89f7e3d
Remove unnecessary changes
simonrozsival Apr 26, 2022
eb038d1
Update apkdesc
simonrozsival Apr 26, 2022
5da8631
Improve test proj items
simonrozsival Apr 26, 2022
4210916
Run the tests for both .net6 and legacy xamarin
simonrozsival Apr 26, 2022
2900b05
Make the tests AndroidMessageHandler specific
simonrozsival Apr 26, 2022
dc45876
Move AndroidMessageHandler tests out of AndroidClientHandlerTests.cs
simonrozsival Apr 26, 2022
90f4bf2
WIP try to preserve X509TrustManagerWithValidationCallback
simonrozsival Apr 27, 2022
ac0a75c
Revert "WIP try to preserve X509TrustManagerWithValidationCallback"
simonrozsival Apr 28, 2022
cd03384
Revert "Add the Register attribute"
simonrozsival Apr 28, 2022
19fca32
Fix the type of exception that we catch
simonrozsival Apr 28, 2022
0d61693
Add proguard rule
simonrozsival Apr 28, 2022
25f7961
Fix several tests
simonrozsival Apr 28, 2022
a91b69c
Merge branch 'main' of github.com:xamarin/xamarin-android into simonr…
simonrozsival Apr 28, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Xamarin.Android-Tests.sln
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xamarin.Android.Tools.Aidl-
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xamarin.Android.Tools.Aidl", "src\Xamarin.Android.Tools.Aidl\Xamarin.Android.Tools.Aidl.csproj", "{302D9D2E-1F98-4374-9B6B-922F78620C4B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.Net.Tests", "tests\Xamarin.Android.Net-Tests\Xamarin.Android.Net-Tests.csproj", "{629E079E-D898-4C71-ACB9-B4F58C22B892}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
tests\Mono.Android-Tests\Mono.Android-Test.Shared.projitems*{0ab4956e-6fb9-4da0-9d49-ab65a3ff403a}*SharedItemsImports = 13
Expand Down
9 changes: 9 additions & 0 deletions build-tools/automation/azure-pipelines.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,15 @@ stages:
artifactSource: bin/Test$(ApkTestConfiguration)/Xamarin.Android.EmbeddedDSO_Test-Signed.apk
artifactFolder: Default

- template: yaml-templates/apk-instrumentation.yaml
parameters:
configuration: $(ApkTestConfiguration)
testName: Xamarin.Android.Net-Tests
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@simonrozsival: Why add a new app instead of adding the new tests to the existing tests/Mono.Android-Tests app? I don't understand the rationale.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started with a test in tests/Mono.Android-Tests but I wasn't able to get it woring. I was getting Java.Lang.ClassNotFoundException : xamarin.android.net.X509TrustManagerWithValidationCallback because a java wrapper for the class I added, X509TrustManagerWithValidationCallback, isn't being generated automatically. I got some tips from you and I instead created an apk project which allowed me to run the test successfully.

I reverted the apk project. I think I somehow need to special-case the X509TrustManagerWithValidationCallback type so that it's built into mono.android.jar to make the test work inside of tests/Mono.Android-Tests.

project: tests/Xamarin.Android.Net-Tests/Xamarin.Android.Net-Tests.csproj
testResultsFiles: TestResult-Xamarin.Android.Net_Tests.nunit-$(ApkTestConfiguration).xml
artifactSource: bin/Test$(ApkTestConfiguration)/Xamarin.Android.Net_Tests-Signed.apk
artifactFolder: Default

- template: yaml-templates/apk-instrumentation.yaml
parameters:
configuration: $(ApkTestConfiguration)
Expand Down
1 change: 1 addition & 0 deletions build-tools/scripts/RunTests.targets
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<_ApkTestProject Include="$(_TopDir)\tests\EmbeddedDSOs\EmbeddedDSO\EmbeddedDSO.csproj" />
<_ApkTestProject Include="$(_TopDir)\tests\Mono.Android-Tests\Runtime-AppBundle\Mono.Android-TestsAppBundle.csproj" />
<_ApkTestProject Include="$(_TopDir)\tests\Mono.Android-Tests\Runtime-MultiDex\Mono.Android-TestsMultiDex.csproj" />
<_ApkTestProject Include="$(_TopDir)\tests\Xamarin.Android.Net-Tests\Xamarin.Android.Net-Tests.csproj" />
<_ApkTestProjectAot Include="$(_TopDir)\tests\Mono.Android-Tests\Mono.Android-Tests.csproj" />
<_ApkTestProjectAot Include="$(_TopDir)\tests\locales\Xamarin.Android.Locale-Tests\Xamarin.Android.Locale-Tests.csproj" />
<_ApkTestProjectAot Include="$(_TopDir)\tests\Xamarin.Forms-Performance-Integration\Droid\Xamarin.Forms.Performance.Integration.Droid.csproj" />
Expand Down
1 change: 1 addition & 0 deletions src/Mono.Android/Mono.Android.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@
<Compile Include="Xamarin.Android.Net\AuthModuleBasic.cs" />
<Compile Include="Xamarin.Android.Net\AuthModuleDigest.cs" />
<Compile Include="Xamarin.Android.Net\IAndroidAuthenticationModule.cs" />
<Compile Include="Xamarin.Android.Net\X509TrustManagerWithValidationCallback.cs" />
<Compile Condition=" '$(TargetFramework)' == 'monoandroid10' " Include="Xamarin.Android.Net\OldAndroidSSLSocketFactory.cs" />
</ItemGroup>

Expand Down
54 changes: 33 additions & 21 deletions src/Mono.Android/Xamarin.Android.Net/AndroidClientHandler.Legacy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -838,7 +838,7 @@ void AppendEncoding (string encoding, ref List <string>? list)

// SSL context must be set up as soon as possible, before adding any content or
// headers. Otherwise Java won't use the socket factory
SetupSSL (httpConnection as HttpsURLConnection);
SetupSSL (httpConnection as HttpsURLConnection, request);
if (request.Content != null)
AddHeaders (httpConnection, request.Content.Headers);
AddHeaders (httpConnection, request.Headers);
Expand Down Expand Up @@ -893,7 +893,7 @@ void AppendEncoding (string encoding, ref List <string>? list)
return null;
}

void SetupSSL (HttpsURLConnection? httpsConnection)
void SetupSSL (HttpsURLConnection? httpsConnection, HttpRequestMessage requestMessage)
{
if (httpsConnection == null)
return;
Expand All @@ -911,37 +911,49 @@ void SetupSSL (HttpsURLConnection? httpsConnection)
return;
}

var keyStore = KeyStore.GetInstance (KeyStore.DefaultType);
keyStore?.Load (null, null);
bool gotCerts = TrustedCerts?.Count > 0;
if (gotCerts) {
for (int i = 0; i < TrustedCerts!.Count; i++) {
Certificate cert = TrustedCerts [i];
if (cert == null)
continue;
keyStore?.SetCertificateEntry ($"ca{i}", cert);
}
}
var keyStore = InitializeKeyStore (out bool gotCerts);
keyStore = ConfigureKeyStore (keyStore);
var kmf = ConfigureKeyManagerFactory (keyStore);
var tmf = ConfigureTrustManagerFactory (keyStore);

if (tmf == null) {
// If there are no certs and no trust manager factory, we can't use a custom manager
// because it will cause all the HTTPS requests to fail because of unverified trust
// chain
if (!gotCerts)
// If there are no trusted certs, no custom trust manager factory or custom certificate validation callback
// there is no point in changing the behavior of the default SSL socket factory
if (!gotCerts && ServerCertificateCustomValidationCallback == null)
return;

tmf = TrustManagerFactory.GetInstance (TrustManagerFactory.DefaultAlgorithm);
tmf?.Init (keyStore);
tmf?.Init (gotCerts ? keyStore : null); // only use the custom key store if the user defined any trusted certs
}

ITrustManager[]? trustManagers =
ServerCertificateCustomValidationCallback == null
? tmf?.GetTrustManagers ()
: X509TrustManagerWithValidationCallback.Inject(tmf?.GetTrustManagers (), requestMessage, ServerCertificateCustomValidationCallback);

var context = SSLContext.GetInstance ("TLS");
context?.Init (kmf?.GetKeyManagers (), tmf?.GetTrustManagers (), null);
context?.Init (kmf?.GetKeyManagers (), trustManagers, null);
httpsConnection.SSLSocketFactory = context?.SocketFactory;
}


KeyStore? InitializeKeyStore (out bool gotCerts)
{
var keyStore = KeyStore.GetInstance (KeyStore.DefaultType);
keyStore?.Load (null, null);
gotCerts = TrustedCerts?.Count > 0;

if (gotCerts) {
for (int i = 0; i < TrustedCerts!.Count; i++) {
Certificate cert = TrustedCerts [i];
if (cert == null)
continue;
keyStore?.SetCertificateEntry ($"ca{i}", cert);
}
}

return keyStore;
}

void HandlePreAuthentication (HttpURLConnection httpConnection)
{
var data = PreAuthenticationData;
Expand Down
97 changes: 56 additions & 41 deletions src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ public CookieContainer CookieContainer

public bool CheckCertificateRevocationList { get; set; } = false;

public Func<HttpRequestMessage, X509Certificate2, X509Chain, SslPolicyErrors, bool> ServerCertificateCustomValidationCallback { get; set; }
public Func<HttpRequestMessage, X509Certificate2?, X509Chain?, SslPolicyErrors, bool>? ServerCertificateCustomValidationCallback { get; set; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If users never set this, would the changes here be a no-op? There is a lot of certificate loading here, and I'm wondering how that will impact performance.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be a no-op now.


// See: https://developer.android.com/reference/javax/net/ssl/SSLSocket#protocols
public SslProtocols SslProtocols { get; set; } =
Expand Down Expand Up @@ -194,12 +194,12 @@ public int MaxAutomaticRedirections
/// </summary>
/// <value>The pre authentication data.</value>
public AuthenticationData? PreAuthenticationData { get; set; }

/// <summary>
/// If the website requires authentication, this property will contain data about each scheme supported
/// by the server after the response. Note that unauthorized request will return a valid response - you
/// need to check the status code and and (re)configure AndroidMessageHandler instance accordingly by providing
/// both the credentials and the authentication scheme by setting the <see cref="PreAuthenticationData"/>
/// both the credentials and the authentication scheme by setting the <see cref="PreAuthenticationData"/>
/// property. If AndroidMessageHandler is not able to detect the kind of authentication scheme it will store an
/// instance of <see cref="AuthenticationData"/> with its <see cref="AuthenticationData.Scheme"/> property
/// set to <c>AuthenticationScheme.Unsupported</c> and the application will be responsible for providing an
Expand All @@ -226,12 +226,12 @@ public bool RequestNeedsAuthorization {
/// <summary>
/// <para>
/// If the request is to the server protected with a self-signed (or otherwise untrusted) SSL certificate, the request will
/// fail security chain verification unless the application provides either the CA certificate of the entity which issued the
/// fail security chain verification unless the application provides either the CA certificate of the entity which issued the
/// server's certificate or, alternatively, provides the server public key. Whichever the case, the certificate(s) must be stored
/// in this property in order for AndroidMessageHandler to configure the request to accept the server certificate.</para>
/// <para>AndroidMessageHandler uses a custom <see cref="KeyStore"/> and <see cref="TrustManagerFactory"/> to configure the connection.
/// <para>AndroidMessageHandler uses a custom <see cref="KeyStore"/> and <see cref="TrustManagerFactory"/> to configure the connection.
/// If, however, the application requires finer control over the SSL configuration (e.g. it implements its own TrustManager) then
/// it should leave this property empty and instead derive a custom class from AndroidMessageHandler and override, as needed, the
/// it should leave this property empty and instead derive a custom class from AndroidMessageHandler and override, as needed, the
/// <see cref="ConfigureTrustManagerFactory"/>, <see cref="ConfigureKeyManagerFactory"/> and <see cref="ConfigureKeyStore"/> methods
/// instead</para>
/// </summary>
Expand Down Expand Up @@ -328,7 +328,7 @@ string EncodeUrl (Uri url)
AssertSelf ();
if (request == null)
throw new ArgumentNullException (nameof (request));

if (!request.RequestUri.IsAbsoluteUri)
throw new ArgumentException ("Must represent an absolute URI", "request");

Expand Down Expand Up @@ -625,7 +625,7 @@ internal Task WriteRequestContentToOutputInternal (HttpRequestMessage request, H
return ret;
}

HttpContent GetErrorContent (HttpURLConnection httpConnection, HttpContent fallbackContent)
HttpContent GetErrorContent (HttpURLConnection httpConnection, HttpContent fallbackContent)
{
var contentStream = httpConnection.ErrorStream;

Expand Down Expand Up @@ -788,7 +788,7 @@ void CollectAuthInfo (HttpHeaderValueCollection <AuthenticationHeaderValue> head

RequestedAuthentication = authData.AsReadOnly ();
}

AuthenticationScheme GetAuthScheme (string scheme)
{
if (String.Compare ("basic", scheme, StringComparison.OrdinalIgnoreCase) == 0)
Expand Down Expand Up @@ -843,7 +843,7 @@ void CopyHeaders (HttpURLConnection httpConnection, HttpResponseMessage response
/// <summary>
/// Configure the <see cref="HttpURLConnection"/> before the request is sent. This method is meant to be overriden
/// by applications which need to perform some extra configuration steps on the connection. It is called with all
/// the request headers set, pre-authentication performed (if applicable) but before the request body is set
/// the request headers set, pre-authentication performed (if applicable) but before the request body is set
/// (e.g. for POST requests). The default implementation in AndroidMessageHandler does nothing.
/// </summary>
/// <param name="request">Request data</param>
Expand Down Expand Up @@ -897,9 +897,9 @@ internal Task SetupRequestInternal (HttpRequestMessage request, HttpURLConnectio
/// <summary>
/// Create and configure an instance of <see cref="TrustManagerFactory"/>. The <paramref name="keyStore"/> parameter is set to the
/// return value of the <see cref="ConfigureKeyStore"/> method, so it might be null if the application overrode the method and provided
/// no key store. It will not be <c>null</c> when the default implementation is used. The application can return <c>null</c> from this
/// no key store. It will not be <c>null</c> when the default implementation is used. The application can return <c>null</c> from this
/// method in which case AndroidMessageHandler will create its own instance of the trust manager factory provided that the <see cref="TrustCerts"/>
/// list contains at least one valid certificate. If there are no valid certificates and this method returns <c>null</c>, no custom
/// list contains at least one valid certificate. If there are no valid certificates and this method returns <c>null</c>, no custom
/// trust manager will be created since that would make all the HTTPS requests fail.
/// </summary>
/// <returns>The trust manager factory.</returns>
Expand All @@ -922,7 +922,7 @@ void AppendEncoding (string encoding, ref List <string>? list)
return;
list.Add (encoding);
}

async Task <HttpURLConnection> SetupRequestInternal (HttpRequestMessage request, URLConnection conn)
{
if (conn == null)
Expand All @@ -939,19 +939,19 @@ void AppendEncoding (string encoding, ref List <string>? list)

// SSL context must be set up as soon as possible, before adding any content or
// headers. Otherwise Java won't use the socket factory
SetupSSL (httpConnection as HttpsURLConnection);
SetupSSL (httpConnection as HttpsURLConnection, request);
if (request.Content != null)
AddHeaders (httpConnection, request.Content.Headers);
AddHeaders (httpConnection, request.Headers);

List <string>? accept_encoding = null;

decompress_here = false;
if ((AutomaticDecompression & DecompressionMethods.GZip) != 0) {
AppendEncoding (GZIP_ENCODING, ref accept_encoding);
decompress_here = true;
}

if ((AutomaticDecompression & DecompressionMethods.Deflate) != 0) {
AppendEncoding (DEFLATE_ENCODING, ref accept_encoding);
decompress_here = true;
Expand All @@ -970,7 +970,7 @@ void AppendEncoding (string encoding, ref List <string>? list)
if (!String.IsNullOrEmpty (cookieHeaderValue))
httpConnection.SetRequestProperty ("Cookie", cookieHeaderValue);
}

HandlePreAuthentication (httpConnection);
await SetupRequest (request, httpConnection).ConfigureAwait (continueOnCapturedContext: false);;
SetupRequestBody (httpConnection, request);
Expand All @@ -997,10 +997,11 @@ void AppendEncoding (string encoding, ref List <string>? list)
internal SSLSocketFactory? ConfigureCustomSSLSocketFactoryInternal (HttpsURLConnection connection)
=> ConfigureCustomSSLSocketFactoryInternal (connection);

void SetupSSL (HttpsURLConnection? httpsConnection)
void SetupSSL (HttpsURLConnection? httpsConnection, HttpRequestMessage requestMessage)
{
if (httpsConnection == null)
if (httpsConnection == null) {
return;
}

var socketFactory = ConfigureCustomSSLSocketFactory (httpsConnection);
if (socketFactory != null) {
Expand All @@ -1017,37 +1018,51 @@ void SetupSSL (HttpsURLConnection? httpsConnection)
}
#endif

var keyStore = KeyStore.GetInstance (KeyStore.DefaultType);
keyStore?.Load (null, null);
bool gotCerts = TrustedCerts?.Count > 0;
if (gotCerts) {
for (int i = 0; i < TrustedCerts!.Count; i++) {
Certificate cert = TrustedCerts [i];
if (cert == null)
continue;
keyStore?.SetCertificateEntry ($"ca{i}", cert);
}
}
var keyStore = InitializeKeyStore (out bool gotCerts);
keyStore = ConfigureKeyStore (keyStore);
var kmf = ConfigureKeyManagerFactory (keyStore);
var tmf = ConfigureTrustManagerFactory (keyStore);

if (tmf == null) {
// If there are no certs and no trust manager factory, we can't use a custom manager
// because it will cause all the HTTPS requests to fail because of unverified trust
// chain
if (!gotCerts)
if (tmf == null)
{
// If there are no trusted certs, no custom trust manager factory or custom certificate validation callback
// there is no point in changing the behavior of the default SSL socket factory
if (!gotCerts && ServerCertificateCustomValidationCallback == null)
return;

tmf = TrustManagerFactory.GetInstance (TrustManagerFactory.DefaultAlgorithm);
tmf?.Init (keyStore);
tmf?.Init (gotCerts ? keyStore : null); // only use the custom key store if the user defined any trusted certs
}

ITrustManager[]? trustManagers =
ServerCertificateCustomValidationCallback == null
? tmf?.GetTrustManagers ()
: X509TrustManagerWithValidationCallback.Inject(tmf?.GetTrustManagers (), requestMessage, ServerCertificateCustomValidationCallback);

var context = SSLContext.GetInstance ("TLS");
context?.Init (kmf?.GetKeyManagers (), tmf?.GetTrustManagers (), null);
context?.Init (kmf?.GetKeyManagers (), trustManagers, null);
httpsConnection.SSLSocketFactory = context?.SocketFactory;

KeyStore? InitializeKeyStore (out bool gotCerts)
{
var keyStore = KeyStore.GetInstance (KeyStore.DefaultType);
keyStore?.Load (null, null);
gotCerts = TrustedCerts?.Count > 0;

if (gotCerts) {
for (int i = 0; i < TrustedCerts!.Count; i++) {
Certificate cert = TrustedCerts [i];
if (cert == null)
continue;
keyStore?.SetCertificateEntry ($"ca{i}", cert);
}
}

return keyStore;
}
}



void HandlePreAuthentication (HttpURLConnection httpConnection)
{
var data = PreAuthenticationData;
Expand Down Expand Up @@ -1093,7 +1108,7 @@ void AddHeaders (HttpURLConnection conn, HttpHeaders headers)
conn.SetRequestProperty (header.Key, header.Value != null ? String.Join (GetHeaderSeparator (header.Key), header.Value) : String.Empty);
}
}

void SetupRequestBody (HttpURLConnection httpConnection, HttpRequestMessage request)
{
if (request.Content == null) {
Expand All @@ -1116,4 +1131,4 @@ void SetupRequestBody (HttpURLConnection httpConnection, HttpRequestMessage requ
}
}

}
}
Loading