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

certificate validation not working properly in .Net Android #95506

Closed
usbatwork opened this issue Dec 1, 2023 · 10 comments · Fixed by dotnet/android#8594 or dotnet/android#8637
Closed

Comments

@usbatwork
Copy link

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.None indicating that there are no errors and hence breaking the validation. The expectation would be to get SslPolicyErrors.RemoteCertificateChainErrors.
  • in a debug build the parameter returns SslPolicyErrors.RemoteCertificateChainErrors as 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 value RemoteCertificateChainErrors as expected
    release build -> parameter sslErrors has the value None

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.

@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Dec 1, 2023
@ghost ghost added the untriaged New issue has not been triaged by the area owner label Dec 1, 2023
@usbatwork usbatwork changed the title certificate validation nor working properly in .Net Android certificate validation not working properly in .Net Android Dec 1, 2023
@vcsjones vcsjones added area-System.Security os-android and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Dec 1, 2023
@ghost
Copy link

ghost commented Dec 1, 2023

Tagging subscribers to this area: @dotnet/area-system-security, @bartonjs, @vcsjones
See info in area-owners.md if you want to be subscribed.

Issue Details

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.None indicating that there are no errors and hence breaking the validation. The expectation would be to get SslPolicyErrors.RemoteCertificateChainErrors.
  • in a debug build the parameter returns SslPolicyErrors.RemoteCertificateChainErrors as 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 value RemoteCertificateChainErrors as expected
    release build -> parameter sslErrors has the value None

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.

Author: usbatwork
Assignees: -
Labels:

area-System.Security, os-android, untriaged

Milestone: -

@steveisok
Copy link
Member

/cc @simonrozsival

@simonrozsival simonrozsival self-assigned this Dec 8, 2023
@simonrozsival
Copy link
Member

@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 AndroidMessageHandler or more specifically in ServerCertificateCustomValidator. I will investigate what exactly the problem is next week.

@usbatwork
Copy link
Author

@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
Copy link
Author

@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 true the 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
Copy link
Member

@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
Copy link
Member

@usbatwork Just to make sure: did you configure NSAppTransportSecurity in 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
Copy link
Author

@simonrozsival Thanks a lot for the hint! I checked this.
NSAppTransportSecurity isn't set at the moment as it was not required in the past. We have been using the C# HttpClientHandler for both Android and iOS up to now. (That's why I quickly tried out whether setting UseNativeHttpHandler would do the trick for iOS, too).
We will check further whether it would better to set NSAppTransportSecurity rather than enforcing the C# handler.

@usbatwork
Copy link
Author

@simonrozsival We checked about NSAppTransportSecurity as 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
Copy link
Member

@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.

@ghost ghost removed the untriaged New issue has not been triaged by the area owner label Jan 4, 2024
grendello pushed a commit to dotnet/android that referenced this issue Jan 16, 2024
grendello pushed a commit to dotnet/android that referenced this issue Jan 17, 2024
jonathanpeppers pushed a commit to dotnet/android that referenced this issue Jan 17, 2024
Fixes: dotnet/runtime#95506

In Release configuration the `X509ExtendedTrustManagerInvoker` class is trimmed and so 
the `trustManager is IX509TrustManager tm` pattern matching doesn't work. 

This PR addresses the problem in two ways:

    * an internal X509 trust manager is now required - it can't silently work with a null 
       internal trust manager anymore
    * `[DynamicDependency]` attribute to prevent trimming of the invoker classes for 
       the `IX509TrustManager` interface and for the `X509ExtendedTrustManager` 
       abstract class
jonathanpeppers pushed a commit to dotnet/android that referenced this issue Jan 17, 2024
@github-actions github-actions bot locked and limited conversation to collaborators Feb 4, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.