From 771973e5d7cd6bc4b4e39cbc5eb2b65eab594771 Mon Sep 17 00:00:00 2001 From: prikhap Date: Mon, 11 Aug 2025 19:57:05 -0700 Subject: [PATCH 1/7] capture hash functionality --- .github/workflows/test-cpp-capture-hash.yml | 48 +++++ crypto/CMakeLists.txt | 10 + tests/ci/run_capture_hash_cpp.bat | 85 ++++++++ util/fipstools/inject_hash_cpp/CMakeLists.txt | 68 ++++-- .../inject_hash_cpp/capture_hash.cpp | 195 ++++++++++++++++++ 5 files changed, 384 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/test-cpp-capture-hash.yml create mode 100644 tests/ci/run_capture_hash_cpp.bat create mode 100644 util/fipstools/inject_hash_cpp/capture_hash.cpp diff --git a/.github/workflows/test-cpp-capture-hash.yml b/.github/workflows/test-cpp-capture-hash.yml new file mode 100644 index 0000000000..a2fac23181 --- /dev/null +++ b/.github/workflows/test-cpp-capture-hash.yml @@ -0,0 +1,48 @@ +name: Test C++ capture_hash Implementation for Windows + +on: + pull_request: + branches: [ inject-hash-cpp-experiment ] + +jobs: + test-cpp-capture-hash: + runs-on: windows-latest + strategy: + matrix: + build-type: ['shared'] + # include: + # - build-type: 'static' + #skipping handling of static builds for now + # Don't cancel all jobs if one fails + fail-fast: false + + steps: + - uses: actions/checkout@v2 + with: + submodules: 'recursive' + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: '1.20' + + - name: Setup Visual Studio Environment + uses: microsoft/setup-msbuild@v1.1 + + - name: Install Dependencies (Windows) + run: | + choco install ninja cmake perl golang --confirm + + - name: Verify Dependencies + run: | + cmake --version + ninja --version + go version + perl --version + cl + + - name: Build and Test (Windows) + env: + BUILD_TYPE: ${{ matrix.build-type }} + run: | + .\tests\ci\run_capture_hash_cpp.bat \ No newline at end of file diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index fd9866f6f9..0027380d2b 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -624,6 +624,15 @@ if(FIPS_SHARED) target_link_libraries(fips_empty_main PUBLIC precrypto) target_include_directories(fips_empty_main PRIVATE ${AWSLC_SOURCE_DIR}/include) target_include_directories(fips_empty_main BEFORE PRIVATE ${AWSLC_BINARY_DIR}/symbol_prefix_include) + if(USE_CPP_INJECT_HASH) + add_custom_command(OUTPUT generated_fips_shared_support.c + COMMAND ${CMAKE_BINARY_DIR}/util/fipstools/inject_hash_cpp/capture_hash_cpp + -in-executable $ + > generated_fips_shared_support.c + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS fips_empty_main capture_hash_cpp + ) + else() add_custom_command(OUTPUT generated_fips_shared_support.c COMMAND ${GO_EXECUTABLE} run ${AWSLC_SOURCE_DIR}/util/fipstools/capture_hash/capture_hash.go @@ -631,6 +640,7 @@ if(FIPS_SHARED) WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} DEPENDS fips_empty_main ${AWSLC_SOURCE_DIR}/util/fipstools/capture_hash/capture_hash.go ) + endif() add_library( generated_fipsmodule diff --git a/tests/ci/run_capture_hash_cpp.bat b/tests/ci/run_capture_hash_cpp.bat new file mode 100644 index 0000000000..dc2ad47dcb --- /dev/null +++ b/tests/ci/run_capture_hash_cpp.bat @@ -0,0 +1,85 @@ +@echo off +setlocal enabledelayedexpansion + +REM Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +REM SPDX-License-Identifier: Apache-2.0 OR ISC +REM Adapted from tests/ci/run_windows_tests.bat + +echo Running capture_hash.cpp tests... + +REM Setup environment (from run_windows_tests.bat) +set SRC_ROOT=%cd% +set BUILD_DIR=%SRC_ROOT%\test_build_dir + +REM Setup Visual Studio environment - using same pattern as run_windows_tests.bat +REM We'll assume GitHub Actions provides the right VS environment, but check anyway +cl >nul 2>&1 +if !errorlevel! neq 0 ( + echo Error: Visual Studio build tools not available + exit /b 1 +) + +REM Build with our specific configuration (adapted from existing :build function) +call :build RelWithDebInfo "-DFIPS=1 -DBUILD_SHARED_LIBS=1 -DUSE_CPP_INJECT_HASH=ON" || goto error + +cd /d "%BUILD_DIR%" + +REM Initialize error counter +set ERRORS=0 + +echo. +echo TESTING CAPTURE_HASH.CPP WITH EDGE CASES... + +REM Test 1: No arguments (should fail) +echo Running test: No arguments test +util\fipstools\inject_hash_cpp\capture_hash_cpp.exe >nul 2>&1 +if !errorlevel! == 0 ( + echo Test 'No arguments test' was expected to fail but succeeded + set /a ERRORS+=1 +) else ( + echo Test 'No arguments test' failed as expected +) + +REM Test 2: Invalid file (should fail) +echo Running test: Invalid file test +util\fipstools\inject_hash_cpp\capture_hash_cpp.exe -in-executable nonexistent.exe >nul 2>&1 +if !errorlevel! == 0 ( + echo Test 'Invalid file test' was expected to fail but succeeded + set /a ERRORS+=1 +) else ( + echo Test 'Invalid file test' failed as expected +) + +REM ... rest of your tests ... + +echo. +echo === Summary === +echo Total errors: !ERRORS! + +if !ERRORS! gtr 0 ( + echo One or more tests failed + exit /b 1 +) else ( + echo All tests passed + exit /b 0 +) + +REM Build function copied from run_windows_tests.bat +:build +@echo on +@echo LOG: %date%-%time% %1 %2 build started with cmake generation +cd %SRC_ROOT% +rmdir /s /q %BUILD_DIR% +mkdir %BUILD_DIR% +cd %BUILD_DIR% + +cmake -GNinja -DCMAKE_BUILD_TYPE=%~1 %~2 %SRC_ROOT% || goto error + +@echo LOG: %date%-%time% %1 %2 cmake generation complete, starting build +ninja || goto error +@echo LOG: %date%-%time% %1 %2 build complete +exit /b 0 + +:error +echo Failed with error #%errorlevel%. +exit /b 1 \ No newline at end of file diff --git a/util/fipstools/inject_hash_cpp/CMakeLists.txt b/util/fipstools/inject_hash_cpp/CMakeLists.txt index 90a41efbe8..122849e562 100644 --- a/util/fipstools/inject_hash_cpp/CMakeLists.txt +++ b/util/fipstools/inject_hash_cpp/CMakeLists.txt @@ -1,28 +1,52 @@ if(USE_CPP_INJECT_HASH) - add_executable(inject_hash_cpp - inject_hash.cpp - ../../../tool/args.cc - ) + if(MSVC) + # On Windows, only build capture_hash_cpp + add_executable(capture_hash_cpp + capture_hash.cpp + ../../../tool/args.cc + ) - target_include_directories(inject_hash_cpp PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_SOURCE_DIR}/include - ${CMAKE_SOURCE_DIR} # Add this line to find tool/internal.h - ) - - # due to aws-lc's nature of converting every warning into an error, - # we need to disable some warnings that are coming from the LIEF submodule - if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") - target_compile_options(inject_hash_cpp PRIVATE - -Wno-overloaded-virtual - -Wno-unused-parameter + target_include_directories(capture_hash_cpp PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR} + ) + + target_compile_options(capture_hash_cpp PRIVATE + /wd4577 # Suppress 'noexcept' warning ) - endif() - target_link_libraries(inject_hash_cpp PRIVATE - LIEF::LIEF - fips_hashing - ) + set_target_properties(capture_hash_cpp PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON + ) + else() + add_executable(inject_hash_cpp + inject_hash.cpp + ../../../tool/args.cc + ) + + target_include_directories(inject_hash_cpp PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR} # Add this line to find tool/internal.h + ) + + # due to aws-lc's nature of converting every warning into an error, + # we need to disable some warnings that are coming from the LIEF submodule + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + target_compile_options(inject_hash_cpp PRIVATE + -Wno-overloaded-virtual + -Wno-unused-parameter + ) + endif() + + target_link_libraries(inject_hash_cpp PRIVATE + LIEF::LIEF + fips_hashing + ) + endif() endif() diff --git a/util/fipstools/inject_hash_cpp/capture_hash.cpp b/util/fipstools/inject_hash_cpp/capture_hash.cpp new file mode 100644 index 0000000000..bbc8c2feac --- /dev/null +++ b/util/fipstools/inject_hash_cpp/capture_hash.cpp @@ -0,0 +1,195 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +// capture_hash runs another executable that has been linked with libcrypto. It expects the libcrypto to run the +// power-on self-tests and fail due to a fingerprint mismatch. capture_hash parses the output, takes the correct +// fingerprint value, and generates a new C file that contains the correct fingerprint which is used to build the +// final libcrypto. + +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOG_ERROR(fmt, ...) fprintf(stderr, "Error: " fmt "\n", ##__VA_ARGS__) +#define LOG_INFO(fmt, ...) fprintf(stdout, fmt "\n", ##__VA_ARGS__) + +const std::string LINE0 = "AWS-LC FIPS failure caused by:"; +const std::string LINE1 = "FIPS integrity test failed."; +// This must match what is in crypto/fipsmodule/fips_shared_support.c +const std::string LINE2 = "Expected: ae2cea2abda6f3ec977f9bf6949afc836827cba0a09f6b6fde52cde2cdff3180"; +const int HASH_LEN = 64; + +static const argument_t kArguments[] = { + {"-in-executable", kRequiredArgument, "Input executable file path"}, + {"", kOptionalArgument, ""} // Terminating element +}; + +static void print_usage() { + LOG_INFO("Usage: capture_hash_cpp -in-executable "); + PrintUsage(kArguments); +} + +// Function declarations +static std::vector split_string(const std::string& str, const std::string& delimiter); +static std::vector split_by_space(const std::string& str); +static bool execute_command(const std::string& executable, std::string& output); + +static std::vector split_string(const std::string& str, const std::string& delimiter) { + std::vector tokens; + size_t start = 0; + size_t end = str.find(delimiter); + + while (end != std::string::npos) { + tokens.push_back(str.substr(start, end - start)); + start = end + delimiter.length(); + end = str.find(delimiter, start); + } + + tokens.push_back(str.substr(start)); + return tokens; +} + +static std::vector split_by_space(const std::string& str) { + std::vector tokens; + std::istringstream iss(str); + std::string token; + + while (iss >> token) { + tokens.push_back(token); + } + + return tokens; +} + +static bool execute_command(const std::string& executable, std::string& output) { + std::string command = executable + " 2>&1"; // Redirect stderr to stdout + std::cerr << "Executing command: " << command << std::endl; + char buffer[128]; + FILE* pipe = _popen(command.c_str(), "r"); + + if (!pipe) { + std::cerr << "Error: _popen() failed: " << strerror(errno) << std::endl; + return false; + } + + while (fgets(buffer, sizeof(buffer), pipe) != nullptr) { + output += buffer; + } + std::cerr << "Raw command output:\n" << output << std::endl; + + int status = _pclose(pipe); + bool succeeded = (status == 0); + + std::cerr << "Command exit status: " << status << std::endl; + + // Check if the command succeeded when it should have failed + if (succeeded) { + std::cerr << output; + std::cerr << "Error: Executable did not fail as expected" << std::endl; + return false; + } + + return true; +} + +int main(int argc, char* argv[]) { + std::vector args; + for (int i = 1; i < argc; i++) { + args.push_back(argv[i]); + } + + args_map_t args_map; + args_list_t extra_args; + + if (!ParseKeyValueArguments(args_map, extra_args, args, kArguments)) { + LOG_ERROR("Failed to parse arguments"); + print_usage(); + return 1; + } + + std::string executable; + if (!GetString(&executable, "-in-executable", "", args_map)) { + LOG_ERROR("Error getting executable path"); + print_usage(); + return 1; + } + + + std::string output; + if (!execute_command(executable, output)) { + return 1; + } + + // Split output into lines - checking for both Windows (\r\n) and Unix (\n) line endings + std::vector lines; + if (output.find("\r\n") != std::string::npos) { + lines = split_string(output, "\r\n"); + std::cerr << "Found Windows-style line endings" << std::endl; + } else { + lines = split_string(output, "\n"); + std::cerr << "Found Unix-style line endings" << std::endl; + } + + std::cerr << "Number of lines in output: " << lines.size() << std::endl; + + if (lines.size() != 6) { + std::cerr << output; + std::cerr << "Error: Expected 6 lines in output but got " << lines.size() << std::endl; + return 1; + } + + if (lines[0] != LINE0) { + std::cerr << output; + std::cerr << "Error: Expected \"" << LINE0 << "\" got \"" << lines[0] << "\"" << std::endl; + return 1; + } + + if (lines[1] != LINE1) { + std::cerr << output; + std::cerr << "Error: Expected \"" << LINE1 << "\" got \"" << lines[1] << "\"" << std::endl; + return 1; + } + + if (lines[2] != LINE2) { + std::cerr << output; + std::cerr << "Error: Expected \"" << LINE2 << "\" got \"" << lines[2] << "\"" << std::endl; + return 1; + } + + auto hash_parts = split_by_space(lines[3]); + if (hash_parts.size() < 2) { + std::cerr << output; + std::cerr << "Error: Could not parse hash from line: " << lines[3] << std::endl; + return 1; + } + + std::string hash = hash_parts[1]; + std::cerr << "Found hash value: " << hash << std::endl; + + if (hash.length() != HASH_LEN) { + std::cerr << output; + std::cerr << "Error: Hash \"" << hash << "\" is " << hash.length() + << " long, expected " << HASH_LEN << std::endl; + return 1; + } + + // Output the generated C code + std::cout << "// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.\n" + << "// SPDX-License-Identifier: Apache-2.0 OR ISC\n" + << "// This file is generated by: 'capture_hash_cpp -in-executable " << executable << "'\n" + << "#include \n" + << "const uint8_t BORINGSSL_bcm_text_hash[32] = {\n"; + + for (size_t i = 0; i < hash.length(); i += 2) { + std::cout << "0x" << hash.substr(i, 2) << ", "; + } + + std::cout << "\n};\n"; + + return 0; +} From 5260db7c2cde1d99e929a101f7d811e09553403f Mon Sep 17 00:00:00 2001 From: prikhap Date: Mon, 11 Aug 2025 20:08:16 -0700 Subject: [PATCH 2/7] installation fixes --- .github/workflows/test-cpp-capture-hash.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-cpp-capture-hash.yml b/.github/workflows/test-cpp-capture-hash.yml index a2fac23181..ee21b9e3f8 100644 --- a/.github/workflows/test-cpp-capture-hash.yml +++ b/.github/workflows/test-cpp-capture-hash.yml @@ -31,8 +31,11 @@ jobs: - name: Install Dependencies (Windows) run: | - choco install ninja cmake perl golang --confirm - + choco install cmake --confirm --no-progress || echo "cmake already installed" + choco install ninja --confirm --no-progress || echo "ninja already installed" + choco install strawberryperl --confirm --no-progress || echo "strawberryperl already installed" + choco install golang --confirm --no-progress || echo "golang already installed" + - name: Verify Dependencies run: | cmake --version From 5975c6a4bf754274e0dc717d8014705cfeac466e Mon Sep 17 00:00:00 2001 From: prikhap Date: Mon, 11 Aug 2025 20:17:06 -0700 Subject: [PATCH 3/7] installation vs env fix --- .github/workflows/test-cpp-capture-hash.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-cpp-capture-hash.yml b/.github/workflows/test-cpp-capture-hash.yml index ee21b9e3f8..d7112eff13 100644 --- a/.github/workflows/test-cpp-capture-hash.yml +++ b/.github/workflows/test-cpp-capture-hash.yml @@ -10,10 +10,6 @@ jobs: strategy: matrix: build-type: ['shared'] - # include: - # - build-type: 'static' - #skipping handling of static builds for now - # Don't cancel all jobs if one fails fail-fast: false steps: @@ -27,7 +23,9 @@ jobs: go-version: '1.20' - name: Setup Visual Studio Environment - uses: microsoft/setup-msbuild@v1.1 + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 - name: Install Dependencies (Windows) run: | From d60a2fe8bee31a8a70f0cf757a2bf3f9136c760a Mon Sep 17 00:00:00 2001 From: prikhap Date: Mon, 11 Aug 2025 22:59:17 -0700 Subject: [PATCH 4/7] installation nasm fix --- .github/workflows/test-cpp-capture-hash.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test-cpp-capture-hash.yml b/.github/workflows/test-cpp-capture-hash.yml index d7112eff13..1b5179ce97 100644 --- a/.github/workflows/test-cpp-capture-hash.yml +++ b/.github/workflows/test-cpp-capture-hash.yml @@ -33,6 +33,7 @@ jobs: choco install ninja --confirm --no-progress || echo "ninja already installed" choco install strawberryperl --confirm --no-progress || echo "strawberryperl already installed" choco install golang --confirm --no-progress || echo "golang already installed" + choco install nasm --confirm --no-progress || echo "nasm already installed" - name: Verify Dependencies run: | From c0135f5ab24d5035d06a90a985ba397e91854a0f Mon Sep 17 00:00:00 2001 From: prikhap Date: Mon, 11 Aug 2025 23:35:46 -0700 Subject: [PATCH 5/7] sanity test case --- tests/ci/run_capture_hash_cpp.bat | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/ci/run_capture_hash_cpp.bat b/tests/ci/run_capture_hash_cpp.bat index dc2ad47dcb..85c7ade6b8 100644 --- a/tests/ci/run_capture_hash_cpp.bat +++ b/tests/ci/run_capture_hash_cpp.bat @@ -50,7 +50,15 @@ if !errorlevel! == 0 ( echo Test 'Invalid file test' failed as expected ) -REM ... rest of your tests ... +REM Test 3: FIPS integrity sanity check +echo Running test: FIPS integrity sanity check +crypto\crypto_test.exe >nul 2>&1 +if !errorlevel! neq 0 ( + echo Test 'FIPS integrity sanity check' failed - FIPS module has integrity issues + set /a ERRORS+=1 +) else ( + echo Test 'FIPS integrity sanity check' passed - FIPS module integrity OK +) echo. echo === Summary === From 98cdc0c3288d960b2fca0dc83ac9f136438e452c Mon Sep 17 00:00:00 2001 From: prikhap Date: Tue, 12 Aug 2025 13:02:49 -0700 Subject: [PATCH 6/7] minor fixes in the ci/test --- .github/workflows/test-cpp-capture-hash.yml | 6 ------ tests/ci/run_capture_hash_cpp.bat | 8 ++++++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test-cpp-capture-hash.yml b/.github/workflows/test-cpp-capture-hash.yml index 1b5179ce97..826271e4f4 100644 --- a/.github/workflows/test-cpp-capture-hash.yml +++ b/.github/workflows/test-cpp-capture-hash.yml @@ -7,10 +7,6 @@ on: jobs: test-cpp-capture-hash: runs-on: windows-latest - strategy: - matrix: - build-type: ['shared'] - fail-fast: false steps: - uses: actions/checkout@v2 @@ -44,7 +40,5 @@ jobs: cl - name: Build and Test (Windows) - env: - BUILD_TYPE: ${{ matrix.build-type }} run: | .\tests\ci\run_capture_hash_cpp.bat \ No newline at end of file diff --git a/tests/ci/run_capture_hash_cpp.bat b/tests/ci/run_capture_hash_cpp.bat index 85c7ade6b8..3d7554c050 100644 --- a/tests/ci/run_capture_hash_cpp.bat +++ b/tests/ci/run_capture_hash_cpp.bat @@ -50,7 +50,7 @@ if !errorlevel! == 0 ( echo Test 'Invalid file test' failed as expected ) -REM Test 3: FIPS integrity sanity check +REM Test 3: FIPS integrity sanity check(should succeed) echo Running test: FIPS integrity sanity check crypto\crypto_test.exe >nul 2>&1 if !errorlevel! neq 0 ( @@ -73,6 +73,9 @@ if !ERRORS! gtr 0 ( ) REM Build function copied from run_windows_tests.bat +REM Note: The build function is intentionally duplicated from run_windows_tests.bat +REM as Windows batch files don't support function sharing like bash scripts. +REM This keeps the script self-contained and more reliable. :build @echo on @echo LOG: %date%-%time% %1 %2 build started with cmake generation @@ -86,8 +89,9 @@ cmake -GNinja -DCMAKE_BUILD_TYPE=%~1 %~2 %SRC_ROOT% || goto error @echo LOG: %date%-%time% %1 %2 cmake generation complete, starting build ninja || goto error @echo LOG: %date%-%time% %1 %2 build complete +@echo off exit /b 0 :error echo Failed with error #%errorlevel%. -exit /b 1 \ No newline at end of file +exit /b 1 From 9ec30a14ac603f156549fd40124e0b8450e255e2 Mon Sep 17 00:00:00 2001 From: prikhap Date: Wed, 13 Aug 2025 14:56:32 -0700 Subject: [PATCH 7/7] lief submodule ci integration --- .github/workflows/test-cpp-inject-hash.yml | 26 ++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-cpp-inject-hash.yml b/.github/workflows/test-cpp-inject-hash.yml index c11f16d76a..241afaf4e3 100644 --- a/.github/workflows/test-cpp-inject-hash.yml +++ b/.github/workflows/test-cpp-inject-hash.yml @@ -19,9 +19,31 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v2 + - name: Checkout main repository + uses: actions/checkout@v4 with: - submodules: 'recursive' + fetch-depth: 0 + # Don't initialize submodules yet + + - name: Handle LIEF submodule specifically + run: | + git config --global --add safe.directory '*' + echo "Cleaning any existing LIEF directory..." + rm -rf third_party/lief + + echo "Initializing only the LIEF submodule..." + git submodule init third_party/lief + git submodule update third_party/lief + + echo "Verifying LIEF submodule..." + ls -la third_party/lief + + echo "Now initializing remaining submodules..." + git submodule update --init --recursive + + - name: Verify all submodules + run: | + git submodule status - name: Set up Go uses: actions/setup-go@v2