commit 8c30bb399587d6361b2ef989449a0e5779f5edac Author: Davide De Rosa Date: Thu Aug 23 10:19:25 2018 +0200 Initial commit diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..ad24b5f --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,34 @@ +# Summary + +### Steps to reproduce + +### What is the current bug behavior? + +(What actually happens) + +### What is the expected correct behavior? + +(What you should see instead) + +### Relevant logs and/or screenshots + +### Possible fixes suggested remediation + +### Assignees and labels + +(delete as applicable) + +~bug ~confirmed ~regression ~suggestion + +~blocker ~major ~minor + +~tablet ~mobile ~tv + +~ios9 ~ios10 ~ios11 + +%@ms + +/cc @Dev /assign @Tester + +* [ ] Patched +* [ ] Verified patch diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..f649a3a --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,40 @@ +A similar PR may already be submitted! +Please search among the Pull requests before creating one. + +Thanks for submitting a pull request! Please provide enough information so that others can review your pull request: + +For more information, see the [CONTRIBUTING](/.github/CONTRIBUTING.md) readme. + + +**Summary** + + + +This PR fixes/implements the following **bugs/features** + +* [ ] Bug 1 +* [ ] Bug 2 +* [ ] Feature 1 +* [ ] Feature 2 +* [ ] Breaking changes + + + +Explain the **motivation** for making this change. What existing problem does the pull request solve? + + + +**Test plan (required)** + +Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes UI. + + + +**Code formatting** + + + +**Closing issues** + + +Fixes # diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ad77c22 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +*.swp +*.pbxuser +**/*.xcworkspace/xcuserdata +**/*.xcodeproj/project.xcworkspace +**/*.xcodeproj/xcuserdata +Pods +docs diff --git a/.jazzy.yaml b/.jazzy.yaml new file mode 100644 index 0000000..5b4475f --- /dev/null +++ b/.jazzy.yaml @@ -0,0 +1,26 @@ +clean: +module: "PIATunnel" +author: "Davide De Rosa @ London Trust Media, Inc." +author_url: "https://www.privateinternetaccess.com" + +theme: fullwidth + +xcodebuild_arguments: + - "-workspace" + - "PIATunnel.xcworkspace" + - "-scheme" + - "PIATunnel-iOS" + +custom_categories: + - name: Core + children: + - EncryptionProxy + - IOInterface + - LinkInterface + - TunnelInterface + - SessionProxy + - SessionProxyDelegate + - SessionError + - name: AppExtension + children: + - PIATunnelProvider diff --git a/.swift-version b/.swift-version new file mode 100644 index 0000000..5186d07 --- /dev/null +++ b/.swift-version @@ -0,0 +1 @@ +4.0 diff --git a/CLA.rst b/CLA.rst new file mode 100644 index 0000000..ba6396d --- /dev/null +++ b/CLA.rst @@ -0,0 +1,12 @@ + +# Contributor agreement + +By contributing any improvement, modification, or change to this project, I hereby certify that: + +(a) The contribution was authored or created in whole or in part by me and I have the full and unrestricted ownership right and title to submit the contribution under the MIT license; or + +(b) The contribution is based upon previously authored work that, to the best of my knowledge, is licensed appropriately under an open source license and I have the full and unrestricted right under that open source license to submit that work with modifications, whether created in whole or in part by me, under the MIT license; or + +(c) The contribution was lawfully provided to me by a licensed third-party who certified (a), (b) or (c) and I have not modified the contribution. + +I understand and agree that the contents of this project and the contents of this contribution are considered to be part of the public record and that a record of the contribution (including all personal information I submit with it) shall be maintained indefinitely and may be redistributed to third-parties consistent with this terms of this project or the open source license(s) involved. \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..248fdc7 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,22 @@ +# Contribution Guidelines +Private Internet Access welcomes community contributions, and are always looking for ways in which to improve. Please take a look at our contribution guidelines, and get involved with the PIA community. + +## Bugs and Issues +Have you found a bug? Is our software behaving in an unexpected way? Please check the open issues for duplicates -- perhaps a fix is already in development, or maybe a solution has already been published. +If not then please submit a bug report using our [template](/.github/ISSUE_TEMPLATE.md) + +## Feature Requests +Feature requests can also be submitted as issues. We’d be grateful if you checked for duplicates also before submitting a feature request. + +## Working with our Code +* Fork the repository. Make sure to keep your repository synced with the source repo. +* When you are ready to start working on a new feature, cut a new branch from “develop” with the prefix “feature/” (e.g. “feature/name-of-feature”). +* Refer to the [README](/README.md) for instructions on how to install and build. + +## Making Pull Requests +* Sync the develop branch in your fork with the develop branch in the source repo. +* Make your pull request from “feature” in your fork to “develop” in the source. +* Use short and concise commit messages. +* Lint your code before committing and making a pull request. +* Write unit tests for new features and make sure all tests are passing. +* If your pull request contains multiple commits or commits that are not meaningful, consider squashing them. diff --git a/Demo/.gitignore b/Demo/.gitignore new file mode 100644 index 0000000..82515c6 --- /dev/null +++ b/Demo/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +*.swp +*.pbxuser +**/*.xcworkspace/xcuserdata +**/*.xcodeproj/project.xcworkspace +**/*.xcodeproj/xcuserdata +Pods diff --git a/Demo/BasicTunnel-iOS/AppDelegate.swift b/Demo/BasicTunnel-iOS/AppDelegate.swift new file mode 100644 index 0000000..398d5c0 --- /dev/null +++ b/Demo/BasicTunnel-iOS/AppDelegate.swift @@ -0,0 +1,52 @@ +// +// AppDelegate.swift +// BasicTunnel-iOS +// +// Created by Davide De Rosa on 2/11/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import UIKit +import NetworkExtension +import SwiftyBeaver + +private let log = SwiftyBeaver.self + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: 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/BasicTunnel-iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/Demo/BasicTunnel-iOS/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d8db8d6 --- /dev/null +++ b/Demo/BasicTunnel-iOS/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Demo/BasicTunnel-iOS/Base.lproj/LaunchScreen.storyboard b/Demo/BasicTunnel-iOS/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f83f6fd --- /dev/null +++ b/Demo/BasicTunnel-iOS/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Demo/BasicTunnel-iOS/Base.lproj/Main.storyboard b/Demo/BasicTunnel-iOS/Base.lproj/Main.storyboard new file mode 100644 index 0000000..8d31e94 --- /dev/null +++ b/Demo/BasicTunnel-iOS/Base.lproj/Main.storyboard @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Demo/BasicTunnel-iOS/BasicTunnel-iOS.entitlements b/Demo/BasicTunnel-iOS/BasicTunnel-iOS.entitlements new file mode 100644 index 0000000..dd2ee03 --- /dev/null +++ b/Demo/BasicTunnel-iOS/BasicTunnel-iOS.entitlements @@ -0,0 +1,18 @@ + + + + + com.apple.developer.networking.networkextension + + packet-tunnel-provider + + com.apple.security.application-groups + + group.com.privateinternetaccess.ios.demo.BasicTunnel + + keychain-access-groups + + $(AppIdentifierPrefix)group.com.privateinternetaccess.ios.demo.BasicTunnel + + + diff --git a/Demo/BasicTunnel-iOS/Info.plist b/Demo/BasicTunnel-iOS/Info.plist new file mode 100644 index 0000000..16be3b6 --- /dev/null +++ b/Demo/BasicTunnel-iOS/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Demo/BasicTunnel-iOS/ViewController.swift b/Demo/BasicTunnel-iOS/ViewController.swift new file mode 100644 index 0000000..4c170b9 --- /dev/null +++ b/Demo/BasicTunnel-iOS/ViewController.swift @@ -0,0 +1,336 @@ +// +// ViewController.swift +// BasicTunnel-iOS +// +// Created by Davide De Rosa on 2/11/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import UIKit +import NetworkExtension +import PIATunnel + +class ViewController: UIViewController, URLSessionDataDelegate { + static let APP_GROUP = "group.com.privateinternetaccess.ios.demo.BasicTunnel" + + static let VPN_BUNDLE = "com.privateinternetaccess.ios.demo.BasicTunnel.BasicTunnelExtension" + + static let CIPHER: PIATunnelProvider.Cipher = .aes128cbc + + static let DIGEST: PIATunnelProvider.Digest = .sha1 + + static let HANDSHAKE: PIATunnelProvider.Handshake = .rsa2048 + + static let RENEG: Int? = nil + + static let DOWNLOAD_COUNT = 5 + + @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! + + // + + @IBOutlet var buttonDownload: UIButton! + + @IBOutlet var labelDownload: UILabel! + + var currentManager: NETunnelProviderManager? + + var status = NEVPNStatus.invalid + + var downloadTask: URLSessionDataTask! + + var downloadCount = 0 + + var downloadTimes = [TimeInterval]() + + override func viewDidLoad() { + super.viewDidLoad() + + textServer.text = "germany" + textDomain.text = "privateinternetaccess.com" +// textServer.text = "159.122.133.238" +// textDomain.text = "" + textPort.text = "1198" + switchTCP.isOn = false + textUsername.text = "myusername" + textPassword.text = "mypassword" + + NotificationCenter.default.addObserver(self, + selector: #selector(VPNStatusDidChange(notification:)), + name: .NEVPNStatusDidChange, + object: nil) + + reloadCurrentManager(nil) + + // + + testFetchRef() + } + + @IBAction func connectionClicked(_ sender: Any) { + let block = { + switch (self.status) { + case .invalid, .disconnected: + self.connect() + + case .connected, .connecting: + self.disconnect() + + default: + break + } + } + + if (status == .invalid) { + reloadCurrentManager({ (error) in + block() + }) + } + else { + block() + } + } + + @IBAction func tcpClicked(_ sender: Any) { + if switchTCP.isOn { + textPort.text = "443" + } else { + textPort.text = "8080" + } + } + + func connect() { + let server = textServer.text! + let domain = textDomain.text! + + let hostname = ((domain == "") ? server : [server, domain].joined(separator: ".")) + let port = UInt16(textPort.text!)! + let username = textUsername.text! + let password = textPassword.text! + + configureVPN({ (manager) in +// manager.isOnDemandEnabled = true +// manager.onDemandRules = [NEOnDemandRuleConnect()] + + let endpoint = PIATunnelProvider.AuthenticatedEndpoint( + hostname: hostname, + username: username, + password: password + ) + + var builder = PIATunnelProvider.ConfigurationBuilder(appGroup: ViewController.APP_GROUP) + let socketType: PIATunnelProvider.SocketType = (self.switchTCP.isOn ? .tcp : .udp) + builder.endpointProtocols = [PIATunnelProvider.EndpointProtocol(socketType, port, .vanilla)] + builder.cipher = ViewController.CIPHER + builder.digest = ViewController.DIGEST + builder.handshake = ViewController.HANDSHAKE + builder.mtu = 1350 + builder.renegotiatesAfterSeconds = ViewController.RENEG + builder.shouldDebug = true + builder.debugLogKey = "Log" + + let configuration = builder.build() + return try! configuration.generatedTunnelProtocol(withBundleIdentifier: ViewController.VPN_BUNDLE, endpoint: endpoint) + }, completionHandler: { (error) in + if let error = error { + print("configure error: \(error)") + return + } + let session = self.currentManager?.connection as! NETunnelProviderSession + do { + try session.startTunnel() + } catch let e { + print("error starting tunnel: \(e)") + } + }) + } + + func disconnect() { + configureVPN({ (manager) in +// manager.isOnDemandEnabled = false + return nil + }, completionHandler: { (error) in + self.currentManager?.connection.stopVPNTunnel() + }) + } + + @IBAction func displayLog() { + guard let vpn = currentManager?.connection as? NETunnelProviderSession else { + return + } + try? vpn.sendProviderMessage(PIATunnelProvider.Message.requestLog.data) { (data) in + guard let log = String(data: data!, encoding: .utf8) else { + return + } + self.textLog.text = log + } + } + + @IBAction func download() { + downloadCount = ViewController.DOWNLOAD_COUNT + downloadTimes.removeAll() + buttonDownload.isEnabled = false + labelDownload.text = "" + + doDownload() + } + + func doDownload() { + let url = URL(string: "https://example.bogus/test/100mb")! + var req = URLRequest(url: url) + req.httpMethod = "GET" + let cfg = URLSessionConfiguration.ephemeral + let sess = URLSession(configuration: cfg, delegate: self, delegateQueue: nil) + + let start = Date() + downloadTask = sess.dataTask(with: req) { (data, response, error) in + if let error = error { + print("error downloading: \(error)") + return + } + + let elapsed = -start.timeIntervalSinceNow + print("download finished: \(elapsed) seconds") + self.downloadTimes.append(elapsed) + + DispatchQueue.main.async { + self.downloadCount -= 1 + if (self.downloadCount > 0) { + self.labelDownload.text = "\(self.labelDownload.text!)\(elapsed) seconds\n" + self.doDownload() + } else { + var avg = 0.0 + for n in self.downloadTimes { + avg += n + } + avg /= Double(ViewController.DOWNLOAD_COUNT) + + self.labelDownload.text = "\(avg) seconds" + self.buttonDownload.isEnabled = true + } + } + } + downloadTask.resume() + } + + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + print("received \(data.count) bytes") + } + + func configureVPN(_ configure: @escaping (NETunnelProviderManager) -> NETunnelProviderProtocol?, completionHandler: @escaping (Error?) -> Void) { + reloadCurrentManager { (error) in + if let error = error { + print("error reloading preferences: \(error)") + completionHandler(error) + return + } + + let manager = self.currentManager! + if let protocolConfiguration = configure(manager) { + manager.protocolConfiguration = protocolConfiguration + } + manager.isEnabled = true + + manager.saveToPreferences { (error) in + if let error = error { + print("error saving preferences: \(error)") + completionHandler(error) + return + } + print("saved preferences") + self.reloadCurrentManager(completionHandler) + } + } + } + + func reloadCurrentManager(_ completionHandler: ((Error?) -> Void)?) { + NETunnelProviderManager.loadAllFromPreferences { (managers, error) in + if let error = error { + completionHandler?(error) + return + } + + var manager: NETunnelProviderManager? + + for m in managers! { + if let p = m.protocolConfiguration as? NETunnelProviderProtocol { + if (p.providerBundleIdentifier == ViewController.VPN_BUNDLE) { + manager = m + break + } + } + } + + if (manager == nil) { + manager = NETunnelProviderManager() + } + + self.currentManager = manager + self.status = manager!.connection.status + self.updateButton() + completionHandler?(nil) + } + } + + func updateButton() { + switch status { + case .connected, .connecting: + buttonConnection.setTitle("Disconnect", for: .normal) + + case .disconnected: + buttonConnection.setTitle("Connect", for: .normal) + + case .disconnecting: + buttonConnection.setTitle("Disconnecting", for: .normal) + + default: + break + } + } + + @objc private func VPNStatusDidChange(notification: NSNotification) { + guard let status = currentManager?.connection.status else { + print("VPNStatusDidChange") + return + } + print("VPNStatusDidChange: \(status.rawValue)") + self.status = status + updateButton() + } + + private func testFetchRef() { +// let keychain = Keychain(group: ViewController.APP_GROUP) +// let username = "foo" +// let password = "bar" +// +// guard let _ = try? keychain.set(password: password, for: username) else { +// print("Couldn't set password") +// return +// } +// guard let passwordReference = try? keychain.passwordReference(for: username) else { +// print("Couldn't get password reference") +// return +// } +// guard let fetchedPassword = try? Keychain.password(for: username, reference: passwordReference) else { +// print("Couldn't fetch password") +// return +// } +// +// print("\(username) -> \(password)") +// print("\(username) -> \(fetchedPassword)") + } +} diff --git a/Demo/BasicTunnel-macOS/AppDelegate.swift b/Demo/BasicTunnel-macOS/AppDelegate.swift new file mode 100644 index 0000000..ac6e234 --- /dev/null +++ b/Demo/BasicTunnel-macOS/AppDelegate.swift @@ -0,0 +1,34 @@ +// +// AppDelegate.swift +// BasicTunnel-macOS +// +// Created by Davide De Rosa on 10/15/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Cocoa +import SwiftyBeaver + +private let log = SwiftyBeaver.self + +@NSApplicationMain +class AppDelegate: NSObject, NSApplicationDelegate { + + + + func applicationDidFinishLaunching(_ aNotification: Notification) { + let logDestination = ConsoleDestination() + logDestination.minLevel = .debug + logDestination.format = "$DHH:mm:ss$d $L $N.$F:$l - $M" + log.addDestination(logDestination) + + // Insert code here to initialize your application + } + + func applicationWillTerminate(_ aNotification: Notification) { + // Insert code here to tear down your application + } + + +} + diff --git a/Demo/BasicTunnel-macOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/Demo/BasicTunnel-macOS/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..2db2b1c --- /dev/null +++ b/Demo/BasicTunnel-macOS/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,58 @@ +{ + "images" : [ + { + "idiom" : "mac", + "size" : "16x16", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "16x16", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "32x32", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "32x32", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "128x128", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "128x128", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "256x256", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "256x256", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "512x512", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "512x512", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Demo/BasicTunnel-macOS/Base.lproj/Main.storyboard b/Demo/BasicTunnel-macOS/Base.lproj/Main.storyboard new file mode 100644 index 0000000..009442e --- /dev/null +++ b/Demo/BasicTunnel-macOS/Base.lproj/Main.storyboard @@ -0,0 +1,804 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Demo/BasicTunnel-macOS/BasicTunnel-macOS.entitlements b/Demo/BasicTunnel-macOS/BasicTunnel-macOS.entitlements new file mode 100644 index 0000000..4e920af --- /dev/null +++ b/Demo/BasicTunnel-macOS/BasicTunnel-macOS.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.developer.networking.networkextension + + packet-tunnel-provider + + com.apple.security.app-sandbox + + com.apple.security.network.client + + + diff --git a/Demo/BasicTunnel-macOS/Info.plist b/Demo/BasicTunnel-macOS/Info.plist new file mode 100644 index 0000000..5afe53a --- /dev/null +++ b/Demo/BasicTunnel-macOS/Info.plist @@ -0,0 +1,34 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSApplicationCategoryType + public.app-category.productivity + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + Copyright © 2018 London Trust Media. All rights reserved. + NSMainStoryboardFile + Main + NSPrincipalClass + NSApplication + + diff --git a/Demo/BasicTunnel-macOS/ViewController.swift b/Demo/BasicTunnel-macOS/ViewController.swift new file mode 100644 index 0000000..50ccd2c --- /dev/null +++ b/Demo/BasicTunnel-macOS/ViewController.swift @@ -0,0 +1,257 @@ +// +// ViewController.swift +// BasicTunnel-macOS +// +// Created by Davide De Rosa on 10/15/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Cocoa +import NetworkExtension +import PIATunnel + +class ViewController: NSViewController { + static let APP_GROUP = "group.com.privateinternetaccess.macos.demo.BasicTunnel" + + static let VPN_BUNDLE = "com.privateinternetaccess.macos.demo.BasicTunnel.BasicTunnelExtension" + + static let CIPHER: PIATunnelProvider.Cipher = .aes128cbc + + static let DIGEST: PIATunnelProvider.Digest = .sha1 + + static let HANDSHAKE: PIATunnelProvider.Handshake = .rsa2048 + + static let RENEG: Int? = nil + + static let DOWNLOAD_COUNT = 5 + + @IBOutlet var textUsername: NSTextField! + + @IBOutlet var textPassword: NSTextField! + + @IBOutlet var textServer: NSTextField! + + @IBOutlet var textDomain: NSTextField! + + @IBOutlet var textPort: NSTextField! + + @IBOutlet var buttonConnection: NSButton! + + var currentManager: NETunnelProviderManager? + + var status = NEVPNStatus.invalid + + var downloadTask: URLSessionDataTask! + + var downloadCount = 0 + + var downloadTimes = [TimeInterval]() + + override func viewDidLoad() { + super.viewDidLoad() + + textServer.stringValue = "germany" + textDomain.stringValue = "privateinternetaccess.com" +// textServer.text = "159.122.133.238" +// textDomain.text = "" + textPort.stringValue = "1198" +// textPort.text = "8080" + textUsername.stringValue = "myusername" + textPassword.stringValue = "mypassword" + + NotificationCenter.default.addObserver(self, + selector: #selector(VPNStatusDidChange(notification:)), + name: .NEVPNStatusDidChange, + object: nil) + + reloadCurrentManager(nil) + + // + + testFetchRef() + } + + @IBAction func connectionClicked(_ sender: Any) { + let block = { + switch (self.status) { + case .invalid, .disconnected: + self.connect() + + case .connected, .connecting: + self.disconnect() + + default: + break + } + } + + if (status == .invalid) { + reloadCurrentManager({ (error) in + block() + }) + } + else { + block() + } + } + + func connect() { + let server = textServer.stringValue + let domain = textDomain.stringValue + + let hostname = ((domain == "") ? server : [server, domain].joined(separator: ".")) + let port = UInt16(textPort.stringValue)! + let username = textUsername.stringValue + let password = textPassword.stringValue + + configureVPN({ (manager) in +// manager.isOnDemandEnabled = true +// manager.onDemandRules = [NEOnDemandRuleConnect()] + + let endpoint = PIATunnelProvider.AuthenticatedEndpoint( + hostname: hostname, + username: username, + password: password + ) + + var builder = PIATunnelProvider.ConfigurationBuilder(appGroup: ViewController.APP_GROUP) +// let socketType: PIATunnelProvider.SocketType = (self.switchTCP.isOn ? .tcp : .udp) + let socketType: PIATunnelProvider.SocketType = .udp + builder.endpointProtocols = [PIATunnelProvider.EndpointProtocol(socketType, port, .vanilla)] + builder.cipher = ViewController.CIPHER + builder.digest = ViewController.DIGEST + builder.handshake = ViewController.HANDSHAKE + builder.mtu = 1350 + builder.renegotiatesAfterSeconds = ViewController.RENEG + builder.shouldDebug = true + builder.debugLogKey = "Log" + + let configuration = builder.build() + return try! configuration.generatedTunnelProtocol(withBundleIdentifier: ViewController.VPN_BUNDLE, endpoint: endpoint) + }, completionHandler: { (error) in + if let error = error { + print("configure error: \(error)") + return + } + let session = self.currentManager?.connection as! NETunnelProviderSession + do { + try session.startTunnel() + } catch let e { + print("error starting tunnel: \(e)") + } + }) + } + + func disconnect() { + configureVPN({ (manager) in + // manager.isOnDemandEnabled = false + return nil + }, completionHandler: { (error) in + self.currentManager?.connection.stopVPNTunnel() + }) + } + + func configureVPN(_ configure: @escaping (NETunnelProviderManager) -> NETunnelProviderProtocol?, completionHandler: @escaping (Error?) -> Void) { + reloadCurrentManager { (error) in + if let error = error { + print("error reloading preferences: \(error)") + completionHandler(error) + return + } + + let manager = self.currentManager! + if let protocolConfiguration = configure(manager) { + manager.protocolConfiguration = protocolConfiguration + } + manager.isEnabled = true + + manager.saveToPreferences { (error) in + if let error = error { + print("error saving preferences: \(error)") + completionHandler(error) + return + } + print("saved preferences") + self.reloadCurrentManager(completionHandler) + } + } + } + + func reloadCurrentManager(_ completionHandler: ((Error?) -> Void)?) { + NETunnelProviderManager.loadAllFromPreferences { (managers, error) in + if let error = error { + completionHandler?(error) + return + } + + var manager: NETunnelProviderManager? + + for m in managers! { + if let p = m.protocolConfiguration as? NETunnelProviderProtocol { + if (p.providerBundleIdentifier == ViewController.VPN_BUNDLE) { + manager = m + break + } + } + } + + if (manager == nil) { + manager = NETunnelProviderManager() + } + + self.currentManager = manager + self.status = manager!.connection.status + self.updateButton() + completionHandler?(nil) + } + } + + func updateButton() { + switch status { + case .connected, .connecting: + buttonConnection.title = "Disconnect" + + case .disconnected: + buttonConnection.title = "Connect" + + case .disconnecting: + buttonConnection.title = "Disconnecting" + + default: + break + } + } + + @objc private func VPNStatusDidChange(notification: NSNotification) { + guard let status = currentManager?.connection.status else { + print("VPNStatusDidChange") + return + } + print("VPNStatusDidChange: \(status.rawValue)") + self.status = status + updateButton() + } + + private func testFetchRef() { +// let keychain = Keychain(group: ViewController.APP_GROUP) +// let username = "foo" +// let password = "bar" +// +// guard let _ = try? keychain.set(password: password, for: username) else { +// print("Couldn't set password") +// return +// } +// guard let passwordReference = try? keychain.passwordReference(for: username) else { +// print("Couldn't get password reference") +// return +// } +// guard let fetchedPassword = try? Keychain.password(for: username, reference: passwordReference) else { +// print("Couldn't fetch password") +// return +// } +// +// print("\(username) -> \(password)") +// print("\(username) -> \(fetchedPassword)") + } +} + diff --git a/Demo/BasicTunnelExtension-iOS/BasicTunnelExtension-iOS.entitlements b/Demo/BasicTunnelExtension-iOS/BasicTunnelExtension-iOS.entitlements new file mode 100644 index 0000000..dd2ee03 --- /dev/null +++ b/Demo/BasicTunnelExtension-iOS/BasicTunnelExtension-iOS.entitlements @@ -0,0 +1,18 @@ + + + + + com.apple.developer.networking.networkextension + + packet-tunnel-provider + + com.apple.security.application-groups + + group.com.privateinternetaccess.ios.demo.BasicTunnel + + keychain-access-groups + + $(AppIdentifierPrefix)group.com.privateinternetaccess.ios.demo.BasicTunnel + + + diff --git a/Demo/BasicTunnelExtension-iOS/Info.plist b/Demo/BasicTunnelExtension-iOS/Info.plist new file mode 100644 index 0000000..e143b34 --- /dev/null +++ b/Demo/BasicTunnelExtension-iOS/Info.plist @@ -0,0 +1,31 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + BasicTunnelExtension + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + XPC! + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + NSExtension + + NSExtensionPointIdentifier + com.apple.networkextension.packet-tunnel + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).PacketTunnelProvider + + + diff --git a/Demo/BasicTunnelExtension-iOS/PacketTunnelProvider.swift b/Demo/BasicTunnelExtension-iOS/PacketTunnelProvider.swift new file mode 100644 index 0000000..d5265ef --- /dev/null +++ b/Demo/BasicTunnelExtension-iOS/PacketTunnelProvider.swift @@ -0,0 +1,12 @@ +// +// PacketTunnelProvider.swift +// BasicTunnelExtension-iOS +// +// Created by Davide De Rosa on 9/15/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import PIATunnel + +class PacketTunnelProvider: PIATunnelProvider { +} diff --git a/Demo/BasicTunnelExtension-macOS/BasicTunnelExtension-macOS.entitlements b/Demo/BasicTunnelExtension-macOS/BasicTunnelExtension-macOS.entitlements new file mode 100644 index 0000000..4e920af --- /dev/null +++ b/Demo/BasicTunnelExtension-macOS/BasicTunnelExtension-macOS.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.developer.networking.networkextension + + packet-tunnel-provider + + com.apple.security.app-sandbox + + com.apple.security.network.client + + + diff --git a/Demo/BasicTunnelExtension-macOS/Info.plist b/Demo/BasicTunnelExtension-macOS/Info.plist new file mode 100644 index 0000000..f441e25 --- /dev/null +++ b/Demo/BasicTunnelExtension-macOS/Info.plist @@ -0,0 +1,35 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + BasicTunnelExtension + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + XPC! + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSExtension + + NSExtensionPointIdentifier + com.apple.networkextension.packet-tunnel + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).PacketTunnelProvider + + NSHumanReadableCopyright + Copyright © 2018 London Trust Media. All rights reserved. + + diff --git a/Demo/BasicTunnelExtension-macOS/PacketTunnelProvider.swift b/Demo/BasicTunnelExtension-macOS/PacketTunnelProvider.swift new file mode 100644 index 0000000..37c06b8 --- /dev/null +++ b/Demo/BasicTunnelExtension-macOS/PacketTunnelProvider.swift @@ -0,0 +1,12 @@ +// +// PacketTunnelProvider.swift +// BasicTunnelExtension-macOS +// +// Created by Davide De Rosa on 10/15/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import PIATunnel + +class PacketTunnelProvider: PIATunnelProvider { +} diff --git a/Demo/Demo.xcodeproj/project.pbxproj b/Demo/Demo.xcodeproj/project.pbxproj new file mode 100644 index 0000000..691e393 --- /dev/null +++ b/Demo/Demo.xcodeproj/project.pbxproj @@ -0,0 +1,987 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 48; + objects = { + +/* Begin PBXBuildFile section */ + 0EB39FE91F7424F80023AFFC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB39FE81F7424F80023AFFC /* AppDelegate.swift */; }; + 0EB39FEB1F7424F80023AFFC /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB39FEA1F7424F80023AFFC /* ViewController.swift */; }; + 0EB39FEE1F7424F80023AFFC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0EB39FEC1F7424F80023AFFC /* Main.storyboard */; }; + 0EB39FF01F7424F80023AFFC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0EB39FEF1F7424F80023AFFC /* Assets.xcassets */; }; + 0EB39FF31F7424F80023AFFC /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0EB39FF11F7424F80023AFFC /* LaunchScreen.storyboard */; }; + 0EB3A0011F7425140023AFFC /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB3A0001F7425140023AFFC /* PacketTunnelProvider.swift */; }; + 0EB3A0051F7425140023AFFC /* BasicTunnelExtension-iOS.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 0EB39FFC1F7425140023AFFC /* BasicTunnelExtension-iOS.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 0EB6EEBA1F92D417005F6221 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB6EEB91F92D417005F6221 /* AppDelegate.swift */; }; + 0EB6EEBC1F92D417005F6221 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB6EEBB1F92D417005F6221 /* ViewController.swift */; }; + 0EB6EEBE1F92D417005F6221 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0EB6EEBD1F92D417005F6221 /* Assets.xcassets */; }; + 0EB6EEC11F92D417005F6221 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0EB6EEBF1F92D417005F6221 /* Main.storyboard */; }; + 0EB6EED01F92D43D005F6221 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB6EECF1F92D43D005F6221 /* PacketTunnelProvider.swift */; }; + 0EB6EED41F92D43D005F6221 /* BasicTunnelExtension-macOS.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 0EB6EECB1F92D43D005F6221 /* BasicTunnelExtension-macOS.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 0EB6EEDA1F92D4BA005F6221 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0EB6EED91F92D4BA005F6221 /* NetworkExtension.framework */; }; + 0EE878F81F936469002A0D58 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0EB6EED91F92D4BA005F6221 /* NetworkExtension.framework */; }; + 3908146517D6E4FB2A9D937D /* Pods_iOS_BasicTunnelExtension_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 793C61EE3021675F5E1E7851 /* Pods_iOS_BasicTunnelExtension_iOS.framework */; }; + 7A8E24264F7886297FF7360E /* Pods_iOS_BasicTunnel_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58CD8D66DB993CAA3D95BF58 /* Pods_iOS_BasicTunnel_iOS.framework */; }; + DDB7740C50825D06A90A8F85 /* Pods_macOS_BasicTunnel_macOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FE4702495E15F3783163A9DD /* Pods_macOS_BasicTunnel_macOS.framework */; }; + ED81344C597F479F327D005F /* Pods_macOS_BasicTunnelExtension_macOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C5E38D0BFB111803E5B140 /* Pods_macOS_BasicTunnelExtension_macOS.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 0EB3A0031F7425140023AFFC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0EB39FC51F7424580023AFFC /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0EB39FFB1F7425140023AFFC; + remoteInfo = BasicTunnelExtension; + }; + 0EB6EED21F92D43D005F6221 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0EB39FC51F7424580023AFFC /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0EB6EECA1F92D43D005F6221; + remoteInfo = BasicTunnelExtensionMac; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 0EB3A0091F7425140023AFFC /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 0EB3A0051F7425140023AFFC /* BasicTunnelExtension-iOS.appex in Embed App Extensions */, + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; + 0EB6EED81F92D43D005F6221 /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 0EB6EED41F92D43D005F6221 /* BasicTunnelExtension-macOS.appex in Embed App Extensions */, + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0EB39FE61F7424F80023AFFC /* BasicTunnel-iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "BasicTunnel-iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 0EB39FE81F7424F80023AFFC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 0EB39FEA1F7424F80023AFFC /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 0EB39FED1F7424F80023AFFC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 0EB39FEF1F7424F80023AFFC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 0EB39FF21F7424F80023AFFC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 0EB39FF41F7424F80023AFFC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 0EB39FFC1F7425140023AFFC /* BasicTunnelExtension-iOS.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "BasicTunnelExtension-iOS.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; + 0EB39FFF1F7425140023AFFC /* BasicTunnelExtension-iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "BasicTunnelExtension-iOS.entitlements"; sourceTree = ""; }; + 0EB3A0001F7425140023AFFC /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = ""; }; + 0EB3A0021F7425140023AFFC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 0EB3A00A1F7425C20023AFFC /* BasicTunnel-iOS.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = "BasicTunnel-iOS.entitlements"; sourceTree = ""; }; + 0EB6EEB71F92D417005F6221 /* BasicTunnel-macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "BasicTunnel-macOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 0EB6EEB91F92D417005F6221 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 0EB6EEBB1F92D417005F6221 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 0EB6EEBD1F92D417005F6221 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 0EB6EEC01F92D417005F6221 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 0EB6EEC21F92D417005F6221 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 0EB6EEC31F92D417005F6221 /* BasicTunnel-macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "BasicTunnel-macOS.entitlements"; sourceTree = ""; }; + 0EB6EECB1F92D43D005F6221 /* BasicTunnelExtension-macOS.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "BasicTunnelExtension-macOS.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; + 0EB6EECE1F92D43D005F6221 /* BasicTunnelExtension-macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "BasicTunnelExtension-macOS.entitlements"; sourceTree = ""; }; + 0EB6EECF1F92D43D005F6221 /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = ""; }; + 0EB6EED11F92D43D005F6221 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 0EB6EED91F92D4BA005F6221 /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk/System/Library/Frameworks/NetworkExtension.framework; sourceTree = DEVELOPER_DIR; }; + 5052DB12F617640E1D481436 /* Pods-iOS-BasicTunnel-iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-BasicTunnel-iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-iOS-BasicTunnel-iOS/Pods-iOS-BasicTunnel-iOS.debug.xcconfig"; sourceTree = ""; }; + 58CD8D66DB993CAA3D95BF58 /* Pods_iOS_BasicTunnel_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_BasicTunnel_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6435864DB434E6A2405C0A03 /* Pods-iOS-BasicTunnelExtension-iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-BasicTunnelExtension-iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-iOS-BasicTunnelExtension-iOS/Pods-iOS-BasicTunnelExtension-iOS.debug.xcconfig"; sourceTree = ""; }; + 644A04AFE7563DC48EFAF754 /* Pods-macOS-BasicTunnelExtension-macOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-macOS-BasicTunnelExtension-macOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-macOS-BasicTunnelExtension-macOS/Pods-macOS-BasicTunnelExtension-macOS.release.xcconfig"; sourceTree = ""; }; + 64C5E38D0BFB111803E5B140 /* Pods_macOS_BasicTunnelExtension_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_macOS_BasicTunnelExtension_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 793C61EE3021675F5E1E7851 /* Pods_iOS_BasicTunnelExtension_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_BasicTunnelExtension_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 86573C14FDBC5268162C4955 /* Pods-macOS-BasicTunnel-macOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-macOS-BasicTunnel-macOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-macOS-BasicTunnel-macOS/Pods-macOS-BasicTunnel-macOS.release.xcconfig"; sourceTree = ""; }; + 9E2D2F7DF58A0C0F6427E281 /* Pods-iOS-BasicTunnel-iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-BasicTunnel-iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-iOS-BasicTunnel-iOS/Pods-iOS-BasicTunnel-iOS.release.xcconfig"; sourceTree = ""; }; + A288006EEDE2BE66F8448A44 /* Pods-macOS-BasicTunnelExtension-macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-macOS-BasicTunnelExtension-macOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-macOS-BasicTunnelExtension-macOS/Pods-macOS-BasicTunnelExtension-macOS.debug.xcconfig"; sourceTree = ""; }; + D43202F48A06F897845BB823 /* Pods-macOS-BasicTunnel-macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-macOS-BasicTunnel-macOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-macOS-BasicTunnel-macOS/Pods-macOS-BasicTunnel-macOS.debug.xcconfig"; sourceTree = ""; }; + EACBBF6B797755CE91FFFE75 /* Pods-iOS-BasicTunnelExtension-iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-BasicTunnelExtension-iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-iOS-BasicTunnelExtension-iOS/Pods-iOS-BasicTunnelExtension-iOS.release.xcconfig"; sourceTree = ""; }; + FE4702495E15F3783163A9DD /* Pods_macOS_BasicTunnel_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_macOS_BasicTunnel_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 0EB39FE31F7424F80023AFFC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 7A8E24264F7886297FF7360E /* Pods_iOS_BasicTunnel_iOS.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0EB39FF91F7425140023AFFC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 3908146517D6E4FB2A9D937D /* Pods_iOS_BasicTunnelExtension_iOS.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0EB6EEB41F92D417005F6221 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0EB6EEDA1F92D4BA005F6221 /* NetworkExtension.framework in Frameworks */, + DDB7740C50825D06A90A8F85 /* Pods_macOS_BasicTunnel_macOS.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0EB6EEC81F92D43D005F6221 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0EE878F81F936469002A0D58 /* NetworkExtension.framework in Frameworks */, + ED81344C597F479F327D005F /* Pods_macOS_BasicTunnelExtension_macOS.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0EB39FC41F7424580023AFFC = { + isa = PBXGroup; + children = ( + 0EB39FE71F7424F80023AFFC /* BasicTunnel-iOS */, + 0EB39FFD1F7425140023AFFC /* BasicTunnelExtension-iOS */, + 0EB6EEB81F92D417005F6221 /* BasicTunnel-macOS */, + 0EB6EECC1F92D43D005F6221 /* BasicTunnelExtension-macOS */, + 0EB39FCE1F7424580023AFFC /* Products */, + B850E57E641AD1B37E79BAB5 /* Frameworks */, + 34D3F90470498D814E74A5C6 /* Pods */, + ); + sourceTree = ""; + }; + 0EB39FCE1F7424580023AFFC /* Products */ = { + isa = PBXGroup; + children = ( + 0EB39FE61F7424F80023AFFC /* BasicTunnel-iOS.app */, + 0EB39FFC1F7425140023AFFC /* BasicTunnelExtension-iOS.appex */, + 0EB6EEB71F92D417005F6221 /* BasicTunnel-macOS.app */, + 0EB6EECB1F92D43D005F6221 /* BasicTunnelExtension-macOS.appex */, + ); + name = Products; + sourceTree = ""; + }; + 0EB39FE71F7424F80023AFFC /* BasicTunnel-iOS */ = { + isa = PBXGroup; + children = ( + 0EB39FE81F7424F80023AFFC /* AppDelegate.swift */, + 0EB39FEA1F7424F80023AFFC /* ViewController.swift */, + 0EB39FEC1F7424F80023AFFC /* Main.storyboard */, + 0EB39FEF1F7424F80023AFFC /* Assets.xcassets */, + 0EB39FF11F7424F80023AFFC /* LaunchScreen.storyboard */, + 0EB39FF41F7424F80023AFFC /* Info.plist */, + 0EB3A00B1F7425C60023AFFC /* Supporting files */, + ); + path = "BasicTunnel-iOS"; + sourceTree = ""; + }; + 0EB39FFD1F7425140023AFFC /* BasicTunnelExtension-iOS */ = { + isa = PBXGroup; + children = ( + 0EB3A0001F7425140023AFFC /* PacketTunnelProvider.swift */, + 0EB3A0021F7425140023AFFC /* Info.plist */, + 0EB39FFE1F7425140023AFFC /* Supporting Files */, + ); + path = "BasicTunnelExtension-iOS"; + sourceTree = ""; + }; + 0EB39FFE1F7425140023AFFC /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 0EB39FFF1F7425140023AFFC /* BasicTunnelExtension-iOS.entitlements */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 0EB3A00B1F7425C60023AFFC /* Supporting files */ = { + isa = PBXGroup; + children = ( + 0EB3A00A1F7425C20023AFFC /* BasicTunnel-iOS.entitlements */, + ); + name = "Supporting files"; + sourceTree = ""; + }; + 0EB6EEB81F92D417005F6221 /* BasicTunnel-macOS */ = { + isa = PBXGroup; + children = ( + 0EB6EEB91F92D417005F6221 /* AppDelegate.swift */, + 0EB6EEBB1F92D417005F6221 /* ViewController.swift */, + 0EB6EEBD1F92D417005F6221 /* Assets.xcassets */, + 0EB6EEBF1F92D417005F6221 /* Main.storyboard */, + 0EB6EEC21F92D417005F6221 /* Info.plist */, + 0EB6EEDB1F92D597005F6221 /* Supporting files */, + ); + path = "BasicTunnel-macOS"; + sourceTree = ""; + }; + 0EB6EECC1F92D43D005F6221 /* BasicTunnelExtension-macOS */ = { + isa = PBXGroup; + children = ( + 0EB6EECF1F92D43D005F6221 /* PacketTunnelProvider.swift */, + 0EB6EED11F92D43D005F6221 /* Info.plist */, + 0EB6EECD1F92D43D005F6221 /* Supporting Files */, + ); + path = "BasicTunnelExtension-macOS"; + sourceTree = ""; + }; + 0EB6EECD1F92D43D005F6221 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 0EB6EECE1F92D43D005F6221 /* BasicTunnelExtension-macOS.entitlements */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 0EB6EEDB1F92D597005F6221 /* Supporting files */ = { + isa = PBXGroup; + children = ( + 0EB6EEC31F92D417005F6221 /* BasicTunnel-macOS.entitlements */, + ); + name = "Supporting files"; + sourceTree = ""; + }; + 34D3F90470498D814E74A5C6 /* Pods */ = { + isa = PBXGroup; + children = ( + 5052DB12F617640E1D481436 /* Pods-iOS-BasicTunnel-iOS.debug.xcconfig */, + 9E2D2F7DF58A0C0F6427E281 /* Pods-iOS-BasicTunnel-iOS.release.xcconfig */, + 6435864DB434E6A2405C0A03 /* Pods-iOS-BasicTunnelExtension-iOS.debug.xcconfig */, + EACBBF6B797755CE91FFFE75 /* Pods-iOS-BasicTunnelExtension-iOS.release.xcconfig */, + D43202F48A06F897845BB823 /* Pods-macOS-BasicTunnel-macOS.debug.xcconfig */, + 86573C14FDBC5268162C4955 /* Pods-macOS-BasicTunnel-macOS.release.xcconfig */, + A288006EEDE2BE66F8448A44 /* Pods-macOS-BasicTunnelExtension-macOS.debug.xcconfig */, + 644A04AFE7563DC48EFAF754 /* Pods-macOS-BasicTunnelExtension-macOS.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + B850E57E641AD1B37E79BAB5 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 0EB6EED91F92D4BA005F6221 /* NetworkExtension.framework */, + 58CD8D66DB993CAA3D95BF58 /* Pods_iOS_BasicTunnel_iOS.framework */, + 793C61EE3021675F5E1E7851 /* Pods_iOS_BasicTunnelExtension_iOS.framework */, + FE4702495E15F3783163A9DD /* Pods_macOS_BasicTunnel_macOS.framework */, + 64C5E38D0BFB111803E5B140 /* Pods_macOS_BasicTunnelExtension_macOS.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 0EB39FE51F7424F80023AFFC /* BasicTunnel-iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0EB39FF51F7424F80023AFFC /* Build configuration list for PBXNativeTarget "BasicTunnel-iOS" */; + buildPhases = ( + 8654E3AEB3A2369E83089DE1 /* [CP] Check Pods Manifest.lock */, + 0EB39FE21F7424F80023AFFC /* Sources */, + 0EB39FE31F7424F80023AFFC /* Frameworks */, + 0EB39FE41F7424F80023AFFC /* Resources */, + 0EB3A0091F7425140023AFFC /* Embed App Extensions */, + C01795F3F1E523D48472410F /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 0EB3A0041F7425140023AFFC /* PBXTargetDependency */, + ); + name = "BasicTunnel-iOS"; + productName = BasicTunnel; + productReference = 0EB39FE61F7424F80023AFFC /* BasicTunnel-iOS.app */; + productType = "com.apple.product-type.application"; + }; + 0EB39FFB1F7425140023AFFC /* BasicTunnelExtension-iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0EB3A0061F7425140023AFFC /* Build configuration list for PBXNativeTarget "BasicTunnelExtension-iOS" */; + buildPhases = ( + D83479D2193DDBE1F61F7994 /* [CP] Check Pods Manifest.lock */, + 0EB39FF81F7425140023AFFC /* Sources */, + 0EB39FF91F7425140023AFFC /* Frameworks */, + 0EB39FFA1F7425140023AFFC /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "BasicTunnelExtension-iOS"; + productName = BasicTunnelExtension; + productReference = 0EB39FFC1F7425140023AFFC /* BasicTunnelExtension-iOS.appex */; + productType = "com.apple.product-type.app-extension"; + }; + 0EB6EEB61F92D417005F6221 /* BasicTunnel-macOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0EB6EEC61F92D417005F6221 /* Build configuration list for PBXNativeTarget "BasicTunnel-macOS" */; + buildPhases = ( + 52FA651791DFCB2780F05CC0 /* [CP] Check Pods Manifest.lock */, + 0EB6EEB31F92D417005F6221 /* Sources */, + 0EB6EEB41F92D417005F6221 /* Frameworks */, + 0EB6EEB51F92D417005F6221 /* Resources */, + 0EB6EED81F92D43D005F6221 /* Embed App Extensions */, + C808C600FAEEFF648BCD5A99 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 0EB6EED31F92D43D005F6221 /* PBXTargetDependency */, + ); + name = "BasicTunnel-macOS"; + productName = BasicTunnelMac; + productReference = 0EB6EEB71F92D417005F6221 /* BasicTunnel-macOS.app */; + productType = "com.apple.product-type.application"; + }; + 0EB6EECA1F92D43D005F6221 /* BasicTunnelExtension-macOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0EB6EED51F92D43D005F6221 /* Build configuration list for PBXNativeTarget "BasicTunnelExtension-macOS" */; + buildPhases = ( + EEDE45EF0F6328D22A7732F5 /* [CP] Check Pods Manifest.lock */, + 0EB6EEC71F92D43D005F6221 /* Sources */, + 0EB6EEC81F92D43D005F6221 /* Frameworks */, + 0EB6EEC91F92D43D005F6221 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "BasicTunnelExtension-macOS"; + productName = BasicTunnelExtensionMac; + productReference = 0EB6EECB1F92D43D005F6221 /* BasicTunnelExtension-macOS.appex */; + productType = "com.apple.product-type.app-extension"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 0EB39FC51F7424580023AFFC /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0900; + LastUpgradeCheck = 0900; + ORGANIZATIONNAME = "London Trust Media"; + TargetAttributes = { + 0EB39FE51F7424F80023AFFC = { + CreatedOnToolsVersion = 9.0; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.ApplicationGroups.iOS = { + enabled = 1; + }; + com.apple.Keychain = { + enabled = 1; + }; + }; + }; + 0EB39FFB1F7425140023AFFC = { + CreatedOnToolsVersion = 9.0; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.ApplicationGroups.iOS = { + enabled = 1; + }; + com.apple.Keychain = { + enabled = 1; + }; + }; + }; + 0EB6EEB61F92D417005F6221 = { + CreatedOnToolsVersion = 9.0; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.ApplicationGroups.Mac = { + enabled = 0; + }; + com.apple.Keychain = { + enabled = 0; + }; + com.apple.NetworkExtensions = { + enabled = 1; + }; + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 0EB6EECA1F92D43D005F6221 = { + CreatedOnToolsVersion = 9.0; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.ApplicationGroups.Mac = { + enabled = 0; + }; + com.apple.Keychain = { + enabled = 0; + }; + com.apple.NetworkExtensions = { + enabled = 1; + }; + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + }; + }; + buildConfigurationList = 0EB39FC81F7424580023AFFC /* Build configuration list for PBXProject "Demo" */; + compatibilityVersion = "Xcode 8.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 0EB39FC41F7424580023AFFC; + productRefGroup = 0EB39FCE1F7424580023AFFC /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 0EB39FE51F7424F80023AFFC /* BasicTunnel-iOS */, + 0EB39FFB1F7425140023AFFC /* BasicTunnelExtension-iOS */, + 0EB6EEB61F92D417005F6221 /* BasicTunnel-macOS */, + 0EB6EECA1F92D43D005F6221 /* BasicTunnelExtension-macOS */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 0EB39FE41F7424F80023AFFC /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0EB39FF31F7424F80023AFFC /* LaunchScreen.storyboard in Resources */, + 0EB39FF01F7424F80023AFFC /* Assets.xcassets in Resources */, + 0EB39FEE1F7424F80023AFFC /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0EB39FFA1F7425140023AFFC /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0EB6EEB51F92D417005F6221 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0EB6EEBE1F92D417005F6221 /* Assets.xcassets in Resources */, + 0EB6EEC11F92D417005F6221 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0EB6EEC91F92D43D005F6221 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 52FA651791DFCB2780F05CC0 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-macOS-BasicTunnel-macOS-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 8654E3AEB3A2369E83089DE1 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-BasicTunnel-iOS-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + C01795F3F1E523D48472410F /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-iOS-BasicTunnel-iOS/Pods-iOS-BasicTunnel-iOS-frameworks.sh", + "${PODS_ROOT}/OpenSSL-Apple/frameworks/iPhone/openssl.framework", + "${BUILT_PRODUCTS_DIR}/PIATunnel-iOS/PIATunnel.framework", + "${BUILT_PRODUCTS_DIR}/SwiftyBeaver-iOS/SwiftyBeaver.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/openssl.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PIATunnel.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyBeaver.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-iOS-BasicTunnel-iOS/Pods-iOS-BasicTunnel-iOS-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + C808C600FAEEFF648BCD5A99 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-macOS-BasicTunnel-macOS/Pods-macOS-BasicTunnel-macOS-frameworks.sh", + "${PODS_ROOT}/OpenSSL-Apple/frameworks/MacOSX/openssl.framework", + "${BUILT_PRODUCTS_DIR}/PIATunnel-macOS/PIATunnel.framework", + "${BUILT_PRODUCTS_DIR}/SwiftyBeaver-macOS/SwiftyBeaver.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/openssl.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PIATunnel.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyBeaver.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-macOS-BasicTunnel-macOS/Pods-macOS-BasicTunnel-macOS-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + D83479D2193DDBE1F61F7994 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-BasicTunnelExtension-iOS-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + EEDE45EF0F6328D22A7732F5 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-macOS-BasicTunnelExtension-macOS-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 0EB39FE21F7424F80023AFFC /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0EB39FEB1F7424F80023AFFC /* ViewController.swift in Sources */, + 0EB39FE91F7424F80023AFFC /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0EB39FF81F7425140023AFFC /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0EB3A0011F7425140023AFFC /* PacketTunnelProvider.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0EB6EEB31F92D417005F6221 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0EB6EEBC1F92D417005F6221 /* ViewController.swift in Sources */, + 0EB6EEBA1F92D417005F6221 /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0EB6EEC71F92D43D005F6221 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0EB6EED01F92D43D005F6221 /* PacketTunnelProvider.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 0EB3A0041F7425140023AFFC /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0EB39FFB1F7425140023AFFC /* BasicTunnelExtension-iOS */; + targetProxy = 0EB3A0031F7425140023AFFC /* PBXContainerItemProxy */; + }; + 0EB6EED31F92D43D005F6221 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0EB6EECA1F92D43D005F6221 /* BasicTunnelExtension-macOS */; + targetProxy = 0EB6EED21F92D43D005F6221 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 0EB39FEC1F7424F80023AFFC /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 0EB39FED1F7424F80023AFFC /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 0EB39FF11F7424F80023AFFC /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 0EB39FF21F7424F80023AFFC /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; + 0EB6EEBF1F92D417005F6221 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 0EB6EEC01F92D417005F6221 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 0EB39FDD1F7424580023AFFC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; + }; + name = Debug; + }; + 0EB39FDE1F7424580023AFFC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.0; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 0EB39FF61F7424F80023AFFC /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5052DB12F617640E1D481436 /* Pods-iOS-BasicTunnel-iOS.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = "BasicTunnel-iOS/BasicTunnel-iOS.entitlements"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 5357M5NW9W; + INFOPLIST_FILE = "BasicTunnel-iOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.privateinternetaccess.ios.demo.BasicTunnel; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 0EB39FF71F7424F80023AFFC /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9E2D2F7DF58A0C0F6427E281 /* Pods-iOS-BasicTunnel-iOS.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = "BasicTunnel-iOS/BasicTunnel-iOS.entitlements"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 5357M5NW9W; + INFOPLIST_FILE = "BasicTunnel-iOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.privateinternetaccess.ios.demo.BasicTunnel; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 0EB3A0071F7425140023AFFC /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6435864DB434E6A2405C0A03 /* Pods-iOS-BasicTunnelExtension-iOS.debug.xcconfig */; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "BasicTunnelExtension-iOS/BasicTunnelExtension-iOS.entitlements"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 5357M5NW9W; + INFOPLIST_FILE = "BasicTunnelExtension-iOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.privateinternetaccess.ios.demo.BasicTunnel.BasicTunnelExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 0EB3A0081F7425140023AFFC /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = EACBBF6B797755CE91FFFE75 /* Pods-iOS-BasicTunnelExtension-iOS.release.xcconfig */; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "BasicTunnelExtension-iOS/BasicTunnelExtension-iOS.entitlements"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 5357M5NW9W; + INFOPLIST_FILE = "BasicTunnelExtension-iOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.privateinternetaccess.ios.demo.BasicTunnel.BasicTunnelExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 0EB6EEC41F92D417005F6221 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D43202F48A06F897845BB823 /* Pods-macOS-BasicTunnel-macOS.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = "BasicTunnel-macOS/BasicTunnel-macOS.entitlements"; + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 5357M5NW9W; + INFOPLIST_FILE = "BasicTunnel-macOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.privateinternetaccess.macos.demo.BasicTunnel; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + }; + name = Debug; + }; + 0EB6EEC51F92D417005F6221 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 86573C14FDBC5268162C4955 /* Pods-macOS-BasicTunnel-macOS.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = "BasicTunnel-macOS/BasicTunnel-macOS.entitlements"; + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 5357M5NW9W; + INFOPLIST_FILE = "BasicTunnel-macOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.privateinternetaccess.macos.demo.BasicTunnel; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + }; + name = Release; + }; + 0EB6EED61F92D43D005F6221 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A288006EEDE2BE66F8448A44 /* Pods-macOS-BasicTunnelExtension-macOS.debug.xcconfig */; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "BasicTunnelExtension-macOS/BasicTunnelExtension-macOS.entitlements"; + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 5357M5NW9W; + INFOPLIST_FILE = "BasicTunnelExtension-macOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.privateinternetaccess.macos.demo.BasicTunnel.BasicTunnelExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 0EB6EED71F92D43D005F6221 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 644A04AFE7563DC48EFAF754 /* Pods-macOS-BasicTunnelExtension-macOS.release.xcconfig */; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "BasicTunnelExtension-macOS/BasicTunnelExtension-macOS.entitlements"; + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 5357M5NW9W; + INFOPLIST_FILE = "BasicTunnelExtension-macOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.privateinternetaccess.macos.demo.BasicTunnel.BasicTunnelExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SKIP_INSTALL = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 0EB39FC81F7424580023AFFC /* Build configuration list for PBXProject "Demo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0EB39FDD1F7424580023AFFC /* Debug */, + 0EB39FDE1F7424580023AFFC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 0EB39FF51F7424F80023AFFC /* Build configuration list for PBXNativeTarget "BasicTunnel-iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0EB39FF61F7424F80023AFFC /* Debug */, + 0EB39FF71F7424F80023AFFC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 0EB3A0061F7425140023AFFC /* Build configuration list for PBXNativeTarget "BasicTunnelExtension-iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0EB3A0071F7425140023AFFC /* Debug */, + 0EB3A0081F7425140023AFFC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 0EB6EEC61F92D417005F6221 /* Build configuration list for PBXNativeTarget "BasicTunnel-macOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0EB6EEC41F92D417005F6221 /* Debug */, + 0EB6EEC51F92D417005F6221 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 0EB6EED51F92D43D005F6221 /* Build configuration list for PBXNativeTarget "BasicTunnelExtension-macOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0EB6EED61F92D43D005F6221 /* Debug */, + 0EB6EED71F92D43D005F6221 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 0EB39FC51F7424580023AFFC /* Project object */; +} diff --git a/Demo/Demo.xcworkspace/contents.xcworkspacedata b/Demo/Demo.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1f14019 --- /dev/null +++ b/Demo/Demo.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/Demo/Demo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Demo/Demo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Demo/Demo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Demo/Podfile b/Demo/Podfile new file mode 100644 index 0000000..588aa80 --- /dev/null +++ b/Demo/Podfile @@ -0,0 +1,22 @@ +source 'https://github.com/CocoaPods/Specs.git' +use_frameworks! + +abstract_target 'iOS' do + platform :ios, '9.0' + + target 'BasicTunnelExtension-iOS' do + pod 'PIATunnel', :path => '..' + end + target 'BasicTunnel-iOS' do + end +end + +abstract_target 'macOS' do + platform :osx, '10.11' + + target 'BasicTunnelExtension-macOS' do + pod 'PIATunnel', :path => '..' + end + target 'BasicTunnel-macOS' do + end +end diff --git a/Demo/Podfile.lock b/Demo/Podfile.lock new file mode 100644 index 0000000..b9c80fd --- /dev/null +++ b/Demo/Podfile.lock @@ -0,0 +1,33 @@ +PODS: + - OpenSSL-Apple (1.1.0h) + - PIATunnel (1.1.6): + - PIATunnel/AppExtension (= 1.1.6) + - PIATunnel/Core (= 1.1.6) + - PIATunnel/AppExtension (1.1.6): + - PIATunnel/Core + - SwiftyBeaver + - PIATunnel/Core (1.1.6): + - OpenSSL-Apple (~> 1.1.0h) + - SwiftyBeaver + - SwiftyBeaver (1.6.0) + +DEPENDENCIES: + - PIATunnel (from `..`) + +SPEC REPOS: + https://github.com/cocoapods/specs.git: + - OpenSSL-Apple + - SwiftyBeaver + +EXTERNAL SOURCES: + PIATunnel: + :path: ".." + +SPEC CHECKSUMS: + OpenSSL-Apple: cd153d705ef350eb834ae7ff5f21f792b51ed208 + PIATunnel: b98780a58c8826bd82f1e15878078997ed172680 + SwiftyBeaver: e45759613e50b522b0e6f53b1f0f14389b45ca34 + +PODFILE CHECKSUM: 1199e89f72f37e986463243abe5442ffdc81274c + +COCOAPODS: 1.5.3 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..771cbdf --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright (c) 2018-Present Private Internet Access + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/PIATunnel.podspec b/PIATunnel.podspec new file mode 100644 index 0000000..24acf7e --- /dev/null +++ b/PIATunnel.podspec @@ -0,0 +1,33 @@ +Pod::Spec.new do |s| + s.name = "PIATunnel" + s.version = "1.1.6" + s.summary = "PIA tunnel implementation in Swift." + + s.homepage = "https://www.privateinternetaccess.com/" + s.license = { :type => "MIT", :file => "LICENSE" } + s.author = { "Davide De Rosa" => "davide@londontrustmedia.com" } + s.source = { :git => "https://github.com/pia-foss/tunnel-apple.git", :tag => "v#{s.version}" } + + s.ios.deployment_target = "9.0" + s.osx.deployment_target = "10.11" + + s.subspec "Core" do |p| + p.source_files = "PIATunnel/Sources/Core/**/*.{h,m,swift}" + p.private_header_files = "PIATunnel/Sources/Core/**/*.h" + p.preserve_paths = "PIATunnel/Sources/Core/*.modulemap" + p.pod_target_xcconfig = { "SWIFT_INCLUDE_PATHS" => "${PODS_TARGET_SRCROOT}/PIATunnel/Sources/Core", + "APPLICATION_EXTENSION_API_ONLY" => "YES" } + p.dependency "SwiftyBeaver" + p.dependency "OpenSSL-Apple", "~> 1.1.0h" + end + + s.subspec "AppExtension" do |p| + p.source_files = "PIATunnel/Sources/AppExtension/**/*.swift" + p.resources = "PIATunnel/Resources/AppExtension/**/*" + p.frameworks = "NetworkExtension" + p.pod_target_xcconfig = { "APPLICATION_EXTENSION_API_ONLY" => "YES" } + + p.dependency "PIATunnel/Core" + p.dependency "SwiftyBeaver" + end +end diff --git a/PIATunnel.xcodeproj/project.pbxproj b/PIATunnel.xcodeproj/project.pbxproj new file mode 100644 index 0000000..94bac45 --- /dev/null +++ b/PIATunnel.xcodeproj/project.pbxproj @@ -0,0 +1,1311 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 48; + objects = { + +/* Begin PBXBuildFile section */ + 0E07595F20EF6D1400F38FD8 /* CryptoCBC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E07595C20EF6D1400F38FD8 /* CryptoCBC.m */; }; + 0E07596020EF6D1400F38FD8 /* CryptoCBC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E07595C20EF6D1400F38FD8 /* CryptoCBC.m */; }; + 0E07596320EF733F00F38FD8 /* CryptoMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E07596120EF733F00F38FD8 /* CryptoMacros.h */; }; + 0E07596420EF733F00F38FD8 /* CryptoMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E07596120EF733F00F38FD8 /* CryptoMacros.h */; }; + 0E07596B20EF79AB00F38FD8 /* Encryption.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E07596A20EF79AB00F38FD8 /* Encryption.h */; }; + 0E07596C20EF79AB00F38FD8 /* Encryption.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E07596A20EF79AB00F38FD8 /* Encryption.h */; }; + 0E07596E20EF79B400F38FD8 /* CryptoCBC.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E07596D20EF79B400F38FD8 /* CryptoCBC.h */; }; + 0E07596F20EF79B400F38FD8 /* CryptoCBC.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E07596D20EF79B400F38FD8 /* CryptoCBC.h */; }; + 0E07597E20F0060E00F38FD8 /* CryptoAEAD.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E07597C20F0060E00F38FD8 /* CryptoAEAD.h */; }; + 0E07597F20F0060E00F38FD8 /* CryptoAEAD.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E07597C20F0060E00F38FD8 /* CryptoAEAD.h */; }; + 0E07598020F0060E00F38FD8 /* CryptoAEAD.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E07597D20F0060E00F38FD8 /* CryptoAEAD.m */; }; + 0E07598120F0060E00F38FD8 /* CryptoAEAD.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E07597D20F0060E00F38FD8 /* CryptoAEAD.m */; }; + 0E11089F1F77B9E800A92462 /* PIATunnel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E17D7F91F730D9F009EE129 /* PIATunnel.framework */; }; + 0E1108AC1F77B9F900A92462 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1108AB1F77B9F900A92462 /* AppDelegate.swift */; }; + 0E1108AE1F77B9F900A92462 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1108AD1F77B9F900A92462 /* ViewController.swift */; }; + 0E1108B11F77B9F900A92462 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E1108AF1F77B9F900A92462 /* Main.storyboard */; }; + 0E1108B31F77B9F900A92462 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E1108B21F77B9F900A92462 /* Assets.xcassets */; }; + 0E1108B61F77B9F900A92462 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E1108B41F77B9F900A92462 /* LaunchScreen.storyboard */; }; + 0E3E0F212108A8CC00B371C1 /* PushReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3E0F202108A8CC00B371C1 /* PushReply.swift */; }; + 0E3E0F222108A8CC00B371C1 /* PushReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3E0F202108A8CC00B371C1 /* PushReply.swift */; }; + 0E85A25A202CC5AF0059E9F9 /* AppExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E85A259202CC5AE0059E9F9 /* AppExtensionTests.swift */; }; + 0E9379C91F819A4300CE91B6 /* PIATunnel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E17D7F91F730D9F009EE129 /* PIATunnel.framework */; }; + 0EA8E2072024D4B200A92DB6 /* PIA-ECC-256k1.pem in Resources */ = {isa = PBXBuildFile; fileRef = 0EA8E2042024D4B100A92DB6 /* PIA-ECC-256k1.pem */; }; + 0EA8E2082024D4B200A92DB6 /* PIA-ECC-256r1.pem in Resources */ = {isa = PBXBuildFile; fileRef = 0EA8E2052024D4B100A92DB6 /* PIA-ECC-256r1.pem */; }; + 0EA8E2092024D4B200A92DB6 /* PIA-ECC-521r1.pem in Resources */ = {isa = PBXBuildFile; fileRef = 0EA8E2062024D4B200A92DB6 /* PIA-ECC-521r1.pem */; }; + 0EA8E20A2024D5D500A92DB6 /* PIA-ECC-256r1.pem in Resources */ = {isa = PBXBuildFile; fileRef = 0EA8E2052024D4B100A92DB6 /* PIA-ECC-256r1.pem */; }; + 0EA8E20B2024D5D500A92DB6 /* PIA-ECC-256k1.pem in Resources */ = {isa = PBXBuildFile; fileRef = 0EA8E2042024D4B100A92DB6 /* PIA-ECC-256k1.pem */; }; + 0EA8E20C2024D5D500A92DB6 /* PIA-ECC-521r1.pem in Resources */ = {isa = PBXBuildFile; fileRef = 0EA8E2062024D4B200A92DB6 /* PIA-ECC-521r1.pem */; }; + 0EA8E20D2024D5D500A92DB6 /* PIA-RSA-2048.pem in Resources */ = {isa = PBXBuildFile; fileRef = 0EFEB4822006D3D000F81029 /* PIA-RSA-2048.pem */; }; + 0EA8E20E2024D5D500A92DB6 /* PIA-RSA-3072.pem in Resources */ = {isa = PBXBuildFile; fileRef = 0EFEB4802006D3D000F81029 /* PIA-RSA-3072.pem */; }; + 0EA8E20F2024D5D500A92DB6 /* PIA-RSA-4096.pem in Resources */ = {isa = PBXBuildFile; fileRef = 0EFEB4812006D3D000F81029 /* PIA-RSA-4096.pem */; }; + 0EAAD70920E4F2BC0088754A /* CommunicationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAAD70820E4F2BC0088754A /* CommunicationType.swift */; }; + 0EAAD70A20E4F2BC0088754A /* CommunicationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAAD70820E4F2BC0088754A /* CommunicationType.swift */; }; + 0EAAD70C20E4F85A0088754A /* LinkInterface+Strategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAAD70B20E4F85A0088754A /* LinkInterface+Strategy.swift */; }; + 0EAAD70D20E4F85A0088754A /* LinkInterface+Strategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAAD70B20E4F85A0088754A /* LinkInterface+Strategy.swift */; }; + 0EB2B45320F0BB44004233D7 /* EncryptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB2B45220F0BB44004233D7 /* EncryptionTests.swift */; }; + 0EB2B45520F0BB53004233D7 /* DataManipulationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB2B45420F0BB53004233D7 /* DataManipulationTests.swift */; }; + 0EB2B45720F0BD16004233D7 /* RandomTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB2B45620F0BD16004233D7 /* RandomTests.swift */; }; + 0EB2B45920F0BD9A004233D7 /* LinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB2B45820F0BD9A004233D7 /* LinkTests.swift */; }; + 0EB2B45B20F0BE4C004233D7 /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB2B45A20F0BE4C004233D7 /* TestUtils.swift */; }; + 0EB2B45D20F0BF41004233D7 /* RawPerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB2B45C20F0BF41004233D7 /* RawPerformanceTests.swift */; }; + 0EB2B45F20F0C098004233D7 /* EncryptionPerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB2B45E20F0C098004233D7 /* EncryptionPerformanceTests.swift */; }; + 0EB2B46120F0C0A4004233D7 /* DataPathPerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB2B46020F0C0A4004233D7 /* DataPathPerformanceTests.swift */; }; + 0EBBF2E52084FE6F00E36B40 /* GenericSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBBF2E42084FE6F00E36B40 /* GenericSocket.swift */; }; + 0EBBF2E62084FE6F00E36B40 /* GenericSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBBF2E42084FE6F00E36B40 /* GenericSocket.swift */; }; + 0EBBF2F3208505D300E36B40 /* NEUDPInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBBF2EC2085055100E36B40 /* NEUDPInterface.swift */; }; + 0EBBF2F4208505D400E36B40 /* NEUDPInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBBF2EC2085055100E36B40 /* NEUDPInterface.swift */; }; + 0EBBF2F5208505D700E36B40 /* NETunnelInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBBF2EB2085055100E36B40 /* NETunnelInterface.swift */; }; + 0EBBF2F6208505D700E36B40 /* NETunnelInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBBF2EB2085055100E36B40 /* NETunnelInterface.swift */; }; + 0EBBF2F7208505DD00E36B40 /* NWUDPSessionState+Description.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBBF2EA2085055100E36B40 /* NWUDPSessionState+Description.swift */; }; + 0EBBF2F8208505DD00E36B40 /* NWUDPSessionState+Description.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBBF2EA2085055100E36B40 /* NWUDPSessionState+Description.swift */; }; + 0EBBF2FA2085061600E36B40 /* NETCPInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBBF2F92085061600E36B40 /* NETCPInterface.swift */; }; + 0EBBF2FB2085061600E36B40 /* NETCPInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBBF2F92085061600E36B40 /* NETCPInterface.swift */; }; + 0EBBF3002085196000E36B40 /* NWTCPConnectionState+Description.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBBF2FF2085196000E36B40 /* NWTCPConnectionState+Description.swift */; }; + 0EBBF3012085196000E36B40 /* NWTCPConnectionState+Description.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBBF2FF2085196000E36B40 /* NWTCPConnectionState+Description.swift */; }; + 0EC1BBA520D71190007C4C7B /* DNSResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC1BBA420D71190007C4C7B /* DNSResolver.swift */; }; + 0EC1BBA620D712DE007C4C7B /* DNSResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC1BBA420D71190007C4C7B /* DNSResolver.swift */; }; + 0EC1BBA820D7D803007C4C7B /* ConnectionStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC1BBA720D7D803007C4C7B /* ConnectionStrategy.swift */; }; + 0EC1BBA920D7D803007C4C7B /* ConnectionStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC1BBA720D7D803007C4C7B /* ConnectionStrategy.swift */; }; + 0EE7A79520F61EDC00B42E6A /* PacketMacros.h in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7A79420F61EDC00B42E6A /* PacketMacros.h */; }; + 0EE7A79620F61EDC00B42E6A /* PacketMacros.h in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7A79420F61EDC00B42E6A /* PacketMacros.h */; }; + 0EE7A79820F6296F00B42E6A /* PacketMacros.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7A79720F6296F00B42E6A /* PacketMacros.m */; }; + 0EE7A79920F6296F00B42E6A /* PacketMacros.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7A79720F6296F00B42E6A /* PacketMacros.m */; }; + 0EE7A7A120F664AC00B42E6A /* DataPathEncryptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7A7A020F664AB00B42E6A /* DataPathEncryptionTests.swift */; }; + 0EEC49DC20B5E732008FEB91 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EEC49DB20B5E732008FEB91 /* Utils.swift */; }; + 0EEC49DD20B5E732008FEB91 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EEC49DB20B5E732008FEB91 /* Utils.swift */; }; + 0EEC49E120B5F7EA008FEB91 /* Allocation.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFEB42E2006D3C800F81029 /* Allocation.h */; }; + 0EEC49E220B5F7F6008FEB91 /* CryptoBox.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFEB4402006D3C800F81029 /* CryptoBox.h */; }; + 0EEC49E320B5F7F6008FEB91 /* DataPath.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFEB4352006D3C800F81029 /* DataPath.h */; }; + 0EEC49E520B5F7F6008FEB91 /* Errors.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFEB4362006D3C800F81029 /* Errors.h */; }; + 0EEC49E620B5F7F6008FEB91 /* MSS.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFEB42D2006D3C800F81029 /* MSS.h */; }; + 0EEC49E820B5F7F6008FEB91 /* ReplayProtector.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFEB4392006D3C800F81029 /* ReplayProtector.h */; }; + 0EEC49E920B5F7F6008FEB91 /* TLSBox.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFEB4442006D3C800F81029 /* TLSBox.h */; }; + 0EEC49EA20B5F7F6008FEB91 /* ZeroingData.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFEB4412006D3C800F81029 /* ZeroingData.h */; }; + 0EFEB4552006D3C800F81029 /* EncryptionProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB42A2006D3C800F81029 /* EncryptionProxy.swift */; }; + 0EFEB4562006D3C800F81029 /* SessionKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB42B2006D3C800F81029 /* SessionKey.swift */; }; + 0EFEB4582006D3C800F81029 /* MSS.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFEB42D2006D3C800F81029 /* MSS.h */; }; + 0EFEB4592006D3C800F81029 /* Allocation.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFEB42E2006D3C800F81029 /* Allocation.h */; }; + 0EFEB45A2006D3C800F81029 /* TunnelInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB42F2006D3C800F81029 /* TunnelInterface.swift */; }; + 0EFEB45B2006D3C800F81029 /* TLSBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4302006D3C800F81029 /* TLSBox.m */; }; + 0EFEB45C2006D3C800F81029 /* ZeroingData.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4312006D3C800F81029 /* ZeroingData.m */; }; + 0EFEB45D2006D3C800F81029 /* CryptoBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4322006D3C800F81029 /* CryptoBox.m */; }; + 0EFEB4602006D3C800F81029 /* DataPath.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFEB4352006D3C800F81029 /* DataPath.h */; }; + 0EFEB4612006D3C800F81029 /* Errors.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFEB4362006D3C800F81029 /* Errors.h */; }; + 0EFEB4622006D3C800F81029 /* SecureRandom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4372006D3C800F81029 /* SecureRandom.swift */; }; + 0EFEB4632006D3C800F81029 /* ProtocolMacros.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4382006D3C800F81029 /* ProtocolMacros.swift */; }; + 0EFEB4642006D3C800F81029 /* ReplayProtector.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFEB4392006D3C800F81029 /* ReplayProtector.h */; }; + 0EFEB4652006D3C800F81029 /* Authenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB43A2006D3C800F81029 /* Authenticator.swift */; }; + 0EFEB4662006D3C800F81029 /* ZeroingData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB43B2006D3C800F81029 /* ZeroingData.swift */; }; + 0EFEB4672006D3C800F81029 /* SessionProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB43C2006D3C800F81029 /* SessionProxy.swift */; }; + 0EFEB4682006D3C800F81029 /* MSS.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB43D2006D3C800F81029 /* MSS.m */; }; + 0EFEB4692006D3C800F81029 /* Packet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB43E2006D3C800F81029 /* Packet.swift */; }; + 0EFEB46B2006D3C800F81029 /* CryptoBox.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFEB4402006D3C800F81029 /* CryptoBox.h */; }; + 0EFEB46C2006D3C800F81029 /* ZeroingData.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFEB4412006D3C800F81029 /* ZeroingData.h */; }; + 0EFEB46D2006D3C800F81029 /* Data+Manipulation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4432006D3C800F81029 /* Data+Manipulation.swift */; }; + 0EFEB46E2006D3C800F81029 /* TLSBox.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFEB4442006D3C800F81029 /* TLSBox.h */; }; + 0EFEB46F2006D3C800F81029 /* IOInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4452006D3C800F81029 /* IOInterface.swift */; }; + 0EFEB4702006D3C800F81029 /* Allocation.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4462006D3C800F81029 /* Allocation.m */; }; + 0EFEB4712006D3C800F81029 /* TunnelSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4472006D3C800F81029 /* TunnelSettings.swift */; }; + 0EFEB4722006D3C800F81029 /* ReplayProtector.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4482006D3C800F81029 /* ReplayProtector.m */; }; + 0EFEB4732006D3C800F81029 /* LinkInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4492006D3C800F81029 /* LinkInterface.swift */; }; + 0EFEB4742006D3C800F81029 /* CoreConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB44A2006D3C800F81029 /* CoreConfiguration.swift */; }; + 0EFEB4752006D3C800F81029 /* Errors.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB44B2006D3C800F81029 /* Errors.m */; }; + 0EFEB4762006D3C800F81029 /* DataPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB44C2006D3C800F81029 /* DataPath.m */; }; + 0EFEB4782006D3C800F81029 /* PIATunnelProvider+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB44F2006D3C800F81029 /* PIATunnelProvider+Configuration.swift */; }; + 0EFEB4792006D3C800F81029 /* PIATunnelProvider+Interaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4502006D3C800F81029 /* PIATunnelProvider+Interaction.swift */; }; + 0EFEB47B2006D3C800F81029 /* PIATunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4522006D3C800F81029 /* PIATunnelProvider.swift */; }; + 0EFEB4832006D3D000F81029 /* PIA-RSA-3072.pem in Resources */ = {isa = PBXBuildFile; fileRef = 0EFEB4802006D3D000F81029 /* PIA-RSA-3072.pem */; }; + 0EFEB4842006D3D000F81029 /* PIA-RSA-4096.pem in Resources */ = {isa = PBXBuildFile; fileRef = 0EFEB4812006D3D000F81029 /* PIA-RSA-4096.pem */; }; + 0EFEB4852006D3D000F81029 /* PIA-RSA-2048.pem in Resources */ = {isa = PBXBuildFile; fileRef = 0EFEB4822006D3D000F81029 /* PIA-RSA-2048.pem */; }; + 0EFEB4872006D7C400F81029 /* PIATunnelProvider+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB44F2006D3C800F81029 /* PIATunnelProvider+Configuration.swift */; }; + 0EFEB4882006D7C400F81029 /* PIATunnelProvider+Interaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4502006D3C800F81029 /* PIATunnelProvider+Interaction.swift */; }; + 0EFEB48A2006D7C400F81029 /* PIATunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4522006D3C800F81029 /* PIATunnelProvider.swift */; }; + 0EFEB48D2006D7F300F81029 /* EncryptionProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB42A2006D3C800F81029 /* EncryptionProxy.swift */; }; + 0EFEB48E2006D7F300F81029 /* SessionKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB42B2006D3C800F81029 /* SessionKey.swift */; }; + 0EFEB4902006D7F300F81029 /* TunnelInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB42F2006D3C800F81029 /* TunnelInterface.swift */; }; + 0EFEB4912006D7F300F81029 /* TLSBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4302006D3C800F81029 /* TLSBox.m */; }; + 0EFEB4922006D7F300F81029 /* ZeroingData.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4312006D3C800F81029 /* ZeroingData.m */; }; + 0EFEB4932006D7F300F81029 /* CryptoBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4322006D3C800F81029 /* CryptoBox.m */; }; + 0EFEB4952006D7F300F81029 /* SecureRandom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4372006D3C800F81029 /* SecureRandom.swift */; }; + 0EFEB4962006D7F300F81029 /* ProtocolMacros.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4382006D3C800F81029 /* ProtocolMacros.swift */; }; + 0EFEB4972006D7F300F81029 /* Authenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB43A2006D3C800F81029 /* Authenticator.swift */; }; + 0EFEB4982006D7F300F81029 /* ZeroingData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB43B2006D3C800F81029 /* ZeroingData.swift */; }; + 0EFEB4992006D7F300F81029 /* SessionProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB43C2006D3C800F81029 /* SessionProxy.swift */; }; + 0EFEB49A2006D7F300F81029 /* MSS.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB43D2006D3C800F81029 /* MSS.m */; }; + 0EFEB49B2006D7F300F81029 /* Packet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB43E2006D3C800F81029 /* Packet.swift */; }; + 0EFEB49C2006D7F300F81029 /* Data+Manipulation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4432006D3C800F81029 /* Data+Manipulation.swift */; }; + 0EFEB49D2006D7F300F81029 /* IOInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4452006D3C800F81029 /* IOInterface.swift */; }; + 0EFEB49E2006D7F300F81029 /* Allocation.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4462006D3C800F81029 /* Allocation.m */; }; + 0EFEB49F2006D7F300F81029 /* TunnelSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4472006D3C800F81029 /* TunnelSettings.swift */; }; + 0EFEB4A02006D7F300F81029 /* ReplayProtector.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4482006D3C800F81029 /* ReplayProtector.m */; }; + 0EFEB4A12006D7F300F81029 /* LinkInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4492006D3C800F81029 /* LinkInterface.swift */; }; + 0EFEB4A22006D7F300F81029 /* CoreConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB44A2006D3C800F81029 /* CoreConfiguration.swift */; }; + 0EFEB4A32006D7F300F81029 /* Errors.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB44B2006D3C800F81029 /* Errors.m */; }; + 0EFEB4A42006D7F300F81029 /* DataPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB44C2006D3C800F81029 /* DataPath.m */; }; + 0EFEB4AB200760EC00F81029 /* MemoryDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4A9200760EB00F81029 /* MemoryDestination.swift */; }; + 0EFEB4AC200760EC00F81029 /* InterfaceObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4AA200760EC00F81029 /* InterfaceObserver.swift */; }; + 0EFEB4AE2007625E00F81029 /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4AD2007625E00F81029 /* Keychain.swift */; }; + 0EFEB4AF2007627700F81029 /* InterfaceObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4AA200760EC00F81029 /* InterfaceObserver.swift */; }; + 0EFEB4B02007627700F81029 /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4AD2007625E00F81029 /* Keychain.swift */; }; + 0EFEB4B12007627700F81029 /* MemoryDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4A9200760EB00F81029 /* MemoryDestination.swift */; }; + 7824F3587AB5DCEE2EC390DB /* Pods_PIATunnel_PIATunnelHost.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2EA91073AAC5BE66714244D3 /* Pods_PIATunnel_PIATunnelHost.framework */; }; + 82BA0AEEFB1911C6CF99E721 /* Pods_PIATunnel_PIATunnel_macOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C017D38784E2EBDA13B546A9 /* Pods_PIATunnel_PIATunnel_macOS.framework */; }; + FBDC1B556E0886795C1FCAF6 /* Pods_PIATunnel_PIATunnel_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 47F39D305605C29A803DF509 /* Pods_PIATunnel_PIATunnel_iOS.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 0E1108A01F77B9E800A92462 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0E17D7F01F730D9F009EE129 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0E17D7F81F730D9F009EE129; + remoteInfo = PIATunnel; + }; + 0E1108BB1F77BA0200A92462 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0E17D7F01F730D9F009EE129 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0E1108A81F77B9F900A92462; + remoteInfo = PIATunnelHost; + }; + 0E9379CA1F819A4600CE91B6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0E17D7F01F730D9F009EE129 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0E17D7F81F730D9F009EE129; + remoteInfo = PIATunnel; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 0E07595C20EF6D1400F38FD8 /* CryptoCBC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CryptoCBC.m; sourceTree = ""; }; + 0E07596120EF733F00F38FD8 /* CryptoMacros.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CryptoMacros.h; sourceTree = ""; }; + 0E07596A20EF79AB00F38FD8 /* Encryption.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Encryption.h; sourceTree = ""; }; + 0E07596D20EF79B400F38FD8 /* CryptoCBC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptoCBC.h; sourceTree = ""; }; + 0E07597C20F0060E00F38FD8 /* CryptoAEAD.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptoAEAD.h; sourceTree = ""; }; + 0E07597D20F0060E00F38FD8 /* CryptoAEAD.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CryptoAEAD.m; sourceTree = ""; }; + 0E11089A1F77B9E800A92462 /* PIATunnelTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PIATunnelTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 0E11089E1F77B9E800A92462 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 0E1108A91F77B9F900A92462 /* PIATunnelHost.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PIATunnelHost.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 0E1108AB1F77B9F900A92462 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 0E1108AD1F77B9F900A92462 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 0E1108B01F77B9F900A92462 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 0E1108B21F77B9F900A92462 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 0E1108B51F77B9F900A92462 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 0E1108B71F77B9F900A92462 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 0E17D7F91F730D9F009EE129 /* PIATunnel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PIATunnel.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 0E17D7FD1F730D9F009EE129 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 0E3251C51F95770D00C108D9 /* PIATunnel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PIATunnel.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 0E3E0F202108A8CC00B371C1 /* PushReply.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushReply.swift; sourceTree = ""; }; + 0E85A259202CC5AE0059E9F9 /* AppExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppExtensionTests.swift; sourceTree = ""; }; + 0E85A25B202CCA3D0059E9F9 /* PIATunnelHost.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PIATunnelHost.entitlements; sourceTree = ""; }; + 0EA8E2042024D4B100A92DB6 /* PIA-ECC-256k1.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "PIA-ECC-256k1.pem"; sourceTree = ""; }; + 0EA8E2052024D4B100A92DB6 /* PIA-ECC-256r1.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "PIA-ECC-256r1.pem"; sourceTree = ""; }; + 0EA8E2062024D4B200A92DB6 /* PIA-ECC-521r1.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "PIA-ECC-521r1.pem"; sourceTree = ""; }; + 0EAAD70820E4F2BC0088754A /* CommunicationType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommunicationType.swift; sourceTree = ""; }; + 0EAAD70B20E4F85A0088754A /* LinkInterface+Strategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LinkInterface+Strategy.swift"; sourceTree = ""; }; + 0EB2B45220F0BB44004233D7 /* EncryptionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionTests.swift; sourceTree = ""; }; + 0EB2B45420F0BB53004233D7 /* DataManipulationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataManipulationTests.swift; sourceTree = ""; }; + 0EB2B45620F0BD16004233D7 /* RandomTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomTests.swift; sourceTree = ""; }; + 0EB2B45820F0BD9A004233D7 /* LinkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkTests.swift; sourceTree = ""; }; + 0EB2B45A20F0BE4C004233D7 /* TestUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestUtils.swift; sourceTree = ""; }; + 0EB2B45C20F0BF41004233D7 /* RawPerformanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawPerformanceTests.swift; sourceTree = ""; }; + 0EB2B45E20F0C098004233D7 /* EncryptionPerformanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionPerformanceTests.swift; sourceTree = ""; }; + 0EB2B46020F0C0A4004233D7 /* DataPathPerformanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataPathPerformanceTests.swift; sourceTree = ""; }; + 0EBBF2E42084FE6F00E36B40 /* GenericSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenericSocket.swift; sourceTree = ""; }; + 0EBBF2EA2085055100E36B40 /* NWUDPSessionState+Description.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NWUDPSessionState+Description.swift"; sourceTree = ""; }; + 0EBBF2EB2085055100E36B40 /* NETunnelInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NETunnelInterface.swift; sourceTree = ""; }; + 0EBBF2EC2085055100E36B40 /* NEUDPInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NEUDPInterface.swift; sourceTree = ""; }; + 0EBBF2F92085061600E36B40 /* NETCPInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NETCPInterface.swift; sourceTree = ""; }; + 0EBBF2FF2085196000E36B40 /* NWTCPConnectionState+Description.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NWTCPConnectionState+Description.swift"; sourceTree = ""; }; + 0EC1BBA420D71190007C4C7B /* DNSResolver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DNSResolver.swift; sourceTree = ""; }; + 0EC1BBA720D7D803007C4C7B /* ConnectionStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionStrategy.swift; sourceTree = ""; }; + 0EE7A79420F61EDC00B42E6A /* PacketMacros.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PacketMacros.h; sourceTree = ""; }; + 0EE7A79720F6296F00B42E6A /* PacketMacros.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PacketMacros.m; sourceTree = ""; }; + 0EE7A79D20F6488400B42E6A /* DataPathEncryption.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DataPathEncryption.h; sourceTree = ""; }; + 0EE7A7A020F664AB00B42E6A /* DataPathEncryptionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataPathEncryptionTests.swift; sourceTree = ""; }; + 0EEC49DB20B5E732008FEB91 /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; + 0EFEB42A2006D3C800F81029 /* EncryptionProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EncryptionProxy.swift; sourceTree = ""; }; + 0EFEB42B2006D3C800F81029 /* SessionKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionKey.swift; sourceTree = ""; }; + 0EFEB42D2006D3C800F81029 /* MSS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSS.h; sourceTree = ""; }; + 0EFEB42E2006D3C800F81029 /* Allocation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Allocation.h; sourceTree = ""; }; + 0EFEB42F2006D3C800F81029 /* TunnelInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelInterface.swift; sourceTree = ""; }; + 0EFEB4302006D3C800F81029 /* TLSBox.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TLSBox.m; sourceTree = ""; }; + 0EFEB4312006D3C800F81029 /* ZeroingData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZeroingData.m; sourceTree = ""; }; + 0EFEB4322006D3C800F81029 /* CryptoBox.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CryptoBox.m; sourceTree = ""; }; + 0EFEB4352006D3C800F81029 /* DataPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataPath.h; sourceTree = ""; }; + 0EFEB4362006D3C800F81029 /* Errors.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Errors.h; sourceTree = ""; }; + 0EFEB4372006D3C800F81029 /* SecureRandom.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecureRandom.swift; sourceTree = ""; }; + 0EFEB4382006D3C800F81029 /* ProtocolMacros.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProtocolMacros.swift; sourceTree = ""; }; + 0EFEB4392006D3C800F81029 /* ReplayProtector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReplayProtector.h; sourceTree = ""; }; + 0EFEB43A2006D3C800F81029 /* Authenticator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Authenticator.swift; sourceTree = ""; }; + 0EFEB43B2006D3C800F81029 /* ZeroingData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZeroingData.swift; sourceTree = ""; }; + 0EFEB43C2006D3C800F81029 /* SessionProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionProxy.swift; sourceTree = ""; }; + 0EFEB43D2006D3C800F81029 /* MSS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSS.m; sourceTree = ""; }; + 0EFEB43E2006D3C800F81029 /* Packet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Packet.swift; sourceTree = ""; }; + 0EFEB4402006D3C800F81029 /* CryptoBox.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptoBox.h; sourceTree = ""; }; + 0EFEB4412006D3C800F81029 /* ZeroingData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZeroingData.h; sourceTree = ""; }; + 0EFEB4422006D3C800F81029 /* module.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; + 0EFEB4432006D3C800F81029 /* Data+Manipulation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Manipulation.swift"; sourceTree = ""; }; + 0EFEB4442006D3C800F81029 /* TLSBox.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TLSBox.h; sourceTree = ""; }; + 0EFEB4452006D3C800F81029 /* IOInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IOInterface.swift; sourceTree = ""; }; + 0EFEB4462006D3C800F81029 /* Allocation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Allocation.m; sourceTree = ""; }; + 0EFEB4472006D3C800F81029 /* TunnelSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelSettings.swift; sourceTree = ""; }; + 0EFEB4482006D3C800F81029 /* ReplayProtector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ReplayProtector.m; sourceTree = ""; }; + 0EFEB4492006D3C800F81029 /* LinkInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkInterface.swift; sourceTree = ""; }; + 0EFEB44A2006D3C800F81029 /* CoreConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreConfiguration.swift; sourceTree = ""; }; + 0EFEB44B2006D3C800F81029 /* Errors.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Errors.m; sourceTree = ""; }; + 0EFEB44C2006D3C800F81029 /* DataPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DataPath.m; sourceTree = ""; }; + 0EFEB44F2006D3C800F81029 /* PIATunnelProvider+Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PIATunnelProvider+Configuration.swift"; sourceTree = ""; }; + 0EFEB4502006D3C800F81029 /* PIATunnelProvider+Interaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PIATunnelProvider+Interaction.swift"; sourceTree = ""; }; + 0EFEB4522006D3C800F81029 /* PIATunnelProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PIATunnelProvider.swift; sourceTree = ""; }; + 0EFEB4802006D3D000F81029 /* PIA-RSA-3072.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "PIA-RSA-3072.pem"; sourceTree = ""; }; + 0EFEB4812006D3D000F81029 /* PIA-RSA-4096.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "PIA-RSA-4096.pem"; sourceTree = ""; }; + 0EFEB4822006D3D000F81029 /* PIA-RSA-2048.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "PIA-RSA-2048.pem"; sourceTree = ""; }; + 0EFEB4A9200760EB00F81029 /* MemoryDestination.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemoryDestination.swift; sourceTree = ""; }; + 0EFEB4AA200760EC00F81029 /* InterfaceObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InterfaceObserver.swift; sourceTree = ""; }; + 0EFEB4AD2007625E00F81029 /* Keychain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = ""; }; + 2EA91073AAC5BE66714244D3 /* Pods_PIATunnel_PIATunnelHost.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PIATunnel_PIATunnelHost.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 47F39D305605C29A803DF509 /* Pods_PIATunnel_PIATunnel_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PIATunnel_PIATunnel_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4EF01042B93805901ADF144F /* Pods-PIATunnel-PIATunnel-iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PIATunnel-PIATunnel-iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-PIATunnel-PIATunnel-iOS/Pods-PIATunnel-PIATunnel-iOS.debug.xcconfig"; sourceTree = ""; }; + 4FCEB45EA990C21162F820EE /* Pods-PIATunnel-PIATunnelHost.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PIATunnel-PIATunnelHost.debug.xcconfig"; path = "Pods/Target Support Files/Pods-PIATunnel-PIATunnelHost/Pods-PIATunnel-PIATunnelHost.debug.xcconfig"; sourceTree = ""; }; + 5CCF5EF1B897AB67ECDE05D0 /* Pods-PIATunnel-PIATunnel-iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PIATunnel-PIATunnel-iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-PIATunnel-PIATunnel-iOS/Pods-PIATunnel-PIATunnel-iOS.release.xcconfig"; sourceTree = ""; }; + 889C502914303CDCADF8C2B9 /* Pods-PIATunnel-PIATunnel-macOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PIATunnel-PIATunnel-macOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-PIATunnel-PIATunnel-macOS/Pods-PIATunnel-PIATunnel-macOS.release.xcconfig"; sourceTree = ""; }; + 99F9C9FAEB7F699A60047D42 /* Pods-PIATunnel-PIATunnelHost.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PIATunnel-PIATunnelHost.release.xcconfig"; path = "Pods/Target Support Files/Pods-PIATunnel-PIATunnelHost/Pods-PIATunnel-PIATunnelHost.release.xcconfig"; sourceTree = ""; }; + C017D38784E2EBDA13B546A9 /* Pods_PIATunnel_PIATunnel_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PIATunnel_PIATunnel_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D65AFCC4FA1CB9B85E7B1451 /* Pods-PIATunnel-PIATunnel-macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PIATunnel-PIATunnel-macOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-PIATunnel-PIATunnel-macOS/Pods-PIATunnel-PIATunnel-macOS.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 0E1108971F77B9E800A92462 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0E11089F1F77B9E800A92462 /* PIATunnel.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0E1108A61F77B9F900A92462 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0E9379C91F819A4300CE91B6 /* PIATunnel.framework in Frameworks */, + 7824F3587AB5DCEE2EC390DB /* Pods_PIATunnel_PIATunnelHost.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0E17D7F51F730D9F009EE129 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + FBDC1B556E0886795C1FCAF6 /* Pods_PIATunnel_PIATunnel_iOS.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0E3251C11F95770D00C108D9 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 82BA0AEEFB1911C6CF99E721 /* Pods_PIATunnel_PIATunnel_macOS.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0E11089B1F77B9E800A92462 /* PIATunnelTests */ = { + isa = PBXGroup; + children = ( + 0E11089E1F77B9E800A92462 /* Info.plist */, + 0E85A259202CC5AE0059E9F9 /* AppExtensionTests.swift */, + 0EB2B45420F0BB53004233D7 /* DataManipulationTests.swift */, + 0EE7A7A020F664AB00B42E6A /* DataPathEncryptionTests.swift */, + 0EB2B46020F0C0A4004233D7 /* DataPathPerformanceTests.swift */, + 0EB2B45E20F0C098004233D7 /* EncryptionPerformanceTests.swift */, + 0EB2B45220F0BB44004233D7 /* EncryptionTests.swift */, + 0EB2B45820F0BD9A004233D7 /* LinkTests.swift */, + 0EB2B45620F0BD16004233D7 /* RandomTests.swift */, + 0EB2B45C20F0BF41004233D7 /* RawPerformanceTests.swift */, + 0EB2B45A20F0BE4C004233D7 /* TestUtils.swift */, + ); + path = PIATunnelTests; + sourceTree = ""; + }; + 0E1108AA1F77B9F900A92462 /* PIATunnelHost */ = { + isa = PBXGroup; + children = ( + 0E85A25B202CCA3D0059E9F9 /* PIATunnelHost.entitlements */, + 0E1108AB1F77B9F900A92462 /* AppDelegate.swift */, + 0E1108AD1F77B9F900A92462 /* ViewController.swift */, + 0E1108AF1F77B9F900A92462 /* Main.storyboard */, + 0E1108B21F77B9F900A92462 /* Assets.xcassets */, + 0E1108B41F77B9F900A92462 /* LaunchScreen.storyboard */, + 0E1108B71F77B9F900A92462 /* Info.plist */, + ); + path = PIATunnelHost; + sourceTree = ""; + }; + 0E17D7EF1F730D9F009EE129 = { + isa = PBXGroup; + children = ( + 0E17D7FB1F730D9F009EE129 /* PIATunnel */, + 0E11089B1F77B9E800A92462 /* PIATunnelTests */, + 0E1108AA1F77B9F900A92462 /* PIATunnelHost */, + 0E17D7FA1F730D9F009EE129 /* Products */, + CC09E28B727F735539F4CBE9 /* Pods */, + 8C8D529E2705C98E32A8218E /* Frameworks */, + ); + sourceTree = ""; + }; + 0E17D7FA1F730D9F009EE129 /* Products */ = { + isa = PBXGroup; + children = ( + 0E17D7F91F730D9F009EE129 /* PIATunnel.framework */, + 0E11089A1F77B9E800A92462 /* PIATunnelTests.xctest */, + 0E1108A91F77B9F900A92462 /* PIATunnelHost.app */, + 0E3251C51F95770D00C108D9 /* PIATunnel.framework */, + ); + name = Products; + sourceTree = ""; + }; + 0E17D7FB1F730D9F009EE129 /* PIATunnel */ = { + isa = PBXGroup; + children = ( + 0EFEB47E2006D3D000F81029 /* Resources */, + 0E17D8041F730DDD009EE129 /* Sources */, + 0E17D7FD1F730D9F009EE129 /* Info.plist */, + ); + path = PIATunnel; + sourceTree = ""; + }; + 0E17D8041F730DDD009EE129 /* Sources */ = { + isa = PBXGroup; + children = ( + 0EFEB44D2006D3C800F81029 /* AppExtension */, + 0EFEB4292006D3C800F81029 /* Core */, + ); + path = Sources; + sourceTree = ""; + }; + 0EBBF2E32084FDF400E36B40 /* Transport */ = { + isa = PBXGroup; + children = ( + 0EAAD70B20E4F85A0088754A /* LinkInterface+Strategy.swift */, + 0EBBF2F92085061600E36B40 /* NETCPInterface.swift */, + 0EBBF2EB2085055100E36B40 /* NETunnelInterface.swift */, + 0EBBF2EC2085055100E36B40 /* NEUDPInterface.swift */, + 0EBBF2FF2085196000E36B40 /* NWTCPConnectionState+Description.swift */, + 0EBBF2EA2085055100E36B40 /* NWUDPSessionState+Description.swift */, + ); + path = Transport; + sourceTree = ""; + }; + 0EFEB4292006D3C800F81029 /* Core */ = { + isa = PBXGroup; + children = ( + 0EFEB42E2006D3C800F81029 /* Allocation.h */, + 0EFEB4462006D3C800F81029 /* Allocation.m */, + 0EFEB43A2006D3C800F81029 /* Authenticator.swift */, + 0EAAD70820E4F2BC0088754A /* CommunicationType.swift */, + 0EFEB44A2006D3C800F81029 /* CoreConfiguration.swift */, + 0E07597C20F0060E00F38FD8 /* CryptoAEAD.h */, + 0E07597D20F0060E00F38FD8 /* CryptoAEAD.m */, + 0EFEB4402006D3C800F81029 /* CryptoBox.h */, + 0EFEB4322006D3C800F81029 /* CryptoBox.m */, + 0E07596D20EF79B400F38FD8 /* CryptoCBC.h */, + 0E07595C20EF6D1400F38FD8 /* CryptoCBC.m */, + 0E07596120EF733F00F38FD8 /* CryptoMacros.h */, + 0EFEB4432006D3C800F81029 /* Data+Manipulation.swift */, + 0EFEB4352006D3C800F81029 /* DataPath.h */, + 0EFEB44C2006D3C800F81029 /* DataPath.m */, + 0EE7A79D20F6488400B42E6A /* DataPathEncryption.h */, + 0E07596A20EF79AB00F38FD8 /* Encryption.h */, + 0EFEB42A2006D3C800F81029 /* EncryptionProxy.swift */, + 0EFEB4362006D3C800F81029 /* Errors.h */, + 0EFEB44B2006D3C800F81029 /* Errors.m */, + 0EFEB4452006D3C800F81029 /* IOInterface.swift */, + 0EFEB4492006D3C800F81029 /* LinkInterface.swift */, + 0EFEB4422006D3C800F81029 /* module.modulemap */, + 0EFEB42D2006D3C800F81029 /* MSS.h */, + 0EFEB43D2006D3C800F81029 /* MSS.m */, + 0EFEB43E2006D3C800F81029 /* Packet.swift */, + 0EE7A79420F61EDC00B42E6A /* PacketMacros.h */, + 0EE7A79720F6296F00B42E6A /* PacketMacros.m */, + 0EFEB4382006D3C800F81029 /* ProtocolMacros.swift */, + 0E3E0F202108A8CC00B371C1 /* PushReply.swift */, + 0EFEB4392006D3C800F81029 /* ReplayProtector.h */, + 0EFEB4482006D3C800F81029 /* ReplayProtector.m */, + 0EFEB4372006D3C800F81029 /* SecureRandom.swift */, + 0EFEB42B2006D3C800F81029 /* SessionKey.swift */, + 0EFEB43C2006D3C800F81029 /* SessionProxy.swift */, + 0EFEB4442006D3C800F81029 /* TLSBox.h */, + 0EFEB4302006D3C800F81029 /* TLSBox.m */, + 0EFEB42F2006D3C800F81029 /* TunnelInterface.swift */, + 0EFEB4472006D3C800F81029 /* TunnelSettings.swift */, + 0EFEB4412006D3C800F81029 /* ZeroingData.h */, + 0EFEB4312006D3C800F81029 /* ZeroingData.m */, + 0EFEB43B2006D3C800F81029 /* ZeroingData.swift */, + ); + path = Core; + sourceTree = ""; + }; + 0EFEB44D2006D3C800F81029 /* AppExtension */ = { + isa = PBXGroup; + children = ( + 0EBBF2E32084FDF400E36B40 /* Transport */, + 0EC1BBA720D7D803007C4C7B /* ConnectionStrategy.swift */, + 0EC1BBA420D71190007C4C7B /* DNSResolver.swift */, + 0EBBF2E42084FE6F00E36B40 /* GenericSocket.swift */, + 0EFEB4AA200760EC00F81029 /* InterfaceObserver.swift */, + 0EFEB4AD2007625E00F81029 /* Keychain.swift */, + 0EFEB4A9200760EB00F81029 /* MemoryDestination.swift */, + 0EFEB4522006D3C800F81029 /* PIATunnelProvider.swift */, + 0EFEB44F2006D3C800F81029 /* PIATunnelProvider+Configuration.swift */, + 0EFEB4502006D3C800F81029 /* PIATunnelProvider+Interaction.swift */, + 0EEC49DB20B5E732008FEB91 /* Utils.swift */, + ); + path = AppExtension; + sourceTree = ""; + }; + 0EFEB47E2006D3D000F81029 /* Resources */ = { + isa = PBXGroup; + children = ( + 0EFEB47F2006D3D000F81029 /* AppExtension */, + ); + path = Resources; + sourceTree = ""; + }; + 0EFEB47F2006D3D000F81029 /* AppExtension */ = { + isa = PBXGroup; + children = ( + 0EA8E2042024D4B100A92DB6 /* PIA-ECC-256k1.pem */, + 0EA8E2052024D4B100A92DB6 /* PIA-ECC-256r1.pem */, + 0EA8E2062024D4B200A92DB6 /* PIA-ECC-521r1.pem */, + 0EFEB4822006D3D000F81029 /* PIA-RSA-2048.pem */, + 0EFEB4802006D3D000F81029 /* PIA-RSA-3072.pem */, + 0EFEB4812006D3D000F81029 /* PIA-RSA-4096.pem */, + ); + path = AppExtension; + sourceTree = ""; + }; + 8C8D529E2705C98E32A8218E /* Frameworks */ = { + isa = PBXGroup; + children = ( + 47F39D305605C29A803DF509 /* Pods_PIATunnel_PIATunnel_iOS.framework */, + C017D38784E2EBDA13B546A9 /* Pods_PIATunnel_PIATunnel_macOS.framework */, + 2EA91073AAC5BE66714244D3 /* Pods_PIATunnel_PIATunnelHost.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + CC09E28B727F735539F4CBE9 /* Pods */ = { + isa = PBXGroup; + children = ( + 4EF01042B93805901ADF144F /* Pods-PIATunnel-PIATunnel-iOS.debug.xcconfig */, + 5CCF5EF1B897AB67ECDE05D0 /* Pods-PIATunnel-PIATunnel-iOS.release.xcconfig */, + D65AFCC4FA1CB9B85E7B1451 /* Pods-PIATunnel-PIATunnel-macOS.debug.xcconfig */, + 889C502914303CDCADF8C2B9 /* Pods-PIATunnel-PIATunnel-macOS.release.xcconfig */, + 4FCEB45EA990C21162F820EE /* Pods-PIATunnel-PIATunnelHost.debug.xcconfig */, + 99F9C9FAEB7F699A60047D42 /* Pods-PIATunnel-PIATunnelHost.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 0E17D7F61F730D9F009EE129 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 0EFEB4642006D3C800F81029 /* ReplayProtector.h in Headers */, + 0EFEB4612006D3C800F81029 /* Errors.h in Headers */, + 0E07596E20EF79B400F38FD8 /* CryptoCBC.h in Headers */, + 0E07596320EF733F00F38FD8 /* CryptoMacros.h in Headers */, + 0EFEB46E2006D3C800F81029 /* TLSBox.h in Headers */, + 0E07596B20EF79AB00F38FD8 /* Encryption.h in Headers */, + 0EFEB46B2006D3C800F81029 /* CryptoBox.h in Headers */, + 0EFEB4592006D3C800F81029 /* Allocation.h in Headers */, + 0EFEB4582006D3C800F81029 /* MSS.h in Headers */, + 0EFEB4602006D3C800F81029 /* DataPath.h in Headers */, + 0E07597E20F0060E00F38FD8 /* CryptoAEAD.h in Headers */, + 0EFEB46C2006D3C800F81029 /* ZeroingData.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0E3251C21F95770D00C108D9 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 0EEC49E620B5F7F6008FEB91 /* MSS.h in Headers */, + 0EEC49E520B5F7F6008FEB91 /* Errors.h in Headers */, + 0E07596F20EF79B400F38FD8 /* CryptoCBC.h in Headers */, + 0E07596420EF733F00F38FD8 /* CryptoMacros.h in Headers */, + 0EEC49EA20B5F7F6008FEB91 /* ZeroingData.h in Headers */, + 0E07596C20EF79AB00F38FD8 /* Encryption.h in Headers */, + 0EEC49E120B5F7EA008FEB91 /* Allocation.h in Headers */, + 0EEC49E320B5F7F6008FEB91 /* DataPath.h in Headers */, + 0EEC49E820B5F7F6008FEB91 /* ReplayProtector.h in Headers */, + 0EEC49E920B5F7F6008FEB91 /* TLSBox.h in Headers */, + 0E07597F20F0060E00F38FD8 /* CryptoAEAD.h in Headers */, + 0EEC49E220B5F7F6008FEB91 /* CryptoBox.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 0E1108991F77B9E800A92462 /* PIATunnelTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0E1108A41F77B9E800A92462 /* Build configuration list for PBXNativeTarget "PIATunnelTests" */; + buildPhases = ( + 0E1108961F77B9E800A92462 /* Sources */, + 0E1108971F77B9E800A92462 /* Frameworks */, + 0E1108981F77B9E800A92462 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 0E1108A11F77B9E800A92462 /* PBXTargetDependency */, + 0E1108BC1F77BA0200A92462 /* PBXTargetDependency */, + ); + name = PIATunnelTests; + productName = PIATunnelTests; + productReference = 0E11089A1F77B9E800A92462 /* PIATunnelTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 0E1108A81F77B9F900A92462 /* PIATunnelHost */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0E1108B81F77B9F900A92462 /* Build configuration list for PBXNativeTarget "PIATunnelHost" */; + buildPhases = ( + E92F2EA8869D758CA069C089 /* [CP] Check Pods Manifest.lock */, + 0E1108A51F77B9F900A92462 /* Sources */, + 0E1108A61F77B9F900A92462 /* Frameworks */, + 0E1108A71F77B9F900A92462 /* Resources */, + D8CE7C5A74A8E75A0756E08F /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 0E9379CB1F819A4600CE91B6 /* PBXTargetDependency */, + ); + name = PIATunnelHost; + productName = PIATunnelHost; + productReference = 0E1108A91F77B9F900A92462 /* PIATunnelHost.app */; + productType = "com.apple.product-type.application"; + }; + 0E17D7F81F730D9F009EE129 /* PIATunnel-iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0E17D8011F730D9F009EE129 /* Build configuration list for PBXNativeTarget "PIATunnel-iOS" */; + buildPhases = ( + B81ED2229B05F6217EE28199 /* [CP] Check Pods Manifest.lock */, + 0E17D7F41F730D9F009EE129 /* Sources */, + 0E17D7F51F730D9F009EE129 /* Frameworks */, + 0E17D7F61F730D9F009EE129 /* Headers */, + 0E17D7F71F730D9F009EE129 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "PIATunnel-iOS"; + productName = PIATunnel; + productReference = 0E17D7F91F730D9F009EE129 /* PIATunnel.framework */; + productType = "com.apple.product-type.framework"; + }; + 0E3251C41F95770D00C108D9 /* PIATunnel-macOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0E3251CA1F95770D00C108D9 /* Build configuration list for PBXNativeTarget "PIATunnel-macOS" */; + buildPhases = ( + 5578C17CB1FA354FB3939356 /* [CP] Check Pods Manifest.lock */, + 0E3251C01F95770D00C108D9 /* Sources */, + 0E3251C11F95770D00C108D9 /* Frameworks */, + 0E3251C21F95770D00C108D9 /* Headers */, + 0E3251C31F95770D00C108D9 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "PIATunnel-macOS"; + productName = "PIATunnel-macOS"; + productReference = 0E3251C51F95770D00C108D9 /* PIATunnel.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 0E17D7F01F730D9F009EE129 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0900; + LastUpgradeCheck = 0930; + ORGANIZATIONNAME = "London Trust Media"; + TargetAttributes = { + 0E1108991F77B9E800A92462 = { + CreatedOnToolsVersion = 9.0; + ProvisioningStyle = Automatic; + TestTargetID = 0E1108A81F77B9F900A92462; + }; + 0E1108A81F77B9F900A92462 = { + CreatedOnToolsVersion = 9.0; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.ApplicationGroups.iOS = { + enabled = 1; + }; + com.apple.Keychain = { + enabled = 1; + }; + }; + }; + 0E17D7F81F730D9F009EE129 = { + CreatedOnToolsVersion = 9.0; + ProvisioningStyle = Automatic; + }; + 0E3251C41F95770D00C108D9 = { + CreatedOnToolsVersion = 9.0; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 0E17D7F31F730D9F009EE129 /* Build configuration list for PBXProject "PIATunnel" */; + compatibilityVersion = "Xcode 8.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 0E17D7EF1F730D9F009EE129; + productRefGroup = 0E17D7FA1F730D9F009EE129 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 0E17D7F81F730D9F009EE129 /* PIATunnel-iOS */, + 0E3251C41F95770D00C108D9 /* PIATunnel-macOS */, + 0E1108991F77B9E800A92462 /* PIATunnelTests */, + 0E1108A81F77B9F900A92462 /* PIATunnelHost */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 0E1108981F77B9E800A92462 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0E1108A71F77B9F900A92462 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0E1108B61F77B9F900A92462 /* LaunchScreen.storyboard in Resources */, + 0E1108B31F77B9F900A92462 /* Assets.xcassets in Resources */, + 0E1108B11F77B9F900A92462 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0E17D7F71F730D9F009EE129 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0EA8E2072024D4B200A92DB6 /* PIA-ECC-256k1.pem in Resources */, + 0EFEB4852006D3D000F81029 /* PIA-RSA-2048.pem in Resources */, + 0EA8E2082024D4B200A92DB6 /* PIA-ECC-256r1.pem in Resources */, + 0EA8E2092024D4B200A92DB6 /* PIA-ECC-521r1.pem in Resources */, + 0EFEB4832006D3D000F81029 /* PIA-RSA-3072.pem in Resources */, + 0EFEB4842006D3D000F81029 /* PIA-RSA-4096.pem in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0E3251C31F95770D00C108D9 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0EA8E20D2024D5D500A92DB6 /* PIA-RSA-2048.pem in Resources */, + 0EA8E20E2024D5D500A92DB6 /* PIA-RSA-3072.pem in Resources */, + 0EA8E20C2024D5D500A92DB6 /* PIA-ECC-521r1.pem in Resources */, + 0EA8E20F2024D5D500A92DB6 /* PIA-RSA-4096.pem in Resources */, + 0EA8E20B2024D5D500A92DB6 /* PIA-ECC-256k1.pem in Resources */, + 0EA8E20A2024D5D500A92DB6 /* PIA-ECC-256r1.pem in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 5578C17CB1FA354FB3939356 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-PIATunnel-PIATunnel-macOS-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + B81ED2229B05F6217EE28199 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-PIATunnel-PIATunnel-iOS-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + D8CE7C5A74A8E75A0756E08F /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-PIATunnel-PIATunnelHost/Pods-PIATunnel-PIATunnelHost-frameworks.sh", + "${PODS_ROOT}/OpenSSL-Apple/frameworks/iPhone/openssl.framework", + "${BUILT_PRODUCTS_DIR}/SwiftyBeaver-iOS/SwiftyBeaver.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/openssl.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyBeaver.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-PIATunnel-PIATunnelHost/Pods-PIATunnel-PIATunnelHost-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + E92F2EA8869D758CA069C089 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-PIATunnel-PIATunnelHost-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 0E1108961F77B9E800A92462 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0EB2B45720F0BD16004233D7 /* RandomTests.swift in Sources */, + 0EB2B45920F0BD9A004233D7 /* LinkTests.swift in Sources */, + 0EB2B45520F0BB53004233D7 /* DataManipulationTests.swift in Sources */, + 0EB2B45320F0BB44004233D7 /* EncryptionTests.swift in Sources */, + 0EB2B45B20F0BE4C004233D7 /* TestUtils.swift in Sources */, + 0EB2B46120F0C0A4004233D7 /* DataPathPerformanceTests.swift in Sources */, + 0EB2B45F20F0C098004233D7 /* EncryptionPerformanceTests.swift in Sources */, + 0EE7A7A120F664AC00B42E6A /* DataPathEncryptionTests.swift in Sources */, + 0EB2B45D20F0BF41004233D7 /* RawPerformanceTests.swift in Sources */, + 0E85A25A202CC5AF0059E9F9 /* AppExtensionTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0E1108A51F77B9F900A92462 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0E1108AE1F77B9F900A92462 /* ViewController.swift in Sources */, + 0E1108AC1F77B9F900A92462 /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0E17D7F41F730D9F009EE129 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0EBBF2F5208505D700E36B40 /* NETunnelInterface.swift in Sources */, + 0EAAD70920E4F2BC0088754A /* CommunicationType.swift in Sources */, + 0EFEB4732006D3C800F81029 /* LinkInterface.swift in Sources */, + 0EBBF2F8208505DD00E36B40 /* NWUDPSessionState+Description.swift in Sources */, + 0EFEB4652006D3C800F81029 /* Authenticator.swift in Sources */, + 0EE7A79820F6296F00B42E6A /* PacketMacros.m in Sources */, + 0EEC49DC20B5E732008FEB91 /* Utils.swift in Sources */, + 0EAAD70C20E4F85A0088754A /* LinkInterface+Strategy.swift in Sources */, + 0EFEB4562006D3C800F81029 /* SessionKey.swift in Sources */, + 0EC1BBA520D71190007C4C7B /* DNSResolver.swift in Sources */, + 0EFEB4AB200760EC00F81029 /* MemoryDestination.swift in Sources */, + 0EFEB4AE2007625E00F81029 /* Keychain.swift in Sources */, + 0EBBF3002085196000E36B40 /* NWTCPConnectionState+Description.swift in Sources */, + 0EFEB4622006D3C800F81029 /* SecureRandom.swift in Sources */, + 0EE7A79520F61EDC00B42E6A /* PacketMacros.h in Sources */, + 0EFEB45D2006D3C800F81029 /* CryptoBox.m in Sources */, + 0EBBF2FA2085061600E36B40 /* NETCPInterface.swift in Sources */, + 0EFEB4552006D3C800F81029 /* EncryptionProxy.swift in Sources */, + 0EFEB45C2006D3C800F81029 /* ZeroingData.m in Sources */, + 0EFEB4632006D3C800F81029 /* ProtocolMacros.swift in Sources */, + 0EFEB4AC200760EC00F81029 /* InterfaceObserver.swift in Sources */, + 0EFEB46D2006D3C800F81029 /* Data+Manipulation.swift in Sources */, + 0EFEB47B2006D3C800F81029 /* PIATunnelProvider.swift in Sources */, + 0EFEB4742006D3C800F81029 /* CoreConfiguration.swift in Sources */, + 0E07595F20EF6D1400F38FD8 /* CryptoCBC.m in Sources */, + 0EC1BBA820D7D803007C4C7B /* ConnectionStrategy.swift in Sources */, + 0EFEB46F2006D3C800F81029 /* IOInterface.swift in Sources */, + 0E07598020F0060E00F38FD8 /* CryptoAEAD.m in Sources */, + 0EFEB4662006D3C800F81029 /* ZeroingData.swift in Sources */, + 0EBBF2F3208505D300E36B40 /* NEUDPInterface.swift in Sources */, + 0EFEB4682006D3C800F81029 /* MSS.m in Sources */, + 0EFEB45B2006D3C800F81029 /* TLSBox.m in Sources */, + 0EFEB4792006D3C800F81029 /* PIATunnelProvider+Interaction.swift in Sources */, + 0EFEB4702006D3C800F81029 /* Allocation.m in Sources */, + 0EFEB4672006D3C800F81029 /* SessionProxy.swift in Sources */, + 0EFEB4722006D3C800F81029 /* ReplayProtector.m in Sources */, + 0EFEB4712006D3C800F81029 /* TunnelSettings.swift in Sources */, + 0EFEB4782006D3C800F81029 /* PIATunnelProvider+Configuration.swift in Sources */, + 0E3E0F212108A8CC00B371C1 /* PushReply.swift in Sources */, + 0EFEB4752006D3C800F81029 /* Errors.m in Sources */, + 0EBBF2E52084FE6F00E36B40 /* GenericSocket.swift in Sources */, + 0EFEB4762006D3C800F81029 /* DataPath.m in Sources */, + 0EFEB4692006D3C800F81029 /* Packet.swift in Sources */, + 0EFEB45A2006D3C800F81029 /* TunnelInterface.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0E3251C01F95770D00C108D9 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0EBBF2F6208505D700E36B40 /* NETunnelInterface.swift in Sources */, + 0EAAD70A20E4F2BC0088754A /* CommunicationType.swift in Sources */, + 0EFEB4A12006D7F300F81029 /* LinkInterface.swift in Sources */, + 0EFEB4872006D7C400F81029 /* PIATunnelProvider+Configuration.swift in Sources */, + 0EBBF2F7208505DD00E36B40 /* NWUDPSessionState+Description.swift in Sources */, + 0EFEB4882006D7C400F81029 /* PIATunnelProvider+Interaction.swift in Sources */, + 0EE7A79920F6296F00B42E6A /* PacketMacros.m in Sources */, + 0EEC49DD20B5E732008FEB91 /* Utils.swift in Sources */, + 0EAAD70D20E4F85A0088754A /* LinkInterface+Strategy.swift in Sources */, + 0EFEB4B12007627700F81029 /* MemoryDestination.swift in Sources */, + 0EC1BBA620D712DE007C4C7B /* DNSResolver.swift in Sources */, + 0EFEB4A02006D7F300F81029 /* ReplayProtector.m in Sources */, + 0EFEB4992006D7F300F81029 /* SessionProxy.swift in Sources */, + 0EBBF3012085196000E36B40 /* NWTCPConnectionState+Description.swift in Sources */, + 0EFEB4962006D7F300F81029 /* ProtocolMacros.swift in Sources */, + 0EE7A79620F61EDC00B42E6A /* PacketMacros.h in Sources */, + 0EFEB48A2006D7C400F81029 /* PIATunnelProvider.swift in Sources */, + 0EBBF2FB2085061600E36B40 /* NETCPInterface.swift in Sources */, + 0EFEB4982006D7F300F81029 /* ZeroingData.swift in Sources */, + 0EFEB4A32006D7F300F81029 /* Errors.m in Sources */, + 0EFEB4A22006D7F300F81029 /* CoreConfiguration.swift in Sources */, + 0EFEB4952006D7F300F81029 /* SecureRandom.swift in Sources */, + 0EFEB49A2006D7F300F81029 /* MSS.m in Sources */, + 0EFEB48D2006D7F300F81029 /* EncryptionProxy.swift in Sources */, + 0EFEB4922006D7F300F81029 /* ZeroingData.m in Sources */, + 0E07596020EF6D1400F38FD8 /* CryptoCBC.m in Sources */, + 0EC1BBA920D7D803007C4C7B /* ConnectionStrategy.swift in Sources */, + 0EFEB4932006D7F300F81029 /* CryptoBox.m in Sources */, + 0E07598120F0060E00F38FD8 /* CryptoAEAD.m in Sources */, + 0EFEB49C2006D7F300F81029 /* Data+Manipulation.swift in Sources */, + 0EBBF2F4208505D400E36B40 /* NEUDPInterface.swift in Sources */, + 0EFEB4902006D7F300F81029 /* TunnelInterface.swift in Sources */, + 0EFEB49E2006D7F300F81029 /* Allocation.m in Sources */, + 0EFEB4B02007627700F81029 /* Keychain.swift in Sources */, + 0EFEB48E2006D7F300F81029 /* SessionKey.swift in Sources */, + 0EFEB4AF2007627700F81029 /* InterfaceObserver.swift in Sources */, + 0EFEB4A42006D7F300F81029 /* DataPath.m in Sources */, + 0EBBF2E62084FE6F00E36B40 /* GenericSocket.swift in Sources */, + 0E3E0F222108A8CC00B371C1 /* PushReply.swift in Sources */, + 0EFEB4912006D7F300F81029 /* TLSBox.m in Sources */, + 0EFEB49D2006D7F300F81029 /* IOInterface.swift in Sources */, + 0EFEB4972006D7F300F81029 /* Authenticator.swift in Sources */, + 0EFEB49F2006D7F300F81029 /* TunnelSettings.swift in Sources */, + 0EFEB49B2006D7F300F81029 /* Packet.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 0E1108A11F77B9E800A92462 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0E17D7F81F730D9F009EE129 /* PIATunnel-iOS */; + targetProxy = 0E1108A01F77B9E800A92462 /* PBXContainerItemProxy */; + }; + 0E1108BC1F77BA0200A92462 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0E1108A81F77B9F900A92462 /* PIATunnelHost */; + targetProxy = 0E1108BB1F77BA0200A92462 /* PBXContainerItemProxy */; + }; + 0E9379CB1F819A4600CE91B6 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0E17D7F81F730D9F009EE129 /* PIATunnel-iOS */; + targetProxy = 0E9379CA1F819A4600CE91B6 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 0E1108AF1F77B9F900A92462 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 0E1108B01F77B9F900A92462 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 0E1108B41F77B9F900A92462 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 0E1108B51F77B9F900A92462 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 0E1108A21F77B9E800A92462 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = PIATunnelTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.privateinternetaccess.apple.PIATunnelTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PIATunnelHost.app/PIATunnelHost"; + }; + name = Debug; + }; + 0E1108A31F77B9E800A92462 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = PIATunnelTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.privateinternetaccess.apple.PIATunnelTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PIATunnelHost.app/PIATunnelHost"; + }; + name = Release; + }; + 0E1108B91F77B9F900A92462 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4FCEB45EA990C21162F820EE /* Pods-PIATunnel-PIATunnelHost.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = PIATunnelHost/PIATunnelHost.entitlements; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 5357M5NW9W; + INFOPLIST_FILE = PIATunnelHost/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.privateinternetaccess.apple.PIATunnelHost; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 0E1108BA1F77B9F900A92462 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 99F9C9FAEB7F699A60047D42 /* Pods-PIATunnel-PIATunnelHost.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = PIATunnelHost/PIATunnelHost.entitlements; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 5357M5NW9W; + INFOPLIST_FILE = PIATunnelHost/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.privateinternetaccess.apple.PIATunnelHost; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 0E17D7FF1F730D9F009EE129 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 0E17D8001F730D9F009EE129 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.0; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 0E17D8021F730D9F009EE129 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4EF01042B93805901ADF144F /* Pods-PIATunnel-PIATunnel-iOS.debug.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + INFOPLIST_FILE = PIATunnel/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.privateinternetaccess.apple.PIATunnel; + PRODUCT_NAME = PIATunnel; + SKIP_INSTALL = YES; + SWIFT_INCLUDE_PATHS = "$(SRCROOT)/PIATunnel/Sources"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 0E17D8031F730D9F009EE129 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5CCF5EF1B897AB67ECDE05D0 /* Pods-PIATunnel-PIATunnel-iOS.release.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + INFOPLIST_FILE = PIATunnel/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.privateinternetaccess.apple.PIATunnel; + PRODUCT_NAME = PIATunnel; + SKIP_INSTALL = YES; + SWIFT_INCLUDE_PATHS = "$(SRCROOT)/PIATunnel/Sources"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 0E3251CB1F95770D00C108D9 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D65AFCC4FA1CB9B85E7B1451 /* Pods-PIATunnel-PIATunnel-macOS.debug.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = PIATunnel/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.privateinternetaccess.apple.PIATunnel; + PRODUCT_NAME = PIATunnel; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_INCLUDE_PATHS = "$(SRCROOT)/PIATunnel/Sources"; + }; + name = Debug; + }; + 0E3251CC1F95770D00C108D9 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 889C502914303CDCADF8C2B9 /* Pods-PIATunnel-PIATunnel-macOS.release.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = PIATunnel/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.privateinternetaccess.apple.PIATunnel; + PRODUCT_NAME = PIATunnel; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_INCLUDE_PATHS = "$(SRCROOT)/PIATunnel/Sources"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 0E1108A41F77B9E800A92462 /* Build configuration list for PBXNativeTarget "PIATunnelTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0E1108A21F77B9E800A92462 /* Debug */, + 0E1108A31F77B9E800A92462 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 0E1108B81F77B9F900A92462 /* Build configuration list for PBXNativeTarget "PIATunnelHost" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0E1108B91F77B9F900A92462 /* Debug */, + 0E1108BA1F77B9F900A92462 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 0E17D7F31F730D9F009EE129 /* Build configuration list for PBXProject "PIATunnel" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0E17D7FF1F730D9F009EE129 /* Debug */, + 0E17D8001F730D9F009EE129 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 0E17D8011F730D9F009EE129 /* Build configuration list for PBXNativeTarget "PIATunnel-iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0E17D8021F730D9F009EE129 /* Debug */, + 0E17D8031F730D9F009EE129 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 0E3251CA1F95770D00C108D9 /* Build configuration list for PBXNativeTarget "PIATunnel-macOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0E3251CB1F95770D00C108D9 /* Debug */, + 0E3251CC1F95770D00C108D9 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 0E17D7F01F730D9F009EE129 /* Project object */; +} diff --git a/PIATunnel.xcodeproj/xcshareddata/xcschemes/PIATunnel-iOS.xcscheme b/PIATunnel.xcodeproj/xcshareddata/xcschemes/PIATunnel-iOS.xcscheme new file mode 100644 index 0000000..ffb8869 --- /dev/null +++ b/PIATunnel.xcodeproj/xcshareddata/xcschemes/PIATunnel-iOS.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PIATunnel.xcodeproj/xcshareddata/xcschemes/PIATunnel-macOS.xcscheme b/PIATunnel.xcodeproj/xcshareddata/xcschemes/PIATunnel-macOS.xcscheme new file mode 100644 index 0000000..bd4e83b --- /dev/null +++ b/PIATunnel.xcodeproj/xcshareddata/xcschemes/PIATunnel-macOS.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PIATunnel.xcodeproj/xcshareddata/xcschemes/PIATunnelHost.xcscheme b/PIATunnel.xcodeproj/xcshareddata/xcschemes/PIATunnelHost.xcscheme new file mode 100644 index 0000000..c90e370 --- /dev/null +++ b/PIATunnel.xcodeproj/xcshareddata/xcschemes/PIATunnelHost.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PIATunnel.xcworkspace/contents.xcworkspacedata b/PIATunnel.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..ea3ba69 --- /dev/null +++ b/PIATunnel.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/PIATunnel.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/PIATunnel.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/PIATunnel.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/PIATunnel/Info.plist b/PIATunnel/Info.plist new file mode 100644 index 0000000..1007fd9 --- /dev/null +++ b/PIATunnel/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/PIATunnel/Resources/AppExtension/PIA-ECC-256k1.pem b/PIATunnel/Resources/AppExtension/PIA-ECC-256k1.pem new file mode 100644 index 0000000..a109f66 --- /dev/null +++ b/PIATunnel/Resources/AppExtension/PIA-ECC-256k1.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEHTCCA8KgAwIBAgIJALJnZBxsuxbRMAoGCCqGSM49BAMEMIHoMQswCQYDVQQG +EwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoT +F1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVy +bmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAe +BgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBz +ZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzIxMzRa +Fw0zNDA0MTIxNzIxMzRaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEzAR +BgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNj +ZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEAxMX +UHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJu +ZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVybmV0 +YWNjZXNzLmNvbTBWMBAGByqGSM49AgEGBSuBBAAKA0IABB7ROYGdprNfDX0CHR7F +IuMR8Sv3CVXyTJFqGIM6GvaS8HEHvLXsLRMbEMdiMvqLE+RCMNI82wNKRA61P2Ui +u+SjggFUMIIBUDAdBgNVHQ4EFgQUmTwsDwUw8uyHViBcXsFlH9uWBBwwggEfBgNV +HSMEggEWMIIBEoAUmTwsDwUw8uyHViBcXsFlH9uWBByhge6kgeswgegxCzAJBgNV +BAYTAlVTMQswCQYDVQQIEwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4GA1UE +ChMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUgSW50 +ZXJuZXQgQWNjZXNzMSAwHgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2VzczEg +MB4GA1UEKRMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0BCQEW +IHNlY3VyZUBwcml2YXRlaW50ZXJuZXRhY2Nlc3MuY29tggkAsmdkHGy7FtEwDAYD +VR0TBAUwAwEB/zAKBggqhkjOPQQDBANJADBGAiEA6WWy4+Td9LJ3HNYKzqfvMwvB +Qeq8/d6uWFdJ0gi17DACIQCysjd6+CBR5YcTHxeSkF7IvvbVTO2axvXhbv8fIsQx +Qw== +-----END CERTIFICATE----- diff --git a/PIATunnel/Resources/AppExtension/PIA-ECC-256r1.pem b/PIATunnel/Resources/AppExtension/PIA-ECC-256r1.pem new file mode 100644 index 0000000..6178023 --- /dev/null +++ b/PIATunnel/Resources/AppExtension/PIA-ECC-256r1.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEHjCCA8WgAwIBAgIJANBplv/w3alWMAoGCCqGSM49BAMEMIHoMQswCQYDVQQG +EwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoT +F1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVy +bmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAe +BgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBz +ZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzMwMjNa +Fw0zNDA0MTIxNzMwMjNaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEzAR +BgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNj +ZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEAxMX +UHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJu +ZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVybmV0 +YWNjZXNzLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABL+pqsojiu0lBbw+ +2KpfDWAFUmyLh5ImRPxrJ76lgeY/i0g/q/jyiWK/lQsRXLiG78GO+gWf/k9/WBSb +1SwJ7KSjggFUMIIBUDAdBgNVHQ4EFgQUP3QXfywjHCGBH55HVh5EFNHBll4wggEf +BgNVHSMEggEWMIIBEoAUP3QXfywjHCGBH55HVh5EFNHBll6hge6kgeswgegxCzAJ +BgNVBAYTAlVTMQswCQYDVQQIEwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4G +A1UEChMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUg +SW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2Vz +czEgMB4GA1UEKRMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0B +CQEWIHNlY3VyZUBwcml2YXRlaW50ZXJuZXRhY2Nlc3MuY29tggkA0GmW//DdqVYw +DAYDVR0TBAUwAwEB/zAKBggqhkjOPQQDBANHADBEAiBEAKGWcMmUawABsNg6l5bY +Mt/nr2amk53mHfIrE4gkMAIgWzZRIJ4XzcXy0i4crrPrMIx8CYP8EQfvLI4rsVPg +RP8= +-----END CERTIFICATE----- diff --git a/PIATunnel/Resources/AppExtension/PIA-ECC-521r1.pem b/PIATunnel/Resources/AppExtension/PIA-ECC-521r1.pem new file mode 100644 index 0000000..98ffcf1 --- /dev/null +++ b/PIATunnel/Resources/AppExtension/PIA-ECC-521r1.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEpzCCBAigAwIBAgIJAKbEcZk5BSQwMAoGCCqGSM49BAMEMIHoMQswCQYDVQQG +EwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoT +F1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVy +bmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAe +BgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBz +ZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzMxMTda +Fw0zNDA0MTIxNzMxMTdaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEzAR +BgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNj +ZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEAxMX +UHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJu +ZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVybmV0 +YWNjZXNzLmNvbTCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAEAZpxAANwoBkstZQb +5xGN4qodr52/gWoRTQD5LJePOE9+WdkPmmxf5MD6Ov+VDhZLi2RZ18ACbZ5BDVGF +lt+l76djARDd8ROgrcXUW+zA5LqBSwuV3QeBk3fLcHIhB9GO0NE7DJVTQwnQ+FNa +v16VL+0etX3D1g+ayC5AsH6rxgEFsSafo4IBVDCCAVAwHQYDVR0OBBYEFPisdXMF +uwSE8d6sh50WCAF8yLmuMIIBHwYDVR0jBIIBFjCCARKAFPisdXMFuwSE8d6sh50W +CAF8yLmuoYHupIHrMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNV +BAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNz +MSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJp +dmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQg +QWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNj +ZXNzLmNvbYIJAKbEcZk5BSQwMAwGA1UdEwQFMAMBAf8wCgYIKoZIzj0EAwQDgYwA +MIGIAkIAkeK3axSbFAXKDKcI72eVT55JYZpRV4PYDgbQNSUGsyzT0iBQaiEP15EU +HT4Stz531yJ3j3gm6JuWqDpqmMX4dToCQgH83DbGDvpx97wJtG1i+yg9GXhzmyYM +4RCsSuLgT98WTwZXnoPUyh/Qbgiihnjkg/F6v7vvMi8P6AbTKwixmCyZtA== +-----END CERTIFICATE----- diff --git a/PIATunnel/Resources/AppExtension/PIA-RSA-2048.pem b/PIATunnel/Resources/AppExtension/PIA-RSA-2048.pem new file mode 100644 index 0000000..f1a7d30 --- /dev/null +++ b/PIATunnel/Resources/AppExtension/PIA-RSA-2048.pem @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIIFqzCCBJOgAwIBAgIJAKZ7D5Yv87qDMA0GCSqGSIb3DQEBDQUAMIHoMQswCQYD +VQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNV +BAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIElu +dGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3Mx +IDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkB +FiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzM1 +MThaFw0zNDA0MTIxNzM1MThaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex +EzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQg +QWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UE +AxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50 +ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVy +bmV0YWNjZXNzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPXD +L1L9tX6DGf36liA7UBTy5I869z0UVo3lImfOs/GSiFKPtInlesP65577nd7UNzzX +lH/P/CnFPdBWlLp5ze3HRBCc/Avgr5CdMRkEsySL5GHBZsx6w2cayQ2EcRhVTwWp +cdldeNO+pPr9rIgPrtXqT4SWViTQRBeGM8CDxAyTopTsobjSiYZCF9Ta1gunl0G/ +8Vfp+SXfYCC+ZzWvP+L1pFhPRqzQQ8k+wMZIovObK1s+nlwPaLyayzw9a8sUnvWB +/5rGPdIYnQWPgoNlLN9HpSmsAcw2z8DXI9pIxbr74cb3/HSfuYGOLkRqrOk6h4RC +OfuWoTrZup1uEOn+fw8CAwEAAaOCAVQwggFQMB0GA1UdDgQWBBQv63nQ/pJAt5tL +y8VJcbHe22ZOsjCCAR8GA1UdIwSCARYwggESgBQv63nQ/pJAt5tLy8VJcbHe22ZO +sqGB7qSB6zCB6DELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRMwEQYDVQQHEwpM +b3NBbmdlbGVzMSAwHgYDVQQKExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4G +A1UECxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAMTF1ByaXZhdGUg +SW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQpExdQcml2YXRlIEludGVybmV0IEFjY2Vz +czEvMC0GCSqGSIb3DQEJARYgc2VjdXJlQHByaXZhdGVpbnRlcm5ldGFjY2Vzcy5j +b22CCQCmew+WL/O6gzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4IBAQAn +a5PgrtxfwTumD4+3/SYvwoD66cB8IcK//h1mCzAduU8KgUXocLx7QgJWo9lnZ8xU +ryXvWab2usg4fqk7FPi00bED4f4qVQFVfGfPZIH9QQ7/48bPM9RyfzImZWUCenK3 +7pdw4Bvgoys2rHLHbGen7f28knT2j/cbMxd78tQc20TIObGjo8+ISTRclSTRBtyC +GohseKYpTS9himFERpUgNtefvYHbn70mIOzfOJFTVqfrptf9jXa9N8Mpy3ayfodz +1wiqdteqFXkTYoSDctgKMiZ6GdocK9nMroQipIQtpnwd4yBDWIyC6Bvlkrq5TQUt +YDQ8z9v+DMO6iwyIDRiU +-----END CERTIFICATE----- + diff --git a/PIATunnel/Resources/AppExtension/PIA-RSA-3072.pem b/PIATunnel/Resources/AppExtension/PIA-RSA-3072.pem new file mode 100644 index 0000000..cf7368e --- /dev/null +++ b/PIATunnel/Resources/AppExtension/PIA-RSA-3072.pem @@ -0,0 +1,38 @@ +-----BEGIN CERTIFICATE----- +MIIGqzCCBROgAwIBAgIJAL2eMgp0qeyCMA0GCSqGSIb3DQEBDQUAMIHoMQswCQYD +VQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNV +BAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIElu +dGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3Mx +IDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkB +FiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzM5 +MDZaFw0zNDA0MTIxNzM5MDZaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex +EzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQg +QWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UE +AxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50 +ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVy +bmV0YWNjZXNzLmNvbTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAN+H +UO8UiS16NJl9PY1W3nuRU5+i1eay4aF3anplKB/9jeH9dy1dzMVu2vwfndOpdoAa +RCZde0dGK8piD48zCTO4EJuSDvMD/P+YVzagernhM0t0tSYPmTcEI04h9KDbzFaB +iYQz+wMrb52QyN+cnTm02K/01UeBwifKHqBe1GTV0GGECOTyzRDxUZ5Ro2x2XDhW +/1wxn4olLxpV+17I5Of6SXVCP4aD8qem81ryXSmp1WVlBMPRXS74v2aKMRJoaxV+ +NB5O6lcND6mVLgie1WA3/LFgjY08bRFanfoX4AOj4TQvOHgxrtKKoCueMgQP/Ey1 +7hcCY0cnyyMYZvDT4zR7Rl29wQGV4cPLzC0fyQ/ry4UjzTtMdaIb3zB4iErYMV+d +TqBh3VzkhjYPf/i0YJHGOH1Z3jcNXuvL14H238DF6eKcWZzZzSHg7aQZtjwehTVD +Z5Rt/iqxD5XlmzrpgbHy6jwiLtZZ6tBqckRjVcBzsQnYcA1r3WPTzwjlOBI2mwID +AQABo4IBVDCCAVAwHQYDVR0OBBYEFP17KSHfvxCi6I9MddTzo5YnXU3NMIIBHwYD +VR0jBIIBFjCCARKAFP17KSHfvxCi6I9MddTzo5YnXU3NoYHupIHrMIHoMQswCQYD +VQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNV +BAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIElu +dGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3Mx +IDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkB +FiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbYIJAL2eMgp0qeyCMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQENBQADggGBANXsaYsnegzIcEMQdOMMCuj7 +GHrp5XIyvxQ7H9fp57SPi3dFWZEKcd8N6VdjDpBNHYIwyUc3kSPnnfA03AeKOKDs +sJNV/ebyRTouzZrUgiwFshi0FIralfLPaJouYUaqm7p2uji/m672nDLzy1HsBNNs +HPGJQ5ahXvCbXQSKbRxMIT1AX69iD/+dW/aPjFNikfdtkvg6xuVBgOFmttkC1CL8 ++L+UlQfHlEWq7/USN8Ob6u2TbU2LfPeWtaBJFKLHsejIfGzkWQMp4m/r0h6cain9 +UNhe9IN19GGSm9W1YkZwANmpPL2AZgGYpLu8jFrz2n+zR7cyNENn2j69h1+tiPIz +s4SrEKNBvZDxT6Z7F5z6BHOB/7l+68TbBLiNb2Ahn0pcbqAaY00dYgVGCRY/g6gV +ceaLeH73I0HclfCeMUW/FQpxL83UM8QcOoSe+Z+3YQc87/z7KESTLzcVuB/caZjY +00uYIdq+89LCaymtg4kEe805OO6y9vEqaIEqzpvwUw== +-----END CERTIFICATE----- diff --git a/PIATunnel/Resources/AppExtension/PIA-RSA-4096.pem b/PIATunnel/Resources/AppExtension/PIA-RSA-4096.pem new file mode 100644 index 0000000..82dec69 --- /dev/null +++ b/PIATunnel/Resources/AppExtension/PIA-RSA-4096.pem @@ -0,0 +1,43 @@ +-----BEGIN CERTIFICATE----- +MIIHqzCCBZOgAwIBAgIJAJ0u+vODZJntMA0GCSqGSIb3DQEBDQUAMIHoMQswCQYD +VQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNV +BAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIElu +dGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3Mx +IDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkB +FiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzQw +MzNaFw0zNDA0MTIxNzQwMzNaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex +EzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQg +QWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UE +AxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50 +ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVy +bmV0YWNjZXNzLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALVk +hjumaqBbL8aSgj6xbX1QPTfTd1qHsAZd2B97m8Vw31c/2yQgZNf5qZY0+jOIHULN +De4R9TIvyBEbvnAg/OkPw8n/+ScgYOeH876VUXzjLDBnDb8DLr/+w9oVsuDeFJ9K +V2UFM1OYX0SnkHnrYAN2QLF98ESK4NCSU01h5zkcgmQ+qKSfA9Ny0/UpsKPBFqsQ +25NvjDWFhCpeqCHKUJ4Be27CDbSl7lAkBuHMPHJs8f8xPgAbHRXZOxVCpayZ2SND +fCwsnGWpWFoMGvdMbygngCn6jA/W1VSFOlRlfLuuGe7QFfDwA0jaLCxuWt/BgZyl +p7tAzYKR8lnWmtUCPm4+BtjyVDYtDCiGBD9Z4P13RFWvJHw5aapx/5W/CuvVyI7p +Kwvc2IT+KPxCUhH1XI8ca5RN3C9NoPJJf6qpg4g0rJH3aaWkoMRrYvQ+5PXXYUzj +tRHImghRGd/ydERYoAZXuGSbPkm9Y/p2X8unLcW+F0xpJD98+ZI+tzSsI99Zs5wi +jSUGYr9/j18KHFTMQ8n+1jauc5bCCegN27dPeKXNSZ5riXFL2XX6BkY68y58UaNz +meGMiUL9BOV1iV+PMb7B7PYs7oFLjAhh0EdyvfHkrh/ZV9BEhtFa7yXp8XR0J6vz +1YV9R6DYJmLjOEbhU8N0gc3tZm4Qz39lIIG6w3FDAgMBAAGjggFUMIIBUDAdBgNV +HQ4EFgQUrsRtyWJftjpdRM0+925Y6Cl08SUwggEfBgNVHSMEggEWMIIBEoAUrsRt +yWJftjpdRM0+925Y6Cl08SWhge6kgeswgegxCzAJBgNVBAYTAlVTMQswCQYDVQQI +EwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4GA1UEChMXUHJpdmF0ZSBJbnRl +cm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAw +HgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEKRMXUHJpdmF0 +ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0BCQEWIHNlY3VyZUBwcml2YXRl +aW50ZXJuZXRhY2Nlc3MuY29tggkAnS7684Nkme0wDAYDVR0TBAUwAwEB/zANBgkq +hkiG9w0BAQ0FAAOCAgEAJsfhsPk3r8kLXLxY+v+vHzbr4ufNtqnL9/1Uuf8NrsCt +pXAoyZ0YqfbkWx3NHTZ7OE9ZRhdMP/RqHQE1p4N4Sa1nZKhTKasV6KhHDqSCt/dv +Em89xWm2MVA7nyzQxVlHa9AkcBaemcXEiyT19XdpiXOP4Vhs+J1R5m8zQOxZlV1G +tF9vsXmJqWZpOVPmZ8f35BCsYPvv4yMewnrtAC8PFEK/bOPeYcKN50bol22QYaZu +LfpkHfNiFTnfMh8sl/ablPyNY7DUNiP5DRcMdIwmfGQxR5WEQoHL3yPJ42LkB5zs +6jIm26DGNXfwura/mi105+ENH1CaROtRYwkiHb08U6qLXXJz80mWJkT90nr8Asj3 +5xN2cUppg74nG3YVav/38P48T56hG1NHbYF5uOCske19F6wi9maUoto/3vEr0rnX +JUp2KODmKdvBI7co245lHBABWikk8VfejQSlCtDBXn644ZMtAdoxKNfR2WTFVEwJ +iyd1Fzx0yujuiXDROLhISLQDRjVVAvawrAtLZWYK31bY7KlezPlQnl/D9Asxe85l +8jO5+0LdJ6VyOs/Hd4w52alDW/MFySDZSfQHMTIc30hLBJ8OnCEIvluVQQ2UQvoW ++no177N9L2Y+M9TcTA62ZyMXShHQGeh20rb4kK8f+iFX8NxtdHVSkxMEFSfDDyQ= +-----END CERTIFICATE----- diff --git a/PIATunnel/Sources/AppExtension/ConnectionStrategy.swift b/PIATunnel/Sources/AppExtension/ConnectionStrategy.swift new file mode 100644 index 0000000..1576589 --- /dev/null +++ b/PIATunnel/Sources/AppExtension/ConnectionStrategy.swift @@ -0,0 +1,126 @@ +// +// ConnectionStrategy.swift +// PIATunnel +// +// Created by Davide De Rosa on 6/18/18. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation +import NetworkExtension +import SwiftyBeaver + +private let log = SwiftyBeaver.self + +class ConnectionStrategy { + private let hostname: String + + private let prefersResolvedAddresses: Bool + + private var resolvedAddresses: [String]? + + private let endpointProtocols: [PIATunnelProvider.EndpointProtocol] + + private var currentProtocolIndex = 0 + + init(hostname: String, configuration: PIATunnelProvider.Configuration) { + precondition(!configuration.prefersResolvedAddresses || !(configuration.resolvedAddresses?.isEmpty ?? true)) + + self.hostname = hostname + prefersResolvedAddresses = configuration.prefersResolvedAddresses + resolvedAddresses = configuration.resolvedAddresses + endpointProtocols = configuration.endpointProtocols + } + + func createSocket( + from provider: NEProvider, + timeout: Int, + preferredAddress: String? = nil, + queue: DispatchQueue, + completionHandler: @escaping (GenericSocket?, Error?) -> Void) { + + // reuse preferred address + if let preferredAddress = preferredAddress { + log.debug("Pick preferred address: \(preferredAddress)") + let socket = provider.createSocket(to: preferredAddress, protocol: currentProtocol()) + completionHandler(socket, nil) + return + } + + // use any resolved address + if prefersResolvedAddresses, let resolvedAddress = anyResolvedAddress() { + log.debug("Pick resolved address: \(resolvedAddress)") + let socket = provider.createSocket(to: resolvedAddress, protocol: currentProtocol()) + completionHandler(socket, nil) + return + } + + // fall back to DNS + log.debug("DNS resolve hostname: \(hostname)") + DNSResolver.resolve(hostname, timeout: timeout, queue: queue) { (addresses, error) in + + // refresh resolved addresses + if let resolved = addresses, !resolved.isEmpty { + self.resolvedAddresses = resolved + + log.debug("DNS resolved addresses: \(resolved)") + } else { + log.error("DNS resolution failed!") + } + + guard let targetAddress = self.resolvedAddress(from: addresses) else { + log.error("No resolved or fallback address available") + completionHandler(nil, PIATunnelProvider.ProviderError.dnsFailure) + return + } + + let socket = provider.createSocket(to: targetAddress, protocol: self.currentProtocol()) + completionHandler(socket, nil) + } + } + + func tryNextProtocol() -> Bool { + let next = currentProtocolIndex + 1 + guard next < endpointProtocols.count else { + log.debug("No more protocols available") + return false + } + currentProtocolIndex = next + log.debug("Fall back to next protocol: \(currentProtocol())") + return true + } + + private func currentProtocol() -> PIATunnelProvider.EndpointProtocol { + return endpointProtocols[currentProtocolIndex] + } + + private func resolvedAddress(from addresses: [String]?) -> String? { + guard let resolved = addresses, !resolved.isEmpty else { + return anyResolvedAddress() + } + return resolved[0] + } + + private func anyResolvedAddress() -> String? { + guard let addresses = resolvedAddresses, !addresses.isEmpty else { + return nil + } + let n = Int(arc4random() % UInt32(addresses.count)) + return addresses[n] + } +} + +private extension NEProvider { + func createSocket(to address: String, protocol endpointProtocol: PIATunnelProvider.EndpointProtocol) -> GenericSocket { + let endpoint = NWHostEndpoint(hostname: address, port: "\(endpointProtocol.port)") + switch endpointProtocol.socketType { + case .udp: + let impl = createUDPSession(to: endpoint, from: nil) + return NEUDPInterface(impl: impl, communicationType: endpointProtocol.communicationType) + + case .tcp: + let impl = createTCPConnection(to: endpoint, enableTLS: false, tlsParameters: nil, delegate: nil) + return NETCPInterface(impl: impl, communicationType: endpointProtocol.communicationType) + } + } +} diff --git a/PIATunnel/Sources/AppExtension/DNSResolver.swift b/PIATunnel/Sources/AppExtension/DNSResolver.swift new file mode 100644 index 0000000..ffc2136 --- /dev/null +++ b/PIATunnel/Sources/AppExtension/DNSResolver.swift @@ -0,0 +1,80 @@ +// +// DNSResolver.swift +// PIATunnel +// +// Created by Davide De Rosa on 12/15/17. +// Copyright © 2017 London Trust Media. All rights reserved. +// + +import Foundation + +/// :nodoc: +public class DNSResolver { + private static let queue = DispatchQueue(label: "DNSResolver") + + public static func resolve(_ hostname: String, timeout: Int, queue: DispatchQueue, completionHandler: @escaping ([String]?, Error?) -> Void) { + var pendingHandler: (([String]?, Error?) -> Void)? = completionHandler + let host = CFHostCreateWithName(nil, hostname as CFString).takeRetainedValue() + DNSResolver.queue.async { + CFHostStartInfoResolution(host, .addresses, nil) + guard let handler = pendingHandler else { + return + } + DNSResolver.didResolve(host: host) { (addrs, error) in + queue.async { + handler(addrs, error) + pendingHandler = nil + } + } + } + queue.asyncAfter(deadline: .now() + .milliseconds(timeout)) { + guard let handler = pendingHandler else { + return + } + CFHostCancelInfoResolution(host, .addresses) + handler(nil, nil) + pendingHandler = nil + } + } + + private static func didResolve(host: CFHost, completionHandler: @escaping ([String]?, Error?) -> Void) { + var success: DarwinBoolean = false + guard let rawAddresses = CFHostGetAddressing(host, &success)?.takeUnretainedValue() as Array? else { + completionHandler(nil, nil) + return + } + + var ipAddresses: [String] = [] + for case var rawAddress as Data in rawAddresses { + var ipAddress = [CChar](repeating: 0, count: Int(NI_MAXHOST)) + let result = rawAddress.withUnsafeBytes { (addr: UnsafePointer) in + return getnameinfo( + addr, + socklen_t(rawAddress.count), + &ipAddress, + socklen_t(ipAddress.count), + nil, + 0, + NI_NUMERICHOST + ) + } + guard result == 0 else { + continue + } + ipAddresses.append(String(cString: ipAddress)) + } + completionHandler(ipAddresses, nil) + } + + public static func string(fromIPv4 ipv4: UInt32) -> String { + let a = UInt8(ipv4 & UInt32(0xff)) + let b = UInt8((ipv4 >> 8) & UInt32(0xff)) + let c = UInt8((ipv4 >> 16) & UInt32(0xff)) + let d = UInt8((ipv4 >> 24) & UInt32(0xff)) + + return "\(a).\(b).\(c).\(d)" + } + + private init() { + } +} diff --git a/PIATunnel/Sources/AppExtension/GenericSocket.swift b/PIATunnel/Sources/AppExtension/GenericSocket.swift new file mode 100644 index 0000000..a1df045 --- /dev/null +++ b/PIATunnel/Sources/AppExtension/GenericSocket.swift @@ -0,0 +1,43 @@ +// +// GenericSocket.swift +// PIATunnel +// +// Created by Davide De Rosa on 4/16/18. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation + +protocol LinkProducer { + func link() -> LinkInterface +} + +protocol GenericSocketDelegate: class { + func socketDidTimeout(_ socket: GenericSocket) + + func socketShouldChangeProtocol(_ socket: GenericSocket) + + func socketDidBecomeActive(_ socket: GenericSocket) + + func socket(_ socket: GenericSocket, didShutdownWithFailure failure: Bool) + + func socketHasBetterPath(_ socket: GenericSocket) +} + +protocol GenericSocket: LinkProducer { + var remoteAddress: String? { get } + + var hasBetterPath: Bool { get } + + var isShutdown: Bool { get } + + var delegate: GenericSocketDelegate? { get set } + + func observe(queue: DispatchQueue, activeTimeout: Int) + + func unobserve() + + func shutdown() + + func upgraded() -> GenericSocket? +} diff --git a/PIATunnel/Sources/AppExtension/InterfaceObserver.swift b/PIATunnel/Sources/AppExtension/InterfaceObserver.swift new file mode 100644 index 0000000..854210f --- /dev/null +++ b/PIATunnel/Sources/AppExtension/InterfaceObserver.swift @@ -0,0 +1,78 @@ +// +// InterfaceObserver.swift +// PIATunnel +// +// Created by Davide De Rosa on 6/14/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation +import SystemConfiguration.CaptiveNetwork +import SwiftyBeaver + +private let log = SwiftyBeaver.self + +extension NSNotification.Name { + static let __InterfaceObserverDidDetectWifiChange = NSNotification.Name("__InterfaceObserverDidDetectWifiChange") +} + +class InterfaceObserver: NSObject { + private var queue: DispatchQueue? + + private var timer: DispatchSourceTimer? + + private var lastWifiName: String? + + func start(queue: DispatchQueue) { + self.queue = queue + + let timer = DispatchSource.makeTimerSource(flags: DispatchSource.TimerFlags(rawValue: UInt(0)), queue: queue) + timer.schedule(deadline: .now(), repeating: .seconds(2)) + timer.setEventHandler { + self.fireWifiChangeObserver() + } + timer.resume() + + self.timer = timer + } + + func stop() { + timer?.cancel() + timer = nil + queue = nil + } + + private func fireWifiChangeObserver() { + let currentWifiName = currentWifiNetworkName() + if (currentWifiName != lastWifiName) { + if let current = currentWifiName { + log.debug("SSID is now '\(current)'") + if let last = lastWifiName, (current != last) { + queue?.async { + NotificationCenter.default.post(name: .__InterfaceObserverDidDetectWifiChange, object: nil) + } + } + } else { + log.debug("SSID is null") + } + } + lastWifiName = currentWifiName + } + + func currentWifiNetworkName() -> String? { + #if os(iOS) + guard let interfaceNames = CNCopySupportedInterfaces() as? [CFString] else { + return nil + } + for name in interfaceNames { + guard let iface = CNCopyCurrentNetworkInfo(name) as? [String: Any] else { + continue + } + if let ssid = iface["SSID"] as? String { + return ssid + } + } + #endif + return nil + } +} diff --git a/PIATunnel/Sources/AppExtension/Keychain.swift b/PIATunnel/Sources/AppExtension/Keychain.swift new file mode 100644 index 0000000..7dfbde0 --- /dev/null +++ b/PIATunnel/Sources/AppExtension/Keychain.swift @@ -0,0 +1,213 @@ +// +// Keychain.swift +// PIATunnel +// +// Created by Davide De Rosa on 2/12/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation + +/// :nodoc: +public enum KeychainError: Error { + case add + + case notFound + + case typeMismatch +} + +/// :nodoc: +public class Keychain { + private let service: String? + + private let accessGroup: String? + + public init() { + service = Bundle.main.bundleIdentifier + accessGroup = nil + } + + public init(group: String) { + service = nil + accessGroup = group + } + + public init(team: String, group: String) { + service = nil + accessGroup = "\(team).\(group)" + } + + // MARK: Password + + public func set(password: String, for username: String, label: String? = nil) throws { + do { + let currentPassword = try self.password(for: username) + guard password != currentPassword else { + return + } + } catch { + // no pre-existing password + } + + removePassword(for: username) + + var query = [String: Any]() + setScope(query: &query) + query[kSecClass as String] = kSecClassGenericPassword + if let label = label { + query[kSecAttrLabel as String] = label + } + query[kSecAttrAccount as String] = username + query[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock + query[kSecValueData as String] = password.data(using: .utf8) + + let status = SecItemAdd(query as CFDictionary, nil) + guard (status == errSecSuccess) else { + throw KeychainError.add + } + } + + @discardableResult public func removePassword(for username: String) -> Bool { + var query = [String: Any]() + setScope(query: &query) + query[kSecClass as String] = kSecClassGenericPassword + query[kSecAttrAccount as String] = username + + let status = SecItemDelete(query as CFDictionary) + return (status == errSecSuccess) + } + + public func password(for username: String) throws -> String { + var query = [String: Any]() + setScope(query: &query) + query[kSecClass as String] = kSecClassGenericPassword + query[kSecAttrAccount as String] = username + query[kSecMatchLimit as String] = kSecMatchLimitOne + query[kSecReturnData as String] = true + + var result: AnyObject? + let status = SecItemCopyMatching(query as CFDictionary, &result) + guard (status == errSecSuccess) else { + throw KeychainError.notFound + } + guard let data = result as? Data else { + throw KeychainError.notFound + } + guard let password = String(data: data, encoding: .utf8) else { + throw KeychainError.notFound + } + return password + } + + public func passwordReference(for username: String) throws -> Data { + var query = [String: Any]() + setScope(query: &query) + query[kSecClass as String] = kSecClassGenericPassword + query[kSecAttrAccount as String] = username + query[kSecMatchLimit as String] = kSecMatchLimitOne + query[kSecReturnPersistentRef as String] = true + + var result: AnyObject? + let status = SecItemCopyMatching(query as CFDictionary, &result) + guard (status == errSecSuccess) else { + throw KeychainError.notFound + } + guard let data = result as? Data else { + throw KeychainError.notFound + } + return data + } + + public static func password(for username: String, reference: Data) throws -> String { + var query = [String: Any]() + query[kSecClass as String] = kSecClassGenericPassword + query[kSecAttrAccount as String] = username + query[kSecMatchItemList as String] = [reference] + query[kSecReturnData as String] = true + + var result: AnyObject? + let status = SecItemCopyMatching(query as CFDictionary, &result) + guard (status == errSecSuccess) else { + throw KeychainError.notFound + } + guard let data = result as? Data else { + throw KeychainError.notFound + } + guard let password = String(data: data, encoding: .utf8) else { + throw KeychainError.notFound + } + return password + } + + // MARK: Key + + // https://forums.developer.apple.com/thread/13748 + + public func add(publicKeyWithIdentifier identifier: String, data: Data) throws -> SecKey { + var query = [String: Any]() + query[kSecClass as String] = kSecClassKey + query[kSecAttrApplicationTag as String] = identifier + query[kSecAttrKeyType as String] = kSecAttrKeyTypeRSA + query[kSecAttrKeyClass as String] = kSecAttrKeyClassPublic + query[kSecValueData as String] = data + + // XXX + query.removeValue(forKey: kSecAttrService as String) + + let status = SecItemAdd(query as CFDictionary, nil) + guard (status == errSecSuccess) else { + throw KeychainError.add + } + return try publicKey(withIdentifier: identifier) + } + + public func publicKey(withIdentifier identifier: String) throws -> SecKey { + var query = [String: Any]() + query[kSecClass as String] = kSecClassKey + query[kSecAttrApplicationTag as String] = identifier + query[kSecAttrKeyType as String] = kSecAttrKeyTypeRSA + query[kSecAttrKeyClass as String] = kSecAttrKeyClassPublic + query[kSecReturnRef as String] = true + + // XXX + query.removeValue(forKey: kSecAttrService as String) + + var result: AnyObject? + let status = SecItemCopyMatching(query as CFDictionary, &result) + guard (status == errSecSuccess) else { + throw KeychainError.notFound + } +// guard let key = result as? SecKey else { +// throw KeychainError.typeMismatch +// } +// return key + return result as! SecKey + } + + @discardableResult public func remove(publicKeyWithIdentifier identifier: String) -> Bool { + var query = [String: Any]() + query[kSecClass as String] = kSecClassKey + query[kSecAttrApplicationTag as String] = identifier + query[kSecAttrKeyType as String] = kSecAttrKeyTypeRSA + query[kSecAttrKeyClass as String] = kSecAttrKeyClassPublic + + // XXX + query.removeValue(forKey: kSecAttrService as String) + + let status = SecItemDelete(query as CFDictionary) + return (status == errSecSuccess) + } + + // MARK: Helpers + + private func setScope(query: inout [String: Any]) { + if let service = service { + query[kSecAttrService as String] = service + } else if let accessGroup = accessGroup { + query[kSecAttrAccessGroup as String] = accessGroup + } else { + fatalError("No service nor accessGroup set") + } + } +} diff --git a/PIATunnel/Sources/AppExtension/MemoryDestination.swift b/PIATunnel/Sources/AppExtension/MemoryDestination.swift new file mode 100644 index 0000000..9ed3624 --- /dev/null +++ b/PIATunnel/Sources/AppExtension/MemoryDestination.swift @@ -0,0 +1,59 @@ +// +// MemoryDestination.swift +// PIATunnel +// +// Created by Davide De Rosa on 7/26/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation +import SwiftyBeaver + +class MemoryDestination: BaseDestination, CustomStringConvertible { + private let queue = DispatchQueue(label: "MemoryDestination") + + private var buffer: [String] = [] + + var maxLines: Int? + + override init() { + super.init() + asynchronously = false + } + + func start(with existing: [String]) { + queue.sync { + buffer = existing + } + } + + func flush(to: UserDefaults, with key: String) { + queue.sync { + to.set(buffer, forKey: key) + } + to.synchronize() + } + + var description: String { + return queue.sync { + return buffer.joined(separator: "\n") + } + } + + // MARK: BaseDestination + + override func send(_ level: SwiftyBeaver.Level, msg: String, thread: String, file: String, function: String, line: Int, context: Any?) -> String? { + guard let message = super.send(level, msg: msg, thread: thread, file: file, function: function, line: line) else { + return nil + } + queue.sync { + buffer.append(message) + if let maxLines = maxLines { + while (buffer.count > maxLines) { + buffer.removeFirst() + } + } + } + return message + } +} diff --git a/PIATunnel/Sources/AppExtension/PIATunnelProvider+Configuration.swift b/PIATunnel/Sources/AppExtension/PIATunnelProvider+Configuration.swift new file mode 100644 index 0000000..7b2e766 --- /dev/null +++ b/PIATunnel/Sources/AppExtension/PIATunnelProvider+Configuration.swift @@ -0,0 +1,582 @@ +// +// PIATunnelProvider+Configuration.swift +// PIATunnel +// +// Created by Davide De Rosa on 10/23/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation +import NetworkExtension +import SwiftyBeaver + +private let log = SwiftyBeaver.self + +extension PIATunnelProvider { + + // MARK: Cryptography + + /// The available encryption algorithms. + public enum Cipher: String { + + // WARNING: must match OpenSSL algorithm names + + /// AES encryption with 128-bit key size and CBC. + case aes128cbc = "AES-128-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 256-bit key size and GCM. + case aes256gcm = "AES-256-GCM" + } + + /// The available message digest algorithms. + public enum Digest: String { + + // WARNING: must match OpenSSL algorithm names + + /// SHA1 message digest. + case sha1 = "SHA1" + + /// SHA256 message digest. + case sha256 = "SHA256" + } + + /// The available certificates for handshake. + public enum Handshake: String { + + /// Certificate with RSA 2048-bit key. + case rsa2048 = "RSA-2048" + + /// Certificate with RSA 3072-bit key. + case rsa3072 = "RSA-3072" + + /// Certificate with RSA 4096-bit key. + case rsa4096 = "RSA-4096" + + /// Certificate with ECC based on secp256r1 curve. + case ecc256r1 = "ECC-256r1" + + /// Certificate with ECC based on secp256k1 curve. + case ecc256k1 = "ECC-256k1" + + /// Certificate with ECC based on secp521r1 curve. + case ecc521r1 = "ECC-521r1" + + /// Custom certificate. + /// + /// - Seealso: + case custom = "Custom" + + private static let allDigests: [Handshake: String] = [ + .rsa2048: "e2fccccaba712ccc68449b1c56427ac1", + .rsa3072: "2fcdb65712df9db7dae34a1f4a84e32d", + .rsa4096: "ec085790314aa0ad4b01dda7b756a932", + .ecc256r1: "6f0f23a616479329ce54614f76b52254", + .ecc256k1: "80c3b0f34001e4101e34fde9eb1dfa87", + .ecc521r1: "82446e0c80706e33e6e793cebf1b0c59" + ] + + var digest: String? { + return Handshake.allDigests[self] + } + + func write(to url: URL, custom: String? = nil) throws { + precondition((self != .custom) || (custom != nil)) + + // custom certificate? + if self == .custom, let content = custom { + try content.write(to: url, atomically: true, encoding: .ascii) + return + } + + let bundle = Bundle(for: PIATunnelProvider.self) + let certName = "PIA-\(rawValue)" + guard let certUrl = bundle.url(forResource: certName, withExtension: "pem") else { + fatalError("Could not find \(certName) TLS certificate") + } + let content = try String(contentsOf: certUrl) + try content.write(to: url, atomically: true, encoding: .ascii) + } + } +} + +extension PIATunnelProvider { + + // MARK: Configuration + + /// A socket type between UDP (recommended) and TCP. + public enum SocketType: String { + + /// UDP socket type. + case udp = "UDP" + + /// TCP socket type. + case tcp = "TCP" + } + + /// Defines the communication protocol of an endpoint. + public struct EndpointProtocol: Equatable, CustomStringConvertible { + + /// The socket type. + public let socketType: SocketType + + /// The remote port. + public let port: UInt16 + + /// The communication type. + public let communicationType: CommunicationType + + /// :nodoc: + public init(_ socketType: SocketType, _ port: UInt16, _ communicationType: CommunicationType) { + self.socketType = socketType + self.port = port + self.communicationType = communicationType + } + + // MARK: Equatable + + /// :nodoc: + public static func ==(lhs: EndpointProtocol, rhs: EndpointProtocol) -> Bool { + return (lhs.socketType == rhs.socketType) && (lhs.port == rhs.port) && (lhs.communicationType == rhs.communicationType) + } + + // MARK: CustomStringConvertible + + /// :nodoc: + public var description: String { + return "\(socketType.rawValue):\(port)" + } + } + + /// Encapsulates an endpoint along with the authentication credentials. + public struct AuthenticatedEndpoint { + + /// The remote hostname or IP address. + public let hostname: String + + /// The username. + public let username: String + + /// The password. + public let password: String + + /// :nodoc: + public init(hostname: String, username: String, password: String) { + self.hostname = hostname + self.username = username + self.password = password + } + + init(protocolConfiguration: NEVPNProtocol) throws { + guard let hostname = protocolConfiguration.serverAddress else { + throw ProviderError.configuration(field: "protocolConfiguration.serverAddress") + } + guard let username = protocolConfiguration.username else { + throw ProviderError.credentials(field: "protocolConfiguration.username") + } + guard let passwordReference = protocolConfiguration.passwordReference else { + throw ProviderError.credentials(field: "protocolConfiguration.passwordReference") + } + guard let password = try? Keychain.password(for: username, reference: passwordReference) else { + throw ProviderError.credentials(field: "protocolConfiguration.passwordReference (keychain)") + } + + self.hostname = hostname + self.username = username + self.password = password + } + } + + /// The way to create a `PIATunnelProvider.Configuration` object for the tunnel profile. + public struct ConfigurationBuilder { + + // MARK: App group + + /// The name of a shared app group. + public let appGroup: String + + // MARK: Tunnel parameters + + /// Prefers resolved addresses over DNS resolution. `resolvedAddresses` must be set and non-empty. Default is `false`. + /// + /// - Seealso: `fallbackServerAddresses` + public var prefersResolvedAddresses: Bool + + /// Resolved addresses in case DNS fails or `prefersResolvedAddresses` is `true`. + public var resolvedAddresses: [String]? + + /// The accepted communication protocols. Must be non-empty. + public var endpointProtocols: [EndpointProtocol] + + /// The encryption algorithm. + public var cipher: Cipher + + /// The message digest algorithm. + public var digest: Digest + + /// The handshake certificate. + public var handshake: Handshake + + /// The custom CA certificate in PEM format in case `handshake == .custom`. Ignored otherwise. + public var ca: String? + + /// The MTU of the tunnel. + public var mtu: NSNumber + + /// The number of seconds after which a renegotiation is started. Set to `nil` to disable renegotiation. + public var renegotiatesAfterSeconds: Int? + + // MARK: Debugging + + /// Enables debugging. If `true`, then `debugLogKey` is a mandatory field. + public var shouldDebug: Bool + + /// The key in `defaults` where the latest debug log snapshot is stored. Ignored if `shouldDebug` is `false`. + public var debugLogKey: String? + + /// Optional debug log format (SwiftyBeaver format). + public var debugLogFormat: String? + + // MARK: Building + + /** + Default initializer. + + - Parameter appGroup: The name of the app group in which the tunnel extension lives in. + */ + public init(appGroup: String) { + self.appGroup = appGroup + prefersResolvedAddresses = false + resolvedAddresses = nil + endpointProtocols = [EndpointProtocol(.udp, 1194, .pia)] + cipher = .aes128cbc + digest = .sha1 + handshake = .rsa2048 + ca = nil + mtu = 1500 + renegotiatesAfterSeconds = nil + shouldDebug = false + debugLogKey = nil + debugLogFormat = nil + } + + fileprivate init(providerConfiguration: [String: Any]) throws { + let S = Configuration.Keys.self + + guard let appGroup = providerConfiguration[S.appGroup] as? String else { + throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.appGroup)]") + } + guard let cipherAlgorithm = providerConfiguration[S.cipherAlgorithm] as? String, let cipher = Cipher(rawValue: cipherAlgorithm) else { + throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.cipherAlgorithm)]") + } + guard let digestAlgorithm = providerConfiguration[S.digestAlgorithm] as? String, let digest = Digest(rawValue: digestAlgorithm) else { + throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.digestAlgorithm)]") + } + + // fallback to .rsa2048 in < 0.7 configurations (ca/caDigest) + let fallbackHandshake: Handshake = .rsa2048 + var handshake: Handshake = fallbackHandshake + if let handshakeCertificate = providerConfiguration[S.handshakeCertificate] as? String { + handshake = Handshake(rawValue: handshakeCertificate) ?? fallbackHandshake + } + if handshake == .custom { + guard let ca = providerConfiguration[S.ca] as? String else { + throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.ca)]") + } + self.ca = ca + } + + self.appGroup = appGroup + + prefersResolvedAddresses = providerConfiguration[S.prefersResolvedAddresses] as? Bool ?? false + resolvedAddresses = providerConfiguration[S.resolvedAddresses] as? [String] + guard let endpointProtocolsStrings = providerConfiguration[S.endpointProtocols] as? [String], !endpointProtocolsStrings.isEmpty else { + throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.endpointProtocols)] is nil or empty") + } + endpointProtocols = try endpointProtocolsStrings.map { + let components = $0.components(separatedBy: ":") + guard components.count == 3 else { + throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.endpointProtocols)] entries must be in the form 'socketType:port:communicationType'") + } + let socketTypeString = components[0] + let portString = components[1] + let communicationTypeString = components[2] + guard let socketType = SocketType(rawValue: socketTypeString) else { + throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.endpointProtocols)] unrecognized socketType '\(socketTypeString)'") + } + guard let port = UInt16(portString) else { + throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.endpointProtocols)] non-numeric port '\(portString)'") + } + guard let communicationType = CommunicationType(rawValue: communicationTypeString) else { + throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.endpointProtocols)] unrecognized communicationType '\(communicationTypeString)'") + } + return EndpointProtocol(socketType, port, communicationType) + } + + self.cipher = cipher + self.digest = digest + self.handshake = handshake + mtu = providerConfiguration[S.mtu] as? NSNumber ?? 1500 + renegotiatesAfterSeconds = providerConfiguration[S.renegotiatesAfter] as? Int + + shouldDebug = providerConfiguration[S.debug] as? Bool ?? false + if shouldDebug { + guard let debugLogKey = providerConfiguration[S.debugLogKey] as? String else { + throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.debugLogKey)]") + } + self.debugLogKey = debugLogKey + debugLogFormat = providerConfiguration[S.debugLogFormat] as? String + } else { + debugLogKey = nil + } + + guard !prefersResolvedAddresses || !(resolvedAddresses?.isEmpty ?? true) else { + throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.prefersResolvedAddresses)] is true but no [\(S.resolvedAddresses)]") + } + } + + /** + Builds a `PIATunnelProvider.Configuration` object that will connect to the provided endpoint. + + - Returns: A `PIATunnelProvider.Configuration` object with this builder and the additional method parameters. + */ + public func build() -> Configuration { + return Configuration( + appGroup: appGroup, + prefersResolvedAddresses: prefersResolvedAddresses, + resolvedAddresses: resolvedAddresses, + endpointProtocols: endpointProtocols, + cipher: cipher, + digest: digest, + handshake: handshake, + ca: ca, + mtu: mtu, + renegotiatesAfterSeconds: renegotiatesAfterSeconds, + shouldDebug: shouldDebug, + debugLogKey: shouldDebug ? debugLogKey : nil, + debugLogFormat: shouldDebug ? debugLogFormat : nil + ) + } + } + + /// Offers a bridge between the abstract `PIATunnelProvider.ConfigurationBuilder` and a concrete `NETunnelProviderProtocol` profile. + public struct Configuration { + struct Keys { + static let appGroup = "AppGroup" + + static let prefersResolvedAddresses = "PrefersResolvedAddresses" + + static let resolvedAddresses = "ResolvedAddresses" + + static let endpointProtocols = "EndpointProtocols" + + static let cipherAlgorithm = "CipherAlgorithm" + + static let digestAlgorithm = "DigestAlgorithm" + + static let handshakeCertificate = "HandshakeCertificate" + + static let ca = "CA" + + static let mtu = "MTU" + + static let renegotiatesAfter = "RenegotiatesAfter" + + static let debug = "Debug" + + static let debugLogKey = "DebugLogKey" + + static let debugLogFormat = "DebugLogFormat" + } + + /// - Seealso: `PIATunnelProvider.ConfigurationBuilder.appGroup` + public let appGroup: String + + /// - Seealso: `PIATunnelProvider.ConfigurationBuilder.prefersResolvedAddresses` + public let prefersResolvedAddresses: Bool + + /// - Seealso: `PIATunnelProvider.ConfigurationBuilder.resolvedAddresses` + public let resolvedAddresses: [String]? + + /// - Seealso: `PIATunnelProvider.ConfigurationBuilder.endpointProtocols` + public let endpointProtocols: [EndpointProtocol] + + /// - Seealso: `PIATunnelProvider.ConfigurationBuilder.cipher` + public let cipher: Cipher + + /// - Seealso: `PIATunnelProvider.ConfigurationBuilder.digest` + public let digest: Digest + + /// - Seealso: `PIATunnelProvider.ConfigurationBuilder.handshake` + public let handshake: Handshake + + /// - Seealso: `PIATunnelProvider.ConfigurationBuilder.ca` + public let ca: String? + + /// - Seealso: `PIATunnelProvider.ConfigurationBuilder.mtu` + public let mtu: NSNumber + + /// - Seealso: `PIATunnelProvider.ConfigurationBuilder.renegotiatesAfterSeconds` + public let renegotiatesAfterSeconds: Int? + + /// - Seealso: `PIATunnelProvider.ConfigurationBuilder.shouldDebug` + public let shouldDebug: Bool + + /// - Seealso: `PIATunnelProvider.ConfigurationBuilder.debugLogKey` + public let debugLogKey: String? + + /// - Seealso: `PIATunnelProvider.ConfigurationBuilder.debugLogFormat` + public let debugLogFormat: String? + + // MARK: Shortcuts + + var defaults: UserDefaults? { + return UserDefaults(suiteName: appGroup) + } + + var existingLog: [String]? { + guard shouldDebug, let key = debugLogKey else { + return nil + } + return defaults?.array(forKey: key) as? [String] + } + + // MARK: API + + /** + Parses a new `PIATunnelProvider.Configuration` object from a provider configuration map. + + - Parameter from: The map to parse. + - Returns: The parsed `PIATunnelProvider.Configuration` object. + - Throws: `ProviderError.configuration` if `providerConfiguration` is incomplete. + */ + public static func parsed(from providerConfiguration: [String: Any]) throws -> Configuration { + let builder = try ConfigurationBuilder(providerConfiguration: providerConfiguration) + return builder.build() + } + + /** + Returns a dictionary representation of this configuration for use with `NETunnelProviderProtocol.providerConfiguration`. + + - Returns: The dictionary representation of `self`. + */ + public func generatedProviderConfiguration() -> [String: Any] { + let S = Keys.self + + var dict: [String: Any] = [ + S.appGroup: appGroup, + S.prefersResolvedAddresses: prefersResolvedAddresses, + S.endpointProtocols: endpointProtocols.map { + "\($0.socketType.rawValue):\($0.port):\($0.communicationType.rawValue)" + }, + S.cipherAlgorithm: cipher.rawValue, + S.digestAlgorithm: digest.rawValue, + S.handshakeCertificate: handshake.rawValue, + S.mtu: mtu, + S.debug: shouldDebug + ] + if let ca = ca { + dict[S.ca] = ca + } + if let resolvedAddresses = resolvedAddresses { + dict[S.resolvedAddresses] = resolvedAddresses + } + if let renegotiatesAfterSeconds = renegotiatesAfterSeconds { + dict[S.renegotiatesAfter] = renegotiatesAfterSeconds + } + if let debugLogKey = debugLogKey { + dict[S.debugLogKey] = debugLogKey + } + if let debugLogFormat = debugLogFormat { + dict[S.debugLogFormat] = debugLogFormat + } + return dict + } + + /** + Generates a `NETunnelProviderProtocol` from this configuration. + + - Parameter bundleIdentifier: The provider bundle identifier required to locate the tunnel extension. + - Parameter endpoint: The `PIATunnelProvider.AuthenticatedEndpoint` the tunnel will connect to. + - Returns: The generated `NETunnelProviderProtocol` object. + - Throws: `ProviderError.configuration` if unable to store the `endpoint.password` to the `appGroup` keychain. + */ + public func generatedTunnelProtocol(withBundleIdentifier bundleIdentifier: String, endpoint: AuthenticatedEndpoint) throws -> NETunnelProviderProtocol { + let protocolConfiguration = NETunnelProviderProtocol() + + let keychain = Keychain(group: appGroup) + do { + try keychain.set(password: endpoint.password, for: endpoint.username, label: Bundle.main.bundleIdentifier) + } catch _ { + throw ProviderError.credentials(field: "keychain.set()") + } + + protocolConfiguration.providerBundleIdentifier = bundleIdentifier + protocolConfiguration.serverAddress = endpoint.hostname + protocolConfiguration.username = endpoint.username + protocolConfiguration.passwordReference = try? keychain.passwordReference(for: endpoint.username) + protocolConfiguration.providerConfiguration = generatedProviderConfiguration() + + return protocolConfiguration + } + + func print(appVersion: String?) { + if let appVersion = appVersion { + log.info("App version: \(appVersion)") + } + +// log.info("Address: \(endpoint.hostname):\(endpoint.port)") + log.info("Protocols: \(endpointProtocols)") + log.info("Cipher: \(cipher.rawValue)") + log.info("Digest: \(digest.rawValue)") + log.info("Handshake: \(handshake.rawValue)") + log.info("MTU: \(mtu)") + if let renegotiatesAfterSeconds = renegotiatesAfterSeconds { + log.info("Renegotiation: \(renegotiatesAfterSeconds) seconds") + } else { + log.info("Renegotiation: never") + } + log.info("Debug: \(shouldDebug)") + } + } +} + +// MARK: Modification + +extension PIATunnelProvider.Configuration: Equatable { + + /** + Returns a `PIATunnelProvider.ConfigurationBuilder` to use this configuration as a starting point for a new one. + + - Returns: An editable `PIATunnelProvider.ConfigurationBuilder` initialized with this configuration. + */ + public func builder() -> PIATunnelProvider.ConfigurationBuilder { + var builder = PIATunnelProvider.ConfigurationBuilder(appGroup: appGroup) + builder.endpointProtocols = endpointProtocols + builder.cipher = cipher + builder.digest = digest + builder.handshake = handshake + builder.mtu = mtu + builder.renegotiatesAfterSeconds = renegotiatesAfterSeconds + builder.shouldDebug = shouldDebug + builder.debugLogKey = debugLogKey + return builder + } + + /// :nodoc: + public static func ==(lhs: PIATunnelProvider.Configuration, rhs: PIATunnelProvider.Configuration) -> Bool { + return ( + (lhs.endpointProtocols == rhs.endpointProtocols) && + (lhs.cipher == rhs.cipher) && + (lhs.digest == rhs.digest) && + (lhs.handshake == rhs.handshake) && + (lhs.mtu == rhs.mtu) && + (lhs.renegotiatesAfterSeconds == rhs.renegotiatesAfterSeconds) + ) + } +} diff --git a/PIATunnel/Sources/AppExtension/PIATunnelProvider+Interaction.swift b/PIATunnel/Sources/AppExtension/PIATunnelProvider+Interaction.swift new file mode 100644 index 0000000..2f18c00 --- /dev/null +++ b/PIATunnel/Sources/AppExtension/PIATunnelProvider+Interaction.swift @@ -0,0 +1,75 @@ +// +// PIATunnelProvider+Interaction.swift +// PIATunnel +// +// Created by Davide De Rosa on 9/24/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation + +extension PIATunnelProvider { + + // MARK: Interaction + + /// The messages accepted by `PIATunnelProvider`. + public class Message: Equatable { + + /// Requests a snapshot of the latest debug log. Returns the log data decoded from UTF-8. + public static let requestLog = Message(0xff) + + /// Requests the current bytes count from data channel (if connected). + /// + /// Data is 16 bytes: low 8 = received, high 8 = sent. + public static let dataCount = Message(0xfe) + + /// The underlying raw message `Data` to forward to the tunnel via IPC. + public let data: Data + + private init(_ byte: UInt8) { + data = Data(bytes: [byte]) + } + + init(_ data: Data) { + self.data = data + } + + // MARK: Equatable + + /// :nodoc: + public static func ==(lhs: Message, rhs: Message) -> Bool { + return (lhs.data == rhs.data) + } + } + + /// The errors raised by `PIATunnelProvider`. + public enum ProviderError: Error { + + /// The `PIATunnelProvider.Configuration` provided is incorrect or incomplete. + case configuration(field: String) + + /// Credentials are missing or protected (e.g. device locked). + case credentials(field: String) + + /// The pseudo-random number generator could not be initialized. + case prngInitialization + + /// The TLS certificate could not be serialized. + case certificateSerialization + + /// Socket endpoint could not be resolved. + case dnsFailure + + /// No more protocols available to try. + case exhaustedProtocols + + /// Socket failed to reach active state. + case socketActivity + + /// An error occurred at the link level. + case linkError + + /// The current network changed (e.g. switched from WiFi to data connection). + case networkChanged + } +} diff --git a/PIATunnel/Sources/AppExtension/PIATunnelProvider.swift b/PIATunnel/Sources/AppExtension/PIATunnelProvider.swift new file mode 100644 index 0000000..1d1f328 --- /dev/null +++ b/PIATunnel/Sources/AppExtension/PIATunnelProvider.swift @@ -0,0 +1,489 @@ +// +// PIATunnelProvider.swift +// PIATunnel +// +// Created by Davide De Rosa on 2/1/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import NetworkExtension +import SwiftyBeaver + +private let log = SwiftyBeaver.self + +/** + Provides an all-in-one `NEPacketTunnelProvider` implementation for use in a + Packet Tunnel Provider extension both on iOS and macOS. + */ +open class PIATunnelProvider: 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 number of lines in the log. + public var maxLogLines = 1000 + + /// 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 + + // MARK: Constants + + private let memoryLog = MemoryDestination() + + private let observer = InterfaceObserver() + + private let tunnelQueue = DispatchQueue(label: PIATunnelProvider.description()) + + private let prngSeedLength = 64 + + private let caTmpFilename = "CA.pem" + + private var cachesURL: URL { + return URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)[0]) + } + + private var tmpCaURL: URL { + return cachesURL.appendingPathComponent(caTmpFilename) + } + + // MARK: Tunnel configuration + + private var cfg: Configuration! + + private var strategy: ConnectionStrategy! + + // MARK: Internal state + + private var proxy: SessionProxy? + + private var socket: GenericSocket? + + private var linkFailures = 0 + + private var pendingStartHandler: ((Error?) -> Void)? + + private var pendingStopHandler: (() -> Void)? + + // MARK: NEPacketTunnelProvider (XPC queue) + + /// :nodoc: + open override func startTunnel(options: [String : NSObject]? = nil, completionHandler: @escaping (Error?) -> Void) { + let endpoint: AuthenticatedEndpoint + do { + guard let tunnelProtocol = protocolConfiguration as? NETunnelProviderProtocol else { + throw ProviderError.configuration(field: "protocolConfiguration") + } + guard let providerConfiguration = tunnelProtocol.providerConfiguration else { + throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration") + } + try endpoint = AuthenticatedEndpoint(protocolConfiguration: tunnelProtocol) + try cfg = Configuration.parsed(from: providerConfiguration) + } catch let e { + var message: String? + if let te = e as? ProviderError { + switch te { + case .credentials(let field): + message = "Tunnel credentials unavailable: \(field)" + + case .configuration(let field): + message = "Tunnel configuration incomplete: \(field)" + + default: + break + } + } + NSLog(message ?? "Unexpected error in tunnel configuration: \(e)") + completionHandler(e) + return + } + + strategy = ConnectionStrategy(hostname: endpoint.hostname, configuration: cfg) + + if var existingLog = cfg.existingLog { + if let i = existingLog.index(of: logSeparator) { + existingLog.removeFirst(i + 2) + } + + existingLog.append("") + existingLog.append(logSeparator) + existingLog.append("") + memoryLog.start(with: existingLog) + } + + configureLogging( + debug: cfg.shouldDebug, + customFormat: cfg.debugLogFormat + ) + + log.info("Starting tunnel...") + + guard EncryptionProxy.prepareRandomNumberGenerator(seedLength: prngSeedLength) else { + completionHandler(ProviderError.prngInitialization) + return + } + + do { + try cfg.handshake.write(to: tmpCaURL, custom: cfg.ca) + } catch { + completionHandler(ProviderError.certificateSerialization) + return + } + + cfg.print(appVersion: appVersion) + + let caPath = tmpCaURL.path +// log.info("Temporary CA is stored to: \(caPath)") + let encryption = SessionProxy.EncryptionParameters(cfg.cipher.rawValue, cfg.digest.rawValue, caPath, cfg.handshake.digest) + let credentials = SessionProxy.Credentials(endpoint.username, endpoint.password) + + let proxy: SessionProxy + do { + proxy = try SessionProxy(queue: tunnelQueue, encryption: encryption, credentials: credentials) + } catch let e { + completionHandler(e) + return + } + if let renegotiatesAfterSeconds = cfg.renegotiatesAfterSeconds { + proxy.renegotiatesAfter = Double(renegotiatesAfterSeconds) + } + proxy.keepAliveInterval = CoreConfiguration.pingInterval + proxy.delegate = self + self.proxy = proxy + + logCurrentSSID() + + pendingStartHandler = completionHandler + tunnelQueue.sync { + self.connectTunnel() + } + } + + /// :nodoc: + open override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { + pendingStartHandler = nil + log.info("Stopping tunnel...") + + guard let proxy = proxy else { + flushLog() + completionHandler() + return + } + + pendingStopHandler = completionHandler + tunnelQueue.schedule(after: .milliseconds(shutdownTimeout)) { + guard let pendingHandler = self.pendingStopHandler else { + return + } + log.warning("Tunnel not responding after \(self.shutdownTimeout) milliseconds, forcing stop") + self.flushLog() + pendingHandler() + } + tunnelQueue.sync { + proxy.shutdown(error: nil) + } + } + + /// :nodoc: + open override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { + var response: Data? + switch Message(messageData) { + case .requestLog: + response = memoryLog.description.data(using: .utf8) + + case .dataCount: + if let proxy = proxy { + response = Data() + response?.append(UInt64(proxy.bytesIn)) + response?.append(UInt64(proxy.bytesOut)) + } + + default: + break + } + completionHandler?(response) + } + + // MARK: Connection (tunnel queue) + + private func connectTunnel(upgradedSocket: GenericSocket? = nil, preferredAddress: String? = 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, preferredAddress: preferredAddress, queue: tunnelQueue) { (socket, error) in + guard let socket = socket else { + self.disposeTunnel(error: error) + return + } + self.connectTunnel(via: socket) + } + } + + private func connectTunnel(via socket: GenericSocket) { + log.info("Will connect to \(socket)") + + log.debug("Socket type is \(type(of: socket))") + self.socket = socket + self.socket?.delegate = self + self.socket?.observe(queue: tunnelQueue, activeTimeout: socketTimeout) + } + + private func finishTunnelDisconnection(error: Error?) { + if let proxy = proxy, !(reasserting && proxy.canRebindLink()) { + proxy.cleanup() + } + + socket?.delegate = nil + socket?.unobserve() + socket = nil + + if let error = error { + log.error("Tunnel did stop (error: \(error))") + } else { + log.info("Tunnel did stop on request") + } + } + + private func disposeTunnel(error: Error?) { + flushLog() + + // failed to start + if (pendingStartHandler != nil) { + + // + // CAUTION + // + // passing nil to this callback will result in an extremely undesired situation, + // because NetworkExtension would interpret it as "successfully connected to VPN" + // + // if we end up here disposing the tunnel with a pending start handled, we are + // 100% sure that something wrong happened while starting the tunnel. in such + // case, here we then must also make sure that an error object is ALWAYS + // provided, so we do this with optional fallback to .socketActivity + // + // socketActivity makes sense, given that any other error would normally come + // from SessionProxy.stopError. other paths to disposeTunnel() are only coming + // from stopTunnel(), in which case we don't need to feed an error parameter to + // the stop completion handler + // + pendingStartHandler?(error ?? ProviderError.socketActivity) + pendingStartHandler = nil + } + // stopped intentionally + else if (pendingStopHandler != nil) { + pendingStopHandler?() + pendingStopHandler = nil + } + // stopped externally, unrecoverable + else { + let fm = FileManager.default + try? fm.removeItem(at: tmpCaURL) + cancelTunnelWithError(error) + } + } +} + +extension PIATunnelProvider: GenericSocketDelegate { + + // MARK: GenericSocketDelegate (tunnel queue) + + func socketDidTimeout(_ socket: GenericSocket) { + log.debug("Socket timed out waiting for activity, cancelling...") + reasserting = true + socket.shutdown() + } + + func socketShouldChangeProtocol(_ socket: GenericSocket) { + guard strategy.tryNextProtocol() else { + disposeTunnel(error: ProviderError.exhaustedProtocols) + return + } + } + + func socketDidBecomeActive(_ socket: GenericSocket) { + guard let proxy = proxy else { + return + } + if proxy.canRebindLink() { + proxy.rebindLink(socket.link()) + reasserting = false + } else { + proxy.setLink(socket.link()) + } + } + + func socket(_ socket: GenericSocket, didShutdownWithFailure failure: Bool) { + guard let proxy = proxy else { + return + } + + // upgrade available? + let upgradedSocket = socket.upgraded() + + var shutdownError: Error? + if !failure { + shutdownError = proxy.stopError + } else { + shutdownError = proxy.stopError ?? ProviderError.linkError + linkFailures += 1 + log.debug("Link failures so far: \(linkFailures) (max = \(maxLinkFailures))") + } + + // treat negotiation timeout as socket timeout, UDP is connection-less + if proxy.stopError as? SessionError == SessionError.negotiationTimeout { + socketShouldChangeProtocol(socket) + } + + finishTunnelDisconnection(error: shutdownError) + if reasserting { + guard (linkFailures < maxLinkFailures) else { + log.debug("Too many link failures (\(linkFailures)), tunnel will die now") + reasserting = false + disposeTunnel(error: shutdownError) + return + } + log.debug("Disconnection is recoverable, tunnel will reconnect in \(reconnectionDelay) milliseconds...") + tunnelQueue.schedule(after: .milliseconds(reconnectionDelay)) { + self.connectTunnel(upgradedSocket: upgradedSocket, preferredAddress: socket.remoteAddress) + } + return + } + disposeTunnel(error: shutdownError) + } + + func socketHasBetterPath(_ socket: GenericSocket) { + log.debug("Stopping tunnel due to a new better path") + logCurrentSSID() + proxy?.reconnect(error: ProviderError.networkChanged) + } +} + +extension PIATunnelProvider: SessionProxyDelegate { + + // MARK: SessionProxyDelegate (tunnel queue) + + /// :nodoc: + public func sessionDidStart(_ proxy: SessionProxy, remoteAddress: String, address: String, gatewayAddress: String, dnsServers: [String]) { + reasserting = false + + log.info("Session did start") + + log.info("Returned ifconfig parameters:") + log.info("\tTunnel: \(remoteAddress)") + log.info("\tOwn address: \(address)") + log.info("\tGateway: \(gatewayAddress)") + log.info("\tDNS: \(dnsServers)") + + bringNetworkUp(tunnel: remoteAddress, vpn: address, gateway: gatewayAddress, dnsServers: dnsServers) { (error) in + 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") + + proxy.setTunnel(tunnel: NETunnelInterface(impl: self.packetFlow)) + + self.pendingStartHandler?(nil) + self.pendingStartHandler = nil + } + } + + /// :nodoc: + public func sessionDidStop(_: SessionProxy, shouldReconnect: Bool) { + log.info("Session did stop") + + if shouldReconnect { + reasserting = true + } + socket?.shutdown() + } + + private func bringNetworkUp(tunnel: String, vpn: String, gateway: String, dnsServers: [String], completionHandler: @escaping (Error?) -> Void) { + + // route all traffic to VPN + let defaultRoute = NEIPv4Route.default() + defaultRoute.gatewayAddress = gateway + + let ipv4Settings = NEIPv4Settings(addresses: [vpn], subnetMasks: ["255.255.255.255"]) + ipv4Settings.includedRoutes = [defaultRoute] + ipv4Settings.excludedRoutes = [] + + let dnsSettings = NEDNSSettings(servers: dnsServers) + + let newSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: tunnel) + newSettings.ipv4Settings = ipv4Settings + newSettings.dnsSettings = dnsSettings + newSettings.mtu = cfg.mtu + + setTunnelNetworkSettings(newSettings, completionHandler: completionHandler) + } +} + +extension PIATunnelProvider { + + // MARK: Helpers + + private func configureLogging(debug: Bool, customFormat: String? = nil) { + let logLevel: SwiftyBeaver.Level = (debug ? .debug : .info) + let logFormat = customFormat ?? "$Dyyyy-MM-dd HH:mm:ss.SSS$d $L $N.$F:$l - $M" + + if debug { + let console = ConsoleDestination() + console.useNSLog = true + console.minLevel = logLevel + console.format = logFormat + log.addDestination(console) + } + + let memory = memoryLog + memory.minLevel = logLevel + memory.format = logFormat + memory.maxLines = maxLogLines + log.addDestination(memoryLog) + } + + private func flushLog() { + log.debug("Flushing log...") + if let defaults = cfg.defaults, let key = cfg.debugLogKey { + memoryLog.flush(to: defaults, with: key) + } + } + + private func logCurrentSSID() { + if let ssid = observer.currentWifiNetworkName() { + log.debug("Current SSID: '\(ssid)'") + } else { + log.debug("Current SSID: none (disconnected from WiFi)") + } + } + +// private func anyPointer(_ object: Any?) -> UnsafeMutableRawPointer { +// let anyObject = object as AnyObject +// return Unmanaged.passUnretained(anyObject).toOpaque() +// } +} diff --git a/PIATunnel/Sources/AppExtension/Transport/LinkInterface+Strategy.swift b/PIATunnel/Sources/AppExtension/Transport/LinkInterface+Strategy.swift new file mode 100644 index 0000000..5f58c3c --- /dev/null +++ b/PIATunnel/Sources/AppExtension/Transport/LinkInterface+Strategy.swift @@ -0,0 +1,30 @@ +// +// LinkInterface+Strategy.swift +// PIATunnel +// +// Created by Davide De Rosa on 6/28/18. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation + +extension LinkInterface { + func hardReset(with encryption: SessionProxy.EncryptionParameters) -> Data? { + switch communicationType { + case .pia: + guard let caDigest = encryption.caDigest else { + fatalError("PIA communication requires CA MD5 digest") + } + let settings = TunnelSettings( + caMd5Digest: caDigest, + cipherName: encryption.cipherName, + digestName: encryption.digestName + ) + return (try? settings.encodedData()) ?? Data() + + default: + break + } + return nil + } +} diff --git a/PIATunnel/Sources/AppExtension/Transport/NETCPInterface.swift b/PIATunnel/Sources/AppExtension/Transport/NETCPInterface.swift new file mode 100644 index 0000000..2584946 --- /dev/null +++ b/PIATunnel/Sources/AppExtension/Transport/NETCPInterface.swift @@ -0,0 +1,219 @@ +// +// NETCPInterface.swift +// PIATunnel +// +// Created by Davide De Rosa on 4/15/18. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation +import NetworkExtension +import SwiftyBeaver + +private let log = SwiftyBeaver.self + +class NETCPInterface: NSObject, GenericSocket, LinkInterface { + private static var linkContext = 0 + + private let impl: NWTCPConnection + + private let maxPacketSize: Int + + init(impl: NWTCPConnection, communicationType: CommunicationType, maxPacketSize: Int? = nil) { + self.impl = impl + self.communicationType = communicationType + self.maxPacketSize = maxPacketSize ?? (512 * 1024) + isActive = false + isShutdown = false + } + + // MARK: GenericSocket + + private weak var queue: DispatchQueue? + + private var isActive: Bool + + private(set) var isShutdown: Bool + + var remoteAddress: String? { + return (impl.remoteAddress as? NWHostEndpoint)?.hostname + } + + var hasBetterPath: Bool { + return impl.hasBetterPath + } + + weak var delegate: GenericSocketDelegate? + + func observe(queue: DispatchQueue, activeTimeout: Int) { + isActive = false + + self.queue = queue + queue.schedule(after: .milliseconds(activeTimeout)) { [weak self] in + guard let _self = self else { + return + } + guard _self.isActive else { + _self.delegate?.socketShouldChangeProtocol(_self) + _self.delegate?.socketDidTimeout(_self) + return + } + } + impl.addObserver(self, forKeyPath: #keyPath(NWTCPConnection.state), options: [.initial, .new], context: &NETCPInterface.linkContext) + impl.addObserver(self, forKeyPath: #keyPath(NWTCPConnection.hasBetterPath), options: .new, context: &NETCPInterface.linkContext) + } + + func unobserve() { + impl.removeObserver(self, forKeyPath: #keyPath(NWTCPConnection.state), context: &NETCPInterface.linkContext) + impl.removeObserver(self, forKeyPath: #keyPath(NWTCPConnection.hasBetterPath), context: &NETCPInterface.linkContext) + } + + func shutdown() { + impl.writeClose() + impl.cancel() + } + + func upgraded() -> GenericSocket? { + guard impl.hasBetterPath else { + return nil + } + return NETCPInterface(impl: NWTCPConnection(upgradeFor: impl), communicationType: communicationType) + } + + func link() -> LinkInterface { + return self + } + + // MARK: Connection KVO (any queue) + + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + guard (context == &NETCPInterface.linkContext) else { + super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) + return + } +// if let keyPath = keyPath { +// log.debug("KVO change reported (\(anyPointer(object)).\(keyPath))") +// } + queue?.async { + self.observeValueInTunnelQueue(forKeyPath: keyPath, of: object, change: change, context: context) + } + } + + 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))") +// } + guard let impl = object as? NWTCPConnection, (impl == self.impl) else { + log.warning("Discard KVO change from old socket") + return + } + guard let keyPath = keyPath else { + return + } + switch keyPath { + case #keyPath(NWTCPConnection.state): + if let resolvedEndpoint = impl.remoteAddress { + log.debug("Socket state is \(impl.state) (endpoint: \(impl.endpoint) -> \(resolvedEndpoint))") + } else { + log.debug("Socket state is \(impl.state) (endpoint: \(impl.endpoint) -> in progress)") + } + + switch impl.state { + case .connected: + guard !isActive else { + return + } + 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 + } + } + + // MARK: LinkInterface + + let isReliable: Bool = true + + let mtu: Int = .max + + var packetBufferSize: Int { + return maxPacketSize + } + + let communicationType: CommunicationType + + let negotiationTimeout: TimeInterval = 10.0 + + let hardResetTimeout: TimeInterval = 5.0 + + 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 else { + return + } + queue.sync { + guard (error == nil), let data = data else { + handler(nil, error) + return + } + + var newBuffer = buffer + newBuffer.append(contentsOf: data) + let (until, packets) = CommonPacket.parsed(newBuffer) + newBuffer = newBuffer.subdata(in: until.. Void)?) { + let stream = CommonPacket.stream(packet) + impl.write(stream) { (error) in + completionHandler?(error) + } + } + + func writePackets(_ packets: [Data], completionHandler: ((Error?) -> Void)?) { + let stream = CommonPacket.stream(packets) + impl.write(stream) { (error) in + completionHandler?(error) + } + } +} + +extension NETCPInterface { + override var description: String { + guard let hostEndpoint = impl.endpoint as? NWHostEndpoint else { + return impl.endpoint.description + } + return "\(hostEndpoint.hostname):\(hostEndpoint.port)" + } +} diff --git a/PIATunnel/Sources/AppExtension/Transport/NETunnelInterface.swift b/PIATunnel/Sources/AppExtension/Transport/NETunnelInterface.swift new file mode 100644 index 0000000..1e57efd --- /dev/null +++ b/PIATunnel/Sources/AppExtension/Transport/NETunnelInterface.swift @@ -0,0 +1,48 @@ +// +// NETunnelInterface.swift +// PIATunnel +// +// Created by Davide De Rosa on 8/27/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation +import NetworkExtension + +class NETunnelInterface: TunnelInterface { + private weak var impl: NEPacketTunnelFlow? + + var isPersistent: Bool { + return false + } + + init(impl: NEPacketTunnelFlow) { + self.impl = impl + } + + 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 + queue.sync { + self?.loopReadPackets(queue, handler) + handler(packets, nil) + } + } + } + + func writePacket(_ packet: Data, completionHandler: ((Error?) -> Void)?) { + impl?.writePackets([packet], withProtocols: [AF_INET] as [NSNumber]) + completionHandler?(nil) + } + + func writePackets(_ packets: [Data], completionHandler: ((Error?) -> Void)?) { + let protocols = [Int32](repeating: AF_INET, count: packets.count) as [NSNumber] + impl?.writePackets(packets, withProtocols: protocols) + completionHandler?(nil) + } +} diff --git a/PIATunnel/Sources/AppExtension/Transport/NEUDPInterface.swift b/PIATunnel/Sources/AppExtension/Transport/NEUDPInterface.swift new file mode 100644 index 0000000..e740eac --- /dev/null +++ b/PIATunnel/Sources/AppExtension/Transport/NEUDPInterface.swift @@ -0,0 +1,204 @@ +// +// NEUDPInterface.swift +// PIATunnel +// +// Created by Davide De Rosa on 8/27/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation +import NetworkExtension +import SwiftyBeaver + +private let log = SwiftyBeaver.self + +class NEUDPInterface: NSObject, GenericSocket, LinkInterface { + private static var linkContext = 0 + + private let impl: NWUDPSession + + private let maxDatagrams: Int + + init(impl: NWUDPSession, communicationType: CommunicationType, maxDatagrams: Int? = nil) { + self.impl = impl + self.communicationType = communicationType + self.maxDatagrams = maxDatagrams ?? 200 + + isActive = false + isShutdown = false + } + + // MARK: GenericSocket + + private weak var queue: DispatchQueue? + + private var isActive: Bool + + private(set) var isShutdown: Bool + + var remoteAddress: String? { + return (impl.resolvedEndpoint as? NWHostEndpoint)?.hostname + } + + var hasBetterPath: Bool { + return impl.hasBetterPath + } + + weak var delegate: GenericSocketDelegate? + + func observe(queue: DispatchQueue, activeTimeout: Int) { + isActive = false + + self.queue = queue + queue.schedule(after: .milliseconds(activeTimeout)) { [weak self] in + guard let _self = self else { + return + } + guard _self.isActive else { + _self.delegate?.socketDidTimeout(_self) + return + } + } + impl.addObserver(self, forKeyPath: #keyPath(NWUDPSession.state), options: [.initial, .new], context: &NEUDPInterface.linkContext) + impl.addObserver(self, forKeyPath: #keyPath(NWUDPSession.hasBetterPath), options: .new, context: &NEUDPInterface.linkContext) + } + + func unobserve() { + impl.removeObserver(self, forKeyPath: #keyPath(NWUDPSession.state), context: &NEUDPInterface.linkContext) + impl.removeObserver(self, forKeyPath: #keyPath(NWUDPSession.hasBetterPath), context: &NEUDPInterface.linkContext) + } + + func shutdown() { + impl.cancel() + } + + func upgraded() -> GenericSocket? { + guard impl.hasBetterPath else { + return nil + } + return NEUDPInterface(impl: NWUDPSession(upgradeFor: impl), communicationType: communicationType) + } + + func link() -> LinkInterface { + return self + } + + // MARK: Connection KVO (any queue) + + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + guard (context == &NEUDPInterface.linkContext) else { + super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) + return + } +// if let keyPath = keyPath { +// log.debug("KVO change reported (\(anyPointer(object)).\(keyPath))") +// } + queue?.async { + self.observeValueInTunnelQueue(forKeyPath: keyPath, of: object, change: change, context: context) + } + } + + 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))") +// } + guard let impl = object as? NWUDPSession, (impl == self.impl) else { + log.warning("Discard KVO change from old socket") + return + } + guard let keyPath = keyPath else { + return + } + switch keyPath { + case #keyPath(NWUDPSession.state): + if let resolvedEndpoint = impl.resolvedEndpoint { + log.debug("Socket state is \(impl.state) (endpoint: \(impl.endpoint) -> \(resolvedEndpoint))") + } else { + log.debug("Socket state is \(impl.state) (endpoint: \(impl.endpoint) -> in progress)") + } + + switch impl.state { + case .ready: + guard !isActive else { + return + } + 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 + } + } + + // MARK: LinkInterface + + let isReliable: Bool = false + + let mtu: Int = 1000 + + var packetBufferSize: Int { + return maxDatagrams + } + + let communicationType: CommunicationType + + let negotiationTimeout: TimeInterval = 10.0 + + let hardResetTimeout: TimeInterval = 5.0 + + 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 else { + return + } + queue.sync { + handler(packets, error) + } + }, maxDatagrams: maxDatagrams) + } + + func writePacket(_ packet: Data, completionHandler: ((Error?) -> Void)?) { + impl.writeDatagram(packet) { (error) in + completionHandler?(error) + } + } + + func writePackets(_ packets: [Data], completionHandler: ((Error?) -> Void)?) { + impl.writeMultipleDatagrams(packets) { (error) in + completionHandler?(error) + } + } +} + +extension NEUDPInterface { + override var description: String { + guard let hostEndpoint = impl.endpoint as? NWHostEndpoint else { + return impl.endpoint.description + } + return "\(hostEndpoint.hostname):\(hostEndpoint.port)" + } +} diff --git a/PIATunnel/Sources/AppExtension/Transport/NWTCPConnectionState+Description.swift b/PIATunnel/Sources/AppExtension/Transport/NWTCPConnectionState+Description.swift new file mode 100644 index 0000000..8459eff --- /dev/null +++ b/PIATunnel/Sources/AppExtension/Transport/NWTCPConnectionState+Description.swift @@ -0,0 +1,24 @@ +// +// NWTCPConnectionState+Description.swift +// PIATunnel +// +// Created by Davide De Rosa on 4/16/18. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation +import NetworkExtension + +/// :nodoc: +extension NWTCPConnectionState: CustomStringConvertible { + public var description: String { + switch self { + case .cancelled: return "cancelled" + case .connected: return "connected" + case .connecting: return "connecting" + case .disconnected: return "disconnected" + case .invalid: return "invalid" + case .waiting: return "waiting" + } + } +} diff --git a/PIATunnel/Sources/AppExtension/Transport/NWUDPSessionState+Description.swift b/PIATunnel/Sources/AppExtension/Transport/NWUDPSessionState+Description.swift new file mode 100644 index 0000000..d4f454c --- /dev/null +++ b/PIATunnel/Sources/AppExtension/Transport/NWUDPSessionState+Description.swift @@ -0,0 +1,24 @@ +// +// NWUDPSessionState+Description.swift +// PIATunnel +// +// Created by Davide De Rosa on 9/24/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation +import NetworkExtension + +/// :nodoc: +extension NWUDPSessionState: CustomStringConvertible { + public var description: String { + switch self { + case .cancelled: return "cancelled" + case .failed: return "failed" + case .invalid: return "invalid" + case .preparing: return "preparing" + case .ready: return "ready" + case .waiting: return "waiting" + } + } +} diff --git a/PIATunnel/Sources/AppExtension/Utils.swift b/PIATunnel/Sources/AppExtension/Utils.swift new file mode 100644 index 0000000..57a2db9 --- /dev/null +++ b/PIATunnel/Sources/AppExtension/Utils.swift @@ -0,0 +1,15 @@ +// +// Utils.swift +// PIATunnel +// +// Created by Davide De Rosa on 5/23/18. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation + +extension DispatchQueue { + func schedule(after: DispatchTimeInterval, block: @escaping () -> Void) { + asyncAfter(deadline: .now() + after, execute: block) + } +} diff --git a/PIATunnel/Sources/Core/Allocation.h b/PIATunnel/Sources/Core/Allocation.h new file mode 100644 index 0000000..61fc539 --- /dev/null +++ b/PIATunnel/Sources/Core/Allocation.h @@ -0,0 +1,13 @@ +// +// Allocation.h +// PIATunnel +// +// Created by Davide De Rosa on 5/5/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +#import + +void *allocate_safely(size_t size); + +size_t safe_crypto_capacity(size_t size, size_t overhead); diff --git a/PIATunnel/Sources/Core/Allocation.m b/PIATunnel/Sources/Core/Allocation.m new file mode 100644 index 0000000..421439d --- /dev/null +++ b/PIATunnel/Sources/Core/Allocation.m @@ -0,0 +1,29 @@ +// +// Allocation.m +// PIATunnel +// +// Created by Davide De Rosa on 5/5/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +#import + +#import "Allocation.h" + +#define MAX_BLOCK_SIZE 16 // AES only, block is 128-bit + +void *allocate_safely(size_t size) { + void *memory = malloc(size); + if (!memory) { +// abort("malloc() call failed") + abort(); + return NULL; + } + return memory; +} + +size_t safe_crypto_capacity(size_t size, size_t overhead) { + + // encryption, byte-alignment, overhead (e.g. IV, digest) + return 2 * size + MAX_BLOCK_SIZE + overhead; +} diff --git a/PIATunnel/Sources/Core/Authenticator.swift b/PIATunnel/Sources/Core/Authenticator.swift new file mode 100644 index 0000000..a5b7eb6 --- /dev/null +++ b/PIATunnel/Sources/Core/Authenticator.swift @@ -0,0 +1,150 @@ +// +// Authenticator.swift +// PIATunnel +// +// Created by Davide De Rosa on 2/9/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation +import SwiftyBeaver +import __PIATunnelNative + +private let log = SwiftyBeaver.self + +fileprivate extension ZeroingData { + fileprivate func appendSized(_ buf: ZeroingData) { + append(Z(UInt16(buf.count).bigEndian)) + append(buf) + } +} + +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? + + let username: ZeroingData + + let password: ZeroingData + + init(_ username: String, _ password: String) throws { + preMaster = try SecureRandom.safeData(length: CoreConfiguration.preMasterLength) + random1 = try SecureRandom.safeData(length: CoreConfiguration.randomLength) + random2 = try SecureRandom.safeData(length: CoreConfiguration.randomLength) + + // XXX: not 100% secure, can't erase input username/password + self.username = Z(username, nullTerminated: true) + self.password = Z(password, nullTerminated: true) + + controlBuffer = Z() + } + + // MARK: Authentication request + + // Ruby: on_tls_connect + func putAuth(into: TLSBox) throws { + let raw = Z(ProtocolMacros.tlsPrefix) + + // local keys + raw.append(preMaster) + raw.append(random1) + raw.append(random2) + + // opts + raw.appendSized(Z(UInt8(0))) + + // credentials + raw.appendSized(username) + raw.appendSized(password) + + // peer info + raw.appendSized(Z(CoreConfiguration.peerInfo)) + + if CoreConfiguration.logsSensitiveData { + log.debug("TLS.auth: Put plaintext (\(raw.count) bytes): \(raw.toHex())") + } 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.randomLength + 2) else { + return false + } + + let prefix = controlBuffer.withOffset(0, count: prefixLength) + guard prefix.isEqual(to: ProtocolMacros.tlsPrefix) else { + throw SessionError.wrongControlDataPrefix + } + + var offset = ProtocolMacros.tlsPrefix.count + + let serverRandom1 = controlBuffer.withOffset(offset, count: CoreConfiguration.randomLength) + offset += CoreConfiguration.randomLength + + let serverRandom2 = controlBuffer.withOffset(offset, count: CoreConfiguration.randomLength) + offset += CoreConfiguration.randomLength + + let serverOptsLength = Int(controlBuffer.networkUInt16Value(fromOffset: offset)) + offset += 2 + + guard controlBuffer.count >= offset + serverOptsLength else { + return false + } + let serverOpts = controlBuffer.withOffset(offset, count: serverOptsLength) + offset += serverOptsLength + + if CoreConfiguration.logsSensitiveData { + log.debug("TLS.auth: Parsed server random: [\(serverRandom1.toHex()), \(serverRandom2.toHex())]") + } else { + log.debug("TLS.auth: Parsed server random") + } + + if let serverOptsString = serverOpts.nullTerminatedString(fromOffset: 0) { + log.debug("TLS.auth: Parsed server opts: \"\(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 + } + messages.append(msg) + offset += msg.count + 1 + } + + controlBuffer.remove(untilOffset: offset) + + return messages + } +} diff --git a/PIATunnel/Sources/Core/CommunicationType.swift b/PIATunnel/Sources/Core/CommunicationType.swift new file mode 100644 index 0000000..79c1808 --- /dev/null +++ b/PIATunnel/Sources/Core/CommunicationType.swift @@ -0,0 +1,19 @@ +// +// CommunicationType.swift +// PIATunnel +// +// Created by Davide De Rosa on 6/28/18. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation + +/// The language spoken over a link. +public enum CommunicationType: String { + + /// PIA-patched OpenVPN server. + case pia + + /// Stock OpenVPN server. + case vanilla +} diff --git a/PIATunnel/Sources/Core/CoreConfiguration.swift b/PIATunnel/Sources/Core/CoreConfiguration.swift new file mode 100644 index 0000000..ba032a4 --- /dev/null +++ b/PIATunnel/Sources/Core/CoreConfiguration.swift @@ -0,0 +1,54 @@ +// +// CoreConfiguration.swift +// PIATunnel +// +// Created by Davide De Rosa on 9/1/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation + +struct CoreConfiguration { + + // MARK: Session + + static let logsSensitiveData = false + + static let usesReplayProtection = true + +// static let usesDataOptimization = true + + static let tickInterval = 0.2 + + static let pingInterval = 10.0 + + static let pingTimeout = 120.0 + + static let retransmissionLimit = 0.1 + + static let softResetDelay = 5.0 + + static let softNegotiationTimeout = 120.0 + + // MARK: Authentication + + static let peerInfo = [ + "IV_VER=2.3.99", + "IV_PROTO=2", + "" + ].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/PIATunnel/Sources/Core/CryptoAEAD.h b/PIATunnel/Sources/Core/CryptoAEAD.h new file mode 100644 index 0000000..498086a --- /dev/null +++ b/PIATunnel/Sources/Core/CryptoAEAD.h @@ -0,0 +1,32 @@ +// +// CryptoAEAD.h +// PIATunnel +// +// Created by Davide De Rosa on 06/07/2018. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +#import + +#import "Encryption.h" +#import "DataPathEncryption.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface CryptoAEAD : NSObject + +@property (nonatomic, assign) int extraLength; + +- (instancetype)initWithCipherName:(nonnull NSString *)cipherName; + +@end + +@interface DataPathCryptoAEAD : NSObject + +@property (nonatomic, assign) uint32_t peerId; + +- (instancetype)initWithCrypto:(nonnull CryptoAEAD *)crypto; + +@end + +NS_ASSUME_NONNULL_END diff --git a/PIATunnel/Sources/Core/CryptoAEAD.m b/PIATunnel/Sources/Core/CryptoAEAD.m new file mode 100644 index 0000000..f521949 --- /dev/null +++ b/PIATunnel/Sources/Core/CryptoAEAD.m @@ -0,0 +1,341 @@ +// +// CryptoAEAD.m +// PIATunnel +// +// Created by Davide De Rosa on 06/07/2018. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +#import +#import +#import + +#import "CryptoAEAD.h" +#import "CryptoMacros.h" +#import "Allocation.h" +#import "Errors.h" + +const NSInteger CryptoAEADTagLength = 16; + +@interface CryptoAEAD () + +@property (nonatomic, unsafe_unretained) const EVP_CIPHER *cipher; +@property (nonatomic, assign) int cipherKeyLength; +@property (nonatomic, assign) int cipherIVLength; // 12 (AD packetId + HMAC key) +@property (nonatomic, assign) int overheadLength; +@property (nonatomic, assign) int extraPacketIdOffset; + +@property (nonatomic, unsafe_unretained) EVP_CIPHER_CTX *cipherCtxEnc; +@property (nonatomic, unsafe_unretained) EVP_CIPHER_CTX *cipherCtxDec; +@property (nonatomic, unsafe_unretained) uint8_t *cipherIVEnc; +@property (nonatomic, unsafe_unretained) uint8_t *cipherIVDec; + +@end + +@implementation CryptoAEAD + +- (instancetype)initWithCipherName:(NSString *)cipherName +{ + NSParameterAssert([[cipherName uppercaseString] hasSuffix:@"GCM"]); + + self = [super init]; + if (self) { + self.cipher = EVP_get_cipherbyname([cipherName cStringUsingEncoding:NSASCIIStringEncoding]); + NSAssert(self.cipher, @"Unknown cipher '%@'", cipherName); + + self.cipherKeyLength = EVP_CIPHER_key_length(self.cipher); + self.cipherIVLength = EVP_CIPHER_iv_length(self.cipher); + self.overheadLength = CryptoAEADTagLength; + self.extraLength = PacketIdLength; + self.extraPacketIdOffset = 0; + + self.cipherCtxEnc = EVP_CIPHER_CTX_new(); + self.cipherCtxDec = EVP_CIPHER_CTX_new(); + self.cipherIVEnc = allocate_safely(self.cipherIVLength); + self.cipherIVDec = allocate_safely(self.cipherIVLength); + } + return self; +} + +- (void)dealloc +{ + EVP_CIPHER_CTX_free(self.cipherCtxEnc); + EVP_CIPHER_CTX_free(self.cipherCtxDec); + bzero(self.cipherIVEnc, self.cipherIVLength); + bzero(self.cipherIVDec, self.cipherIVLength); + free(self.cipherIVEnc); + free(self.cipherIVDec); + + self.cipher = NULL; +} + +#pragma mark Encrypter + +- (void)configureEncryptionWithCipherKey:(ZeroingData *)cipherKey hmacKey:(ZeroingData *)hmacKey +{ + NSParameterAssert(cipherKey.count >= self.cipherKeyLength); + + EVP_CIPHER_CTX_reset(self.cipherCtxEnc); + EVP_CipherInit(self.cipherCtxEnc, self.cipher, cipherKey.bytes, NULL, 1); + + [self prepareIV:self.cipherIVEnc withHMACKey:hmacKey]; +} + +- (NSData *)encryptData:(NSData *)data offset:(NSInteger)offset extra:(nonnull const uint8_t *)extra error:(NSError *__autoreleasing *)error +{ + NSParameterAssert(data); + NSParameterAssert(extra); + + const uint8_t *bytes = data.bytes + offset; + const int length = (int)(data.length - offset); + const int maxOutputSize = (int)safe_crypto_capacity(data.length, self.overheadLength); + + NSMutableData *dest = [[NSMutableData alloc] initWithLength:maxOutputSize]; + NSInteger encryptedLength = INT_MAX; + if (![self encryptBytes:bytes length:length dest:dest.mutableBytes destLength:&encryptedLength extra:extra error:error]) { + return nil; + } + dest.length = encryptedLength; + return dest; +} + +- (BOOL)encryptBytes:(const uint8_t *)bytes length:(NSInteger)length dest:(uint8_t *)dest destLength:(NSInteger *)destLength extra:(nonnull const uint8_t *)extra error:(NSError *__autoreleasing *)error +{ + NSParameterAssert(extra); + + int l1 = 0, l2 = 0; + int x = 0; + int code = 1; + + assert(self.extraLength >= PacketIdLength); + memcpy(self.cipherIVEnc, extra + self.extraPacketIdOffset, PacketIdLength); + + PIA_CRYPTO_TRACK_STATUS(code) EVP_CipherInit(self.cipherCtxEnc, NULL, NULL, self.cipherIVEnc, -1); + PIA_CRYPTO_TRACK_STATUS(code) EVP_CipherUpdate(self.cipherCtxEnc, NULL, &x, extra, self.extraLength); + PIA_CRYPTO_TRACK_STATUS(code) EVP_CipherUpdate(self.cipherCtxEnc, dest + CryptoAEADTagLength, &l1, bytes, (int)length); + PIA_CRYPTO_TRACK_STATUS(code) EVP_CipherFinal(self.cipherCtxEnc, dest + CryptoAEADTagLength + l1, &l2); + PIA_CRYPTO_TRACK_STATUS(code) EVP_CIPHER_CTX_ctrl(self.cipherCtxEnc, EVP_CTRL_GCM_GET_TAG, CryptoAEADTagLength, dest); + + *destLength = CryptoAEADTagLength + l1 + l2; + +// NSLog(@">>> ENC iv: %@", [NSData dataWithBytes:self.cipherIVEnc length:self.cipherIVLength]); +// NSLog(@">>> ENC ad: %@", [NSData dataWithBytes:extra length:self.extraLength]); +// NSLog(@">>> ENC x: %d", x); +// NSLog(@">>> ENC tag: %@", [NSData dataWithBytes:dest length:CryptoAEADTagLength]); +// NSLog(@">>> ENC dest: %@", [NSData dataWithBytes:dest + CryptoAEADTagLength length:*destLength - CryptoAEADTagLength]); + + PIA_CRYPTO_RETURN_STATUS(code) +} + +- (id)dataPathEncrypter +{ + return [[DataPathCryptoAEAD alloc] initWithCrypto:self]; +} + +#pragma mark Decrypter + +- (void)configureDecryptionWithCipherKey:(ZeroingData *)cipherKey hmacKey:(ZeroingData *)hmacKey +{ + NSParameterAssert(cipherKey.count >= self.cipherKeyLength); + + EVP_CIPHER_CTX_reset(self.cipherCtxDec); + EVP_CipherInit(self.cipherCtxDec, self.cipher, cipherKey.bytes, NULL, 0); + + [self prepareIV:self.cipherIVDec withHMACKey:hmacKey]; +} + +- (NSData *)decryptData:(NSData *)data offset:(NSInteger)offset extra:(nonnull const uint8_t *)extra error:(NSError *__autoreleasing *)error +{ + NSParameterAssert(data); + NSParameterAssert(extra); + + const uint8_t *bytes = data.bytes + offset; + const int length = (int)(data.length - offset); + const int maxOutputSize = (int)safe_crypto_capacity(data.length, self.overheadLength); + + NSMutableData *dest = [[NSMutableData alloc] initWithLength:maxOutputSize]; + NSInteger decryptedLength; + if (![self decryptBytes:bytes length:length dest:dest.mutableBytes destLength:&decryptedLength extra:extra error:error]) { + return nil; + } + dest.length = decryptedLength; + return dest; +} + +- (BOOL)decryptBytes:(const uint8_t *)bytes length:(NSInteger)length dest:(uint8_t *)dest destLength:(NSInteger *)destLength extra:(nonnull const uint8_t *)extra error:(NSError *__autoreleasing *)error +{ + NSParameterAssert(extra); + + int l1 = 0, l2 = 0; + int x = 0; + int code = 1; + + assert(self.extraLength >= PacketIdLength); + memcpy(self.cipherIVDec, extra + self.extraPacketIdOffset, PacketIdLength); + + PIA_CRYPTO_TRACK_STATUS(code) EVP_CipherInit(self.cipherCtxDec, NULL, NULL, self.cipherIVDec, -1); + PIA_CRYPTO_TRACK_STATUS(code) EVP_CIPHER_CTX_ctrl(self.cipherCtxDec, EVP_CTRL_GCM_SET_TAG, CryptoAEADTagLength, (uint8_t *)bytes); + PIA_CRYPTO_TRACK_STATUS(code) EVP_CipherUpdate(self.cipherCtxDec, NULL, &x, extra, self.extraLength); + PIA_CRYPTO_TRACK_STATUS(code) EVP_CipherUpdate(self.cipherCtxDec, dest, &l1, bytes + CryptoAEADTagLength, (int)length - CryptoAEADTagLength); + PIA_CRYPTO_TRACK_STATUS(code) EVP_CipherFinal(self.cipherCtxDec, dest + l1, &l2); + + *destLength = l1 + l2; + +// NSLog(@">>> DEC iv: %@", [NSData dataWithBytes:self.cipherIVDec length:self.cipherIVLength]); +// NSLog(@">>> DEC ad: %@", [NSData dataWithBytes:extra length:self.extraLength]); +// NSLog(@">>> DEC x: %d", x); +// NSLog(@">>> DEC tag: %@", [NSData dataWithBytes:bytes length:CryptoAEADTagLength]); +// NSLog(@">>> DEC dest: %@", [NSData dataWithBytes:dest length:*destLength]); + + PIA_CRYPTO_RETURN_STATUS(code) +} + +- (id)dataPathDecrypter +{ + return [[DataPathCryptoAEAD alloc] initWithCrypto:self]; +} + +#pragma mark Helpers + +- (void)prepareIV:(uint8_t *)iv withHMACKey:(ZeroingData *)hmacKey +{ + bzero(iv, PacketIdLength); + memcpy(iv + PacketIdLength, hmacKey.bytes, self.cipherIVLength - PacketIdLength); +} + +@end + +#pragma mark - + +@interface DataPathCryptoAEAD () + +@property (nonatomic, strong) CryptoAEAD *crypto; +@property (nonatomic, assign) int headerLength; +@property (nonatomic, copy) void (^setDataHeader)(uint8_t *, uint8_t); +@property (nonatomic, copy) BOOL (^checkPeerId)(const uint8_t *); + +@end + +@implementation DataPathCryptoAEAD + +- (instancetype)initWithCrypto:(CryptoAEAD *)crypto +{ + if ((self = [super init])) { + self.crypto = crypto; + self.peerId = PacketPeerIdDisabled; + } + return self; +} + +- (int)overheadLength +{ + return self.crypto.overheadLength; +} + +- (void)setPeerId:(uint32_t)peerId +{ + _peerId = peerId & 0xffffff; + + if (_peerId == PacketPeerIdDisabled) { + self.headerLength = 1; + self.crypto.extraLength = PacketIdLength; + self.crypto.extraPacketIdOffset = 0; + self.setDataHeader = ^(uint8_t *to, uint8_t key) { + PacketHeaderSet(to, PacketCodeDataV1, key); + }; + } + else { + self.headerLength = 4; + self.crypto.extraLength = self.headerLength + PacketIdLength; + self.crypto.extraPacketIdOffset = self.headerLength; + self.setDataHeader = ^(uint8_t *to, uint8_t key) { + PacketHeaderSetDataV2(to, key, peerId); + }; + self.checkPeerId = ^BOOL(const uint8_t *ptr) { + return (PacketHeaderGetDataV2PeerId(ptr) == self.peerId); + }; + } +} + +#pragma mark DataPathEncrypter + +- (void)assembleDataPacketWithPacketId:(uint32_t)packetId compression:(uint8_t)compression payload:(NSData *)payload into:(uint8_t *)dest length:(NSInteger *)length +{ + uint8_t *ptr = dest; + *ptr = compression; + ptr += sizeof(uint8_t); + memcpy(ptr, payload.bytes, payload.length); + *length = (int)(ptr - dest + payload.length); +} + +- (NSData *)encryptedDataPacketWithKey:(uint8_t)key packetId:(uint32_t)packetId payload:(const uint8_t *)payload payloadLength:(NSInteger)payloadLength error:(NSError *__autoreleasing *)error +{ + const int capacity = self.headerLength + PacketIdLength + (int)safe_crypto_capacity(payloadLength, self.crypto.overheadLength); + NSMutableData *encryptedPacket = [[NSMutableData alloc] initWithLength:capacity]; + uint8_t *ptr = encryptedPacket.mutableBytes; + NSInteger encryptedPayloadLength = INT_MAX; + + self.setDataHeader(ptr, key); + *(uint32_t *)(ptr + self.headerLength) = htonl(packetId); + + const uint8_t *extra = ptr; // AD = header + peer id + packet id + if (self.peerId == PacketPeerIdDisabled) { + extra += self.headerLength; // AD = packet id only + } + + const BOOL success = [self.crypto encryptBytes:payload + length:payloadLength + dest:(ptr + self.headerLength + PacketIdLength) // skip header and packet id + destLength:&encryptedPayloadLength + extra:extra + error:error]; + + NSAssert(encryptedPayloadLength <= capacity, @"Did not allocate enough bytes for payload"); + + if (!success) { + return nil; + } + + encryptedPacket.length = self.headerLength + PacketIdLength + encryptedPayloadLength; + return encryptedPacket; +} + +#pragma mark DataPathDecrypter + +- (BOOL)decryptDataPacket:(NSData *)packet into:(uint8_t *)dest length:(NSInteger *)length packetId:(uint32_t *)packetId error:(NSError *__autoreleasing *)error +{ + const uint8_t *extra = packet.bytes; // AD = header + peer id + packet id + if (self.peerId == PacketPeerIdDisabled) { + extra += self.headerLength; // AD = packet id only + } + + // skip header + packet id + const BOOL success = [self.crypto decryptBytes:(packet.bytes + self.headerLength + PacketIdLength) + length:(int)(packet.length - (self.headerLength + PacketIdLength)) + dest:dest + destLength:length + extra:extra + error:error]; + if (!success) { + return NO; + } + if (self.checkPeerId && !self.checkPeerId(packet.bytes)) { + if (error) { + *error = PIATunnelErrorWithCode(PIATunnelErrorCodeDataPathPeerIdMismatch); + } + return NO; + } + *packetId = ntohl(*(const uint32_t *)(packet.bytes + self.headerLength)); + return YES; +} + +- (const uint8_t *)parsePayloadWithDataPacket:(const uint8_t *)packet packetLength:(NSInteger)packetLength length:(NSInteger *)length compression:(uint8_t *)compression +{ + const uint8_t *ptr = packet; + *compression = *ptr; + ptr += sizeof(uint8_t); // compression byte + *length = packetLength - (int)(ptr - packet); + return ptr; +} + +@end diff --git a/PIATunnel/Sources/Core/CryptoBox.h b/PIATunnel/Sources/Core/CryptoBox.h new file mode 100644 index 0000000..616f239 --- /dev/null +++ b/PIATunnel/Sources/Core/CryptoBox.h @@ -0,0 +1,43 @@ +// +// CryptoBox.h +// PIATunnel +// +// Created by Davide De Rosa on 2/4/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +#import + +#import "ZeroingData.h" + +@protocol Encrypter; +@protocol Decrypter; + +@interface CryptoBox : NSObject + ++ (BOOL)preparePRNGWithSeed:(nonnull const uint8_t *)seed length:(NSInteger)length; + +- (nonnull instancetype)initWithCipherAlgorithm:(nonnull NSString *)cipherAlgorithm + digestAlgorithm:(nullable NSString *)digestAlgorithm; + +- (BOOL)configureWithCipherEncKey:(nonnull ZeroingData *)cipherEncKey + cipherDecKey:(nonnull ZeroingData *)cipherDecKey + hmacEncKey:(nonnull ZeroingData *)hmacEncKey + hmacDecKey:(nonnull ZeroingData *)hmacDecKey + error:(NSError **)error; + +// WARNING: hmac must be able to hold HMAC result ++ (BOOL)hmacWithDigestName:(nonnull NSString *)digestName + secret:(nonnull const uint8_t *)secret + secretLength:(NSInteger)secretLength + data:(nonnull const uint8_t *)data + dataLength:(NSInteger)dataLength + hmac:(nonnull uint8_t *)hmac + hmacLength:(nonnull NSInteger *)hmacLength + error:(NSError **)error; + +// encrypt/decrypt are mutually thread-safe +- (nonnull id)encrypter; +- (nonnull id)decrypter; + +@end diff --git a/PIATunnel/Sources/Core/CryptoBox.m b/PIATunnel/Sources/Core/CryptoBox.m new file mode 100644 index 0000000..b260def --- /dev/null +++ b/PIATunnel/Sources/Core/CryptoBox.m @@ -0,0 +1,137 @@ +// +// CryptoBox.m +// PIATunnel +// +// Created by Davide De Rosa on 2/4/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +#import +#import +#import + +#import "CryptoBox.h" +#import "CryptoMacros.h" +#import "Allocation.h" +#import "Errors.h" + +#import "CryptoCBC.h" +#import "CryptoAEAD.h" + +@interface CryptoBox () + +@property (nonatomic, strong) NSString *cipherAlgorithm; +@property (nonatomic, strong) NSString *digestAlgorithm; + +@property (nonatomic, strong) id encrypter; +@property (nonatomic, strong) id decrypter; + +@end + +@implementation CryptoBox + ++ (void)initialize +{ +} + ++ (BOOL)preparePRNGWithSeed:(const uint8_t *)seed length:(NSInteger)length +{ + unsigned char x[1]; + // make sure its initialized before seeding + if (RAND_bytes(x, 1) != 1) { + return NO; + } + RAND_seed(seed, (int)length); + return YES; +} + +- (instancetype)initWithCipherAlgorithm:(NSString *)cipherAlgorithm digestAlgorithm:(NSString *)digestAlgorithm +{ + NSParameterAssert(cipherAlgorithm); +// NSParameterAssert(digestAlgorithm); + + if ((self = [super init])) { + self.cipherAlgorithm = [cipherAlgorithm lowercaseString]; + self.digestAlgorithm = [digestAlgorithm lowercaseString]; + } + return self; +} + +- (void)dealloc +{ + self.encrypter = nil; + self.decrypter = nil; +} + +// these keys are coming from the OpenVPN negotiation despite the cipher +- (BOOL)configureWithCipherEncKey:(ZeroingData *)cipherEncKey + cipherDecKey:(ZeroingData *)cipherDecKey + hmacEncKey:(ZeroingData *)hmacEncKey + hmacDecKey:(ZeroingData *)hmacDecKey + error:(NSError *__autoreleasing *)error +{ + NSParameterAssert(cipherEncKey); + NSParameterAssert(cipherDecKey); + NSParameterAssert(hmacEncKey); + NSParameterAssert(hmacDecKey); + + if ([self.cipherAlgorithm hasSuffix:@"-cbc"]) { + if (!self.digestAlgorithm) { + if (error) { + *error = PIATunnelErrorWithCode(PIATunnelErrorCodeCryptoBoxAlgorithm); + } + return NO; + } + CryptoCBC *cbc = [[CryptoCBC alloc] initWithCipherName:self.cipherAlgorithm + digestName:self.digestAlgorithm]; + self.encrypter = cbc; + self.decrypter = cbc; + } + else if ([self.cipherAlgorithm hasSuffix:@"-gcm"]) { + CryptoAEAD *gcm = [[CryptoAEAD alloc] initWithCipherName:self.cipherAlgorithm]; + self.encrypter = gcm; + self.decrypter = gcm; + } + // not supported + else { + if (error) { + *error = PIATunnelErrorWithCode(PIATunnelErrorCodeCryptoBoxAlgorithm); + } + return NO; + } + + [self.encrypter configureEncryptionWithCipherKey:cipherEncKey hmacKey:hmacEncKey]; + [self.decrypter configureDecryptionWithCipherKey:cipherDecKey hmacKey:hmacDecKey]; + + return YES; +} + ++ (BOOL)hmacWithDigestName:(NSString *)digestName + secret:(const uint8_t *)secret + secretLength:(NSInteger)secretLength + data:(const uint8_t *)data + dataLength:(NSInteger)dataLength + hmac:(uint8_t *)hmac + hmacLength:(NSInteger *)hmacLength + error:(NSError **)error +{ + NSParameterAssert(digestName); + NSParameterAssert(secret); + NSParameterAssert(data); + + unsigned int l = 0; + int code = 1; + + HMAC_CTX *ctx = HMAC_CTX_new(); + PIA_CRYPTO_TRACK_STATUS(code) HMAC_CTX_reset(ctx); + PIA_CRYPTO_TRACK_STATUS(code) HMAC_Init_ex(ctx, secret, (int)secretLength, EVP_get_digestbyname([digestName cStringUsingEncoding:NSASCIIStringEncoding]), NULL); + PIA_CRYPTO_TRACK_STATUS(code) HMAC_Update(ctx, data, dataLength); + PIA_CRYPTO_TRACK_STATUS(code) HMAC_Final(ctx, hmac, &l); + HMAC_CTX_free(ctx); + + *hmacLength = l; + + PIA_CRYPTO_RETURN_STATUS(code) +} + +@end diff --git a/PIATunnel/Sources/Core/CryptoCBC.h b/PIATunnel/Sources/Core/CryptoCBC.h new file mode 100644 index 0000000..717b50e --- /dev/null +++ b/PIATunnel/Sources/Core/CryptoCBC.h @@ -0,0 +1,31 @@ +// +// CryptoCBC.h +// PIATunnel +// +// Created by Davide De Rosa on 06/07/2018. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +#import + +#import "Encryption.h" +#import "DataPathEncryption.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface CryptoCBC : NSObject + +- (instancetype)initWithCipherName:(nonnull NSString *)cipherName + digestName:(nonnull NSString *)digestName; + +@end + +@interface DataPathCryptoCBC : NSObject + +@property (nonatomic, assign) uint32_t peerId; + +- (instancetype)initWithCrypto:(nonnull CryptoCBC *)crypto; + +@end + +NS_ASSUME_NONNULL_END diff --git a/PIATunnel/Sources/Core/CryptoCBC.m b/PIATunnel/Sources/Core/CryptoCBC.m new file mode 100644 index 0000000..4004f90 --- /dev/null +++ b/PIATunnel/Sources/Core/CryptoCBC.m @@ -0,0 +1,329 @@ +// +// CryptoCBC.m +// PIATunnel +// +// Created by Davide De Rosa on 06/07/2018. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +#import +#import +#import + +#import "CryptoCBC.h" +#import "CryptoMacros.h" +#import "PacketMacros.h" +#import "Allocation.h" +#import "Errors.h" + +const NSInteger CryptoCBCMaxHMACLength = 100; + +@interface CryptoCBC () + +@property (nonatomic, unsafe_unretained) const EVP_CIPHER *cipher; +@property (nonatomic, unsafe_unretained) const EVP_MD *digest; +@property (nonatomic, assign) int cipherKeyLength; +@property (nonatomic, assign) int cipherIVLength; +@property (nonatomic, assign) int digestLength; +@property (nonatomic, assign) int overheadLength; + +@property (nonatomic, unsafe_unretained) EVP_CIPHER_CTX *cipherCtxEnc; +@property (nonatomic, unsafe_unretained) EVP_CIPHER_CTX *cipherCtxDec; +@property (nonatomic, unsafe_unretained) HMAC_CTX *hmacCtxEnc; +@property (nonatomic, unsafe_unretained) HMAC_CTX *hmacCtxDec; +@property (nonatomic, unsafe_unretained) uint8_t *bufferDecHMAC; + +@end + +@implementation CryptoCBC + +- (instancetype)initWithCipherName:(NSString *)cipherName digestName:(NSString *)digestName +{ + NSParameterAssert([[cipherName uppercaseString] hasSuffix:@"CBC"]); + + self = [super init]; + if (self) { + self.cipher = EVP_get_cipherbyname([cipherName cStringUsingEncoding:NSASCIIStringEncoding]); + NSAssert(self.cipher, @"Unknown cipher '%@'", cipherName); + self.digest = EVP_get_digestbyname([digestName cStringUsingEncoding:NSASCIIStringEncoding]); + NSAssert(self.digest, @"Unknown digest '%@'", digestName); + + self.cipherKeyLength = EVP_CIPHER_key_length(self.cipher); + self.cipherIVLength = EVP_CIPHER_iv_length(self.cipher); + self.digestLength = EVP_MD_size(self.digest); + self.overheadLength = self.cipherIVLength + self.digestLength; + + self.cipherCtxEnc = EVP_CIPHER_CTX_new(); + self.cipherCtxDec = EVP_CIPHER_CTX_new(); + self.hmacCtxEnc = HMAC_CTX_new(); + self.hmacCtxDec = HMAC_CTX_new(); + self.bufferDecHMAC = allocate_safely(CryptoCBCMaxHMACLength); + } + return self; +} + +- (void)dealloc +{ + EVP_CIPHER_CTX_free(self.cipherCtxEnc); + EVP_CIPHER_CTX_free(self.cipherCtxDec); + HMAC_CTX_free(self.hmacCtxEnc); + HMAC_CTX_free(self.hmacCtxDec); + bzero(self.bufferDecHMAC, CryptoCBCMaxHMACLength); + free(self.bufferDecHMAC); + + self.cipher = NULL; + self.digest = NULL; +} + +- (int)extraLength +{ + return 0; +} + +#pragma mark Encrypter + +- (void)configureEncryptionWithCipherKey:(ZeroingData *)cipherKey hmacKey:(ZeroingData *)hmacKey +{ + NSParameterAssert(cipherKey.count >= self.cipherKeyLength); + + EVP_CIPHER_CTX_reset(self.cipherCtxEnc); + EVP_CipherInit(self.cipherCtxEnc, self.cipher, cipherKey.bytes, NULL, 1); + + HMAC_CTX_reset(self.hmacCtxEnc); + HMAC_Init_ex(self.hmacCtxEnc, hmacKey.bytes, self.digestLength, self.digest, NULL); +} + +- (NSData *)encryptData:(NSData *)data offset:(NSInteger)offset extra:(nonnull const uint8_t *)extra error:(NSError *__autoreleasing *)error +{ + NSParameterAssert(data); + + const uint8_t *bytes = data.bytes + offset; + const int length = (int)(data.length - offset); + const int maxOutputSize = (int)safe_crypto_capacity(data.length, self.overheadLength); + + NSMutableData *dest = [[NSMutableData alloc] initWithLength:maxOutputSize]; + NSInteger encryptedLength = INT_MAX; + if (![self encryptBytes:bytes length:length dest:dest.mutableBytes destLength:&encryptedLength extra:extra error:error]) { + return nil; + } + dest.length = encryptedLength; + return dest; +} + +- (BOOL)encryptBytes:(const uint8_t *)bytes length:(NSInteger)length dest:(uint8_t *)dest destLength:(NSInteger *)destLength extra:(nonnull const uint8_t *)extra error:(NSError *__autoreleasing *)error +{ + uint8_t *outIV = dest + self.digestLength; + uint8_t *outEncrypted = dest + self.digestLength + self.cipherIVLength; + int l1 = 0, l2 = 0; + unsigned int l3 = 0; + int code = 1; + + if (RAND_bytes(outIV, self.cipherIVLength) != 1) { + if (error) { + *error = PIATunnelErrorWithCode(PIATunnelErrorCodeCryptoBoxRandomGenerator); + } + return NO; + } + + PIA_CRYPTO_TRACK_STATUS(code) EVP_CipherInit(self.cipherCtxEnc, NULL, NULL, outIV, -1); + PIA_CRYPTO_TRACK_STATUS(code) EVP_CipherUpdate(self.cipherCtxEnc, outEncrypted, &l1, bytes, (int)length); + PIA_CRYPTO_TRACK_STATUS(code) EVP_CipherFinal(self.cipherCtxEnc, outEncrypted + l1, &l2); + + PIA_CRYPTO_TRACK_STATUS(code) HMAC_Init_ex(self.hmacCtxEnc, NULL, 0, NULL, NULL); + PIA_CRYPTO_TRACK_STATUS(code) HMAC_Update(self.hmacCtxEnc, outIV, l1 + l2 + self.cipherIVLength); + PIA_CRYPTO_TRACK_STATUS(code) HMAC_Final(self.hmacCtxEnc, dest, &l3); + + *destLength = l1 + l2 + self.cipherIVLength + self.digestLength; + + PIA_CRYPTO_RETURN_STATUS(code) +} + +- (id)dataPathEncrypter +{ + return [[DataPathCryptoCBC alloc] initWithCrypto:self]; +} + +#pragma mark Decrypter + +- (void)configureDecryptionWithCipherKey:(ZeroingData *)cipherKey hmacKey:(ZeroingData *)hmacKey +{ + NSParameterAssert(cipherKey.count >= self.cipherKeyLength); + + EVP_CIPHER_CTX_reset(self.cipherCtxDec); + EVP_CipherInit(self.cipherCtxDec, self.cipher, cipherKey.bytes, NULL, 0); + + HMAC_CTX_reset(self.hmacCtxDec); + HMAC_Init_ex(self.hmacCtxDec, hmacKey.bytes, self.digestLength, self.digest, NULL); +} + +- (NSData *)decryptData:(NSData *)data offset:(NSInteger)offset extra:(const uint8_t *)extra error:(NSError *__autoreleasing *)error +{ + NSParameterAssert(data); + + const uint8_t *bytes = data.bytes + offset; + const int length = (int)(data.length - offset); + const int maxOutputSize = (int)safe_crypto_capacity(data.length, self.overheadLength); + + NSMutableData *dest = [[NSMutableData alloc] initWithLength:maxOutputSize]; + NSInteger decryptedLength; + if (![self decryptBytes:bytes length:length dest:dest.mutableBytes destLength:&decryptedLength extra:extra error:error]) { + return nil; + } + dest.length = decryptedLength; + return dest; +} + +- (BOOL)decryptBytes:(const uint8_t *)bytes length:(NSInteger)length dest:(uint8_t *)dest destLength:(NSInteger *)destLength extra:(const uint8_t *)extra error:(NSError *__autoreleasing *)error +{ + const uint8_t *iv = bytes + self.digestLength; + const uint8_t *encrypted = bytes + self.digestLength + self.cipherIVLength; + int l1 = 0, l2 = 0; + int code = 1; + + PIA_CRYPTO_TRACK_STATUS(code) HMAC_Init_ex(self.hmacCtxDec, NULL, 0, NULL, NULL); + PIA_CRYPTO_TRACK_STATUS(code) HMAC_Update(self.hmacCtxDec, bytes + self.digestLength, length - self.digestLength); + PIA_CRYPTO_TRACK_STATUS(code) HMAC_Final(self.hmacCtxDec, self.bufferDecHMAC, (unsigned *)&l1); + + if (PIA_CRYPTO_SUCCESS(code) && CRYPTO_memcmp(self.bufferDecHMAC, bytes, self.digestLength) != 0) { + if (error) { + *error = PIATunnelErrorWithCode(PIATunnelErrorCodeCryptoBoxHMAC); + } + return NO; + } + + PIA_CRYPTO_TRACK_STATUS(code) EVP_CipherInit(self.cipherCtxDec, NULL, NULL, iv, -1); + PIA_CRYPTO_TRACK_STATUS(code) EVP_CipherUpdate(self.cipherCtxDec, dest, &l1, encrypted, (int)length - self.digestLength - self.cipherIVLength); + PIA_CRYPTO_TRACK_STATUS(code) EVP_CipherFinal(self.cipherCtxDec, dest + l1, &l2); + + *destLength = l1 + l2; + + PIA_CRYPTO_RETURN_STATUS(code) +} + +- (id)dataPathDecrypter +{ + return [[DataPathCryptoCBC alloc] initWithCrypto:self]; +} + +@end + +#pragma mark - + +@interface DataPathCryptoCBC () + +@property (nonatomic, strong) CryptoCBC *crypto; +@property (nonatomic, assign) int headerLength; +@property (nonatomic, copy) void (^setDataHeader)(uint8_t *, uint8_t); +@property (nonatomic, copy) BOOL (^checkPeerId)(const uint8_t *); + +@end + +@implementation DataPathCryptoCBC + +- (instancetype)initWithCrypto:(CryptoCBC *)crypto +{ + if ((self = [super init])) { + self.crypto = crypto; + self.peerId = PacketPeerIdDisabled; + } + return self; +} + +- (int)overheadLength +{ + return self.crypto.overheadLength; +} + +- (void)setPeerId:(uint32_t)peerId +{ + _peerId = peerId & 0xffffff; + + if (_peerId == PacketPeerIdDisabled) { + self.headerLength = 1; + self.setDataHeader = ^(uint8_t *to, uint8_t key) { + PacketHeaderSet(to, PacketCodeDataV1, key); + }; + } + else { + self.headerLength = 4; + self.setDataHeader = ^(uint8_t *to, uint8_t key) { + PacketHeaderSetDataV2(to, key, peerId); + }; + self.checkPeerId = ^BOOL(const uint8_t *ptr) { + return (PacketHeaderGetDataV2PeerId(ptr) == self.peerId); + }; + } +} + +#pragma mark DataPathEncrypter + +- (void)assembleDataPacketWithPacketId:(uint32_t)packetId compression:(uint8_t)compression payload:(NSData *)payload into:(uint8_t *)dest length:(NSInteger *)length +{ + uint8_t *ptr = dest; + *(uint32_t *)ptr = htonl(packetId); + ptr += sizeof(uint32_t); + *ptr = compression; + ptr += sizeof(uint8_t); + memcpy(ptr, payload.bytes, payload.length); + *length = (int)(ptr - dest + payload.length); +} + +- (NSData *)encryptedDataPacketWithKey:(uint8_t)key packetId:(uint32_t)packetId payload:(const uint8_t *)payload payloadLength:(NSInteger)payloadLength error:(NSError *__autoreleasing *)error +{ + const int capacity = self.headerLength + (int)safe_crypto_capacity(payloadLength, self.crypto.overheadLength); + NSMutableData *encryptedPacket = [[NSMutableData alloc] initWithLength:capacity]; + uint8_t *ptr = encryptedPacket.mutableBytes; + NSInteger encryptedPayloadLength = INT_MAX; + const BOOL success = [self.crypto encryptBytes:payload + length:payloadLength + dest:(ptr + self.headerLength) // skip header byte + destLength:&encryptedPayloadLength + extra:NULL + error:error]; + + NSAssert(encryptedPayloadLength <= capacity, @"Did not allocate enough bytes for payload"); + + if (!success) { + return nil; + } + + self.setDataHeader(ptr, key); + encryptedPacket.length = self.headerLength + encryptedPayloadLength; + return encryptedPacket; +} + +#pragma mark DataPathDecrypter + +- (BOOL)decryptDataPacket:(NSData *)packet into:(uint8_t *)dest length:(NSInteger *)length packetId:(nonnull uint32_t *)packetId error:(NSError *__autoreleasing *)error +{ + // skip header = (code, key) + const BOOL success = [self.crypto decryptBytes:(packet.bytes + self.headerLength) + length:(int)(packet.length - self.headerLength) + dest:dest + destLength:length + extra:NULL + error:error]; + if (!success) { + return NO; + } + if (self.checkPeerId && !self.checkPeerId(packet.bytes)) { + if (error) { + *error = PIATunnelErrorWithCode(PIATunnelErrorCodeDataPathPeerIdMismatch); + } + return NO; + } + *packetId = ntohl(*(uint32_t *)dest); + return YES; +} + +- (const uint8_t *)parsePayloadWithDataPacket:(const uint8_t *)packet packetLength:(NSInteger)packetLength length:(NSInteger *)length compression:(uint8_t *)compression +{ + const uint8_t *ptr = packet; + ptr += sizeof(uint32_t); // packet id + *compression = *ptr; + ptr += sizeof(uint8_t); // compression byte + *length = packetLength - (int)(ptr - packet); + return ptr; +} + +@end diff --git a/PIATunnel/Sources/Core/CryptoMacros.h b/PIATunnel/Sources/Core/CryptoMacros.h new file mode 100644 index 0000000..a68aeda --- /dev/null +++ b/PIATunnel/Sources/Core/CryptoMacros.h @@ -0,0 +1,20 @@ +// +// CryptoMacros.h +// PIATunnel +// +// Created by Davide De Rosa on 06/07/2018. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +#import + +#define PIA_CRYPTO_SUCCESS(ret) (ret > 0) +#define PIA_CRYPTO_TRACK_STATUS(ret) if (ret > 0) ret = +#define PIA_CRYPTO_RETURN_STATUS(ret)\ +if (ret <= 0) {\ + if (error) {\ + *error = PIATunnelErrorWithCode(PIATunnelErrorCodeCryptoBoxEncryption);\ + }\ + return NO;\ +}\ +return YES; diff --git a/PIATunnel/Sources/Core/Data+Manipulation.swift b/PIATunnel/Sources/Core/Data+Manipulation.swift new file mode 100644 index 0000000..dccd727 --- /dev/null +++ b/PIATunnel/Sources/Core/Data+Manipulation.swift @@ -0,0 +1,178 @@ +// +// Data+Manipulation.swift +// PIATunnel +// +// Created by Davide De Rosa on 2/3/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation + +// hex -> Data conversion code from: http://stackoverflow.com/questions/32231926/nsdata-from-hex-string +// Data -> hex conversion code from: http://stackoverflow.com/questions/39075043/how-to-convert-data-to-hex-string-in-swift + +extension UnicodeScalar { + var hexNibble: UInt8 { + let value = self.value + if 48 <= value && value <= 57 { + return UInt8(value - 48) + } + else if 65 <= value && value <= 70 { + return UInt8(value - 55) + } + else if 97 <= value && value <= 102 { + return UInt8(value - 87) + } + fatalError("\(self) not a legal hex nibble") + } +} + +extension Data { + init(hex: String) { + let scalars = hex.unicodeScalars + var bytes = Array(repeating: 0, count: (scalars.count + 1) >> 1) + for (index, scalar) in scalars.enumerated() { + var nibble = scalar.hexNibble + if index & 1 == 0 { + nibble <<= 4 + } + bytes[index >> 1] |= nibble + } + self = Data(bytes: bytes) + } + + func toHex() -> String { + return map { String(format: "%02hhx", $0) }.joined() + } + + mutating func zero() { + resetBytes(in: 0.. String? { + var nullOffset: Int? + for i in from.. UInt16 { + var value: UInt16 = 0 + for i in 0..<2 { + let byte = self[from + i] +// print("byte: \(String(format: "%x", byte))") + value |= (UInt16(byte) << UInt16(8 * i)) + } +// print("value: \(String(format: "%x", value))") + return value + } + + @available(*, deprecated) + func UInt16ValueFromPointers(from: Int) -> UInt16 { + return subdata(in: from..<(from + 2)).withUnsafeBytes { $0.pointee } + } + + @available(*, deprecated) + func UInt16ValueFromReboundPointers(from: Int) -> UInt16 { + let data = subdata(in: from..<(from + 2)) +// print("data: \(data.toHex())") + let value = data.withUnsafeBytes { (bytes: UnsafePointer) -> UInt16 in + bytes.withMemoryRebound(to: UInt16.self, capacity: 1) { + $0.pointee + } + } +// print("value: \(String(format: "%x", value))") + return value + } + + @available(*, deprecated) + func UInt32ValueFromBuffer(from: Int) -> UInt32 { + var value: UInt32 = 0 + for i in 0..<4 { + let byte = self[from + i] +// print("byte: \(String(format: "%x", byte))") + value |= (UInt32(byte) << UInt32(8 * i)) + } +// print("value: \(String(format: "%x", value))") + return value + } + + // best + func UInt32Value(from: Int) -> UInt32 { + return subdata(in: from..<(from + 4)).withUnsafeBytes { $0.pointee } + } + + @available(*, deprecated) + func UInt32ValueFromReboundPointers(from: Int) -> UInt32 { + let data = subdata(in: from..<(from + 4)) +// print("data: \(data.toHex())") + let value = data.withUnsafeBytes { (bytes: UnsafePointer) -> UInt32 in + bytes.withMemoryRebound(to: UInt32.self, capacity: 1) { + $0.pointee + } + } +// print("value: \(String(format: "%x", value))") + return value + } + + func networkUInt16Value(from: Int) -> UInt16 { + return UInt16(bigEndian: subdata(in: from..<(from + 2)).withUnsafeBytes { + $0.pointee + }) + } + + func networkUInt32Value(from: Int) -> UInt32 { + return UInt32(bigEndian: subdata(in: from..<(from + 4)).withUnsafeBytes { + $0.pointee + }) + } +} + +extension Data { + func subdata(offset: Int, count: Int) -> Data { + return subdata(in: offset..<(offset + count)) + } +} + +extension Array where Element == Data { + var flatCount: Int { + return map { $0.count }.reduce(0) { $0 + $1 } + } +} diff --git a/PIATunnel/Sources/Core/DataPath.h b/PIATunnel/Sources/Core/DataPath.h new file mode 100644 index 0000000..97e436d --- /dev/null +++ b/PIATunnel/Sources/Core/DataPath.h @@ -0,0 +1,30 @@ +// +// DataPath.h +// PIATunnel +// +// Created by Davide De Rosa on 3/2/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +#import + +@protocol DataPathEncrypter; +@protocol DataPathDecrypter; + +// send/receive should be mutually thread-safe + +@interface DataPath : NSObject + +@property (nonatomic, assign) uint32_t maxPacketId; + +- (nonnull instancetype)initWithEncrypter:(nonnull id)encrypter + decrypter:(nonnull id)decrypter + maxPackets:(NSInteger)maxPackets + usesReplayProtection:(BOOL)usesReplayProtection; + +- (void)setPeerId:(uint32_t)peerId; // 24-bit, discard most significant byte + +- (NSArray *)encryptPackets:(nonnull NSArray *)packets key:(uint8_t)key error:(NSError **)error; +- (NSArray *)decryptPackets:(nonnull NSArray *)packets keepAlive:(nullable bool *)keepAlive error:(NSError **)error; + +@end diff --git a/PIATunnel/Sources/Core/DataPath.m b/PIATunnel/Sources/Core/DataPath.m new file mode 100644 index 0000000..644fbc4 --- /dev/null +++ b/PIATunnel/Sources/Core/DataPath.m @@ -0,0 +1,236 @@ +// +// DataPath.m +// PIATunnel +// +// Created by Davide De Rosa on 3/2/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +#import + +#import "DataPath.h" +#import "DataPathEncryption.h" +#import "MSS.h" +#import "ReplayProtector.h" +#import "Allocation.h" +#import "Errors.h" + +#define DataPathByteAlignment 16 + +@interface DataPath () + +@property (nonatomic, strong) id encrypter; +@property (nonatomic, strong) id decrypter; +@property (nonatomic, assign) int packetCapacity; + +// outbound -> UDP +@property (nonatomic, strong) NSMutableArray *outPackets; +@property (nonatomic, assign) uint32_t outPacketId; +@property (nonatomic, unsafe_unretained) uint8_t *encBuffer; +@property (nonatomic, assign) int encBufferCapacity; + +// inbound -> TUN +@property (nonatomic, strong) NSMutableArray *inPackets; +@property (nonatomic, strong) NSArray *inProtocols; +@property (nonatomic, unsafe_unretained) uint8_t *decBuffer; +@property (nonatomic, assign) int decBufferCapacity; +@property (nonatomic, strong) ReplayProtector *inReplay; + +@end + +@implementation DataPath + ++ (uint8_t *)alignedPointer:(uint8_t *)pointer +{ + uint8_t *stack = pointer; + uintptr_t addr = (uintptr_t)stack; + if (addr % DataPathByteAlignment != 0) { + addr += DataPathByteAlignment - addr % DataPathByteAlignment; + } + return (uint8_t *)addr; +} + +- (instancetype)initWithEncrypter:(id)encrypter decrypter:(id)decrypter maxPackets:(NSInteger)maxPackets usesReplayProtection:(BOOL)usesReplayProtection +{ + NSParameterAssert(encrypter); + NSParameterAssert(decrypter); + NSParameterAssert(maxPackets > 0); + + if ((self = [super init])) { + self.encrypter = encrypter; + self.decrypter = decrypter; + + self.maxPacketId = UINT32_MAX - 10000; + self.outPackets = [[NSMutableArray alloc] initWithCapacity:maxPackets]; + self.outPacketId = 0; + self.encBufferCapacity = 65000; + self.encBuffer = allocate_safely(self.encBufferCapacity); + + self.inPackets = [[NSMutableArray alloc] initWithCapacity:maxPackets]; + NSMutableArray *protocols = [[NSMutableArray alloc] initWithCapacity:maxPackets]; + for (NSUInteger i = 0; i < maxPackets; ++i) { + [protocols addObject:@(AF_INET)]; + } + self.inProtocols = protocols; + self.decBufferCapacity = 65000; + self.decBuffer = allocate_safely(self.decBufferCapacity); + if (usesReplayProtection) { + self.inReplay = [[ReplayProtector alloc] init]; + } + } + return self; +} + +- (void)dealloc +{ + bzero(self.encBuffer, self.encBufferCapacity); + bzero(self.decBuffer, self.decBufferCapacity); + free(self.encBuffer); + free(self.decBuffer); +} + +- (void)adjustEncBufferToPacketSize:(int)size +{ + const int neededCapacity = DataPathByteAlignment + (int)safe_crypto_capacity(size, self.encrypter.overheadLength); + if (self.encBufferCapacity >= neededCapacity) { + return; + } + bzero(self.encBuffer, self.encBufferCapacity); + free(self.encBuffer); + self.encBufferCapacity = neededCapacity; + self.encBuffer = allocate_safely(self.encBufferCapacity); +} + +- (void)adjustDecBufferToPacketSize:(int)size +{ + const int neededCapacity = DataPathByteAlignment + (int)safe_crypto_capacity(size, self.decrypter.overheadLength); + if (self.decBufferCapacity >= neededCapacity) { + return; + } + bzero(self.decBuffer, self.decBufferCapacity); + free(self.decBuffer); + self.decBufferCapacity = neededCapacity; + self.decBuffer = allocate_safely(self.decBufferCapacity); +} + +- (uint8_t *)encBufferAligned +{ + return [[self class] alignedPointer:self.encBuffer]; +} + +- (uint8_t *)decBufferAligned +{ + return [[self class] alignedPointer:self.decBuffer]; +} + +- (void)setPeerId:(uint32_t)peerId +{ + NSAssert(self.encrypter, @"Setting peer-id to nil encrypter"); + NSAssert(self.decrypter, @"Setting peer-id to nil decrypter"); + + [self.encrypter setPeerId:peerId]; + [self.decrypter setPeerId:peerId]; +} + +#pragma mark DataPath + +- (NSArray *)encryptPackets:(NSArray *)packets key:(uint8_t)key error:(NSError *__autoreleasing *)error +{ + NSAssert(self.encrypter.peerId == self.decrypter.peerId, @"Peer-id mismatch in DataPath encrypter/decrypter"); + + if (self.outPacketId > self.maxPacketId) { + if (error) { + *error = PIATunnelErrorWithCode(PIATunnelErrorCodeDataPathOverflow); + } + return nil; + } + + [self.outPackets removeAllObjects]; + + for (NSData *raw in packets) { + self.outPacketId += 1; + + // may resize encBuffer to hold encrypted payload + [self adjustEncBufferToPacketSize:(int)raw.length]; + + uint8_t *payload = self.encBufferAligned; + NSInteger payloadLength; + [self.encrypter assembleDataPacketWithPacketId:self.outPacketId + compression:DataPacketCompressNone + payload:raw + into:payload + length:&payloadLength]; + MSSFix(payload, payloadLength); + + NSData *encryptedPacket = [self.encrypter encryptedDataPacketWithKey:key + packetId:self.outPacketId + payload:payload + payloadLength:payloadLength + error:error]; + if (!encryptedPacket) { + return nil; + } + + [self.outPackets addObject:encryptedPacket]; + } + + return self.outPackets; +} + +//- (NSArray *)decryptPackets:(NSArray *)packets error:(NSError *__autoreleasing *)error +- (NSArray *)decryptPackets:(NSArray *)packets keepAlive:(bool *)keepAlive error:(NSError *__autoreleasing *)error +{ + NSAssert(self.encrypter.peerId == self.decrypter.peerId, @"Peer-id mismatch in DataPath encrypter/decrypter"); + + [self.inPackets removeAllObjects]; + + for (NSData *encryptedPacket in packets) { + + // may resize decBuffer to encryptedPacket.length + [self adjustDecBufferToPacketSize:(int)encryptedPacket.length]; + + uint8_t *packet = self.decBufferAligned; + NSInteger packetLength = INT_MAX; + uint32_t packetId; + const BOOL success = [self.decrypter decryptDataPacket:encryptedPacket + into:packet + length:&packetLength + packetId:&packetId + error:error]; + if (!success) { + return nil; + } + if (packetId > self.maxPacketId) { + if (error) { + *error = PIATunnelErrorWithCode(PIATunnelErrorCodeDataPathOverflow); + } + return nil; + } + if (self.inReplay && [self.inReplay isReplayedPacketId:packetId]) { + continue; + } + + NSInteger payloadLength; + uint8_t compression; + const uint8_t *payload = [self.decrypter parsePayloadWithDataPacket:packet + packetLength:packetLength + length:&payloadLength + compression:&compression]; + + if ((payloadLength == sizeof(DataPacketPingData)) && !memcmp(payload, DataPacketPingData, payloadLength)) { + if (keepAlive) { + *keepAlive = true; + } + continue; + } + +// MSSFix(payload, payloadLength); + + NSData *raw = [[NSData alloc] initWithBytes:payload length:payloadLength]; + [self.inPackets addObject:raw]; + } + + return self.inPackets; +} + +@end diff --git a/PIATunnel/Sources/Core/DataPathEncryption.h b/PIATunnel/Sources/Core/DataPathEncryption.h new file mode 100644 index 0000000..b48239c --- /dev/null +++ b/PIATunnel/Sources/Core/DataPathEncryption.h @@ -0,0 +1,29 @@ +// +// DataPathEncryption.h +// PIATunnel +// +// Created by Davide De Rosa on 11/07/2018. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +#import + +@protocol DataPathEncrypter + +- (int)overheadLength; +- (uint32_t)peerId; +- (void)setPeerId:(uint32_t)peerId; +- (void)assembleDataPacketWithPacketId:(uint32_t)packetId compression:(uint8_t)compression payload:(NSData *)payload into:(nonnull uint8_t *)dest length:(nonnull NSInteger *)length; +- (NSData *)encryptedDataPacketWithKey:(uint8_t)key packetId:(uint32_t)packetId payload:(const uint8_t *)payload payloadLength:(NSInteger)payloadLength error:(NSError **)error; + +@end + +@protocol DataPathDecrypter + +- (int)overheadLength; +- (uint32_t)peerId; +- (void)setPeerId:(uint32_t)peerId; +- (BOOL)decryptDataPacket:(nonnull NSData *)packet into:(nonnull uint8_t *)dest length:(nonnull NSInteger *)length packetId:(nonnull uint32_t *)packetId error:(NSError **)error; +- (nonnull const uint8_t *)parsePayloadWithDataPacket:(nonnull const uint8_t *)packet packetLength:(NSInteger)packetLength length:(nonnull NSInteger *)length compression:(nonnull uint8_t *)compression; + +@end diff --git a/PIATunnel/Sources/Core/Encryption.h b/PIATunnel/Sources/Core/Encryption.h new file mode 100644 index 0000000..ab87ad6 --- /dev/null +++ b/PIATunnel/Sources/Core/Encryption.h @@ -0,0 +1,42 @@ +// +// Encryption.h +// PIATunnel +// +// Created by Davide De Rosa on 3/3/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +#import + +#import "ZeroingData.h" + +@protocol DataPathEncrypter; +@protocol DataPathDecrypter; + +// WARNING: dest must be able to hold ciphertext +@protocol Encrypter + +- (void)configureEncryptionWithCipherKey:(nonnull ZeroingData *)cipherKey hmacKey:(nonnull ZeroingData *)hmacKey; +- (int)overheadLength; +- (int)extraLength; + +- (NSData *)encryptData:(nonnull NSData *)data offset:(NSInteger)offset extra:(const uint8_t *)extra error:(NSError **)error; +- (BOOL)encryptBytes:(nonnull const uint8_t *)bytes length:(NSInteger)length dest:(nonnull uint8_t *)dest destLength:(nonnull NSInteger *)destLength extra:(const uint8_t *)extra error:(NSError **)error; + +- (nonnull id)dataPathEncrypter; + +@end + +// WARNING: dest must be able to hold plaintext +@protocol Decrypter + +- (void)configureDecryptionWithCipherKey:(nonnull ZeroingData *)cipherKey hmacKey:(nonnull ZeroingData *)hmacKey; +- (int)overheadLength; +- (int)extraLength; + +- (NSData *)decryptData:(nonnull NSData *)data offset:(NSInteger)offset extra:(const uint8_t *)extra error:(NSError **)error; +- (BOOL)decryptBytes:(nonnull const uint8_t *)bytes length:(NSInteger)length dest:(nonnull uint8_t *)dest destLength:(nonnull NSInteger *)destLength extra:(const uint8_t *)extra error:(NSError **)error; + +- (nonnull id)dataPathDecrypter; + +@end diff --git a/PIATunnel/Sources/Core/EncryptionProxy.swift b/PIATunnel/Sources/Core/EncryptionProxy.swift new file mode 100644 index 0000000..a9b41d9 --- /dev/null +++ b/PIATunnel/Sources/Core/EncryptionProxy.swift @@ -0,0 +1,150 @@ +// +// EncryptionProxy.swift +// PIATunnel +// +// Created by Davide De Rosa on 2/8/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation +import __PIATunnelNative + +/// Bridges native encryption for high-level operations. +public class EncryptionProxy { + private static let maxHmacLength = 100 + + private let box: CryptoBox + + /** + Initializes the PRNG. Must be issued before using `SessionProxy`. + + - Parameter seedLength: The length in bytes of the pseudorandom seed that will feed the PRNG. + */ + public static func prepareRandomNumberGenerator(seedLength: Int) -> Bool { + let seed: ZeroingData + do { + seed = try SecureRandom.safeData(length: seedLength) + } catch { + return false + } + return CryptoBox.preparePRNG(withSeed: seed.bytes, length: seed.count) + } + + // Ruby: keys_prf + private static func keysPRF( + _ label: String, + _ secret: ZeroingData, + _ clientSeed: ZeroingData, + _ serverSeed: ZeroingData, + _ clientSessionId: Data?, + _ serverSessionId: Data?, + _ size: Int) throws -> ZeroingData { + + let seed = Z(label) + seed.append(clientSeed) + seed.append(serverSeed) + if let csi = clientSessionId { + seed.append(Z(csi)) + } + if let ssi = serverSessionId { + seed.append(Z(ssi)) + } + let len = secret.count / 2 + 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: EncryptionProxy.maxHmacLength) + var chain = try EncryptionProxy.hmac(buffer, digestName, secret, seed) + while (out.count < size) { + out.append(try EncryptionProxy.hmac(buffer, digestName, secret, chain.appending(seed))) + chain = try EncryptionProxy.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, + secretLength: secret.count, + data: data.bytes, + dataLength: data.count, + hmac: buffer.mutableBytes, + hmacLength: &length + ) + + return buffer.withOffset(0, count: length) + } + + convenience init(_ cipher: String, _ digest: String, _ 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 EncryptionProxy.keysPRF( + CoreConfiguration.label1, auth.preMaster, auth.random1, + serverRandom1, nil, nil, + CoreConfiguration.preMasterLength + ) + + let keysData = try EncryptionProxy.keysPRF( + CoreConfiguration.label2, masterData, auth.random2, + serverRandom2, sessionId, remoteSessionId, + CoreConfiguration.keysCount * CoreConfiguration.keyLength + ) + + var keysArray = [ZeroingData]() + for i in 0.. DataPathEncrypter { + return box.encrypter().dataPathEncrypter() + } + + func decrypter() -> DataPathDecrypter { + return box.decrypter().dataPathDecrypter() + } +} diff --git a/PIATunnel/Sources/Core/Errors.h b/PIATunnel/Sources/Core/Errors.h new file mode 100644 index 0000000..f177d22 --- /dev/null +++ b/PIATunnel/Sources/Core/Errors.h @@ -0,0 +1,27 @@ +// +// Errors.h +// PIATunnel +// +// Created by Davide De Rosa on 10/10/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +#import + +extern NSString *const PIATunnelErrorDomain; + +typedef NS_ENUM(NSInteger, PIATunnelErrorCode) { + PIATunnelErrorCodeCryptoBoxRandomGenerator = 101, + PIATunnelErrorCodeCryptoBoxHMAC, + PIATunnelErrorCodeCryptoBoxEncryption, + PIATunnelErrorCodeCryptoBoxAlgorithm, + PIATunnelErrorCodeTLSBoxCA = 201, + PIATunnelErrorCodeTLSBoxHandshake, + PIATunnelErrorCodeTLSBoxGeneric, + PIATunnelErrorCodeDataPathOverflow = 301, + PIATunnelErrorCodeDataPathPeerIdMismatch +}; + +static inline NSError *PIATunnelErrorWithCode(PIATunnelErrorCode code) { + return [NSError errorWithDomain:PIATunnelErrorDomain code:code userInfo:nil]; +} diff --git a/PIATunnel/Sources/Core/Errors.m b/PIATunnel/Sources/Core/Errors.m new file mode 100644 index 0000000..bf57146 --- /dev/null +++ b/PIATunnel/Sources/Core/Errors.m @@ -0,0 +1,11 @@ +// +// Errors.m +// PIATunnel +// +// Created by Davide De Rosa on 10/10/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +#import "Errors.h" + +NSString *const PIATunnelErrorDomain = @"PIATunnelNative"; diff --git a/PIATunnel/Sources/Core/IOInterface.swift b/PIATunnel/Sources/Core/IOInterface.swift new file mode 100644 index 0000000..a5f14a3 --- /dev/null +++ b/PIATunnel/Sources/Core/IOInterface.swift @@ -0,0 +1,37 @@ +// +// IOInterface.swift +// PIATunnel +// +// Created by Davide De Rosa on 8/27/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation + +/// Represents an I/O interface able to read and write data. +public protocol IOInterface: class { + + /** + Sets the handler for incoming packets. This only needs to be set once. + + - Parameter queue: The queue where to invoke the handler on. + - 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. + + - Parameter packet: The `Data` packet to write. + - 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. + + - Parameter packets: The array of `Data` packets to write. + - Parameter completionHandler: Invoked on write completion, with an optional `Error` in case a network failure occurs. + */ + func writePackets(_ packets: [Data], completionHandler: ((Error?) -> Void)?) +} diff --git a/PIATunnel/Sources/Core/LinkInterface.swift b/PIATunnel/Sources/Core/LinkInterface.swift new file mode 100644 index 0000000..e5582de --- /dev/null +++ b/PIATunnel/Sources/Core/LinkInterface.swift @@ -0,0 +1,42 @@ +// +// LinkInterface.swift +// PIATunnel +// +// Created by Davide De Rosa on 8/27/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +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 } + + /// The literal address of the remote host. + var remoteAddress: String? { get } + + /// The maximum size of a packet. + var mtu: Int { get } + + /// The number of packets that this interface is able to bufferize. + var packetBufferSize: Int { get } + + /// The language spoken over this link. + var communicationType: CommunicationType { get } + + /// Timeout in seconds for negotiation start. + var negotiationTimeout: TimeInterval { get } + + /// Timeout in seconds for HARD_RESET response. + var hardResetTimeout: TimeInterval { get } + + /** + Returns an optional payload to attach to the HARD_RESET packet. + + - Parameter encryption: The `SessionProxy.EncryptionParameters` to establish for this session. + - Returns: The optional HARD_RESET payload. + */ + func hardReset(with encryption: SessionProxy.EncryptionParameters) -> Data? +} diff --git a/PIATunnel/Sources/Core/MSS.h b/PIATunnel/Sources/Core/MSS.h new file mode 100644 index 0000000..4161f32 --- /dev/null +++ b/PIATunnel/Sources/Core/MSS.h @@ -0,0 +1,11 @@ +// +// MSS.h +// PIATunnel +// +// Created by Davide De Rosa on 2/7/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +#import + +void MSSFix(uint8_t *data, NSInteger data_len); diff --git a/PIATunnel/Sources/Core/MSS.m b/PIATunnel/Sources/Core/MSS.m new file mode 100644 index 0000000..cee9f24 --- /dev/null +++ b/PIATunnel/Sources/Core/MSS.m @@ -0,0 +1,69 @@ +// +// MSS.m +// PIATunnel +// +// Created by Davide De Rosa on 2/7/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +#import + +#import "MSS.h" + +const int FLAG_SYN = 2; +const int PROTO_TCP = 6; +const int OPT_END = 0; +const int OPT_NOP = 1; +const int OPT_MSS = 2; +const int MSS_VAL = 1250; + +typedef struct { + uint8_t hdr_len:4, ver:4, x[8], proto; +} ip_hdr_t; + +typedef struct { + uint8_t x1[12]; + uint8_t x2:4, hdr_len:4, flags; + uint16_t x3, sum, x4; +} tcp_hdr_t; + +typedef struct { + uint8_t opt, size; + uint16_t mss; +} tcp_opt_t; + +static inline void MSSUpdateSum(uint16_t* sum_ptr, uint16_t* val_ptr, uint16_t new_val) +{ + uint32_t sum = (~ntohs(*sum_ptr) & 0xffff) + (~ntohs(*val_ptr) & 0xffff) + new_val; + sum = (sum >> 16) + (sum & 0xffff); + sum += (sum >> 16); + *sum_ptr = htons(~sum & 0xffff); + *val_ptr = htons(new_val); +} + +void MSSFix(uint8_t *data, NSInteger data_len) +{ + ip_hdr_t *iph = (ip_hdr_t*)data; + if (iph->proto != PROTO_TCP) return; + uint32_t iph_size = iph->hdr_len*4; + if (iph_size+sizeof(tcp_hdr_t) > data_len) return; + + tcp_hdr_t *tcph = (tcp_hdr_t*)(data + iph_size); + if (!(tcph->flags & FLAG_SYN)) return; + uint8_t *opts = data + iph_size + sizeof(tcp_hdr_t); + + uint32_t tcph_len = tcph->hdr_len*4, optlen = tcph_len-sizeof(tcp_hdr_t); + if (iph_size+sizeof(tcp_hdr_t)+optlen > data_len) return; + + for (uint32_t i = 0; i < optlen;) { + tcp_opt_t *o = (tcp_opt_t*)&opts[i]; + if (o->opt == OPT_END) return; + if (o->opt == OPT_MSS) { + if (i+o->size > optlen) return; + if (ntohs(o->mss) <= MSS_VAL) return; + MSSUpdateSum(&tcph->sum, &o->mss, MSS_VAL); + return; + } + i += (o->opt == OPT_NOP) ? 1 : o->size; + } +} diff --git a/PIATunnel/Sources/Core/Packet.swift b/PIATunnel/Sources/Core/Packet.swift new file mode 100644 index 0000000..5f5759e --- /dev/null +++ b/PIATunnel/Sources/Core/Packet.swift @@ -0,0 +1,81 @@ +// +// Packet.swift +// PIATunnel +// +// Created by Davide De Rosa on 2/3/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation +import __PIATunnelNative + +class CommonPacket { + let packetId: UInt32 + + let code: PacketCode + + let key: UInt8 + + let sessionId: Data? + + let payload: Data? + + var sentDate: Date? + + static func parsed(_ stream: Data) -> (Int, [Data]) { + var ni = 0 + var parsed: [Data] = [] + while (ni + 2 <= stream.count) { + let packlen = Int(stream.networkUInt16Value(from: ni)) + let start = ni + 2 + let end = start + packlen + guard (end <= stream.count) else { + break + } + let packet = stream.subdata(offset: start, count: end - start) + parsed.append(packet) + ni = end + } + return (ni, parsed) + } + + static func stream(_ packet: Data) -> Data { + var stream = Data(capacity: 2 + packet.count) + stream.append(UInt16(packet.count).bigEndian) + stream.append(contentsOf: packet) + return stream + } + + static func stream(_ packets: [Data]) -> Data { + var raw = Data() + for payload in packets { + raw.append(UInt16(payload.count).bigEndian) + raw.append(payload) + } + return raw + } + + init(_ packetId: UInt32, _ code: PacketCode, _ key: UInt8, _ sessionId: Data?, _ payload: Data?) { + self.packetId = packetId + self.code = code + self.key = key + self.sessionId = sessionId + self.payload = payload + self.sentDate = nil + } + + // Ruby: send_ctrl + func toBuffer() -> Data { + var raw = PacketWithHeader(code, key, sessionId) + raw.append(UInt8(0)) + raw.append(UInt32(packetId).bigEndian) + if let payload = payload { + raw.append(payload) + } + return raw + } +} + +class DataPacket { + static let pingString = Data(hex: "2a187bf3641eb4cb07ed2d0a981fc748") +} diff --git a/PIATunnel/Sources/Core/PacketMacros.h b/PIATunnel/Sources/Core/PacketMacros.h new file mode 100644 index 0000000..06e848b --- /dev/null +++ b/PIATunnel/Sources/Core/PacketMacros.h @@ -0,0 +1,64 @@ +// +// PacketMacros.h +// PIATunnel +// +// Created by Davide De Rosa on 11/07/2018. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +#import + +#define PacketPeerIdDisabled 0xffffffu +#define PacketIdLength 4 + +typedef NS_ENUM(uint8_t, PacketCode) { + PacketCodeSoftResetV1 = 0x03, + PacketCodeControlV1 = 0x04, + PacketCodeAckV1 = 0x05, + PacketCodeDataV1 = 0x06, + PacketCodeHardResetClientV2 = 0x07, + PacketCodeHardResetServerV2 = 0x08, + PacketCodeDataV2 = 0x09, + PacketCodeUnknown = 0xff +}; + +extern const uint8_t DataPacketCompressNone; +extern const uint8_t DataPacketPingData[16]; + +static inline int PacketHeaderSet(uint8_t *_Nonnull to, PacketCode code, uint8_t key) +{ + *(uint8_t *)to = (code << 3) | (key & 0b111); + return sizeof(uint8_t); +} + +// Ruby: header +static inline NSData *_Nonnull PacketWithHeader(PacketCode code, uint8_t key, NSData *sessionId) +{ + NSMutableData *to = [[NSMutableData alloc] initWithLength:(sizeof(uint8_t) + (sessionId ? sessionId.length : 0))]; + const int offset = PacketHeaderSet(to.mutableBytes, code, key); + if (sessionId) { + memcpy(to.mutableBytes + offset, sessionId.bytes, sessionId.length); + } + return to; +} + +static inline int PacketHeaderSetDataV2(uint8_t *_Nonnull to, uint8_t key, uint32_t peerId) +{ + *(uint32_t *)to = ((PacketCodeDataV2 << 3) | (key & 0b111)) | htonl(peerId & 0xffffff); + return sizeof(uint32_t); +} + +static inline int PacketHeaderGetDataV2PeerId(const uint8_t *_Nonnull from) +{ + return ntohl(*(const uint32_t *)from & 0xffffff00); +} + +static inline NSData *_Nonnull PacketWithHeaderDataV2(uint8_t key, uint32_t peerId, NSData *sessionId) +{ + NSMutableData *to = [[NSMutableData alloc] initWithLength:(sizeof(uint32_t) + (sessionId ? sessionId.length : 0))]; + const int offset = PacketHeaderSetDataV2(to.mutableBytes, key, peerId); + if (sessionId) { + memcpy(to.mutableBytes + offset, sessionId.bytes, sessionId.length); + } + return to; +} diff --git a/PIATunnel/Sources/Core/PacketMacros.m b/PIATunnel/Sources/Core/PacketMacros.m new file mode 100644 index 0000000..866ed81 --- /dev/null +++ b/PIATunnel/Sources/Core/PacketMacros.m @@ -0,0 +1,12 @@ +// +// PacketMacros.m +// PIATunnel +// +// Created by Davide De Rosa on 11/07/2018. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +#import "PacketMacros.h" + +const uint8_t DataPacketCompressNone = 0xfa; +const uint8_t DataPacketPingData[] = { 0x2a, 0x18, 0x7b, 0xf3, 0x64, 0x1e, 0xb4, 0xcb, 0x07, 0xed, 0x2d, 0x0a, 0x98, 0x1f, 0xc7, 0x48 }; diff --git a/PIATunnel/Sources/Core/ProtocolMacros.swift b/PIATunnel/Sources/Core/ProtocolMacros.swift new file mode 100644 index 0000000..51a3fb4 --- /dev/null +++ b/PIATunnel/Sources/Core/ProtocolMacros.swift @@ -0,0 +1,23 @@ +// +// ProtocolMacros.swift +// PIATunnel +// +// Created by Davide De Rosa on 2/8/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation +import __PIATunnelNative + +class ProtocolMacros { + static let peerIdLength = 3 + + static let sessionIdLength = 8 + + static let packetIdLength = 4 + + // UInt32(0) + UInt8(KeyMethod = 2) + static let tlsPrefix = Data(hex: "0000000002") + + static let numberOfKeys = UInt8(8) // 3-bit +} diff --git a/PIATunnel/Sources/Core/PushReply.swift b/PIATunnel/Sources/Core/PushReply.swift new file mode 100644 index 0000000..e5b7719 --- /dev/null +++ b/PIATunnel/Sources/Core/PushReply.swift @@ -0,0 +1,88 @@ +// +// PushReply.swift +// PIATunnel +// +// Created by Davide De Rosa on 25/07/2018. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation + +struct PushReply { + private static let ifconfigRegexp = try! NSRegularExpression(pattern: "ifconfig [\\d\\.]+ [\\d\\.]+", options: []) + + private static let dnsRegexp = try! NSRegularExpression(pattern: "dhcp-option DNS [\\d\\.]+", options: []) + + private static let authTokenRegexp = try! NSRegularExpression(pattern: "auth-token [a-zA-Z0-9/=+]+", options: []) + + private static let peerIdRegexp = try! NSRegularExpression(pattern: "peer-id [0-9]+", options: []) + + let address: String + + let gatewayAddress: String + + let dnsServers: [String] + + let authToken: String? + + let peerId: UInt32? + + init?(message: String) throws { + guard message.hasPrefix("PUSH_REPLY") else { + return nil + } + + var ifconfigComponents: [String]? + var dnsServers = [String]() + var authToken: String? + var peerId: UInt32? + + PushReply.ifconfigRegexp.enumerateMatches(in: message, options: [], range: NSMakeRange(0, message.count)) { (result, flags, _) in + guard let range = result?.range else { return } + + let match = (message as NSString).substring(with: range) + ifconfigComponents = match.components(separatedBy: " ") + } + + guard let addresses = ifconfigComponents, addresses.count >= 2 else { + throw SessionError.malformedPushReply + } + + PushReply.dnsRegexp.enumerateMatches(in: message, options: [], range: NSMakeRange(0, message.count)) { (result, flags, _) in + guard let range = result?.range else { return } + + let match = (message as NSString).substring(with: range) + let dnsEntryComponents = match.components(separatedBy: " ") + + dnsServers.append(dnsEntryComponents[2]) + } + + PushReply.authTokenRegexp.enumerateMatches(in: message, options: [], range: NSMakeRange(0, message.count)) { (result, flags, _) in + guard let range = result?.range else { return } + + let match = (message as NSString).substring(with: range) + let tokenComponents = match.components(separatedBy: " ") + + if (tokenComponents.count > 1) { + authToken = tokenComponents[1] + } + } + + PushReply.peerIdRegexp.enumerateMatches(in: message, options: [], range: NSMakeRange(0, message.count)) { (result, flags, _) in + guard let range = result?.range else { return } + + let match = (message as NSString).substring(with: range) + let tokenComponents = match.components(separatedBy: " ") + + if (tokenComponents.count > 1) { + peerId = UInt32(tokenComponents[1]) + } + } + + address = addresses[1] + gatewayAddress = addresses[2] + self.dnsServers = dnsServers + self.authToken = authToken + self.peerId = peerId + } +} diff --git a/PIATunnel/Sources/Core/ReplayProtector.h b/PIATunnel/Sources/Core/ReplayProtector.h new file mode 100644 index 0000000..808e102 --- /dev/null +++ b/PIATunnel/Sources/Core/ReplayProtector.h @@ -0,0 +1,15 @@ +// +// ReplayProtector.h +// PIATunnel +// +// Created by Davide De Rosa on 2/17/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +#import + +@interface ReplayProtector : NSObject + +- (BOOL)isReplayedPacketId:(uint32_t)packetId; + +@end diff --git a/PIATunnel/Sources/Core/ReplayProtector.m b/PIATunnel/Sources/Core/ReplayProtector.m new file mode 100644 index 0000000..4bcea1b --- /dev/null +++ b/PIATunnel/Sources/Core/ReplayProtector.m @@ -0,0 +1,77 @@ +// +// ReplayProtector.m +// PIATunnel +// +// Created by Davide De Rosa on 2/17/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +#import "ReplayProtector.h" +#import "Allocation.h" + +#define HIDDEN_WINSIZE 128 +#define BITMAP_LEN (HIDDEN_WINSIZE / 32) +#define BITMAP_INDEX_MASK (BITMAP_LEN - 1) +#define REDUNDANT_BIT_SHIFTS 5 +#define REDUNDANT_BITS (1 << REDUNDANT_BIT_SHIFTS) +#define BITMAP_LOC_MASK (REDUNDANT_BITS - 1) +#define REPLAY_WINSIZE (HIDDEN_WINSIZE - REDUNDANT_BITS) + +@interface ReplayProtector () + +@property (nonatomic, assign) uint32_t highestPacketId; +@property (nonatomic, unsafe_unretained) uint32_t *bitmap; + +@end + +@implementation ReplayProtector + +- (instancetype)init +{ + if ((self = [super init])) { + self.highestPacketId = 0; + self.bitmap = allocate_safely(BITMAP_LEN * sizeof(uint32_t)); + bzero(self.bitmap, BITMAP_LEN * sizeof(uint32_t)); + } + return self; +} + +- (void)dealloc +{ + free(self.bitmap); +} + +- (BOOL)isReplayedPacketId:(uint32_t)packetId +{ + if (packetId == 0) { + return YES; + } + if ((REPLAY_WINSIZE + packetId) < self.highestPacketId) { + return YES; + } + + uint32_t index = (packetId >> REDUNDANT_BIT_SHIFTS); + + if (packetId > self.highestPacketId) { + const uint32_t currentIndex = self.highestPacketId >> REDUNDANT_BIT_SHIFTS; + const uint32_t diff = MIN(index - currentIndex, BITMAP_LEN); + + for (uint32_t bid = 0; bid < diff; ++bid) { + self.bitmap[(bid + currentIndex + 1) & BITMAP_INDEX_MASK] = 0; + } + + self.highestPacketId = packetId; + } + + index &= BITMAP_INDEX_MASK; + const uint32_t bitLocation = packetId & BITMAP_LOC_MASK; + const uint32_t bitmask = (1 << bitLocation); + + if (self.bitmap[index] & bitmask) { + return YES; + } + self.bitmap[index] |= bitmask; + return NO; +} + +@end diff --git a/PIATunnel/Sources/Core/SecureRandom.swift b/PIATunnel/Sources/Core/SecureRandom.swift new file mode 100644 index 0000000..08783d6 --- /dev/null +++ b/PIATunnel/Sources/Core/SecureRandom.swift @@ -0,0 +1,74 @@ +// +// SecureRandom.swift +// PIATunnel +// +// Created by Davide De Rosa on 2/3/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation +import Security.SecRandom +import __PIATunnelNative + +enum SecureRandomError: Error { + case randomGenerator +} + +class SecureRandom { + @available(*, deprecated) + static func uint32FromBuffer() throws -> UInt32 { + var randomBuffer = [UInt8](repeating: 0, count: 4) + + if (SecRandomCopyBytes(kSecRandomDefault, 4, &randomBuffer) != 0) { + throw SecureRandomError.randomGenerator + } + + var randomNumber: UInt32 = 0 + for i in 0..<4 { + let byte = randomBuffer[i] + randomNumber |= (UInt32(byte) << UInt32(8 * i)) + } + return randomNumber + } + + 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 { + throw SecureRandomError.randomGenerator + } + } + } + + return randomNumber + } + + static func data(length: Int) throws -> Data { + var randomData = Data(count: length) + + try randomData.withUnsafeMutableBytes { (randomBytes: UnsafeMutablePointer) -> Void in + guard (SecRandomCopyBytes(kSecRandomDefault, length, randomBytes) == 0) else { + throw SecureRandomError.randomGenerator + } + } + + return randomData + } + + static func safeData(length: Int) throws -> ZeroingData { + let randomBytes = UnsafeMutablePointer.allocate(capacity: length) + defer { +// randomBytes.initialize(to: 0, count: length) + bzero(randomBytes, length) + randomBytes.deallocate() + } + + guard (SecRandomCopyBytes(kSecRandomDefault, length, randomBytes) == 0) else { + throw SecureRandomError.randomGenerator + } + + return Z(bytes: randomBytes, count: length) + } +} diff --git a/PIATunnel/Sources/Core/SessionKey.swift b/PIATunnel/Sources/Core/SessionKey.swift new file mode 100644 index 0000000..f783b03 --- /dev/null +++ b/PIATunnel/Sources/Core/SessionKey.swift @@ -0,0 +1,120 @@ +// +// SessionKey.swift +// PIATunnel +// +// Created by Davide De Rosa on 4/12/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation +import __PIATunnelNative +import SwiftyBeaver + +private let log = SwiftyBeaver.self + +class SessionKey { + enum State { + case invalid, hardReset, softReset, tls + } + + enum ControlState { + case preAuth, preIfConfig, connected + } + + let id: UInt8 // 3-bit + + let startTime: Date + + var state = State.invalid + + var controlState: ControlState? + + var tlsOptional: TLSBox? + + var tls: TLSBox { + guard let tls = tlsOptional else { + fatalError("TLSBox accessed when nil") + } + return tls + } + + var dataPath: DataPath? + + var softReset: Bool + + private var isTLSConnected: Bool + + private var canHandlePackets: Bool + + init(id: UInt8) { + self.id = id + + startTime = Date() + state = .invalid + softReset = false + isTLSConnected = false + canHandlePackets = false + } + + // Ruby: Key.hard_reset_timeout + func didHardResetTimeOut(link: LinkInterface) -> Bool { + return ((state == .hardReset) && (-startTime.timeIntervalSinceNow > link.hardResetTimeout)) + } + + // Ruby: Key.negotiate_timeout + func didNegotiationTimeOut(link: LinkInterface) -> Bool { + let timeout = (softReset ? CoreConfiguration.softNegotiationTimeout : link.negotiationTimeout) + + return ((controlState != .connected) && (-startTime.timeIntervalSinceNow > timeout)) + } + + // Ruby: Key.on_tls_connect + func shouldOnTLSConnect() -> Bool { + guard !isTLSConnected else { + return false + } + if tls.isConnected() { + isTLSConnected = true + } + return isTLSConnected + } + + func startHandlingPackets(withPeerId peerId: UInt32? = nil) { + dataPath?.setPeerId(peerId ?? PacketPeerIdDisabled) + canHandlePackets = true + } + + func encrypt(packets: [Data]) throws -> [Data]? { + guard let dataPath = dataPath else { + log.warning("Data: Set dataPath first") + return nil + } + guard canHandlePackets else { + log.warning("Data: Invoke startHandlingPackets() before encrypting") + return nil + } + return try dataPath.encryptPackets(packets, key: id) + } + + func decrypt(packets: [Data]) throws -> [Data]? { + guard let dataPath = dataPath else { + log.warning("Data: Set dataPath first") + return nil + } + guard canHandlePackets else { + log.warning("Data: Invoke startHandlingPackets() before decrypting") + return nil + } + var keepAlive = false + let decrypted = try dataPath.decryptPackets(packets, keepAlive: &keepAlive) + if keepAlive { + log.debug("Data: Received ping, do nothing") + } + return decrypted + } + +// func dispose() { +// tlsOptional = nil +// dataPath = nil +// } +} diff --git a/PIATunnel/Sources/Core/SessionProxy.swift b/PIATunnel/Sources/Core/SessionProxy.swift new file mode 100644 index 0000000..410b560 --- /dev/null +++ b/PIATunnel/Sources/Core/SessionProxy.swift @@ -0,0 +1,1269 @@ +// +// SessionProxy.swift +// PIATunnel +// +// Created by Davide De Rosa on 2/3/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation +import SwiftyBeaver +import __PIATunnelNative + +private let log = SwiftyBeaver.self + +private extension Error { + func isDataPathOverflow() -> Bool { + let te = self as NSError + return te.domain == PIATunnelErrorDomain && te.code == PIATunnelErrorCode.dataPathOverflow.rawValue + } +} + +/// The possible errors raised/thrown during `SessionProxy` operation. +public enum SessionError: Error { + + /// The negotiation timed out. + case negotiationTimeout + + /// The peer failed to verify. + case peerVerification + + /// 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 TLS negotiation failed. + case tlsError + + /// The control packet has an incorrect prefix payload. + case wrongControlDataPrefix + + /// The provided credentials failed authentication. + case badCredentials + + /// 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 +} + +/// Observes major events notified by a `SessionProxy`. +public protocol SessionProxyDelegate: class { + + /** + Called after starting a session. + + - Parameter remoteAddress: The address of the VPN server. + - Parameter address: The obtained address. + - Parameter gatewayAddress: The address of the gateway. + - Parameter dnsServers: The DNS servers set up for this session. + */ + func sessionDidStart(_: SessionProxy, remoteAddress: String, address: String, gatewayAddress: String, dnsServers: [String]) + + /** + Called after stopping a session. + + - Parameter shouldReconnect: When `true`, the session can/should be restarted. Usually because the stop reason was recoverable. + - Seealso: `SessionProxy.reconnect(...)` + */ + func sessionDidStop(_: SessionProxy, shouldReconnect: Bool) +} + +/// Provides methods to set up and maintain an OpenVPN session. +public class SessionProxy { + + /// Wraps the encryption parameters of the session. + public struct EncryptionParameters { + + /// The cipher algorithm for data encryption. Must follow OpenSSL nomenclature, e.g. "AES-128-CBC". + public let cipherName: String + + /// The digest algorithm for HMAC. Must follow OpenSSL nomenclature, e.g. "SHA-1". + public let digestName: String + + /// The path to the CA for TLS negotiation (PEM format). + public let caPath: String + + /// The MD5 digest of the CA (computed from DER format). + public let caDigest: String? + + /// :nodoc: + public init(_ cipherName: String, _ digestName: String, _ caPath: String, _ caDigest: String?) { + self.cipherName = cipherName + self.digestName = digestName + self.caPath = caPath + self.caDigest = caDigest + } + } + + /// A set of credentials. + public struct Credentials { + + /// An username. + public let username: String + + /// A password. + public let password: String + + /// :nodoc: + public init(_ username: String, _ password: String) { + self.username = username + self.password = password + } + } + + private enum StopMethod { + case shutdown + + case reconnect + } + + // MARK: Configuration + + private let encryption: EncryptionParameters + + private let credentials: Credentials + + /// Sends periodical keep-alive packets if set. + public var keepAliveInterval: 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? + + /// An optional `SessionProxyDelegate` for receiving session events. + public weak var delegate: SessionProxyDelegate? + + // MARK: State + + private let queue: DispatchQueue + + private var tlsObserver: NSObjectProtocol? + + private var keys: [UInt8: SessionKey] + + private var oldKeys: [SessionKey] + + private var negotiationKeyIdx: UInt8 + + private var currentKeyIdx: UInt8? + + private var negotiationKey: SessionKey { + guard let key = keys[negotiationKeyIdx] else { + fatalError("Keys are empty or index \(negotiationKeyIdx) not found in \(keys.keys)") + } + return key + } + + private var currentKey: 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 + } + + private var sessionId: Data? + + private var remoteSessionId: Data? + + private var authToken: String? + + private var peerId: UInt32? + + private var nextPushRequestDate: Date? + + private var connectedDate: Date? + + private var lastPingOut: Date + + private var lastPingIn: Date + + private var isStopping: Bool + + /// The optional reason why the session stopped. + public private(set) var stopError: Error? + + // MARK: Control + + private let controlPlainBuffer: ZeroingData + + private var controlQueueOut: [CommonPacket] + + private var controlQueueIn: [CommonPacket] + + private var controlPendingAcks: Set + + private var controlPacketIdOut: UInt32 + + private var controlPacketIdIn: UInt32 + + private var authenticator: Authenticator? + + // MARK: Data + + private(set) var bytesIn: Int + + private(set) var bytesOut: Int + + // MARK: Init + + /** + Creates a VPN session. + + - Parameter queue: The `DispatchQueue` where to run the session loop. + - Parameter encryption: The `SessionProxy.EncryptionParameters` to establish for this session. + - Parameter credentials: The `SessionProxy.Credentials` required for authentication. + */ + public init(queue: DispatchQueue, encryption: EncryptionParameters, credentials: Credentials) throws { + self.queue = queue + self.encryption = encryption + self.credentials = credentials + + keepAliveInterval = nil + renegotiatesAfter = nil + + keys = [:] + oldKeys = [] + negotiationKeyIdx = 0 + lastPingOut = Date.distantPast + lastPingIn = Date.distantPast + isStopping = false + + controlPlainBuffer = Z(count: TLSBoxMaxBufferLength) + controlQueueOut = [] + controlQueueIn = [] + controlPendingAcks = [] + controlPacketIdOut = 0 + controlPacketIdIn = 0 + bytesIn = 0 + bytesOut = 0 + } + + deinit { + cleanup() + } + + // MARK: Public interface + + /** + Establishes the link interface for this session. The interface must be up and running for sending and receiving packets. + + - Precondition: `link` is an active network interface. + - Postcondition: The VPN negotiation is started. + - Parameter link: The `LinkInterface` on which to establish the VPN session. + */ + public func setLink(_ link: LinkInterface) { + 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 + self.queue.async { + self.deferStop(.shutdown, SessionError.peerVerification) + } + } + + self.link = link + start() + } + + /** + Returns `true` if the current session can rebind to a new link with `rebindLink(...)`. + + - Returns: `true` if supports link rebinding. + */ + public func canRebindLink() -> Bool { + return (peerId != nil) + } + + /** + Rebinds the session to a new link if supported. + + - Precondition: `link` is an active network interface. + - Postcondition: The VPN session is active. + - Parameter link: The `LinkInterface` on which to establish the VPN session. + - Seealso: `canRebindLink()`. + */ + public func rebindLink(_ link: LinkInterface) { + guard let _ = peerId else { + log.warning("Session doesn't support link rebinding!") + return + } + + isStopping = false + stopError = nil + + log.debug("Rebinding VPN session to a new link") + self.link = link + loopLink() + } + + /** + Establishes the tunnel interface for this session. The interface must be up and running for sending and receiving packets. + + - Precondition: `tunnel` is an active network interface. + - Postcondition: The VPN data channel is open. + - Parameter tunnel: The `TunnelInterface` on which to exchange the VPN data traffic. + */ + public func setTunnel(tunnel: TunnelInterface) { + guard (self.tunnel == nil) else { + log.warning("Tunnel interface already set!") + return + } + self.tunnel = tunnel + loopTunnel() + } + + /** + Shuts down the session with an optional `Error` reason. Does nothing if the session is already stopped or about to stop. + + - Parameter error: An optional `Error` being the reason of the shutdown. + */ + public func shutdown(error: Error?) { + guard !isStopping else { + log.warning("Ignore stop request, already stopping!") + return + } + deferStop(.shutdown, error) + } + + /** + Shuts down the session with an optional `Error` reason and signals a reconnect flag to `SessionProxyDelegate.sessionDidStop(...)`. Does nothing if the session is already stopped or about to stop. + + - Parameter error: An optional `Error` being the reason of the shutdown. + - Seealso: `SessionProxyDelegate.sessionDidStop(...)` + */ + public func reconnect(error: Error?) { + guard !isStopping else { + log.warning("Ignore stop request, already stopping!") + return + } + deferStop(.reconnect, error) + } + + // Ruby: cleanup + /** + Cleans up the session resources. + */ + public func cleanup() { + log.info("Cleaning up...") + + if let observer = tlsObserver { + NotificationCenter.default.removeObserver(observer) + tlsObserver = nil + } + +// for (_, key) in keys { +// key.dispose() +// } + keys.removeAll() + oldKeys.removeAll() + negotiationKeyIdx = 0 + currentKeyIdx = nil + + sessionId = nil + remoteSessionId = nil + authToken = nil + nextPushRequestDate = nil + connectedDate = nil + authenticator = nil + peerId = nil + link = nil + if !(tunnel?.isPersistent ?? false) { + tunnel = nil + } + + isStopping = false + stopError = nil + } + + // MARK: Loop + + // Ruby: start + private func start() { + loopLink() + hardReset() + + guard !keys.isEmpty else { + fatalError("Main loop must follow hard reset, keys are empty!") + } + + loopNegotiation() + } + + private func loopNegotiation() { + guard let link = link else { + return + } + guard !keys.isEmpty else { + return + } + + guard !negotiationKey.didHardResetTimeOut(link: link) else { + doReconnect(error: SessionError.negotiationTimeout) + return + } + guard !negotiationKey.didNegotiationTimeOut(link: link) else { + doShutdown(error: SessionError.negotiationTimeout) + return + } + + if !isReliableLink { + pushRequest() + flushControlQueue() + } + + guard (negotiationKey.controlState == .connected) else { + queue.asyncAfter(deadline: .now() + CoreConfiguration.tickInterval) { [weak self] in + self?.loopNegotiation() + } + return + } + + // let loop die when negotiation is complete + } + + // Ruby: udp_loop + private func loopLink() { + let loopedLink = link + loopedLink?.setReadHandler(queue: queue) { [weak self] (newPackets, error) in + guard loopedLink === self?.link else { + log.warning("Ignoring read from outdated LINK") + return + } + if let error = error { + log.error("Failed LINK read: \(error)") + return + } + + if let packets = newPackets, !packets.isEmpty { + self?.maybeRenegotiate() + +// log.verbose("Received \(packets.count) packets from LINK") + self?.receiveLink(packets: packets) + } + } + } + + // Ruby: tun_loop + private func loopTunnel() { + tunnel?.setReadHandler(queue: queue) { [weak self] (newPackets, error) in + if let error = error { + log.error("Failed TUN read: \(error)") + return + } + + if let packets = newPackets, !packets.isEmpty { +// log.verbose("Received \(packets.count) packets from \(self.tunnelName)") + self?.receiveTunnel(packets: packets) + } + } + } + + // Ruby: recv_link + private func receiveLink(packets: [Data]) { + guard shouldHandlePackets() else { + return + } + + lastPingIn = Date() + + var dataPacketsByKey = [UInt8: [Data]]() + + for packet in packets { +// log.verbose("Received data from LINK (\(packet.count) bytes): \(packet.toHex())") + + guard let firstByte = packet.first else { + log.warning("Dropped malformed packet (missing header)") + continue + } + let codeValue = firstByte >> 3 + guard let code = PacketCode(rawValue: codeValue) else { + log.warning("Dropped malformed packet (unknown code: \(codeValue))") + continue + } + let key = firstByte & 0b111 + +// log.verbose("Parsed packet with (code, key) = (\(code.rawValue), \(key))") + + var offset = 1 + if (code == .dataV2) { + guard packet.count >= offset + ProtocolMacros.peerIdLength else { + log.warning("Dropped malformed packet (missing peerId)") + continue + } + offset += ProtocolMacros.peerIdLength + } + + if (code == .dataV1) || (code == .dataV2) { + guard let _ = keys[key] else { + log.error("Key with id \(key) not found") + deferStop(.shutdown, SessionError.badKey) + return + } + + // XXX: improve with array reference + var dataPackets = dataPacketsByKey[key] ?? [Data]() + dataPackets.append(packet) + dataPacketsByKey[key] = dataPackets + + continue + } + + guard packet.count >= offset + ProtocolMacros.sessionIdLength else { + log.warning("Dropped malformed packet (missing sessionId)") + continue + } + let sessionId = packet.subdata(offset: offset, count: ProtocolMacros.sessionIdLength) + offset += ProtocolMacros.sessionIdLength + + guard packet.count >= offset + 1 else { + log.warning("Dropped malformed packet (missing ackSize)") + continue + } + let ackSize = packet[offset] + offset += 1 + + log.debug("Packet has code \(code.rawValue), key \(key), sessionId \(sessionId.toHex()) and \(ackSize) acks entries") + + if (ackSize > 0) { + guard packet.count >= (offset + Int(ackSize) * ProtocolMacros.packetIdLength) else { + log.warning("Dropped malformed packet (missing acks)") + continue + } + var ackedPacketIds = [UInt32]() + for _ in 0..= offset + ProtocolMacros.sessionIdLength else { + log.warning("Dropped malformed packet (missing remoteSessionId)") + continue + } + let remoteSessionId = packet.subdata(offset: offset, count: ProtocolMacros.sessionIdLength) + offset += ProtocolMacros.sessionIdLength + + log.debug("Server acked packetIds \(ackedPacketIds) with remoteSessionId \(remoteSessionId.toHex())") + + handleAcks(ackedPacketIds, remoteSessionId: remoteSessionId) + } + + if (code == .ackV1) { + continue + } + + guard packet.count >= offset + ProtocolMacros.packetIdLength else { + log.warning("Dropped malformed packet (missing packetId)") + continue + } + let packetId = packet.networkUInt32Value(from: offset) + log.debug("Control packet has packetId \(packetId)") + offset += ProtocolMacros.packetIdLength + + sendAck(key: key, packetId: packetId, remoteSessionId: sessionId) + + var payload: Data? + if (offset < packet.count) { + payload = packet.subdata(in: offset..= interval) else { + let remaining = min(interval, interval - elapsed) + queue.asyncAfter(deadline: .now() + remaining) { [weak self] in + self?.ping() + } + return + } + } + + log.debug("Send ping") + sendDataPackets([DataPacket.pingString]) + lastPingOut = Date() + + if let interval = keepAliveInterval { + queue.asyncAfter(deadline: .now() + interval) { [weak self] in + self?.ping() + } + } + } + + // MARK: Handshake + + // Ruby: reset_ctrl + private func resetControlChannel() { + controlPlainBuffer.zero() + controlQueueOut.removeAll() + controlQueueIn.removeAll() + controlPendingAcks.removeAll() + controlPacketIdOut = 0 + controlPacketIdIn = 0 + authenticator = nil + peerId = nil + bytesIn = 0 + bytesOut = 0 + } + + // Ruby: hard_reset + private func hardReset() { + log.debug("Send hard reset") + + resetControlChannel() + do { + try sessionId = SecureRandom.data(length: ProtocolMacros.sessionIdLength) + } catch let e { + deferStop(.shutdown, e) + return + } + negotiationKeyIdx = 0 + let newKey = SessionKey(id: UInt8(negotiationKeyIdx)) + keys[negotiationKeyIdx] = newKey + log.debug("Negotiation key index is \(negotiationKeyIdx)") + + let payload = link?.hardReset(with: encryption) ?? Data() + negotiationKey.state = .hardReset + enqueueControlPackets(code: .hardResetClientV2, key: UInt8(negotiationKeyIdx), payload: payload) + } + + // Ruby: soft_reset + private func softReset() { + log.debug("Send soft reset") + + resetControlChannel() + negotiationKeyIdx = max(1, (negotiationKeyIdx + 1) % ProtocolMacros.numberOfKeys) + let newKey = SessionKey(id: UInt8(negotiationKeyIdx)) + keys[negotiationKeyIdx] = newKey + log.debug("Negotiation key index is \(negotiationKeyIdx)") + + negotiationKey.state = .softReset + negotiationKey.softReset = true + loopNegotiation() + 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 Authenticator(credentials.username, authToken ?? credentials.password) + try authenticator?.putAuth(into: negotiationKey.tls) + } catch let e { + deferStop(.shutdown, e) + return + } + + guard let cipherTextOut = try? negotiationKey.tls.pullCipherText() else { + log.verbose("TLS.auth: Still can't pull ciphertext") + return + } + + 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 { + return + } + if !isReliableLink { + guard let targetDate = nextPushRequestDate, (Date() > targetDate) else { + return + } + } + + log.debug("TLS.ifconfig: Put plaintext (PUSH_REQUEST)") + try? negotiationKey.tls.putPlainText("PUSH_REQUEST\0") + + guard let cipherTextOut = try? negotiationKey.tls.pullCipherText() else { + 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 negotiationKey.softReset { + authenticator = nil + negotiationKey.startHandlingPackets(withPeerId: peerId) + negotiationKey.controlState = .connected + connectedDate = Date() + transitionKeys() + } + nextPushRequestDate = Date().addingTimeInterval(CoreConfiguration.retransmissionLimit) + } + + private func maybeRenegotiate() { + guard let renegotiatesAfter = renegotiatesAfter else { + return + } + guard (negotiationKeyIdx == currentKeyIdx) else { + return + } + + let elapsed = -negotiationKey.startTime.timeIntervalSinceNow + if (elapsed > renegotiatesAfter) { + log.debug("Renegotiating after \(elapsed) seconds") + softReset() + } + } + + // MARK: Control + + // Ruby: handle_ctrl_pkt + private func handleControlPacket(_ packet: CommonPacket) { + guard (packet.key == negotiationKey.id) else { + log.error("Bad key in control packet (\(packet.key) != \(negotiationKey.id))") +// deferStop(.shutdown, SessionError.badKey) + return + } + + log.debug("Handle control packet with code \(packet.code.rawValue) and id \(packet.packetId)") + + if (((packet.code == .hardResetServerV2) && (negotiationKey.state == .hardReset)) || + ((packet.code == .softResetV1) && (negotiationKey.state == .softReset))) { + + if (negotiationKey.state == .hardReset) { + guard let sessionId = packet.sessionId else { + deferStop(.shutdown, SessionError.missingSessionId) + return + } + remoteSessionId = sessionId + } + guard let remoteSessionId = remoteSessionId else { + log.error("No remote session id") + deferStop(.shutdown, SessionError.missingSessionId) + return + } + guard (packet.sessionId == remoteSessionId) else { + if let packetSessionId = packet.sessionId { + log.error("Packet session mismatch (\(packetSessionId.toHex()) != \(remoteSessionId.toHex()))") + } + deferStop(.shutdown, SessionError.sessionMismatch) + return + } + + negotiationKey.state = .tls + + log.debug("Remote sessionId is \(remoteSessionId.toHex())") + log.debug("Start TLS handshake") + + negotiationKey.tlsOptional = TLSBox(caPath: encryption.caPath) + do { + try negotiationKey.tls.start(withPeerVerification: true) + } catch let e { + deferStop(.shutdown, e) + return + } + + guard let cipherTextOut = try? negotiationKey.tls.pullCipherText() else { + deferStop(.shutdown, SessionError.tlsError) + return + } + + log.debug("TLS.connect: Pulled ciphertext (\(cipherTextOut.count) bytes)") + enqueueControlPackets(code: .controlV1, key: negotiationKey.id, payload: cipherTextOut) + } + else if ((packet.code == .controlV1) && (negotiationKey.state == .tls)) { + guard let remoteSessionId = remoteSessionId else { + deferStop(.shutdown, SessionError.missingSessionId) + return + } + guard (packet.sessionId == remoteSessionId) else { + if let packetSessionId = packet.sessionId { + log.error("Packet session mismatch (\(packetSessionId.toHex()) != \(remoteSessionId.toHex()))") + } + deferStop(.shutdown, SessionError.sessionMismatch) + return + } + + guard let cipherTextIn = packet.payload else { + log.warning("TLS.connect: Control packet with empty payload?") + return + } + + log.debug("TLS.connect: Put received ciphertext (\(cipherTextIn.count) bytes)") + try? negotiationKey.tls.putCipherText(cipherTextIn) + + if let cipherTextOut = try? negotiationKey.tls.pullCipherText() { + log.debug("TLS.connect: Send pulled ciphertext (\(cipherTextOut.count) bytes)") + enqueueControlPackets(code: .controlV1, key: negotiationKey.id, payload: cipherTextOut) + } + + if negotiationKey.shouldOnTLSConnect() { + onTLSConnect() + } + + do { + var length = 0 + try negotiationKey.tls.pullRawPlainText(controlPlainBuffer.mutableBytes, length: &length) + + let controlData = controlPlainBuffer.withOffset(0, count: length) + handleControlData(controlData) + } catch _ { + } + } + } + + // Ruby: handle_ctrl_data + private func handleControlData(_ data: ZeroingData) { + guard let auth = authenticator else { return } + + if CoreConfiguration.logsSensitiveData { + log.debug("Pulled plain control data (\(data.count) bytes): \(data.toHex())") + } else { + log.debug("Pulled plain control data (\(data.count) bytes)") + } + + auth.appendControlData(data) + + if (negotiationKey.controlState == .preAuth) { + do { + guard try auth.parseAuthReply() else { + return + } + } catch let e { + deferStop(.shutdown, e) + return + } + + setupKeys() + + negotiationKey.controlState = .preIfConfig + nextPushRequestDate = Date().addingTimeInterval(negotiationKey.softReset ? CoreConfiguration.softResetDelay : CoreConfiguration.retransmissionLimit) + pushRequest() + } + + for message in auth.parseMessages() { + if CoreConfiguration.logsSensitiveData { + log.debug("Parsed control message (\(message.count) bytes): \"\(message)\"") + } else { + log.debug("Parsed control message (\(message.count) bytes)") + } + handleControlMessage(message) + } + } + + // Ruby: handle_ctrl_msg + private func handleControlMessage(_ message: String) { + guard !message.hasPrefix("AUTH_FAILED") else { + deferStop(.shutdown, SessionError.badCredentials) + return + } + + guard (negotiationKey.controlState == .preIfConfig) else { + return + } + + log.debug("Received control message: \"\(message)\"") + + let reply: PushReply + do { + guard let optionalReply = try PushReply(message: message) else { + return + } + reply = optionalReply + authToken = reply.authToken + peerId = reply.peerId + } catch let e { + deferStop(.shutdown, e) + return + } + + authenticator = nil + negotiationKey.startHandlingPackets(withPeerId: peerId) + negotiationKey.controlState = .connected + connectedDate = Date() + transitionKeys() + + guard let remoteAddress = link?.remoteAddress else { + fatalError("Could not resolve link remote address") + } + + delegate?.sessionDidStart( + self, + remoteAddress: remoteAddress, + address: reply.address, + gatewayAddress: reply.gatewayAddress, + dnsServers: reply.dnsServers + ) + + if let interval = keepAliveInterval { + queue.asyncAfter(deadline: .now() + interval) { [weak self] in + self?.ping() + } + } + } + + // Ruby: transition_keys + private func transitionKeys() { + if let key = currentKey { + oldKeys.append(key) + } + currentKeyIdx = negotiationKeyIdx + cleanKeys() + } + + // Ruby: clean_keys + private func cleanKeys() { + while (oldKeys.count > 1) { + let key = oldKeys.removeFirst() + keys.removeValue(forKey: key.id) +// key.dispose() + } + } + + // Ruby: q_ctrl + private func enqueueControlPackets(code: PacketCode, key: UInt8, payload: Data) { + guard let link = link else { + log.warning("Not writing to LINK, interface is down") + return + } + + let oldIdOut = controlPacketIdOut + let maxCount = link.mtu + var queuedCount = 0 + var offset = 0 + + repeat { + let subPayloadLength = min(maxCount, payload.count - offset) + let subPayloadData = payload.subdata(offset: offset, count: subPayloadLength) + let packet = CommonPacket(controlPacketIdOut, code, key, sessionId, subPayloadData) + + controlQueueOut.append(packet) + controlPacketIdOut += 1 + offset += maxCount + queuedCount += subPayloadLength + } while (offset < payload.count) + + assert(queuedCount == payload.count) + + let packetCount = controlPacketIdOut - oldIdOut + if (packetCount > 1) { + log.debug("Enqueued \(packetCount) control packets [\(oldIdOut)-\(controlPacketIdOut - 1)]") + } else { + log.debug("Enqueued 1 control packet [\(oldIdOut)]") + } + + flushControlQueue() + } + + // Ruby: flush_ctrl_q_out + private func flushControlQueue() { + for controlPacket in controlQueueOut { + if let sentDate = controlPacket.sentDate { + let timeAgo = -sentDate.timeIntervalSinceNow + guard (timeAgo >= CoreConfiguration.retransmissionLimit) else { + log.debug("Skip control packet with id \(controlPacket.packetId) (sent on \(sentDate), \(timeAgo) seconds ago)") + continue + } + } + + log.debug("Send control packet with code \(controlPacket.code.rawValue)") + + if let payload = controlPacket.payload { + if CoreConfiguration.logsSensitiveData { + log.debug("Control packet has payload (\(payload.count) bytes): \(payload.toHex())") + } else { + log.debug("Control packet has payload (\(payload.count) bytes)") + } + } + + let raw = controlPacket.toBuffer() + log.debug("Send control packet (\(raw.count) bytes): \(raw.toHex())") + + // track pending acks for sent packets + controlPendingAcks.insert(controlPacket.packetId) + + // WARNING: runs in Network.framework queue + link?.writePacket(raw) { [weak self] (error) in + if let error = error { + self?.queue.sync { + log.error("Failed LINK write during control flush: \(error)") + self?.deferStop(.reconnect, SessionError.failedLinkWrite) + return + } + } + } + controlPacket.sentDate = Date() + } +// log.verbose("Packets now pending ack: \(controlPendingAcks)") + } + + // Ruby: setup_keys + private func setupKeys() { + guard let auth = authenticator else { + fatalError("Setting up keys without having authenticated") + } + guard let sessionId = sessionId else { + fatalError("Setting up keys without a local sessionId") + } + guard let remoteSessionId = remoteSessionId else { + fatalError("Setting up keys without a remote sessionId") + } + guard let serverRandom1 = auth.serverRandom1, let serverRandom2 = auth.serverRandom2 else { + fatalError("Setting up keys without server randoms") + } + + if CoreConfiguration.logsSensitiveData { + log.debug("Setup keys from the following components:") + log.debug("\tpreMaster: \(auth.preMaster.toHex())") + log.debug("\trandom1: \(auth.random1.toHex())") + log.debug("\trandom2: \(auth.random2.toHex())") + log.debug("\tserverRandom1: \(serverRandom1.toHex())") + log.debug("\tserverRandom2: \(serverRandom2.toHex())") + log.debug("\tsessionId: \(sessionId.toHex())") + log.debug("\tremoteSessionId: \(remoteSessionId.toHex())") + } else { + log.debug("Setup keys") + } + + let proxy: EncryptionProxy + do { + proxy = try EncryptionProxy(encryption.cipherName, encryption.digestName, auth, sessionId, remoteSessionId) + } catch let e { + deferStop(.shutdown, e) + return + } + + negotiationKey.dataPath = DataPath( + encrypter: proxy.encrypter(), + decrypter: proxy.decrypter(), + maxPackets: link?.packetBufferSize ?? 200, + usesReplayProtection: CoreConfiguration.usesReplayProtection + ) + } + + // MARK: Data + + // Ruby: handle_data_pkt + private func handleDataPackets(_ packets: [Data], key: SessionKey) { + bytesIn += packets.flatCount + do { + guard let decryptedPackets = try key.decrypt(packets: packets) else { + log.warning("Could not decrypt packets, is SessionKey properly configured (dataPath, peerId)?") + return + } + guard !decryptedPackets.isEmpty else { + return + } + + tunnel?.writePackets(decryptedPackets, completionHandler: nil) + } catch let e { + guard !e.isDataPathOverflow() else { + deferStop(.shutdown, e) + return + } + deferStop(.reconnect, e) + } + } + + // Ruby: send_data_pkt + private func sendDataPackets(_ packets: [Data]) { + guard let key = currentKey else { + return + } + do { + guard let encryptedPackets = try key.encrypt(packets: packets) else { + log.warning("Could not encrypt packets, is SessionKey properly configured (dataPath, peerId)?") + return + } + guard !encryptedPackets.isEmpty else { + return + } + + // WARNING: runs in Network.framework queue + bytesOut += encryptedPackets.flatCount + link?.writePackets(encryptedPackets) { [weak self] (error) in + if let error = error { + self?.queue.sync { + log.error("Data: Failed LINK write during send data: \(error)") + self?.deferStop(.reconnect, SessionError.failedLinkWrite) + return + } + } +// log.verbose("Data: \(encryptedPackets.count) packets successfully written to LINK") + } + } catch let e { + guard !e.isDataPathOverflow() else { + deferStop(.shutdown, e) + return + } + deferStop(.reconnect, e) + } + } + + // MARK: Acks + + // Ruby: handle_acks + private func handleAcks(_ packetIds: [UInt32], remoteSessionId: Data) { + guard (remoteSessionId == sessionId) else { + if let sessionId = sessionId { + log.error("Ack session mismatch (\(remoteSessionId.toHex()) != \(sessionId.toHex()))") + } + deferStop(.shutdown, SessionError.sessionMismatch) + return + } + + // drop queued out packets if ack-ed + for (i, controlPacket) in controlQueueOut.enumerated() { + if packetIds.contains(controlPacket.packetId) { + controlQueueOut.remove(at: i) + } + } + + // remove ack-ed packets from pending + controlPendingAcks.subtract(packetIds) +// log.verbose("Packets still pending ack: \(controlPendingAcks)") + + // retry PUSH_REQUEST if ack queue is empty (all sent packets were ack'ed) + if (isReliableLink && controlPendingAcks.isEmpty) { + pushRequest() + } + } + + // Ruby: send_ack + private func sendAck(key: UInt8, packetId: UInt32, remoteSessionId: Data) { + log.debug("Send ack for received packetId \(packetId)") + + var raw = PacketWithHeader(.ackV1, key, sessionId) + raw.append(UInt8(1)) // ackSize + raw.append(UInt32(packetId).bigEndian) + raw.append(remoteSessionId) + + // WARNING: runs in Network.framework queue + link?.writePacket(raw) { [weak self] (error) in + if let error = error { + self?.queue.sync { + log.error("Failed LINK write during send ack for packetId \(packetId): \(error)") + self?.deferStop(.reconnect, SessionError.failedLinkWrite) + return + } + } + log.debug("Ack successfully written to LINK for packetId \(packetId)") + } + } + + // MARK: Stop + + private func shouldHandlePackets() -> Bool { + return (!isStopping && !keys.isEmpty) + } + + private func deferStop(_ method: StopMethod, _ error: Error?) { + isStopping = true + + switch method { + case .shutdown: + doShutdown(error: error) + + case .reconnect: + doReconnect(error: error) + } + } + + private func doShutdown(error: Error?) { + if let error = error { + log.error("Trigger shutdown (error: \(error))") + } else { + log.info("Trigger shutdown on request") + } + stopError = error + delegate?.sessionDidStop(self, shouldReconnect: false) + } + + private func doReconnect(error: Error?) { + if let error = error { + log.error("Trigger reconnection (error: \(error))") + } else { + log.info("Trigger reconnection on request") + } + stopError = error + delegate?.sessionDidStop(self, shouldReconnect: true) + } +} diff --git a/PIATunnel/Sources/Core/TLSBox.h b/PIATunnel/Sources/Core/TLSBox.h new file mode 100644 index 0000000..6370874 --- /dev/null +++ b/PIATunnel/Sources/Core/TLSBox.h @@ -0,0 +1,38 @@ +// +// TLSBox.h +// PIATunnel +// +// Created by Davide De Rosa on 2/3/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +#import + +extern const NSInteger TLSBoxMaxBufferLength; + +extern NSString *const TLSBoxPeerVerificationErrorNotification; + +// +// cipher text is safe within NSData +// plain text might be sensitive and must avoid NSData +// +// WARNING: not thread-safe! +// +@interface TLSBox : NSObject + +- (nonnull instancetype)initWithCAPath:(NSString *)caPath; + +- (BOOL)startWithPeerVerification:(BOOL)peerVerification error:(NSError **)error; + +- (NSData *)pullCipherTextWithError:(NSError **)error; +// WARNING: text must be able to hold plain text output +- (BOOL)pullRawPlainText:(uint8_t *)text length:(NSInteger *)length error:(NSError **)error; + +- (BOOL)putCipherText:(NSData *)text error:(NSError **)error; +- (BOOL)putRawCipherText:(const uint8_t *)text length:(NSInteger)length error:(NSError **)error; +- (BOOL)putPlainText:(NSString *)text error:(NSError **)error; +- (BOOL)putRawPlainText:(const uint8_t *)text length:(NSInteger)length error:(NSError **)error; + +- (BOOL)isConnected; + +@end diff --git a/PIATunnel/Sources/Core/TLSBox.m b/PIATunnel/Sources/Core/TLSBox.m new file mode 100644 index 0000000..2fe6fc2 --- /dev/null +++ b/PIATunnel/Sources/Core/TLSBox.m @@ -0,0 +1,206 @@ +// +// TLSBox.m +// PIATunnel +// +// Created by Davide De Rosa on 2/3/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +#import +#import +#import + +#import "TLSBox.h" +#import "Allocation.h" +#import "Errors.h" + +const NSInteger TLSBoxMaxBufferLength = 16384; + +NSString *const TLSBoxPeerVerificationErrorNotification = @"TLSBoxPeerVerificationErrorNotification"; + +static BOOL TLSBoxIsOpenSSLLoaded; + +int TLSBoxVerifyPeer(int ok, X509_STORE_CTX *ctx) { + if (!ok) { + [[NSNotificationCenter defaultCenter] postNotificationName:TLSBoxPeerVerificationErrorNotification object:nil]; + } + return ok; +} + +@interface TLSBox () + +@property (nonatomic, strong) NSString *caPath; +@property (nonatomic, assign) BOOL isConnected; + +@property (nonatomic, unsafe_unretained) SSL_CTX *ctx; +@property (nonatomic, unsafe_unretained) SSL *ssl; +@property (nonatomic, unsafe_unretained) BIO *bioPlainText; +@property (nonatomic, unsafe_unretained) BIO *bioCipherTextIn; +@property (nonatomic, unsafe_unretained) BIO *bioCipherTextOut; + +@property (nonatomic, unsafe_unretained) uint8_t *bufferCipherText; + +@end + +@implementation TLSBox + +- (instancetype)init +{ + return [self initWithCAPath:nil]; +} + +- (instancetype)initWithCAPath:(NSString *)caPath +{ + if ((self = [super init])) { + self.caPath = caPath; + self.bufferCipherText = allocate_safely(TLSBoxMaxBufferLength); + } + return self; +} + +- (void)dealloc +{ + if (!self.ctx) { + return; + } + + BIO_free_all(self.bioPlainText); + SSL_free(self.ssl); + SSL_CTX_free(self.ctx); + self.isConnected = NO; + self.ctx = NULL; + + bzero(self.bufferCipherText, TLSBoxMaxBufferLength); + free(self.bufferCipherText); +} + +- (BOOL)startWithPeerVerification:(BOOL)peerVerification error:(NSError *__autoreleasing *)error +{ + if (!TLSBoxIsOpenSSLLoaded) { +// OPENSSL_init_ssl(0, NULL); + + TLSBoxIsOpenSSLLoaded = YES; + } + + self.ctx = SSL_CTX_new(TLS_client_method()); + SSL_CTX_set_options(self.ctx, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_COMPRESSION); + if (peerVerification && self.caPath) { + SSL_CTX_set_verify(self.ctx, SSL_VERIFY_PEER, TLSBoxVerifyPeer); + if (!SSL_CTX_load_verify_locations(self.ctx, [self.caPath cStringUsingEncoding:NSASCIIStringEncoding], NULL)) { + ERR_print_errors_fp(stdout); + if (error) { + *error = PIATunnelErrorWithCode(PIATunnelErrorCodeTLSBoxCA); + } + return NO; + } + } + else { + SSL_CTX_set_verify(self.ctx, SSL_VERIFY_NONE, NULL); + } + SSL_CTX_set1_curves_list(self.ctx, "X25519:prime256v1:secp521r1:secp384r1:secp256k1"); + + self.ssl = SSL_new(self.ctx); + + self.bioPlainText = BIO_new(BIO_f_ssl()); + self.bioCipherTextIn = BIO_new(BIO_s_mem()); + self.bioCipherTextOut = BIO_new(BIO_s_mem()); + + SSL_set_connect_state(self.ssl); + + SSL_set_bio(self.ssl, self.bioCipherTextIn, self.bioCipherTextOut); + BIO_set_ssl(self.bioPlainText, self.ssl, BIO_NOCLOSE); + + if (!SSL_do_handshake(self.ssl)) { + if (error) { + *error = PIATunnelErrorWithCode(PIATunnelErrorCodeTLSBoxHandshake); + } + return NO; + } + return YES; +} + +#pragma mark Pull + +- (NSData *)pullCipherTextWithError:(NSError *__autoreleasing *)error +{ + if (!self.isConnected && !SSL_is_init_finished(self.ssl)) { + SSL_do_handshake(self.ssl); + } + const int ret = BIO_read(self.bioCipherTextOut, self.bufferCipherText, TLSBoxMaxBufferLength); + if (!self.isConnected && SSL_is_init_finished(self.ssl)) { + self.isConnected = YES; + } + if (ret > 0) { + return [NSData dataWithBytes:self.bufferCipherText length:ret]; + } + if ((ret < 0) && !BIO_should_retry(self.bioCipherTextOut)) { + if (error) { + *error = PIATunnelErrorWithCode(PIATunnelErrorCodeTLSBoxGeneric); + } + } + return nil; +} + +- (BOOL)pullRawPlainText:(uint8_t *)text length:(NSInteger *)length error:(NSError *__autoreleasing *)error +{ + NSParameterAssert(text); + NSParameterAssert(length); + + const int ret = BIO_read(self.bioPlainText, text, TLSBoxMaxBufferLength); + if (ret > 0) { + *length = ret; + return YES; + } + if ((ret < 0) && !BIO_should_retry(self.bioPlainText)) { + if (error) { + *error = PIATunnelErrorWithCode(PIATunnelErrorCodeTLSBoxGeneric); + } + } + return NO; +} + +#pragma mark Put + +- (BOOL)putCipherText:(NSData *)text error:(NSError *__autoreleasing *)error +{ + NSParameterAssert(text); + + return [self putRawCipherText:(const uint8_t *)text.bytes length:text.length error:error]; +} + +- (BOOL)putRawCipherText:(const uint8_t *)text length:(NSInteger)length error:(NSError *__autoreleasing *)error +{ + NSParameterAssert(text); + + const int ret = BIO_write(self.bioCipherTextIn, text, (int)length); + if (ret != length) { + if (error) { + *error = PIATunnelErrorWithCode(PIATunnelErrorCodeTLSBoxGeneric); + } + return NO; + } + return YES; +} + +- (BOOL)putPlainText:(NSString *)text error:(NSError *__autoreleasing *)error +{ + NSParameterAssert(text); + + return [self putRawPlainText:(const uint8_t *)[text cStringUsingEncoding:NSASCIIStringEncoding] length:text.length error:error]; +} + +- (BOOL)putRawPlainText:(const uint8_t *)text length:(NSInteger)length error:(NSError *__autoreleasing *)error +{ + NSParameterAssert(text); + + const int ret = BIO_write(self.bioPlainText, text, (int)length); + if (ret != length) { + if (error) { + *error = PIATunnelErrorWithCode(PIATunnelErrorCodeTLSBoxGeneric); + } + return NO; + } + return YES; +} + +@end diff --git a/PIATunnel/Sources/Core/TunnelInterface.swift b/PIATunnel/Sources/Core/TunnelInterface.swift new file mode 100644 index 0000000..a8369ff --- /dev/null +++ b/PIATunnel/Sources/Core/TunnelInterface.swift @@ -0,0 +1,16 @@ +// +// TunnelInterface.swift +// PIATunnel +// +// Created by Davide De Rosa on 8/27/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation + +/// Represents a specific I/O interface meant to work at the tunnel layer (e.g. VPN). +public protocol TunnelInterface: IOInterface { + + /// When `true`, interface survives sessions. + var isPersistent: Bool { get } +} diff --git a/PIATunnel/Sources/Core/TunnelSettings.swift b/PIATunnel/Sources/Core/TunnelSettings.swift new file mode 100644 index 0000000..d94ba1a --- /dev/null +++ b/PIATunnel/Sources/Core/TunnelSettings.swift @@ -0,0 +1,50 @@ +// +// TunnelSettings.swift +// PIATunnel +// +// Created by Davide De Rosa on 2/7/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation + +enum TunnelSettingsError: Error { + case encoding +} + +struct TunnelSettings { + 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, cipherName: String, digestName: String) { + self.caMd5Digest = caMd5Digest + self.cipherName = cipherName + self.digestName = digestName + } + + // Ruby: pia_settings + func encodedData() throws -> Data { + guard let plainData = String(format: TunnelSettings.encodedFormat, cipherName, digestName, caMd5Digest).data(using: .ascii) else { + throw TunnelSettingsError.encoding + } + let keyBytes = try SecureRandom.data(length: TunnelSettings.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/PIATunnel/Sources/Core/ZeroingData.h b/PIATunnel/Sources/Core/ZeroingData.h new file mode 100644 index 0000000..34c6d87 --- /dev/null +++ b/PIATunnel/Sources/Core/ZeroingData.h @@ -0,0 +1,40 @@ +// +// ZeroingData.h +// PIATunnel +// +// Created by Davide De Rosa on 4/28/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +#import + +@interface ZeroingData : NSObject + +@property (nonatomic, readonly) const uint8_t *bytes; +@property (nonatomic, readonly) uint8_t *mutableBytes; +@property (nonatomic, readonly) NSInteger count; + +- (instancetype)initWithCount:(NSInteger)count; +- (instancetype)initWithBytes:(const uint8_t *)bytes count:(NSInteger)count; +- (instancetype)initWithUInt8:(uint8_t)uint8; +- (instancetype)initWithUInt16:(uint16_t)uint16; + +- (instancetype)initWithData:(NSData *)data; +- (instancetype)initWithData:(NSData *)data offset:(NSInteger)offset count:(NSInteger)count; +- (instancetype)initWithString:(NSString *)string nullTerminated:(BOOL)nullTerminated; + +- (void)appendData:(ZeroingData *)other; +//- (void)truncateToSize:(NSInteger)size; +- (void)removeUntilOffset:(NSInteger)until; +- (void)zero; + +- (nonnull ZeroingData *)appendingData:(ZeroingData *)other; +- (nonnull ZeroingData *)withOffset:(NSInteger)offset count:(NSInteger)count; +- (uint16_t)UInt16ValueFromOffset:(NSInteger)from; +- (uint16_t)networkUInt16ValueFromOffset:(NSInteger)from; +- (NSString *)nullTerminatedStringFromOffset:(NSInteger)from; + +- (BOOL)isEqualToData:(NSData *)data; +- (nonnull NSString *)toHex; + +@end diff --git a/PIATunnel/Sources/Core/ZeroingData.m b/PIATunnel/Sources/Core/ZeroingData.m new file mode 100644 index 0000000..e31267d --- /dev/null +++ b/PIATunnel/Sources/Core/ZeroingData.m @@ -0,0 +1,260 @@ +// +// ZeroingData.m +// PIATunnel +// +// Created by Davide De Rosa on 4/28/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +#import "ZeroingData.h" +#import "Allocation.h" + +@interface ZeroingData () { + uint8_t *_bytes; +} + +@end + +@implementation ZeroingData + +- (instancetype)init +{ + return [self initWithBytes:NULL count:0]; +} + +- (instancetype)initWithCount:(NSInteger)count +{ + if ((self = [super init])) { + _count = count; + _bytes = allocate_safely(count); + } + return self; +} + +- (instancetype)initWithBytes:(const uint8_t *)bytes count:(NSInteger)count +{ +// NSParameterAssert(bytes); + + if ((self = [super init])) { + _count = count; + _bytes = allocate_safely(count); + memcpy(_bytes, bytes, count); + } + return self; +} + +- (instancetype)initWithBytesNoCopy:(uint8_t *)bytes count:(NSInteger)count +{ + NSParameterAssert(bytes); + + if ((self = [super init])) { + _count = count; + _bytes = bytes; + } + return self; +} + +- (instancetype)initWithUInt8:(uint8_t)uint8 +{ + if ((self = [super init])) { + _count = 1; + _bytes = allocate_safely(_count); + _bytes[0] = uint8; + } + return self; +} + +- (instancetype)initWithUInt16:(uint16_t)uint16 +{ + if ((self = [super init])) { + _count = 2; + _bytes = allocate_safely(_count); + _bytes[0] = (uint16 & 0xff); + _bytes[1] = (uint16 >> 8); + } + return self; +} + +- (instancetype)initWithData:(NSData *)data +{ + return [self initWithData:data offset:0 count:data.length]; +} + +- (instancetype)initWithData:(NSData *)data offset:(NSInteger)offset count:(NSInteger)count +{ + NSParameterAssert(data); +// NSParameterAssert(offset <= data.length); + NSParameterAssert(offset + count <= data.length); + + if ((self = [super init])) { + _count = count; + _bytes = allocate_safely(count); + memcpy(_bytes, data.bytes + offset, count); + } + return self; +} + +- (instancetype)initWithString:(NSString *)string nullTerminated:(BOOL)nullTerminated +{ + NSParameterAssert(string); + + if ((self = [super init])) { + const char *stringBytes = [string cStringUsingEncoding:NSASCIIStringEncoding]; + const int stringLength = (int)string.length; + + _count = stringLength + (nullTerminated ? 1 : 0); + _bytes = allocate_safely(_count); + memcpy(_bytes, stringBytes, stringLength); + if (nullTerminated) { + _bytes[stringLength] = '\0'; + } + } + return self; +} + +- (void)dealloc +{ + bzero(_bytes, _count); + free(_bytes); +} + +- (const uint8_t *)bytes +{ + return _bytes; +} + +- (uint8_t *)mutableBytes +{ + return _bytes; +} + +- (void)appendData:(ZeroingData *)other +{ + NSParameterAssert(other); + + const NSInteger newCount = _count + other.count; + uint8_t *newBytes = allocate_safely(newCount); + memcpy(newBytes, _bytes, _count); + memcpy(newBytes + _count, other.bytes, other.count); + + bzero(_bytes, _count); + free(_bytes); + + _bytes = newBytes; + _count = newCount; +} + +- (void)truncateToSize:(NSInteger)size +{ + NSParameterAssert(size <= _count); + + uint8_t *newBytes = allocate_safely(size); + memcpy(newBytes, _bytes, size); + + bzero(_bytes, _count); + free(_bytes); + + _bytes = newBytes; + _count = size; +} + +- (void)removeUntilOffset:(NSInteger)until +{ + NSParameterAssert(until <= _count); + + const NSInteger newCount = _count - until; + uint8_t *newBytes = allocate_safely(newCount); + memcpy(newBytes, _bytes + until, newCount); + + bzero(_bytes, _count); + free(_bytes); + + _bytes = newBytes; + _count = newCount; +} + +- (void)zero +{ + bzero(_bytes, _count); +} + +- (ZeroingData *)appendingData:(ZeroingData *)other +{ + NSParameterAssert(other); + + const NSInteger newCount = _count + other.count; + uint8_t *newBytes = allocate_safely(newCount); + memcpy(newBytes, _bytes, _count); + memcpy(newBytes + _count, other.bytes, other.count); + + return [[ZeroingData alloc] initWithBytesNoCopy:newBytes count:newCount]; +} + +- (ZeroingData *)withOffset:(NSInteger)offset count:(NSInteger)count +{ +// NSParameterAssert(offset <= _count); + NSParameterAssert(offset + count <= _count); + + uint8_t *newBytes = allocate_safely(count); + memcpy(newBytes, _bytes + offset, count); + + return [[ZeroingData alloc] initWithBytesNoCopy:newBytes count:count]; +} + +- (uint16_t)UInt16ValueFromOffset:(NSInteger)from +{ + NSParameterAssert(from + 2 <= _count); + + uint16_t value = 0; + value |= _bytes[from]; + value |= _bytes[from + 1] << 8; + return value; +} + +- (uint16_t)networkUInt16ValueFromOffset:(NSInteger)from +{ + NSParameterAssert(from + 2 <= _count); + + uint16_t value = 0; + value |= _bytes[from]; + value |= _bytes[from + 1] << 8; + return CFSwapInt16BigToHost(value); +} + +- (NSString *)nullTerminatedStringFromOffset:(NSInteger)from +{ + NSParameterAssert(from <= _count); + + NSInteger nullOffset = NSNotFound; + for (NSInteger i = from; i < _count; ++i) { + if (_bytes[i] == 0) { + nullOffset = i; + break; + } + } + if (nullOffset == NSNotFound) { + return nil; + } + const NSInteger stringLength = nullOffset - from; + return [[NSString alloc] initWithBytes:_bytes length:stringLength encoding:NSASCIIStringEncoding]; +} + +- (BOOL)isEqualToData:(NSData *)data +{ + NSParameterAssert(data); + NSParameterAssert(data.length <= _count); + + return !memcmp(_bytes, data.bytes, _count); +} + +- (NSString *)toHex +{ + const NSUInteger capacity = _count * 2; + NSMutableString *hexString = [[NSMutableString alloc] initWithCapacity:capacity]; + for (int i = 0; i < _count; ++i) { + [hexString appendFormat:@"%02x", _bytes[i]]; + } + return hexString; +} + +@end diff --git a/PIATunnel/Sources/Core/ZeroingData.swift b/PIATunnel/Sources/Core/ZeroingData.swift new file mode 100644 index 0000000..ddb0e2e --- /dev/null +++ b/PIATunnel/Sources/Core/ZeroingData.swift @@ -0,0 +1,42 @@ +// +// ZeroingData.swift +// PIATunnel +// +// Created by Davide De Rosa on 4/27/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation +import __PIATunnelNative + +func Z() -> ZeroingData { + return ZeroingData() +} + +func Z(count: Int) -> ZeroingData { + return ZeroingData(count: count) +} + +func Z(bytes: UnsafePointer, count: Int) -> ZeroingData { + return ZeroingData(bytes: bytes, count: count) +} + +func Z(_ uint8: UInt8) -> ZeroingData { + return ZeroingData(uInt8: uint8) +} + +func Z(_ uint16: UInt16) -> ZeroingData { + return ZeroingData(uInt16: uint16) +} + +func Z(_ data: Data) -> ZeroingData { + return ZeroingData(data: data) +} + +//func Z(_ data: Data, _ offset: Int, _ count: Int) -> ZeroingData { +// return ZeroingData(data: data, offset: offset, count: count) +//} + +func Z(_ string: String, nullTerminated: Bool = false) -> ZeroingData { + return ZeroingData(string: string, nullTerminated: nullTerminated) +} diff --git a/PIATunnel/Sources/Core/module.modulemap b/PIATunnel/Sources/Core/module.modulemap new file mode 100644 index 0000000..7fc2bb4 --- /dev/null +++ b/PIATunnel/Sources/Core/module.modulemap @@ -0,0 +1,13 @@ +module __PIATunnelNative { + header "Errors.h" + header "ZeroingData.h" + header "TLSBox.h" + header "CryptoBox.h" + header "Encryption.h" + header "MSS.h" + header "PacketMacros.h" + header "ReplayProtector.h" + header "DataPath.h" + header "DataPathEncryption.h" + export * +} diff --git a/PIATunnelHost/AppDelegate.swift b/PIATunnelHost/AppDelegate.swift new file mode 100644 index 0000000..015c0eb --- /dev/null +++ b/PIATunnelHost/AppDelegate.swift @@ -0,0 +1,46 @@ +// +// AppDelegate.swift +// PIATunnelHost +// +// Created by Davide De Rosa on 9/24/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + // 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/PIATunnelHost/Assets.xcassets/AppIcon.appiconset/Contents.json b/PIATunnelHost/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..1d060ed --- /dev/null +++ b/PIATunnelHost/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,93 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/PIATunnelHost/Base.lproj/LaunchScreen.storyboard b/PIATunnelHost/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f83f6fd --- /dev/null +++ b/PIATunnelHost/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PIATunnelHost/Base.lproj/Main.storyboard b/PIATunnelHost/Base.lproj/Main.storyboard new file mode 100644 index 0000000..03c13c2 --- /dev/null +++ b/PIATunnelHost/Base.lproj/Main.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PIATunnelHost/Info.plist b/PIATunnelHost/Info.plist new file mode 100644 index 0000000..16be3b6 --- /dev/null +++ b/PIATunnelHost/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/PIATunnelHost/PIATunnelHost.entitlements b/PIATunnelHost/PIATunnelHost.entitlements new file mode 100644 index 0000000..0e617b3 --- /dev/null +++ b/PIATunnelHost/PIATunnelHost.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.application-groups + + group.com.privateinternetaccess + + keychain-access-groups + + $(AppIdentifierPrefix)group.com.privateinternetaccess + + + diff --git a/PIATunnelHost/ViewController.swift b/PIATunnelHost/ViewController.swift new file mode 100644 index 0000000..c9db307 --- /dev/null +++ b/PIATunnelHost/ViewController.swift @@ -0,0 +1,25 @@ +// +// ViewController.swift +// PIATunnelHost +// +// Created by Davide De Rosa on 9/24/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import UIKit + +class ViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view, typically from a nib. + } + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + + +} + diff --git a/PIATunnelTests/AppExtensionTests.swift b/PIATunnelTests/AppExtensionTests.swift new file mode 100644 index 0000000..b5e43d2 --- /dev/null +++ b/PIATunnelTests/AppExtensionTests.swift @@ -0,0 +1,82 @@ +// +// AppExtensionTests.swift +// PIATunnelTests +// +// Created by Davide De Rosa on 10/23/17. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import XCTest +@testable import PIATunnel +import NetworkExtension + +class AppExtensionTests: 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 testConfiguration() { + var builder: PIATunnelProvider.ConfigurationBuilder! + var cfg: PIATunnelProvider.Configuration! + + let identifier = "com.example.Provider" + let appGroup = "group.com.privateinternetaccess" + let endpoint = PIATunnelProvider.AuthenticatedEndpoint( + hostname: "example.com", + username: "foo", + password: "bar" + ) + + builder = PIATunnelProvider.ConfigurationBuilder(appGroup: appGroup) + XCTAssertNotNil(builder) + + builder.cipher = .aes128cbc + builder.digest = .sha256 + builder.handshake = .rsa3072 + cfg = builder.build() + + let proto = try? cfg.generatedTunnelProtocol(withBundleIdentifier: identifier, endpoint: endpoint) + XCTAssertNotNil(proto) + + XCTAssertEqual(proto?.providerBundleIdentifier, identifier) + XCTAssertEqual(proto?.serverAddress, endpoint.hostname) + XCTAssertEqual(proto?.username, endpoint.username) + XCTAssertEqual(proto?.passwordReference, try? Keychain(group: appGroup).passwordReference(for: endpoint.username)) + + if let pc = proto?.providerConfiguration { + print("\(pc)") + } + + let K = PIATunnelProvider.Configuration.Keys.self + XCTAssertEqual(proto?.providerConfiguration?[K.appGroup] as? String, cfg.appGroup) + XCTAssertEqual(proto?.providerConfiguration?[K.cipherAlgorithm] as? String, cfg.cipher.rawValue) + XCTAssertEqual(proto?.providerConfiguration?[K.digestAlgorithm] as? String, cfg.digest.rawValue) + XCTAssertEqual(proto?.providerConfiguration?[K.handshakeCertificate] as? String, cfg.handshake.rawValue) + XCTAssertEqual(proto?.providerConfiguration?[K.mtu] as? NSNumber, cfg.mtu) + XCTAssertEqual(proto?.providerConfiguration?[K.renegotiatesAfter] as? Int, cfg.renegotiatesAfterSeconds) + XCTAssertEqual(proto?.providerConfiguration?[K.debug] as? Bool, cfg.shouldDebug) + XCTAssertEqual(proto?.providerConfiguration?[K.debugLogKey] as? String, cfg.debugLogKey) + } + + func testDNSResolver() { + let exp = expectation(description: "DNS") + DNSResolver.resolve("djsbjhcbjzhbxjnvsd.com", timeout: 1000, queue: DispatchQueue.main) { (addrs, error) in + defer { + exp.fulfill() + } + guard let addrs = addrs else { + print("Can't resolve") + return + } + print("\(addrs)") + } + waitForExpectations(timeout: 5.0, handler: nil) + } +} diff --git a/PIATunnelTests/DataManipulationTests.swift b/PIATunnelTests/DataManipulationTests.swift new file mode 100644 index 0000000..51666dc --- /dev/null +++ b/PIATunnelTests/DataManipulationTests.swift @@ -0,0 +1,47 @@ +// +// DataManipulationTests.swift +// PIATunnelTests +// +// Created by Davide De Rosa on 07/07/2018. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import XCTest +@testable import PIATunnel + +class DataManipulationTests: 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 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.data, Data(hex: "12345678abcdef")) + XCTAssertEqual(z2.data, Data(hex: "5678ab")) + XCTAssertEqual(z3.data, Data(hex: "5678abaaddcc")) + } +} diff --git a/PIATunnelTests/DataPathEncryptionTests.swift b/PIATunnelTests/DataPathEncryptionTests.swift new file mode 100644 index 0000000..22f74b2 --- /dev/null +++ b/PIATunnelTests/DataPathEncryptionTests.swift @@ -0,0 +1,78 @@ +// +// DataPathEncryptionTests.swift +// PIATunnelTests +// +// Created by Davide De Rosa on 11/07/2018. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import XCTest +@testable import PIATunnel +@testable import __PIATunnelNative + +class DataPathEncryptionTests: XCTestCase { + private var cipherKey: ZeroingData! + + private var hmacKey: ZeroingData! + + override func setUp() { + cipherKey = try! SecureRandom.safeData(length: 32) + hmacKey = try! SecureRandom.safeData(length: 32) + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testCBC() { + privateTestDataPath(cipher: "aes-128-cbc", digest: "sha256", peerId: nil) + } + + func testFloatingCBC() { + privateTestDataPath(cipher: "aes-128-cbc", digest: "sha256", peerId: 0x64385837) + } + + func testGCM() { + privateTestDataPath(cipher: "aes-256-gcm", digest: nil, peerId: nil) + } + + func testFloatingGCM() { + privateTestDataPath(cipher: "aes-256-gcm", digest: nil, peerId: 0x64385837) + } + + func privateTestDataPath(cipher: String, digest: String?, peerId: UInt32?) { + let box = CryptoBox(cipherAlgorithm: cipher, digestAlgorithm: digest) + try! box.configure(withCipherEncKey: cipherKey, cipherDecKey: cipherKey, hmacEncKey: hmacKey, hmacDecKey: hmacKey) + let enc = box.encrypter().dataPathEncrypter() + let dec = box.decrypter().dataPathDecrypter() + + if let peerId = peerId { + enc.setPeerId(peerId) + dec.setPeerId(peerId) + XCTAssertEqual(enc.peerId(), peerId & 0xffffff) + XCTAssertEqual(dec.peerId(), peerId & 0xffffff) + } + + let payload = Data(hex: "00112233445566778899") + let packetId: UInt32 = 0x56341200 + let key: UInt8 = 4 + let compression: UInt8 = DataPacketCompressNone + var encryptedPayload: [UInt8] = [UInt8](repeating: 0, count: 1000) + var encryptedPayloadLength: Int = 0 + enc.assembleDataPacket(withPacketId: packetId, compression: compression, payload: payload, into: &encryptedPayload, length: &encryptedPayloadLength) + let encrypted = try! enc.encryptedDataPacket(withKey: key, packetId: packetId, payload: encryptedPayload, payloadLength: encryptedPayloadLength) + + var decrypted: [UInt8] = [UInt8](repeating: 0, count: 1000) + var decryptedLength: Int = 0 + var decryptedPacketId: UInt32 = 0 + var decryptedPayloadLength: Int = 0 + var decryptedCompression: UInt8 = 0 + try! dec.decryptDataPacket(encrypted, into: &decrypted, length: &decryptedLength, packetId: &decryptedPacketId) + let decryptedPtr = dec.parsePayload(withDataPacket: &decrypted, packetLength: decryptedLength, length: &decryptedPayloadLength, compression: &decryptedCompression) + let decryptedPayload = Data(bytes: decryptedPtr, count: decryptedPayloadLength) + + XCTAssertEqual(payload, decryptedPayload) + XCTAssertEqual(packetId, decryptedPacketId) + XCTAssertEqual(compression, decryptedCompression) + } +} diff --git a/PIATunnelTests/DataPathPerformanceTests.swift b/PIATunnelTests/DataPathPerformanceTests.swift new file mode 100644 index 0000000..63ae9cc --- /dev/null +++ b/PIATunnelTests/DataPathPerformanceTests.swift @@ -0,0 +1,64 @@ +// +// DataPathPerformanceTests.swift +// PIATunnelTests +// +// Created by Davide De Rosa on 07/07/2018. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import XCTest +@testable import PIATunnel +@testable import __PIATunnelNative + +class DataPathPerformanceTests: XCTestCase { + private var dataPath: DataPath! + + 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! EncryptionProxy("aes-128-cbc", "sha1", ck, ck, hk, hk) + encrypter = crypto.encrypter() + decrypter = crypto.decrypter() + + dataPath = DataPath(encrypter: encrypter, decrypter: decrypter, maxPackets: 200, usesReplayProtection: false) + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + +// // 28ms +// func testHighLevel() { +// let packets = TestUtils.generateDataSuite(1200, 1000) +// var encryptedPackets: [Data]! +// var decryptedPackets: [Data]! +// +// measure { +// encryptedPackets = try! self.swiftDP.encryptPackets(packets, key: 0) +// decryptedPackets = try! self.swiftDP.decryptPackets(encryptedPackets, keepAlive: nil) +// } +// +//// 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/PIATunnelTests/EncryptionPerformanceTests.swift b/PIATunnelTests/EncryptionPerformanceTests.swift new file mode 100644 index 0000000..d3b8179 --- /dev/null +++ b/PIATunnelTests/EncryptionPerformanceTests.swift @@ -0,0 +1,61 @@ +// +// EncryptionPerformanceTests.swift +// PIATunnelTests +// +// Created by Davide De Rosa on 07/07/2018. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import XCTest +@testable import PIATunnel +@testable import __PIATunnelNative + +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() + cbcDecrypter = cbc.decrypter() + + let gcm = CryptoBox(cipherAlgorithm: "aes-128-gcm", digestAlgorithm: nil) + try! gcm.configure(withCipherEncKey: cipherKey, cipherDecKey: cipherKey, hmacEncKey: hmacKey, hmacDecKey: hmacKey) + gcmEncrypter = gcm.encrypter() + gcmDecrypter = gcm.decrypter() + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + // 1.150s + func testCBCEncryption() { + let suite = TestUtils.generateDataSuite(1000, 100000) + measure { + for data in suite { + let _ = try! self.cbcEncrypter.encryptData(data, offset: 0, extra: nil) + } + } + } + + // 0.684s + func testGCMEncryption() { + let suite = TestUtils.generateDataSuite(1000, 100000) + let extra: [UInt8] = [0x11, 0x22, 0x33, 0x44] + measure { + for data in suite { + let _ = try! self.gcmEncrypter.encryptData(data, offset: 0, extra: extra) + } + } + } +} diff --git a/PIATunnelTests/EncryptionTests.swift b/PIATunnelTests/EncryptionTests.swift new file mode 100644 index 0000000..9b79491 --- /dev/null +++ b/PIATunnelTests/EncryptionTests.swift @@ -0,0 +1,75 @@ +// +// EncryptionTests.swift +// PIATunnelTests +// +// Created by Davide De Rosa on 07/07/2018. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import XCTest +@testable import PIATunnel +@testable import __PIATunnelNative + +class EncryptionTests: XCTestCase { + private var cipherKey: ZeroingData! + + private var hmacKey: ZeroingData! + + override func setUp() { + cipherKey = try! SecureRandom.safeData(length: 32) + hmacKey = try! SecureRandom.safeData(length: 32) + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testCBC() { + let cbc = CryptoBox(cipherAlgorithm: "aes-128-cbc", digestAlgorithm: "sha256") + try! cbc.configure(withCipherEncKey: cipherKey, cipherDecKey: cipherKey, hmacEncKey: hmacKey, hmacDecKey: hmacKey) + let enc = cbc.encrypter() + let dec = cbc.decrypter() + + let plain = Data(hex: "00112233445566778899") + let encrypted = try! enc.encryptData(plain, offset: 0, extra: nil) + let decrypted = try! dec.decryptData(encrypted, offset: 0, extra: nil) + XCTAssertEqual(plain, decrypted) + } + + func testGCM() { + let gcm = CryptoBox(cipherAlgorithm: "aes-256-gcm", digestAlgorithm: nil) + try! gcm.configure(withCipherEncKey: cipherKey, cipherDecKey: cipherKey, hmacEncKey: hmacKey, hmacDecKey: hmacKey) + let enc = gcm.encrypter() + let dec = gcm.decrypter() + +// let packetId: UInt32 = 0x56341200 + let extra: [UInt8] = [0x00, 0x12, 0x34, 0x56] + let plain = Data(hex: "00112233445566778899") + let encrypted = try! enc.encryptData(plain, offset: 0, extra: extra) + let decrypted = try! dec.decryptData(encrypted, offset: 0, extra: extra) + XCTAssertEqual(plain, decrypted) + } + +// func testCryptoOperation() { +// let data = Data(hex: "aabbccddeeff") +// +// print("Original : \(data.toHex())") +// var enc: Data +// var dec: Data +// +// enc = Data() +// enc.append(try! encrypter.encryptData(data, offset: 0, packetId: 0)) +// print("Encrypted: \(enc.toHex())") +// dec = try! decrypter.decryptData(enc, offset: 0, packetId: 0) +// print("Decrypted: \(dec.toHex())") +// XCTAssert(dec == data) +// +// let prefix = "abcdef" +// enc = Data(hex: prefix) +// enc.append(try! encrypter.encryptData(data, offset: 0, packetId: 0)) +// print("Encrypted: \(enc.toHex())") +// dec = try! decrypter.decryptData(enc, offset: (prefix.count / 2), packetId: 0) +// print("Decrypted: \(dec.toHex())") +// XCTAssert(dec == data) +// } +} diff --git a/PIATunnelTests/Info.plist b/PIATunnelTests/Info.plist new file mode 100644 index 0000000..6c40a6c --- /dev/null +++ b/PIATunnelTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/PIATunnelTests/LinkTests.swift b/PIATunnelTests/LinkTests.swift new file mode 100644 index 0000000..f06a54b --- /dev/null +++ b/PIATunnelTests/LinkTests.swift @@ -0,0 +1,155 @@ +// +// LinkTests.swift +// PIATunnelTests +// +// Created by Davide De Rosa on 07/07/2018. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import XCTest +@testable import PIATunnel + +class LinkTests: 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. + } + + // 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 + + func testPacketStream() { + var bytes: [UInt8] = [] + var until: Int + var packets: [Data] + + bytes.append(contentsOf: [0x00, 0x04]) + bytes.append(contentsOf: [0x10, 0x20, 0x30, 0x40]) + bytes.append(contentsOf: [0x00, 0x07]) + bytes.append(contentsOf: [0x10, 0x20, 0x30, 0x40, 0x50, 0x66, 0x77]) + bytes.append(contentsOf: [0x00, 0x01]) + bytes.append(contentsOf: [0xff]) + bytes.append(contentsOf: [0x00, 0x03]) + bytes.append(contentsOf: [0xaa]) + XCTAssertEqual(bytes.count, 21) + + (until, packets) = CommonPacket.parsed(Data(bytes: bytes)) + XCTAssertEqual(until, 18) + XCTAssertEqual(packets.count, 3) + + bytes.append(contentsOf: [0xbb, 0xcc]) + (until, packets) = CommonPacket.parsed(Data(bytes: bytes)) + XCTAssertEqual(until, 23) + XCTAssertEqual(packets.count, 4) + + bytes.append(contentsOf: [0x00, 0x05]) + (until, packets) = CommonPacket.parsed(Data(bytes: bytes)) + XCTAssertEqual(until, 23) + XCTAssertEqual(packets.count, 4) + + bytes.append(contentsOf: [0x11, 0x22, 0x33, 0x44]) + (until, packets) = CommonPacket.parsed(Data(bytes: bytes)) + XCTAssertEqual(until, 23) + XCTAssertEqual(packets.count, 4) + + bytes.append(contentsOf: [0x55]) + (until, packets) = CommonPacket.parsed(Data(bytes: bytes)) + XCTAssertEqual(until, 30) + XCTAssertEqual(packets.count, 5) + + // + + bytes.removeSubrange(0.. [Int] { + var q = [Int]() + var id = 0 + var hdl = [Int]() + for p in seq { + enqueueControl(&q, &id, p) { + hdl.append($0) + } + print() + } + 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) { + q.removeFirst() + continue + } + if (p != id) { + return + } + + h(p) + print("handle(\(p))") + id += 1 + q.removeFirst() + } + } +} diff --git a/PIATunnelTests/RandomTests.swift b/PIATunnelTests/RandomTests.swift new file mode 100644 index 0000000..0dc4314 --- /dev/null +++ b/PIATunnelTests/RandomTests.swift @@ -0,0 +1,34 @@ +// +// RandomTests.swift +// PIATunnelTests +// +// Created by Davide De Rosa on 07/07/2018. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import XCTest +@testable import PIATunnel + +class RandomTests: 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 testRandom1() { + print(try! SecureRandom.uint32()) + print(try! SecureRandom.uint32()) + print(try! SecureRandom.uint32()) + print(try! SecureRandom.uint32()) + print(try! SecureRandom.uint32()) + } + + func testRandom2() { + print("random UInt32: \(try! SecureRandom.uint32())") + print("random bytes: \(try! SecureRandom.data(length: 12).toHex())") + } +} diff --git a/PIATunnelTests/RawPerformanceTests.swift b/PIATunnelTests/RawPerformanceTests.swift new file mode 100644 index 0000000..5473650 --- /dev/null +++ b/PIATunnelTests/RawPerformanceTests.swift @@ -0,0 +1,126 @@ +// +// RawPerformanceTests.swift +// PIATunnelTests +// +// Created by Davide De Rosa on 07/07/2018. +// Copyright © 2018 London Trust Media. All rights reserved. +// + +import Foundation + +import XCTest +@testable import PIATunnel + +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) + } + } + } + + // 0.463s + func testUInt16FromPointers() { + let data = Data([0x22, 0xff, 0xaa, 0xbb, 0x55, 0x66]) + + measure { + for _ in 0..<1000000 { + let _ = data.UInt16ValueFromPointers(from: 3) + } + } + } + + // 0.863s + func testUInt32FromBuffer() { + let data = Data([0x22, 0xff, 0xaa, 0xbb, 0x55, 0x66]) + + measure { + for _ in 0..<1000000 { + let _ = data.UInt32ValueFromBuffer(from: 1) + } + } + } + + // 0.469s + func testUInt32FromPointers() { + let data = Data([0x22, 0xff, 0xaa, 0xbb, 0x55, 0x66]) + + measure { + for _ in 0..<1000000 { + let _ = data.UInt32Value(from: 1) + } + } + } + + // 0.071s + func testRandomUInt32FromBuffer() { + measure { + for _ in 0..<10000 { + let _ = try! SecureRandom.uint32FromBuffer() + } + } + } + + // 0.063s + func testRandomUInt32FromPointers() { + measure { + for _ in 0..<10000 { + let _ = try! SecureRandom.uint32() + } + } + } + + // 0.215s + func testMyPacketHeader() { + let suite = TestUtils.generateDataSuite(1000, 200000) + measure { + for data in suite { + CFSwapInt32BigToHost(data.UInt32Value(from: 0)) + } + } + } + + // 0.146s + func testStevePacketHeader() { + let suite = TestUtils.generateDataSuite(1000, 200000) + measure { + for data in suite { +// let _ = UInt32(bigEndian: data.subdata(in: 0..<4).withUnsafeBytes { $0.pointee }) + let _ = data.networkUInt32Value(from: 0) + } + } + } + + // 0.060s + func testDataSubdata() { + let suite = TestUtils.generateDataSuite(1000, 100000) + measure { + for data in suite { + let _ = data.subdata(in: 5.. [Int] { + return v.reduce([]){ $0.contains($1) ? $0 : $0 + [$1] } + } + + static func generateDataSuite(_ size: Int, _ count: Int) -> [Data] { + var suite = [Data]() + for _ in 0.. 1.1.0h' + + target 'PIATunnel-iOS' do + platform :ios, '9.0' + end + target 'PIATunnelHost' do + platform :ios, '9.0' + end + + target 'PIATunnel-macOS' do + platform :osx, '10.11' + end +end diff --git a/Podfile.lock b/Podfile.lock new file mode 100644 index 0000000..1254d23 --- /dev/null +++ b/Podfile.lock @@ -0,0 +1,20 @@ +PODS: + - OpenSSL-Apple (1.1.0h) + - SwiftyBeaver (1.6.0) + +DEPENDENCIES: + - OpenSSL-Apple (~> 1.1.0h) + - SwiftyBeaver + +SPEC REPOS: + https://github.com/cocoapods/specs.git: + - OpenSSL-Apple + - SwiftyBeaver + +SPEC CHECKSUMS: + OpenSSL-Apple: cd153d705ef350eb834ae7ff5f21f792b51ed208 + SwiftyBeaver: e45759613e50b522b0e6f53b1f0f14389b45ca34 + +PODFILE CHECKSUM: d93463e96c0da9f94811327c6e2b123c6453c591 + +COCOAPODS: 1.5.3 diff --git a/README.md b/README.md new file mode 100644 index 0000000..0e80fe6 --- /dev/null +++ b/README.md @@ -0,0 +1,157 @@ +[![PIA logo][pia-image]][pia-url] + +# Private Internet Access + +Private Internet Access is the world's leading consumer VPN service. At Private Internet Access we believe in unfettered access for all, and as a firm supporter of the open source ecosystem we have made the decision to open source our VPN clients. For more information about the PIA service, please visit our website [privateinternetaccess.com][pia-url] or check out the [Wiki][pia-wiki]. + +# Tunnel for Apple platforms + +This library provides a simplified Swift/Obj-C implementation of the OpenVPN® protocol for the Apple platforms, while also taking advantage of the Private Internet Access [client patch customizations](https://www.privateinternetaccess.com/forum/discussion/9093/pia-openvpn-client-encryption-patch). The crypto layer is built on top of [OpenSSL][dep-openssl] 1.1.0h, which in turn enables support for a certain range of encryption and digest algorithms. + +## Getting started + +The client is known to work with [OpenVPN®][openvpn] 2.3+ servers. Key renegotiation and replay protection are also included, but full-fledged configuration files (.ovpn) are not currently supported. + +- [x] Handshake and tunneling over UDP or TCP +- [x] Client-initiated renegotiation +- [x] Replay protection (hardcoded window) +- [x] Data encryption + - AES-CBC (128 and 256 bit) + - AES-GCM (128 and 256 bit) +- [x] HMAC digest + - SHA-1 + - SHA-256 +- [x] TLS CA validation + - RSA (2048, 3072 and 4096 bit) + - ECC (secp256r1, secp521r1, secp256k1) + - Custom certificate + +## Installation + +### Requirements + +- iOS 9.0+ / macOS 10.11+ +- Xcode 9+ (Swift 4) +- Git (preinstalled with Xcode Command Line Tools) +- Ruby (preinstalled with macOS) +- [CocoaPods 1.5.0][dep-cocoapods] +- [jazzy][dep-jazzy] (optional, for documentation) + +It's highly recommended to use the Git and Ruby packages provided by [Homebrew][dep-brew]. + +### CocoaPods + +To use with CocoaPods just add this to your Podfile: + +```ruby +pod 'PIATunnel' +``` + +### Testing + +Download the library codebase locally: + + $ git clone https://github.com/pia-foss/tunnel-apple.git + +Assuming you have a [working CocoaPods environment][dep-cocoapods], setting up the library workspace only requires installing the pod dependencies: + + $ pod install + +After that, open `PIATunnel.xcworkspace` in Xcode and run the unit tests found in the `PIATunnelTests` target. A simple CMD+U while on `PIATunnel-iOS` should do that as well. + +#### Demo + +There is a `Demo` directory containing a simple app for testing the tunnel, called `BasicTunnel`. As usual, prepare for CocoaPods: + + $ pod install + +then open `Demo.xcworkspace` and run the `BasicTunnel-iOS` target. + +For the VPN to work properly, the `BasicTunnel` demo requires: + +- _App Groups_ and _Keychain Sharing_ capabilities +- App IDs with _Packet Tunnel_ entitlements + +both in the main app and the tunnel extension target. + +In order to test connection to your own server rather than a PIA server, modify the file `Demo/BasicTunnel-[iOS|macOS]/ViewController.swift` and make sure to: + +- Replace `.pia` with `.vanilla` in `builder.endpointProtocols`. +- Set `builder.handshake` to `.custom`. +- Set `builder.ca` to the PEM formatted certificate of your VPN server's CA. + +Example: + + builder.endpointProtocols = [PIATunnelProvider.EndpointProtocol(.udp, 1194, .vanilla)] + builder.handshake = .custom + builder.ca = """ + -----BEGIN CERTIFICATE----- + MIIFJDCC... + -----END CERTIFICATE----- + """ + +## Documentation + +The library is split into two modules, in order to decouple the low-level protocol implementation from the platform-specific bridging, namely the [NetworkExtension][ne-home] VPN framework. + +Full documentation of the public interface is available and can be generated with [jazzy][dep-jazzy]. After installing the jazzy Ruby gem with: + + $ gem install jazzy + +enter the root directory of the repository and run: + + $ jazzy + +The generated output is stored into the `docs` directory in HTML format. + +### Core + +Here you will find the low-level entities on top of which the connection is established. Code is mixed Swift and Obj-C, most of it is not exposed to consumers. The *Core* module depends on OpenSSL and is mostly platform-agnostic. + +The entry point is the `SessionProxy` class. The networking layer is fully abstract and delegated externally with the use of opaque `IOInterface` (`LinkInterface` and `TunnelInterface`) and `SessionProxyDelegate` protocols. + +### AppExtension + +The goal of this module is packaging up a black box implementation of a [NEPacketTunnelProvider][ne-ptp], which is the essential part of a Packet Tunnel Provider app extension. You will find the main implementation in the `PIATunnelProvider` class. + +Currently, the extension supports VPN over both [UDP][ne-udp] and [TCP][ne-tcp] sockets. A debug log snapshot is optionally maintained and shared to host apps via `UserDefaults` in a shared App Group. + +## Contributing + +By contributing to this project you are agreeing to the terms stated in the Contributor License Agreement (CLA) [here](/CLA.rst). + +For more details please see [CONTRIBUTING](/CONTRIBUTING.md). + +Issues and Pull Requests should use these templates: [ISSUE](/.github/ISSUE_TEMPLATE.md) and [PULL REQUEST](/.github/PULL_REQUEST_TEMPLATE.md). + +## Authors + +- Davide De Rosa - [keeshux](https://github.com/keeshux) +- Steve + +## License + +This project is licensed under the [MIT (Expat) license](https://choosealicense.com/licenses/mit/), which can be found [here](/LICENSE). + +## Acknowledgements + +- SwiftyBeaver - © 2015 Sebastian Kreutzberger + +This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit. ([https://www.openssl.org/][dep-openssl]) + +© 2002-2018 OpenVPN Inc. - OpenVPN is a registered trademark of OpenVPN Inc. + +[pia-image]: https://www.privateinternetaccess.com/assets/PIALogo2x-0d1e1094ac909ea4c93df06e2da3db4ee8a73d8b2770f0f7d768a8603c62a82f.png +[pia-url]: https://www.privateinternetaccess.com/ +[pia-wiki]: https://en.wikipedia.org/wiki/Private_Internet_Access + +[openvpn]: https://openvpn.net/index.php/open-source/overview.html +[dep-cocoapods]: https://guides.cocoapods.org/using/getting-started.html +[dep-jazzy]: https://github.com/realm/jazzy +[dep-brew]: https://brew.sh/ +[dep-openssl]: https://www.openssl.org/ + +[ne-home]: https://developer.apple.com/documentation/networkextension +[ne-ptp]: https://developer.apple.com/documentation/networkextension/nepackettunnelprovider +[ne-udp]: https://developer.apple.com/documentation/networkextension/nwudpsession +[ne-tcp]: https://developer.apple.com/documentation/networkextension/nwtcpconnection