Error Domain=NSURLErrorDomain Code=-1206

I have developed an app that connects to different pages and allows validation using a digital certificate. In most of the webs I can access without problem, but when I try to connect to the url: https://www.sedecatastro.***.es/Accesos/SECAccTitular.aspx?Dest=24, I always get the error: Error Domain = NSURLErrorDomain Code = -1206 The server “xxxx” requires a client certificate. I do not understand the error because I am doing the validation in other websites correctly and even in other sections of this same website.

class SessionDelegate:NSObject, URLSessionDelegate
{
  let certificadosName: [String] = ["AC_Administracion_Publica","ac_raiz_fnmt","Camerfirma_AAPP_II_Chambers_of_Commerce_Root","Camerfirma_Corporate_Server_II_Chambers_of_Commerce_Root","Chambers_of_Commerce_Root","claveRaiz","DigiCert_High_Assurance_EV_Root_CA","Entrust_Root_Certification_Authority_G","GeoTrust_SSL_CA_G_GeoTrust_Global_CA","Izenpe_com"
    ,"GlobalSign", "GlobalSign_Extended_Validation", "USERTrust_RSA", "SEDE_CATASTRO"]

  let certFileType = "cer"

  func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
     
    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust
      && challenge.protectionSpace.serverTrust != nil
    {

      let trust = challenge.protectionSpace.serverTrust
       
      var certs: [SecCertificate] = [SecCertificate]()
      for certificadoName in certificadosName
      {
        let pem = Bundle.main.url(forResource: certificadoName, withExtension: certFileType)
        if (pem != nil)
        {
          let data = NSData(contentsOf: pem!)
          let cert = SecCertificateCreateWithData(nil, data!)
           
          certs.append(cert!)
        }
      }
       
      SecTrustSetAnchorCertificates(trust!, certs as CFArray)
      var result=SecTrustResultType.invalid
      if SecTrustEvaluate(trust!,&result)==errSecSuccess {
       if result==SecTrustResultType.proceed || result==SecTrustResultType.unspecified {
        let proposedCredential = URLCredential(trust: trust!)
        completionHandler(.useCredential,proposedCredential)
        return
       }
      }
       
      completionHandler(.performDefaultHandling, nil)
    }
    else if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate)
    {
     sendClientCertificate(for: challenge, via: completionHandler)
    }
    else
    {
      completionHandler(.performDefaultHandling, nil)
    }

  }
   
  func sendClientCertificate(for challenge: URLAuthenticationChallenge, via completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

    let certificado: String? = Tramite.selectedCertificado
    let password: String? = Tramite.passwordCertificado
     
    if(certificado != nil && password != nil )
    {
      guard let data = try? Data(contentsOf: URL(fileURLWithPath: certificado!)),
      let credential = credential(from: data, withPassword: password ?? "") else {

       challenge.sender?.cancel(challenge)
       return completionHandler(.rejectProtectionSpace, .none)
      }


      return completionHandler(.useCredential, credential);
    }
    else{
      return completionHandler(.rejectProtectionSpace, .none)
    }
  }
   
  func credential(from data: Data, withPassword password: String) -> URLCredential? {

    guard let security = security(from: data, withPassword: password) else {
     return .none
    }

         
    return URLCredential(
     identity: security.identity,
     certificates: security.certificates,
     persistence: .forSession
    )

   }

   func security(from data: Data, withPassword password: String) -> (identity: SecIdentity, trust: SecTrust, certificates: [SecCertificate])? {

    var _items: CFArray?

    let securityError = SecPKCS12Import(data as NSData,
                      [ kSecImportExportPassphrase as String : password ] as CFDictionary,
                      &_items);

    guard let items = _items as? [Any],
     let dict = items.first as? [String:Any],
     securityError == errSecSuccess else {
      return .none
    }

    let identity = dict["identity"] as! SecIdentity

    let trust = dict["trust"] as! SecTrust;

    // Certificate chain
    var certificate: SecCertificate!
    SecIdentityCopyCertificate(identity, &certificate);

    return (identity, trust, [certificate]);
   }

}

Hi I have faced this issue recently and would like to share some information about it: There are some servers, that does not respect http response codes protocol, and "abusing" the 403 response code. From Google:

"An HTTP 403 response code means that a client is forbidden from accessing a valid URL"

iOS, with respect to the protocol, as long as there was a client certificate challenge, will process this error inside it's own network stack, and will replace the 403 and it's response data and code, with the -1206, resulting in the loss of the response from the server and a general error instead.

I was able to prove that, but returning 403 response from an F5 instance that required client certificate authentication. As long as I returned 403 if I saw a particular header (which will be possible off course only after handshake completed), the iOS finished the task with didCompleteWithError and no response data / original 403 code.

When I changed the code to 401, I got back 401 response in didReceiveResponse method.

I really hope this will be addressed one day by apple, but until than, please avoid abusing the 403 response code in your server protocol, especially of you wish to use client certificates with it.

Roy

Error Domain=NSURLErrorDomain Code=-1206
 
 
Q