mirror of
https://github.com/veracrypt/VeraCrypt.git
synced 2026-10-21 22:02:37 +00:00
Tests: read known data from the test volumes without mounting (#1907)
The Linux workflow checks the content of the Tests/*.hc volumes by mounting them, which needs root, FUSE and the installed package, and so only runs on x86_64. Add a test that recovers the same data through the volume layer instead, so it also runs on other architectures, e.g. on the big-endian cross build under qemu-user. Tests/volume_read_test.cpp opens a volume read-only and reads its data area with Volume::ReadSectors: in one call, as multi-sector ranges at nonzero offsets with odd sector counts, again with the encryption thread pool running, and one sector at a time; every read must match the first. CPU features are detected as in the application, and the character set is taken from the environment (LC_CTYPE) so that paths with non-ASCII characters work; if that locale is unavailable, the test continues in the C locale and says so. Tests/test_volume_read.py builds it against Volume.a and Platform.a of a finished standard Linux build (CXX and NM can be set for a cross build) and checks the plaintext of every Tests/test.*.hc volume: the FAT boot sector, DUMMY.TXT with the content "Dummy" at the cluster given by the root directory, and a known SHA-256 digest of the whole data area. A volume without a digest, or a digest without a volume, is an error. It runs in the Linux workflow after the .deb build and in the build job of the big-endian workflow (test.sha512.hc only in the automatic check, all volumes in the full run), and requires the thread pool there. Signed-off-by: Ville Takio <ville+git@takio.fi>
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
// Decrypt the data area of a volume through the volume layer, without
|
||||
// mounting. Usage:
|
||||
// volume_read_test VOLUME PASSWORD KDF OUTPUT [--require-thread-pool]
|
||||
//
|
||||
// The volume is opened read-only with PIM 0 and the given KDF (as with
|
||||
// --hash). CPU features are detected as in the application, so the
|
||||
// accelerated cipher code is used where available. The data area is read in
|
||||
// one call, then as ranges of several sectors at nonzero offsets (odd sector
|
||||
// counts, so the work is split unevenly), again with the encryption thread
|
||||
// pool running, and one sector at a time; every read must match the first.
|
||||
// The pool does not start with fewer than two processors: that part is then
|
||||
// reported as skipped, or fails with --require-thread-pool. The plaintext is
|
||||
// written to OUTPUT, where Tests/test_volume_read.py checks it.
|
||||
#include "Platform/Platform.h"
|
||||
#include "Platform/Finally.h"
|
||||
#include "Volume/EncryptionThreadPool.h"
|
||||
#include "Volume/Pkcs5Kdf.h"
|
||||
#include "Volume/Volume.h"
|
||||
#include "Crypto/cpu.h"
|
||||
#include <clocale>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
using namespace VeraCrypt;
|
||||
|
||||
// Same matching as --hash on the command line: the KDF name, the hash name
|
||||
// or its alternative name, or "argon2"/"argon2id".
|
||||
static shared_ptr <Pkcs5Kdf> FindKdf (const string &name)
|
||||
{
|
||||
string lowerName = StringConverter::ToLower (name);
|
||||
foreach (shared_ptr <Pkcs5Kdf> kdf, Pkcs5Kdf::GetAvailableAlgorithms())
|
||||
{
|
||||
if (StringConverter::ToLower (StringConverter::ToSingle (kdf->GetName())) == lowerName)
|
||||
return kdf;
|
||||
if (kdf->IsArgon2())
|
||||
{
|
||||
if (lowerName == "argon2" || lowerName == "argon2id")
|
||||
return kdf;
|
||||
continue;
|
||||
}
|
||||
if (StringConverter::ToLower (StringConverter::ToSingle (kdf->GetHash()->GetName())) == lowerName
|
||||
|| StringConverter::ToLower (StringConverter::ToSingle (kdf->GetHash()->GetAltName())) == lowerName)
|
||||
return kdf;
|
||||
}
|
||||
throw ParameterIncorrect (SRC_POS);
|
||||
}
|
||||
|
||||
static void Require (bool condition, const char *message)
|
||||
{
|
||||
if (!condition)
|
||||
throw std::runtime_error (message);
|
||||
}
|
||||
|
||||
// Read several multi-sector ranges at nonzero offsets and compare them with
|
||||
// the data area read in one call.
|
||||
static void CheckRanges (Volume &volume, const SecureBuffer &data, const char *message)
|
||||
{
|
||||
const size_t sectorSize = volume.GetSectorSize();
|
||||
const uint64 sectors = data.Size() / sectorSize;
|
||||
Require (sectors >= 34, "data area too small for the range checks");
|
||||
const uint64 ranges[][2] = {
|
||||
{ 1, 3 }, { 7, 5 }, { sectors / 2 - 4, 9 }, { sectors - 17, 17 }
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < sizeof (ranges) / sizeof (ranges[0]); i++)
|
||||
{
|
||||
const uint64 offset = ranges[i][0] * sectorSize;
|
||||
const size_t length = (size_t) (ranges[i][1] * sectorSize);
|
||||
Require (ranges[i][0] > 0 && ranges[i][0] + ranges[i][1] <= sectors, "data area too small for the range checks");
|
||||
|
||||
SecureBuffer buffer (length);
|
||||
volume.ReadSectors (buffer, offset);
|
||||
Require (memcmp (buffer.Ptr(), data.Ptr() + offset, length) == 0, message);
|
||||
}
|
||||
}
|
||||
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
const bool requireThreadPool = (argc == 6 && strcmp (argv[5], "--require-thread-pool") == 0);
|
||||
if (argc != 5 && !requireThreadPool)
|
||||
{
|
||||
std::cerr << "usage: volume_read_test VOLUME PASSWORD KDF OUTPUT [--require-thread-pool]\n";
|
||||
return 2;
|
||||
}
|
||||
|
||||
// The volume path is converted to a wide string with mbsrtowcs, which
|
||||
// needs the character set of the environment (e.g. for "répertoire").
|
||||
// Without it (e.g. the locale is not installed) only ASCII paths work.
|
||||
const bool localeSet = setlocale (LC_CTYPE, "") != nullptr;
|
||||
|
||||
try
|
||||
{
|
||||
#ifdef CRYPTOPP_CPUID_AVAILABLE
|
||||
DetectX86Features ();
|
||||
#endif
|
||||
#if CRYPTOPP_BOOL_ARMV8
|
||||
DetectArmFeatures ();
|
||||
#endif
|
||||
shared_ptr <VolumePassword> password (new VolumePassword ((const uint8 *) argv[2], strlen (argv[2])));
|
||||
Volume volume;
|
||||
volume.Open (VolumePath (string (argv[1])), false, password, 0, FindKdf (argv[3]),
|
||||
shared_ptr <KeyfileList> (), false, VolumeProtection::ReadOnly);
|
||||
|
||||
const uint64 size = volume.GetSize();
|
||||
const size_t sectorSize = volume.GetSectorSize();
|
||||
Require (size != 0 && size % sectorSize == 0, "unexpected data area size");
|
||||
|
||||
SecureBuffer data ((size_t) size);
|
||||
volume.ReadSectors (data, 0);
|
||||
CheckRanges (volume, data, "read of several sectors differs");
|
||||
|
||||
EncryptionThreadPool::Start();
|
||||
finally_do ({ EncryptionThreadPool::Stop(); });
|
||||
|
||||
const bool threaded = EncryptionThreadPool::IsRunning();
|
||||
if (threaded)
|
||||
{
|
||||
SecureBuffer all ((size_t) size);
|
||||
volume.ReadSectors (all, 0);
|
||||
Require (memcmp (all.Ptr(), data.Ptr(), (size_t) size) == 0, "read with the thread pool differs");
|
||||
CheckRanges (volume, data, "read of several sectors with the thread pool differs");
|
||||
}
|
||||
else
|
||||
Require (!requireThreadPool, "the encryption thread pool did not start (fewer than two processors?)");
|
||||
|
||||
SecureBuffer sector (sectorSize);
|
||||
for (uint64 offset = 0; offset < size; offset += sectorSize)
|
||||
{
|
||||
volume.ReadSectors (sector, offset);
|
||||
Require (memcmp (sector.Ptr(), data.Ptr() + offset, sectorSize) == 0, "read one sector at a time differs");
|
||||
}
|
||||
|
||||
std::ofstream output (argv[4], std::ios::binary);
|
||||
output.write ((const char *) data.Ptr(), (std::streamsize) data.Size());
|
||||
output.close();
|
||||
Require (!output.fail(), "cannot write the output file");
|
||||
|
||||
std::cout << StringConverter::ToSingle (volume.GetEncryptionAlgorithm()->GetName())
|
||||
<< " " << size << " bytes" << (threaded ? "" : ", thread pool reads skipped (pool not started)")
|
||||
<< (localeSet ? "" : ", locale from the environment unavailable, using C") << "\n";
|
||||
volume.Close();
|
||||
}
|
||||
catch (std::exception &e)
|
||||
{
|
||||
std::cerr << "error: " << StringConverter::ToSingle (StringConverter::ToExceptionString (e)) << "\n";
|
||||
return 1;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
std::cerr << "error: unknown exception\n";
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user