diff --git a/Demo/Demo/OpenVPNPacketTunnelProvider.swift b/Demo/Demo/OpenVPNPacketTunnelProvider.swift index 96b5030..35393c0 100644 --- a/Demo/Demo/OpenVPNPacketTunnelProvider.swift +++ b/Demo/Demo/OpenVPNPacketTunnelProvider.swift @@ -27,7 +27,7 @@ import Foundation import TunnelKitOpenVPNAppExtension class PacketTunnelProvider: OpenVPNTunnelProvider { - override func startTunnel(options: [String : NSObject]? = nil) async throws { + override func startTunnel(options: [String: NSObject]? = nil) async throws { dataCountInterval = 3 try await super.startTunnel(options: options) } diff --git a/Demo/Demo/iOS/AppDelegate.swift b/Demo/Demo/iOS/AppDelegate.swift index 43da5d3..dce65af 100644 --- a/Demo/Demo/iOS/AppDelegate.swift +++ b/Demo/Demo/iOS/AppDelegate.swift @@ -31,38 +31,37 @@ private let log = SwiftyBeaver.self @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { - + var window: UIWindow? - - + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let logDestination = ConsoleDestination() logDestination.minLevel = .debug logDestination.format = "$DHH:mm:ss$d $L $N.$F:$l - $M" log.addDestination(logDestination) - + // Override point for customization after application launch. return true } - + func applicationWillResignActive(_ application: UIApplication) { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. } - + func applicationDidEnterBackground(_ application: UIApplication) { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } - + func applicationWillEnterForeground(_ application: UIApplication) { // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. } - + func applicationDidBecomeActive(_ application: UIApplication) { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } - + func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } diff --git a/Demo/Demo/iOS/OpenVPNViewController.swift b/Demo/Demo/iOS/OpenVPNViewController.swift index ef91919..b736a17 100644 --- a/Demo/Demo/iOS/OpenVPNViewController.swift +++ b/Demo/Demo/iOS/OpenVPNViewController.swift @@ -34,39 +34,39 @@ private let tunnelIdentifier = "com.algoritmico.ios.TunnelKit.Demo.OpenVPN.Tunne class OpenVPNViewController: UIViewController { @IBOutlet var textUsername: UITextField! - + @IBOutlet var textPassword: UITextField! - + @IBOutlet var textServer: UITextField! - + @IBOutlet var textDomain: UITextField! - + @IBOutlet var textPort: UITextField! - + @IBOutlet var switchTCP: UISwitch! - + @IBOutlet var buttonConnection: UIButton! @IBOutlet var textLog: UITextView! private let vpn = NetworkExtensionVPN() - + private var vpnStatus: VPNStatus = .disconnected private let keychain = Keychain(group: appGroup) - + private var cfg: OpenVPN.ProviderConfiguration? - + override func viewDidLoad() { super.viewDidLoad() - + textServer.text = "nl-free-50" textDomain.text = "protonvpn.net" textPort.text = "80" switchTCP.isOn = false textUsername.text = "" textPassword.text = "" - + NotificationCenter.default.addObserver( self, selector: #selector(VPNStatusDidChange(notification:)), @@ -86,20 +86,20 @@ class OpenVPNViewController: UIViewController { // testFetchRef() } - + @IBAction func connectionClicked(_ sender: Any) { switch vpnStatus { case .disconnected: connect() - + case .connected, .connecting, .disconnecting: disconnect() } } - + @IBAction func tcpClicked(_ sender: Any) { } - + func connect() { let server = textServer.text! let domain = textDomain.text! @@ -136,7 +136,7 @@ class OpenVPNViewController: UIViewController { ) } } - + func disconnect() { Task { await vpn.disconnect() @@ -152,20 +152,20 @@ class OpenVPNViewController: UIViewController { } textLog.text = try? String(contentsOf: url) } - + func updateButton() { switch vpnStatus { case .connected, .connecting: buttonConnection.setTitle("Disconnect", for: .normal) - + case .disconnected: buttonConnection.setTitle("Connect", for: .normal) - + case .disconnecting: buttonConnection.setTitle("Disconnecting", for: .normal) } } - + @objc private func VPNStatusDidChange(notification: Notification) { vpnStatus = notification.vpnStatus print("VPNStatusDidChange: \(vpnStatus)") diff --git a/Demo/Demo/iOS/WireGuardViewController.swift b/Demo/Demo/iOS/WireGuardViewController.swift index 117e211..c67cb51 100644 --- a/Demo/Demo/iOS/WireGuardViewController.swift +++ b/Demo/Demo/iOS/WireGuardViewController.swift @@ -33,24 +33,24 @@ private let tunnelIdentifier = "com.algoritmico.ios.TunnelKit.Demo.WireGuard.Tun class WireGuardViewController: UIViewController { @IBOutlet var textClientPrivateKey: UITextField! - + @IBOutlet var textAddress: UITextField! - + @IBOutlet var textServerPublicKey: UITextField! - + @IBOutlet var textServerAddress: UITextField! - + @IBOutlet var textServerPort: UITextField! - + @IBOutlet var buttonConnection: UIButton! - + private let vpn = NetworkExtensionVPN() - + private var vpnStatus: VPNStatus = .disconnected override func viewDidLoad() { super.viewDidLoad() - + textClientPrivateKey.placeholder = "client private key" textAddress.placeholder = "client address" textServerPublicKey.placeholder = "server public key" @@ -81,7 +81,7 @@ class WireGuardViewController: UIViewController { switch vpnStatus { case .disconnected: connect() - + case .connected, .connecting, .disconnecting: disconnect() } @@ -116,7 +116,7 @@ class WireGuardViewController: UIViewController { ) } } - + func disconnect() { Task { await vpn.disconnect() @@ -127,15 +127,15 @@ class WireGuardViewController: UIViewController { switch vpnStatus { case .connected, .connecting: buttonConnection.setTitle("Disconnect", for: .normal) - + case .disconnected: buttonConnection.setTitle("Connect", for: .normal) - + case .disconnecting: buttonConnection.setTitle("Disconnecting", for: .normal) } } - + @objc private func VPNStatusDidChange(notification: Notification) { vpnStatus = notification.vpnStatus print("VPNStatusDidChange: \(notification.vpnStatus)") diff --git a/Demo/Demo/macOS/AppDelegate.swift b/Demo/Demo/macOS/AppDelegate.swift index f6d460a..75157df 100644 --- a/Demo/Demo/macOS/AppDelegate.swift +++ b/Demo/Demo/macOS/AppDelegate.swift @@ -31,8 +31,6 @@ private let log = SwiftyBeaver.self @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { - - func applicationDidFinishLaunching(_ aNotification: Notification) { let logDestination = ConsoleDestination() logDestination.minLevel = .debug @@ -46,6 +44,4 @@ class AppDelegate: NSObject, NSApplicationDelegate { // Insert code here to tear down your application } - } - diff --git a/Demo/Demo/macOS/OpenVPNViewController.swift b/Demo/Demo/macOS/OpenVPNViewController.swift index 3479326..6a405c8 100644 --- a/Demo/Demo/macOS/OpenVPNViewController.swift +++ b/Demo/Demo/macOS/OpenVPNViewController.swift @@ -34,34 +34,34 @@ private let tunnelIdentifier = "com.algoritmico.macos.TunnelKit.Demo.OpenVPN.Tun class OpenVPNViewController: NSViewController { @IBOutlet var textUsername: NSTextField! - + @IBOutlet var textPassword: NSTextField! - + @IBOutlet var textServer: NSTextField! - + @IBOutlet var textDomain: NSTextField! - + @IBOutlet var textPort: NSTextField! - + @IBOutlet var buttonConnection: NSButton! - + private let vpn = NetworkExtensionVPN() - + private var vpnStatus: VPNStatus = .disconnected private let keychain = Keychain(group: appGroup) - + private var cfg: OpenVPN.ProviderConfiguration? - + override func viewDidLoad() { super.viewDidLoad() - + textServer.stringValue = "nl-free-50" textDomain.stringValue = "protonvpn.net" textPort.stringValue = "80" textUsername.stringValue = "" textPassword.stringValue = "" - + NotificationCenter.default.addObserver( self, selector: #selector(VPNStatusDidChange(notification:)), @@ -78,20 +78,20 @@ class OpenVPNViewController: NSViewController { Task { await vpn.prepare() } - + // testFetchRef() } - + @IBAction func connectionClicked(_ sender: Any) { switch vpnStatus { case .disconnected: connect() - + case .connected, .connecting, .disconnecting: disconnect() } } - + func connect() { let server = textServer.stringValue let domain = textDomain.stringValue @@ -127,7 +127,7 @@ class OpenVPNViewController: NSViewController { ) } } - + func disconnect() { Task { await vpn.disconnect() @@ -138,15 +138,15 @@ class OpenVPNViewController: NSViewController { switch vpnStatus { case .connected, .connecting: buttonConnection.title = "Disconnect" - + case .disconnected: buttonConnection.title = "Connect" - + case .disconnecting: buttonConnection.title = "Disconnecting" } } - + @objc private func VPNStatusDidChange(notification: Notification) { vpnStatus = notification.vpnStatus print("VPNStatusDidChange: \(vpnStatus)") @@ -175,4 +175,3 @@ class OpenVPNViewController: NSViewController { // print("\(username) -> \(fetchedPassword)") // } } - diff --git a/Demo/Demo/macOS/WireGuardViewController.swift b/Demo/Demo/macOS/WireGuardViewController.swift index e3087ff..f23f207 100644 --- a/Demo/Demo/macOS/WireGuardViewController.swift +++ b/Demo/Demo/macOS/WireGuardViewController.swift @@ -33,24 +33,24 @@ private let tunnelIdentifier = "com.algoritmico.macos.TunnelKit.Demo.WireGuard.T class WireGuardViewController: NSViewController { @IBOutlet var textClientPrivateKey: NSTextField! - + @IBOutlet var textAddress: NSTextField! - + @IBOutlet var textServerPublicKey: NSTextField! - + @IBOutlet var textServerAddress: NSTextField! - + @IBOutlet var textServerPort: NSTextField! - + @IBOutlet var buttonConnection: NSButton! - + private let vpn = NetworkExtensionVPN() private var vpnStatus: VPNStatus = .disconnected override func viewDidLoad() { super.viewDidLoad() - + textClientPrivateKey.placeholderString = "client private key" textAddress.placeholderString = "client address" textServerPublicKey.placeholderString = "server public key" @@ -81,7 +81,7 @@ class WireGuardViewController: NSViewController { switch vpnStatus { case .disconnected: connect() - + case .connected, .connecting, .disconnecting: disconnect() } @@ -116,7 +116,7 @@ class WireGuardViewController: NSViewController { ) } } - + func disconnect() { Task { await vpn.disconnect() @@ -127,15 +127,15 @@ class WireGuardViewController: NSViewController { switch vpnStatus { case .connected, .connecting: buttonConnection.title = "Disconnect" - + case .disconnected: buttonConnection.title = "Connect" - + case .disconnecting: buttonConnection.title = "Disconnecting" } } - + @objc private func VPNStatusDidChange(notification: Notification) { vpnStatus = notification.vpnStatus print("VPNStatusDidChange: \(vpnStatus)") diff --git a/Demo/Host/AppDelegate.swift b/Demo/Host/AppDelegate.swift index 963e7b6..d9d0145 100644 --- a/Demo/Host/AppDelegate.swift +++ b/Demo/Host/AppDelegate.swift @@ -41,7 +41,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true @@ -69,6 +68,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } - } - diff --git a/Demo/Host/ViewController.swift b/Demo/Host/ViewController.swift index 71d1f7e..5ecce47 100644 --- a/Demo/Host/ViewController.swift +++ b/Demo/Host/ViewController.swift @@ -48,6 +48,4 @@ class ViewController: UIViewController { // Dispose of any resources that can be recreated. } - } - diff --git a/Demo/TunnelKit.xcodeproj/project.pbxproj b/Demo/TunnelKit.xcodeproj/project.pbxproj index 7d930c1..c2e3052 100644 --- a/Demo/TunnelKit.xcodeproj/project.pbxproj +++ b/Demo/TunnelKit.xcodeproj/project.pbxproj @@ -418,6 +418,7 @@ 0E05416725A232FD00EFC5FF /* Resources */, 0E0541AD25A2343500EFC5FF /* Embed App Extensions */, 0E05438525A240E400EFC5FF /* Embed Frameworks */, + 0EB5A56B29F1C9C8005313B3 /* SwiftLint */, ); buildRules = ( ); @@ -463,6 +464,7 @@ 0E05422A25A236EB00EFC5FF /* Resources */, 0E05428425A239C600EFC5FF /* Embed App Extensions */, 0E05438825A240E900EFC5FF /* Embed Frameworks */, + 0EB5A56A29F1C8FC005313B3 /* SwiftLint */, ); buildRules = ( ); @@ -700,6 +702,45 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 0EB5A56A29F1C8FC005313B3 /* SwiftLint */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = SwiftLint; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "PATH=\"/opt/homebrew/bin:${PATH}\"\nif which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + }; + 0EB5A56B29F1C9C8005313B3 /* SwiftLint */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = SwiftLint; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "PATH=\"/opt/homebrew/bin:${PATH}\"\nif which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 0E05416525A232FD00EFC5FF /* Sources */ = { isa = PBXSourcesBuildPhase; diff --git a/Package.swift b/Package.swift index d99a99f..d9beaa2 100644 --- a/Package.swift +++ b/Package.swift @@ -188,6 +188,6 @@ let package = Package( dependencies: [ "TunnelKitCore", "TunnelKitLZO" - ]), + ]) ] ) diff --git a/Sources/TunnelKitAppExtension/GenericSocket.swift b/Sources/TunnelKitAppExtension/GenericSocket.swift index 9818c71..c393001 100644 --- a/Sources/TunnelKitAppExtension/GenericSocket.swift +++ b/Sources/TunnelKitAppExtension/GenericSocket.swift @@ -67,10 +67,10 @@ public protocol GenericSocket { /// The address of the remote endpoint. var remoteAddress: String? { get } - + /// `true` if the socket has a better path. var hasBetterPath: Bool { get } - + /// `true` if the socket was shut down. var isShutdown: Bool { get } @@ -94,7 +94,7 @@ public protocol GenericSocket { Shuts down the socket **/ func shutdown() - + /** Returns an upgraded socket if available (e.g. when a better path exists). diff --git a/Sources/TunnelKitAppExtension/InterfaceObserver.swift b/Sources/TunnelKitAppExtension/InterfaceObserver.swift index eacecd1..5c78c3c 100644 --- a/Sources/TunnelKitAppExtension/InterfaceObserver.swift +++ b/Sources/TunnelKitAppExtension/InterfaceObserver.swift @@ -52,9 +52,9 @@ public class InterfaceObserver: NSObject { public static let didDetectWifiChange = Notification.Name("InterfaceObserverDidDetectWifiChange") private var queue: DispatchQueue? - + private var timer: DispatchSourceTimer? - + private var lastWifiName: String? /** @@ -89,7 +89,7 @@ public class InterfaceObserver: NSObject { self.fireWifiChange(withSSID: $0) } } - + private func fireWifiChange(withSSID ssid: String?) { if ssid != lastWifiName { if let current = ssid { diff --git a/Sources/TunnelKitAppExtension/MemoryDestination.swift b/Sources/TunnelKitAppExtension/MemoryDestination.swift index d14d12e..1c1d720 100644 --- a/Sources/TunnelKitAppExtension/MemoryDestination.swift +++ b/Sources/TunnelKitAppExtension/MemoryDestination.swift @@ -48,7 +48,7 @@ public class MemoryDestination: BaseDestination, CustomStringConvertible { super.init() asynchronously = false } - + /** Starts logging. Optionally prepend an array of lines. @@ -71,7 +71,7 @@ public class MemoryDestination: BaseDestination, CustomStringConvertible { try? content.write(to: url, atomically: true, encoding: .utf8) } } - + // MARK: BaseDestination // XXX: executed in SwiftyBeaver queue. DO NOT invoke execute* here (sync in sync would crash otherwise) @@ -89,7 +89,7 @@ public class MemoryDestination: BaseDestination, CustomStringConvertible { } // MARK: CustomStringConvertible - + public var description: String { return executeSynchronously { return self.buffer.joined(separator: "\n") diff --git a/Sources/TunnelKitAppExtension/Transport/NETCPSocket.swift b/Sources/TunnelKitAppExtension/Transport/NETCPSocket.swift index d4e206c..d6faecd 100644 --- a/Sources/TunnelKitAppExtension/Transport/NETCPSocket.swift +++ b/Sources/TunnelKitAppExtension/Transport/NETCPSocket.swift @@ -44,36 +44,36 @@ private let log = SwiftyBeaver.self /// TCP implementation of a `GenericSocket` via NetworkExtension. public class NETCPSocket: NSObject, GenericSocket { private static var linkContext = 0 - + public let impl: NWTCPConnection - + public init(impl: NWTCPConnection) { self.impl = impl isActive = false isShutdown = false } - + // MARK: GenericSocket - + private weak var queue: DispatchQueue? - + private var isActive: Bool - + public private(set) var isShutdown: Bool - + public var remoteAddress: String? { return (impl.remoteAddress as? NWHostEndpoint)?.hostname } - + public var hasBetterPath: Bool { return impl.hasBetterPath } - + public weak var delegate: GenericSocketDelegate? - + public func observe(queue: DispatchQueue, activeTimeout: Int) { isActive = false - + self.queue = queue queue.schedule(after: .milliseconds(activeTimeout)) { [weak self] in guard let _self = self else { @@ -87,28 +87,28 @@ public class NETCPSocket: NSObject, GenericSocket { impl.addObserver(self, forKeyPath: #keyPath(NWTCPConnection.state), options: [.initial, .new], context: &NETCPSocket.linkContext) impl.addObserver(self, forKeyPath: #keyPath(NWTCPConnection.hasBetterPath), options: .new, context: &NETCPSocket.linkContext) } - + public func unobserve() { impl.removeObserver(self, forKeyPath: #keyPath(NWTCPConnection.state), context: &NETCPSocket.linkContext) impl.removeObserver(self, forKeyPath: #keyPath(NWTCPConnection.hasBetterPath), context: &NETCPSocket.linkContext) } - + public func shutdown() { impl.writeClose() impl.cancel() } - + public func upgraded() -> GenericSocket? { guard impl.hasBetterPath else { return nil } return NETCPSocket(impl: NWTCPConnection(upgradeFor: impl)) } - + // MARK: Connection KVO (any queue) - - public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { - guard (context == &NETCPSocket.linkContext) else { + + public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { + guard context == &NETCPSocket.linkContext else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) return } @@ -119,8 +119,8 @@ public class NETCPSocket: NSObject, GenericSocket { self.observeValueInTunnelQueue(forKeyPath: keyPath, of: object, change: change, context: context) } } - - private func observeValueInTunnelQueue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + + private func observeValueInTunnelQueue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { // if let keyPath = keyPath { // log.debug("KVO change reported (\(anyPointer(object)).\(keyPath))") // } @@ -138,7 +138,7 @@ public class NETCPSocket: NSObject, GenericSocket { } else { log.debug("Socket state is \(impl.state) (endpoint: \(impl.endpoint.maskedDescription) -> in progress)") } - + switch impl.state { case .connected: guard !isActive else { @@ -146,26 +146,26 @@ public class NETCPSocket: NSObject, GenericSocket { } isActive = true delegate?.socketDidBecomeActive(self) - + case .cancelled: isShutdown = true delegate?.socket(self, didShutdownWithFailure: false) - + case .disconnected: isShutdown = true delegate?.socket(self, didShutdownWithFailure: true) - + default: break } - + case #keyPath(NWTCPConnection.hasBetterPath): guard impl.hasBetterPath else { break } log.debug("Socket has a better path") delegate?.socketHasBetterPath(self) - + default: break } diff --git a/Sources/TunnelKitAppExtension/Transport/NETunnelInterface.swift b/Sources/TunnelKitAppExtension/Transport/NETunnelInterface.swift index e57807f..55fdf76 100644 --- a/Sources/TunnelKitAppExtension/Transport/NETunnelInterface.swift +++ b/Sources/TunnelKitAppExtension/Transport/NETunnelInterface.swift @@ -44,40 +44,40 @@ private let log = SwiftyBeaver.self /// `TunnelInterface` implementation via NetworkExtension. public class NETunnelInterface: TunnelInterface { private weak var impl: NEPacketTunnelFlow? - + public init(impl: NEPacketTunnelFlow) { self.impl = impl } - + // MARK: TunnelInterface - + public var isPersistent: Bool { return false } - + // MARK: IOInterface - + public func setReadHandler(queue: DispatchQueue, _ handler: @escaping ([Data]?, Error?) -> Void) { loopReadPackets(queue, handler) } - + private func loopReadPackets(_ queue: DispatchQueue, _ handler: @escaping ([Data]?, Error?) -> Void) { // WARNING: runs in NEPacketTunnelFlow queue - impl?.readPackets { [weak self] (packets, protocols) in + impl?.readPackets { [weak self] (packets, _) in queue.sync { self?.loopReadPackets(queue, handler) handler(packets, nil) } } } - + public func writePacket(_ packet: Data, completionHandler: ((Error?) -> Void)?) { let protocolNumber = IPHeader.protocolNumber(inPacket: packet) impl?.writePackets([packet], withProtocols: [protocolNumber]) completionHandler?(nil) } - + public func writePackets(_ packets: [Data], completionHandler: ((Error?) -> Void)?) { let protocols = packets.map { IPHeader.protocolNumber(inPacket: $0) diff --git a/Sources/TunnelKitAppExtension/Transport/NEUDPSocket.swift b/Sources/TunnelKitAppExtension/Transport/NEUDPSocket.swift index c0b7a6a..72f071f 100644 --- a/Sources/TunnelKitAppExtension/Transport/NEUDPSocket.swift +++ b/Sources/TunnelKitAppExtension/Transport/NEUDPSocket.swift @@ -44,37 +44,37 @@ private let log = SwiftyBeaver.self /// UDP implementation of a `GenericSocket` via NetworkExtension. public class NEUDPSocket: NSObject, GenericSocket { private static var linkContext = 0 - + public let impl: NWUDPSession - + public init(impl: NWUDPSession) { self.impl = impl isActive = false isShutdown = false } - + // MARK: GenericSocket - + private weak var queue: DispatchQueue? - + private var isActive: Bool - + public private(set) var isShutdown: Bool public var remoteAddress: String? { return (impl.resolvedEndpoint as? NWHostEndpoint)?.hostname } - + public var hasBetterPath: Bool { return impl.hasBetterPath } - + public weak var delegate: GenericSocketDelegate? - + public func observe(queue: DispatchQueue, activeTimeout: Int) { isActive = false - + self.queue = queue queue.schedule(after: .milliseconds(activeTimeout)) { [weak self] in guard let _self = self else { @@ -88,27 +88,27 @@ public class NEUDPSocket: NSObject, GenericSocket { impl.addObserver(self, forKeyPath: #keyPath(NWUDPSession.state), options: [.initial, .new], context: &NEUDPSocket.linkContext) impl.addObserver(self, forKeyPath: #keyPath(NWUDPSession.hasBetterPath), options: .new, context: &NEUDPSocket.linkContext) } - + public func unobserve() { impl.removeObserver(self, forKeyPath: #keyPath(NWUDPSession.state), context: &NEUDPSocket.linkContext) impl.removeObserver(self, forKeyPath: #keyPath(NWUDPSession.hasBetterPath), context: &NEUDPSocket.linkContext) } - + public func shutdown() { impl.cancel() } - + public func upgraded() -> GenericSocket? { guard impl.hasBetterPath else { return nil } return NEUDPSocket(impl: NWUDPSession(upgradeFor: impl)) } - + // MARK: Connection KVO (any queue) - - public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { - guard (context == &NEUDPSocket.linkContext) else { + + public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { + guard context == &NEUDPSocket.linkContext else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) return } @@ -119,8 +119,8 @@ public class NEUDPSocket: NSObject, GenericSocket { self.observeValueInTunnelQueue(forKeyPath: keyPath, of: object, change: change, context: context) } } - - private func observeValueInTunnelQueue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + + private func observeValueInTunnelQueue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { // if let keyPath = keyPath { // log.debug("KVO change reported (\(anyPointer(object)).\(keyPath))") // } @@ -138,7 +138,7 @@ public class NEUDPSocket: NSObject, GenericSocket { } else { log.debug("Socket state is \(impl.state) (endpoint: \(impl.endpoint.maskedDescription) -> in progress)") } - + switch impl.state { case .ready: guard !isActive else { @@ -146,29 +146,29 @@ public class NEUDPSocket: NSObject, GenericSocket { } isActive = true delegate?.socketDidBecomeActive(self) - + case .cancelled: isShutdown = true delegate?.socket(self, didShutdownWithFailure: false) - + case .failed: isShutdown = true // if timedOut { // delegate?.socketShouldChangeProtocol(self) // } delegate?.socket(self, didShutdownWithFailure: true) - + default: break } - + case #keyPath(NWUDPSession.hasBetterPath): guard impl.hasBetterPath else { break } log.debug("Socket has a better path") delegate?.socketHasBetterPath(self) - + default: break } diff --git a/Sources/TunnelKitCore/BidirectionalState.swift b/Sources/TunnelKitCore/BidirectionalState.swift index 47b8557..23cfb9a 100644 --- a/Sources/TunnelKitCore/BidirectionalState.swift +++ b/Sources/TunnelKitCore/BidirectionalState.swift @@ -31,10 +31,10 @@ public struct BidirectionalState { /// The inbound state. public var inbound: T - + /// The outbound state. public var outbound: T - + /** Returns current state as a pair. @@ -43,7 +43,7 @@ public struct BidirectionalState { public var pair: (T, T) { return (inbound, outbound) } - + /** Inits state with a value that will later be reused by `reset()`. @@ -54,7 +54,7 @@ public struct BidirectionalState { outbound = value resetValue = value } - + /** Resets state to the value provided with `init(withResetValue:)`. */ diff --git a/Sources/TunnelKitCore/CoreConfiguration.swift b/Sources/TunnelKitCore/CoreConfiguration.swift index 77d4d6d..0674503 100644 --- a/Sources/TunnelKitCore/CoreConfiguration.swift +++ b/Sources/TunnelKitCore/CoreConfiguration.swift @@ -41,7 +41,7 @@ public class CoreConfiguration { /// Unique identifier of the library. public static let identifier = "com.algoritmico.TunnelKit" - + /// Library version as seen in `Info.plist`. public static let version: String = { let bundle = Bundle(for: CoreConfiguration.self) @@ -63,7 +63,7 @@ public class CoreConfiguration { /// String representing library version. public static var versionIdentifier: String? - + /// Enables logging of sensitive data (hardcoded to false). public static let logsSensitiveData = false } diff --git a/Sources/TunnelKitCore/DNSProtocol.swift b/Sources/TunnelKitCore/DNSProtocol.swift index 398505c..0de4990 100644 --- a/Sources/TunnelKitCore/DNSProtocol.swift +++ b/Sources/TunnelKitCore/DNSProtocol.swift @@ -33,10 +33,10 @@ public enum DNSProtocol: String, Codable { /// Standard plaintext DNS (port 53). case plain - + /// DNS over HTTPS. case https - + /// DNS over TLS (port 853). case tls } diff --git a/Sources/TunnelKitCore/DNSResolver.swift b/Sources/TunnelKitCore/DNSResolver.swift index 3a34540..cec0efd 100644 --- a/Sources/TunnelKitCore/DNSResolver.swift +++ b/Sources/TunnelKitCore/DNSResolver.swift @@ -54,7 +54,7 @@ public struct DNSRecord { /// Errors coming from `DNSResolver`. public enum DNSError: Error { case failure - + case timeout } @@ -95,14 +95,14 @@ public class DNSResolver { pendingHandler = nil } } - + private static func didResolve(host: CFHost, completionHandler: @escaping (Result<[DNSRecord], DNSError>) -> Void) { var success: DarwinBoolean = false guard let rawAddresses = CFHostGetAddressing(host, &success)?.takeUnretainedValue() as Array? else { completionHandler(.failure(.failure)) return } - + var records: [DNSRecord] = [] for case let rawAddress as Data in rawAddresses { var ipAddress = [CChar](repeating: 0, count: Int(NI_MAXHOST)) @@ -152,7 +152,7 @@ public class DNSResolver { } return groups.map { "\($0)" }.joined(separator: ".") } - + /** Returns a numeric representation from an IPv4 address. diff --git a/Sources/TunnelKitCore/DataCount.swift b/Sources/TunnelKitCore/DataCount.swift index c772fd4..8c3937d 100644 --- a/Sources/TunnelKitCore/DataCount.swift +++ b/Sources/TunnelKitCore/DataCount.swift @@ -30,10 +30,10 @@ public struct DataCount: Equatable { /// Received bytes count. public let received: UInt - + /// Sent bytes count. public let sent: UInt - + public init(_ received: UInt, _ sent: UInt) { self.received = received self.sent = sent diff --git a/Sources/TunnelKitCore/DataUnit.swift b/Sources/TunnelKitCore/DataUnit.swift index 7fdf0b0..8e69ccd 100644 --- a/Sources/TunnelKitCore/DataUnit.swift +++ b/Sources/TunnelKitCore/DataUnit.swift @@ -28,29 +28,29 @@ import Foundation /// Helps expressing integers in bytes, kB, MB, GB. public enum DataUnit: UInt, CustomStringConvertible { case byte = 1 - + case kilobyte = 1024 - + case megabyte = 1048576 - + case gigabyte = 1073741824 fileprivate var showsDecimals: Bool { switch self { case .byte, .kilobyte: return false - + case .megabyte, .gigabyte: return true } } - + fileprivate var boundary: UInt { return UInt(0.1 * Double(rawValue)) } - + // MARK: CustomStringConvertible - + public var description: String { switch self { case .byte: @@ -82,7 +82,7 @@ extension UInt: DataUnitRepresentable { .kilobyte, .byte ] - + public var descriptionAsDataUnit: String { if self == 0 { return "0B" diff --git a/Sources/TunnelKitCore/Endpoint.swift b/Sources/TunnelKitCore/Endpoint.swift index eddab62..4a8f893 100644 --- a/Sources/TunnelKitCore/Endpoint.swift +++ b/Sources/TunnelKitCore/Endpoint.swift @@ -33,7 +33,7 @@ public struct Endpoint: RawRepresentable, Codable, Equatable, CustomStringConver private static let rx = NSRegularExpression("^([^\\s]+):(UDP[46]?|TCP[46]?):(\\d+)$") public let address: String - + public let proto: EndpointProtocol public init(_ address: String, _ proto: EndpointProtocol) { @@ -60,7 +60,7 @@ public struct Endpoint: RawRepresentable, Codable, Equatable, CustomStringConver public var isHostname: Bool { !isIPv4 && !isIPv6 } - + public func withRandomPrefixLength(_ length: Int) throws -> Endpoint { guard isHostname else { return self @@ -69,9 +69,9 @@ public struct Endpoint: RawRepresentable, Codable, Equatable, CustomStringConver let prefixedAddress = "\(prefix.toHex()).\(address)" return Endpoint(prefixedAddress, proto) } - + // MARK: RawRepresentable - + public init?(rawValue: String) { let components = Self.rx.groups(in: rawValue) guard components.count == 3 else { @@ -90,9 +90,9 @@ public struct Endpoint: RawRepresentable, Codable, Equatable, CustomStringConver public var rawValue: String { "\(address):\(proto.socketType.rawValue):\(proto.port)" } - + // MARK: CustomStringConvertible - + public var description: String { "\(address.maskedDescription):\(proto.rawValue)" } @@ -100,10 +100,10 @@ public struct Endpoint: RawRepresentable, Codable, Equatable, CustomStringConver /// Defines the communication protocol of an endpoint. public struct EndpointProtocol: RawRepresentable, Equatable, CustomStringConvertible { - + /// The socket type. public let socketType: SocketType - + /// The remote port. public let port: UInt16 @@ -111,9 +111,9 @@ public struct EndpointProtocol: RawRepresentable, Equatable, CustomStringConvert self.socketType = socketType self.port = port } - + // MARK: RawRepresentable - + public init?(rawValue: String) { let components = rawValue.components(separatedBy: ":") guard components.count == 2 else { @@ -127,13 +127,13 @@ public struct EndpointProtocol: RawRepresentable, Equatable, CustomStringConvert } self.init(socketType, port) } - + public var rawValue: String { "\(socketType.rawValue):\(port)" } - + // MARK: CustomStringConvertible - + public var description: String { rawValue } @@ -146,7 +146,7 @@ extension EndpointProtocol: Codable { let proto = EndpointProtocol(rawValue: rawValue) ?? EndpointProtocol(.udp, 1198) self.init(proto.socketType, proto.port) } - + public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(rawValue) diff --git a/Sources/TunnelKitCore/IOInterface.swift b/Sources/TunnelKitCore/IOInterface.swift index b7ab4e5..30b88d5 100644 --- a/Sources/TunnelKitCore/IOInterface.swift +++ b/Sources/TunnelKitCore/IOInterface.swift @@ -46,7 +46,7 @@ public protocol IOInterface: AnyObject { - Parameter handler: The handler invoked whenever an array of `Data` packets is received, with an optional `Error` in case a network failure occurs. */ func setReadHandler(queue: DispatchQueue, _ handler: @escaping ([Data]?, Error?) -> Void) - + /** Writes a packet to the interface. @@ -54,7 +54,7 @@ public protocol IOInterface: AnyObject { - Parameter completionHandler: Invoked on write completion, with an optional `Error` in case a network failure occurs. */ func writePacket(_ packet: Data, completionHandler: ((Error?) -> Void)?) - + /** Writes some packets to the interface. diff --git a/Sources/TunnelKitCore/IPHeader.swift b/Sources/TunnelKitCore/IPHeader.swift index 03b6762..5189dba 100644 --- a/Sources/TunnelKitCore/IPHeader.swift +++ b/Sources/TunnelKitCore/IPHeader.swift @@ -27,9 +27,9 @@ import Foundation /// Helper for handling IP headers. public struct IPHeader { private static let ipV4: UInt8 = 4 - + private static let ipV6: UInt8 = 6 - + private static let ipV4ProtocolNumber = AF_INET as NSNumber private static let ipV6ProtocolNumber = AF_INET6 as NSNumber diff --git a/Sources/TunnelKitCore/IPv4Settings.swift b/Sources/TunnelKitCore/IPv4Settings.swift index 4d6d057..8ca023f 100644 --- a/Sources/TunnelKitCore/IPv4Settings.swift +++ b/Sources/TunnelKitCore/IPv4Settings.swift @@ -27,45 +27,45 @@ import Foundation /// Encapsulates the IPv4 settings for the tunnel. public struct IPv4Settings: Codable, Equatable, CustomStringConvertible { - + /// Represents an IPv4 route in the routing table. public struct Route: Codable, Hashable, CustomStringConvertible { - + /// The destination host or subnet. public let destination: String - + /// The address mask. public let mask: String - + /// The address of the gateway (falls back to global gateway). public let gateway: String? - + public init(_ destination: String, _ mask: String?, _ gateway: String?) { self.destination = destination self.mask = mask ?? "255.255.255.255" self.gateway = gateway } - + // MARK: CustomStringConvertible - + public var description: String { "{\(destination)/\(mask) \(gateway?.description ?? "*")}" } } - + /// The address. public let address: String - + /// The address mask. public let addressMask: String - + /// The address of the default gateway. public let defaultGateway: String - + /// The additional routes. @available(*, deprecated, message: "Store routes separately") public let routes: [Route] - + public init(address: String, addressMask: String, defaultGateway: String) { self.address = address self.addressMask = addressMask @@ -80,9 +80,9 @@ public struct IPv4Settings: Codable, Equatable, CustomStringConvertible { self.defaultGateway = defaultGateway self.routes = routes } - + // MARK: CustomStringConvertible - + public var description: String { "addr \(address) netmask \(addressMask) gw \(defaultGateway)" } diff --git a/Sources/TunnelKitCore/IPv6Settings.swift b/Sources/TunnelKitCore/IPv6Settings.swift index b40dc24..9363f5b 100644 --- a/Sources/TunnelKitCore/IPv6Settings.swift +++ b/Sources/TunnelKitCore/IPv6Settings.swift @@ -27,45 +27,45 @@ import Foundation /// Encapsulates the IPv6 settings for the tunnel. public struct IPv6Settings: Codable, Equatable, CustomStringConvertible { - + /// Represents an IPv6 route in the routing table. public struct Route: Codable, Hashable, CustomStringConvertible { - + /// The destination host or subnet. public let destination: String - + /// The address prefix length. public let prefixLength: UInt8 - + /// The address of the gateway (falls back to global gateway). public let gateway: String? - + public init(_ destination: String, _ prefixLength: UInt8?, _ gateway: String?) { self.destination = destination self.prefixLength = prefixLength ?? 3 self.gateway = gateway } - + // MARK: CustomStringConvertible - + public var description: String { "{\(destination.maskedDescription)/\(prefixLength) \(gateway?.maskedDescription ?? "*")}" } } - + /// The address. public let address: String - + /// The address prefix length. public let addressPrefixLength: UInt8 - + /// The address of the default gateway. public let defaultGateway: String - + /// The additional routes. @available(*, deprecated, message: "Store routes separately") public let routes: [Route] - + public init(address: String, addressPrefixLength: UInt8, defaultGateway: String) { self.address = address self.addressPrefixLength = addressPrefixLength @@ -82,7 +82,7 @@ public struct IPv6Settings: Codable, Equatable, CustomStringConvertible { } // MARK: CustomStringConvertible - + public var description: String { "addr \(address)/\(addressPrefixLength) gw \(defaultGateway)" } diff --git a/Sources/TunnelKitCore/LinkInterface.swift b/Sources/TunnelKitCore/LinkInterface.swift index ae3ddb7..6b8cb13 100644 --- a/Sources/TunnelKitCore/LinkInterface.swift +++ b/Sources/TunnelKitCore/LinkInterface.swift @@ -38,7 +38,7 @@ import Foundation /// Represents a specific I/O interface meant to work at the link layer (e.g. TCP/IP). public protocol LinkInterface: IOInterface { - + /// When `true`, packets delivery is guaranteed. var isReliable: Bool { get } diff --git a/Sources/TunnelKitCore/Proxy.swift b/Sources/TunnelKitCore/Proxy.swift index 3c4f5e1..f3b9dbe 100644 --- a/Sources/TunnelKitCore/Proxy.swift +++ b/Sources/TunnelKitCore/Proxy.swift @@ -27,24 +27,24 @@ import Foundation /// Encapsulates a proxy setting. public struct Proxy: Codable, Equatable, RawRepresentable, CustomStringConvertible { - + /// The proxy address. public let address: String - + /// The proxy port. public let port: UInt16 - + public init(_ address: String, _ port: UInt16) { self.address = address self.port = port } - + // MARK: RawRepresentable - + public var rawValue: String { return "\(address):\(port)" } - + public init?(rawValue: String) { let comps = rawValue.components(separatedBy: ":") guard comps.count == 2, let port = UInt16(comps[1]) else { @@ -52,9 +52,9 @@ public struct Proxy: Codable, Equatable, RawRepresentable, CustomStringConvertib } self.init(comps[0], port) } - + // MARK: CustomStringConvertible - + public var description: String { return rawValue } diff --git a/Sources/TunnelKitCore/SecureRandom.swift b/Sources/TunnelKitCore/SecureRandom.swift index 247d65d..a34753b 100644 --- a/Sources/TunnelKitCore/SecureRandom.swift +++ b/Sources/TunnelKitCore/SecureRandom.swift @@ -64,10 +64,10 @@ public class SecureRandom { } return randomNumber } - + public static func uint32() throws -> UInt32 { var randomNumber: UInt32 = 0 - + try withUnsafeMutablePointer(to: &randomNumber) { try $0.withMemoryRebound(to: UInt8.self, capacity: 4) { (randomBytes: UnsafeMutablePointer) -> Void in guard SecRandomCopyBytes(kSecRandomDefault, 4, randomBytes) == 0 else { @@ -75,7 +75,7 @@ public class SecureRandom { } } } - + return randomNumber } @@ -88,7 +88,7 @@ public class SecureRandom { throw SecureRandomError.randomGenerator } } - + return randomData } @@ -99,7 +99,7 @@ public class SecureRandom { bzero(randomBytes, length) randomBytes.deallocate() } - + guard SecRandomCopyBytes(kSecRandomDefault, length, randomBytes) == 0 else { throw SecureRandomError.randomGenerator } diff --git a/Sources/TunnelKitCore/Session.swift b/Sources/TunnelKitCore/Session.swift index 22b988b..ae1c731 100644 --- a/Sources/TunnelKitCore/Session.swift +++ b/Sources/TunnelKitCore/Session.swift @@ -27,7 +27,7 @@ import Foundation /// Defines the basics of a VPN session. public protocol Session { - + /** Establishes the link interface for this session. The interface must be up and running for sending and receiving packets. @@ -36,14 +36,14 @@ public protocol Session { - Parameter link: The `LinkInterface` on which to establish the VPN session. */ func setLink(_ link: LinkInterface) - + /** Returns `true` if the current session can rebind to a new link with `rebindLink(...)`. - Returns: `true` if supports link rebinding. */ func canRebindLink() -> Bool - + /** Rebinds the session to a new link if supported. @@ -53,7 +53,7 @@ public protocol Session { - Seealso: `canRebindLink()` */ func rebindLink(_ link: LinkInterface) - + /** Establishes the tunnel interface for this session. The interface must be up and running for sending and receiving packets. @@ -62,14 +62,14 @@ public protocol Session { - Parameter tunnel: The `TunnelInterface` on which to exchange the VPN data traffic. */ func setTunnel(tunnel: TunnelInterface) - + /** Returns the current data bytes count. - Returns: The current data bytes count. */ func dataCount() -> DataCount? - + /** Returns the current server configuration. @@ -83,7 +83,7 @@ public protocol Session { - Parameter error: An optional `Error` being the reason of the shutdown. */ func shutdown(error: Error?) - + /** Shuts down the session with an optional `Error` reason and signals a reconnect flag to `OpenVPNSessionDelegate.sessionDidStop(...)`. Does nothing if the session is already stopped or about to stop. @@ -91,7 +91,7 @@ public protocol Session { - Seealso: `OpenVPNSessionDelegate.sessionDidStop(...)` */ func reconnect(error: Error?) - + /** Cleans up the session resources. */ diff --git a/Sources/TunnelKitCore/SocketType.swift b/Sources/TunnelKitCore/SocketType.swift index 1e52a3c..a0d3354 100644 --- a/Sources/TunnelKitCore/SocketType.swift +++ b/Sources/TunnelKitCore/SocketType.swift @@ -27,22 +27,22 @@ import Foundation /// A socket type between UDP (recommended) and TCP. public enum SocketType: String { - + /// UDP socket type. case udp = "UDP" - + /// TCP socket type. case tcp = "TCP" /// UDP socket type (IPv4). case udp4 = "UDP4" - + /// TCP socket type (IPv4). case tcp4 = "TCP4" /// UDP socket type (IPv6). case udp6 = "UDP6" - + /// TCP socket type (IPv6). case tcp6 = "TCP6" } diff --git a/Sources/TunnelKitCore/ZeroingData.swift b/Sources/TunnelKitCore/ZeroingData.swift index 74697fc..6eaef1e 100644 --- a/Sources/TunnelKitCore/ZeroingData.swift +++ b/Sources/TunnelKitCore/ZeroingData.swift @@ -61,9 +61,9 @@ public func Z(_ data: Data) -> ZeroingData { return ZeroingData(data: data) } -//public func Z(_ data: Data, _ offset: Int, _ count: Int) -> ZeroingData { +// public func Z(_ data: Data, _ offset: Int, _ count: Int) -> ZeroingData { // return ZeroingData(data: data, offset: offset, count: count) -//} +// } public func Z(_ string: String, nullTerminated: Bool) -> ZeroingData { return ZeroingData(string: string, nullTerminated: nullTerminated) diff --git a/Sources/TunnelKitManager/Keychain.swift b/Sources/TunnelKitManager/Keychain.swift index 9672971..97d8da8 100644 --- a/Sources/TunnelKitManager/Keychain.swift +++ b/Sources/TunnelKitManager/Keychain.swift @@ -46,13 +46,13 @@ public enum KeychainError: Error { /// Unable to add. case add - + /// Item not found. case notFound - + /// Operation cancelled or unauthorized. case userCancelled - + // /// Unexpected item type returned. // case typeMismatch } @@ -70,9 +70,9 @@ public class Keychain { public init(group: String?) { accessGroup = group } - + // MARK: Password - + /** Sets a password. @@ -118,7 +118,7 @@ public class Keychain { } return refData } - + /** Removes a password. @@ -153,12 +153,12 @@ public class Keychain { query[kSecAttrAccount as String] = username query[kSecMatchLimit as String] = kSecMatchLimitOne query[kSecReturnData as String] = true - + var result: AnyObject? switch SecItemCopyMatching(query as CFDictionary, &result) { case errSecSuccess: break - + case errSecUserCanceled: throw KeychainError.userCancelled @@ -190,12 +190,12 @@ public class Keychain { query[kSecAttrAccount as String] = username query[kSecMatchLimit as String] = kSecMatchLimitOne query[kSecReturnPersistentRef as String] = true - + var result: AnyObject? switch SecItemCopyMatching(query as CFDictionary, &result) { case errSecSuccess: break - + case errSecUserCanceled: throw KeychainError.userCancelled @@ -207,7 +207,7 @@ public class Keychain { } return data } - + /** Gets a password associated with a password reference. @@ -219,12 +219,12 @@ public class Keychain { var query = [String: Any]() query[kSecValuePersistentRef as String] = reference query[kSecReturnData as String] = true - + var result: AnyObject? switch SecItemCopyMatching(query as CFDictionary, &result) { case errSecSuccess: break - + case errSecUserCanceled: throw KeychainError.userCancelled @@ -239,11 +239,11 @@ public class Keychain { } return password } - + // MARK: Key - + // https://forums.developer.apple.com/thread/13748 - + /** Adds a public key. @@ -269,7 +269,7 @@ public class Keychain { } return try publicKey(withIdentifier: identifier) } - + /** Gets a public key. @@ -292,7 +292,7 @@ public class Keychain { switch SecItemCopyMatching(query as CFDictionary, &result) { case errSecSuccess: break - + case errSecUserCanceled: throw KeychainError.userCancelled @@ -305,7 +305,7 @@ public class Keychain { // return key return result as! SecKey } - + /** Removes a public key. @@ -325,9 +325,9 @@ public class Keychain { let status = SecItemDelete(query as CFDictionary) return status == errSecSuccess } - + // MARK: Helpers - + public func setScope(query: inout [String: Any], context: String, userDefined: String?) { if let accessGroup = accessGroup { query[kSecAttrAccessGroup as String] = accessGroup diff --git a/Sources/TunnelKitManager/MockVPN.swift b/Sources/TunnelKitManager/MockVPN.swift index 216eee5..05f71d0 100644 --- a/Sources/TunnelKitManager/MockVPN.swift +++ b/Sources/TunnelKitManager/MockVPN.swift @@ -29,32 +29,32 @@ import NetworkExtension /// Simulates a VPN provider. public class MockVPN: VPN { private var tunnelBundleIdentifier: String? - + private var isEnabled: Bool { didSet { notifyReinstall(isEnabled) } } - + private var vpnStatus: VPNStatus { didSet { notifyStatus(vpnStatus) } } - + private let delayNanoseconds: UInt64 - + public init(delay: Int = 1) { delayNanoseconds = DispatchTimeInterval.seconds(delay).nanoseconds isEnabled = false vpnStatus = .disconnected } - + // MARK: VPN - + public func prepare() { } - + public func install( _ tunnelBundleIdentifier: String, configuration: NetworkExtensionConfiguration, @@ -64,7 +64,7 @@ public class MockVPN: VPN { isEnabled = true vpnStatus = .disconnected } - + public func reconnect(after: DispatchTimeInterval) async throws { if vpnStatus == .connected { vpnStatus = .disconnecting @@ -74,7 +74,7 @@ public class MockVPN: VPN { await delay() vpnStatus = .connected } - + public func reconnect( _ tunnelBundleIdentifier: String, configuration: NetworkExtensionConfiguration, @@ -91,7 +91,7 @@ public class MockVPN: VPN { await delay() vpnStatus = .connected } - + public func disconnect() async { guard vpnStatus != .disconnected else { return @@ -101,21 +101,21 @@ public class MockVPN: VPN { vpnStatus = .disconnected isEnabled = false } - + public func uninstall() async { vpnStatus = .disconnected isEnabled = false } - + // MARK: Helpers - + private func notifyReinstall(_ isEnabled: Bool) { var notification = Notification(name: VPNNotification.didReinstall) notification.vpnBundleIdentifier = tunnelBundleIdentifier notification.vpnIsEnabled = isEnabled NotificationCenter.default.post(notification) } - + private func notifyStatus(_ status: VPNStatus) { var notification = Notification(name: VPNNotification.didChangeStatus) notification.vpnBundleIdentifier = tunnelBundleIdentifier diff --git a/Sources/TunnelKitManager/NetworkExtensionConfiguration.swift b/Sources/TunnelKitManager/NetworkExtensionConfiguration.swift index e9c4f73..6156820 100644 --- a/Sources/TunnelKitManager/NetworkExtensionConfiguration.swift +++ b/Sources/TunnelKitManager/NetworkExtensionConfiguration.swift @@ -37,10 +37,10 @@ public struct NetworkExtensionExtra { /// Disconnects on sleep if `true`. public var disconnectsOnSleep = false - + /// Enables best-effort kill switch. public var killSwitch = false - + public init() { } } @@ -50,7 +50,7 @@ public protocol NetworkExtensionConfiguration { /// The profile title in device settings. var title: String { get } - + /** Returns a representation for use with tunnel implementations. diff --git a/Sources/TunnelKitManager/NetworkExtensionVPN.swift b/Sources/TunnelKitManager/NetworkExtensionVPN.swift index cb43c4a..3e09699 100644 --- a/Sources/TunnelKitManager/NetworkExtensionVPN.swift +++ b/Sources/TunnelKitManager/NetworkExtensionVPN.swift @@ -44,13 +44,13 @@ public class NetworkExtensionVPN: VPN { deinit { NotificationCenter.default.removeObserver(self) } - + // MARK: Public public func prepare() async { _ = try? await NETunnelProviderManager.loadAllFromPreferences() } - + public func install( _ tunnelBundleIdentifier: String, configuration: NetworkExtensionConfiguration, @@ -62,7 +62,7 @@ public class NetworkExtensionVPN: VPN { extra: extra ) } - + public func reconnect(after: DispatchTimeInterval) async throws { let managers = try await lookupAll() guard let manager = managers.first else { @@ -97,7 +97,7 @@ public class NetworkExtensionVPN: VPN { throw error } } - + public func disconnect() async { guard let managers = try? await lookupAll() else { return @@ -112,7 +112,7 @@ public class NetworkExtensionVPN: VPN { try? await m.saveToPreferences() } } - + public func uninstall() async { guard let managers = try? await lookupAll() else { return @@ -156,10 +156,10 @@ public class NetworkExtensionVPN: VPN { await retainManagers(managers) { $0.isTunnel(withIdentifier: tunnelBundleIdentifier) } - + return targetManager } - + @discardableResult private func install( _ manager: NETunnelProviderManager, @@ -202,11 +202,11 @@ public class NetworkExtensionVPN: VPN { try? await o.removeFromPreferences() } } - + private func lookupAll() async throws -> [NETunnelProviderManager] { try await NETunnelProviderManager.loadAllFromPreferences() } - + // MARK: Notifications @objc private func vpnDidUpdate(_ notification: Notification) { @@ -222,7 +222,7 @@ public class NetworkExtensionVPN: VPN { } notifyReinstall(manager) } - + private func notifyReinstall(_ manager: NETunnelProviderManager) { let bundleId = manager.tunnelBundleIdentifier log.debug("VPN did reinstall (\(bundleId ?? "?")): isEnabled=\(manager.isEnabled)") @@ -232,7 +232,7 @@ public class NetworkExtensionVPN: VPN { notification.vpnIsEnabled = manager.isEnabled NotificationCenter.default.post(notification) } - + private func notifyStatus(_ connection: NETunnelProviderSession) { guard let _ = connection.manager.localizedDescription else { log.verbose("Ignoring VPN notification from bogus manager") @@ -247,7 +247,7 @@ public class NetworkExtensionVPN: VPN { notification.vpnStatus = connection.status.wrappedStatus NotificationCenter.default.post(notification) } - + private func notifyInstallError(_ error: Error) { log.error("VPN installation failed: \(error))") @@ -265,7 +265,7 @@ private extension NEVPNManager { } return proto.providerBundleIdentifier } - + func isTunnel(withIdentifier bundleIdentifier: String) -> Bool { return tunnelBundleIdentifier == bundleIdentifier } @@ -276,13 +276,13 @@ private extension NEVPNStatus { switch self { case .connected: return .connected - + case .connecting, .reasserting: return .connecting - + case .disconnecting: return .disconnecting - + case .disconnected, .invalid: return .disconnected diff --git a/Sources/TunnelKitManager/VPN.swift b/Sources/TunnelKitManager/VPN.swift index db10710..51f8e40 100644 --- a/Sources/TunnelKitManager/VPN.swift +++ b/Sources/TunnelKitManager/VPN.swift @@ -28,14 +28,14 @@ import Foundation /// Helps controlling a VPN without messing with underlying implementations. public protocol VPN { associatedtype Configuration - + associatedtype Extra - + /** Synchronizes with the current VPN state. */ func prepare() async - + /** Installs the VPN profile. @@ -72,12 +72,12 @@ public protocol VPN { extra: Extra?, after: DispatchTimeInterval ) async throws - + /** Disconnects from the VPN. */ func disconnect() async - + /** Uninstalls the VPN profile. */ @@ -91,19 +91,19 @@ extension DispatchTimeInterval { switch self { case .seconds(let sec): return UInt64(sec) * NSEC_PER_SEC - + case .milliseconds(let msec): return UInt64(msec) * NSEC_PER_MSEC - + case .microseconds(let usec): return UInt64(usec) * NSEC_PER_USEC - + case .nanoseconds(let nsec): return UInt64(nsec) - + case .never: return 0 - + @unknown default: return 0 } diff --git a/Sources/TunnelKitManager/VPNStatus.swift b/Sources/TunnelKitManager/VPNStatus.swift index 7d897ec..85eba41 100644 --- a/Sources/TunnelKitManager/VPNStatus.swift +++ b/Sources/TunnelKitManager/VPNStatus.swift @@ -30,13 +30,13 @@ public enum VPNStatus: String { /// VPN is connected. case connected - + /// VPN is attempting a connection. case connecting - + /// VPN is disconnected. case disconnected - + /// VPN is completing a disconnection. case disconnecting } diff --git a/Sources/TunnelKitOpenVPNAppExtension/ConnectionStrategy.swift b/Sources/TunnelKitOpenVPNAppExtension/ConnectionStrategy.swift index 4e5cdb2..0c6aca9 100644 --- a/Sources/TunnelKitOpenVPNAppExtension/ConnectionStrategy.swift +++ b/Sources/TunnelKitOpenVPNAppExtension/ConnectionStrategy.swift @@ -48,7 +48,7 @@ class ConnectionStrategy { private var remotes: [ResolvedRemote] private var currentRemoteIndex: Int - + var currentRemote: ResolvedRemote? { guard currentRemoteIndex < remotes.count else { return nil @@ -89,13 +89,12 @@ class ConnectionStrategy { } return true } - + func createSocket( from provider: NEProvider, timeout: Int, queue: DispatchQueue, - completionHandler: @escaping (Result) -> Void) - { + completionHandler: @escaping (Result) -> Void) { guard let remote = currentRemote else { completionHandler(.failure(.exhaustedEndpoints)) return @@ -130,7 +129,7 @@ private extension NEProvider { case .udp, .udp4, .udp6: let impl = createUDPSession(to: ep, from: nil) return NEUDPSocket(impl: impl) - + case .tcp, .tcp4, .tcp6: let impl = createTCPConnection(to: ep, enableTLS: false, tlsParameters: nil, delegate: nil) return NETCPSocket(impl: impl) diff --git a/Sources/TunnelKitOpenVPNAppExtension/NETCPLink.swift b/Sources/TunnelKitOpenVPNAppExtension/NETCPLink.swift index a430229..d9f0374 100644 --- a/Sources/TunnelKitOpenVPNAppExtension/NETCPLink.swift +++ b/Sources/TunnelKitOpenVPNAppExtension/NETCPLink.swift @@ -32,7 +32,7 @@ import CTunnelKitOpenVPNProtocol class NETCPLink: LinkInterface { private let impl: NWTCPConnection - + private let maxPacketSize: Int private let xorMethod: OpenVPN.XORMethod? @@ -45,15 +45,15 @@ class NETCPLink: LinkInterface { self.xorMethod = xorMethod xorMask = xorMethod?.mask } - + // MARK: LinkInterface - + let isReliable: Bool = true - + var remoteAddress: String? { (impl.remoteAddress as? NWHostEndpoint)?.hostname } - + var remoteProtocol: String? { guard let remote = impl.remoteAddress as? NWHostEndpoint else { return nil @@ -64,13 +64,13 @@ class NETCPLink: LinkInterface { var packetBufferSize: Int { return maxPacketSize } - + func setReadHandler(queue: DispatchQueue, _ handler: @escaping ([Data]?, Error?) -> Void) { loopReadPackets(queue, Data(), handler) } - + private func loopReadPackets(_ queue: DispatchQueue, _ buffer: Data, _ handler: @escaping ([Data]?, Error?) -> Void) { - + // WARNING: runs in Network.framework queue impl.readMinimumLength(2, maximumLength: packetBufferSize) { [weak self] (data, error) in guard let self = self else { @@ -81,7 +81,7 @@ class NETCPLink: LinkInterface { handler(nil, error) return } - + var newBuffer = buffer newBuffer.append(contentsOf: data) var until = 0 @@ -93,12 +93,12 @@ class NETCPLink: LinkInterface { ) newBuffer = newBuffer.subdata(in: until.. Void)?) { let stream = PacketStream.outboundStream( fromPacket: packet, @@ -109,7 +109,7 @@ class NETCPLink: LinkInterface { completionHandler?(error) } } - + func writePackets(_ packets: [Data], completionHandler: ((Error?) -> Void)?) { let stream = PacketStream.outboundStream( fromPackets: packets, diff --git a/Sources/TunnelKitOpenVPNAppExtension/NEUDPLink.swift b/Sources/TunnelKitOpenVPNAppExtension/NEUDPLink.swift index 2a8bf7a..cb091ce 100644 --- a/Sources/TunnelKitOpenVPNAppExtension/NEUDPLink.swift +++ b/Sources/TunnelKitOpenVPNAppExtension/NEUDPLink.swift @@ -32,9 +32,9 @@ import TunnelKitOpenVPNProtocol class NEUDPLink: LinkInterface { private let impl: NWUDPSession - + private let maxDatagrams: Int - + private let xor: XORProcessor init(impl: NWUDPSession, maxDatagrams: Int? = nil, xorMethod: OpenVPN.XORMethod?) { @@ -42,15 +42,15 @@ class NEUDPLink: LinkInterface { self.maxDatagrams = maxDatagrams ?? 200 xor = XORProcessor(method: xorMethod) } - + // MARK: LinkInterface - + let isReliable: Bool = false - + var remoteAddress: String? { (impl.resolvedEndpoint as? NWHostEndpoint)?.hostname } - + var remoteProtocol: String? { guard let remote = impl.resolvedEndpoint as? NWHostEndpoint else { return nil @@ -61,9 +61,9 @@ class NEUDPLink: LinkInterface { var packetBufferSize: Int { return maxDatagrams } - + func setReadHandler(queue: DispatchQueue, _ handler: @escaping ([Data]?, Error?) -> Void) { - + // WARNING: runs in Network.framework queue impl.setReadHandler({ [weak self] packets, error in guard let self = self else { @@ -78,14 +78,14 @@ class NEUDPLink: LinkInterface { } }, maxDatagrams: maxDatagrams) } - + func writePacket(_ packet: Data, completionHandler: ((Error?) -> Void)?) { let dataToUse = xor.processPacket(packet, outbound: true) impl.writeDatagram(dataToUse) { error in completionHandler?(error) } } - + func writePackets(_ packets: [Data], completionHandler: ((Error?) -> Void)?) { let packetsToUse = xor.processPackets(packets, outbound: true) impl.writeMultipleDatagrams(packetsToUse) { error in diff --git a/Sources/TunnelKitOpenVPNAppExtension/NetworkSettingsBuilder.swift b/Sources/TunnelKitOpenVPNAppExtension/NetworkSettingsBuilder.swift index 9d294f9..eca9adc 100644 --- a/Sources/TunnelKitOpenVPNAppExtension/NetworkSettingsBuilder.swift +++ b/Sources/TunnelKitOpenVPNAppExtension/NetworkSettingsBuilder.swift @@ -33,17 +33,17 @@ private let log = SwiftyBeaver.self struct NetworkSettingsBuilder { let remoteAddress: String - + let localOptions: OpenVPN.Configuration - + let remoteOptions: OpenVPN.Configuration - + init(remoteAddress: String, localOptions: OpenVPN.Configuration, remoteOptions: OpenVPN.Configuration) { self.remoteAddress = remoteAddress self.localOptions = localOptions self.remoteOptions = remoteOptions } - + func build() -> NEPacketTunnelNetworkSettings { let ipv4Settings = computedIPv4Settings let ipv6Settings = computedIPv6Settings @@ -91,19 +91,19 @@ extension NetworkSettingsBuilder { var isGateway: Bool { isIPv4Gateway || isIPv6Gateway } - + private var routingPolicies: [OpenVPN.RoutingPolicy]? { pullRoutes ? (remoteOptions.routingPolicies ?? localOptions.routingPolicies) : localOptions.routingPolicies } - + private var isIPv4Gateway: Bool { routingPolicies?.contains(.IPv4) ?? false } - + private var isIPv6Gateway: Bool { routingPolicies?.contains(.IPv6) ?? false } - + private var allRoutes4: [IPv4Settings.Route] { var routes = localOptions.routes4 ?? [] if pullRoutes, let remoteRoutes = remoteOptions.routes4 { @@ -111,7 +111,7 @@ extension NetworkSettingsBuilder { } return routes } - + private var allRoutes6: [IPv6Settings.Route] { var routes = localOptions.routes6 ?? [] if pullRoutes, let remoteRoutes = remoteOptions.routes6 { @@ -119,7 +119,7 @@ extension NetworkSettingsBuilder { } return routes } - + private var allDNSServers: [String] { var servers = localOptions.dnsServers ?? [] if pullDNS, let remoteServers = remoteOptions.dnsServers { @@ -127,7 +127,7 @@ extension NetworkSettingsBuilder { } return servers } - + private var dnsDomain: String? { var domain = localOptions.dnsDomain if pullDNS, let remoteDomain = remoteOptions.dnsDomain { @@ -143,7 +143,7 @@ extension NetworkSettingsBuilder { } return searchDomains } - + private var allProxyBypassDomains: [String] { var bypass = localOptions.proxyBypassDomains ?? [] if pullProxy, let remoteBypass = remoteOptions.proxyBypassDomains { @@ -164,7 +164,7 @@ extension NetworkSettingsBuilder { } let ipv4Settings = NEIPv4Settings(addresses: [ipv4.address], subnetMasks: [ipv4.addressMask]) var neRoutes: [NEIPv4Route] = [] - + // route all traffic to VPN? if isIPv4Gateway { let defaultRoute = NEIPv4Route.default() @@ -172,7 +172,7 @@ extension NetworkSettingsBuilder { neRoutes.append(defaultRoute) log.info("Routing.IPv4: Setting default gateway to \(ipv4.defaultGateway)") } - + for r in allRoutes4 { let ipv4Route = NEIPv4Route(destinationAddress: r.destination, subnetMask: r.mask) let gw = r.gateway ?? ipv4.defaultGateway @@ -185,14 +185,14 @@ extension NetworkSettingsBuilder { ipv4Settings.excludedRoutes = [] return ipv4Settings } - + private var computedIPv6Settings: NEIPv6Settings? { guard let ipv6 = remoteOptions.ipv6 else { return nil } let ipv6Settings = NEIPv6Settings(addresses: [ipv6.address], networkPrefixLengths: [ipv6.addressPrefixLength as NSNumber]) var neRoutes: [NEIPv6Route] = [] - + // route all traffic to VPN? if isIPv6Gateway { let defaultRoute = NEIPv6Route.default() @@ -200,7 +200,7 @@ extension NetworkSettingsBuilder { neRoutes.append(defaultRoute) log.info("Routing.IPv6: Setting default gateway to \(ipv6.defaultGateway)") } - + for r in allRoutes6 { let ipv6Route = NEIPv6Route(destinationAddress: r.destination, networkPrefixLength: r.prefixLength as NSNumber) let gw = r.gateway ?? ipv6.defaultGateway @@ -213,7 +213,7 @@ extension NetworkSettingsBuilder { ipv6Settings.excludedRoutes = [] return ipv6Settings } - + var hasGateway: Bool { var hasGateway = false if isIPv4Gateway && remoteOptions.ipv4 != nil { @@ -258,7 +258,7 @@ extension NetworkSettingsBuilder { default: break } - + // fall back if dnsSettings == nil { let dnsServers = allDNSServers @@ -275,7 +275,7 @@ extension NetworkSettingsBuilder { } } } - + // "hack" for split DNS (i.e. use VPN only for DNS) if !isGateway { dnsSettings?.matchDomains = [""] @@ -294,7 +294,7 @@ extension NetworkSettingsBuilder { dnsSettings?.matchDomains = dnsSettings?.searchDomains } } - + return dnsSettings } } @@ -327,7 +327,7 @@ extension NetworkSettingsBuilder { proxySettings?.autoProxyConfigurationEnabled = true log.info("Routing: Setting PAC \(pacURL)") } - + // only set if there is a proxy (proxySettings set to non-nil above) if proxySettings != nil { let bypass = allProxyBypassDomains diff --git a/Sources/TunnelKitOpenVPNAppExtension/OpenVPNTunnelProvider.swift b/Sources/TunnelKitOpenVPNAppExtension/OpenVPNTunnelProvider.swift index edc35d8..fe61164 100644 --- a/Sources/TunnelKitOpenVPNAppExtension/OpenVPNTunnelProvider.swift +++ b/Sources/TunnelKitOpenVPNAppExtension/OpenVPNTunnelProvider.swift @@ -57,39 +57,39 @@ private let log = SwiftyBeaver.self Packet Tunnel Provider extension both on iOS and macOS. */ open class OpenVPNTunnelProvider: NEPacketTunnelProvider { - + // MARK: Tweaks - + /// An optional string describing host app version on tunnel start. public var appVersion: String? /// The log separator between sessions. public var logSeparator = "--- EOF ---" - + /// The maximum size of the log. public var maxLogSize = 20000 - + /// The log level when `OpenVPNTunnelProvider.Configuration.shouldDebug` is enabled. public var debugLogLevel: SwiftyBeaver.Level = .debug - + /// The number of milliseconds after which a DNS resolution fails. public var dnsTimeout = 3000 - + /// The number of milliseconds after which the tunnel gives up on a connection attempt. public var socketTimeout = 5000 - + /// The number of milliseconds after which the tunnel is shut down forcibly. public var shutdownTimeout = 2000 - + /// The number of milliseconds after which a reconnection attempt is issued. public var reconnectionDelay = 1000 - + /// The number of link failures after which the tunnel is expected to die. public var maxLinkFailures = 3 /// The number of milliseconds between data count updates. Set to 0 to disable updates (default). public var dataCountInterval = 0 - + /// A list of public DNS servers to use as fallback when none are provided (defaults to CloudFlare). public var fallbackDNSServers = [ "1.1.1.1", @@ -97,13 +97,13 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider { "2606:4700:4700::1111", "2606:4700:4700::1001" ] - + // MARK: Constants - + private let tunnelQueue = DispatchQueue(label: OpenVPNTunnelProvider.description(), qos: .utility) - + private let prngSeedLength = 64 - + private var cachesURL: URL { let appGroup = cfg.appGroup guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else { @@ -115,32 +115,32 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider { // MARK: Tunnel configuration private var cfg: OpenVPN.ProviderConfiguration! - + private var strategy: ConnectionStrategy! - + // MARK: Internal state private var session: OpenVPNSession? - + private var socket: GenericSocket? private var pendingStartHandler: ((Error?) -> Void)? - + private var pendingStopHandler: (() -> Void)? - + private var isCountingData = false - + private var shouldReconnect = false // MARK: NEPacketTunnelProvider (XPC queue) - + open override var reasserting: Bool { didSet { log.debug("Reasserting flag \(reasserting ? "set" : "cleared")") } } - - open override func startTunnel(options: [String : NSObject]? = nil, completionHandler: @escaping (Error?) -> Void) { + + open override func startTunnel(options: [String: NSObject]? = nil, completionHandler: @escaping (Error?) -> Void) { // required configuration do { @@ -160,7 +160,7 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider { switch te { case .parameter(let name): message = "Tunnel configuration incomplete: \(name)" - + default: break } @@ -198,7 +198,7 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider { log.info("Starting tunnel...") cfg._appexSetLastError(nil) - + guard OpenVPN.prepareRandomNumberGenerator(seedLength: prngSeedLength) else { completionHandler(OpenVPNProviderConfigurationError.prngInitialization) return @@ -231,7 +231,7 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider { self.connectTunnel() } } - + open override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { pendingStartHandler = nil log.info("Stopping tunnel...") @@ -261,35 +261,35 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider { session.shutdown(error: nil) } } - + // MARK: Wake/Sleep (debugging placeholders) open override func wake() { log.verbose("Wake signal received") } - + open override func sleep(completionHandler: @escaping () -> Void) { log.verbose("Sleep signal received") completionHandler() } - + // MARK: Connection (tunnel queue) - + private func connectTunnel(upgradedSocket: GenericSocket? = nil) { log.info("Creating link session") - + // reuse upgraded socket if let upgradedSocket = upgradedSocket, !upgradedSocket.isShutdown { log.debug("Socket follows a path upgrade") connectTunnel(via: upgradedSocket) return } - + strategy.createSocket(from: self, timeout: dnsTimeout, queue: tunnelQueue) { switch $0 { case .success(let socket): self.connectTunnel(via: socket) - + case .failure(let error): if case .dnsFailure = error { self.tunnelQueue.async { @@ -302,7 +302,7 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider { } } } - + private func connectTunnel(via socket: GenericSocket) { log.info("Will connect to \(socket)") cfg._appexSetLastError(nil) @@ -312,16 +312,16 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider { self.socket?.delegate = self self.socket?.observe(queue: tunnelQueue, activeTimeout: socketTimeout) } - + private func finishTunnelDisconnection(error: Error?) { if let session = session, !(shouldReconnect && session.canRebindLink()) { session.cleanup() } - + socket?.delegate = nil socket?.unobserve() socket = nil - + if let error = error { log.error("Tunnel did stop (error: \(error))") setErrorStatus(with: error) @@ -329,7 +329,7 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider { log.info("Tunnel did stop on request") } } - + private func disposeTunnel(error: Error?) { log.info("Dispose tunnel in \(reconnectionDelay) milliseconds...") tunnelQueue.asyncAfter(deadline: .now() + .milliseconds(reconnectionDelay)) { [weak self] in @@ -342,7 +342,7 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider { // failed to start if pendingStartHandler != nil { - + // // CAUTION // @@ -374,7 +374,7 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider { forceExitOnMac() } } - + // MARK: Data counter (tunnel queue) private func refreshDataCount() { @@ -393,9 +393,9 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider { } extension OpenVPNTunnelProvider: GenericSocketDelegate { - + // MARK: GenericSocketDelegate (tunnel queue) - + public func socketDidTimeout(_ socket: GenericSocket) { log.debug("Socket timed out waiting for activity, cancelling...") shouldReconnect = true @@ -409,7 +409,7 @@ extension OpenVPNTunnelProvider: GenericSocketDelegate { } } } - + public func socketDidBecomeActive(_ socket: GenericSocket) { guard let session = session, let producer = socket as? LinkProducer else { return @@ -421,12 +421,12 @@ extension OpenVPNTunnelProvider: GenericSocketDelegate { session.setLink(producer.link(userObject: cfg.configuration.xorMethod)) } } - + public func socket(_ socket: GenericSocket, didShutdownWithFailure failure: Bool) { guard let session = session else { return } - + var shutdownError: Error? let didTimeoutNegotiation: Bool var upgradedSocket: GenericSocket? @@ -437,7 +437,7 @@ extension OpenVPNTunnelProvider: GenericSocketDelegate { shutdownError = OpenVPNProviderError.linkError } didTimeoutNegotiation = (shutdownError as? OpenVPNError == .negotiationTimeout) - + // only try upgrade on network errors if shutdownError as? OpenVPNError == nil { upgradedSocket = socket.upgraded() @@ -475,7 +475,7 @@ extension OpenVPNTunnelProvider: GenericSocketDelegate { // shut down disposeTunnel(error: shutdownError) } - + public func socketHasBetterPath(_ socket: GenericSocket) { log.debug("Stopping tunnel due to a new better path") logCurrentSSID() @@ -484,9 +484,9 @@ extension OpenVPNTunnelProvider: GenericSocketDelegate { } extension OpenVPNTunnelProvider: OpenVPNSessionDelegate { - + // MARK: OpenVPNSessionDelegate (tunnel queue) - + public func sessionDidStart(_ session: OpenVPNSession, remoteAddress: String, remoteProtocol: String?, options: OpenVPN.Configuration) { log.info("Session did start") log.info("\tAddress: \(remoteAddress.maskedDescription)") @@ -504,18 +504,18 @@ extension OpenVPNTunnelProvider: OpenVPNSessionDelegate { bringNetworkUp(remoteAddress: remoteAddress, localOptions: session.configuration, remoteOptions: options) { (error) in // FIXME: XPC queue - + self.reasserting = false - + if let error = error { log.error("Failed to configure tunnel: \(error)") self.pendingStartHandler?(error) self.pendingStartHandler = nil return } - + log.info("Tunnel interface is now UP") - + session.setTunnel(tunnel: NETunnelInterface(impl: self.packetFlow)) self.pendingStartHandler?(nil) @@ -525,7 +525,7 @@ extension OpenVPNTunnelProvider: OpenVPNSessionDelegate { isCountingData = true refreshDataCount() } - + public func sessionDidStop(_: OpenVPNSession, withError error: Error?, shouldReconnect: Bool) { cfg._appexSetServerConfiguration(nil) @@ -541,7 +541,7 @@ extension OpenVPNTunnelProvider: OpenVPNSessionDelegate { self.shouldReconnect = shouldReconnect socket?.shutdown() } - + private func bringNetworkUp(remoteAddress: String, localOptions: OpenVPN.Configuration, remoteOptions: OpenVPN.Configuration, completionHandler: @escaping (Error?) -> Void) { let newSettings = NetworkSettingsBuilder(remoteAddress: remoteAddress, localOptions: localOptions, remoteOptions: remoteOptions) @@ -599,13 +599,13 @@ extension OpenVPNTunnelProvider { } return true } - + // MARK: Logging - + private func configureLogging() { let logLevel: SwiftyBeaver.Level = (cfg.shouldDebug ? debugLogLevel : .info) let logFormat = cfg.debugLogFormat ?? "$Dyyyy-MM-dd HH:mm:ss.SSS$d $L $N.$F:$l - $M" - + if cfg.shouldDebug { let console = ConsoleDestination() console.useNSLog = true @@ -623,7 +623,7 @@ extension OpenVPNTunnelProvider { // store path for clients cfg._appexSetDebugLogPath() } - + private func flushLog() { log.debug("Flushing log...") @@ -649,37 +649,37 @@ extension OpenVPNTunnelProvider { // } // MARK: Errors - + private func setErrorStatus(with error: Error) { cfg._appexSetLastError(unifiedError(from: error)) } - + private func unifiedError(from error: Error) -> OpenVPNProviderError { if let te = error.openVPNErrorCode() { switch te { case .cryptoRandomGenerator, .cryptoAlgorithm: return .encryptionInitialization - + case .cryptoEncryption, .cryptoHMAC: return .encryptionData - + case .tlscaRead, .tlscaUse, .tlscaPeerVerification, .tlsClientCertificateRead, .tlsClientCertificateUse, .tlsClientKeyRead, .tlsClientKeyUse: return .tlsInitialization - + case .tlsServerCertificate, .tlsServerEKU, .tlsServerHost: return .tlsServerVerification - + case .tlsHandshake: return .tlsHandshake - + case .dataPathOverflow, .dataPathPeerIdMismatch: return .unexpectedReply - + case .dataPathCompression: return .serverCompression - + default: break } @@ -687,19 +687,19 @@ extension OpenVPNTunnelProvider { switch se { case .negotiationTimeout, .pingTimeout, .staleSession: return .timeout - + case .badCredentials: return .authentication - + case .serverCompression: return .serverCompression - + case .failedLinkWrite: return .linkError - + case .noRouting: return .routing - + case .serverShutdown: return .serverShutdown diff --git a/Sources/TunnelKitOpenVPNAppExtension/ResolvedRemote.swift b/Sources/TunnelKitOpenVPNAppExtension/ResolvedRemote.swift index 2682bce..6a17449 100644 --- a/Sources/TunnelKitOpenVPNAppExtension/ResolvedRemote.swift +++ b/Sources/TunnelKitOpenVPNAppExtension/ResolvedRemote.swift @@ -31,27 +31,27 @@ private let log = SwiftyBeaver.self class ResolvedRemote: CustomStringConvertible { let originalEndpoint: Endpoint - + private(set) var isResolved: Bool - + private(set) var resolvedEndpoints: [Endpoint] private var currentEndpointIndex: Int - + var currentEndpoint: Endpoint? { guard currentEndpointIndex < resolvedEndpoints.count else { return nil } return resolvedEndpoints[currentEndpointIndex] } - + init(_ originalEndpoint: Endpoint) { self.originalEndpoint = originalEndpoint isResolved = false resolvedEndpoints = [] currentEndpointIndex = 0 } - + func nextEndpoint() -> Bool { currentEndpointIndex += 1 return currentEndpointIndex < resolvedEndpoints.count @@ -63,7 +63,7 @@ class ResolvedRemote: CustomStringConvertible { completionHandler() } } - + private func handleResult(_ result: Result<[DNSRecord], DNSError>) { switch result { case .success(let records): @@ -87,9 +87,9 @@ class ResolvedRemote: CustomStringConvertible { log.debug("Unrolled endpoints: \(endpoints.maskedDescription)") return endpoints } - + // MARK: CustomStringConvertible - + var description: String { "{\(originalEndpoint.maskedDescription), resolved: \(resolvedEndpoints.maskedDescription)}" } diff --git a/Sources/TunnelKitOpenVPNCore/CompressionAlgorithm.swift b/Sources/TunnelKitOpenVPNCore/CompressionAlgorithm.swift index 815bca2..ba24eb7 100644 --- a/Sources/TunnelKitOpenVPNCore/CompressionAlgorithm.swift +++ b/Sources/TunnelKitOpenVPNCore/CompressionAlgorithm.swift @@ -27,36 +27,36 @@ import Foundation import CTunnelKitOpenVPNCore extension OpenVPN { - + /// Defines the type of compression algorithm. public enum CompressionAlgorithm: Int, Codable, CustomStringConvertible { - + /// No compression. case disabled - + /// LZO compression. case LZO - + /// Any other compression algorithm (unsupported). case other - + public var native: CompressionAlgorithmNative { guard let val = CompressionAlgorithmNative(rawValue: rawValue) else { fatalError("Unhandled CompressionAlgorithm bridging") } return val } - + // MARK: CustomStringConvertible - + public var description: String { switch self { case .disabled: return "disabled" - + case .LZO: return "lzo" - + case .other: return "other" } diff --git a/Sources/TunnelKitOpenVPNCore/CompressionFraming.swift b/Sources/TunnelKitOpenVPNCore/CompressionFraming.swift index 500bd95..e154db7 100644 --- a/Sources/TunnelKitOpenVPNCore/CompressionFraming.swift +++ b/Sources/TunnelKitOpenVPNCore/CompressionFraming.swift @@ -33,7 +33,7 @@ extension OpenVPN { /// No compression framing. case disabled - + /// Framing compatible with `comp-lzo` (deprecated in 2.4). case compLZO @@ -42,27 +42,27 @@ extension OpenVPN { /// Framing compatible with 2.4 `compress` (version 2, e.g. stub-v2). case compressV2 - + public var native: CompressionFramingNative { guard let val = CompressionFramingNative(rawValue: rawValue) else { fatalError("Unhandled CompressionFraming bridging") } return val } - + // MARK: CustomStringConvertible - + public var description: String { switch self { case .disabled: return "disabled" - + case .compress: return "compress" - + case .compressV2: return "compress" - + case .compLZO: return "comp-lzo" } diff --git a/Sources/TunnelKitOpenVPNCore/Configuration.swift b/Sources/TunnelKitOpenVPNCore/Configuration.swift index 2bd1463..b37c780 100644 --- a/Sources/TunnelKitOpenVPNCore/Configuration.swift +++ b/Sources/TunnelKitOpenVPNCore/Configuration.swift @@ -41,16 +41,16 @@ import TunnelKitCore private let log = SwiftyBeaver.self extension OpenVPN { - + /// A pair of credentials for authentication. public struct Credentials: Codable, Equatable { /// The username. public let username: String - + /// The password. public let password: String - + public init(_ username: String, _ password: String) { self.username = username self.password = password @@ -59,64 +59,64 @@ extension OpenVPN { /// Encryption algorithm. public enum Cipher: String, Codable, CustomStringConvertible { - + // WARNING: must match OpenSSL algorithm names - + /// AES encryption with 128-bit key size and CBC. case aes128cbc = "AES-128-CBC" - + /// AES encryption with 192-bit key size and CBC. case aes192cbc = "AES-192-CBC" - + /// AES encryption with 256-bit key size and CBC. case aes256cbc = "AES-256-CBC" - + /// AES encryption with 128-bit key size and GCM. case aes128gcm = "AES-128-GCM" - + /// AES encryption with 192-bit key size and GCM. case aes192gcm = "AES-192-GCM" - + /// AES encryption with 256-bit key size and GCM. case aes256gcm = "AES-256-GCM" - + /// Returns the key size for this cipher. public var keySize: Int { switch self { case .aes128cbc, .aes128gcm: return 128 - + case .aes192cbc, .aes192gcm: return 192 - + case .aes256cbc, .aes256gcm: return 256 } } - + /// Digest should be ignored when this is `true`. public var embedsDigest: Bool { return rawValue.hasSuffix("-GCM") } - + /// Returns a generic name for this cipher. public var genericName: String { return rawValue.hasSuffix("-GCM") ? "AES-GCM" : "AES-CBC" } - + public var description: String { return rawValue } } - + /// Message digest algorithm. public enum Digest: String, Codable, CustomStringConvertible { - + // WARNING: must match OpenSSL algorithm names - + /// SHA1 message digest. case sha1 = "SHA1" - + /// SHA224 message digest. case sha224 = "SHA224" @@ -128,17 +128,17 @@ extension OpenVPN { /// SHA256 message digest. case sha512 = "SHA512" - + /// Returns a generic name for this digest. public var genericName: String { return "HMAC" } - + public var description: String { return "\(genericName)-\(rawValue)" } } - + /// Routing policy. public enum RoutingPolicy: String, Codable { @@ -147,20 +147,20 @@ extension OpenVPN { /// All IPv6 traffic goes through the VPN. case IPv6 - + /// Block LAN while connected. case blockLocal } - + /// Settings that can be pulled from server. public enum PullMask: String, Codable, CaseIterable { /// Routes and gateways. case routes - + /// DNS settings. case dns - + /// Proxy settings. case proxy } @@ -169,91 +169,91 @@ extension OpenVPN { public struct ConfigurationBuilder { // MARK: General - + /// The cipher algorithm for data encryption. public var cipher: Cipher? - + /// The set of supported cipher algorithms for data encryption (2.5.). public var dataCiphers: [Cipher]? - + /// The digest algorithm for HMAC. public var digest: Digest? - + /// Compression framing, disabled by default. public var compressionFraming: CompressionFraming? - + /// Compression algorithm, disabled by default. public var compressionAlgorithm: CompressionAlgorithm? - + /// The CA for TLS negotiation (PEM format). public var ca: CryptoContainer? - + /// The optional client certificate for TLS negotiation (PEM format). public var clientCertificate: CryptoContainer? - + /// The private key for the certificate in `clientCertificate` (PEM format). public var clientKey: CryptoContainer? - + /// The optional TLS wrapping. public var tlsWrap: TLSWrap? - + /// If set, overrides TLS security level (0 = lowest). public var tlsSecurityLevel: Int? /// Sends periodical keep-alive packets if set. public var keepAliveInterval: TimeInterval? - + /// Disconnects after no keep-alive packets are received within timeout interval if set. public var keepAliveTimeout: TimeInterval? - + /// The number of seconds after which a renegotiation should be initiated. If `nil`, the client will never initiate a renegotiation. public var renegotiatesAfter: TimeInterval? - + // MARK: Client - + /// The list of server endpoints. public var remotes: [Endpoint]? - + /// If true, checks EKU of server certificate. public var checksEKU: Bool? - + /// If true, checks if hostname (sanHost) is present in certificates SAN. public var checksSANHost: Bool? - + /// The server hostname used for checking certificate SAN. public var sanHost: String? - + /// Picks endpoint from `remotes` randomly. public var randomizeEndpoint: Bool? - + /// Prepend hostnames with a number of random bytes defined in `Configuration.randomHostnamePrefixLength`. public var randomizeHostnames: Bool? - + /// Server is patched for the PIA VPN provider. public var usesPIAPatches: Bool? - + /// The tunnel MTU. public var mtu: Int? - + /// Requires username authentication. public var authUserPass: Bool? // MARK: Server - + /// The auth-token returned by the server. public var authToken: String? - + /// The peer-id returned by the server. public var peerId: UInt32? - + // MARK: Routing - + /// The settings for IPv4. `OpenVPNSession` only evaluates this server-side. public var ipv4: IPv4Settings? - + /// The settings for IPv6. `OpenVPNSession` only evaluates this server-side. public var ipv6: IPv6Settings? - + /// The IPv4 routes if `ipv4` is nil. public var routes4: [IPv4Settings.Route]? @@ -262,13 +262,13 @@ extension OpenVPN { /// Set false to ignore DNS settings, even when pushed. public var isDNSEnabled: Bool? - + /// The DNS protocol, defaults to `.plain` (iOS 14+ / macOS 11+). public var dnsProtocol: DNSProtocol? /// The DNS servers if `dnsProtocol = .plain` or nil. public var dnsServers: [String]? - + /// The server URL if `dnsProtocol = .https`. public var dnsHTTPSURL: URL? @@ -295,30 +295,30 @@ extension OpenVPN { /// The Proxy Auto-Configuration (PAC) url. public var proxyAutoConfigurationURL: URL? - + /// Set false to ignore proxy settings, even when pushed. public var isProxyEnabled: Bool? - + /// The HTTP proxy. public var httpProxy: Proxy? /// The HTTPS proxy. public var httpsProxy: Proxy? - + /// The list of domains not passing through the proxy. public var proxyBypassDomains: [String]? - + /// Policies for redirecting traffic through the VPN gateway. public var routingPolicies: [RoutingPolicy]? - + /// Server settings that must not be pulled. public var noPullMask: [PullMask]? // MARK: Extra - + /// The method to follow in regards to the XOR patch. public var xorMethod: XORMethod? - + /** Creates a `ConfigurationBuilder`. @@ -332,7 +332,7 @@ extension OpenVPN { compressionAlgorithm = Configuration.Fallback.compressionAlgorithm } } - + /** Builds a `Configuration` object. @@ -386,45 +386,45 @@ extension OpenVPN { ) } } - + /// The immutable configuration for `OpenVPNSession`. public struct Configuration: Codable, Equatable { struct Fallback { static let cipher: Cipher = .aes128cbc - + static let digest: Digest = .sha1 - + static let compressionFraming: CompressionFraming = .disabled - + static let compressionAlgorithm: CompressionAlgorithm = .disabled } - + private static let randomHostnamePrefixLength = 6 - + /// - Seealso: `ConfigurationBuilder.cipher` public let cipher: Cipher? - + /// - Seealso: `ConfigurationBuilder.dataCiphers` public let dataCiphers: [Cipher]? - + /// - Seealso: `ConfigurationBuilder.digest` public let digest: Digest? - + /// - Seealso: `ConfigurationBuilder.compressionFraming` public let compressionFraming: CompressionFraming? - + /// - Seealso: `ConfigurationBuilder.compressionAlgorithm` public let compressionAlgorithm: CompressionAlgorithm? - + /// - Seealso: `ConfigurationBuilder.ca` public let ca: CryptoContainer? - + /// - Seealso: `ConfigurationBuilder.clientCertificate` public let clientCertificate: CryptoContainer? - + /// - Seealso: `ConfigurationBuilder.clientKey` public let clientKey: CryptoContainer? - + /// - Seealso: `ConfigurationBuilder.tlsWrap` public let tlsWrap: TLSWrap? @@ -433,7 +433,7 @@ extension OpenVPN { /// - Seealso: `ConfigurationBuilder.keepAliveInterval` public let keepAliveInterval: TimeInterval? - + /// - Seealso: `ConfigurationBuilder.keepAliveTimeout` public let keepAliveTimeout: TimeInterval? @@ -445,22 +445,22 @@ extension OpenVPN { /// - Seealso: `ConfigurationBuilder.checksEKU` public let checksEKU: Bool? - + /// - Seealso: `ConfigurationBuilder.checksSANHost` public let checksSANHost: Bool? - + /// - Seealso: `ConfigurationBuilder.sanHost` public let sanHost: String? - + /// - Seealso: `ConfigurationBuilder.randomizeEndpoint` public let randomizeEndpoint: Bool? - + /// - Seealso: `ConfigurationBuilder.randomizeHostnames` public var randomizeHostnames: Bool? /// - Seealso: `ConfigurationBuilder.usesPIAPatches` public let usesPIAPatches: Bool? - + /// - Seealso: `ConfigurationBuilder.mtu` public let mtu: Int? @@ -469,10 +469,10 @@ extension OpenVPN { /// - Seealso: `ConfigurationBuilder.authToken` public let authToken: String? - + /// - Seealso: `ConfigurationBuilder.peerId` public let peerId: UInt32? - + /// - Seealso: `ConfigurationBuilder.ipv4` public let ipv4: IPv4Settings? @@ -490,16 +490,16 @@ extension OpenVPN { /// - Seealso: `ConfigurationBuilder.dnsProtocol` public let dnsProtocol: DNSProtocol? - + /// - Seealso: `ConfigurationBuilder.dnsServers` public let dnsServers: [String]? - + /// - Seealso: `ConfigurationBuilder.dnsHTTPSURL` public let dnsHTTPSURL: URL? - + /// - Seealso: `ConfigurationBuilder.dnsTLSServerName` public let dnsTLSServerName: String? - + /// - Seealso: `ConfigurationBuilder.dnsDomain` public let dnsDomain: String? @@ -514,24 +514,24 @@ extension OpenVPN { /// - Seealso: `ConfigurationBuilder.httpsProxy` public let httpsProxy: Proxy? - + /// - Seealso: `ConfigurationBuilder.proxyAutoConfigurationURL` public let proxyAutoConfigurationURL: URL? /// - Seealso: `ConfigurationBuilder.proxyBypassDomains` public let proxyBypassDomains: [String]? - + /// - Seealso: `ConfigurationBuilder.routingPolicies` public let routingPolicies: [RoutingPolicy]? - + /// - Seealso: `ConfigurationBuilder.noPullMask` public let noPullMask: [PullMask]? - + /// - Seealso: `ConfigurationBuilder.xorMethod` public let xorMethod: XORMethod? - + // MARK: Shortcuts - + public var fallbackCipher: Cipher { return cipher ?? Fallback.cipher } @@ -556,9 +556,9 @@ extension OpenVPN { let pulled = Array(Set(all).subtracting(notPulled)) return !pulled.isEmpty ? pulled : nil } - + // MARK: Computed - + public var processedRemotes: [Endpoint]? { guard var processedRemotes = remotes else { return nil @@ -584,7 +584,7 @@ extension OpenVPN { // MARK: Modification extension OpenVPN.Configuration { - + /** Returns a `ConfigurationBuilder` to use this configuration as a starting point for a new one. @@ -701,7 +701,7 @@ extension OpenVPN.Configuration { log.info("\tTLS security level: default") } } - + if let keepAliveSeconds = keepAliveInterval, keepAliveSeconds > 0 { log.info("\tKeep-alive interval: \(keepAliveSeconds.asTimeString)") } else if isLocal { diff --git a/Sources/TunnelKitOpenVPNCore/ConfigurationError.swift b/Sources/TunnelKitOpenVPNCore/ConfigurationError.swift index 851845f..ca872ec 100644 --- a/Sources/TunnelKitOpenVPNCore/ConfigurationError.swift +++ b/Sources/TunnelKitOpenVPNCore/ConfigurationError.swift @@ -29,19 +29,19 @@ extension OpenVPN { /// Error raised by the configuration parser, with details about the line that triggered it. public enum ConfigurationError: Error { - + /// Option syntax is incorrect. case malformed(option: String) - + /// A required option is missing. case missingConfiguration(option: String) - + /// An option is unsupported. case unsupportedConfiguration(option: String) - + /// Passphrase required to decrypt private keys. case encryptionPassphrase - + /// Encryption passphrase is incorrect or key is corrupt. case unableToDecrypt(error: Error) } diff --git a/Sources/TunnelKitOpenVPNCore/ConfigurationParser.swift b/Sources/TunnelKitOpenVPNCore/ConfigurationParser.swift index d5237ff..f8ae052 100644 --- a/Sources/TunnelKitOpenVPNCore/ConfigurationParser.swift +++ b/Sources/TunnelKitOpenVPNCore/ConfigurationParser.swift @@ -37,136 +37,136 @@ extension OpenVPN { public class ConfigurationParser { // XXX: parsing is very optimistic - + /// Regexes used to parse OpenVPN options. public struct Regex { - + // MARK: General - + static let cipher = NSRegularExpression("^cipher +[^,\\s]+") - + static let dataCiphers = NSRegularExpression("^(data-ciphers|ncp-ciphers) +[^,\\s]+(:[^,\\s]+)*") - + static let dataCiphersFallback = NSRegularExpression("^data-ciphers-fallback +[^,\\s]+") - + static let auth = NSRegularExpression("^auth +[\\w\\-]+") - + static let compLZO = NSRegularExpression("^comp-lzo.*") - + static let compress = NSRegularExpression("^compress.*") - + static let keyDirection = NSRegularExpression("^key-direction +\\d") - + static let ping = NSRegularExpression("^ping +\\d+") - + static let pingRestart = NSRegularExpression("^ping-restart +\\d+") - + static let keepAlive = NSRegularExpression("^keepalive +\\d+ ++\\d+") - + static let renegSec = NSRegularExpression("^reneg-sec +\\d+") - + static let blockBegin = NSRegularExpression("^<[\\w\\-]+>") - + static let blockEnd = NSRegularExpression("^<\\/[\\w\\-]+>") - + // MARK: Client - + static let proto = NSRegularExpression("^proto +(udp[46]?|tcp[46]?)") - + static let port = NSRegularExpression("^port +\\d+") - + static let remote = NSRegularExpression("^remote +[^ ]+( +\\d+)?( +(udp[46]?|tcp[46]?))?") - + static let authUserPass = NSRegularExpression("^auth-user-pass") - + static let eku = NSRegularExpression("^remote-cert-tls +server") - + static let remoteRandom = NSRegularExpression("^remote-random") - + static let remoteRandomHostname = NSRegularExpression("^remote-random-hostname") - + static let mtu = NSRegularExpression("^tun-mtu +\\d+") - + // MARK: Server - + public static let authToken = NSRegularExpression("^auth-token +[a-zA-Z0-9/=+]+") - + static let peerId = NSRegularExpression("^peer-id +[0-9]+") - + // MARK: Routing - + static let topology = NSRegularExpression("^topology +(net30|p2p|subnet)") - + static let ifconfig = NSRegularExpression("^ifconfig +[\\d\\.]+ [\\d\\.]+") - + static let ifconfig6 = NSRegularExpression("^ifconfig-ipv6 +[\\da-fA-F:]+/\\d+ [\\da-fA-F:]+") - + static let route = NSRegularExpression("^route +[\\d\\.]+( +[\\d\\.]+){0,2}") - + static let route6 = NSRegularExpression("^route-ipv6 +[\\da-fA-F:]+/\\d+( +[\\da-fA-F:]+){0,2}") - + static let gateway = NSRegularExpression("^route-gateway +[\\d\\.]+") - + static let dns = NSRegularExpression("^dhcp-option +DNS6? +[\\d\\.a-fA-F:]+") - + static let domain = NSRegularExpression("^dhcp-option +DOMAIN +[^ ]+") - + static let domainSearch = NSRegularExpression("^dhcp-option +DOMAIN-SEARCH +[^ ]+") static let proxy = NSRegularExpression("^dhcp-option +PROXY_(HTTPS? +[^ ]+ +\\d+|AUTO_CONFIG_URL +[^ ]+)") - + static let proxyBypass = NSRegularExpression("^dhcp-option +PROXY_BYPASS +.+") - + static let redirectGateway = NSRegularExpression("^redirect-gateway.*") static let routeNoPull = NSRegularExpression("^route-nopull") - + // MARK: Extra static let xorInfo = NSRegularExpression("^scramble +(xormask|xorptrpos|reverse|obfuscate)[\\s]?([^\\s]+)?") - + // MARK: Unsupported - + // static let fragment = NSRegularExpression("^fragment +\\d+") static let fragment = NSRegularExpression("^fragment") - + static let connectionProxy = NSRegularExpression("^\\w+-proxy") - + static let externalFiles = NSRegularExpression("^(ca|cert|key|tls-auth|tls-crypt) ") - + static let connection = NSRegularExpression("^") - + // MARK: Continuation - + static let continuation = NSRegularExpression("^push-continuation [12]") } - + private enum Topology: String { case net30 - + case p2p - + case subnet } - + private enum RedirectGateway: String { case def1 // default case noIPv4 = "!ipv4" - + case ipv6 case local - + case autolocal - + case blockLocal = "block-local" case bypassDHCP = "bypass-dhcp" - + case bypassDNS = "bypass-dns" } - + /// Result of the parser. public struct Result { @@ -181,11 +181,11 @@ extension OpenVPN { /// /// - Seealso: `ConfigurationParser.parsed(...)` public let strippedLines: [String]? - + /// Holds an optional `ConfigurationError` that didn't block the parser, but it would be worth taking care of. public let warning: ConfigurationError? } - + /** Parses a configuration from a .ovpn file. @@ -254,7 +254,7 @@ extension OpenVPN { var unsupportedError: ConfigurationError? var currentBlockName: String? var currentBlock: [String] = [] - + var optDataCiphers: [Cipher]? var optDataCiphersFallback: Cipher? var optCipher: Cipher? @@ -304,7 +304,7 @@ extension OpenVPN { log.verbose("Configuration file:") for line in lines { log.verbose(line) - + var isHandled = false var strippedLine = line defer { @@ -312,9 +312,9 @@ extension OpenVPN { optStrippedLines?.append(strippedLine) } } - + // MARK: Unsupported - + // check blocks first Regex.connection.enumerateSpacedComponents(in: line) { (_) in unsupportedError = ConfigurationError.unsupportedConfiguration(option: " blocks") @@ -331,7 +331,7 @@ extension OpenVPN { if line.contains("mtu") || line.contains("mssfix") { isHandled = true } - + // MARK: Continuation var isContinuation = false @@ -343,7 +343,7 @@ extension OpenVPN { } // MARK: Inline content - + if unsupportedError == nil { if currentBlockName == nil { Regex.blockBegin.enumerateSpacedComponents(in: line) { @@ -351,7 +351,7 @@ extension OpenVPN { let tag = $0.first! let from = tag.index(after: tag.startIndex) let to = tag.index(before: tag.endIndex) - + currentBlockName = String(tag[from.. 1) ? routeEntryArguments[1] : "255.255.255.255" var gateway = (routeEntryArguments.count > 2) ? routeEntryArguments[2] : nil // defaultGateway4 @@ -625,7 +625,7 @@ extension OpenVPN { } Regex.route6.enumerateSpacedArguments(in: line) { let routeEntryArguments = $0 - + let destinationComponents = routeEntryArguments[0].components(separatedBy: "/") guard destinationComponents.count == 2 else { return @@ -633,7 +633,7 @@ extension OpenVPN { guard let prefix = UInt8(destinationComponents[1]) else { return } - + let destination = destinationComponents[0] var gateway = (routeEntryArguments.count > 1) ? routeEntryArguments[1] : nil // defaultGateway6 if gateway == "vpn_gateway" { @@ -687,7 +687,7 @@ extension OpenVPN { switch $0[0] { case "PROXY_HTTPS": optHTTPSProxy = Proxy($0[1], port) - + case "PROXY_HTTP": optHTTPProxy = Proxy($0[1], port) @@ -717,7 +717,7 @@ extension OpenVPN { Regex.routeNoPull.enumerateSpacedComponents(in: line) { _ in optRouteNoPull = true } - + // MARK: Extra Regex.xorInfo.enumerateSpacedArguments(in: line) { @@ -747,14 +747,14 @@ extension OpenVPN { return } } - + // - + if let error = unsupportedError { throw error } } - + if isClient { guard let _ = optCA else { throw ConfigurationError.missingConfiguration(option: "ca") @@ -763,9 +763,9 @@ extension OpenVPN { throw ConfigurationError.missingConfiguration(option: "cipher or data-ciphers") } } - + // MARK: Post-processing - + // ensure that non-nil network settings also imply non-empty if let array = optRoutes4 { assert(!array.isEmpty) @@ -784,11 +784,11 @@ extension OpenVPN { } // - + var sessionBuilder = ConfigurationBuilder() - + // MARK: General - + sessionBuilder.cipher = optDataCiphersFallback ?? optCipher sessionBuilder.dataCiphers = optDataCiphers sessionBuilder.digest = optDigest @@ -797,7 +797,7 @@ extension OpenVPN { sessionBuilder.ca = optCA sessionBuilder.clientCertificate = optClientCertificate sessionBuilder.authUserPass = authUserPass - + if let clientKey = optClientKey, clientKey.isEncrypted { // FIXME: remove dependency on TLSBox guard let passphrase = passphrase, !passphrase.isEmpty else { @@ -811,13 +811,13 @@ extension OpenVPN { } else { sessionBuilder.clientKey = optClientKey } - + if let keyLines = optTLSKeyLines, let strategy = optTLSStrategy { let optKey: StaticKey? switch strategy { case .auth: optKey = StaticKey(lines: keyLines, direction: optKeyDirection) - + case .crypt: optKey = StaticKey(lines: keyLines, direction: .client) } @@ -825,13 +825,13 @@ extension OpenVPN { sessionBuilder.tlsWrap = TLSWrap(strategy: strategy, key: key) } } - + sessionBuilder.keepAliveInterval = optKeepAliveSeconds sessionBuilder.keepAliveTimeout = optKeepAliveTimeoutSeconds sessionBuilder.renegotiatesAfter = optRenegotiateAfterSeconds - + // MARK: Client - + optDefaultProto = optDefaultProto ?? .udp optDefaultPort = optDefaultPort ?? 1194 if !optRemotes.isEmpty { @@ -850,20 +850,20 @@ extension OpenVPN { Endpoint($0.0, .init($0.2, $0.1)) } } - + sessionBuilder.authUserPass = authUserPass sessionBuilder.checksEKU = optChecksEKU sessionBuilder.randomizeEndpoint = optRandomizeEndpoint sessionBuilder.randomizeHostnames = optRandomizeHostnames sessionBuilder.mtu = optMTU - + // MARK: Server - + sessionBuilder.authToken = optAuthToken sessionBuilder.peerId = optPeerId - + // MARK: Routing - + // // excerpts from OpenVPN manpage // @@ -881,15 +881,15 @@ extension OpenVPN { guard ifconfig4Arguments.count == 2 else { throw ConfigurationError.malformed(option: "ifconfig takes 2 arguments") } - + let address4: String let addressMask4: String let defaultGateway4: String - + let topology = Topology(rawValue: optTopology ?? "") ?? .net30 switch topology { case .subnet: - + // default gateway required when topology is subnet guard let gateway4Arguments = optGateway4Arguments, gateway4Arguments.count == 1 else { throw ConfigurationError.malformed(option: "route-gateway takes 1 argument") @@ -897,7 +897,7 @@ extension OpenVPN { address4 = ifconfig4Arguments[0] addressMask4 = ifconfig4Arguments[1] defaultGateway4 = gateway4Arguments[0] - + default: address4 = ifconfig4Arguments[0] addressMask4 = "255.255.255.255" @@ -913,7 +913,7 @@ extension OpenVPN { sessionBuilder.routes4 = optRoutes4?.map { IPv4Settings.Route($0.0, $0.1, $0.2) } - + if let ifconfig6Arguments = optIfconfig6Arguments { guard ifconfig6Arguments.count == 2 else { throw ConfigurationError.malformed(option: "ifconfig-ipv6 takes 2 arguments") @@ -925,10 +925,10 @@ extension OpenVPN { guard let addressPrefix6 = UInt8(address6Components[1]) else { throw ConfigurationError.malformed(option: "ifconfig-ipv6 address prefix must be a 8-bit number") } - + let address6 = address6Components[0] let defaultGateway6 = ifconfig6Arguments[1] - + sessionBuilder.ipv6 = IPv6Settings( address: address6, addressPrefixLength: addressPrefix6, @@ -938,7 +938,7 @@ extension OpenVPN { sessionBuilder.routes6 = optRoutes6?.map { IPv6Settings.Route($0.0, $0.1, $0.2) } - + sessionBuilder.dnsServers = optDNSServers sessionBuilder.dnsDomain = optDomain sessionBuilder.searchDomains = optSearchDomains @@ -956,10 +956,10 @@ extension OpenVPN { switch opt { case .def1: policies.insert(.IPv4) - + case .ipv6: policies.insert(.IPv6) - + case .blockLocal: policies.insert(.blockLocal) @@ -973,13 +973,13 @@ extension OpenVPN { } sessionBuilder.routingPolicies = [RoutingPolicy](policies) } - + // MARK: Extra - + sessionBuilder.xorMethod = optXorMethod // - + return Result( url: originalURL, configuration: sessionBuilder.build(), @@ -992,7 +992,7 @@ extension OpenVPN { // if block.count >= 1 && block[0].contains("ENCRYPTED") { // return true // } - + // XXX: restore blank line after encryption header (easier than tweaking trimmedLines) if block.count >= 3 && block[1].contains("Proc-Type") { block.insert("", at: 3) diff --git a/Sources/TunnelKitOpenVPNCore/CryptoContainer.swift b/Sources/TunnelKitOpenVPNCore/CryptoContainer.swift index d90e26b..f6f2076 100644 --- a/Sources/TunnelKitOpenVPNCore/CryptoContainer.swift +++ b/Sources/TunnelKitOpenVPNCore/CryptoContainer.swift @@ -46,14 +46,14 @@ extension OpenVPN { private static let begin = "-----BEGIN " private static let end = "-----END " - + /// The content in PEM format (ASCII). public let pem: String - + var isEncrypted: Bool { return pem.contains("ENCRYPTED") } - + public init(pem: String) { guard let beginRange = pem.range(of: CryptoContainer.begin) else { self.pem = "" @@ -61,7 +61,7 @@ extension OpenVPN { } self.pem = String(pem[beginRange.lowerBound...]) } - + func write(to url: URL) throws { try pem.write(to: url, atomically: true, encoding: .ascii) } @@ -73,13 +73,13 @@ extension OpenVPN { } // MARK: Codable - + public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let pem = try container.decode(String.self) self.init(pem: pem) } - + public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(pem) diff --git a/Sources/TunnelKitOpenVPNCore/Errors.swift b/Sources/TunnelKitOpenVPNCore/Errors.swift index 21fb5d8..c96c3be 100644 --- a/Sources/TunnelKitOpenVPNCore/Errors.swift +++ b/Sources/TunnelKitOpenVPNCore/Errors.swift @@ -31,7 +31,7 @@ extension Error { let te = self as NSError return te.domain == OpenVPNErrorDomain } - + public func openVPNErrorCode() -> OpenVPNErrorCode? { let te = self as NSError guard te.domain == OpenVPNErrorDomain else { diff --git a/Sources/TunnelKitOpenVPNCore/OpenVPNError.swift b/Sources/TunnelKitOpenVPNCore/OpenVPNError.swift index 5ea1035..4124ddf 100644 --- a/Sources/TunnelKitOpenVPNCore/OpenVPNError.swift +++ b/Sources/TunnelKitOpenVPNCore/OpenVPNError.swift @@ -38,37 +38,37 @@ import Foundation /// The possible errors raised/thrown during `OpenVPNSession` operation. public enum OpenVPNError: String, Error { - + /// The negotiation timed out. case negotiationTimeout - + /// The VPN session id is missing. case missingSessionId - + /// The VPN session id doesn't match. case sessionMismatch - + /// The connection key is wrong or wasn't expected. case badKey - + /// The control packet has an incorrect prefix payload. case wrongControlDataPrefix - + /// The provided credentials failed authentication. case badCredentials - + /// The PUSH_REPLY is multipart. case continuationPushReply - + /// The reply to PUSH_REQUEST is malformed. case malformedPushReply - + /// A write operation failed at the link layer (e.g. network unreachable). case failedLinkWrite - + /// The server couldn't ping back before timeout. case pingTimeout - + /// The session reached a stale state and can't be recovered. case staleSession diff --git a/Sources/TunnelKitOpenVPNCore/StaticKey.swift b/Sources/TunnelKitOpenVPNCore/StaticKey.swift index 3bcbc9f..f51e7b8 100644 --- a/Sources/TunnelKitOpenVPNCore/StaticKey.swift +++ b/Sources/TunnelKitOpenVPNCore/StaticKey.swift @@ -33,7 +33,7 @@ extension OpenVPN { public struct StaticKey: Codable, Equatable { enum CodingKeys: CodingKey { case data - + case dir } @@ -42,27 +42,27 @@ extension OpenVPN { /// Conventional server direction (implicit for tls-crypt). case server = 0 - + /// Conventional client direction (implicit for tls-crypt). case client = 1 } - + private static let contentLength = 256 // 2048-bit - + private static let keyCount = 4 - + private static let keyLength = StaticKey.contentLength / StaticKey.keyCount private static let fileHead = "-----BEGIN OpenVPN Static key V1-----" - + private static let fileFoot = "-----END OpenVPN Static key V1-----" - + private static let nonHexCharset = CharacterSet(charactersIn: "0123456789abcdefABCDEF").inverted - + private let secureData: ZeroingData public let direction: Direction? - + /// Returns the encryption key. /// /// - Precondition: `direction` must be non-nil. @@ -74,7 +74,7 @@ extension OpenVPN { switch direction { case .server: return key(at: 0) - + case .client: return key(at: 2) } @@ -91,12 +91,12 @@ extension OpenVPN { switch direction { case .server: return key(at: 2) - + case .client: return key(at: 0) } } - + /// Returns the HMAC sending key. /// /// - Seealso: `ConfigurationBuilder.tlsWrap` @@ -107,12 +107,12 @@ extension OpenVPN { switch direction { case .server: return key(at: 1) - + case .client: return key(at: 3) } } - + /// Returns the HMAC receiving key. /// /// - Seealso: `ConfigurationBuilder.tlsWrap` @@ -123,12 +123,12 @@ extension OpenVPN { switch direction { case .server: return key(at: 3) - + case .client: return key(at: 1) } } - + /** Initializes with data and direction. @@ -140,7 +140,7 @@ extension OpenVPN { secureData = Z(data) self.direction = direction } - + /** Initializes with file content and direction. @@ -151,7 +151,7 @@ extension OpenVPN { let lines = file.split(separator: "\n") self.init(lines: lines, direction: direction) } - + public init?(lines: [Substring], direction: Direction?) { var isHead = true var hexLines: [Substring] = [] @@ -187,10 +187,10 @@ extension OpenVPN { return nil } let data = Data(hex: hex) - + self.init(data: data, direction: direction) } - + /** Initializes as bidirectional. @@ -199,41 +199,41 @@ extension OpenVPN { public init(biData data: Data) { self.init(data: data, direction: nil) } - + private func key(at: Int) -> ZeroingData { let size = secureData.count / StaticKey.keyCount // 64 bytes each assert(size == StaticKey.keyLength) return secureData.withOffset(at * size, count: size) } - + public static func deserialized(_ data: Data) throws -> StaticKey { return try JSONDecoder().decode(StaticKey.self, from: data) } - + public func serialized() -> Data? { return try? JSONEncoder().encode(self) } - + // MARK: Equatable - + public static func ==(lhs: Self, rhs: Self) -> Bool { return lhs.secureData.toData() == rhs.secureData.toData() } - + // MARK: Codable - + public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) secureData = Z(try container.decode(Data.self, forKey: .data)) direction = try container.decodeIfPresent(Direction.self, forKey: .dir) } - + public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(secureData.toData(), forKey: .data) try container.encodeIfPresent(direction, forKey: .dir) } - + public var hexString: String { return secureData.toHex() } diff --git a/Sources/TunnelKitOpenVPNCore/TLSWrap.swift b/Sources/TunnelKitOpenVPNCore/TLSWrap.swift index cf14fd3..22bae49 100644 --- a/Sources/TunnelKitOpenVPNCore/TLSWrap.swift +++ b/Sources/TunnelKitOpenVPNCore/TLSWrap.swift @@ -32,7 +32,7 @@ extension OpenVPN { /// The wrapping strategy. public enum Strategy: String, Codable, Equatable { - + /// Authenticates payload (--tls-auth). case auth @@ -42,10 +42,10 @@ extension OpenVPN { /// The wrapping strategy. public let strategy: Strategy - + /// The static encryption key. public let key: StaticKey - + public init(strategy: Strategy, key: StaticKey) { self.strategy = strategy self.key = key @@ -54,7 +54,7 @@ extension OpenVPN { public static func deserialized(_ data: Data) throws -> TLSWrap { return try JSONDecoder().decode(TLSWrap.self, from: data) } - + public func serialized() -> Data? { return try? JSONEncoder().encode(self) } diff --git a/Sources/TunnelKitOpenVPNCore/XORMethod.swift b/Sources/TunnelKitOpenVPNCore/XORMethod.swift index e7b45e7..c4d218a 100644 --- a/Sources/TunnelKitOpenVPNCore/XORMethod.swift +++ b/Sources/TunnelKitOpenVPNCore/XORMethod.swift @@ -27,19 +27,19 @@ import Foundation import CTunnelKitOpenVPNCore extension OpenVPN { - + /// The obfuscation method. public enum XORMethod: Codable, Equatable { - + /// XORs the bytes in each buffer with the given mask. case xormask(mask: Data) - + /// XORs each byte with its position in the packet. case xorptrpos - + /// Reverses the order of bytes in each buffer except for the first (abcde becomes aedcb). case reverse - + /// Performs several of the above steps (xormask -> xorptrpos -> reverse -> xorptrpos). case obfuscate(mask: Data) @@ -48,13 +48,13 @@ extension OpenVPN { switch self { case .xormask: return .mask - + case .xorptrpos: return .ptrPos - + case .reverse: return .reverse - + case .obfuscate: return .obfuscate } @@ -65,10 +65,10 @@ extension OpenVPN { switch self { case .xormask(let mask): return mask - + case .obfuscate(let mask): return mask - + default: return nil } diff --git a/Sources/TunnelKitOpenVPNManager/OpenVPN+ProviderConfiguration.swift b/Sources/TunnelKitOpenVPNManager/OpenVPN+ProviderConfiguration.swift index f13aed7..c784db5 100644 --- a/Sources/TunnelKitOpenVPNManager/OpenVPN+ProviderConfiguration.swift +++ b/Sources/TunnelKitOpenVPNManager/OpenVPN+ProviderConfiguration.swift @@ -46,34 +46,34 @@ extension OpenVPN { case lastError = "OpenVPN.LastError" } - + /// Optional version identifier about the client pushed to server in peer-info as `IV_UI_VER`. public var versionIdentifier: String? /// The configuration title. public let title: String - + /// The access group for shared data. public let appGroup: String /// The client configuration. public let configuration: OpenVPN.Configuration - + /// The optional username. public var username: String? - + /// Enables debugging. public var shouldDebug = false - + /// Debug log path. - public var debugLogPath: String? = nil + public var debugLogPath: String? /// Optional debug log format (SwiftyBeaver format). - public var debugLogFormat: String? = nil - + public var debugLogFormat: String? + /// Mask private data in debug log (default is `true`). public var masksPrivateData = true - + public init(_ title: String, appGroup: String, configuration: OpenVPN.Configuration) { self.title = title self.appGroup = appGroup @@ -142,7 +142,7 @@ extension OpenVPN.ProviderConfiguration { public var lastError: OpenVPNProviderError? { return defaults?.openVPNLastError } - + /** The URL of the latest debug log. */ @@ -208,7 +208,7 @@ extension UserDefaults { openVPNDataCountArray = [newValue.received, newValue.sent] } } - + @objc private var openVPNDataCountArray: [UInt]? { get { return array(forKey: OpenVPN.ProviderConfiguration.Keys.dataCount.rawValue) as? [UInt] @@ -217,7 +217,7 @@ extension UserDefaults { set(newValue, forKey: OpenVPN.ProviderConfiguration.Keys.dataCount.rawValue) } } - + private func openVPNRemoveDataCountArray() { removeObject(forKey: OpenVPN.ProviderConfiguration.Keys.dataCount.rawValue) } @@ -249,7 +249,7 @@ extension UserDefaults { } } } - + public fileprivate(set) var openVPNLastError: OpenVPNProviderError? { get { guard let rawValue = string(forKey: OpenVPN.ProviderConfiguration.Keys.lastError.rawValue) else { diff --git a/Sources/TunnelKitOpenVPNManager/OpenVPNProviderError.swift b/Sources/TunnelKitOpenVPNManager/OpenVPNProviderError.swift index adf3ec2..8c40385 100644 --- a/Sources/TunnelKitOpenVPNManager/OpenVPNProviderError.swift +++ b/Sources/TunnelKitOpenVPNManager/OpenVPNProviderError.swift @@ -38,35 +38,35 @@ import Foundation /// Mostly programming errors by host app. public enum OpenVPNProviderConfigurationError: Error { - + /// A field in the `OpenVPNProvider.Configuration` provided is incorrect or incomplete. case parameter(name: String) - + /// Credentials are missing or inaccessible. case credentials(details: String) - + /// The pseudo-random number generator could not be initialized. case prngInitialization - + /// The TLS certificate could not be serialized. case certificateSerialization } - + /// The errors causing a tunnel disconnection. public enum OpenVPNProviderError: String, Error { - + /// Socket endpoint could not be resolved. case dnsFailure - + /// No more endpoints available to try. case exhaustedEndpoints - + /// Socket failed to reach active state. case socketActivity - + /// Credentials authentication failed. case authentication - + /// TLS could not be initialized (e.g. malformed CA or client PEMs). case tlsInitialization @@ -75,37 +75,37 @@ public enum OpenVPNProviderError: String, Error { /// TLS handshake failed. case tlsHandshake - + /// The encryption logic could not be initialized (e.g. PRNG, algorithms). case encryptionInitialization - + /// Data encryption/decryption failed. case encryptionData - + /// The LZO engine failed. case lzo - + /// Server uses an unsupported compression algorithm. case serverCompression - + /// Tunnel timed out. case timeout - + /// An error occurred at the link level. case linkError - + /// Network routing information is missing or incomplete. case routing - + /// The current network changed (e.g. switched from WiFi to data connection). case networkChanged - + /// Default gateway could not be attained. case gatewayUnattainable - + /// Remove server has shut down. case serverShutdown - + /// The server replied in an unexpected way. case unexpectedReply } diff --git a/Sources/TunnelKitOpenVPNProtocol/Authenticator.swift b/Sources/TunnelKitOpenVPNProtocol/Authenticator.swift index 1a6f2ae..d9abf06 100644 --- a/Sources/TunnelKitOpenVPNProtocol/Authenticator.swift +++ b/Sources/TunnelKitOpenVPNProtocol/Authenticator.swift @@ -53,28 +53,28 @@ fileprivate extension ZeroingData { extension OpenVPN { class Authenticator { private var controlBuffer: ZeroingData - + private(set) var preMaster: ZeroingData - + private(set) var random1: ZeroingData - + private(set) var random2: ZeroingData - + private(set) var serverRandom1: ZeroingData? private(set) var serverRandom2: ZeroingData? private(set) var username: ZeroingData? - + private(set) var password: ZeroingData? - + var withLocalOptions: Bool - + init(_ username: String?, _ password: String?) throws { preMaster = try SecureRandom.safeData(length: CoreConfiguration.OpenVPN.preMasterLength) random1 = try SecureRandom.safeData(length: CoreConfiguration.OpenVPN.randomLength) random2 = try SecureRandom.safeData(length: CoreConfiguration.OpenVPN.randomLength) - + // XXX: not 100% secure, can't erase input username/password if let username = username, let password = password { self.username = Z(username, nullTerminated: true) @@ -83,12 +83,12 @@ extension OpenVPN { self.username = nil self.password = nil } - + withLocalOptions = true - + controlBuffer = Z() } - + func reset() { controlBuffer.zero() preMaster.zero() @@ -99,18 +99,18 @@ extension OpenVPN { username = nil password = nil } - + // MARK: Authentication request // Ruby: on_tls_connect func putAuth(into: TLSBox, options: Configuration) throws { let raw = Z(ProtocolMacros.tlsPrefix) - + // local keys raw.append(preMaster) raw.append(random1) raw.append(random2) - + // options string let optsString: String if withLocalOptions { @@ -122,10 +122,10 @@ extension OpenVPN { switch comp { case .compLZO: opts.append("comp-lzo") - + case .compress: opts.append("compress") - + default: break } @@ -147,7 +147,7 @@ extension OpenVPN { } log.debug("TLS.auth: Local options: \(optsString)") raw.appendSized(Z(optsString, nullTerminated: true)) - + // credentials if let username = username, let password = password { raw.appendSized(username) @@ -169,40 +169,40 @@ extension OpenVPN { } else { log.debug("TLS.auth: Put plaintext (\(raw.count) bytes)") } - + try into.putRawPlainText(raw.bytes, length: raw.count) } - + // MARK: Server replies func appendControlData(_ data: ZeroingData) { controlBuffer.append(data) } - + func parseAuthReply() throws -> Bool { let prefixLength = ProtocolMacros.tlsPrefix.count // TLS prefix + random (x2) + opts length [+ opts] - guard (controlBuffer.count >= prefixLength + 2 * CoreConfiguration.OpenVPN.randomLength + 2) else { + guard controlBuffer.count >= prefixLength + 2 * CoreConfiguration.OpenVPN.randomLength + 2 else { return false } - + let prefix = controlBuffer.withOffset(0, count: prefixLength) guard prefix.isEqual(to: ProtocolMacros.tlsPrefix) else { throw OpenVPNError.wrongControlDataPrefix } - + var offset = ProtocolMacros.tlsPrefix.count - + let serverRandom1 = controlBuffer.withOffset(offset, count: CoreConfiguration.OpenVPN.randomLength) offset += CoreConfiguration.OpenVPN.randomLength - + let serverRandom2 = controlBuffer.withOffset(offset, count: CoreConfiguration.OpenVPN.randomLength) offset += CoreConfiguration.OpenVPN.randomLength - + let serverOptsLength = Int(controlBuffer.networkUInt16Value(fromOffset: offset)) offset += 2 - + guard controlBuffer.count >= offset + serverOptsLength else { return false } @@ -214,22 +214,22 @@ extension OpenVPN { } else { log.debug("TLS.auth: Parsed server random") } - + if let serverOptsString = serverOpts.nullTerminatedString(fromOffset: 0) { log.debug("TLS.auth: Parsed server options: \"\(serverOptsString)\"") } - + self.serverRandom1 = serverRandom1 self.serverRandom2 = serverRandom2 controlBuffer.remove(untilOffset: offset) - + return true } - + func parseMessages() -> [String] { var messages = [String]() var offset = 0 - + while true { guard let msg = controlBuffer.nullTerminatedString(fromOffset: offset) else { break diff --git a/Sources/TunnelKitOpenVPNProtocol/ControlChannel.swift b/Sources/TunnelKitOpenVPNProtocol/ControlChannel.swift index c084c4f..013b2de 100644 --- a/Sources/TunnelKitOpenVPNProtocol/ControlChannel.swift +++ b/Sources/TunnelKitOpenVPNProtocol/ControlChannel.swift @@ -35,7 +35,7 @@ private let log = SwiftyBeaver.self extension OpenVPN { class ControlChannelError: Error, CustomStringConvertible { let description: String - + init(_ message: String) { description = "\(String(describing: ControlChannelError.self))(\(message))" } @@ -43,9 +43,9 @@ extension OpenVPN { class ControlChannel { private let serializer: ControlChannelSerializer - + private(set) var sessionId: Data? - + var remoteSessionId: Data? { didSet { if let id = remoteSessionId { @@ -63,11 +63,11 @@ extension OpenVPN { private var plainBuffer: ZeroingData private var dataCount: BidirectionalState - + convenience init() { self.init(serializer: PlainSerializer()) } - + convenience init(withAuthKey key: StaticKey, digest: Digest) throws { self.init(serializer: try AuthSerializer(withKey: key, digest: digest)) } @@ -75,7 +75,7 @@ extension OpenVPN { convenience init(withCryptKey key: StaticKey) throws { self.init(serializer: try CryptSerializer(withKey: key)) } - + private init(serializer: ControlChannelSerializer) { self.serializer = serializer sessionId = nil @@ -86,7 +86,7 @@ extension OpenVPN { plainBuffer = Z(count: TLSBoxMaxBufferLength) dataCount = BidirectionalState(withResetValue: 0) } - + func reset(forNewSession: Bool) throws { if forNewSession { try sessionId = SecureRandom.data(length: PacketSessionIdLength) @@ -112,7 +112,7 @@ extension OpenVPN { func enqueueInboundPacket(packet: ControlPacket) -> [ControlPacket] { queue.inbound.append(packet) queue.inbound.sort { $0.packetId < $1.packetId } - + var toHandle: [ControlPacket] = [] for queuedPacket in queue.inbound { if queuedPacket.packetId < currentPacketId.inbound { @@ -122,15 +122,15 @@ extension OpenVPN { if queuedPacket.packetId != currentPacketId.inbound { continue } - + toHandle.append(queuedPacket) - + currentPacketId.inbound += 1 queue.inbound.removeFirst() } return toHandle } - + func enqueueOutboundPackets(withCode code: PacketCode, key: UInt8, payload: Data, maxPacketSize: Int) { guard let sessionId = sessionId else { fatalError("Missing sessionId, do reset(forNewSession: true) first") @@ -139,40 +139,40 @@ extension OpenVPN { let oldIdOut = currentPacketId.outbound var queuedCount = 0 var offset = 0 - + repeat { let subPayloadLength = min(maxPacketSize, payload.count - offset) let subPayloadData = payload.subdata(offset: offset, count: subPayloadLength) let packet = ControlPacket(code: code, key: key, sessionId: sessionId, packetId: currentPacketId.outbound, payload: subPayloadData) - + queue.outbound.append(packet) currentPacketId.outbound += 1 offset += maxPacketSize queuedCount += subPayloadLength } while (offset < payload.count) - + assert(queuedCount == payload.count) - + // packet count let packetCount = currentPacketId.outbound - oldIdOut - if (packetCount > 1) { + if packetCount > 1 { log.debug("Control: Enqueued \(packetCount) packets [\(oldIdOut)-\(currentPacketId.outbound - 1)]") } else { log.debug("Control: Enqueued 1 packet [\(oldIdOut)]") } } - + func writeOutboundPackets() throws -> [Data] { var rawList: [Data] = [] for packet in queue.outbound { if let sentDate = packet.sentDate { let timeAgo = -sentDate.timeIntervalSinceNow - guard (timeAgo >= CoreConfiguration.OpenVPN.retransmissionLimit) else { + guard timeAgo >= CoreConfiguration.OpenVPN.retransmissionLimit else { log.debug("Control: Skip writing packet with packetId \(packet.packetId) (sent on \(sentDate), \(timeAgo) seconds ago)") continue } } - + log.debug("Control: Write control packet \(packet)") let raw = try serializer.serialize(packet: packet) @@ -185,11 +185,11 @@ extension OpenVPN { // log.verbose("Packets now pending ack: \(pendingAcks)") return rawList } - + func hasPendingAcks() -> Bool { return !pendingAcks.isEmpty } - + // Ruby: handle_acks private func readAcks(_ packetIds: [UInt32], acksRemoteSessionId: Data) throws { guard let sessionId = sessionId else { @@ -199,18 +199,18 @@ extension OpenVPN { log.error("Control: Ack session mismatch (\(acksRemoteSessionId.toHex()) != \(sessionId.toHex()))") throw OpenVPNError.sessionMismatch } - + // drop queued out packets if ack-ed queue.outbound.removeAll { return packetIds.contains($0.packetId) } - + // remove ack-ed packets from pending pendingAcks.subtract(packetIds) - + // log.verbose("Packets still pending ack: \(pendingAcks)") } - + func writeAcks(withKey key: UInt8, ackPacketIds: [UInt32], ackRemoteSessionId: Data) throws -> Data { guard let sessionId = sessionId else { throw OpenVPNError.missingSessionId @@ -219,13 +219,13 @@ extension OpenVPN { log.debug("Control: Write ack packet \(packet)") return try serializer.serialize(packet: packet) } - + func currentControlData(withTLS tls: TLSBox) throws -> ZeroingData { var length = 0 try tls.pullRawPlainText(plainBuffer.mutableBytes, length: &length) return plainBuffer.withOffset(0, count: length) } - + func addReceivedDataCount(_ count: Int) { dataCount.inbound += count } @@ -233,7 +233,7 @@ extension OpenVPN { func addSentDataCount(_ count: Int) { dataCount.outbound += count } - + func currentDataCount() -> DataCount { return DataCount(UInt(dataCount.inbound), UInt(dataCount.outbound)) } diff --git a/Sources/TunnelKitOpenVPNProtocol/ControlChannelSerializer.swift b/Sources/TunnelKitOpenVPNProtocol/ControlChannelSerializer.swift index 3a1e090..6315df5 100644 --- a/Sources/TunnelKitOpenVPNProtocol/ControlChannelSerializer.swift +++ b/Sources/TunnelKitOpenVPNProtocol/ControlChannelSerializer.swift @@ -34,7 +34,7 @@ private let log = SwiftyBeaver.self protocol ControlChannelSerializer { func reset() - + func serialize(packet: ControlPacket) throws -> Data func deserialize(data: Data, start: Int, end: Int?) throws -> ControlPacket @@ -44,15 +44,15 @@ extension OpenVPN.ControlChannel { class PlainSerializer: ControlChannelSerializer { func reset() { } - + func serialize(packet: ControlPacket) throws -> Data { return packet.serialized() } - + func deserialize(data packet: Data, start: Int, end: Int?) throws -> ControlPacket { var offset = start let end = end ?? packet.count - + guard end >= offset + PacketOpcodeLength else { throw OpenVPN.ControlChannelError("Missing opcode") } @@ -64,7 +64,7 @@ extension OpenVPN.ControlChannel { offset += PacketOpcodeLength log.debug("Control: Try read packet with code \(code) and key \(key)") - + guard end >= offset + PacketSessionIdLength else { throw OpenVPN.ControlChannelError("Missing sessionId") } @@ -134,23 +134,23 @@ extension OpenVPN.ControlChannel { extension OpenVPN.ControlChannel { class AuthSerializer: ControlChannelSerializer { private let encrypter: Encrypter - + private let decrypter: Decrypter - + private let prefixLength: Int - + private let hmacLength: Int - + private let authLength: Int - + private let preambleLength: Int - + private var currentReplayId: BidirectionalState - + private let timestamp: UInt32 - + private let plain: PlainSerializer - + init(withKey key: OpenVPN.StaticKey, digest: OpenVPN.Digest) throws { let crypto = CryptoBox(cipherAlgorithm: nil, digestAlgorithm: digest.rawValue) try crypto.configure( @@ -161,40 +161,40 @@ extension OpenVPN.ControlChannel { ) encrypter = crypto.encrypter() decrypter = crypto.decrypter() - + prefixLength = PacketOpcodeLength + PacketSessionIdLength hmacLength = crypto.digestLength() authLength = hmacLength + PacketReplayIdLength + PacketReplayTimestampLength preambleLength = prefixLength + authLength - + currentReplayId = BidirectionalState(withResetValue: 1) timestamp = UInt32(Date().timeIntervalSince1970) plain = PlainSerializer() } - + func reset() { currentReplayId.reset() } - + func serialize(packet: ControlPacket) throws -> Data { return try serialize(packet: packet, timestamp: timestamp) } - + func serialize(packet: ControlPacket, timestamp: UInt32) throws -> Data { let data = try packet.serialized(withAuthenticator: encrypter, replayId: currentReplayId.outbound, timestamp: timestamp) currentReplayId.outbound += 1 return data } - + // XXX: start/end are ignored, parses whole packet func deserialize(data packet: Data, start: Int, end: Int?) throws -> ControlPacket { let end = packet.count - + // data starts with (prefix=(header + sessionId) + auth=(hmac + replayId)) guard end >= preambleLength else { throw OpenVPN.ControlChannelError("Missing HMAC") } - + // needs a copy for swapping var authPacket = packet let authCount = authPacket.count @@ -203,9 +203,9 @@ extension OpenVPN.ControlChannel { PacketSwapCopy(ptr, packet, prefixLength, authLength) try decrypter.verifyBytes(ptr, length: authCount, flags: nil) } - + // TODO: validate replay packet id - + return try plain.deserialize(data: authPacket, start: authLength, end: nil) } } @@ -214,19 +214,19 @@ extension OpenVPN.ControlChannel { extension OpenVPN.ControlChannel { class CryptSerializer: ControlChannelSerializer { private let encrypter: Encrypter - + private let decrypter: Decrypter - + private let headerLength: Int - + private var adLength: Int - + private let tagLength: Int - + private var currentReplayId: BidirectionalState - + private let timestamp: UInt32 - + private let plain: PlainSerializer init(withKey key: OpenVPN.StaticKey) throws { @@ -239,7 +239,7 @@ extension OpenVPN.ControlChannel { ) encrypter = crypto.encrypter() decrypter = crypto.decrypter() - + headerLength = PacketOpcodeLength + PacketSessionIdLength adLength = headerLength + PacketReplayIdLength + PacketReplayTimestampLength tagLength = crypto.tagLength() @@ -248,30 +248,30 @@ extension OpenVPN.ControlChannel { timestamp = UInt32(Date().timeIntervalSince1970) plain = PlainSerializer() } - + func reset() { currentReplayId.reset() } - + func serialize(packet: ControlPacket) throws -> Data { return try serialize(packet: packet, timestamp: timestamp) } - + func serialize(packet: ControlPacket, timestamp: UInt32) throws -> Data { let data = try packet.serialized(with: encrypter, replayId: currentReplayId.outbound, timestamp: timestamp, adLength: adLength) currentReplayId.outbound += 1 return data } - + // XXX: start/end are ignored, parses whole packet func deserialize(data packet: Data, start: Int, end: Int?) throws -> ControlPacket { let end = end ?? packet.count - + // data starts with (ad=(header + sessionId + replayId) + tag) guard end >= start + adLength + tagLength else { throw OpenVPN.ControlChannelError("Missing AD+TAG") } - + let encryptedCount = packet.count - adLength var decryptedPacket = Data(count: decrypter.encryptionCapacity(withLength: encryptedCount)) var decryptedCount = 0 @@ -285,9 +285,9 @@ extension OpenVPN.ControlChannel { } } decryptedPacket.count = headerLength + decryptedCount - + // TODO: validate replay packet id - + return try plain.deserialize(data: decryptedPacket, start: 0, end: nil) } } diff --git a/Sources/TunnelKitOpenVPNProtocol/CoreConfiguration+OpenVPN.swift b/Sources/TunnelKitOpenVPNProtocol/CoreConfiguration+OpenVPN.swift index c8d94d3..da304da 100644 --- a/Sources/TunnelKitOpenVPNProtocol/CoreConfiguration+OpenVPN.swift +++ b/Sources/TunnelKitOpenVPNProtocol/CoreConfiguration+OpenVPN.swift @@ -41,29 +41,29 @@ import CTunnelKitOpenVPNProtocol extension CoreConfiguration { struct OpenVPN { - + // MARK: Session - + static let usesReplayProtection = true static let negotiationTimeout = 30.0 - + static let hardResetTimeout = 10.0 static let tickInterval = 0.2 - + static let pushRequestInterval = 2.0 - + static let pingTimeoutCheckInterval = 10.0 - + static let pingTimeout = 120.0 - + static let retransmissionLimit = 0.1 - + static let softNegotiationTimeout = 120.0 - + // MARK: Authentication - + static func peerInfo(extra: [String: String]? = nil) -> String { let platform: String let platformVersion = ProcessInfo.processInfo.operatingSystemVersion @@ -79,7 +79,7 @@ extension CoreConfiguration { "IV_UI_VER=\(uiVersion)", "IV_PROTO=2", "IV_NCP=2", - "IV_LZO_STUB=1", + "IV_LZO_STUB=1" ] if LZOFactory.isSupported() { info.append("IV_LZO=1") @@ -97,19 +97,19 @@ extension CoreConfiguration { info.append("") return info.joined(separator: "\n") } - + static let randomLength = 32 - + // MARK: Keys - + static let label1 = "OpenVPN master secret" - + static let label2 = "OpenVPN key expansion" - + static let preMasterLength = 48 - + static let keyLength = 64 - + static let keysCount = 4 } } diff --git a/Sources/TunnelKitOpenVPNProtocol/EncryptionBridge.swift b/Sources/TunnelKitOpenVPNProtocol/EncryptionBridge.swift index 76870a0..93b2d0b 100644 --- a/Sources/TunnelKitOpenVPNProtocol/EncryptionBridge.swift +++ b/Sources/TunnelKitOpenVPNProtocol/EncryptionBridge.swift @@ -43,9 +43,9 @@ import CTunnelKitOpenVPNProtocol extension OpenVPN { class EncryptionBridge { private static let maxHmacLength = 100 - + private let box: CryptoBox - + // Ruby: keys_prf private static func keysPRF( _ label: String, @@ -55,7 +55,7 @@ extension OpenVPN { _ clientSessionId: Data?, _ serverSessionId: Data?, _ size: Int) throws -> ZeroingData { - + let seed = Z(label, nullTerminated: false) seed.append(clientSeed) seed.append(serverSeed) @@ -69,36 +69,36 @@ extension OpenVPN { let lenx = len + (secret.count & 1) let secret1 = secret.withOffset(0, count: lenx) let secret2 = secret.withOffset(len, count: lenx) - + let hash1 = try keysHash("md5", secret1, seed, size) let hash2 = try keysHash("sha1", secret2, seed, size) - + let prf = Z() for i in 0.. ZeroingData { let out = Z() let buffer = Z(count: EncryptionBridge.maxHmacLength) var chain = try EncryptionBridge.hmac(buffer, digestName, secret, seed) - while (out.count < size) { + while out.count < size { out.append(try EncryptionBridge.hmac(buffer, digestName, secret, chain.appending(seed))) chain = try EncryptionBridge.hmac(buffer, digestName, secret, chain) } return out.withOffset(0, count: size) } - + // Ruby: hmac private static func hmac(_ buffer: ZeroingData, _ digestName: String, _ secret: ZeroingData, _ data: ZeroingData) throws -> ZeroingData { var length = 0 - + try CryptoBox.hmac( withDigestName: digestName, secret: secret.bytes, @@ -108,44 +108,44 @@ extension OpenVPN { hmac: buffer.mutableBytes, hmacLength: &length ) - + return buffer.withOffset(0, count: length) } - + convenience init(_ cipher: Cipher, _ digest: Digest, _ auth: Authenticator, _ sessionId: Data, _ remoteSessionId: Data) throws { - + guard let serverRandom1 = auth.serverRandom1, let serverRandom2 = auth.serverRandom2 else { fatalError("Configuring encryption without server randoms") } - + let masterData = try EncryptionBridge.keysPRF( CoreConfiguration.OpenVPN.label1, auth.preMaster, auth.random1, serverRandom1, nil, nil, CoreConfiguration.OpenVPN.preMasterLength ) - + let keysData = try EncryptionBridge.keysPRF( CoreConfiguration.OpenVPN.label2, masterData, auth.random2, serverRandom2, sessionId, remoteSessionId, CoreConfiguration.OpenVPN.keysCount * CoreConfiguration.OpenVPN.keyLength ) - + var keysArray = [ZeroingData]() for i in 0.. DataPathEncrypter { return box.encrypter().dataPathEncrypter() } diff --git a/Sources/TunnelKitOpenVPNProtocol/OpenVPNSession+PIA.swift b/Sources/TunnelKitOpenVPNProtocol/OpenVPNSession+PIA.swift index 21af8d4..d93e043 100644 --- a/Sources/TunnelKitOpenVPNProtocol/OpenVPNSession+PIA.swift +++ b/Sources/TunnelKitOpenVPNProtocol/OpenVPNSession+PIA.swift @@ -41,35 +41,35 @@ import TunnelKitOpenVPNCore extension OpenVPNSession { struct PIAHardReset { private static let obfuscationKeyLength = 3 - + private static let magic = "53eo0rk92gxic98p1asgl5auh59r1vp4lmry1e3chzi100qntd" - + private static let encodedFormat = "\(magic)crypto\t%@|%@\tca\t%@" - + private let caMd5Digest: String - + private let cipherName: String - + private let digestName: String - + init(caMd5Digest: String, cipher: OpenVPN.Cipher, digest: OpenVPN.Digest) { self.caMd5Digest = caMd5Digest cipherName = cipher.rawValue.lowercased() digestName = digest.rawValue.lowercased() } - + // Ruby: pia_settings func encodedData() throws -> Data { guard let plainData = String(format: PIAHardReset.encodedFormat, cipherName, digestName, caMd5Digest).data(using: .ascii) else { fatalError("Unable to encode string to ASCII") } let keyBytes = try SecureRandom.data(length: PIAHardReset.obfuscationKeyLength) - + var encodedData = Data(keyBytes) for (i, b) in plainData.enumerated() { let keyChar = keyBytes[i % keyBytes.count] let xorredB = b ^ keyChar - + encodedData.append(xorredB) } return encodedData diff --git a/Sources/TunnelKitOpenVPNProtocol/OpenVPNSession.swift b/Sources/TunnelKitOpenVPNProtocol/OpenVPNSession.swift index 02cc208..635a6a3 100644 --- a/Sources/TunnelKitOpenVPNProtocol/OpenVPNSession.swift +++ b/Sources/TunnelKitOpenVPNProtocol/OpenVPNSession.swift @@ -45,7 +45,7 @@ private let log = SwiftyBeaver.self /// Observes major events notified by a `OpenVPNSession`. public protocol OpenVPNSessionDelegate: AnyObject { - + /** Called after starting a session. @@ -54,7 +54,7 @@ public protocol OpenVPNSessionDelegate: AnyObject { - Parameter options: The pulled tunnel settings. */ func sessionDidStart(_: OpenVPNSession, remoteAddress: String, remoteProtocol: String?, options: OpenVPN.Configuration) - + /** Called after stopping a session. @@ -69,22 +69,22 @@ public protocol OpenVPNSessionDelegate: AnyObject { public class OpenVPNSession: Session { private enum StopMethod { case shutdown - + case reconnect } - + private struct Caches { static let ca = "ca.pem" } // MARK: Configuration - + /// The session base configuration. public let configuration: OpenVPN.Configuration - + /// The optional credentials. public var credentials: OpenVPN.Credentials? - + private var keepAliveInterval: TimeInterval? { let interval: TimeInterval? if let negInterval = pushReply?.options.keepAliveInterval, negInterval > 0.0 { @@ -96,7 +96,7 @@ public class OpenVPNSession: Session { } return interval } - + private var keepAliveTimeout: TimeInterval { if let negTimeout = pushReply?.options.keepAliveTimeout, negTimeout > 0.0 { return negTimeout @@ -106,16 +106,16 @@ public class OpenVPNSession: Session { return CoreConfiguration.OpenVPN.pingTimeout } } - + /// An optional `OpenVPNSessionDelegate` for receiving session events. public weak var delegate: OpenVPNSessionDelegate? - + // MARK: State private let queue: DispatchQueue private var tlsObserver: NSObjectProtocol? - + private var withLocalOptions: Bool private var keys: [UInt8: OpenVPN.SessionKey] @@ -123,29 +123,29 @@ public class OpenVPNSession: Session { private var oldKeys: [OpenVPN.SessionKey] private var negotiationKeyIdx: UInt8 - + private var currentKeyIdx: UInt8? - + private var isRenegotiating: Bool - + private var negotiationKey: OpenVPN.SessionKey { guard let key = keys[negotiationKeyIdx] else { fatalError("Keys are empty or index \(negotiationKeyIdx) not found in \(keys.keys)") } return key } - + private var currentKey: OpenVPN.SessionKey? { guard let i = currentKeyIdx else { return nil } return keys[i] } - + private var link: LinkInterface? - + private var tunnel: TunnelInterface? - + private var isReliableLink: Bool { return link?.isReliable ?? false } @@ -153,24 +153,24 @@ public class OpenVPNSession: Session { private var continuatedPushReplyMessage: String? private var pushReply: OpenVPN.PushReply? - + private var nextPushRequestDate: Date? - + private var connectedDate: Date? private var lastPing: BidirectionalState - + private(set) var isStopping: Bool - + /// The optional reason why the session stopped. public private(set) var stopError: Error? - + // MARK: Control - + private var controlChannel: OpenVPN.ControlChannel - + private var authenticator: OpenVPN.Authenticator? - + // MARK: Caching private let cachesURL: URL @@ -191,7 +191,7 @@ public class OpenVPNSession: Session { guard let ca = configuration.ca else { throw OpenVPN.ConfigurationError.missingConfiguration(option: "ca") } - + self.queue = queue self.configuration = configuration self.cachesURL = cachesURL @@ -203,7 +203,7 @@ public class OpenVPNSession: Session { isRenegotiating = false lastPing = BidirectionalState(withResetValue: Date.distantPast) isStopping = false - + if let tlsWrap = configuration.tlsWrap { switch tlsWrap.strategy { case .auth: @@ -219,22 +219,22 @@ public class OpenVPNSession: Session { // cache CA locally (mandatory for OpenSSL) try ca.pem.write(to: caURL, atomically: true, encoding: .ascii) } - + deinit { cleanup() cleanupCache() } - + // MARK: Session public func setLink(_ link: LinkInterface) { - guard (self.link == nil) else { + guard self.link == nil else { log.warning("Link interface already set!") return } log.debug("Starting VPN session") - + // WARNING: runs in notification source queue (we know it's "queue", but better be safe than sorry) tlsObserver = NotificationCenter.default.addObserver(forName: .TLSBoxPeerVerificationError, object: nil, queue: nil) { (notification) in let error = notification.userInfo?[OpenVPNErrorKey] as? Error @@ -242,18 +242,18 @@ public class OpenVPNSession: Session { self.deferStop(.shutdown, error) } } - + self.link = link start() } - + public func canRebindLink() -> Bool { // return (pushReply?.peerId != nil) // FIXME: floating is currently unreliable return false } - + public func rebindLink(_ link: LinkInterface) { guard let _ = pushReply?.options.peerId else { log.warning("Session doesn't support link rebinding!") @@ -269,7 +269,7 @@ public class OpenVPNSession: Session { } public func setTunnel(tunnel: TunnelInterface) { - guard (self.tunnel == nil) else { + guard self.tunnel == nil else { log.warning("Tunnel interface already set!") return } @@ -283,11 +283,11 @@ public class OpenVPNSession: Session { } return controlChannel.currentDataCount() } - + public func serverConfiguration() -> Any? { return pushReply?.options } - + public func shutdown(error: Error?) { guard !isStopping else { log.warning("Ignore stop request, already stopping!") @@ -295,7 +295,7 @@ public class OpenVPNSession: Session { } deferStop(.shutdown, error) } - + public func reconnect(error: Error?) { guard !isStopping else { log.warning("Ignore stop request, already stopping!") @@ -303,7 +303,7 @@ public class OpenVPNSession: Session { } deferStop(.reconnect, error) } - + // Ruby: cleanup public func cleanup() { log.info("Cleaning up...") @@ -312,13 +312,13 @@ public class OpenVPNSession: Session { NotificationCenter.default.removeObserver(observer) tlsObserver = nil } - + keys.removeAll() oldKeys.removeAll() negotiationKeyIdx = 0 currentKeyIdx = nil isRenegotiating = false - + nextPushRequestDate = nil connectedDate = nil authenticator = nil @@ -328,7 +328,7 @@ public class OpenVPNSession: Session { if !(tunnel?.isPersistent ?? false) { tunnel = nil } - + isStopping = false stopError = nil } @@ -347,7 +347,7 @@ public class OpenVPNSession: Session { loopLink() hardReset() } - + private func loopNegotiation() { guard let link = link else { return @@ -364,12 +364,12 @@ public class OpenVPNSession: Session { doShutdown(error: OpenVPNError.negotiationTimeout) return } - + pushRequest() if !isReliableLink { flushControlQueue() } - + guard negotiationKey.controlState == .connected else { queue.asyncAfter(deadline: .now() + CoreConfiguration.OpenVPN.tickInterval) { [weak self] in self?.loopNegotiation() @@ -390,11 +390,11 @@ public class OpenVPNSession: Session { } if let error = error { log.error("Failed LINK read: \(error)") - + // XXX: why isn't the tunnel shutting down at this point? return } - + if let packets = newPackets, !packets.isEmpty { self?.maybeRenegotiate() @@ -425,11 +425,11 @@ public class OpenVPNSession: Session { log.warning("Discarding \(packets.count) LINK packets (should not handle)") return } - + lastPing.inbound = Date() var dataPacketsByKey = [UInt8: [Data]]() - + for packet in packets { // log.verbose("Received data from LINK (\(packet.count) bytes): \(packet.toHex())") @@ -445,7 +445,7 @@ public class OpenVPNSession: Session { // log.verbose("Parsed packet with code \(code)") var offset = 1 - if (code == .dataV2) { + if code == .dataV2 { guard packet.count >= offset + PacketPeerIdLength else { log.warning("Dropped malformed packet (missing peerId)") continue @@ -491,7 +491,7 @@ public class OpenVPNSession: Session { deferStop(.shutdown, OpenVPNError.staleSession) return } - + case .softResetV1: if !isRenegotiating { softReset(isServerInitiated: true) @@ -518,7 +518,7 @@ public class OpenVPNSession: Session { handleDataPackets(dataPackets, key: sessionKey) } } - + // Ruby: recv_tun private func receiveTunnel(packets: [Data]) { guard shouldHandlePackets() else { @@ -527,13 +527,13 @@ public class OpenVPNSession: Session { } sendDataPackets(packets) } - + // Ruby: ping private func ping() { guard currentKey?.controlState == .connected else { return } - + let now = Date() guard now.timeIntervalSince(lastPing.inbound) <= keepAliveTimeout else { deferStop(.shutdown, OpenVPNError.pingTimeout) @@ -550,7 +550,7 @@ public class OpenVPNSession: Session { // schedule even just to check for ping timeout scheduleNextPing() } - + private func scheduleNextPing() { let interval: TimeInterval if let keepAliveInterval = keepAliveInterval { @@ -565,9 +565,9 @@ public class OpenVPNSession: Session { self?.ping() } } - + // MARK: Handshake - + // Ruby: reset_ctrl private func resetControlChannel(forNewSession: Bool) { authenticator = nil @@ -577,7 +577,7 @@ public class OpenVPNSession: Session { deferStop(.shutdown, e) } } - + // Ruby: hard_reset private func hardReset() { log.debug("Send hard reset") @@ -598,7 +598,7 @@ public class OpenVPNSession: Session { loopNegotiation() enqueueControlPackets(code: .hardResetClientV2, key: UInt8(negotiationKeyIdx), payload: payload) } - + private func hardResetPayload() -> Data? { guard !(configuration.usesPIAPatches ?? false) else { guard let _ = configuration.ca else { @@ -621,7 +621,7 @@ public class OpenVPNSession: Session { } return nil } - + // Ruby: soft_reset private func softReset(isServerInitiated: Bool) { guard !isRenegotiating else { @@ -633,7 +633,7 @@ public class OpenVPNSession: Session { } else { log.debug("Send soft reset") } - + resetControlChannel(forNewSession: false) negotiationKeyIdx = max(1, (negotiationKeyIdx + 1) % OpenVPN.ProtocolMacros.numberOfKeys) let newKey = OpenVPN.SessionKey(id: UInt8(negotiationKeyIdx), timeout: CoreConfiguration.OpenVPN.softNegotiationTimeout) @@ -647,13 +647,13 @@ public class OpenVPNSession: Session { enqueueControlPackets(code: .softResetV1, key: UInt8(negotiationKeyIdx), payload: Data()) } } - + // Ruby: on_tls_connect private func onTLSConnect() { log.debug("TLS.connect: Handshake is complete") negotiationKey.controlState = .preAuth - + do { authenticator = try OpenVPN.Authenticator(credentials?.username, pushReply?.options.authToken ?? credentials?.password) authenticator?.withLocalOptions = withLocalOptions @@ -679,7 +679,7 @@ public class OpenVPNSession: Session { log.debug("TLS.auth: Pulled ciphertext (\(cipherTextOut.count) bytes)") enqueueControlPackets(code: .controlV1, key: negotiationKey.id, payload: cipherTextOut) } - + // Ruby: push_request private func pushRequest() { guard negotiationKey.controlState == .preIfConfig else { @@ -688,10 +688,10 @@ public class OpenVPNSession: Session { guard let targetDate = nextPushRequestDate, Date() > targetDate else { return } - + log.debug("TLS.ifconfig: Put plaintext (PUSH_REQUEST)") try? negotiationKey.tls.putPlainText("PUSH_REQUEST\0") - + let cipherTextOut: Data do { cipherTextOut = try negotiationKey.tls.pullCipherText() @@ -704,32 +704,32 @@ public class OpenVPNSession: Session { log.verbose("TLS.ifconfig: Still can't pull ciphertext") return } - + log.debug("TLS.ifconfig: Send pulled ciphertext (\(cipherTextOut.count) bytes)") enqueueControlPackets(code: .controlV1, key: negotiationKey.id, payload: cipherTextOut) - + if isRenegotiating { completeConnection() isRenegotiating = false } nextPushRequestDate = Date().addingTimeInterval(CoreConfiguration.OpenVPN.pushRequestInterval) } - + private func maybeRenegotiate() { guard let renegotiatesAfter = configuration.renegotiatesAfter, renegotiatesAfter > 0 else { return } - guard (negotiationKeyIdx == currentKeyIdx) else { + guard negotiationKeyIdx == currentKeyIdx else { return } - + let elapsed = -negotiationKey.startTime.timeIntervalSinceNow - if (elapsed > renegotiatesAfter) { + if elapsed > renegotiatesAfter { log.debug("Renegotiating after \(elapsed.asTimeString)") softReset(isServerInitiated: false) } } - + private func completeConnection() { setupEncryption() authenticator?.reset() @@ -737,7 +737,7 @@ public class OpenVPNSession: Session { connectedDate = Date() transitionKeys() } - + // MARK: Control // Ruby: handle_ctrl_pkt @@ -747,16 +747,16 @@ public class OpenVPNSession: Session { // deferStop(.shutdown, OpenVPNError.badKey) return } - + guard let _ = configuration.ca else { log.error("Configuration doesn't have a CA") return } - + // start new TLS handshake if ((packet.code == .hardResetServerV2) && (negotiationKey.state == .hardReset)) || ((packet.code == .softResetV1) && (negotiationKey.state == .softReset)) { - + if negotiationKey.state == .hardReset { controlChannel.remoteSessionId = packet.sessionId } @@ -811,7 +811,7 @@ public class OpenVPNSession: Session { enqueueControlPackets(code: .controlV1, key: negotiationKey.id, payload: cipherTextOut) } // exchange TLS ciphertext - else if ((packet.code == .controlV1) && (negotiationKey.state == .tls)) { + else if (packet.code == .controlV1) && (negotiationKey.state == .tls) { guard let remoteSessionId = controlChannel.remoteSessionId else { log.error("No remote sessionId found in packet (control packets before server HARD_RESET)") deferStop(.shutdown, OpenVPNError.missingSessionId) @@ -822,7 +822,7 @@ public class OpenVPNSession: Session { deferStop(.shutdown, OpenVPNError.sessionMismatch) return } - + guard let cipherTextIn = packet.payload else { log.warning("TLS.connect: Control packet with empty payload?") return @@ -844,7 +844,7 @@ public class OpenVPNSession: Session { } log.verbose("TLS.connect: No available ciphertext to pull") } - + if negotiationKey.shouldOnTLSConnect() { onTLSConnect() } @@ -873,7 +873,7 @@ public class OpenVPNSession: Session { auth.appendControlData(data) - if (negotiationKey.controlState == .preAuth) { + if negotiationKey.controlState == .preAuth { do { guard try auth.parseAuthReply() else { return @@ -882,13 +882,13 @@ public class OpenVPNSession: Session { deferStop(.shutdown, e) return } - + negotiationKey.controlState = .preIfConfig nextPushRequestDate = Date() pushRequest() nextPushRequestDate?.addTimeInterval(isRenegotiating ? CoreConfiguration.OpenVPN.pushRequestInterval : CoreConfiguration.OpenVPN.retransmissionLimit) } - + for message in auth.parseMessages() { if CoreConfiguration.logsSensitiveData { log.debug("Parsed control message (\(message.count) bytes): \"\(message)\"") @@ -919,14 +919,14 @@ public class OpenVPNSession: Session { deferStop(.shutdown, OpenVPNError.badCredentials) return } - + // disconnect on remote server restart (--explicit-exit-notify) guard !message.hasPrefix("RESTART") else { log.debug("Disconnecting due to server shutdown") deferStop(.shutdown, OpenVPNError.serverShutdown) return } - + // handle authentication from now on guard negotiationKey.controlState == .preIfConfig else { return @@ -945,7 +945,7 @@ public class OpenVPNSession: Session { } reply = optionalReply log.debug("Received PUSH_REPLY: \"\(reply)\"") - + if let framing = reply.options.compressionFraming, let compression = reply.options.compressionAlgorithm { switch compression { case .disabled: @@ -970,13 +970,13 @@ public class OpenVPNSession: Session { deferStop(.shutdown, e) return } - + pushReply = reply guard reply.options.ipv4 != nil || reply.options.ipv6 != nil else { deferStop(.shutdown, OpenVPNError.noRouting) return } - + completeConnection() guard let remoteAddress = link?.remoteAddress else { @@ -991,7 +991,7 @@ public class OpenVPNSession: Session { scheduleNextPing() } - + // Ruby: transition_keys private func transitionKeys() { if let key = currentKey { @@ -1000,15 +1000,15 @@ public class OpenVPNSession: Session { currentKeyIdx = negotiationKeyIdx cleanKeys() } - + // Ruby: clean_keys private func cleanKeys() { - while (oldKeys.count > 1) { + while oldKeys.count > 1 { let key = oldKeys.removeFirst() keys.removeValue(forKey: key.id) } } - + // Ruby: q_ctrl private func enqueueControlPackets(code: PacketCode, key: UInt8, payload: Data) { guard let _ = link else { @@ -1019,7 +1019,7 @@ public class OpenVPNSession: Session { controlChannel.enqueueOutboundPackets(withCode: code, key: key, payload: payload, maxPacketSize: 1000) flushControlQueue() } - + // Ruby: flush_ctrl_q_out private func flushControlQueue() { let rawList: [Data] @@ -1033,7 +1033,7 @@ public class OpenVPNSession: Session { for raw in rawList { log.debug("Send control packet (\(raw.count) bytes): \(raw.toHex())") } - + // WARNING: runs in Network.framework queue let writeLink = link link?.writePackets(rawList) { [weak self] (error) in @@ -1050,7 +1050,7 @@ public class OpenVPNSession: Session { } } } - + // Ruby: setup_keys private func setupEncryption() { guard let auth = authenticator else { @@ -1081,7 +1081,7 @@ public class OpenVPNSession: Session { } else { log.debug("Set up encryption") } - + let pushedCipher = pushReply.options.cipher if let negCipher = pushedCipher { log.info("\tNegotiated cipher: \(negCipher.rawValue)") @@ -1125,7 +1125,7 @@ public class OpenVPNSession: Session { usesReplayProtection: CoreConfiguration.OpenVPN.usesReplayProtection ) } - + // MARK: Data // Ruby: handle_data_pkt @@ -1149,7 +1149,7 @@ public class OpenVPNSession: Session { deferStop(.reconnect, e) } } - + // Ruby: send_data_pkt private func sendDataPackets(_ packets: [Data]) { guard let key = currentKey else { @@ -1163,7 +1163,7 @@ public class OpenVPNSession: Session { guard !encryptedPackets.isEmpty else { return } - + // WARNING: runs in Network.framework queue controlChannel.addSentDataCount(encryptedPackets.flatCount) let writeLink = link @@ -1189,12 +1189,12 @@ public class OpenVPNSession: Session { deferStop(.reconnect, e) } } - + // MARK: Acks - + private func handleAcks() { } - + // Ruby: send_ack private func sendAck(for controlPacket: ControlPacket) { log.debug("Send ack for received packetId \(controlPacket.packetId)") @@ -1210,7 +1210,7 @@ public class OpenVPNSession: Session { deferStop(.shutdown, e) return } - + // WARNING: runs in Network.framework queue let writeLink = link link?.writePacket(raw) { [weak self] (error) in @@ -1228,13 +1228,13 @@ public class OpenVPNSession: Session { } } } - + // MARK: Stop - + private func shouldHandlePackets() -> Bool { return !isStopping && !keys.isEmpty } - + private func deferStop(_ method: StopMethod, _ error: Error?) { guard !isStopping else { return @@ -1246,7 +1246,7 @@ public class OpenVPNSession: Session { case .shutdown: self?.doShutdown(error: error) self?.cleanupCache() - + case .reconnect: self?.doReconnect(error: error) } @@ -1259,7 +1259,7 @@ public class OpenVPNSession: Session { completion() return } - link.writePackets(packets) { [weak self] (error) in + link.writePackets(packets) { [weak self] (_) in self?.queue.sync { completion() } @@ -1271,7 +1271,7 @@ public class OpenVPNSession: Session { completion() } } - + private func doShutdown(error: Error?) { if let error = error { log.error("Trigger shutdown (error: \(error))") @@ -1281,7 +1281,7 @@ public class OpenVPNSession: Session { stopError = error delegate?.sessionDidStop(self, withError: error, shouldReconnect: false) } - + private func doReconnect(error: Error?) { if let error = error { log.error("Trigger reconnection (error: \(error))") diff --git a/Sources/TunnelKitOpenVPNProtocol/Packet.swift b/Sources/TunnelKitOpenVPNProtocol/Packet.swift index c17d992..7121f6e 100644 --- a/Sources/TunnelKitOpenVPNProtocol/Packet.swift +++ b/Sources/TunnelKitOpenVPNProtocol/Packet.swift @@ -69,7 +69,7 @@ extension OpenVPN { enum OCCPacket: UInt8 { case exit = 0x06 - + private static let magicString = Data(hex: "287f346bd4ef7a812d56b8d3afc5459c") func serialized(_ info: Any? = nil) -> Data { diff --git a/Sources/TunnelKitOpenVPNProtocol/ProtocolMacros.swift b/Sources/TunnelKitOpenVPNProtocol/ProtocolMacros.swift index 77f2b93..1689ba9 100644 --- a/Sources/TunnelKitOpenVPNProtocol/ProtocolMacros.swift +++ b/Sources/TunnelKitOpenVPNProtocol/ProtocolMacros.swift @@ -39,7 +39,7 @@ import TunnelKitOpenVPNCore extension OpenVPN { class ProtocolMacros { - + // UInt32(0) + UInt8(KeyMethod = 2) static let tlsPrefix = Data(hex: "0000000002") diff --git a/Sources/TunnelKitOpenVPNProtocol/PushReply.swift b/Sources/TunnelKitOpenVPNProtocol/PushReply.swift index 4761587..d4fd033 100644 --- a/Sources/TunnelKitOpenVPNProtocol/PushReply.swift +++ b/Sources/TunnelKitOpenVPNProtocol/PushReply.swift @@ -40,11 +40,11 @@ import TunnelKitOpenVPNCore extension OpenVPN { struct PushReply: CustomStringConvertible { private static let prefix = "PUSH_REPLY," - + private let original: String let options: Configuration - + init?(message: String) throws { guard message.hasPrefix(PushReply.prefix) else { return nil @@ -57,15 +57,15 @@ extension OpenVPN { let lines = original.components(separatedBy: ",") options = try ConfigurationParser.parsed(fromLines: lines).configuration } - + // MARK: CustomStringConvertible - + var description: String { let stripped = NSMutableString(string: original) ConfigurationParser.Regex.authToken.replaceMatches( in: stripped, options: [], - range: NSMakeRange(0, stripped.length), + range: NSRange(location: 0, length: stripped.length), withTemplate: "auth-token" ) return stripped as String diff --git a/Sources/TunnelKitOpenVPNProtocol/SessionKey.swift b/Sources/TunnelKitOpenVPNProtocol/SessionKey.swift index d1e69e8..309ece8 100644 --- a/Sources/TunnelKitOpenVPNProtocol/SessionKey.swift +++ b/Sources/TunnelKitOpenVPNProtocol/SessionKey.swift @@ -48,21 +48,21 @@ extension OpenVPN { enum State { case invalid, hardReset, softReset, tls } - + enum ControlState { case preAuth, preIfConfig, connected } let id: UInt8 // 3-bit - + let timeout: TimeInterval - + let startTime: Date - + var state = State.invalid - + var controlState: ControlState? - + var tlsOptional: TLSBox? var tls: TLSBox { @@ -71,11 +71,11 @@ extension OpenVPN { } return tls } - + var dataPath: DataPath? - + private var isTLSConnected: Bool - + init(id: UInt8, timeout: TimeInterval) { self.id = id self.timeout = timeout @@ -89,12 +89,12 @@ extension OpenVPN { func didHardResetTimeOut(link: LinkInterface) -> Bool { return ((state == .hardReset) && (-startTime.timeIntervalSinceNow > CoreConfiguration.OpenVPN.hardResetTimeout)) } - + // Ruby: Key.negotiate_timeout func didNegotiationTimeOut(link: LinkInterface) -> Bool { return ((controlState != .connected) && (-startTime.timeIntervalSinceNow > timeout)) } - + // Ruby: Key.on_tls_connect func shouldOnTLSConnect() -> Bool { guard !isTLSConnected else { @@ -105,7 +105,7 @@ extension OpenVPN { } return isTLSConnected } - + func encrypt(packets: [Data]) throws -> [Data]? { guard let dataPath = dataPath else { log.warning("Data: Set dataPath first") @@ -113,7 +113,7 @@ extension OpenVPN { } return try dataPath.encryptPackets(packets, key: id) } - + func decrypt(packets: [Data]) throws -> [Data]? { guard let dataPath = dataPath else { log.warning("Data: Set dataPath first") diff --git a/Sources/TunnelKitOpenVPNProtocol/XORProcessor.swift b/Sources/TunnelKitOpenVPNProtocol/XORProcessor.swift index eb5647c..493edc6 100644 --- a/Sources/TunnelKitOpenVPNProtocol/XORProcessor.swift +++ b/Sources/TunnelKitOpenVPNProtocol/XORProcessor.swift @@ -29,11 +29,11 @@ import TunnelKitOpenVPNCore /// Processes data packets according to a XOR method. public struct XORProcessor { private let method: OpenVPN.XORMethod? - + public init(method: OpenVPN.XORMethod?) { self.method = method } - + /** Returns an array of data packets processed according to XOR method. @@ -49,7 +49,7 @@ public struct XORProcessor { processPacket($0, outbound: outbound) } } - + /** Returns a data packet processed according to XOR method. @@ -64,13 +64,13 @@ public struct XORProcessor { switch method { case .xormask(let mask): return Self.xormask(packet: packet, mask: mask) - + case .xorptrpos: return Self.xorptrpos(packet: packet) - + case .reverse: return Self.reverse(packet: packet) - + case .obfuscate(let mask): if outbound { return Self.xormask(packet: Self.xorptrpos(packet: Self.reverse(packet: Self.xorptrpos(packet: packet))), mask: mask) @@ -87,13 +87,13 @@ extension XORProcessor { byte ^ [UInt8](mask)[index % mask.count] }) } - + private static func xorptrpos(packet: Data) -> Data { Data(packet.enumerated().map { (index, byte) in byte ^ UInt8(truncatingIfNeeded: index &+ 1) }) } - + private static func reverse(packet: Data) -> Data { Data(([UInt8](packet))[0..<1] + ([UInt8](packet)[1...]).reversed()) } diff --git a/Sources/TunnelKitWireGuardAppExtension/WireGuardTunnelProvider.swift b/Sources/TunnelKitWireGuardAppExtension/WireGuardTunnelProvider.swift index 63f17c8..e8c4427 100644 --- a/Sources/TunnelKitWireGuardAppExtension/WireGuardTunnelProvider.swift +++ b/Sources/TunnelKitWireGuardAppExtension/WireGuardTunnelProvider.swift @@ -23,7 +23,7 @@ open class WireGuardTunnelProvider: NEPacketTunnelProvider { open override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) { // BEGIN: TunnelKit - + guard let tunnelProviderProtocol = protocolConfiguration as? NETunnelProviderProtocol else { fatalError("Not a NETunnelProviderProtocol") } @@ -39,7 +39,7 @@ open class WireGuardTunnelProvider: NEPacketTunnelProvider { completionHandler(WireGuardProviderError.savedProtocolConfigurationIsInvalid) return } - + configureLogging() // END: TunnelKit @@ -128,7 +128,7 @@ extension WireGuardTunnelProvider { private func configureLogging() { let logLevel: SwiftyBeaver.Level = (cfg.shouldDebug ? .debug : .info) let logFormat = cfg.debugLogFormat ?? "$Dyyyy-MM-dd HH:mm:ss.SSS$d $L $N.$F:$l - $M" - + if cfg.shouldDebug { let console = ConsoleDestination() console.useNSLog = true diff --git a/Sources/TunnelKitWireGuardCore/Configuration.swift b/Sources/TunnelKitWireGuardCore/Configuration.swift index 50c6778..dc4e4a3 100644 --- a/Sources/TunnelKitWireGuardCore/Configuration.swift +++ b/Sources/TunnelKitWireGuardCore/Configuration.swift @@ -29,15 +29,15 @@ import NetworkExtension public protocol WireGuardConfigurationProviding { var interface: InterfaceConfiguration { get } - + var peers: [PeerConfiguration] { get } - + var privateKey: String { get } var publicKey: String { get } var addresses: [String] { get } - + var dnsServers: [String] { get } var dnsSearchDomains: [String] { get } @@ -64,13 +64,13 @@ public protocol WireGuardConfigurationProviding { extension WireGuard { public struct ConfigurationBuilder: WireGuardConfigurationProviding { private static let defaultGateway4 = IPAddressRange(from: "0.0.0.0/0")! - + private static let defaultGateway6 = IPAddressRange(from: "::/0")! - + public private(set) var interface: InterfaceConfiguration public private(set) var peers: [PeerConfiguration] - + public init() { self.init(PrivateKey()) } @@ -81,19 +81,19 @@ extension WireGuard { } self.init(privateKey) } - + private init(_ privateKey: PrivateKey) { interface = InterfaceConfiguration(privateKey: privateKey) peers = [] } - + public init(_ tunnelConfiguration: TunnelConfiguration) { interface = tunnelConfiguration.interface peers = tunnelConfiguration.peers } - + // MARK: WireGuardConfigurationProviding - + public var privateKey: String { get { interface.privateKey.base64Key @@ -114,7 +114,7 @@ extension WireGuard { interface.addresses = newValue.compactMap(IPAddressRange.init) } } - + public var dnsServers: [String] { get { interface.dns.map(\.stringRepresentation) @@ -159,7 +159,7 @@ extension WireGuard { interface.mtu = newValue } } - + // MARK: Modification public mutating func addPeer(_ base64PublicKey: String, endpoint: String, allowedIPs: [String] = []) throws { @@ -198,7 +198,7 @@ extension WireGuard { $0 == Self.defaultGateway6 } } - + public mutating func removeDefaultGateways(fromPeer peerIndex: Int) { peers[peerIndex].allowedIPs.removeAll { $0 == Self.defaultGateway4 || $0 == Self.defaultGateway6 @@ -230,7 +230,7 @@ extension WireGuard { public mutating func setKeepAlive(_ keepAlive: UInt16, forPeer peerIndex: Int) { peers[peerIndex].persistentKeepAlive = keepAlive } - + public func build() -> Configuration { let tunnelConfiguration = TunnelConfiguration(name: nil, interface: interface, peers: peers) return Configuration(tunnelConfiguration: tunnelConfiguration) @@ -239,25 +239,25 @@ extension WireGuard { public struct Configuration: Codable, Equatable, WireGuardConfigurationProviding { public let tunnelConfiguration: TunnelConfiguration - + public var interface: InterfaceConfiguration { tunnelConfiguration.interface } - + public var peers: [PeerConfiguration] { tunnelConfiguration.peers } - + public init(tunnelConfiguration: TunnelConfiguration) { self.tunnelConfiguration = tunnelConfiguration } - + public func builder() -> WireGuard.ConfigurationBuilder { WireGuard.ConfigurationBuilder(tunnelConfiguration) } // MARK: WireGuardConfigurationProviding - + public var privateKey: String { interface.privateKey.base64Key } @@ -269,7 +269,7 @@ extension WireGuard { public var addresses: [String] { interface.addresses.map(\.stringRepresentation) } - + public var dnsServers: [String] { interface.dns.map(\.stringRepresentation) } @@ -291,14 +291,14 @@ extension WireGuard { } // MARK: Codable - + public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let wg = try container.decode(String.self) let cfg = try TunnelConfiguration(fromWgQuickConfig: wg, called: nil) self.init(tunnelConfiguration: cfg) } - + public func encode(to encoder: Encoder) throws { let wg = tunnelConfiguration.asWgQuickConfig() var container = encoder.singleValueContainer() @@ -315,7 +315,7 @@ extension WireGuardConfigurationProviding { public var peersCount: Int { peers.count } - + public func publicKey(ofPeer peerIndex: Int) -> String { peers[peerIndex].publicKey.base64Key } diff --git a/Sources/TunnelKitWireGuardCore/Internal/Logger.swift b/Sources/TunnelKitWireGuardCore/Internal/Logger.swift index b02ca7f..bf33b6a 100644 --- a/Sources/TunnelKitWireGuardCore/Internal/Logger.swift +++ b/Sources/TunnelKitWireGuardCore/Internal/Logger.swift @@ -7,10 +7,10 @@ extension OSLogType { switch self { case .debug: return .debug - + case .info: return .info - + case .error, .fault: return .error diff --git a/Sources/TunnelKitWireGuardManager/WireGuard+ProviderConfiguration.swift b/Sources/TunnelKitWireGuardManager/WireGuard+ProviderConfiguration.swift index 6cfe080..b81e582 100644 --- a/Sources/TunnelKitWireGuardManager/WireGuard+ProviderConfiguration.swift +++ b/Sources/TunnelKitWireGuardManager/WireGuard+ProviderConfiguration.swift @@ -42,18 +42,18 @@ extension WireGuard { case lastError = "WireGuard.LastError" } - + public let title: String - + public let appGroup: String public let configuration: WireGuard.Configuration public var shouldDebug = false - public var debugLogPath: String? = nil + public var debugLogPath: String? - public var debugLogFormat: String? = nil + public var debugLogFormat: String? public init(_ title: String, appGroup: String, configuration: WireGuard.Configuration) { self.title = title @@ -68,7 +68,7 @@ extension WireGuard { } } } - + // MARK: NetworkExtensionConfiguration extension WireGuard.ProviderConfiguration: NetworkExtensionConfiguration { @@ -94,7 +94,6 @@ extension WireGuard.ProviderConfiguration { public var lastError: WireGuardProviderError? { return defaults?.wireGuardLastError } - public var urlForDebugLog: URL? { return defaults?.wireGuardURLForDebugLog(appGroup: appGroup) diff --git a/Sources/__TunnelKitUtils/Data+Manipulation.swift b/Sources/__TunnelKitUtils/Data+Manipulation.swift index d290818..04cb42e 100644 --- a/Sources/__TunnelKitUtils/Data+Manipulation.swift +++ b/Sources/__TunnelKitUtils/Data+Manipulation.swift @@ -44,11 +44,9 @@ extension UnicodeScalar { let value = self.value if 48 <= value && value <= 57 { return UInt8(value - 48) - } - else if 65 <= value && value <= 70 { + } else if 65 <= value && value <= 70 { return UInt8(value - 55) - } - else if 97 <= value && value <= 102 { + } else if 97 <= value && value <= 102 { return UInt8(value - 87) } fatalError("\(self) not a legal hex nibble") @@ -58,7 +56,7 @@ extension UnicodeScalar { extension Data { public init(hex: String) { let scalars = hex.unicodeScalars - var bytes = Array(repeating: 0, count: (scalars.count + 1) >> 1) + var bytes = [UInt8](repeating: 0, count: (scalars.count + 1) >> 1) for (index, scalar) in scalars.enumerated() { var nibble = scalar.hexNibble if index & 1 == 0 { @@ -72,7 +70,7 @@ extension Data { public func toHex() -> String { return map { String(format: "%02hhx", $0) }.joined() } - + public mutating func zero() { resetBytes(in: 0.. String? { var nullOffset: Int? for i in from.. UInt16 { return subdata(in: from..<(from + 2)).withUnsafeBytes { $0.pointee } @@ -155,7 +153,7 @@ extension Data { // print("value: \(String(format: "%x", value))") return value } - + @available(*, deprecated) func UInt32ValueFromBuffer(from: Int) -> UInt32 { var value: UInt32 = 0 @@ -167,7 +165,7 @@ extension Data { // print("value: \(String(format: "%x", value))") return value } - + // best public func UInt32Value(from: Int) -> UInt32 { return subdata(in: from..<(from + 4)).withUnsafeBytes { diff --git a/Sources/__TunnelKitUtils/NSRegularExpression+Shortcuts.swift b/Sources/__TunnelKitUtils/NSRegularExpression+Shortcuts.swift index 27d8e8f..55a48fa 100644 --- a/Sources/__TunnelKitUtils/NSRegularExpression+Shortcuts.swift +++ b/Sources/__TunnelKitUtils/NSRegularExpression+Shortcuts.swift @@ -29,10 +29,10 @@ extension NSRegularExpression { public convenience init(_ pattern: String) { try! self.init(pattern: pattern, options: []) } - + public func groups(in string: String) -> [String] { var results: [String] = [] - enumerateMatches(in: string, options: [], range: NSMakeRange(0, string.count)) { result, flags, stop in + enumerateMatches(in: string, options: [], range: NSRange(location: 0, length: string.count)) { result, _, _ in guard let result = result else { return } @@ -48,7 +48,7 @@ extension NSRegularExpression { extension NSRegularExpression { public func enumerateSpacedComponents(in string: String, using block: ([String]) -> Void) { - enumerateMatches(in: string, options: [], range: NSMakeRange(0, string.count)) { result, flags, stop in + enumerateMatches(in: string, options: [], range: NSRange(location: 0, length: string.count)) { result, _, _ in guard let range = result?.range else { return } @@ -57,7 +57,7 @@ extension NSRegularExpression { block(tokens) } } - + public func enumerateSpacedArguments(in string: String, using block: ([String]) -> Void) { enumerateSpacedComponents(in: string) { (tokens) in var args = tokens diff --git a/Tests/TunnelKitCoreTests/DataManipulationTests.swift b/Tests/TunnelKitCoreTests/DataManipulationTests.swift index a1fe936..c74bd92 100644 --- a/Tests/TunnelKitCoreTests/DataManipulationTests.swift +++ b/Tests/TunnelKitCoreTests/DataManipulationTests.swift @@ -49,30 +49,30 @@ class DataManipulationTests: XCTestCase { func testUInt() { let data = Data([0x22, 0xff, 0xaa, 0xbb, 0x55, 0x66]) - + XCTAssertEqual(data.UInt16Value(from: 3), 0x55bb) XCTAssertEqual(data.UInt32Value(from: 2), 0x6655bbaa) XCTAssertEqual(data.UInt16Value(from: 4), 0x6655) XCTAssertEqual(data.UInt32Value(from: 0), 0xbbaaff22) - + // XCTAssertEqual(data.UInt16Value(from: 3), data.UInt16ValueFromPointers(from: 3)) // XCTAssertEqual(data.UInt32Value(from: 2), data.UInt32ValueFromBuffer(from: 2)) // XCTAssertEqual(data.UInt16Value(from: 4), data.UInt16ValueFromPointers(from: 4)) // XCTAssertEqual(data.UInt32Value(from: 0), data.UInt32ValueFromBuffer(from: 0)) } - + func testZeroingData() { let z1 = Z() z1.append(Z(Data(hex: "12345678"))) z1.append(Z(Data(hex: "abcdef"))) let z2 = z1.withOffset(2, count: 3) // 5678ab let z3 = z2.appending(Z(Data(hex: "aaddcc"))) // 5678abaaddcc - + XCTAssertEqual(z1.toData(), Data(hex: "12345678abcdef")) XCTAssertEqual(z2.toData(), Data(hex: "5678ab")) XCTAssertEqual(z3.toData(), Data(hex: "5678abaaddcc")) } - + func testFlatCount() { var v: [Data] = [] v.append(Data(hex: "11223344")) diff --git a/Tests/TunnelKitCoreTests/RawPerformanceTests.swift b/Tests/TunnelKitCoreTests/RawPerformanceTests.swift index e74ce82..c2a222f 100644 --- a/Tests/TunnelKitCoreTests/RawPerformanceTests.swift +++ b/Tests/TunnelKitCoreTests/RawPerformanceTests.swift @@ -40,26 +40,26 @@ import XCTest @testable import TunnelKitCore class RawPerformanceTests: XCTestCase { - + override func setUp() { // Put setup code here. This method is called before the invocation of each test method in the class. } - + override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. } - + // 0.434s func testUInt16FromBuffer() { let data = Data([0x22, 0xff, 0xaa, 0xbb, 0x55, 0x66]) - + measure { for _ in 0..<1000000 { - let _ = data.UInt16Value(from: 3) + _ = data.UInt16Value(from: 3) } } } - + // // 0.463s // func testUInt16FromPointers() { // let data = Data([0x22, 0xff, 0xaa, 0xbb, 0x55, 0x66]) @@ -81,18 +81,18 @@ class RawPerformanceTests: XCTestCase { // } // } // } - + // 0.469s func testUInt32FromPointers() { let data = Data([0x22, 0xff, 0xaa, 0xbb, 0x55, 0x66]) - + measure { for _ in 0..<1000000 { - let _ = data.UInt32Value(from: 1) + _ = data.UInt32Value(from: 1) } } } - + // // 0.071s // func testRandomUInt32FromBuffer() { // measure { @@ -101,12 +101,12 @@ class RawPerformanceTests: XCTestCase { // } // } // } - + // 0.063s func testRandomUInt32FromPointers() { measure { for _ in 0..<10000 { - let _ = try! SecureRandom.uint32() + _ = try! SecureRandom.uint32() } } } @@ -127,7 +127,7 @@ class RawPerformanceTests: XCTestCase { measure { for data in suite { // let _ = UInt32(bigEndian: data.subdata(in: 0..<4).withUnsafeBytes { $0.pointee }) - let _ = data.networkUInt32Value(from: 0) + _ = data.networkUInt32Value(from: 0) } } } @@ -137,7 +137,7 @@ class RawPerformanceTests: XCTestCase { let suite = TestUtils.generateDataSuite(1000, 100000) measure { for data in suite { - let _ = data.subdata(in: 5.. URL { return Bundle.module.url(forResource: name, withExtension: "ovpn")! } - + } diff --git a/Tests/TunnelKitOpenVPNTests/ConfigurationTests.swift b/Tests/TunnelKitOpenVPNTests/ConfigurationTests.swift index 8f34c5b..a991701 100644 --- a/Tests/TunnelKitOpenVPNTests/ConfigurationTests.swift +++ b/Tests/TunnelKitOpenVPNTests/ConfigurationTests.swift @@ -30,15 +30,15 @@ import TunnelKitOpenVPNCore class ConfigurationTests: XCTestCase { override func setUp() { super.setUp() - + CoreConfiguration.masksPrivateData = false } - + override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } - + func testRandomizeHostnames() { var builder = OpenVPN.ConfigurationBuilder() let hostname = "my.host.name" @@ -49,7 +49,7 @@ class ConfigurationTests: XCTestCase { ] builder.randomizeHostnames = true let cfg = builder.build() - + cfg.processedRemotes?.forEach { let comps = $0.address.components(separatedBy: ".") guard let first = comps.first else { diff --git a/Tests/TunnelKitOpenVPNTests/ControlChannelTests.swift b/Tests/TunnelKitOpenVPNTests/ControlChannelTests.swift index 504471a..c00952a 100644 --- a/Tests/TunnelKitOpenVPNTests/ControlChannelTests.swift +++ b/Tests/TunnelKitOpenVPNTests/ControlChannelTests.swift @@ -39,7 +39,7 @@ class ControlChannelTests: XCTestCase { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. } - + override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() @@ -55,16 +55,16 @@ class ControlChannelTests: XCTestCase { let key = OpenVPN.StaticKey(biData: Data(hex: hex)) let server = CryptoBox(cipherAlgorithm: nil, digestAlgorithm: OpenVPN.Digest.sha1.rawValue) XCTAssertNoThrow(try server.configure(withCipherEncKey: nil, cipherDecKey: nil, hmacEncKey: key.hmacReceiveKey, hmacDecKey: key.hmacSendKey)) - + // let original = Data(hex: "38858fe14742fdae40e67c9137933a412a711c0d0514aca6db6476d17d000000015b96c9470000000000") let hmac = Data(hex: "e67c9137933a412a711c0d0514aca6db6476d17d") let subject = Data(hex: "000000015b96c94738858fe14742fdae400000000000") let data = hmac + subject print(data.toHex()) - + XCTAssertNoThrow(try server.decrypter().verifyData(data, flags: nil)) } - + // 38 // HARD_RESET // bccfd171ce22e085 // session_id // e01a3454c354f3c3093b00fc8d6228a8b69ef503d56f6a572ebd26a800711b4cd4df2b9daf06cb90f82379e7815e39fb73be4ac5461752db4f35120474af82b2 // hmac @@ -74,11 +74,11 @@ class ControlChannelTests: XCTestCase { func testAuth() { let client = try! OpenVPN.ControlChannel.AuthSerializer(withKey: OpenVPN.StaticKey(data: Data(hex: hex), direction: .client), digest: .sha512) let server = try! OpenVPN.ControlChannel.AuthSerializer(withKey: OpenVPN.StaticKey(data: Data(hex: hex), direction: .server), digest: .sha512) - + // let original = Data(hex: "38bccfd1") let original = Data(hex: "38bccfd171ce22e085e01a3454c354f3c3093b00fc8d6228a8b69ef503d56f6a572ebd26a800711b4cd4df2b9daf06cb90f82379e7815e39fb73be4ac5461752db4f35120474af82b2000000015b93b65d0000000000") let timestamp = UInt32(0x5b93b65d) - + let packet: ControlPacket do { packet = try client.deserialize(data: original, start: 0, end: nil) @@ -90,7 +90,7 @@ class ControlChannelTests: XCTestCase { XCTAssertEqual(packet.sessionId, Data(hex: "bccfd171ce22e085")) XCTAssertNil(packet.ackIds) XCTAssertEqual(packet.packetId, 0) - + let raw: Data do { raw = try server.serialize(packet: packet, timestamp: timestamp) @@ -109,7 +109,7 @@ class ControlChannelTests: XCTestCase { let original = Data(hex: "407bf3d6a260e6476d000000015ba4155887940856ddb70e01693980c5c955cb5506ecf9fd3e0bcee0c802ec269427d43bf1cda1837ffbf30c83cacff852cd0b7f4c") let timestamp = UInt32(0x5ba41558) - + let packet: ControlPacket do { packet = try client.deserialize(data: original, start: 0, end: nil) diff --git a/Tests/TunnelKitOpenVPNTests/DataPathEncryptionTests.swift b/Tests/TunnelKitOpenVPNTests/DataPathEncryptionTests.swift index 67196d1..b00ca89 100644 --- a/Tests/TunnelKitOpenVPNTests/DataPathEncryptionTests.swift +++ b/Tests/TunnelKitOpenVPNTests/DataPathEncryptionTests.swift @@ -45,28 +45,28 @@ class DataPathEncryptionTests: XCTestCase { private let hmacKey = try! SecureRandom.safeData(length: 32) private var enc: DataPathEncrypter! - + private var dec: DataPathDecrypter! - + override func setUp() { } - + override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. } - + func testCBC() { prepareBox(cipher: "aes-128-cbc", digest: "sha256") privateTestDataPathHigh(peerId: nil) privateTestDataPathLow(peerId: nil) } - + func testFloatingCBC() { prepareBox(cipher: "aes-128-cbc", digest: "sha256") privateTestDataPathHigh(peerId: 0x64385837) privateTestDataPathLow(peerId: 0x64385837) } - + func testGCM() { prepareBox(cipher: "aes-256-gcm", digest: nil) privateTestDataPathHigh(peerId: nil) @@ -78,14 +78,14 @@ class DataPathEncryptionTests: XCTestCase { privateTestDataPathHigh(peerId: 0x64385837) privateTestDataPathLow(peerId: 0x64385837) } - + func prepareBox(cipher: String, digest: String?) { let box = CryptoBox(cipherAlgorithm: cipher, digestAlgorithm: digest) try! box.configure(withCipherEncKey: cipherKey, cipherDecKey: cipherKey, hmacEncKey: hmacKey, hmacDecKey: hmacKey) enc = box.encrypter().dataPathEncrypter() dec = box.decrypter().dataPathDecrypter() } - + func privateTestDataPathHigh(peerId: UInt32?) { let path = DataPath( encrypter: enc, diff --git a/Tests/TunnelKitOpenVPNTests/DataPathPerformanceTests.swift b/Tests/TunnelKitOpenVPNTests/DataPathPerformanceTests.swift index c853f4d..54d6c9f 100644 --- a/Tests/TunnelKitOpenVPNTests/DataPathPerformanceTests.swift +++ b/Tests/TunnelKitOpenVPNTests/DataPathPerformanceTests.swift @@ -47,15 +47,15 @@ class DataPathPerformanceTests: XCTestCase { private var encrypter: DataPathEncrypter! private var decrypter: DataPathDecrypter! - + override func setUp() { let ck = try! SecureRandom.safeData(length: 32) let hk = try! SecureRandom.safeData(length: 32) - + let crypto = try! OpenVPN.EncryptionBridge(.aes128cbc, .sha1, ck, ck, hk, hk) encrypter = crypto.encrypter() decrypter = crypto.decrypter() - + dataPath = DataPath( encrypter: encrypter, decrypter: decrypter, @@ -85,18 +85,18 @@ class DataPathPerformanceTests: XCTestCase { //// print(">>> \(packets?.count) packets") // XCTAssertEqual(decryptedPackets, packets) // } - + // 16ms func testPointerBased() { let packets = TestUtils.generateDataSuite(1200, 1000) var encryptedPackets: [Data]! var decryptedPackets: [Data]! - + measure { encryptedPackets = try! self.dataPath.encryptPackets(packets, key: 0) decryptedPackets = try! self.dataPath.decryptPackets(encryptedPackets, keepAlive: nil) } - + // print(">>> \(packets?.count) packets") XCTAssertEqual(decryptedPackets, packets) } diff --git a/Tests/TunnelKitOpenVPNTests/EncryptionPerformanceTests.swift b/Tests/TunnelKitOpenVPNTests/EncryptionPerformanceTests.swift index da61a3f..5bef3c2 100644 --- a/Tests/TunnelKitOpenVPNTests/EncryptionPerformanceTests.swift +++ b/Tests/TunnelKitOpenVPNTests/EncryptionPerformanceTests.swift @@ -41,17 +41,17 @@ import CTunnelKitOpenVPNProtocol class EncryptionPerformanceTests: XCTestCase { private var cbcEncrypter: Encrypter! - + private var cbcDecrypter: Decrypter! - + private var gcmEncrypter: Encrypter! - + private var gcmDecrypter: Decrypter! - + override func setUp() { let cipherKey = try! SecureRandom.safeData(length: 32) let hmacKey = try! SecureRandom.safeData(length: 32) - + let cbc = CryptoBox(cipherAlgorithm: "aes-128-cbc", digestAlgorithm: "sha1") try! cbc.configure(withCipherEncKey: cipherKey, cipherDecKey: cipherKey, hmacEncKey: hmacKey, hmacDecKey: hmacKey) cbcEncrypter = cbc.encrypter() @@ -72,7 +72,7 @@ class EncryptionPerformanceTests: XCTestCase { let suite = TestUtils.generateDataSuite(1000, 100000) measure { for data in suite { - let _ = try! self.cbcEncrypter.encryptData(data, flags: nil) + _ = try! self.cbcEncrypter.encryptData(data, flags: nil) } } } @@ -86,7 +86,7 @@ class EncryptionPerformanceTests: XCTestCase { } measure { for data in suite { - let _ = try! self.gcmEncrypter.encryptData(data, flags: &flags) + _ = try! self.gcmEncrypter.encryptData(data, flags: &flags) } } } diff --git a/Tests/TunnelKitOpenVPNTests/EncryptionTests.swift b/Tests/TunnelKitOpenVPNTests/EncryptionTests.swift index 63df5f9..4d46330 100644 --- a/Tests/TunnelKitOpenVPNTests/EncryptionTests.swift +++ b/Tests/TunnelKitOpenVPNTests/EncryptionTests.swift @@ -44,11 +44,11 @@ class EncryptionTests: XCTestCase { private var cipherEncKey: ZeroingData! private var cipherDecKey: ZeroingData! - + private var hmacEncKey: ZeroingData! - + private var hmacDecKey: ZeroingData! - + override func setUp() { cipherEncKey = try! SecureRandom.safeData(length: 32) cipherDecKey = try! SecureRandom.safeData(length: 32) @@ -76,10 +76,10 @@ class EncryptionTests: XCTestCase { let encrypted = try! client.encrypter().encryptData(plain, flags: nil) XCTAssertNoThrow(try server.decrypter().verifyData(encrypted, flags: nil)) } - + func testGCM() { let (client, server) = clientServer("aes-256-gcm", nil) - + let packetId: [UInt8] = [0x56, 0x34, 0x12, 0x00] let ad: [UInt8] = [0x00, 0x12, 0x34, 0x56] var flags = packetId.withUnsafeBufferPointer { (iv) in @@ -92,7 +92,7 @@ class EncryptionTests: XCTestCase { let decrypted = try! server.decrypter().decryptData(encrypted, flags: &flags) XCTAssertEqual(plain, decrypted) } - + func testCTR() { let (client, server) = clientServer("aes-256-ctr", "sha256") @@ -119,17 +119,17 @@ class EncryptionTests: XCTestCase { print(md5) XCTAssertEqual(md5, exp) } - + func testPrivateKeyDecryption() { privateTestPrivateKeyDecryption(pkcs: "1") privateTestPrivateKeyDecryption(pkcs: "8") } - + private func privateTestPrivateKeyDecryption(pkcs: String) { let bundle = Bundle.module let encryptedPath = bundle.path(forResource: "tunnelbear", ofType: "enc.\(pkcs).key")! let decryptedPath = bundle.path(forResource: "tunnelbear", ofType: "key")! - + XCTAssertThrowsError(try TLSBox.decryptedPrivateKey(fromPath: encryptedPath, passphrase: "wrongone")) let decryptedViaPath = try! TLSBox.decryptedPrivateKey(fromPath: encryptedPath, passphrase: "foobar") print(decryptedViaPath) @@ -137,17 +137,17 @@ class EncryptionTests: XCTestCase { let decryptedViaString = try! TLSBox.decryptedPrivateKey(fromPEM: encryptedPEM, passphrase: "foobar") print(decryptedViaString) XCTAssertEqual(decryptedViaPath, decryptedViaString) - + let expDecrypted = try! String(contentsOfFile: decryptedPath) XCTAssertEqual(decryptedViaPath, expDecrypted) } - + func testCertificatePreamble() { let url = Bundle.module.url(forResource: "tunnelbear", withExtension: "crt")! let cert = OpenVPN.CryptoContainer(pem: try! String(contentsOf: url)) XCTAssert(cert.pem.hasPrefix("-----BEGIN")) } - + private func clientServer(_ c: String?, _ d: String?) -> (CryptoBox, CryptoBox) { let client = CryptoBox(cipherAlgorithm: c, digestAlgorithm: d) let server = CryptoBox(cipherAlgorithm: c, digestAlgorithm: d) diff --git a/Tests/TunnelKitOpenVPNTests/LinkTests.swift b/Tests/TunnelKitOpenVPNTests/LinkTests.swift index f19dda6..4214c24 100644 --- a/Tests/TunnelKitOpenVPNTests/LinkTests.swift +++ b/Tests/TunnelKitOpenVPNTests/LinkTests.swift @@ -49,19 +49,19 @@ class LinkTests: XCTestCase { } // UDP - + func testUnreliableControlQueue() { let seq1 = [0, 5, 2, 1, 4, 3] let seq2 = [5, 2, 1, 9, 4, 3, 0, 8, 7, 10, 4, 3, 5, 6] let seq3 = [5, 2, 11, 1, 2, 9, 4, 5, 5, 3, 8, 0, 6, 8, 2, 7, 10, 4, 3, 5, 6] - + for seq in [seq1, seq2, seq3] { XCTAssertEqual(TestUtils.uniqArray(seq.sorted()), handleControlSequence(seq)) } } - + // TCP - + // private func testPacketStream() { // var bytes: [UInt8] = [] // var until: Int @@ -156,25 +156,25 @@ class LinkTests: XCTestCase { } return hdl } - + private func enqueueControl(_ q: inout [Int], _ id: inout Int, _ p: Int, _ h: (Int) -> Void) { q.append(p) q.sort { (p1, p2) -> Bool in return (p1 < p2) } - + print("q = \(q)") print("id = \(id)") for p in q { print("test(\(p))") - if (p < id) { + if p < id { q.removeFirst() continue } - if (p != id) { + if p != id { return } - + h(p) print("handle(\(p))") id += 1 diff --git a/Tests/TunnelKitOpenVPNTests/PacketTests.swift b/Tests/TunnelKitOpenVPNTests/PacketTests.swift index cbacd8a..4b46df6 100644 --- a/Tests/TunnelKitOpenVPNTests/PacketTests.swift +++ b/Tests/TunnelKitOpenVPNTests/PacketTests.swift @@ -28,17 +28,17 @@ import XCTest import CTunnelKitOpenVPNProtocol class PacketTests: XCTestCase { - + override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. } - + override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } - + func testControlPacket() { let id: UInt32 = 0x1456 let code: PacketCode = .controlV1 @@ -64,7 +64,7 @@ class PacketTests: XCTestCase { let expected = Data(hex: "2b112233445566778805000000aa000000bb000000cc000000dd000000eea639328cbf03490e") print("Serialized: \(serialized.toHex())") print("Expected : \(expected.toHex())") - + XCTAssertEqual(serialized, expected) } } diff --git a/Tests/TunnelKitOpenVPNTests/PushTests.swift b/Tests/TunnelKitOpenVPNTests/PushTests.swift index 850abdc..a4b55a0 100644 --- a/Tests/TunnelKitOpenVPNTests/PushTests.swift +++ b/Tests/TunnelKitOpenVPNTests/PushTests.swift @@ -40,15 +40,15 @@ private extension OpenVPN.PushReply { } class PushTests: XCTestCase { - + override func setUp() { // Put setup code here. This method is called before the invocation of each test method in the class. } - + override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. } - + func testNet30() { let msg = "PUSH_REPLY,redirect-gateway def1,dhcp-option DNS 209.222.18.222,dhcp-option DNS 209.222.18.218,ping 10,comp-lzo no,route 10.5.10.1,topology net30,ifconfig 10.5.10.6 10.5.10.5,auth-token AUkQf/b3nj3L+CH4RJPP0Vuq8/gpntr7uPqzjQhncig=" let reply = try! OpenVPN.PushReply(message: msg)! @@ -59,25 +59,25 @@ class PushTests: XCTestCase { XCTAssertEqual(reply.options.ipv4?.defaultGateway, "10.5.10.5") XCTAssertEqual(reply.options.dnsServers, ["209.222.18.222", "209.222.18.218"]) } - + func testSubnet() { let msg = "PUSH_REPLY,dhcp-option DNS 8.8.8.8,dhcp-option DNS 4.4.4.4,route-gateway 10.8.0.1,topology subnet,ping 10,ping-restart 120,ifconfig 10.8.0.2 255.255.255.0,peer-id 0" let reply = try! OpenVPN.PushReply(message: msg)! reply.debug() - + XCTAssertEqual(reply.options.ipv4?.address, "10.8.0.2") XCTAssertEqual(reply.options.ipv4?.addressMask, "255.255.255.0") XCTAssertEqual(reply.options.ipv4?.defaultGateway, "10.8.0.1") XCTAssertEqual(reply.options.dnsServers, ["8.8.8.8", "4.4.4.4"]) } - + func testRoute() { let msg = "PUSH_REPLY,dhcp-option DNS 8.8.8.8,dhcp-option DNS 4.4.4.4,route-gateway 10.8.0.1,route 192.168.0.0 255.255.255.0 10.8.0.12,topology subnet,ping 10,ping-restart 120,ifconfig 10.8.0.2 255.255.255.0,peer-id 0" let reply = try! OpenVPN.PushReply(message: msg)! reply.debug() - + let route = reply.options.routes4!.first! - + XCTAssertEqual(route.destination, "192.168.0.0") XCTAssertEqual(route.mask, "255.255.255.0") XCTAssertEqual(route.gateway, "10.8.0.12") @@ -87,7 +87,7 @@ class PushTests: XCTestCase { let msg = "PUSH_REPLY,dhcp-option DNS6 2001:4860:4860::8888,dhcp-option DNS6 2001:4860:4860::8844,tun-ipv6,route-gateway 10.8.0.1,topology subnet,ping 10,ping-restart 120,ifconfig-ipv6 fe80::601:30ff:feb7:ec01/64 fe80::601:30ff:feb7:dc02,ifconfig 10.8.0.2 255.255.255.0,peer-id 0" let reply = try! OpenVPN.PushReply(message: msg)! reply.debug() - + XCTAssertEqual(reply.options.ipv4?.address, "10.8.0.2") XCTAssertEqual(reply.options.ipv4?.addressMask, "255.255.255.0") XCTAssertEqual(reply.options.ipv4?.defaultGateway, "10.8.0.1") @@ -96,19 +96,19 @@ class PushTests: XCTestCase { XCTAssertEqual(reply.options.ipv6?.defaultGateway, "fe80::601:30ff:feb7:dc02") XCTAssertEqual(reply.options.dnsServers, ["2001:4860:4860::8888", "2001:4860:4860::8844"]) } - + func testCompressionFraming() { let msg = "PUSH_REPLY,dhcp-option DNS 8.8.8.8,dhcp-option DNS 4.4.4.4,comp-lzo no,route 10.8.0.1,topology net30,ping 10,ping-restart 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-CBC" let reply = try! OpenVPN.PushReply(message: msg)! reply.debug() - + XCTAssertEqual(reply.options.compressionFraming, .compLZO) } - + func testCompression() { let msg = "PUSH_REPLY,dhcp-option DNS 8.8.8.8,dhcp-option DNS 4.4.4.4,route 10.8.0.1,topology net30,ping 10,ping-restart 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-CBC" var reply: OpenVPN.PushReply - + reply = try! OpenVPN.PushReply(message: msg.appending(",comp-lzo no"))! reply.debug() XCTAssertEqual(reply.options.compressionFraming, .compLZO) @@ -134,7 +134,7 @@ class PushTests: XCTestCase { XCTAssertEqual(reply.options.compressionFraming, .compress) XCTAssertEqual(reply.options.compressionAlgorithm, .other) } - + func testNCP() { let msg = "PUSH_REPLY,dhcp-option DNS 8.8.8.8,dhcp-option DNS 4.4.4.4,comp-lzo no,route 10.8.0.1,topology net30,ping 10,ping-restart 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-GCM" let reply = try! OpenVPN.PushReply(message: msg)! @@ -147,32 +147,32 @@ class PushTests: XCTestCase { let msg = "PUSH_REPLY,dhcp-option DNS 8.8.8.8,dhcp-option DNS 4.4.4.4,comp-lzo no,route 10.8.0.1,topology net30,ping 10,ping-restart 120,ifconfig 10.8.0.18 10.8.0.17,peer-id 3,cipher AES-256-GCM,auth-token" let reply = try! OpenVPN.PushReply(message: msg)! reply.debug() - + XCTAssertEqual(reply.options.cipher, .aes256gcm) } - + func testPing() { let msg = "PUSH_REPLY,route 192.168.1.0 255.255.255.0,route 10.0.2.0 255.255.255.0,dhcp-option DNS 192.168.1.99,dhcp-option DNS 176.103.130.130,route 10.0.2.1,topology net30,ping 10,ping-restart 60,ifconfig 10.0.2.14 10.0.2.13" let reply = try! OpenVPN.PushReply(message: msg)! reply.debug() - + XCTAssertEqual(reply.options.keepAliveInterval, 10) } - + func testPingRestart() { let msg = "PUSH_REPLY,route 192.168.1.0 255.255.255.0,route 10.0.2.0 255.255.255.0,dhcp-option DNS 192.168.1.99,dhcp-option DNS 176.103.130.130,route 10.0.2.1,topology net30,ping 10,ping-restart 60,ifconfig 10.0.2.14 10.0.2.13" let reply = try! OpenVPN.PushReply(message: msg)! reply.debug() - + XCTAssertEqual(reply.options.keepAliveTimeout, 60) } - + func testProvost() { let msg = "PUSH_REPLY,route 87.233.192.218,route 87.233.192.219,route 87.233.192.220,route 87.248.186.252,route 92.241.171.245,route 103.246.200.0 255.255.252.0,route 109.239.140.0 255.255.255.0,route 128.199.0.0 255.255.0.0,route 13.125.0.0 255.255.0.0,route 13.230.0.0 255.254.0.0,route 13.56.0.0 255.252.0.0,route 149.154.160.0 255.255.252.0,route 149.154.164.0 255.255.252.0,route 149.154.168.0 255.255.252.0,route 149.154.172.0 255.255.252.0,route 159.122.128.0 255.255.192.0,route 159.203.0.0 255.255.0.0,route 159.65.0.0 255.255.0.0,route 159.89.0.0 255.255.0.0,route 165.227.0.0 255.255.0.0,route 167.99.0.0 255.255.0.0,route 174.138.0.0 255.255.128.0,route 176.67.169.0 255.255.255.0,route 178.239.88.0 255.255.248.0,route 178.63.0.0 255.255.0.0,route 18.130.0.0 255.255.0.0,route 18.144.0.0 255.255.0.0,route 18.184.0.0 255.254.0.0,route 18.194.0.0 255.254.0.0,route 18.196.0.0 255.254.0.0,route 18.204.0.0 255.252.0.0,push-continuation 2" let reply = try? OpenVPN.PushReply(message: msg)! reply?.debug() } - + func testPeerInfo() { let peerInfo = CoreConfiguration.OpenVPN.peerInfo() print(peerInfo) diff --git a/Tests/TunnelKitOpenVPNTests/StaticKeyTests.swift b/Tests/TunnelKitOpenVPNTests/StaticKeyTests.swift index 78ef546..bd11c0e 100644 --- a/Tests/TunnelKitOpenVPNTests/StaticKeyTests.swift +++ b/Tests/TunnelKitOpenVPNTests/StaticKeyTests.swift @@ -51,22 +51,22 @@ dccdb953cdf32bea03f365760b0ed800 7aed27125592a7148d25c87fdbe0a3f6 -----END OpenVPN Static key V1----- """ - + override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. } - + override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } - + func testFileBidirectional() { let expected = Data(hex: "cf55d863fcbe314df5f0b45dbe974d9bde33ef5b4803c3985531c6c23ca6906d6cd028efc8585d1b9e71003566bd7891b9cc9212bcba510109922eed87f5c8e6") let key = OpenVPN.StaticKey(file: content, direction: nil) XCTAssertNotNil(key) - + XCTAssertEqual(key?.hmacSendKey.toData(), expected) XCTAssertEqual(key?.hmacReceiveKey.toData(), expected) } @@ -76,7 +76,7 @@ dccdb953cdf32bea03f365760b0ed800 let receive = Data(hex: "cf55d863fcbe314df5f0b45dbe974d9bde33ef5b4803c3985531c6c23ca6906d6cd028efc8585d1b9e71003566bd7891b9cc9212bcba510109922eed87f5c8e6") let key = OpenVPN.StaticKey(file: content, direction: .client) XCTAssertNotNil(key) - + XCTAssertEqual(key?.hmacSendKey.toData(), send) XCTAssertEqual(key?.hmacReceiveKey.toData(), receive) } diff --git a/Tests/TunnelKitOpenVPNTests/TestUtils.swift b/Tests/TunnelKitOpenVPNTests/TestUtils.swift index f7671e0..b701921 100644 --- a/Tests/TunnelKitOpenVPNTests/TestUtils.swift +++ b/Tests/TunnelKitOpenVPNTests/TestUtils.swift @@ -41,9 +41,9 @@ import CTunnelKitOpenVPNProtocol public class TestUtils { public static func uniqArray(_ v: [Int]) -> [Int] { - return v.reduce([]){ $0.contains($1) ? $0 : $0 + [$1] } + return v.reduce([]) { $0.contains($1) ? $0 : $0 + [$1] } } - + public static func generateDataSuite(_ size: Int, _ count: Int) -> [Data] { var suite = [Data]() for _ in 0..?) throws { let srcLength = data.count try data.withUnsafeBytes { diff --git a/Tests/TunnelKitOpenVPNTests/XORTests.swift b/Tests/TunnelKitOpenVPNTests/XORTests.swift index c91fdd8..2ad2056 100644 --- a/Tests/TunnelKitOpenVPNTests/XORTests.swift +++ b/Tests/TunnelKitOpenVPNTests/XORTests.swift @@ -30,7 +30,7 @@ import CTunnelKitOpenVPNProtocol final class XORTests: XCTestCase { private let mask = Data(hex: "f76dab30") - + override func setUpWithError() throws { // Put setup code here. This method is called before the invocation of each test method in the class. }