Forked from daniel-rueda/CertificatePinningURLSessionDelegate.swift
Created
November 6, 2018 18:57
-
-
Save jayantnd/bbf354cdb08e7bb3f458b658fd6b3657 to your computer and use it in GitHub Desktop.
Certificate and Public Key Pinning for URLSession using Swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Based on https://code.tutsplus.com/articles/securing-communications-on-ios--cms-28529 | |
import Foundation | |
import Security | |
struct Certificate { | |
let certificate: SecCertificate | |
let data: Data | |
} | |
extension Certificate { | |
static func localCertificates(with names: [String] = ["CertificateRenewed", "Certificate"], | |
from bundle: Bundle = .main) -> [Certificate] { | |
return names.lazy.map({ | |
guard let file = bundle.url(forResource: $0, withExtension: "cer"), | |
let data = try? Data(contentsOf: file), | |
let cert = SecCertificateCreateWithData(nil, data as CFData) else { | |
return nil | |
} | |
return Certificate(certificate: cert, data: data) | |
}).flatMap({$0}) | |
} | |
func validate(against certData: Data, using secTrust: SecTrust) -> Bool { | |
let certArray = [certificate] as CFArray | |
SecTrustSetAnchorCertificates(secTrust, certArray) | |
//validates a certificate by verifying its signature plus the signatures of | |
// the certificates in its certificate chain, up to the anchor certificate | |
var result = SecTrustResultType.invalid | |
SecTrustEvaluate(secTrust, &result) | |
let isValid = (result == .unspecified || result == .proceed) | |
//Validate host certificate against pinned certificate. | |
return isValid && certData == self.data | |
} | |
} | |
class CertificatePinningURLSessionDelegate: NSObject, URLSessionDelegate { | |
func urlSession(_ session: URLSession, | |
didReceive challenge: URLAuthenticationChallenge, | |
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) { | |
guard let serverTrust = challenge.protectionSpace.serverTrust, | |
challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust else { | |
completionHandler(.cancelAuthenticationChallenge, nil) | |
return | |
} | |
//Set policy to validate domain | |
let policy = SecPolicyCreateSSL(true, "yourdomain.com" as CFString) | |
let policies = NSArray(object: policy) | |
SecTrustSetPolicies(serverTrust, policies) | |
let certificateCount = SecTrustGetCertificateCount(serverTrust) | |
guard certificateCount > 0, | |
let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else { | |
completionHandler(.cancelAuthenticationChallenge, nil) | |
return | |
} | |
let serverCertificateData = SecCertificateCopyData(certificate) as Data | |
let certificates = Certificate.localCertificates() | |
for localCert in certificates { | |
if localCert.validate(against: serverCertificateData, using: serverTrust) { | |
completionHandler(.useCredential, URLCredential(trust: serverTrust)) | |
return // exit as soon as we found a match | |
} | |
} | |
// No valid cert available | |
completionHandler(.cancelAuthenticationChallenge, nil) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment