问题
I'm using URLSession to make a get request with TLS 1.2 protocol and certificates (which are all self-signed) included in the main bundle. I managed to do the pinning but server also requires a client certificate for authentication so I'm trying to respond to the AuthenticationChallenge with UrlCredential but it's not working: i keep getting NSURLErrorDomain Code=-1206 which is "The server “my_server_domain.it” requires a client certificate."
Here is my request:
func makeGetRequest(){
let configuration = URLSessionConfiguration.default
var request = try! URLRequest(url: requestUrl, method: .get)
let session = URLSession(configuration: configuration,
delegate: self,
delegateQueue: OperationQueue.main)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
print("Data = \(data)")
print("Response = \(response)")
print("Error = \(error)")
})
task.resume()
}
URLSessionDelegate, where I respond to the AuthenticationChallenge:
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
let authenticationMethod = challenge.protectionSpace.authenticationMethod
print("authenticationMethod=\(authenticationMethod)")
if authenticationMethod == NSURLAuthenticationMethodClientCertificate {
completionHandler(.useCredential, getClientUrlCredential())
} else if authenticationMethod == NSURLAuthenticationMethodServerTrust {
let serverCredential = getServerUrlCredential(protectionSpace: challenge.protectionSpace)
guard serverCredential != nil else {
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
completionHandler(.useCredential, serverCredential)
}
}
Server certificate pinning:
func getServerUrlCredential(protectionSpace:URLProtectionSpace)->URLCredential?{
if let serverTrust = protectionSpace.serverTrust {
//Check if is valid
var result = SecTrustResultType.invalid
let status = SecTrustEvaluate(serverTrust, &result)
print("SecTrustEvaluate res = \(result.rawValue)")
if(status == errSecSuccess),
let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
//Get Server Certificate Data
let serverCertificateData = SecCertificateCopyData(serverCertificate)
//Get Local Certificate NSData
let localServerCertNSData = certificateHelper.getCertificateNSData(withName: "localServerCertName", andExtension: "cer")
//Check if certificates are equals, otherwhise pinning failed and return nil
guard serverCertificateData == localServerCertNSData else{
print("Certificates doesn't match.")
return nil
}
//Certificates does match, so we can trust the server
return URLCredential(trust: serverTrust)
}
}
return nil
}
And here is where i obtain the client URLCredential from the PKCS12 (.pfx) certificate:
func getClientUrlCredential()->URLCredential {
let userCertificate = certificateHelper.getCertificateNSData(withName: "certificate",
andExtension: "pfx")
let userIdentityAndTrust = certificateHelper.extractIdentityAndTrust(fromCertificateData: userCertificate, certPassword: "cert_psw")
//Create URLCredential
let urlCredential = URLCredential(identity: userIdentityAndTrust.identityRef,
certificates: userIdentityAndTrust.certArray as [AnyObject],
persistence: URLCredential.Persistence.permanent)
return urlCredential
}
Note that func 'extractIdentityAndTrust' -successfully- returns a struct with pointers to identity, certificate-chain and trust extracted from the PKCS12; I know that identity and certificates should be stored in the keychain but at the moment I'm just including them in the bundle mainly because the documentation for keychain is anything but good.
I've also added App Transport Security Settings to my Info.plist file like this
It looks like client doesn't even try to authenticate, so I'm missing something, I guess...
回答1:
If your getClientCredential() function is being called, then your client is trying to authenticate. If not, then the server logs (such as /var/log/nginx/access.log) might indicate why.
The PKCS12 class in this answer worked for me.
Regarding the keychain, this Apple documentation says
To use digital identities in your own apps, you will need to write code to import them. This typically means reading in a PKCS#12-formatted blob and then importing the contents of the blob into the app's keychain using the function SecPKCS12Import documented in Certificate, Key, and Trust Services Reference.
This way, your new keychain items are created with your app's keychain access group.
来源:https://stackoverflow.com/questions/44023540/swift-3-urlsession-with-client-authentication-by-certificate