mirror of
https://github.com/passepartoutvpn/wireguard-apple.git
synced 2025-02-16 12:52:06 +00:00
Added validation before saving any tunnels
Signed-off-by: Eric Kuck <eric@bluelinelabs.com>
This commit is contained in:
parent
449bd53b1e
commit
b306149222
@ -28,6 +28,7 @@ public enum EndpointValidationError: Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Endpoint {
|
struct Endpoint {
|
||||||
var ipAddress: String
|
var ipAddress: String
|
||||||
var port: Int32
|
var port: Int32
|
||||||
@ -72,3 +73,48 @@ func validateIpAddress(ipToValidate: String) -> AddressType {
|
|||||||
|
|
||||||
return .other
|
return .other
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum CIDRAddressValidationError: Error {
|
||||||
|
case noIpAndSubnet(String)
|
||||||
|
case invalidIP(String)
|
||||||
|
case invalidSubnet(String)
|
||||||
|
|
||||||
|
var localizedDescription: String {
|
||||||
|
switch self {
|
||||||
|
case .noIpAndSubnet:
|
||||||
|
return NSLocalizedString("CIDRAddressValidationError", comment: "Error message for malformed CIDR address.")
|
||||||
|
case .invalidIP:
|
||||||
|
return NSLocalizedString("CIDRAddressValidationError", comment: "Error message for invalid address ip.")
|
||||||
|
case .invalidSubnet:
|
||||||
|
return NSLocalizedString("CIDRAddressValidationError", comment: "Error message invalid address subnet.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CIDRAddress {
|
||||||
|
var ipAddress: String
|
||||||
|
var subnet: Int32
|
||||||
|
var addressType: AddressType
|
||||||
|
|
||||||
|
init?(stringRepresentation: String) throws {
|
||||||
|
guard let range = stringRepresentation.range(of: "/", options: .backwards, range: nil, locale: nil) else {
|
||||||
|
throw CIDRAddressValidationError.noIpAndSubnet(stringRepresentation)
|
||||||
|
}
|
||||||
|
|
||||||
|
let ipString = stringRepresentation[..<range.lowerBound].replacingOccurrences(of: "[", with: "").replacingOccurrences(of: "]", with: "")
|
||||||
|
let subnetString = stringRepresentation[range.upperBound...]
|
||||||
|
|
||||||
|
guard let subnet = Int32(subnetString) else {
|
||||||
|
throw CIDRAddressValidationError.invalidSubnet(String(subnetString))
|
||||||
|
}
|
||||||
|
|
||||||
|
ipAddress = String(ipString)
|
||||||
|
let addressType = validateIpAddress(ipToValidate: ipAddress)
|
||||||
|
guard addressType == .IPv4 || addressType == .IPv6 else {
|
||||||
|
throw CIDRAddressValidationError.invalidIP(ipAddress)
|
||||||
|
}
|
||||||
|
self.addressType = addressType
|
||||||
|
|
||||||
|
self.subnet = subnet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -41,6 +41,10 @@
|
|||||||
4AD095CC20DC42CD000E9CF5 /* WireGuardGoWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AD095CB20DC42CD000E9CF5 /* WireGuardGoWrapper.m */; };
|
4AD095CC20DC42CD000E9CF5 /* WireGuardGoWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AD095CB20DC42CD000E9CF5 /* WireGuardGoWrapper.m */; };
|
||||||
4AEAC32920F14B3B007B67AB /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AEAC32820F14B3B007B67AB /* Log.swift */; };
|
4AEAC32920F14B3B007B67AB /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AEAC32820F14B3B007B67AB /* Log.swift */; };
|
||||||
4AEAC32B20F14BA9007B67AB /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AEAC32A20F14BA9007B67AB /* Log.swift */; };
|
4AEAC32B20F14BA9007B67AB /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AEAC32A20F14BA9007B67AB /* Log.swift */; };
|
||||||
|
5FA1D4CB21249F7D00DBA2E6 /* Peer+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FA1D4CA21249F7D00DBA2E6 /* Peer+Extension.swift */; };
|
||||||
|
5FA1D4CD2124A05C00DBA2E6 /* Interface+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FA1D4CC2124A05C00DBA2E6 /* Interface+Extension.swift */; };
|
||||||
|
5FA1D5102124D80C00DBA2E6 /* String+Arrays.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FA1D50F2124D80C00DBA2E6 /* String+Arrays.swift */; };
|
||||||
|
5FA1D5122124DA6400DBA2E6 /* String+Base64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FA1D5112124DA6400DBA2E6 /* String+Base64.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@ -117,6 +121,10 @@
|
|||||||
4AD095CB20DC42CD000E9CF5 /* WireGuardGoWrapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WireGuardGoWrapper.m; sourceTree = "<group>"; };
|
4AD095CB20DC42CD000E9CF5 /* WireGuardGoWrapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WireGuardGoWrapper.m; sourceTree = "<group>"; };
|
||||||
4AEAC32820F14B3B007B67AB /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = "<group>"; };
|
4AEAC32820F14B3B007B67AB /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = "<group>"; };
|
||||||
4AEAC32A20F14BA9007B67AB /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = "<group>"; };
|
4AEAC32A20F14BA9007B67AB /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = "<group>"; };
|
||||||
|
5FA1D4CA21249F7D00DBA2E6 /* Peer+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Peer+Extension.swift"; sourceTree = "<group>"; };
|
||||||
|
5FA1D4CC2124A05C00DBA2E6 /* Interface+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Interface+Extension.swift"; sourceTree = "<group>"; };
|
||||||
|
5FA1D50F2124D80C00DBA2E6 /* String+Arrays.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Arrays.swift"; sourceTree = "<group>"; };
|
||||||
|
5FA1D5112124DA6400DBA2E6 /* String+Base64.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Base64.swift"; sourceTree = "<group>"; };
|
||||||
861983CAE8FDC13BC83E7E04 /* Pods_WireGuard.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_WireGuard.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
861983CAE8FDC13BC83E7E04 /* Pods_WireGuard.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_WireGuard.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
@ -181,6 +189,7 @@
|
|||||||
4A4BAD1420B5F8C000F12B28 /* Models */,
|
4A4BAD1420B5F8C000F12B28 /* Models */,
|
||||||
4A4BAD1120B5F7A000F12B28 /* ViewControllers */,
|
4A4BAD1120B5F7A000F12B28 /* ViewControllers */,
|
||||||
4A4BAD0A20B5F65800F12B28 /* Coordinators */,
|
4A4BAD0A20B5F65800F12B28 /* Coordinators */,
|
||||||
|
5FA1D50E2124D7F200DBA2E6 /* Extensions */,
|
||||||
4A4BACE520B5F1BF00F12B28 /* AppDelegate.swift */,
|
4A4BACE520B5F1BF00F12B28 /* AppDelegate.swift */,
|
||||||
4A4BACE920B5F1BF00F12B28 /* Main.storyboard */,
|
4A4BACE920B5F1BF00F12B28 /* Main.storyboard */,
|
||||||
4A4BACEC20B5F1C100F12B28 /* Assets.xcassets */,
|
4A4BACEC20B5F1C100F12B28 /* Assets.xcassets */,
|
||||||
@ -234,8 +243,10 @@
|
|||||||
children = (
|
children = (
|
||||||
4A4BAD1D20B6026900F12B28 /* Peer+CoreDataClass.swift */,
|
4A4BAD1D20B6026900F12B28 /* Peer+CoreDataClass.swift */,
|
||||||
4A4BAD1C20B6026900F12B28 /* Peer+CoreDataProperties.swift */,
|
4A4BAD1C20B6026900F12B28 /* Peer+CoreDataProperties.swift */,
|
||||||
|
5FA1D4CA21249F7D00DBA2E6 /* Peer+Extension.swift */,
|
||||||
4A4BAD1F20B6026900F12B28 /* Interface+CoreDataClass.swift */,
|
4A4BAD1F20B6026900F12B28 /* Interface+CoreDataClass.swift */,
|
||||||
4A4BAD1E20B6026900F12B28 /* Interface+CoreDataProperties.swift */,
|
4A4BAD1E20B6026900F12B28 /* Interface+CoreDataProperties.swift */,
|
||||||
|
5FA1D4CC2124A05C00DBA2E6 /* Interface+Extension.swift */,
|
||||||
4A4BAD1820B5F8FF00F12B28 /* Tunnel+CoreDataClass.swift */,
|
4A4BAD1820B5F8FF00F12B28 /* Tunnel+CoreDataClass.swift */,
|
||||||
4A4BAD1920B5F8FF00F12B28 /* Tunnel+CoreDataProperties.swift */,
|
4A4BAD1920B5F8FF00F12B28 /* Tunnel+CoreDataProperties.swift */,
|
||||||
4AC5462D2116306F00749D21 /* Tunnel+Extension.swift */,
|
4AC5462D2116306F00749D21 /* Tunnel+Extension.swift */,
|
||||||
@ -276,6 +287,15 @@
|
|||||||
path = "wireguard-go-bridge";
|
path = "wireguard-go-bridge";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
5FA1D50E2124D7F200DBA2E6 /* Extensions */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
5FA1D50F2124D80C00DBA2E6 /* String+Arrays.swift */,
|
||||||
|
5FA1D5112124DA6400DBA2E6 /* String+Base64.swift */,
|
||||||
|
);
|
||||||
|
path = Extensions;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
87B9E27C2D1820573644527C /* Pods */ = {
|
87B9E27C2D1820573644527C /* Pods */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -533,13 +553,17 @@
|
|||||||
4A4BACE820B5F1BF00F12B28 /* TunnelsTableViewController.swift in Sources */,
|
4A4BACE820B5F1BF00F12B28 /* TunnelsTableViewController.swift in Sources */,
|
||||||
4A4BAD1020B5F6EC00F12B28 /* RootCoordinator.swift in Sources */,
|
4A4BAD1020B5F6EC00F12B28 /* RootCoordinator.swift in Sources */,
|
||||||
4A4351592124956200261999 /* Validators.swift in Sources */,
|
4A4351592124956200261999 /* Validators.swift in Sources */,
|
||||||
|
5FA1D4CB21249F7D00DBA2E6 /* Peer+Extension.swift in Sources */,
|
||||||
|
5FA1D5122124DA6400DBA2E6 /* String+Base64.swift in Sources */,
|
||||||
4AC5462E2116306F00749D21 /* Tunnel+Extension.swift in Sources */,
|
4AC5462E2116306F00749D21 /* Tunnel+Extension.swift in Sources */,
|
||||||
4A4BAD0E20B5F6C300F12B28 /* Coordinator.swift in Sources */,
|
4A4BAD0E20B5F6C300F12B28 /* Coordinator.swift in Sources */,
|
||||||
4A4BA6D820B73CBA00223AB8 /* TunnelConfigurationTableViewController.swift in Sources */,
|
4A4BA6D820B73CBA00223AB8 /* TunnelConfigurationTableViewController.swift in Sources */,
|
||||||
4A4BAD2020B6026900F12B28 /* Peer+CoreDataProperties.swift in Sources */,
|
4A4BAD2020B6026900F12B28 /* Peer+CoreDataProperties.swift in Sources */,
|
||||||
|
5FA1D4CD2124A05C00DBA2E6 /* Interface+Extension.swift in Sources */,
|
||||||
4A4BAD2320B6026900F12B28 /* Interface+CoreDataClass.swift in Sources */,
|
4A4BAD2320B6026900F12B28 /* Interface+CoreDataClass.swift in Sources */,
|
||||||
4AC086832120B9F900CEE5ED /* ProviderConfigurationKeys.swift in Sources */,
|
4AC086832120B9F900CEE5ED /* ProviderConfigurationKeys.swift in Sources */,
|
||||||
4A8AABD820B6A79100B6D8C1 /* UITableView+WireGuard.swift in Sources */,
|
4A8AABD820B6A79100B6D8C1 /* UITableView+WireGuard.swift in Sources */,
|
||||||
|
5FA1D5102124D80C00DBA2E6 /* String+Arrays.swift in Sources */,
|
||||||
4A4BAD2120B6026900F12B28 /* Peer+CoreDataClass.swift in Sources */,
|
4A4BAD2120B6026900F12B28 /* Peer+CoreDataClass.swift in Sources */,
|
||||||
4A4BAD1B20B5F8FF00F12B28 /* Tunnel+CoreDataProperties.swift in Sources */,
|
4A4BAD1B20B5F8FF00F12B28 /* Tunnel+CoreDataProperties.swift in Sources */,
|
||||||
4A4BACE620B5F1BF00F12B28 /* AppDelegate.swift in Sources */,
|
4A4BACE620B5F1BF00F12B28 /* AppDelegate.swift in Sources */,
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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">
|
<device id="retina4_7" orientation="portrait">
|
||||||
<adaptation id="fullscreen"/>
|
<adaptation id="fullscreen"/>
|
||||||
</device>
|
</device>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<deployment identifier="iOS"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.9"/>
|
||||||
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
|
||||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<scenes>
|
<scenes>
|
||||||
@ -140,7 +138,7 @@
|
|||||||
<rect key="frame" x="0.0" y="0.0" width="232" height="35.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="232" height="35.5"/>
|
||||||
<nil key="textColor"/>
|
<nil key="textColor"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||||
<textInputTraits key="textInputTraits"/>
|
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" smartDashesType="no" smartInsertDeleteType="no" smartQuotesType="no"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="textfieldDidChange:" destination="06N-KU-LSv" eventType="editingChanged" id="CQd-My-M7G"/>
|
<action selector="textfieldDidChange:" destination="06N-KU-LSv" eventType="editingChanged" id="CQd-My-M7G"/>
|
||||||
<outlet property="delegate" destination="06N-KU-LSv" id="7YF-mU-HZT"/>
|
<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"/>
|
<rect key="frame" x="0.0" y="16" width="343" height="30"/>
|
||||||
<nil key="textColor"/>
|
<nil key="textColor"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||||
<textInputTraits key="textInputTraits"/>
|
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" smartDashesType="no" smartInsertDeleteType="no" smartQuotesType="no"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="textfieldDidChange:" destination="gzz-88-0IG" eventType="editingChanged" id="taX-Ji-pVu"/>
|
<action selector="textfieldDidChange:" destination="gzz-88-0IG" eventType="editingChanged" id="taX-Ji-pVu"/>
|
||||||
<outlet property="delegate" destination="gzz-88-0IG" id="bkQ-Nc-VH4"/>
|
<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"/>
|
<rect key="frame" x="0.0" y="240" width="343" height="46"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="8uI-Ux-Xhj">
|
<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>
|
<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">
|
<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"/>
|
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
||||||
<color key="textColor" red="0.60784313729999995" green="0.60784313729999995" blue="0.60784313729999995" alpha="1" colorSpace="calibratedRGB"/>
|
<color key="textColor" red="0.60784313729999995" green="0.60784313729999995" blue="0.60784313729999995" alpha="1" colorSpace="calibratedRGB"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="W88-H0-dFZ">
|
<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"/>
|
<nil key="textColor"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||||
<textInputTraits key="textInputTraits"/>
|
<textInputTraits key="textInputTraits"/>
|
||||||
@ -411,16 +409,16 @@
|
|||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
</stackView>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="eLi-QT-QwF">
|
<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>
|
<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">
|
<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"/>
|
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
||||||
<color key="textColor" red="0.60784313729999995" green="0.60784313729999995" blue="0.60784313729999995" alpha="1" colorSpace="calibratedRGB"/>
|
<color key="textColor" red="0.60784313729999995" green="0.60784313729999995" blue="0.60784313729999995" alpha="1" colorSpace="calibratedRGB"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="(optional)" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="Q8w-jh-0ox">
|
<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"/>
|
<nil key="textColor"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||||
<textInputTraits key="textInputTraits"/>
|
<textInputTraits key="textInputTraits"/>
|
||||||
|
24
WireGuard/Extensions/String+Arrays.swift
Normal file
24
WireGuard/Extensions/String+Arrays.swift
Normal 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
WireGuard/Extensions/String+Base64.swift
Normal file
18
WireGuard/Extensions/String+Base64.swift
Normal 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
46
WireGuard/Models/Interface+Extension.swift
Normal file
46
WireGuard/Models/Interface+Extension.swift
Normal file
@ -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 presharedKey: String?
|
||||||
@NSManaged public var allowedIPs: String?
|
@NSManaged public var allowedIPs: String?
|
||||||
@NSManaged public var endpoint: String?
|
@NSManaged public var endpoint: String?
|
||||||
@NSManaged public var persistentKeepalive: Int16
|
@NSManaged public var persistentKeepalive: Int32
|
||||||
@NSManaged public var tunnel: Tunnel?
|
@NSManaged public var tunnel: Tunnel?
|
||||||
|
|
||||||
}
|
}
|
||||||
|
56
WireGuard/Models/Peer+Extension.swift
Normal file
56
WireGuard/Models/Peer+Extension.swift
Normal 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
|
||||||
|
}
|
@ -7,6 +7,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import CoreData
|
||||||
|
|
||||||
extension Tunnel {
|
extension Tunnel {
|
||||||
public func generateProviderConfiguration() -> [String: Any] {
|
public func generateProviderConfiguration() -> [String: Any] {
|
||||||
@ -38,7 +39,7 @@ extension Tunnel {
|
|||||||
return providerConfiguration
|
return providerConfiguration
|
||||||
}
|
}
|
||||||
|
|
||||||
private func generateInterfaceProviderConfiguration(_ interface: Interface) -> String {
|
private func generateInterfaceProviderConfiguration(_ interface: Interface) -> String {
|
||||||
var settingsString = ""
|
var settingsString = ""
|
||||||
|
|
||||||
if let hexPrivateKey = base64KeyToHex(interface.privateKey) {
|
if let hexPrivateKey = base64KeyToHex(interface.privateKey) {
|
||||||
@ -54,7 +55,7 @@ extension Tunnel {
|
|||||||
return settingsString
|
return settingsString
|
||||||
}
|
}
|
||||||
|
|
||||||
private func generatePeerProviderConfiguration(_ peer: Peer) -> String {
|
private func generatePeerProviderConfiguration(_ peer: Peer) -> String {
|
||||||
var settingsString = ""
|
var settingsString = ""
|
||||||
|
|
||||||
if let hexPublicKey = base64KeyToHex(peer.publicKey) {
|
if let hexPublicKey = base64KeyToHex(peer.publicKey) {
|
||||||
@ -77,6 +78,39 @@ extension Tunnel {
|
|||||||
|
|
||||||
return settingsString
|
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? {
|
private func base64KeyToHex(_ base64: String?) -> String? {
|
||||||
@ -104,3 +138,11 @@ private func base64KeyToHex(_ base64: String?) -> String? {
|
|||||||
|
|
||||||
return hexKey
|
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"?>
|
<?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">
|
<entity name="Interface" representedClassName="Interface" syncable="YES">
|
||||||
<attribute name="addresses" optional="YES" attributeType="String" syncable="YES"/>
|
<attribute name="addresses" optional="YES" attributeType="String" syncable="YES"/>
|
||||||
<attribute name="dns" 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">
|
<entity name="Peer" representedClassName="Peer" syncable="YES">
|
||||||
<attribute name="allowedIPs" attributeType="String" syncable="YES"/>
|
<attribute name="allowedIPs" attributeType="String" syncable="YES"/>
|
||||||
<attribute name="endpoint" optional="YES" 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="presharedKey" optional="YES" attributeType="String" syncable="YES"/>
|
||||||
<attribute name="publicKey" 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"/>
|
<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) {
|
@IBAction func saveTunnelConfiguration(_ sender: Any) {
|
||||||
Promise<Void>(resolver: { (seal) in
|
Promise<Void>(resolver: { (seal) in
|
||||||
|
do {
|
||||||
|
try tunnel.validate()
|
||||||
|
} catch {
|
||||||
|
seal.reject(error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
viewContext.perform({
|
viewContext.perform({
|
||||||
self.viewContext.saveContext({ (result) in
|
self.viewContext.saveContext({ (result) in
|
||||||
switch result {
|
switch result {
|
||||||
@ -115,7 +122,9 @@ class TunnelConfigurationTableViewController: UITableViewController {
|
|||||||
self.delegate?.didSave(tunnel: self.tunnel, tunnelConfigurationTableViewController: self)
|
self.delegate?.didSave(tunnel: self.tunnel, tunnelConfigurationTableViewController: self)
|
||||||
return Promise.value(())
|
return Promise.value(())
|
||||||
}.catch { error in
|
}.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 {
|
} else if sender == endpointField {
|
||||||
peer.endpoint = string
|
peer.endpoint = string
|
||||||
} else if sender == persistentKeepaliveField {
|
} else if sender == persistentKeepaliveField {
|
||||||
if let string = string, let persistentKeepalive = Int16(string) {
|
if let string = string, let persistentKeepalive = Int32(string) {
|
||||||
peer.persistentKeepalive = persistentKeepalive
|
peer.persistentKeepalive = persistentKeepalive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,4 +62,62 @@ class ValidatorsTests: XCTestCase {
|
|||||||
executeTest(endpointString: "192.168.0.1")
|
executeTest(endpointString: "192.168.0.1")
|
||||||
executeTest(endpointString: "12345")
|
executeTest(endpointString: "12345")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testCIDRAddress() throws {
|
||||||
|
_ = try CIDRAddress(stringRepresentation: "2607:f938:3001:4000::aac/24")
|
||||||
|
_ = try CIDRAddress(stringRepresentation: "192.168.0.1/24")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testIPv4CIDRAddress() throws {
|
||||||
|
_ = try CIDRAddress(stringRepresentation: "192.168.0.1/24")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCIDRAddress_invalidIP() throws {
|
||||||
|
func executeTest(stringRepresentation: String, ipString: String, file: StaticString = #file, line: UInt = #line) {
|
||||||
|
XCTAssertThrowsError(try CIDRAddress(stringRepresentation: stringRepresentation)) { (error) in
|
||||||
|
guard case CIDRAddressValidationError.invalidIP(let value) = error else {
|
||||||
|
return XCTFail("Unexpected error: \(error)", file: file, line: line)
|
||||||
|
}
|
||||||
|
XCTAssertEqual(value, ipString, file: file, line: line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
executeTest(stringRepresentation: "12345/12345", ipString: "12345")
|
||||||
|
executeTest(stringRepresentation: "/12345", ipString: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCIDRAddress_invalidSubnet() throws {
|
||||||
|
func executeTest(stringRepresentation: String, subnetString: String, file: StaticString = #file, line: UInt = #line) {
|
||||||
|
XCTAssertThrowsError(try CIDRAddress(stringRepresentation: stringRepresentation)) { (error) in
|
||||||
|
guard case CIDRAddressValidationError.invalidSubnet(let value) = error else {
|
||||||
|
return XCTFail("Unexpected error: \(error)", file: file, line: line)
|
||||||
|
}
|
||||||
|
XCTAssertEqual(value, subnetString, file: file, line: line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
executeTest(stringRepresentation: "/", subnetString: "")
|
||||||
|
executeTest(stringRepresentation: "2607:f938:3001:4000::aac/a", subnetString: "a")
|
||||||
|
executeTest(stringRepresentation: "2607:f938:3001:4000:/aac", subnetString: "aac")
|
||||||
|
executeTest(stringRepresentation: "2607:f938:3001:4000::aac/", subnetString: "")
|
||||||
|
executeTest(stringRepresentation: "192.168.0.1/a", subnetString: "a")
|
||||||
|
executeTest(stringRepresentation: "192.168.0.1/", subnetString: "")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCIDRAddress_noIpAndSubnet() throws {
|
||||||
|
|
||||||
|
func executeTest(stringRepresentation: String, file: StaticString = #file, line: UInt = #line) {
|
||||||
|
XCTAssertThrowsError(try CIDRAddress(stringRepresentation: stringRepresentation)) { (error) in
|
||||||
|
guard case CIDRAddressValidationError.noIpAndSubnet(let value) = error else {
|
||||||
|
return XCTFail("Unexpected error: \(error)", file: file, line: line)
|
||||||
|
}
|
||||||
|
XCTAssertEqual(value, stringRepresentation, file: file, line: line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
executeTest(stringRepresentation: "192.168.0.1")
|
||||||
|
executeTest(stringRepresentation: "12345")
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user