7

I want to implement client certificate authentication in my xamarin app. On top of that I am using a custom Certificate Authority (CA) and TLS 1.2.

Until now I managed to get it running using android, UWP and WPF. The only platform missing is ios.

Here is my 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); } } 

This works if no client certificate is configured DidReceiveChallenge is called once with AuthenticationMethodServerTrust and the custom CA is accepted.

But as soon as a client certificate is configured DidReceiveChallenge gets called 4 times (twice for each AuthenticationMethod) and I am getting NSURLErrorDomain (-1200) error.

Anyone any idea what I am doing wrong?


Update

The SSLSessionDelegate is used like this:

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); } } } 

And here my 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> 

Update 2

Neither I found the solution nor could anyone give me a hint, so I finally dropped client-certificates for now. I switched to OAuth2 for authorization and use my own certificate-authority (no self-signed certificate) for server -authentication which works well.

But still I am interested in this issue and glad for every idea in how to make it work.

1 Answer 1

1
+100

I would suggest using ModernHttpClient. It supports ClientCertificates for Android and iOS. It's opensource so you could always check out their github for reference if you want to finish your own implementation.

ModernHttpClient

Sign up to request clarification or add additional context in comments.

3 Comments

In fact ModernHttpClient would have been my first choice, but since it is pretty old (latest release in May 2015) it is not an option for me. But at least you proposed a solution so I will reward you.
It looks like the project has been picked back up here github.com/alexrainman/ModernHttpClient
This library has only one private maintainer and the latest release was 2017. Therefore I don't consider it for produktion, allthough I apprechiate the efford of Alexander Reyes)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.