Guide··SSLAndroid.NET MAUI

Resolving Javax.Net.Ssl.SSLHandshakeException

Learn how to connect to localhost over SSL and resolve handshake errors like java.security.cert.CertPathValidatorException from an Android emulator in .NET MAUI.

Aptabase Team @aptabase

When working with local HTTPS servers in mobile development, you may encounter SSL handshake issues such as Javax.Net.Ssl.SSLHandshakeException, Java.Security.Cert.CertificateException and CertPathValidatorException: Trust anchor for certification path not found. These errors often happen due to issues with trusting self-signed certificates or custom root certificates. This post will guide you through handling these exceptions in a .NET MAUI app using a custom DelegatingHandler and mkcert certificates.

Understanding the Error

The error stack trace highlights a common issue when using HTTPS in local development environments:

System.Net.Http.HttpRequestException: Connection failure
 ---> Javax.Net.Ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

This occurs because the Android emulator or iOS simulator does not trust the certificate used by the local server. Typically, development certificates are self-signed or issued by a local Certificate Authority (CA), like mkcert, and are not trusted by default on mobile devices.

Solution Overview

To solve this problem, you can create a custom DelegatingHandler that bypasses the SSL certificate validation for development purposes. This handler will specifically trust certificates generated by mkcert for local servers.

Create the Custom Handler

Create a class LocalHttpsClientHandler that inherits from DelegatingHandler and sets the InnerHandler to the appropriate platform-specific handler.

public class LocalHttpsClientHandler : DelegatingHandler
{
    public LocalHttpsClientHandler()
    {
#if ANDROID
        InnerHandler = new AndroidMessageHandler
        {
            ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
            {
                if (cert?.Issuer != null && cert.Issuer.Contains("CN=mkcert"))
                {
                    return true;
                }
                return errors == System.Net.Security.SslPolicyErrors.None;
            }
        };
#elif IOS
        InnerHandler = new NSUrlSessionHandler
        {
            TrustOverrideForUrl = (sender, url, trust) => url.StartsWith("https://localhost"),
        };
#else
        InnerHandler = new HttpClientHandler(); // Fallback handler for other platforms
#endif
    }
}

Use the Custom Handler in HttpClient

public class DataService
{
    public HttpClient CreateHttpClient()
    {
        var handler = new LocalHttpsClientHandler();
        return new HttpClient(handler);
    }

    public async Task<string> GetDataAsync(string url)
    {
        using var client = CreateHttpClient();
        var response = await client.GetStringAsync(url);
        return response;
    }
}

Handling URLs

When setting the base URL for your HTTP client in a .NET MAUI app, you need to account for differences in accessing localhost between Android and other platforms. Each instance of the Android emulator is isolated from your development machine network interfaces, and runs behind a virtual router. Therefore, an emulated device can’t see your development machine or other emulator instances on the network.

However, the virtual router for each emulator manages a special network space that includes pre-allocated addresses, with the 10.0.2.2 address being an alias to your host loopback interface (127.0.0.1 on your development machine).

string baseUrl = DeviceInfo.Platform == DevicePlatform.Android ? 
    "https://10.0.2.2:3000" : "https://localhost:3000";

Example usage

public class DataService
{
    private readonly HttpClient _httpClient;

    public DataService()
    {
        var handler = new LocalHttpsClientHandler();
        _httpClient = new HttpClient(handler)
        {
            BaseAddress = new Uri(DeviceInfo.Platform == DevicePlatform.Android ? 
                "https://10.0.2.2:3000" : "https://localhost:3000")
        };
    }

    public async Task<string> FetchDataAsync(string endpoint)
    {
        var response = await _httpClient.GetStringAsync(endpoint);
        return response;
    }
}

Additional Resources

Conclusion

Using a custom DelegatingHandler like LocalHttpsClientHandler in .NET MAUI can help bypass SSL handshake issues with self-signed or local development certificates, allowing you to focus on building your app without worrying about certificate trust issues.

At Aptabase, we’re developing an open-source and privacy-centric analytics platform for desktop and mobile apps. Aptabase has SDKs for various frameworks, including .NET MAUI.

If you have any questions or feedback, feel free to reach out on Twitter or join us on Discord and we’ll be happy to help!

Analytics for MAUI appsWithout compromising on privacy

Aptabase is a privacy-first analytics platform for .NET MAUI apps. Get insights into your app's usage in minutes.

Learn more
Aptabase Dashboard Screenshot

Want to learn more?

Asynchronous Events in .NET MAUI

Guide··System.Threading.Channels.NET MAUI

Asynchronous Events in .NET MAUI

Learn how to use System.Threading.Channels for efficient asynchronous event processing in your .NET MAUI applications.

Reading PDFs in .NET MAUI

Guide··PDFFilePicker.NET MAUI

Reading PDFs in .NET MAUI

Learn how to configure permissions, build a file-picker and handle PDF files in a cross-platform compatible way in .NET MAUI.

Where would you prefer to host your data?

European UnionGermanyUnited StatesVirginia

We typically advise selecting the region nearest to the majority of your users' locations. However, if your user base is global and dispersed, opt for the region that is geographically closest to your own location.