Swift Certificate pinning example via cert.ist api
#!/usr/bin/env swift -suppress-warnings | |
import Foundation | |
import Security | |
import CommonCrypto | |
import CryptoKit | |
let sema = DispatchSemaphore(value: 0) | |
// Structure to hold the api response values we need | |
struct Cert: Decodable { | |
struct OpenSSLStruct: Decodable { | |
struct PubKey: Decodable { | |
// we will pin this value | |
let cer_base64: String | |
} | |
let pubkey: PubKey | |
} | |
let openssl: OpenSSLStruct | |
} | |
class DomainCertificateValidator: NSObject, URLSessionTaskDelegate { | |
required init(domain: String) { | |
self.domain = domain | |
} | |
var domain: String | |
var apiKey: String? = nil | |
public func makeConfig() -> URLSessionConfiguration { | |
let configuration = URLSessionConfiguration.default | |
configuration.waitsForConnectivity = true | |
configuration.connectionProxyDictionary = [String: String]() | |
// Required for this... TLS negation is cached locally | |
configuration.requestCachePolicy = .reloadIgnoringCacheData | |
configuration.urlCache = nil | |
return configuration | |
} | |
public func pinDomainToCertificate(cerFileBase64: String, isLast: Bool) { | |
self.apiKey = cerFileBase64 | |
if let target = URL(string: "https://\(self.domain)") { | |
let pinnedSession = URLSession( | |
// disables caches | |
configuration: self.makeConfig(), | |
// set ourselves as a callback to perform server certificate validation | |
delegate: self, | |
delegateQueue: nil) | |
pinnedSession.dataTask(with: target) { (data: Data?, response: URLResponse?, error: Error?) -> () in | |
if isLast { | |
defer { | |
sleep(1) | |
sema.signal() | |
} | |
} | |
// certificates have been validated; do whatever with the data | |
}.resume() | |
} | |
} | |
public func pinCertificateForDomain(isLast: Bool = false) { | |
let normalUrlSession = URLSession(configuration: self.makeConfig()) | |
if let url = URL(string: "https://api.cert.ist/\(self.domain)") { | |
normalUrlSession.dataTask(with: url) { (data: Data?, response: URLResponse?, error: Error?) -> () in | |
do { | |
let apiResponse = try JSONDecoder().decode(Cert.self, from: data!) | |
self.pinDomainToCertificate(cerFileBase64: apiResponse.openssl.pubkey.cer_base64, isLast: isLast) | |
} catch { | |
print("ERROR:", error) | |
} | |
}.resume() | |
} | |
} | |
public func urlSession(_ session: URLSession, | |
didReceive challenge: URLAuthenticationChallenge, | |
completionHandler: (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { | |
if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) { | |
if let serverTrust = challenge.protectionSpace.serverTrust { | |
var secResult = SecTrustResultType.invalid | |
let status = SecTrustEvaluate(serverTrust, &secResult) | |
if (errSecSuccess == status) { | |
if (self.domain != challenge.protectionSpace.host) { | |
completionHandler(.useCredential, URLCredential(trust: serverTrust)) | |
} | |
if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) { | |
let serverCertificateData: NSData = SecCertificateCopyData(serverCertificate) | |
let string: String = serverCertificateData.base64EncodedString() | |
if (self.apiKey != nil) { | |
let certificatesMatch: Bool = string.elementsEqual(self.apiKey!) | |
print("\(self.domain): Do certificates match? \(certificatesMatch)") | |
if certificatesMatch { | |
// successful certificate pinning match! | |
completionHandler(.useCredential, URLCredential(trust: serverTrust)) | |
return | |
} | |
} | |
} | |
} | |
} | |
} | |
// Pinning failed | |
completionHandler(.cancelAuthenticationChallenge, nil) | |
} | |
} | |
DomainCertificateValidator(domain: "tilltrump.com").pinCertificateForDomain() | |
DomainCertificateValidator.init(domain: "urip.io").pinCertificateForDomain() | |
DomainCertificateValidator.init(domain: "asciirange.com").pinCertificateForDomain() | |
DomainCertificateValidator(domain: "jbird.dev").pinCertificateForDomain(isLast: true) | |
sema.wait() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment