#!/bin/bash # Package Signature Verification Script for SAW # Version: 1.0 # Date: 2026-04-02 # # This script verifies package signatures against your custom Certificate Authority (CA). # It ensures that only packages signed by your trusted CA can be installed on the system. # # Usage: sudo ./verify-signature.sh # sudo ./verify-signature.sh --verify-all # sudo ./verify-signature.sh --check-all # # SECURITY RATIONALE: # Package verification is critical for security because it: # 1. Prevents installation of malicious packages (tampering detection) # 2. Ensures package integrity (not modified in transit) # 3. Authenticates package source (only your CA can sign) # 4. Provides non-repudiation (can prove who signed the package) # 5. Prevents supply chain attacks # 6. Blocks unsigned packages completely # # Implementation Details: # - Uses RPM GPG signature verification # - Verifies against custom CA public key # - Checks CRL (Certificate Revocation List) for revoked certificates # - Logs all verification attempts # # Reference: https://docs.fedoraproject.org/en-US/fedora-coreos/security-verification/ set -e # Configuration CA_KEY_PATH="/etc/pki/rpm-gpg/RPM-GPG-KEY-custom-ca" CRL_FILE="/etc/pki/ca-trust/source/anchors/crl.pem" LOG_FILE="/var/log/package-verification.log" VERIFY_SCRIPT_DIR="/usr/local/bin" CONFIG_FILE="/etc/saw/package-verification.conf" # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # Print functions log_info() { local timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo -e "${GREEN}[$timestamp]${NC} ${BLUE}[INFO]${NC} $1" | tee -a "$LOG_FILE" } log_warning() { local timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo -e "${YELLOW}[$timestamp]${NC} ${YELLOW}[WARNING]${NC} $1" | tee -a "$LOG_FILE" } log_error() { local timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo -e "${RED}[$timestamp]${NC} ${RED}[ERROR]${NC} $1" | tee -a "$LOG_FILE" } # Check if running as root check_root() { if [ "$EUID" -ne 0 ]; then log_error "This script must be run as root" exit 1 fi } # Check if CA key exists check_ca_key() { if [ ! -f "$CA_KEY_PATH" ]; then log_error "CA key not found: $CA_KEY_PATH" log_info "Package verification requires CA key" log_info "Copy your CA public key to: $CA_KEY_PATH" exit 1 fi log_info "CA key found: $CA_KEY_PATH" } # Verify a single package # # SECURITY RATIONALE: # Verifying individual package signatures ensures: # 1. Package wasn't tampered with during download # 2. Package comes from trusted source # 3. Package wasn't modified by attacker # 4. Package hasn't been replaced with malicious version # # The verification process: # 1. Extract package header # 2. Verify RSA signature using CA public key # 3. Compare hash of package contents # 4. Check signature validity # # If verification fails, the package should NOT be installed. verify_package() { local package="$1" log_info "Verifying package: $package" # Check if file exists if [ ! -f "$package" ]; then log_error "Package not found: $package" return 1 fi # Verify package signature if ! rpm --checksig "$package" > /dev/null 2>&1; then log_error "Package signature verification failed: $package" log_info "Package may be tampered or unsigned" return 1 fi # Extract signature info local sig_info=$(rpm --checksig "$package" 2>&1) # Check if signed with our CA if echo "$sig_info" | grep -q "$CA_KEY_PATH"; then log_info "Package verified successfully (signed with CA)" else log_warning "Package signed but not with our CA" log_info "Signature info: $sig_info" return 1 fi # Verify CRL if available if [ -f "$CRL_FILE" ]; then log_info "Checking CRL for revoked certificates..." # Extract certificate ID from signature local cert_id=$(rpm --queryformat='%{SIGPGP:pgpsig}\n' -p "$package" 2>/dev/null | head -1 | awk '{print $2}' | cut -d: -f2) if [ -n "$cert_id" ]; then # Check if certificate is revoked if openssl crl -in "$CRL_FILE" -noout -text 2>/dev/null | grep -q "$cert_id"; then log_error "Package signed with REVOKED certificate: $cert_id" return 1 fi log_info "Certificate not revoked: $cert_id" fi fi log_info "Package verified successfully: $package" return 0 } # Verify all installed packages # # SECURITY RATIONALE: # Regular verification of all installed packages ensures: # 1. No packages were tampered with after installation # 2. No malicious packages were added # 3. All packages are still properly signed # 4. Any compromised packages are detected # # This should be run regularly (weekly/monthly) to ensure # the integrity of the entire system. verify_all_packages() { log_info "Verifying all installed packages..." local failed=0 local total=0 local revoked=0 # Get list of all installed packages local packages=$(rpm -qa --queryformat='%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}.rpm\n' 2>/dev/null) for pkg in $packages; do # Get package file path local pkg_path=$(rpm -ql "$pkg" 2>/dev/null | head -1 | xargs dirname) local pkg_name=$(echo "$pkg" | sed 's/\.rpm$//') # Check if package is signed if rpm --queryformat='%{SIGPGP:pgpsig}\n' -q "$pkg_name" 2>/dev/null | grep -q "0x0"; then log_warning "Package $pkg_name has no signature" ((failed++)) else # Check signature if ! rpm --queryformat='%{SIGPGP:pgpsig}\n' -q "$pkg_name" 2>/dev/null | grep -q "0x"; then log_warning "Package $pkg_name signature verification failed" ((failed++)) else log_info "Package $pkg_name verified" fi fi ((total++)) done echo "" echo "=== Verification Summary ===" echo "Total packages: $total" echo "Verified: $((total - failed))" echo "Failed: $failed" echo "Revoked: $revoked" echo "" if [ $failed -gt 0 ]; then log_error "Some packages failed verification!" return 1 fi log_info "All packages verified successfully" return 0 } # Check DNF configuration check_dnf_config() { log_info "Checking DNF configuration..." if [ ! -f /etc/dnf/dnf.conf ]; then log_warning "DNF configuration not found" return 1 fi # Check if signature verification is enabled if grep -q "^gpgcheck=1" /etc/dnf/dnf.conf; then log_info "DNF signature verification enabled" else log_error "DNF signature verification NOT enabled" return 1 fi # Check if repo signature verification is enabled if grep -q "^repo_gpgcheck=1" /etc/dnf/dnf.conf; then log_info "DNF repo signature verification enabled" else log_error "DNF repo signature verification NOT enabled" return 1 fi return 0 } # Verify DNF cache verify_dnf_cache() { log_info "Verifying DNF cache..." # Check if DNF cache exists if [ ! -d /var/cache/dnf ]; then log_warning "DNF cache not found" return 1 fi # List cached packages log_info "Cached packages:" find /var/cache/dnf -name "*.rpm" -type f 2>/dev/null | while read -r pkg; do local pkg_name=$(basename "$pkg") if rpm --checksig "$pkg" > /dev/null 2>&1; then log_info " $pkg_name: VERIFIED" else log_error " $pkg_name: FAILED" fi done return 0 } # Create package verification script for users create_user_script() { log_info "Creating user package verification script..." cat > "$VERIFY_SCRIPT_DIR/verify-package.sh" << 'VERIFYSCRIPT' #!/bin/bash # Package verification script for SAW # Usage: ./verify-package.sh CA_KEY="/etc/pki/rpm-gpg/RPM-GPG-KEY-custom-ca" CRL_FILE="/etc/pki/ca-trust/source/anchors/crl.pem" # Check if CA key exists if [ ! -f "$CA_KEY" ]; then echo "ERROR: CA key not found: $CA_KEY" echo "Contact system administrator" exit 1 fi # Check if file is provided if [ $# -lt 1 ]; then echo "Usage: $0 " exit 1 fi PACKAGE="$1" # Check if file exists if [ ! -f "$PACKAGE" ]; then echo "ERROR: Package not found: $PACKAGE" exit 1 fi # Verify package signature if ! rpm --checksig "$PACKAGE" > /dev/null 2>&1; then echo "ERROR: Package signature verification failed: $PACKAGE" echo "Package may be tampered or unsigned" exit 1 fi # Extract signature info SIG_INFO=$(rpm --checksig "$PACKAGE" 2>&1) # Check if signed with our CA if echo "$SIG_INFO" | grep -q "$CA_KEY"; then echo "OK: Package verified successfully (signed with CA)" else echo "WARNING: Package signed but not with our CA" echo "Signature info: $SIG_INFO" exit 1 fi # Verify CRL if available if [ -f "$CRL_FILE" ]; then echo "Checking CRL for revoked certificates..." if ! openssl crl -in "$CRL_FILE" -CAfile "$CA_KEY" -noout 2>/dev/null; then echo "WARNING: CRL verification failed" fi fi echo "Package verification complete" exit 0 VERIFYSCRIPT chmod +x "$VERIFY_SCRIPT_DIR/verify-package.sh" log_info "User verification script created" } # Display help show_help() { cat << 'HELP' Package Signature Verification Script for SAW ============================================== Usage: ./verify-signature.sh [OPTIONS] Options: --verify-all Verify all installed packages --check-all Check all packages (same as --verify-all) --check-dnf Check DNF configuration --verify-dnf Verify DNF cache --help Show this help message Examples: # Verify a single package ./verify-signature.sh package.rpm # Verify all installed packages ./verify-signature.sh --verify-all # Check DNF configuration ./verify-signature.sh --check-dnf Security Notes: - This script requires root privileges - Only packages signed by your CA will pass verification - Unsigned packages will be rejected - Packages with revoked certificates will be rejected Reference: https://docs.fedoraproject.org/en-US/fedora-coreos/security-verification/ HELP } # Main execution main() { echo "" echo "==========================================" echo " Package Signature Verification" echo " Fedora Kinoite SAW" echo "==========================================" echo "" check_root check_ca_key case "${1:-}" in --verify-all|--check-all) verify_all_packages ;; --check-dnf) check_dnf_config ;; --verify-dnf) verify_dnf_cache ;; --help|-h) show_help ;; *) if [ $# -lt 1 ]; then show_help exit 0 fi verify_package "$1" ;; esac } # Run main function main "$@"