Created
October 14, 2020 18:27
-
-
Save JBirdVegas/60d9b5c1c0b9dc9bed619d0d8a33c866 to your computer and use it in GitHub Desktop.
Swift Certificate pinning example via cert.ist api
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
#!/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