diff --git a/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift b/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift index 46ecb30..dfcd60d 100644 --- a/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift +++ b/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift @@ -83,13 +83,15 @@ open class TunnelKitProvider: NEPacketTunnelProvider { private let prngSeedLength = 64 private var cachesURL: URL { - return URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)[0]) + guard let appGroup = appGroup else { + fatalError("Accessing cachesURL before parsing app group") + } + guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else { + fatalError("No access to app group: \(appGroup)") + } + return containerURL.appendingPathComponent("Library/Caches/") } - private func temporaryURL(forKey key: String) -> URL { - return cachesURL.appendingPathComponent("\(key).pem") - } - // MARK: Tunnel configuration private var appGroup: String! @@ -181,42 +183,6 @@ open class TunnelKitProvider: NEPacketTunnelProvider { completionHandler(ProviderConfigurationError.prngInitialization) return } - -// let caPath: String -// let clientCertificatePath: String? -// let clientKeyPath: String? -// do { -// let url = temporaryURL(forKey: Configuration.Keys.ca) -// try cfg.ca.write(to: url) -// caPath = url.path -// } catch { -// completionHandler(ProviderConfigurationError.certificateSerialization) -// return -// } -// if let clientCertificate = cfg.clientCertificate { -// do { -// let url = temporaryURL(forKey: Configuration.Keys.clientCertificate) -// try clientCertificate.write(to: url) -// clientCertificatePath = url.path -// } catch { -// completionHandler(ProviderConfigurationError.certificateSerialization) -// return -// } -// } else { -// clientCertificatePath = nil -// } -// if let clientKey = cfg.clientKey { -// do { -// let url = temporaryURL(forKey: Configuration.Keys.clientKey) -// try clientKey.write(to: url) -// clientKeyPath = url.path -// } catch { -// completionHandler(ProviderConfigurationError.certificateSerialization) -// return -// } -// } else { -// clientKeyPath = nil -// } cfg.print(appVersion: appVersion) @@ -239,7 +205,7 @@ open class TunnelKitProvider: NEPacketTunnelProvider { let proxy: SessionProxy do { - proxy = try SessionProxy(queue: tunnelQueue, configuration: sessionConfiguration.build()) + proxy = try SessionProxy(queue: tunnelQueue, configuration: sessionConfiguration.build(), cachesURL: cachesURL) } catch let e { completionHandler(e) return @@ -382,10 +348,6 @@ open class TunnelKitProvider: NEPacketTunnelProvider { } // stopped externally, unrecoverable else { - let fm = FileManager.default - for key in [Configuration.Keys.ca, Configuration.Keys.clientCertificate, Configuration.Keys.clientKey] { - try? fm.removeItem(at: temporaryURL(forKey: key)) - } cancelTunnelWithError(error) } } diff --git a/TunnelKit/Sources/Core/SessionProxy.swift b/TunnelKit/Sources/Core/SessionProxy.swift index c9ff7eb..1165dc3 100644 --- a/TunnelKit/Sources/Core/SessionProxy.swift +++ b/TunnelKit/Sources/Core/SessionProxy.swift @@ -69,6 +69,14 @@ public class SessionProxy { case reconnect } + private struct Caches { + static let ca = "ca.pem" + + static let clientCertificate = "cert.pem" + + static let clientKey = "key.pem" + } + // MARK: Configuration private let configuration: Configuration @@ -143,6 +151,22 @@ public class SessionProxy { private var authenticator: Authenticator? + // MARK: Caching + + private let cachesURL: URL + + private var caURL: URL { + return cachesURL.appendingPathComponent(Caches.ca) + } + + private var clientCertificateURL: URL { + return cachesURL.appendingPathComponent(Caches.clientCertificate) + } + + private var clientKeyURL: URL { + return cachesURL.appendingPathComponent(Caches.clientKey) + } + // MARK: Init /** @@ -151,9 +175,10 @@ public class SessionProxy { - Parameter queue: The `DispatchQueue` where to run the session loop. - Parameter configuration: The `SessionProxy.Configuration` to use for this session. */ - public init(queue: DispatchQueue, configuration: Configuration) throws { + public init(queue: DispatchQueue, configuration: Configuration, cachesURL: URL) throws { self.queue = queue self.configuration = configuration + self.cachesURL = cachesURL keys = [:] oldKeys = [] @@ -172,10 +197,29 @@ public class SessionProxy { } else { controlChannel = ControlChannel() } + + // cache PEMs locally (mandatory for OpenSSL) + let fm = FileManager.default + try configuration.ca.pem.write(to: caURL, atomically: true, encoding: .ascii) + if let container = configuration.clientCertificate { + try container.pem.write(to: clientCertificateURL, atomically: true, encoding: .ascii) + } else { + try? fm.removeItem(at: clientCertificateURL) + } + if let container = configuration.clientKey { + try container.pem.write(to: clientKeyURL, atomically: true, encoding: .ascii) + } else { + try? fm.removeItem(at: clientKeyURL) + } } deinit { cleanup() + + let fm = FileManager.default + for url in [caURL, clientCertificateURL, clientKeyURL] { + try? fm.removeItem(at: url) + } } // MARK: Public interface @@ -566,7 +610,7 @@ public class SessionProxy { private func hardResetPayload() -> Data? { guard !configuration.usesPIAPatches else { - let caMD5 = TLSBox.md5(forCertificatePath: configuration.caPath) + let caMD5 = TLSBox.md5(forCertificatePath: caURL.path) log.debug("CA MD5 is: \(caMD5)") return try? PIAHardReset( caMd5Digest: caMD5, @@ -722,9 +766,9 @@ public class SessionProxy { log.debug("Start TLS handshake") negotiationKey.tlsOptional = TLSBox( - caPath: configuration.caPath, - clientCertificatePath: configuration.clientCertificatePath, - clientKeyPath: configuration.clientKeyPath + caPath: caURL.path, + clientCertificatePath: (configuration.clientCertificate != nil) ? clientCertificateURL.path : nil, + clientKeyPath: (configuration.clientKey != nil) ? clientKeyURL.path : nil ) do { try negotiationKey.tls.start()