From 530b733037fbddab2eeb9a75d02c32179de7036e Mon Sep 17 00:00:00 2001 From: Jens Utbult Date: Tue, 5 Nov 2024 13:56:50 +0100 Subject: [PATCH 1/2] Fixes a NFC issue after a FIDO reset over Lightning and various UI tweaks in the configuration sub views. --- Authenticator/Localizable.xcstrings | 89 +++++++++++++++++++ .../Model/ConfigurationViewModel.swift | 17 +++- Authenticator/Model/Connection.swift | 13 +-- .../ConfigurationView.swift | 5 +- .../UI/YubiKeyConfiguration/FIDOPINView.swift | 4 +- .../YubiKeyConfiguration/FIDOResetView.swift | 21 +++-- .../OATHPasswordView.swift | 4 +- .../YubiKeyConfiguration/OATHResetView.swift | 17 ++-- .../OATHSavedPasswordsView.swift | 12 ++- 9 files changed, 154 insertions(+), 28 deletions(-) diff --git a/Authenticator/Localizable.xcstrings b/Authenticator/Localizable.xcstrings index 5b9cb901..8d9c958f 100644 --- a/Authenticator/Localizable.xcstrings +++ b/Authenticator/Localizable.xcstrings @@ -552,6 +552,7 @@ } }, "Confirm OATH reset" : { + "extractionState" : "stale", "localizations" : { "fr" : { "stringUnit" : { @@ -1048,6 +1049,7 @@ } }, "FIDO factory reset" : { + "extractionState" : "stale", "localizations" : { "fr" : { "stringUnit" : { @@ -1113,6 +1115,7 @@ } }, "FIDO PIN protection" : { + "extractionState" : "stale", "localizations" : { "fr" : { "stringUnit" : { @@ -1129,6 +1132,7 @@ } }, "FIDO reset" : { + "extractionState" : "stale", "localizations" : { "fr" : { "stringUnit" : { @@ -1178,6 +1182,7 @@ } }, "For additional security and to prevent unauthorized access the FIDO application can be protected by a PIN." : { + "extractionState" : "stale", "localizations" : { "fr" : { "stringUnit" : { @@ -1194,6 +1199,7 @@ } }, "For additional security and to prevent unauthorized access the YubiKey can be password protected." : { + "extractionState" : "stale", "localizations" : { "fr" : { "stringUnit" : { @@ -1708,7 +1714,24 @@ } } }, + "OATH password" : { + "localizations" : { + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mots de passe OATH" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "OATHパスワード" + } + } + } + }, "OATH password protection" : { + "extractionState" : "stale", "localizations" : { "fr" : { "stringUnit" : { @@ -2453,6 +2476,22 @@ } } }, + "Reset FIDO" : { + "localizations" : { + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Réinitialiser FIDO" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "FIDOのリセット" + } + } + } + }, "Reset FIDO application" : { "comment" : "FIDO reset NFC confirmation message", "localizations" : { @@ -2587,6 +2626,7 @@ } }, "Saved passwords has been cleared" : { + "extractionState" : "stale", "localizations" : { "fr" : { "stringUnit" : { @@ -3121,6 +3161,22 @@ } } }, + "This will irrevocably delete all OATH TOTP/HOTP accounts from your YubiKey." : { + "localizations" : { + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cela supprimera définitivement tous les comptes OATH TOTP/HOTP de votre YubiKey." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "これにより、すべてのOATH TOTP/HOTPアカウントがYubiKeyから削除されます。削除は取り消すことができません。" + } + } + } + }, "This will irrevocably delete all U2F and FIDO2 accounts, including passkeys, from your YubiKey." : { "localizations" : { "fr" : { @@ -3169,6 +3225,38 @@ } } }, + "To prevent unauthorized access the FIDO application can be protected by a PIN." : { + "localizations" : { + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pour éviter tout accès non autorisé, l'application FIDO peut être protégée par un code PIN." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "不正アクセスを防ぐため、FIDOアプリケーションはPINで保護することができる。" + } + } + } + }, + "To prevent unauthorized access the OATH application can be protected by a password." : { + "localizations" : { + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pour empêcher tout accès non autorisé, l'application OATH peut être protégée par un mot de passe." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "不正アクセスを防ぐため、OATHアプリケーションはパスワードで保護することができます。" + } + } + } + }, "To prevent unauthorized access this YubiKey is protected with a password." : { "comment" : "OATH password entry enter password", "localizations" : { @@ -3604,6 +3692,7 @@ } }, "YubiKey has been reset" : { + "extractionState" : "stale", "localizations" : { "fr" : { "stringUnit" : { diff --git a/Authenticator/Model/ConfigurationViewModel.swift b/Authenticator/Model/ConfigurationViewModel.swift index 14654891..81d2e143 100644 --- a/Authenticator/Model/ConfigurationViewModel.swift +++ b/Authenticator/Model/ConfigurationViewModel.swift @@ -21,7 +21,14 @@ class ConfigurationViewModel: ObservableObject { let connection = Connection() - @Published var deviceInfo: YKFManagementDeviceInfo? + @Published var deviceInfo: YKFManagementDeviceInfo? { + didSet { + resetDeviceInfoTask?.cancel() + resetDeviceInfoTask = nil + } + } + + var resetDeviceInfoTask: Task? init() { Logger.allocation.debug("ConfigurationViewModel: init") @@ -52,7 +59,13 @@ class ConfigurationViewModel: ObservableObject { } func start() { - deviceInfo = nil + resetDeviceInfoTask = Task.detached { + try? await Task.sleep(for: .seconds(1)) + guard !Task.isCancelled else { return } + Task.detached { @MainActor in + self.deviceInfo = nil + } + } waitForConnection() } diff --git a/Authenticator/Model/Connection.swift b/Authenticator/Model/Connection.swift index 6318d09b..bed6942e 100644 --- a/Authenticator/Model/Connection.swift +++ b/Authenticator/Model/Connection.swift @@ -94,11 +94,14 @@ class Connection: NSObject { smartCardConnection?.stop() accessoryConnection?.stop() nfcConnection?.stop() - if YubiKitDeviceCapabilities.supportsMFIAccessoryKey { - YubiKitManager.shared.startAccessoryConnection() - } - if YubiKitDeviceCapabilities.supportsSmartCardOverUSBC { - YubiKitManager.shared.startSmartCardConnection() + // stop() returns immediately but closing the connection will take a few cycles so we need to wait to make sure it's closed before restarting. + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + if YubiKitDeviceCapabilities.supportsMFIAccessoryKey { + YubiKitManager.shared.startAccessoryConnection() + } + if YubiKitDeviceCapabilities.supportsSmartCardOverUSBC { + YubiKitManager.shared.startSmartCardConnection() + } } } diff --git a/Authenticator/UI/YubiKeyConfiguration/ConfigurationView.swift b/Authenticator/UI/YubiKeyConfiguration/ConfigurationView.swift index 38989017..6863e3fb 100644 --- a/Authenticator/UI/YubiKeyConfiguration/ConfigurationView.swift +++ b/Authenticator/UI/YubiKeyConfiguration/ConfigurationView.swift @@ -150,10 +150,11 @@ struct ConfigurationView: View { Text("Manage PIN") } NavigationLink { - FIDOResetView() - .onDisappear { + FIDOResetView { + Task.detached { @MainActor in model.start() } + } } label: { ListIconView(image: Image(systemName: "trash"), color: Color(.systemRed), padding: 5) Text("Reset FIDO application") diff --git a/Authenticator/UI/YubiKeyConfiguration/FIDOPINView.swift b/Authenticator/UI/YubiKeyConfiguration/FIDOPINView.swift index 193550a7..13dc6239 100644 --- a/Authenticator/UI/YubiKeyConfiguration/FIDOPINView.swift +++ b/Authenticator/UI/YubiKeyConfiguration/FIDOPINView.swift @@ -28,9 +28,9 @@ struct FIDOPINView: View { var body: some View { SettingsView(image: Image(systemName: "lock.shield"), imageColor: Color(.systemPurple)) { - Text("FIDO PIN protection").font(.title2).bold() + Text("FIDO PIN").font(.title2).bold() - Text("For additional security and to prevent unauthorized access the FIDO application can be protected by a PIN.") + Text("To prevent unauthorized access the FIDO application can be protected by a PIN.") .font(.subheadline) .multilineTextAlignment(.center) diff --git a/Authenticator/UI/YubiKeyConfiguration/FIDOResetView.swift b/Authenticator/UI/YubiKeyConfiguration/FIDOResetView.swift index 6abc4fb6..f55189e6 100644 --- a/Authenticator/UI/YubiKeyConfiguration/FIDOResetView.swift +++ b/Authenticator/UI/YubiKeyConfiguration/FIDOResetView.swift @@ -20,6 +20,7 @@ fileprivate let resetMessageText = String(localized: "Your credentials, as well struct FIDOResetView: View { + var completion: (() -> Void) @Environment(\.dismiss) private var dismiss @StateObject var model = FIDOResetViewModel() @@ -29,20 +30,22 @@ struct FIDOResetView: View { @State var messageText = resetMessageText @State var enableResetButton = true @State var errorMessage: String? = nil - @State var opacity = 1.0 + @State var keyHasBeenReset = false + @State var image = Image(systemName: "exclamationmark.triangle") + @State var imageColor = Color(.systemRed) var body: some View { - SettingsView(image: Image(systemName: "exclamationmark.triangle"), imageColor: Color(.systemRed)) { - Text("FIDO factory reset").font(.title2).bold().opacity(opacity) - ProgressView(value: progress, total: 4.0).opacity(opacity) - Text(messageText).font(.subheadline).multilineTextAlignment(.center) + SettingsView(image: image, imageColor: imageColor) { + Text("Reset FIDO application").font(.title2).bold().opacity(keyHasBeenReset ? 0.2 : 1.0) + ProgressView(value: progress, total: 4.0).opacity(keyHasBeenReset ? 0.2 : 1.0) + Text(messageText).font(.subheadline).multilineTextAlignment(.center).opacity(keyHasBeenReset ? 0.2 : 1.0) } buttons: { SettingsButton("Reset FIDO") { presentConfirmAlert.toggle() } .disabled(!enableResetButton) } - .navigationBarTitle(Text("FIDO reset"), displayMode: .inline) + .navigationBarTitle(Text("Reset FIDO"), displayMode: .inline) .alert("Warning!", isPresented: $presentConfirmAlert, presenting: model, actions: { model in Button(role: .destructive) { presentConfirmAlert.toggle() @@ -75,8 +78,8 @@ struct FIDOResetView: View { updateState() } .onDisappear { - print("onDisappear") model.cancelReset() + completion() } } @@ -100,9 +103,11 @@ struct FIDOResetView: View { self.messageText = String(localized: "Touch the button on the YubiKey now.", comment: "FIDO reset view") case .success: self.progress = 4.0 - self.opacity = 0.5 + self.keyHasBeenReset = true self.enableResetButton = false self.messageText = String(localized: "The FIDO application of your YubiKey has been reset to factory defaults.", comment: "FIDO reset view") + self.image = Image(systemName: "checkmark.circle") + self.imageColor = Color(.systemGreen) case .error(let error): self.enableResetButton = true self.presentErrorAlert = true diff --git a/Authenticator/UI/YubiKeyConfiguration/OATHPasswordView.swift b/Authenticator/UI/YubiKeyConfiguration/OATHPasswordView.swift index 112f0427..951abdbd 100644 --- a/Authenticator/UI/YubiKeyConfiguration/OATHPasswordView.swift +++ b/Authenticator/UI/YubiKeyConfiguration/OATHPasswordView.swift @@ -31,8 +31,8 @@ struct OATHPasswordView: View { var body: some View { SettingsView(image: Image(systemName: "lock.shield"), imageColor: Color(.systemPurple)) { - Text("OATH password protection").font(.title2).bold() - Text("For additional security and to prevent unauthorized access the YubiKey can be password protected.") + Text("OATH password").font(.title2).bold() + Text("To prevent unauthorized access the OATH application can be protected by a password.") .font(.subheadline) .multilineTextAlignment(.center) } buttons: { diff --git a/Authenticator/UI/YubiKeyConfiguration/OATHResetView.swift b/Authenticator/UI/YubiKeyConfiguration/OATHResetView.swift index d0fda7e2..14456647 100644 --- a/Authenticator/UI/YubiKeyConfiguration/OATHResetView.swift +++ b/Authenticator/UI/YubiKeyConfiguration/OATHResetView.swift @@ -23,22 +23,25 @@ struct OATHResetView: View { @State var presentErrorAlert = false @State var keyHasBeenReset = false @State var errorMessage: String? = nil - + @State var image = Image(systemName: "exclamationmark.triangle") + @State var imageColor = Color(.systemRed) + var body: some View { - SettingsView(image: Image(systemName: "exclamationmark.triangle"), imageColor: Color(.systemRed)) { - Text(keyHasBeenReset ? String(localized: "YubiKey has been reset") : String(localized: "Reset OATH application")).font(.title2).bold() + SettingsView(image: image, imageColor: imageColor) { + Text(String(localized: "Reset OATH application")).font(.title2).bold() + .opacity(keyHasBeenReset ? 0.2 : 1.0) Text("Reset all accounts stored on YubiKey, make sure they are not in use anywhere before doing this.") .font(.subheadline) .multilineTextAlignment(.center) .opacity(keyHasBeenReset ? 0.2 : 1.0) } buttons: { - SettingsButton("Reset YubiKey") { + SettingsButton("Reset OATH") { presentConfirmAlert.toggle() } .disabled(keyHasBeenReset) } .navigationBarTitle(Text("Reset OATH"), displayMode: .inline) - .alert("Confirm OATH reset", isPresented: $presentConfirmAlert, presenting: model, actions: { model in + .alert("Warning!", isPresented: $presentConfirmAlert, presenting: model, actions: { model in Button(role: .destructive) { presentConfirmAlert.toggle() model.reset() @@ -50,6 +53,8 @@ struct OATHResetView: View { } label: { Text("Cancel") } + }, message: { _ in + Text("This will irrevocably delete all OATH TOTP/HOTP accounts from your YubiKey.") }) .alert(errorMessage ?? String(localized: "Unknown error"), isPresented: $presentErrorAlert, actions: { }) .onChange(of: model.state) { state in @@ -59,6 +64,8 @@ struct OATHResetView: View { self.keyHasBeenReset = false case .success: self.keyHasBeenReset = true + self.image = Image(systemName: "checkmark.circle") + self.imageColor = Color(.systemGreen) case .error(let message): self.presentErrorAlert = true self.errorMessage = message diff --git a/Authenticator/UI/YubiKeyConfiguration/OATHSavedPasswordsView.swift b/Authenticator/UI/YubiKeyConfiguration/OATHSavedPasswordsView.swift index f626ea7a..168f980a 100644 --- a/Authenticator/UI/YubiKeyConfiguration/OATHSavedPasswordsView.swift +++ b/Authenticator/UI/YubiKeyConfiguration/OATHSavedPasswordsView.swift @@ -22,10 +22,16 @@ struct OATHSavedPasswordsView: View { @State var presentErrorAlert = false @State var errorMessage: String? = nil @State var passwordsHasBeenCleared = false + @State var image = Image(systemName: "xmark.circle") + @State var imageColor = Color(.systemRed) var body: some View { - SettingsView(image: Image(systemName: "xmark.circle"), imageColor: Color(.systemRed)) { - Text(passwordsHasBeenCleared ? "Saved passwords has been cleared" : "Clear saved OATH passwords").font(.title2).bold() + SettingsView(image: image, imageColor: imageColor) { + Text("Clear saved OATH passwords") + .multilineTextAlignment(.center) + .font(.title2) + .bold() + .opacity(passwordsHasBeenCleared ? 0.2 : 1.0) Text("Clear passwords saved on this device. This will prompt for a password next time a password protected YubiKey is used.") .font(.subheadline) .multilineTextAlignment(.center) @@ -58,6 +64,8 @@ struct OATHSavedPasswordsView: View { self.passwordsHasBeenCleared = false case .success: self.passwordsHasBeenCleared = true + self.image = Image(systemName: "checkmark.circle") + self.imageColor = Color(.systemGreen) case .error(let message): self.presentErrorAlert = true self.errorMessage = message From 62afd8a4ef9ff3c06a6b226adbc4245481fc8514 Mon Sep 17 00:00:00 2001 From: Jens Utbult Date: Thu, 14 Nov 2024 15:17:38 +0100 Subject: [PATCH 2/2] Bumped build number and updated release date. --- Authenticator.xcodeproj/project.pbxproj | 32 +++++++++++++++++-------- Authenticator/VersionHistory.plist | 2 +- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/Authenticator.xcodeproj/project.pbxproj b/Authenticator.xcodeproj/project.pbxproj index 20ec1684..c5afa4b7 100644 --- a/Authenticator.xcodeproj/project.pbxproj +++ b/Authenticator.xcodeproj/project.pbxproj @@ -117,6 +117,7 @@ B4C93E8929B89DE300C2A8B8 /* DetachedMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4C93E8829B89DE300C2A8B8 /* DetachedMenu.swift */; }; B4C93E9329C1B2BC00C2A8B8 /* AboutWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4C93E9229C1B2BC00C2A8B8 /* AboutWrapper.swift */; }; B4C93E9529C1B90900C2A8B8 /* AddAccountWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4C93E9429C1B90900C2A8B8 /* AddAccountWrapper.swift */; }; + B4D71E812CE4E6E6004D4C29 /* YubiKit in Frameworks */ = {isa = PBXBuildFile; productRef = B4D71E802CE4E6E6004D4C29 /* YubiKit */; }; B4DB228A299BC373003110ED /* OATHSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4DB2289299BC373003110ED /* OATHSession.swift */; }; B4FE90D02A42028400B59170 /* VersionHistoryWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4FE90CF2A42028400B59170 /* VersionHistoryWrapper.swift */; }; B4FE90D22A4431AB00B59170 /* NotificationsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4FE90D12A4431AB00B59170 /* NotificationsViewModel.swift */; }; @@ -299,6 +300,7 @@ buildActionMask = 2147483647; files = ( B44E5E842C74C8CC007ABB79 /* YubiKit in Frameworks */, + B4D71E812CE4E6E6004D4C29 /* YubiKit in Frameworks */, B42A39332B2A03D20039DB26 /* YubiKit in Frameworks */, B9F0FF11F842A39183974083 /* (null) in Frameworks */, 51AFD4DA271D4278008F2630 /* QuartzCore.framework in Frameworks */, @@ -633,6 +635,7 @@ packageProductDependencies = ( B42A39322B2A03D20039DB26 /* YubiKit */, B44E5E832C74C8CC007ABB79 /* YubiKit */, + B4D71E802CE4E6E6004D4C29 /* YubiKit */, ); productName = Authenticator; productReference = 818866B322DFD729006BC0A8 /* Authenticator.app */; @@ -695,7 +698,7 @@ ); mainGroup = 818866AA22DFD729006BC0A8; packageReferences = ( - B44E5E822C74C8CC007ABB79 /* XCLocalSwiftPackageReference "../yubikit-ios" */, + B4D71E7F2CE4E6E6004D4C29 /* XCRemoteSwiftPackageReference "yubikit-ios" */, ); productRefGroup = 818866B422DFD729006BC0A8 /* Products */; projectDirPath = ""; @@ -932,7 +935,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = TokenExtension/TokenExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 143; + CURRENT_PROJECT_VERSION = 163; DEVELOPMENT_TEAM = LQA3CS5MM7; INFOPLIST_FILE = TokenExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; @@ -956,7 +959,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = TokenExtension/TokenExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 143; + CURRENT_PROJECT_VERSION = 163; DEVELOPMENT_TEAM = LQA3CS5MM7; INFOPLIST_FILE = TokenExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; @@ -1106,7 +1109,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = Authenticator/Authenticator.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 143; + CURRENT_PROJECT_VERSION = 163; DEVELOPMENT_TEAM = LQA3CS5MM7; HEADER_SEARCH_PATHS = "../Submodules/YubiKit/**"; INFOPLIST_FILE = Authenticator/Info.plist; @@ -1136,7 +1139,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = Authenticator/Authenticator.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 143; + CURRENT_PROJECT_VERSION = 163; DEVELOPMENT_TEAM = LQA3CS5MM7; HEADER_SEARCH_PATHS = "../Submodules/YubiKit/**"; INFOPLIST_FILE = Authenticator/Info.plist; @@ -1244,12 +1247,16 @@ }; /* End XCConfigurationList section */ -/* Begin XCLocalSwiftPackageReference section */ - B44E5E822C74C8CC007ABB79 /* XCLocalSwiftPackageReference "../yubikit-ios" */ = { - isa = XCLocalSwiftPackageReference; - relativePath = "../yubikit-ios"; +/* Begin XCRemoteSwiftPackageReference section */ + B4D71E7F2CE4E6E6004D4C29 /* XCRemoteSwiftPackageReference "yubikit-ios" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Yubico/yubikit-ios"; + requirement = { + branch = main; + kind = branch; + }; }; -/* End XCLocalSwiftPackageReference section */ +/* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ B42A39322B2A03D20039DB26 /* YubiKit */ = { @@ -1260,6 +1267,11 @@ isa = XCSwiftPackageProductDependency; productName = YubiKit; }; + B4D71E802CE4E6E6004D4C29 /* YubiKit */ = { + isa = XCSwiftPackageProductDependency; + package = B4D71E7F2CE4E6E6004D4C29 /* XCRemoteSwiftPackageReference "yubikit-ios" */; + productName = YubiKit; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 818866AB22DFD729006BC0A8 /* Project object */; diff --git a/Authenticator/VersionHistory.plist b/Authenticator/VersionHistory.plist index 5f4e92b0..e76f4e00 100644 --- a/Authenticator/VersionHistory.plist +++ b/Authenticator/VersionHistory.plist @@ -6,7 +6,7 @@ version 1.8 date - 2024-11-06T09:41:00Z + 2024-11-18T09:41:00Z shouldPromptUser changes