Created
August 18, 2016 08:50
-
-
Save angelolloqui/c45db7151bac31d404238dcf9899e612 to your computer and use it in GitHub Desktop.
SSL pinning validator with implementation for the Subject public key info (SPKI), based on the one at https://github.com/datatheorem/TrustKit
This file contains hidden or 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
// | |
// SSLPinningValidator.swift | |
// | |
// Created by Angel Garcia on 17/08/16. | |
// | |
import Foundation | |
import CommonCrypto | |
protocol SSLPinningValidator { | |
func canHandleChallenge(challenge: NSURLAuthenticationChallenge) -> Bool | |
func isChallengeValid(challenge: NSURLAuthenticationChallenge) -> Bool | |
} | |
extension SSLPinningValidator { | |
func canHandleChallenge(challenge: NSURLAuthenticationChallenge) -> Bool { | |
return challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust | |
} | |
} | |
private let lockQueue = dispatch_queue_create("com.sslpinningvalidator", nil) | |
private let certificateCache = NSCache() | |
//Code for SPKI validation based on the one at https://github.com/datatheorem/TrustKit | |
class SSLPinningSPKIValidator: NSObject, SSLPinningValidator { | |
enum PublicKeyAlgorithm: String { | |
case Rsa2048 | |
case Rsa4096 | |
case EcDsaSecp256r1 | |
var asn1HeaderBytes: [UInt8] { | |
switch self { | |
case .Rsa2048: | |
return [ 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, | |
0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00 ] | |
case .Rsa4096: | |
return [ 0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, | |
0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00 ] | |
case .EcDsaSecp256r1: | |
return [ 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, | |
0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, | |
0x42, 0x00 ] | |
} | |
} | |
} | |
typealias SPKI = (hostname: String, algorithm: PublicKeyAlgorithm, sha256: NSData) | |
let validSPKIs: [SPKI] | |
init(validSPKIs: [SPKI]) { | |
self.validSPKIs = validSPKIs | |
} | |
func isChallengeValid(challenge: NSURLAuthenticationChallenge) -> Bool { | |
let hostname = challenge.protectionSpace.host | |
let spkis = self.validSPKIs.filter({ hostname.containsString($0.hostname) }) | |
let serverTrust = challenge.protectionSpace.serverTrust! | |
//Domain not pinned, then is valid | |
guard spkis.count > 0 else { return true } | |
// First re-check the certificate chain using the default SSL validation in case it was disabled | |
// This gives us revocation (only for EV certs I think?) and also ensures the certificate chain is sane | |
// And also gives us the exact path that successfully validated the chain | |
let sslPolicy = SecPolicyCreateSSL(true, hostname) | |
SecTrustSetPolicies(serverTrust, sslPolicy) | |
var trustResult: SecTrustResultType = 0 | |
guard SecTrustEvaluate(serverTrust, &trustResult) == errSecSuccess else { | |
return false | |
} | |
guard trustResult == UInt32(kSecTrustResultUnspecified) || trustResult == UInt32(kSecTrustResultProceed) else { return false } | |
// Check each certificate in the server's certificate chain (the trust object); start with the CA all the way down to the leaf | |
let certificateChainLen = SecTrustGetCertificateCount(serverTrust) | |
for i in (0..<certificateChainLen).reverse() { | |
// Extract the certificate | |
if let certificate = SecTrustGetCertificateAtIndex(serverTrust, i) { | |
// For each spki key configuration, generate the subject public key info hash | |
for spki in spkis { | |
if let subjectPublicKeyInfoHash = sha256SubjectPublicKeyInfoFromCertificate(certificate, algorithm: spki.algorithm) | |
where subjectPublicKeyInfoHash == spki.sha256 { | |
return true | |
} | |
} | |
} | |
} | |
return false | |
} | |
func sha256SubjectPublicKeyInfoFromCertificate(certificate: SecCertificate, algorithm: PublicKeyAlgorithm) -> NSData? { | |
//Check the cache for already processed sha256 | |
let cacheKey = cacheKeyForCertificate(certificate, algorithm: algorithm) | |
if let data = certificateCache.objectForKey(cacheKey) as? NSData { | |
return data | |
} | |
// First extract the public key bytes | |
guard let publicKeyData = extractPublicKeyDataFromCertificate(certificate) else { return nil } | |
// Generate a hash of the subject public key info | |
let subjectPublicKeyInfoHash = NSMutableData(length:Int(CC_SHA256_DIGEST_LENGTH))! | |
var shaCtx = CC_SHA256_CTX() | |
CC_SHA256_Init(&shaCtx) | |
// Add the missing ASN1 header for public keys to re-create the subject public key info | |
let header = algorithm.asn1HeaderBytes | |
CC_SHA256_Update(&shaCtx, header, CC_LONG(header.count)) | |
// Add the public key | |
CC_SHA256_Update(&shaCtx, publicKeyData.bytes, CC_LONG(publicKeyData.length)) | |
CC_SHA256_Final(UnsafeMutablePointer<UInt8>(subjectPublicKeyInfoHash.mutableBytes), &shaCtx) | |
//Save in the cache for later usage | |
certificateCache.setObject(subjectPublicKeyInfoHash, forKey: cacheKey) | |
return subjectPublicKeyInfoHash | |
} | |
func extractPublicKeyDataFromCertificate(certificate: SecCertificate) -> NSData? { | |
var tempTrust: SecTrust? = nil | |
let policy = SecPolicyCreateBasicX509() | |
// Get a public key reference from the certificate | |
SecTrustCreateWithCertificates(certificate, policy, &tempTrust) | |
SecTrustEvaluate(tempTrust!, nil) | |
let publicKey = SecTrustCopyPublicKey(tempTrust!)! | |
// Extract the actual bytes from the key reference using the Keychain | |
// Prepare the dictionary to add the key | |
let peerPublicKeyAdd: [NSString: AnyObject] = [ | |
kSecClass: kSecClassKey, | |
kSecAttrApplicationTag: "SSLPinningSPKIValidator", | |
kSecValueRef: publicKey, | |
kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, | |
kSecReturnData: kCFBooleanTrue | |
] | |
// Prepare the dictionary to retrieve and delete the key | |
let publicKeyGet: [NSString: AnyObject] = [ | |
kSecClass: kSecClassKey, | |
kSecAttrApplicationTag: "SSLPinningSPKIValidator", | |
kSecReturnData: kCFBooleanTrue | |
] | |
var publicKeyData: NSData? = nil | |
dispatch_sync(lockQueue) { | |
var data: AnyObject? = nil | |
SecItemAdd(peerPublicKeyAdd, &data) | |
SecItemDelete(publicKeyGet) | |
publicKeyData = data as? NSData | |
} | |
return publicKeyData | |
} | |
func cacheKeyForCertificate(certificate: SecCertificate, algorithm: PublicKeyAlgorithm) -> NSData { | |
let data = NSMutableData(data: SecCertificateCopyData(certificate)) | |
data.appendData(algorithm.rawValue.dataUsingEncoding(NSUTF8StringEncoding)!) | |
return data | |
} | |
} | |
I have updated your gist to Swift 4 here: https://gist.github.com/tsafrir/492b9b2cd993948118af6da41414e755
thanks for porting it to Swift!!
Hellow angelolloqui ,
i am very new to certificate pinning
could you please tell me what will come in "sha256" please tell me the steps to get that
var sslPinningValidator: SSLPinningValidator? {
//You can apply custom logic here to return different validators depending on business logic
return SSLPinningSPKIValidator(validSPKIs: [
(hostname: self.host, algorithm: SSLPinningSPKIValidator.PublicKeyAlgorithm.Rsa2048, sha256: NSData(hexString: "942a6916a6e4ae527711c5450247a2a74fb8e156a8254ca66e739a11493bb445"))
])
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example of usage:
Set the
NSURLSession
delegate to aMyClass
instance, and add: