#!/bin/bash # ----------------------------------------------------------------------------- # rotate-rekor-key.sh # Sprint: SPRINT_20260125_003_Attestor_trust_workflows_conformance # Task: WORKFLOW-002 - Create key rotation workflow script # Description: Rotate Rekor public key with grace period # ----------------------------------------------------------------------------- set -euo pipefail RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' log_info() { echo -e "${GREEN}[INFO]${NC} $1"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; } log_step() { echo -e "${BLUE}[STEP]${NC} $1"; } usage() { echo "Usage: $0 [options]" echo "" echo "Rotate Rekor public key through a dual-key grace period." echo "" echo "Phases:" echo " add-key Add new key to TUF (starts grace period)" echo " verify Verify both keys are active" echo " remove-old Remove old key (after grace period)" echo "" echo "Options:" echo " --repo DIR TUF repository directory" echo " --new-key FILE Path to new Rekor public key" echo " --new-key-name NAME Target name for new key (default: rekor-key-v{N+1})" echo " --old-key-name NAME Target name for old key to remove" echo " --grace-days N Grace period in days (default: 7)" echo " -h, --help Show this help message" echo "" echo "Example (3-phase rotation):" echo " # Phase 1: Add new key" echo " $0 add-key --repo /path/to/tuf --new-key rekor-key-v2.pub" echo "" echo " # Wait for grace period (clients sync)" echo " sleep 7d" echo "" echo " # Phase 2: Verify" echo " $0 verify" echo "" echo " # Phase 3: Remove old key" echo " $0 remove-old --repo /path/to/tuf --old-key-name rekor-key-v1" exit 1 } PHASE="" REPO_DIR="" NEW_KEY="" NEW_KEY_NAME="" OLD_KEY_NAME="" GRACE_DAYS=7 while [[ $# -gt 0 ]]; do case $1 in add-key|verify|remove-old) PHASE="$1" shift ;; --repo) REPO_DIR="$2"; shift 2 ;; --new-key) NEW_KEY="$2"; shift 2 ;; --new-key-name) NEW_KEY_NAME="$2"; shift 2 ;; --old-key-name) OLD_KEY_NAME="$2"; shift 2 ;; --grace-days) GRACE_DAYS="$2"; shift 2 ;; -h|--help) usage ;; *) log_error "Unknown argument: $1"; usage ;; esac done if [[ -z "$PHASE" ]]; then log_error "Phase is required" usage fi echo "" echo "================================================" echo " Rekor Key Rotation - Phase: $PHASE" echo "================================================" echo "" case "$PHASE" in add-key) if [[ -z "$REPO_DIR" ]] || [[ -z "$NEW_KEY" ]]; then log_error "add-key requires --repo and --new-key" usage fi if [[ ! -f "$NEW_KEY" ]]; then log_error "New key file not found: $NEW_KEY" exit 1 fi if [[ ! -d "$REPO_DIR" ]]; then log_error "TUF repository not found: $REPO_DIR" exit 1 fi # Determine new key name if not specified if [[ -z "$NEW_KEY_NAME" ]]; then # Find highest version and increment HIGHEST=$(ls "$REPO_DIR/targets/" 2>/dev/null | grep -E '^rekor-key-v[0-9]+' | \ sed 's/rekor-key-v//' | sed 's/\.pub$//' | sort -n | tail -1 || echo "0") NEW_VERSION=$((HIGHEST + 1)) NEW_KEY_NAME="rekor-key-v${NEW_VERSION}" fi log_step "Adding new Rekor key: $NEW_KEY_NAME" log_info "Source: $NEW_KEY" # Copy key to targets cp "$NEW_KEY" "$REPO_DIR/targets/${NEW_KEY_NAME}.pub" # Add to targets.json if [[ -x "$REPO_DIR/scripts/add-target.sh" ]]; then "$REPO_DIR/scripts/add-target.sh" "$REPO_DIR/targets/${NEW_KEY_NAME}.pub" "${NEW_KEY_NAME}.pub" --repo "$REPO_DIR" else log_warn "add-target.sh not found, updating targets.json manually required" fi log_info "" log_info "Key added: $NEW_KEY_NAME" log_info "" log_warn "IMPORTANT: Dual-key period has started." log_warn "Wait at least $GRACE_DAYS days before running 'remove-old' phase." log_warn "During this time, clients will sync and receive both keys." log_info "" log_info "Next steps:" echo " 1. Sign and publish updated TUF metadata" echo " 2. Monitor client sync status" echo " 3. After $GRACE_DAYS days, run: $0 remove-old --repo $REPO_DIR --old-key-name " ;; verify) log_step "Verifying key rotation status..." # Check local trust state stella trust status --show-keys log_info "" log_info "Verify that:" echo " 1. Both old and new Rekor keys are listed" echo " 2. Service endpoints are resolving correctly" echo " 3. Attestations signed with old key still verify" ;; remove-old) if [[ -z "$REPO_DIR" ]] || [[ -z "$OLD_KEY_NAME" ]]; then log_error "remove-old requires --repo and --old-key-name" usage fi if [[ ! -d "$REPO_DIR" ]]; then log_error "TUF repository not found: $REPO_DIR" exit 1 fi OLD_KEY_FILE="$REPO_DIR/targets/${OLD_KEY_NAME}.pub" if [[ ! -f "$OLD_KEY_FILE" ]]; then OLD_KEY_FILE="$REPO_DIR/targets/${OLD_KEY_NAME}" fi if [[ ! -f "$OLD_KEY_FILE" ]]; then log_error "Old key not found: $OLD_KEY_NAME" exit 1 fi log_step "Removing old Rekor key: $OLD_KEY_NAME" log_warn "This is IRREVERSIBLE. Ensure all clients have synced the new key." read -p "Type 'CONFIRM' to proceed: " CONFIRM if [[ "$CONFIRM" != "CONFIRM" ]]; then log_error "Aborted" exit 1 fi # Remove key file rm -f "$OLD_KEY_FILE" # Remove from targets.json (simplified - production should use proper JSON manipulation) log_warn "Remember to update targets.json to remove the old key entry" log_warn "Then sign and publish the updated metadata" log_info "" log_info "Old key removed: $OLD_KEY_NAME" log_info "Key rotation complete!" ;; esac echo ""