123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- // ServerTrustPolicy.swift
- //
- // Copyright (c) 2014–2016 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.
- public 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.
- */
- public func serverTrustPolicyForHost(host: String) -> ServerTrustPolicy? {
- return policies[host]
- }
- }
- // MARK: -
- extension NSURLSession {
- private struct AssociatedKeys {
- static var ManagerKey = "NSURLSession.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.
- - 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 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 certificatesInBundle(bundle: NSBundle = NSBundle.mainBundle()) -> [SecCertificate] {
- var certificates: [SecCertificate] = []
- let paths = Set([".cer", ".CER", ".crt", ".CRT", ".der", ".DER"].map { fileExtension in
- bundle.pathsForResourcesOfType(fileExtension, inDirectory: nil)
- }.flatten())
- for path in paths {
- if let
- certificateData = NSData(contentsOfFile: path),
- 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 publicKeysInBundle(bundle: NSBundle = NSBundle.mainBundle()) -> [SecKey] {
- var publicKeys: [SecKey] = []
- for certificate in certificatesInBundle(bundle) {
- if let publicKey = publicKeyForCertificate(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 evaluateServerTrust(serverTrust: SecTrust, isValidForHost 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 .PinCertificates(pinnedCertificates, validateCertificateChain, validateHost):
- if validateCertificateChain {
- let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
- SecTrustSetPolicies(serverTrust, [policy])
- SecTrustSetAnchorCertificates(serverTrust, pinnedCertificates)
- SecTrustSetAnchorCertificatesOnly(serverTrust, true)
- serverTrustIsValid = trustIsValid(serverTrust)
- } else {
- let serverCertificatesDataArray = certificateDataForTrust(serverTrust)
- let pinnedCertificatesDataArray = certificateDataForCertificates(pinnedCertificates)
- outerLoop: for serverCertificateData in serverCertificatesDataArray {
- for pinnedCertificateData in pinnedCertificatesDataArray {
- if serverCertificateData.isEqualToData(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.publicKeysForTrust(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: serverTrust, host: host)
- }
- return serverTrustIsValid
- }
- // MARK: - Private - Trust Validation
- private func trustIsValid(trust: SecTrust) -> Bool {
- var isValid = false
- var result = SecTrustResultType(kSecTrustResultInvalid)
- let status = SecTrustEvaluate(trust, &result)
- if status == errSecSuccess {
- let unspecified = SecTrustResultType(kSecTrustResultUnspecified)
- let proceed = SecTrustResultType(kSecTrustResultProceed)
- isValid = result == unspecified || result == proceed
- }
- return isValid
- }
- // MARK: - Private - Certificate Data
- private func certificateDataForTrust(trust: SecTrust) -> [NSData] {
- var certificates: [SecCertificate] = []
- for index in 0..<SecTrustGetCertificateCount(trust) {
- if let certificate = SecTrustGetCertificateAtIndex(trust, index) {
- certificates.append(certificate)
- }
- }
- return certificateDataForCertificates(certificates)
- }
- private func certificateDataForCertificates(certificates: [SecCertificate]) -> [NSData] {
- return certificates.map { SecCertificateCopyData($0) as NSData }
- }
- // MARK: - Private - Public Key Extraction
- private static func publicKeysForTrust(trust: SecTrust) -> [SecKey] {
- var publicKeys: [SecKey] = []
- for index in 0..<SecTrustGetCertificateCount(trust) {
- if let
- certificate = SecTrustGetCertificateAtIndex(trust, index),
- publicKey = publicKeyForCertificate(certificate)
- {
- publicKeys.append(publicKey)
- }
- }
- return publicKeys
- }
- private static func publicKeyForCertificate(certificate: SecCertificate) -> SecKey? {
- var publicKey: SecKey?
- let policy = SecPolicyCreateBasicX509()
- var trust: SecTrust?
- let trustCreationStatus = SecTrustCreateWithCertificates(certificate, policy, &trust)
- if let trust = trust where trustCreationStatus == errSecSuccess {
- publicKey = SecTrustCopyPublicKey(trust)
- }
- return publicKey
- }
- }
|