diff options
Diffstat (limited to 'src/VBox/Main/testcase/tstVBoxCrypto.cpp')
-rw-r--r-- | src/VBox/Main/testcase/tstVBoxCrypto.cpp | 431 |
1 files changed, 431 insertions, 0 deletions
diff --git a/src/VBox/Main/testcase/tstVBoxCrypto.cpp b/src/VBox/Main/testcase/tstVBoxCrypto.cpp new file mode 100644 index 00000000..0f3fbcf6 --- /dev/null +++ b/src/VBox/Main/testcase/tstVBoxCrypto.cpp @@ -0,0 +1,431 @@ +/* $Id: tstVBoxCrypto.cpp $ */ +/** @file + * tstVBoxCrypto - Testcase for the cryptographic support module. + */ + +/* + * Copyright (C) 2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/VBoxCryptoIf.h> +#include <VBox/err.h> + +#include <iprt/file.h> +#include <iprt/test.h> +#include <iprt/ldr.h> +#include <iprt/mem.h> +#include <iprt/memsafer.h> +#include <iprt/rand.h> +#include <iprt/string.h> +#include <iprt/vfs.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static RTTEST g_hTest; +static const uint8_t g_abDek[64] = { 0x42 }; +static const char g_szPassword[] = "testtesttest"; +static const char g_szPasswordWrong[] = "testtest"; + +static const char *g_aCiphers[] = +{ + "AES-XTS128-PLAIN64", + "AES-GCM128", + "AES-CTR128", + + "AES-XTS256-PLAIN64", + "AES-GCM256", + "AES-CTR256" +}; + +#define CHECK_STR(str1, str2) do { if (strcmp(str1, str2)) { RTTestIFailed("line %u: '%s' != '%s' (*)", __LINE__, str1, str2); } } while (0) +#define CHECK_BYTES(bytes1, bytes2, size) do { if (memcmp(bytes1, bytes2, size)) { RTTestIFailed("line %u: '%s' != '%s' (*)", __LINE__, #bytes1, bytes2); } } while (0) + + +/** + * Creates a new cryptographic context and returns the encoded string version on success. + * + * @returns VBox status code. + * @param pCryptoIf Pointer to the cryptographic interface. + * @param pszCipher The cipher to use. + * @param pszPassword The password to use. + * @param ppszCtx Where to store the pointer to the context on success. + */ +static int tstCryptoCtxCreate(PCVBOXCRYPTOIF pCryptoIf, const char *pszCipher, const char *pszPassword, char **ppszCtx) +{ + VBOXCRYPTOCTX hCryptoCtx; + + int rc = pCryptoIf->pfnCryptoCtxCreate(pszCipher, pszPassword, &hCryptoCtx); + if (RT_SUCCESS(rc)) + { + rc = pCryptoIf->pfnCryptoCtxSave(hCryptoCtx, ppszCtx); + int rc2 = pCryptoIf->pfnCryptoCtxDestroy(hCryptoCtx); + AssertReleaseRC(rc2); + } + + return rc; +} + + +/** + * Writes data to the given file until the given size is reached. + * + * @returns VBox status code. + * @param hVfsFile The file handle to write to. + * @param cbWrite Number of bytes to write. + */ +static int tstCryptoVfsWrite(RTVFSFILE hVfsFile, size_t cbWrite) +{ + RTTestISub("Writing to encrypted file"); + + int rc = VINF_SUCCESS; + size_t cbBufLeft = _128K; + void *pv = RTMemTmpAllocZ(cbBufLeft); + if (pv) + { + size_t cbLeft = cbWrite; + uint32_t cCounter = 0; + uint8_t *pb = (uint8_t *)pv; + + /* Fill the counter buffer. */ + uint32_t *pu32 = (uint32_t *)pv; + for (uint32_t i = 0; i < cbBufLeft / sizeof(uint32_t); i++) + *pu32++ = cCounter++; + + + for (;;) + { + size_t cbThisWrite = RTRandU64Ex(1, RT_MIN(cbBufLeft, cbLeft)); + rc = RTVfsFileWrite(hVfsFile, pb, cbThisWrite, NULL /*pcbWritten*/); + if (RT_FAILURE(rc)) + { + RTTestIFailed("Writing to file failed with %Rrc (cbLeft=%zu, cbBufLeft=%zu, cbThisWrite=%zu)", + rc, cbLeft, cbBufLeft, cbThisWrite); + break; + } + + cbLeft -= cbThisWrite; + cbBufLeft -= cbThisWrite; + pb += cbThisWrite; + + if (!cbBufLeft) + { + /* Fill the counter buffer again. */ + pu32 = (uint32_t *)pv; + pb = (uint8_t *)pv; + cbBufLeft = _128K; + for (uint32_t i = 0; i < cbBufLeft / sizeof(uint32_t); i++) + *pu32++ = cCounter++; + } + + if (!cbLeft) + break; + } + + RTMemTmpFree(pv); + } + else + { + RTTestIFailed("Allocating write buffer failed - out of memory"); + rc = VERR_NO_MEMORY; + } + + RTTestISubDone(); + return rc; +} + + +/** + * Writes data to the given file until the given size is reached. + * + * @returns VBox status code. + * @param hVfsFile The file handle to write to. + * @param cbFile Size of the file payload in bytes. + */ +static int tstCryptoVfsReadAndVerify(RTVFSFILE hVfsFile, size_t cbFile) +{ + RTTestISub("Reading from encrypted file and verifying data"); + + int rc = VINF_SUCCESS; + void *pv = RTMemTmpAllocZ(_128K); + if (pv) + { + size_t cbLeft = cbFile; + uint32_t cCounter = 0; + + for (;;) + { + /* Read the data in multiple calls. */ + size_t cbBufLeft = RT_MIN(cbLeft, _128K); + uint8_t *pb = (uint8_t *)pv; + + while (cbBufLeft) + { + size_t cbThisRead = RTRandU64Ex(1, RT_MIN(cbBufLeft, cbLeft)); + rc = RTVfsFileRead(hVfsFile, pb, cbThisRead, NULL /*pcbWritten*/); + if (RT_FAILURE(rc)) + { + RTTestIFailed("Reading from file failed with %Rrc (cbLeft=%zu, cbBufLeft=%zu, cbThisRead=%zu)", + rc, cbLeft, cbBufLeft, cbThisRead); + break; + } + + cbBufLeft -= cbThisRead; + pb += cbThisRead; + } + + if (RT_FAILURE(rc)) + break; + + /* Verify the read data. */ + size_t cbInBuffer = RT_MIN(cbLeft, _128K); + Assert(!(cbInBuffer % sizeof(uint32_t))); + uint32_t *pu32 = (uint32_t *)pv; + + for (uint32_t i = 0; i < cbInBuffer / sizeof(uint32_t); i++) + { + if (*pu32 != cCounter) + { + RTTestIFailed("Reading from file resulted in corrupted data (expected '%#x' got '%#x')", + cCounter, *pu32); + break; + } + + pu32++; + cCounter++; + } + + cbLeft -= RT_MIN(cbLeft, _128K); + if (!cbLeft) + break; + } + + RTMemTmpFree(pv); + } + else + { + RTTestIFailed("Allocating read buffer failed - out of memory"); + rc = VERR_NO_MEMORY; + } + + RTTestISubDone(); + return rc; +} + + +/** + * Testing some basics of the encrypted file VFS code. + * + * @returns nothing. + * @param pCryptoIf Pointer to the callback table. + */ +static void tstCryptoVfsBasics(PCVBOXCRYPTOIF pCryptoIf) +{ + RTTestISub("Encrypted file - Basics"); + + RTTestDisableAssertions(g_hTest); + + char *pszCtx = NULL; + int rc = tstCryptoCtxCreate(pCryptoIf, g_aCiphers[4], g_szPassword, &pszCtx); + if (RT_SUCCESS(rc)) + { + /* Create the memory file to write to. */ + RTVFSFILE hVfsFile; + rc = RTVfsMemFileCreate(NIL_RTVFSIOSTREAM, 0 /*cbEstimate*/, &hVfsFile); + if (RT_SUCCESS(rc)) + { + RTVFSFILE hVfsFileEnc; + + RTTestISub("Creating encrypted file"); + + rc = pCryptoIf->pfnCryptoFileFromVfsFile(hVfsFile, pszCtx, g_szPassword, &hVfsFileEnc); + if (RT_SUCCESS(rc)) + { + RTTestISubDone(); + + size_t cbFile = RT_ALIGN_Z(RTRandU32Ex(_1K, 10 * _1M), sizeof(uint32_t)); /* Align to full counter field size. */ + rc = tstCryptoVfsWrite(hVfsFileEnc, cbFile); + RTVfsFileRelease(hVfsFileEnc); /* Close file. */ + if (RT_SUCCESS(rc)) + { + /* Reopen for reading. */ + RTTestISub("Open encrypted file"); + + /* Reset the memory file offset. */ + RTVfsFileSeek(hVfsFile, 0, RTFILE_SEEK_BEGIN, NULL /*poffActual*/); + + rc = pCryptoIf->pfnCryptoFileFromVfsFile(hVfsFile, pszCtx, g_szPassword, &hVfsFileEnc); + if (RT_SUCCESS(rc)) + { + RTTestISubDone(); + + RTTestISub("Query encrypted file size"); + uint64_t cbFileRd; + rc = RTVfsFileQuerySize(hVfsFileEnc, &cbFileRd); + if (RT_SUCCESS(rc)) + { + if (cbFile != cbFileRd) + RTTestIFailed("Unexpected file size, got %#llx expected %#zx", cbFileRd, cbFile); + + RTTestISubDone(); + tstCryptoVfsReadAndVerify(hVfsFileEnc, cbFile); + } + else + RTTestIFailed("Querying encrypted file size failed %Rrc", rc); + + RTVfsFileRelease(hVfsFileEnc); /* Close file. */ + } + else + RTTestIFailed("Opening encrypted file for reading failed with %Rrc", rc); + + } + /* Error set on failure. */ + } + else + RTTestIFailed("Creating encrypted file handle failed with %Rrc", rc); + + RTVfsFileRelease(hVfsFile); + } + else + RTTestIFailed("Creating a new encrypted file failed with %Rrc", rc); + + RTMemFree(pszCtx); + } + else + RTTestIFailed("Creating a new encrypted context failed with %Rrc", rc); + + RTTestRestoreAssertions(g_hTest); + RTTestISubDone(); +} + + +/** + * Testing some basics of the crypto keystore code. + * + * @returns nothing. + * @param pCryptoIf Pointer to the callback table. + */ +static void tstCryptoKeyStoreBasics(PCVBOXCRYPTOIF pCryptoIf) +{ + RTTestISub("Crypto Keystore - Basics"); + + RTTestDisableAssertions(g_hTest); + + for (uint32_t i = 0; i < RT_ELEMENTS(g_aCiphers); i++) + { + RTTestISubF("Creating a new keystore for cipher '%s'", g_aCiphers[i]); + + char *pszKeystoreEnc = NULL; /**< The encoded keystore. */ + int rc = pCryptoIf->pfnCryptoKeyStoreCreate(g_szPassword, &g_abDek[0], sizeof(g_abDek), + g_aCiphers[i], &pszKeystoreEnc); + if (RT_SUCCESS(rc)) + { + uint8_t *pbKey = NULL; + size_t cbKey = 0; + char *pszCipher = NULL; + + RTTestSub(g_hTest, "Trying to unlock DEK with wrong password"); + rc = pCryptoIf->pfnCryptoKeyStoreGetDekFromEncoded(pszKeystoreEnc, g_szPasswordWrong, + &pbKey, &cbKey, &pszCipher); + RTTESTI_CHECK_RC(rc, VERR_VD_PASSWORD_INCORRECT); + + RTTestSub(g_hTest, "Trying to unlock DEK with correct password"); + rc = pCryptoIf->pfnCryptoKeyStoreGetDekFromEncoded(pszKeystoreEnc, g_szPassword, + &pbKey, &cbKey, &pszCipher); + RTTESTI_CHECK_RC_OK(rc); + if (RT_SUCCESS(rc)) + { + RTTESTI_CHECK(cbKey == sizeof(g_abDek)); + CHECK_STR(pszCipher, g_aCiphers[i]); + CHECK_BYTES(pbKey, &g_abDek[0], sizeof(g_abDek)); + + RTMemSaferFree(pbKey, cbKey); + } + + RTMemFree(pszKeystoreEnc); + } + else + RTTestIFailed("Creating a new keystore failed with %Rrc", rc); + } + + RTTestRestoreAssertions(g_hTest); +} + + +int main(int argc, char *argv[]) +{ + /* + * Initialization. + */ + RTEXITCODE rcExit = RTTestInitAndCreate("tstVBoxCrypto", &g_hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(g_hTest); + + RTTestSub(g_hTest, "Loading the cryptographic support module"); + const char *pszModCrypto = NULL; + if (argc == 2) + { + /* The module to load is given on the command line. */ + pszModCrypto = argv[1]; + } + else + { + /* Try find it in the extension pack. */ + /** @todo */ + RTTestSkipped(g_hTest, "Getting the module from the extension pack is not implemented yet, skipping testcase"); + } + + if (pszModCrypto) + { + RTLDRMOD hLdrModCrypto = NIL_RTLDRMOD; + int rc = RTLdrLoad(pszModCrypto, &hLdrModCrypto); + if (RT_SUCCESS(rc)) + { + PFNVBOXCRYPTOENTRY pfnCryptoEntry = NULL; + rc = RTLdrGetSymbol(hLdrModCrypto, VBOX_CRYPTO_MOD_ENTRY_POINT, (void **)&pfnCryptoEntry); + if (RT_SUCCESS(rc)) + { + PCVBOXCRYPTOIF pCryptoIf = NULL; + rc = pfnCryptoEntry(&pCryptoIf); + if (RT_SUCCESS(rc)) + { + /* Loading succeeded, now we can start real testing. */ + tstCryptoKeyStoreBasics(pCryptoIf); + tstCryptoVfsBasics(pCryptoIf); + } + else + RTTestIFailed("Calling '%s' failed with %Rrc", VBOX_CRYPTO_MOD_ENTRY_POINT, rc); + } + else + RTTestIFailed("Failed to resolve entry point '%s' with %Rrc", VBOX_CRYPTO_MOD_ENTRY_POINT, rc); + } + else + RTTestIFailed("Failed to load the crypto module '%s' with %Rrc", pszModCrypto, rc); + } + + return RTTestSummaryAndDestroy(g_hTest); +} |