“找不到认证路径的信任锚。” 在 .NET Maui 项目中尝试联系本地 .NET WebApi

Mar*_*arc 12 c# ssl-certificate xamarin asp.net-core maui

我是移动开发新手,我正在尝试将我的 .NET Maui 应用程序连接到本地 ASP.NET Core 网站 (API)。

我目前被此异常阻止:

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

我已关注这篇文章https://learn.microsoft.com/en-us/xamarin/cross-platform/deploy-test/connect-to-local-web-services#bypass-the-certificate-security-check

运行dotnet dev-certs https --trust回报A valid HTTPS certificate is already present.

我当前的代码是:

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"));
Run Code Online (Sandbox Code Playgroud)

但问题是我从未输入 ServerCertificateCustomValidationCallback。

我也尝试过

        ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) =>
        {
            return true;
        };
Run Code Online (Sandbox Code Playgroud)

但也没有运气。

.NET MAUI 中有什么变化吗?

小智 9

当我尝试让 SignalR 客户端连接我的本地测试服务器时,我遇到了完全相同的问题。在深入研究源代码后,我发现HttpClientHandler实际上使用AndroidMessageHandler它作为其底层处理程序

虽然AndroidMessageHandler实现了一个属性,但在发送请求时永远不会使用ServerCertificateCustomValidationCallback它的值。此拉取请求中解决了此问题。

目前,要在 Android 上禁用服务器证书验证,您可以实现一个自定义TrustProvider,它将绕过任何证书验证:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您还想禁用主机名验证,则需要动态继承AndroidMessageHandler并覆盖其内部GetSSLHostnameVerifier方法,以返回一个虚拟值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();
    }
}
Run Code Online (Sandbox Code Playgroud)

致电DangerousAndroidMessageHandlerEmitter.RegisterDangerousTrustProvider在您的MauiProgram

#if ANDROID && DEBUG
Platforms.Android.DangerousAndroidMessageHandlerEmitter.Register();
Platforms.Android.DangerousTrustProvider.Register();
#endif
Run Code Online (Sandbox Code Playgroud)

最后一步,您需要告诉 Xamarin 使用动态生成的DangerousAndroidMessageHandler. 您应该能够通过AndroidHttpClientHandlerType在文件中设置处理程序类型的完全限定名称来执行此操作csproj

<PropertyGroup>
    <AndroidHttpClientHandlerType>DangerousAndroidMessageHandler, DangerousAndroidMessageHandler</AndroidHttpClientHandlerType>
</PropertyGroup>
Run Code Online (Sandbox Code Playgroud)

或者将 Android 运行时环境变量设置XA_HTTP_CLIENT_HANDLER_TYPE为处理程序的名称:

XA_HTTP_CLIENT_HANDLER_TYPE=DangerousAndroidMessageHandler, DangerousAndroidMessageHandler
Run Code Online (Sandbox Code Playgroud)

上述解决方法也适用于ClientWebSocket任何其他使用SslStream. 这意味着您可以使用 WebSocket 传输连接到测试 SignalR 服务器(这就是我试图实现的目标)。

请记住,仅在调试版本中执行此操作