Skip to content

Instantly share code, notes, and snippets.

@JBirdVegas
Created October 14, 2020 18:27
Show Gist options
  • Save JBirdVegas/60d9b5c1c0b9dc9bed619d0d8a33c866 to your computer and use it in GitHub Desktop.
Save JBirdVegas/60d9b5c1c0b9dc9bed619d0d8a33c866 to your computer and use it in GitHub Desktop.
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