You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

310 lines
14 KiB

6 years ago
5 years ago
6 years ago
5 years ago
6 years ago
5 years ago
6 years ago
5 years ago
6 years ago
5 years ago
6 years ago
  1. //
  2. // ServerTrustPolicy.swift
  3. //
  4. // Copyright (c) 2014 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 `ServerTrustPolicy` objects to a given host.
  26. open class ServerTrustPolicyManager {
  27. /// The dictionary of policies mapped to a particular host.
  28. public let policies: [String: ServerTrustPolicy]
  29. /// Initializes the `ServerTrustPolicyManager` instance with the given policies.
  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 policies: A dictionary of all policies mapped to a particular host.
  37. ///
  38. /// - returns: The new `ServerTrustPolicyManager` instance.
  39. public init(policies: [String: ServerTrustPolicy]) {
  40. self.policies = policies
  41. }
  42. /// Returns the `ServerTrustPolicy` for the given host if applicable.
  43. ///
  44. /// By default, this method will return the policy that perfectly matches the given host. Subclasses could override
  45. /// this method and implement more complex mapping implementations such as wildcards.
  46. ///
  47. /// - parameter host: The host to use when searching for a matching policy.
  48. ///
  49. /// - returns: The server trust policy for the given host if found.
  50. open func serverTrustPolicy(forHost host: String) -> ServerTrustPolicy? {
  51. return policies[host]
  52. }
  53. }
  54. // MARK: -
  55. extension URLSession {
  56. private struct AssociatedKeys {
  57. static var managerKey = "URLSession.ServerTrustPolicyManager"
  58. }
  59. var serverTrustPolicyManager: ServerTrustPolicyManager? {
  60. get {
  61. return objc_getAssociatedObject(self, &AssociatedKeys.managerKey) as? ServerTrustPolicyManager
  62. }
  63. set (manager) {
  64. objc_setAssociatedObject(self, &AssociatedKeys.managerKey, manager, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  65. }
  66. }
  67. }
  68. // MARK: - ServerTrustPolicy
  69. /// The `ServerTrustPolicy` evaluates the server trust generally provided by an `NSURLAuthenticationChallenge` when
  70. /// connecting to a server over a secure HTTPS connection. The policy configuration then evaluates the server trust
  71. /// with a given set of criteria to determine whether the server trust is valid and the connection should be made.
  72. ///
  73. /// Using pinned certificates or public keys for evaluation helps prevent man-in-the-middle (MITM) attacks and other
  74. /// vulnerabilities. Applications dealing with sensitive customer data or financial information are strongly encouraged
  75. /// to route all communication over an HTTPS connection with pinning enabled.
  76. ///
  77. /// - performDefaultEvaluation: Uses the default server trust evaluation while allowing you to control whether to
  78. /// validate the host provided by the challenge. Applications are encouraged to always
  79. /// validate the host in production environments to guarantee the validity of the server's
  80. /// certificate chain.
  81. ///
  82. /// - performRevokedEvaluation: Uses the default and revoked server trust evaluations allowing you to control whether to
  83. /// validate the host provided by the challenge as well as specify the revocation flags for
  84. /// testing for revoked certificates. Apple platforms did not start testing for revoked
  85. /// certificates automatically until iOS 10.1, macOS 10.12 and tvOS 10.1 which is
  86. /// demonstrated in our TLS tests. Applications are encouraged to always validate the host
  87. /// in production environments to guarantee the validity of the server's certificate chain.
  88. ///
  89. /// - pinCertificates: Uses the pinned certificates to validate the server trust. The server trust is
  90. /// considered valid if one of the pinned certificates match one of the server certificates.
  91. /// By validating both the certificate chain and host, certificate pinning provides a very
  92. /// secure form of server trust validation mitigating most, if not all, MITM attacks.
  93. /// Applications are encouraged to always validate the host and require a valid certificate
  94. /// chain in production environments.
  95. ///
  96. /// - pinPublicKeys: Uses the pinned public keys to validate the server trust. The server trust is considered
  97. /// valid if one of the pinned public keys match one of the server certificate public keys.
  98. /// By validating both the certificate chain and host, public key pinning provides a very
  99. /// secure form of server trust validation mitigating most, if not all, MITM attacks.
  100. /// Applications are encouraged to always validate the host and require a valid certificate
  101. /// chain in production environments.
  102. ///
  103. /// - disableEvaluation: Disables all evaluation which in turn will always consider any server trust as valid.
  104. ///
  105. /// - customEvaluation: Uses the associated closure to evaluate the validity of the server trust.
  106. public enum ServerTrustPolicy {
  107. case performDefaultEvaluation(validateHost: Bool)
  108. case performRevokedEvaluation(validateHost: Bool, revocationFlags: CFOptionFlags)
  109. case pinCertificates(certificates: [SecCertificate], validateCertificateChain: Bool, validateHost: Bool)
  110. case pinPublicKeys(publicKeys: [SecKey], validateCertificateChain: Bool, validateHost: Bool)
  111. case disableEvaluation
  112. case customEvaluation((_ serverTrust: SecTrust, _ host: String) -> Bool)
  113. // MARK: - Bundle Location
  114. /// Returns all certificates within the given bundle with a `.cer` file extension.
  115. ///
  116. /// - parameter bundle: The bundle to search for all `.cer` files.
  117. ///
  118. /// - returns: All certificates within the given bundle.
  119. public static func certificates(in bundle: Bundle = Bundle.main) -> [SecCertificate] {
  120. var certificates: [SecCertificate] = []
  121. let paths = Set([".cer", ".CER", ".crt", ".CRT", ".der", ".DER"].map { fileExtension in
  122. bundle.paths(forResourcesOfType: fileExtension, inDirectory: nil)
  123. }.joined())
  124. for path in paths {
  125. if
  126. let certificateData = try? Data(contentsOf: URL(fileURLWithPath: path)) as CFData,
  127. let certificate = SecCertificateCreateWithData(nil, certificateData)
  128. {
  129. certificates.append(certificate)
  130. }
  131. }
  132. return certificates
  133. }
  134. /// Returns all public keys within the given bundle with a `.cer` file extension.
  135. ///
  136. /// - parameter bundle: The bundle to search for all `*.cer` files.
  137. ///
  138. /// - returns: All public keys within the given bundle.
  139. public static func publicKeys(in bundle: Bundle = Bundle.main) -> [SecKey] {
  140. var publicKeys: [SecKey] = []
  141. for certificate in certificates(in: bundle) {
  142. if let publicKey = publicKey(for: certificate) {
  143. publicKeys.append(publicKey)
  144. }
  145. }
  146. return publicKeys
  147. }
  148. // MARK: - Evaluation
  149. /// Evaluates whether the server trust is valid for the given host.
  150. ///
  151. /// - parameter serverTrust: The server trust to evaluate.
  152. /// - parameter host: The host of the challenge protection space.
  153. ///
  154. /// - returns: Whether the server trust is valid.
  155. public func evaluate(_ serverTrust: SecTrust, forHost host: String) -> Bool {
  156. var serverTrustIsValid = false
  157. switch self {
  158. case let .performDefaultEvaluation(validateHost):
  159. let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
  160. SecTrustSetPolicies(serverTrust, policy)
  161. serverTrustIsValid = trustIsValid(serverTrust)
  162. case let .performRevokedEvaluation(validateHost, revocationFlags):
  163. let defaultPolicy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
  164. let revokedPolicy = SecPolicyCreateRevocation(revocationFlags)
  165. SecTrustSetPolicies(serverTrust, [defaultPolicy, revokedPolicy] as CFTypeRef)
  166. serverTrustIsValid = trustIsValid(serverTrust)
  167. case let .pinCertificates(pinnedCertificates, validateCertificateChain, validateHost):
  168. if validateCertificateChain {
  169. let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
  170. SecTrustSetPolicies(serverTrust, policy)
  171. SecTrustSetAnchorCertificates(serverTrust, pinnedCertificates as CFArray)
  172. SecTrustSetAnchorCertificatesOnly(serverTrust, true)
  173. serverTrustIsValid = trustIsValid(serverTrust)
  174. } else {
  175. let serverCertificatesDataArray = certificateData(for: serverTrust)
  176. let pinnedCertificatesDataArray = certificateData(for: pinnedCertificates)
  177. outerLoop: for serverCertificateData in serverCertificatesDataArray {
  178. for pinnedCertificateData in pinnedCertificatesDataArray {
  179. if serverCertificateData == pinnedCertificateData {
  180. serverTrustIsValid = true
  181. break outerLoop
  182. }
  183. }
  184. }
  185. }
  186. case let .pinPublicKeys(pinnedPublicKeys, validateCertificateChain, validateHost):
  187. var certificateChainEvaluationPassed = true
  188. if validateCertificateChain {
  189. let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
  190. SecTrustSetPolicies(serverTrust, policy)
  191. certificateChainEvaluationPassed = trustIsValid(serverTrust)
  192. }
  193. if certificateChainEvaluationPassed {
  194. outerLoop: for serverPublicKey in ServerTrustPolicy.publicKeys(for: serverTrust) as [AnyObject] {
  195. for pinnedPublicKey in pinnedPublicKeys as [AnyObject] {
  196. if serverPublicKey.isEqual(pinnedPublicKey) {
  197. serverTrustIsValid = true
  198. break outerLoop
  199. }
  200. }
  201. }
  202. }
  203. case .disableEvaluation:
  204. serverTrustIsValid = true
  205. case let .customEvaluation(closure):
  206. serverTrustIsValid = closure(serverTrust, host)
  207. }
  208. return serverTrustIsValid
  209. }
  210. // MARK: - Private - Trust Validation
  211. private func trustIsValid(_ trust: SecTrust) -> Bool {
  212. var isValid = false
  213. if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) {
  214. isValid = SecTrustEvaluateWithError(trust, nil)
  215. } else {
  216. var result = SecTrustResultType.invalid
  217. let status = SecTrustEvaluate(trust, &result)
  218. if status == errSecSuccess {
  219. let unspecified = SecTrustResultType.unspecified
  220. let proceed = SecTrustResultType.proceed
  221. isValid = result == unspecified || result == proceed
  222. }
  223. }
  224. return isValid
  225. }
  226. // MARK: - Private - Certificate Data
  227. private func certificateData(for trust: SecTrust) -> [Data] {
  228. var certificates: [SecCertificate] = []
  229. for index in 0..<SecTrustGetCertificateCount(trust) {
  230. if let certificate = SecTrustGetCertificateAtIndex(trust, index) {
  231. certificates.append(certificate)
  232. }
  233. }
  234. return certificateData(for: certificates)
  235. }
  236. private func certificateData(for certificates: [SecCertificate]) -> [Data] {
  237. return certificates.map { SecCertificateCopyData($0) as Data }
  238. }
  239. // MARK: - Private - Public Key Extraction
  240. private static func publicKeys(for trust: SecTrust) -> [SecKey] {
  241. var publicKeys: [SecKey] = []
  242. for index in 0..<SecTrustGetCertificateCount(trust) {
  243. if
  244. let certificate = SecTrustGetCertificateAtIndex(trust, index),
  245. let publicKey = publicKey(for: certificate)
  246. {
  247. publicKeys.append(publicKey)
  248. }
  249. }
  250. return publicKeys
  251. }
  252. private static func publicKey(for certificate: SecCertificate) -> SecKey? {
  253. var publicKey: SecKey?
  254. let policy = SecPolicyCreateBasicX509()
  255. var trust: SecTrust?
  256. let trustCreationStatus = SecTrustCreateWithCertificates(certificate, policy, &trust)
  257. if let trust = trust, trustCreationStatus == errSecSuccess {
  258. publicKey = SecTrustCopyPublicKey(trust)
  259. }
  260. return publicKey
  261. }
  262. }