How do I get a non-nil serverTrust value when creating a URLAuthenticationChallenge programmatically?

Viewed 1076

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))
        }
    }
}
2 Answers

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
Related