check-pgp-expiry.sh
1 #!/bin/bash 2 3 # Validates PGP keys in the scripts/keys directory by checking their expiration 4 # status and signing capabilities. It iterates through all .asc files and uses 5 # GPG to parse key information. 6 7 set -euo pipefail 8 shopt -s nullglob 9 10 error() { 11 RED='\033[0;31m' 12 NC='\033[0m' # No Color 13 echo -e "${RED}ERROR: $1${NC}" 14 exit_code=1 15 } 16 17 # Check if a key has expired or is expiring soon 18 # Args: $1 = expiry timestamp, $2 = key_info 19 # Returns: 0 if key is valid or has no expiry, 1 if expired/expiring soon 20 check_key_expiry() { 21 local expiry="$1" 22 local key_info="$2" 23 24 # If expiry is empty, the key does not expire. 25 if [[ -z "$expiry" ]]; then 26 echo "INFO: $key_info does not expire" 27 return 0 28 fi 29 30 # Convert expiry timestamp to human readable date for logging. 31 local expiry_date 32 if ! expiry_date=$(date -d "@$expiry" "+%Y-%m-%d" 2>/dev/null \ 33 || date -r "$expiry" "+%Y-%m-%d" 2>/dev/null); then 34 error "Invalid expiry timestamp for $key_info $expiry" 35 return 1 36 fi 37 38 if (( expiry < $(date +%s) )); then 39 echo "WARN: $key_info has already expired ($expiry_date)" 40 return 1 41 fi 42 43 if (( expiry < EXPIRE_THRESHOLD )); then 44 echo "WARN: $key_info expires soon ($expiry_date)" 45 return 1 46 fi 47 48 echo "INFO: $key_info is valid until $expiry_date" 49 return 0 50 } 51 52 echo 53 echo "Starting PGP key validation..." 54 55 KEY_DIR="./scripts/keys" 56 if [[ ! -d "$KEY_DIR" ]]; then 57 error "Directory $KEY_DIR does not exist" 58 exit $exit_code 59 fi 60 61 key_files=("$KEY_DIR"/*.asc) 62 if (( ${#key_files[@]} == 0 )); then 63 error "No PGP keys found in $KEY_DIR" 64 exit $exit_code 65 fi 66 67 # 2 weeks = 14 days * (24 hours * 60 minutes * 60 seconds). 68 EXPIRE_THRESHOLD=$(($(date +%s) + 14 * 86400)) 69 exit_code=0 70 71 echo "Found ${#key_files[@]} key file(s) in $KEY_DIR" 72 echo 73 74 for key_file in "${key_files[@]}"; do 75 echo "────────────────────────────────────────────────────────────────────" 76 echo "Checking $(basename "$key_file")..." 77 78 gpg_output=$(gpg --with-colons --import-options show-only \ 79 --import "$key_file" 2>&1 | grep -E '^(pub|sub):') 80 81 key_name=$(basename "$key_file") 82 83 # Parse GPG output line by line to find key type, id, expiry and 84 # capabilities. 85 valid_sign_key_found=false 86 while IFS=: read -r type _ _ _ id _ expiry _ _ _ _ capabilities _; do 87 if [[ "$valid_sign_key_found" == true ]]; then 88 # If we already found a valid signing key, skip further checks for 89 # this keychain. 90 break 91 fi 92 93 key_info="$type:$id ($capabilities)" 94 95 # Check primary key expiry. 96 if [[ "$type" == "pub" ]] && 97 ! check_key_expiry "$expiry" "$key_info"; then 98 error "$key_info primary key is invalid" 99 break 100 fi 101 102 # Filter out keys that cannot sign releases. 103 if ! [[ "$capabilities" =~ [sS] ]]; then 104 continue 105 fi 106 107 # Check sub key expiry. 108 if [[ "$type" == "sub" ]] && 109 ! check_key_expiry "$expiry" "$key_info"; then 110 continue 111 fi 112 113 # If we reach here, we have a valid signing key. 114 valid_sign_key_found=true 115 done <<< "$gpg_output" 116 117 if [[ "$valid_sign_key_found" == false ]]; then 118 error "$key_name does not have any valid sign key" 119 fi 120 done 121 122 echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" 123 if [[ $exit_code -eq 0 ]]; then 124 echo "All PGP keys are valid and ready for use!" 125 else 126 error "Some PGP keys have issues that need attention." 127 fi 128 129 exit $exit_code