aboutsummaryrefslogtreecommitdiffstats
path: root/WireGuard
diff options
context:
space:
mode:
authorEric Kuck <eric@bluelinelabs.com>2018-08-21 11:00:41 -0500
committerEric Kuck <eric@bluelinelabs.com>2018-08-21 11:00:41 -0500
commit39ae9db11c3b243b8171301e31f690b9ba3941ae (patch)
tree4d467381c08de38de7c749c306e5dbe8f19d124c /WireGuard
parentUse first peer's endpoint as the tunnel remote address. (diff)
downloadwireguard-apple-39ae9db11c3b243b8171301e31f690b9ba3941ae.tar.xz
wireguard-apple-39ae9db11c3b243b8171301e31f690b9ba3941ae.zip
Added ability to add tunnels with a QR code scan. Logic in place to parse conf files as well.
Signed-off-by: Eric Kuck <eric@bluelinelabs.com>
Diffstat (limited to 'WireGuard')
-rw-r--r--WireGuard/Base.lproj/Main.storyboard37
-rw-r--r--WireGuard/Coordinators/AppCoordinator.swift41
-rw-r--r--WireGuard/Info.plist2
-rw-r--r--WireGuard/Models/Attribute.swift44
-rw-r--r--WireGuard/Models/Interface+Extension.swift21
-rw-r--r--WireGuard/Models/Peer+Extension.swift19
-rw-r--r--WireGuard/Models/Tunnel+Extension.swift53
-rw-r--r--WireGuard/ViewControllers/QRScanViewController.swift107
-rw-r--r--WireGuard/ViewControllers/TunnelConfigurationTableViewController.swift1
9 files changed, 317 insertions, 8 deletions
diff --git a/WireGuard/Base.lproj/Main.storyboard b/WireGuard/Base.lproj/Main.storyboard
index 49fa373..80639f1 100644
--- a/WireGuard/Base.lproj/Main.storyboard
+++ b/WireGuard/Base.lproj/Main.storyboard
@@ -6,6 +6,7 @@
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.9"/>
+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
@@ -73,12 +74,12 @@
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="4uZ-Vv-Fry" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
- <point key="canvasLocation" x="34" y="154"/>
+ <point key="canvasLocation" x="-670" y="200"/>
</scene>
<!--Tunnel settings-->
<scene sceneID="xV8-BW-4R7">
<objects>
- <tableViewController storyboardIdentifier="TunnelConfigurationTableViewController" id="0VM-73-EPX" customClass="TunnelConfigurationTableViewController" customModule="WireGuard" customModuleProvider="target" sceneMemberID="viewController">
+ <tableViewController storyboardIdentifier="TunnelConfigurationTableViewController" useStoryboardIdentifierAsRestorationIdentifier="YES" id="0VM-73-EPX" customClass="TunnelConfigurationTableViewController" customModule="WireGuard" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" allowsSelection="NO" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="0Uy-k2-O3i">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@@ -472,7 +473,37 @@
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="j96-PK-ghN" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
- <point key="canvasLocation" x="1128.8" y="321.58920539730138"/>
+ <point key="canvasLocation" x="122" y="-239"/>
+ </scene>
+ <!--Scan Code-->
+ <scene sceneID="gKN-k2-HoW">
+ <objects>
+ <viewController storyboardIdentifier="QRScanViewController" useStoryboardIdentifierAsRestorationIdentifier="YES" id="Efe-yN-iDH" customClass="QRScanViewController" customModule="WireGuard" customModuleProvider="target" sceneMemberID="viewController">
+ <view key="view" contentMode="scaleToFill" id="NXo-On-ea8">
+ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Tip: generate with `qrencode -t ansiutf8 &lt; tunnel.conf`" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XYc-tx-YNF">
+ <rect key="frame" x="16" y="628.5" width="343" height="14.5"/>
+ <fontDescription key="fontDescription" type="system" pointSize="12"/>
+ <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+ <nil key="highlightedColor"/>
+ </label>
+ </subviews>
+ <color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+ <constraints>
+ <constraint firstItem="Soo-c9-MsX" firstAttribute="bottom" secondItem="XYc-tx-YNF" secondAttribute="bottom" constant="24" id="QhS-p5-jbw"/>
+ <constraint firstItem="XYc-tx-YNF" firstAttribute="leading" secondItem="NXo-On-ea8" secondAttribute="leading" constant="16" id="Y3P-Py-ueV"/>
+ <constraint firstAttribute="trailing" secondItem="XYc-tx-YNF" secondAttribute="trailing" constant="16" id="sB1-h9-ueh"/>
+ </constraints>
+ <viewLayoutGuide key="safeArea" id="Soo-c9-MsX"/>
+ </view>
+ <navigationItem key="navigationItem" title="Scan Code" largeTitleDisplayMode="never" id="WGY-tY-ySz"/>
+ <simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="TQ2-zp-o40" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="121" y="461"/>
</scene>
</scenes>
<resources>
diff --git a/WireGuard/Coordinators/AppCoordinator.swift b/WireGuard/Coordinators/AppCoordinator.swift
index 75657f0..f57a9d5 100644
--- a/WireGuard/Coordinators/AppCoordinator.swift
+++ b/WireGuard/Coordinators/AppCoordinator.swift
@@ -32,7 +32,6 @@ class AppCoordinator: RootViewCoordinator {
return self.tunnelsTableViewController
}
-
var tunnelsTableViewController: TunnelsTableViewController!
/// Window to manage
@@ -134,10 +133,33 @@ class AppCoordinator: RootViewCoordinator {
extension AppCoordinator: TunnelsTableViewControllerDelegate {
func addProvider(tunnelsTableViewController: TunnelsTableViewController) {
+ let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
+ actionSheet.addAction(UIAlertAction(title: "Add Manually", style: .default) { [unowned self] _ in
+ self.addProviderManually()
+ })
+ actionSheet.addAction(UIAlertAction(title: "Scan QR Code", style: .default) { [unowned self] _ in
+ self.addProviderWithQRScan()
+ })
+ actionSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel))
+
+ tunnelsTableViewController.present(actionSheet, animated: true, completion: nil)
+ }
+
+ func addProviderManually() {
let addContext = persistentContainer.newBackgroundContext()
showTunnelConfigurationViewController(tunnel: nil, context: addContext)
}
+ func addProviderWithQRScan() {
+ let addContext = persistentContainer.newBackgroundContext()
+
+ let qrScanViewController = storyboard.instantiateViewController(type: QRScanViewController.self)
+
+ qrScanViewController.configure(context: addContext, delegate: self)
+
+ self.navigationController.pushViewController(qrScanViewController, animated: true)
+ }
+
func connect(tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController) {
let manager = self.providerManager(for: tunnel)!
let block = {
@@ -237,10 +259,8 @@ extension AppCoordinator: TunnelsTableViewControllerDelegate {
return tunnelIdentifier == tunnel.tunnelIdentifier
}
}
-}
-extension AppCoordinator: TunnelConfigurationTableViewControllerDelegate {
- func didSave(tunnel: Tunnel, tunnelConfigurationTableViewController: TunnelConfigurationTableViewController) {
+ private func saveTunnel(_ tunnel: Tunnel) {
let manager = providerManager(for: tunnel) ?? NETunnelProviderManager()
manager.localizedDescription = tunnel.title
@@ -265,5 +285,18 @@ extension AppCoordinator: TunnelConfigurationTableViewControllerDelegate {
navigationController.popToRootViewController(animated: true)
}
+}
+
+extension AppCoordinator: TunnelConfigurationTableViewControllerDelegate {
+ func didSave(tunnel: Tunnel, tunnelConfigurationTableViewController: TunnelConfigurationTableViewController) {
+ saveTunnel(tunnel)
+ }
+
+}
+
+extension AppCoordinator: QRScanViewControllerDelegate {
+ func didSave(tunnel: Tunnel, qrScanViewController: QRScanViewController) {
+ showTunnelConfigurationViewController(tunnel: tunnel, context: tunnel.managedObjectContext!)
+ }
}
diff --git a/WireGuard/Info.plist b/WireGuard/Info.plist
index 685b9e2..f3073e3 100644
--- a/WireGuard/Info.plist
+++ b/WireGuard/Info.plist
@@ -2,6 +2,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>NSCameraUsageDescription</key>
+ <string>Camera is used to scan QR codes</string>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
diff --git a/WireGuard/Models/Attribute.swift b/WireGuard/Models/Attribute.swift
new file mode 100644
index 0000000..51f7864
--- /dev/null
+++ b/WireGuard/Models/Attribute.swift
@@ -0,0 +1,44 @@
+//
+// Attribute.swift
+// WireGuard
+//
+// Created by Eric Kuck on 8/20/18.
+// Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All rights reserved.
+//
+
+import Foundation
+
+struct Attribute {
+
+ enum Key: String, CaseIterable {
+ case address = "Address"
+ case allowedIPs = "AllowedIPs"
+ case dns = "DNS"
+ case endpoint = "Endpoint"
+ case listenPort = "ListenPort"
+ case mtu = "MTU"
+ case persistentKeepalive = "PersistentKeepalive"
+ case presharedKey = "PresharedKey"
+ case privateKey = "PrivateKey"
+ case publicKey = "PublicKey"
+ }
+
+ private static let separatorPattern = (try? NSRegularExpression(pattern: "\\s|=", options: []))!
+
+ let line: String
+ let key: Key
+ let stringValue: String
+ var arrayValue: [String] {
+ return stringValue.commaSeparatedToArray()
+ }
+
+ static func match(line: String) -> Attribute? {
+ guard let equalsIndex = line.firstIndex(of: "=") else { return nil }
+ let keyString = line[..<equalsIndex].trimmingCharacters(in: .whitespaces)
+ let value = line[line.index(equalsIndex, offsetBy: 1)...].trimmingCharacters(in: .whitespaces)
+ guard let key = Key.allCases.first(where: { $0.rawValue.lowercased() == keyString.lowercased() }) else { return nil }
+
+ return Attribute(line: line, key: key, stringValue: value)
+ }
+
+}
diff --git a/WireGuard/Models/Interface+Extension.swift b/WireGuard/Models/Interface+Extension.swift
index 78ef567..060b4b7 100644
--- a/WireGuard/Models/Interface+Extension.swift
+++ b/WireGuard/Models/Interface+Extension.swift
@@ -36,6 +36,27 @@ extension Interface {
}
}
+ func parse(attribute: Attribute) throws {
+ switch attribute.key {
+ case .address:
+ addresses = attribute.stringValue
+ case .dns:
+ dns = attribute.stringValue
+ case .listenPort:
+ if let port = Int16(attribute.stringValue) {
+ listenPort = port
+ }
+ case .mtu:
+ if let mtu = Int32(attribute.stringValue) {
+ self.mtu = mtu
+ }
+ case .privateKey:
+ privateKey = attribute.stringValue
+ default:
+ throw TunnelParseError.invalidLine(attribute.line)
+ }
+ }
+
}
enum InterfaceValidationError: Error {
diff --git a/WireGuard/Models/Peer+Extension.swift b/WireGuard/Models/Peer+Extension.swift
index c27748c..f83c038 100644
--- a/WireGuard/Models/Peer+Extension.swift
+++ b/WireGuard/Models/Peer+Extension.swift
@@ -44,6 +44,25 @@ extension Peer {
}
}
+ func parse(attribute: Attribute) throws {
+ switch attribute.key {
+ case .allowedIPs:
+ allowedIPs = attribute.stringValue
+ case .endpoint:
+ endpoint = attribute.stringValue
+ case .persistentKeepalive:
+ if let keepAlive = Int32(attribute.stringValue) {
+ persistentKeepalive = keepAlive
+ }
+ case .presharedKey:
+ presharedKey = attribute.stringValue
+ case .publicKey:
+ publicKey = attribute.stringValue
+ default:
+ throw TunnelParseError.invalidLine(attribute.line)
+ }
+ }
+
}
enum PeerValidationError: Error {
diff --git a/WireGuard/Models/Tunnel+Extension.swift b/WireGuard/Models/Tunnel+Extension.swift
index 038e47d..62014f1 100644
--- a/WireGuard/Models/Tunnel+Extension.swift
+++ b/WireGuard/Models/Tunnel+Extension.swift
@@ -111,6 +111,54 @@ extension Tunnel {
}
}
+ static func fromConfig(_ text: String, context: NSManagedObjectContext) throws -> Tunnel {
+ let lines = text.split(separator: "\n")
+
+ var currentPeer: Peer?
+ var isInInterfaceSection = false
+
+ var tunnel: Tunnel!
+ context.performAndWait {
+ tunnel = Tunnel(context: context)
+ tunnel.interface = Interface(context: context)
+ }
+ tunnel.tunnelIdentifier = UUID().uuidString
+
+ for line in lines {
+ var trimmedLine: String
+ if let commentRange = line.range(of: "#") {
+ trimmedLine = String(line[..<commentRange.lowerBound])
+ } else {
+ trimmedLine = String(line)
+ }
+
+ trimmedLine = trimmedLine.trimmingCharacters(in: .whitespaces)
+
+ guard trimmedLine.count > 0 else { continue }
+
+ if "[interface]" == line.lowercased() {
+ currentPeer = nil
+ isInInterfaceSection = true
+ } else if "[peer]" == line.lowercased() {
+ context.performAndWait { currentPeer = Peer(context: context) }
+ tunnel.insertIntoPeers(currentPeer!, at: tunnel.peers?.count ?? 0)
+ isInInterfaceSection = false
+ } else if isInInterfaceSection, let attribute = Attribute.match(line: String(line)) {
+ try tunnel.interface!.parse(attribute: attribute)
+ } else if let currentPeer = currentPeer, let attribute = Attribute.match(line: String(line)) {
+ try currentPeer.parse(attribute: attribute)
+ } else {
+ throw TunnelParseError.invalidLine(String(line))
+ }
+ }
+
+ if !isInInterfaceSection && currentPeer == nil {
+ throw TunnelParseError.noConfigInfo
+ }
+
+ return tunnel
+ }
+
}
private func base64KeyToHex(_ base64: String?) -> String? {
@@ -146,3 +194,8 @@ enum TunnelValidationError: Error {
case nilPeers
case invalidPeer
}
+
+enum TunnelParseError: Error {
+ case invalidLine(_ line: String)
+ case noConfigInfo
+}
diff --git a/WireGuard/ViewControllers/QRScanViewController.swift b/WireGuard/ViewControllers/QRScanViewController.swift
new file mode 100644
index 0000000..f15d30b
--- /dev/null
+++ b/WireGuard/ViewControllers/QRScanViewController.swift
@@ -0,0 +1,107 @@
+//
+// QRScanViewController.swift
+// WireGuard
+//
+// Created by Eric Kuck on 8/20/18.
+// Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All rights reserved.
+//
+
+import AVFoundation
+import CoreData
+import UIKit
+
+protocol QRScanViewControllerDelegate: class {
+ func didSave(tunnel: Tunnel, qrScanViewController: QRScanViewController)
+}
+
+class QRScanViewController: UIViewController {
+
+ private var viewContext: NSManagedObjectContext!
+ private weak var delegate: QRScanViewControllerDelegate?
+ var captureSession: AVCaptureSession? = AVCaptureSession()
+ let metadataOutput = AVCaptureMetadataOutput()
+ var previewLayer: AVCaptureVideoPreviewLayer!
+
+ func configure(context: NSManagedObjectContext, delegate: QRScanViewControllerDelegate? = nil) {
+ viewContext = context
+ self.delegate = delegate
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ guard let videoCaptureDevice = AVCaptureDevice.default(for: .video),
+ let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice),
+ let captureSession = captureSession,
+ captureSession.canAddInput(videoInput),
+ captureSession.canAddOutput(metadataOutput) else {
+ scanDidEncounterError(title: "Scanning Not Supported", message: "This device does not have the ability to scan QR codes.")
+ return
+ }
+
+ captureSession.addInput(videoInput)
+ captureSession.addOutput(metadataOutput)
+
+ metadataOutput.setMetadataObjectsDelegate(self, queue: .main)
+ metadataOutput.metadataObjectTypes = [.qr]
+
+ previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
+ previewLayer.frame = view.layer.bounds
+ previewLayer.videoGravity = .resizeAspectFill
+ view.layer.addSublayer(previewLayer)
+ }
+
+ override func viewWillAppear(_ animated: Bool) {
+ super.viewWillAppear(animated)
+
+ if captureSession?.isRunning == false {
+ captureSession?.startRunning()
+ }
+ }
+
+ override func viewWillDisappear(_ animated: Bool) {
+ super.viewWillDisappear(animated)
+
+ if captureSession?.isRunning == true {
+ captureSession?.stopRunning()
+ }
+ }
+
+ func scanDidComplete(withCode code: String) {
+ do {
+ let tunnel = try Tunnel.fromConfig(code, context: viewContext)
+ delegate?.didSave(tunnel: tunnel, qrScanViewController: self)
+ } catch {
+ scanDidEncounterError(title: "Invalid Code", message: "The scanned code is not a valid WireGuard config file.")
+ }
+ }
+
+ func scanDidEncounterError(title: String, message: String) {
+ let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
+ alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { [weak self] _ in
+ self?.navigationController?.popViewController(animated: true)
+ }))
+ present(alertController, animated: true)
+ captureSession = nil
+ }
+
+}
+
+extension QRScanViewController: AVCaptureMetadataOutputObjectsDelegate {
+ func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
+ captureSession?.stopRunning()
+
+ guard let metadataObject = metadataObjects.first,
+ let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject,
+ let stringValue = readableObject.stringValue else {
+ scanDidEncounterError(title: "Invalid Code", message: "The scanned code could not be read.")
+ return
+ }
+
+ AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
+ scanDidComplete(withCode: stringValue)
+ }
+
+}
+
+extension QRScanViewController: Identifyable {}
diff --git a/WireGuard/ViewControllers/TunnelConfigurationTableViewController.swift b/WireGuard/ViewControllers/TunnelConfigurationTableViewController.swift
index 6159b00..8aff110 100644
--- a/WireGuard/ViewControllers/TunnelConfigurationTableViewController.swift
+++ b/WireGuard/ViewControllers/TunnelConfigurationTableViewController.swift
@@ -27,7 +27,6 @@ class TunnelConfigurationTableViewController: UITableViewController {
viewContext = context
self.delegate = delegate
self.tunnel = tunnel ?? generateNewTunnelConfig()
-
}
private func generateNewTunnelConfig() -> Tunnel {