Initial commit
This commit is contained in:
commit
8c30bb3995
|
@ -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
|
|
@ -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 #
|
|
@ -0,0 +1,8 @@
|
|||
.DS_Store
|
||||
*.swp
|
||||
*.pbxuser
|
||||
**/*.xcworkspace/xcuserdata
|
||||
**/*.xcodeproj/project.xcworkspace
|
||||
**/*.xcodeproj/xcuserdata
|
||||
Pods
|
||||
docs
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
4.0
|
|
@ -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.
|
|
@ -0,0 +1,22 @@
|
|||
# Contribution Guidelines
|
||||
Private Internet Access welcomes community contributions, and are always looking for ways in which to improve. Please take a look at our contribution guidelines, and get involved with the PIA community.
|
||||
|
||||
## Bugs and Issues
|
||||
Have you found a bug? Is our software behaving in an unexpected way? Please check the open issues for duplicates -- perhaps a fix is already in development, or maybe a solution has already been published.
|
||||
If not then please submit a bug report using our [template](/.github/ISSUE_TEMPLATE.md)
|
||||
|
||||
## Feature Requests
|
||||
Feature requests can also be submitted as issues. We’d be grateful if you checked for duplicates also before submitting a feature request.
|
||||
|
||||
## Working with our Code
|
||||
* Fork the repository. Make sure to keep your repository synced with the source repo.
|
||||
* When you are ready to start working on a new feature, cut a new branch from “develop” with the prefix “feature/” (e.g. “feature/name-of-feature”).
|
||||
* Refer to the [README](/README.md) for instructions on how to install and build.
|
||||
|
||||
## Making Pull Requests
|
||||
* Sync the develop branch in your fork with the develop branch in the source repo.
|
||||
* Make your pull request from “feature” in your fork to “develop” in the source.
|
||||
* Use short and concise commit messages.
|
||||
* Lint your code before committing and making a pull request.
|
||||
* Write unit tests for new features and make sure all tests are passing.
|
||||
* If your pull request contains multiple commits or commits that are not meaningful, consider squashing them.
|
|
@ -0,0 +1,7 @@
|
|||
.DS_Store
|
||||
*.swp
|
||||
*.pbxuser
|
||||
**/*.xcworkspace/xcuserdata
|
||||
**/*.xcodeproj/project.xcworkspace
|
||||
**/*.xcodeproj/xcuserdata
|
||||
Pods
|
|
@ -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:.
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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)")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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)")
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
|
@ -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>
|
|
@ -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 {
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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 {
|
||||
}
|
|
@ -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 */;
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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
|
|
@ -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
|
|
@ -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.
|
|
@ -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
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
||||
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
}
|
|
@ -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?
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
// }
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)"
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)"
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
|
@ -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 }
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
}
|
|
@ -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";
|
|
@ -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)?)
|
||||
}
|
|
@ -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?
|
||||
}
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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 };
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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
|
|
@ -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
|
|
@ -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
Loading…
Reference in New Issue