iOS/Sources/Shared/API/SecurityExceptions.swift

96 lines
2.7 KiB
Swift

import Foundation
public enum SecurityExceptionError: Error {
case invariantFailure
}
public struct SecurityExceptions: Codable, Equatable {
private var exceptions: [SecurityException] = []
public init(exceptions: [SecurityException] = []) {
self.exceptions = exceptions
}
public var hasExceptions: Bool { !exceptions.isEmpty }
public mutating func add(for secTrust: SecTrust) {
if let exception = SecurityException(secTrust: secTrust) {
exceptions.append(exception)
}
}
public func evaluate(_ secTrust: SecTrust) throws {
var baseError: CFError?
let isAlreadyTrusted = SecTrustEvaluateWithError(secTrust, &baseError)
guard !isAlreadyTrusted else {
return
}
let baseThrowable = baseError as Error? ?? SecurityExceptionError.invariantFailure
for exception in exceptions {
do {
try exception.evaluate(secTrust)
// we want to preserve this one modifying the sec trust
// so if it succeeds, we immediately return
return
} catch {
// this one errored, so try the next one
}
}
// always throw if we don't find a successful one above
throw baseThrowable
}
public func evaluate(_ challenge: URLAuthenticationChallenge)
-> (URLSession.AuthChallengeDisposition, URLCredential?) {
guard let secTrust = challenge.protectionSpace.serverTrust else {
return (.performDefaultHandling, nil)
}
do {
try evaluate(secTrust)
return (.useCredential, .init(trust: secTrust))
} catch {
return (.rejectProtectionSpace, nil)
}
}
}
public struct SecurityException: Codable, Equatable {
private var data: Data
public init?(secTrust: SecTrust) {
if let data = SecTrustCopyExceptions(secTrust) as Data? {
self.data = data
} else {
return nil
}
}
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let string = try container.decode(String.self)
self.data = Data(base64Encoded: string) ?? Data()
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(data.base64EncodedString())
}
public func evaluate(_ secTrust: SecTrust) throws {
SecTrustSetExceptions(secTrust, data as CFData)
var error: CFError?
if SecTrustEvaluateWithError(secTrust, &error) {
return
} else {
throw error as Error? ?? SecurityExceptionError.invariantFailure
}
}
}