openpencil/tools/check-op-auth-prebuilt.sh
Kayshen-X 24e1ad3d05 feat(auth): accept signed-unobfuscated ABI v3 op-auth prebuilt
The reviewed obfuscator that also performed standard hardening was never produced, so ABI-v2/v3 archives could not ship. Add an explicit, signature-bound signed-unobfuscated profile and accept ABI v3 (relay token) across the build-time validator, the audit gate, and the packager. The archive stays Ed25519-signed and ABI-pinned; private Rust symbol strings/paths/debug are retained by design and declared as hardening=op-auth-signed-unobfuscated-v1.
2026-08-10 09:55:00 +08:00

222 lines
7.9 KiB
Bash
Executable file

#!/usr/bin/env bash
# Audit committed op-auth archives without modifying their bytes.
#
# Legacy ABI-v1 artifacts are integrity-pinned compatibility inputs and may
# still contain source/debug metadata. ABI-v2 artifacts are production
# collaboration inputs: they must pass the hardened profile and signed
# provenance checks in addition to exposing only the documented C ABI.
set -euo pipefail
script_dir=$(CDPATH= cd "$(dirname "$0")" && pwd)
repo_root=$(CDPATH= cd "$script_dir/.." && pwd)
cd "$repo_root"
require_hardened=0
if [[ "${1:-}" == "--require-hardened" ]]; then
require_hardened=1
shift
fi
if [[ "$#" -ne 0 ]]; then
printf 'usage: %s [--require-hardened]\n' "$0" >&2
exit 2
fi
prebuilt_root=${OP_AUTH_PREBUILT_ROOT:-crates/op-auth-bridge/prebuilt}
failures=()
artifact_count=0
record_failure() {
failures+=("$1")
}
sha256_file() {
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$1" | awk '{ print $1 }'
elif command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$1" | awk '{ print $1 }'
else
return 127
fi
}
archive_symbols() {
LC_ALL=C strings -a "$1" \
| LC_ALL=C sed 's/^_//' \
| LC_ALL=C grep -E '^op_auth_[A-Za-z0-9_]+$' \
| LC_ALL=C sort -u \
|| true
}
expected_symbols() {
printf '%s\n' \
op_auth_abi_version \
op_auth_cancel \
op_auth_login_begin \
op_auth_poll \
op_auth_restore \
op_auth_runtime_init \
op_auth_sign_out \
op_auth_string_free
if [[ "$1" -ge 2 ]]; then
printf '%s\n' \
op_auth_collab_ticket_begin \
op_auth_collab_ticket_cancel \
op_auth_collab_ticket_poll
fi
if [[ "$1" -ge 3 ]]; then
printf '%s\n' \
op_auth_collab_relay_token_begin
fi
}
metadata_leak_count() {
LC_ALL=C strings -a "$1" \
| LC_ALL=C grep -Ec \
'/Users/|/home/|/builds/|/workspace/|/private/tmp/|[A-Za-z]:[/\\]Users[/\\]|\.cargo[/\\]registry[/\\]src|/rustc/|op_auth_(core|native)[/\\]src[/\\]' \
|| true
}
debug_marker_count() {
LC_ALL=C strings -a "$1" \
| LC_ALL=C grep -Ec \
'^(\.debug_(abbrev|info|line|line_str|ranges|rnglists|str)|__debug_(abbrev|info|line|str)|\.debug\$[A-Z]|.*\.pdb)$' \
|| true
}
private_symbol_count() {
LC_ALL=C strings -a "$1" \
| LC_ALL=C grep -Ec \
'op_auth_core|op_auth_native|_ZN[0-9A-Za-z_$.]*op_auth|_R[0-9A-Za-z_$.]*op_auth' \
|| true
}
if [[ ! -d "$prebuilt_root" ]]; then
printf 'check-op-auth-prebuilt.sh: no committed prebuilt directory; stub build only.\n'
exit 0
fi
while IFS= read -r artifact; do
artifact_count=$((artifact_count + 1))
target_dir=$(dirname "$artifact")
target=$(basename "$target_dir")
artifact_name=$(basename "$artifact")
expected_name=libop_auth.a
if [[ "$target" == *-pc-windows-msvc ]]; then
expected_name=op_auth.lib
fi
if [[ "$artifact_name" != "$expected_name" ]]; then
record_failure "$target: artifact must be named $expected_name"
fi
checksum_path=$target_dir/SHA256
if [[ ! -f "$checksum_path" ]]; then
record_failure "$target: SHA256 is missing"
else
expected_sha256=$(tr -d '[:space:]' < "$checksum_path")
actual_sha256=$(sha256_file "$artifact") \
|| {
record_failure "$target: no SHA-256 implementation is available"
actual_sha256=
}
if [[ ! "$expected_sha256" =~ ^[0-9a-f]{64}$ ]]; then
record_failure "$target: SHA256 must be one lowercase hexadecimal digest"
elif [[ "$expected_sha256" != "$actual_sha256" ]]; then
record_failure "$target: artifact SHA-256 mismatch"
fi
fi
version_path=$target_dir/VERSION
if [[ ! -s "$version_path" ]]; then
record_failure "$target: VERSION is missing"
fi
abi_version=1
if [[ -f "$target_dir/ABI_VERSION" ]]; then
abi_version=$(tr -d '[:space:]' < "$target_dir/ABI_VERSION")
fi
if [[ ! "$abi_version" =~ ^[123]$ ]]; then
record_failure "$target: ABI_VERSION must be 1, 2, or 3"
abi_version=1
fi
actual_symbols=$(archive_symbols "$artifact")
required_symbols=$(expected_symbols "$abi_version" | LC_ALL=C sort -u)
if [[ "$actual_symbols" != "$required_symbols" ]]; then
missing_symbols=$(comm -23 \
<(printf '%s\n' "$required_symbols") \
<(printf '%s\n' "$actual_symbols"))
extra_symbols=$(comm -13 \
<(printf '%s\n' "$required_symbols") \
<(printf '%s\n' "$actual_symbols"))
[[ -z "$missing_symbols" ]] \
|| record_failure "$target: required C ABI symbols are missing: ${missing_symbols//$'\n'/, }"
[[ -z "$extra_symbols" ]] \
|| record_failure "$target: undocumented op_auth C ABI symbols are exposed: ${extra_symbols//$'\n'/, }"
fi
path_leaks=$(metadata_leak_count "$artifact")
debug_markers=$(debug_marker_count "$artifact")
private_symbols=$(private_symbol_count "$artifact")
hardened=$require_hardened
# Obfuscated profiles must additionally scrub private Rust symbol strings,
# source paths, and debug markers. The signed-unobfuscated profile is an
# explicit, signature-bound opt-out of those scrubs; signature presence and
# the C ABI surface are still enforced.
obfuscated=1
if [[ "$abi_version" -ge 2 ]]; then
hardened=1
for signed_file in PROVENANCE PROVENANCE.sig; do
if [[ ! -s "$target_dir/$signed_file" ]]; then
record_failure "$target: signed ABI-v2 $signed_file is missing"
fi
done
if [[ ! -s "$prebuilt_root/PROVENANCE_PUBKEY" ]]; then
record_failure "$target: release provenance public key is missing"
fi
if [[ -f "$target_dir/PROVENANCE" ]]; then
if grep -Fxq 'hardening=op-auth-hardened-v1' "$target_dir/PROVENANCE"; then
obfuscated=1
elif grep -Fxq 'hardening=op-auth-signed-unobfuscated-v1' \
"$target_dir/PROVENANCE"; then
obfuscated=0
else
record_failure "$target: signed hardening profile is missing or unrecognized"
fi
fi
fi
if [[ "$hardened" -eq 1 && "$obfuscated" -eq 1 ]]; then
[[ "$path_leaks" -eq 0 ]] \
|| record_failure "$target: archive leaks $path_leaks source/build path strings"
[[ "$debug_markers" -eq 0 ]] \
|| record_failure "$target: archive contains $debug_markers debug metadata markers"
[[ "$private_symbols" -eq 0 ]] \
|| record_failure "$target: archive exposes $private_symbols private Rust symbol/module strings"
elif [[ "$hardened" -eq 1 && "$obfuscated" -eq 0 ]]; then
printf \
'warning: %s is signed-unobfuscated (%s path strings, %s debug markers, %s private Rust symbol/module strings retained by design)\n' \
"$target" "$path_leaks" "$debug_markers" "$private_symbols" >&2
elif [[ "$path_leaks" -gt 0 || "$debug_markers" -gt 0 || "$private_symbols" -gt 0 ]]; then
printf \
'warning: %s is legacy ABI-v1 (%s path strings, %s debug markers, %s private Rust symbol/module strings); rebuild in private CI before production ABI-v2 use\n' \
"$target" "$path_leaks" "$debug_markers" "$private_symbols" >&2
fi
done < <(
find "$prebuilt_root" -mindepth 2 -maxdepth 2 \
-type f \( -name '*.a' -o -name '*.lib' \) \
-print | LC_ALL=C sort
)
if [[ "$artifact_count" -eq 0 && "$require_hardened" -eq 1 ]]; then
record_failure "no op-auth archive was supplied for hardened release validation"
fi
if [[ "${#failures[@]}" -ne 0 ]]; then
printf 'check-op-auth-prebuilt.sh: %s failure(s):\n' "${#failures[@]}" >&2
printf ' - %s\n' "${failures[@]}" >&2
exit 1
fi
printf 'check-op-auth-prebuilt.sh: audited %s archive(s); no bytes were modified.\n' "$artifact_count"