Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fcca2d4fec | |||
| 58181a4d40 | |||
| cf816ede13 | |||
| fbac282bdc | |||
| 61f5e017c8 | |||
| 4547e01283 | |||
| 5792db22a6 | |||
| 9d5aa1d8fa | |||
| 6331b81b5d | |||
| 77f929789c | |||
| b5b72b309f | |||
| 966fa7909b | |||
| 115059f2bb | |||
| e53c2d4d17 | |||
| 0a3a5ee900 | |||
| 7720307fc9 |
+145
@@ -0,0 +1,145 @@
|
|||||||
|
# Installing WireGuard tunnels using Configuration Profiles
|
||||||
|
|
||||||
|
WireGuard configurations can be installed using Configuration Profiles
|
||||||
|
through .mobileconfig files.
|
||||||
|
|
||||||
|
### Top-level payload entries
|
||||||
|
|
||||||
|
A .mobileconfig file is a plist file in XML format. The top-level XML item is a top-level payload dictionary (dict). This payload dictionary should contain the following keys:
|
||||||
|
|
||||||
|
- `PayloadDisplayName` (string): The name of the configuration profile, visible when installing the profile
|
||||||
|
|
||||||
|
- `PayloadType` (string): Should be `Configuration`
|
||||||
|
|
||||||
|
- `PayloadVersion` (integer): Should be `1`
|
||||||
|
|
||||||
|
- `PayloadIdentifier` (string): A reverse-DNS style unique identifier for the profile file.
|
||||||
|
|
||||||
|
If you install another .mobileconfig file with the same identifier, the new one
|
||||||
|
overwrites the old one.
|
||||||
|
|
||||||
|
- `PayloadUUID` (string): A randomly generated UUID for this payload
|
||||||
|
|
||||||
|
- `PayloadContent` (array): Should contain an array of payload dictionaries.
|
||||||
|
|
||||||
|
Each of these payload dictionaries can represent a WireGuard tunnel
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
Here's an example .mobileconfig with the above fields filled in:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<?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>PayloadDisplayName</key>
|
||||||
|
<string>WireGuard Demo Configuration Profile</string>
|
||||||
|
<key>PayloadType</key>
|
||||||
|
<string>Configuration</string>
|
||||||
|
<key>PayloadVersion</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>PayloadIdentifier</key>
|
||||||
|
<string>com.your-org.wireguard.FCC9BF80-C540-44C1-B243-521FDD1B2905</string>
|
||||||
|
<key>PayloadUUID</key>
|
||||||
|
<string>F346AAF4-53A2-4FA1-ACA3-EEE74DBED029</string>
|
||||||
|
<key>PayloadContent</key>
|
||||||
|
<array>
|
||||||
|
<!-- An array of WireGuard configuration payload dictionaries -->
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
```
|
||||||
|
|
||||||
|
### WireGuard payload entries
|
||||||
|
|
||||||
|
Each WireGuard configuration payload dictionary should contain the following
|
||||||
|
keys:
|
||||||
|
|
||||||
|
- `PayloadDisplayName` (string): Should be `VPN`
|
||||||
|
|
||||||
|
- `PayloadType` (string): Should be `com.apple.vpn.managed`
|
||||||
|
|
||||||
|
- `PayloadVersion` (integer): Should be `1`
|
||||||
|
|
||||||
|
- `PayloadIdentifier` (string): A reverse-DNS style unique identifier for the WireGuard configuration profile.
|
||||||
|
|
||||||
|
- `PayloadUUID` (string): A randomly generated UUID for this payload
|
||||||
|
|
||||||
|
- `UserDefinedName` (string): The name of the WireGuard tunnel.
|
||||||
|
|
||||||
|
This name shall be used to represent the tunnel in the WireGuard app, and in the System UI for VPNs (Settings > VPN on iOS, System Preferences > Network on macOS).
|
||||||
|
|
||||||
|
- `VPNType` (string): Should be `VPN`
|
||||||
|
|
||||||
|
- `VPNSubType` (string): Should be set as the bundle identifier of the WireGuard app.
|
||||||
|
|
||||||
|
- iOS: `com.wireguard.ios`
|
||||||
|
- macOS: `com.wireguard.macos`
|
||||||
|
|
||||||
|
- `VendorConfig` (dict): Should be a dictionary with the following key:
|
||||||
|
|
||||||
|
- `WgQuickConfig` (string): Should be a WireGuard configuration in [wg-quick(8)] / [wg(8)] format.
|
||||||
|
|
||||||
|
The keys 'FwMark', 'Table', 'PreUp', 'PostUp', 'PreDown', 'PostDown' and 'SaveConfig' are not supported.
|
||||||
|
|
||||||
|
- `VPN` (dict): Should be a dictionary with the following keys:
|
||||||
|
|
||||||
|
- `RemoteAddress` (string): A non-empty string.
|
||||||
|
|
||||||
|
This string is displayed as the server name in the System UI for
|
||||||
|
VPNs (Settings > VPN on iOS, System Preferences > Network on macOS).
|
||||||
|
|
||||||
|
- `AuthenticationMethod` (string): Should be `Password`
|
||||||
|
|
||||||
|
Here's an example WireGuard configuration payload dictionary:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- A WireGuard configuration payload dictionary -->
|
||||||
|
<dict>
|
||||||
|
<key>PayloadDisplayName</key>
|
||||||
|
<string>VPN</string>
|
||||||
|
<key>PayloadType</key>
|
||||||
|
<string>com.apple.vpn.managed</string>
|
||||||
|
<key>PayloadVersion</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>PayloadIdentifier</key>
|
||||||
|
<string>com.your-org.wireguard.demo-profile-1.demo-tunnel</string>
|
||||||
|
<key>PayloadUUID</key>
|
||||||
|
<string>44CDFE9F-4DC7-472A-956F-61C68055117C</string>
|
||||||
|
<key>UserDefinedName</key>
|
||||||
|
<string>Demo from MobileConfig file</string>
|
||||||
|
<key>VPNType</key>
|
||||||
|
<string>VPN</string>
|
||||||
|
<key>VPNSubType</key>
|
||||||
|
<string>com.wireguard.ios</string>
|
||||||
|
<key>VendorConfig</key>
|
||||||
|
<dict>
|
||||||
|
<key>WgQuickConfig</key>
|
||||||
|
<string>
|
||||||
|
[Interface]
|
||||||
|
PrivateKey = mInDaw06K0NgfULRObHJjkWD3ahUC8XC1tVjIf6W+Vo=
|
||||||
|
Address = 10.10.1.0/24
|
||||||
|
DNS = 1.1.1.1, 1.0.0.1
|
||||||
|
|
||||||
|
[Peer]
|
||||||
|
PublicKey = JRI8Xc0zKP9kXk8qP84NdUQA04h6DLfFbwJn4g+/PFs=
|
||||||
|
Endpoint = demo.wireguard.com:12912
|
||||||
|
AllowedIPs = 0.0.0.0/0
|
||||||
|
</string>
|
||||||
|
</dict>
|
||||||
|
<key>VPN</key>
|
||||||
|
<dict>
|
||||||
|
<key>RemoteAddress</key>
|
||||||
|
<string>demo.wireguard.com:12912</string>
|
||||||
|
<key>AuthenticationMethod</key>
|
||||||
|
<string>Password</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Caveats
|
||||||
|
|
||||||
|
Configurations added via .mobileconfig will not be migrated into keychain until the WireGuard application is opened once.
|
||||||
|
|
||||||
|
[wg-quick(8)]: https://git.zx2c4.com/WireGuard/about/src/tools/man/wg-quick.8
|
||||||
|
[wg(8)]: https://git.zx2c4.com/WireGuard/about/src/tools/man/wg.8
|
||||||
@@ -34,17 +34,14 @@ extension NETunnelProviderProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func asTunnelConfiguration(called name: String? = nil) -> TunnelConfiguration? {
|
func asTunnelConfiguration(called name: String? = nil) -> TunnelConfiguration? {
|
||||||
migrateConfigurationIfNeeded(called: name ?? "unknown")
|
if let passwordReference = passwordReference,
|
||||||
//TODO: in the case where migrateConfigurationIfNeeded is called by the network extension,
|
let config = Keychain.openReference(called: passwordReference) {
|
||||||
// before the app has started, and when there is, in fact, configuration that needs to be
|
return try? TunnelConfiguration(fromWgQuickConfig: config, called: name)
|
||||||
// put into the keychain, this will generate one new keychain item every time it is started,
|
}
|
||||||
// until finally the app is open. Would it be possible to call saveToPreferences here? Or is
|
if let oldConfig = providerConfiguration?["WgQuickConfig"] as? String {
|
||||||
// that generally not available to network extensions? In which case, what should our
|
return try? TunnelConfiguration(fromWgQuickConfig: oldConfig, called: name)
|
||||||
// behavior be?
|
}
|
||||||
|
return nil
|
||||||
guard let passwordReference = passwordReference else { return nil }
|
|
||||||
guard let config = Keychain.openReference(called: passwordReference) else { return nil }
|
|
||||||
return try? TunnelConfiguration(fromWgQuickConfig: config, called: name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func destroyConfigurationReference() {
|
func destroyConfigurationReference() {
|
||||||
@@ -66,6 +63,7 @@ extension NETunnelProviderProtocol {
|
|||||||
guard let oldConfig = providerConfiguration?["WgQuickConfig"] as? String else { return false }
|
guard let oldConfig = providerConfiguration?["WgQuickConfig"] as? String else { return false }
|
||||||
providerConfiguration = nil
|
providerConfiguration = nil
|
||||||
guard passwordReference == nil else { return true }
|
guard passwordReference == nil else { return true }
|
||||||
|
wg_log(.debug, message: "Migrating tunnel configuration '\(name)'")
|
||||||
passwordReference = Keychain.makeReference(containing: oldConfig, called: name)
|
passwordReference = Keychain.makeReference(containing: oldConfig, called: name)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
6B653B86220DE2960050E69C /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6FF4AC462120B9E0002C96EB /* NetworkExtension.framework */; };
|
6B653B86220DE2960050E69C /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6FF4AC462120B9E0002C96EB /* NetworkExtension.framework */; };
|
||||||
6B707D8421F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */; };
|
6B707D8421F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */; };
|
||||||
6B707D8621F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */; };
|
6B707D8621F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */; };
|
||||||
|
6BAC16E6221634B300A5FB78 /* AppStorePrivacyNotice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BAC16E42216324B00A5FB78 /* AppStorePrivacyNotice.swift */; };
|
||||||
6BD5C97B220D1AE200784E08 /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5C979220D1AE100784E08 /* key.c */; };
|
6BD5C97B220D1AE200784E08 /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5C979220D1AE100784E08 /* key.c */; };
|
||||||
6BD5C97C220D1AE200784E08 /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5C979220D1AE100784E08 /* key.c */; };
|
6BD5C97C220D1AE200784E08 /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5C979220D1AE100784E08 /* key.c */; };
|
||||||
6BD5C97D220D1AE200784E08 /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5C979220D1AE100784E08 /* key.c */; };
|
6BD5C97D220D1AE200784E08 /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5C979220D1AE100784E08 /* key.c */; };
|
||||||
@@ -63,6 +64,8 @@
|
|||||||
6F6899A62180447E0012E523 /* x25519.c in Sources */ = {isa = PBXBuildFile; fileRef = 6F6899A52180447E0012E523 /* x25519.c */; };
|
6F6899A62180447E0012E523 /* x25519.c in Sources */ = {isa = PBXBuildFile; fileRef = 6F6899A52180447E0012E523 /* x25519.c */; };
|
||||||
6F6899A8218044FC0012E523 /* Curve25519.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F6899A7218044FC0012E523 /* Curve25519.swift */; };
|
6F6899A8218044FC0012E523 /* Curve25519.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F6899A7218044FC0012E523 /* Curve25519.swift */; };
|
||||||
6F693A562179E556008551C1 /* Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F693A552179E556008551C1 /* Endpoint.swift */; };
|
6F693A562179E556008551C1 /* Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F693A552179E556008551C1 /* Endpoint.swift */; };
|
||||||
|
6F70E20E221058E1008BDFB4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6F70E20C221058DF008BDFB4 /* InfoPlist.strings */; };
|
||||||
|
6F70E20F221058E1008BDFB4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6F70E20C221058DF008BDFB4 /* InfoPlist.strings */; };
|
||||||
6F7774E1217181B1006A79B3 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774DF217181B1006A79B3 /* MainViewController.swift */; };
|
6F7774E1217181B1006A79B3 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774DF217181B1006A79B3 /* MainViewController.swift */; };
|
||||||
6F7774E2217181B1006A79B3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E0217181B1006A79B3 /* AppDelegate.swift */; };
|
6F7774E2217181B1006A79B3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E0217181B1006A79B3 /* AppDelegate.swift */; };
|
||||||
6F7774E421718281006A79B3 /* TunnelsListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E321718281006A79B3 /* TunnelsListTableViewController.swift */; };
|
6F7774E421718281006A79B3 /* TunnelsListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E321718281006A79B3 /* TunnelsListTableViewController.swift */; };
|
||||||
@@ -253,6 +256,7 @@
|
|||||||
6B5C5E26220A48D30024272E /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = "<group>"; };
|
6B5C5E26220A48D30024272E /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = "<group>"; };
|
||||||
6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateDataConfirmation.swift; sourceTree = "<group>"; };
|
6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateDataConfirmation.swift; sourceTree = "<group>"; };
|
||||||
6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelConfiguration+UapiConfig.swift"; sourceTree = "<group>"; };
|
6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelConfiguration+UapiConfig.swift"; sourceTree = "<group>"; };
|
||||||
|
6BAC16E42216324B00A5FB78 /* AppStorePrivacyNotice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStorePrivacyNotice.swift; sourceTree = "<group>"; };
|
||||||
6BD5C979220D1AE100784E08 /* key.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = key.c; sourceTree = "<group>"; };
|
6BD5C979220D1AE100784E08 /* key.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = key.c; sourceTree = "<group>"; };
|
||||||
6BD5C97A220D1AE200784E08 /* key.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = key.h; sourceTree = "<group>"; };
|
6BD5C97A220D1AE200784E08 /* key.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = key.h; sourceTree = "<group>"; };
|
||||||
6F4DD16721DA552B00690EAE /* NSTableView+Reuse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTableView+Reuse.swift"; sourceTree = "<group>"; };
|
6F4DD16721DA552B00690EAE /* NSTableView+Reuse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTableView+Reuse.swift"; sourceTree = "<group>"; };
|
||||||
@@ -277,6 +281,7 @@
|
|||||||
6F6899A52180447E0012E523 /* x25519.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = x25519.c; sourceTree = "<group>"; };
|
6F6899A52180447E0012E523 /* x25519.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = x25519.c; sourceTree = "<group>"; };
|
||||||
6F6899A7218044FC0012E523 /* Curve25519.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Curve25519.swift; sourceTree = "<group>"; };
|
6F6899A7218044FC0012E523 /* Curve25519.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Curve25519.swift; sourceTree = "<group>"; };
|
||||||
6F693A552179E556008551C1 /* Endpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Endpoint.swift; sourceTree = "<group>"; };
|
6F693A552179E556008551C1 /* Endpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Endpoint.swift; sourceTree = "<group>"; };
|
||||||
|
6F70E20D221058DF008BDFB4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = WireGuard/Base.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
6F7774DF217181B1006A79B3 /* MainViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
|
6F7774DF217181B1006A79B3 /* MainViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
|
||||||
6F7774E0217181B1006A79B3 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
6F7774E0217181B1006A79B3 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
6F7774E321718281006A79B3 /* TunnelsListTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelsListTableViewController.swift; sourceTree = "<group>"; };
|
6F7774E321718281006A79B3 /* TunnelsListTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelsListTableViewController.swift; sourceTree = "<group>"; };
|
||||||
@@ -558,6 +563,7 @@
|
|||||||
6F4DD16721DA552B00690EAE /* NSTableView+Reuse.swift */,
|
6F4DD16721DA552B00690EAE /* NSTableView+Reuse.swift */,
|
||||||
5F52D0BE21E3788900283CEA /* NSColor+Hex.swift */,
|
5F52D0BE21E3788900283CEA /* NSColor+Hex.swift */,
|
||||||
6FFACD1E21E4D89600E9A2A5 /* ParseError+WireGuardAppError.swift */,
|
6FFACD1E21E4D89600E9A2A5 /* ParseError+WireGuardAppError.swift */,
|
||||||
|
6BAC16E42216324B00A5FB78 /* AppStorePrivacyNotice.swift */,
|
||||||
);
|
);
|
||||||
path = macOS;
|
path = macOS;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -619,6 +625,7 @@
|
|||||||
6FF4AC0B211EC46F002C96EB = {
|
6FF4AC0B211EC46F002C96EB = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
6F70E20C221058DF008BDFB4 /* InfoPlist.strings */,
|
||||||
6FE1765421C90BBE002690EA /* Localizable.strings */,
|
6FE1765421C90BBE002690EA /* Localizable.strings */,
|
||||||
6F5D0C432183B4A4000F85AD /* Shared */,
|
6F5D0C432183B4A4000F85AD /* Shared */,
|
||||||
6FF4AC16211EC46F002C96EB /* WireGuard */,
|
6FF4AC16211EC46F002C96EB /* WireGuard */,
|
||||||
@@ -874,6 +881,7 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
6FB1BD6221D2607E00A991BF /* Assets.xcassets in Resources */,
|
6FB1BD6221D2607E00A991BF /* Assets.xcassets in Resources */,
|
||||||
|
6F70E20F221058E1008BDFB4 /* InfoPlist.strings in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -889,6 +897,7 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
6F919ED9218C65C50023B400 /* wireguard_doc_logo_22x29.png in Resources */,
|
6F919ED9218C65C50023B400 /* wireguard_doc_logo_22x29.png in Resources */,
|
||||||
|
6F70E20E221058E1008BDFB4 /* InfoPlist.strings in Resources */,
|
||||||
6FE1765621C90BBE002690EA /* Localizable.strings in Resources */,
|
6FE1765621C90BBE002690EA /* Localizable.strings in Resources */,
|
||||||
6FF4AC22211EC472002C96EB /* LaunchScreen.storyboard in Resources */,
|
6FF4AC22211EC472002C96EB /* LaunchScreen.storyboard in Resources */,
|
||||||
6F919EDA218C65C50023B400 /* wireguard_doc_logo_44x58.png in Resources */,
|
6F919EDA218C65C50023B400 /* wireguard_doc_logo_44x58.png in Resources */,
|
||||||
@@ -1150,6 +1159,7 @@
|
|||||||
6FB1BDCB21D50F1700A991BF /* Curve25519.swift in Sources */,
|
6FB1BDCB21D50F1700A991BF /* Curve25519.swift in Sources */,
|
||||||
6B586C55220CBA6D00427C51 /* Data+KeyEncoding.swift in Sources */,
|
6B586C55220CBA6D00427C51 /* Data+KeyEncoding.swift in Sources */,
|
||||||
6F9B582921E8D6D100544D02 /* PopupRow.swift in Sources */,
|
6F9B582921E8D6D100544D02 /* PopupRow.swift in Sources */,
|
||||||
|
6BAC16E6221634B300A5FB78 /* AppStorePrivacyNotice.swift in Sources */,
|
||||||
6FB1BDBB21D50F0200A991BF /* Localizable.strings in Sources */,
|
6FB1BDBB21D50F0200A991BF /* Localizable.strings in Sources */,
|
||||||
6FB1BDBC21D50F0200A991BF /* ringlogger.c in Sources */,
|
6FB1BDBC21D50F0200A991BF /* ringlogger.c in Sources */,
|
||||||
6FB1BDBD21D50F0200A991BF /* ringlogger.h in Sources */,
|
6FB1BDBD21D50F0200A991BF /* ringlogger.h in Sources */,
|
||||||
@@ -1291,6 +1301,14 @@
|
|||||||
/* End PBXTargetDependency section */
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
/* Begin PBXVariantGroup section */
|
/* Begin PBXVariantGroup section */
|
||||||
|
6F70E20C221058DF008BDFB4 /* InfoPlist.strings */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
6F70E20D221058DF008BDFB4 /* Base */,
|
||||||
|
);
|
||||||
|
name = InfoPlist.strings;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
6FE1765421C90BBE002690EA /* Localizable.strings */ = {
|
6FE1765421C90BBE002690EA /* Localizable.strings */ = {
|
||||||
isa = PBXVariantGroup;
|
isa = PBXVariantGroup;
|
||||||
children = (
|
children = (
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
// iOS permission prompts
|
||||||
|
|
||||||
|
NSCameraUsageDescription = "Camera is used for scanning QR codes for importing WireGuard configurations";
|
||||||
|
NSFaceIDUsageDescription = "Face ID is used for authenticating viewing and exporting of private keys";
|
||||||
@@ -111,6 +111,8 @@
|
|||||||
"tunnelHandshakeTimestampHours hh:mm:ss (%@)" = "%@ hours";
|
"tunnelHandshakeTimestampHours hh:mm:ss (%@)" = "%@ hours";
|
||||||
"tunnelHandshakeTimestampMinutes mm:ss (%@)" = "%@ minutes";
|
"tunnelHandshakeTimestampMinutes mm:ss (%@)" = "%@ minutes";
|
||||||
|
|
||||||
|
"tunnelPeerPresharedKeyEnabled" = "enabled";
|
||||||
|
|
||||||
// Error alerts while creating / editing a tunnel configuration
|
// Error alerts while creating / editing a tunnel configuration
|
||||||
|
|
||||||
/* Alert title for error in the interface data */
|
/* Alert title for error in the interface data */
|
||||||
@@ -331,3 +333,5 @@
|
|||||||
|
|
||||||
"macAppExitingWithActiveTunnelMessage" = "WireGuard is exiting with an active tunnel";
|
"macAppExitingWithActiveTunnelMessage" = "WireGuard is exiting with an active tunnel";
|
||||||
"macAppExitingWithActiveTunnelInfo" = "The tunnel will remain active after exiting. You may disable it by reopening this application or through the Network panel in System Preferences.";
|
"macAppExitingWithActiveTunnelInfo" = "The tunnel will remain active after exiting. You may disable it by reopening this application or through the Network panel in System Preferences.";
|
||||||
|
"macPrivacyNoticeMessage" = "Privacy notice: be sure you trust this configuration file";
|
||||||
|
"macPrivacyNoticeInfo" = "You will be prompted by the system to allow or disallow adding a VPN configuration. While this application does not send any information to the WireGuard project, information is by design sent to the servers specified inside of the configuration file you have just added, which configures your computer to use those servers as a VPN. Be certain that you trust this configuration before clicking “Allow” in the following dialog.";
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
VERSION_NAME = 0.0.20190207
|
VERSION_NAME = 0.0.20190207
|
||||||
VERSION_ID = 2
|
VERSION_ID = 3
|
||||||
|
|||||||
@@ -83,6 +83,11 @@ class TunnelsManager {
|
|||||||
matchingTunnel.refreshStatus()
|
matchingTunnel.refreshStatus()
|
||||||
} else {
|
} else {
|
||||||
// Tunnel was added outside the app
|
// Tunnel was added outside the app
|
||||||
|
if let proto = loadedTunnelProvider.protocolConfiguration as? NETunnelProviderProtocol {
|
||||||
|
if proto.migrateConfigurationIfNeeded(called: loadedTunnelProvider.localizedDescription ?? "unknown") {
|
||||||
|
loadedTunnelProvider.saveToPreferences { _ in }
|
||||||
|
}
|
||||||
|
}
|
||||||
let tunnel = TunnelContainer(tunnel: loadedTunnelProvider)
|
let tunnel = TunnelContainer(tunnel: loadedTunnelProvider)
|
||||||
self.tunnels.append(tunnel)
|
self.tunnels.append(tunnel)
|
||||||
self.tunnels.sort { $0.name < $1.name }
|
self.tunnels.sort { $0.name < $1.name }
|
||||||
@@ -313,11 +318,7 @@ class TunnelsManager {
|
|||||||
guard let self = self,
|
guard let self = self,
|
||||||
let session = statusChangeNotification.object as? NETunnelProviderSession,
|
let session = statusChangeNotification.object as? NETunnelProviderSession,
|
||||||
let tunnelProvider = session.manager as? NETunnelProviderManager,
|
let tunnelProvider = session.manager as? NETunnelProviderManager,
|
||||||
let tunnelConfiguration = tunnelProvider.tunnelConfiguration,
|
let tunnel = self.tunnels.first(where: { $0.tunnelProvider == tunnelProvider }) else { return }
|
||||||
let tunnel = self.tunnels.first(where: { $0.tunnelConfiguration == tunnelConfiguration }) else { return }
|
|
||||||
if tunnel.tunnelProvider != tunnelProvider {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
wg_log(.debug, message: "Tunnel '\(tunnel.name)' connection status changed to '\(tunnel.tunnelProvider.connection.status)'")
|
wg_log(.debug, message: "Tunnel '\(tunnel.name)' connection status changed to '\(tunnel.tunnelProvider.connection.status)'")
|
||||||
|
|
||||||
|
|||||||
@@ -67,17 +67,17 @@ class TunnelViewModel {
|
|||||||
|
|
||||||
static let keyLengthInBase64 = 44
|
static let keyLengthInBase64 = 44
|
||||||
|
|
||||||
struct ChangeHandlers {
|
struct Changes {
|
||||||
enum FieldChange {
|
enum FieldChange: Equatable {
|
||||||
case added
|
case added
|
||||||
case removed
|
case removed
|
||||||
case modified
|
case modified(newValue: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
var interfaceChanged: ([InterfaceField: FieldChange]) -> Void
|
var interfaceChanges: [InterfaceField: FieldChange]
|
||||||
var peerChangedAt: (Int, [PeerField: FieldChange]) -> Void
|
var peerChanges: [(peerIndex: Int, changes: [PeerField: FieldChange])]
|
||||||
var peersRemovedAt: ([Int]) -> Void
|
var peersRemovedIndices: [Int]
|
||||||
var peersInsertedAt: ([Int]) -> Void
|
var peersInsertedIndices: [Int]
|
||||||
}
|
}
|
||||||
|
|
||||||
class InterfaceData {
|
class InterfaceData {
|
||||||
@@ -217,12 +217,12 @@ class TunnelViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyConfiguration(other: InterfaceConfiguration, otherName: String, changeHandler: ([InterfaceField: ChangeHandlers.FieldChange]) -> Void) {
|
func applyConfiguration(other: InterfaceConfiguration, otherName: String) -> [InterfaceField: Changes.FieldChange] {
|
||||||
if scratchpad.isEmpty {
|
if scratchpad.isEmpty {
|
||||||
populateScratchpad()
|
populateScratchpad()
|
||||||
}
|
}
|
||||||
let otherScratchPad = InterfaceData.createScratchPad(from: other, name: otherName)
|
let otherScratchPad = InterfaceData.createScratchPad(from: other, name: otherName)
|
||||||
var changes = [InterfaceField: ChangeHandlers.FieldChange]()
|
var changes = [InterfaceField: Changes.FieldChange]()
|
||||||
for field in InterfaceField.allCases {
|
for field in InterfaceField.allCases {
|
||||||
switch (scratchpad[field] ?? "", otherScratchPad[field] ?? "") {
|
switch (scratchpad[field] ?? "", otherScratchPad[field] ?? "") {
|
||||||
case ("", ""):
|
case ("", ""):
|
||||||
@@ -233,14 +233,12 @@ class TunnelViewModel {
|
|||||||
changes[field] = .removed
|
changes[field] = .removed
|
||||||
case (let this, let other):
|
case (let this, let other):
|
||||||
if this != other {
|
if this != other {
|
||||||
changes[field] = .modified
|
changes[field] = .modified(newValue: other)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scratchpad = otherScratchPad
|
scratchpad = otherScratchPad
|
||||||
if !changes.isEmpty {
|
return changes
|
||||||
changeHandler(changes)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,12 +439,12 @@ class TunnelViewModel {
|
|||||||
excludePrivateIPsValue = isOn
|
excludePrivateIPsValue = isOn
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyConfiguration(other: PeerConfiguration, peerIndex: Int, changeHandler: (Int, [PeerField: ChangeHandlers.FieldChange]) -> Void) {
|
func applyConfiguration(other: PeerConfiguration) -> [PeerField: Changes.FieldChange] {
|
||||||
if scratchpad.isEmpty {
|
if scratchpad.isEmpty {
|
||||||
populateScratchpad()
|
populateScratchpad()
|
||||||
}
|
}
|
||||||
let otherScratchPad = PeerData.createScratchPad(from: other)
|
let otherScratchPad = PeerData.createScratchPad(from: other)
|
||||||
var changes = [PeerField: ChangeHandlers.FieldChange]()
|
var changes = [PeerField: Changes.FieldChange]()
|
||||||
for field in PeerField.allCases {
|
for field in PeerField.allCases {
|
||||||
switch (scratchpad[field] ?? "", otherScratchPad[field] ?? "") {
|
switch (scratchpad[field] ?? "", otherScratchPad[field] ?? "") {
|
||||||
case ("", ""):
|
case ("", ""):
|
||||||
@@ -457,14 +455,12 @@ class TunnelViewModel {
|
|||||||
changes[field] = .removed
|
changes[field] = .removed
|
||||||
case (let this, let other):
|
case (let this, let other):
|
||||||
if this != other {
|
if this != other {
|
||||||
changes[field] = .modified
|
changes[field] = .modified(newValue: other)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scratchpad = otherScratchPad
|
scratchpad = otherScratchPad
|
||||||
if !changes.isEmpty {
|
return changes
|
||||||
changeHandler(peerIndex, changes)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -548,21 +544,20 @@ class TunnelViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyConfiguration(other: TunnelConfiguration, changeHandlers: ChangeHandlers) {
|
@discardableResult
|
||||||
|
func applyConfiguration(other: TunnelConfiguration) -> Changes {
|
||||||
// Replaces current data with data from other TunnelConfiguration, ignoring any changes in peer ordering.
|
// Replaces current data with data from other TunnelConfiguration, ignoring any changes in peer ordering.
|
||||||
// Change handler callbacks are processed in the following order, which is designed to work with both the
|
|
||||||
// UITableView way (modify - delete - insert) and the NSTableView way (indices are based on past operations):
|
|
||||||
// - interfaceChanged
|
|
||||||
// - peerChangedAt
|
|
||||||
// - peersRemovedAt
|
|
||||||
// - peersInsertedAt
|
|
||||||
|
|
||||||
interfaceData.applyConfiguration(other: other.interface, otherName: other.name ?? "", changeHandler: changeHandlers.interfaceChanged)
|
let interfaceChanges = interfaceData.applyConfiguration(other: other.interface, otherName: other.name ?? "")
|
||||||
|
|
||||||
|
var peerChanges = [(peerIndex: Int, changes: [PeerField: Changes.FieldChange])]()
|
||||||
for otherPeer in other.peers {
|
for otherPeer in other.peers {
|
||||||
if let peersDataIndex = peersData.firstIndex(where: { $0.publicKey == otherPeer.publicKey }) {
|
if let peersDataIndex = peersData.firstIndex(where: { $0.publicKey == otherPeer.publicKey }) {
|
||||||
let peerData = peersData[peersDataIndex]
|
let peerData = peersData[peersDataIndex]
|
||||||
peerData.applyConfiguration(other: otherPeer, peerIndex: peersDataIndex, changeHandler: changeHandlers.peerChangedAt)
|
let changes = peerData.applyConfiguration(other: otherPeer)
|
||||||
|
if !changes.isEmpty {
|
||||||
|
peerChanges.append((peerIndex: peersDataIndex, changes: changes))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -573,9 +568,6 @@ class TunnelViewModel {
|
|||||||
peersData.remove(at: index)
|
peersData.remove(at: index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !removedPeerIndices.isEmpty {
|
|
||||||
changeHandlers.peersRemovedAt(removedPeerIndices)
|
|
||||||
}
|
|
||||||
|
|
||||||
var addedPeerIndices = [Int]()
|
var addedPeerIndices = [Int]()
|
||||||
for otherPeer in other.peers {
|
for otherPeer in other.peers {
|
||||||
@@ -586,15 +578,14 @@ class TunnelViewModel {
|
|||||||
peersData.append(peerData)
|
peersData.append(peerData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !addedPeerIndices.isEmpty {
|
|
||||||
changeHandlers.peersInsertedAt(addedPeerIndices)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (index, peer) in peersData.enumerated() {
|
for (index, peer) in peersData.enumerated() {
|
||||||
peer.index = index
|
peer.index = index
|
||||||
peer.numberOfPeers = peersData.count
|
peer.numberOfPeers = peersData.count
|
||||||
peer.updateExcludePrivateIPsFieldState()
|
peer.updateExcludePrivateIPsFieldState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Changes(interfaceChanges: interfaceChanges, peerChanges: peerChanges, peersRemovedIndices: removedPeerIndices, peersInsertedIndices: addedPeerIndices)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,7 @@
|
|||||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
<string>Camera is used for scanning QR codes for importing WireGuard configurations</string>
|
<string>Localized</string>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIRequiredDeviceCapabilities</key>
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
@@ -123,7 +123,7 @@
|
|||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>NSFaceIDUsageDescription</key>
|
<key>NSFaceIDUsageDescription</key>
|
||||||
<string>Face ID is used for authenticating viewing and exporting of private keys</string>
|
<string>Localized</string>
|
||||||
<key>com.wireguard.ios.app_group_id</key>
|
<key>com.wireguard.ios.app_group_id</key>
|
||||||
<string>group.$(APP_ID_IOS)</string>
|
<string>group.$(APP_ID_IOS)</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
@@ -158,16 +158,21 @@ class TunnelDetailTableViewController: UITableViewController {
|
|||||||
var interfaceFieldIsVisible = self.interfaceFieldIsVisible
|
var interfaceFieldIsVisible = self.interfaceFieldIsVisible
|
||||||
var peerFieldIsVisible = self.peerFieldIsVisible
|
var peerFieldIsVisible = self.peerFieldIsVisible
|
||||||
|
|
||||||
func sectionChanged<T>(fields: [T], fieldIsVisible fieldIsVisibleInput: [Bool], tableView: UITableView, section: Int, changes: [T: TunnelViewModel.ChangeHandlers.FieldChange]) {
|
func handleSectionFieldsModified<T>(fields: [T], fieldIsVisible: [Bool], section: Int, changes: [T: TunnelViewModel.Changes.FieldChange]) {
|
||||||
|
for (index, field) in fields.enumerated() {
|
||||||
|
guard let change = changes[field] else { continue }
|
||||||
|
if case .modified(let newValue) = change {
|
||||||
|
let row = fieldIsVisible[0 ..< index].filter { $0 }.count
|
||||||
|
let indexPath = IndexPath(row: row, section: section)
|
||||||
|
if let cell = tableView.cellForRow(at: indexPath) as? KeyValueCell {
|
||||||
|
cell.value = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSectionRowsInsertedOrRemoved<T>(fields: [T], fieldIsVisible fieldIsVisibleInput: [Bool], section: Int, changes: [T: TunnelViewModel.Changes.FieldChange]) {
|
||||||
var fieldIsVisible = fieldIsVisibleInput
|
var fieldIsVisible = fieldIsVisibleInput
|
||||||
var modifiedIndexPaths = [IndexPath]()
|
|
||||||
for (index, field) in fields.enumerated() where changes[field] == .modified {
|
|
||||||
let row = fieldIsVisible[0 ..< index].filter { $0 }.count
|
|
||||||
modifiedIndexPaths.append(IndexPath(row: row, section: section))
|
|
||||||
}
|
|
||||||
if !modifiedIndexPaths.isEmpty {
|
|
||||||
tableView.reloadRows(at: modifiedIndexPaths, with: .none)
|
|
||||||
}
|
|
||||||
|
|
||||||
var removedIndexPaths = [IndexPath]()
|
var removedIndexPaths = [IndexPath]()
|
||||||
for (index, field) in fields.enumerated().reversed() where changes[field] == .removed {
|
for (index, field) in fields.enumerated().reversed() where changes[field] == .removed {
|
||||||
@@ -190,30 +195,44 @@ class TunnelDetailTableViewController: UITableViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let changeHandlers = TunnelViewModel.ChangeHandlers(
|
let changes = self.tunnelViewModel.applyConfiguration(other: tunnelConfiguration)
|
||||||
interfaceChanged: { changes in
|
|
||||||
sectionChanged(fields: TunnelDetailTableViewController.interfaceFields, fieldIsVisible: interfaceFieldIsVisible,
|
|
||||||
tableView: tableView, section: interfaceSectionIndex, changes: changes)
|
|
||||||
},
|
|
||||||
peerChangedAt: { peerIndex, changes in
|
|
||||||
sectionChanged(fields: TunnelDetailTableViewController.peerFields, fieldIsVisible: peerFieldIsVisible[peerIndex],
|
|
||||||
tableView: tableView, section: firstPeerSectionIndex + peerIndex, changes: changes)
|
|
||||||
},
|
|
||||||
peersRemovedAt: { peerIndices in
|
|
||||||
let sectionIndices = peerIndices.map { firstPeerSectionIndex + $0 }
|
|
||||||
tableView.deleteSections(IndexSet(sectionIndices), with: .automatic)
|
|
||||||
},
|
|
||||||
peersInsertedAt: { peerIndices in
|
|
||||||
let sectionIndices = peerIndices.map { firstPeerSectionIndex + $0 }
|
|
||||||
tableView.insertSections(IndexSet(sectionIndices), with: .automatic)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
tableView.beginUpdates()
|
if !changes.interfaceChanges.isEmpty {
|
||||||
self.tunnelViewModel.applyConfiguration(other: tunnelConfiguration, changeHandlers: changeHandlers)
|
handleSectionFieldsModified(fields: TunnelDetailTableViewController.interfaceFields, fieldIsVisible: interfaceFieldIsVisible,
|
||||||
self.loadSections()
|
section: interfaceSectionIndex, changes: changes.interfaceChanges)
|
||||||
self.loadVisibleFields()
|
}
|
||||||
tableView.endUpdates()
|
for (peerIndex, peerChanges) in changes.peerChanges {
|
||||||
|
handleSectionFieldsModified(fields: TunnelDetailTableViewController.peerFields, fieldIsVisible: peerFieldIsVisible[peerIndex], section: firstPeerSectionIndex + peerIndex, changes: peerChanges)
|
||||||
|
}
|
||||||
|
|
||||||
|
let isAnyInterfaceFieldAddedOrRemoved = changes.interfaceChanges.contains { $0.value == .added || $0.value == .removed }
|
||||||
|
let isAnyPeerFieldAddedOrRemoved = changes.peerChanges.contains { $0.changes.contains { $0.value == .added || $0.value == .removed } }
|
||||||
|
let peersRemovedSectionIndices = changes.peersRemovedIndices.map { firstPeerSectionIndex + $0 }
|
||||||
|
let peersInsertedSectionIndices = changes.peersInsertedIndices.map { firstPeerSectionIndex + $0 }
|
||||||
|
|
||||||
|
if isAnyInterfaceFieldAddedOrRemoved || isAnyPeerFieldAddedOrRemoved || !peersRemovedSectionIndices.isEmpty || !peersInsertedSectionIndices.isEmpty {
|
||||||
|
tableView.beginUpdates()
|
||||||
|
if isAnyInterfaceFieldAddedOrRemoved {
|
||||||
|
handleSectionRowsInsertedOrRemoved(fields: TunnelDetailTableViewController.interfaceFields, fieldIsVisible: interfaceFieldIsVisible, section: interfaceSectionIndex, changes: changes.interfaceChanges)
|
||||||
|
}
|
||||||
|
if isAnyPeerFieldAddedOrRemoved {
|
||||||
|
for (peerIndex, peerChanges) in changes.peerChanges {
|
||||||
|
handleSectionRowsInsertedOrRemoved(fields: TunnelDetailTableViewController.peerFields, fieldIsVisible: peerFieldIsVisible[peerIndex], section: firstPeerSectionIndex + peerIndex, changes: peerChanges)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !peersRemovedSectionIndices.isEmpty {
|
||||||
|
tableView.deleteSections(IndexSet(peersRemovedSectionIndices), with: .automatic)
|
||||||
|
}
|
||||||
|
if !peersInsertedSectionIndices.isEmpty {
|
||||||
|
tableView.insertSections(IndexSet(peersInsertedSectionIndices), with: .automatic)
|
||||||
|
}
|
||||||
|
self.loadSections()
|
||||||
|
self.loadVisibleFields()
|
||||||
|
tableView.endUpdates()
|
||||||
|
} else {
|
||||||
|
self.loadSections()
|
||||||
|
self.loadVisibleFields()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func reloadRuntimeConfiguration() {
|
private func reloadRuntimeConfiguration() {
|
||||||
@@ -351,6 +370,8 @@ extension TunnelDetailTableViewController {
|
|||||||
cell.key = field.localizedUIString
|
cell.key = field.localizedUIString
|
||||||
if field == .persistentKeepAlive {
|
if field == .persistentKeepAlive {
|
||||||
cell.value = tr(format: "tunnelPeerPersistentKeepaliveValue (%@)", peerData[field])
|
cell.value = tr(format: "tunnelPeerPersistentKeepaliveValue (%@)", peerData[field])
|
||||||
|
} else if field == .preSharedKey {
|
||||||
|
cell.value = tr("tunnelPeerPresharedKeyEnabled")
|
||||||
} else {
|
} else {
|
||||||
cell.value = peerData[field]
|
cell.value = peerData[field]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
class AppStorePrivacyNotice {
|
||||||
|
// The App Store Review Board does not comprehend the fact that this application
|
||||||
|
// is not a service and does not have any servers of its own. They therefore require
|
||||||
|
// us to give a notice regarding collection of user data using our non-existent
|
||||||
|
// servers. This demand is obviously impossible to fulfill, since it doesn't make sense,
|
||||||
|
// but we do our best here to show something in that category.
|
||||||
|
static func show(from sourceVC: NSViewController?, into tunnelsManager: TunnelsManager, _ callback: @escaping () -> Void) {
|
||||||
|
if tunnelsManager.numberOfTunnels() > 0 {
|
||||||
|
callback()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let alert = NSAlert()
|
||||||
|
|
||||||
|
alert.messageText = tr("macPrivacyNoticeMessage")
|
||||||
|
alert.informativeText = tr("macPrivacyNoticeInfo")
|
||||||
|
alert.alertStyle = NSAlert.Style.warning
|
||||||
|
if let window = sourceVC?.view.window {
|
||||||
|
alert.beginSheetModal(for: window) { _ in callback() }
|
||||||
|
} else {
|
||||||
|
alert.runModal()
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,9 @@ class ImportPanelPresenter {
|
|||||||
guard let tunnelsManager = tunnelsManager else { return }
|
guard let tunnelsManager = tunnelsManager else { return }
|
||||||
guard response == .OK else { return }
|
guard response == .OK else { return }
|
||||||
guard let url = openPanel.url else { return }
|
guard let url = openPanel.url else { return }
|
||||||
TunnelImporter.importFromFile(url: url, into: tunnelsManager, sourceVC: sourceVC, errorPresenterType: ErrorPresenter.self)
|
AppStorePrivacyNotice.show(from: sourceVC, into: tunnelsManager) {
|
||||||
|
TunnelImporter.importFromFile(url: url, into: tunnelsManager, sourceVC: sourceVC, errorPresenterType: ErrorPresenter.self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ extension ManageTunnelsRootViewController {
|
|||||||
switch event.charactersIgnoringModifiers {
|
switch event.charactersIgnoringModifiers {
|
||||||
case "n":
|
case "n":
|
||||||
tunnelsListVC?.handleAddEmptyTunnelAction()
|
tunnelsListVC?.handleAddEmptyTunnelAction()
|
||||||
case "i":
|
case "o":
|
||||||
tunnelsListVC?.handleImportTunnelAction()
|
tunnelsListVC?.handleImportTunnelAction()
|
||||||
case "t":
|
case "t":
|
||||||
tunnelDetailVC?.handleToggleActiveStatusAction()
|
tunnelDetailVC?.handleToggleActiveStatusAction()
|
||||||
|
|||||||
@@ -263,7 +263,24 @@ class TunnelDetailTableViewController: NSViewController {
|
|||||||
|
|
||||||
func applyTunnelConfiguration(tunnelConfiguration: TunnelConfiguration) {
|
func applyTunnelConfiguration(tunnelConfiguration: TunnelConfiguration) {
|
||||||
// Incorporates changes from tunnelConfiguation. Ignores any changes in peer ordering.
|
// Incorporates changes from tunnelConfiguation. Ignores any changes in peer ordering.
|
||||||
func sectionChanged<T>(fields: [T], modelRowsInSection: inout [(isVisible: Bool, modelRow: TableViewModelRow)], tableView: NSTableView, rowOffset: Int, changes: [T: TunnelViewModel.ChangeHandlers.FieldChange]) {
|
|
||||||
|
let tableView = self.tableView
|
||||||
|
|
||||||
|
func handleSectionFieldsModified<T>(fields: [T], modelRowsInSection: [(isVisible: Bool, modelRow: TableViewModelRow)], rowOffset: Int, changes: [T: TunnelViewModel.Changes.FieldChange]) {
|
||||||
|
var modifiedRowIndices = IndexSet()
|
||||||
|
for (index, field) in fields.enumerated() {
|
||||||
|
guard let change = changes[field] else { continue }
|
||||||
|
if case .modified(_) = change {
|
||||||
|
let row = modelRowsInSection[0 ..< index].filter { $0.isVisible }.count
|
||||||
|
modifiedRowIndices.insert(rowOffset + row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !modifiedRowIndices.isEmpty {
|
||||||
|
tableView.reloadData(forRowIndexes: modifiedRowIndices, columnIndexes: IndexSet(integer: 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSectionFieldsAddedOrRemoved<T>(fields: [T], modelRowsInSection: inout [(isVisible: Bool, modelRow: TableViewModelRow)], rowOffset: Int, changes: [T: TunnelViewModel.Changes.FieldChange]) {
|
||||||
for (index, field) in fields.enumerated() {
|
for (index, field) in fields.enumerated() {
|
||||||
guard let change = changes[field] else { continue }
|
guard let change = changes[field] else { continue }
|
||||||
let row = modelRowsInSection[0 ..< index].filter { $0.isVisible }.count
|
let row = modelRowsInSection[0 ..< index].filter { $0.isVisible }.count
|
||||||
@@ -275,42 +292,54 @@ class TunnelDetailTableViewController: NSViewController {
|
|||||||
tableView.removeRows(at: IndexSet(integer: rowOffset + row), withAnimation: .effectFade)
|
tableView.removeRows(at: IndexSet(integer: rowOffset + row), withAnimation: .effectFade)
|
||||||
modelRowsInSection[index].isVisible = false
|
modelRowsInSection[index].isVisible = false
|
||||||
case .modified:
|
case .modified:
|
||||||
tableView.removeRows(at: IndexSet(integer: rowOffset + row), withAnimation: [])
|
break
|
||||||
tableView.insertRows(at: IndexSet(integer: rowOffset + row), withAnimation: [])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var isChanged = false
|
let changes = self.tunnelViewModel.applyConfiguration(other: tunnelConfiguration)
|
||||||
let changeHandlers = TunnelViewModel.ChangeHandlers(
|
|
||||||
interfaceChanged: { [weak self] changes in
|
if !changes.interfaceChanges.isEmpty {
|
||||||
guard let self = self else { return }
|
handleSectionFieldsModified(fields: TunnelDetailTableViewController.interfaceFields,
|
||||||
sectionChanged(fields: TunnelDetailTableViewController.interfaceFields, modelRowsInSection: &self.tableViewModelRowsBySection[0],
|
modelRowsInSection: self.tableViewModelRowsBySection[0],
|
||||||
tableView: self.tableView, rowOffset: 0, changes: changes)
|
rowOffset: 0, changes: changes.interfaceChanges)
|
||||||
isChanged = true
|
}
|
||||||
},
|
for (peerIndex, peerChanges) in changes.peerChanges {
|
||||||
peerChangedAt: { [weak self] peerIndex, changes in
|
let sectionIndex = 1 + peerIndex
|
||||||
guard let self = self else { return }
|
let rowOffset = self.tableViewModelRowsBySection[0 ..< sectionIndex].flatMap { $0.filter { $0.isVisible } }.count
|
||||||
let sectionIndex = 1 + peerIndex
|
handleSectionFieldsModified(fields: TunnelDetailTableViewController.peerFields,
|
||||||
let rowOffset = self.tableViewModelRowsBySection[0 ..< sectionIndex].flatMap { $0.filter { $0.isVisible } }.count
|
modelRowsInSection: self.tableViewModelRowsBySection[sectionIndex],
|
||||||
sectionChanged(fields: TunnelDetailTableViewController.peerFields, modelRowsInSection: &self.tableViewModelRowsBySection[sectionIndex],
|
rowOffset: rowOffset, changes: peerChanges)
|
||||||
tableView: self.tableView, rowOffset: rowOffset, changes: changes)
|
}
|
||||||
isChanged = true
|
|
||||||
},
|
let isAnyInterfaceFieldAddedOrRemoved = changes.interfaceChanges.contains { $0.value == .added || $0.value == .removed }
|
||||||
peersRemovedAt: { [weak self] peerIndices in
|
let isAnyPeerFieldAddedOrRemoved = changes.peerChanges.contains { $0.changes.contains { $0.value == .added || $0.value == .removed } }
|
||||||
guard let self = self else { return }
|
|
||||||
for peerIndex in peerIndices {
|
if isAnyInterfaceFieldAddedOrRemoved || isAnyPeerFieldAddedOrRemoved || !changes.peersRemovedIndices.isEmpty || !changes.peersInsertedIndices.isEmpty {
|
||||||
|
tableView.beginUpdates()
|
||||||
|
if isAnyInterfaceFieldAddedOrRemoved {
|
||||||
|
handleSectionFieldsAddedOrRemoved(fields: TunnelDetailTableViewController.interfaceFields,
|
||||||
|
modelRowsInSection: &self.tableViewModelRowsBySection[0],
|
||||||
|
rowOffset: 0, changes: changes.interfaceChanges)
|
||||||
|
}
|
||||||
|
if isAnyPeerFieldAddedOrRemoved {
|
||||||
|
for (peerIndex, peerChanges) in changes.peerChanges {
|
||||||
|
let sectionIndex = 1 + peerIndex
|
||||||
|
let rowOffset = self.tableViewModelRowsBySection[0 ..< sectionIndex].flatMap { $0.filter { $0.isVisible } }.count
|
||||||
|
handleSectionFieldsAddedOrRemoved(fields: TunnelDetailTableViewController.peerFields, modelRowsInSection: &self.tableViewModelRowsBySection[sectionIndex], rowOffset: rowOffset, changes: peerChanges)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !changes.peersRemovedIndices.isEmpty {
|
||||||
|
for peerIndex in changes.peersRemovedIndices {
|
||||||
let sectionIndex = 1 + peerIndex
|
let sectionIndex = 1 + peerIndex
|
||||||
let rowOffset = self.tableViewModelRowsBySection[0 ..< sectionIndex].flatMap { $0.filter { $0.isVisible } }.count
|
let rowOffset = self.tableViewModelRowsBySection[0 ..< sectionIndex].flatMap { $0.filter { $0.isVisible } }.count
|
||||||
let count = self.tableViewModelRowsBySection[sectionIndex].filter { $0.isVisible }.count
|
let count = self.tableViewModelRowsBySection[sectionIndex].filter { $0.isVisible }.count
|
||||||
self.tableView.removeRows(at: IndexSet(integersIn: rowOffset ..< rowOffset + count), withAnimation: .effectFade)
|
self.tableView.removeRows(at: IndexSet(integersIn: rowOffset ..< rowOffset + count), withAnimation: .effectFade)
|
||||||
self.tableViewModelRowsBySection.remove(at: sectionIndex)
|
self.tableViewModelRowsBySection.remove(at: sectionIndex)
|
||||||
}
|
}
|
||||||
isChanged = true
|
}
|
||||||
},
|
if !changes.peersInsertedIndices.isEmpty {
|
||||||
peersInsertedAt: { [weak self] peerIndices in
|
for peerIndex in changes.peersInsertedIndices {
|
||||||
guard let self = self else { return }
|
|
||||||
for peerIndex in peerIndices {
|
|
||||||
let peerData = self.tunnelViewModel.peersData[peerIndex]
|
let peerData = self.tunnelViewModel.peersData[peerIndex]
|
||||||
let sectionIndex = 1 + peerIndex
|
let sectionIndex = 1 + peerIndex
|
||||||
let rowOffset = self.tableViewModelRowsBySection[0 ..< sectionIndex].flatMap { $0.filter { $0.isVisible } }.count
|
let rowOffset = self.tableViewModelRowsBySection[0 ..< sectionIndex].flatMap { $0.filter { $0.isVisible } }.count
|
||||||
@@ -322,16 +351,10 @@ class TunnelDetailTableViewController: NSViewController {
|
|||||||
self.tableView.insertRows(at: IndexSet(integersIn: rowOffset ..< rowOffset + count), withAnimation: .effectFade)
|
self.tableView.insertRows(at: IndexSet(integersIn: rowOffset ..< rowOffset + count), withAnimation: .effectFade)
|
||||||
self.tableViewModelRowsBySection.insert(modelRowsInSection, at: sectionIndex)
|
self.tableViewModelRowsBySection.insert(modelRowsInSection, at: sectionIndex)
|
||||||
}
|
}
|
||||||
isChanged = true
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
|
||||||
tableView.beginUpdates()
|
|
||||||
self.tunnelViewModel.applyConfiguration(other: tunnelConfiguration, changeHandlers: changeHandlers)
|
|
||||||
if isChanged {
|
|
||||||
updateTableViewModelRows()
|
updateTableViewModelRows()
|
||||||
|
tableView.endUpdates()
|
||||||
}
|
}
|
||||||
tableView.endUpdates()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func reloadRuntimeConfiguration() {
|
private func reloadRuntimeConfiguration() {
|
||||||
@@ -382,6 +405,8 @@ extension TunnelDetailTableViewController: NSTableViewDelegate {
|
|||||||
cell.key = tr(format: "macFieldKey (%@)", localizedKeyString)
|
cell.key = tr(format: "macFieldKey (%@)", localizedKeyString)
|
||||||
if field == .persistentKeepAlive {
|
if field == .persistentKeepAlive {
|
||||||
cell.value = tr(format: "tunnelPeerPersistentKeepaliveValue (%@)", peerData[field])
|
cell.value = tr(format: "tunnelPeerPersistentKeepaliveValue (%@)", peerData[field])
|
||||||
|
} else if field == .preSharedKey {
|
||||||
|
cell.value = tr("tunnelPeerPresharedKeyEnabled")
|
||||||
} else {
|
} else {
|
||||||
cell.value = peerData[field]
|
cell.value = peerData[field]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -219,14 +219,16 @@ class TunnelEditViewController: NSViewController {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// We're creating a new tunnel
|
// We're creating a new tunnel
|
||||||
tunnelsManager.add(tunnelConfiguration: tunnelConfiguration, activateOnDemandSetting: onDemandSetting) { [weak self] result in
|
AppStorePrivacyNotice.show(from: self, into: tunnelsManager) { [weak self] in
|
||||||
if let error = result.error {
|
self?.tunnelsManager.add(tunnelConfiguration: tunnelConfiguration, activateOnDemandSetting: onDemandSetting) { [weak self] result in
|
||||||
ErrorPresenter.showErrorAlert(error: error, from: self)
|
if let error = result.error {
|
||||||
return
|
ErrorPresenter.showErrorAlert(error: error, from: self)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let tunnel: TunnelContainer = result.value!
|
||||||
|
self?.dismiss(self)
|
||||||
|
self?.delegate?.tunnelSaved(tunnel: tunnel)
|
||||||
}
|
}
|
||||||
let tunnel: TunnelContainer = result.value!
|
|
||||||
self?.dismiss(self)
|
|
||||||
self?.delegate?.tunnelSaved(tunnel: tunnel)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class TunnelsListTableViewController: NSViewController {
|
|||||||
let addMenu: NSMenu = {
|
let addMenu: NSMenu = {
|
||||||
let addMenu = NSMenu(title: "TunnelsListAdd")
|
let addMenu = NSMenu(title: "TunnelsListAdd")
|
||||||
addMenu.addItem(withTitle: tr("macMenuAddEmptyTunnel"), action: #selector(handleAddEmptyTunnelAction), keyEquivalent: "n")
|
addMenu.addItem(withTitle: tr("macMenuAddEmptyTunnel"), action: #selector(handleAddEmptyTunnelAction), keyEquivalent: "n")
|
||||||
addMenu.addItem(withTitle: tr("macMenuImportTunnels"), action: #selector(handleImportTunnelAction), keyEquivalent: "i")
|
addMenu.addItem(withTitle: tr("macMenuImportTunnels"), action: #selector(handleImportTunnelAction), keyEquivalent: "o")
|
||||||
return addMenu
|
return addMenu
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ class ZipArchive {
|
|||||||
static func unarchive(url: URL, requiredFileExtensions: [String]) throws -> [(fileBaseName: String, contents: Data)] {
|
static func unarchive(url: URL, requiredFileExtensions: [String]) throws -> [(fileBaseName: String, contents: Data)] {
|
||||||
|
|
||||||
var results = [(fileBaseName: String, contents: Data)]()
|
var results = [(fileBaseName: String, contents: Data)]()
|
||||||
|
var requiredFileExtensionsLowercased = requiredFileExtensions.map { $0.lowercased() }
|
||||||
|
|
||||||
guard let zipFile = unzOpen64(url.path) else {
|
guard let zipFile = unzOpen64(url.path) else {
|
||||||
throw ZipArchiveError.cantOpenInputZipFile
|
throw ZipArchiveError.cantOpenInputZipFile
|
||||||
@@ -70,7 +71,7 @@ class ZipArchive {
|
|||||||
let isDirectory = (lastChar == "/" || lastChar == "\\")
|
let isDirectory = (lastChar == "/" || lastChar == "\\")
|
||||||
let fileURL = URL(fileURLWithFileSystemRepresentation: fileNameBuffer, isDirectory: isDirectory, relativeTo: nil)
|
let fileURL = URL(fileURLWithFileSystemRepresentation: fileNameBuffer, isDirectory: isDirectory, relativeTo: nil)
|
||||||
|
|
||||||
if !isDirectory && requiredFileExtensions.contains(fileURL.pathExtension) {
|
if !isDirectory && requiredFileExtensionsLowercased.contains(fileURL.pathExtension.lowercased()) {
|
||||||
var unzippedData = Data()
|
var unzippedData = Data()
|
||||||
var bytesRead: Int32 = 0
|
var bytesRead: Int32 = 0
|
||||||
repeat {
|
repeat {
|
||||||
|
|||||||
+1
-1
Submodule wireguard-go updated: c4b43e35a7...f7170e5de2
Reference in New Issue
Block a user