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