PrivateDataConfirmation: prompt with touch/face/pin/password ID for viewing/exporting keys
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
parent
ed8dc516dc
commit
bebcaa012b
|
@ -41,7 +41,7 @@ extension NETunnelProviderProtocol {
|
||||||
// until finally the app is open. Would it be possible to call saveToPreferences here? Or is
|
// 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
|
// that generally not available to network extensions? In which case, what should our
|
||||||
// behavior be?
|
// behavior be?
|
||||||
|
|
||||||
guard let passwordReference = passwordReference else { return nil }
|
guard let passwordReference = passwordReference else { return nil }
|
||||||
guard let config = Keychain.openReference(called: passwordReference) else { return nil }
|
guard let config = Keychain.openReference(called: passwordReference) else { return nil }
|
||||||
return try? TunnelConfiguration(fromWgQuickConfig: config, called: name)
|
return try? TunnelConfiguration(fromWgQuickConfig: config, called: name)
|
||||||
|
@ -56,7 +56,7 @@ extension NETunnelProviderProtocol {
|
||||||
guard let ref = passwordReference else { return nil }
|
guard let ref = passwordReference else { return nil }
|
||||||
return Keychain.verifyReference(called: ref) ? ref : nil
|
return Keychain.verifyReference(called: ref) ? ref : nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func migrateConfigurationIfNeeded(called name: String) -> Bool {
|
func migrateConfigurationIfNeeded(called name: String) -> Bool {
|
||||||
/* This is how we did things before we switched to putting items
|
/* This is how we did things before we switched to putting items
|
||||||
|
|
|
@ -32,6 +32,8 @@
|
||||||
6B5C5E28220A48D30024272E /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B5C5E26220A48D30024272E /* Keychain.swift */; };
|
6B5C5E28220A48D30024272E /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B5C5E26220A48D30024272E /* Keychain.swift */; };
|
||||||
6B5C5E29220A48D30024272E /* 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 */; };
|
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 */; };
|
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 */; };
|
||||||
6F4DD16B21DA558800690EAE /* TunnelListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16A21DA558800690EAE /* TunnelListRow.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 = "<group>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
6F4DD16A21DA558800690EAE /* TunnelListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelListRow.swift; sourceTree = "<group>"; };
|
||||||
|
@ -450,6 +453,7 @@
|
||||||
6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */,
|
6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */,
|
||||||
6FBA103D21D6B6D70051C35F /* TunnelImporter.swift */,
|
6FBA103D21D6B6D70051C35F /* TunnelImporter.swift */,
|
||||||
6FBA103A21D6B4280051C35F /* ErrorPresenterProtocol.swift */,
|
6FBA103A21D6B4280051C35F /* ErrorPresenterProtocol.swift */,
|
||||||
|
6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */,
|
||||||
);
|
);
|
||||||
path = UI;
|
path = UI;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1119,6 +1123,7 @@
|
||||||
6B707D8621F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift in Sources */,
|
6B707D8621F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift in Sources */,
|
||||||
6FB1BDD921D50F5300A991BF /* LocalizationHelper.swift in Sources */,
|
6FB1BDD921D50F5300A991BF /* LocalizationHelper.swift in Sources */,
|
||||||
6F89E17C21F090CC00C97BB9 /* TunnelsTracker.swift in Sources */,
|
6F89E17C21F090CC00C97BB9 /* TunnelsTracker.swift in Sources */,
|
||||||
|
6B62E460220A6FA900EF34A6 /* PrivateDataConfirmation.swift in Sources */,
|
||||||
6FCD99B121E0EDA900BA4C82 /* TunnelEditViewController.swift in Sources */,
|
6FCD99B121E0EDA900BA4C82 /* TunnelEditViewController.swift in Sources */,
|
||||||
6FB1BDCA21D50F1700A991BF /* x25519.c in Sources */,
|
6FB1BDCA21D50F1700A991BF /* x25519.c in Sources */,
|
||||||
6FB1BDCB21D50F1700A991BF /* Curve25519.swift in Sources */,
|
6FB1BDCB21D50F1700A991BF /* Curve25519.swift in Sources */,
|
||||||
|
@ -1207,6 +1212,7 @@
|
||||||
5F45419821C2D60500994C13 /* KeyValueCell.swift in Sources */,
|
5F45419821C2D60500994C13 /* KeyValueCell.swift in Sources */,
|
||||||
6FBA103E21D6B6D70051C35F /* TunnelImporter.swift in Sources */,
|
6FBA103E21D6B6D70051C35F /* TunnelImporter.swift in Sources */,
|
||||||
6F919EC3218A2AE90023B400 /* ErrorPresenter.swift in Sources */,
|
6F919EC3218A2AE90023B400 /* ErrorPresenter.swift in Sources */,
|
||||||
|
6B62E45F220A6FA900EF34A6 /* PrivateDataConfirmation.swift in Sources */,
|
||||||
6F5A2B4821AFF49A0081EDD8 /* FileManager+Extension.swift in Sources */,
|
6F5A2B4821AFF49A0081EDD8 /* FileManager+Extension.swift in Sources */,
|
||||||
5F45418C21C2D48200994C13 /* TunnelEditKeyValueCell.swift in Sources */,
|
5F45418C21C2D48200994C13 /* TunnelEditKeyValueCell.swift in Sources */,
|
||||||
6FE254FB219C10800028284D /* ZipImporter.swift in Sources */,
|
6FE254FB219C10800028284D /* ZipImporter.swift in Sources */,
|
||||||
|
|
|
@ -316,3 +316,10 @@
|
||||||
|
|
||||||
"macAppVersion (%@)" = "App version: %@";
|
"macAppVersion (%@)" = "App version: %@";
|
||||||
"macGoBackendVersion (%@)" = "Go backend 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.";
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -122,6 +122,8 @@
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</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>
|
<key>com.wireguard.ios.app_group_id</key>
|
||||||
<string>group.$(APP_ID_IOS)</string>
|
<string>group.$(APP_ID_IOS)</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
|
|
@ -86,22 +86,25 @@ class SettingsTableViewController: UITableViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
func exportConfigurationsAsZipFile(sourceView: UIView) {
|
func exportConfigurationsAsZipFile(sourceView: UIView) {
|
||||||
guard let tunnelsManager = tunnelsManager else { return }
|
PrivateDataConfirmation.confirmAccess(to: tr("iosExportPrivateData")) { [weak self] in
|
||||||
guard let destinationDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
|
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")
|
let destinationURL = destinationDir.appendingPathComponent("wireguard-export.zip")
|
||||||
_ = FileManager.deleteFile(at: destinationURL)
|
_ = FileManager.deleteFile(at: destinationURL)
|
||||||
|
|
||||||
let count = tunnelsManager.numberOfTunnels()
|
let count = tunnelsManager.numberOfTunnels()
|
||||||
let tunnelConfigurations = (0 ..< count).compactMap { tunnelsManager.tunnel(at: $0).tunnelConfiguration }
|
let tunnelConfigurations = (0 ..< count).compactMap { tunnelsManager.tunnel(at: $0).tunnelConfiguration }
|
||||||
ZipExporter.exportConfigFiles(tunnelConfigurations: tunnelConfigurations, to: destinationURL) { [weak self] error in
|
ZipExporter.exportConfigFiles(tunnelConfigurations: tunnelConfigurations, to: destinationURL) { [weak self] error in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
ErrorPresenter.showErrorAlert(error: error, from: self)
|
ErrorPresenter.showErrorAlert(error: error, from: self)
|
||||||
return
|
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -103,11 +103,14 @@ class TunnelDetailTableViewController: UITableViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func editTapped() {
|
@objc func editTapped() {
|
||||||
let editVC = TunnelEditTableViewController(tunnelsManager: tunnelsManager, tunnel: tunnel)
|
PrivateDataConfirmation.confirmAccess(to: tr("iosViewPrivateData")) { [weak self] in
|
||||||
editVC.delegate = self
|
guard let self = self else { return }
|
||||||
let editNC = UINavigationController(rootViewController: editVC)
|
let editVC = TunnelEditTableViewController(tunnelsManager: self.tunnelsManager, tunnel: self.tunnel)
|
||||||
editNC.modalPresentationStyle = .formSheet
|
editVC.delegate = self
|
||||||
present(editNC, animated: true)
|
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)) {
|
func showConfirmationAlert(message: String, buttonTitle: String, from sourceView: UIView, onConfirmed: @escaping (() -> Void)) {
|
||||||
|
|
|
@ -227,10 +227,13 @@ class TunnelDetailTableViewController: NSViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func handleEditTunnelAction() {
|
@objc func handleEditTunnelAction() {
|
||||||
let tunnelEditVC = TunnelEditViewController(tunnelsManager: tunnelsManager, tunnel: tunnel)
|
PrivateDataConfirmation.confirmAccess(to: tr("macViewPrivateData")) { [weak self] in
|
||||||
tunnelEditVC.delegate = self
|
guard let self = self else { return }
|
||||||
presentAsSheet(tunnelEditVC)
|
let tunnelEditVC = TunnelEditViewController(tunnelsManager: self.tunnelsManager, tunnel: self.tunnel)
|
||||||
self.tunnelEditVC = tunnelEditVC
|
tunnelEditVC.delegate = self
|
||||||
|
self.presentAsSheet(tunnelEditVC)
|
||||||
|
self.tunnelEditVC = tunnelEditVC
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func handleToggleActiveStatusAction() {
|
@objc func handleToggleActiveStatusAction() {
|
||||||
|
|
|
@ -207,22 +207,26 @@ class TunnelsListTableViewController: NSViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func handleExportTunnelsAction() {
|
@objc func handleExportTunnelsAction() {
|
||||||
guard let window = view.window else { return }
|
PrivateDataConfirmation.confirmAccess(to: tr("macExportPrivateData")) { [weak self] in
|
||||||
let savePanel = NSSavePanel()
|
guard let self = self else { return }
|
||||||
savePanel.allowedFileTypes = ["zip"]
|
guard let window = self.view.window else { return }
|
||||||
savePanel.prompt = tr("macSheetButtonExportZip")
|
let savePanel = NSSavePanel()
|
||||||
savePanel.nameFieldLabel = tr("macNameFieldExportZip")
|
savePanel.allowedFileTypes = ["zip"]
|
||||||
savePanel.nameFieldStringValue = "wireguard-export.zip"
|
savePanel.prompt = tr("macSheetButtonExportZip")
|
||||||
savePanel.beginSheetModal(for: window) { [weak tunnelsManager] response in
|
savePanel.nameFieldLabel = tr("macNameFieldExportZip")
|
||||||
guard let tunnelsManager = tunnelsManager else { return }
|
savePanel.nameFieldStringValue = "wireguard-export.zip"
|
||||||
guard response == .OK else { return }
|
let tunnelsManager = self.tunnelsManager
|
||||||
guard let destinationURL = savePanel.url else { return }
|
savePanel.beginSheetModal(for: window) { [weak tunnelsManager] response in
|
||||||
let count = tunnelsManager.numberOfTunnels()
|
guard let tunnelsManager = tunnelsManager else { return }
|
||||||
let tunnelConfigurations = (0 ..< count).compactMap { tunnelsManager.tunnel(at: $0).tunnelConfiguration }
|
guard response == .OK else { return }
|
||||||
ZipExporter.exportConfigFiles(tunnelConfigurations: tunnelConfigurations, to: destinationURL) { [weak self] error in
|
guard let destinationURL = savePanel.url else { return }
|
||||||
if let error = error {
|
let count = tunnelsManager.numberOfTunnels()
|
||||||
ErrorPresenter.showErrorAlert(error: error, from: self)
|
let tunnelConfigurations = (0 ..< count).compactMap { tunnelsManager.tunnel(at: $0).tunnelConfiguration }
|
||||||
return
|
ZipExporter.exportConfigFiles(tunnelConfigurations: tunnelConfigurations, to: destinationURL) { [weak self] error in
|
||||||
|
if let error = error {
|
||||||
|
ErrorPresenter.showErrorAlert(error: error, from: self)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue