c# - xamarin 中带有 NSUrlSessionDelegate 的客户端证书

标签 c# xamarin xamarin.ios client-certificates

我想在我的 xamarin 应用程序中实现客户端证书身份验证。 最重要的是,我使用的是自定义证书颁发机构 (CA) 和 TLS 1.2。

到目前为止,我设法使用 android、UWP 和 WPF 让它运行起来。唯一缺少的平台是 ios。

这是我的 NSUrlSessionDelegate:

public class SSLSessionDelegate : NSUrlSessionDelegate, INSUrlSessionDelegate
{
    private NSUrlCredential Credential { get; set; }
    private SecIdentity identity = null;
    private X509Certificate2 ClientCertificate = null;

    private readonly SecCertificate CACertificate = null;

    public SSLSessionDelegate(byte[] caCert) : base()
    {
        if (caCert != null)
        {
            CACertificate = new SecCertificate(new X509Certificate2(caCert));
        }
    }

    public void SetClientCertificate(byte[] pkcs12, char[] password)
    {
        if (pkcs12 != null)
        {
            ClientCertificate = new X509Certificate2(pkcs12, new string(password));
            identity = SecIdentity.Import(ClientCertificate);

            SecCertificate certificate = new SecCertificate(ClientCertificate);
            SecCertificate[] certificates = { certificate };

            Credential = NSUrlCredential.FromIdentityCertificatesPersistance(identity, certificates, NSUrlCredentialPersistence.ForSession);
        }
        else
        {
            ClientCertificate = null;
            identity = null;
            Credential = null;
        }
    }

    public override void DidReceiveChallenge(NSUrlSession session, NSUrlAuthenticationChallenge challenge, Action<NSUrlSessionAuthChallengeDisposition, NSUrlCredential> completionHandler)
    {
        if (challenge.ProtectionSpace.AuthenticationMethod == NSUrlProtectionSpace.AuthenticationMethodClientCertificate)
        {
            NSUrlCredential c = Credential;
            if (c != null)
            {
                completionHandler.Invoke(NSUrlSessionAuthChallengeDisposition.UseCredential, c);
                return;
            }
        }

        if (challenge.ProtectionSpace.AuthenticationMethod == NSUrlProtectionSpace.AuthenticationMethodServerTrust)
        {
            SecTrust secTrust = challenge.ProtectionSpace.ServerSecTrust;
            secTrust.SetAnchorCertificates(new SecCertificate[] {
                CACertificate
            });
            secTrust.SetAnchorCertificatesOnly(true);

        }
        completionHandler.Invoke(NSUrlSessionAuthChallengeDisposition.PerformDefaultHandling, null);
    }
}

如果没有配置客户端证书,此方法有效 DidReceiveChallenge 使用 AuthenticationMethodServerTrust 调用一次并接受自定义 CA。

但是一旦配置了客户端证书,DidReceiveChallenge 就会被调用 4 次(每个 AuthenticationMethod 调用两次),我得到 NSURLErrorDomain (-1200)错误。

有人知道我做错了什么吗?


更新

SSLSessionDelegate 的用法如下:

public class HttpsServer : AbstractRemoteServer, IRemoteServer
{
    private static readonly Logger LOG = LogManager.GetLogger();

    private SSLSessionDelegate sSLSessionDelegate;

    private NSUrlSession session;

    private NSUrl baseAddress;

    public HttpsServer()
    {
        sSLSessionDelegate = new SSLSessionDelegate(SSLSupport.GetTruststoreRaw());
        NSUrlSessionConfiguration configuration = NSUrlSessionConfiguration.DefaultSessionConfiguration;
        configuration.HttpShouldSetCookies = true;
        configuration.TimeoutIntervalForRequest = 30;
        configuration.TLSMinimumSupportedProtocol = SslProtocol.Tls_1_2;
        configuration.TimeoutIntervalForResource = 30;
        NSMutableDictionary requestHeaders;
        if (configuration.HttpAdditionalHeaders != null)
        {
            requestHeaders = (NSMutableDictionary)configuration.HttpAdditionalHeaders.MutableCopy();
        }
        else
        {
            requestHeaders = new NSMutableDictionary();
        }
        AppendHeaders(requestHeaders, SSLSupport.GetDefaultHeaders());
        configuration.HttpAdditionalHeaders = requestHeaders;

        session = NSUrlSession.FromConfiguration(configuration, (INSUrlSessionDelegate)sSLSessionDelegate, NSOperationQueue.MainQueue);
        baseAddress = NSUrl.FromString(SSLSupport.GetBaseAddress());
    }

    public void SetClientCertificate(byte[] pkcs12, char[] password)
    {
        sSLSessionDelegate.SetClientCertificate(pkcs12, password);
    }

    public override async Task<string> GetString(string url, Dictionary<string, string> headers, CancellationToken cancellationToken)
    {
        NSData responseContent = await GetRaw(url, headers, cancellationToken);
        return NSString.FromData(responseContent, NSStringEncoding.UTF8).ToString();
    }

    private async Task<NSData> GetRaw(string url, Dictionary<string, string> headers, CancellationToken cancellationToken)
    {
        NSMutableUrlRequest request = GetRequest(url);
        request.HttpMethod = "GET";
        request.Headers = AppendHeaders(request.Headers, headers);

        Task<NSUrlSessionDataTaskRequest> taskRequest = session.CreateDataTaskAsync(request, out NSUrlSessionDataTask task);
        cancellationToken.Register(() =>
        {
            if (task != null)
            {
                task.Cancel();
            }
        });
        try
        {
            task.Resume();
            NSUrlSessionDataTaskRequest taskResponse = await taskRequest;
            if (taskResponse == null || taskResponse.Response == null)
            {
                throw new Exception(task.Error.Description);
            }
            else
            {
                NSHttpUrlResponse httpResponse = (NSHttpUrlResponse)taskResponse.Response;
                if (httpResponse.StatusCode == 303)
                {
                    if (!httpResponse.AllHeaderFields.TryGetValue(new NSString("Location"), out NSObject locationValue))
                    {
                        throw new Exception("redirect received without Location-header!");
                    }
                    return await GetRaw(locationValue.ToString(), headers, cancellationToken);
                }
                if (httpResponse.StatusCode != 200)
                {
                    throw new Exception("unsupported statuscode: " + httpResponse.Description);
                }
                return taskResponse.Data;
            }
        }
        catch (Exception ex)
        {
            throw new Exception("communication exception: " + ex.Message);
        }
    }
}

这是我的 Info.plist

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>XXXXXXXXXX</key>
        <dict>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
            <key>NSIncludesSubdomains</key>
            <true/>
        </dict>
    </dict>
</dict>

更新2

我既没有找到解决方案,也没有人给我提示,所以我最终暂时放弃了客户端证书。我切换到 OAuth2 进行授权,并使用我自己的证书颁发机构(无自签名证书)进行服务器身份验证,效果很好。

但我仍然对这个问题很感兴趣,并且对如何让它发挥作用的每一个想法都很高兴。

最佳答案

我建议使用 ModernHttpClient。它支持 Android 和 iOS 的 ClientCertificates。 它是开源的,因此如果您想完成自己的实现,您可以随时查看他们的 github 以供引用。

ModernHttpClient

关于c# - xamarin 中带有 NSUrlSessionDelegate 的客户端证书,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51087853/

相关文章:

c# - 以 WebAPI 和 MVC 作为客户端架构的 Restful 服务

c# - 拥有多个 WCF 项目与具有多个服务的 1 个 WCF 项目

c# - 从另一个 ViewController 接收后的初始化数据

c# - Xamarin 媒体管理器和静态实例化存在问题

c# - 更改找不到 TreeView 控件的背景颜色?

xamarin.ios - Xamarin Studio + Xamarin.iOS : how do you set a breakpoint in C or C++ (not F# or C#)?

c# - MonoTouch 对话框部分中的纯文本?

C# 控制台应用程序 GetConsoleMode 错误代码 6

c# - 将 DateTime 调整为 DST

c# - 是否可以忽略 C# 中的属性?或者禁用它们的处理?