mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-08-04 00:34:09 +02:00
173 lines
7.1 KiB
Bash
Executable File
173 lines
7.1 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# fips-hash-offline.sh
|
|
#
|
|
# Copyright (C) 2006-2026 wolfSSL Inc.
|
|
#
|
|
# This file is part of wolfSSL.
|
|
#
|
|
# wolfSSL is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# wolfSSL is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
|
|
|
|
# This script computes the wolfCrypt FIPS in-core integrity hash at compile
|
|
# time directly from an already linked binary, and patches the result into the
|
|
# verifyCore[] array inside that binary.
|
|
#
|
|
# This reproduces, at build time and against the final ELF image, exactly what
|
|
# DoInCoreCheck() in wolfcrypt/src/fips_test.c computes at run time:
|
|
#
|
|
# HMAC-SHA256( coreKey,
|
|
# .text bytes in [wolfCrypt_FIPS_first, wolfCrypt_FIPS_last)
|
|
# ||
|
|
# .rodata bytes in [wolfCrypt_FIPS_ro_start, wolfCrypt_FIPS_ro_end)
|
|
# with the verifyCore[] bytes skipped )
|
|
#
|
|
# Because verifyCore[] is excluded from the digest, overwriting it with the
|
|
# computed value does not change the digest, so a single pass over the linked
|
|
# binary is sufficient -- no rebuild and no scraping of the module's reported
|
|
# hash is required.
|
|
#
|
|
# Assumptions (match the optest user-space static FIPS build):
|
|
# - single fixed-address (ET_EXEC) image, not stripped
|
|
# - measured .text/.rodata bracketed by the wolfCrypt_FIPS_first/last and
|
|
# wolfCrypt_FIPS_ro_start/ro_end symbols
|
|
# - no text-segment canonicalizer and no relocation-table indirection in
|
|
# effect (i.e. the plain #else path of DoInCoreCheck())
|
|
#
|
|
# Usage: fips-hash-offline.sh [path-to-binary]
|
|
# default binary: wolfcrypt/test/testwolfcrypt
|
|
|
|
set -euo pipefail
|
|
|
|
BIN="${1:-wolfcrypt/test/testwolfcrypt}"
|
|
|
|
die() { echo "fips-hash-offline: error: $*" >&2; exit 1; }
|
|
|
|
[ -w "$BIN" ] || die "binary not writable: $BIN"
|
|
command -v awk >/dev/null 2>&1 || die "awk not found"
|
|
command -v dd >/dev/null 2>&1 || die "dd not found"
|
|
command -v readelf >/dev/null 2>&1 || die "readelf not found"
|
|
command -v openssl >/dev/null 2>&1 || die "openssl not found"
|
|
command -v xxd >/dev/null 2>&1 || die "xxd not found"
|
|
|
|
# Symbol lookup from the ELF symbol table.
|
|
# readelf -s columns: Num: Value Size Type Bind Vis Ndx Name
|
|
# -> Value ($2) is hexadecimal, Size ($3) is decimal, Ndx ($7), Name ($8).
|
|
sym_val() { readelf -sW "$BIN" | awk -v n="$1" '{if (!printed && $8==n && $7!="UND"){print $2; printed=1; }}'; }
|
|
sym_size() { readelf -sW "$BIN" | awk -v n="$1" '{if (!printed && $8==n && $7!="UND"){print $3; printed=1; }}'; }
|
|
|
|
# Map a virtual address to a file offset using the containing PROGBITS section.
|
|
# This avoids assuming any particular load base or section padding.
|
|
vaddr_to_off() {
|
|
local v="$1"
|
|
local name type addr off size a o s
|
|
while read -r name type addr off size; do
|
|
[ "$type" = "PROGBITS" ] || continue
|
|
a=$((0x$addr)); o=$((0x$off)); s=$((0x$size))
|
|
if [ "$v" -ge "$a" ] && [ "$v" -lt $((a + s)) ]; then
|
|
echo $((o + v - a))
|
|
return 0
|
|
fi
|
|
done < <(readelf -SW "$BIN" | sed 's/\[[ 0-9]*\]//' | awk '/PROGBITS/{print $1, $2, $3, $4, $5}')
|
|
return 1
|
|
}
|
|
|
|
# Extract byte_count bytes starting at file_offset (both in bytes) to stdout.
|
|
# Uses a 1-byte block size so skip/count are byte-granular on any POSIX dd.
|
|
# dd seeks over the skip on a regular file, so the offset is not read byte
|
|
# by byte; only the small count is copied one byte at a time.
|
|
extract() {
|
|
dd if="$BIN" bs=1 skip="$1" count="$2" 2>/dev/null
|
|
}
|
|
|
|
# --- gather the FIPS boundary and verifyCore/coreKey symbols ---
|
|
FIRST_H=$(sym_val wolfCrypt_FIPS_first)
|
|
LAST_H=$(sym_val wolfCrypt_FIPS_last)
|
|
ROSTART_H=$(sym_val wolfCrypt_FIPS_ro_start)
|
|
ROEND_H=$(sym_val wolfCrypt_FIPS_ro_end)
|
|
VC_H=$(sym_val verifyCore)
|
|
VCSZ=$(sym_size verifyCore)
|
|
KEY_H=$(sym_val coreKey)
|
|
KEYSZ=$(sym_size coreKey)
|
|
|
|
[ -n "$FIRST_H" ] && [ -n "$LAST_H" ] && [ -n "$ROSTART_H" ] && [ -n "$ROEND_H" ] \
|
|
&& [ -n "$VC_H" ] && [ -n "$VCSZ" ] && [ -n "$KEY_H" ] && [ -n "$KEYSZ" ] \
|
|
|| die "missing FIPS boundary symbols (is $BIN a non-stripped static FIPS build?)"
|
|
|
|
first=$((0x$FIRST_H))
|
|
last=$((0x$LAST_H))
|
|
rostart=$((0x$ROSTART_H))
|
|
roend=$((0x$ROEND_H))
|
|
vc=$((0x$VC_H))
|
|
keyaddr=$((0x$KEY_H))
|
|
|
|
[ "$last" -gt "$first" ] || die "wolfCrypt_FIPS_last <= wolfCrypt_FIPS_first"
|
|
[ "$roend" -gt "$rostart" ] || die "wolfCrypt_FIPS_ro_end <= wolfCrypt_FIPS_ro_start"
|
|
|
|
# Select the digest from the size of verifyCore[] (= digest_bytes*2 + 1).
|
|
digest_bytes=$(( (VCSZ - 1) / 2 ))
|
|
case "$digest_bytes" in
|
|
32) ALG=sha256 ;;
|
|
48) ALG=sha384 ;;
|
|
*) die "unexpected verifyCore size ($VCSZ); cannot determine digest" ;;
|
|
esac
|
|
|
|
# Read the HMAC key (coreKey) as ASCII hex straight out of the binary.
|
|
keyoff=$(vaddr_to_off "$keyaddr") || die "cannot map coreKey address to file offset"
|
|
KEYHEX=$(extract "$keyoff" $((KEYSZ - 1)))
|
|
[ "${#KEYHEX}" -eq $((digest_bytes * 2)) ] || die "coreKey length mismatch in binary"
|
|
|
|
# File offsets for the measured regions.
|
|
codeoff=$(vaddr_to_off "$first") || die "cannot map wolfCrypt_FIPS_first"
|
|
roff=$(vaddr_to_off "$rostart") || die "cannot map wolfCrypt_FIPS_ro_start"
|
|
|
|
vc_in_ro=0
|
|
if [ "$vc" -ge "$rostart" ] && [ "$vc" -lt "$roend" ]; then
|
|
vc_in_ro=1
|
|
aoff=$(vaddr_to_off $((vc + VCSZ))) || die "cannot map verifyCore tail"
|
|
fi
|
|
|
|
# Compute the digest, streaming the measured bytes in the same order and with
|
|
# the same verifyCore exclusion as DoInCoreCheck().
|
|
NEWHASH=$(
|
|
{
|
|
extract "$codeoff" $((last - first))
|
|
if [ "$vc_in_ro" -eq 1 ]; then
|
|
extract "$roff" $((vc - rostart))
|
|
extract "$aoff" $((roend - vc - VCSZ))
|
|
else
|
|
extract "$roff" $((roend - rostart))
|
|
fi
|
|
} | openssl dgst -"$ALG" -mac HMAC -macopt hexkey:"$KEYHEX" -binary \
|
|
| xxd -p -c 1000 | tr -d '\n' | tr 'a-f' 'A-F'
|
|
)
|
|
[ "${#NEWHASH}" -eq $((digest_bytes * 2)) ] || die "hash computation failed"
|
|
|
|
# Overwrite the first digest_bytes*2 ASCII characters of verifyCore[] in place.
|
|
# The trailing NUL terminator (byte digest_bytes*2) is left untouched.
|
|
vcoff=$(vaddr_to_off "$vc") || die "cannot map verifyCore address to file offset"
|
|
printf '%s' "$NEWHASH" | dd of="$BIN" bs=1 conv=notrunc seek="$vcoff" 2>/dev/null \
|
|
|| die "failed to write verifyCore"
|
|
|
|
# Confirm the patch landed.
|
|
CHECK=$(extract "$vcoff" $((digest_bytes * 2)))
|
|
[ "$CHECK" = "$NEWHASH" ] || die "verifyCore patch verification failed"
|
|
|
|
ALG_UC=$(echo "$ALG" | tr 'a-z' 'A-Z')
|
|
echo "fips-hash-offline: patched $BIN"
|
|
echo " algorithm : HMAC-$ALG_UC"
|
|
echo " code range : [0x$FIRST_H, 0x$LAST_H) ($((last - first)) bytes)"
|
|
echo " ro range : [0x$ROSTART_H, 0x$ROEND_H) (verifyCore $([ "$vc_in_ro" -eq 1 ] && echo excluded || echo 'not in range'))"
|
|
echo " hash : $NEWHASH"
|