ServerTrustEvaluation.swift 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  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. import Foundation
  25. /// Responsible for managing the mapping of `ServerTrustEvaluating` values to given hosts.
  26. open class ServerTrustManager {
  27. /// Determines whether all hosts for this `ServerTrustManager` must be evaluated. Defaults to `true`.
  28. public let allHostsMustBeEvaluated: Bool
  29. /// The dictionary of policies mapped to a particular host.
  30. public let evaluators: [String: ServerTrustEvaluating]
  31. /// Initializes the `ServerTrustManager` instance with the given evaluators.
  32. ///
  33. /// Since different servers and web services can have different leaf certificates, intermediate and even root
  34. /// certficates, it is important to have the flexibility to specify evaluation policies on a per host basis. This
  35. /// allows for scenarios such as using default evaluation for host1, certificate pinning for host2, public key
  36. /// pinning for host3 and disabling evaluation for host4.
  37. ///
  38. /// - Parameters:
  39. /// - allHostsMustBeEvaluated: The value determining whether all hosts for this instance must be evaluated.
  40. /// Defaults to `true`.
  41. /// - evaluators: A dictionary of evaluators mappend to hosts.
  42. public init(allHostsMustBeEvaluated: Bool = true, evaluators: [String: ServerTrustEvaluating]) {
  43. self.allHostsMustBeEvaluated = allHostsMustBeEvaluated
  44. self.evaluators = evaluators
  45. }
  46. /// Returns the `ServerTrustEvaluating` value for the given host, if one is set.
  47. ///
  48. /// By default, this method will return the policy that perfectly matches the given host. Subclasses could override
  49. /// this method and implement more complex mapping implementations such as wildcards.
  50. ///
  51. /// - Parameter host: The host to use when searching for a matching policy.
  52. /// - Returns: The `ServerTrustEvaluating` value for the given host if found, `nil` otherwise.
  53. /// - Throws: `AFError.serverTrustEvaluationFailed` if `allHostsMustBeEvaluated` is `true` and no matching
  54. /// evaluators are found.
  55. open func serverTrustEvaluator(forHost host: String) throws -> ServerTrustEvaluating? {
  56. guard let evaluator = evaluators[host] else {
  57. if allHostsMustBeEvaluated {
  58. throw AFError.serverTrustEvaluationFailed(reason: .noRequiredEvaluator(host: host))
  59. }
  60. return nil
  61. }
  62. return evaluator
  63. }
  64. }
  65. /// A protocol describing the API used to evaluate server trusts.
  66. public protocol ServerTrustEvaluating {
  67. #if os(Linux)
  68. // Implement this once Linux has API for evaluating server trusts.
  69. #else
  70. /// Evaluates the given `SecTrust` value for the given `host`.
  71. ///
  72. /// - Parameters:
  73. /// - trust: The `SecTrust` value to evaluate.
  74. /// - host: The host for which to evaluate the `SecTrust` value.
  75. /// - Returns: A `Bool` indicating whether the evaluator considers the `SecTrust` value valid for `host`.
  76. func evaluate(_ trust: SecTrust, forHost host: String) throws
  77. #endif
  78. }
  79. extension Array where Element == ServerTrustEvaluating {
  80. #if os(Linux)
  81. // Add this same convenience method for Linux.
  82. #else
  83. /// Evaluates the given `SecTrust` value for the given `host`.
  84. ///
  85. /// - Parameters:
  86. /// - trust: The `SecTrust` value to evaluate.
  87. /// - host: The host for which to evaluate the `SecTrust` value.
  88. /// - Returns: Whether or not the evaluator considers the `SecTrust` value valid for `host`.
  89. func evaluate(_ trust: SecTrust, forHost host: String) throws {
  90. for evaluator in self {
  91. try evaluator.evaluate(trust, forHost: host)
  92. }
  93. }
  94. #endif
  95. }
  96. // MARK: - Server Trust Evaluators
  97. /// An evaluator which uses the default server trust evaluation while allowing you to control whether to validate the
  98. /// host provided by the challenge. Applications are encouraged to always validate the host in production environments
  99. /// to guarantee the validity of the server's certificate chain.
  100. public final class DefaultTrustEvaluator: ServerTrustEvaluating {
  101. private let validateHost: Bool
  102. /// Creates a `DefaultTrustEvalutor`.
  103. ///
  104. /// - Parameter validateHost: Determines whether or not the evaluator should validate the host. Defaults to `true`.
  105. public init(validateHost: Bool = true) {
  106. self.validateHost = validateHost
  107. }
  108. public func evaluate(_ trust: SecTrust, forHost host: String) throws {
  109. if validateHost {
  110. try trust.validateHost(host)
  111. }
  112. try trust.performDefaultEvaluation(forHost: host)
  113. }
  114. }
  115. /// An evaluator which Uses the default and revoked server trust evaluations allowing you to control whether to validate
  116. /// the host provided by the challenge as well as specify the revocation flags for testing for revoked certificates.
  117. /// Apple platforms did not start testing for revoked certificates automatically until iOS 10.1, macOS 10.12 and tvOS
  118. /// 10.1 which is demonstrated in our TLS tests. Applications are encouraged to always validate the host in production
  119. /// environments to guarantee the validity of the server's certificate chain.
  120. public final class RevocationTrustEvaluator: ServerTrustEvaluating {
  121. /// Represents the options to be use when evaluating the status of a certificate.
  122. /// 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).
  123. public struct Options: OptionSet {
  124. /// Perform revocation checking using the CRL (Certification Revocation List) method.
  125. public static let crl = Options(rawValue: kSecRevocationCRLMethod)
  126. /// Consult only locally cached replies; do not use network access.
  127. public static let networkAccessDisabled = Options(rawValue: kSecRevocationNetworkAccessDisabled)
  128. /// Perform revocation checking using OCSP (Online Certificate Status Protocol).
  129. public static let ocsp = Options(rawValue: kSecRevocationOCSPMethod)
  130. /// Prefer CRL revocation checking over OCSP; by default, OCSP is preferred.
  131. public static let preferCRL = Options(rawValue: kSecRevocationPreferCRL)
  132. /// Require a positive response to pass the policy. If the flag is not set, revocation checking is done on a
  133. /// "best attempt" basis, where failure to reach the server is not considered fatal.
  134. public static let requirePositiveResponse = Options(rawValue: kSecRevocationRequirePositiveResponse)
  135. /// Perform either OCSP or CRL checking. The checking is performed according to the method(s) specified in the
  136. /// certificate and the value of `preferCRL`.
  137. public static let any = Options(rawValue: kSecRevocationUseAnyAvailableMethod)
  138. /// The raw value of the option.
  139. public let rawValue: CFOptionFlags
  140. /// Creates an `Options` value with the given `CFOptionFlags`.
  141. ///
  142. /// - Parameter rawValue: The `CFOptionFlags` value to initialize with.
  143. public init(rawValue: CFOptionFlags) {
  144. self.rawValue = rawValue
  145. }
  146. }
  147. private let performDefaultValidation: Bool
  148. private let validateHost: Bool
  149. private let options: Options
  150. /// Creates a `RevocationTrustEvaluator`.
  151. ///
  152. /// - Note: Default and host validation will fail when using this evaluator with self-signed certificates. Use
  153. /// `PinnedCertificatesTrustEvaluator` if you need to use self-signed certificates.
  154. ///
  155. /// - Parameters:
  156. /// - performDefaultValidation: Determines whether default validation should be performed in addition to
  157. /// evaluating the pinned certificates. Defaults to `true`.
  158. /// - validateHost: Determines whether or not the evaluator should validate the host, in addition
  159. /// to performing the default evaluation, even if `performDefaultValidation` is
  160. /// `false`. Defaults to `true`.
  161. /// - options: The `Options` to use to check the revocation status of the certificate. Defaults to `.any`.
  162. public init(performDefaultValidation: Bool = true, validateHost: Bool = true, options: Options = .any) {
  163. self.performDefaultValidation = performDefaultValidation
  164. self.validateHost = validateHost
  165. self.options = options
  166. }
  167. public func evaluate(_ trust: SecTrust, forHost host: String) throws {
  168. if performDefaultValidation {
  169. try trust.performDefaultEvaluation(forHost: host)
  170. }
  171. if validateHost {
  172. try trust.validateHost(host)
  173. }
  174. try trust.validate(policy: .revocation(options: options)) { (status, result) in
  175. AFError.serverTrustEvaluationFailed(reason: .revocationCheckFailed(output: .init(host, trust, status, result), options: options))
  176. }
  177. }
  178. }
  179. /// Uses the pinned certificates to validate the server trust. The server trust is considered valid if one of the pinned
  180. /// certificates match one of the server certificates. By validating both the certificate chain and host, certificate
  181. /// pinning provides a very secure form of server trust validation mitigating most, if not all, MITM attacks.
  182. /// Applications are encouraged to always validate the host and require a valid certificate chain in production
  183. /// environments.
  184. public final class PinnedCertificatesTrustEvaluator: ServerTrustEvaluating {
  185. private let certificates: [SecCertificate]
  186. private let acceptSelfSignedCertificates: Bool
  187. private let performDefaultValidation: Bool
  188. private let validateHost: Bool
  189. /// Creates a `PinnedCertificatesTrustEvaluator`.
  190. ///
  191. /// - Parameters:
  192. /// - certificates: The certificates to use to evalute the trust. Defaults to all `cer`, `crt`,
  193. /// `der` certificates in `Bundle.main`.
  194. /// - acceptSelfSignedCertificates: Adds the provided certificates as anchors for the trust evaulation, allowing
  195. /// self-signed certificates to pass. Defaults to `false`. THIS SETTING SHOULD BE
  196. /// FALSE IN PRODUCTION!
  197. /// - performDefaultValidation: Determines whether default validation should be performed in addition to
  198. /// evaluating the pinned certificates. Defaults to `true`.
  199. /// - validateHost: Determines whether or not the evaluator should validate the host, in addition
  200. /// to performing the default evaluation, even if `performDefaultValidation` is
  201. /// `false`. Defaults to `true`.
  202. public init(certificates: [SecCertificate] = Bundle.main.certificates,
  203. acceptSelfSignedCertificates: Bool = false,
  204. performDefaultValidation: Bool = true,
  205. validateHost: Bool = true) {
  206. self.certificates = certificates
  207. self.acceptSelfSignedCertificates = acceptSelfSignedCertificates
  208. self.performDefaultValidation = performDefaultValidation
  209. self.validateHost = validateHost
  210. }
  211. public func evaluate(_ trust: SecTrust, forHost host: String) throws {
  212. guard !certificates.isEmpty else {
  213. throw AFError.serverTrustEvaluationFailed(reason: .noCertificatesFound)
  214. }
  215. if acceptSelfSignedCertificates {
  216. try trust.setAnchorCertificates(certificates)
  217. }
  218. if performDefaultValidation {
  219. try trust.performDefaultEvaluation(forHost: host)
  220. }
  221. if validateHost {
  222. try trust.validateHost(host)
  223. }
  224. let serverCertificatesData = Set(trust.certificateData)
  225. let pinnedCertificatesData = Set(certificates.data)
  226. let pinnedCertificatesInServerData = !serverCertificatesData.isDisjoint(with: pinnedCertificatesData)
  227. if !pinnedCertificatesInServerData {
  228. throw AFError.serverTrustEvaluationFailed(reason: .certificatePinningFailed(host: host,
  229. trust: trust,
  230. pinnedCertificates: certificates,
  231. serverCertificates: trust.certificates))
  232. }
  233. }
  234. }
  235. /// Uses the pinned public keys to validate the server trust. The server trust is considered valid if one of the pinned
  236. /// public keys match one of the server certificate public keys. By validating both the certificate chain and host,
  237. /// public key pinning provides a very secure form of server trust validation mitigating most, if not all, MITM attacks.
  238. /// Applications are encouraged to always validate the host and require a valid certificate chain in production
  239. /// environments.
  240. public final class PublicKeysTrustEvaluator: ServerTrustEvaluating {
  241. private let keys: [SecKey]
  242. private let performDefaultValidation: Bool
  243. private let validateHost: Bool
  244. /// Creates a `PublicKeysTrustEvaluator`.
  245. ///
  246. /// - Note: Default and host validation will fail when using this evaluator with self-signed certificates. Use
  247. /// `PinnedCertificatesTrustEvaluator` if you need to use self-signed certificates.
  248. ///
  249. /// - Parameters:
  250. /// - keys: The `SecKey`s to use to validate public keys. Defaults to the public keys of all
  251. /// certificates included in the main bundle.
  252. /// - performDefaultValidation: Determines whether default validation should be performed in addition to
  253. /// evaluating the pinned certificates. Defaults to `true`.
  254. /// - validateHost: Determines whether or not the evaluator should validate the host, in addition to
  255. /// performing the default evaluation, even if `performDefaultValidation` is `false`.
  256. /// Defaults to `true`.
  257. public init(keys: [SecKey] = Bundle.main.publicKeys,
  258. performDefaultValidation: Bool = true,
  259. validateHost: Bool = true) {
  260. self.keys = keys
  261. self.performDefaultValidation = performDefaultValidation
  262. self.validateHost = validateHost
  263. }
  264. public func evaluate(_ trust: SecTrust, forHost host: String) throws {
  265. guard !keys.isEmpty else {
  266. throw AFError.serverTrustEvaluationFailed(reason: .noPublicKeysFound)
  267. }
  268. if performDefaultValidation {
  269. try trust.performDefaultEvaluation(forHost: host)
  270. }
  271. if validateHost {
  272. try trust.validateHost(host)
  273. }
  274. let pinnedKeysInServerKeys: Bool = {
  275. for serverPublicKey in trust.publicKeys as [AnyHashable] {
  276. for pinnedPublicKey in keys as [AnyHashable] {
  277. if serverPublicKey == pinnedPublicKey {
  278. return true
  279. }
  280. }
  281. }
  282. return false
  283. }()
  284. if !pinnedKeysInServerKeys {
  285. throw AFError.serverTrustEvaluationFailed(reason: .publicKeyPinningFailed(host: host,
  286. trust: trust,
  287. pinnedKeys: keys,
  288. serverKeys: trust.publicKeys))
  289. }
  290. }
  291. }
  292. /// Uses the provided evaluators to validate the server trust. The trust is only considered valid if all of the
  293. /// evaluators consider it valid.
  294. public final class CompositeTrustEvaluator: ServerTrustEvaluating {
  295. private let evaluators: [ServerTrustEvaluating]
  296. /// Creates a `CompositeTrustEvaluator`.
  297. ///
  298. /// - Parameter evaluators: The `ServerTrustEvaluating` values used to evaluate the server trust.
  299. public init(evaluators: [ServerTrustEvaluating]) {
  300. self.evaluators = evaluators
  301. }
  302. public func evaluate(_ trust: SecTrust, forHost host: String) throws {
  303. try evaluators.evaluate(trust, forHost: host)
  304. }
  305. }
  306. /// Disables all evaluation which in turn will always consider any server trust as valid.
  307. ///
  308. /// THIS EVALUATOR SHOULD NEVER BE USED IN PRODUCTION!
  309. public final class DisabledEvaluator: ServerTrustEvaluating {
  310. public init() { }
  311. public func evaluate(_ trust: SecTrust, forHost host: String) throws { }
  312. }
  313. extension Bundle {
  314. /// Returns all valid `cer`, `crt`, and `der` certificates in the bundle.
  315. public var certificates: [SecCertificate] {
  316. return paths(forResourcesOfTypes: [".cer", ".CER", ".crt", ".CRT", ".der", ".DER"]).compactMap { path in
  317. guard
  318. let certificateData = try? Data(contentsOf: URL(fileURLWithPath: path)) as CFData,
  319. let certificate = SecCertificateCreateWithData(nil, certificateData) else { return nil }
  320. return certificate
  321. }
  322. }
  323. /// Returns all public keys for the valid certificates in the bundle.
  324. public var publicKeys: [SecKey] {
  325. return certificates.publicKeys
  326. }
  327. /// Returns all pathnames for the resources identified by the provided file extensions.
  328. ///
  329. /// - Parameter types: The filename extensions locate.
  330. /// - Returns: All pathnames for the given filename extensions.
  331. func paths(forResourcesOfTypes types: [String]) -> [String] {
  332. return Array(Set(types.flatMap { paths(forResourcesOfType: $0, inDirectory: nil) }))
  333. }
  334. }
  335. public extension SecTrust {
  336. func validate(policy: SecPolicy, errorProducer: (_ status: OSStatus, _ result: SecTrustResultType) -> Error) throws {
  337. try apply(policy: policy).validate(errorProducer: errorProducer)
  338. }
  339. func validate(errorProducer: (_ status: OSStatus, _ result: SecTrustResultType) -> Error) throws {
  340. var result = SecTrustResultType.invalid
  341. let status = SecTrustEvaluate(self, &result)
  342. guard status.isSuccess && result.isSuccess else {
  343. throw errorProducer(status, result)
  344. }
  345. }
  346. func apply(policy: SecPolicy) throws -> SecTrust {
  347. let status = SecTrustSetPolicies(self, policy)
  348. guard status.isSuccess else {
  349. throw AFError.serverTrustEvaluationFailed(reason: .policyApplicationFailed(trust: self,
  350. policy: policy,
  351. status: status))
  352. }
  353. return self
  354. }
  355. func setAnchorCertificates(_ certificates: [SecCertificate]) throws {
  356. // Add additional anchor certificates.
  357. let status = SecTrustSetAnchorCertificates(self, certificates as CFArray)
  358. guard status.isSuccess else {
  359. throw AFError.serverTrustEvaluationFailed(reason: .settingAnchorCertificatesFailed(status: status,
  360. certificates: certificates))
  361. }
  362. // Reenable system anchor certificates.
  363. let systemStatus = SecTrustSetAnchorCertificatesOnly(self, true)
  364. guard systemStatus.isSuccess else {
  365. throw AFError.serverTrustEvaluationFailed(reason: .settingAnchorCertificatesFailed(status: systemStatus,
  366. certificates: certificates))
  367. }
  368. }
  369. /// The public keys contained in `self`.
  370. var publicKeys: [SecKey] {
  371. return certificates.publicKeys
  372. }
  373. /// The `Data` values for all certificates contained in `self`.
  374. var certificateData: [Data] {
  375. return certificates.data
  376. }
  377. var certificates: [SecCertificate] {
  378. return (0..<SecTrustGetCertificateCount(self)).compactMap { index in
  379. SecTrustGetCertificateAtIndex(self, index)
  380. }
  381. }
  382. func performDefaultEvaluation(forHost host: String) throws {
  383. try validate(policy: .default) { (status, result) in
  384. AFError.serverTrustEvaluationFailed(reason: .defaultEvaluationFailed(output: .init(host, self, status, result)))
  385. }
  386. }
  387. func validateHost(_ host: String) throws {
  388. try validate(policy: .hostname(host)) { (status, result) in
  389. AFError.serverTrustEvaluationFailed(reason: .hostValidationFailed(output: .init(host, self, status, result)))
  390. }
  391. }
  392. }
  393. extension SecPolicy {
  394. static let `default` = SecPolicyCreateSSL(true, nil)
  395. static func hostname(_ hostname: String) -> SecPolicy {
  396. return SecPolicyCreateSSL(true, hostname as CFString)
  397. }
  398. static func revocation(options: RevocationTrustEvaluator.Options) throws -> SecPolicy {
  399. guard let policy = SecPolicyCreateRevocation(options.rawValue) else {
  400. throw AFError.serverTrustEvaluationFailed(reason: .revocationPolicyCreationFailed)
  401. }
  402. return policy
  403. }
  404. }
  405. extension Array where Element == SecCertificate {
  406. /// All `Data` values for the contained `SecCertificate`s.
  407. var data: [Data] {
  408. return map { SecCertificateCopyData($0) as Data }
  409. }
  410. /// All public `SecKey` values for the contained `SecCertificate`s.
  411. public var publicKeys: [SecKey] {
  412. return compactMap { $0.publicKey }
  413. }
  414. }
  415. extension SecCertificate {
  416. /// The public key for `self`, if it can be extracted.
  417. var publicKey: SecKey? {
  418. let policy = SecPolicyCreateBasicX509()
  419. var trust: SecTrust?
  420. let trustCreationStatus = SecTrustCreateWithCertificates(self, policy, &trust)
  421. guard let createdTrust = trust, trustCreationStatus == errSecSuccess else { return nil }
  422. return SecTrustCopyPublicKey(createdTrust)
  423. }
  424. }
  425. extension OSStatus {
  426. var isSuccess: Bool { return self == errSecSuccess }
  427. }
  428. extension SecTrustResultType {
  429. var isSuccess: Bool {
  430. return (self == .unspecified || self == .proceed)
  431. }
  432. }