Added validation before saving any tunnels

Signed-off-by: Eric Kuck <eric@bluelinelabs.com>
This commit is contained in:
Eric Kuck
2018-08-15 17:34:16 -05:00
parent 449bd53b1e
commit b306149222
12 changed files with 340 additions and 19 deletions
+10 -12
View File
@@ -1,13 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14113" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14313.13.2" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.9"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
@@ -140,7 +138,7 @@
<rect key="frame" x="0.0" y="0.0" width="232" height="35.5"/>
<nil key="textColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" smartDashesType="no" smartInsertDeleteType="no" smartQuotesType="no"/>
<connections>
<action selector="textfieldDidChange:" destination="06N-KU-LSv" eventType="editingChanged" id="CQd-My-M7G"/>
<outlet property="delegate" destination="06N-KU-LSv" id="7YF-mU-HZT"/>
@@ -336,7 +334,7 @@
<rect key="frame" x="0.0" y="16" width="343" height="30"/>
<nil key="textColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" smartDashesType="no" smartInsertDeleteType="no" smartQuotesType="no"/>
<connections>
<action selector="textfieldDidChange:" destination="gzz-88-0IG" eventType="editingChanged" id="taX-Ji-pVu"/>
<outlet property="delegate" destination="gzz-88-0IG" id="bkQ-Nc-VH4"/>
@@ -390,16 +388,16 @@
<rect key="frame" x="0.0" y="240" width="343" height="46"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="8uI-Ux-Xhj">
<rect key="frame" x="0.0" y="0.0" width="232.5" height="46"/>
<rect key="frame" x="0.0" y="0.0" width="232" height="46"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Endpoint" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="P3u-6Z-3Hk">
<rect key="frame" x="0.0" y="0.0" width="232.5" height="16"/>
<rect key="frame" x="0.0" y="0.0" width="232" height="16"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<color key="textColor" red="0.60784313729999995" green="0.60784313729999995" blue="0.60784313729999995" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="W88-H0-dFZ">
<rect key="frame" x="0.0" y="16" width="232.5" height="30"/>
<rect key="frame" x="0.0" y="16" width="232" height="30"/>
<nil key="textColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
@@ -411,16 +409,16 @@
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="eLi-QT-QwF">
<rect key="frame" x="240.5" y="0.0" width="102.5" height="46"/>
<rect key="frame" x="240" y="0.0" width="103" height="46"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Persistent keepalive" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="tja-cM-cGL">
<rect key="frame" x="0.0" y="0.0" width="102.5" height="16"/>
<rect key="frame" x="0.0" y="0.0" width="103" height="16"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<color key="textColor" red="0.60784313729999995" green="0.60784313729999995" blue="0.60784313729999995" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="(optional)" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="Q8w-jh-0ox">
<rect key="frame" x="0.0" y="16" width="102.5" height="30"/>
<rect key="frame" x="0.0" y="16" width="103" height="30"/>
<nil key="textColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
+24
View File
@@ -0,0 +1,24 @@
//
// String+Arrays.swift
// WireGuard
//
// Created by Eric Kuck on 8/15/18.
// Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All rights reserved.
//
import Foundation
extension String {
static func commaSeparatedStringFrom(elements: [String]) -> String {
return elements.joined(separator: ",")
}
func commaSeparatedToArray() -> [String] {
return components(separatedBy: .whitespaces)
.joined()
.split(separator: ",")
.map(String.init)
}
}
+18
View File
@@ -0,0 +1,18 @@
//
// String+Base64.swift
// WireGuard
//
// Created by Eric Kuck on 8/15/18.
// Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All rights reserved.
//
import Foundation
extension String {
func isBase64() -> Bool {
let base64Predicate = NSPredicate(format: "SELF MATCHES %@", "^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$")
return base64Predicate.evaluate(with: self)
}
}
@@ -0,0 +1,46 @@
//
// Interface+Extension.swift
// WireGuard
//
// Created by Eric Kuck on 8/15/18.
// Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All rights reserved.
//
import Foundation
extension Interface {
func validate() throws {
guard let privateKey = privateKey, !privateKey.isEmpty else {
throw InterfaceValidationError.emptyPrivateKey
}
guard privateKey.isBase64() else {
throw InterfaceValidationError.invalidPrivateKey
}
try? addresses?.commaSeparatedToArray().forEach { address in
do {
try _ = CIDRAddress(stringRepresentation: address)
} catch {
throw InterfaceValidationError.invalidAddress(cause: error)
}
}
try? dns?.commaSeparatedToArray().forEach { address in
do {
try _ = Endpoint(endpointString: address)
} catch {
throw InterfaceValidationError.invalidDNSServer(cause: error)
}
}
}
}
enum InterfaceValidationError: Error {
case emptyPrivateKey
case invalidPrivateKey
case invalidAddress(cause: Error)
case invalidDNSServer(cause: Error)
}
@@ -19,7 +19,7 @@ extension Peer {
@NSManaged public var presharedKey: String?
@NSManaged public var allowedIPs: String?
@NSManaged public var endpoint: String?
@NSManaged public var persistentKeepalive: Int16
@NSManaged public var persistentKeepalive: Int32
@NSManaged public var tunnel: Tunnel?
}
+56
View File
@@ -0,0 +1,56 @@
//
// Peer+Extension.swift
// WireGuard
//
// Created by Eric Kuck on 8/15/18.
// Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All rights reserved.
//
import Foundation
extension Peer {
func validate() throws {
guard let publicKey = publicKey, !publicKey.isEmpty else {
throw PeerValidationError.emptyPublicKey
}
guard publicKey.isBase64() else {
throw PeerValidationError.invalidPublicKey
}
guard let allowedIPs = allowedIPs, !allowedIPs.isEmpty else {
throw PeerValidationError.nilAllowedIps
}
try allowedIPs.commaSeparatedToArray().forEach { address in
do {
try _ = CIDRAddress(stringRepresentation: address)
} catch {
throw PeerValidationError.invalidAllowedIPs(cause: error)
}
}
if let endpoint = endpoint {
do {
try _ = Endpoint(endpointString: endpoint)
} catch {
throw PeerValidationError.invalidEndpoint(cause: error)
}
}
guard persistentKeepalive >= 0, persistentKeepalive <= 65535 else {
throw PeerValidationError.invalidPersistedKeepAlive
}
}
}
enum PeerValidationError: Error {
case emptyPublicKey
case invalidPublicKey
case nilAllowedIps
case invalidAllowedIPs(cause: Error)
case invalidEndpoint(cause: Error)
case invalidPersistedKeepAlive
}
+44 -2
View File
@@ -7,6 +7,7 @@
//
import Foundation
import CoreData
extension Tunnel {
public func generateProviderConfiguration() -> [String: Any] {
@@ -38,7 +39,7 @@ extension Tunnel {
return providerConfiguration
}
private func generateInterfaceProviderConfiguration(_ interface: Interface) -> String {
private func generateInterfaceProviderConfiguration(_ interface: Interface) -> String {
var settingsString = ""
if let hexPrivateKey = base64KeyToHex(interface.privateKey) {
@@ -54,7 +55,7 @@ extension Tunnel {
return settingsString
}
private func generatePeerProviderConfiguration(_ peer: Peer) -> String {
private func generatePeerProviderConfiguration(_ peer: Peer) -> String {
var settingsString = ""
if let hexPublicKey = base64KeyToHex(peer.publicKey) {
@@ -77,6 +78,39 @@ extension Tunnel {
return settingsString
}
func validate() throws {
let nameRegex = "[a-zA-Z0-9_=+.-]{1,15}"
let nameTest = NSPredicate(format: "SELF MATCHES %@", nameRegex)
guard let title = title, nameTest.evaluate(with: title) else {
throw TunnelValidationError.invalidTitle
}
let fetchRequest: NSFetchRequest<Tunnel> = Tunnel.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "title == %@", title)
guard (try? managedObjectContext?.count(for: fetchRequest)) == 1 else {
throw TunnelValidationError.titleExists
}
guard let interface = interface else {
throw TunnelValidationError.nilInterface
}
try interface.validate()
guard let peers = peers else {
throw TunnelValidationError.nilPeers
}
try peers.forEach {
guard let peer = $0 as? Peer else {
throw TunnelValidationError.invalidPeer
}
try peer.validate()
}
}
}
private func base64KeyToHex(_ base64: String?) -> String? {
@@ -104,3 +138,11 @@ private func base64KeyToHex(_ base64: String?) -> String? {
return hexKey
}
enum TunnelValidationError: Error {
case invalidTitle
case titleExists
case nilInterface
case nilPeers
case invalidPeer
}
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14135" systemVersion="17G65" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14315.12.1" systemVersion="17G2208" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="Interface" representedClassName="Interface" syncable="YES">
<attribute name="addresses" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="dns" optional="YES" attributeType="String" syncable="YES"/>
@@ -11,7 +11,7 @@
<entity name="Peer" representedClassName="Peer" syncable="YES">
<attribute name="allowedIPs" attributeType="String" syncable="YES"/>
<attribute name="endpoint" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="persistentKeepalive" attributeType="Integer 16" minValueString="0" maxValueString="65535" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="persistentKeepalive" attributeType="Integer 32" minValueString="0" maxValueString="65535" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="presharedKey" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="publicKey" attributeType="String" syncable="YES"/>
<relationship name="tunnel" maxCount="1" deletionRule="Nullify" destinationEntity="Tunnel" inverseName="peers" inverseEntity="Tunnel" syncable="YES"/>
@@ -101,6 +101,13 @@ class TunnelConfigurationTableViewController: UITableViewController {
@IBAction func saveTunnelConfiguration(_ sender: Any) {
Promise<Void>(resolver: { (seal) in
do {
try tunnel.validate()
} catch {
seal.reject(error)
return
}
viewContext.perform({
self.viewContext.saveContext({ (result) in
switch result {
@@ -115,7 +122,9 @@ class TunnelConfigurationTableViewController: UITableViewController {
self.delegate?.didSave(tunnel: self.tunnel, tunnelConfigurationTableViewController: self)
return Promise.value(())
}.catch { error in
print("Error saving: \(error)")
let alert = UIAlertController(title: "Error", message: "\(error)", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
}
@@ -220,7 +229,7 @@ extension PeerTableViewCell: UITextFieldDelegate {
} else if sender == endpointField {
peer.endpoint = string
} else if sender == persistentKeepaliveField {
if let string = string, let persistentKeepalive = Int16(string) {
if let string = string, let persistentKeepalive = Int32(string) {
peer.persistentKeepalive = persistentKeepalive
}
}