Posting the corresponding Swift 5 Version. If your server is validating client certificate, the behavior is two challenges: first NSURLAuthenticationMethodServerTrust
and then NSURLAuthenticationMethodClientCertificate
import Foundation
struct DiskCredsProvider{
let clientCert: URL = Bundle.main.url(forResource: "client", withExtension: "crt")!
let clientKey: URL = Bundle.main.url(forResource: "client", withExtension: "key")!
let pkcs12: URL = Bundle.main.url(forResource: "cert", withExtension: "p12")!
let pkcs12Password: String = "xxx"
func keyData() throws -> Data{
return try Data.init(contentsOf: clientKey)
}
func certData() throws -> Data{
return try Data.init(contentsOf: clientCert)
}
func p12Data() throws -> Data{
return try Data.init(contentsOf: pkcs12)
}
/// Provides URL Session Auth Chanllenge disposition by loading on disk pkcs12 file
/// - Returns: tuple with URLSession disposition and URLCredential
func provideUrlSessionDispostionWithPKCS12Data(data: Data) -> (URLSession.AuthChallengeDisposition, URLCredential){
//You can also import a PKCS #12 file directly into your app using the certificate, key, and trust services API.
let pcks12Data:CFData = data as CFData
// CKTS API won’t even import PKCS #12 data that lacks a password. Password during import, you create an options dictionary with the password string:
let password = pkcs12Password
let options = [ kSecImportExportPassphrase as String: password ]
//Because the PKCS #12 format allows for bundling multiple cryptographic objects together, this function populates an array object. In our case, it's the cert&key, we create a c type array to store it.
var rawItems: CFArray?
let status = SecPKCS12Import(pcks12Data, options as CFDictionary, &rawItems)
//Check SecPCKCS12 import status code.
precondition(status == errSecSuccess)
let items = rawItems! as! Array<Dictionary<String, Any>>
precondition(items.count == 1)
let firstItem = items[0]
let identity = firstItem[kSecImportItemIdentity as String] as! SecIdentity?
let disposition: URLSession.AuthChallengeDisposition = .useCredential
// In most cases the server does not need any intermediate certificates in order to evaluate trust on your client certificate (it either has a fixed list of client certificates, or requires that the client certificate be issued by a specific issuer, in which case it already has any intermediates leading to that issuer), and thus you don’t need to include them.
let creds = URLCredential(identity: identity!, certificates: nil, persistence: .forSession)
return (disposition,creds)
}
}
enum APIError: Error {
case pcks12ImportFailed
case serverError
}
let endPointUrl: URL = URL(string: "https://\(endPointHost)/xx/xx/conf")!
let endPointHost: String = "xxxxx"
class WGAPIHelper:NSObject{
var session: URLSession!
let credsProvider: DiskCredsProvider = DiskCredsProvider()
override init() {
super.init()
session = URLSession(configuration: .default, delegate: self, delegateQueue: queue)
}
lazy var queue: OperationQueue = {
let oq = OperationQueue()
oq.name = Bundle.main.bundleIdentifier!
return oq
}()
func requestWGConfig(completionHandler: @escaping (String?,Error?) -> Void){
let dataTask = session.dataTask(with: endPointUrl) { data, response, error in
// check for fundamental networking error
guard let data = data,
let response = response as? HTTPURLResponse,
error == nil else {
print("request error: \(error?.localizedDescription ?? "unknown error")")
completionHandler(nil,APIError.serverError)
return
}
// check for http errors
guard (200 ... 299) ~= response.statusCode else {
print("request statusCode should be 2xx, but is \(response.statusCode)")
print("response = \(response)")
completionHandler(nil,APIError.serverError)
return
}
completionHandler(String(data: data, encoding: .utf8), nil)
}
dataTask.resume()
}
}
//MARK: - URLSessionDelegate
extension WGAPIHelper: URLSessionDelegate{
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
switch (challenge.protectionSpace.authenticationMethod,challenge.protectionSpace.host) {
case (NSURLAuthenticationMethodServerTrust, endPointHost):
let servTrust = challenge.protectionSpace.serverTrust!
let credential = URLCredential(trust: servTrust)
//By Default trust it. if you are using self sign server cert, overide trust settings here.
completionHandler(.useCredential, credential)
return
case (NSURLAuthenticationMethodClientCertificate, endPointHost):
let data = try! credsProvider.p12Data()
let (disposition, creds) = credsProvider.provideUrlSessionDispostionWithPKCS12Data(data: data)
completionHandler(disposition, creds)
return
default:
completionHandler(URLSession.AuthChallengeDisposition.performDefaultHandling, nil)
}
}
}