123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728 |
- //
- // ServerTrustPolicy.swift
- //
- // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- // THE SOFTWARE.
- //
- import Foundation
- /// Responsible for managing the mapping of `ServerTrustEvaluating` values to given hosts.
- open class ServerTrustManager {
- /// Determines whether all hosts for this `ServerTrustManager` must be evaluated. `true` by default.
- public let allHostsMustBeEvaluated: Bool
- /// The dictionary of policies mapped to a particular host.
- public let evaluators: [String: ServerTrustEvaluating]
- /// Initializes the `ServerTrustManager` instance with the given evaluators.
- ///
- /// Since different servers and web services can have different leaf certificates, intermediate and even root
- /// certificates, it is important to have the flexibility to specify evaluation policies on a per host basis. This
- /// allows for scenarios such as using default evaluation for host1, certificate pinning for host2, public key
- /// pinning for host3 and disabling evaluation for host4.
- ///
- /// - Parameters:
- /// - allHostsMustBeEvaluated: The value determining whether all hosts for this instance must be evaluated. `true`
- /// by default.
- /// - evaluators: A dictionary of evaluators mapped to hosts.
- public init(allHostsMustBeEvaluated: Bool = true, evaluators: [String: ServerTrustEvaluating]) {
- self.allHostsMustBeEvaluated = allHostsMustBeEvaluated
- self.evaluators = evaluators
- }
- #if !(os(Linux) || os(Windows))
- /// Returns the `ServerTrustEvaluating` value for the given host, if one is set.
- ///
- /// By default, this method will return the policy that perfectly matches the given host. Subclasses could override
- /// this method and implement more complex mapping implementations such as wildcards.
- ///
- /// - Parameter host: The host to use when searching for a matching policy.
- ///
- /// - Returns: The `ServerTrustEvaluating` value for the given host if found, `nil` otherwise.
- /// - Throws: `AFError.serverTrustEvaluationFailed` if `allHostsMustBeEvaluated` is `true` and no matching
- /// evaluators are found.
- open func serverTrustEvaluator(forHost host: String) throws -> ServerTrustEvaluating? {
- guard let evaluator = evaluators[host] else {
- if allHostsMustBeEvaluated {
- throw AFError.serverTrustEvaluationFailed(reason: .noRequiredEvaluator(host: host))
- }
- return nil
- }
- return evaluator
- }
- #endif
- }
- /// A protocol describing the API used to evaluate server trusts.
- public protocol ServerTrustEvaluating {
- #if os(Linux) || os(Windows)
- // Implement this once Linux/Windows has API for evaluating server trusts.
- #else
- /// Evaluates the given `SecTrust` value for the given `host`.
- ///
- /// - Parameters:
- /// - trust: The `SecTrust` value to evaluate.
- /// - host: The host for which to evaluate the `SecTrust` value.
- ///
- /// - Returns: A `Bool` indicating whether the evaluator considers the `SecTrust` value valid for `host`.
- func evaluate(_ trust: SecTrust, forHost host: String) throws
- #endif
- }
- // MARK: - Server Trust Evaluators
- #if !(os(Linux) || os(Windows))
- /// An evaluator which uses the default server trust evaluation while allowing you to control whether to validate the
- /// host provided by the challenge. Applications are encouraged to always validate the host in production environments
- /// to guarantee the validity of the server's certificate chain.
- public final class DefaultTrustEvaluator: ServerTrustEvaluating {
- private let validateHost: Bool
- /// Creates a `DefaultTrustEvaluator`.
- ///
- /// - Parameter validateHost: Determines whether or not the evaluator should validate the host. `true` by default.
- public init(validateHost: Bool = true) {
- self.validateHost = validateHost
- }
- public func evaluate(_ trust: SecTrust, forHost host: String) throws {
- if validateHost {
- try trust.af.performValidation(forHost: host)
- }
- try trust.af.performDefaultValidation(forHost: host)
- }
- }
- /// An evaluator which Uses the default and revoked server trust evaluations allowing you to control whether to validate
- /// the host provided by the challenge as well as specify the revocation flags for testing for revoked certificates.
- /// Apple platforms did not start testing for revoked certificates automatically until iOS 10.1, macOS 10.12 and tvOS
- /// 10.1 which is demonstrated in our TLS tests. Applications are encouraged to always validate the host in production
- /// environments to guarantee the validity of the server's certificate chain.
- public final class RevocationTrustEvaluator: ServerTrustEvaluating {
- /// Represents the options to be use when evaluating the status of a certificate.
- /// 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).
- public struct Options: OptionSet {
- /// Perform revocation checking using the CRL (Certification Revocation List) method.
- public static let crl = Options(rawValue: kSecRevocationCRLMethod)
- /// Consult only locally cached replies; do not use network access.
- public static let networkAccessDisabled = Options(rawValue: kSecRevocationNetworkAccessDisabled)
- /// Perform revocation checking using OCSP (Online Certificate Status Protocol).
- public static let ocsp = Options(rawValue: kSecRevocationOCSPMethod)
- /// Prefer CRL revocation checking over OCSP; by default, OCSP is preferred.
- public static let preferCRL = Options(rawValue: kSecRevocationPreferCRL)
- /// Require a positive response to pass the policy. If the flag is not set, revocation checking is done on a
- /// "best attempt" basis, where failure to reach the server is not considered fatal.
- public static let requirePositiveResponse = Options(rawValue: kSecRevocationRequirePositiveResponse)
- /// Perform either OCSP or CRL checking. The checking is performed according to the method(s) specified in the
- /// certificate and the value of `preferCRL`.
- public static let any = Options(rawValue: kSecRevocationUseAnyAvailableMethod)
- /// The raw value of the option.
- public let rawValue: CFOptionFlags
- /// Creates an `Options` value with the given `CFOptionFlags`.
- ///
- /// - Parameter rawValue: The `CFOptionFlags` value to initialize with.
- public init(rawValue: CFOptionFlags) {
- self.rawValue = rawValue
- }
- }
- private let performDefaultValidation: Bool
- private let validateHost: Bool
- private let options: Options
- /// Creates a `RevocationTrustEvaluator` using the provided parameters.
- ///
- /// - Note: Default and host validation will fail when using this evaluator with self-signed certificates. Use
- /// `PinnedCertificatesTrustEvaluator` if you need to use self-signed certificates.
- ///
- /// - Parameters:
- /// - performDefaultValidation: Determines whether default validation should be performed in addition to
- /// evaluating the pinned certificates. `true` by default.
- /// - validateHost: Determines whether or not the evaluator should validate the host, in addition to
- /// performing the default evaluation, even if `performDefaultValidation` is `false`.
- /// `true` by default.
- /// - options: The `Options` to use to check the revocation status of the certificate. `.any` by
- /// default.
- public init(performDefaultValidation: Bool = true, validateHost: Bool = true, options: Options = .any) {
- self.performDefaultValidation = performDefaultValidation
- self.validateHost = validateHost
- self.options = options
- }
- public func evaluate(_ trust: SecTrust, forHost host: String) throws {
- if performDefaultValidation {
- try trust.af.performDefaultValidation(forHost: host)
- }
- if validateHost {
- try trust.af.performValidation(forHost: host)
- }
- if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) {
- try trust.af.evaluate(afterApplying: SecPolicy.af.revocation(options: options))
- } else {
- try trust.af.validate(policy: SecPolicy.af.revocation(options: options)) { status, result in
- AFError.serverTrustEvaluationFailed(reason: .revocationCheckFailed(output: .init(host, trust, status, result), options: options))
- }
- }
- }
- }
- extension ServerTrustEvaluating where Self == RevocationTrustEvaluator {
- /// Provides a default `RevocationTrustEvaluator` instance.
- public static var revocationChecking: RevocationTrustEvaluator { RevocationTrustEvaluator() }
- /// Creates a `RevocationTrustEvaluator` using the provided parameters.
- ///
- /// - Note: Default and host validation will fail when using this evaluator with self-signed certificates. Use
- /// `PinnedCertificatesTrustEvaluator` if you need to use self-signed certificates.
- ///
- /// - Parameters:
- /// - performDefaultValidation: Determines whether default validation should be performed in addition to
- /// evaluating the pinned certificates. `true` by default.
- /// - validateHost: Determines whether or not the evaluator should validate the host, in addition
- /// to performing the default evaluation, even if `performDefaultValidation` is
- /// `false`. `true` by default.
- /// - options: The `Options` to use to check the revocation status of the certificate. `.any`
- /// by default.
- /// - Returns: The `RevocationTrustEvaluator`.
- public static func revocationChecking(performDefaultValidation: Bool = true,
- validateHost: Bool = true,
- options: RevocationTrustEvaluator.Options = .any) -> RevocationTrustEvaluator {
- RevocationTrustEvaluator(performDefaultValidation: performDefaultValidation,
- validateHost: validateHost,
- options: options)
- }
- }
- /// Uses the pinned certificates to validate the server trust. The server trust is considered valid if one of the pinned
- /// certificates match one of the server certificates. By validating both the certificate chain and host, certificate
- /// pinning provides a very secure form of server trust validation mitigating most, if not all, MITM attacks.
- /// Applications are encouraged to always validate the host and require a valid certificate chain in production
- /// environments.
- public final class PinnedCertificatesTrustEvaluator: ServerTrustEvaluating {
- private let certificates: [SecCertificate]
- private let acceptSelfSignedCertificates: Bool
- private let performDefaultValidation: Bool
- private let validateHost: Bool
- /// Creates a `PinnedCertificatesTrustEvaluator` from the provided parameters.
- ///
- /// - Parameters:
- /// - certificates: The certificates to use to evaluate the trust. All `cer`, `crt`, and `der`
- /// certificates in `Bundle.main` by default.
- /// - acceptSelfSignedCertificates: Adds the provided certificates as anchors for the trust evaluation, allowing
- /// self-signed certificates to pass. `false` by default. THIS SETTING SHOULD BE
- /// FALSE IN PRODUCTION!
- /// - performDefaultValidation: Determines whether default validation should be performed in addition to
- /// evaluating the pinned certificates. `true` by default.
- /// - validateHost: Determines whether or not the evaluator should validate the host, in addition
- /// to performing the default evaluation, even if `performDefaultValidation` is
- /// `false`. `true` by default.
- public init(certificates: [SecCertificate] = Bundle.main.af.certificates,
- acceptSelfSignedCertificates: Bool = false,
- performDefaultValidation: Bool = true,
- validateHost: Bool = true) {
- self.certificates = certificates
- self.acceptSelfSignedCertificates = acceptSelfSignedCertificates
- self.performDefaultValidation = performDefaultValidation
- self.validateHost = validateHost
- }
- public func evaluate(_ trust: SecTrust, forHost host: String) throws {
- guard !certificates.isEmpty else {
- throw AFError.serverTrustEvaluationFailed(reason: .noCertificatesFound)
- }
- if acceptSelfSignedCertificates {
- try trust.af.setAnchorCertificates(certificates)
- }
- if performDefaultValidation {
- try trust.af.performDefaultValidation(forHost: host)
- }
- if validateHost {
- try trust.af.performValidation(forHost: host)
- }
- let serverCertificatesData = Set(trust.af.certificateData)
- let pinnedCertificatesData = Set(certificates.af.data)
- let pinnedCertificatesInServerData = !serverCertificatesData.isDisjoint(with: pinnedCertificatesData)
- if !pinnedCertificatesInServerData {
- throw AFError.serverTrustEvaluationFailed(reason: .certificatePinningFailed(host: host,
- trust: trust,
- pinnedCertificates: certificates,
- serverCertificates: trust.af.certificates))
- }
- }
- }
- extension ServerTrustEvaluating where Self == PinnedCertificatesTrustEvaluator {
- /// Provides a default `PinnedCertificatesTrustEvaluator` instance.
- public static var pinnedCertificates: PinnedCertificatesTrustEvaluator { PinnedCertificatesTrustEvaluator() }
- /// Creates a `PinnedCertificatesTrustEvaluator` using the provided parameters.
- ///
- /// - Parameters:
- /// - certificates: The certificates to use to evaluate the trust. All `cer`, `crt`, and `der`
- /// certificates in `Bundle.main` by default.
- /// - acceptSelfSignedCertificates: Adds the provided certificates as anchors for the trust evaluation, allowing
- /// self-signed certificates to pass. `false` by default. THIS SETTING SHOULD BE
- /// FALSE IN PRODUCTION!
- /// - performDefaultValidation: Determines whether default validation should be performed in addition to
- /// evaluating the pinned certificates. `true` by default.
- /// - validateHost: Determines whether or not the evaluator should validate the host, in addition
- /// to performing the default evaluation, even if `performDefaultValidation` is
- /// `false`. `true` by default.
- public static func pinnedCertificates(certificates: [SecCertificate] = Bundle.main.af.certificates,
- acceptSelfSignedCertificates: Bool = false,
- performDefaultValidation: Bool = true,
- validateHost: Bool = true) -> PinnedCertificatesTrustEvaluator {
- PinnedCertificatesTrustEvaluator(certificates: certificates,
- acceptSelfSignedCertificates: acceptSelfSignedCertificates,
- performDefaultValidation: performDefaultValidation,
- validateHost: validateHost)
- }
- }
- /// Uses the pinned public keys to validate the server trust. The server trust is considered valid if one of the pinned
- /// public keys match one of the server certificate public keys. By validating both the certificate chain and host,
- /// public key pinning provides a very secure form of server trust validation mitigating most, if not all, MITM attacks.
- /// Applications are encouraged to always validate the host and require a valid certificate chain in production
- /// environments.
- public final class PublicKeysTrustEvaluator: ServerTrustEvaluating {
- private let keys: [SecKey]
- private let performDefaultValidation: Bool
- private let validateHost: Bool
- /// Creates a `PublicKeysTrustEvaluator` from the provided parameters.
- ///
- /// - Note: Default and host validation will fail when using this evaluator with self-signed certificates. Use
- /// `PinnedCertificatesTrustEvaluator` if you need to use self-signed certificates.
- ///
- /// - Parameters:
- /// - keys: The `SecKey`s to use to validate public keys. Defaults to the public keys of all
- /// certificates included in the main bundle.
- /// - performDefaultValidation: Determines whether default validation should be performed in addition to
- /// evaluating the pinned certificates. `true` by default.
- /// - validateHost: Determines whether or not the evaluator should validate the host, in addition to
- /// performing the default evaluation, even if `performDefaultValidation` is `false`.
- /// `true` by default.
- public init(keys: [SecKey] = Bundle.main.af.publicKeys,
- performDefaultValidation: Bool = true,
- validateHost: Bool = true) {
- self.keys = keys
- self.performDefaultValidation = performDefaultValidation
- self.validateHost = validateHost
- }
- public func evaluate(_ trust: SecTrust, forHost host: String) throws {
- guard !keys.isEmpty else {
- throw AFError.serverTrustEvaluationFailed(reason: .noPublicKeysFound)
- }
- if performDefaultValidation {
- try trust.af.performDefaultValidation(forHost: host)
- }
- if validateHost {
- try trust.af.performValidation(forHost: host)
- }
- let pinnedKeysInServerKeys: Bool = {
- for serverPublicKey in trust.af.publicKeys {
- for pinnedPublicKey in keys {
- if serverPublicKey == pinnedPublicKey {
- return true
- }
- }
- }
- return false
- }()
- if !pinnedKeysInServerKeys {
- throw AFError.serverTrustEvaluationFailed(reason: .publicKeyPinningFailed(host: host,
- trust: trust,
- pinnedKeys: keys,
- serverKeys: trust.af.publicKeys))
- }
- }
- }
- extension ServerTrustEvaluating where Self == PublicKeysTrustEvaluator {
- /// Provides a default `PublicKeysTrustEvaluator` instance.
- public static var publicKeys: PublicKeysTrustEvaluator { PublicKeysTrustEvaluator() }
- /// Creates a `PublicKeysTrustEvaluator` from the provided parameters.
- ///
- /// - Note: Default and host validation will fail when using this evaluator with self-signed certificates. Use
- /// `PinnedCertificatesTrustEvaluator` if you need to use self-signed certificates.
- ///
- /// - Parameters:
- /// - keys: The `SecKey`s to use to validate public keys. Defaults to the public keys of all
- /// certificates included in the main bundle.
- /// - performDefaultValidation: Determines whether default validation should be performed in addition to
- /// evaluating the pinned certificates. `true` by default.
- /// - validateHost: Determines whether or not the evaluator should validate the host, in addition to
- /// performing the default evaluation, even if `performDefaultValidation` is `false`.
- /// `true` by default.
- public static func publicKeys(keys: [SecKey] = Bundle.main.af.publicKeys,
- performDefaultValidation: Bool = true,
- validateHost: Bool = true) -> PublicKeysTrustEvaluator {
- PublicKeysTrustEvaluator(keys: keys, performDefaultValidation: performDefaultValidation, validateHost: validateHost)
- }
- }
- /// Uses the provided evaluators to validate the server trust. The trust is only considered valid if all of the
- /// evaluators consider it valid.
- public final class CompositeTrustEvaluator: ServerTrustEvaluating {
- private let evaluators: [ServerTrustEvaluating]
- /// Creates a `CompositeTrustEvaluator` from the provided evaluators.
- ///
- /// - Parameter evaluators: The `ServerTrustEvaluating` values used to evaluate the server trust.
- public init(evaluators: [ServerTrustEvaluating]) {
- self.evaluators = evaluators
- }
- public func evaluate(_ trust: SecTrust, forHost host: String) throws {
- try evaluators.evaluate(trust, forHost: host)
- }
- }
- extension ServerTrustEvaluating where Self == CompositeTrustEvaluator {
- /// Creates a `CompositeTrustEvaluator` from the provided evaluators.
- ///
- /// - Parameter evaluators: The `ServerTrustEvaluating` values used to evaluate the server trust.
- public static func composite(evaluators: [ServerTrustEvaluating]) -> CompositeTrustEvaluator {
- CompositeTrustEvaluator(evaluators: evaluators)
- }
- }
- /// Disables all evaluation which in turn will always consider any server trust as valid.
- ///
- /// - Note: Instead of disabling server trust evaluation, it's a better idea to configure systems to properly trust test
- /// certificates, as outlined in [this Apple tech note](https://developer.apple.com/library/archive/qa/qa1948/_index.html).
- ///
- /// **THIS EVALUATOR SHOULD NEVER BE USED IN PRODUCTION!**
- @available(*, deprecated, renamed: "DisabledTrustEvaluator", message: "DisabledEvaluator has been renamed DisabledTrustEvaluator.")
- public typealias DisabledEvaluator = DisabledTrustEvaluator
- /// Disables all evaluation which in turn will always consider any server trust as valid.
- ///
- ///
- /// - Note: Instead of disabling server trust evaluation, it's a better idea to configure systems to properly trust test
- /// certificates, as outlined in [this Apple tech note](https://developer.apple.com/library/archive/qa/qa1948/_index.html).
- ///
- /// **THIS EVALUATOR SHOULD NEVER BE USED IN PRODUCTION!**
- public final class DisabledTrustEvaluator: ServerTrustEvaluating {
- /// Creates an instance.
- public init() {}
- public func evaluate(_ trust: SecTrust, forHost host: String) throws {}
- }
- // MARK: - Extensions
- extension Array where Element == ServerTrustEvaluating {
- #if os(Linux) || os(Windows)
- // Add this same convenience method for Linux/Windows.
- #else
- /// Evaluates the given `SecTrust` value for the given `host`.
- ///
- /// - Parameters:
- /// - trust: The `SecTrust` value to evaluate.
- /// - host: The host for which to evaluate the `SecTrust` value.
- ///
- /// - Returns: Whether or not the evaluator considers the `SecTrust` value valid for `host`.
- public func evaluate(_ trust: SecTrust, forHost host: String) throws {
- for evaluator in self {
- try evaluator.evaluate(trust, forHost: host)
- }
- }
- #endif
- }
- extension Bundle: AlamofireExtended {}
- extension AlamofireExtension where ExtendedType: Bundle {
- /// Returns all valid `cer`, `crt`, and `der` certificates in the bundle.
- public var certificates: [SecCertificate] {
- paths(forResourcesOfTypes: [".cer", ".CER", ".crt", ".CRT", ".der", ".DER"]).compactMap { path in
- guard
- let certificateData = try? Data(contentsOf: URL(fileURLWithPath: path)) as CFData,
- let certificate = SecCertificateCreateWithData(nil, certificateData) else { return nil }
- return certificate
- }
- }
- /// Returns all public keys for the valid certificates in the bundle.
- public var publicKeys: [SecKey] {
- certificates.af.publicKeys
- }
- /// Returns all pathnames for the resources identified by the provided file extensions.
- ///
- /// - Parameter types: The filename extensions locate.
- ///
- /// - Returns: All pathnames for the given filename extensions.
- public func paths(forResourcesOfTypes types: [String]) -> [String] {
- Array(Set(types.flatMap { type.paths(forResourcesOfType: $0, inDirectory: nil) }))
- }
- }
- extension SecTrust: AlamofireExtended {}
- extension AlamofireExtension where ExtendedType == SecTrust {
- /// Evaluates `self` after applying the `SecPolicy` value provided.
- ///
- /// - Parameter policy: The `SecPolicy` to apply to `self` before evaluation.
- ///
- /// - Throws: Any `Error` from applying the `SecPolicy` or from evaluation.
- @available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *)
- public func evaluate(afterApplying policy: SecPolicy) throws {
- try apply(policy: policy).af.evaluate()
- }
- /// Attempts to validate `self` using the `SecPolicy` provided and transforming any error produced using the closure passed.
- ///
- /// - Parameters:
- /// - policy: The `SecPolicy` used to evaluate `self`.
- /// - errorProducer: The closure used transform the failed `OSStatus` and `SecTrustResultType`.
- /// - Throws: Any `Error` from applying the `policy`, or the result of `errorProducer` if validation fails.
- @available(iOS, introduced: 10, deprecated: 12, renamed: "evaluate(afterApplying:)")
- @available(macOS, introduced: 10.12, deprecated: 10.14, renamed: "evaluate(afterApplying:)")
- @available(tvOS, introduced: 10, deprecated: 12, renamed: "evaluate(afterApplying:)")
- @available(watchOS, introduced: 3, deprecated: 5, renamed: "evaluate(afterApplying:)")
- public func validate(policy: SecPolicy, errorProducer: (_ status: OSStatus, _ result: SecTrustResultType) -> Error) throws {
- try apply(policy: policy).af.validate(errorProducer: errorProducer)
- }
- /// Applies a `SecPolicy` to `self`, throwing if it fails.
- ///
- /// - Parameter policy: The `SecPolicy`.
- ///
- /// - Returns: `self`, with the policy applied.
- /// - Throws: An `AFError.serverTrustEvaluationFailed` instance with a `.policyApplicationFailed` reason.
- public func apply(policy: SecPolicy) throws -> SecTrust {
- let status = SecTrustSetPolicies(type, policy)
- guard status.af.isSuccess else {
- throw AFError.serverTrustEvaluationFailed(reason: .policyApplicationFailed(trust: type,
- policy: policy,
- status: status))
- }
- return type
- }
- /// Evaluate `self`, throwing an `Error` if evaluation fails.
- ///
- /// - Throws: `AFError.serverTrustEvaluationFailed` with reason `.trustValidationFailed` and associated error from
- /// the underlying evaluation.
- @available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *)
- public func evaluate() throws {
- var error: CFError?
- let evaluationSucceeded = SecTrustEvaluateWithError(type, &error)
- if !evaluationSucceeded {
- throw AFError.serverTrustEvaluationFailed(reason: .trustEvaluationFailed(error: error))
- }
- }
- /// Validate `self`, passing any failure values through `errorProducer`.
- ///
- /// - Parameter errorProducer: The closure used to transform the failed `OSStatus` and `SecTrustResultType` into an
- /// `Error`.
- /// - Throws: The `Error` produced by the `errorProducer` closure.
- @available(iOS, introduced: 10, deprecated: 12, renamed: "evaluate()")
- @available(macOS, introduced: 10.12, deprecated: 10.14, renamed: "evaluate()")
- @available(tvOS, introduced: 10, deprecated: 12, renamed: "evaluate()")
- @available(watchOS, introduced: 3, deprecated: 5, renamed: "evaluate()")
- public func validate(errorProducer: (_ status: OSStatus, _ result: SecTrustResultType) -> Error) throws {
- var result = SecTrustResultType.invalid
- let status = SecTrustEvaluate(type, &result)
- guard status.af.isSuccess && result.af.isSuccess else {
- throw errorProducer(status, result)
- }
- }
- /// Sets a custom certificate chain on `self`, allowing full validation of a self-signed certificate and its chain.
- ///
- /// - Parameter certificates: The `SecCertificate`s to add to the chain.
- /// - Throws: Any error produced when applying the new certificate chain.
- public func setAnchorCertificates(_ certificates: [SecCertificate]) throws {
- // Add additional anchor certificates.
- let status = SecTrustSetAnchorCertificates(type, certificates as CFArray)
- guard status.af.isSuccess else {
- throw AFError.serverTrustEvaluationFailed(reason: .settingAnchorCertificatesFailed(status: status,
- certificates: certificates))
- }
- // Trust only the set anchor certs.
- let onlyStatus = SecTrustSetAnchorCertificatesOnly(type, true)
- guard onlyStatus.af.isSuccess else {
- throw AFError.serverTrustEvaluationFailed(reason: .settingAnchorCertificatesFailed(status: onlyStatus,
- certificates: certificates))
- }
- }
- /// The public keys contained in `self`.
- public var publicKeys: [SecKey] {
- certificates.af.publicKeys
- }
- /// The `SecCertificate`s contained in `self`.
- public var certificates: [SecCertificate] {
- #if swift(>=5.5.1) // Xcode 13.1 / 2021 SDKs.
- if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) {
- return (SecTrustCopyCertificateChain(type) as? [SecCertificate]) ?? []
- } else {
- return (0..<SecTrustGetCertificateCount(type)).compactMap { index in
- SecTrustGetCertificateAtIndex(type, index)
- }
- }
- #else
- (0..<SecTrustGetCertificateCount(type)).compactMap { index in
- SecTrustGetCertificateAtIndex(type, index)
- }
- #endif
- }
- /// The `Data` values for all certificates contained in `self`.
- public var certificateData: [Data] {
- certificates.af.data
- }
- /// Validates `self` after applying `SecPolicy.af.default`. This evaluation does not validate the hostname.
- ///
- /// - Parameter host: The hostname, used only in the error output if validation fails.
- /// - Throws: An `AFError.serverTrustEvaluationFailed` instance with a `.defaultEvaluationFailed` reason.
- public func performDefaultValidation(forHost host: String) throws {
- if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) {
- try evaluate(afterApplying: SecPolicy.af.default)
- } else {
- try validate(policy: SecPolicy.af.default) { status, result in
- AFError.serverTrustEvaluationFailed(reason: .defaultEvaluationFailed(output: .init(host, type, status, result)))
- }
- }
- }
- /// Validates `self` after applying `SecPolicy.af.hostname(host)`, which performs the default validation as well as
- /// hostname validation.
- ///
- /// - Parameter host: The hostname to use in the validation.
- /// - Throws: An `AFError.serverTrustEvaluationFailed` instance with a `.defaultEvaluationFailed` reason.
- public func performValidation(forHost host: String) throws {
- if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) {
- try evaluate(afterApplying: SecPolicy.af.hostname(host))
- } else {
- try validate(policy: SecPolicy.af.hostname(host)) { status, result in
- AFError.serverTrustEvaluationFailed(reason: .hostValidationFailed(output: .init(host, type, status, result)))
- }
- }
- }
- }
- extension SecPolicy: AlamofireExtended {}
- extension AlamofireExtension where ExtendedType == SecPolicy {
- /// Creates a `SecPolicy` instance which will validate server certificates but not require a host name match.
- public static let `default` = SecPolicyCreateSSL(true, nil)
- /// Creates a `SecPolicy` instance which will validate server certificates and much match the provided hostname.
- ///
- /// - Parameter hostname: The hostname to validate against.
- ///
- /// - Returns: The `SecPolicy`.
- public static func hostname(_ hostname: String) -> SecPolicy {
- SecPolicyCreateSSL(true, hostname as CFString)
- }
- /// Creates a `SecPolicy` which checks the revocation of certificates.
- ///
- /// - Parameter options: The `RevocationTrustEvaluator.Options` for evaluation.
- ///
- /// - Returns: The `SecPolicy`.
- /// - Throws: An `AFError.serverTrustEvaluationFailed` error with reason `.revocationPolicyCreationFailed`
- /// if the policy cannot be created.
- public static func revocation(options: RevocationTrustEvaluator.Options) throws -> SecPolicy {
- guard let policy = SecPolicyCreateRevocation(options.rawValue) else {
- throw AFError.serverTrustEvaluationFailed(reason: .revocationPolicyCreationFailed)
- }
- return policy
- }
- }
- extension Array: AlamofireExtended {}
- extension AlamofireExtension where ExtendedType == [SecCertificate] {
- /// All `Data` values for the contained `SecCertificate`s.
- public var data: [Data] {
- type.map { SecCertificateCopyData($0) as Data }
- }
- /// All public `SecKey` values for the contained `SecCertificate`s.
- public var publicKeys: [SecKey] {
- type.compactMap(\.af.publicKey)
- }
- }
- extension SecCertificate: AlamofireExtended {}
- extension AlamofireExtension where ExtendedType == SecCertificate {
- /// The public key for `self`, if it can be extracted.
- ///
- /// - Note: On 2020 OSes and newer, only RSA and ECDSA keys are supported.
- ///
- public var publicKey: SecKey? {
- let policy = SecPolicyCreateBasicX509()
- var trust: SecTrust?
- let trustCreationStatus = SecTrustCreateWithCertificates(type, policy, &trust)
- guard let createdTrust = trust, trustCreationStatus == errSecSuccess else { return nil }
- if #available(iOS 14, macOS 11, tvOS 14, watchOS 7, *) {
- return SecTrustCopyKey(createdTrust)
- } else {
- return SecTrustCopyPublicKey(createdTrust)
- }
- }
- }
- extension OSStatus: AlamofireExtended {}
- extension AlamofireExtension where ExtendedType == OSStatus {
- /// Returns whether `self` is `errSecSuccess`.
- public var isSuccess: Bool { type == errSecSuccess }
- }
- extension SecTrustResultType: AlamofireExtended {}
- extension AlamofireExtension where ExtendedType == SecTrustResultType {
- /// Returns whether `self is `.unspecified` or `.proceed`.
- public var isSuccess: Bool {
- type == .unspecified || type == .proceed
- }
- }
- #endif
|