xamarin .NET Maui项目中的“未找到证书路径的信任锚”正在尝试联系本地.NET WebApi

but5z9lq  于 2022-12-25  发布在  .NET
关注(0)|答案(5)|浏览(466)

我是移动开发新手,我正在尝试让我的. NET Maui应用程序连接到本地ASP.NET核心网站(API)。
我当前被此异常阻止:
第一个月
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
运行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"));

但问题是,我从来没有进入ServerCertificateCustomValidationCallback。
我也试过

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

但也没找到。
. NET MAUI中有什么变化吗?

euoag5mw

euoag5mw1#

当我试图让SignalR客户端连接我的本地测试服务器时,我遇到了完全相同的问题,在深入研究源代码后,我发现HttpClientHandler actually uses AndroidMessageHandler as its underlying handler
虽然AndroidMessageHandler实现了ServerCertificateCustomValidationCallback属性,但在发送请求时从不使用其值。此问题在this pull request中得到解决。
现在,要在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;
        }
    }
}

如果您还想禁用主机名验证,则需要动态继承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();
    }
}

在您的MauiProgram中调用DangerousAndroidMessageHandlerEmitter.RegisterDangerousTrustProvider

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

最后一步,你需要告诉Xamarin使用你动态生成的DangerousAndroidMessageHandler,你应该可以通过在你的csproj文件中设置AndroidHttpClientHandlerType为处理程序类型的全限定名来实现:

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

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

XA_HTTP_CLIENT_HANDLER_TYPE=DangerousAndroidMessageHandler, DangerousAndroidMessageHandler

上述解决方案也适用于ClientWebSocket和其他使用SslStream的应用,这意味着您可以使用WebSocket传输连接到测试SignalR服务器(这是我尝试实现的)。
请记住,仅在调试版本中执行此操作

mklgxw1f

mklgxw1f2#

在Android平台的MainApplication.cs中:

#if DEBUG
[Application(AllowBackup = false, Debuggable = true, UsesCleartextTraffic = true)]
#else
[Application]
#endif
public class MainApplication : MauiApplication

在ASP.NET核心API程序.cs中:

#if !DEBUG
app.UseHttpsRedirection();
#endif

在Maui程序.cs中:

#if DEBUG
        private static readonly string Base = "http://192.168.0.15";
        private static readonly string ApiBaseUrl = $"{Base}:5010/";
    #else
        private static readonly string ApiBaseUrl = "https://YOUR_APP_SERVICE.azurewebsites.net/";
    #endif

...

builder.Services.AddSingleton(sp => new HttpClient { BaseAddress = new Uri(ApiBaseUrl) });

在ASP.NET核心API启动设置.json中:

"applicationUrl": "https://*:5011;http://*:5010"
jgovgodb

jgovgodb3#

简介

因为一些"超级聪明"的评论家认为它会-引用-
为了推广产品或服务而污损帖子,或故意破坏帖子
如果nolex的回答被编辑成

  • 修复了导致她/他的解决方案在最新MAUI中失败的错误(使用VS版本17.2 Preview 2.1)
  • 从她/他的代码中删除不必要/过时的内容
  • 通过使用至少从C#10.0(如果不是9.0)开始可用的C#语法糖来简化它

我将更新后的代码作为一个单独的答案发布。
问题
正如nolex在他的回答中已经指出的,HttpClientHandleractually usesAndroidMessageHandler作为它的底层处理程序-它 * 确实 * 实现了已知的ServerCertificateCustomValidationCallback。然而,它的值在发送请求时从未使用过,您可以通过搜索链接的源代码文件中该属性的另一个示例来轻松地验证自己。
甚至有一个pull request从今年2月11日开始等待(进一步的)批准和合并来解决这个问题。但是即使在17天前的今天,它仍然没有合并。另外,5个检查现在又失败了。

唯一的解决方案-暂时是

如果您希望(甚至要求)在运行Android Emulator的同一台机器上运行(调试)服务器构建版本,并且需要它们之间的安全连接,那么您只有一种方法:使用您自己的DangerousTrustManager覆盖Android的默认TrustManager。这允许您的应用绕过***任何***证书验证,因此使用前缀Dangerous。😉
我再怎么强调都不为过,所以再一次:不要***************使用本地运行的调试版本以外的代码。不要在测试环境中使用。不要在临时环境中使用。说真的!
不过,这里也有一个好处:这个解决方案允许任何使用SslStream的连接尝试,例如ClientWebSocket,成功。2因此,你本地SignalR服务器的WebSocket传输也能正常工作!

关于以下代码的注解:

1.由于我为整个MAUI项目启用了Nullable,您将在string s等上看到?后缀。
1.我无法忍受代码在任何地方水平滚动,因此过度使用了换行符。
好吧,让我们进入正题:

MyMauiApp\Platforms\Android\DangerousTrustProvider.cs
#if DEBUG // Ensure this never leaves debug stages.
using Java.Net;
using Java.Security;
using Java.Security.Cert;
using Javax.Net.Ssl;

namespace MyMauiApp.Platforms.Android;

internal class DangerousTrustProvider : Provider
{
  private const string DANGEROUS_ALGORITHM = nameof(DANGEROUS_ALGORITHM);

  // NOTE: Empty ctor, i. e. without Put(), works for me as well,
  // but I'll keep it for the sake of completeness.
  public DangerousTrustProvider()
    : base(nameof(DangerousTrustProvider), 1, "Dangerous debug TrustProvider") =>
    Put(
      $"{nameof(DangerousTrustManagerFactory)}.{DANGEROUS_ALGORITHM}",
      Java.Lang.Class.FromType(typeof(DangerousTrustManagerFactory)).Name);

  public static void Register()
  {
    if (Security.GetProvider(nameof(DangerousTrustProvider)) is null)
    {
      Security.InsertProviderAt(new DangerousTrustProvider(), 1);
      Security.SetProperty(
        $"ssl.{nameof(DangerousTrustManagerFactory)}.algorithm", DANGEROUS_ALGORITHM);
    }
  }

  public class DangerousTrustManager : X509ExtendedTrustManager
  {
    public override void CheckClientTrusted(X509Certificate[]? chain, string? authType) { }
    public override void CheckClientTrusted(X509Certificate[]? chain, string? authType,
      Socket? socket) { }
    public override void CheckClientTrusted(X509Certificate[]? chain, string? authType,
      SSLEngine? engine) { }
    public override void CheckServerTrusted(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 X509Certificate[] GetAcceptedIssuers() =>
      Array.Empty<X509Certificate>();
  }

  public class DangerousTrustManagerFactory : TrustManagerFactorySpi
  {
    protected override ITrustManager[] EngineGetTrustManagers() =>
      new[] { new DangerousTrustManager() };

    protected override void EngineInit(IManagerFactoryParameters? parameters) { }

    protected override void EngineInit(KeyStore? store) { }
  }
}
#endif

由于Android执行额外的主机名验证,因此还需要动态继承AndroidMessageHandler,以便通过返回一个伪IHostNameVerifier来覆盖其内部GetSSLHostnameVerifier方法。

MyMauiApp\Platforms\Android\DangerousAndroidMessageHandlerEmitter.cs
#if DEBUG // Ensure this never leaves debug stages.
using System.Reflection;
using System.Reflection.Emit;

using Javax.Net.Ssl;
using Xamarin.Android.Net;

namespace MyMauiApp.Platforms.Android;

internal static class DangerousAndroidMessageHandlerEmitter
{
  private const string NAME = "DangerousAndroidMessageHandler";

  private static Assembly? EmittedAssembly { get; set; } = null;

  public static void Register(string handlerName = NAME, string assemblyName = NAME) =>
    AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
      assemblyName.Equals(args.Name)
        ? (EmittedAssembly ??= Emit(handlerName, assemblyName))
        : null;

  private static AssemblyBuilder Emit(string handlerName, string assemblyName)
  {
    var assembly = AssemblyBuilder.DefineDynamicAssembly(
      new AssemblyName(assemblyName), AssemblyBuilderAccess.Run);
    var builder = assembly.DefineDynamicModule(assemblyName)
                          .DefineType(handlerName, TypeAttributes.Public);
    builder.SetParent(typeof(AndroidMessageHandler));
    builder.DefineDefaultConstructor(MethodAttributes.Public);

    var generator = builder.DefineMethod(
                             "GetSSLHostnameVerifier",
                             MethodAttributes.Public | MethodAttributes.Virtual,
                             typeof(IHostnameVerifier),
                             new[] { typeof(HttpsURLConnection) })
                           .GetILGenerator();
    generator.Emit(
      OpCodes.Call,
      typeof(DangerousHostNameVerifier)
        .GetMethod(nameof(DangerousHostNameVerifier.Create))!);
    generator.Emit(OpCodes.Ret);

    builder.CreateType();

    return assembly;
  }

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

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

作为最后一步,需要为Android MAUI调试构建注册新创建的类型。

MyMauiApp\MauiProgram.cs
namespace MyMauiApp;

public static class MauiProgram
{
  public static MauiApp CreateMauiApp()
  {
    var builder = MauiApp.CreateBuilder();
    builder.UseMauiApp<App>()
           .ConfigureFonts(fonts => fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"));
    builder.Services.AddTransient(provider => new HttpClient
    {
      BaseAddress = new Uri($@"https://{(DeviceInfo.DeviceType == DeviceType.Virtual
        ? "10.0.2.2" : "localhost")}:5001/"),
      Timeout = TimeSpan.FromSeconds(10)
    });

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

    return builder.Build();
  }
}

最后,为了让MAUI/Xamarin * 真正 * 使用动态生成的DangerousAndroidMessageHandler,需要在MyMauiApp.csproj文件中包含一个AndroidHttpClientHandlerType属性,该属性包含***两次***处理程序的名称。

MyMauiApp\Platforms\Android\MyMauiApp.csproj
<PropertyGroup>
  <AndroidHttpClientHandlerType>DangerousAndroidMessageHandler, DangerousAndroidMessageHandler</AndroidHttpClientHandlerType>
</PropertyGroup>

或者,也可以将Android运行时环境变量XA_HTTP_CLIENT_HANDLER_TYPE设置为相同的值:

XA_HTTP_CLIENT_HANDLER_TYPE=DangerousAndroidMessageHandler, DangerousAndroidMessageHandler

结束

在正式修复到来之前,请记住:为了这个世界的安全,不要在生产中使用这个!
现在,去追逐你的(应用)梦想吧🥳

btqmn9zl

btqmn9zl4#

如果我们被迫实现覆盖证书验证的类,强调这一点,而不离开开发环境,可能也会用更少的代码行做坏事。
把https改成http就行了。
在客户端项目中,将API的URL更改为http,并在AndroidManifest. xml中添加android:usesCleartextTraffic="true"
在服务器项目中注解掉行app. UseHttpRedirection();
这太可怕了,我希望它能很快得到修复。

syqv5f0l

syqv5f0l5#

忽略所有证书的替代方案是install certificate on your dev device yourself,它也将解决MAUI/Xamarin问题与ServerCertificateCustomValidationCallback的Android SSL连接。对于iOS,它的工作开箱即用,对于Android,你需要允许应用使用用户证书,如下所述:如何在Android设备上安装可信CA证书?

相关问题