diff --git a/TunnelKit/Sources/AppExtension/TunnelKitProvider+Configuration.swift b/TunnelKit/Sources/AppExtension/TunnelKitProvider+Configuration.swift index a2a229f..7fc3fee 100644 --- a/TunnelKit/Sources/AppExtension/TunnelKitProvider+Configuration.swift +++ b/TunnelKit/Sources/AppExtension/TunnelKitProvider+Configuration.swift @@ -105,41 +105,6 @@ extension TunnelKitProvider { } } - /// Encapsulates an endpoint along with the authentication credentials. - public struct AuthenticatedEndpoint { - - /// The remote hostname or IP address. - public let hostname: String - - /// The username. - public let username: String? - - /// The password. - public let password: String? - - /// :nodoc: - public init(hostname: String, username: String, password: String) { - self.hostname = hostname - self.username = username - self.password = password - } - - init(protocolConfiguration: NEVPNProtocol) throws { - guard let hostname = protocolConfiguration.serverAddress else { - throw ProviderError.configuration(field: "protocolConfiguration.serverAddress") - } - - self.hostname = hostname - if let username = protocolConfiguration.username, let passwordReference = protocolConfiguration.passwordReference { - self.username = username - password = try? Keychain.password(for: username, reference: passwordReference) - } else { - username = nil - password = nil - } - } - } - /// The way to create a `TunnelKitProvider.Configuration` object for the tunnel profile. public struct ConfigurationBuilder { @@ -477,16 +442,17 @@ extension TunnelKitProvider { - 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. + - Parameter hostname: The hostname the tunnel will connect to. + - Parameter credentials: The optional credentials to authenticate with. - Returns: The generated `NETunnelProviderProtocol` object. - - Throws: `ProviderError.configuration` if unable to store the `endpoint.password` to the `appGroup` keychain. + - Throws: `ProviderError.credentials` if unable to store `credentials.password` to the `appGroup` keychain. */ - public func generatedTunnelProtocol(withBundleIdentifier bundleIdentifier: String, appGroup: String, endpoint: AuthenticatedEndpoint) throws -> NETunnelProviderProtocol { + public func generatedTunnelProtocol(withBundleIdentifier bundleIdentifier: String, appGroup: String, hostname: String, credentials: SessionProxy.Credentials? = nil) throws -> NETunnelProviderProtocol { let protocolConfiguration = NETunnelProviderProtocol() protocolConfiguration.providerBundleIdentifier = bundleIdentifier - protocolConfiguration.serverAddress = endpoint.hostname - if let username = endpoint.username, let password = endpoint.password { + protocolConfiguration.serverAddress = hostname + if let username = credentials?.username, let password = credentials?.password { let keychain = Keychain(group: appGroup) do { try keychain.set(password: password, for: username, label: Bundle.main.bundleIdentifier) diff --git a/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift b/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift index d452af6..184ab25 100644 --- a/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift +++ b/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift @@ -116,24 +116,26 @@ open class TunnelKitProvider: NEPacketTunnelProvider { /// :nodoc: open override func startTunnel(options: [String : NSObject]? = nil, completionHandler: @escaping (Error?) -> Void) { - let endpoint: AuthenticatedEndpoint + + // required configuration + let hostname: String do { guard let tunnelProtocol = protocolConfiguration as? NETunnelProviderProtocol else { throw ProviderError.configuration(field: "protocolConfiguration") } + guard let serverAddress = tunnelProtocol.serverAddress else { + throw ProviderError.configuration(field: "protocolConfiguration.serverAddress") + } guard let providerConfiguration = tunnelProtocol.providerConfiguration else { throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration") } - try endpoint = AuthenticatedEndpoint(protocolConfiguration: tunnelProtocol) + hostname = serverAddress try appGroup = Configuration.appGroup(from: providerConfiguration) try cfg = Configuration.parsed(from: providerConfiguration) } catch let e { var message: String? if let te = e as? ProviderError { switch te { - case .credentials(let field): - message = "Tunnel credentials unavailable: \(field)" - case .configuration(let field): message = "Tunnel configuration incomplete: \(field)" @@ -146,7 +148,16 @@ open class TunnelKitProvider: NEPacketTunnelProvider { return } - strategy = ConnectionStrategy(hostname: endpoint.hostname, configuration: cfg) + // optional credentials + let credentials: SessionProxy.Credentials? + if let username = protocolConfiguration.username, let passwordReference = protocolConfiguration.passwordReference, + let password = try? Keychain.password(for: username, reference: passwordReference) { + credentials = SessionProxy.Credentials(username, password) + } else { + credentials = nil + } + + strategy = ConnectionStrategy(hostname: hostname, configuration: cfg) if let defaults = defaults, var existingLog = cfg.existingLog(in: defaults) { if let i = existingLog.index(of: logSeparator) { @@ -211,8 +222,7 @@ open class TunnelKitProvider: NEPacketTunnelProvider { // log.info("Temporary CA is stored to: \(caPath)") var sessionConfiguration = SessionProxy.ConfigurationBuilder(caPath: caPath) - sessionConfiguration.username = endpoint.username - sessionConfiguration.password = endpoint.password + sessionConfiguration.credentials = credentials sessionConfiguration.cipher = cfg.cipher sessionConfiguration.digest = cfg.digest sessionConfiguration.clientCertificatePath = clientCertificatePath diff --git a/TunnelKit/Sources/Core/SessionProxy+Configuration.swift b/TunnelKit/Sources/Core/SessionProxy+Configuration.swift index 81ff4f5..7e72abf 100644 --- a/TunnelKit/Sources/Core/SessionProxy+Configuration.swift +++ b/TunnelKit/Sources/Core/SessionProxy+Configuration.swift @@ -38,6 +38,29 @@ import Foundation extension SessionProxy { + + /// A pair of credentials for authentication. + public struct Credentials: Codable, Equatable { + + /// The username. + public let username: String + + /// The password. + public let password: String + + /// :nodoc + public init(_ username: String, _ password: String) { + self.username = username + self.password = password + } + + // MARK: Equatable + + /// :nodoc: + public static func ==(lhs: Credentials, rhs: Credentials) -> Bool { + return (lhs.username == rhs.username) && (lhs.password == rhs.password) + } + } /// The available encryption algorithms. public enum Cipher: String, Codable, CustomStringConvertible { @@ -112,11 +135,8 @@ extension SessionProxy { /// The way to create a `SessionProxy.Configuration` object for a `SessionProxy`. public struct ConfigurationBuilder { - /// An username. - public var username: String? - - /// A password. - public var password: String? + /// The credentials. + public var credentials: Credentials? /// The cipher algorithm for data encryption. public var cipher: Cipher @@ -144,8 +164,7 @@ extension SessionProxy { /// :nodoc: public init(caPath: String) { - username = nil - password = nil + credentials = nil cipher = .aes128cbc digest = .sha1 self.caPath = caPath @@ -163,8 +182,7 @@ extension SessionProxy { */ public func build() -> Configuration { return Configuration( - username: username, - password: password, + credentials: credentials, cipher: cipher, digest: digest, caPath: caPath, @@ -180,11 +198,8 @@ extension SessionProxy { /// The immutable configuration for `SessionProxy`. public struct Configuration: Codable { - /// - Seealso: `SessionProxy.ConfigurationBuilder.username` - public let username: String? - - /// - Seealso: `SessionProxy.ConfigurationBuilder.password` - public let password: String? + /// - Seealso: `SessionProxy.ConfigurationBuilder.credentials` + public let credentials: Credentials? /// - Seealso: `SessionProxy.ConfigurationBuilder.cipher` public let cipher: Cipher diff --git a/TunnelKit/Sources/Core/SessionProxy.swift b/TunnelKit/Sources/Core/SessionProxy.swift index a3c9d47..3c073cf 100644 --- a/TunnelKit/Sources/Core/SessionProxy.swift +++ b/TunnelKit/Sources/Core/SessionProxy.swift @@ -583,7 +583,7 @@ public class SessionProxy { negotiationKey.controlState = .preAuth do { - authenticator = try Authenticator(configuration.username, pushReply?.authToken ?? configuration.password) + authenticator = try Authenticator(configuration.credentials?.username, pushReply?.authToken ?? configuration.credentials?.password) try authenticator?.putAuth(into: negotiationKey.tls) } catch let e { deferStop(.shutdown, e) diff --git a/TunnelKitTests/AppExtensionTests.swift b/TunnelKitTests/AppExtensionTests.swift index 49445d1..4e5ae20 100644 --- a/TunnelKitTests/AppExtensionTests.swift +++ b/TunnelKitTests/AppExtensionTests.swift @@ -57,11 +57,8 @@ class AppExtensionTests: XCTestCase { let identifier = "com.example.Provider" let appGroup = "group.com.algoritmico.TunnelKit" - let endpoint = TunnelKitProvider.AuthenticatedEndpoint( - hostname: "example.com", - username: "foo", - password: "bar" - ) + let hostname = "example.com" + let credentials = SessionProxy.Credentials("foo", "bar") builder = TunnelKitProvider.ConfigurationBuilder(ca: CryptoContainer(pem: "abcdef")) XCTAssertNotNil(builder) @@ -70,13 +67,13 @@ class AppExtensionTests: XCTestCase { builder.digest = .sha256 cfg = builder.build() - let proto = try? cfg.generatedTunnelProtocol(withBundleIdentifier: identifier, appGroup: appGroup, endpoint: endpoint) + let proto = try? cfg.generatedTunnelProtocol(withBundleIdentifier: identifier, appGroup: appGroup, hostname: hostname, credentials: credentials) XCTAssertNotNil(proto) XCTAssertEqual(proto?.providerBundleIdentifier, identifier) - XCTAssertEqual(proto?.serverAddress, endpoint.hostname) - XCTAssertEqual(proto?.username, endpoint.username) - XCTAssertEqual(proto?.passwordReference, try? Keychain(group: appGroup).passwordReference(for: endpoint.username)) + XCTAssertEqual(proto?.serverAddress, hostname) + XCTAssertEqual(proto?.username, credentials.username) + XCTAssertEqual(proto?.passwordReference, try? Keychain(group: appGroup).passwordReference(for: credentials.username)) if let pc = proto?.providerConfiguration { print("\(pc)")