2 // ServerTrustPolicy.swift
4 // Copyright (c) 2014-2017 Alamofire Software Foundation (http://alamofire.org/)
6 // Permission is hereby granted, free of charge, to any person obtaining a copy
7 // of this software and associated documentation files (the "Software"), to deal
8 // in the Software without restriction, including without limitation the rights
9 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 // copies of the Software, and to permit persons to whom the Software is
11 // furnished to do so, subject to the following conditions:
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 /// Responsible for managing the mapping of `ServerTrustPolicy` objects to a given host.
28 open class ServerTrustPolicyManager {
29 /// The dictionary of policies mapped to a particular host.
30 open let policies: [String: ServerTrustPolicy]
32 /// Initializes the `ServerTrustPolicyManager` instance with the given policies.
34 /// Since different servers and web services can have different leaf certificates, intermediate and even root
35 /// certficates, it is important to have the flexibility to specify evaluation policies on a per host basis. This
36 /// allows for scenarios such as using default evaluation for host1, certificate pinning for host2, public key
37 /// pinning for host3 and disabling evaluation for host4.
39 /// - parameter policies: A dictionary of all policies mapped to a particular host.
41 /// - returns: The new `ServerTrustPolicyManager` instance.
42 public init(policies: [String: ServerTrustPolicy]) {
43 self.policies = policies
46 /// Returns the `ServerTrustPolicy` for the given host if applicable.
48 /// By default, this method will return the policy that perfectly matches the given host. Subclasses could override
49 /// this method and implement more complex mapping implementations such as wildcards.
51 /// - parameter host: The host to use when searching for a matching policy.
53 /// - returns: The server trust policy for the given host if found.
54 open func serverTrustPolicy(forHost host: String) -> ServerTrustPolicy? {
61 extension URLSession {
62 private struct AssociatedKeys {
63 static var managerKey = "URLSession.ServerTrustPolicyManager"
66 var serverTrustPolicyManager: ServerTrustPolicyManager? {
68 return objc_getAssociatedObject(self, &AssociatedKeys.managerKey) as? ServerTrustPolicyManager
71 objc_setAssociatedObject(self, &AssociatedKeys.managerKey, manager, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
76 // MARK: - ServerTrustPolicy
78 /// The `ServerTrustPolicy` evaluates the server trust generally provided by an `NSURLAuthenticationChallenge` when
79 /// connecting to a server over a secure HTTPS connection. The policy configuration then evaluates the server trust
80 /// with a given set of criteria to determine whether the server trust is valid and the connection should be made.
82 /// Using pinned certificates or public keys for evaluation helps prevent man-in-the-middle (MITM) attacks and other
83 /// vulnerabilities. Applications dealing with sensitive customer data or financial information are strongly encouraged
84 /// to route all communication over an HTTPS connection with pinning enabled.
86 /// - performDefaultEvaluation: Uses the default server trust evaluation while allowing you to control whether to
87 /// validate the host provided by the challenge. Applications are encouraged to always
88 /// validate the host in production environments to guarantee the validity of the server's
89 /// certificate chain.
91 /// - performRevokedEvaluation: Uses the default and revoked server trust evaluations allowing you to control whether to
92 /// validate the host provided by the challenge as well as specify the revocation flags for
93 /// testing for revoked certificates. Apple platforms did not start testing for revoked
94 /// certificates automatically until iOS 10.1, macOS 10.12 and tvOS 10.1 which is
95 /// demonstrated in our TLS tests. Applications are encouraged to always validate the host
96 /// in production environments to guarantee the validity of the server's certificate chain.
98 /// - pinCertificates: Uses the pinned certificates to validate the server trust. The server trust is
99 /// considered valid if one of the pinned certificates match one of the server certificates.
100 /// By validating both the certificate chain and host, certificate pinning provides a very
101 /// secure form of server trust validation mitigating most, if not all, MITM attacks.
102 /// Applications are encouraged to always validate the host and require a valid certificate
103 /// chain in production environments.
105 /// - pinPublicKeys: Uses the pinned public keys to validate the server trust. The server trust is considered
106 /// valid if one of the pinned public keys match one of the server certificate public keys.
107 /// By validating both the certificate chain and host, public key pinning provides a very
108 /// secure form of server trust validation mitigating most, if not all, MITM attacks.
109 /// Applications are encouraged to always validate the host and require a valid certificate
110 /// chain in production environments.
112 /// - disableEvaluation: Disables all evaluation which in turn will always consider any server trust as valid.
114 /// - customEvaluation: Uses the associated closure to evaluate the validity of the server trust.
115 public enum ServerTrustPolicy {
116 case performDefaultEvaluation(validateHost: Bool)
117 case performRevokedEvaluation(validateHost: Bool, revocationFlags: CFOptionFlags)
118 case pinCertificates(certificates: [SecCertificate], validateCertificateChain: Bool, validateHost: Bool)
119 case pinPublicKeys(publicKeys: [SecKey], validateCertificateChain: Bool, validateHost: Bool)
120 case disableEvaluation
121 case customEvaluation((_ serverTrust: SecTrust, _ host: String) -> Bool)
123 // MARK: - Bundle Location
125 /// Returns all certificates within the given bundle with a `.cer` file extension.
127 /// - parameter bundle: The bundle to search for all `.cer` files.
129 /// - returns: All certificates within the given bundle.
130 public static func certificates(in bundle: Bundle = Bundle.main) -> [SecCertificate] {
131 var certificates: [SecCertificate] = []
133 let paths = Set([".cer", ".CER", ".crt", ".CRT", ".der", ".DER"].map { fileExtension in
134 bundle.paths(forResourcesOfType: fileExtension, inDirectory: nil)
139 let certificateData = try? Data(contentsOf: URL(fileURLWithPath: path)) as CFData,
140 let certificate = SecCertificateCreateWithData(nil, certificateData)
142 certificates.append(certificate)
149 /// Returns all public keys within the given bundle with a `.cer` file extension.
151 /// - parameter bundle: The bundle to search for all `*.cer` files.
153 /// - returns: All public keys within the given bundle.
154 public static func publicKeys(in bundle: Bundle = Bundle.main) -> [SecKey] {
155 var publicKeys: [SecKey] = []
157 for certificate in certificates(in: bundle) {
158 if let publicKey = publicKey(for: certificate) {
159 publicKeys.append(publicKey)
166 // MARK: - Evaluation
168 /// Evaluates whether the server trust is valid for the given host.
170 /// - parameter serverTrust: The server trust to evaluate.
171 /// - parameter host: The host of the challenge protection space.
173 /// - returns: Whether the server trust is valid.
174 public func evaluate(_ serverTrust: SecTrust, forHost host: String) -> Bool {
175 var serverTrustIsValid = false
178 case let .performDefaultEvaluation(validateHost):
179 let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
180 SecTrustSetPolicies(serverTrust, policy)
182 serverTrustIsValid = trustIsValid(serverTrust)
183 case let .performRevokedEvaluation(validateHost, revocationFlags):
184 let defaultPolicy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
185 let revokedPolicy = SecPolicyCreateRevocation(revocationFlags)
186 SecTrustSetPolicies(serverTrust, [defaultPolicy, revokedPolicy] as CFTypeRef)
188 serverTrustIsValid = trustIsValid(serverTrust)
189 case let .pinCertificates(pinnedCertificates, validateCertificateChain, validateHost):
190 if validateCertificateChain {
191 let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
192 SecTrustSetPolicies(serverTrust, policy)
194 SecTrustSetAnchorCertificates(serverTrust, pinnedCertificates as CFArray)
195 SecTrustSetAnchorCertificatesOnly(serverTrust, true)
197 serverTrustIsValid = trustIsValid(serverTrust)
199 let serverCertificatesDataArray = certificateData(for: serverTrust)
200 let pinnedCertificatesDataArray = certificateData(for: pinnedCertificates)
202 outerLoop: for serverCertificateData in serverCertificatesDataArray {
203 for pinnedCertificateData in pinnedCertificatesDataArray {
204 if serverCertificateData == pinnedCertificateData {
205 serverTrustIsValid = true
211 case let .pinPublicKeys(pinnedPublicKeys, validateCertificateChain, validateHost):
212 var certificateChainEvaluationPassed = true
214 if validateCertificateChain {
215 let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
216 SecTrustSetPolicies(serverTrust, policy)
218 certificateChainEvaluationPassed = trustIsValid(serverTrust)
221 if certificateChainEvaluationPassed {
222 outerLoop: for serverPublicKey in ServerTrustPolicy.publicKeys(for: serverTrust) as [AnyObject] {
223 for pinnedPublicKey in pinnedPublicKeys as [AnyObject] {
224 if serverPublicKey.isEqual(pinnedPublicKey) {
225 serverTrustIsValid = true
231 case .disableEvaluation:
232 serverTrustIsValid = true
233 case let .customEvaluation(closure):
234 serverTrustIsValid = closure(serverTrust, host)
237 return serverTrustIsValid
240 // MARK: - Private - Trust Validation
242 private func trustIsValid(_ trust: SecTrust) -> Bool {
245 var result = SecTrustResultType.invalid
246 let status = SecTrustEvaluate(trust, &result)
248 if status == errSecSuccess {
249 let unspecified = SecTrustResultType.unspecified
250 let proceed = SecTrustResultType.proceed
253 isValid = result == unspecified || result == proceed
259 // MARK: - Private - Certificate Data
261 private func certificateData(for trust: SecTrust) -> [Data] {
262 var certificates: [SecCertificate] = []
264 for index in 0..<SecTrustGetCertificateCount(trust) {
265 if let certificate = SecTrustGetCertificateAtIndex(trust, index) {
266 certificates.append(certificate)
270 return certificateData(for: certificates)
273 private func certificateData(for certificates: [SecCertificate]) -> [Data] {
274 return certificates.map { SecCertificateCopyData($0) as Data }
277 // MARK: - Private - Public Key Extraction
279 private static func publicKeys(for trust: SecTrust) -> [SecKey] {
280 var publicKeys: [SecKey] = []
282 for index in 0..<SecTrustGetCertificateCount(trust) {
284 let certificate = SecTrustGetCertificateAtIndex(trust, index),
285 let publicKey = publicKey(for: certificate)
287 publicKeys.append(publicKey)
294 private static func publicKey(for certificate: SecCertificate) -> SecKey? {
295 var publicKey: SecKey?
297 let policy = SecPolicyCreateBasicX509()
299 let trustCreationStatus = SecTrustCreateWithCertificates(certificate, policy, &trust)
301 if let trust = trust, trustCreationStatus == errSecSuccess {
302 publicKey = SecTrustCopyPublicKey(trust)