xamarin中带有NSUrlSessionDelegate的客户端证书

时间:2018-06-28 16:42:39

标签: 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进行授权,并使用自己的证书授权(无自签名证书)进行服务器身份验证,效果很好。

但是我仍然对此问题感兴趣,并且对如何使它起作用的每个想法都很高兴。

1 个答案:

答案 0 :(得分:1)

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

ModernHttpClient