]> git.ipfire.org Git - thirdparty/wireguard-apple.git/commitdiff
PrivateDataConfirmation: prompt with touch/face/pin/password ID for viewing/exporting...
authorJason A. Donenfeld <Jason@zx2c4.com>
Wed, 6 Feb 2019 02:23:51 +0000 (03:23 +0100)
committerJason A. Donenfeld <Jason@zx2c4.com>
Wed, 6 Feb 2019 05:20:23 +0000 (06:20 +0100)
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
WireGuard/Shared/Model/NETunnelProviderProtocol+Extension.swift
WireGuard/WireGuard.xcodeproj/project.pbxproj
WireGuard/WireGuard/Base.lproj/Localizable.strings
WireGuard/WireGuard/UI/PrivateDataConfirmation.swift [new file with mode: 0644]
WireGuard/WireGuard/UI/iOS/Info.plist
WireGuard/WireGuard/UI/iOS/ViewController/SettingsTableViewController.swift
WireGuard/WireGuard/UI/iOS/ViewController/TunnelDetailTableViewController.swift
WireGuard/WireGuard/UI/macOS/ViewController/TunnelDetailTableViewController.swift
WireGuard/WireGuard/UI/macOS/ViewController/TunnelsListTableViewController.swift

index bdc17ac0ac939122d2deba3d695649f838bd4655..73457940152de9536a3e1d50ae9b403330e5a4d9 100644 (file)
@@ -41,7 +41,7 @@ extension NETunnelProviderProtocol {
         // until finally the app is open. Would it be possible to call saveToPreferences here? Or is
         // that generally not available to network extensions? In which case, what should our
         // behavior be?
-        
+
         guard let passwordReference = passwordReference else { return nil }
         guard let config = Keychain.openReference(called: passwordReference) else { return nil }
         return try? TunnelConfiguration(fromWgQuickConfig: config, called: name)
@@ -56,7 +56,7 @@ extension NETunnelProviderProtocol {
         guard let ref = passwordReference else { return nil }
         return Keychain.verifyReference(called: ref) ? ref : nil
     }
-    
+
     @discardableResult
     func migrateConfigurationIfNeeded(called name: String) -> Bool {
         /* This is how we did things before we switched to putting items
index 0f2b31b13ae43dcb7f287f415b69665b64b773a0..2d3dc9a56c4d630e1b2fe459649c9db6bcbc7c0c 100644 (file)
@@ -32,6 +32,8 @@
                6B5C5E28220A48D30024272E /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B5C5E26220A48D30024272E /* Keychain.swift */; };
                6B5C5E29220A48D30024272E /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B5C5E26220A48D30024272E /* Keychain.swift */; };
                6B5C5E2A220A48D30024272E /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B5C5E26220A48D30024272E /* Keychain.swift */; };
+               6B62E45F220A6FA900EF34A6 /* PrivateDataConfirmation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */; };
+               6B62E460220A6FA900EF34A6 /* PrivateDataConfirmation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.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 */; };
                6F4DD16B21DA558800690EAE /* TunnelListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16A21DA558800690EAE /* TunnelListRow.swift */; };
                5FF7B96121CC95DE00A7DD74 /* InterfaceConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterfaceConfiguration.swift; sourceTree = "<group>"; };
                5FF7B96421CC95FA00A7DD74 /* PeerConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerConfiguration.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>"; };
                6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelConfiguration+UapiConfig.swift"; sourceTree = "<group>"; };
                6F4DD16721DA552B00690EAE /* NSTableView+Reuse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTableView+Reuse.swift"; sourceTree = "<group>"; };
                6F4DD16A21DA558800690EAE /* TunnelListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelListRow.swift; sourceTree = "<group>"; };
                                6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */,
                                6FBA103D21D6B6D70051C35F /* TunnelImporter.swift */,
                                6FBA103A21D6B4280051C35F /* ErrorPresenterProtocol.swift */,
+                               6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */,
                        );
                        path = UI;
                        sourceTree = "<group>";
                                6B707D8621F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift in Sources */,
                                6FB1BDD921D50F5300A991BF /* LocalizationHelper.swift in Sources */,
                                6F89E17C21F090CC00C97BB9 /* TunnelsTracker.swift in Sources */,
+                               6B62E460220A6FA900EF34A6 /* PrivateDataConfirmation.swift in Sources */,
                                6FCD99B121E0EDA900BA4C82 /* TunnelEditViewController.swift in Sources */,
                                6FB1BDCA21D50F1700A991BF /* x25519.c in Sources */,
                                6FB1BDCB21D50F1700A991BF /* Curve25519.swift in Sources */,
                                5F45419821C2D60500994C13 /* KeyValueCell.swift in Sources */,
                                6FBA103E21D6B6D70051C35F /* TunnelImporter.swift in Sources */,
                                6F919EC3218A2AE90023B400 /* ErrorPresenter.swift in Sources */,
+                               6B62E45F220A6FA900EF34A6 /* PrivateDataConfirmation.swift in Sources */,
                                6F5A2B4821AFF49A0081EDD8 /* FileManager+Extension.swift in Sources */,
                                5F45418C21C2D48200994C13 /* TunnelEditKeyValueCell.swift in Sources */,
                                6FE254FB219C10800028284D /* ZipImporter.swift in Sources */,
index c93c702f76684c502dee2c9603f05c66ac6d4be1..25d773936a5ee95b073dc9b2f40fde7ed5e535b9 100644 (file)
 
 "macAppVersion (%@)" = "App version: %@";
 "macGoBackendVersion (%@)" = "Go backend version: %@";
+
+// Privacy
+
+"macExportPrivateData" = "export tunnel private keys";
+"macViewPrivateData" = "view tunnel private keys";
+"iosExportPrivateData" = "Authenticate to export tunnel private keys.";
+"iosViewPrivateData" = "Authenticate to view tunnel private keys.";
diff --git a/WireGuard/WireGuard/UI/PrivateDataConfirmation.swift b/WireGuard/WireGuard/UI/PrivateDataConfirmation.swift
new file mode 100644 (file)
index 0000000..c03e64a
--- /dev/null
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
+
+import Foundation
+import LocalAuthentication
+#if os(macOS)
+import AppKit
+#endif
+
+class PrivateDataConfirmation {
+    static func confirmAccess(to reason: String, _ after: @escaping () -> Void) {
+        let context = LAContext()
+
+        var error: NSError?
+        if !context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
+            guard let error = error as? LAError else { return }
+            if error.code == .passcodeNotSet {
+                // We give no protection to folks who just don't set a passcode.
+                after()
+            }
+            return
+        }
+
+        context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { success, _ in
+            DispatchQueue.main.async {
+                #if os(macOS)
+                if !NSApp.isActive {
+                    NSApp.activate(ignoringOtherApps: true)
+                }
+                #endif
+                if success {
+                    after()
+                }
+            }
+        }
+    }
+}
index 2a943eb0f1a821a827261ecf8f60e12aae0a1eae..0c2d8b062e4550737aacb12481900bcfdc687e5f 100644 (file)
                        </dict>
                </dict>
        </array>
+       <key>NSFaceIDUsageDescription</key>
+       <string>Face ID is used for authenticating viewing and exporting of private keys</string>
        <key>com.wireguard.ios.app_group_id</key>
        <string>group.$(APP_ID_IOS)</string>
 </dict>
index 64cd0f7dfe989530a5335e2e481d8e55d342c241..3addea46b37b2c66af3ad6ea8ed5955f964d2e47 100644 (file)
@@ -86,22 +86,25 @@ class SettingsTableViewController: UITableViewController {
     }
 
     func exportConfigurationsAsZipFile(sourceView: UIView) {
-        guard let tunnelsManager = tunnelsManager else { return }
-        guard let destinationDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
-
-        let destinationURL = destinationDir.appendingPathComponent("wireguard-export.zip")
-        _ = FileManager.deleteFile(at: destinationURL)
+        PrivateDataConfirmation.confirmAccess(to: tr("iosExportPrivateData")) { [weak self] in
+            guard let self = self else { return }
+            guard let tunnelsManager = self.tunnelsManager else { return }
+            guard let destinationDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
+
+            let destinationURL = destinationDir.appendingPathComponent("wireguard-export.zip")
+            _ = FileManager.deleteFile(at: destinationURL)
+
+            let count = tunnelsManager.numberOfTunnels()
+            let tunnelConfigurations = (0 ..< count).compactMap { tunnelsManager.tunnel(at: $0).tunnelConfiguration }
+            ZipExporter.exportConfigFiles(tunnelConfigurations: tunnelConfigurations, to: destinationURL) { [weak self] error in
+                if let error = error {
+                    ErrorPresenter.showErrorAlert(error: error, from: self)
+                    return
+                }
 
-        let count = tunnelsManager.numberOfTunnels()
-        let tunnelConfigurations = (0 ..< count).compactMap { tunnelsManager.tunnel(at: $0).tunnelConfiguration }
-        ZipExporter.exportConfigFiles(tunnelConfigurations: tunnelConfigurations, to: destinationURL) { [weak self] error in
-            if let error = error {
-                ErrorPresenter.showErrorAlert(error: error, from: self)
-                return
+                let fileExportVC = UIDocumentPickerViewController(url: destinationURL, in: .exportToService)
+                self?.present(fileExportVC, animated: true, completion: nil)
             }
-
-            let fileExportVC = UIDocumentPickerViewController(url: destinationURL, in: .exportToService)
-            self?.present(fileExportVC, animated: true, completion: nil)
         }
     }
 
index 955bf91e09e2b2bd7dbdda71c0b85c8241804ae0..f65ca2374313b22629a11eabdbfdc55fbbd31140 100644 (file)
@@ -103,11 +103,14 @@ class TunnelDetailTableViewController: UITableViewController {
     }
 
     @objc func editTapped() {
-        let editVC = TunnelEditTableViewController(tunnelsManager: tunnelsManager, tunnel: tunnel)
-        editVC.delegate = self
-        let editNC = UINavigationController(rootViewController: editVC)
-        editNC.modalPresentationStyle = .formSheet
-        present(editNC, animated: true)
+        PrivateDataConfirmation.confirmAccess(to: tr("iosViewPrivateData")) { [weak self] in
+            guard let self = self else { return }
+            let editVC = TunnelEditTableViewController(tunnelsManager: self.tunnelsManager, tunnel: self.tunnel)
+            editVC.delegate = self
+            let editNC = UINavigationController(rootViewController: editVC)
+            editNC.modalPresentationStyle = .formSheet
+            self.present(editNC, animated: true)
+        }
     }
 
     func showConfirmationAlert(message: String, buttonTitle: String, from sourceView: UIView, onConfirmed: @escaping (() -> Void)) {
index 89dcbe8c8339b953681cb3c08d60fe02cbb6b3e8..d7dcb5ffde01a61d187e1a3e1cb59ffb38af848d 100644 (file)
@@ -227,10 +227,13 @@ class TunnelDetailTableViewController: NSViewController {
     }
 
     @objc func handleEditTunnelAction() {
-        let tunnelEditVC = TunnelEditViewController(tunnelsManager: tunnelsManager, tunnel: tunnel)
-        tunnelEditVC.delegate = self
-        presentAsSheet(tunnelEditVC)
-        self.tunnelEditVC = tunnelEditVC
+        PrivateDataConfirmation.confirmAccess(to: tr("macViewPrivateData")) { [weak self] in
+            guard let self = self else { return }
+            let tunnelEditVC = TunnelEditViewController(tunnelsManager: self.tunnelsManager, tunnel: self.tunnel)
+            tunnelEditVC.delegate = self
+            self.presentAsSheet(tunnelEditVC)
+            self.tunnelEditVC = tunnelEditVC
+        }
     }
 
     @objc func handleToggleActiveStatusAction() {
index e83e6165defd60fe85da657ca6fd340949933a69..cfeb8f1f8fd5c335fe8e6bea97bea4fb61219fb6 100644 (file)
@@ -207,22 +207,26 @@ class TunnelsListTableViewController: NSViewController {
     }
 
     @objc func handleExportTunnelsAction() {
-        guard let window = view.window else { return }
-        let savePanel = NSSavePanel()
-        savePanel.allowedFileTypes = ["zip"]
-        savePanel.prompt = tr("macSheetButtonExportZip")
-        savePanel.nameFieldLabel = tr("macNameFieldExportZip")
-        savePanel.nameFieldStringValue = "wireguard-export.zip"
-        savePanel.beginSheetModal(for: window) { [weak tunnelsManager] response in
-            guard let tunnelsManager = tunnelsManager else { return }
-            guard response == .OK else { return }
-            guard let destinationURL = savePanel.url else { return }
-            let count = tunnelsManager.numberOfTunnels()
-            let tunnelConfigurations = (0 ..< count).compactMap { tunnelsManager.tunnel(at: $0).tunnelConfiguration }
-            ZipExporter.exportConfigFiles(tunnelConfigurations: tunnelConfigurations, to: destinationURL) { [weak self] error in
-                if let error = error {
-                    ErrorPresenter.showErrorAlert(error: error, from: self)
-                    return
+        PrivateDataConfirmation.confirmAccess(to: tr("macExportPrivateData")) { [weak self] in
+            guard let self = self else { return }
+            guard let window = self.view.window else { return }
+            let savePanel = NSSavePanel()
+            savePanel.allowedFileTypes = ["zip"]
+            savePanel.prompt = tr("macSheetButtonExportZip")
+            savePanel.nameFieldLabel = tr("macNameFieldExportZip")
+            savePanel.nameFieldStringValue = "wireguard-export.zip"
+            let tunnelsManager = self.tunnelsManager
+            savePanel.beginSheetModal(for: window) { [weak tunnelsManager] response in
+                guard let tunnelsManager = tunnelsManager else { return }
+                guard response == .OK else { return }
+                guard let destinationURL = savePanel.url else { return }
+                let count = tunnelsManager.numberOfTunnels()
+                let tunnelConfigurations = (0 ..< count).compactMap { tunnelsManager.tunnel(at: $0).tunnelConfiguration }
+                ZipExporter.exportConfigFiles(tunnelConfigurations: tunnelConfigurations, to: destinationURL) { [weak self] error in
+                    if let error = error {
+                        ErrorPresenter.showErrorAlert(error: error, from: self)
+                        return
+                    }
                 }
             }
         }