Forum Xamarin.iOS

NSUrlSession: client certificate authentication with self-signed certificates fails: "not trusted"

TobeTobe DEMember ✭✭

Hello,

we want to connect our app to our IIS webservice. We use self signed certificates and also a client certificate for authentication.

When the webservice doesn't require client certificate authentication, everything works fine, NSURLAuthenticationMethodServerTrust gets called, I give it the CA and intermediate certs, and the request continues. But when I activate client certificate authentication on our server, after DidReceiveChallenge with NSURLAuthenticationMethodServerTrust as the challenge, DidCompleteWithError gets called. Error message is: "The certificate for this server is invalid. You might be connecting to a server that is pretending to be "192.168.221.118" which could put your confidential information at risk.".

Note: "NSURLAuthenticationMethodClientCertificate" never gets called, the app crashes before that. The client certificate is signed by the intermediate certificate with is signed by the CA which is self-signed, so I don't understand why the ServerTrust Challenge fails when it doesn't fail with the same two certificates when there's no client certificate involved.

Also: in my opinion it should not be necessary, but I also tried adding the client certificate to the collection of AnchorCertificates of the Sectrust inside the if clause "NSURLAuthenticationMethodServerTrust".

Thanks in advance for your help.

Here is my code:

private class SessionDelegate : NSUrlSessionDataDelegate, INSUrlSessionDelegate
        {
            private Action<bool, string> completed_callback;
            private string antwortCache;
            private int status_code;

            public SessionDelegate(Action<bool, string> completed)
            {
                completed_callback = completed;
                antwortCache = "";
            }
public override void DidReceiveChallenge(NSUrlSession session, NSUrlSessionTask task, NSUrlAuthenticationChallenge challenge, Action<NSUrlSessionAuthChallengeDisposition, NSUrlCredential> completionHandler)
            {
                if (challenge.PreviousFailureCount == 0)
                {
                    if (challenge.ProtectionSpace.AuthenticationMethod.Equals("NSURLAuthenticationMethodServerTrust"))
                    {
// GetParent is correct, because I'm too lazy to copy the certs into to the correct folders...

                        var path = Directory.GetParent(GlobaleObjekte.SSLZertifikatePath);
                        var caPath = Path.Combine(path.FullName, "ca.cert.der");
                        var caByteArray = File.ReadAllBytes(caPath);
                        var caCert = new X509Certificate2(caByteArray);

                        var interPath = Path.Combine(path.FullName, "intermediate.cert.der");
                        var interByteArray = File.ReadAllBytes(interPath);
                        var interCert = new X509Certificate2(interByteArray);

                        var secTrust = challenge.ProtectionSpace.ServerSecTrust;

                        var certCollection = new X509CertificateCollection();
                        certCollection.Add(caCert);
                        certCollection.Add(interCert);

                        secTrust.SetAnchorCertificates(certCollection);
                        var credential = new NSUrlCredential(secTrust);
                        completionHandler(NSUrlSessionAuthChallengeDisposition.UseCredential, credential);

                        return;
                    }

                    if (challenge.ProtectionSpace.AuthenticationMethod.Equals("NSURLAuthenticationMethodClientCertificate"))
                    {
                        var path = Directory.GetParent(GlobaleObjekte.SSLZertifikatePath);
                        var certPath = Path.Combine(path.FullName, "client.pfx");
                        var certByteArray = File.ReadAllBytes(certPath);
                        var cert = new X509Certificate2(certByteArray, Settings.WSClientCertPasswort);

                        var ident = SecIdentity.Import(certByteArray, Settings.WSClientCertPasswort);
                        var credential = new NSUrlCredential(ident, new SecCertificate[] { new SecCertificate(cert) }, NSUrlCredentialPersistence.ForSession); 
                       completionHandler(NSUrlSessionAuthChallengeDisposition.UseCredential, credential);
                        return;
                    }

                    if (challenge.ProtectionSpace.AuthenticationMethod.Equals("NSURLAuthenticationMethodHTTPBasic"))
                    {
                        var credential = new NSUrlCredential(Settings.WebserviceBenutzer, Settings.WebservicePasswort, NSUrlCredentialPersistence.ForSession);
         completionHandler(NSUrlSessionAuthChallengeDisposition.UseCredential, credential);
                        return;
                    }

                    completed_callback(false, "Unbekannte Authentifizierungsanfrage: " + challenge?.ProtectionSpace?.AuthenticationMethod);
                }
                else
                {
                    completed_callback(false, "Authentifizierung fehlgeschlagen: " + challenge?.ProtectionSpace?.AuthenticationMethod);
                }
            }
}

Best Answer

  • TobeTobe DEMember ✭✭
    Accepted Answer

    It's working now. I had to create the credential object in a different way, with the client cert as the identy:

     if (challenge.ProtectionSpace.AuthenticationMethod.Equals("NSURLAuthenticationMethodServerTrust"))
                        {
                            var path = Directory.GetParent(GlobaleObjekte.SSLZertifikatePath);
                            var caPath = Path.Combine(path.FullName, "ca.cert.der");
                            var caByteArray = File.ReadAllBytes(caPath);
                            var caCert = new SecCertificate(caByteArray);
    
                            var interPath = Path.Combine(path.FullName, "intermediate.cert.der");
                            var interByteArray = File.ReadAllBytes(interPath);
                            var interCert = new SecCertificate(interByteArray);
    
                            var clientPath = Path.Combine(path.FullName, "client.pfx");
                            var clientByteArray = File.ReadAllBytes(clientPath);
                            var clientCert = new X509Certificate2(clientByteArray, Settings.WSClientCertPasswort);
    
                            var identity = SecIdentity.Import(clientCert);
                            var credential = new NSUrlCredential(identity, new SecCertificate[] { caCert, interCert }, NSUrlCredentialPersistence.ForSession);
    
                            completionHandler(NSUrlSessionAuthChallengeDisposition.UseCredential, credential);
    
                            return;
                        }
    

Answers

  • HortinzHortinz GBMember ✭✭

    Have a look here:

    https://github.com/paulcbetts/ModernHttpClient/pull/169

    This is a pull request for the library ModernHttpClient that basically adds support for Client Certificates. The iOS specific code should be what you're looking for

  • TobeTobe DEMember ✭✭
    edited May 2018

    Unfortunately this is not helping me :-(
    The pull request shows the implementation for "NSURLAuthenticationMethodClientCertificate", but my problem is that this is never called because "NSURLAuthenticationMethodServerTrust" fails before that.

    Sadly I don't completely understand the "NSURLAuthenticationMethodServerTrust" part inside the ModernHTTPClient code. It looks like they build the chain from challenge.ProtectionSpace.ServerSecTrust which I don't understand why that works, it probably uses the certificates that are inside the devices keychain or something.

    The thing is, my "NSURLAuthenticationMethodServerTrust" part is working fine, except when the server also requests a client certificate.

  • TobeTobe DEMember ✭✭
    Accepted Answer

    It's working now. I had to create the credential object in a different way, with the client cert as the identy:

     if (challenge.ProtectionSpace.AuthenticationMethod.Equals("NSURLAuthenticationMethodServerTrust"))
                        {
                            var path = Directory.GetParent(GlobaleObjekte.SSLZertifikatePath);
                            var caPath = Path.Combine(path.FullName, "ca.cert.der");
                            var caByteArray = File.ReadAllBytes(caPath);
                            var caCert = new SecCertificate(caByteArray);
    
                            var interPath = Path.Combine(path.FullName, "intermediate.cert.der");
                            var interByteArray = File.ReadAllBytes(interPath);
                            var interCert = new SecCertificate(interByteArray);
    
                            var clientPath = Path.Combine(path.FullName, "client.pfx");
                            var clientByteArray = File.ReadAllBytes(clientPath);
                            var clientCert = new X509Certificate2(clientByteArray, Settings.WSClientCertPasswort);
    
                            var identity = SecIdentity.Import(clientCert);
                            var credential = new NSUrlCredential(identity, new SecCertificate[] { caCert, interCert }, NSUrlCredentialPersistence.ForSession);
    
                            completionHandler(NSUrlSessionAuthChallengeDisposition.UseCredential, credential);
    
                            return;
                        }
    
Sign In or Register to comment.