Repository navigation
certificate validation not working properly in .Net Android #95506
Description
Description
I'm trying to validate a certificate using
clientHandler.ServerCertificateCustomValidationCallback
as described here:
https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclienthandler.servercertificatecustomvalidationcallback?view=net-8.0&viewFallbackFrom=net-android-34.0
In a .NET Android project it works as expected in debug mode only - but not in release mode: When connecting to a server with a self-signed certificate
- in a release build the parameter sslErrors returns
SslPolicyErrors.Noneindicating that there are no errors and hence breaking the validation. The expectation would be to getSslPolicyErrors.RemoteCertificateChainErrors. - in a debug build the parameter returns
SslPolicyErrors.RemoteCertificateChainErrorsas expected.
The settings for a debug and release build are the same to my knowledge.
In a .NET iOS project both debug and release builds work as expected.
Reproduction Steps
- create a .Net Android sample (just use the template that comes with VS)
- add some code to make a web service call as described in the above link
- connect to a server with a server certificate directly signed by a private CA with the private CA not trusted by the application
- run the app and check the callback parameter
sslErrors
debug build -> parameter sslErrors has the valueRemoteCertificateChainErrorsas expected
release build -> parameter sslErrors has the valueNone
Expected behavior
For both debug and release build SslPolicyErrors.RemoteCertificateChainErrors is returned when connecting to a server with a self-signed certificate.
Actual behavior
debug build -> parameter sslErrors has the value 'RemoteCertificateChainErrors' as expected
release build -> parameter sslErrors has the value 'None'
Regression?
It worked perfectly fine in Xamarin.Android. The issue started to occur in .NET6 Android and persists in .NET8 Android.
Known Workarounds
None found yet. A potential workaround could be to (re-)build the chain in the callback and validate the new chain. But this didn't work out likely due to the following issue:
#84202
Configuration
.NET6 Android and .NET 8 Android (.Net 7 hasn't been tested)
Android 14 simulator and also on real Android 14 device
Visual Studio Enterprise 2022 (64-bit) - Version 17.7.4 building on Windows 11
Other information
If I don't register the callback at all I get a trust exception as expected for release and debug builds.
I have tested only with a privately signed certificate as described in the repro steps. I haven't tested with a self-signed cert but would expect that the same happens here, too.
Activity
[-]certificate validation nor working properly in .Net Android[/-][+]certificate validation not working properly in .Net Android[/+]steveisok commentedon Dec 8, 2023
/cc @simonrozsival
simonrozsival commentedon Dec 8, 2023
@usbatwork I was able to replicate the issue. As a workaround, consider setting
<UseNativeHttpHandler>false</UseNativeHttpHandler>in your app's .csproj file. That fixed the issue for me. The managed SocketsHttpHandler works correctly. Note: This workaround will work only in .NET 8 and not in .NET 6.The problem seems to be in
AndroidMessageHandleror more specifically inServerCertificateCustomValidator. I will investigate what exactly the problem is next week.usbatwork commentedon Dec 11, 2023
@simonrozsival Thanks a lot for looking into this for us!
Yes, the workaround works in .NET8 for me, too. I tried this unsuccessfully earlier already. Chances are it was with .NET6 since we haven't fully migrated to .NET8 yet.
Many thanks for pointing out that it works in .NET8 only!
usbatwork commentedon Dec 14, 2023
@simonrozsival Not entirely sure but I might have found a related issue: We updated to .NET8 and applied the workaround on Android as suggested. Now we get an exception on iOS:
System.Net.Http.HttpRequestException: An SSL error has occurred and a secure connection to the server cannot be made.The validation callback seems to work fine. The parameters have the expected values. However, even if the validation callback returns
truethe exception is bubbling up and not suppressed as expected.Explicitly setting
<UseNativeHttpHandler>false</UseNativeHttpHandler>in the iOS project file seems to do the trick for iOS, too. This made me think that the issues might be related. As we have a workaround it is not critical for us but I thought I let you know.
simonrozsival commentedon Dec 14, 2023
@usbatwork hmm, that's interesting. I think the iOS issue is unrelated, the internal implementation is very different. I will try to reproduce the iOS issue and narrow down what could be going on there.
simonrozsival commentedon Dec 14, 2023
@usbatwork Just to make sure: did you configure
NSAppTransportSecurityin your Info.plist to allow insecure HTTP loads for your domain (https://developer.apple.com/documentation/security/preventing_insecure_network_connections)? If not, that is probably the cause of the issue.usbatwork commentedon Dec 15, 2023
@simonrozsival Thanks a lot for the hint! I checked this.
NSAppTransportSecurityisn't set at the moment as it was not required in the past. We have been using the C#HttpClientHandlerfor both Android and iOS up to now. (That's why I quickly tried out whether settingUseNativeHttpHandlerwould do the trick for iOS, too).We will check further whether it would better to set
NSAppTransportSecurityrather than enforcing the C# handler.usbatwork commentedon Dec 20, 2023
@simonrozsival We checked about
NSAppTransportSecurityas you suggested. Based on the Apple documentation it looks like this will result in a more strict review by Apple requiring explanation / justification why exceptions are made.Unfortunately we cannot limit these exceptions to certain domains only but would have to set it in general as we are not in control of this.
Our concern would be that using this tag allows much more than accepting specific self-signed certificates by the user. For example it would allow http which we don't want.
Please let us know in case we missed something here.
Hence enforcing
<UseNativeHttpHandler>false</UseNativeHttpHandler>in iOS, too seems the better option for us. Not supporting self-signed certificates is not an option right now.If you are aware of any side effects of setting
<UseNativeHttpHandler>false</UseNativeHttpHandler>on iOS or would advise against it for whatever reason please let us know so that we can check it.simonrozsival commentedon Jan 2, 2024
@usbatwork from what you describe, it seems that the managed handler is a better fit for your use case. The only side effect that I can think of is a slight increase in the .ipa size. Let me know if you run into any unexpected problems.
[Mono.Android] Fix ServerCertificateCustomValidator (#8594)
Fix ServerCertificateCustomValidator on API 21-23 (#8637)
Fix ServerCertificateCustomValidator on API 21-23 (#8637)
[Mono.Android] Fix ServerCertificateCustomValidator (#8594)
Fix ServerCertificateCustomValidator on API 21-23 (#8637)