diff --git a/.gitignore b/.gitignore index 8e24b65..56bd78b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea cmake-build-debug +design diff --git a/modules/build/CMakeLists.txt b/modules/build/CMakeLists.txt index 41a5ff0..a808430 100644 --- a/modules/build/CMakeLists.txt +++ b/modules/build/CMakeLists.txt @@ -35,6 +35,7 @@ src/signing.cpp src/checksums.cpp src/metadata.cpp src/sealing.cpp + src/archive_reader.cpp ) # Set output properties @@ -67,6 +68,7 @@ src/signing.cpp src/checksums.cpp src/metadata.cpp src/sealing.cpp + src/archive_reader.cpp ) # Define the BUILD_STANDALONE macro for the standalone build diff --git a/modules/build/include/archive_reader.hpp b/modules/build/include/archive_reader.hpp new file mode 100644 index 0000000..e93576f --- /dev/null +++ b/modules/build/include/archive_reader.hpp @@ -0,0 +1,43 @@ +/** +* @file archive_reader.hpp + * @brief Functions for in-memory archive reading and verification + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "checksums.hpp" + +extern "C" { + /** + * Extracts a specific file from a package file (gzipped tarball) + * + * @param package_file_path Path to the package file (.dpm) + * @param file_path_in_archive Path of the file to extract within the archive + * @param data Pointer to buffer pointer - will be allocated by function + * @param data_size Pointer to size variable that will receive file size + * @return true on success, false on failure + */ + bool get_file_from_package_file(const char* package_file_path, const char* file_path_in_archive, unsigned char** data, size_t* data_size); + + /** + * Extracts a specific file from an in-memory archive (gzipped tarball) + * + * @param archive_data Pointer to the archive data in memory + * @param archive_data_size Size of the archive data in memory + * @param file_path_in_archive Path of the file to extract within the archive + * @param result_data Pointer to buffer pointer - will be allocated by function + * @param result_data_size Pointer to size variable that will receive file size + * @return true on success, false on failure + */ + bool get_file_from_memory_loaded_archive(const unsigned char* archive_data, const size_t archive_data_size, + const char* file_path_in_archive, + unsigned char** result_data, size_t* result_data_size); +} \ No newline at end of file diff --git a/modules/build/src/archive_reader.cpp b/modules/build/src/archive_reader.cpp new file mode 100644 index 0000000..c6c3743 --- /dev/null +++ b/modules/build/src/archive_reader.cpp @@ -0,0 +1,205 @@ +/** + * @file archive_reader.cpp + * @brief Implementation of in-memory archive reading and verification + */ + +#include "archive_reader.hpp" +#include "checksums.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + + +/** + * Extracts a specific file from a package file (gzipped tarball) + * + * @param package_file_path Path to the package file (.dpm) + * @param file_path_in_archive Path of the file to extract within the archive + * @param data Pointer to buffer pointer - will be allocated by function + * @param data_size Pointer to size variable that will receive file size + * @return true on success, false on failure + */ +extern "C" bool get_file_from_package_file(const char* package_file_path, const char* file_path_in_archive, unsigned char** data, size_t* data_size) +{ + if (!package_file_path || !file_path_in_archive || !data || !data_size) { + dpm_log(LOG_ERROR, "Invalid parameters passed to get_file_from_package_file"); + return false; + } + + // Initialize output parameters + *data = NULL; + *data_size = 0; + + // Create a new archive for reading + struct archive* a = archive_read_new(); + if (!a) { + dpm_log(LOG_ERROR, "Failed to create archive object"); + return false; + } + + // Enable support for gzipped tarballs + archive_read_support_filter_gzip(a); + archive_read_support_format_tar(a); + + // Open the package file - using 0 for block size lets libarchive choose the optimal size + int r = archive_read_open_filename(a, package_file_path, 0); + if (r != ARCHIVE_OK) { + dpm_log(LOG_ERROR, ("Failed to open package file: " + std::string(package_file_path) + + " - " + std::string(archive_error_string(a))).c_str()); + archive_read_free(a); + return false; + } + + // Iterate through archive entries + bool found = false; + struct archive_entry* entry; + while (archive_read_next_header(a, &entry) == ARCHIVE_OK) { + const char* current_path = archive_entry_pathname(entry); + + // Check if this is the file we're looking for + if (strcmp(current_path, file_path_in_archive) == 0) { + // Get the file size + size_t file_size = archive_entry_size(entry); + *data_size = file_size; + + // Allocate buffer of appropriate size + *data = (unsigned char*)malloc(file_size); + if (!*data) { + dpm_log(LOG_ERROR, "Failed to allocate memory for file contents"); + archive_read_free(a); + return false; + } + + // Read the file content into the buffer + ssize_t bytes_read = archive_read_data(a, *data, file_size); + if (bytes_read < 0 || (size_t)bytes_read != file_size) { + dpm_log(LOG_ERROR, ("Failed to read file data: " + + std::string(archive_error_string(a))).c_str()); + free(*data); + *data = NULL; + *data_size = 0; + archive_read_free(a); + return false; + } + + found = true; + break; + } + + // Skip to next entry + archive_read_data_skip(a); + } + + // Clean up + archive_read_free(a); + + if (!found) { + dpm_log(LOG_ERROR, ("File not found in package: " + + std::string(file_path_in_archive)).c_str()); + return false; + } + + return true; +} + +/** + * Extracts a specific file from an in-memory archive (gzipped tarball) + * + * @param archive_data Pointer to the archive data in memory + * @param archive_data_size Size of the archive data in memory + * @param file_path_in_archive Path of the file to extract within the archive + * @param result_data Pointer to buffer pointer - will be allocated by function + * @param result_data_size Pointer to size variable that will receive file size + * @return true on success, false on failure + */ +extern "C" bool get_file_from_memory_loaded_archive(const unsigned char* archive_data, const size_t archive_data_size, + const char* file_path_in_archive, + unsigned char** result_data, size_t* result_data_size) +{ + if (!archive_data || archive_data_size == 0 || !file_path_in_archive || + !result_data || !result_data_size) { + dpm_log(LOG_ERROR, "Invalid parameters passed to get_file_from_memory_loaded_archive"); + return false; + } + + // Initialize output parameters + *result_data = NULL; + *result_data_size = 0; + + // Create a new archive for reading + struct archive* a = archive_read_new(); + if (!a) { + dpm_log(LOG_ERROR, "Failed to create archive object"); + return false; + } + + // Enable support for gzipped tarballs + archive_read_support_filter_gzip(a); + archive_read_support_format_tar(a); + + // Open the archive from memory + int r = archive_read_open_memory(a, (void*)archive_data, archive_data_size); + if (r != ARCHIVE_OK) { + dpm_log(LOG_ERROR, ("Failed to open archive from memory: " + + std::string(archive_error_string(a))).c_str()); + archive_read_free(a); + return false; + } + + // Iterate through archive entries + bool found = false; + struct archive_entry* entry; + while (archive_read_next_header(a, &entry) == ARCHIVE_OK) { + const char* current_path = archive_entry_pathname(entry); + + // Check if this is the file we're looking for + if (strcmp(current_path, file_path_in_archive) == 0) { + // Get the file size + size_t file_size = archive_entry_size(entry); + *result_data_size = file_size; + + // Allocate buffer of appropriate size + *result_data = (unsigned char*)malloc(file_size); + if (!*result_data) { + dpm_log(LOG_ERROR, "Failed to allocate memory for file contents"); + archive_read_free(a); + return false; + } + + // Read the file content into the buffer + ssize_t bytes_read = archive_read_data(a, *result_data, file_size); + if (bytes_read < 0 || (size_t)bytes_read != file_size) { + dpm_log(LOG_ERROR, ("Failed to read file data from memory archive: " + + std::string(archive_error_string(a))).c_str()); + free(*result_data); + *result_data = NULL; + *result_data_size = 0; + archive_read_free(a); + return false; + } + + found = true; + break; + } + + // Skip to next entry + archive_read_data_skip(a); + } + + // Clean up + archive_read_free(a); + + if (!found) { + dpm_log(LOG_ERROR, ("File not found in memory archive: " + + std::string(file_path_in_archive)).c_str()); + return false; + } + + return true; +} + diff --git a/modules/verify/CMakeLists.txt b/modules/verify/CMakeLists.txt index 7976931..438545b 100644 --- a/modules/verify/CMakeLists.txt +++ b/modules/verify/CMakeLists.txt @@ -19,6 +19,8 @@ add_library(verify MODULE src/verification.cpp src/checksum.cpp ../../dpmdk/src/ModuleOperations.cpp + src/package_operations.cpp + src/checksum_memory.cpp ) # Set output properties @@ -47,6 +49,8 @@ add_executable(verify_standalone src/verification.cpp src/checksum.cpp ../../dpmdk/src/ModuleOperations.cpp + src/package_operations.cpp + src/checksum_memory.cpp ) # Define the BUILD_STANDALONE macro for the standalone build diff --git a/modules/verify/include/checksum_memory.hpp b/modules/verify/include/checksum_memory.hpp new file mode 100644 index 0000000..e57a722 --- /dev/null +++ b/modules/verify/include/checksum_memory.hpp @@ -0,0 +1,75 @@ +/** +* @file checksum_memory.hpp + * @brief In-memory package checksum verification functions + * + * Defines functions for verifying checksums of DPM package components in memory + * without requiring them to be extracted to disk first. + * + * @copyright Copyright (c) 2025 SILO GROUP LLC + * @author Chris Punches + * + * Part of the Dark Horse Linux Package Manager (DPM) + */ +#pragma once + +#include +#include +#include "package_operations.hpp" +#include +#include + +/** + * @brief Verifies the package digest from in-memory metadata + * + * Calculates the package digest from in-memory CONTENTS_MANIFEST_DIGEST and + * HOOKS_DIGEST files and compares it to the value in PACKAGE_DIGEST. + * + * @param data Pointer to the metadata file data + * @param data_size Size of the metadata file data + * @param build_module Handle to the loaded build module + * @return 0 on successful verification, non-zero on failure + */ +int checksum_verify_package_digest_memory( + const unsigned char* data, + size_t data_size, + void* build_module); + +/** + * @brief Verifies the contents manifest digest from in-memory data + * + * Compares checksums in the contents manifest with actual file checksums + * using in-memory data rather than extracting files to disk. + * + * @param contents_data Pointer to the contents component data + * @param contents_data_size Size of the contents component data + * @param metadata_data Pointer to the metadata component data + * @param metadata_data_size Size of the metadata component data + * @param build_module Handle to the loaded build module + * @return 0 on successful verification, non-zero on failure + */ +int checksum_verify_contents_digest_memory( + const unsigned char* contents_data, + size_t contents_data_size, + const unsigned char* metadata_data, + size_t metadata_data_size, + void* build_module); + +/** + * @brief Verifies the hooks digest from in-memory data + * + * Calculates the digest of the hooks archive and compares it with the + * value stored in HOOKS_DIGEST metadata file. + * + * @param hooks_data Pointer to the hooks component data + * @param hooks_data_size Size of the hooks component data + * @param metadata_data Pointer to the metadata component data + * @param metadata_data_size Size of the metadata component data + * @param build_module Handle to the loaded build module + * @return 0 on successful verification, non-zero on failure + */ +int checksum_verify_hooks_digest_memory( + const unsigned char* hooks_data, + size_t hooks_data_size, + const unsigned char* metadata_data, + size_t metadata_data_size, + void* build_module); \ No newline at end of file diff --git a/modules/verify/include/commands.hpp b/modules/verify/include/commands.hpp index bf5a0ee..6d42f3e 100644 --- a/modules/verify/include/commands.hpp +++ b/modules/verify/include/commands.hpp @@ -18,6 +18,8 @@ #include #include #include +#include "checksum_memory.hpp" +#include "package_operations.hpp" /** * @brief Handler for the checksum command @@ -157,4 +159,27 @@ int verify_signature_package(const std::string& package_path); * @param stage_dir Path to the stage directory * @return 0 on success, non-zero on failure */ -int verify_signature_stage(const std::string& stage_dir); \ No newline at end of file +int verify_signature_stage(const std::string& stage_dir); + +/** + * @brief Verifies checksums of a package file in memory + * + * Loads the components of a package file into memory and verifies their checksums + * without extracting them to disk. + * + * @param package_path Path to the package file + * @return 0 on success, non-zero on failure + */ +int verify_checksums_package_memory(const std::string& package_path); + +/** + * @brief Converts binary data to a C++ string + * + * Takes a buffer of binary data and its size, creates a properly + * null-terminated string, and returns it as an std::string. + * + * @param data Pointer to the binary data + * @param data_size Size of the binary data + * @return std::string containing the data, or empty string on error + */ +std::string binary_to_string(const unsigned char* data, size_t data_size); \ No newline at end of file diff --git a/modules/verify/include/package_operations.hpp b/modules/verify/include/package_operations.hpp new file mode 100644 index 0000000..557da11 --- /dev/null +++ b/modules/verify/include/package_operations.hpp @@ -0,0 +1,53 @@ +/** + * @file package_operations.hpp + * @brief Functions for operating on DPM packages + * + * Defines functions for extracting and verifying components from DPM packages. + * + * @copyright Copyright (c) 2025 SILO GROUP LLC + * @author Chris Punches + * + * Part of the Dark Horse Linux Package Manager (DPM) + */ +#pragma once + +#include +#include +#include "commands.hpp" +#include + +/** + * @brief Extracts a component from a package file + * + * Loads a component (metadata, contents, hooks, signatures) from a package file + * by calling into the build module's get_file_from_package_file function. + * + * @param package_path Path to the package file + * @param component_name Name of the component to extract (metadata, contents, hooks, signatures) + * @param data Pointer to a pointer that will be populated with the component data + * @param data_size Pointer to a size_t that will be populated with the size of the component data + * @return 0 on success, non-zero on failure + */ +int get_component_from_package(const std::string& package_path, + const std::string& component_name, + unsigned char** data, + size_t* data_size); + +/** + * @brief Extracts a file from a component archive + * + * Extracts a specific file from a component archive that has already been loaded into memory. + * Uses the build module's get_file_from_memory_loaded_archive function. + * + * @param component_data Pointer to the component archive data in memory + * @param component_size Size of the component archive in memory + * @param filename Name of the file to extract from the component + * @param data Pointer to a pointer that will be populated with the file data + * @param data_size Pointer to a size_t that will be populated with the size of the file data + * @return 0 on success, non-zero on failure + */ +int get_file_from_component(const unsigned char* component_data, + size_t component_size, + const std::string& filename, + unsigned char** data, + size_t* data_size); diff --git a/modules/verify/src/checksum.cpp b/modules/verify/src/checksum.cpp index 02cf556..a8e01fe 100644 --- a/modules/verify/src/checksum.cpp +++ b/modules/verify/src/checksum.cpp @@ -297,4 +297,5 @@ int checksum_verify_package_digest(const std::string& stage_dir, void* build_mod dpm_log(LOG_INFO, "Package digest verification successful"); return 0; -} \ No newline at end of file +} + diff --git a/modules/verify/src/checksum_memory.cpp b/modules/verify/src/checksum_memory.cpp new file mode 100644 index 0000000..34e89cd --- /dev/null +++ b/modules/verify/src/checksum_memory.cpp @@ -0,0 +1,427 @@ +/** + * @file checksum_memory.cpp + * @brief Implementation of in-memory package checksum verification functions + * + * Implements functions for verifying checksums of DPM package components in memory + * without requiring them to be extracted to disk first. + * + * @copyright Copyright (c) 2025 SILO GROUP LLC + * @author Chris Punches + * + * Part of the Dark Horse Linux Package Manager (DPM) + */ + +#include "checksum_memory.hpp" + +/** + * @brief Converts binary data to a C++ string + * + * Takes a buffer of binary data and its size, creates a properly + * null-terminated string, and returns it as an std::string. + * + * @param data Pointer to the binary data + * @param data_size Size of the binary data + * @return std::string containing the data, or empty string on error + */ +std::string binary_to_string(const unsigned char* data, size_t data_size) { + if (!data || data_size == 0) { + return std::string(); + } + + // Create a temporary C-string with null termination + char* temp = (char*)malloc(data_size + 1); + if (!temp) { + return std::string(); + } + + memcpy(temp, data, data_size); + temp[data_size] = '\0'; + + // Create std::string from the C-string + std::string result(temp); + + // Free the temporary buffer + free(temp); + + return result; +} + +/** + * @brief Verifies the package digest from in-memory metadata + * + * Calculates the package digest from in-memory CONTENTS_MANIFEST_DIGEST and + * HOOKS_DIGEST files and compares it to the value in PACKAGE_DIGEST. + * + * @param package_data Pointer to the metadata component data + * @param package_data_size Size of the metadata component data + * @param build_module Handle to the loaded build module + * @return 0 on successful verification, non-zero on failure + */ +int checksum_verify_package_digest_memory( + const unsigned char* package_data, + size_t package_data_size, + void* build_module) +{ + // Validate input parameters + if (!package_data || package_data_size == 0 || !build_module) { + dpm_log(LOG_ERROR, "Invalid parameters passed to checksum_verify_package_digest_memory"); + return 1; + } + + dpm_log(LOG_INFO, "Verifying package digest from in-memory data..."); + + // First, extract PACKAGE_DIGEST, CONTENTS_MANIFEST_DIGEST, and HOOKS_DIGEST from the metadata component + unsigned char* package_digest_data = nullptr; + size_t package_digest_size = 0; + unsigned char* contents_manifest_data = nullptr; + size_t contents_manifest_size = 0; + unsigned char* hooks_digest_data = nullptr; + size_t hooks_digest_size = 0; + + // Get PACKAGE_DIGEST from the metadata component + int result = get_file_from_component( + package_data, + package_data_size, + "PACKAGE_DIGEST", + &package_digest_data, + &package_digest_size + ); + + if (result != 0 || !package_digest_data || package_digest_size == 0) { + dpm_log(LOG_ERROR, "Failed to extract PACKAGE_DIGEST from metadata component"); + return 1; + } + + // Get CONTENTS_MANIFEST_DIGEST from the metadata component + result = get_file_from_component( + package_data, + package_data_size, + "CONTENTS_MANIFEST_DIGEST", + &contents_manifest_data, + &contents_manifest_size + ); + + if (result != 0 || !contents_manifest_data || contents_manifest_size == 0) { + dpm_log(LOG_ERROR, "Failed to extract CONTENTS_MANIFEST_DIGEST from metadata component"); + free(package_digest_data); + return 1; + } + + // Get HOOKS_DIGEST from the metadata component + result = get_file_from_component( + package_data, + package_data_size, + "HOOKS_DIGEST", + &hooks_digest_data, + &hooks_digest_size + ); + + if (result != 0 || !hooks_digest_data || hooks_digest_size == 0) { + dpm_log(LOG_ERROR, "Failed to extract HOOKS_DIGEST from metadata component"); + free(package_digest_data); + free(contents_manifest_data); + return 1; + } + + // Convert binary data to strings using our utility function + std::string package_digest_str = binary_to_string(package_digest_data, package_digest_size); + std::string contents_manifest_str = binary_to_string(contents_manifest_data, contents_manifest_size); + std::string hooks_digest_str = binary_to_string(hooks_digest_data, hooks_digest_size); + + // Check if any conversion failed + if (package_digest_str.empty() || contents_manifest_str.empty() || hooks_digest_str.empty()) { + dpm_log(LOG_ERROR, "Failed to convert binary data to strings"); + free(package_digest_data); + free(contents_manifest_data); + free(hooks_digest_data); + return 1; + } + + // Calculate checksums using the build module's functions through dpm_execute_symbol + std::string contents_manifest_checksum; + result = dpm_execute_symbol(build_module, "generate_string_checksum", + contents_manifest_str, &contents_manifest_checksum); + + if (result != 0 || contents_manifest_checksum.empty()) { + dpm_log(LOG_ERROR, "Failed to calculate checksum for contents manifest"); + free(package_digest_data); + free(contents_manifest_data); + free(hooks_digest_data); + return 1; + } + + std::string hooks_digest_checksum; + result = dpm_execute_symbol(build_module, "generate_string_checksum", + hooks_digest_str, &hooks_digest_checksum); + + if (result != 0 || hooks_digest_checksum.empty()) { + dpm_log(LOG_ERROR, "Failed to calculate checksum for hooks digest"); + free(package_digest_data); + free(contents_manifest_data); + free(hooks_digest_data); + return 1; + } + + // Combine checksums and calculate package digest + std::string combined_checksums = contents_manifest_checksum + hooks_digest_checksum; + std::string calculated_package_digest; + + result = dpm_execute_symbol(build_module, "generate_string_checksum", + combined_checksums, &calculated_package_digest); + + if (result != 0 || calculated_package_digest.empty()) { + dpm_log(LOG_ERROR, "Failed to calculate package digest"); + free(package_digest_data); + free(contents_manifest_data); + free(hooks_digest_data); + return 1; + } + + // Compare with the stored package digest + bool match = (calculated_package_digest == package_digest_str); + + // Clean up + free(package_digest_data); + free(contents_manifest_data); + free(hooks_digest_data); + + if (!match) { + dpm_log(LOG_ERROR, ("Package digest mismatch\n Expected: " + package_digest_str + + "\n Actual: " + calculated_package_digest).c_str()); + return 1; + } + + dpm_log(LOG_INFO, "Package digest verification successful"); + return 0; +} + +/** + * @brief Verifies the contents manifest digest from in-memory data + * + * Compares checksums in the contents manifest with actual file checksums + * using in-memory data rather than extracting files to disk. + * + * @param contents_data Pointer to the contents component data + * @param contents_data_size Size of the contents component data + * @param metadata_data Pointer to the metadata component data + * @param metadata_data_size Size of the metadata component data + * @param build_module Handle to the loaded build module + * @return 0 on successful verification, non-zero on failure + */ +int checksum_verify_contents_digest_memory( + const unsigned char* contents_data, + size_t contents_data_size, + const unsigned char* metadata_data, + size_t metadata_data_size, + void* build_module) +{ + // Validate input parameters + if (!contents_data || contents_data_size == 0 || + !metadata_data || metadata_data_size == 0 || !build_module) { + dpm_log(LOG_ERROR, "Invalid parameters passed to checksum_verify_contents_digest_memory"); + return 1; + } + + dpm_log(LOG_INFO, "Verifying contents manifest digest from in-memory data..."); + + // Extract CONTENTS_MANIFEST_DIGEST from the metadata component + unsigned char* manifest_data = nullptr; + size_t manifest_size = 0; + + int result = get_file_from_component( + metadata_data, + metadata_data_size, + "CONTENTS_MANIFEST_DIGEST", + &manifest_data, + &manifest_size + ); + + if (result != 0 || !manifest_data || manifest_size == 0) { + dpm_log(LOG_ERROR, "Failed to extract CONTENTS_MANIFEST_DIGEST from metadata component"); + return 1; + } + + // Convert binary data to string + std::string manifest_str = binary_to_string(manifest_data, manifest_size); + if (manifest_str.empty()) { + dpm_log(LOG_ERROR, "Failed to convert manifest data to string"); + free(manifest_data); + return 1; + } + + // Parse the manifest lines + std::istringstream manifest_stream(manifest_str); + std::string line; + int errors = 0; + int line_number = 0; + + // Process each line in the manifest + while (std::getline(manifest_stream, line)) { + line_number++; + + // Skip empty lines + if (line.empty()) { + continue; + } + + // Parse the line into its components + std::istringstream iss(line); + char control_designation; + std::string expected_checksum, permissions, ownership, file_path; + + // Extract components (C checksum permissions owner:group /path/to/file) + if (!(iss >> control_designation >> expected_checksum >> permissions >> ownership)) { + dpm_log(LOG_WARN, ("Malformed manifest line " + std::to_string(line_number) + + ": " + line).c_str()); + continue; + } + + // Get the rest of the line as the file path + std::getline(iss >> std::ws, file_path); + + if (file_path.empty()) { + dpm_log(LOG_WARN, ("Missing file path in manifest line " + + std::to_string(line_number)).c_str()); + continue; + } + + // Remove leading slash if present + if (file_path[0] == '/') { + file_path = file_path.substr(1); + } + + // Extract the file from the contents component + unsigned char* file_data = nullptr; + size_t file_size = 0; + + result = get_file_from_component( + contents_data, + contents_data_size, + file_path, + &file_data, + &file_size + ); + + if (result != 0 || !file_data) { + dpm_log(LOG_ERROR, ("Failed to extract file from contents: " + file_path).c_str()); + errors++; + continue; + } + + // Calculate the checksum of the file data + std::string calculated_checksum; + result = dpm_execute_symbol(build_module, "generate_string_checksum", + std::string(reinterpret_cast(file_data), file_size), + &calculated_checksum); + + // Free the file data now that we're done with it + free(file_data); + + if (result != 0 || calculated_checksum.empty()) { + dpm_log(LOG_ERROR, ("Failed to calculate checksum for file: " + file_path).c_str()); + errors++; + continue; + } + + // Compare with the expected checksum + if (calculated_checksum != expected_checksum) { + dpm_log(LOG_ERROR, ("Checksum mismatch for " + file_path + + "\n Expected: " + expected_checksum + + "\n Actual: " + calculated_checksum).c_str()); + errors++; + } + } + + // Clean up + free(manifest_data); + + if (errors > 0) { + dpm_log(LOG_ERROR, (std::to_string(errors) + " checksum errors found in contents manifest").c_str()); + return 1; + } + + dpm_log(LOG_INFO, "Contents manifest checksum verification successful"); + return 0; +} + +/** + * @brief Verifies the hooks digest from in-memory data + * + * Calculates the digest of the hooks archive and compares it with the + * value stored in HOOKS_DIGEST metadata file. + * + * @param hooks_data Pointer to the hooks component data + * @param hooks_data_size Size of the hooks component data + * @param metadata_data Pointer to the metadata component data + * @param metadata_data_size Size of the metadata component data + * @param build_module Handle to the loaded build module + * @return 0 on successful verification, non-zero on failure + */ +int checksum_verify_hooks_digest_memory( + const unsigned char* hooks_data, + size_t hooks_data_size, + const unsigned char* metadata_data, + size_t metadata_data_size, + void* build_module) +{ + // Validate input parameters + if (!hooks_data || hooks_data_size == 0 || + !metadata_data || metadata_data_size == 0 || !build_module) { + dpm_log(LOG_ERROR, "Invalid parameters passed to checksum_verify_hooks_digest_memory"); + return 1; + } + + dpm_log(LOG_INFO, "Verifying hooks digest from in-memory data..."); + + // Extract HOOKS_DIGEST from the metadata component + unsigned char* hooks_digest_data = nullptr; + size_t hooks_digest_size = 0; + + int result = get_file_from_component( + metadata_data, + metadata_data_size, + "HOOKS_DIGEST", + &hooks_digest_data, + &hooks_digest_size + ); + + if (result != 0 || !hooks_digest_data || hooks_digest_size == 0) { + dpm_log(LOG_ERROR, "Failed to extract HOOKS_DIGEST from metadata component"); + return 1; + } + + // Convert binary data to string + std::string stored_hooks_digest = binary_to_string(hooks_digest_data, hooks_digest_size); + if (stored_hooks_digest.empty()) { + dpm_log(LOG_ERROR, "Failed to convert hooks digest data to string"); + free(hooks_digest_data); + return 1; + } + + // Trim whitespace and newlines + stored_hooks_digest = stored_hooks_digest.substr(0, stored_hooks_digest.find_first_of("\r\n")); + + // Calculate the checksum for the hooks archive data + std::string calculated_hooks_digest; + result = dpm_execute_symbol(build_module, "generate_string_checksum", + std::string(reinterpret_cast(hooks_data), hooks_data_size), + &calculated_hooks_digest); + + // Clean up + free(hooks_digest_data); + + if (result != 0 || calculated_hooks_digest.empty()) { + dpm_log(LOG_ERROR, "Failed to calculate hooks digest"); + return 1; + } + + // Compare with the stored digest + if (calculated_hooks_digest != stored_hooks_digest) { + dpm_log(LOG_ERROR, ("Hooks digest mismatch\n Expected: " + stored_hooks_digest + + "\n Actual: " + calculated_hooks_digest).c_str()); + return 1; + } + + dpm_log(LOG_INFO, "Hooks digest verification successful"); + return 0; +} \ No newline at end of file diff --git a/modules/verify/src/commands.cpp b/modules/verify/src/commands.cpp index b504864..3304829 100644 --- a/modules/verify/src/commands.cpp +++ b/modules/verify/src/commands.cpp @@ -269,5 +269,131 @@ int cmd_check(int argc, char** argv) { dpm_unload_module(module_handle); } + return 0; +} + +/** + * @brief Verifies checksums of a package file in memory + * + * Loads the components of a package file into memory and verifies their checksums + * without extracting them to disk. + * + * @param package_path Path to the package file + * @return 0 on success, non-zero on failure + */ +int verify_checksums_package_memory(const std::string& package_path) { + // Check if the package file exists + if (!std::filesystem::exists(package_path)) { + dpm_log(LOG_ERROR, ("Package file not found: " + package_path).c_str()); + return 1; + } + + dpm_log(LOG_INFO, ("Verifying checksums for package in memory: " + package_path).c_str()); + + // Load the build module + void* build_module = nullptr; + int result = check_and_load_build_module(build_module); + if (result != 0 || build_module == nullptr) { + dpm_log(LOG_ERROR, "Failed to load build module"); + return 1; + } + + // Extract package components into memory + unsigned char* metadata_data = nullptr; + size_t metadata_data_size = 0; + unsigned char* contents_data = nullptr; + size_t contents_data_size = 0; + unsigned char* hooks_data = nullptr; + size_t hooks_data_size = 0; + + // Load metadata component + dpm_log(LOG_INFO, "Loading metadata component..."); + result = get_component_from_package(package_path, "metadata", &metadata_data, &metadata_data_size); + if (result != 0 || !metadata_data || metadata_data_size == 0) { + dpm_log(LOG_ERROR, "Failed to load metadata component"); + dpm_unload_module(build_module); + return 1; + } + + // Load contents component + dpm_log(LOG_INFO, "Loading contents component..."); + result = get_component_from_package(package_path, "contents", &contents_data, &contents_data_size); + if (result != 0 || !contents_data || contents_data_size == 0) { + dpm_log(LOG_ERROR, "Failed to load contents component"); + free(metadata_data); + dpm_unload_module(build_module); + return 1; + } + + // Load hooks component + dpm_log(LOG_INFO, "Loading hooks component..."); + result = get_component_from_package(package_path, "hooks", &hooks_data, &hooks_data_size); + if (result != 0 || !hooks_data || hooks_data_size == 0) { + dpm_log(LOG_ERROR, "Failed to load hooks component"); + free(metadata_data); + free(contents_data); + dpm_unload_module(build_module); + return 1; + } + + // Verify package digest + dpm_log(LOG_INFO, "Verifying package digest..."); + result = checksum_verify_package_digest_memory( + metadata_data, + metadata_data_size, + build_module + ); + if (result != 0) { + dpm_log(LOG_ERROR, "Package digest verification failed"); + free(metadata_data); + free(contents_data); + free(hooks_data); + dpm_unload_module(build_module); + return 1; + } + + // Verify contents manifest digest + dpm_log(LOG_INFO, "Verifying contents manifest digest..."); + result = checksum_verify_contents_digest_memory( + contents_data, + contents_data_size, + metadata_data, + metadata_data_size, + build_module + ); + if (result != 0) { + dpm_log(LOG_ERROR, "Contents manifest verification failed"); + free(metadata_data); + free(contents_data); + free(hooks_data); + dpm_unload_module(build_module); + return 1; + } + + // Verify hooks digest + dpm_log(LOG_INFO, "Verifying hooks digest..."); + result = checksum_verify_hooks_digest_memory( + hooks_data, + hooks_data_size, + metadata_data, + metadata_data_size, + build_module + ); + if (result != 0) { + dpm_log(LOG_ERROR, "Hooks digest verification failed"); + free(metadata_data); + free(contents_data); + free(hooks_data); + dpm_unload_module(build_module); + return 1; + } + + // Clean up + free(metadata_data); + free(contents_data); + free(hooks_data); + dpm_unload_module(build_module); + + dpm_log(LOG_INFO, "All in-memory checksums verified successfully"); return 0; } \ No newline at end of file diff --git a/modules/verify/src/package_operations.cpp b/modules/verify/src/package_operations.cpp new file mode 100644 index 0000000..d9ee1fb --- /dev/null +++ b/modules/verify/src/package_operations.cpp @@ -0,0 +1,125 @@ +/** + * @file package_operations.cpp + * @brief Implementation of package operation functions + * + * Implements functions for extracting and verifying components from DPM packages. + * + * @copyright Copyright (c) 2025 SILO GROUP LLC + * @author Chris Punches + * + * Part of the Dark Horse Linux Package Manager (DPM) + */ + +#include "package_operations.hpp" + + +int get_component_from_package(const std::string& package_path, + const std::string& component_name, + unsigned char** data, + size_t* data_size) +{ + // Validate input parameters + if (package_path.empty() || component_name.empty() || !data || !data_size) { + dpm_log(LOG_ERROR, "Invalid parameters passed to get_component_from_package"); + return 1; + } + + // Initialize output parameters + *data = nullptr; + *data_size = 0; + + // Check if the package file exists + if (!std::filesystem::exists(package_path)) { + dpm_log(LOG_ERROR, ("Package file not found: " + package_path).c_str()); + return 1; + } + + // Load the build module + void* build_module = nullptr; + int result = check_and_load_build_module(build_module); + if (result != 0 || build_module == nullptr) { + dpm_log(LOG_ERROR, "Failed to load build module"); + return 1; + } + + dpm_log(LOG_DEBUG, ("Extracting " + component_name + " from package: " + package_path).c_str()); + + // Call the function from the build module + bool success = dpm_execute_symbol(build_module, "get_file_from_package_file", + package_path.c_str(), component_name.c_str(), + data, data_size); + + // Unload the build module + dpm_unload_module(build_module); + + // Check if the function call was successful + if (!success || *data == nullptr || *data_size == 0) { + dpm_log(LOG_ERROR, ("Failed to extract " + component_name + " from package").c_str()); + return 1; + } + + dpm_log(LOG_DEBUG, ("Successfully extracted " + component_name + " (" + + std::to_string(*data_size) + " bytes)").c_str()); + + return 0; +} + +/** + * @brief Extracts a file from a component archive + * + * Extracts a specific file from a component archive that has already been loaded into memory. + * Uses the build module's get_file_from_memory_loaded_archive function. + * + * @param component_data Pointer to the component archive data in memory + * @param component_size Size of the component archive in memory + * @param filename Name of the file to extract from the component + * @param data Pointer to a pointer that will be populated with the file data + * @param data_size Pointer to a size_t that will be populated with the size of the file data + * @return 0 on success, non-zero on failure + */ +int get_file_from_component(const unsigned char* component_data, + size_t component_size, + const std::string& filename, + unsigned char** data, + size_t* data_size) +{ + // Validate input parameters + if (!component_data || component_size == 0 || filename.empty() || !data || !data_size) { + dpm_log(LOG_ERROR, "Invalid parameters passed to get_file_from_component"); + return 1; + } + + // Initialize output parameters + *data = nullptr; + *data_size = 0; + + // Load the build module + void* build_module = nullptr; + int result = check_and_load_build_module(build_module); + if (result != 0 || build_module == nullptr) { + dpm_log(LOG_ERROR, "Failed to load build module"); + return 1; + } + + dpm_log(LOG_DEBUG, ("Extracting file '" + filename + "' from component archive").c_str()); + + // Call the function from the build module + bool success = dpm_execute_symbol(build_module, "get_file_from_memory_loaded_archive", + component_data, component_size, + filename.c_str(), + data, data_size); + + // Unload the build module + dpm_unload_module(build_module); + + // Check if the function call was successful + if (!success || *data == nullptr || *data_size == 0) { + dpm_log(LOG_ERROR, ("Failed to extract file '" + filename + "' from component archive").c_str()); + return 1; + } + + dpm_log(LOG_DEBUG, ("Successfully extracted file '" + filename + "' (" + + std::to_string(*data_size) + " bytes)").c_str()); + + return 0; +} \ No newline at end of file diff --git a/modules/verify/src/verification.cpp b/modules/verify/src/verification.cpp index d50fb10..dfa4984 100644 --- a/modules/verify/src/verification.cpp +++ b/modules/verify/src/verification.cpp @@ -10,10 +10,8 @@ * * Part of the Dark Horse Linux Package Manager (DPM) */ - #include "verification.hpp" - int verify_checksums_package(const std::string& package_path) { // Check if the package file exists if (!std::filesystem::exists(package_path)) { @@ -193,7 +191,6 @@ int verify_signature_package(const std::string& package_path) { return 0; } - int verify_signature_stage(const std::string& stage_dir) { // Check if the stage directory exists if (!std::filesystem::exists(stage_dir)) { @@ -212,4 +209,4 @@ int verify_signature_stage(const std::string& stage_dir) { dpm_log(LOG_INFO, "Stage directory signature verification not yet implemented"); return 0; -} \ No newline at end of file +}