NET MAUI开发Android安全浏览器实现mTLS证书认证

Viewed 0

本文分享在Android设备上使用mTLS(双向认证)证书时遇到的各种问题,以及如何通过.NET MAUI开发专属的“安全套壳浏览器”来优雅解决这些问题的完整实战经验。

在上一篇文章中,我们配置了Nginx的mTLS双向认证“叹息之墙”,实现了服务器端的固若金汤防御。但当我们将视线转回移动端时,残酷的现实给了我们当头一棒:即使成功安装了用户证书,系统自带的浏览器、Chrome或Firefox也往往不弹出证书选择框,导致无法访问。折腾各种系统的凭据安装配置后依然无解。

既然现成的工具都失效,作为.NET开发者,我们决定使用.NET MAUI自己开发一个“安全套壳浏览器”。通过夺回底层网络的控制权,绕开系统的限制,实现真正的无感安全访问。

核心思路:绕过系统限制

常规浏览器的证书由Android系统的KeyChain统一接管,限制极多。但在MAUI中,我们可以利用WebView控件,并通过自定义Android原生的WebViewClient,拦截Nginx发来的证书请求。策略是彻底无视系统的证书库,在App内部直接读取本地的.pfx文件,并在底层回调中强行把证书塞给服务器。

构建MAUI极简界面

我们需要一个设置页面(用于导入证书、填写密码和URL)以及一个承载网页的主页面。

设置页面UI

提供基础的配置输入框,并利用MAUI的FilePicker将用户选择的证书文件拷贝到App的私有安全目录中。以下是SettingsPage.xaml的代码示例:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="StealthClaw.SettingsPage"
             Title="安全配置">
    <VerticalStackLayout Padding="20" Spacing="15">
        <Label Text="控制面板地址 (https://...)" FontAttributes="Bold" />
        <Entry x:Name="UrlEntry" />
        <Button Text="选择并导入 .pfx 证书" Clicked="OnSelectCertClicked" />
        <Label Text="证书提取密码" FontAttributes="Bold" />
        <Entry x:Name="PasswordEntry" IsPassword="True" />
        <Button Text="保存配置" Clicked="OnSaveClicked" BackgroundColor="#2196F3" TextColor="White"/>
    </VerticalStackLayout>
</ContentPage>

后台的C#代码利用Preferences和SecureStorage将路径和密码存储起来,确保安全性。

编写Android底层拦截器

在MAUI项目的Platforms/Android目录下,新建MTlsWebViewClient.cs。这是整个App的核心,需要注意.NET MAUI与Android底层JNI交互时的类型转换异常(Specified cast is not valid)。C#的X509Certificate无法直接强转为Java的证书接口,必须使用MAUI提供的JavaCast()扩展方法。以下是排雷完毕的代码:

#if ANDROID
using Android.Webkit;
using Android.Runtime;
using Java.Security;
using Java.Security.Cert;
using System.IO;
using Microsoft.Maui.Storage;

namespace StealthClaw.Platforms.Android;

public class MTlsWebViewClient : WebViewClient
{
    public override void OnReceivedClientCertRequest(global::Android.Webkit.WebView view, ClientCertRequest request)
    {
        try
        {
            string certPath = Preferences.Default.Get("CertPath", "");
            string certPassword = SecureStorage.Default.GetAsync("CertPassword").GetAwaiter().GetResult() ?? "";
            using var stream = new FileStream(certPath, FileMode.Open, FileAccess.Read);
            var keyStore = KeyStore.GetInstance("PKCS12");
            keyStore.Load(stream, certPassword.ToCharArray());
            string targetAlias = null;
            var aliases = keyStore.Aliases();
            while (aliases.HasMoreElements)
            {
                string currentAlias = aliases.NextElement().ToString();
                if (keyStore.IsKeyEntry(currentAlias))
                {
                    targetAlias = currentAlias;
                    break;
                }
            }
            var rawKey = keyStore.GetKey(targetAlias, certPassword.ToCharArray());
            var privateKey = rawKey.JavaCast<IPrivateKey>();
            var certChain = keyStore.GetCertificateChain(targetAlias);
            var x509CertChain = new Java.Security.Cert.X509Certificate[certChain.Length];
            for (int i = 0; i < certChain.Length; i++)
            {
                x509CertChain[i] = certChain[i].JavaCast<Java.Security.Cert.X509Certificate>();
            }
            request.Proceed(privateKey, x509CertChain);
        }
        catch (System.Exception ex)
        {
            System.Diagnostics.Debug.WriteLine($"拦截器异常: {ex.Message}");
            request.Ignore();
        }
    }
    public override void OnReceivedSslError(global::Android.Webkit.WebView view, SslErrorHandler handler, Android.Net.Http.SslError error)
    {
        handler.Proceed();
    }
}
#endif

注入拦截器并治理“白屏”

代码写好后,需要在App启动时将其挂载到MAUI的WebView上。此外,现代前端面板大量使用Vue/React等单页应用框架,如果Android WebView不开启特定设置,网页加载后可能白屏。修改MauiProgram.cs,设置WebView的底层映射:

public static MauiApp CreateMauiApp()
{
    var builder = MauiApp.CreateBuilder();
    builder.UseMauiApp<App>();
    Microsoft.Maui.Handlers.WebViewHandler.Mapper.AppendToMapping("mTLS_Setup", (handler, view) =>
    {
#if ANDROID
        handler.PlatformView.SetWebViewClient(new StealthClaw.Platforms.Android.MTlsWebViewClient());
        handler.PlatformView.SetWebChromeClient(new global::Android.Webkit.WebChromeClient());
        handler.PlatformView.Settings.JavaScriptEnabled = true;
        handler.PlatformView.Settings.DomStorageEnabled = true;
        global::Android.Webkit.WebView.SetWebContentsDebuggingEnabled(true);
#endif
    });
    return builder.Build();
}

运行效果

完成以上配置并编译运行后,在设置页面填入地址,导入.pfx证书并输入密码进行验证。导入成功后,会显示证书的基本信息。此时返回主页面,之前那些在原生浏览器里无法连接的面板现在可以秒开。之后输入密码或Token登录,在控制台或已登录的session中批准登录请求,即可实现安全访问。

总结

借助.NET MAUI强大的平台穿透能力,我们完美绕过了Android系统对TLS握手的苛刻限制。这个轻量级的“套壳浏览器”不仅是访问特定面板的神器,更是任何需要mTLS高级安全认证场景的通用解法,实现了独占的零信任公网服务访问。

0 Answers