|
1 | 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. |
2 | 2 | // SPDX-License-Identifier: Apache-2.0 OR ISC |
3 | 3 |
|
4 | | -// capture_hash runs another executable that has been linked with libcrypto. It expects the libcrypto to run the |
5 | | -// power-on self-tests and fail due to a fingerprint mismatch. capture_hash parses the output, takes the correct |
6 | | -// fingerprint value, and generates a new C file that contains the correct fingerprint which is used to build the |
7 | | -// final libcrypto. |
| 4 | +// capture_hash runs another executable that has been linked with libcrypto. It |
| 5 | +// expects the libcrypto to run the power-on self-tests and fail due to a |
| 6 | +// fingerprint mismatch. capture_hash parses the output to extract the correct |
| 7 | +// fingerprint value. |
| 8 | +// |
| 9 | +// By default it generates a C source file on stdout containing the correct |
| 10 | +// hash, which is used to build the final libcrypto (the two-DLL approach used |
| 11 | +// on Windows x64). |
| 12 | +// |
| 13 | +// When -patch-dll is specified, it instead reads the given DLL, finds the |
| 14 | +// placeholder hash value, replaces it with the captured hash, and writes the |
| 15 | +// patched DLL back. This single-DLL approach avoids building two separate |
| 16 | +// DLLs whose linker output might differ (as happens on ARM64 Windows where |
| 17 | +// mandatory ASLR causes ADRP immediate differences between DLLs). |
8 | 18 |
|
9 | 19 | package main |
10 | 20 |
|
11 | 21 | import ( |
| 22 | + "bytes" |
| 23 | + "encoding/hex" |
12 | 24 | "flag" |
13 | 25 | "fmt" |
14 | 26 | "os" |
15 | 27 | "os/exec" |
16 | 28 | "strings" |
| 29 | + |
| 30 | + "github.com/aws/aws-lc/util/fipstools/fipscommon" |
17 | 31 | ) |
18 | 32 |
|
19 | | -const line0 = "AWS-LC FIPS failure caused by:" |
20 | | -const line1 = "FIPS integrity test failed." |
| 33 | +const expectedFailureMsg = "FIPS integrity test failed." |
21 | 34 |
|
22 | 35 | // This must match what is in crypto/fipsmodule/fips_shared_support.c |
23 | | -const line2 = "Expected: ae2cea2abda6f3ec977f9bf6949afc836827cba0a09f6b6fde52cde2cdff3180" |
24 | | -const hash_len = 64 |
| 36 | +const expectedHashLine = "Expected: ae2cea2abda6f3ec977f9bf6949afc836827cba0a09f6b6fde52cde2cdff3180" |
| 37 | +const calculatedPrefix = "Calculated: " |
| 38 | +const hashHexLen = 64 |
25 | 39 |
|
26 | 40 | func main() { |
27 | 41 | executable := flag.String("in-executable", "", "Path to the executable file") |
| 42 | + patchDll := flag.String("patch-dll", "", "Path to a DLL to binary-patch with the captured hash (single-DLL mode)") |
28 | 43 | flag.Parse() |
| 44 | + |
| 45 | + if *executable == "" { |
| 46 | + fmt.Fprintf(os.Stderr, "capture_hash: -in-executable is required\n") |
| 47 | + os.Exit(1) |
| 48 | + } |
| 49 | + |
29 | 50 | cmd := exec.Command(*executable) |
30 | 51 | out, err := cmd.CombinedOutput() |
31 | 52 | if err == nil { |
32 | | - fmt.Fprintf(os.Stderr, string(out)) |
33 | | - panic("Executable did not fail as expected") |
| 53 | + fmt.Fprintf(os.Stderr, "%s", out) |
| 54 | + fmt.Fprintf(os.Stderr, "capture_hash: executable did not fail as expected\n") |
| 55 | + os.Exit(1) |
34 | 56 | } |
| 57 | + |
| 58 | + // Search for the expected lines by content rather than by strict line |
| 59 | + // numbers. This makes the parser tolerant of additional diagnostic output |
| 60 | + // that may be printed before or between the FIPS integrity test messages. |
35 | 61 | lines := strings.Split(string(out), "\r\n") |
36 | | - if len(lines) != 6 { |
37 | | - fmt.Fprintf(os.Stderr, string(out)) |
38 | | - panic(fmt.Sprintf("Expected 6 lines in output but got %d", len(lines))) |
| 62 | + |
| 63 | + foundFailureMsg := false |
| 64 | + foundExpectedHash := false |
| 65 | + hashHex := "" |
| 66 | + |
| 67 | + for _, line := range lines { |
| 68 | + line = strings.TrimSpace(line) |
| 69 | + if line == expectedFailureMsg { |
| 70 | + foundFailureMsg = true |
| 71 | + } |
| 72 | + if line == expectedHashLine { |
| 73 | + foundExpectedHash = true |
| 74 | + } |
| 75 | + if strings.HasPrefix(line, calculatedPrefix) { |
| 76 | + parts := strings.Fields(line) |
| 77 | + if len(parts) >= 2 { |
| 78 | + hashHex = parts[1] |
| 79 | + } |
| 80 | + } |
39 | 81 | } |
40 | 82 |
|
41 | | - if lines[0] != line0 { |
42 | | - fmt.Fprintf(os.Stderr, string(out)) |
43 | | - panic(fmt.Sprintf("Expected \"%s\" got \"%s\"", line0, lines[0])) |
| 83 | + if !foundFailureMsg { |
| 84 | + fmt.Fprintf(os.Stderr, "%s", out) |
| 85 | + fmt.Fprintf(os.Stderr, "capture_hash: did not find %q in output\n", expectedFailureMsg) |
| 86 | + os.Exit(1) |
44 | 87 | } |
45 | 88 |
|
46 | | - if lines[1] != line1 { |
47 | | - fmt.Fprintf(os.Stderr, string(out)) |
48 | | - panic(fmt.Sprintf("Expected \"%s\" got \"%s\"", line1, lines[1])) |
| 89 | + if !foundExpectedHash { |
| 90 | + fmt.Fprintf(os.Stderr, "%s", out) |
| 91 | + fmt.Fprintf(os.Stderr, "capture_hash: did not find %q in output\n", expectedHashLine) |
| 92 | + os.Exit(1) |
49 | 93 | } |
50 | | - if lines[2] != line2 { |
51 | | - fmt.Fprintf(os.Stderr, string(out)) |
52 | | - panic(fmt.Sprintf("Expected \"%s\" got \"%s\"", line1, lines[1])) |
| 94 | + |
| 95 | + if hashHex == "" { |
| 96 | + fmt.Fprintf(os.Stderr, "%s", out) |
| 97 | + fmt.Fprintf(os.Stderr, "capture_hash: did not find %q line in output\n", calculatedPrefix) |
| 98 | + os.Exit(1) |
53 | 99 | } |
54 | | - hash := strings.Split(lines[3], " ")[1] |
55 | 100 |
|
56 | | - if len(hash) != hash_len { |
57 | | - fmt.Fprintf(os.Stderr, string(out)) |
58 | | - panic(fmt.Sprintf("Hash \"%s\" is %d long, expected %d", hash, len(hash), hash_len)) |
| 101 | + if len(hashHex) != hashHexLen { |
| 102 | + fmt.Fprintf(os.Stderr, "%s", out) |
| 103 | + fmt.Fprintf(os.Stderr, "capture_hash: hash %q is %d chars, expected %d\n", hashHex, len(hashHex), hashHexLen) |
| 104 | + os.Exit(1) |
59 | 105 | } |
60 | 106 |
|
61 | | - fmt.Printf(`// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. |
| 107 | + fmt.Fprintf(os.Stderr, "capture_hash: captured hash = %s\n", hashHex) |
| 108 | + |
| 109 | + if *patchDll != "" { |
| 110 | + // Single-DLL mode: binary-patch the placeholder hash in the DLL. |
| 111 | + hashBytes, err := hex.DecodeString(hashHex) |
| 112 | + if err != nil { |
| 113 | + fmt.Fprintf(os.Stderr, "capture_hash: failed to decode hash hex: %v\n", err) |
| 114 | + os.Exit(1) |
| 115 | + } |
| 116 | + |
| 117 | + fi, err := os.Stat(*patchDll) |
| 118 | + if err != nil { |
| 119 | + fmt.Fprintf(os.Stderr, "capture_hash: %v\n", err) |
| 120 | + os.Exit(1) |
| 121 | + } |
| 122 | + perm := fi.Mode() & 0777 |
| 123 | + |
| 124 | + dllBytes, err := os.ReadFile(*patchDll) |
| 125 | + if err != nil { |
| 126 | + fmt.Fprintf(os.Stderr, "capture_hash: failed to read DLL: %v\n", err) |
| 127 | + os.Exit(1) |
| 128 | + } |
| 129 | + |
| 130 | + offset := bytes.Index(dllBytes, fipscommon.UninitHashValue[:]) |
| 131 | + if offset < 0 { |
| 132 | + fmt.Fprintf(os.Stderr, "capture_hash: placeholder hash not found in %s\n", *patchDll) |
| 133 | + os.Exit(1) |
| 134 | + } |
| 135 | + |
| 136 | + // Verify uniqueness — the placeholder must appear exactly once. |
| 137 | + if bytes.Index(dllBytes[offset+len(fipscommon.UninitHashValue):], fipscommon.UninitHashValue[:]) >= 0 { |
| 138 | + fmt.Fprintf(os.Stderr, "capture_hash: found multiple occurrences of placeholder hash in %s\n", *patchDll) |
| 139 | + os.Exit(1) |
| 140 | + } |
| 141 | + |
| 142 | + copy(dllBytes[offset:], hashBytes) |
| 143 | + |
| 144 | + if err := os.WriteFile(*patchDll, dllBytes, perm); err != nil { |
| 145 | + fmt.Fprintf(os.Stderr, "capture_hash: failed to write patched DLL: %v\n", err) |
| 146 | + os.Exit(1) |
| 147 | + } |
| 148 | + |
| 149 | + fmt.Fprintf(os.Stderr, "capture_hash: patched %s at offset 0x%x\n", *patchDll, offset) |
| 150 | + } else { |
| 151 | + // Two-DLL mode: generate a C source file with the correct hash on stdout. |
| 152 | + fmt.Printf(`// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. |
62 | 153 | // SPDX-License-Identifier: Apache-2.0 OR ISC |
63 | 154 | // This file is generated by: 'go run util/fipstools/capture_hash/capture_hash.go -in-executable %s' |
64 | 155 | #include <stdint.h> |
65 | 156 | const uint8_t BORINGSSL_bcm_text_hash[32] = { |
66 | 157 | `, *executable) |
67 | | - for i := 0; i < len(hash); i += 2 { |
68 | | - fmt.Printf("0x%s, ", hash[i:i+2]) |
69 | | - } |
70 | | - fmt.Printf(` |
| 158 | + for i := 0; i < len(hashHex); i += 2 { |
| 159 | + fmt.Printf("0x%s, ", hashHex[i:i+2]) |
| 160 | + } |
| 161 | + fmt.Printf(` |
71 | 162 | }; |
72 | 163 | `) |
| 164 | + } |
73 | 165 | } |
0 commit comments