| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310 | ////  ServerTrustPolicy.swift////  Copyright (c) 2014 Alamofire Software Foundation (http://alamofire.org/)////  Permission is hereby granted, free of charge, to any person obtaining a copy//  of this software and associated documentation files (the "Software"), to deal//  in the Software without restriction, including without limitation the rights//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell//  copies of the Software, and to permit persons to whom the Software is//  furnished to do so, subject to the following conditions:////  The above copyright notice and this permission notice shall be included in//  all copies or substantial portions of the Software.////  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN//  THE SOFTWARE.//import Foundation/// Responsible for managing the mapping of `ServerTrustPolicy` objects to a given host.open class ServerTrustPolicyManager {    /// The dictionary of policies mapped to a particular host.    public let policies: [String: ServerTrustPolicy]    /// Initializes the `ServerTrustPolicyManager` instance with the given policies.    ///    /// Since different servers and web services can have different leaf certificates, intermediate and even root    /// certficates, it is important to have the flexibility to specify evaluation policies on a per host basis. This    /// allows for scenarios such as using default evaluation for host1, certificate pinning for host2, public key    /// pinning for host3 and disabling evaluation for host4.    ///    /// - parameter policies: A dictionary of all policies mapped to a particular host.    ///    /// - returns: The new `ServerTrustPolicyManager` instance.    public init(policies: [String: ServerTrustPolicy]) {        self.policies = policies    }    /// Returns the `ServerTrustPolicy` for the given host if applicable.    ///    /// By default, this method will return the policy that perfectly matches the given host. Subclasses could override    /// this method and implement more complex mapping implementations such as wildcards.    ///    /// - parameter host: The host to use when searching for a matching policy.    ///    /// - returns: The server trust policy for the given host if found.    open func serverTrustPolicy(forHost host: String) -> ServerTrustPolicy? {        return policies[host]    }}// MARK: -extension URLSession {    private struct AssociatedKeys {        static var managerKey = "URLSession.ServerTrustPolicyManager"    }    var serverTrustPolicyManager: ServerTrustPolicyManager? {        get {            return objc_getAssociatedObject(self, &AssociatedKeys.managerKey) as? ServerTrustPolicyManager        }        set (manager) {            objc_setAssociatedObject(self, &AssociatedKeys.managerKey, manager, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)        }    }}// MARK: - ServerTrustPolicy/// The `ServerTrustPolicy` evaluates the server trust generally provided by an `NSURLAuthenticationChallenge` when/// connecting to a server over a secure HTTPS connection. The policy configuration then evaluates the server trust/// with a given set of criteria to determine whether the server trust is valid and the connection should be made.////// Using pinned certificates or public keys for evaluation helps prevent man-in-the-middle (MITM) attacks and other/// vulnerabilities. Applications dealing with sensitive customer data or financial information are strongly encouraged/// to route all communication over an HTTPS connection with pinning enabled.////// - performDefaultEvaluation: Uses the default server trust evaluation while allowing you to control whether to///                             validate the host provided by the challenge. Applications are encouraged to always///                             validate the host in production environments to guarantee the validity of the server's///                             certificate chain.////// - performRevokedEvaluation: Uses the default and revoked server trust evaluations allowing you to control whether to///                             validate the host provided by the challenge as well as specify the revocation flags for///                             testing for revoked certificates. Apple platforms did not start testing for revoked///                             certificates automatically until iOS 10.1, macOS 10.12 and tvOS 10.1 which is///                             demonstrated in our TLS tests. Applications are encouraged to always validate the host///                             in production environments to guarantee the validity of the server's certificate chain.////// - pinCertificates:          Uses the pinned certificates to validate the server trust. The server trust is///                             considered valid if one of the pinned certificates match one of the server certificates.///                             By validating both the certificate chain and host, certificate pinning provides a very///                             secure form of server trust validation mitigating most, if not all, MITM attacks.///                             Applications are encouraged to always validate the host and require a valid certificate///                             chain in production environments.////// - pinPublicKeys:            Uses the pinned public keys to validate the server trust. The server trust is considered///                             valid if one of the pinned public keys match one of the server certificate public keys.///                             By validating both the certificate chain and host, public key pinning provides a very///                             secure form of server trust validation mitigating most, if not all, MITM attacks.///                             Applications are encouraged to always validate the host and require a valid certificate///                             chain in production environments.////// - disableEvaluation:        Disables all evaluation which in turn will always consider any server trust as valid.////// - customEvaluation:         Uses the associated closure to evaluate the validity of the server trust.public enum ServerTrustPolicy {    case performDefaultEvaluation(validateHost: Bool)    case performRevokedEvaluation(validateHost: Bool, revocationFlags: CFOptionFlags)    case pinCertificates(certificates: [SecCertificate], validateCertificateChain: Bool, validateHost: Bool)    case pinPublicKeys(publicKeys: [SecKey], validateCertificateChain: Bool, validateHost: Bool)    case disableEvaluation    case customEvaluation((_ serverTrust: SecTrust, _ host: String) -> Bool)    // MARK: - Bundle Location    /// Returns all certificates within the given bundle with a `.cer` file extension.    ///    /// - parameter bundle: The bundle to search for all `.cer` files.    ///    /// - returns: All certificates within the given bundle.    public static func certificates(in bundle: Bundle = Bundle.main) -> [SecCertificate] {        var certificates: [SecCertificate] = []        let paths = Set([".cer", ".CER", ".crt", ".CRT", ".der", ".DER"].map { fileExtension in            bundle.paths(forResourcesOfType: fileExtension, inDirectory: nil)        }.joined())        for path in paths {            if                let certificateData = try? Data(contentsOf: URL(fileURLWithPath: path)) as CFData,                let certificate = SecCertificateCreateWithData(nil, certificateData)            {                certificates.append(certificate)            }        }        return certificates    }    /// Returns all public keys within the given bundle with a `.cer` file extension.    ///    /// - parameter bundle: The bundle to search for all `*.cer` files.    ///    /// - returns: All public keys within the given bundle.    public static func publicKeys(in bundle: Bundle = Bundle.main) -> [SecKey] {        var publicKeys: [SecKey] = []        for certificate in certificates(in: bundle) {            if let publicKey = publicKey(for: certificate) {                publicKeys.append(publicKey)            }        }        return publicKeys    }    // MARK: - Evaluation    /// Evaluates whether the server trust is valid for the given host.    ///    /// - parameter serverTrust: The server trust to evaluate.    /// - parameter host:        The host of the challenge protection space.    ///    /// - returns: Whether the server trust is valid.    public func evaluate(_ serverTrust: SecTrust, forHost host: String) -> Bool {        var serverTrustIsValid = false        switch self {        case let .performDefaultEvaluation(validateHost):            let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)            SecTrustSetPolicies(serverTrust, policy)            serverTrustIsValid = trustIsValid(serverTrust)        case let .performRevokedEvaluation(validateHost, revocationFlags):            let defaultPolicy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)            let revokedPolicy = SecPolicyCreateRevocation(revocationFlags)            SecTrustSetPolicies(serverTrust, [defaultPolicy, revokedPolicy] as CFTypeRef)            serverTrustIsValid = trustIsValid(serverTrust)        case let .pinCertificates(pinnedCertificates, validateCertificateChain, validateHost):            if validateCertificateChain {                let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)                SecTrustSetPolicies(serverTrust, policy)                SecTrustSetAnchorCertificates(serverTrust, pinnedCertificates as CFArray)                SecTrustSetAnchorCertificatesOnly(serverTrust, true)                serverTrustIsValid = trustIsValid(serverTrust)            } else {                let serverCertificatesDataArray = certificateData(for: serverTrust)                let pinnedCertificatesDataArray = certificateData(for: pinnedCertificates)                outerLoop: for serverCertificateData in serverCertificatesDataArray {                    for pinnedCertificateData in pinnedCertificatesDataArray {                        if serverCertificateData == pinnedCertificateData {                            serverTrustIsValid = true                            break outerLoop                        }                    }                }            }        case let .pinPublicKeys(pinnedPublicKeys, validateCertificateChain, validateHost):            var certificateChainEvaluationPassed = true            if validateCertificateChain {                let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)                SecTrustSetPolicies(serverTrust, policy)                certificateChainEvaluationPassed = trustIsValid(serverTrust)            }            if certificateChainEvaluationPassed {                outerLoop: for serverPublicKey in ServerTrustPolicy.publicKeys(for: serverTrust) as [AnyObject] {                    for pinnedPublicKey in pinnedPublicKeys as [AnyObject] {                        if serverPublicKey.isEqual(pinnedPublicKey) {                            serverTrustIsValid = true                            break outerLoop                        }                    }                }            }        case .disableEvaluation:            serverTrustIsValid = true        case let .customEvaluation(closure):            serverTrustIsValid = closure(serverTrust, host)        }        return serverTrustIsValid    }    // MARK: - Private - Trust Validation    private func trustIsValid(_ trust: SecTrust) -> Bool {        var isValid = false        if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) {            isValid = SecTrustEvaluateWithError(trust, nil)        } else {            var result = SecTrustResultType.invalid            let status = SecTrustEvaluate(trust, &result)            if status == errSecSuccess {                let unspecified = SecTrustResultType.unspecified                let proceed = SecTrustResultType.proceed                isValid = result == unspecified || result == proceed            }        }        return isValid    }    // MARK: - Private - Certificate Data    private func certificateData(for trust: SecTrust) -> [Data] {        var certificates: [SecCertificate] = []        for index in 0..<SecTrustGetCertificateCount(trust) {            if let certificate = SecTrustGetCertificateAtIndex(trust, index) {                certificates.append(certificate)            }        }        return certificateData(for: certificates)    }    private func certificateData(for certificates: [SecCertificate]) -> [Data] {        return certificates.map { SecCertificateCopyData($0) as Data }    }    // MARK: - Private - Public Key Extraction    private static func publicKeys(for trust: SecTrust) -> [SecKey] {        var publicKeys: [SecKey] = []        for index in 0..<SecTrustGetCertificateCount(trust) {            if                let certificate = SecTrustGetCertificateAtIndex(trust, index),                let publicKey = publicKey(for: certificate)            {                publicKeys.append(publicKey)            }        }        return publicKeys    }    private static func publicKey(for certificate: SecCertificate) -> SecKey? {        var publicKey: SecKey?        let policy = SecPolicyCreateBasicX509()        var trust: SecTrust?        let trustCreationStatus = SecTrustCreateWithCertificates(certificate, policy, &trust)        if let trust = trust, trustCreationStatus == errSecSuccess {            publicKey = SecTrustCopyPublicKey(trust)        }        return publicKey    }}
 |