Initial commit

This commit is contained in:
Davide De Rosa 2018-08-23 10:19:25 +02:00
commit 8c30bb3995
126 changed files with 13223 additions and 0 deletions

34
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@ -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

40
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -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**
<!-- Summary of the PR -->
This PR fixes/implements the following **bugs/features**
* [ ] Bug 1
* [ ] Bug 2
* [ ] Feature 1
* [ ] Feature 2
* [ ] Breaking changes
<!-- You can skip this if you're fixing a typo or adding an app to the Showcase. -->
Explain the **motivation** for making this change. What existing problem does the pull request solve?
<!-- Example: When "Adding a function to do X", explain why it is necessary to have a way to do X. -->
**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.
<!-- Make sure tests pass on both Travis and Circle CI. -->
**Code formatting**
<!-- See the simple style guide. -->
**Closing issues**
<!-- Put `closes #XXXX` in your comment to auto-close the issue that your PR fixes (if such). -->
Fixes #

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
.DS_Store
*.swp
*.pbxuser
**/*.xcworkspace/xcuserdata
**/*.xcodeproj/project.xcworkspace
**/*.xcodeproj/xcuserdata
Pods
docs

26
.jazzy.yaml Normal file
View File

@ -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

1
.swift-version Normal file
View File

@ -0,0 +1 @@
4.0

12
CLA.rst Normal file
View File

@ -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.

22
CONTRIBUTING.md Normal file
View File

@ -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. Wed 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.

7
Demo/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
.DS_Store
*.swp
*.pbxuser
**/*.xcworkspace/xcuserdata
**/*.xcodeproj/project.xcworkspace
**/*.xcodeproj/xcuserdata
Pods

View File

@ -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:.
}
}

View File

@ -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"
}
}

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" systemVersion="17A277" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>

View File

@ -0,0 +1,159 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14109" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="BasicTunnel_iOS" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" clearButtonMode="always" translatesAutoresizingMaskIntoConstraints="NO" id="bc6-yT-aty">
<rect key="frame" x="20" y="190" width="335" height="30"/>
<nil key="textColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" keyboardType="alphabet" secureTextEntry="YES"/>
</textField>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" clearButtonMode="always" translatesAutoresizingMaskIntoConstraints="NO" id="ONL-vF-iUY">
<rect key="frame" x="20" y="40" width="335" height="30"/>
<nil key="textColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" keyboardType="alphabet"/>
</textField>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" clearButtonMode="always" translatesAutoresizingMaskIntoConstraints="NO" id="dQS-Ma-dYP">
<rect key="frame" x="20" y="140" width="335" height="30"/>
<nil key="textColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" keyboardType="alphabet"/>
</textField>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" clearButtonMode="always" translatesAutoresizingMaskIntoConstraints="NO" id="XwE-sE-aPN">
<rect key="frame" x="20" y="90" width="245" height="30"/>
<nil key="textColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" keyboardType="alphabet"/>
</textField>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="7LH-tE-it9">
<rect key="frame" x="275" y="90" width="80" height="30"/>
<constraints>
<constraint firstAttribute="width" constant="80" id="aWP-Ug-b9B"/>
</constraints>
<nil key="textColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
</textField>
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="249" contentHorizontalAlignment="left" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Teo-8d-LYJ">
<rect key="frame" x="20" y="240" width="237" height="70"/>
<fontDescription key="fontDescription" type="system" pointSize="48"/>
<state key="normal" title="Connect"/>
<connections>
<action selector="connectionClicked:" destination="BYZ-38-t0r" eventType="touchUpInside" id="gmm-j5-8Zz"/>
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="TCP" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sA4-W9-jxo">
<rect key="frame" x="312" y="240" width="37.5" height="24"/>
<fontDescription key="fontDescription" type="system" pointSize="20"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="WZf-S5-SqC">
<rect key="frame" x="306" y="279" width="51" height="31"/>
<connections>
<action selector="tcpClicked:" destination="BYZ-38-t0r" eventType="valueChanged" id="ZJI-Jw-pow"/>
</connections>
</switch>
<button hidden="YES" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Gg2-18-Pq8">
<rect key="frame" x="267" y="257" width="88" height="36"/>
<fontDescription key="fontDescription" type="system" pointSize="20"/>
<state key="normal" title="Download"/>
<connections>
<action selector="download" destination="BYZ-38-t0r" eventType="touchUpInside" id="D3u-Dg-Bcd"/>
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="??" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="b1e-IE-2kv">
<rect key="frame" x="179" y="318" width="17" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="249" contentHorizontalAlignment="left" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="6dU-fF-FSg">
<rect key="frame" x="20" y="310" width="78" height="41"/>
<fontDescription key="fontDescription" type="system" pointSize="24"/>
<state key="normal" title="See log"/>
<connections>
<action selector="displayLog" destination="BYZ-38-t0r" eventType="touchUpInside" id="0ZH-zK-igi"/>
</connections>
</button>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="UNN-CR-rdr">
<rect key="frame" x="20" y="371" width="335" height="276"/>
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
<fontDescription key="fontDescription" name="CourierNewPSMT" family="Courier New" pointSize="17"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="UNN-CR-rdr" firstAttribute="top" secondItem="6dU-fF-FSg" secondAttribute="bottom" constant="20" id="03h-H3-dSN"/>
<constraint firstItem="7LH-tE-it9" firstAttribute="centerY" secondItem="XwE-sE-aPN" secondAttribute="centerY" id="0wJ-c9-Gcy"/>
<constraint firstItem="Teo-8d-LYJ" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="20" id="2uM-A3-gZl"/>
<constraint firstItem="dQS-Ma-dYP" firstAttribute="leading" secondItem="bc6-yT-aty" secondAttribute="leading" id="3Ra-eC-A82"/>
<constraint firstItem="bc6-yT-aty" firstAttribute="top" secondItem="dQS-Ma-dYP" secondAttribute="bottom" constant="20" id="C2Y-oZ-jdP"/>
<constraint firstItem="WZf-S5-SqC" firstAttribute="bottom" secondItem="Teo-8d-LYJ" secondAttribute="bottom" id="HGt-O3-p5F"/>
<constraint firstItem="ONL-vF-iUY" firstAttribute="trailing" secondItem="bc6-yT-aty" secondAttribute="trailing" id="OS0-Uz-cCz"/>
<constraint firstItem="6dU-fF-FSg" firstAttribute="leading" secondItem="Teo-8d-LYJ" secondAttribute="leading" id="P9y-bj-2tC"/>
<constraint firstItem="XwE-sE-aPN" firstAttribute="top" secondItem="ONL-vF-iUY" secondAttribute="bottom" constant="20" id="XvO-b0-4Pl"/>
<constraint firstItem="UNN-CR-rdr" firstAttribute="trailing" secondItem="bc6-yT-aty" secondAttribute="trailing" id="Zep-Ze-DNM"/>
<constraint firstItem="UNN-CR-rdr" firstAttribute="leading" secondItem="bc6-yT-aty" secondAttribute="leading" id="Zix-pG-8bU"/>
<constraint firstAttribute="trailing" secondItem="bc6-yT-aty" secondAttribute="trailing" constant="20" id="cR0-Np-zTX"/>
<constraint firstItem="dQS-Ma-dYP" firstAttribute="trailing" secondItem="bc6-yT-aty" secondAttribute="trailing" id="eNP-iD-wgR"/>
<constraint firstItem="wfy-db-euE" firstAttribute="top" secondItem="UNN-CR-rdr" secondAttribute="bottom" constant="20" id="efh-iS-51K"/>
<constraint firstItem="XwE-sE-aPN" firstAttribute="leading" secondItem="ONL-vF-iUY" secondAttribute="leading" id="fJB-38-JYZ"/>
<constraint firstItem="b1e-IE-2kv" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="fuR-6q-fd6"/>
<constraint firstItem="sA4-W9-jxo" firstAttribute="top" secondItem="Teo-8d-LYJ" secondAttribute="top" id="grl-36-NDr"/>
<constraint firstItem="ONL-vF-iUY" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" constant="20" id="lNh-0d-b3b"/>
<constraint firstItem="dQS-Ma-dYP" firstAttribute="top" secondItem="XwE-sE-aPN" secondAttribute="bottom" constant="20" id="lxB-za-cHQ"/>
<constraint firstItem="7LH-tE-it9" firstAttribute="leading" secondItem="XwE-sE-aPN" secondAttribute="trailing" constant="10" id="oYJ-8E-Ieq"/>
<constraint firstItem="6dU-fF-FSg" firstAttribute="top" secondItem="Teo-8d-LYJ" secondAttribute="bottom" id="pAc-Ee-Ryj"/>
<constraint firstItem="Teo-8d-LYJ" firstAttribute="top" secondItem="bc6-yT-aty" secondAttribute="bottom" constant="20" id="qnB-Iz-Gbp"/>
<constraint firstItem="b1e-IE-2kv" firstAttribute="top" secondItem="Teo-8d-LYJ" secondAttribute="bottom" constant="8" id="rCG-di-kDu"/>
<constraint firstItem="ONL-vF-iUY" firstAttribute="leading" secondItem="bc6-yT-aty" secondAttribute="leading" id="sez-Cv-mp9"/>
<constraint firstItem="Gg2-18-Pq8" firstAttribute="centerY" secondItem="Teo-8d-LYJ" secondAttribute="centerY" id="tHU-1i-TyC"/>
<constraint firstItem="bc6-yT-aty" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="20" id="tZc-ru-OTc"/>
<constraint firstItem="7LH-tE-it9" firstAttribute="trailing" secondItem="ONL-vF-iUY" secondAttribute="trailing" id="tlD-XS-qwO"/>
<constraint firstItem="sA4-W9-jxo" firstAttribute="centerX" secondItem="WZf-S5-SqC" secondAttribute="centerX" id="v80-dG-aB7"/>
<constraint firstAttribute="trailing" secondItem="Gg2-18-Pq8" secondAttribute="trailing" constant="20" id="vzB-UP-pVi"/>
<constraint firstAttribute="trailing" secondItem="WZf-S5-SqC" secondAttribute="trailing" constant="20" id="wMy-Qf-9Bi"/>
<constraint firstItem="Gg2-18-Pq8" firstAttribute="leading" secondItem="Teo-8d-LYJ" secondAttribute="trailing" constant="10" id="yWA-EY-Qao"/>
</constraints>
</view>
<connections>
<outlet property="buttonConnection" destination="Teo-8d-LYJ" id="evE-2I-13A"/>
<outlet property="buttonDownload" destination="Gg2-18-Pq8" id="3u5-YL-8tg"/>
<outlet property="labelDownload" destination="b1e-IE-2kv" id="AXL-Lj-KVd"/>
<outlet property="switchTCP" destination="WZf-S5-SqC" id="UyR-J2-iX0"/>
<outlet property="textDomain" destination="XwE-sE-aPN" id="byL-Ai-eAD"/>
<outlet property="textLog" destination="UNN-CR-rdr" id="a9o-b3-nHT"/>
<outlet property="textPassword" destination="bc6-yT-aty" id="y4O-yE-C7B"/>
<outlet property="textPort" destination="7LH-tE-it9" id="KhW-ql-oN9"/>
<outlet property="textServer" destination="ONL-vF-iUY" id="To8-yq-phE"/>
<outlet property="textUsername" destination="dQS-Ma-dYP" id="2Ew-4O-QqC"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="140" y="137.18140929535232"/>
</scene>
</scenes>
</document>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>packet-tunnel-provider</string>
</array>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.privateinternetaccess.ios.demo.BasicTunnel</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)group.com.privateinternetaccess.ios.demo.BasicTunnel</string>
</array>
</dict>
</plist>

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View File

@ -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)")
}
}

View File

@ -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
}
}

View File

@ -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"
}
}

View File

@ -0,0 +1,804 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Application-->
<scene sceneID="JPo-4y-FX3">
<objects>
<application id="hnw-xV-0zn" sceneMemberID="viewController">
<menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
<menuItem title="BasicTunnelMac" id="1Xt-HY-uBw">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="BasicTunnelMac" systemMenu="apple" id="uQy-DD-JDr">
<items>
<menuItem title="About BasicTunnelMac" id="5kV-Vb-QxS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="Ady-hI-5gd" id="Exp-CZ-Vem"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
<menuItem title="Services" id="NMo-om-nkz">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
<menuItem title="Hide BasicTunnelMac" keyEquivalent="h" id="Olw-nP-bQN">
<connections>
<action selector="hide:" target="Ady-hI-5gd" id="PnN-Uc-m68"/>
</connections>
</menuItem>
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="hideOtherApplications:" target="Ady-hI-5gd" id="VT4-aY-XCT"/>
</connections>
</menuItem>
<menuItem title="Show All" id="Kd2-mp-pUS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unhideAllApplications:" target="Ady-hI-5gd" id="Dhg-Le-xox"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
<menuItem title="Quit BasicTunnelMac" keyEquivalent="q" id="4sb-4s-VLi">
<connections>
<action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="File" id="dMs-cI-mzQ">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="File" id="bib-Uj-vzu">
<items>
<menuItem title="New" keyEquivalent="n" id="Was-JA-tGl">
<connections>
<action selector="newDocument:" target="Ady-hI-5gd" id="4Si-XN-c54"/>
</connections>
</menuItem>
<menuItem title="Open…" keyEquivalent="o" id="IAo-SY-fd9">
<connections>
<action selector="openDocument:" target="Ady-hI-5gd" id="bVn-NM-KNZ"/>
</connections>
</menuItem>
<menuItem title="Open Recent" id="tXI-mr-wws">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Open Recent" systemMenu="recentDocuments" id="oas-Oc-fiZ">
<items>
<menuItem title="Clear Menu" id="vNY-rz-j42">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="clearRecentDocuments:" target="Ady-hI-5gd" id="Daa-9d-B3U"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="m54-Is-iLE"/>
<menuItem title="Close" keyEquivalent="w" id="DVo-aG-piG">
<connections>
<action selector="performClose:" target="Ady-hI-5gd" id="HmO-Ls-i7Q"/>
</connections>
</menuItem>
<menuItem title="Save…" keyEquivalent="s" id="pxx-59-PXV">
<connections>
<action selector="saveDocument:" target="Ady-hI-5gd" id="teZ-XB-qJY"/>
</connections>
</menuItem>
<menuItem title="Save As…" keyEquivalent="S" id="Bw7-FT-i3A">
<connections>
<action selector="saveDocumentAs:" target="Ady-hI-5gd" id="mDf-zr-I0C"/>
</connections>
</menuItem>
<menuItem title="Revert to Saved" keyEquivalent="r" id="KaW-ft-85H">
<connections>
<action selector="revertDocumentToSaved:" target="Ady-hI-5gd" id="iJ3-Pv-kwq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="aJh-i4-bef"/>
<menuItem title="Page Setup…" keyEquivalent="P" id="qIS-W8-SiK">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="runPageLayout:" target="Ady-hI-5gd" id="Din-rz-gC5"/>
</connections>
</menuItem>
<menuItem title="Print…" keyEquivalent="p" id="aTl-1u-JFS">
<connections>
<action selector="print:" target="Ady-hI-5gd" id="qaZ-4w-aoO"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Edit" id="5QF-Oa-p0T">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
<items>
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
<connections>
<action selector="undo:" target="Ady-hI-5gd" id="M6e-cu-g7V"/>
</connections>
</menuItem>
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
<connections>
<action selector="redo:" target="Ady-hI-5gd" id="oIA-Rs-6OD"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
<connections>
<action selector="cut:" target="Ady-hI-5gd" id="YJe-68-I9s"/>
</connections>
</menuItem>
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
<connections>
<action selector="copy:" target="Ady-hI-5gd" id="G1f-GL-Joy"/>
</connections>
</menuItem>
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
<connections>
<action selector="paste:" target="Ady-hI-5gd" id="UvS-8e-Qdg"/>
</connections>
</menuItem>
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteAsPlainText:" target="Ady-hI-5gd" id="cEh-KX-wJQ"/>
</connections>
</menuItem>
<menuItem title="Delete" id="pa3-QI-u2k">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="delete:" target="Ady-hI-5gd" id="0Mk-Ml-PaM"/>
</connections>
</menuItem>
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
<connections>
<action selector="selectAll:" target="Ady-hI-5gd" id="VNm-Mi-diN"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
<menuItem title="Find" id="4EN-yA-p0u">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Find" id="1b7-l0-nxx">
<items>
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="cD7-Qs-BN4"/>
</connections>
</menuItem>
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="WD3-Gg-5AJ"/>
</connections>
</menuItem>
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="NDo-RZ-v9R"/>
</connections>
</menuItem>
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="HOh-sY-3ay"/>
</connections>
</menuItem>
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="U76-nv-p5D"/>
</connections>
</menuItem>
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
<connections>
<action selector="centerSelectionInVisibleArea:" target="Ady-hI-5gd" id="IOG-6D-g5B"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
<items>
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
<connections>
<action selector="showGuessPanel:" target="Ady-hI-5gd" id="vFj-Ks-hy3"/>
</connections>
</menuItem>
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
<connections>
<action selector="checkSpelling:" target="Ady-hI-5gd" id="fz7-VC-reM"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleContinuousSpellChecking:" target="Ady-hI-5gd" id="7w6-Qz-0kB"/>
</connections>
</menuItem>
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleGrammarChecking:" target="Ady-hI-5gd" id="muD-Qn-j4w"/>
</connections>
</menuItem>
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticSpellingCorrection:" target="Ady-hI-5gd" id="2lM-Qi-WAP"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Substitutions" id="9ic-FL-obx">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
<items>
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontSubstitutionsPanel:" target="Ady-hI-5gd" id="oku-mr-iSq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleSmartInsertDelete:" target="Ady-hI-5gd" id="3IJ-Se-DZD"/>
</connections>
</menuItem>
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticQuoteSubstitution:" target="Ady-hI-5gd" id="ptq-xd-QOA"/>
</connections>
</menuItem>
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDashSubstitution:" target="Ady-hI-5gd" id="oCt-pO-9gS"/>
</connections>
</menuItem>
<menuItem title="Smart Links" id="cwL-P1-jid">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticLinkDetection:" target="Ady-hI-5gd" id="Gip-E3-Fov"/>
</connections>
</menuItem>
<menuItem title="Data Detectors" id="tRr-pd-1PS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDataDetection:" target="Ady-hI-5gd" id="R1I-Nq-Kbl"/>
</connections>
</menuItem>
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticTextReplacement:" target="Ady-hI-5gd" id="DvP-Fe-Py6"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Transformations" id="2oI-Rn-ZJC">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
<items>
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="uppercaseWord:" target="Ady-hI-5gd" id="sPh-Tk-edu"/>
</connections>
</menuItem>
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowercaseWord:" target="Ady-hI-5gd" id="iUZ-b5-hil"/>
</connections>
</menuItem>
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="capitalizeWord:" target="Ady-hI-5gd" id="26H-TL-nsh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Speech" id="xrE-MZ-jX0">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
<items>
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="startSpeaking:" target="Ady-hI-5gd" id="654-Ng-kyl"/>
</connections>
</menuItem>
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="stopSpeaking:" target="Ady-hI-5gd" id="dX8-6p-jy9"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Format" id="jxT-CU-nIS">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Format" id="GEO-Iw-cKr">
<items>
<menuItem title="Font" id="Gi5-1S-RQB">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Font" systemMenu="font" id="aXa-aM-Jaq">
<items>
<menuItem title="Show Fonts" keyEquivalent="t" id="Q5e-8K-NDq">
<connections>
<action selector="orderFrontFontPanel:" target="YLy-65-1bz" id="WHr-nq-2xA"/>
</connections>
</menuItem>
<menuItem title="Bold" tag="2" keyEquivalent="b" id="GB9-OM-e27">
<connections>
<action selector="addFontTrait:" target="YLy-65-1bz" id="hqk-hr-sYV"/>
</connections>
</menuItem>
<menuItem title="Italic" tag="1" keyEquivalent="i" id="Vjx-xi-njq">
<connections>
<action selector="addFontTrait:" target="YLy-65-1bz" id="IHV-OB-c03"/>
</connections>
</menuItem>
<menuItem title="Underline" keyEquivalent="u" id="WRG-CD-K1S">
<connections>
<action selector="underline:" target="Ady-hI-5gd" id="FYS-2b-JAY"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="5gT-KC-WSO"/>
<menuItem title="Bigger" tag="3" keyEquivalent="+" id="Ptp-SP-VEL">
<connections>
<action selector="modifyFont:" target="YLy-65-1bz" id="Uc7-di-UnL"/>
</connections>
</menuItem>
<menuItem title="Smaller" tag="4" keyEquivalent="-" id="i1d-Er-qST">
<connections>
<action selector="modifyFont:" target="YLy-65-1bz" id="HcX-Lf-eNd"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kx3-Dk-x3B"/>
<menuItem title="Kern" id="jBQ-r6-VK2">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Kern" id="tlD-Oa-oAM">
<items>
<menuItem title="Use Default" id="GUa-eO-cwY">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useStandardKerning:" target="Ady-hI-5gd" id="6dk-9l-Ckg"/>
</connections>
</menuItem>
<menuItem title="Use None" id="cDB-IK-hbR">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="turnOffKerning:" target="Ady-hI-5gd" id="U8a-gz-Maa"/>
</connections>
</menuItem>
<menuItem title="Tighten" id="46P-cB-AYj">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="tightenKerning:" target="Ady-hI-5gd" id="hr7-Nz-8ro"/>
</connections>
</menuItem>
<menuItem title="Loosen" id="ogc-rX-tC1">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="loosenKerning:" target="Ady-hI-5gd" id="8i4-f9-FKE"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Ligatures" id="o6e-r0-MWq">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Ligatures" id="w0m-vy-SC9">
<items>
<menuItem title="Use Default" id="agt-UL-0e3">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useStandardLigatures:" target="Ady-hI-5gd" id="7uR-wd-Dx6"/>
</connections>
</menuItem>
<menuItem title="Use None" id="J7y-lM-qPV">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="turnOffLigatures:" target="Ady-hI-5gd" id="iX2-gA-Ilz"/>
</connections>
</menuItem>
<menuItem title="Use All" id="xQD-1f-W4t">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useAllLigatures:" target="Ady-hI-5gd" id="KcB-kA-TuK"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Baseline" id="OaQ-X3-Vso">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Baseline" id="ijk-EB-dga">
<items>
<menuItem title="Use Default" id="3Om-Ey-2VK">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unscript:" target="Ady-hI-5gd" id="0vZ-95-Ywn"/>
</connections>
</menuItem>
<menuItem title="Superscript" id="Rqc-34-cIF">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="superscript:" target="Ady-hI-5gd" id="3qV-fo-wpU"/>
</connections>
</menuItem>
<menuItem title="Subscript" id="I0S-gh-46l">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="subscript:" target="Ady-hI-5gd" id="Q6W-4W-IGz"/>
</connections>
</menuItem>
<menuItem title="Raise" id="2h7-ER-AoG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="raiseBaseline:" target="Ady-hI-5gd" id="4sk-31-7Q9"/>
</connections>
</menuItem>
<menuItem title="Lower" id="1tx-W0-xDw">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowerBaseline:" target="Ady-hI-5gd" id="OF1-bc-KW4"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="Ndw-q3-faq"/>
<menuItem title="Show Colors" keyEquivalent="C" id="bgn-CT-cEk">
<connections>
<action selector="orderFrontColorPanel:" target="Ady-hI-5gd" id="mSX-Xz-DV3"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="iMs-zA-UFJ"/>
<menuItem title="Copy Style" keyEquivalent="c" id="5Vv-lz-BsD">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="copyFont:" target="Ady-hI-5gd" id="GJO-xA-L4q"/>
</connections>
</menuItem>
<menuItem title="Paste Style" keyEquivalent="v" id="vKC-jM-MkH">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteFont:" target="Ady-hI-5gd" id="JfD-CL-leO"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Text" id="Fal-I4-PZk">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Text" id="d9c-me-L2H">
<items>
<menuItem title="Align Left" keyEquivalent="{" id="ZM1-6Q-yy1">
<connections>
<action selector="alignLeft:" target="Ady-hI-5gd" id="zUv-R1-uAa"/>
</connections>
</menuItem>
<menuItem title="Center" keyEquivalent="|" id="VIY-Ag-zcb">
<connections>
<action selector="alignCenter:" target="Ady-hI-5gd" id="spX-mk-kcS"/>
</connections>
</menuItem>
<menuItem title="Justify" id="J5U-5w-g23">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="alignJustified:" target="Ady-hI-5gd" id="ljL-7U-jND"/>
</connections>
</menuItem>
<menuItem title="Align Right" keyEquivalent="}" id="wb2-vD-lq4">
<connections>
<action selector="alignRight:" target="Ady-hI-5gd" id="r48-bG-YeY"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="4s2-GY-VfK"/>
<menuItem title="Writing Direction" id="H1b-Si-o9J">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Writing Direction" id="8mr-sm-Yjd">
<items>
<menuItem title="Paragraph" enabled="NO" id="ZvO-Gk-QUH">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem id="YGs-j5-SAR">
<string key="title"> Default</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeBaseWritingDirectionNatural:" target="Ady-hI-5gd" id="qtV-5e-UBP"/>
</connections>
</menuItem>
<menuItem id="Lbh-J2-qVU">
<string key="title"> Left to Right</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeBaseWritingDirectionLeftToRight:" target="Ady-hI-5gd" id="S0X-9S-QSf"/>
</connections>
</menuItem>
<menuItem id="jFq-tB-4Kx">
<string key="title"> Right to Left</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeBaseWritingDirectionRightToLeft:" target="Ady-hI-5gd" id="5fk-qB-AqJ"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="swp-gr-a21"/>
<menuItem title="Selection" enabled="NO" id="cqv-fj-IhA">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem id="Nop-cj-93Q">
<string key="title"> Default</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeTextWritingDirectionNatural:" target="Ady-hI-5gd" id="lPI-Se-ZHp"/>
</connections>
</menuItem>
<menuItem id="BgM-ve-c93">
<string key="title"> Left to Right</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeTextWritingDirectionLeftToRight:" target="Ady-hI-5gd" id="caW-Bv-w94"/>
</connections>
</menuItem>
<menuItem id="RB4-Sm-HuC">
<string key="title"> Right to Left</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeTextWritingDirectionRightToLeft:" target="Ady-hI-5gd" id="EXD-6r-ZUu"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="fKy-g9-1gm"/>
<menuItem title="Show Ruler" id="vLm-3I-IUL">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleRuler:" target="Ady-hI-5gd" id="FOx-HJ-KwY"/>
</connections>
</menuItem>
<menuItem title="Copy Ruler" keyEquivalent="c" id="MkV-Pr-PK5">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="copyRuler:" target="Ady-hI-5gd" id="71i-fW-3W2"/>
</connections>
</menuItem>
<menuItem title="Paste Ruler" keyEquivalent="v" id="LVM-kO-fVI">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="pasteRuler:" target="Ady-hI-5gd" id="cSh-wd-qM2"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="View" id="H8h-7b-M4v">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="View" id="HyV-fh-RgO">
<items>
<menuItem title="Show Toolbar" keyEquivalent="t" id="snW-S8-Cw5">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="toggleToolbarShown:" target="Ady-hI-5gd" id="BXY-wc-z0C"/>
</connections>
</menuItem>
<menuItem title="Customize Toolbar…" id="1UK-8n-QPP">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="runToolbarCustomizationPalette:" target="Ady-hI-5gd" id="pQI-g3-MTW"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="hB3-LF-h0Y"/>
<menuItem title="Show Sidebar" keyEquivalent="s" id="kIP-vf-haE">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="toggleSourceList:" target="Ady-hI-5gd" id="iwa-gc-5KM"/>
</connections>
</menuItem>
<menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="toggleFullScreen:" target="Ady-hI-5gd" id="dU3-MA-1Rq"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Window" id="aUF-d1-5bR">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
<items>
<menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
<connections>
<action selector="performMiniaturize:" target="Ady-hI-5gd" id="VwT-WD-YPe"/>
</connections>
</menuItem>
<menuItem title="Zoom" id="R4o-n2-Eq4">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="performZoom:" target="Ady-hI-5gd" id="DIl-cC-cCs"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="arrangeInFront:" target="Ady-hI-5gd" id="DRN-fu-gQh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Help" id="wpr-3q-Mcd">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
<items>
<menuItem title="BasicTunnelMac Help" keyEquivalent="?" id="FKE-Sm-Kum">
<connections>
<action selector="showHelp:" target="Ady-hI-5gd" id="y7X-2Q-9no"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
</connections>
</application>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="BasicTunnel_macOS" customModuleProvider="target"/>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="75" y="0.0"/>
</scene>
<!--Window Controller-->
<scene sceneID="R2V-B0-nI4">
<objects>
<windowController id="B8D-0N-5wS" sceneMemberID="viewController">
<window key="window" title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="IQv-IB-iLA">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="480" height="480"/>
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/>
<connections>
<outlet property="delegate" destination="B8D-0N-5wS" id="98r-iN-zZc"/>
</connections>
</window>
<connections>
<segue destination="XfG-lQ-9wD" kind="relationship" relationship="window.shadowedContentViewController" id="cq2-FE-JQM"/>
</connections>
</windowController>
<customObject id="Oky-zY-oP4" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="75" y="250"/>
</scene>
<!--View Controller-->
<scene sceneID="hIz-AP-VOD">
<objects>
<viewController id="XfG-lQ-9wD" customClass="ViewController" customModule="BasicTunnel_macOS" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" wantsLayer="YES" id="m2S-Jp-Qdl">
<rect key="frame" x="0.0" y="0.0" width="480" height="143"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="i3T-uV-wV3">
<rect key="frame" x="20" y="101" width="100" height="22"/>
<constraints>
<constraint firstAttribute="width" constant="100" id="15v-2e-tlT"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" drawsBackground="YES" id="qXL-ZI-P0P">
<font key="font" metaFont="system"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LIN-Ps-ANN">
<rect key="frame" x="140" y="101" width="230" height="22"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" drawsBackground="YES" id="1ir-yf-OCa">
<font key="font" metaFont="system"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="P4h-p9-zCb">
<rect key="frame" x="390" y="101" width="70" height="22"/>
<constraints>
<constraint firstAttribute="width" constant="70" id="Cba-GL-f5a"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" drawsBackground="YES" id="nYd-Lk-W7w">
<font key="font" metaFont="system"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="sFz-8v-h8e">
<rect key="frame" x="20" y="61" width="210" height="22"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" drawsBackground="YES" id="ljE-Uv-5Le">
<font key="font" metaFont="system"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="et1-VP-AGe">
<rect key="frame" x="250" y="61" width="210" height="22"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" drawsBackground="YES" id="0sQ-3n-XfE">
<font key="font" metaFont="system"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nd2-H7-u7j">
<rect key="frame" x="14" y="13" width="452" height="32"/>
<buttonCell key="cell" type="push" title="Connect" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="bKB-ql-HWr">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="connectionClicked:" target="XfG-lQ-9wD" id="Wxi-t1-Xkk"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="et1-VP-AGe" firstAttribute="width" secondItem="sFz-8v-h8e" secondAttribute="width" id="4Gv-dj-kcY"/>
<constraint firstItem="nd2-H7-u7j" firstAttribute="top" secondItem="sFz-8v-h8e" secondAttribute="bottom" constant="20" id="5q8-DS-aIh"/>
<constraint firstItem="et1-VP-AGe" firstAttribute="leading" secondItem="sFz-8v-h8e" secondAttribute="trailing" constant="20" id="9Mq-Mv-kga"/>
<constraint firstAttribute="trailing" secondItem="P4h-p9-zCb" secondAttribute="trailing" constant="20" id="Drw-b1-BNu"/>
<constraint firstItem="et1-VP-AGe" firstAttribute="top" secondItem="sFz-8v-h8e" secondAttribute="top" id="EEi-XF-PFd"/>
<constraint firstItem="nd2-H7-u7j" firstAttribute="leading" secondItem="sFz-8v-h8e" secondAttribute="leading" id="JxD-ho-4hD"/>
<constraint firstItem="LIN-Ps-ANN" firstAttribute="top" secondItem="i3T-uV-wV3" secondAttribute="top" id="KJu-xG-JnJ"/>
<constraint firstItem="nd2-H7-u7j" firstAttribute="trailing" secondItem="et1-VP-AGe" secondAttribute="trailing" id="Mwb-AU-hUO"/>
<constraint firstItem="et1-VP-AGe" firstAttribute="trailing" secondItem="P4h-p9-zCb" secondAttribute="trailing" id="Rbb-6h-w4w"/>
<constraint firstItem="P4h-p9-zCb" firstAttribute="leading" secondItem="LIN-Ps-ANN" secondAttribute="trailing" constant="20" id="Rge-dl-vui"/>
<constraint firstItem="i3T-uV-wV3" firstAttribute="leading" secondItem="m2S-Jp-Qdl" secondAttribute="leading" constant="20" id="Xha-4o-0rg"/>
<constraint firstItem="sFz-8v-h8e" firstAttribute="top" secondItem="i3T-uV-wV3" secondAttribute="bottom" constant="18" id="bRU-rq-aRR"/>
<constraint firstItem="LIN-Ps-ANN" firstAttribute="leading" secondItem="i3T-uV-wV3" secondAttribute="trailing" constant="20" id="cDe-a1-OxC"/>
<constraint firstItem="i3T-uV-wV3" firstAttribute="top" secondItem="m2S-Jp-Qdl" secondAttribute="top" constant="20" id="ndO-I5-v8j"/>
<constraint firstItem="sFz-8v-h8e" firstAttribute="leading" secondItem="i3T-uV-wV3" secondAttribute="leading" id="o5w-BS-zO3"/>
<constraint firstItem="P4h-p9-zCb" firstAttribute="top" secondItem="LIN-Ps-ANN" secondAttribute="top" id="qhX-g7-cF2"/>
<constraint firstAttribute="bottom" secondItem="nd2-H7-u7j" secondAttribute="bottom" constant="20" id="tku-oH-p9N"/>
</constraints>
</view>
<connections>
<outlet property="buttonConnection" destination="nd2-H7-u7j" id="nV5-a5-qvm"/>
<outlet property="textDomain" destination="LIN-Ps-ANN" id="1dX-5K-li3"/>
<outlet property="textPassword" destination="et1-VP-AGe" id="Vae-qT-ZLy"/>
<outlet property="textPort" destination="P4h-p9-zCb" id="p0u-oI-Snm"/>
<outlet property="textServer" destination="i3T-uV-wV3" id="oeC-EZ-BPe"/>
<outlet property="textUsername" destination="sFz-8v-h8e" id="Dte-NJ-cMr"/>
</connections>
</viewController>
<customObject id="rPt-NT-nkU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="75" y="708"/>
</scene>
</scenes>
</document>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>packet-tunnel-provider</string>
</array>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.productivity</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2018 London Trust Media. All rights reserved.</string>
<key>NSMainStoryboardFile</key>
<string>Main</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

View File

@ -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)")
}
}

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>packet-tunnel-provider</string>
</array>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.privateinternetaccess.ios.demo.BasicTunnel</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)group.com.privateinternetaccess.ios.demo.BasicTunnel</string>
</array>
</dict>
</plist>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>BasicTunnelExtension</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.networkextension.packet-tunnel</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
</dict>
</dict>
</plist>

View File

@ -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 {
}

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>packet-tunnel-provider</string>
</array>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>BasicTunnelExtension</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.networkextension.packet-tunnel</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
</dict>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2018 London Trust Media. All rights reserved.</string>
</dict>
</plist>

View File

@ -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 {
}

View File

@ -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 = "<group>"; };
0EB39FEA1F7424F80023AFFC /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
0EB39FED1F7424F80023AFFC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
0EB39FEF1F7424F80023AFFC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
0EB39FF21F7424F80023AFFC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
0EB39FF41F7424F80023AFFC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
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 = "<group>"; };
0EB3A0001F7425140023AFFC /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = "<group>"; };
0EB3A0021F7425140023AFFC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
0EB3A00A1F7425C20023AFFC /* BasicTunnel-iOS.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = "BasicTunnel-iOS.entitlements"; sourceTree = "<group>"; };
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 = "<group>"; };
0EB6EEBB1F92D417005F6221 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
0EB6EEBD1F92D417005F6221 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
0EB6EEC01F92D417005F6221 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
0EB6EEC21F92D417005F6221 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
0EB6EEC31F92D417005F6221 /* BasicTunnel-macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "BasicTunnel-macOS.entitlements"; sourceTree = "<group>"; };
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 = "<group>"; };
0EB6EECF1F92D43D005F6221 /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = "<group>"; };
0EB6EED11F92D43D005F6221 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
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 = "<group>";
};
0EB39FCE1F7424580023AFFC /* Products */ = {
isa = PBXGroup;
children = (
0EB39FE61F7424F80023AFFC /* BasicTunnel-iOS.app */,
0EB39FFC1F7425140023AFFC /* BasicTunnelExtension-iOS.appex */,
0EB6EEB71F92D417005F6221 /* BasicTunnel-macOS.app */,
0EB6EECB1F92D43D005F6221 /* BasicTunnelExtension-macOS.appex */,
);
name = Products;
sourceTree = "<group>";
};
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 = "<group>";
};
0EB39FFD1F7425140023AFFC /* BasicTunnelExtension-iOS */ = {
isa = PBXGroup;
children = (
0EB3A0001F7425140023AFFC /* PacketTunnelProvider.swift */,
0EB3A0021F7425140023AFFC /* Info.plist */,
0EB39FFE1F7425140023AFFC /* Supporting Files */,
);
path = "BasicTunnelExtension-iOS";
sourceTree = "<group>";
};
0EB39FFE1F7425140023AFFC /* Supporting Files */ = {
isa = PBXGroup;
children = (
0EB39FFF1F7425140023AFFC /* BasicTunnelExtension-iOS.entitlements */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
0EB3A00B1F7425C60023AFFC /* Supporting files */ = {
isa = PBXGroup;
children = (
0EB3A00A1F7425C20023AFFC /* BasicTunnel-iOS.entitlements */,
);
name = "Supporting files";
sourceTree = "<group>";
};
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 = "<group>";
};
0EB6EECC1F92D43D005F6221 /* BasicTunnelExtension-macOS */ = {
isa = PBXGroup;
children = (
0EB6EECF1F92D43D005F6221 /* PacketTunnelProvider.swift */,
0EB6EED11F92D43D005F6221 /* Info.plist */,
0EB6EECD1F92D43D005F6221 /* Supporting Files */,
);
path = "BasicTunnelExtension-macOS";
sourceTree = "<group>";
};
0EB6EECD1F92D43D005F6221 /* Supporting Files */ = {
isa = PBXGroup;
children = (
0EB6EECE1F92D43D005F6221 /* BasicTunnelExtension-macOS.entitlements */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
0EB6EEDB1F92D597005F6221 /* Supporting files */ = {
isa = PBXGroup;
children = (
0EB6EEC31F92D417005F6221 /* BasicTunnel-macOS.entitlements */,
);
name = "Supporting files";
sourceTree = "<group>";
};
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 = "<group>";
};
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 = "<group>";
};
/* 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 = "<group>";
};
0EB39FF11F7424F80023AFFC /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
0EB39FF21F7424F80023AFFC /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
0EB6EEBF1F92D417005F6221 /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
0EB6EEC01F92D417005F6221 /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
/* 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 */;
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Demo.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

22
Demo/Podfile Normal file
View File

@ -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

33
Demo/Podfile.lock Normal file
View File

@ -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

7
LICENSE Normal file
View File

@ -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.

33
PIATunnel.podspec Normal file
View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0930"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0E17D7F81F730D9F009EE129"
BuildableName = "PIATunnel.framework"
BlueprintName = "PIATunnel-iOS"
ReferencedContainer = "container:PIATunnel.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0E1108991F77B9E800A92462"
BuildableName = "PIATunnelTests.xctest"
BlueprintName = "PIATunnelTests"
ReferencedContainer = "container:PIATunnel.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0E17D7F81F730D9F009EE129"
BuildableName = "PIATunnel.framework"
BlueprintName = "PIATunnel-iOS"
ReferencedContainer = "container:PIATunnel.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0E17D7F81F730D9F009EE129"
BuildableName = "PIATunnel.framework"
BlueprintName = "PIATunnel-iOS"
ReferencedContainer = "container:PIATunnel.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0E17D7F81F730D9F009EE129"
BuildableName = "PIATunnel.framework"
BlueprintName = "PIATunnel-iOS"
ReferencedContainer = "container:PIATunnel.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0930"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0E3251C41F95770D00C108D9"
BuildableName = "PIATunnel.framework"
BlueprintName = "PIATunnel-macOS"
ReferencedContainer = "container:PIATunnel.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0E3251C41F95770D00C108D9"
BuildableName = "PIATunnel.framework"
BlueprintName = "PIATunnel-macOS"
ReferencedContainer = "container:PIATunnel.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0E3251C41F95770D00C108D9"
BuildableName = "PIATunnel.framework"
BlueprintName = "PIATunnel-macOS"
ReferencedContainer = "container:PIATunnel.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0930"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0E1108A81F77B9F900A92462"
BuildableName = "PIATunnelHost.app"
BlueprintName = "PIATunnelHost"
ReferencedContainer = "container:PIATunnel.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0E1108A81F77B9F900A92462"
BuildableName = "PIATunnelHost.app"
BlueprintName = "PIATunnelHost"
ReferencedContainer = "container:PIATunnel.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0E1108A81F77B9F900A92462"
BuildableName = "PIATunnelHost.app"
BlueprintName = "PIATunnelHost"
ReferencedContainer = "container:PIATunnel.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0E1108A81F77B9F900A92462"
BuildableName = "PIATunnelHost.app"
BlueprintName = "PIATunnelHost"
ReferencedContainer = "container:PIATunnel.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:PIATunnel.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

24
PIATunnel/Info.plist Normal file
View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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)
}
}
}

View File

@ -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<sockaddr>) 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() {
}
}

View File

@ -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?
}

View File

@ -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
}
}

View File

@ -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")
}
}
}

View File

@ -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
}
}

View File

@ -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)
)
}
}

View File

@ -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
}
}

View File

@ -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<AnyObject>.passUnretained(anyObject).toOpaque()
// }
}

View File

@ -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
}
}

View File

@ -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..<newBuffer.count)
self?.loopReadPackets(queue, newBuffer, handler)
handler(packets, nil)
}
}
}
func writePacket(_ packet: Data, completionHandler: ((Error?) -> 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)"
}
}

View File

@ -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)
}
}

View File

@ -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)"
}
}

View File

@ -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"
}
}
}

View File

@ -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"
}
}
}

View File

@ -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)
}
}

View File

@ -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 <stddef.h>
void *allocate_safely(size_t size);
size_t safe_crypto_capacity(size_t size, size_t overhead);

View File

@ -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 <stdlib.h>
#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;
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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 <Foundation/Foundation.h>
#import "Encryption.h"
#import "DataPathEncryption.h"
NS_ASSUME_NONNULL_BEGIN
@interface CryptoAEAD : NSObject <Encrypter, Decrypter>
@property (nonatomic, assign) int extraLength;
- (instancetype)initWithCipherName:(nonnull NSString *)cipherName;
@end
@interface DataPathCryptoAEAD : NSObject <DataPathEncrypter, DataPathDecrypter>
@property (nonatomic, assign) uint32_t peerId;
- (instancetype)initWithCrypto:(nonnull CryptoAEAD *)crypto;
@end
NS_ASSUME_NONNULL_END

View File

@ -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 <openssl/evp.h>
#import <openssl/hmac.h>
#import <openssl/rand.h>
#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>)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>)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

View File

@ -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 <Foundation/Foundation.h>
#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>)encrypter;
- (nonnull id<Decrypter>)decrypter;
@end

View File

@ -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 <openssl/evp.h>
#import <openssl/hmac.h>
#import <openssl/rand.h>
#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> encrypter;
@property (nonatomic, strong) id<Decrypter> 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

View File

@ -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 <Foundation/Foundation.h>
#import "Encryption.h"
#import "DataPathEncryption.h"
NS_ASSUME_NONNULL_BEGIN
@interface CryptoCBC : NSObject <Encrypter, Decrypter>
- (instancetype)initWithCipherName:(nonnull NSString *)cipherName
digestName:(nonnull NSString *)digestName;
@end
@interface DataPathCryptoCBC : NSObject <DataPathEncrypter, DataPathDecrypter>
@property (nonatomic, assign) uint32_t peerId;
- (instancetype)initWithCrypto:(nonnull CryptoCBC *)crypto;
@end
NS_ASSUME_NONNULL_END

View File

@ -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 <openssl/evp.h>
#import <openssl/hmac.h>
#import <openssl/rand.h>
#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>)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>)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

View File

@ -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 <Foundation/Foundation.h>
#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;

View File

@ -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<UInt8>(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..<count)
}
mutating func zero(from: Int, to: Int) {
resetBytes(in: from..<to)
}
}
extension Data {
mutating func append(_ value: UInt16) {
var localValue = value
let buffer = UnsafeBufferPointer(start: &localValue, count: 1)
append(buffer)
}
mutating func append(_ value: UInt32) {
var localValue = value
let buffer = UnsafeBufferPointer(start: &localValue, count: 1)
append(buffer)
}
mutating func append(_ value: UInt64) {
var localValue = value
let buffer = UnsafeBufferPointer(start: &localValue, count: 1)
append(buffer)
}
mutating func append(nullTerminatedString: String) {
append(nullTerminatedString.data(using: .ascii)!)
append(UInt8(0))
}
func nullTerminatedString(from: Int) -> String? {
var nullOffset: Int?
for i in from..<count {
if (self[i] == 0) {
nullOffset = i
break
}
}
guard let to = nullOffset else {
return nil
}
return String(data: subdata(in: from..<to), encoding: .ascii)
}
// best
func UInt16Value(from: Int) -> 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<UInt8>) -> 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<UInt8>) -> 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 }
}
}

View File

@ -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 <Foundation/Foundation.h>
@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<DataPathEncrypter>)encrypter
decrypter:(nonnull id<DataPathDecrypter>)decrypter
maxPackets:(NSInteger)maxPackets
usesReplayProtection:(BOOL)usesReplayProtection;
- (void)setPeerId:(uint32_t)peerId; // 24-bit, discard most significant byte
- (NSArray<NSData *> *)encryptPackets:(nonnull NSArray<NSData *> *)packets key:(uint8_t)key error:(NSError **)error;
- (NSArray<NSData *> *)decryptPackets:(nonnull NSArray<NSData *> *)packets keepAlive:(nullable bool *)keepAlive error:(NSError **)error;
@end

View File

@ -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 <arpa/inet.h>
#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<DataPathEncrypter> encrypter;
@property (nonatomic, strong) id<DataPathDecrypter> 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<DataPathEncrypter>)encrypter decrypter:(id<DataPathDecrypter>)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<NSData *> *)encryptPackets:(NSArray<NSData *> *)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<NSData *> *)decryptPackets:(NSArray<NSData *> *)packets error:(NSError *__autoreleasing *)error
- (NSArray<NSData *> *)decryptPackets:(NSArray<NSData *> *)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

View File

@ -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 <Foundation/Foundation.h>
@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

View File

@ -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 <Foundation/Foundation.h>
#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>)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>)dataPathDecrypter;
@end

View File

@ -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..<hash1.count {
let h1 = hash1.bytes[i]
let h2 = hash2.bytes[i]
prf.append(Z(h1 ^ h2))
}
return prf
}
// Ruby: keys_hash
private static func keysHash(_ digestName: String, _ secret: ZeroingData, _ seed: ZeroingData, _ size: Int) throws -> 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..<CoreConfiguration.keysCount {
let offset = i * CoreConfiguration.keyLength
let zbuf = keysData.withOffset(offset, count: CoreConfiguration.keyLength)
keysArray.append(zbuf)
}
let cipherEncKey = keysArray[0]
let hmacEncKey = keysArray[1]
let cipherDecKey = keysArray[2]
let hmacDecKey = keysArray[3]
try self.init(cipher, digest, cipherEncKey, cipherDecKey, hmacEncKey, hmacDecKey)
}
init(_ cipher: String, _ digest: String, _ cipherEncKey: ZeroingData, _ cipherDecKey: ZeroingData, _ hmacEncKey: ZeroingData, _ hmacDecKey: ZeroingData) throws {
box = CryptoBox(cipherAlgorithm: cipher, digestAlgorithm: digest)
try box.configure(
withCipherEncKey: cipherEncKey,
cipherDecKey: cipherDecKey,
hmacEncKey: hmacEncKey,
hmacDecKey: hmacDecKey
)
}
func encrypter() -> DataPathEncrypter {
return box.encrypter().dataPathEncrypter()
}
func decrypter() -> DataPathDecrypter {
return box.decrypter().dataPathDecrypter()
}
}

View File

@ -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 <Foundation/Foundation.h>
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];
}

View File

@ -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";

View File

@ -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)?)
}

View File

@ -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?
}

View File

@ -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 <stdint.h>
void MSSFix(uint8_t *data, NSInteger data_len);

View File

@ -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 <arpa/inet.h>
#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;
}
}

View File

@ -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")
}

View File

@ -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 <Foundation/Foundation.h>
#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;
}

View File

@ -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 };

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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 <Foundation/Foundation.h>
@interface ReplayProtector : NSObject
- (BOOL)isReplayedPacketId:(uint32_t)packetId;
@end

View File

@ -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

View File

@ -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<UInt8>) -> 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<UInt8>) -> Void in
guard (SecRandomCopyBytes(kSecRandomDefault, length, randomBytes) == 0) else {
throw SecureRandomError.randomGenerator
}
}
return randomData
}
static func safeData(length: Int) throws -> ZeroingData {
let randomBytes = UnsafeMutablePointer<UInt8>.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)
}
}

View File

@ -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
// }
}

File diff suppressed because it is too large Load Diff

View File

@ -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 <Foundation/Foundation.h>
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

View File

@ -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 <openssl/ssl.h>
#import <openssl/err.h>
#import <openssl/evp.h>
#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

View File

@ -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 }
}

Some files were not shown because too many files have changed in this diff Show More