子类化URLProtectionSpace [英] Subclassing URLProtectionSpace

查看:99
本文介绍了子类化URLProtectionSpace的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

目标:

使用URLProtocol测试SSL固定。

Test SSL pinning by using URLProtocol.

问题:

无法以预期的方式对URLProtectionSpace进行子类化。永远不会调用服务器信任属性,并且即使调用了自定义类的初始化程序,alamofire auth回调也只会收到URLProtectionSpace类类型,而不是我的类。

Cannot subclass URLProtectionSpace in the expected manner. The server trust property is never called and the alamofire auth callback only receives a URLProtectionSpace class type instead of my class even though the initializer of my custom class gets called.

配置: [使用Alamofire]

Configuration: [using Alamofire]

let sessionConfiguration: URLSessionConfiguration = .default
    sessionConfiguration.protocolClasses?.insert(BaseURLProtocol.self, at: 0)
    let sessionManager = AlamofireSessionBuilderImpl(configuration: sessionConfiguration).default
    // overriding the auth challenge in Alamofire in order to test what is being called
    sessionManager.delegate.sessionDidReceiveChallengeWithCompletion = { session, challenge, completionHandler in
        let protectionSpace = challenge.protectionSpace

        guard protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
            protectionSpace.host.contains("myDummyURL.com") else {
                // otherwise it means a different challenge is encountered and we are only interested in certificate validation
                completionHandler(.performDefaultHandling, nil)
                return
        }

        guard let serverTrust = protectionSpace.serverTrust else {
            completionHandler(.performDefaultHandling, nil)
            return
        }

        guard let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else {
            return completionHandler(.cancelAuthenticationChallenge, nil)
        }

        let serverCertificateData = SecCertificateCopyData(serverCertificate) as Data

        // cannot find the local certificate
        guard let localCertPath = Bundle.main.path(forResource: "cert", ofType: "der"),
            let localCertificateData = NSData(contentsOfFile: localCertPath) else {
                return completionHandler(.cancelAuthenticationChallenge, nil)
        }

        guard localCertificateData.isEqual(to: serverCertificateData) else {
            // the certificate received from the server is invalid
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }

        let credential = URLCredential(trust: serverTrust)

        completionHandler(.useCredential, credential)
    }

BaseURLProtocol定义:

class BaseURLProtocol: URLProtocol {
override class func canInit(with request: URLRequest) -> Bool {
    return true
}

override class func canonicalRequest(for request: URLRequest) -> URLRequest {
    return request
}

override func startLoading() {
    debugPrint("--- request loading \(request)")

    guard request.url?.host?.contains("myDummyURL.com") ?? false else {
        debugPrint("--- caught untargetted request --- skipping ---host is \(request.url?.host)")
        return
    }

    let protectionSpace = CertificatePinningMockURLProtectionSpace(host: "https://myDummyURL.com", port: 443, protocol: nil, realm: nil, authenticationMethod: NSURLAuthenticationMethodServerTrust)

    let challenge = URLAuthenticationChallenge(protectionSpace: protectionSpace, proposedCredential: nil, previousFailureCount: 0, failureResponse: nil, error: nil, sender: self)

    client?.urlProtocol(self, didReceive: challenge)
}

}

CertificatePinningMockURLProtectionSpace: [使用Alamofire ServerTrustPolicy获取证书]

CertificatePinningMockURLProtectionSpace: [using Alamofire ServerTrustPolicy for getting the certs]

-从不调用serverTrust属性。我还重写了URLProtectionSpace的所有其他属性,除了调用了init外什么都没有。

-- The serverTrust property never gets called. I've also overridden all other properties of URLProtectionSpace and nothing except for the init gets called.

class CertificatePinningMockURLProtectionSpace: URLProtectionSpace {
private static let expectedHost = "myDummyURL.com"

override init(host: String, port: Int, protocol: String?, realm: String?, authenticationMethod: String?) {
    debugPrint("--- super init will be called")
    super.init(host: host, port: port, protocol: `protocol`, realm: realm, authenticationMethod: authenticationMethod)
}

override var serverTrust: SecTrust? {
    guard let certificate = ServerTrustPolicy.certificates(in: .main).first else {
        return nil
    }

    let policy: SecPolicy = SecPolicyCreateSSL(true, CertificatePinningMockURLProtectionSpace.expectedHost as CFString)

    var serverTrust: SecTrust?

    SecTrustCreateWithCertificates(certificate, policy, &serverTrust)

    return serverTrust
}

}

测试语句:

sessionManager.request("https://myDummyURL.com").responseString(completionHandler: { response in
                        debugPrint("--- response is \(response)")
                        done()
                    })

URLProtectionSpace是否可以成功覆盖并作为URLProtocol内部的URLProtocolClient的模拟?

Can the URLProtectionSpace successfully be overridden and provided as a mock to the URLProtocolClient inside the URLProtocol?

推荐答案

许多由Core Foundation派生的类对子类有很高的抵抗力,所以这也就不足为奇了。它可能基本上只是一个带有引擎盖下的魔术胶的结构。 :-)

A lot of those Core-Foundation-derived "classes" are highly resistant to subclassing, so it's no surprise that this one would be, too. It is probably basically just a struct with some magic glue under the hood. :-)

在这里,单元测试可能比功能测试更好。创建一个任意的保护空间对象,将其填充,然后将其直接传递给委托方法,并断言使用预期结果调用了该回调。

A unit test might be a better approach here than a functional test. Create an arbitrary protection space object, populate it, and pass it to the delegate method directly, and assert that the callback gets called with the expected results.

要进行完整的端到端测试,您可以实例化本地Web服务器,然后您的测试可以调整其配置以实时控制提供给应用程序的凭据。

Or if you really want to do a complete end-to-end test, you could instantiate a local web server, and then your test could tweak its configuration to control the credentials provided to your app on-the-fly.

这篇关于子类化URLProtectionSpace的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆