与NSUrlSessionDelegate在xamarin客户端证书

问题描述 投票:7回答:1

我想实现我的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);
    }
}

如果没有客户端证书配置DidReceiveChallengeAuthenticationMethodServerTrust调用一次,自定义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授权和使用自己的证书颁发机构(没有自签名证书),用于服务器-authentication效果很好。

不过还是我对这个问题感兴趣,并很高兴在如何使其工作每一个想法。

c# xamarin xamarin.ios client-certificates
1个回答
1
投票

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

ModernHttpClient

© www.soinside.com 2019 - 2024. All rights reserved.