Split endpoint and credentials
Basically drop AuthenticatedEndpoint.
This commit is contained in:
parent
40b733db57
commit
98c5a015f3
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)")
|
||||||
|
|
Loading…
Reference in New Issue