ServerTrustEvaluation.swift 19 KB

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