diff --git a/Demo/BasicTunnel-iOS/ViewController.swift b/Demo/BasicTunnel-iOS/ViewController.swift index b411947..6b574ea 100644 --- a/Demo/BasicTunnel-iOS/ViewController.swift +++ b/Demo/BasicTunnel-iOS/ViewController.swift @@ -88,15 +88,16 @@ extension ViewController { let port = UInt16(textPort.text!)! let credentials = SessionProxy.Credentials(textUsername.text!, textPassword.text!) - var builder = TunnelKitProvider.ConfigurationBuilder(ca: ca) + var sessionBuilder = SessionProxy.ConfigurationBuilder(ca: ca) + sessionBuilder.cipher = .aes256gcm + sessionBuilder.digest = .sha1 + sessionBuilder.compressionFraming = .compLZO + sessionBuilder.renegotiatesAfter = nil + sessionBuilder.usesPIAPatches = true + var builder = TunnelKitProvider.ConfigurationBuilder(sessionConfiguration: sessionBuilder.build()) let socketType: TunnelKitProvider.SocketType = switchTCP.isOn ? .tcp : .udp builder.endpointProtocols = [TunnelKitProvider.EndpointProtocol(socketType, port)] - builder.cipher = .aes256gcm - builder.digest = .sha1 builder.mtu = 1350 - builder.compressionFraming = .compLZO - builder.renegotiatesAfterSeconds = nil - builder.usesPIAPatches = true builder.shouldDebug = true builder.debugLogKey = "Log" diff --git a/Demo/BasicTunnel-macOS/ViewController.swift b/Demo/BasicTunnel-macOS/ViewController.swift index db1890e..13a9c5d 100644 --- a/Demo/BasicTunnel-macOS/ViewController.swift +++ b/Demo/BasicTunnel-macOS/ViewController.swift @@ -88,15 +88,17 @@ extension ViewController { let port = UInt16(textPort.stringValue)! let credentials = SessionProxy.Credentials(textUsername.stringValue, textPassword.stringValue) - var builder = TunnelKitProvider.ConfigurationBuilder(ca: ca) + var sessionBuilder = SessionProxy.ConfigurationBuilder(ca: ca) + sessionBuilder.cipher = .aes128cbc + sessionBuilder.digest = .sha1 + sessionBuilder.compressionFraming = .compLZO + sessionBuilder.renegotiatesAfter = nil + sessionBuilder.usesPIAPatches = true + var builder = TunnelKitProvider.ConfigurationBuilder(sessionConfiguration: sessionBuilder.build()) // let socketType: TunnelKitProvider.SocketType = isTCP ? .tcp : .udp let socketType: TunnelKitProvider.SocketType = .udp builder.endpointProtocols = [TunnelKitProvider.EndpointProtocol(socketType, port)] - builder.cipher = .aes128cbc - builder.digest = .sha1 builder.mtu = 1350 - builder.compressionFraming = .compLZO - builder.renegotiatesAfterSeconds = nil builder.shouldDebug = true builder.debugLogKey = "Log" diff --git a/Demo/Podfile.lock b/Demo/Podfile.lock index c4e5a6b..3f745b3 100644 --- a/Demo/Podfile.lock +++ b/Demo/Podfile.lock @@ -1,13 +1,13 @@ PODS: - OpenSSL-Apple (1.1.0i-v2) - SwiftyBeaver (1.6.1) - - TunnelKit (1.2.2): - - TunnelKit/AppExtension (= 1.2.2) - - TunnelKit/Core (= 1.2.2) - - TunnelKit/AppExtension (1.2.2): + - TunnelKit (1.3.0): + - TunnelKit/AppExtension (= 1.3.0) + - TunnelKit/Core (= 1.3.0) + - TunnelKit/AppExtension (1.3.0): - SwiftyBeaver - TunnelKit/Core - - TunnelKit/Core (1.2.2): + - TunnelKit/Core (1.3.0): - OpenSSL-Apple (~> 1.1.0h) - SwiftyBeaver @@ -26,7 +26,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: OpenSSL-Apple: a93b8f2eec8783ff40d9a9304de180ab68bb647c SwiftyBeaver: ccfcdf85a04d429f1633f668650b0ce8020bda3a - TunnelKit: 15c88f0cef7b926883566a9455e912a1e55f4048 + TunnelKit: 8e747cac28959ebfdfa4eeab589c933f1856c0fb PODFILE CHECKSUM: f66dfaaa92a8d04ab2743f3caeab0ac9f9f25859 diff --git a/TunnelKit.xcodeproj/project.pbxproj b/TunnelKit.xcodeproj/project.pbxproj index 0032a51..e182c9d 100644 --- a/TunnelKit.xcodeproj/project.pbxproj +++ b/TunnelKit.xcodeproj/project.pbxproj @@ -53,9 +53,9 @@ 0E3E0F222108A8CC00B371C1 /* SessionProxy+PushReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3E0F202108A8CC00B371C1 /* SessionProxy+PushReply.swift */; }; 0E50D57521634E0A00FC87A8 /* ControlChannelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E50D57421634E0A00FC87A8 /* ControlChannelTests.swift */; }; 0E58F1302138AC2F00A49F27 /* DNSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E58F12F2138AC2F00A49F27 /* DNSTests.swift */; }; - 0E749F622178911D00BB2701 /* pia-2048.pem in Resources */ = {isa = PBXBuildFile; fileRef = 0E749F612178911C00BB2701 /* pia-2048.pem */; }; 0E749F5F2178885500BB2701 /* SessionProxy+PIA.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E749F5E2178885500BB2701 /* SessionProxy+PIA.swift */; }; 0E749F602178885500BB2701 /* SessionProxy+PIA.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E749F5E2178885500BB2701 /* SessionProxy+PIA.swift */; }; + 0E749F622178911D00BB2701 /* pia-2048.pem in Resources */ = {isa = PBXBuildFile; fileRef = 0E749F612178911C00BB2701 /* pia-2048.pem */; }; 0E85A25A202CC5AF0059E9F9 /* AppExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E85A259202CC5AE0059E9F9 /* AppExtensionTests.swift */; }; 0E9379C91F819A4300CE91B6 /* TunnelKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E17D7F91F730D9F009EE129 /* TunnelKit.framework */; }; 0EB2B45320F0BB44004233D7 /* EncryptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB2B45220F0BB44004233D7 /* EncryptionTests.swift */; }; @@ -232,8 +232,8 @@ 0E58F12F2138AC2F00A49F27 /* DNSTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSTests.swift; sourceTree = ""; }; 0E6479DD212EAC96008E6888 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 0E6479E0212EACD6008E6888 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 0E749F612178911C00BB2701 /* pia-2048.pem */ = {isa = PBXFileReference; lastKnownFileType = text; path = "pia-2048.pem"; sourceTree = ""; }; 0E749F5E2178885500BB2701 /* SessionProxy+PIA.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionProxy+PIA.swift"; sourceTree = ""; }; + 0E749F612178911C00BB2701 /* pia-2048.pem */ = {isa = PBXFileReference; lastKnownFileType = text; path = "pia-2048.pem"; sourceTree = ""; }; 0E85A259202CC5AE0059E9F9 /* AppExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppExtensionTests.swift; sourceTree = ""; }; 0E85A25B202CCA3D0059E9F9 /* TunnelKitHost.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TunnelKitHost.entitlements; sourceTree = ""; }; 0EB2B45220F0BB44004233D7 /* EncryptionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionTests.swift; sourceTree = ""; }; @@ -471,6 +471,7 @@ 0EFEB4322006D3C800F81029 /* CryptoBox.m */, 0E07596D20EF79B400F38FD8 /* CryptoCBC.h */, 0E07595C20EF6D1400F38FD8 /* CryptoCBC.m */, + 0ECE3527212EB7770040F253 /* CryptoContainer.swift */, 0E3B15C52152B05E00984B17 /* CryptoCTR.h */, 0E3B15C62152B05E00984B17 /* CryptoCTR.m */, 0E07596120EF733F00F38FD8 /* CryptoMacros.h */, @@ -518,7 +519,6 @@ children = ( 0EBBF2E32084FDF400E36B40 /* Transport */, 0EC1BBA720D7D803007C4C7B /* ConnectionStrategy.swift */, - 0ECE3527212EB7770040F253 /* CryptoContainer.swift */, 0EC1BBA420D71190007C4C7B /* DNSResolver.swift */, 0EBBF2E42084FE6F00E36B40 /* GenericSocket.swift */, 0EFEB4AA200760EC00F81029 /* InterfaceObserver.swift */, diff --git a/TunnelKit/Sources/AppExtension/TunnelKitProvider+Configuration.swift b/TunnelKit/Sources/AppExtension/TunnelKitProvider+Configuration.swift index 6656453..4fabf92 100644 --- a/TunnelKit/Sources/AppExtension/TunnelKitProvider+Configuration.swift +++ b/TunnelKit/Sources/AppExtension/TunnelKitProvider+Configuration.swift @@ -121,38 +121,11 @@ extension TunnelKitProvider { /// The accepted communication protocols. Must be non-empty. public var endpointProtocols: [EndpointProtocol] - /// The encryption algorithm. - public var cipher: SessionProxy.Cipher - - /// The message digest algorithm. - public var digest: SessionProxy.Digest - - /// The CA certificate to validate server against. - public let ca: CryptoContainer - - /// The optional client certificate to authenticate with. Set to `nil` to disable client authentication (default). - public var clientCertificate: CryptoContainer? - - /// The optional key for `clientCertificate`. Set to `nil` if client authentication unused (default). - public var clientKey: CryptoContainer? - /// The MTU of the link. public var mtu: Int - /// Sets compression framing, disabled by default. - public var compressionFraming: SessionProxy.CompressionFraming - - /// The optional TLS wrapping. When `strategy == .auth`, uses `digest` as HMAC algorithm. - public var tlsWrap: SessionProxy.TLSWrap? - - /// Sends periodical keep-alive packets (ping) if set. Useful with stateful firewalls. - public var keepAliveSeconds: Int? - - /// The number of seconds after which a renegotiation is started. Set to `nil` to disable renegotiation (default). - public var renegotiatesAfterSeconds: Int? - - /// Server is patched for the PIA VPN provider. - public var usesPIAPatches: Bool? + /// The session configuration. + public var sessionConfiguration: SessionProxy.Configuration // MARK: Debugging @@ -175,21 +148,12 @@ extension TunnelKitProvider { - Parameter ca: The CA certificate. */ - public init(ca: CryptoContainer) { + public init(sessionConfiguration: SessionProxy.Configuration) { prefersResolvedAddresses = false resolvedAddresses = nil endpointProtocols = [EndpointProtocol(.udp, 1194)] - cipher = .aes128cbc - digest = .sha1 - self.ca = ca - clientCertificate = nil - clientKey = nil mtu = 1500 - compressionFraming = .disabled - tlsWrap = nil - keepAliveSeconds = nil - renegotiatesAfterSeconds = nil - usesPIAPatches = false + self.sessionConfiguration = sessionConfiguration shouldDebug = false debugLogKey = nil debugLogFormat = nil @@ -199,6 +163,21 @@ extension TunnelKitProvider { fileprivate init(providerConfiguration: [String: Any]) throws { let S = Configuration.Keys.self + prefersResolvedAddresses = providerConfiguration[S.prefersResolvedAddresses] as? Bool ?? false + resolvedAddresses = providerConfiguration[S.resolvedAddresses] as? [String] + guard let endpointProtocolsStrings = providerConfiguration[S.endpointProtocols] as? [String], !endpointProtocolsStrings.isEmpty else { + throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.endpointProtocols)] is nil or empty") + } + endpointProtocols = try endpointProtocolsStrings.map { + guard let ep = EndpointProtocol(rawValue: $0) else { + throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.endpointProtocols)] has a badly formed element") + } + return ep + } + mtu = providerConfiguration[S.mtu] as? Int ?? 1250 + + // + guard let cipherAlgorithm = providerConfiguration[S.cipherAlgorithm] as? String, let cipher = SessionProxy.Cipher(rawValue: cipherAlgorithm) else { throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.cipherAlgorithm)]") } @@ -224,41 +203,28 @@ extension TunnelKitProvider { clientCertificate = nil clientKey = nil } - - prefersResolvedAddresses = providerConfiguration[S.prefersResolvedAddresses] as? Bool ?? false - resolvedAddresses = providerConfiguration[S.resolvedAddresses] as? [String] - - guard let endpointProtocolsStrings = providerConfiguration[S.endpointProtocols] as? [String], !endpointProtocolsStrings.isEmpty else { - throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.endpointProtocols)] is nil or empty") - } - endpointProtocols = try endpointProtocolsStrings.map { - guard let ep = EndpointProtocol(rawValue: $0) else { - throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.endpointProtocols)] has a badly formed element") - } - return ep - } - - self.cipher = cipher - self.digest = digest - self.ca = ca - self.clientCertificate = clientCertificate - self.clientKey = clientKey - mtu = providerConfiguration[S.mtu] as? Int ?? 1250 + + var sessionConfigurationBuilder = SessionProxy.ConfigurationBuilder(ca: ca) + sessionConfigurationBuilder.cipher = cipher + sessionConfigurationBuilder.digest = digest + sessionConfigurationBuilder.clientCertificate = clientCertificate + sessionConfigurationBuilder.clientKey = clientKey if let compressionFramingValue = providerConfiguration[S.compressionFraming] as? Int, let compressionFraming = SessionProxy.CompressionFraming(rawValue: compressionFramingValue) { - self.compressionFraming = compressionFraming + sessionConfigurationBuilder.compressionFraming = compressionFraming } else { - compressionFraming = .disabled + sessionConfigurationBuilder.compressionFraming = .disabled } if let tlsWrapData = providerConfiguration[S.tlsWrap] as? Data { do { - tlsWrap = try SessionProxy.TLSWrap.deserialized(tlsWrapData) + sessionConfigurationBuilder.tlsWrap = try SessionProxy.TLSWrap.deserialized(tlsWrapData) } catch { throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.tlsWrap)]") } } - keepAliveSeconds = providerConfiguration[S.keepAlive] as? Int - renegotiatesAfterSeconds = providerConfiguration[S.renegotiatesAfter] as? Int - usesPIAPatches = providerConfiguration[S.usesPIAPatches] as? Bool ?? false + sessionConfigurationBuilder.keepAliveInterval = providerConfiguration[S.keepAlive] as? TimeInterval + sessionConfigurationBuilder.renegotiatesAfter = providerConfiguration[S.renegotiatesAfter] as? TimeInterval + sessionConfigurationBuilder.usesPIAPatches = providerConfiguration[S.usesPIAPatches] as? Bool ?? false + sessionConfiguration = sessionConfigurationBuilder.build() shouldDebug = providerConfiguration[S.debug] as? Bool ?? false if shouldDebug { @@ -287,17 +253,8 @@ extension TunnelKitProvider { prefersResolvedAddresses: prefersResolvedAddresses, resolvedAddresses: resolvedAddresses, endpointProtocols: endpointProtocols, - cipher: cipher, - digest: digest, - ca: ca, - clientCertificate: clientCertificate, - clientKey: clientKey, mtu: mtu, - compressionFraming: compressionFraming, - tlsWrap: tlsWrap, - keepAliveSeconds: keepAliveSeconds, - renegotiatesAfterSeconds: renegotiatesAfterSeconds, - usesPIAPatches: usesPIAPatches, + sessionConfiguration: sessionConfiguration, shouldDebug: shouldDebug, debugLogKey: shouldDebug ? debugLogKey : nil, debugLogFormat: shouldDebug ? debugLogFormat : nil, @@ -317,6 +274,10 @@ extension TunnelKitProvider { static let endpointProtocols = "EndpointProtocols" + static let mtu = "MTU" + + // MARK: SessionConfiguration + static let cipherAlgorithm = "CipherAlgorithm" static let digestAlgorithm = "DigestAlgorithm" @@ -327,8 +288,6 @@ extension TunnelKitProvider { static let clientKey = "ClientKey" - static let mtu = "MTU" - static let compressionFraming = "CompressionFraming" static let tlsWrap = "TLSWrap" @@ -338,6 +297,8 @@ extension TunnelKitProvider { static let renegotiatesAfter = "RenegotiatesAfter" static let usesPIAPatches = "UsesPIAPatches" + + // MARK: Debugging static let debug = "Debug" @@ -357,38 +318,11 @@ extension TunnelKitProvider { /// - Seealso: `TunnelKitProvider.ConfigurationBuilder.endpointProtocols` public let endpointProtocols: [EndpointProtocol] - /// - Seealso: `TunnelKitProvider.ConfigurationBuilder.cipher` - public let cipher: SessionProxy.Cipher - - /// - Seealso: `TunnelKitProvider.ConfigurationBuilder.digest` - public let digest: SessionProxy.Digest - - /// - Seealso: `TunnelKitProvider.ConfigurationBuilder.ca` - public let ca: CryptoContainer - - /// - Seealso: `TunnelKitProvider.ConfigurationBuilder.clientCertificate` - public let clientCertificate: CryptoContainer? - - /// - Seealso: `TunnelKitProvider.ConfigurationBuilder.clientKey` - public let clientKey: CryptoContainer? - /// - Seealso: `TunnelKitProvider.ConfigurationBuilder.mtu` public let mtu: Int - /// - Seealso: `TunnelKitProvider.ConfigurationBuilder.compressionFraming` - public let compressionFraming: SessionProxy.CompressionFraming - - /// - Seealso: `TunnelKitProvider.ConfigurationBuilder.tlsWrap` - public let tlsWrap: SessionProxy.TLSWrap? - - /// - Seealso: `TunnelKitProvider.ConfigurationBuilder.keepAliveSeconds` - public let keepAliveSeconds: Int? - - /// - Seealso: `TunnelKitProvider.ConfigurationBuilder.renegotiatesAfterSeconds` - public let renegotiatesAfterSeconds: Int? - - /// - Seealso: `TunnelKitProvider.ConfigurationBuilder.usesPIAPatches` - public let usesPIAPatches: Bool? + /// - Seealso: `TunnelKitProvider.ConfigurationBuilder.sessionConfiguration` + public let sessionConfiguration: SessionProxy.Configuration /// - Seealso: `TunnelKitProvider.ConfigurationBuilder.shouldDebug` public let shouldDebug: Bool @@ -474,32 +408,32 @@ extension TunnelKitProvider { S.appGroup: appGroup, S.prefersResolvedAddresses: prefersResolvedAddresses, S.endpointProtocols: endpointProtocols.map { $0.rawValue }, - S.cipherAlgorithm: cipher.rawValue, - S.digestAlgorithm: digest.rawValue, - S.ca: ca.pem, + S.cipherAlgorithm: sessionConfiguration.cipher.rawValue, + S.digestAlgorithm: sessionConfiguration.digest.rawValue, + S.ca: sessionConfiguration.ca.pem, S.mtu: mtu, S.debug: shouldDebug ] - if let clientCertificate = clientCertificate { + if let clientCertificate = sessionConfiguration.clientCertificate { dict[S.clientCertificate] = clientCertificate.pem } - if let clientKey = clientKey { + if let clientKey = sessionConfiguration.clientKey { dict[S.clientKey] = clientKey.pem } if let resolvedAddresses = resolvedAddresses { dict[S.resolvedAddresses] = resolvedAddresses } - dict[S.compressionFraming] = compressionFraming.rawValue - if let tlsWrapData = tlsWrap?.serialized() { + dict[S.compressionFraming] = sessionConfiguration.compressionFraming.rawValue + if let tlsWrapData = sessionConfiguration.tlsWrap?.serialized() { dict[S.tlsWrap] = tlsWrapData } - if let keepAliveSeconds = keepAliveSeconds { + if let keepAliveSeconds = sessionConfiguration.keepAliveInterval { dict[S.keepAlive] = keepAliveSeconds } - if let renegotiatesAfterSeconds = renegotiatesAfterSeconds { + if let renegotiatesAfterSeconds = sessionConfiguration.renegotiatesAfter { dict[S.renegotiatesAfter] = renegotiatesAfterSeconds } - if let usesPIAPatches = usesPIAPatches { + if let usesPIAPatches = sessionConfiguration.usesPIAPatches { dict[S.usesPIAPatches] = usesPIAPatches } if let debugLogKey = debugLogKey { @@ -550,26 +484,26 @@ extension TunnelKitProvider { } log.info("\tProtocols: \(endpointProtocols)") - log.info("\tCipher: \(cipher)") - log.info("\tDigest: \(digest)") - if let _ = clientCertificate { + log.info("\tCipher: \(sessionConfiguration.cipher)") + log.info("\tDigest: \(sessionConfiguration.digest)") + if let _ = sessionConfiguration.clientCertificate { log.info("\tClient verification: enabled") } else { log.info("\tClient verification: disabled") } log.info("\tMTU: \(mtu)") - log.info("\tCompression framing: \(compressionFraming)") - if let keepAliveSeconds = keepAliveSeconds, keepAliveSeconds > 0 { + log.info("\tCompression framing: \(sessionConfiguration.compressionFraming)") + if let keepAliveSeconds = sessionConfiguration.keepAliveInterval, keepAliveSeconds > 0 { log.info("\tKeep-alive: \(keepAliveSeconds) seconds") } else { log.info("\tKeep-alive: never") } - if let renegotiatesAfterSeconds = renegotiatesAfterSeconds, renegotiatesAfterSeconds > 0 { + if let renegotiatesAfterSeconds = sessionConfiguration.renegotiatesAfter, renegotiatesAfterSeconds > 0 { log.info("\tRenegotiation: \(renegotiatesAfterSeconds) seconds") } else { log.info("\tRenegotiation: never") } - if let tlsWrap = tlsWrap { + if let tlsWrap = sessionConfiguration.tlsWrap { log.info("\tTLS wrapping: \(tlsWrap.strategy)") } else { log.info("\tTLS wrapping: disabled") @@ -589,18 +523,9 @@ extension TunnelKitProvider.Configuration: Equatable { - Returns: An editable `TunnelKitProvider.ConfigurationBuilder` initialized with this configuration. */ public func builder() -> TunnelKitProvider.ConfigurationBuilder { - var builder = TunnelKitProvider.ConfigurationBuilder(ca: ca) + var builder = TunnelKitProvider.ConfigurationBuilder(sessionConfiguration: sessionConfiguration) builder.endpointProtocols = endpointProtocols - builder.cipher = cipher - builder.digest = digest - builder.clientCertificate = clientCertificate - builder.clientKey = clientKey builder.mtu = mtu - builder.compressionFraming = compressionFraming - builder.tlsWrap = tlsWrap - builder.keepAliveSeconds = keepAliveSeconds - builder.renegotiatesAfterSeconds = renegotiatesAfterSeconds - builder.usesPIAPatches = usesPIAPatches builder.shouldDebug = shouldDebug builder.debugLogKey = debugLogKey builder.debugLogFormat = debugLogFormat @@ -612,15 +537,8 @@ extension TunnelKitProvider.Configuration: Equatable { public static func ==(lhs: TunnelKitProvider.Configuration, rhs: TunnelKitProvider.Configuration) -> Bool { return ( (lhs.endpointProtocols == rhs.endpointProtocols) && - (lhs.cipher == rhs.cipher) && - (lhs.digest == rhs.digest) && - (lhs.ca == rhs.ca) && - (lhs.clientCertificate == rhs.clientCertificate) && - (lhs.clientKey == rhs.clientKey) && (lhs.mtu == rhs.mtu) && - (lhs.compressionFraming == rhs.compressionFraming) && - (lhs.keepAliveSeconds == rhs.keepAliveSeconds) && - (lhs.renegotiatesAfterSeconds == rhs.renegotiatesAfterSeconds) + (lhs.sessionConfiguration == rhs.sessionConfiguration) // XXX: tlsWrap not copied ) } diff --git a/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift b/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift index 35e867c..250cdcf 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,69 +183,17 @@ 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) -// log.info("Temporary CA is stored to: \(caPath)") - var sessionConfiguration = SessionProxy.ConfigurationBuilder(caPath: caPath) - sessionConfiguration.credentials = credentials - sessionConfiguration.cipher = cfg.cipher - sessionConfiguration.digest = cfg.digest - sessionConfiguration.clientCertificatePath = clientCertificatePath - sessionConfiguration.clientKeyPath = clientKeyPath - sessionConfiguration.compressionFraming = cfg.compressionFraming - sessionConfiguration.tlsWrap = cfg.tlsWrap - if let keepAliveSeconds = cfg.keepAliveSeconds { - sessionConfiguration.keepAliveInterval = TimeInterval(keepAliveSeconds) - } - if let renegotiatesAfterSeconds = cfg.renegotiatesAfterSeconds { - sessionConfiguration.renegotiatesAfter = TimeInterval(renegotiatesAfterSeconds) - } - sessionConfiguration.usesPIAPatches = cfg.usesPIAPatches ?? false - let proxy: SessionProxy do { - proxy = try SessionProxy(queue: tunnelQueue, configuration: sessionConfiguration.build()) + proxy = try SessionProxy(queue: tunnelQueue, configuration: cfg.sessionConfiguration, cachesURL: cachesURL) } catch let e { completionHandler(e) return } + proxy.credentials = credentials proxy.delegate = self self.proxy = proxy @@ -382,10 +332,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/AppExtension/CryptoContainer.swift b/TunnelKit/Sources/Core/CryptoContainer.swift similarity index 100% rename from TunnelKit/Sources/AppExtension/CryptoContainer.swift rename to TunnelKit/Sources/Core/CryptoContainer.swift diff --git a/TunnelKit/Sources/Core/SessionProxy+Configuration.swift b/TunnelKit/Sources/Core/SessionProxy+Configuration.swift index c697ce2..ae3bb82 100644 --- a/TunnelKit/Sources/Core/SessionProxy+Configuration.swift +++ b/TunnelKit/Sources/Core/SessionProxy+Configuration.swift @@ -135,23 +135,20 @@ extension SessionProxy { /// The way to create a `SessionProxy.Configuration` object for a `SessionProxy`. public struct ConfigurationBuilder { - /// The credentials. - public var credentials: Credentials? - /// The cipher algorithm for data encryption. public var cipher: Cipher /// The digest algorithm for HMAC. public var digest: Digest - /// The path to the CA for TLS negotiation (PEM format). - public let caPath: String + /// The CA for TLS negotiation (PEM format). + public let ca: CryptoContainer - /// The path to the optional client certificate for TLS negotiation (PEM format). - public var clientCertificatePath: String? + /// The optional client certificate for TLS negotiation (PEM format). + public var clientCertificate: CryptoContainer? - /// The path to the private key for the certificate at `clientCertificatePath` (PEM format). - public var clientKeyPath: String? + /// The private key for the certificate in `clientCertificate` (PEM format). + public var clientKey: CryptoContainer? /// Sets compression framing, disabled by default. public var compressionFraming: CompressionFraming @@ -166,16 +163,15 @@ extension SessionProxy { public var renegotiatesAfter: TimeInterval? /// Server is patched for the PIA VPN provider. - public var usesPIAPatches: Bool + public var usesPIAPatches: Bool? /// :nodoc: - public init(caPath: String) { - credentials = nil + public init(ca: CryptoContainer) { cipher = .aes128cbc digest = .sha1 - self.caPath = caPath - clientCertificatePath = nil - clientKeyPath = nil + self.ca = ca + clientCertificate = nil + clientKey = nil compressionFraming = .disabled tlsWrap = nil keepAliveInterval = nil @@ -190,12 +186,11 @@ extension SessionProxy { */ public func build() -> Configuration { return Configuration( - credentials: credentials, cipher: cipher, digest: digest, - caPath: caPath, - clientCertificatePath: clientCertificatePath, - clientKeyPath: clientKeyPath, + ca: ca, + clientCertificate: clientCertificate, + clientKey: clientKey, compressionFraming: compressionFraming, tlsWrap: tlsWrap, keepAliveInterval: keepAliveInterval, @@ -206,25 +201,22 @@ extension SessionProxy { } /// The immutable configuration for `SessionProxy`. - public struct Configuration: Codable { + public struct Configuration: Codable, Equatable { - /// - Seealso: `SessionProxy.ConfigurationBuilder.credentials` - public let credentials: Credentials? - /// - Seealso: `SessionProxy.ConfigurationBuilder.cipher` public let cipher: Cipher /// - Seealso: `SessionProxy.ConfigurationBuilder.digest` public let digest: Digest - /// - Seealso: `SessionProxy.ConfigurationBuilder.caPath` - public let caPath: String + /// - Seealso: `SessionProxy.ConfigurationBuilder.ca` + public let ca: CryptoContainer - /// - Seealso: `SessionProxy.ConfigurationBuilder.clientCertificatePath` - public let clientCertificatePath: String? + /// - Seealso: `SessionProxy.ConfigurationBuilder.clientCertificate` + public let clientCertificate: CryptoContainer? - /// - Seealso: `SessionProxy.ConfigurationBuilder.clientKeyPath` - public let clientKeyPath: String? + /// - Seealso: `SessionProxy.ConfigurationBuilder.clientKey` + public let clientKey: CryptoContainer? /// - Seealso: `SessionProxy.ConfigurationBuilder.compressionFraming` public let compressionFraming: CompressionFraming @@ -239,6 +231,41 @@ extension SessionProxy { public let renegotiatesAfter: TimeInterval? /// - Seealso: `SessionProxy.ConfigurationBuilder.usesPIAPatches` - public let usesPIAPatches: Bool + public let usesPIAPatches: Bool? + + /** + Returns a `SessionProxy.ConfigurationBuilder` to use this configuration as a starting point for a new one. + + - Returns: An editable `SessionProxy.ConfigurationBuilder` initialized with this configuration. + */ + public func builder() -> SessionProxy.ConfigurationBuilder { + var builder = SessionProxy.ConfigurationBuilder(ca: ca) + builder.cipher = cipher + builder.digest = digest + builder.clientCertificate = clientCertificate + builder.clientKey = clientKey + builder.compressionFraming = compressionFraming + builder.tlsWrap = tlsWrap + builder.keepAliveInterval = keepAliveInterval + builder.renegotiatesAfter = renegotiatesAfter + builder.usesPIAPatches = usesPIAPatches + return builder + } + + // MARK: Equatable + + /// :nodoc: + public static func ==(lhs: Configuration, rhs: Configuration) -> Bool { + return + (lhs.cipher == rhs.cipher) && + (lhs.digest == rhs.digest) && + (lhs.ca == rhs.ca) && + (lhs.clientCertificate == rhs.clientCertificate) && + (lhs.clientKey == rhs.clientKey) && + (lhs.compressionFraming == rhs.compressionFraming) && + (lhs.keepAliveInterval == rhs.keepAliveInterval) && + (lhs.renegotiatesAfter == rhs.renegotiatesAfter) && + (lhs.usesPIAPatches == rhs.usesPIAPatches) + } } } diff --git a/TunnelKit/Sources/Core/SessionProxy.swift b/TunnelKit/Sources/Core/SessionProxy.swift index c9ff7eb..e045c68 100644 --- a/TunnelKit/Sources/Core/SessionProxy.swift +++ b/TunnelKit/Sources/Core/SessionProxy.swift @@ -69,10 +69,21 @@ 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 + /// The optional credentials. + public var credentials: Credentials? + private var keepAliveInterval: TimeInterval? { let interval: TimeInterval? if let negInterval = pushReply?.ping, negInterval > 0 { @@ -143,6 +154,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 +178,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 +200,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 @@ -565,8 +612,8 @@ public class SessionProxy { } private func hardResetPayload() -> Data? { - guard !configuration.usesPIAPatches else { - let caMD5 = TLSBox.md5(forCertificatePath: configuration.caPath) + guard !(configuration.usesPIAPatches ?? false) else { + let caMD5 = TLSBox.md5(forCertificatePath: caURL.path) log.debug("CA MD5 is: \(caMD5)") return try? PIAHardReset( caMd5Digest: caMD5, @@ -606,7 +653,7 @@ public class SessionProxy { negotiationKey.controlState = .preAuth do { - authenticator = try Authenticator(configuration.credentials?.username, pushReply?.authToken ?? configuration.credentials?.password) + authenticator = try Authenticator(credentials?.username, pushReply?.authToken ?? credentials?.password) try authenticator?.putAuth(into: negotiationKey.tls) } catch let e { deferStop(.shutdown, e) @@ -722,9 +769,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() diff --git a/TunnelKitTests/AppExtensionTests.swift b/TunnelKitTests/AppExtensionTests.swift index 4e5ae20..e0d527b 100644 --- a/TunnelKitTests/AppExtensionTests.swift +++ b/TunnelKitTests/AppExtensionTests.swift @@ -60,11 +60,12 @@ class AppExtensionTests: XCTestCase { let hostname = "example.com" let credentials = SessionProxy.Credentials("foo", "bar") - builder = TunnelKitProvider.ConfigurationBuilder(ca: CryptoContainer(pem: "abcdef")) + var sessionBuilder = SessionProxy.ConfigurationBuilder(ca: CryptoContainer(pem: "abcdef")) + sessionBuilder.cipher = .aes128cbc + sessionBuilder.digest = .sha256 + builder = TunnelKitProvider.ConfigurationBuilder(sessionConfiguration: sessionBuilder.build()) XCTAssertNotNil(builder) - builder.cipher = .aes128cbc - builder.digest = .sha256 cfg = builder.build() let proto = try? cfg.generatedTunnelProtocol(withBundleIdentifier: identifier, appGroup: appGroup, hostname: hostname, credentials: credentials) @@ -81,11 +82,11 @@ class AppExtensionTests: XCTestCase { let K = TunnelKitProvider.Configuration.Keys.self XCTAssertEqual(proto?.providerConfiguration?[K.appGroup] as? String, appGroup) - XCTAssertEqual(proto?.providerConfiguration?[K.cipherAlgorithm] as? String, cfg.cipher.rawValue) - XCTAssertEqual(proto?.providerConfiguration?[K.digestAlgorithm] as? String, cfg.digest.rawValue) - XCTAssertEqual(proto?.providerConfiguration?[K.ca] as? String, cfg.ca.pem) + XCTAssertEqual(proto?.providerConfiguration?[K.cipherAlgorithm] as? String, cfg.sessionConfiguration.cipher.rawValue) + XCTAssertEqual(proto?.providerConfiguration?[K.digestAlgorithm] as? String, cfg.sessionConfiguration.digest.rawValue) + XCTAssertEqual(proto?.providerConfiguration?[K.ca] as? String, cfg.sessionConfiguration.ca.pem) XCTAssertEqual(proto?.providerConfiguration?[K.mtu] as? Int, cfg.mtu) - XCTAssertEqual(proto?.providerConfiguration?[K.renegotiatesAfter] as? Int, cfg.renegotiatesAfterSeconds) + XCTAssertEqual(proto?.providerConfiguration?[K.renegotiatesAfter] as? TimeInterval, cfg.sessionConfiguration.renegotiatesAfter) XCTAssertEqual(proto?.providerConfiguration?[K.debug] as? Bool, cfg.shouldDebug) XCTAssertEqual(proto?.providerConfiguration?[K.debugLogKey] as? String, cfg.debugLogKey) }