267 lines
7.3 KiB
Bash
267 lines
7.3 KiB
Bash
#!/usr/bin/env bash
|
|
# Shared Hash/Checksum Utilities
|
|
# Sprint: CI/CD Enhancement - Script Consolidation
|
|
#
|
|
# Purpose: Cryptographic hash and checksum operations for CI/CD scripts
|
|
# Usage: source "$(dirname "${BASH_SOURCE[0]}")/lib/hash-utils.sh"
|
|
|
|
# Prevent multiple sourcing
|
|
if [[ -n "${__STELLAOPS_HASH_UTILS_LOADED:-}" ]]; then
|
|
return 0
|
|
fi
|
|
export __STELLAOPS_HASH_UTILS_LOADED=1
|
|
|
|
# Source dependencies
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
source "${SCRIPT_DIR}/logging.sh" 2>/dev/null || true
|
|
source "${SCRIPT_DIR}/exit-codes.sh" 2>/dev/null || true
|
|
|
|
# ============================================================================
|
|
# Hash Computation
|
|
# ============================================================================
|
|
|
|
# Compute SHA-256 hash of a file
|
|
compute_sha256() {
|
|
local file="$1"
|
|
|
|
if [[ ! -f "$file" ]]; then
|
|
log_error "File not found: $file"
|
|
return "${EXIT_NOT_FOUND:-4}"
|
|
fi
|
|
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
sha256sum "$file" | awk '{print $1}'
|
|
elif command -v shasum >/dev/null 2>&1; then
|
|
shasum -a 256 "$file" | awk '{print $1}'
|
|
elif command -v openssl >/dev/null 2>&1; then
|
|
openssl dgst -sha256 "$file" | awk '{print $NF}'
|
|
else
|
|
log_error "No SHA-256 tool available"
|
|
return "${EXIT_MISSING_TOOL:-10}"
|
|
fi
|
|
}
|
|
|
|
# Compute SHA-512 hash of a file
|
|
compute_sha512() {
|
|
local file="$1"
|
|
|
|
if [[ ! -f "$file" ]]; then
|
|
log_error "File not found: $file"
|
|
return "${EXIT_NOT_FOUND:-4}"
|
|
fi
|
|
|
|
if command -v sha512sum >/dev/null 2>&1; then
|
|
sha512sum "$file" | awk '{print $1}'
|
|
elif command -v shasum >/dev/null 2>&1; then
|
|
shasum -a 512 "$file" | awk '{print $1}'
|
|
elif command -v openssl >/dev/null 2>&1; then
|
|
openssl dgst -sha512 "$file" | awk '{print $NF}'
|
|
else
|
|
log_error "No SHA-512 tool available"
|
|
return "${EXIT_MISSING_TOOL:-10}"
|
|
fi
|
|
}
|
|
|
|
# Compute MD5 hash of a file (for compatibility, not security)
|
|
compute_md5() {
|
|
local file="$1"
|
|
|
|
if [[ ! -f "$file" ]]; then
|
|
log_error "File not found: $file"
|
|
return "${EXIT_NOT_FOUND:-4}"
|
|
fi
|
|
|
|
if command -v md5sum >/dev/null 2>&1; then
|
|
md5sum "$file" | awk '{print $1}'
|
|
elif command -v md5 >/dev/null 2>&1; then
|
|
md5 -q "$file"
|
|
elif command -v openssl >/dev/null 2>&1; then
|
|
openssl dgst -md5 "$file" | awk '{print $NF}'
|
|
else
|
|
log_error "No MD5 tool available"
|
|
return "${EXIT_MISSING_TOOL:-10}"
|
|
fi
|
|
}
|
|
|
|
# Compute hash of string
|
|
compute_string_hash() {
|
|
local string="$1"
|
|
local algorithm="${2:-sha256}"
|
|
|
|
case "$algorithm" in
|
|
sha256)
|
|
echo -n "$string" | sha256sum 2>/dev/null | awk '{print $1}' || \
|
|
echo -n "$string" | shasum -a 256 2>/dev/null | awk '{print $1}'
|
|
;;
|
|
sha512)
|
|
echo -n "$string" | sha512sum 2>/dev/null | awk '{print $1}' || \
|
|
echo -n "$string" | shasum -a 512 2>/dev/null | awk '{print $1}'
|
|
;;
|
|
md5)
|
|
echo -n "$string" | md5sum 2>/dev/null | awk '{print $1}' || \
|
|
echo -n "$string" | md5 2>/dev/null
|
|
;;
|
|
*)
|
|
log_error "Unknown algorithm: $algorithm"
|
|
return "${EXIT_USAGE:-2}"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# ============================================================================
|
|
# Checksum Files
|
|
# ============================================================================
|
|
|
|
# Write checksum file for a single file
|
|
write_checksum() {
|
|
local file="$1"
|
|
local checksum_file="${2:-${file}.sha256}"
|
|
local algorithm="${3:-sha256}"
|
|
|
|
local hash
|
|
case "$algorithm" in
|
|
sha256) hash=$(compute_sha256 "$file") ;;
|
|
sha512) hash=$(compute_sha512 "$file") ;;
|
|
md5) hash=$(compute_md5 "$file") ;;
|
|
*)
|
|
log_error "Unknown algorithm: $algorithm"
|
|
return "${EXIT_USAGE:-2}"
|
|
;;
|
|
esac
|
|
|
|
if [[ -z "$hash" ]]; then
|
|
return "${EXIT_ERROR:-1}"
|
|
fi
|
|
|
|
local basename
|
|
basename=$(basename "$file")
|
|
echo "$hash $basename" > "$checksum_file"
|
|
log_debug "Wrote checksum to $checksum_file"
|
|
}
|
|
|
|
# Write checksums for multiple files
|
|
write_checksums() {
|
|
local output_file="$1"
|
|
shift
|
|
local files=("$@")
|
|
|
|
: > "$output_file"
|
|
|
|
for file in "${files[@]}"; do
|
|
if [[ -f "$file" ]]; then
|
|
local hash basename
|
|
hash=$(compute_sha256 "$file")
|
|
basename=$(basename "$file")
|
|
echo "$hash $basename" >> "$output_file"
|
|
fi
|
|
done
|
|
|
|
log_debug "Wrote checksums to $output_file"
|
|
}
|
|
|
|
# ============================================================================
|
|
# Checksum Verification
|
|
# ============================================================================
|
|
|
|
# Verify checksum of a file
|
|
verify_checksum() {
|
|
local file="$1"
|
|
local expected_hash="$2"
|
|
local algorithm="${3:-sha256}"
|
|
|
|
local actual_hash
|
|
case "$algorithm" in
|
|
sha256) actual_hash=$(compute_sha256 "$file") ;;
|
|
sha512) actual_hash=$(compute_sha512 "$file") ;;
|
|
md5) actual_hash=$(compute_md5 "$file") ;;
|
|
*)
|
|
log_error "Unknown algorithm: $algorithm"
|
|
return "${EXIT_USAGE:-2}"
|
|
;;
|
|
esac
|
|
|
|
if [[ "$actual_hash" == "$expected_hash" ]]; then
|
|
log_debug "Checksum verified: $file"
|
|
return 0
|
|
else
|
|
log_error "Checksum mismatch for $file"
|
|
log_error " Expected: $expected_hash"
|
|
log_error " Actual: $actual_hash"
|
|
return "${EXIT_VERIFY_FAILED:-64}"
|
|
fi
|
|
}
|
|
|
|
# Verify checksums from file (sha256sum -c style)
|
|
verify_checksums_file() {
|
|
local checksum_file="$1"
|
|
local base_dir="${2:-.}"
|
|
|
|
if [[ ! -f "$checksum_file" ]]; then
|
|
log_error "Checksum file not found: $checksum_file"
|
|
return "${EXIT_NOT_FOUND:-4}"
|
|
fi
|
|
|
|
local failures=0
|
|
|
|
while IFS= read -r line; do
|
|
# Skip empty lines and comments
|
|
[[ -z "$line" ]] && continue
|
|
[[ "$line" == \#* ]] && continue
|
|
|
|
local hash filename
|
|
hash=$(echo "$line" | awk '{print $1}')
|
|
filename=$(echo "$line" | awk '{print $2}')
|
|
|
|
local filepath="${base_dir}/${filename}"
|
|
|
|
if [[ ! -f "$filepath" ]]; then
|
|
log_error "File not found: $filepath"
|
|
((failures++))
|
|
continue
|
|
fi
|
|
|
|
if ! verify_checksum "$filepath" "$hash"; then
|
|
((failures++))
|
|
fi
|
|
done < "$checksum_file"
|
|
|
|
if [[ $failures -gt 0 ]]; then
|
|
log_error "$failures checksum verification(s) failed"
|
|
return "${EXIT_VERIFY_FAILED:-64}"
|
|
fi
|
|
|
|
log_info "All checksums verified"
|
|
return 0
|
|
}
|
|
|
|
# ============================================================================
|
|
# Helpers
|
|
# ============================================================================
|
|
|
|
# Check if two files have the same content
|
|
files_identical() {
|
|
local file1="$1"
|
|
local file2="$2"
|
|
|
|
[[ -f "$file1" ]] && [[ -f "$file2" ]] || return 1
|
|
|
|
local hash1 hash2
|
|
hash1=$(compute_sha256 "$file1")
|
|
hash2=$(compute_sha256 "$file2")
|
|
|
|
[[ "$hash1" == "$hash2" ]]
|
|
}
|
|
|
|
# Get short hash for display
|
|
short_hash() {
|
|
local hash="$1"
|
|
local length="${2:-8}"
|
|
echo "${hash:0:$length}"
|
|
}
|
|
|
|
# Generate deterministic ID from inputs
|
|
generate_id() {
|
|
local inputs="$*"
|
|
compute_string_hash "$inputs" sha256 | head -c 16
|
|
}
|