Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Trust anchor for certification path not found." in a .NET Maui Project trying to contact a local .NET WebApi

I'm new to mobile development and I'm trying to have my .NET Maui app connect to a local ASP.NET Core website (API).

I am currently blocked by this exception:

System.Net.WebException: 'java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.'

I have followed this article https://learn.microsoft.com/en-us/xamarin/cross-platform/deploy-test/connect-to-local-web-services#bypass-the-certificate-security-check

Running dotnet dev-certs https --trust returns A valid HTTPS certificate is already present.

My current code is:

HttpClientHandler handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
  {
     if (cert.Issuer.Equals("CN=localhost"))
          return true;
     return errors == System.Net.Security.SslPolicyErrors.None;
  };

var httpclient = new HttpClient(handler);
var test = await httpclient.PostAsync($"https://10.0.2.2:44393/" + uri, new StringContent(serializedItem, Encoding.UTF8, "application/json"));

But the thing is that i never enter the ServerCertificateCustomValidationCallback.

I also tried

        ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) =>
        {
            return true;
        };

But no luck with that either.

Did something change in .NET MAUI?

like image 452
Marc Avatar asked Jan 25 '26 06:01

Marc


1 Answers

I encountered exactly the same problem when I was trying to get SignalR client to connect my local test server. After digging into the source code, I found that HttpClientHandler actually uses AndroidMessageHandler as its underlying handler.

While AndroidMessageHandler implements a ServerCertificateCustomValidationCallback property, its value is never used when sending requests. This issue is addressed in this pull request.

For now, to disable server certificate verification on Android, you can implement a custom TrustProvider which will bypass any certificate verification:

using Java.Net;
using Java.Security;
using Java.Security.Cert;
using Javax.Net.Ssl;

namespace MyApp.Platforms.Android
{
    internal class DangerousTrustProvider : Provider
    {
        private const string TRUST_PROVIDER_ALG = "DangerousTrustAlgorithm";
        private const string TRUST_PROVIDER_ID = "DangerousTrustProvider";

        public DangerousTrustProvider() : base(TRUST_PROVIDER_ID, 1, string.Empty)
        {
            var key = "TrustManagerFactory." + DangerousTrustManagerFactory.GetAlgorithm();
            var val = Java.Lang.Class.FromType(typeof(DangerousTrustManagerFactory)).Name;
            Put(key, val);
        }

        public static void Register()
        {
            Provider registered = Security.GetProvider(TRUST_PROVIDER_ID);
            if (null == registered)
            {
                Security.InsertProviderAt(new DangerousTrustProvider(), 1);
                Security.SetProperty("ssl.TrustManagerFactory.algorithm", TRUST_PROVIDER_ALG);
            }
        }

        public class DangerousTrustManager : X509ExtendedTrustManager
        {
            public override void CheckClientTrusted(X509Certificate[] chain, string authType, Socket socket) { }

            public override void CheckClientTrusted(X509Certificate[] chain, string authType, SSLEngine engine) { }

            public override void CheckClientTrusted(X509Certificate[] chain, string authType) { }

            public override void CheckServerTrusted(X509Certificate[] chain, string authType, Socket socket) { }

            public override void CheckServerTrusted(X509Certificate[] chain, string authType, SSLEngine engine) { }

            public override void CheckServerTrusted(X509Certificate[] chain, string authType) { }

            public override X509Certificate[] GetAcceptedIssuers() => Array.Empty<X509Certificate>();
        }

        public class DangerousTrustManagerFactory : TrustManagerFactorySpi
        {
            protected override void EngineInit(IManagerFactoryParameters mgrparams) { }

            protected override void EngineInit(KeyStore keystore) { }

            protected override ITrustManager[] EngineGetTrustManagers() => new ITrustManager[] { new DangerousTrustManager() };

            public static string GetAlgorithm() => TRUST_PROVIDER_ALG;
        }
    }
}

If you also want to disable host name verfication, you'll need to dynamically inherit from AndroidMessageHandler and override its internal GetSSLHostnameVerifier method, to return a dummy IHostNameVerifier:

using Javax.Net.Ssl;
using System.Reflection;
using System.Reflection.Emit;
using Xamarin.Android.Net;

namespace MyApp.Platforms.Android
{
    static class DangerousAndroidMessageHandlerEmitter
    {
        private static Assembly _emittedAssembly = null;

        public static void Register(string handlerTypeName = "DangerousAndroidMessageHandler", string assemblyName = "DangerousAndroidMessageHandler")
        {
            AppDomain.CurrentDomain.AssemblyResolve += (s, e) =>
            {
                if (e.Name == assemblyName)
                {
                    if (_emittedAssembly == null)
                    {
                        _emittedAssembly = Emit(handlerTypeName, assemblyName);
                    }

                    return _emittedAssembly;
                }
                return null;
            };
        }

        private static AssemblyBuilder Emit(string handlerTypeName, string assemblyName)
        {
            var assembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(assemblyName), AssemblyBuilderAccess.Run);
            var module = assembly.DefineDynamicModule(assemblyName);

            DefineDangerousAndroidMessageHandler(module, handlerTypeName);

            return assembly;
        }

        private static void DefineDangerousAndroidMessageHandler(ModuleBuilder module, string handlerTypeName)
        {
            var typeBuilder = module.DefineType(handlerTypeName, TypeAttributes.Public);
            typeBuilder.SetParent(typeof(AndroidMessageHandler));
            typeBuilder.DefineDefaultConstructor(MethodAttributes.Public);

            var methodBuilder = typeBuilder.DefineMethod(
                "GetSSLHostnameVerifier",
                MethodAttributes.Public | MethodAttributes.Virtual,
                typeof(IHostnameVerifier),
                new[] { typeof(HttpsURLConnection) }
            );

            var generator = methodBuilder.GetILGenerator();
            generator.Emit(OpCodes.Call, typeof(DangerousHostNameVerifier).GetMethod("Create"));
            generator.Emit(OpCodes.Ret);

            typeBuilder.CreateType();
        }
    }

    public class DangerousHostNameVerifier : Java.Lang.Object, IHostnameVerifier
    {
        public bool Verify(string hostname, ISSLSession session)
        {
            return true;
        }

        public static IHostnameVerifier Create() => new DangerousHostNameVerifier();
    }
}

Call DangerousAndroidMessageHandlerEmitter.Register and DangerousTrustProvider in your MauiProgram:

#if ANDROID && DEBUG
Platforms.Android.DangerousAndroidMessageHandlerEmitter.Register();
Platforms.Android.DangerousTrustProvider.Register();
#endif

One last step, you need to tell Xamarin to use your dynamically generated DangerousAndroidMessageHandler. You should be able to do so by setting AndroidHttpClientHandlerType to fully-qualified name of the handler type in your csproj file:

<PropertyGroup>
    <AndroidHttpClientHandlerType>DangerousAndroidMessageHandler, DangerousAndroidMessageHandler</AndroidHttpClientHandlerType>
</PropertyGroup>

Or set Android runtime environment variable XA_HTTP_CLIENT_HANDLER_TYPE to the name of the handler:

XA_HTTP_CLIENT_HANDLER_TYPE=DangerousAndroidMessageHandler, DangerousAndroidMessageHandler

The above workaround will also work for ClientWebSocket and anything else using SslStream. Which means you can connect to your test SignalR server with the WebSocket transport (which is what I was trying to achieve).

Just remember, DO THIS ONLY IN DEBUG BUILDS.

like image 148
noelex Avatar answered Jan 27 '26 20:01

noelex



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!