added iOS source code
[wl-app.git] / iOS / Pods / Alamofire / Source / ServerTrustPolicy.swift
1 //
2 //  ServerTrustPolicy.swift
3 //
4 //  Copyright (c) 2014-2017 Alamofire Software Foundation (http://alamofire.org/)
5 //
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:
12 //
13 //  The above copyright notice and this permission notice shall be included in
14 //  all copies or substantial portions of the Software.
15 //
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
22 //  THE SOFTWARE.
23 //
24
25 import Foundation
26
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]
31
32     /// Initializes the `ServerTrustPolicyManager` instance with the given policies.
33     ///
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.
38     ///
39     /// - parameter policies: A dictionary of all policies mapped to a particular host.
40     ///
41     /// - returns: The new `ServerTrustPolicyManager` instance.
42     public init(policies: [String: ServerTrustPolicy]) {
43         self.policies = policies
44     }
45
46     /// Returns the `ServerTrustPolicy` for the given host if applicable.
47     ///
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.
50     ///
51     /// - parameter host: The host to use when searching for a matching policy.
52     ///
53     /// - returns: The server trust policy for the given host if found.
54     open func serverTrustPolicy(forHost host: String) -> ServerTrustPolicy? {
55         return policies[host]
56     }
57 }
58
59 // MARK: -
60
61 extension URLSession {
62     private struct AssociatedKeys {
63         static var managerKey = "URLSession.ServerTrustPolicyManager"
64     }
65
66     var serverTrustPolicyManager: ServerTrustPolicyManager? {
67         get {
68             return objc_getAssociatedObject(self, &AssociatedKeys.managerKey) as? ServerTrustPolicyManager
69         }
70         set (manager) {
71             objc_setAssociatedObject(self, &AssociatedKeys.managerKey, manager, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
72         }
73     }
74 }
75
76 // MARK: - ServerTrustPolicy
77
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.
81 ///
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.
85 ///
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.
90 ///
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.
97 ///
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.
104 ///
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.
111 ///
112 /// - disableEvaluation:        Disables all evaluation which in turn will always consider any server trust as valid.
113 ///
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)
122
123     // MARK: - Bundle Location
124
125     /// Returns all certificates within the given bundle with a `.cer` file extension.
126     ///
127     /// - parameter bundle: The bundle to search for all `.cer` files.
128     ///
129     /// - returns: All certificates within the given bundle.
130     public static func certificates(in bundle: Bundle = Bundle.main) -> [SecCertificate] {
131         var certificates: [SecCertificate] = []
132
133         let paths = Set([".cer", ".CER", ".crt", ".CRT", ".der", ".DER"].map { fileExtension in
134             bundle.paths(forResourcesOfType: fileExtension, inDirectory: nil)
135         }.joined())
136
137         for path in paths {
138             if
139                 let certificateData = try? Data(contentsOf: URL(fileURLWithPath: path)) as CFData,
140                 let certificate = SecCertificateCreateWithData(nil, certificateData)
141             {
142                 certificates.append(certificate)
143             }
144         }
145
146         return certificates
147     }
148
149     /// Returns all public keys within the given bundle with a `.cer` file extension.
150     ///
151     /// - parameter bundle: The bundle to search for all `*.cer` files.
152     ///
153     /// - returns: All public keys within the given bundle.
154     public static func publicKeys(in bundle: Bundle = Bundle.main) -> [SecKey] {
155         var publicKeys: [SecKey] = []
156
157         for certificate in certificates(in: bundle) {
158             if let publicKey = publicKey(for: certificate) {
159                 publicKeys.append(publicKey)
160             }
161         }
162
163         return publicKeys
164     }
165
166     // MARK: - Evaluation
167
168     /// Evaluates whether the server trust is valid for the given host.
169     ///
170     /// - parameter serverTrust: The server trust to evaluate.
171     /// - parameter host:        The host of the challenge protection space.
172     ///
173     /// - returns: Whether the server trust is valid.
174     public func evaluate(_ serverTrust: SecTrust, forHost host: String) -> Bool {
175         var serverTrustIsValid = false
176
177         switch self {
178         case let .performDefaultEvaluation(validateHost):
179             let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
180             SecTrustSetPolicies(serverTrust, policy)
181
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)
187
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)
193
194                 SecTrustSetAnchorCertificates(serverTrust, pinnedCertificates as CFArray)
195                 SecTrustSetAnchorCertificatesOnly(serverTrust, true)
196
197                 serverTrustIsValid = trustIsValid(serverTrust)
198             } else {
199                 let serverCertificatesDataArray = certificateData(for: serverTrust)
200                 let pinnedCertificatesDataArray = certificateData(for: pinnedCertificates)
201
202                 outerLoop: for serverCertificateData in serverCertificatesDataArray {
203                     for pinnedCertificateData in pinnedCertificatesDataArray {
204                         if serverCertificateData == pinnedCertificateData {
205                             serverTrustIsValid = true
206                             break outerLoop
207                         }
208                     }
209                 }
210             }
211         case let .pinPublicKeys(pinnedPublicKeys, validateCertificateChain, validateHost):
212             var certificateChainEvaluationPassed = true
213
214             if validateCertificateChain {
215                 let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
216                 SecTrustSetPolicies(serverTrust, policy)
217
218                 certificateChainEvaluationPassed = trustIsValid(serverTrust)
219             }
220
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
226                             break outerLoop
227                         }
228                     }
229                 }
230             }
231         case .disableEvaluation:
232             serverTrustIsValid = true
233         case let .customEvaluation(closure):
234             serverTrustIsValid = closure(serverTrust, host)
235         }
236
237         return serverTrustIsValid
238     }
239
240     // MARK: - Private - Trust Validation
241
242     private func trustIsValid(_ trust: SecTrust) -> Bool {
243         var isValid = false
244
245         var result = SecTrustResultType.invalid
246         let status = SecTrustEvaluate(trust, &result)
247
248         if status == errSecSuccess {
249             let unspecified = SecTrustResultType.unspecified
250             let proceed = SecTrustResultType.proceed
251
252
253             isValid = result == unspecified || result == proceed
254         }
255
256         return isValid
257     }
258
259     // MARK: - Private - Certificate Data
260
261     private func certificateData(for trust: SecTrust) -> [Data] {
262         var certificates: [SecCertificate] = []
263
264         for index in 0..<SecTrustGetCertificateCount(trust) {
265             if let certificate = SecTrustGetCertificateAtIndex(trust, index) {
266                 certificates.append(certificate)
267             }
268         }
269
270         return certificateData(for: certificates)
271     }
272
273     private func certificateData(for certificates: [SecCertificate]) -> [Data] {
274         return certificates.map { SecCertificateCopyData($0) as Data }
275     }
276
277     // MARK: - Private - Public Key Extraction
278
279     private static func publicKeys(for trust: SecTrust) -> [SecKey] {
280         var publicKeys: [SecKey] = []
281
282         for index in 0..<SecTrustGetCertificateCount(trust) {
283             if
284                 let certificate = SecTrustGetCertificateAtIndex(trust, index),
285                 let publicKey = publicKey(for: certificate)
286             {
287                 publicKeys.append(publicKey)
288             }
289         }
290
291         return publicKeys
292     }
293
294     private static func publicKey(for certificate: SecCertificate) -> SecKey? {
295         var publicKey: SecKey?
296
297         let policy = SecPolicyCreateBasicX509()
298         var trust: SecTrust?
299         let trustCreationStatus = SecTrustCreateWithCertificates(certificate, policy, &trust)
300
301         if let trust = trust, trustCreationStatus == errSecSuccess {
302             publicKey = SecTrustCopyPublicKey(trust)
303         }
304
305         return publicKey
306     }
307 }