399 lines
11 KiB
Bash
399 lines
11 KiB
Bash
#!/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 <package.rpm>
|
|
# 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 <package.rpm>
|
|
|
|
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 <package.rpm>"
|
|
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] <package.rpm>
|
|
|
|
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 "$@" |