From bebcaa012bfafb5ab1a8ee4db11feffd3843c52c Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Wed, 6 Feb 2019 03:23:51 +0100 Subject: PrivateDataConfirmation: prompt with touch/face/pin/password ID for viewing/exporting keys Signed-off-by: Jason A. Donenfeld --- .../Model/NETunnelProviderProtocol+Extension.swift | 4 +-- WireGuard/WireGuard.xcodeproj/project.pbxproj | 6 ++++ WireGuard/WireGuard/Base.lproj/Localizable.strings | 7 ++++ .../WireGuard/UI/PrivateDataConfirmation.swift | 37 ++++++++++++++++++++++ WireGuard/WireGuard/UI/iOS/Info.plist | 2 ++ .../SettingsTableViewController.swift | 31 ++++++++++-------- .../TunnelDetailTableViewController.swift | 13 +++++--- .../TunnelDetailTableViewController.swift | 11 ++++--- .../TunnelsListTableViewController.swift | 36 +++++++++++---------- 9 files changed, 106 insertions(+), 41 deletions(-) create mode 100644 WireGuard/WireGuard/UI/PrivateDataConfirmation.swift diff --git a/WireGuard/Shared/Model/NETunnelProviderProtocol+Extension.swift b/WireGuard/Shared/Model/NETunnelProviderProtocol+Extension.swift index bdc17ac..7345794 100644 --- a/WireGuard/Shared/Model/NETunnelProviderProtocol+Extension.swift +++ b/WireGuard/Shared/Model/NETunnelProviderProtocol+Extension.swift @@ -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 diff --git a/WireGuard/WireGuard.xcodeproj/project.pbxproj b/WireGuard/WireGuard.xcodeproj/project.pbxproj index 0f2b31b..2d3dc9a 100644 --- a/WireGuard/WireGuard.xcodeproj/project.pbxproj +++ b/WireGuard/WireGuard.xcodeproj/project.pbxproj @@ -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 */; }; @@ -238,6 +240,7 @@ 5FF7B96121CC95DE00A7DD74 /* InterfaceConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterfaceConfiguration.swift; sourceTree = ""; }; 5FF7B96421CC95FA00A7DD74 /* PeerConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerConfiguration.swift; sourceTree = ""; }; 6B5C5E26220A48D30024272E /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = ""; }; + 6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateDataConfirmation.swift; sourceTree = ""; }; 6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelConfiguration+UapiConfig.swift"; sourceTree = ""; }; 6F4DD16721DA552B00690EAE /* NSTableView+Reuse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTableView+Reuse.swift"; sourceTree = ""; }; 6F4DD16A21DA558800690EAE /* TunnelListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelListRow.swift; sourceTree = ""; }; @@ -450,6 +453,7 @@ 6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */, 6FBA103D21D6B6D70051C35F /* TunnelImporter.swift */, 6FBA103A21D6B4280051C35F /* ErrorPresenterProtocol.swift */, + 6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */, ); path = UI; sourceTree = ""; @@ -1119,6 +1123,7 @@ 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 */, @@ -1207,6 +1212,7 @@ 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 */, diff --git a/WireGuard/WireGuard/Base.lproj/Localizable.strings b/WireGuard/WireGuard/Base.lproj/Localizable.strings index c93c702..25d7739 100644 --- a/WireGuard/WireGuard/Base.lproj/Localizable.strings +++ b/WireGuard/WireGuard/Base.lproj/Localizable.strings @@ -316,3 +316,10 @@ "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 index 0000000..c03e64a --- /dev/null +++ b/WireGuard/WireGuard/UI/PrivateDataConfirmation.swift @@ -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() + } + } + } + } +} diff --git a/WireGuard/WireGuard/UI/iOS/Info.plist b/WireGuard/WireGuard/UI/iOS/Info.plist index 2a943eb..0c2d8b0 100644 --- a/WireGuard/WireGuard/UI/iOS/Info.plist +++ b/WireGuard/WireGuard/UI/iOS/Info.plist @@ -122,6 +122,8 @@ + NSFaceIDUsageDescription + Face ID is used for authenticating viewing and exporting of private keys com.wireguard.ios.app_group_id group.$(APP_ID_IOS) diff --git a/WireGuard/WireGuard/UI/iOS/ViewController/SettingsTableViewController.swift b/WireGuard/WireGuard/UI/iOS/ViewController/SettingsTableViewController.swift index 64cd0f7..3addea4 100644 --- a/WireGuard/WireGuard/UI/iOS/ViewController/SettingsTableViewController.swift +++ b/WireGuard/WireGuard/UI/iOS/ViewController/SettingsTableViewController.swift @@ -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) } } diff --git a/WireGuard/WireGuard/UI/iOS/ViewController/TunnelDetailTableViewController.swift b/WireGuard/WireGuard/UI/iOS/ViewController/TunnelDetailTableViewController.swift index 955bf91..f65ca23 100644 --- a/WireGuard/WireGuard/UI/iOS/ViewController/TunnelDetailTableViewController.swift +++ b/WireGuard/WireGuard/UI/iOS/ViewController/TunnelDetailTableViewController.swift @@ -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)) { diff --git a/WireGuard/WireGuard/UI/macOS/ViewController/TunnelDetailTableViewController.swift b/WireGuard/WireGuard/UI/macOS/ViewController/TunnelDetailTableViewController.swift index 89dcbe8..d7dcb5f 100644 --- a/WireGuard/WireGuard/UI/macOS/ViewController/TunnelDetailTableViewController.swift +++ b/WireGuard/WireGuard/UI/macOS/ViewController/TunnelDetailTableViewController.swift @@ -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() { diff --git a/WireGuard/WireGuard/UI/macOS/ViewController/TunnelsListTableViewController.swift b/WireGuard/WireGuard/UI/macOS/ViewController/TunnelsListTableViewController.swift index e83e616..cfeb8f1 100644 --- a/WireGuard/WireGuard/UI/macOS/ViewController/TunnelsListTableViewController.swift +++ b/WireGuard/WireGuard/UI/macOS/ViewController/TunnelsListTableViewController.swift @@ -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 + } } } } -- cgit v1.2.3-59-g8ed1b