Skip to content

Commit dfce905

Browse files
Add fips-hash-offline.sh
This script calculates the FIPS integrity hash for an already-linked binary offline at build time.
1 parent c685293 commit dfce905

3 files changed

Lines changed: 180 additions & 0 deletions

File tree

SCRIPTS-LIST

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ commit-tests.sh - our commit tests, must pass before a commit is accepted, use
88

99
fips-hash.sh - updates the verifyCore hash in fips_test.c
1010

11+
fips-hash-offline.sh - updates the verifyCore hash in an already-linked binary
12+
1113
fips-check.sh - checks if current wolfSSL version works against FIPS wolfCrypt
1214
comment out last line to leave working directory
1315

fips-hash-offline.sh

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
#!/bin/bash
2+
3+
# fips-hash-offline.sh
4+
#
5+
# Copyright (C) 2006-2026 wolfSSL Inc.
6+
#
7+
# This file is part of wolfSSL.
8+
#
9+
# wolfSSL is free software; you can redistribute it and/or modify
10+
# it under the terms of the GNU General Public License as published by
11+
# the Free Software Foundation; either version 3 of the License, or
12+
# (at your option) any later version.
13+
#
14+
# wolfSSL is distributed in the hope that it will be useful,
15+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
# GNU General Public License for more details.
18+
#
19+
# You should have received a copy of the GNU General Public License
20+
# along with this program; if not, write to the Free Software
21+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
22+
23+
# This script computes the wolfCrypt FIPS in-core integrity hash at compile
24+
# time directly from an already linked binary, and patches the result into the
25+
# verifyCore[] array inside that binary.
26+
#
27+
# This reproduces, at build time and against the final ELF image, exactly what
28+
# DoInCoreCheck() in wolfcrypt/src/fips_test.c computes at run time:
29+
#
30+
# HMAC-SHA256( coreKey,
31+
# .text bytes in [wolfCrypt_FIPS_first, wolfCrypt_FIPS_last)
32+
# ||
33+
# .rodata bytes in [wolfCrypt_FIPS_ro_start, wolfCrypt_FIPS_ro_end)
34+
# with the verifyCore[] bytes skipped )
35+
#
36+
# Because verifyCore[] is excluded from the digest, overwriting it with the
37+
# computed value does not change the digest, so a single pass over the linked
38+
# binary is sufficient -- no rebuild and no scraping of the module's reported
39+
# hash is required.
40+
#
41+
# Assumptions (match the optest user-space static FIPS build):
42+
# - single fixed-address (ET_EXEC) image, not stripped
43+
# - measured .text/.rodata bracketed by the wolfCrypt_FIPS_first/last and
44+
# wolfCrypt_FIPS_ro_start/ro_end symbols
45+
# - no text-segment canonicalizer and no relocation-table indirection in
46+
# effect (i.e. the plain #else path of DoInCoreCheck())
47+
#
48+
# Usage: fips-hash-offline.sh [path-to-binary]
49+
# default binary: wolfcrypt/test/testwolfcrypt
50+
51+
set -u
52+
53+
BIN="${1:-wolfcrypt/test/testwolfcrypt}"
54+
55+
die() { echo "fips-hash-offline: error: $*" >&2; exit 1; }
56+
57+
[ -f "$BIN" ] || die "binary not found: $BIN"
58+
command -v awk >/dev/null 2>&1 || die "awk not found"
59+
command -v dd >/dev/null 2>&1 || die "dd not found"
60+
command -v readelf >/dev/null 2>&1 || die "readelf not found"
61+
command -v openssl >/dev/null 2>&1 || die "openssl not found"
62+
command -v xxd >/dev/null 2>&1 || die "xxd not found"
63+
64+
# Symbol lookup from the ELF symbol table.
65+
# readelf -s columns: Num: Value Size Type Bind Vis Ndx Name
66+
# -> Value ($2) is hexadecimal, Size ($3) is decimal, Ndx ($7), Name ($8).
67+
sym_val() { readelf -sW "$BIN" | awk -v n="$1" '$8==n && $7!="UND"{print $2; exit}'; }
68+
sym_size() { readelf -sW "$BIN" | awk -v n="$1" '$8==n && $7!="UND"{print $3; exit}'; }
69+
70+
# Map a virtual address to a file offset using the containing PROGBITS section.
71+
# This avoids assuming any particular load base or section padding.
72+
vaddr_to_off() {
73+
local v="$1"
74+
local name type addr off size a o s
75+
while read -r name type addr off size; do
76+
[ "$type" = "PROGBITS" ] || continue
77+
a=$((0x$addr)); o=$((0x$off)); s=$((0x$size))
78+
if [ "$v" -ge "$a" ] && [ "$v" -lt $((a + s)) ]; then
79+
echo $((o + v - a))
80+
return 0
81+
fi
82+
done < <(readelf -SW "$BIN" | sed 's/\[[ 0-9]*\]//' | awk '/PROGBITS/{print $1, $2, $3, $4, $5}')
83+
return 1
84+
}
85+
86+
# Extract byte_count bytes starting at file_offset (both in bytes) to stdout.
87+
# Uses GNU dd byte-granular skip/count with a large block size for speed.
88+
extract() {
89+
dd if="$BIN" bs=1M iflag=skip_bytes,count_bytes skip="$1" count="$2" 2>/dev/null
90+
}
91+
92+
# --- gather the FIPS boundary and verifyCore/coreKey symbols ---
93+
FIRST_H=$(sym_val wolfCrypt_FIPS_first)
94+
LAST_H=$(sym_val wolfCrypt_FIPS_last)
95+
ROSTART_H=$(sym_val wolfCrypt_FIPS_ro_start)
96+
ROEND_H=$(sym_val wolfCrypt_FIPS_ro_end)
97+
VC_H=$(sym_val verifyCore)
98+
VCSZ=$(sym_size verifyCore)
99+
KEY_H=$(sym_val coreKey)
100+
KEYSZ=$(sym_size coreKey)
101+
102+
[ -n "$FIRST_H" ] && [ -n "$LAST_H" ] && [ -n "$ROSTART_H" ] && [ -n "$ROEND_H" ] \
103+
&& [ -n "$VC_H" ] && [ -n "$VCSZ" ] && [ -n "$KEY_H" ] && [ -n "$KEYSZ" ] \
104+
|| die "missing FIPS boundary symbols (is $BIN a non-stripped static FIPS build?)"
105+
106+
first=$((0x$FIRST_H))
107+
last=$((0x$LAST_H))
108+
rostart=$((0x$ROSTART_H))
109+
roend=$((0x$ROEND_H))
110+
vc=$((0x$VC_H))
111+
keyaddr=$((0x$KEY_H))
112+
113+
[ "$last" -gt "$first" ] || die "wolfCrypt_FIPS_last <= wolfCrypt_FIPS_first"
114+
[ "$roend" -gt "$rostart" ] || die "wolfCrypt_FIPS_ro_end <= wolfCrypt_FIPS_ro_start"
115+
116+
# Select the digest from the size of verifyCore[] (= digest_bytes*2 + 1).
117+
digest_bytes=$(( (VCSZ - 1) / 2 ))
118+
case "$digest_bytes" in
119+
32) ALG=sha256 ;;
120+
48) ALG=sha384 ;;
121+
*) die "unexpected verifyCore size ($VCSZ); cannot determine digest" ;;
122+
esac
123+
124+
# Read the HMAC key (coreKey) as ASCII hex straight out of the binary.
125+
keyoff=$(vaddr_to_off "$keyaddr") || die "cannot map coreKey address to file offset"
126+
KEYHEX=$(extract "$keyoff" $((KEYSZ - 1)))
127+
[ "${#KEYHEX}" -eq $((digest_bytes * 2)) ] || die "coreKey length mismatch in binary"
128+
129+
# File offsets for the measured regions.
130+
codeoff=$(vaddr_to_off "$first") || die "cannot map wolfCrypt_FIPS_first"
131+
roff=$(vaddr_to_off "$rostart") || die "cannot map wolfCrypt_FIPS_ro_start"
132+
133+
vc_in_ro=0
134+
if [ "$vc" -ge "$rostart" ] && [ "$vc" -lt "$roend" ]; then
135+
vc_in_ro=1
136+
aoff=$(vaddr_to_off $((vc + VCSZ))) || die "cannot map verifyCore tail"
137+
fi
138+
139+
# Compute the digest, streaming the measured bytes in the same order and with
140+
# the same verifyCore exclusion as DoInCoreCheck().
141+
NEWHASH=$(
142+
{
143+
extract "$codeoff" $((last - first))
144+
if [ "$vc_in_ro" -eq 1 ]; then
145+
extract "$roff" $((vc - rostart))
146+
extract "$aoff" $((roend - vc - VCSZ))
147+
else
148+
extract "$roff" $((roend - rostart))
149+
fi
150+
} | openssl dgst -"$ALG" -mac HMAC -macopt hexkey:"$KEYHEX" -binary \
151+
| xxd -p -c 1000 | tr -d '\n' | tr 'a-f' 'A-F'
152+
)
153+
[ "${#NEWHASH}" -eq $((digest_bytes * 2)) ] || die "hash computation failed"
154+
155+
# Overwrite the first digest_bytes*2 ASCII characters of verifyCore[] in place.
156+
# The trailing NUL terminator (byte digest_bytes*2) is left untouched.
157+
vcoff=$(vaddr_to_off "$vc") || die "cannot map verifyCore address to file offset"
158+
printf '%s' "$NEWHASH" | dd of="$BIN" bs=1M oflag=seek_bytes conv=notrunc seek="$vcoff" 2>/dev/null \
159+
|| die "failed to write verifyCore"
160+
161+
# Confirm the patch landed.
162+
CHECK=$(extract "$vcoff" $((digest_bytes * 2)))
163+
[ "$CHECK" = "$NEWHASH" ] || die "verifyCore patch verification failed"
164+
165+
ALG_UC=$(echo "$ALG" | tr 'a-z' 'A-Z')
166+
echo "fips-hash-offline: patched $BIN"
167+
echo " algorithm : HMAC-$ALG_UC"
168+
echo " code range : [0x$FIRST_H, 0x$LAST_H) ($((last - first)) bytes)"
169+
echo " ro range : [0x$ROSTART_H, 0x$ROEND_H) (verifyCore $([ "$vc_in_ro" -eq 1 ] && echo excluded || echo 'not in range'))"
170+
echo " hash : $NEWHASH"

fips-hash.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
#!/bin/sh
22

3+
# This script executes the testwolfcrypt binary to report its calculated FIPS
4+
# integrity hash, then it modifies the fips_test.c source code to update the
5+
# expected integrity hash in source.
6+
#
7+
# See fips-hash-offline.sh for a version that calculates the expected FIPS
8+
# integrity hash during the build process on the linked binary. This version is
9+
# suitable for statically linked builds.
10+
311
if test ! -x ./wolfcrypt/test/testwolfcrypt
412
then
513
echo "fips-hash: wolfCrypt test missing"

0 commit comments

Comments
 (0)