ServerTrustEvaluation.swift 36 KB

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