Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 31 additions & 5 deletions crypto/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -628,18 +628,43 @@ endfunction()
if(FIPS_SHARED)
# Rewrite libcrypto.so, libcrypto.dylib, or crypto.dll to inject the correct module
# hash value. For now we support the FIPS build only on Linux, macOS, iOS, and Windows.
if(MSVC)
# On Windows we use capture_hash.go to capture the computed integrity value that bcm.o prints to generate the
# correct value in generated_fips_shared_support.c. See FIPS.md for a full explanation of the process
if(MSVC AND ARCH STREQUAL "aarch64")
# On ARM64 Windows, ASLR is mandatory and cannot be disabled. The linker
# resolves ADRP immediates differently when building two separate DLLs
# (precrypto.dll vs crypto.dll), even when the FIPS module code comes from
# the same bcm.lib. This makes the two-DLL capture_hash approach produce a
# hash mismatch. Instead we use a single-DLL approach: build crypto.dll
# once with a placeholder hash, run fips_empty_main.exe (which triggers the
# integrity check, computes the real hash, and prints it), then binary-patch
# the placeholder in crypto.dll with the captured hash.
build_libcrypto(NAME crypto MODULE_SOURCE $<TARGET_OBJECTS:fipsmodule> SET_OUTPUT_NAME)

add_executable(fips_empty_main fipsmodule/fips_empty_main.c)
target_link_libraries(fips_empty_main PUBLIC crypto)
target_add_awslc_include_paths(TARGET fips_empty_main SCOPE PRIVATE)

add_custom_command(
TARGET fips_empty_main POST_BUILD
COMMAND ${GO_EXECUTABLE} run
${AWSLC_SOURCE_DIR}/util/fipstools/capture_hash/capture_hash.go
-in-executable $<TARGET_FILE:fips_empty_main>
-patch-dll $<TARGET_FILE:crypto>
WORKING_DIRECTORY ${AWSLC_SOURCE_DIR}
)
elseif(MSVC)
# On x64 Windows we use capture_hash.go to capture the computed integrity
# value that the module prints on its first (failing) run, then embed that
# value in generated_fips_shared_support.c for the final crypto.dll.
# See FIPS.md for a full explanation of the process.
build_libcrypto(NAME precrypto MODULE_SOURCE $<TARGET_OBJECTS:fipsmodule>)
add_executable(fips_empty_main fipsmodule/fips_empty_main.c)
target_link_libraries(fips_empty_main PUBLIC precrypto)
target_add_awslc_include_paths(TARGET fips_empty_main SCOPE PRIVATE)
add_custom_command(OUTPUT generated_fips_shared_support.c
COMMAND ${GO_EXECUTABLE} run
${AWSLC_SOURCE_DIR}/util/fipstools/capture_hash/capture_hash.go
-in-executable $<TARGET_FILE:fips_empty_main> > generated_fips_shared_support.c
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
-in-executable $<TARGET_FILE:fips_empty_main> > ${CMAKE_CURRENT_BINARY_DIR}/generated_fips_shared_support.c
WORKING_DIRECTORY ${AWSLC_SOURCE_DIR}
DEPENDS fips_empty_main ${AWSLC_SOURCE_DIR}/util/fipstools/capture_hash/capture_hash.go
)
add_library(
Expand All @@ -650,6 +675,7 @@ if(FIPS_SHARED)
generated_fips_shared_support.c
${AWSLC_SOURCE_DIR}/crypto/fipsmodule/cpucap/cpucap.c
)
target_compile_definitions(generated_fipsmodule PRIVATE BORINGSSL_IMPLEMENTATION S2N_BN_HIDE_SYMBOLS)
target_add_awslc_include_paths(TARGET generated_fipsmodule SCOPE PRIVATE)

build_libcrypto(NAME crypto MODULE_SOURCE $<TARGET_OBJECTS:generated_fipsmodule> SET_OUTPUT_NAME)
Expand Down
4 changes: 4 additions & 0 deletions crypto/fipsmodule/bcm.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
#pragma data_seg(".fipsda$b")
#pragma const_seg(".fipsco$b")
#pragma bss_seg(".fipsbs$b")
// Explicitly declare the FIPS rodata section with correct attributes. This
// ensures the section is known to the compiler/linker even if #pragma const_seg
// is not fully supported (e.g. clang-cl on ARM64).
#pragma section(".fipsco$b", read)
#endif

#include <openssl/chacha.h>
Expand Down
19 changes: 16 additions & 3 deletions crypto/fipsmodule/fips_shared_library_marker.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,35 +23,48 @@
#pragma data_seg(".fipsda$a")
#pragma const_seg(".fipsco$a")
#pragma bss_seg(".fipsbs$a")
// Declare the FIPS rodata section so that __declspec(allocate()) can reference
// it. This provides an explicit fallback in case #pragma const_seg is not fully
// supported by the compiler (e.g. clang-cl on ARM64).
#pragma section(".fipsco$a", read)
#endif

// Dummy but not empty function and array to avoid the compiler completely
// optimizing out the symbols.
const uint8_t *BORINGSSL_bcm_text_start(void) {
return NULL;
}
#if defined(_MSC_VER)
__declspec(allocate(".fipsco$a"))
#endif
const uint8_t BORINGSSL_bcm_rodata_start[16] =
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};

#elif defined(AWSLC_FIPS_SHARED_END)
#if defined(_MSC_VER)
#pragma code_seg(".fipstx$z")
#pragma data_seg(".fipsda$z")
#pragma const_seg(".fipsco$z")
#pragma bss_seg(".fipsbs$z")
// Declare the FIPS rodata section so that __declspec(allocate()) can reference
// it. This provides an explicit fallback in case #pragma const_seg is not fully
// supported by the compiler (e.g. clang-cl on ARM64).
#pragma section(".fipsco$z", read)
#endif

// Dummy but not empty function and array to avoid the compiler completely
// optimizing out the symbols.
const uint8_t *BORINGSSL_bcm_text_end(void){
return NULL;
}
#if defined(_MSC_VER)
__declspec(allocate(".fipsco$z"))
#endif
const uint8_t BORINGSSL_bcm_rodata_end[16] =
{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};

#else

#error "This file should be compiled only as part of the Shared FIPS build on macOS/iOS/Windows."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: "This file should be compiled only as part of the Shared FIPS build on macOS/iOS/Windows." [clang-diagnostic-error]

#error "This file should be compiled only as part of the Shared FIPS build on macOS/iOS/Windows."
 ^


#endif

8 changes: 8 additions & 0 deletions crypto/fipsmodule/fips_shared_support.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@

#include <stdint.h>

// Ensure that no ambient #pragma const_seg is active. BORINGSSL_bcm_text_hash
// MUST be placed in the default .rdata section, outside the FIPS module rodata
// boundary (.fipsco). If it were placed inside the FIPS rodata boundary, the
// integrity check would hash the expected value itself, creating a circular
// dependency that can never be satisfied.
#if defined(_MSC_VER)
#pragma const_seg()
#endif

#if defined(BORINGSSL_FIPS) && defined(BORINGSSL_SHARED_LIBRARY)
// BORINGSSL_bcm_text_hash is is default hash value for the FIPS integrity check
Expand Down
154 changes: 123 additions & 31 deletions util/fipstools/capture_hash/capture_hash.go
Original file line number Diff line number Diff line change
@@ -1,73 +1,165 @@
// 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.
// 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 to extract the correct
// fingerprint value.
//
// By default it generates a C source file on stdout containing the correct
// hash, which is used to build the final libcrypto (the two-DLL approach used
// on Windows x64).
//
// When -patch-dll is specified, it instead reads the given DLL, finds the
// placeholder hash value, replaces it with the captured hash, and writes the
// patched DLL back. This single-DLL approach avoids building two separate
// DLLs whose linker output might differ (as happens on ARM64 Windows where
// mandatory ASLR causes ADRP immediate differences between DLLs).

package main

import (
"bytes"
"encoding/hex"
"flag"
"fmt"
"os"
"os/exec"
"strings"

"github.com/aws/aws-lc/util/fipstools/fipscommon"
)

const line0 = "AWS-LC FIPS failure caused by:"
const line1 = "FIPS integrity test failed."
const expectedFailureMsg = "FIPS integrity test failed."

// This must match what is in crypto/fipsmodule/fips_shared_support.c
const line2 = "Expected: ae2cea2abda6f3ec977f9bf6949afc836827cba0a09f6b6fde52cde2cdff3180"
const hash_len = 64
const expectedHashLine = "Expected: ae2cea2abda6f3ec977f9bf6949afc836827cba0a09f6b6fde52cde2cdff3180"
const calculatedPrefix = "Calculated: "
const hashHexLen = 64

func main() {
executable := flag.String("in-executable", "", "Path to the executable file")
patchDll := flag.String("patch-dll", "", "Path to a DLL to binary-patch with the captured hash (single-DLL mode)")
flag.Parse()

if *executable == "" {
fmt.Fprintf(os.Stderr, "capture_hash: -in-executable is required\n")
os.Exit(1)
}

cmd := exec.Command(*executable)
out, err := cmd.CombinedOutput()
if err == nil {
fmt.Fprintf(os.Stderr, string(out))
panic("Executable did not fail as expected")
fmt.Fprintf(os.Stderr, "%s", out)
fmt.Fprintf(os.Stderr, "capture_hash: executable did not fail as expected\n")
os.Exit(1)
}

// Search for the expected lines by content rather than by strict line
// numbers. This makes the parser tolerant of additional diagnostic output
// that may be printed before or between the FIPS integrity test messages.
lines := strings.Split(string(out), "\r\n")
if len(lines) != 6 {
fmt.Fprintf(os.Stderr, string(out))
panic(fmt.Sprintf("Expected 6 lines in output but got %d", len(lines)))

foundFailureMsg := false
foundExpectedHash := false
hashHex := ""

for _, line := range lines {
line = strings.TrimSpace(line)
if line == expectedFailureMsg {
foundFailureMsg = true
}
if line == expectedHashLine {
foundExpectedHash = true
}
if strings.HasPrefix(line, calculatedPrefix) {
parts := strings.Fields(line)
if len(parts) >= 2 {
hashHex = parts[1]
}
}
}

if lines[0] != line0 {
fmt.Fprintf(os.Stderr, string(out))
panic(fmt.Sprintf("Expected \"%s\" got \"%s\"", line0, lines[0]))
if !foundFailureMsg {
fmt.Fprintf(os.Stderr, "%s", out)
fmt.Fprintf(os.Stderr, "capture_hash: did not find %q in output\n", expectedFailureMsg)
os.Exit(1)
}

if lines[1] != line1 {
fmt.Fprintf(os.Stderr, string(out))
panic(fmt.Sprintf("Expected \"%s\" got \"%s\"", line1, lines[1]))
if !foundExpectedHash {
fmt.Fprintf(os.Stderr, "%s", out)
fmt.Fprintf(os.Stderr, "capture_hash: did not find %q in output\n", expectedHashLine)
os.Exit(1)
}
if lines[2] != line2 {
fmt.Fprintf(os.Stderr, string(out))
panic(fmt.Sprintf("Expected \"%s\" got \"%s\"", line1, lines[1]))

if hashHex == "" {
fmt.Fprintf(os.Stderr, "%s", out)
fmt.Fprintf(os.Stderr, "capture_hash: did not find %q line in output\n", calculatedPrefix)
os.Exit(1)
}
hash := strings.Split(lines[3], " ")[1]

if len(hash) != hash_len {
fmt.Fprintf(os.Stderr, string(out))
panic(fmt.Sprintf("Hash \"%s\" is %d long, expected %d", hash, len(hash), hash_len))
if len(hashHex) != hashHexLen {
fmt.Fprintf(os.Stderr, "%s", out)
fmt.Fprintf(os.Stderr, "capture_hash: hash %q is %d chars, expected %d\n", hashHex, len(hashHex), hashHexLen)
os.Exit(1)
}

fmt.Printf(`// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
fmt.Fprintf(os.Stderr, "capture_hash: captured hash = %s\n", hashHex)

if *patchDll != "" {
// Single-DLL mode: binary-patch the placeholder hash in the DLL.
hashBytes, err := hex.DecodeString(hashHex)
if err != nil {
fmt.Fprintf(os.Stderr, "capture_hash: failed to decode hash hex: %v\n", err)
os.Exit(1)
}

fi, err := os.Stat(*patchDll)
if err != nil {
fmt.Fprintf(os.Stderr, "capture_hash: %v\n", err)
os.Exit(1)
}
perm := fi.Mode() & 0777

dllBytes, err := os.ReadFile(*patchDll)
if err != nil {
fmt.Fprintf(os.Stderr, "capture_hash: failed to read DLL: %v\n", err)
os.Exit(1)
}

offset := bytes.Index(dllBytes, fipscommon.UninitHashValue[:])
if offset < 0 {
fmt.Fprintf(os.Stderr, "capture_hash: placeholder hash not found in %s\n", *patchDll)
os.Exit(1)
}

// Verify uniqueness — the placeholder must appear exactly once.
if bytes.Index(dllBytes[offset+len(fipscommon.UninitHashValue):], fipscommon.UninitHashValue[:]) >= 0 {
fmt.Fprintf(os.Stderr, "capture_hash: found multiple occurrences of placeholder hash in %s\n", *patchDll)
os.Exit(1)
}

copy(dllBytes[offset:], hashBytes)

if err := os.WriteFile(*patchDll, dllBytes, perm); err != nil {
fmt.Fprintf(os.Stderr, "capture_hash: failed to write patched DLL: %v\n", err)
os.Exit(1)
}

fmt.Fprintf(os.Stderr, "capture_hash: patched %s at offset 0x%x\n", *patchDll, offset)
} else {
// Two-DLL mode: generate a C source file with the correct hash on stdout.
fmt.Printf(`// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC
// This file is generated by: 'go run util/fipstools/capture_hash/capture_hash.go -in-executable %s'
#include <stdint.h>
const uint8_t BORINGSSL_bcm_text_hash[32] = {
`, *executable)
for i := 0; i < len(hash); i += 2 {
fmt.Printf("0x%s, ", hash[i:i+2])
}
fmt.Printf(`
for i := 0; i < len(hashHex); i += 2 {
fmt.Printf("0x%s, ", hashHex[i:i+2])
}
fmt.Printf(`
};
`)
}
}
Loading