From bee7d4f1c8f8b14a94d457aa0ff513328b40d090 Mon Sep 17 00:00:00 2001 From: Mounir IDRASSI Date: Fri, 25 Sep 2026 14:28:36 +0200 Subject: [PATCH] GUI: recover hidden protection without replacing outer credentials Handle protection failures before outer-password retries, retain cached and explicit outer credentials without adding default keyfiles, and focus the hidden-protection controls. Initialize hidden PIM and KDF independently, and retain an accepted backup header during protection recovery. Retry embedded backup headers after repeated hidden-protection failures without advancing the outer-password retry count. Restore the accepted primary-header selection if backup authentication fails, and report automatic backup use only when the selected header remains a backup. Keep primary-header recovery available after an automatic backup attempt fails with EIO, while reporting the error. Preserve cancellation and other error handling. Pass known protection failures from favorite mounts directly into GUI recovery, including keyfile-only credentials with a null password. Skip redundant authentication attempts and preserve the text interface's existing retry path. Initialize the backup-header checkbox with the other validated controls, so an initial backup-header request survives the first password prompt. Conceal the accepted outer password and PIM before disabling their controls during protection recovery. --- src/Main/Forms/MountOptionsDialog.cpp | 70 +++++++++++------ src/Main/Forms/MountOptionsDialog.h | 2 + src/Main/Forms/VolumePasswordPanel.cpp | 10 ++- src/Main/Forms/VolumePasswordPanel.h | 3 +- src/Main/GraphicUserInterface.cpp | 100 ++++++++++++++++++++++--- src/Main/GraphicUserInterface.h | 2 + src/Main/UserInterface.cpp | 11 ++- src/Main/UserInterface.h | 1 + 8 files changed, 159 insertions(+), 40 deletions(-) diff --git a/src/Main/Forms/MountOptionsDialog.cpp b/src/Main/Forms/MountOptionsDialog.cpp index 23911c9c..17bd7a1b 100644 --- a/src/Main/Forms/MountOptionsDialog.cpp +++ b/src/Main/Forms/MountOptionsDialog.cpp @@ -33,7 +33,7 @@ namespace VeraCrypt #ifdef __WXGTK__ // GTK apparently needs wxRESIZE_BORDER to support dynamic resizing , wxDefaultPosition, wxSize (-1,-1), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER #endif - ), Options (options) + ), Options (options), ProtectionRecovery (false) #ifdef TC_UNIX , m_showRedBorder(false) #endif @@ -79,6 +79,7 @@ namespace VeraCrypt NoFilesystemCheckBox->SetValidator (wxGenericValidator (&Options.NoFilesystem)); RemovableCheckBox->SetValidator (wxGenericValidator (&Options.Removable)); PartitionInSystemEncryptionScopeCheckBox->SetValidator (wxGenericValidator (&Options.PartitionInSystemEncryptionScope)); + BackupHeaderCheckBox->SetValidator (wxGenericValidator (&Options.UseBackupHeaders)); TransferDataToWindow(); @@ -88,14 +89,16 @@ namespace VeraCrypt FilesystemOptionsTextCtrl->SetValue (Options.FilesystemOptions); ReadOnlyCheckBox->SetValue (Options.Protection == VolumeProtection::ReadOnly); - BackupHeaderCheckBox->SetValidator (wxGenericValidator (&Options.UseBackupHeaders)); ProtectionCheckBox->SetValue (Options.Protection == VolumeProtection::HiddenVolumeReadOnly); OptionsButtonLabel = OptionsButton->GetLabel(); OptionsButton->SetLabel (OptionsButtonLabel + L" >"); OptionsPanel->Show (false); - ProtectionPasswordPanel = new VolumePasswordPanel (ProtectionSizer->GetStaticBox(), &options, options.ProtectionPassword, options.ProtectionKeyfiles, false, true, true, false, true, true, LangString["IDT_HIDDEN_PROT_PASSWD"]); + MountOptions protectionOptions; + protectionOptions.Pim = options.ProtectionPim; + protectionOptions.Kdf = options.ProtectionKdf; + ProtectionPasswordPanel = new VolumePasswordPanel (ProtectionSizer->GetStaticBox(), &protectionOptions, options.ProtectionPassword, options.ProtectionKeyfiles, false, true, true, false, true, true, LangString["IDT_HIDDEN_PROT_PASSWD"]); ProtectionPasswordPanel->TopOwnerParent = this; ProtectionPasswordSizer->Add (ProtectionPasswordPanel, 1, wxALL | wxEXPAND); @@ -105,7 +108,25 @@ namespace VeraCrypt void MountOptionsDialog::OnInitDialog (wxInitDialogEvent& event) { - PasswordPanel->SetFocusToPasswordTextCtrl(); + if (ProtectionRecovery) + ProtectionPasswordPanel->SetFocusToPasswordTextCtrl(); + else + PasswordPanel->SetFocusToPasswordTextCtrl(); + } + + void MountOptionsDialog::SetProtectionRecovery (bool recovery) + { + if (recovery && !ProtectionRecovery) + PasswordPanel->SetPasswordVisible (false); + ProtectionRecovery = recovery; + PasswordPanel->Enable (!recovery); + if (recovery) + { + BackupHeaderCheckBox->SetValue (Options.UseBackupHeaders); + OptionsPanel->Show (true); + OptionsButton->SetLabel (OptionsButtonLabel + L" <"); + UpdateDialog(); + } } void MountOptionsDialog::OnMountPointButtonClick (wxCommandEvent& event) @@ -118,12 +139,12 @@ namespace VeraCrypt void MountOptionsDialog::OnOKButtonClick (wxCommandEvent& event) { /* verify that PIM values are valid before continuing*/ - int Pim = PasswordPanel->GetVolumePim(); + int Pim = ProtectionRecovery ? Options.Pim : PasswordPanel->GetVolumePim(); int ProtectionPim = (!ReadOnlyCheckBox->IsChecked() && ProtectionCheckBox->IsChecked())? ProtectionPasswordPanel->GetVolumePim() : 0; /* invalid PIM: set focus to PIM field and stop processing */ - if (-1 == Pim || (PartitionInSystemEncryptionScopeCheckBox->IsChecked() && Pim > MAX_BOOT_PIM_VALUE)) + if (!ProtectionRecovery && (-1 == Pim || (PartitionInSystemEncryptionScopeCheckBox->IsChecked() && Pim > MAX_BOOT_PIM_VALUE))) { PasswordPanel->SetFocusToPimTextCtrl(); return; @@ -137,25 +158,28 @@ namespace VeraCrypt TransferDataFromWindow(); - try + if (!ProtectionRecovery) { - Options.Password = PasswordPanel->GetPassword(Options.PartitionInSystemEncryptionScope); + try + { + Options.Password = PasswordPanel->GetPassword(Options.PartitionInSystemEncryptionScope); + } + catch (PasswordException& e) + { + Gui->ShowWarning (e); + return; + } + + if (Options.PartitionInSystemEncryptionScope && Options.Password->Size() > VolumePassword::MaxLegacySize) + { + Gui->ShowWarning (StringFormatter (LangString["LINUX_SYSTEM_ENC_PW_LENGTH_NOTE"], (int) VolumePassword::MaxLegacySize)); + return; + } + + Options.Pim = Pim; + Options.Kdf = PasswordPanel->GetPkcs5Kdf(); + Options.Keyfiles = PasswordPanel->GetKeyfiles(); } - catch (PasswordException& e) - { - Gui->ShowWarning (e); - return; - } - - if (Options.PartitionInSystemEncryptionScope && Options.Password->Size() > VolumePassword::MaxLegacySize) - { - Gui->ShowWarning (StringFormatter (LangString["LINUX_SYSTEM_ENC_PW_LENGTH_NOTE"], (int) VolumePassword::MaxLegacySize)); - return; - } - - Options.Pim = Pim; - Options.Kdf = PasswordPanel->GetPkcs5Kdf(); - Options.Keyfiles = PasswordPanel->GetKeyfiles(); if (ReadOnlyCheckBox->IsChecked()) { diff --git a/src/Main/Forms/MountOptionsDialog.h b/src/Main/Forms/MountOptionsDialog.h index c4814b2f..375d9fe4 100644 --- a/src/Main/Forms/MountOptionsDialog.h +++ b/src/Main/Forms/MountOptionsDialog.h @@ -24,6 +24,7 @@ namespace VeraCrypt public: MountOptionsDialog (wxWindow* parent, MountOptions &options, const wxString &title = wxEmptyString, bool disableMountOptions = false); void OnShow (); + void SetProtectionRecovery (bool recovery); #ifdef TC_MACOSX virtual bool ProcessEvent(wxEvent& event); @@ -47,6 +48,7 @@ namespace VeraCrypt #endif MountOptions &Options; + bool ProtectionRecovery; #ifdef TC_UNIX bool m_showRedBorder; #endif diff --git a/src/Main/Forms/VolumePasswordPanel.cpp b/src/Main/Forms/VolumePasswordPanel.cpp index 2659de7a..6b9ccc13 100644 --- a/src/Main/Forms/VolumePasswordPanel.cpp +++ b/src/Main/Forms/VolumePasswordPanel.cpp @@ -401,16 +401,18 @@ namespace VeraCrypt } } - void VolumePasswordPanel::OnDisplayPasswordCheckBoxClick (wxCommandEvent& event) + void VolumePasswordPanel::SetPasswordVisible (bool visible) { + DisplayPasswordCheckBox->SetValue (visible); + if (PasswordTextCtrl->IsShown()) - DisplayPassword (event.IsChecked(), &PasswordTextCtrl, 1); + DisplayPassword (visible, &PasswordTextCtrl, 1); if (ConfirmPasswordTextCtrl->IsShown()) - DisplayPassword (event.IsChecked(), &ConfirmPasswordTextCtrl, 2); + DisplayPassword (visible, &ConfirmPasswordTextCtrl, 2); if (VolumePimTextCtrl->IsShown()) - DisplayPassword (event.IsChecked(), &VolumePimTextCtrl, 3); + DisplayPassword (visible, &VolumePimTextCtrl, 3); OnUpdate(); } diff --git a/src/Main/Forms/VolumePasswordPanel.h b/src/Main/Forms/VolumePasswordPanel.h index 48de7df9..00701976 100644 --- a/src/Main/Forms/VolumePasswordPanel.h +++ b/src/Main/Forms/VolumePasswordPanel.h @@ -37,6 +37,7 @@ namespace VeraCrypt void SetFocusToPimCheckBox () { PimCheckBox->SetFocus(); } void SetFocusToPimTextCtrl () { VolumePimTextCtrl->SetSelection (-1, -1); VolumePimTextCtrl->SetFocus(); } void ResetVolumePimToDefault (); + void SetPasswordVisible (bool visible); void SetVolumePim (int pim); bool PasswordsMatch () const; void EnableUsePim (bool pimOnlyDisplay = false); @@ -54,7 +55,7 @@ namespace VeraCrypt void OnAddKeyfileDirMenuItemSelected (wxCommandEvent& event); void OnAddKeyfilesMenuItemSelected (wxCommandEvent& event); void OnAddSecurityTokenSignatureMenuItemSelected (wxCommandEvent& event); - void OnDisplayPasswordCheckBoxClick (wxCommandEvent& event); + void OnDisplayPasswordCheckBoxClick (wxCommandEvent& event) { SetPasswordVisible (event.IsChecked()); } void OnKeyfilesButtonClick (wxCommandEvent& event); void OnKeyfilesButtonRightClick (wxMouseEvent& event); void OnKeyfilesButtonRightDown (wxMouseEvent& event); diff --git a/src/Main/GraphicUserInterface.cpp b/src/Main/GraphicUserInterface.cpp index 321a9396..053f215b 100644 --- a/src/Main/GraphicUserInterface.cpp +++ b/src/Main/GraphicUserInterface.cpp @@ -15,6 +15,7 @@ #ifdef TC_UNIX #include #include +#include #include #include #include @@ -905,6 +906,17 @@ namespace VeraCrypt } shared_ptr GraphicUserInterface::MountVolume (MountOptions &options, bool tryCachedPasswords) const + { + return MountVolumeInternal (options, tryCachedPasswords, false); + } + + shared_ptr GraphicUserInterface::MountVolumeWithProtectionRecovery (MountOptions &options, const PasswordException &protectionError) const + { + ShowWarning (protectionError); + return MountVolumeInternal (options, false, true); + } + + shared_ptr GraphicUserInterface::MountVolumeInternal (MountOptions &options, bool tryCachedPasswords, bool protectionRecovery) const { CheckRequirementsForMountingVolume(); @@ -937,7 +949,32 @@ namespace VeraCrypt try { - if (tryCachedPasswords + bool protectionError = protectionRecovery; + auto mountWithProtectionRecovery = [&] () -> shared_ptr + { + protectionError = false; + try + { + return UserInterface::MountVolume (options); + } + catch (ProtectionPasswordIncorrect &e) + { + ShowWarning (e); + } + catch (ProtectionPasswordKeyfilesIncorrect &e) + { + ShowWarning (e); + } + + protectionError = true; + // Keep the accepted outer credential source, including the password + // cache. An explicit empty list prevents default keyfiles being added. + if (!options.Keyfiles) + options.Keyfiles = make_shared (); + return shared_ptr (); + }; + + if (!protectionError && tryCachedPasswords && (!options.Password || options.Password->IsEmpty()) && (!options.Keyfiles || options.Keyfiles->empty()) && !Core->IsPasswordCacheEmpty()) @@ -946,21 +983,25 @@ namespace VeraCrypt try { wxBusyCursor busy; - return UserInterface::MountVolume (options); + volume = mountWithProtectionRecovery(); + if (volume) + return volume; } catch (PasswordException&) { } } - if (!options.Keyfiles && GetPreferences().UseKeyfiles && !GetPreferences().DefaultKeyfiles.empty()) + if (!protectionError && !options.Keyfiles && GetPreferences().UseKeyfiles && !GetPreferences().DefaultKeyfiles.empty()) options.Keyfiles = make_shared (GetPreferences().DefaultKeyfiles); - if ((options.Password && !options.Password->IsEmpty()) - || (options.Keyfiles && !options.Keyfiles->empty() && options.Password)) + if (!protectionError && ((options.Password && !options.Password->IsEmpty()) + || (options.Keyfiles && !options.Keyfiles->empty() && options.Password))) { try { wxBusyCursor busy; - return UserInterface::MountVolume (options); + volume = mountWithProtectionRecovery(); + if (volume) + return volume; } catch (PasswordException&) { } } @@ -970,17 +1011,54 @@ namespace VeraCrypt MountOptionsDialog dialog (GetTopWindow(), options); int incorrectPasswordCount = 0; + int incorrectProtectionPasswordCount = 0; + bool autoBackupHeaderUsed = false; while (!volume) { dialog.Hide(); + dialog.SetProtectionRecovery (protectionError); if (dialog.ShowModal() != wxID_OK) return volume; try { wxBusyCursor busy; - volume = UserInterface::MountVolume (options); + volume = mountWithProtectionRecovery(); + + if (!volume && protectionError && !options.UseBackupHeaders + && ++incorrectProtectionPasswordCount > 2) + { + // The outer primary header was accepted, but the hidden primary + // header may be damaged. Try the backups once for this attempt. + options.UseBackupHeaders = true; + try + { + volume = UserInterface::MountVolume (options); + autoBackupHeaderUsed = true; + } + catch (PasswordException&) + { + // Keep recovering against the accepted primary outer header. + options.UseBackupHeaders = false; + } +#ifdef TC_UNIX + catch (SystemException &e) + { + options.UseBackupHeaders = false; + if (e.GetErrorCode() != EIO) + throw; + + // An unreadable backup must not prevent another primary-header attempt. + ShowWarning (e); + } +#endif + catch (...) + { + options.UseBackupHeaders = false; + throw; + } + } } catch (PasswordIncorrect &e) { @@ -988,15 +1066,16 @@ namespace VeraCrypt { // Try to mount the volume using the backup header options.UseBackupHeaders = true; + autoBackupHeaderUsed = true; try { - volume = UserInterface::MountVolume (options); - ShowWarning ("HEADER_DAMAGED_AUTO_USED_HEADER_BAK"); + volume = mountWithProtectionRecovery(); } catch (...) { options.UseBackupHeaders = false; + autoBackupHeaderUsed = false; ShowWarning (e); } } @@ -1008,6 +1087,9 @@ namespace VeraCrypt ShowWarning (e); } } + + if (autoBackupHeaderUsed && options.UseBackupHeaders) + ShowWarning ("HEADER_DAMAGED_AUTO_USED_HEADER_BAK"); } catch (exception &e) { diff --git a/src/Main/GraphicUserInterface.h b/src/Main/GraphicUserInterface.h index fba52b19..fede9a1a 100644 --- a/src/Main/GraphicUserInterface.h +++ b/src/Main/GraphicUserInterface.h @@ -120,6 +120,7 @@ namespace VeraCrypt Event OpenVolumeSystemRequestEvent; protected: + virtual shared_ptr MountVolumeWithProtectionRecovery (MountOptions &options, const PasswordException &protectionError) const; virtual void OnEndSession (wxCloseEvent& event) { OnLogOff(); } #ifdef wxHAS_POWER_EVENTS virtual void OnPowerSuspending (wxPowerEvent& event); @@ -145,6 +146,7 @@ public: #endif private: + shared_ptr MountVolumeInternal (MountOptions &options, bool tryCachedPasswords, bool protectionRecovery) const; GraphicUserInterface (const GraphicUserInterface &); GraphicUserInterface &operator= (const GraphicUserInterface &); }; diff --git a/src/Main/UserInterface.cpp b/src/Main/UserInterface.cpp index b88d5286..7b7c82fb 100644 --- a/src/Main/UserInterface.cpp +++ b/src/Main/UserInterface.cpp @@ -851,11 +851,16 @@ namespace VeraCrypt { CloseSecurityTokenSessionsAfterMountScope closeTokenSessionsScope (Preferences.CloseSecurityTokenSessionsAfterMount); - // A protection failure accepted the outer password. Let the UI recover - // using that cache; only skip a cache sweep that failed outer authentication. + // A protection failure accepted the outer credentials. Preserve that + // state for UI recovery; otherwise skip the failed cache sweep. bool protectionError = dynamic_cast (&e) || dynamic_cast (&e); - shared_ptr volume = MountVolume (favoriteOptions, protectionError); + // Do not add default outer keyfiles to credentials already accepted. + if (protectionError && !favoriteOptions.Keyfiles) + favoriteOptions.Keyfiles = make_shared (); + shared_ptr volume = protectionError + ? MountVolumeWithProtectionRecovery (favoriteOptions, e) + : MountVolume (favoriteOptions, false); if (!volume) break; diff --git a/src/Main/UserInterface.h b/src/Main/UserInterface.h index 6f6792bb..538ae6f9 100644 --- a/src/Main/UserInterface.h +++ b/src/Main/UserInterface.h @@ -102,6 +102,7 @@ namespace VeraCrypt protected: UserInterface (); + virtual shared_ptr MountVolumeWithProtectionRecovery (MountOptions &options, const PasswordException &protectionError) const { return MountVolume (options); } virtual bool OnExceptionInMainLoop () { throw; } virtual void OnUnhandledException (); virtual void OnVolumeMounted (EventArgs &args);