ServerTrustPolicy.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  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 `ServerTrustPolicy` objects to a given host.
  26. open class ServerTrustPolicyManager {
  27. /// The dictionary of policies mapped to a particular host.
  28. open 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. /// - pinCertificates: Uses the pinned certificates to validate the server trust. The server trust is
  83. /// considered valid if one of the pinned certificates match one of the server certificates.
  84. /// By validating both the certificate chain and host, certificate pinning provides a very
  85. /// secure form of server trust validation mitigating most, if not all, MITM attacks.
  86. /// Applications are encouraged to always validate the host and require a valid certificate
  87. /// chain in production environments.
  88. ///
  89. /// - pinPublicKeys: Uses the pinned public keys to validate the server trust. The server trust is considered
  90. /// valid if one of the pinned public keys match one of the server certificate public keys.
  91. /// By validating both the certificate chain and host, public key 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. /// - disableEvaluation: Disables all evaluation which in turn will always consider any server trust as valid.
  97. ///
  98. /// - customEvaluation: Uses the associated closure to evaluate the validity of the server trust.
  99. public enum ServerTrustPolicy {
  100. case performDefaultEvaluation(validateHost: Bool)
  101. case pinCertificates(certificates: [SecCertificate], validateCertificateChain: Bool, validateHost: Bool)
  102. case pinPublicKeys(publicKeys: [SecKey], validateCertificateChain: Bool, validateHost: Bool)
  103. case disableEvaluation
  104. case customEvaluation((_ serverTrust: SecTrust, _ host: String) -> Bool)
  105. // MARK: - Bundle Location
  106. /// Returns all certificates within the given bundle with a `.cer` file extension.
  107. ///
  108. /// - parameter bundle: The bundle to search for all `.cer` files.
  109. ///
  110. /// - returns: All certificates within the given bundle.
  111. public static func certificates(in bundle: Bundle = Bundle.main) -> [SecCertificate] {
  112. var certificates: [SecCertificate] = []
  113. let paths = Set([".cer", ".CER", ".crt", ".CRT", ".der", ".DER"].map { fileExtension in
  114. bundle.paths(forResourcesOfType: fileExtension, inDirectory: nil)
  115. }.joined())
  116. for path in paths {
  117. if
  118. let certificateData = try? Data(contentsOf: URL(fileURLWithPath: path)) as CFData,
  119. let certificate = SecCertificateCreateWithData(nil, certificateData)
  120. {
  121. certificates.append(certificate)
  122. }
  123. }
  124. return certificates
  125. }
  126. /// Returns all public keys within the given bundle with a `.cer` file extension.
  127. ///
  128. /// - parameter bundle: The bundle to search for all `*.cer` files.
  129. ///
  130. /// - returns: All public keys within the given bundle.
  131. public static func publicKeys(in bundle: Bundle = Bundle.main) -> [SecKey] {
  132. var publicKeys: [SecKey] = []
  133. for certificate in certificates(in: bundle) {
  134. if let publicKey = publicKey(for: certificate) {
  135. publicKeys.append(publicKey)
  136. }
  137. }
  138. return publicKeys
  139. }
  140. // MARK: - Evaluation
  141. /// Evaluates whether the server trust is valid for the given host.
  142. ///
  143. /// - parameter serverTrust: The server trust to evaluate.
  144. /// - parameter host: The host of the challenge protection space.
  145. ///
  146. /// - returns: Whether the server trust is valid.
  147. public func evaluate(_ serverTrust: SecTrust, forHost host: String) -> Bool {
  148. var serverTrustIsValid = false
  149. switch self {
  150. case let .performDefaultEvaluation(validateHost):
  151. let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
  152. SecTrustSetPolicies(serverTrust, policy)
  153. serverTrustIsValid = trustIsValid(serverTrust)
  154. case let .pinCertificates(pinnedCertificates, validateCertificateChain, validateHost):
  155. if validateCertificateChain {
  156. let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
  157. SecTrustSetPolicies(serverTrust, policy)
  158. SecTrustSetAnchorCertificates(serverTrust, pinnedCertificates as CFArray)
  159. SecTrustSetAnchorCertificatesOnly(serverTrust, true)
  160. serverTrustIsValid = trustIsValid(serverTrust)
  161. } else {
  162. let serverCertificatesDataArray = certificateData(for: serverTrust)
  163. let pinnedCertificatesDataArray = certificateData(for: pinnedCertificates)
  164. outerLoop: for serverCertificateData in serverCertificatesDataArray {
  165. for pinnedCertificateData in pinnedCertificatesDataArray {
  166. if serverCertificateData == pinnedCertificateData {
  167. serverTrustIsValid = true
  168. break outerLoop
  169. }
  170. }
  171. }
  172. }
  173. case let .pinPublicKeys(pinnedPublicKeys, validateCertificateChain, validateHost):
  174. var certificateChainEvaluationPassed = true
  175. if validateCertificateChain {
  176. let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
  177. SecTrustSetPolicies(serverTrust, policy)
  178. certificateChainEvaluationPassed = trustIsValid(serverTrust)
  179. }
  180. if certificateChainEvaluationPassed {
  181. outerLoop: for serverPublicKey in ServerTrustPolicy.publicKeys(for: serverTrust) as [AnyObject] {
  182. for pinnedPublicKey in pinnedPublicKeys as [AnyObject] {
  183. if serverPublicKey.isEqual(pinnedPublicKey) {
  184. serverTrustIsValid = true
  185. break outerLoop
  186. }
  187. }
  188. }
  189. }
  190. case .disableEvaluation:
  191. serverTrustIsValid = true
  192. case let .customEvaluation(closure):
  193. serverTrustIsValid = closure(serverTrust, host)
  194. }
  195. return serverTrustIsValid
  196. }
  197. // MARK: - Private - Trust Validation
  198. private func trustIsValid(_ trust: SecTrust) -> Bool {
  199. var isValid = false
  200. var result = SecTrustResultType.invalid
  201. let status = SecTrustEvaluate(trust, &result)
  202. if status == errSecSuccess {
  203. let unspecified = SecTrustResultType.unspecified
  204. let proceed = SecTrustResultType.proceed
  205. isValid = result == unspecified || result == proceed
  206. }
  207. return isValid
  208. }
  209. // MARK: - Private - Certificate Data
  210. private func certificateData(for trust: SecTrust) -> [Data] {
  211. var certificates: [SecCertificate] = []
  212. for index in 0..<SecTrustGetCertificateCount(trust) {
  213. if let certificate = SecTrustGetCertificateAtIndex(trust, index) {
  214. certificates.append(certificate)
  215. }
  216. }
  217. return certificateData(for: certificates)
  218. }
  219. private func certificateData(for certificates: [SecCertificate]) -> [Data] {
  220. return certificates.map { SecCertificateCopyData($0) as Data }
  221. }
  222. // MARK: - Private - Public Key Extraction
  223. private static func publicKeys(for trust: SecTrust) -> [SecKey] {
  224. var publicKeys: [SecKey] = []
  225. for index in 0..<SecTrustGetCertificateCount(trust) {
  226. if
  227. let certificate = SecTrustGetCertificateAtIndex(trust, index),
  228. let publicKey = publicKey(for: certificate)
  229. {
  230. publicKeys.append(publicKey)
  231. }
  232. }
  233. return publicKeys
  234. }
  235. private static func publicKey(for certificate: SecCertificate) -> SecKey? {
  236. var publicKey: SecKey?
  237. let policy = SecPolicyCreateBasicX509()
  238. var trust: SecTrust?
  239. let trustCreationStatus = SecTrustCreateWithCertificates(certificate, policy, &trust)
  240. if let trust = trust, trustCreationStatus == errSecSuccess {
  241. publicKey = SecTrustCopyPublicKey(trust)
  242. }
  243. return publicKey
  244. }
  245. }