在xamarin中使用NSUrlSessionDelegate的客户端证书

Ale*_*exS 7 c# client-certificates xamarin.ios xamarin

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

如果未配置任何客户端证书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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在这里我的 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>
Run Code Online (Sandbox Code Playgroud)

更新2

我既没有找到解决方案,也没有人给我一个提示,所以我最后放弃了客户证书.我切换到OAuth2进行授权,并使用我自己的证书颁发机构(没有自签名证书)进行服务器身份验证,该协议运行良好.

但我仍然对这个问题感兴趣,并对如何使其发挥作用感到高兴.

mrm*_*dev 1

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

现代Http客户端