I have public key pinning code in my Swift iOS application that works great.
I'm now working on building unit tests that will test the public key pinning code without the need for network connectivity / real live server. I've got this almost working, but I can't figure out how to create a URLAuthenticationChallenge programmatically that has a non-nil serverTrust? All the Apple documentation states that this should be non-nil if your authentication method is NSURLAuthenticationMethodServerTrust. I'm using the p12 and cer files generated from my local machine to build the URLCredential in the example below. No matter what I do, challenge.protectionSpace.serverTrust always comes back nil.
let protectionSpace = URLProtectionSpace(host: "mockSession",
port: 0,
protocol: "https",
realm: nil,
authenticationMethod: NSURLAuthenticationMethodServerTrust)
var urlCredential:URLCredential?
if let p12Data = try? Data(contentsOf: URL(fileURLWithPath: Bundle.init(for: type(of: self)).path(forResource: "cm7justindomnit", ofType: "p12") ?? "")),
let cerData = try? Data(contentsOf: URL(fileURLWithPath: Bundle.init(for: type(of: self)).path(forResource: "cm7justindomnit", ofType: "cer") ?? "")){
let options: NSDictionary = [kSecImportExportPassphrase:"password"]
var items: CFArray?
let _ = SecPKCS12Import(p12Data as CFData, options, &items)
if let items = items {
let objectsData = Data.init(from: CFArrayGetValueAtIndex(items, 0))
let objects = objectsData.toArray(type: CFDictionary.self).first
let secIdentityData = Data.init(from: CFDictionaryGetValue(objects, Unmanaged.passUnretained(kSecImportItemIdentity).toOpaque()))
if let secIdentity = secIdentityData.toArray(type: SecIdentity.self).first {
if let secCertifiate = SecCertificateCreateWithData(kCFAllocatorDefault, cerData as CFData) {
urlCredential = URLCredential(identity: secIdentity, certificates: [secCertifiate], persistence: .forSession)
}
}
}
}
let challenge = URLAuthenticationChallenge(protectionSpace: protectionSpace,
proposedCredential: urlCredential,
previousFailureCount: 0,
failureResponse: nil,
error: nil,
sender: self)
I have a Data extension to handle the UnsafeBufferPointers.
extension Data {
init<T>(from value: T) {
var value = value
self.init(buffer: UnsafeBufferPointer(start: &value, count: 1))
}
func to<T>(type: T.Type) -> T {
return self.withUnsafeBytes { $0.pointee }
}
func toArray<T>(type: T.Type) -> [T] {
return self.withUnsafeBytes {
[T](UnsafeBufferPointer(start: $0, count: self.count/MemoryLayout<T>.stride))
}
}
}
I realise that this is an older question but I ran into the same issue today while writing tests for my SSL pinning code. I solved it by subclassing URLProtectionSpace
private class TestURLProtectionSpace: URLProtectionSpace {
var internalServerTrust: SecTrust?
override var serverTrust: SecTrust? {
return internalServerTrust
}
}
let protectionSpace = TestURLProtectionSpace(
host: "myhost.com",
port: 443,
protocol: "https",
realm: nil,
authenticationMethod: NSURLAuthenticationMethodServerTrust
)
protectionSpace.internalServerTrust = serverTrust
You can always move didReceive challenge
-method away from the class you are testing and link it with some protocol communication. Then just call its methods when it is needed. For example:
protocol CheckerProtocol {
// or event simplier
func isOkCertificate(_ certificate: ) -> Bool
}
If you need to test some logic with certificate just pass it to your checker dependency directly:
if let serverTrust = challenge.protectionSpace.serverTrust {
var secresult = SecTrustResultType(kSecTrustResultInvalid)
let status = SecTrustEvaluate(serverTrust, &secresult)
if status == errSecSuccess {
if let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
if self.checker.isOkCertificate(certificate) { ... }
}
}
}
Move checking code to
class Checker: CheckerProtocol { ... }
Meenwhile in your test target:
let certificate = // init right/wrong one
let checker = Checker()
XCTAssert[True|False](checker.isOkCertificate(certificate))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With