Split endpoint and credentials

Basically drop AuthenticatedEndpoint.
This commit is contained in:
Davide De Rosa 2018-10-05 19:13:59 +02:00
parent 40b733db57
commit 98c5a015f3
5 changed files with 60 additions and 72 deletions

View File

@ -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. /// The way to create a `TunnelKitProvider.Configuration` object for the tunnel profile.
public struct ConfigurationBuilder { public struct ConfigurationBuilder {
@ -477,16 +442,17 @@ extension TunnelKitProvider {
- Parameter bundleIdentifier: The provider bundle identifier required to locate the tunnel extension. - 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 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. - 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() let protocolConfiguration = NETunnelProviderProtocol()
protocolConfiguration.providerBundleIdentifier = bundleIdentifier protocolConfiguration.providerBundleIdentifier = bundleIdentifier
protocolConfiguration.serverAddress = endpoint.hostname protocolConfiguration.serverAddress = hostname
if let username = endpoint.username, let password = endpoint.password { if let username = credentials?.username, let password = credentials?.password {
let keychain = Keychain(group: appGroup) let keychain = Keychain(group: appGroup)
do { do {
try keychain.set(password: password, for: username, label: Bundle.main.bundleIdentifier) try keychain.set(password: password, for: username, label: Bundle.main.bundleIdentifier)

View File

@ -116,24 +116,26 @@ open class TunnelKitProvider: NEPacketTunnelProvider {
/// :nodoc: /// :nodoc:
open override func startTunnel(options: [String : NSObject]? = nil, completionHandler: @escaping (Error?) -> Void) { open override func startTunnel(options: [String : NSObject]? = nil, completionHandler: @escaping (Error?) -> Void) {
let endpoint: AuthenticatedEndpoint
// required configuration
let hostname: String
do { do {
guard let tunnelProtocol = protocolConfiguration as? NETunnelProviderProtocol else { guard let tunnelProtocol = protocolConfiguration as? NETunnelProviderProtocol else {
throw ProviderError.configuration(field: "protocolConfiguration") throw ProviderError.configuration(field: "protocolConfiguration")
} }
guard let serverAddress = tunnelProtocol.serverAddress else {
throw ProviderError.configuration(field: "protocolConfiguration.serverAddress")
}
guard let providerConfiguration = tunnelProtocol.providerConfiguration else { guard let providerConfiguration = tunnelProtocol.providerConfiguration else {
throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration") throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration")
} }
try endpoint = AuthenticatedEndpoint(protocolConfiguration: tunnelProtocol) hostname = serverAddress
try appGroup = Configuration.appGroup(from: providerConfiguration) try appGroup = Configuration.appGroup(from: providerConfiguration)
try cfg = Configuration.parsed(from: providerConfiguration) try cfg = Configuration.parsed(from: providerConfiguration)
} catch let e { } catch let e {
var message: String? var message: String?
if let te = e as? ProviderError { if let te = e as? ProviderError {
switch te { switch te {
case .credentials(let field):
message = "Tunnel credentials unavailable: \(field)"
case .configuration(let field): case .configuration(let field):
message = "Tunnel configuration incomplete: \(field)" message = "Tunnel configuration incomplete: \(field)"
@ -146,7 +148,16 @@ open class TunnelKitProvider: NEPacketTunnelProvider {
return 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 defaults = defaults, var existingLog = cfg.existingLog(in: defaults) {
if let i = existingLog.index(of: logSeparator) { if let i = existingLog.index(of: logSeparator) {
@ -211,8 +222,7 @@ open class TunnelKitProvider: NEPacketTunnelProvider {
// log.info("Temporary CA is stored to: \(caPath)") // log.info("Temporary CA is stored to: \(caPath)")
var sessionConfiguration = SessionProxy.ConfigurationBuilder(caPath: caPath) var sessionConfiguration = SessionProxy.ConfigurationBuilder(caPath: caPath)
sessionConfiguration.username = endpoint.username sessionConfiguration.credentials = credentials
sessionConfiguration.password = endpoint.password
sessionConfiguration.cipher = cfg.cipher sessionConfiguration.cipher = cfg.cipher
sessionConfiguration.digest = cfg.digest sessionConfiguration.digest = cfg.digest
sessionConfiguration.clientCertificatePath = clientCertificatePath sessionConfiguration.clientCertificatePath = clientCertificatePath

View File

@ -39,6 +39,29 @@ import Foundation
extension SessionProxy { 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. /// The available encryption algorithms.
public enum Cipher: String, Codable, CustomStringConvertible { public enum Cipher: String, Codable, CustomStringConvertible {
@ -112,11 +135,8 @@ extension SessionProxy {
/// The way to create a `SessionProxy.Configuration` object for a `SessionProxy`. /// The way to create a `SessionProxy.Configuration` object for a `SessionProxy`.
public struct ConfigurationBuilder { public struct ConfigurationBuilder {
/// An username. /// The credentials.
public var username: String? public var credentials: Credentials?
/// A password.
public var password: String?
/// The cipher algorithm for data encryption. /// The cipher algorithm for data encryption.
public var cipher: Cipher public var cipher: Cipher
@ -144,8 +164,7 @@ extension SessionProxy {
/// :nodoc: /// :nodoc:
public init(caPath: String) { public init(caPath: String) {
username = nil credentials = nil
password = nil
cipher = .aes128cbc cipher = .aes128cbc
digest = .sha1 digest = .sha1
self.caPath = caPath self.caPath = caPath
@ -163,8 +182,7 @@ extension SessionProxy {
*/ */
public func build() -> Configuration { public func build() -> Configuration {
return Configuration( return Configuration(
username: username, credentials: credentials,
password: password,
cipher: cipher, cipher: cipher,
digest: digest, digest: digest,
caPath: caPath, caPath: caPath,
@ -180,11 +198,8 @@ extension SessionProxy {
/// The immutable configuration for `SessionProxy`. /// The immutable configuration for `SessionProxy`.
public struct Configuration: Codable { public struct Configuration: Codable {
/// - Seealso: `SessionProxy.ConfigurationBuilder.username` /// - Seealso: `SessionProxy.ConfigurationBuilder.credentials`
public let username: String? public let credentials: Credentials?
/// - Seealso: `SessionProxy.ConfigurationBuilder.password`
public let password: String?
/// - Seealso: `SessionProxy.ConfigurationBuilder.cipher` /// - Seealso: `SessionProxy.ConfigurationBuilder.cipher`
public let cipher: Cipher public let cipher: Cipher

View File

@ -583,7 +583,7 @@ public class SessionProxy {
negotiationKey.controlState = .preAuth negotiationKey.controlState = .preAuth
do { 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) try authenticator?.putAuth(into: negotiationKey.tls)
} catch let e { } catch let e {
deferStop(.shutdown, e) deferStop(.shutdown, e)

View File

@ -57,11 +57,8 @@ class AppExtensionTests: XCTestCase {
let identifier = "com.example.Provider" let identifier = "com.example.Provider"
let appGroup = "group.com.algoritmico.TunnelKit" let appGroup = "group.com.algoritmico.TunnelKit"
let endpoint = TunnelKitProvider.AuthenticatedEndpoint( let hostname = "example.com"
hostname: "example.com", let credentials = SessionProxy.Credentials("foo", "bar")
username: "foo",
password: "bar"
)
builder = TunnelKitProvider.ConfigurationBuilder(ca: CryptoContainer(pem: "abcdef")) builder = TunnelKitProvider.ConfigurationBuilder(ca: CryptoContainer(pem: "abcdef"))
XCTAssertNotNil(builder) XCTAssertNotNil(builder)
@ -70,13 +67,13 @@ class AppExtensionTests: XCTestCase {
builder.digest = .sha256 builder.digest = .sha256
cfg = builder.build() 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) XCTAssertNotNil(proto)
XCTAssertEqual(proto?.providerBundleIdentifier, identifier) XCTAssertEqual(proto?.providerBundleIdentifier, identifier)
XCTAssertEqual(proto?.serverAddress, endpoint.hostname) XCTAssertEqual(proto?.serverAddress, hostname)
XCTAssertEqual(proto?.username, endpoint.username) XCTAssertEqual(proto?.username, credentials.username)
XCTAssertEqual(proto?.passwordReference, try? Keychain(group: appGroup).passwordReference(for: endpoint.username)) XCTAssertEqual(proto?.passwordReference, try? Keychain(group: appGroup).passwordReference(for: credentials.username))
if let pc = proto?.providerConfiguration { if let pc = proto?.providerConfiguration {
print("\(pc)") print("\(pc)")