1 |
efrain |
1 |
//
|
|
|
2 |
// ServerTrustPolicy.swift
|
|
|
3 |
//
|
|
|
4 |
// Copyright (c) 2014-2016 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 `ServerTrustEvaluating` values to given hosts.
|
|
|
28 |
open class ServerTrustManager {
|
|
|
29 |
/// Determines whether all hosts for this `ServerTrustManager` must be evaluated. `true` by default.
|
|
|
30 |
public let allHostsMustBeEvaluated: Bool
|
|
|
31 |
|
|
|
32 |
/// The dictionary of policies mapped to a particular host.
|
|
|
33 |
public let evaluators: [String: ServerTrustEvaluating]
|
|
|
34 |
|
|
|
35 |
/// Initializes the `ServerTrustManager` instance with the given evaluators.
|
|
|
36 |
///
|
|
|
37 |
/// Since different servers and web services can have different leaf certificates, intermediate and even root
|
|
|
38 |
/// certificates, it is important to have the flexibility to specify evaluation policies on a per host basis. This
|
|
|
39 |
/// allows for scenarios such as using default evaluation for host1, certificate pinning for host2, public key
|
|
|
40 |
/// pinning for host3 and disabling evaluation for host4.
|
|
|
41 |
///
|
|
|
42 |
/// - Parameters:
|
|
|
43 |
/// - allHostsMustBeEvaluated: The value determining whether all hosts for this instance must be evaluated. `true`
|
|
|
44 |
/// by default.
|
|
|
45 |
/// - evaluators: A dictionary of evaluators mapped to hosts.
|
|
|
46 |
public init(allHostsMustBeEvaluated: Bool = true, evaluators: [String: ServerTrustEvaluating]) {
|
|
|
47 |
self.allHostsMustBeEvaluated = allHostsMustBeEvaluated
|
|
|
48 |
self.evaluators = evaluators
|
|
|
49 |
}
|
|
|
50 |
|
|
|
51 |
#if !(os(Linux) || os(Windows))
|
|
|
52 |
/// Returns the `ServerTrustEvaluating` value for the given host, if one is set.
|
|
|
53 |
///
|
|
|
54 |
/// By default, this method will return the policy that perfectly matches the given host. Subclasses could override
|
|
|
55 |
/// this method and implement more complex mapping implementations such as wildcards.
|
|
|
56 |
///
|
|
|
57 |
/// - Parameter host: The host to use when searching for a matching policy.
|
|
|
58 |
///
|
|
|
59 |
/// - Returns: The `ServerTrustEvaluating` value for the given host if found, `nil` otherwise.
|
|
|
60 |
/// - Throws: `AFError.serverTrustEvaluationFailed` if `allHostsMustBeEvaluated` is `true` and no matching
|
|
|
61 |
/// evaluators are found.
|
|
|
62 |
open func serverTrustEvaluator(forHost host: String) throws -> ServerTrustEvaluating? {
|
|
|
63 |
guard let evaluator = evaluators[host] else {
|
|
|
64 |
if allHostsMustBeEvaluated {
|
|
|
65 |
throw AFError.serverTrustEvaluationFailed(reason: .noRequiredEvaluator(host: host))
|
|
|
66 |
}
|
|
|
67 |
|
|
|
68 |
return nil
|
|
|
69 |
}
|
|
|
70 |
|
|
|
71 |
return evaluator
|
|
|
72 |
}
|
|
|
73 |
#endif
|
|
|
74 |
}
|
|
|
75 |
|
|
|
76 |
/// A protocol describing the API used to evaluate server trusts.
|
|
|
77 |
public protocol ServerTrustEvaluating {
|
|
|
78 |
#if os(Linux) || os(Windows)
|
|
|
79 |
// Implement this once Linux/Windows has API for evaluating server trusts.
|
|
|
80 |
#else
|
|
|
81 |
/// Evaluates the given `SecTrust` value for the given `host`.
|
|
|
82 |
///
|
|
|
83 |
/// - Parameters:
|
|
|
84 |
/// - trust: The `SecTrust` value to evaluate.
|
|
|
85 |
/// - host: The host for which to evaluate the `SecTrust` value.
|
|
|
86 |
///
|
|
|
87 |
/// - Returns: A `Bool` indicating whether the evaluator considers the `SecTrust` value valid for `host`.
|
|
|
88 |
func evaluate(_ trust: SecTrust, forHost host: String) throws
|
|
|
89 |
#endif
|
|
|
90 |
}
|
|
|
91 |
|
|
|
92 |
// MARK: - Server Trust Evaluators
|
|
|
93 |
|
|
|
94 |
#if !(os(Linux) || os(Windows))
|
|
|
95 |
/// An evaluator which uses the default server trust evaluation while allowing you to control whether to validate the
|
|
|
96 |
/// host provided by the challenge. Applications are encouraged to always validate the host in production environments
|
|
|
97 |
/// to guarantee the validity of the server's certificate chain.
|
|
|
98 |
public final class DefaultTrustEvaluator: ServerTrustEvaluating {
|
|
|
99 |
private let validateHost: Bool
|
|
|
100 |
|
|
|
101 |
/// Creates a `DefaultTrustEvaluator`.
|
|
|
102 |
///
|
|
|
103 |
/// - Parameter validateHost: Determines whether or not the evaluator should validate the host. `true` by default.
|
|
|
104 |
public init(validateHost: Bool = true) {
|
|
|
105 |
self.validateHost = validateHost
|
|
|
106 |
}
|
|
|
107 |
|
|
|
108 |
public func evaluate(_ trust: SecTrust, forHost host: String) throws {
|
|
|
109 |
if validateHost {
|
|
|
110 |
try trust.af.performValidation(forHost: host)
|
|
|
111 |
}
|
|
|
112 |
|
|
|
113 |
try trust.af.performDefaultValidation(forHost: host)
|
|
|
114 |
}
|
|
|
115 |
}
|
|
|
116 |
|
|
|
117 |
/// An evaluator which Uses the default and revoked server trust evaluations allowing you to control whether to validate
|
|
|
118 |
/// the host provided by the challenge as well as specify the revocation flags for testing for revoked certificates.
|
|
|
119 |
/// Apple platforms did not start testing for revoked certificates automatically until iOS 10.1, macOS 10.12 and tvOS
|
|
|
120 |
/// 10.1 which is demonstrated in our TLS tests. Applications are encouraged to always validate the host in production
|
|
|
121 |
/// environments to guarantee the validity of the server's certificate chain.
|
|
|
122 |
public final class RevocationTrustEvaluator: ServerTrustEvaluating {
|
|
|
123 |
/// Represents the options to be use when evaluating the status of a certificate.
|
|
|
124 |
/// Only Revocation Policy Constants are valid, and can be found in [Apple's documentation](https://developer.apple.com/documentation/security/certificate_key_and_trust_services/policies/1563600-revocation_policy_constants).
|
|
|
125 |
public struct Options: OptionSet {
|
|
|
126 |
/// Perform revocation checking using the CRL (Certification Revocation List) method.
|
|
|
127 |
public static let crl = Options(rawValue: kSecRevocationCRLMethod)
|
|
|
128 |
/// Consult only locally cached replies; do not use network access.
|
|
|
129 |
public static let networkAccessDisabled = Options(rawValue: kSecRevocationNetworkAccessDisabled)
|
|
|
130 |
/// Perform revocation checking using OCSP (Online Certificate Status Protocol).
|
|
|
131 |
public static let ocsp = Options(rawValue: kSecRevocationOCSPMethod)
|
|
|
132 |
/// Prefer CRL revocation checking over OCSP; by default, OCSP is preferred.
|
|
|
133 |
public static let preferCRL = Options(rawValue: kSecRevocationPreferCRL)
|
|
|
134 |
/// Require a positive response to pass the policy. If the flag is not set, revocation checking is done on a
|
|
|
135 |
/// "best attempt" basis, where failure to reach the server is not considered fatal.
|
|
|
136 |
public static let requirePositiveResponse = Options(rawValue: kSecRevocationRequirePositiveResponse)
|
|
|
137 |
/// Perform either OCSP or CRL checking. The checking is performed according to the method(s) specified in the
|
|
|
138 |
/// certificate and the value of `preferCRL`.
|
|
|
139 |
public static let any = Options(rawValue: kSecRevocationUseAnyAvailableMethod)
|
|
|
140 |
|
|
|
141 |
/// The raw value of the option.
|
|
|
142 |
public let rawValue: CFOptionFlags
|
|
|
143 |
|
|
|
144 |
/// Creates an `Options` value with the given `CFOptionFlags`.
|
|
|
145 |
///
|
|
|
146 |
/// - Parameter rawValue: The `CFOptionFlags` value to initialize with.
|
|
|
147 |
public init(rawValue: CFOptionFlags) {
|
|
|
148 |
self.rawValue = rawValue
|
|
|
149 |
}
|
|
|
150 |
}
|
|
|
151 |
|
|
|
152 |
private let performDefaultValidation: Bool
|
|
|
153 |
private let validateHost: Bool
|
|
|
154 |
private let options: Options
|
|
|
155 |
|
|
|
156 |
/// Creates a `RevocationTrustEvaluator` using the provided parameters.
|
|
|
157 |
///
|
|
|
158 |
/// - Note: Default and host validation will fail when using this evaluator with self-signed certificates. Use
|
|
|
159 |
/// `PinnedCertificatesTrustEvaluator` if you need to use self-signed certificates.
|
|
|
160 |
///
|
|
|
161 |
/// - Parameters:
|
|
|
162 |
/// - performDefaultValidation: Determines whether default validation should be performed in addition to
|
|
|
163 |
/// evaluating the pinned certificates. `true` by default.
|
|
|
164 |
/// - validateHost: Determines whether or not the evaluator should validate the host, in addition to
|
|
|
165 |
/// performing the default evaluation, even if `performDefaultValidation` is `false`.
|
|
|
166 |
/// `true` by default.
|
|
|
167 |
/// - options: The `Options` to use to check the revocation status of the certificate. `.any` by
|
|
|
168 |
/// default.
|
|
|
169 |
public init(performDefaultValidation: Bool = true, validateHost: Bool = true, options: Options = .any) {
|
|
|
170 |
self.performDefaultValidation = performDefaultValidation
|
|
|
171 |
self.validateHost = validateHost
|
|
|
172 |
self.options = options
|
|
|
173 |
}
|
|
|
174 |
|
|
|
175 |
public func evaluate(_ trust: SecTrust, forHost host: String) throws {
|
|
|
176 |
if performDefaultValidation {
|
|
|
177 |
try trust.af.performDefaultValidation(forHost: host)
|
|
|
178 |
}
|
|
|
179 |
|
|
|
180 |
if validateHost {
|
|
|
181 |
try trust.af.performValidation(forHost: host)
|
|
|
182 |
}
|
|
|
183 |
|
|
|
184 |
if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) {
|
|
|
185 |
try trust.af.evaluate(afterApplying: SecPolicy.af.revocation(options: options))
|
|
|
186 |
} else {
|
|
|
187 |
try trust.af.validate(policy: SecPolicy.af.revocation(options: options)) { status, result in
|
|
|
188 |
AFError.serverTrustEvaluationFailed(reason: .revocationCheckFailed(output: .init(host, trust, status, result), options: options))
|
|
|
189 |
}
|
|
|
190 |
}
|
|
|
191 |
}
|
|
|
192 |
}
|
|
|
193 |
|
|
|
194 |
#if swift(>=5.5)
|
|
|
195 |
extension ServerTrustEvaluating where Self == RevocationTrustEvaluator {
|
|
|
196 |
/// Provides a default `RevocationTrustEvaluator` instance.
|
|
|
197 |
public static var revocationChecking: RevocationTrustEvaluator { RevocationTrustEvaluator() }
|
|
|
198 |
|
|
|
199 |
/// Creates a `RevocationTrustEvaluator` using the provided parameters.
|
|
|
200 |
///
|
|
|
201 |
/// - Note: Default and host validation will fail when using this evaluator with self-signed certificates. Use
|
|
|
202 |
/// `PinnedCertificatesTrustEvaluator` if you need to use self-signed certificates.
|
|
|
203 |
///
|
|
|
204 |
/// - Parameters:
|
|
|
205 |
/// - performDefaultValidation: Determines whether default validation should be performed in addition to
|
|
|
206 |
/// evaluating the pinned certificates. `true` by default.
|
|
|
207 |
/// - validateHost: Determines whether or not the evaluator should validate the host, in addition
|
|
|
208 |
/// to performing the default evaluation, even if `performDefaultValidation` is
|
|
|
209 |
/// `false`. `true` by default.
|
|
|
210 |
/// - options: The `Options` to use to check the revocation status of the certificate. `.any`
|
|
|
211 |
/// by default.
|
|
|
212 |
/// - Returns: The `RevocationTrustEvaluator`.
|
|
|
213 |
public static func revocationChecking(performDefaultValidation: Bool = true,
|
|
|
214 |
validateHost: Bool = true,
|
|
|
215 |
options: RevocationTrustEvaluator.Options = .any) -> RevocationTrustEvaluator {
|
|
|
216 |
RevocationTrustEvaluator(performDefaultValidation: performDefaultValidation,
|
|
|
217 |
validateHost: validateHost,
|
|
|
218 |
options: options)
|
|
|
219 |
}
|
|
|
220 |
}
|
|
|
221 |
#endif
|
|
|
222 |
|
|
|
223 |
/// Uses the pinned certificates to validate the server trust. The server trust is considered valid if one of the pinned
|
|
|
224 |
/// certificates match one of the server certificates. By validating both the certificate chain and host, certificate
|
|
|
225 |
/// pinning provides a very secure form of server trust validation mitigating most, if not all, MITM attacks.
|
|
|
226 |
/// Applications are encouraged to always validate the host and require a valid certificate chain in production
|
|
|
227 |
/// environments.
|
|
|
228 |
public final class PinnedCertificatesTrustEvaluator: ServerTrustEvaluating {
|
|
|
229 |
private let certificates: [SecCertificate]
|
|
|
230 |
private let acceptSelfSignedCertificates: Bool
|
|
|
231 |
private let performDefaultValidation: Bool
|
|
|
232 |
private let validateHost: Bool
|
|
|
233 |
|
|
|
234 |
/// Creates a `PinnedCertificatesTrustEvaluator` from the provided parameters.
|
|
|
235 |
///
|
|
|
236 |
/// - Parameters:
|
|
|
237 |
/// - certificates: The certificates to use to evaluate the trust. All `cer`, `crt`, and `der`
|
|
|
238 |
/// certificates in `Bundle.main` by default.
|
|
|
239 |
/// - acceptSelfSignedCertificates: Adds the provided certificates as anchors for the trust evaluation, allowing
|
|
|
240 |
/// self-signed certificates to pass. `false` by default. THIS SETTING SHOULD BE
|
|
|
241 |
/// FALSE IN PRODUCTION!
|
|
|
242 |
/// - performDefaultValidation: Determines whether default validation should be performed in addition to
|
|
|
243 |
/// evaluating the pinned certificates. `true` by default.
|
|
|
244 |
/// - validateHost: Determines whether or not the evaluator should validate the host, in addition
|
|
|
245 |
/// to performing the default evaluation, even if `performDefaultValidation` is
|
|
|
246 |
/// `false`. `true` by default.
|
|
|
247 |
public init(certificates: [SecCertificate] = Bundle.main.af.certificates,
|
|
|
248 |
acceptSelfSignedCertificates: Bool = false,
|
|
|
249 |
performDefaultValidation: Bool = true,
|
|
|
250 |
validateHost: Bool = true) {
|
|
|
251 |
self.certificates = certificates
|
|
|
252 |
self.acceptSelfSignedCertificates = acceptSelfSignedCertificates
|
|
|
253 |
self.performDefaultValidation = performDefaultValidation
|
|
|
254 |
self.validateHost = validateHost
|
|
|
255 |
}
|
|
|
256 |
|
|
|
257 |
public func evaluate(_ trust: SecTrust, forHost host: String) throws {
|
|
|
258 |
guard !certificates.isEmpty else {
|
|
|
259 |
throw AFError.serverTrustEvaluationFailed(reason: .noCertificatesFound)
|
|
|
260 |
}
|
|
|
261 |
|
|
|
262 |
if acceptSelfSignedCertificates {
|
|
|
263 |
try trust.af.setAnchorCertificates(certificates)
|
|
|
264 |
}
|
|
|
265 |
|
|
|
266 |
if performDefaultValidation {
|
|
|
267 |
try trust.af.performDefaultValidation(forHost: host)
|
|
|
268 |
}
|
|
|
269 |
|
|
|
270 |
if validateHost {
|
|
|
271 |
try trust.af.performValidation(forHost: host)
|
|
|
272 |
}
|
|
|
273 |
|
|
|
274 |
let serverCertificatesData = Set(trust.af.certificateData)
|
|
|
275 |
let pinnedCertificatesData = Set(certificates.af.data)
|
|
|
276 |
let pinnedCertificatesInServerData = !serverCertificatesData.isDisjoint(with: pinnedCertificatesData)
|
|
|
277 |
if !pinnedCertificatesInServerData {
|
|
|
278 |
throw AFError.serverTrustEvaluationFailed(reason: .certificatePinningFailed(host: host,
|
|
|
279 |
trust: trust,
|
|
|
280 |
pinnedCertificates: certificates,
|
|
|
281 |
serverCertificates: trust.af.certificates))
|
|
|
282 |
}
|
|
|
283 |
}
|
|
|
284 |
}
|
|
|
285 |
|
|
|
286 |
#if swift(>=5.5)
|
|
|
287 |
extension ServerTrustEvaluating where Self == PinnedCertificatesTrustEvaluator {
|
|
|
288 |
/// Provides a default `PinnedCertificatesTrustEvaluator` instance.
|
|
|
289 |
public static var pinnedCertificates: PinnedCertificatesTrustEvaluator { PinnedCertificatesTrustEvaluator() }
|
|
|
290 |
|
|
|
291 |
/// Creates a `PinnedCertificatesTrustEvaluator` using the provided parameters.
|
|
|
292 |
///
|
|
|
293 |
/// - Parameters:
|
|
|
294 |
/// - certificates: The certificates to use to evaluate the trust. All `cer`, `crt`, and `der`
|
|
|
295 |
/// certificates in `Bundle.main` by default.
|
|
|
296 |
/// - acceptSelfSignedCertificates: Adds the provided certificates as anchors for the trust evaluation, allowing
|
|
|
297 |
/// self-signed certificates to pass. `false` by default. THIS SETTING SHOULD BE
|
|
|
298 |
/// FALSE IN PRODUCTION!
|
|
|
299 |
/// - performDefaultValidation: Determines whether default validation should be performed in addition to
|
|
|
300 |
/// evaluating the pinned certificates. `true` by default.
|
|
|
301 |
/// - validateHost: Determines whether or not the evaluator should validate the host, in addition
|
|
|
302 |
/// to performing the default evaluation, even if `performDefaultValidation` is
|
|
|
303 |
/// `false`. `true` by default.
|
|
|
304 |
public static func pinnedCertificates(certificates: [SecCertificate] = Bundle.main.af.certificates,
|
|
|
305 |
acceptSelfSignedCertificates: Bool = false,
|
|
|
306 |
performDefaultValidation: Bool = true,
|
|
|
307 |
validateHost: Bool = true) -> PinnedCertificatesTrustEvaluator {
|
|
|
308 |
PinnedCertificatesTrustEvaluator(certificates: certificates,
|
|
|
309 |
acceptSelfSignedCertificates: acceptSelfSignedCertificates,
|
|
|
310 |
performDefaultValidation: performDefaultValidation,
|
|
|
311 |
validateHost: validateHost)
|
|
|
312 |
}
|
|
|
313 |
}
|
|
|
314 |
#endif
|
|
|
315 |
|
|
|
316 |
/// Uses the pinned public keys to validate the server trust. The server trust is considered valid if one of the pinned
|
|
|
317 |
/// public keys match one of the server certificate public keys. By validating both the certificate chain and host,
|
|
|
318 |
/// public key pinning provides a very secure form of server trust validation mitigating most, if not all, MITM attacks.
|
|
|
319 |
/// Applications are encouraged to always validate the host and require a valid certificate chain in production
|
|
|
320 |
/// environments.
|
|
|
321 |
public final class PublicKeysTrustEvaluator: ServerTrustEvaluating {
|
|
|
322 |
private let keys: [SecKey]
|
|
|
323 |
private let performDefaultValidation: Bool
|
|
|
324 |
private let validateHost: Bool
|
|
|
325 |
|
|
|
326 |
/// Creates a `PublicKeysTrustEvaluator` from the provided parameters.
|
|
|
327 |
///
|
|
|
328 |
/// - Note: Default and host validation will fail when using this evaluator with self-signed certificates. Use
|
|
|
329 |
/// `PinnedCertificatesTrustEvaluator` if you need to use self-signed certificates.
|
|
|
330 |
///
|
|
|
331 |
/// - Parameters:
|
|
|
332 |
/// - keys: The `SecKey`s to use to validate public keys. Defaults to the public keys of all
|
|
|
333 |
/// certificates included in the main bundle.
|
|
|
334 |
/// - performDefaultValidation: Determines whether default validation should be performed in addition to
|
|
|
335 |
/// evaluating the pinned certificates. `true` by default.
|
|
|
336 |
/// - validateHost: Determines whether or not the evaluator should validate the host, in addition to
|
|
|
337 |
/// performing the default evaluation, even if `performDefaultValidation` is `false`.
|
|
|
338 |
/// `true` by default.
|
|
|
339 |
public init(keys: [SecKey] = Bundle.main.af.publicKeys,
|
|
|
340 |
performDefaultValidation: Bool = true,
|
|
|
341 |
validateHost: Bool = true) {
|
|
|
342 |
self.keys = keys
|
|
|
343 |
self.performDefaultValidation = performDefaultValidation
|
|
|
344 |
self.validateHost = validateHost
|
|
|
345 |
}
|
|
|
346 |
|
|
|
347 |
public func evaluate(_ trust: SecTrust, forHost host: String) throws {
|
|
|
348 |
guard !keys.isEmpty else {
|
|
|
349 |
throw AFError.serverTrustEvaluationFailed(reason: .noPublicKeysFound)
|
|
|
350 |
}
|
|
|
351 |
|
|
|
352 |
if performDefaultValidation {
|
|
|
353 |
try trust.af.performDefaultValidation(forHost: host)
|
|
|
354 |
}
|
|
|
355 |
|
|
|
356 |
if validateHost {
|
|
|
357 |
try trust.af.performValidation(forHost: host)
|
|
|
358 |
}
|
|
|
359 |
|
|
|
360 |
let pinnedKeysInServerKeys: Bool = {
|
|
|
361 |
for serverPublicKey in trust.af.publicKeys {
|
|
|
362 |
for pinnedPublicKey in keys {
|
|
|
363 |
if serverPublicKey == pinnedPublicKey {
|
|
|
364 |
return true
|
|
|
365 |
}
|
|
|
366 |
}
|
|
|
367 |
}
|
|
|
368 |
return false
|
|
|
369 |
}()
|
|
|
370 |
|
|
|
371 |
if !pinnedKeysInServerKeys {
|
|
|
372 |
throw AFError.serverTrustEvaluationFailed(reason: .publicKeyPinningFailed(host: host,
|
|
|
373 |
trust: trust,
|
|
|
374 |
pinnedKeys: keys,
|
|
|
375 |
serverKeys: trust.af.publicKeys))
|
|
|
376 |
}
|
|
|
377 |
}
|
|
|
378 |
}
|
|
|
379 |
|
|
|
380 |
#if swift(>=5.5)
|
|
|
381 |
extension ServerTrustEvaluating where Self == PublicKeysTrustEvaluator {
|
|
|
382 |
/// Provides a default `PublicKeysTrustEvaluator` instance.
|
|
|
383 |
public static var publicKeys: PublicKeysTrustEvaluator { PublicKeysTrustEvaluator() }
|
|
|
384 |
|
|
|
385 |
/// Creates a `PublicKeysTrustEvaluator` from the provided parameters.
|
|
|
386 |
///
|
|
|
387 |
/// - Note: Default and host validation will fail when using this evaluator with self-signed certificates. Use
|
|
|
388 |
/// `PinnedCertificatesTrustEvaluator` if you need to use self-signed certificates.
|
|
|
389 |
///
|
|
|
390 |
/// - Parameters:
|
|
|
391 |
/// - keys: The `SecKey`s to use to validate public keys. Defaults to the public keys of all
|
|
|
392 |
/// certificates included in the main bundle.
|
|
|
393 |
/// - performDefaultValidation: Determines whether default validation should be performed in addition to
|
|
|
394 |
/// evaluating the pinned certificates. `true` by default.
|
|
|
395 |
/// - validateHost: Determines whether or not the evaluator should validate the host, in addition to
|
|
|
396 |
/// performing the default evaluation, even if `performDefaultValidation` is `false`.
|
|
|
397 |
/// `true` by default.
|
|
|
398 |
public static func publicKeys(keys: [SecKey] = Bundle.main.af.publicKeys,
|
|
|
399 |
performDefaultValidation: Bool = true,
|
|
|
400 |
validateHost: Bool = true) -> PublicKeysTrustEvaluator {
|
|
|
401 |
PublicKeysTrustEvaluator(keys: keys, performDefaultValidation: performDefaultValidation, validateHost: validateHost)
|
|
|
402 |
}
|
|
|
403 |
}
|
|
|
404 |
#endif
|
|
|
405 |
|
|
|
406 |
/// Uses the provided evaluators to validate the server trust. The trust is only considered valid if all of the
|
|
|
407 |
/// evaluators consider it valid.
|
|
|
408 |
public final class CompositeTrustEvaluator: ServerTrustEvaluating {
|
|
|
409 |
private let evaluators: [ServerTrustEvaluating]
|
|
|
410 |
|
|
|
411 |
/// Creates a `CompositeTrustEvaluator` from the provided evaluators.
|
|
|
412 |
///
|
|
|
413 |
/// - Parameter evaluators: The `ServerTrustEvaluating` values used to evaluate the server trust.
|
|
|
414 |
public init(evaluators: [ServerTrustEvaluating]) {
|
|
|
415 |
self.evaluators = evaluators
|
|
|
416 |
}
|
|
|
417 |
|
|
|
418 |
public func evaluate(_ trust: SecTrust, forHost host: String) throws {
|
|
|
419 |
try evaluators.evaluate(trust, forHost: host)
|
|
|
420 |
}
|
|
|
421 |
}
|
|
|
422 |
|
|
|
423 |
#if swift(>=5.5)
|
|
|
424 |
extension ServerTrustEvaluating where Self == CompositeTrustEvaluator {
|
|
|
425 |
/// Creates a `CompositeTrustEvaluator` from the provided evaluators.
|
|
|
426 |
///
|
|
|
427 |
/// - Parameter evaluators: The `ServerTrustEvaluating` values used to evaluate the server trust.
|
|
|
428 |
public static func composite(evaluators: [ServerTrustEvaluating]) -> CompositeTrustEvaluator {
|
|
|
429 |
CompositeTrustEvaluator(evaluators: evaluators)
|
|
|
430 |
}
|
|
|
431 |
}
|
|
|
432 |
#endif
|
|
|
433 |
|
|
|
434 |
/// Disables all evaluation which in turn will always consider any server trust as valid.
|
|
|
435 |
///
|
|
|
436 |
/// - Note: Instead of disabling server trust evaluation, it's a better idea to configure systems to properly trust test
|
|
|
437 |
/// certificates, as outlined in [this Apple tech note](https://developer.apple.com/library/archive/qa/qa1948/_index.html).
|
|
|
438 |
///
|
|
|
439 |
/// **THIS EVALUATOR SHOULD NEVER BE USED IN PRODUCTION!**
|
|
|
440 |
@available(*, deprecated, renamed: "DisabledTrustEvaluator", message: "DisabledEvaluator has been renamed DisabledTrustEvaluator.")
|
|
|
441 |
public typealias DisabledEvaluator = DisabledTrustEvaluator
|
|
|
442 |
|
|
|
443 |
/// Disables all evaluation which in turn will always consider any server trust as valid.
|
|
|
444 |
///
|
|
|
445 |
///
|
|
|
446 |
/// - Note: Instead of disabling server trust evaluation, it's a better idea to configure systems to properly trust test
|
|
|
447 |
/// certificates, as outlined in [this Apple tech note](https://developer.apple.com/library/archive/qa/qa1948/_index.html).
|
|
|
448 |
///
|
|
|
449 |
/// **THIS EVALUATOR SHOULD NEVER BE USED IN PRODUCTION!**
|
|
|
450 |
public final class DisabledTrustEvaluator: ServerTrustEvaluating {
|
|
|
451 |
/// Creates an instance.
|
|
|
452 |
public init() {}
|
|
|
453 |
|
|
|
454 |
public func evaluate(_ trust: SecTrust, forHost host: String) throws {}
|
|
|
455 |
}
|
|
|
456 |
|
|
|
457 |
// MARK: - Extensions
|
|
|
458 |
|
|
|
459 |
extension Array where Element == ServerTrustEvaluating {
|
|
|
460 |
#if os(Linux) || os(Windows)
|
|
|
461 |
// Add this same convenience method for Linux/Windows.
|
|
|
462 |
#else
|
|
|
463 |
/// Evaluates the given `SecTrust` value for the given `host`.
|
|
|
464 |
///
|
|
|
465 |
/// - Parameters:
|
|
|
466 |
/// - trust: The `SecTrust` value to evaluate.
|
|
|
467 |
/// - host: The host for which to evaluate the `SecTrust` value.
|
|
|
468 |
///
|
|
|
469 |
/// - Returns: Whether or not the evaluator considers the `SecTrust` value valid for `host`.
|
|
|
470 |
public func evaluate(_ trust: SecTrust, forHost host: String) throws {
|
|
|
471 |
for evaluator in self {
|
|
|
472 |
try evaluator.evaluate(trust, forHost: host)
|
|
|
473 |
}
|
|
|
474 |
}
|
|
|
475 |
#endif
|
|
|
476 |
}
|
|
|
477 |
|
|
|
478 |
extension Bundle: AlamofireExtended {}
|
|
|
479 |
extension AlamofireExtension where ExtendedType: Bundle {
|
|
|
480 |
/// Returns all valid `cer`, `crt`, and `der` certificates in the bundle.
|
|
|
481 |
public var certificates: [SecCertificate] {
|
|
|
482 |
paths(forResourcesOfTypes: [".cer", ".CER", ".crt", ".CRT", ".der", ".DER"]).compactMap { path in
|
|
|
483 |
guard
|
|
|
484 |
let certificateData = try? Data(contentsOf: URL(fileURLWithPath: path)) as CFData,
|
|
|
485 |
let certificate = SecCertificateCreateWithData(nil, certificateData) else { return nil }
|
|
|
486 |
|
|
|
487 |
return certificate
|
|
|
488 |
}
|
|
|
489 |
}
|
|
|
490 |
|
|
|
491 |
/// Returns all public keys for the valid certificates in the bundle.
|
|
|
492 |
public var publicKeys: [SecKey] {
|
|
|
493 |
certificates.af.publicKeys
|
|
|
494 |
}
|
|
|
495 |
|
|
|
496 |
/// Returns all pathnames for the resources identified by the provided file extensions.
|
|
|
497 |
///
|
|
|
498 |
/// - Parameter types: The filename extensions locate.
|
|
|
499 |
///
|
|
|
500 |
/// - Returns: All pathnames for the given filename extensions.
|
|
|
501 |
public func paths(forResourcesOfTypes types: [String]) -> [String] {
|
|
|
502 |
Array(Set(types.flatMap { type.paths(forResourcesOfType: $0, inDirectory: nil) }))
|
|
|
503 |
}
|
|
|
504 |
}
|
|
|
505 |
|
|
|
506 |
extension SecTrust: AlamofireExtended {}
|
|
|
507 |
extension AlamofireExtension where ExtendedType == SecTrust {
|
|
|
508 |
/// Evaluates `self` after applying the `SecPolicy` value provided.
|
|
|
509 |
///
|
|
|
510 |
/// - Parameter policy: The `SecPolicy` to apply to `self` before evaluation.
|
|
|
511 |
///
|
|
|
512 |
/// - Throws: Any `Error` from applying the `SecPolicy` or from evaluation.
|
|
|
513 |
@available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *)
|
|
|
514 |
public func evaluate(afterApplying policy: SecPolicy) throws {
|
|
|
515 |
try apply(policy: policy).af.evaluate()
|
|
|
516 |
}
|
|
|
517 |
|
|
|
518 |
/// Attempts to validate `self` using the `SecPolicy` provided and transforming any error produced using the closure passed.
|
|
|
519 |
///
|
|
|
520 |
/// - Parameters:
|
|
|
521 |
/// - policy: The `SecPolicy` used to evaluate `self`.
|
|
|
522 |
/// - errorProducer: The closure used transform the failed `OSStatus` and `SecTrustResultType`.
|
|
|
523 |
/// - Throws: Any `Error` from applying the `policy`, or the result of `errorProducer` if validation fails.
|
|
|
524 |
@available(iOS, introduced: 10, deprecated: 12, renamed: "evaluate(afterApplying:)")
|
|
|
525 |
@available(macOS, introduced: 10.12, deprecated: 10.14, renamed: "evaluate(afterApplying:)")
|
|
|
526 |
@available(tvOS, introduced: 10, deprecated: 12, renamed: "evaluate(afterApplying:)")
|
|
|
527 |
@available(watchOS, introduced: 3, deprecated: 5, renamed: "evaluate(afterApplying:)")
|
|
|
528 |
public func validate(policy: SecPolicy, errorProducer: (_ status: OSStatus, _ result: SecTrustResultType) -> Error) throws {
|
|
|
529 |
try apply(policy: policy).af.validate(errorProducer: errorProducer)
|
|
|
530 |
}
|
|
|
531 |
|
|
|
532 |
/// Applies a `SecPolicy` to `self`, throwing if it fails.
|
|
|
533 |
///
|
|
|
534 |
/// - Parameter policy: The `SecPolicy`.
|
|
|
535 |
///
|
|
|
536 |
/// - Returns: `self`, with the policy applied.
|
|
|
537 |
/// - Throws: An `AFError.serverTrustEvaluationFailed` instance with a `.policyApplicationFailed` reason.
|
|
|
538 |
public func apply(policy: SecPolicy) throws -> SecTrust {
|
|
|
539 |
let status = SecTrustSetPolicies(type, policy)
|
|
|
540 |
|
|
|
541 |
guard status.af.isSuccess else {
|
|
|
542 |
throw AFError.serverTrustEvaluationFailed(reason: .policyApplicationFailed(trust: type,
|
|
|
543 |
policy: policy,
|
|
|
544 |
status: status))
|
|
|
545 |
}
|
|
|
546 |
|
|
|
547 |
return type
|
|
|
548 |
}
|
|
|
549 |
|
|
|
550 |
/// Evaluate `self`, throwing an `Error` if evaluation fails.
|
|
|
551 |
///
|
|
|
552 |
/// - Throws: `AFError.serverTrustEvaluationFailed` with reason `.trustValidationFailed` and associated error from
|
|
|
553 |
/// the underlying evaluation.
|
|
|
554 |
@available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *)
|
|
|
555 |
public func evaluate() throws {
|
|
|
556 |
var error: CFError?
|
|
|
557 |
let evaluationSucceeded = SecTrustEvaluateWithError(type, &error)
|
|
|
558 |
|
|
|
559 |
if !evaluationSucceeded {
|
|
|
560 |
throw AFError.serverTrustEvaluationFailed(reason: .trustEvaluationFailed(error: error))
|
|
|
561 |
}
|
|
|
562 |
}
|
|
|
563 |
|
|
|
564 |
/// Validate `self`, passing any failure values through `errorProducer`.
|
|
|
565 |
///
|
|
|
566 |
/// - Parameter errorProducer: The closure used to transform the failed `OSStatus` and `SecTrustResultType` into an
|
|
|
567 |
/// `Error`.
|
|
|
568 |
/// - Throws: The `Error` produced by the `errorProducer` closure.
|
|
|
569 |
@available(iOS, introduced: 10, deprecated: 12, renamed: "evaluate()")
|
|
|
570 |
@available(macOS, introduced: 10.12, deprecated: 10.14, renamed: "evaluate()")
|
|
|
571 |
@available(tvOS, introduced: 10, deprecated: 12, renamed: "evaluate()")
|
|
|
572 |
@available(watchOS, introduced: 3, deprecated: 5, renamed: "evaluate()")
|
|
|
573 |
public func validate(errorProducer: (_ status: OSStatus, _ result: SecTrustResultType) -> Error) throws {
|
|
|
574 |
var result = SecTrustResultType.invalid
|
|
|
575 |
let status = SecTrustEvaluate(type, &result)
|
|
|
576 |
|
|
|
577 |
guard status.af.isSuccess && result.af.isSuccess else {
|
|
|
578 |
throw errorProducer(status, result)
|
|
|
579 |
}
|
|
|
580 |
}
|
|
|
581 |
|
|
|
582 |
/// Sets a custom certificate chain on `self`, allowing full validation of a self-signed certificate and its chain.
|
|
|
583 |
///
|
|
|
584 |
/// - Parameter certificates: The `SecCertificate`s to add to the chain.
|
|
|
585 |
/// - Throws: Any error produced when applying the new certificate chain.
|
|
|
586 |
public func setAnchorCertificates(_ certificates: [SecCertificate]) throws {
|
|
|
587 |
// Add additional anchor certificates.
|
|
|
588 |
let status = SecTrustSetAnchorCertificates(type, certificates as CFArray)
|
|
|
589 |
guard status.af.isSuccess else {
|
|
|
590 |
throw AFError.serverTrustEvaluationFailed(reason: .settingAnchorCertificatesFailed(status: status,
|
|
|
591 |
certificates: certificates))
|
|
|
592 |
}
|
|
|
593 |
|
|
|
594 |
// Trust only the set anchor certs.
|
|
|
595 |
let onlyStatus = SecTrustSetAnchorCertificatesOnly(type, true)
|
|
|
596 |
guard onlyStatus.af.isSuccess else {
|
|
|
597 |
throw AFError.serverTrustEvaluationFailed(reason: .settingAnchorCertificatesFailed(status: onlyStatus,
|
|
|
598 |
certificates: certificates))
|
|
|
599 |
}
|
|
|
600 |
}
|
|
|
601 |
|
|
|
602 |
/// The public keys contained in `self`.
|
|
|
603 |
public var publicKeys: [SecKey] {
|
|
|
604 |
certificates.af.publicKeys
|
|
|
605 |
}
|
|
|
606 |
|
|
|
607 |
#if swift(>=5.5) // Xcode 13 / 2021 SDKs.
|
|
|
608 |
/// The `SecCertificate`s contained in `self`.
|
|
|
609 |
public var certificates: [SecCertificate] {
|
|
|
610 |
if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) {
|
|
|
611 |
return (SecTrustCopyCertificateChain(type) as? [SecCertificate]) ?? []
|
|
|
612 |
} else {
|
|
|
613 |
return (0..<SecTrustGetCertificateCount(type)).compactMap { index in
|
|
|
614 |
SecTrustGetCertificateAtIndex(type, index)
|
|
|
615 |
}
|
|
|
616 |
}
|
|
|
617 |
}
|
|
|
618 |
#else
|
|
|
619 |
/// The `SecCertificate`s contained in `self`.
|
|
|
620 |
public var certificates: [SecCertificate] {
|
|
|
621 |
(0..<SecTrustGetCertificateCount(type)).compactMap { index in
|
|
|
622 |
SecTrustGetCertificateAtIndex(type, index)
|
|
|
623 |
}
|
|
|
624 |
}
|
|
|
625 |
#endif
|
|
|
626 |
|
|
|
627 |
/// The `Data` values for all certificates contained in `self`.
|
|
|
628 |
public var certificateData: [Data] {
|
|
|
629 |
certificates.af.data
|
|
|
630 |
}
|
|
|
631 |
|
|
|
632 |
/// Validates `self` after applying `SecPolicy.af.default`. This evaluation does not validate the hostname.
|
|
|
633 |
///
|
|
|
634 |
/// - Parameter host: The hostname, used only in the error output if validation fails.
|
|
|
635 |
/// - Throws: An `AFError.serverTrustEvaluationFailed` instance with a `.defaultEvaluationFailed` reason.
|
|
|
636 |
public func performDefaultValidation(forHost host: String) throws {
|
|
|
637 |
if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) {
|
|
|
638 |
try evaluate(afterApplying: SecPolicy.af.default)
|
|
|
639 |
} else {
|
|
|
640 |
try validate(policy: SecPolicy.af.default) { status, result in
|
|
|
641 |
AFError.serverTrustEvaluationFailed(reason: .defaultEvaluationFailed(output: .init(host, type, status, result)))
|
|
|
642 |
}
|
|
|
643 |
}
|
|
|
644 |
}
|
|
|
645 |
|
|
|
646 |
/// Validates `self` after applying `SecPolicy.af.hostname(host)`, which performs the default validation as well as
|
|
|
647 |
/// hostname validation.
|
|
|
648 |
///
|
|
|
649 |
/// - Parameter host: The hostname to use in the validation.
|
|
|
650 |
/// - Throws: An `AFError.serverTrustEvaluationFailed` instance with a `.defaultEvaluationFailed` reason.
|
|
|
651 |
public func performValidation(forHost host: String) throws {
|
|
|
652 |
if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) {
|
|
|
653 |
try evaluate(afterApplying: SecPolicy.af.hostname(host))
|
|
|
654 |
} else {
|
|
|
655 |
try validate(policy: SecPolicy.af.hostname(host)) { status, result in
|
|
|
656 |
AFError.serverTrustEvaluationFailed(reason: .hostValidationFailed(output: .init(host, type, status, result)))
|
|
|
657 |
}
|
|
|
658 |
}
|
|
|
659 |
}
|
|
|
660 |
}
|
|
|
661 |
|
|
|
662 |
extension SecPolicy: AlamofireExtended {}
|
|
|
663 |
extension AlamofireExtension where ExtendedType == SecPolicy {
|
|
|
664 |
/// Creates a `SecPolicy` instance which will validate server certificates but not require a host name match.
|
|
|
665 |
public static let `default` = SecPolicyCreateSSL(true, nil)
|
|
|
666 |
|
|
|
667 |
/// Creates a `SecPolicy` instance which will validate server certificates and much match the provided hostname.
|
|
|
668 |
///
|
|
|
669 |
/// - Parameter hostname: The hostname to validate against.
|
|
|
670 |
///
|
|
|
671 |
/// - Returns: The `SecPolicy`.
|
|
|
672 |
public static func hostname(_ hostname: String) -> SecPolicy {
|
|
|
673 |
SecPolicyCreateSSL(true, hostname as CFString)
|
|
|
674 |
}
|
|
|
675 |
|
|
|
676 |
/// Creates a `SecPolicy` which checks the revocation of certificates.
|
|
|
677 |
///
|
|
|
678 |
/// - Parameter options: The `RevocationTrustEvaluator.Options` for evaluation.
|
|
|
679 |
///
|
|
|
680 |
/// - Returns: The `SecPolicy`.
|
|
|
681 |
/// - Throws: An `AFError.serverTrustEvaluationFailed` error with reason `.revocationPolicyCreationFailed`
|
|
|
682 |
/// if the policy cannot be created.
|
|
|
683 |
public static func revocation(options: RevocationTrustEvaluator.Options) throws -> SecPolicy {
|
|
|
684 |
guard let policy = SecPolicyCreateRevocation(options.rawValue) else {
|
|
|
685 |
throw AFError.serverTrustEvaluationFailed(reason: .revocationPolicyCreationFailed)
|
|
|
686 |
}
|
|
|
687 |
|
|
|
688 |
return policy
|
|
|
689 |
}
|
|
|
690 |
}
|
|
|
691 |
|
|
|
692 |
extension Array: AlamofireExtended {}
|
|
|
693 |
extension AlamofireExtension where ExtendedType == [SecCertificate] {
|
|
|
694 |
/// All `Data` values for the contained `SecCertificate`s.
|
|
|
695 |
public var data: [Data] {
|
|
|
696 |
type.map { SecCertificateCopyData($0) as Data }
|
|
|
697 |
}
|
|
|
698 |
|
|
|
699 |
/// All public `SecKey` values for the contained `SecCertificate`s.
|
|
|
700 |
public var publicKeys: [SecKey] {
|
|
|
701 |
type.compactMap(\.af.publicKey)
|
|
|
702 |
}
|
|
|
703 |
}
|
|
|
704 |
|
|
|
705 |
extension SecCertificate: AlamofireExtended {}
|
|
|
706 |
extension AlamofireExtension where ExtendedType == SecCertificate {
|
|
|
707 |
/// The public key for `self`, if it can be extracted.
|
|
|
708 |
///
|
|
|
709 |
/// - Note: On 2020 OSes and newer, only RSA and ECDSA keys are supported.
|
|
|
710 |
///
|
|
|
711 |
public var publicKey: SecKey? {
|
|
|
712 |
let policy = SecPolicyCreateBasicX509()
|
|
|
713 |
var trust: SecTrust?
|
|
|
714 |
let trustCreationStatus = SecTrustCreateWithCertificates(type, policy, &trust)
|
|
|
715 |
|
|
|
716 |
guard let createdTrust = trust, trustCreationStatus == errSecSuccess else { return nil }
|
|
|
717 |
|
|
|
718 |
if #available(iOS 14, macOS 11, tvOS 14, watchOS 7, *) {
|
|
|
719 |
return SecTrustCopyKey(createdTrust)
|
|
|
720 |
} else {
|
|
|
721 |
return SecTrustCopyPublicKey(createdTrust)
|
|
|
722 |
}
|
|
|
723 |
}
|
|
|
724 |
}
|
|
|
725 |
|
|
|
726 |
extension OSStatus: AlamofireExtended {}
|
|
|
727 |
extension AlamofireExtension where ExtendedType == OSStatus {
|
|
|
728 |
/// Returns whether `self` is `errSecSuccess`.
|
|
|
729 |
public var isSuccess: Bool { type == errSecSuccess }
|
|
|
730 |
}
|
|
|
731 |
|
|
|
732 |
extension SecTrustResultType: AlamofireExtended {}
|
|
|
733 |
extension AlamofireExtension where ExtendedType == SecTrustResultType {
|
|
|
734 |
/// Returns whether `self is `.unspecified` or `.proceed`.
|
|
|
735 |
public var isSuccess: Bool {
|
|
|
736 |
type == .unspecified || type == .proceed
|
|
|
737 |
}
|
|
|
738 |
}
|
|
|
739 |
#endif
|