diff --git a/Demo/BasicTunnel-iOS/ViewController.swift b/Demo/BasicTunnel-iOS/ViewController.swift index bd412b2..1e003a4 100644 --- a/Demo/BasicTunnel-iOS/ViewController.swift +++ b/Demo/BasicTunnel-iOS/ViewController.swift @@ -30,7 +30,7 @@ extension ViewController { password: password ) - var builder = TunnelKitProvider.ConfigurationBuilder(appGroup: ViewController.appGroup) + var builder = TunnelKitProvider.ConfigurationBuilder() let socketType: TunnelKitProvider.SocketType = switchTCP.isOn ? .tcp : .udp builder.endpointProtocols = [TunnelKitProvider.EndpointProtocol(socketType, port)] builder.cipher = .aes128cbc @@ -44,6 +44,7 @@ extension ViewController { let configuration = builder.build() return try! configuration.generatedTunnelProtocol( withBundleIdentifier: ViewController.bundleIdentifier, + appGroup: ViewController.appGroup, endpoint: endpoint ) } diff --git a/Demo/BasicTunnel-macOS/ViewController.swift b/Demo/BasicTunnel-macOS/ViewController.swift index c8b6670..08a269a 100644 --- a/Demo/BasicTunnel-macOS/ViewController.swift +++ b/Demo/BasicTunnel-macOS/ViewController.swift @@ -30,7 +30,7 @@ extension ViewController { password: password ) - var builder = TunnelKitProvider.ConfigurationBuilder(appGroup: ViewController.appGroup) + var builder = TunnelKitProvider.ConfigurationBuilder() // let socketType: TunnelKitProvider.SocketType = isTCP ? .tcp : .udp let socketType: TunnelKitProvider.SocketType = .udp builder.endpointProtocols = [TunnelKitProvider.EndpointProtocol(socketType, port)] @@ -45,6 +45,7 @@ extension ViewController { let configuration = builder.build() return try! configuration.generatedTunnelProtocol( withBundleIdentifier: ViewController.bundleIdentifier, + appGroup: ViewController.appGroup, endpoint: endpoint ) } diff --git a/TunnelKit.xcodeproj/project.pbxproj b/TunnelKit.xcodeproj/project.pbxproj index c2ce88e..262dc53 100644 --- a/TunnelKit.xcodeproj/project.pbxproj +++ b/TunnelKit.xcodeproj/project.pbxproj @@ -64,8 +64,6 @@ 0ECE352A212EB88E0040F253 /* CryptoContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECE3527212EB7770040F253 /* CryptoContainer.swift */; }; 0ED9C8642138139000621BA3 /* SessionProxy+CompressionFraming.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED9C8632138139000621BA3 /* SessionProxy+CompressionFraming.swift */; }; 0ED9C8652138139000621BA3 /* SessionProxy+CompressionFraming.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED9C8632138139000621BA3 /* SessionProxy+CompressionFraming.swift */; }; - 0EE7A79520F61EDC00B42E6A /* PacketMacros.h in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7A79420F61EDC00B42E6A /* PacketMacros.h */; }; - 0EE7A79620F61EDC00B42E6A /* PacketMacros.h in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7A79420F61EDC00B42E6A /* PacketMacros.h */; }; 0EE7A79820F6296F00B42E6A /* PacketMacros.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7A79720F6296F00B42E6A /* PacketMacros.m */; }; 0EE7A79920F6296F00B42E6A /* PacketMacros.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7A79720F6296F00B42E6A /* PacketMacros.m */; }; 0EE7A7A120F664AC00B42E6A /* DataPathEncryptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7A7A020F664AB00B42E6A /* DataPathEncryptionTests.swift */; }; @@ -79,6 +77,9 @@ 0EEC49E820B5F7F6008FEB91 /* ReplayProtector.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFEB4392006D3C800F81029 /* ReplayProtector.h */; }; 0EEC49E920B5F7F6008FEB91 /* TLSBox.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFEB4442006D3C800F81029 /* TLSBox.h */; }; 0EEC49EA20B5F7F6008FEB91 /* ZeroingData.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFEB4412006D3C800F81029 /* ZeroingData.h */; }; + 0EF5CF262141E142004FF1BD /* PacketMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EE7A79420F61EDC00B42E6A /* PacketMacros.h */; }; + 0EF5CF272141E15B004FF1BD /* PacketMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EE7A79420F61EDC00B42E6A /* PacketMacros.h */; }; + 0EF5CF282141E183004FF1BD /* CompressionFramingNative.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E245D6B2137F73600B012A2 /* CompressionFramingNative.h */; }; 0EFEB4552006D3C800F81029 /* SessionProxy+EncryptionBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB42A2006D3C800F81029 /* SessionProxy+EncryptionBridge.swift */; }; 0EFEB4562006D3C800F81029 /* SessionProxy+SessionKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB42B2006D3C800F81029 /* SessionProxy+SessionKey.swift */; }; 0EFEB4582006D3C800F81029 /* MSS.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFEB42D2006D3C800F81029 /* MSS.h */; }; @@ -172,7 +173,7 @@ /* Begin PBXFileReference section */ 0E07595C20EF6D1400F38FD8 /* CryptoCBC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CryptoCBC.m; sourceTree = ""; }; - 0E07596120EF733F00F38FD8 /* CryptoMacros.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CryptoMacros.h; sourceTree = ""; }; + 0E07596120EF733F00F38FD8 /* CryptoMacros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptoMacros.h; sourceTree = ""; }; 0E07596A20EF79AB00F38FD8 /* Encryption.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Encryption.h; sourceTree = ""; }; 0E07596D20EF79B400F38FD8 /* CryptoCBC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptoCBC.h; sourceTree = ""; }; 0E07597C20F0060E00F38FD8 /* CryptoAEAD.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptoAEAD.h; sourceTree = ""; }; @@ -216,7 +217,7 @@ 0EC1BBA720D7D803007C4C7B /* ConnectionStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionStrategy.swift; sourceTree = ""; }; 0ECE3527212EB7770040F253 /* CryptoContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CryptoContainer.swift; sourceTree = ""; }; 0ED9C8632138139000621BA3 /* SessionProxy+CompressionFraming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionProxy+CompressionFraming.swift"; sourceTree = ""; }; - 0EE7A79420F61EDC00B42E6A /* PacketMacros.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PacketMacros.h; sourceTree = ""; }; + 0EE7A79420F61EDC00B42E6A /* PacketMacros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PacketMacros.h; sourceTree = ""; }; 0EE7A79720F6296F00B42E6A /* PacketMacros.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PacketMacros.m; sourceTree = ""; }; 0EE7A79D20F6488400B42E6A /* DataPathEncryption.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DataPathEncryption.h; sourceTree = ""; }; 0EE7A7A020F664AB00B42E6A /* DataPathEncryptionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataPathEncryptionTests.swift; sourceTree = ""; }; @@ -509,6 +510,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 0EF5CF262141E142004FF1BD /* PacketMacros.h in Headers */, 0EFEB4642006D3C800F81029 /* ReplayProtector.h in Headers */, 0EFEB4612006D3C800F81029 /* Errors.h in Headers */, 0E07596E20EF79B400F38FD8 /* CryptoCBC.h in Headers */, @@ -529,6 +531,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 0EF5CF272141E15B004FF1BD /* PacketMacros.h in Headers */, 0EEC49E620B5F7F6008FEB91 /* MSS.h in Headers */, 0EEC49E520B5F7F6008FEB91 /* Errors.h in Headers */, 0E07596F20EF79B400F38FD8 /* CryptoCBC.h in Headers */, @@ -537,6 +540,7 @@ 0E07596C20EF79AB00F38FD8 /* Encryption.h in Headers */, 0EEC49E120B5F7EA008FEB91 /* Allocation.h in Headers */, 0EEC49E320B5F7F6008FEB91 /* DataPath.h in Headers */, + 0EF5CF282141E183004FF1BD /* CompressionFramingNative.h in Headers */, 0EEC49E820B5F7F6008FEB91 /* ReplayProtector.h in Headers */, 0EEC49E920B5F7F6008FEB91 /* TLSBox.h in Headers */, 0E07597F20F0060E00F38FD8 /* CryptoAEAD.h in Headers */, @@ -838,7 +842,6 @@ 0EFEB4AE2007625E00F81029 /* Keychain.swift in Sources */, 0EBBF3002085196000E36B40 /* NWTCPConnectionState+Description.swift in Sources */, 0EFEB4622006D3C800F81029 /* SecureRandom.swift in Sources */, - 0EE7A79520F61EDC00B42E6A /* PacketMacros.h in Sources */, 0EFEB45D2006D3C800F81029 /* CryptoBox.m in Sources */, 0EBBF2FA2085061600E36B40 /* NETCPInterface.swift in Sources */, 0E0C2125212ED29D008AB282 /* SessionError.swift in Sources */, @@ -891,7 +894,6 @@ 0EFEB4992006D7F300F81029 /* SessionProxy.swift in Sources */, 0EBBF3012085196000E36B40 /* NWTCPConnectionState+Description.swift in Sources */, 0EFEB4962006D7F300F81029 /* ProtocolMacros.swift in Sources */, - 0EE7A79620F61EDC00B42E6A /* PacketMacros.h in Sources */, 0EFEB48A2006D7C400F81029 /* TunnelKitProvider.swift in Sources */, 0E0C2126212ED29D008AB282 /* SessionError.swift in Sources */, 0EBBF2FB2085061600E36B40 /* NETCPInterface.swift in Sources */, diff --git a/TunnelKit/Sources/AppExtension/TunnelKitProvider+Configuration.swift b/TunnelKit/Sources/AppExtension/TunnelKitProvider+Configuration.swift index 3475f86..e3cc037 100644 --- a/TunnelKit/Sources/AppExtension/TunnelKitProvider+Configuration.swift +++ b/TunnelKit/Sources/AppExtension/TunnelKitProvider+Configuration.swift @@ -147,13 +147,6 @@ extension TunnelKitProvider { /// The way to create a `TunnelKitProvider.Configuration` object for the tunnel profile. public struct ConfigurationBuilder { - // MARK: App group - - /// The name of a shared app group. - public let appGroup: String - - // MARK: Tunnel parameters - /// Prefers resolved addresses over DNS resolution. `resolvedAddresses` must be set and non-empty. Default is `false`. /// /// - Seealso: `fallbackServerAddresses` @@ -204,17 +197,16 @@ extension TunnelKitProvider { /** Default initializer. - - - Parameter appGroup: The name of the app group in which the tunnel extension lives in. */ - public init(appGroup: String) { - self.appGroup = appGroup + public init() { prefersResolvedAddresses = false resolvedAddresses = nil endpointProtocols = [EndpointProtocol(.udp, 1194)] cipher = .aes128cbc digest = .sha1 ca = nil + clientCertificate = nil + clientKey = nil mtu = 1500 compressionFraming = .disabled renegotiatesAfterSeconds = nil @@ -226,9 +218,6 @@ extension TunnelKitProvider { fileprivate init(providerConfiguration: [String: Any]) throws { let S = Configuration.Keys.self - guard let appGroup = providerConfiguration[S.appGroup] as? String else { - throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.appGroup)]") - } guard let cipherAlgorithm = providerConfiguration[S.cipherAlgorithm] as? String, let cipher = SessionProxy.Cipher(rawValue: cipherAlgorithm) else { throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.cipherAlgorithm)]") } @@ -264,7 +253,6 @@ extension TunnelKitProvider { } endpointProtocols = try endpointProtocolsStrings.map { try EndpointProtocol.deserialized($0) } - self.appGroup = appGroup self.cipher = cipher self.digest = digest self.ca = ca @@ -301,7 +289,6 @@ extension TunnelKitProvider { */ public func build() -> Configuration { return Configuration( - appGroup: appGroup, prefersResolvedAddresses: prefersResolvedAddresses, resolvedAddresses: resolvedAddresses, endpointProtocols: endpointProtocols, @@ -354,9 +341,6 @@ extension TunnelKitProvider { static let debugLogFormat = "DebugLogFormat" } - /// - Seealso: `TunnelKitProvider.ConfigurationBuilder.appGroup` - public let appGroup: String - /// - Seealso: `TunnelKitProvider.ConfigurationBuilder.prefersResolvedAddresses` public let prefersResolvedAddresses: Bool @@ -401,19 +385,29 @@ extension TunnelKitProvider { // MARK: Shortcuts - var defaults: UserDefaults? { - return UserDefaults(suiteName: appGroup) - } - - var existingLog: [String]? { + func existingLog(in defaults: UserDefaults) -> [String]? { guard shouldDebug, let key = debugLogKey else { return nil } - return defaults?.array(forKey: key) as? [String] + return defaults.array(forKey: key) as? [String] } // MARK: API + /** + Parses the app group from a provider configuration map. + + - Parameter from: The map to parse. + - Returns: The parsed app group. + - Throws: `ProviderError.configuration` if `providerConfiguration` does not contain an app group. + */ + public static func appGroup(from providerConfiguration: [String: Any]) throws -> String { + guard let appGroup = providerConfiguration[Keys.appGroup] as? String else { + throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(Keys.appGroup)]") + } + return appGroup + } + /** Parses a new `TunnelKitProvider.Configuration` object from a provider configuration map. @@ -429,9 +423,10 @@ extension TunnelKitProvider { /** Returns a dictionary representation of this configuration for use with `NETunnelProviderProtocol.providerConfiguration`. + - Parameter appGroup: The name of the app group in which the tunnel extension lives in. - Returns: The dictionary representation of `self`. */ - public func generatedProviderConfiguration() -> [String: Any] { + public func generatedProviderConfiguration(appGroup: String) -> [String: Any] { let S = Keys.self var dict: [String: Any] = [ @@ -472,11 +467,12 @@ extension TunnelKitProvider { Generates a `NETunnelProviderProtocol` from this configuration. - Parameter bundleIdentifier: The provider bundle identifier required to locate the tunnel extension. + - Parameter appGroup: The name of the app group in which the tunnel extension lives in. - Parameter endpoint: The `TunnelKitProvider.AuthenticatedEndpoint` the tunnel will connect to. - Returns: The generated `NETunnelProviderProtocol` object. - Throws: `ProviderError.configuration` if unable to store the `endpoint.password` to the `appGroup` keychain. */ - public func generatedTunnelProtocol(withBundleIdentifier bundleIdentifier: String, endpoint: AuthenticatedEndpoint) throws -> NETunnelProviderProtocol { + public func generatedTunnelProtocol(withBundleIdentifier bundleIdentifier: String, appGroup: String, endpoint: AuthenticatedEndpoint) throws -> NETunnelProviderProtocol { let protocolConfiguration = NETunnelProviderProtocol() let keychain = Keychain(group: appGroup) @@ -490,7 +486,7 @@ extension TunnelKitProvider { protocolConfiguration.serverAddress = endpoint.hostname protocolConfiguration.username = endpoint.username protocolConfiguration.passwordReference = try? keychain.passwordReference(for: endpoint.username) - protocolConfiguration.providerConfiguration = generatedProviderConfiguration() + protocolConfiguration.providerConfiguration = generatedProviderConfiguration(appGroup: appGroup) return protocolConfiguration } @@ -536,7 +532,7 @@ extension TunnelKitProvider.Configuration: Equatable { - Returns: An editable `TunnelKitProvider.ConfigurationBuilder` initialized with this configuration. */ public func builder() -> TunnelKitProvider.ConfigurationBuilder { - var builder = TunnelKitProvider.ConfigurationBuilder(appGroup: appGroup) + var builder = TunnelKitProvider.ConfigurationBuilder() builder.endpointProtocols = endpointProtocols builder.cipher = cipher builder.digest = digest diff --git a/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift b/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift index d4b0c21..eae8246 100644 --- a/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift +++ b/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift @@ -92,10 +92,14 @@ open class TunnelKitProvider: NEPacketTunnelProvider { // MARK: Tunnel configuration + private var appGroup: String! + private var cfg: Configuration! private var strategy: ConnectionStrategy! + private lazy var defaults = UserDefaults(suiteName: appGroup) + // MARK: Internal state private var proxy: SessionProxy? @@ -121,6 +125,7 @@ open class TunnelKitProvider: NEPacketTunnelProvider { throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration") } try endpoint = AuthenticatedEndpoint(protocolConfiguration: tunnelProtocol) + try appGroup = Configuration.appGroup(from: providerConfiguration) try cfg = Configuration.parsed(from: providerConfiguration) } catch let e { var message: String? @@ -143,7 +148,7 @@ open class TunnelKitProvider: NEPacketTunnelProvider { strategy = ConnectionStrategy(hostname: endpoint.hostname, configuration: cfg) - if var existingLog = cfg.existingLog { + if let defaults = defaults, var existingLog = cfg.existingLog(in: defaults) { if let i = existingLog.index(of: logSeparator) { existingLog.removeFirst(i + 2) } @@ -565,7 +570,7 @@ extension TunnelKitProvider { private func flushLog() { log.debug("Flushing log...") - if let defaults = cfg.defaults, let key = cfg.debugLogKey { + if let defaults = defaults, let key = cfg.debugLogKey { memoryLog.flush(to: defaults, with: key) } } diff --git a/TunnelKitTests/AppExtensionTests.swift b/TunnelKitTests/AppExtensionTests.swift index 7c5c262..d02bfed 100644 --- a/TunnelKitTests/AppExtensionTests.swift +++ b/TunnelKitTests/AppExtensionTests.swift @@ -63,7 +63,7 @@ class AppExtensionTests: XCTestCase { password: "bar" ) - builder = TunnelKitProvider.ConfigurationBuilder(appGroup: appGroup) + builder = TunnelKitProvider.ConfigurationBuilder() XCTAssertNotNil(builder) builder.cipher = .aes128cbc @@ -71,7 +71,7 @@ class AppExtensionTests: XCTestCase { builder.ca = CryptoContainer(pem: "abcdef") cfg = builder.build() - let proto = try? cfg.generatedTunnelProtocol(withBundleIdentifier: identifier, endpoint: endpoint) + let proto = try? cfg.generatedTunnelProtocol(withBundleIdentifier: identifier, appGroup: appGroup, endpoint: endpoint) XCTAssertNotNil(proto) XCTAssertEqual(proto?.providerBundleIdentifier, identifier) @@ -84,7 +84,7 @@ class AppExtensionTests: XCTestCase { } let K = TunnelKitProvider.Configuration.Keys.self - XCTAssertEqual(proto?.providerConfiguration?[K.appGroup] as? String, cfg.appGroup) + 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)