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.
This commit is contained in:
Mounir IDRASSI
2026-09-25 16:43:25 +02:00
parent bc9f7d2899
commit bee7d4f1c8
8 changed files with 159 additions and 40 deletions
+47 -23
View File
@@ -33,7 +33,7 @@ namespace VeraCrypt
#ifdef __WXGTK__ // GTK apparently needs wxRESIZE_BORDER to support dynamic resizing #ifdef __WXGTK__ // GTK apparently needs wxRESIZE_BORDER to support dynamic resizing
, wxDefaultPosition, wxSize (-1,-1), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER , wxDefaultPosition, wxSize (-1,-1), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER
#endif #endif
), Options (options) ), Options (options), ProtectionRecovery (false)
#ifdef TC_UNIX #ifdef TC_UNIX
, m_showRedBorder(false) , m_showRedBorder(false)
#endif #endif
@@ -79,6 +79,7 @@ namespace VeraCrypt
NoFilesystemCheckBox->SetValidator (wxGenericValidator (&Options.NoFilesystem)); NoFilesystemCheckBox->SetValidator (wxGenericValidator (&Options.NoFilesystem));
RemovableCheckBox->SetValidator (wxGenericValidator (&Options.Removable)); RemovableCheckBox->SetValidator (wxGenericValidator (&Options.Removable));
PartitionInSystemEncryptionScopeCheckBox->SetValidator (wxGenericValidator (&Options.PartitionInSystemEncryptionScope)); PartitionInSystemEncryptionScopeCheckBox->SetValidator (wxGenericValidator (&Options.PartitionInSystemEncryptionScope));
BackupHeaderCheckBox->SetValidator (wxGenericValidator (&Options.UseBackupHeaders));
TransferDataToWindow(); TransferDataToWindow();
@@ -88,14 +89,16 @@ namespace VeraCrypt
FilesystemOptionsTextCtrl->SetValue (Options.FilesystemOptions); FilesystemOptionsTextCtrl->SetValue (Options.FilesystemOptions);
ReadOnlyCheckBox->SetValue (Options.Protection == VolumeProtection::ReadOnly); ReadOnlyCheckBox->SetValue (Options.Protection == VolumeProtection::ReadOnly);
BackupHeaderCheckBox->SetValidator (wxGenericValidator (&Options.UseBackupHeaders));
ProtectionCheckBox->SetValue (Options.Protection == VolumeProtection::HiddenVolumeReadOnly); ProtectionCheckBox->SetValue (Options.Protection == VolumeProtection::HiddenVolumeReadOnly);
OptionsButtonLabel = OptionsButton->GetLabel(); OptionsButtonLabel = OptionsButton->GetLabel();
OptionsButton->SetLabel (OptionsButtonLabel + L" >"); OptionsButton->SetLabel (OptionsButtonLabel + L" >");
OptionsPanel->Show (false); 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; ProtectionPasswordPanel->TopOwnerParent = this;
ProtectionPasswordSizer->Add (ProtectionPasswordPanel, 1, wxALL | wxEXPAND); ProtectionPasswordSizer->Add (ProtectionPasswordPanel, 1, wxALL | wxEXPAND);
@@ -105,7 +108,25 @@ namespace VeraCrypt
void MountOptionsDialog::OnInitDialog (wxInitDialogEvent& event) 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) void MountOptionsDialog::OnMountPointButtonClick (wxCommandEvent& event)
@@ -118,12 +139,12 @@ namespace VeraCrypt
void MountOptionsDialog::OnOKButtonClick (wxCommandEvent& event) void MountOptionsDialog::OnOKButtonClick (wxCommandEvent& event)
{ {
/* verify that PIM values are valid before continuing*/ /* 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())? int ProtectionPim = (!ReadOnlyCheckBox->IsChecked() && ProtectionCheckBox->IsChecked())?
ProtectionPasswordPanel->GetVolumePim() : 0; ProtectionPasswordPanel->GetVolumePim() : 0;
/* invalid PIM: set focus to PIM field and stop processing */ /* 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(); PasswordPanel->SetFocusToPimTextCtrl();
return; return;
@@ -137,25 +158,28 @@ namespace VeraCrypt
TransferDataFromWindow(); 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()) if (ReadOnlyCheckBox->IsChecked())
{ {
+2
View File
@@ -24,6 +24,7 @@ namespace VeraCrypt
public: public:
MountOptionsDialog (wxWindow* parent, MountOptions &options, const wxString &title = wxEmptyString, bool disableMountOptions = false); MountOptionsDialog (wxWindow* parent, MountOptions &options, const wxString &title = wxEmptyString, bool disableMountOptions = false);
void OnShow (); void OnShow ();
void SetProtectionRecovery (bool recovery);
#ifdef TC_MACOSX #ifdef TC_MACOSX
virtual bool ProcessEvent(wxEvent& event); virtual bool ProcessEvent(wxEvent& event);
@@ -47,6 +48,7 @@ namespace VeraCrypt
#endif #endif
MountOptions &Options; MountOptions &Options;
bool ProtectionRecovery;
#ifdef TC_UNIX #ifdef TC_UNIX
bool m_showRedBorder; bool m_showRedBorder;
#endif #endif
+6 -4
View File
@@ -401,16 +401,18 @@ namespace VeraCrypt
} }
} }
void VolumePasswordPanel::OnDisplayPasswordCheckBoxClick (wxCommandEvent& event) void VolumePasswordPanel::SetPasswordVisible (bool visible)
{ {
DisplayPasswordCheckBox->SetValue (visible);
if (PasswordTextCtrl->IsShown()) if (PasswordTextCtrl->IsShown())
DisplayPassword (event.IsChecked(), &PasswordTextCtrl, 1); DisplayPassword (visible, &PasswordTextCtrl, 1);
if (ConfirmPasswordTextCtrl->IsShown()) if (ConfirmPasswordTextCtrl->IsShown())
DisplayPassword (event.IsChecked(), &ConfirmPasswordTextCtrl, 2); DisplayPassword (visible, &ConfirmPasswordTextCtrl, 2);
if (VolumePimTextCtrl->IsShown()) if (VolumePimTextCtrl->IsShown())
DisplayPassword (event.IsChecked(), &VolumePimTextCtrl, 3); DisplayPassword (visible, &VolumePimTextCtrl, 3);
OnUpdate(); OnUpdate();
} }
+2 -1
View File
@@ -37,6 +37,7 @@ namespace VeraCrypt
void SetFocusToPimCheckBox () { PimCheckBox->SetFocus(); } void SetFocusToPimCheckBox () { PimCheckBox->SetFocus(); }
void SetFocusToPimTextCtrl () { VolumePimTextCtrl->SetSelection (-1, -1); VolumePimTextCtrl->SetFocus(); } void SetFocusToPimTextCtrl () { VolumePimTextCtrl->SetSelection (-1, -1); VolumePimTextCtrl->SetFocus(); }
void ResetVolumePimToDefault (); void ResetVolumePimToDefault ();
void SetPasswordVisible (bool visible);
void SetVolumePim (int pim); void SetVolumePim (int pim);
bool PasswordsMatch () const; bool PasswordsMatch () const;
void EnableUsePim (bool pimOnlyDisplay = false); void EnableUsePim (bool pimOnlyDisplay = false);
@@ -54,7 +55,7 @@ namespace VeraCrypt
void OnAddKeyfileDirMenuItemSelected (wxCommandEvent& event); void OnAddKeyfileDirMenuItemSelected (wxCommandEvent& event);
void OnAddKeyfilesMenuItemSelected (wxCommandEvent& event); void OnAddKeyfilesMenuItemSelected (wxCommandEvent& event);
void OnAddSecurityTokenSignatureMenuItemSelected (wxCommandEvent& event); void OnAddSecurityTokenSignatureMenuItemSelected (wxCommandEvent& event);
void OnDisplayPasswordCheckBoxClick (wxCommandEvent& event); void OnDisplayPasswordCheckBoxClick (wxCommandEvent& event) { SetPasswordVisible (event.IsChecked()); }
void OnKeyfilesButtonClick (wxCommandEvent& event); void OnKeyfilesButtonClick (wxCommandEvent& event);
void OnKeyfilesButtonRightClick (wxMouseEvent& event); void OnKeyfilesButtonRightClick (wxMouseEvent& event);
void OnKeyfilesButtonRightDown (wxMouseEvent& event); void OnKeyfilesButtonRightDown (wxMouseEvent& event);
+91 -9
View File
@@ -15,6 +15,7 @@
#ifdef TC_UNIX #ifdef TC_UNIX
#include <wx/mimetype.h> #include <wx/mimetype.h>
#include <wx/sckipc.h> #include <wx/sckipc.h>
#include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <signal.h> #include <signal.h>
#include <unistd.h> #include <unistd.h>
@@ -905,6 +906,17 @@ namespace VeraCrypt
} }
shared_ptr <VolumeInfo> GraphicUserInterface::MountVolume (MountOptions &options, bool tryCachedPasswords) const shared_ptr <VolumeInfo> GraphicUserInterface::MountVolume (MountOptions &options, bool tryCachedPasswords) const
{
return MountVolumeInternal (options, tryCachedPasswords, false);
}
shared_ptr <VolumeInfo> GraphicUserInterface::MountVolumeWithProtectionRecovery (MountOptions &options, const PasswordException &protectionError) const
{
ShowWarning (protectionError);
return MountVolumeInternal (options, false, true);
}
shared_ptr <VolumeInfo> GraphicUserInterface::MountVolumeInternal (MountOptions &options, bool tryCachedPasswords, bool protectionRecovery) const
{ {
CheckRequirementsForMountingVolume(); CheckRequirementsForMountingVolume();
@@ -937,7 +949,32 @@ namespace VeraCrypt
try try
{ {
if (tryCachedPasswords bool protectionError = protectionRecovery;
auto mountWithProtectionRecovery = [&] () -> shared_ptr <VolumeInfo>
{
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 <KeyfileList>();
return shared_ptr <VolumeInfo>();
};
if (!protectionError && tryCachedPasswords
&& (!options.Password || options.Password->IsEmpty()) && (!options.Password || options.Password->IsEmpty())
&& (!options.Keyfiles || options.Keyfiles->empty()) && (!options.Keyfiles || options.Keyfiles->empty())
&& !Core->IsPasswordCacheEmpty()) && !Core->IsPasswordCacheEmpty())
@@ -946,21 +983,25 @@ namespace VeraCrypt
try try
{ {
wxBusyCursor busy; wxBusyCursor busy;
return UserInterface::MountVolume (options); volume = mountWithProtectionRecovery();
if (volume)
return volume;
} }
catch (PasswordException&) { } catch (PasswordException&) { }
} }
if (!options.Keyfiles && GetPreferences().UseKeyfiles && !GetPreferences().DefaultKeyfiles.empty()) if (!protectionError && !options.Keyfiles && GetPreferences().UseKeyfiles && !GetPreferences().DefaultKeyfiles.empty())
options.Keyfiles = make_shared <KeyfileList> (GetPreferences().DefaultKeyfiles); options.Keyfiles = make_shared <KeyfileList> (GetPreferences().DefaultKeyfiles);
if ((options.Password && !options.Password->IsEmpty()) if (!protectionError && ((options.Password && !options.Password->IsEmpty())
|| (options.Keyfiles && !options.Keyfiles->empty() && options.Password)) || (options.Keyfiles && !options.Keyfiles->empty() && options.Password)))
{ {
try try
{ {
wxBusyCursor busy; wxBusyCursor busy;
return UserInterface::MountVolume (options); volume = mountWithProtectionRecovery();
if (volume)
return volume;
} }
catch (PasswordException&) { } catch (PasswordException&) { }
} }
@@ -970,17 +1011,54 @@ namespace VeraCrypt
MountOptionsDialog dialog (GetTopWindow(), options); MountOptionsDialog dialog (GetTopWindow(), options);
int incorrectPasswordCount = 0; int incorrectPasswordCount = 0;
int incorrectProtectionPasswordCount = 0;
bool autoBackupHeaderUsed = false;
while (!volume) while (!volume)
{ {
dialog.Hide(); dialog.Hide();
dialog.SetProtectionRecovery (protectionError);
if (dialog.ShowModal() != wxID_OK) if (dialog.ShowModal() != wxID_OK)
return volume; return volume;
try try
{ {
wxBusyCursor busy; 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) catch (PasswordIncorrect &e)
{ {
@@ -988,15 +1066,16 @@ namespace VeraCrypt
{ {
// Try to mount the volume using the backup header // Try to mount the volume using the backup header
options.UseBackupHeaders = true; options.UseBackupHeaders = true;
autoBackupHeaderUsed = true;
try try
{ {
volume = UserInterface::MountVolume (options); volume = mountWithProtectionRecovery();
ShowWarning ("HEADER_DAMAGED_AUTO_USED_HEADER_BAK");
} }
catch (...) catch (...)
{ {
options.UseBackupHeaders = false; options.UseBackupHeaders = false;
autoBackupHeaderUsed = false;
ShowWarning (e); ShowWarning (e);
} }
} }
@@ -1008,6 +1087,9 @@ namespace VeraCrypt
ShowWarning (e); ShowWarning (e);
} }
} }
if (autoBackupHeaderUsed && options.UseBackupHeaders)
ShowWarning ("HEADER_DAMAGED_AUTO_USED_HEADER_BAK");
} }
catch (exception &e) catch (exception &e)
{ {
+2
View File
@@ -120,6 +120,7 @@ namespace VeraCrypt
Event OpenVolumeSystemRequestEvent; Event OpenVolumeSystemRequestEvent;
protected: protected:
virtual shared_ptr <VolumeInfo> MountVolumeWithProtectionRecovery (MountOptions &options, const PasswordException &protectionError) const;
virtual void OnEndSession (wxCloseEvent& event) { OnLogOff(); } virtual void OnEndSession (wxCloseEvent& event) { OnLogOff(); }
#ifdef wxHAS_POWER_EVENTS #ifdef wxHAS_POWER_EVENTS
virtual void OnPowerSuspending (wxPowerEvent& event); virtual void OnPowerSuspending (wxPowerEvent& event);
@@ -145,6 +146,7 @@ public:
#endif #endif
private: private:
shared_ptr <VolumeInfo> MountVolumeInternal (MountOptions &options, bool tryCachedPasswords, bool protectionRecovery) const;
GraphicUserInterface (const GraphicUserInterface &); GraphicUserInterface (const GraphicUserInterface &);
GraphicUserInterface &operator= (const GraphicUserInterface &); GraphicUserInterface &operator= (const GraphicUserInterface &);
}; };
+8 -3
View File
@@ -851,11 +851,16 @@ namespace VeraCrypt
{ {
CloseSecurityTokenSessionsAfterMountScope closeTokenSessionsScope (Preferences.CloseSecurityTokenSessionsAfterMount); CloseSecurityTokenSessionsAfterMountScope closeTokenSessionsScope (Preferences.CloseSecurityTokenSessionsAfterMount);
// A protection failure accepted the outer password. Let the UI recover // A protection failure accepted the outer credentials. Preserve that
// using that cache; only skip a cache sweep that failed outer authentication. // state for UI recovery; otherwise skip the failed cache sweep.
bool protectionError = dynamic_cast <ProtectionPasswordIncorrect *> (&e) bool protectionError = dynamic_cast <ProtectionPasswordIncorrect *> (&e)
|| dynamic_cast <ProtectionPasswordKeyfilesIncorrect *> (&e); || dynamic_cast <ProtectionPasswordKeyfilesIncorrect *> (&e);
shared_ptr <VolumeInfo> volume = MountVolume (favoriteOptions, protectionError); // Do not add default outer keyfiles to credentials already accepted.
if (protectionError && !favoriteOptions.Keyfiles)
favoriteOptions.Keyfiles = make_shared <KeyfileList>();
shared_ptr <VolumeInfo> volume = protectionError
? MountVolumeWithProtectionRecovery (favoriteOptions, e)
: MountVolume (favoriteOptions, false);
if (!volume) if (!volume)
break; break;
+1
View File
@@ -102,6 +102,7 @@ namespace VeraCrypt
protected: protected:
UserInterface (); UserInterface ();
virtual shared_ptr <VolumeInfo> MountVolumeWithProtectionRecovery (MountOptions &options, const PasswordException &protectionError) const { return MountVolume (options); }
virtual bool OnExceptionInMainLoop () { throw; } virtual bool OnExceptionInMainLoop () { throw; }
virtual void OnUnhandledException (); virtual void OnUnhandledException ();
virtual void OnVolumeMounted (EventArgs &args); virtual void OnVolumeMounted (EventArgs &args);