mirror of
https://github.com/veracrypt/VeraCrypt.git
synced 2025-11-11 11:08:02 -06:00
Currently the maximum hidden volume size for file containers is limited by available free space on the device the file container resides on, which we do not care about. This commit changes so that only Normal volumes get their `maxVolumeSize` limited by `AvailableDiskSpace`. Also the --size=max parameter is restricted from hidden volume creation as there is no way to determine a good size as we do not mount the outer volume through the CLI process flow to determine available free space on the outer volume.
1802 lines
50 KiB
C++
1802 lines
50 KiB
C++
/*
|
|
Derived from source code of TrueCrypt 7.1a, which is
|
|
Copyright (c) 2008-2012 TrueCrypt Developers Association and which is governed
|
|
by the TrueCrypt License 3.0.
|
|
|
|
Modifications and additions to the original source code (contained in this file)
|
|
and all other portions of this file are Copyright (c) 2013-2017 IDRIX
|
|
and are governed by the Apache License 2.0 the full text of which is
|
|
contained in the file License.txt included in VeraCrypt binary and source
|
|
code distribution packages.
|
|
*/
|
|
|
|
#include "System.h"
|
|
#ifdef TC_UNIX
|
|
#include <signal.h>
|
|
#include <termios.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include "Platform/Unix/Process.h"
|
|
#endif
|
|
|
|
#include <wx/platinfo.h>
|
|
#include "Common/Token.h"
|
|
#include "Common/SecurityToken.h"
|
|
#include "Common/EMVToken.h"
|
|
#include "Core/RandomNumberGenerator.h"
|
|
#include "Application.h"
|
|
#include "TextUserInterface.h"
|
|
|
|
namespace VeraCrypt
|
|
{
|
|
TextUserInterface::TextUserInterface ()
|
|
{
|
|
#ifdef TC_UNIX
|
|
signal (SIGHUP, OnSignal);
|
|
signal (SIGINT, OnSignal);
|
|
signal (SIGQUIT, OnSignal);
|
|
signal (SIGTERM, OnSignal);
|
|
|
|
struct stat statBuf;
|
|
if (fstat (0, &statBuf) != -1)
|
|
#endif
|
|
{
|
|
FInputStream.reset (new wxFFileInputStream (stdin));
|
|
// Set fallback encoding of the stream converter to UTF-8
|
|
// to make sure we interpret multibyte symbols properly
|
|
TextInputStream.reset (new wxTextInputStream (*FInputStream, wxT(" \t"), wxConvAuto(wxFONTENCODING_UTF8)));
|
|
}
|
|
}
|
|
|
|
TextUserInterface::~TextUserInterface ()
|
|
{
|
|
try
|
|
{
|
|
if (RandomNumberGenerator::IsRunning())
|
|
RandomNumberGenerator::Stop();
|
|
}
|
|
catch (...) { }
|
|
|
|
#ifdef TC_UNIX
|
|
signal (SIGHUP, SIG_DFL);
|
|
signal (SIGINT, SIG_DFL);
|
|
signal (SIGQUIT, SIG_DFL);
|
|
signal (SIGTERM, SIG_DFL);
|
|
#endif
|
|
}
|
|
|
|
FilePath TextUserInterface::AskFilePath (const wxString &message) const
|
|
{
|
|
return AskString (!message.empty() ? message : wxString (_("Enter filename: ")));
|
|
}
|
|
|
|
shared_ptr <KeyfileList> TextUserInterface::AskKeyfiles (const wxString &message) const
|
|
{
|
|
wxString msg = _("Enter keyfile");
|
|
if (!message.empty())
|
|
msg = message;
|
|
|
|
make_shared_auto (KeyfileList, keyfiles);
|
|
|
|
wxString s;
|
|
wxString m = msg + L" [" + _("none") + L"]: ";
|
|
while (!(s = AskString (m)).empty())
|
|
{
|
|
keyfiles->push_back (make_shared <Keyfile> (wstring (s)));
|
|
m = msg + L" [" + _("finish") + L"]: ";
|
|
}
|
|
|
|
return keyfiles;
|
|
}
|
|
|
|
shared_ptr <VolumePassword> TextUserInterface::AskPassword (const wxString &message, bool verify) const
|
|
{
|
|
wxString msg = LangString["ENTER_PASSWORD"] + L": ";
|
|
if (!message.empty())
|
|
msg = message + L": ";
|
|
|
|
SetTerminalEcho (false);
|
|
finally_do ({ TextUserInterface::SetTerminalEcho (true); });
|
|
|
|
wchar_t passwordBuf[4096];
|
|
finally_do_arg (BufferPtr, BufferPtr (reinterpret_cast <byte *> (passwordBuf), sizeof (passwordBuf)), { finally_arg.Erase(); });
|
|
|
|
shared_ptr<VolumePassword> password;
|
|
|
|
bool verPhase = false;
|
|
while (true)
|
|
{
|
|
ShowString (verPhase ? wxString (_("Re-enter password: ")) : msg);
|
|
|
|
wxString passwordStr;
|
|
ReadInputStreamLine (passwordStr);
|
|
|
|
size_t length = passwordStr.size();
|
|
|
|
ShowString (L"\n");
|
|
|
|
if (!verPhase && length < 1)
|
|
{
|
|
return shared_ptr <VolumePassword>(new VolumePassword ());
|
|
}
|
|
|
|
for (size_t i = 0; i < length && i < VolumePassword::MaxSize; ++i)
|
|
{
|
|
passwordBuf[i] = (wchar_t) passwordStr[i];
|
|
const_cast <wchar_t *> (passwordStr.wc_str())[i] = L'X';
|
|
}
|
|
|
|
if (verify && verPhase)
|
|
{
|
|
shared_ptr <VolumePassword> verPassword = ToUTF8Password (passwordBuf, length, CmdLine->ArgUseLegacyPassword? VolumePassword::MaxLegacySize : VolumePassword::MaxSize);
|
|
|
|
if (*password != *verPassword)
|
|
{
|
|
ShowInfo (_("Passwords do not match."));
|
|
ShowString (L"\n");
|
|
verPhase = false;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
password = ToUTF8Password (passwordBuf, length, CmdLine->ArgUseLegacyPassword? VolumePassword::MaxLegacySize : VolumePassword::MaxSize);
|
|
|
|
if (!verPhase)
|
|
{
|
|
if (verify)
|
|
{
|
|
if (password->Size() < VolumePassword::WarningSizeThreshold)
|
|
{
|
|
SetTerminalEcho (true);
|
|
finally_do ({ TextUserInterface::SetTerminalEcho (false); });
|
|
|
|
if (!AskYesNo (LangString ["PASSWORD_LENGTH_WARNING"], false, true))
|
|
{
|
|
ShowString (L"\n");
|
|
continue;
|
|
}
|
|
ShowString (L"\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!verify || verPhase)
|
|
return password;
|
|
|
|
if (!verPhase)
|
|
verPhase = true;
|
|
}
|
|
|
|
return password;
|
|
}
|
|
|
|
int TextUserInterface::AskPim (const wxString &message) const
|
|
{
|
|
int pim = -1;
|
|
wxString msg = _("Enter new PIM: ");
|
|
if (!message.empty())
|
|
msg = message + L": ";
|
|
SetTerminalEcho (false);
|
|
finally_do ({ TextUserInterface::SetTerminalEcho (true); });
|
|
while (pim < 0)
|
|
{
|
|
wstring pimStr = AskString (msg);
|
|
ShowString (L"\n");
|
|
|
|
if (pimStr.empty())
|
|
pim = 0;
|
|
else
|
|
{
|
|
try
|
|
{
|
|
pim = (int) StringConverter::ToUInt32 (pimStr);
|
|
if (pim > MAX_PIM_VALUE)
|
|
{
|
|
pim = -1;
|
|
ShowError ("PIM_TOO_BIG");
|
|
continue;
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
pim = -1;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
return pim;
|
|
}
|
|
|
|
ssize_t TextUserInterface::AskSelection (ssize_t optionCount, ssize_t defaultOption) const
|
|
{
|
|
while (true)
|
|
{
|
|
wstring selectionStr = AskString (defaultOption == -1 ? wxString (_("Select: ")) : wxString (wstring (StringFormatter (_("Select [{0}]: "), (uint32) defaultOption))));
|
|
ssize_t selection;
|
|
|
|
if (selectionStr.empty() && defaultOption != -1)
|
|
return defaultOption;
|
|
|
|
try
|
|
{
|
|
selection = StringConverter::ToUInt32 (selectionStr);
|
|
}
|
|
catch (...)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (selection > 0 && selection <= optionCount)
|
|
return selection;
|
|
}
|
|
}
|
|
|
|
wstring TextUserInterface::AskString (const wxString &message) const
|
|
{
|
|
ShowString (message);
|
|
return wstring (ReadInputStreamLine());
|
|
}
|
|
|
|
bool TextUserInterface::AskYesNo (const wxString &message, bool defaultYes, bool warning) const
|
|
{
|
|
while (true)
|
|
{
|
|
wxString s = AskString (StringFormatter (L"{0} (y={1}/n={2}) [{3}]: ",
|
|
message, LangString["UISTR_YES"], LangString["UISTR_NO"], LangString[defaultYes ? "UISTR_YES" : "UISTR_NO"]));
|
|
|
|
if (s.IsSameAs (L'n', false) || s.IsSameAs (L"no", false) || (!defaultYes && s.empty()))
|
|
return false;
|
|
|
|
if (s.IsSameAs (L'y', false) || s.IsSameAs (L"yes", false) || (defaultYes && s.empty()))
|
|
return true;
|
|
};
|
|
}
|
|
|
|
shared_ptr <VolumePath> TextUserInterface::AskVolumePath (const wxString &message) const
|
|
{
|
|
return make_shared <VolumePath> (AskString (message.empty() ? wxString (_("Enter volume path: ")) : message));
|
|
}
|
|
|
|
void TextUserInterface::BackupVolumeHeaders (shared_ptr <VolumePath> volumePath) const
|
|
{
|
|
if (!volumePath)
|
|
volumePath = AskVolumePath();
|
|
|
|
if (!volumePath)
|
|
throw UserAbort (SRC_POS);
|
|
|
|
#ifdef TC_WINDOWS
|
|
if (Core->IsVolumeMounted (*volumePath))
|
|
throw_err (LangString["DISMOUNT_FIRST"]);
|
|
#endif
|
|
|
|
ShowInfo ("EXTERNAL_VOL_HEADER_BAK_FIRST_INFO");
|
|
|
|
shared_ptr <Pkcs5Kdf> kdf;
|
|
if (CmdLine->ArgHash)
|
|
{
|
|
kdf = Pkcs5Kdf::GetAlgorithm (*CmdLine->ArgHash);
|
|
}
|
|
|
|
shared_ptr <Volume> normalVolume;
|
|
shared_ptr <Volume> hiddenVolume;
|
|
|
|
MountOptions normalVolumeMountOptions;
|
|
MountOptions hiddenVolumeMountOptions;
|
|
|
|
normalVolumeMountOptions.Path = volumePath;
|
|
hiddenVolumeMountOptions.Path = volumePath;
|
|
|
|
normalVolumeMountOptions.EMVSupportEnabled = true;
|
|
hiddenVolumeMountOptions.EMVSupportEnabled = true;
|
|
|
|
VolumeType::Enum volumeType = VolumeType::Normal;
|
|
|
|
// Open both types of volumes
|
|
while (true)
|
|
{
|
|
shared_ptr <Volume> volume;
|
|
MountOptions *options = (volumeType == VolumeType::Hidden ? &hiddenVolumeMountOptions : &normalVolumeMountOptions);
|
|
|
|
while (!volume)
|
|
{
|
|
ShowString (L"\n");
|
|
options->Password = AskPassword (LangString[volumeType == VolumeType::Hidden ? "ENTER_HIDDEN_VOL_PASSWORD" : "ENTER_NORMAL_VOL_PASSWORD"]);
|
|
options->Pim = AskPim (volumeType == VolumeType::Hidden ?_("Enter PIM for the hidden volume") : _("Enter PIM for the normal/outer volume"));
|
|
options->Keyfiles = AskKeyfiles();
|
|
|
|
try
|
|
{
|
|
volume = Core->OpenVolume (
|
|
options->Path,
|
|
options->PreserveTimestamps,
|
|
options->Password,
|
|
options->Pim,
|
|
kdf,
|
|
options->Keyfiles,
|
|
options->EMVSupportEnabled,
|
|
options->Protection,
|
|
options->ProtectionPassword,
|
|
options->ProtectionPim,
|
|
options->ProtectionKdf,
|
|
options->ProtectionKeyfiles,
|
|
true,
|
|
volumeType,
|
|
options->UseBackupHeaders
|
|
);
|
|
}
|
|
catch (PasswordException &e)
|
|
{
|
|
bool bFailed = true;
|
|
if (!options->UseBackupHeaders)
|
|
{
|
|
try
|
|
{
|
|
volume = Core->OpenVolume (
|
|
options->Path,
|
|
options->PreserveTimestamps,
|
|
options->Password,
|
|
options->Pim,
|
|
kdf,
|
|
options->Keyfiles,
|
|
options->EMVSupportEnabled,
|
|
options->Protection,
|
|
options->ProtectionPassword,
|
|
options->ProtectionPim,
|
|
options->ProtectionKdf,
|
|
options->ProtectionKeyfiles,
|
|
true,
|
|
volumeType,
|
|
true
|
|
);
|
|
|
|
bFailed = false;
|
|
}
|
|
catch (...)
|
|
{
|
|
}
|
|
}
|
|
|
|
if (bFailed)
|
|
ShowInfo (e);
|
|
else
|
|
ShowInfo ("HEADER_DAMAGED_AUTO_USED_HEADER_BAK");
|
|
}
|
|
}
|
|
|
|
if (volumeType == VolumeType::Hidden)
|
|
hiddenVolume = volume;
|
|
else
|
|
normalVolume = volume;
|
|
|
|
// Ask whether a hidden volume is present
|
|
if (volumeType == VolumeType::Normal && AskYesNo (L"\n" + LangString["DOES_VOLUME_CONTAIN_HIDDEN"]))
|
|
{
|
|
volumeType = VolumeType::Hidden;
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (hiddenVolume)
|
|
{
|
|
if (typeid (*normalVolume->GetLayout()) == typeid (VolumeLayoutV1Normal))
|
|
throw ParameterIncorrect (SRC_POS);
|
|
|
|
if (typeid (*normalVolume->GetLayout()) == typeid (VolumeLayoutV2Normal) && typeid (*hiddenVolume->GetLayout()) != typeid (VolumeLayoutV2Hidden))
|
|
throw ParameterIncorrect (SRC_POS);
|
|
}
|
|
|
|
// Ask user to select backup file path
|
|
wxString confirmMsg = L"\n" + LangString["CONFIRM_VOL_HEADER_BAK"] + L"\n";
|
|
|
|
if (!AskYesNo (wxString::Format (confirmMsg, wstring (*volumePath).c_str()), true))
|
|
return;
|
|
|
|
ShowString (L"\n");
|
|
|
|
FilePath filePath = AskFilePath();
|
|
if (filePath.IsEmpty())
|
|
throw UserAbort (SRC_POS);
|
|
|
|
File backupFile;
|
|
backupFile.Open (filePath, File::CreateWrite);
|
|
|
|
RandomNumberGenerator::Start();
|
|
/* force the display of the random enriching interface */
|
|
RandomNumberGenerator::SetEnrichedByUserStatus (false);
|
|
UserEnrichRandomPool();
|
|
|
|
// Re-encrypt volume header
|
|
SecureBuffer newHeaderBuffer (normalVolume->GetLayout()->GetHeaderSize());
|
|
Core->ReEncryptVolumeHeaderWithNewSalt (newHeaderBuffer, normalVolume->GetHeader(), normalVolumeMountOptions.Password, normalVolumeMountOptions.Pim, normalVolumeMountOptions.Keyfiles, normalVolumeMountOptions.EMVSupportEnabled);
|
|
|
|
backupFile.Write (newHeaderBuffer);
|
|
|
|
if (hiddenVolume)
|
|
{
|
|
// Re-encrypt hidden volume header
|
|
Core->ReEncryptVolumeHeaderWithNewSalt (newHeaderBuffer, hiddenVolume->GetHeader(), hiddenVolumeMountOptions.Password, hiddenVolumeMountOptions.Pim, hiddenVolumeMountOptions.Keyfiles, hiddenVolumeMountOptions.EMVSupportEnabled);
|
|
}
|
|
else
|
|
{
|
|
// Store random data in place of hidden volume header
|
|
shared_ptr <EncryptionAlgorithm> ea = normalVolume->GetEncryptionAlgorithm();
|
|
Core->RandomizeEncryptionAlgorithmKey (ea);
|
|
ea->Encrypt (newHeaderBuffer);
|
|
}
|
|
|
|
backupFile.Write (newHeaderBuffer);
|
|
|
|
ShowString (L"\n");
|
|
ShowInfo ("VOL_HEADER_BACKED_UP");
|
|
}
|
|
|
|
void TextUserInterface::ChangePassword (shared_ptr <VolumePath> volumePath, shared_ptr <VolumePassword> password, int pim, shared_ptr <Hash> currentHash, shared_ptr <KeyfileList> keyfiles, shared_ptr <VolumePassword> newPassword, int newPim, shared_ptr <KeyfileList> newKeyfiles, shared_ptr <Hash> newHash) const
|
|
{
|
|
shared_ptr <Volume> volume;
|
|
|
|
// Volume path
|
|
if (!volumePath.get())
|
|
{
|
|
if (Preferences.NonInteractive)
|
|
throw MissingArgument (SRC_POS);
|
|
|
|
volumePath = AskVolumePath ();
|
|
}
|
|
|
|
if (volumePath->IsEmpty())
|
|
throw UserAbort (SRC_POS);
|
|
|
|
bool passwordInteractive = !password.get();
|
|
bool keyfilesInteractive = !keyfiles.get();
|
|
|
|
shared_ptr<Pkcs5Kdf> kdf;
|
|
if (currentHash)
|
|
{
|
|
kdf = Pkcs5Kdf::GetAlgorithm (*currentHash);
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
// Current password
|
|
if (!passwordInteractive)
|
|
{
|
|
|
|
}
|
|
else if (!Preferences.NonInteractive)
|
|
{
|
|
password = AskPassword ();
|
|
}
|
|
|
|
// current PIM
|
|
if (!Preferences.NonInteractive && (pim < 0))
|
|
{
|
|
pim = AskPim (_("Enter current PIM"));
|
|
}
|
|
|
|
// Current keyfiles
|
|
try
|
|
{
|
|
if (keyfilesInteractive)
|
|
{
|
|
// Ask for keyfiles only if required
|
|
try
|
|
{
|
|
keyfiles.reset (new KeyfileList);
|
|
volume = Core->OpenVolume (volumePath, Preferences.DefaultMountOptions.PreserveTimestamps, password, pim, kdf, keyfiles, true);
|
|
}
|
|
catch (PasswordException&)
|
|
{
|
|
if (!Preferences.NonInteractive)
|
|
keyfiles = AskKeyfiles ();
|
|
}
|
|
}
|
|
|
|
if (!volume.get())
|
|
volume = Core->OpenVolume (volumePath, Preferences.DefaultMountOptions.PreserveTimestamps, password, pim, kdf, keyfiles, true);
|
|
}
|
|
catch (PasswordException &e)
|
|
{
|
|
if (Preferences.NonInteractive || !passwordInteractive || !keyfilesInteractive)
|
|
throw;
|
|
|
|
ShowInfo (e);
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// New password
|
|
if (!newPassword.get() && !Preferences.NonInteractive)
|
|
newPassword = AskPassword (_("Enter new password"), true);
|
|
|
|
// New PIM
|
|
if ((newPim < 0) && !Preferences.NonInteractive)
|
|
newPim = AskPim (_("Enter new PIM"));
|
|
|
|
// New keyfiles
|
|
if (!newKeyfiles.get() && !Preferences.NonInteractive)
|
|
{
|
|
if (keyfiles.get() && keyfiles->size() > 0 && AskYesNo (_("Keep current keyfiles?"), true))
|
|
newKeyfiles = keyfiles;
|
|
else
|
|
newKeyfiles = AskKeyfiles (_("Enter new keyfile"));
|
|
}
|
|
|
|
/* force the display of the random enriching interface */
|
|
RandomNumberGenerator::SetEnrichedByUserStatus (false);
|
|
UserEnrichRandomPool();
|
|
|
|
Core->ChangePassword (volume, newPassword, newPim, newKeyfiles, true,
|
|
newHash ? Pkcs5Kdf::GetAlgorithm (*newHash) : shared_ptr <Pkcs5Kdf>());
|
|
|
|
ShowInfo ("PASSWORD_CHANGED");
|
|
}
|
|
|
|
void TextUserInterface::CreateKeyfile (shared_ptr <FilePath> keyfilePath) const
|
|
{
|
|
FilePath path;
|
|
|
|
RandomNumberGenerator::Start();
|
|
/* force the display of the random enriching interface */
|
|
RandomNumberGenerator::SetEnrichedByUserStatus (false);
|
|
UserEnrichRandomPool();
|
|
|
|
if (keyfilePath)
|
|
{
|
|
Core->CreateKeyfile (*keyfilePath);
|
|
}
|
|
else
|
|
{
|
|
wstring fileName = AskFilePath();
|
|
if (fileName.empty())
|
|
return;
|
|
|
|
Core->CreateKeyfile (fileName);
|
|
}
|
|
|
|
ShowInfo ("KEYFILE_CREATED");
|
|
}
|
|
|
|
void TextUserInterface::CreateVolume (shared_ptr <VolumeCreationOptions> options) const
|
|
{
|
|
// Volume type
|
|
if (options->Type == VolumeType::Unknown)
|
|
{
|
|
if (Preferences.NonInteractive)
|
|
{
|
|
options->Type = VolumeType::Normal;
|
|
}
|
|
else
|
|
{
|
|
ShowString (_("Volume type:\n 1) Normal\n 2) Hidden\n"));
|
|
|
|
switch (AskSelection (2, 1))
|
|
{
|
|
case 1:
|
|
options->Type = VolumeType::Normal;
|
|
break;
|
|
|
|
case 2:
|
|
options->Type = VolumeType::Hidden;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
shared_ptr <VolumeLayout> layout;
|
|
if (options->Type == VolumeType::Hidden)
|
|
layout.reset (new VolumeLayoutV2Hidden);
|
|
else
|
|
layout.reset (new VolumeLayoutV2Normal);
|
|
|
|
if (!Preferences.NonInteractive && options->Type == VolumeType::Hidden)
|
|
ShowInfo (_("\nIMPORTANT: Inexperienced users should use the graphical user interface to create a hidden volume. When using the text interface, the procedure described in the command line help must be followed to create a hidden volume."));
|
|
|
|
// Volume path
|
|
if (options->Path.IsEmpty())
|
|
{
|
|
if (Preferences.NonInteractive)
|
|
throw MissingArgument (SRC_POS);
|
|
|
|
do
|
|
{
|
|
ShowString (L"\n");
|
|
options->Path = VolumePath (*AskVolumePath());
|
|
} while (options->Path.IsEmpty());
|
|
}
|
|
|
|
// Sector size
|
|
if (options->Path.IsDevice())
|
|
options->SectorSize = Core->GetDeviceSectorSize (options->Path);
|
|
else
|
|
options->SectorSize = TC_SECTOR_SIZE_FILE_HOSTED_VOLUME;
|
|
|
|
// Volume size
|
|
uint64 hostSize = 0;
|
|
|
|
if (options->Type == VolumeType::Hidden)
|
|
{
|
|
FilesystemPath fsPath (wstring (options->Path));
|
|
|
|
if (fsPath.IsFile())
|
|
{
|
|
File file;
|
|
file.Open (fsPath);
|
|
hostSize = file.Length();
|
|
}
|
|
else if (fsPath.IsDevice())
|
|
{
|
|
hostSize = Core->GetDeviceSize (fsPath);
|
|
}
|
|
else
|
|
{
|
|
throw_err (_("Hidden volume can be created only in an existing file or device."));
|
|
}
|
|
|
|
if (hostSize < TC_MIN_HIDDEN_VOLUME_HOST_SIZE)
|
|
throw_err (StringFormatter (_("Minimum outer volume size is {0}."), SizeToString (TC_MIN_HIDDEN_VOLUME_HOST_SIZE)));
|
|
}
|
|
|
|
uint64 minVolumeSize = options->Type == VolumeType::Hidden ? TC_MIN_HIDDEN_VOLUME_SIZE : TC_MIN_VOLUME_SIZE;
|
|
uint64 maxVolumeSize = options->Type == VolumeType::Hidden ? VolumeLayoutV2Normal().GetMaxDataSize (hostSize) - TC_MIN_FAT_FS_SIZE : TC_MAX_VOLUME_SIZE_GENERAL;
|
|
|
|
if (options->Path.IsDevice() && options->Type != VolumeType::Hidden)
|
|
{
|
|
if (options->Size != 0)
|
|
throw_err (_("Volume size cannot be changed for device-hosted volumes."));
|
|
|
|
options->Size = Core->GetDeviceSize (options->Path);
|
|
}
|
|
else
|
|
{
|
|
uint64 AvailableDiskSpace = 0;
|
|
if (options->Path.IsDevice())
|
|
{
|
|
AvailableDiskSpace = maxVolumeSize;
|
|
}
|
|
else
|
|
{
|
|
wxLongLong diskSpace = 0;
|
|
wxString parentDir = wxFileName (wstring (options->Path)).GetPath();
|
|
if (parentDir.IsEmpty())
|
|
{
|
|
parentDir = wxT(".");
|
|
}
|
|
if (options->Type == VolumeType::Normal && wxDirExists(parentDir) && wxGetDiskSpace (parentDir, nullptr, &diskSpace))
|
|
{
|
|
AvailableDiskSpace = (uint64) diskSpace.GetValue ();
|
|
if (maxVolumeSize > AvailableDiskSpace)
|
|
maxVolumeSize = AvailableDiskSpace;
|
|
}
|
|
}
|
|
|
|
if (options->Size == (uint64) (-1))
|
|
{
|
|
if (options->Type == VolumeType::Hidden) {
|
|
throw_err (_("Please do not use maximum size for hidden volume. As we do not mount the outer volume to determine the available space, it is your responsibility to choose a value so that the hidden volume does not overlap the outer volume."));
|
|
}
|
|
else if (AvailableDiskSpace)
|
|
{
|
|
// caller requesting maximum size
|
|
// we use maxVolumeSize because it is guaranteed to be less or equal to AvailableDiskSpace for outer volumes
|
|
options->Size = maxVolumeSize;
|
|
}
|
|
else
|
|
{
|
|
throw_err (_("Failed to get available disk space on the selected target."));
|
|
}
|
|
}
|
|
|
|
options->Quick = false;
|
|
|
|
uint32 sectorSizeRem = options->Size % options->SectorSize;
|
|
if (sectorSizeRem != 0)
|
|
options->Size += options->SectorSize - sectorSizeRem;
|
|
|
|
while (options->Size == 0)
|
|
{
|
|
if (Preferences.NonInteractive)
|
|
throw MissingArgument (SRC_POS);
|
|
|
|
uint64 multiplier = 1024 * 1024;
|
|
wxString sizeStr = AskString (options->Type == VolumeType::Hidden ? _("\nEnter hidden volume size (sizeK/size[M]/sizeG/sizeT): ") : _("\nEnter volume size (sizeK/size[M]/sizeG.sizeT/max): "));
|
|
if (sizeStr.CmpNoCase(wxT("max")) == 0)
|
|
{
|
|
multiplier = 1;
|
|
if (options->Type == VolumeType::Hidden) {
|
|
throw_err (_("Please do not use maximum size for hidden volume. As we do not mount the outer volume to determine the available space, it is your responsibility to choose a value so that the hidden volume does not overlap the outer volume."));
|
|
}
|
|
else if (AvailableDiskSpace)
|
|
{
|
|
// caller requesting maximum size
|
|
// we use maxVolumeSize because it is guaranteed to be less or equal to AvailableDiskSpace for outer volumes
|
|
options->Size = maxVolumeSize;
|
|
}
|
|
else
|
|
{
|
|
throw_err (_("Failed to get available disk space on the selected target."));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
multiplier = 1024 * 1024;
|
|
size_t index = sizeStr.find_first_not_of (wxT("0123456789"));
|
|
if (index == 0)
|
|
{
|
|
continue;
|
|
}
|
|
else if (index != (size_t) wxNOT_FOUND)
|
|
{
|
|
wxString sizeSuffix = sizeStr.Mid(index);
|
|
if (sizeSuffix.CmpNoCase(wxT("K")) == 0 || sizeSuffix.CmpNoCase(wxT("KiB")) == 0)
|
|
multiplier = BYTES_PER_KB;
|
|
else if (sizeSuffix.CmpNoCase(wxT("M")) == 0 || sizeSuffix.CmpNoCase(wxT("MiB")) == 0)
|
|
multiplier = BYTES_PER_MB;
|
|
else if (sizeSuffix.CmpNoCase(wxT("G")) == 0 || sizeSuffix.CmpNoCase(wxT("GiB")) == 0)
|
|
multiplier = BYTES_PER_GB;
|
|
else if (sizeSuffix.CmpNoCase(wxT("T")) == 0 || sizeSuffix.CmpNoCase(wxT("TiB")) == 0)
|
|
multiplier = BYTES_PER_TB;
|
|
else
|
|
continue;
|
|
|
|
sizeStr = sizeStr.Left (index);
|
|
}
|
|
|
|
try
|
|
{
|
|
options->Size = StringConverter::ToUInt64 (wstring(sizeStr));
|
|
}
|
|
catch (...)
|
|
{
|
|
options->Size = 0;
|
|
continue;
|
|
}
|
|
}
|
|
options->Size *= multiplier;
|
|
|
|
sectorSizeRem = options->Size % options->SectorSize;
|
|
if (sectorSizeRem != 0)
|
|
options->Size += options->SectorSize - sectorSizeRem;
|
|
|
|
if (options->Size < minVolumeSize)
|
|
{
|
|
ShowError (StringFormatter (_("Minimum volume size is {0}."), SizeToString (minVolumeSize)));
|
|
options->Size = 0;
|
|
}
|
|
|
|
if (options->Size > maxVolumeSize)
|
|
{
|
|
ShowError (StringFormatter (_("Maximum volume size is {0}."), SizeToString (maxVolumeSize)));
|
|
options->Size = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (options->Size < minVolumeSize || options->Size > maxVolumeSize)
|
|
throw_err (_("Incorrect volume size"));
|
|
|
|
if (options->Type == VolumeType::Hidden)
|
|
options->Quick = true;
|
|
|
|
// Encryption algorithm
|
|
if (!options->EA)
|
|
{
|
|
if (Preferences.NonInteractive)
|
|
throw MissingArgument (SRC_POS);
|
|
|
|
ShowInfo (wxString (L"\n") + LangString["ENCRYPTION_ALGORITHM_LV"] + L":");
|
|
|
|
vector < shared_ptr <EncryptionAlgorithm> > encryptionAlgorithms;
|
|
foreach (shared_ptr <EncryptionAlgorithm> ea, EncryptionAlgorithm::GetAvailableAlgorithms())
|
|
{
|
|
if (!ea->IsDeprecated())
|
|
{
|
|
ShowString (StringFormatter (L" {0}) {1}\n", (uint32) encryptionAlgorithms.size() + 1, ea->GetName(true)));
|
|
encryptionAlgorithms.push_back (ea);
|
|
}
|
|
}
|
|
|
|
|
|
options->EA = encryptionAlgorithms[AskSelection (encryptionAlgorithms.size(), 1) - 1];
|
|
}
|
|
|
|
// Hash algorithm
|
|
if (!options->VolumeHeaderKdf)
|
|
{
|
|
if (Preferences.NonInteractive)
|
|
throw MissingArgument (SRC_POS);
|
|
|
|
ShowInfo (_("\nHash algorithm:"));
|
|
|
|
vector < shared_ptr <Hash> > hashes;
|
|
foreach (shared_ptr <Hash> hash, Hash::GetAvailableAlgorithms())
|
|
{
|
|
if (!hash->IsDeprecated())
|
|
{
|
|
ShowString (StringFormatter (L" {0}) {1}\n", (uint32) hashes.size() + 1, hash->GetName()));
|
|
hashes.push_back (hash);
|
|
}
|
|
}
|
|
|
|
shared_ptr <Hash> selectedHash = hashes[AskSelection (hashes.size(), 1) - 1];
|
|
RandomNumberGenerator::SetHash (selectedHash);
|
|
options->VolumeHeaderKdf = Pkcs5Kdf::GetAlgorithm (*selectedHash);
|
|
|
|
}
|
|
|
|
// Filesystem
|
|
options->FilesystemClusterSize = 0;
|
|
uint64 filesystemSize = layout->GetMaxDataSize (options->Size);
|
|
|
|
if (options->Filesystem == VolumeCreationOptions::FilesystemType::Unknown)
|
|
{
|
|
if (Preferences.NonInteractive)
|
|
{
|
|
options->Filesystem = VolumeCreationOptions::FilesystemType::GetPlatformNative();
|
|
}
|
|
else
|
|
{
|
|
ShowInfo (_("\nFilesystem:"));
|
|
|
|
vector <VolumeCreationOptions::FilesystemType::Enum> filesystems;
|
|
|
|
ShowInfo (wxString::Format (L" %li) %s", filesystems.size() + 1, LangString["NONE"])); filesystems.push_back (VolumeCreationOptions::FilesystemType::None);
|
|
ShowInfo (wxString::Format (L" %li) %s", filesystems.size() + 1, "FAT")); filesystems.push_back (VolumeCreationOptions::FilesystemType::FAT);
|
|
#if defined (TC_LINUX)
|
|
ShowInfo (wxString::Format (L" %li) %s", filesystems.size() + 1, "Linux Ext2")); filesystems.push_back (VolumeCreationOptions::FilesystemType::Ext2);
|
|
ShowInfo (wxString::Format (L" %li) %s", filesystems.size() + 1, "Linux Ext3")); filesystems.push_back (VolumeCreationOptions::FilesystemType::Ext3);
|
|
ShowInfo (wxString::Format (L" %li) %s", filesystems.size() + 1, "Linux Ext4")); filesystems.push_back (VolumeCreationOptions::FilesystemType::Ext4);
|
|
ShowInfo (wxString::Format (L" %li) %s", filesystems.size() + 1, "NTFS")); filesystems.push_back (VolumeCreationOptions::FilesystemType::NTFS);
|
|
if (VolumeCreationOptions::FilesystemType::IsFsFormatterPresent (VolumeCreationOptions::FilesystemType::exFAT))
|
|
{
|
|
ShowInfo (wxString::Format (L" %li) %s", filesystems.size() + 1, "exFAT")); filesystems.push_back (VolumeCreationOptions::FilesystemType::exFAT);
|
|
}
|
|
if (VolumeCreationOptions::FilesystemType::IsFsFormatterPresent (VolumeCreationOptions::FilesystemType::Btrfs))
|
|
{
|
|
// minimum size to be able to format as Btrfs is 16777216 bytes
|
|
if (filesystemSize >= VC_MIN_SMALL_BTRFS_VOLUME_SIZE)
|
|
{
|
|
ShowInfo (wxString::Format (L" %li) %s", filesystems.size() + 1, "Btrfs")); filesystems.push_back (VolumeCreationOptions::FilesystemType::Btrfs);
|
|
}
|
|
}
|
|
#elif defined (TC_MACOSX)
|
|
ShowInfo (wxString::Format (L" %li) %s", filesystems.size() + 1, "Mac OS Extended")); filesystems.push_back (VolumeCreationOptions::FilesystemType::MacOsExt);
|
|
ShowInfo (wxString::Format (L" %li) %s", filesystems.size() + 1, "exFAT")); filesystems.push_back (VolumeCreationOptions::FilesystemType::exFAT);
|
|
if (wxPlatformInfo::Get().CheckOSVersion (10, 13))
|
|
{
|
|
ShowInfo (wxString::Format (L" %li) %s", filesystems.size() + 1, "APFS")); filesystems.push_back (VolumeCreationOptions::FilesystemType::APFS);
|
|
}
|
|
#elif defined (TC_FREEBSD) || defined (TC_SOLARIS)
|
|
ShowInfo (wxString::Format (L" %li) %s", filesystems.size() + 1, "UFS")); filesystems.push_back (VolumeCreationOptions::FilesystemType::UFS);
|
|
#endif
|
|
|
|
options->Filesystem = filesystems[AskSelection (filesystems.size(), 2) - 1];
|
|
}
|
|
}
|
|
|
|
if (options->Filesystem == VolumeCreationOptions::FilesystemType::FAT
|
|
&& (filesystemSize < TC_MIN_FAT_FS_SIZE || filesystemSize > TC_MAX_FAT_SECTOR_COUNT * options->SectorSize))
|
|
{
|
|
throw_err (_("Specified volume size cannot be used with FAT filesystem."));
|
|
}
|
|
|
|
if (options->Filesystem == VolumeCreationOptions::FilesystemType::Btrfs
|
|
&& (filesystemSize < VC_MIN_SMALL_BTRFS_VOLUME_SIZE))
|
|
{
|
|
throw_err (_("Specified volume size is too small to be used with Btrfs filesystem."));
|
|
}
|
|
|
|
// Password
|
|
if (!options->Password && !Preferences.NonInteractive)
|
|
{
|
|
ShowString (L"\n");
|
|
options->Password = AskPassword (_("Enter password"), true);
|
|
}
|
|
|
|
// PIM
|
|
if ((options->Pim < 0) && !Preferences.NonInteractive)
|
|
{
|
|
ShowString (L"\n");
|
|
options->Pim = AskPim (_("Enter PIM"));
|
|
}
|
|
|
|
// Keyfiles
|
|
if (!options->Keyfiles && !Preferences.NonInteractive)
|
|
{
|
|
ShowString (L"\n");
|
|
options->Keyfiles = AskKeyfiles (_("Enter keyfile path"));
|
|
}
|
|
|
|
if ((!options->Keyfiles || options->Keyfiles->empty())
|
|
&& (!options->Password || options->Password->IsEmpty()))
|
|
{
|
|
throw_err (_("Password cannot be empty when no keyfile is specified"));
|
|
}
|
|
|
|
// Random data
|
|
RandomNumberGenerator::Start();
|
|
/* force the display of the random enriching interface */
|
|
RandomNumberGenerator::SetEnrichedByUserStatus (false);
|
|
UserEnrichRandomPool();
|
|
|
|
ShowString (L"\n");
|
|
wxLongLong startTime = wxGetLocalTimeMillis();
|
|
|
|
VolumeCreator creator;
|
|
options->EMVSupportEnabled = true;
|
|
creator.CreateVolume (options);
|
|
|
|
bool volumeCreated = false;
|
|
while (!volumeCreated)
|
|
{
|
|
VolumeCreator::ProgressInfo progress = creator.GetProgressInfo();
|
|
|
|
wxLongLong timeDiff = wxGetLocalTimeMillis() - startTime;
|
|
if (timeDiff.GetValue() > 0)
|
|
{
|
|
uint64 speed = progress.SizeDone * 1000 / timeDiff.GetValue();
|
|
|
|
volumeCreated = !progress.CreationInProgress;
|
|
|
|
ShowString (wxString::Format (L"\rDone: %7.3f%% Speed: %9s Left: %s ",
|
|
100.0 - double (options->Size - progress.SizeDone) / (double (options->Size) / 100.0),
|
|
speed > 0 ? (const wchar_t*) SpeedToString (speed).c_str() : L" ",
|
|
speed > 0 ? (const wchar_t*) TimeSpanToString ((options->Size - progress.SizeDone) / speed).c_str() : L""));
|
|
}
|
|
|
|
Thread::Sleep (100);
|
|
}
|
|
|
|
ShowString (L"\n\n");
|
|
creator.CheckResult();
|
|
|
|
#ifdef TC_UNIX
|
|
if (options->Filesystem != VolumeCreationOptions::FilesystemType::None
|
|
&& options->Filesystem != VolumeCreationOptions::FilesystemType::FAT)
|
|
{
|
|
const char *fsFormatter = VolumeCreationOptions::FilesystemType::GetFsFormatter (options->Filesystem);
|
|
if (!fsFormatter)
|
|
throw ParameterIncorrect (SRC_POS);
|
|
|
|
MountOptions mountOptions (GetPreferences().DefaultMountOptions);
|
|
mountOptions.Path = make_shared <VolumePath> (options->Path);
|
|
mountOptions.NoFilesystem = true;
|
|
mountOptions.Protection = VolumeProtection::None;
|
|
mountOptions.Password = options->Password;
|
|
mountOptions.Pim = options->Pim;
|
|
mountOptions.Keyfiles = options->Keyfiles;
|
|
mountOptions.EMVSupportEnabled = true;
|
|
|
|
shared_ptr <VolumeInfo> volume = Core->MountVolume (mountOptions);
|
|
finally_do_arg (shared_ptr <VolumeInfo>, volume, { Core->DismountVolume (finally_arg, true); });
|
|
|
|
Thread::Sleep (2000); // Try to prevent race conditions caused by OS
|
|
|
|
// Temporarily take ownership of the device if the user is not an administrator
|
|
UserId origDeviceOwner ((uid_t) -1);
|
|
|
|
DevicePath virtualDevice = volume->VirtualDevice;
|
|
#ifdef TC_MACOSX
|
|
string virtualDeviceStr = virtualDevice;
|
|
if (virtualDeviceStr.find ("/dev/rdisk") != 0)
|
|
virtualDevice = "/dev/r" + virtualDeviceStr.substr (5);
|
|
#endif
|
|
try
|
|
{
|
|
File file;
|
|
file.Open (virtualDevice, File::OpenReadWrite);
|
|
}
|
|
catch (...)
|
|
{
|
|
if (!Core->HasAdminPrivileges())
|
|
{
|
|
origDeviceOwner = virtualDevice.GetOwner();
|
|
Core->SetFileOwner (virtualDevice, UserId (getuid()));
|
|
}
|
|
}
|
|
|
|
finally_do_arg2 (FilesystemPath, virtualDevice, UserId, origDeviceOwner,
|
|
{
|
|
if (finally_arg2.SystemId != (uid_t) -1)
|
|
Core->SetFileOwner (finally_arg, finally_arg2);
|
|
});
|
|
|
|
// Create filesystem
|
|
list <string> args;
|
|
|
|
if (options->Filesystem == VolumeCreationOptions::FilesystemType::MacOsExt && options->Size >= 10 * BYTES_PER_MB)
|
|
args.push_back ("-J");
|
|
|
|
// Perform a quick NTFS formatting
|
|
if (options->Filesystem == VolumeCreationOptions::FilesystemType::NTFS)
|
|
args.push_back ("-f");
|
|
|
|
if (options->Filesystem == VolumeCreationOptions::FilesystemType::Btrfs)
|
|
{
|
|
args.push_back ("-f");
|
|
if (filesystemSize < VC_MIN_LARGE_BTRFS_VOLUME_SIZE)
|
|
{
|
|
// use mixed mode for small BTRFS volumes
|
|
args.push_back ("-M");
|
|
}
|
|
}
|
|
|
|
args.push_back (string (virtualDevice));
|
|
|
|
Process::Execute (fsFormatter, args);
|
|
}
|
|
#endif // TC_UNIX
|
|
|
|
ShowInfo (options->Type == VolumeType::Hidden ? "HIDVOL_FORMAT_FINISHED_HELP" : "FORMAT_FINISHED_INFO");
|
|
}
|
|
|
|
void TextUserInterface::DeleteSecurityTokenKeyfiles () const
|
|
{
|
|
shared_ptr <KeyfileList> keyfiles = AskKeyfiles();
|
|
if (keyfiles->empty())
|
|
throw UserAbort();
|
|
|
|
foreach_ref (const Keyfile &keyfile, *keyfiles)
|
|
{
|
|
SecurityToken::DeleteKeyfile (TokenKeyfilePath (FilePath (keyfile)));
|
|
}
|
|
}
|
|
|
|
void TextUserInterface::DoShowError (const wxString &message) const
|
|
{
|
|
wcerr << L"Error: " << static_cast<wstring> (message) << endl;
|
|
}
|
|
|
|
void TextUserInterface::DoShowInfo (const wxString &message) const
|
|
{
|
|
wcout << static_cast<wstring> (message) << endl;
|
|
}
|
|
|
|
void TextUserInterface::DoShowString (const wxString &str) const
|
|
{
|
|
wcout << str.c_str() << flush;
|
|
}
|
|
|
|
void TextUserInterface::DoShowWarning (const wxString &message) const
|
|
{
|
|
wcerr << L"Warning: " << static_cast<wstring> (message) << endl;
|
|
}
|
|
|
|
void TextUserInterface::ExportTokenKeyfile () const
|
|
{
|
|
wstring keyfilePath = AskString (_("Enter token keyfile path: "));
|
|
|
|
if (keyfilePath.empty())
|
|
throw UserAbort (SRC_POS);
|
|
|
|
shared_ptr<TokenKeyfile> tokenKeyfile = Token::getTokenKeyfile(keyfilePath);
|
|
|
|
vector <byte> keyfileData;
|
|
tokenKeyfile->GetKeyfileData (keyfileData);
|
|
|
|
BufferPtr keyfileDataBuf (&keyfileData.front(), keyfileData.size());
|
|
finally_do_arg (BufferPtr, keyfileDataBuf, { finally_arg.Erase(); });
|
|
|
|
FilePath exportFilePath = AskFilePath();
|
|
|
|
if (exportFilePath.IsEmpty())
|
|
throw UserAbort (SRC_POS);
|
|
|
|
File keyfile;
|
|
keyfile.Open (exportFilePath, File::CreateWrite);
|
|
keyfile.Write (keyfileDataBuf);
|
|
}
|
|
|
|
shared_ptr <GetStringFunctor> TextUserInterface::GetAdminPasswordRequestHandler ()
|
|
{
|
|
struct AdminPasswordRequestHandler : public GetStringFunctor
|
|
{
|
|
AdminPasswordRequestHandler (TextUserInterface *userInterface) : UI (userInterface) { }
|
|
virtual void operator() (string &passwordStr)
|
|
{
|
|
UI->ShowString (_("Enter your user password or administrator password: "));
|
|
|
|
TextUserInterface::SetTerminalEcho (false);
|
|
finally_do ({ TextUserInterface::SetTerminalEcho (true); });
|
|
|
|
wstring wPassword (UI->ReadInputStreamLine());
|
|
finally_do_arg (wstring *, &wPassword, { StringConverter::Erase (*finally_arg); });
|
|
|
|
UI->ShowString (L"\n");
|
|
|
|
StringConverter::ToSingle (wPassword, passwordStr);
|
|
}
|
|
TextUserInterface *UI;
|
|
};
|
|
|
|
return shared_ptr <GetStringFunctor> (new AdminPasswordRequestHandler (this));
|
|
}
|
|
|
|
void TextUserInterface::ImportTokenKeyfiles () const
|
|
{
|
|
list <shared_ptr<TokenInfo>> tokens = Token::GetAvailableTokens();
|
|
|
|
if (tokens.empty())
|
|
throw_err (LangString ["NO_TOKENS_FOUND"]);
|
|
|
|
CK_SLOT_ID slotId;
|
|
|
|
if (tokens.size() == 1)
|
|
{
|
|
slotId = tokens.front()->SlotId;
|
|
}
|
|
else
|
|
{
|
|
foreach (const shared_ptr<TokenInfo> &token, tokens)
|
|
{
|
|
wstringstream tokenLabel;
|
|
tokenLabel << L"[" << token->SlotId << L"] " << LangString["TOKEN_SLOT_ID"].c_str() << L" " << token->SlotId << L" " << token->Label;
|
|
|
|
ShowInfo (tokenLabel.str());
|
|
}
|
|
|
|
slotId = (CK_SLOT_ID) AskSelection (tokens.back()->SlotId, tokens.front()->SlotId);
|
|
}
|
|
|
|
shared_ptr <KeyfileList> keyfiles;
|
|
|
|
if (CmdLine->ArgKeyfiles.get() && !CmdLine->ArgKeyfiles->empty())
|
|
keyfiles = CmdLine->ArgKeyfiles;
|
|
else if (!Preferences.NonInteractive)
|
|
{
|
|
keyfiles = AskKeyfiles();
|
|
if (keyfiles->empty())
|
|
throw UserAbort();
|
|
}
|
|
else
|
|
throw MissingArgument (SRC_POS);
|
|
|
|
foreach_ref (const Keyfile &keyfilePath, *keyfiles)
|
|
{
|
|
File keyfile;
|
|
keyfile.Open (keyfilePath, File::OpenRead, File::ShareReadWrite, File::PreserveTimestamps);
|
|
|
|
if (keyfile.Length() > 0)
|
|
{
|
|
vector <byte> keyfileData (keyfile.Length());
|
|
BufferPtr keyfileDataBuf (&keyfileData.front(), keyfileData.size());
|
|
|
|
keyfile.ReadCompleteBuffer (keyfileDataBuf);
|
|
finally_do_arg (BufferPtr, keyfileDataBuf, { finally_arg.Erase(); });
|
|
|
|
SecurityToken::CreateKeyfile (slotId, keyfileData, string (FilePath (keyfilePath).ToBaseName()));
|
|
}
|
|
else
|
|
throw InsufficientData (SRC_POS, FilePath (keyfilePath));
|
|
}
|
|
}
|
|
|
|
void TextUserInterface::InitSecurityTokenLibrary () const
|
|
{
|
|
if (Preferences.SecurityTokenModule.IsEmpty())
|
|
throw_err (LangString ["NO_PKCS11_MODULE_SPECIFIED"]);
|
|
|
|
struct PinRequestHandler : public GetPinFunctor
|
|
{
|
|
PinRequestHandler (const TextUserInterface *userInterface) : UI (userInterface) { }
|
|
|
|
virtual void operator() (string &passwordStr)
|
|
{
|
|
if (CmdLine->ArgTokenPin && CmdLine->ArgTokenPin->IsAllocated ())
|
|
{
|
|
passwordStr.clear();
|
|
passwordStr.insert (0, (char*) CmdLine->ArgTokenPin->Ptr (), CmdLine->ArgTokenPin->Size());
|
|
return;
|
|
}
|
|
|
|
if (UI->GetPreferences().NonInteractive)
|
|
throw MissingArgument (SRC_POS);
|
|
|
|
UI->ShowString (wxString::Format (LangString["ENTER_TOKEN_PASSWORD"], StringConverter::ToWide (passwordStr).c_str()) + L" ");
|
|
|
|
TextUserInterface::SetTerminalEcho (false);
|
|
finally_do ({ TextUserInterface::SetTerminalEcho (true); });
|
|
|
|
wstring wPassword (UI->ReadInputStreamLine());
|
|
finally_do_arg (wstring *, &wPassword, { StringConverter::Erase (*finally_arg); });
|
|
|
|
UI->ShowString (L"\n");
|
|
|
|
StringConverter::ToSingle (wPassword, passwordStr);
|
|
}
|
|
|
|
virtual void notifyIncorrectPin ()
|
|
{
|
|
if (CmdLine->ArgTokenPin && CmdLine->ArgTokenPin->IsAllocated ())
|
|
{
|
|
CmdLine->ArgTokenPin->Free ();
|
|
}
|
|
}
|
|
|
|
const TextUserInterface *UI;
|
|
};
|
|
|
|
struct WarningHandler : public SendExceptionFunctor
|
|
{
|
|
WarningHandler (const TextUserInterface *userInterface) : UI (userInterface) { }
|
|
|
|
virtual void operator() (const Exception &e)
|
|
{
|
|
UI->ShowError (e);
|
|
}
|
|
|
|
const TextUserInterface *UI;
|
|
};
|
|
|
|
try
|
|
{
|
|
SecurityToken::InitLibrary (Preferences.SecurityTokenModule, unique_ptr <GetPinFunctor> (new PinRequestHandler (this)), unique_ptr <SendExceptionFunctor> (new WarningHandler (this)));
|
|
}
|
|
catch (Exception &e)
|
|
{
|
|
ShowError (e);
|
|
throw_err (LangString ["PKCS11_MODULE_INIT_FAILED"]);
|
|
}
|
|
}
|
|
|
|
void TextUserInterface::ListTokenKeyfiles () const
|
|
{
|
|
foreach (const shared_ptr<TokenKeyfile> keyfile, Token::GetAvailableKeyfiles(true))
|
|
{
|
|
ShowString (wstring (TokenKeyfilePath (*keyfile)));
|
|
ShowString (L"\n");
|
|
}
|
|
}
|
|
void TextUserInterface::ListSecurityTokenKeyfiles () const
|
|
{
|
|
foreach (const TokenKeyfile &keyfile, SecurityToken::GetAvailableKeyfiles())
|
|
{
|
|
ShowString (wstring (TokenKeyfilePath (keyfile)));
|
|
ShowString (L"\n");
|
|
}
|
|
}
|
|
void TextUserInterface::ListEMVTokenKeyfiles () const
|
|
{
|
|
foreach (const TokenKeyfile &keyfile, EMVToken::GetAvailableKeyfiles())
|
|
{
|
|
ShowString (wstring (TokenKeyfilePath (keyfile)));
|
|
ShowString (L"\n");
|
|
}
|
|
}
|
|
|
|
VolumeInfoList TextUserInterface::MountAllDeviceHostedVolumes (MountOptions &options) const
|
|
{
|
|
while (true)
|
|
{
|
|
if (!options.Password)
|
|
options.Password = AskPassword();
|
|
|
|
if (options.Pim < 0)
|
|
options.Pim = AskPim (_("Enter PIM"));
|
|
|
|
if (!options.Keyfiles)
|
|
options.Keyfiles = AskKeyfiles();
|
|
|
|
options.EMVSupportEnabled = true;
|
|
|
|
VolumeInfoList mountedVolumes = UserInterface::MountAllDeviceHostedVolumes (options);
|
|
|
|
if (!mountedVolumes.empty())
|
|
return mountedVolumes;
|
|
|
|
options.Password.reset();
|
|
options.Pim = -1;
|
|
}
|
|
}
|
|
|
|
shared_ptr <VolumeInfo> TextUserInterface::MountVolume (MountOptions &options) const
|
|
{
|
|
shared_ptr <VolumeInfo> volume;
|
|
|
|
CheckRequirementsForMountingVolume();
|
|
|
|
// Volume path
|
|
while (!options.Path || options.Path->IsEmpty())
|
|
{
|
|
if (Preferences.NonInteractive)
|
|
throw MissingArgument (SRC_POS);
|
|
|
|
options.Path = AskVolumePath ();
|
|
}
|
|
|
|
if (Core->IsVolumeMounted (*options.Path))
|
|
{
|
|
ShowInfo (StringFormatter (LangString["VOLUME_ALREADY_MOUNTED"], wstring (*options.Path)));
|
|
return volume;
|
|
}
|
|
|
|
// Mount point
|
|
if (!options.MountPoint && !options.NoFilesystem)
|
|
options.MountPoint.reset (new DirectoryPath (AskString (_("Enter mount directory [default]: "))));
|
|
|
|
VolumePassword password;
|
|
KeyfileList keyfiles;
|
|
|
|
options.EMVSupportEnabled = true;
|
|
|
|
if ((!options.Password || options.Password->IsEmpty())
|
|
&& (!options.Keyfiles || options.Keyfiles->empty())
|
|
&& !Core->IsPasswordCacheEmpty())
|
|
{
|
|
// Cached password
|
|
try
|
|
{
|
|
volume = UserInterface::MountVolume (options);
|
|
}
|
|
catch (PasswordException&) { }
|
|
}
|
|
|
|
int incorrectPasswordCount = 0;
|
|
|
|
while (!volume)
|
|
{
|
|
// Password
|
|
if (!options.Password)
|
|
{
|
|
options.Password = AskPassword (StringFormatter (_("Enter password for {0}"), wstring (*options.Path)));
|
|
}
|
|
|
|
if (options.Pim < 0)
|
|
{
|
|
options.Pim = AskPim (StringFormatter (_("Enter PIM for {0}"), wstring (*options.Path)));
|
|
}
|
|
|
|
// Keyfiles
|
|
if (!options.Keyfiles)
|
|
options.Keyfiles = AskKeyfiles();
|
|
|
|
// Hidden volume protection
|
|
if (options.Protection == VolumeProtection::None
|
|
&& !CmdLine->ArgNoHiddenVolumeProtection
|
|
&& AskYesNo (_("Protect hidden volume (if any)?")))
|
|
options.Protection = VolumeProtection::HiddenVolumeReadOnly;
|
|
|
|
if (options.Protection == VolumeProtection::HiddenVolumeReadOnly)
|
|
{
|
|
if (!options.ProtectionPassword)
|
|
options.ProtectionPassword = AskPassword (_("Enter password for hidden volume"));
|
|
if (options.ProtectionPim < 0)
|
|
options.ProtectionPim = AskPim (_("Enter PIM for hidden volume"));
|
|
if (!options.ProtectionKeyfiles)
|
|
options.ProtectionKeyfiles = AskKeyfiles (_("Enter keyfile for hidden volume"));
|
|
}
|
|
|
|
try
|
|
{
|
|
volume = UserInterface::MountVolume (options);
|
|
}
|
|
catch (ProtectionPasswordIncorrect &e)
|
|
{
|
|
ShowInfo (e);
|
|
options.ProtectionPassword.reset();
|
|
options.ProtectionPim = -1;
|
|
}
|
|
catch (PasswordIncorrect &e)
|
|
{
|
|
if (++incorrectPasswordCount > 2 && !options.UseBackupHeaders)
|
|
{
|
|
// Try to mount the volume using the backup header
|
|
options.UseBackupHeaders = true;
|
|
|
|
try
|
|
{
|
|
volume = UserInterface::MountVolume (options);
|
|
ShowWarning ("HEADER_DAMAGED_AUTO_USED_HEADER_BAK");
|
|
}
|
|
catch (...)
|
|
{
|
|
options.UseBackupHeaders = false;
|
|
ShowInfo (e);
|
|
options.Password.reset();
|
|
options.Pim = -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ShowInfo (e);
|
|
options.Password.reset();
|
|
options.Pim = -1;
|
|
}
|
|
|
|
ShowString (L"\n");
|
|
}
|
|
catch (PasswordException &e)
|
|
{
|
|
ShowInfo (e);
|
|
options.Password.reset();
|
|
}
|
|
}
|
|
|
|
#ifdef TC_LINUX
|
|
if (!Preferences.NonInteractive && !Preferences.DisableKernelEncryptionModeWarning
|
|
&& volume->EncryptionModeName != L"XTS")
|
|
{
|
|
ShowWarning (LangString["ENCRYPTION_MODE_NOT_SUPPORTED_BY_KERNEL"]);
|
|
}
|
|
#endif
|
|
|
|
return volume;
|
|
}
|
|
|
|
bool TextUserInterface::OnInit ()
|
|
{
|
|
try
|
|
{
|
|
DefaultMessageOutput = new wxMessageOutputStderr;
|
|
wxMessageOutput::Set (DefaultMessageOutput);
|
|
|
|
InterfaceType = UserInterfaceType::Text;
|
|
Init();
|
|
}
|
|
catch (exception &e)
|
|
{
|
|
ShowError (e);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int TextUserInterface::OnRun()
|
|
{
|
|
try
|
|
{
|
|
if (ProcessCommandLine ())
|
|
{
|
|
Application::SetExitCode (0);
|
|
return 0;
|
|
}
|
|
}
|
|
catch (exception &e)
|
|
{
|
|
ShowError (e);
|
|
}
|
|
|
|
Application::SetExitCode (1);
|
|
return 1;
|
|
}
|
|
|
|
void TextUserInterface::OnSignal (int signal)
|
|
{
|
|
#ifdef TC_UNIX
|
|
try
|
|
{
|
|
SetTerminalEcho (true);
|
|
}
|
|
catch (...) { }
|
|
_exit (1);
|
|
#endif
|
|
}
|
|
|
|
void TextUserInterface::ReadInputStreamLine (wxString &line) const
|
|
{
|
|
if (!TextInputStream.get() || feof (stdin) || ferror (stdin))
|
|
throw UserAbort (SRC_POS);
|
|
|
|
line = TextInputStream->ReadLine();
|
|
|
|
if (ferror (stdin) || (line.empty() && feof (stdin)))
|
|
throw UserAbort (SRC_POS);
|
|
}
|
|
|
|
wxString TextUserInterface::ReadInputStreamLine () const
|
|
{
|
|
wxString line;
|
|
ReadInputStreamLine (line);
|
|
return line;
|
|
}
|
|
|
|
void TextUserInterface::RestoreVolumeHeaders (shared_ptr <VolumePath> volumePath) const
|
|
{
|
|
if (!volumePath)
|
|
volumePath = AskVolumePath();
|
|
|
|
if (!volumePath)
|
|
throw UserAbort (SRC_POS);
|
|
|
|
#ifdef TC_WINDOWS
|
|
if (Core->IsVolumeMounted (*volumePath))
|
|
throw_err (LangString["DISMOUNT_FIRST"]);
|
|
#endif
|
|
|
|
// Ask whether to restore internal or external backup
|
|
bool restoreInternalBackup;
|
|
shared_ptr <Pkcs5Kdf> kdf;
|
|
if (CmdLine->ArgHash)
|
|
{
|
|
kdf = Pkcs5Kdf::GetAlgorithm (*CmdLine->ArgHash);
|
|
}
|
|
|
|
ShowInfo (LangString["HEADER_RESTORE_EXTERNAL_INTERNAL"]);
|
|
ShowInfo (L"\n1) " + LangString["HEADER_RESTORE_INTERNAL"]);
|
|
ShowInfo (L"2) " + LangString["HEADER_RESTORE_EXTERNAL"] + L"\n");
|
|
|
|
switch (AskSelection (2))
|
|
{
|
|
case 1:
|
|
restoreInternalBackup = true;
|
|
break;
|
|
|
|
case 2:
|
|
restoreInternalBackup = false;
|
|
break;
|
|
|
|
default:
|
|
throw UserAbort (SRC_POS);
|
|
}
|
|
|
|
/* force the display of the random enriching interface */
|
|
RandomNumberGenerator::SetEnrichedByUserStatus (false);
|
|
|
|
if (restoreInternalBackup)
|
|
{
|
|
// Restore header from the internal backup
|
|
shared_ptr <Volume> volume;
|
|
MountOptions options;
|
|
options.Path = volumePath;
|
|
options.EMVSupportEnabled = true;
|
|
|
|
while (!volume)
|
|
{
|
|
ShowString (L"\n");
|
|
options.Password = AskPassword();
|
|
options.Pim = AskPim();
|
|
options.Keyfiles = AskKeyfiles();
|
|
|
|
try
|
|
{
|
|
volume = Core->OpenVolume (
|
|
options.Path,
|
|
options.PreserveTimestamps,
|
|
options.Password,
|
|
options.Pim,
|
|
kdf,
|
|
options.Keyfiles,
|
|
options.EMVSupportEnabled,
|
|
options.Protection,
|
|
options.ProtectionPassword,
|
|
options.ProtectionPim,
|
|
options.ProtectionKdf,
|
|
options.ProtectionKeyfiles,
|
|
options.SharedAccessAllowed,
|
|
VolumeType::Unknown,
|
|
true
|
|
);
|
|
}
|
|
catch (PasswordException &e)
|
|
{
|
|
ShowInfo (e);
|
|
}
|
|
}
|
|
|
|
shared_ptr <VolumeLayout> layout = volume->GetLayout();
|
|
if (typeid (*layout) == typeid (VolumeLayoutV1Normal))
|
|
{
|
|
throw_err (LangString ["VOLUME_HAS_NO_BACKUP_HEADER"]);
|
|
}
|
|
|
|
RandomNumberGenerator::Start();
|
|
UserEnrichRandomPool();
|
|
|
|
// Re-encrypt volume header
|
|
SecureBuffer newHeaderBuffer (volume->GetLayout()->GetHeaderSize());
|
|
Core->ReEncryptVolumeHeaderWithNewSalt (newHeaderBuffer, volume->GetHeader(), options.Password, options.Pim, options.Keyfiles, options.EMVSupportEnabled);
|
|
|
|
// Write volume header
|
|
int headerOffset = volume->GetLayout()->GetHeaderOffset();
|
|
shared_ptr <File> volumeFile = volume->GetFile();
|
|
|
|
if (headerOffset >= 0)
|
|
volumeFile->SeekAt (headerOffset);
|
|
else
|
|
volumeFile->SeekEnd (headerOffset);
|
|
|
|
volumeFile->Write (newHeaderBuffer);
|
|
}
|
|
else
|
|
{
|
|
// Restore header from an external backup
|
|
|
|
wxString confirmMsg = L"\n\n" + LangString["CONFIRM_VOL_HEADER_RESTORE"];
|
|
|
|
if (!AskYesNo (wxString::Format (confirmMsg, wstring (*volumePath).c_str()), true, true))
|
|
return;
|
|
|
|
ShowString (L"\n");
|
|
|
|
FilePath filePath = AskFilePath();
|
|
if (filePath.IsEmpty())
|
|
throw UserAbort (SRC_POS);
|
|
|
|
File backupFile;
|
|
backupFile.Open (filePath, File::OpenRead);
|
|
|
|
bool legacyBackup;
|
|
|
|
// Determine the format of the backup file
|
|
switch (backupFile.Length())
|
|
{
|
|
case TC_VOLUME_HEADER_GROUP_SIZE:
|
|
legacyBackup = false;
|
|
break;
|
|
|
|
case TC_VOLUME_HEADER_SIZE_LEGACY * 2:
|
|
legacyBackup = true;
|
|
break;
|
|
|
|
default:
|
|
throw_err (LangString ["HEADER_BACKUP_SIZE_INCORRECT"]);
|
|
}
|
|
|
|
// Open the volume header stored in the backup file
|
|
MountOptions options;
|
|
options.EMVSupportEnabled = true;
|
|
|
|
shared_ptr <VolumeLayout> decryptedLayout;
|
|
|
|
while (!decryptedLayout)
|
|
{
|
|
options.Password = AskPassword (L"\n" + LangString["ENTER_HEADER_BACKUP_PASSWORD"]);
|
|
options.Pim = AskPim (_("Enter PIM"));
|
|
options.Keyfiles = AskKeyfiles();
|
|
|
|
try
|
|
{
|
|
// Test volume layouts
|
|
foreach (shared_ptr <VolumeLayout> layout, VolumeLayout::GetAvailableLayouts ())
|
|
{
|
|
if (layout->HasDriveHeader())
|
|
continue;
|
|
|
|
if (!legacyBackup && (typeid (*layout) == typeid (VolumeLayoutV1Normal)))
|
|
continue;
|
|
|
|
if (legacyBackup && (typeid (*layout) == typeid (VolumeLayoutV2Normal) || typeid (*layout) == typeid (VolumeLayoutV2Hidden)))
|
|
continue;
|
|
|
|
SecureBuffer headerBuffer (layout->GetHeaderSize());
|
|
backupFile.ReadAt (headerBuffer, layout->GetType() == VolumeType::Hidden ? layout->GetHeaderSize() : 0);
|
|
|
|
// Decrypt header
|
|
shared_ptr <VolumePassword> passwordKey = Keyfile::ApplyListToPassword (options.Keyfiles, options.Password, options.EMVSupportEnabled);
|
|
if (layout->GetHeader()->Decrypt (headerBuffer, *passwordKey, options.Pim, kdf, layout->GetSupportedKeyDerivationFunctions(), layout->GetSupportedEncryptionAlgorithms(), layout->GetSupportedEncryptionModes()))
|
|
{
|
|
decryptedLayout = layout;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!decryptedLayout)
|
|
throw PasswordIncorrect (SRC_POS);
|
|
}
|
|
catch (PasswordException &e)
|
|
{
|
|
ShowWarning (e);
|
|
}
|
|
}
|
|
|
|
File volumeFile;
|
|
volumeFile.Open (*volumePath, File::OpenReadWrite, File::ShareNone, File::PreserveTimestamps);
|
|
|
|
RandomNumberGenerator::Start();
|
|
UserEnrichRandomPool();
|
|
|
|
// Re-encrypt volume header
|
|
SecureBuffer newHeaderBuffer (decryptedLayout->GetHeaderSize());
|
|
Core->ReEncryptVolumeHeaderWithNewSalt (newHeaderBuffer, decryptedLayout->GetHeader(), options.Password, options.Pim, options.Keyfiles, options.EMVSupportEnabled);
|
|
|
|
// Write volume header
|
|
int headerOffset = decryptedLayout->GetHeaderOffset();
|
|
if (headerOffset >= 0)
|
|
volumeFile.SeekAt (headerOffset);
|
|
else
|
|
volumeFile.SeekEnd (headerOffset);
|
|
|
|
volumeFile.Write (newHeaderBuffer);
|
|
|
|
if (decryptedLayout->HasBackupHeader())
|
|
{
|
|
// Re-encrypt backup volume header
|
|
Core->ReEncryptVolumeHeaderWithNewSalt (newHeaderBuffer, decryptedLayout->GetHeader(), options.Password, options.Pim, options.Keyfiles, options.EMVSupportEnabled);
|
|
|
|
// Write backup volume header
|
|
headerOffset = decryptedLayout->GetBackupHeaderOffset();
|
|
if (headerOffset >= 0)
|
|
volumeFile.SeekAt (headerOffset);
|
|
else
|
|
volumeFile.SeekEnd (headerOffset);
|
|
|
|
volumeFile.Write (newHeaderBuffer);
|
|
}
|
|
}
|
|
|
|
ShowString (L"\n");
|
|
ShowInfo ("VOL_HEADER_RESTORED");
|
|
}
|
|
|
|
void TextUserInterface::SetTerminalEcho (bool enable)
|
|
{
|
|
if (CmdLine->ArgDisplayPassword)
|
|
return;
|
|
|
|
#ifdef TC_UNIX
|
|
struct termios termAttr;
|
|
if (tcgetattr (0, &termAttr) == 0)
|
|
{
|
|
if (!enable)
|
|
{
|
|
termAttr.c_lflag &= ~ECHO;
|
|
throw_sys_if (tcsetattr (0, TCSANOW, &termAttr) != 0);
|
|
}
|
|
else
|
|
{
|
|
termAttr.c_lflag |= ECHO;
|
|
throw_sys_if (tcsetattr (0, TCSANOW, &termAttr) != 0);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void TextUserInterface::UserEnrichRandomPool () const
|
|
{
|
|
RandomNumberGenerator::Start();
|
|
|
|
if (RandomNumberGenerator::IsEnrichedByUser())
|
|
return;
|
|
|
|
if (CmdLine->ArgHash)
|
|
RandomNumberGenerator::SetHash (CmdLine->ArgHash);
|
|
|
|
if (!CmdLine->ArgRandomSourcePath.IsEmpty())
|
|
{
|
|
SecureBuffer buffer (RandomNumberGenerator::PoolSize);
|
|
File randSourceFile;
|
|
|
|
randSourceFile.Open (CmdLine->ArgRandomSourcePath, File::OpenRead);
|
|
|
|
for (size_t i = 0; i < buffer.Size(); ++i)
|
|
{
|
|
if (randSourceFile.Read (buffer.GetRange (i, 1)) < 1)
|
|
break;
|
|
}
|
|
|
|
RandomNumberGenerator::AddToPool (buffer);
|
|
RandomNumberGenerator::SetEnrichedByUserStatus (true);
|
|
}
|
|
else if (!Preferences.NonInteractive)
|
|
{
|
|
int randCharsRequired = RandomNumberGenerator::PoolSize;
|
|
ShowInfo (StringFormatter (_("\nPlease type at least {0} randomly chosen characters and then press Enter:"), randCharsRequired));
|
|
|
|
SetTerminalEcho (false);
|
|
finally_do ({ TextUserInterface::SetTerminalEcho (true); });
|
|
|
|
while (randCharsRequired > 0)
|
|
{
|
|
wstring randStr = AskString();
|
|
RandomNumberGenerator::AddToPool (ConstBufferPtr ((byte *) randStr.c_str(), randStr.size() * sizeof (wchar_t)));
|
|
|
|
randCharsRequired -= randStr.size();
|
|
|
|
if (randCharsRequired > 0)
|
|
ShowInfo (StringFormatter (_("Characters remaining: {0}"), randCharsRequired));
|
|
}
|
|
|
|
ShowString (L"\n");
|
|
RandomNumberGenerator::SetEnrichedByUserStatus (true);
|
|
}
|
|
}
|
|
|
|
wxMessageOutput *DefaultMessageOutput;
|
|
}
|