Certificat iOS épinglé avec Swift et NSURLSession

Comment append un certificate épinglé à une NSURLSession dans Swift?

Le site Web OWASP contient uniquement un exemple pour Objective-C et NSURLConnection.

Mise à jour de Swift 3 :

Définissez simplement une class de délégué pour NSURLSessionDelegate et implémentez la fonction didReceiveChallenge ( ce code est adapté de l'exemple OWASP d'objective-c ):

 class NSURLSessionPinningDelegate: NSObject, URLSessionDelegate { func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) { // Adapted from OWASP https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning#iOS if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) { if let serverTrust = challenge.protectionSpace.serverTrust { var secresult = SecTrustResultType.invalid let status = SecTrustEvaluate(serverTrust, &secresult) if(errSecSuccess == status) { if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) { let serverCertificateData = SecCertificateCopyData(serverCertificate) let data = CFDataGetBytePtr(serverCertificateData); let size = CFDataGetLength(serverCertificateData); let cert1 = NSData(bytes: data, length: size) let file_der = Bundle.main.path(forResource: "certificateeFile", ofType: "der") if let file = file_der { if let cert2 = NSData(contentsOfFile: file) { if cert1.isEqual(to: cert2 as Data) { completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust:serverTrust)) return } } } } } } } // Pinning failed completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil) } } 

(Vous pouvez find un Gist pour Swift 2 ici – à partir de la réponse initiale )

Créez ensuite le file .der pour votre site Web en utilisant openssl

 openssl s_client -connect my-https-website.com:443 -showcerts < /dev/null | openssl x509 -outform DER > my-https-website.der 

et ajoutez-le au projet xcode. Vérifiez qu'il est présent dans l'onglet Build phases , dans la list Copy Bundle Resources . Sinon faites glisser et déposez-le dans cette list.

Enfin, utilisez-le dans votre code pour faire des requests d'URL:

 if let url = NSURL(ssortingng: "https://my-https-website.com") { let session = URLSession( configuration: URLSessionConfiguration.ephemeral, delegate: NSURLSessionPinningDelegate(), delegateQueue: nil) let task = session.dataTask(with: url as URL, completionHandler: { (data, response, error) -> Void in if error != nil { print("error: \(error!.localizedDescription): \(error!)") } else if data != nil { if let str = NSSsortingng(data: data!, encoding: Ssortingng.Encoding.utf8.rawValue) { print("Received data:\n\(str)") } else { print("Unable to convert data to text") } } }) task.resume() } else { print("Unable to create NSURL") } 

Voici une version mise à jour pour Swift 3

 import Foundation import Security class NSURLSessionPinningDelegate: NSObject, URLSessionDelegate { func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) { // Adapted from OWASP https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning#iOS if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) { if let serverTrust = challenge.protectionSpace.serverTrust { var secresult = SecTrustResultType.invalid let status = SecTrustEvaluate(serverTrust, &secresult) if(errSecSuccess == status) { if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) { let serverCertificateData = SecCertificateCopyData(serverCertificate) let data = CFDataGetBytePtr(serverCertificateData); let size = CFDataGetLength(serverCertificateData); let cert1 = NSData(bytes: data, length: size) let file_der = Bundle.main.path(forResource: "name-of-cert-file", ofType: "cer") if let file = file_der { if let cert2 = NSData(contentsOfFile: file) { if cert1.isEqual(to: cert2 as Data) { completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust:serverTrust)) return } } } } } } } // Pinning failed completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil) } } 

La command openssl dans la réponse de @ lifeisfoo donnera une erreur dans OS X pour certains certificates SSL qui utilisent des chiffrements plus récents comme ECDSA.

Si vous obtenez l'erreur suivante lorsque vous exécutez la command openssl dans la réponse @ lifeisfoo:

  write:errno=54 unable to load certificatee 1769:error:0906D06C:PEM routines:PEM_read_bio:no start line:/BuildRoot/Library/Caches/com.apple.xbs/Sources/OpenSSL098/OpenSSL09 8-59.60.1/src/crypto/pem/pem_lib.c:648:Expecting: TRUSTED CERTIFICATE 

Le certificate SSL de votre site Web utilise probablement un algorithm qui n'est pas pris en charge dans la version openssl par défaut d'OS X (v0.9.X, qui ne prend pas en charge ECDSA, entre autres).

Voici la solution:

Pour get le bon file .der , vous devez d'abord brew install openssl , puis replace la command openssl de la réponse @ lifeisfoo par:

/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl [rest of the above command]

Commande d'installation Homebrew:

 /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 

J'espère que cela pourra aider.

Merci à l'exemple trouvé sur ce site: https://www.bugsee.com/blog/ssl-certificatee-pinning-in-mobile-applications/ J'ai construit une version qui épingle la key publique et non le certificate entier (plus pratique si vous renouvelez votre certificate périodiquement).

 import Foundation class SessionDelegate : NSObject, URLSessionDelegate { private static let rsa2048Asn1Header:[UInt8] = [ 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00 ]; private static let google_com_pubkey = ["4xVxzbEegwDBoyoGoJlKcwGM7hyquoFg4l+9um5oPOI="]; private static let google_com_full = ["KjLxfxajzmBH0fTH1/oujb6R5fqBiLxl0zrl2xyFT2E="]; func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { let serverTrust = challenge.protectionSpace.serverTrust; // Set SSL policies for domain name check let policies = NSMutableArray(); policies.add(SecPolicyCreateSSL(true, (challenge.protectionSpace.host as CFSsortingng))); SecTrustSetPolicies(serverTrust!, policies); // Evaluate server certificatee var result = SecTrustResultType.invalid; SecTrustEvaluate(serverTrust!, &result) var isServerTrusted = result == .unspecified || result == .proceed ? true : false; if(isServerTrusted && challenge.protectionSpace.host == "www.google.com") { let certificatee = SecTrustGetCertificateAtIndex(serverTrust!, 0); //Compare public key if #available(iOS 10.0, *) { let policy = SecPolicyCreateBasicX509(); let cfCertificates = [certificatee] as CFArray; var trust: SecTrust? SecTrustCreateWithCertificates(cfCertificates, policy, &trust); let pubKey = SecTrustCopyPublicKey(trust!); var error:Unmanaged<CFError>? if let pubKeyData = SecKeyCopyExternalRepresentation(pubKey!, &error) { var keyWithHeader = Data(bytes: SessionDelegate.rsa2048Asn1Header); keyWithHeader.append(pubKeyData as Data); let sha256Key = sha256(keyWithHeader); if(!SessionDelegate.google_com_pubkey.contains(sha256Key)) { isServerTrusted = false; } } else { isServerTrusted = false; } } else { //Compare full certificatee let remoteCertificateData = SecCertificateCopyData(certificatee!) as Data; let sha256Data = sha256(remoteCertificateData); if(!SessionDelegate.google_com_full.contains(sha256Data)) { isServerTrusted = false; } } } if(isServerTrusted) { let credential = URLCredential(trust: serverTrust!); completionHandler(.useCredential, credential); } else { completionHandler(.cancelAuthenticationChallenge, nil); } } func sha256(_ data : Data) -> Ssortingng { var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) data.withUnsafeBytes { _ = CC_SHA256($0, CC_LONG(data.count), &hash) } return Data(bytes: hash).base64EncodedSsortingng(); } } 

Enregistrez le certificate (en tant que file .cer) de votre site Web dans le bundle principal. Ensuite, utilisez cette méthode URLSessionDelegate:

 func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust, let serverTrust = challenge.protectionSpace.serverTrust, SecTrustEvaluate(serverTrust, nil) == errSecSuccess, let serverCert = SecTrustGetCertificateAtIndex(serverTrust, 0) else { reject(with: completionHandler) return } let serverCertData = SecCertificateCopyData(serverCert) as Data guard let localCertPath = Bundle.main.path(forResource: "shop.rewe.de", ofType: "cer"), let localCertData = NSData(contentsOfFile: localCertPath) as Data?, localCertData == serverCertData else { reject(with: completionHandler) return } accept(with: serverTrust, completionHandler) } 

 func reject(with completionHandler: ((URLSession.AuthChallengeDisposition, URLCredential?) -> Void)) { completionHandler(.cancelAuthenticationChallenge, nil) } func accept(with serverTrust: SecTrust, _ completionHandler: ((URLSession.AuthChallengeDisposition, URLCredential?) -> Void)) { completionHandler(.useCredential, URLCredential(trust: serverTrust)) }