#!/bin/bash # ----------------------------------------------------------------------------- # init-tuf-repo.sh # Sprint: SPRINT_20260125_001_Attestor_tuf_trust_foundation # Task: TUF-006 - Create TUF repository structure template # Description: Initialize a new TUF repository with signing keys # ----------------------------------------------------------------------------- set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" TEMPLATE_DIR="$(dirname "$SCRIPT_DIR")" # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color usage() { echo "Usage: $0 [options]" echo "" echo "Initialize a new TUF repository for StellaOps trust distribution." echo "" echo "Options:" echo " --key-type TYPE Key algorithm: ed25519 (default), ecdsa-p256" echo " --root-expiry DAYS Root metadata expiry (default: 365)" echo " --force Overwrite existing repository" echo " -h, --help Show this help message" echo "" echo "Example:" echo " $0 /var/lib/stellaops/trust-repo --key-type ed25519" exit 1 } log_info() { echo -e "${GREEN}[INFO]${NC} $1" } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } # Parse arguments OUTPUT_DIR="" KEY_TYPE="ed25519" ROOT_EXPIRY=365 FORCE=false while [[ $# -gt 0 ]]; do case $1 in --key-type) KEY_TYPE="$2" shift 2 ;; --root-expiry) ROOT_EXPIRY="$2" shift 2 ;; --force) FORCE=true shift ;; -h|--help) usage ;; *) if [[ -z "$OUTPUT_DIR" ]]; then OUTPUT_DIR="$1" else log_error "Unknown argument: $1" usage fi shift ;; esac done if [[ -z "$OUTPUT_DIR" ]]; then log_error "Output directory is required" usage fi # Check if directory exists if [[ -d "$OUTPUT_DIR" ]] && [[ "$FORCE" != "true" ]]; then log_error "Directory already exists: $OUTPUT_DIR" log_error "Use --force to overwrite" exit 1 fi # Create directory structure log_info "Creating TUF repository at: $OUTPUT_DIR" mkdir -p "$OUTPUT_DIR/keys" "$OUTPUT_DIR/targets" # Generate keys log_info "Generating signing keys (type: $KEY_TYPE)..." generate_key() { local name=$1 local key_file="$OUTPUT_DIR/keys/$name" case $KEY_TYPE in ed25519) # Generate Ed25519 key pair openssl genpkey -algorithm ED25519 -out "$key_file.pem" 2>/dev/null openssl pkey -in "$key_file.pem" -pubout -out "$key_file.pub" 2>/dev/null ;; ecdsa-p256) # Generate ECDSA P-256 key pair openssl ecparam -name prime256v1 -genkey -noout -out "$key_file.pem" 2>/dev/null openssl ec -in "$key_file.pem" -pubout -out "$key_file.pub" 2>/dev/null ;; *) log_error "Unknown key type: $KEY_TYPE" exit 1 ;; esac chmod 600 "$key_file.pem" log_info " Generated: $name" } generate_key "root" generate_key "snapshot" generate_key "timestamp" generate_key "targets" # Calculate expiration dates NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ) ROOT_EXPIRES=$(date -u -d "+${ROOT_EXPIRY} days" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v+${ROOT_EXPIRY}d +%Y-%m-%dT%H:%M:%SZ) SNAPSHOT_EXPIRES=$(date -u -d "+7 days" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v+7d +%Y-%m-%dT%H:%M:%SZ) TIMESTAMP_EXPIRES=$(date -u -d "+1 day" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v+1d +%Y-%m-%dT%H:%M:%SZ) TARGETS_EXPIRES=$(date -u -d "+30 days" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v+30d +%Y-%m-%dT%H:%M:%SZ) # Get key IDs (SHA256 of public key) get_key_id() { local pubkey_file=$1 openssl pkey -pubin -in "$pubkey_file" -outform DER 2>/dev/null | openssl dgst -sha256 -hex | awk '{print $2}' } ROOT_KEY_ID=$(get_key_id "$OUTPUT_DIR/keys/root.pub") SNAPSHOT_KEY_ID=$(get_key_id "$OUTPUT_DIR/keys/snapshot.pub") TIMESTAMP_KEY_ID=$(get_key_id "$OUTPUT_DIR/keys/timestamp.pub") TARGETS_KEY_ID=$(get_key_id "$OUTPUT_DIR/keys/targets.pub") # Create root.json log_info "Creating metadata files..." cat > "$OUTPUT_DIR/root.json" << EOF { "signed": { "_type": "root", "spec_version": "1.0.0", "version": 1, "expires": "$ROOT_EXPIRES", "keys": { "$ROOT_KEY_ID": { "keytype": "$KEY_TYPE", "scheme": "$KEY_TYPE", "keyval": { "public": "$(base64 -w0 "$OUTPUT_DIR/keys/root.pub")" } }, "$SNAPSHOT_KEY_ID": { "keytype": "$KEY_TYPE", "scheme": "$KEY_TYPE", "keyval": { "public": "$(base64 -w0 "$OUTPUT_DIR/keys/snapshot.pub")" } }, "$TIMESTAMP_KEY_ID": { "keytype": "$KEY_TYPE", "scheme": "$KEY_TYPE", "keyval": { "public": "$(base64 -w0 "$OUTPUT_DIR/keys/timestamp.pub")" } }, "$TARGETS_KEY_ID": { "keytype": "$KEY_TYPE", "scheme": "$KEY_TYPE", "keyval": { "public": "$(base64 -w0 "$OUTPUT_DIR/keys/targets.pub")" } } }, "roles": { "root": { "keyids": ["$ROOT_KEY_ID"], "threshold": 1 }, "snapshot": { "keyids": ["$SNAPSHOT_KEY_ID"], "threshold": 1 }, "timestamp": { "keyids": ["$TIMESTAMP_KEY_ID"], "threshold": 1 }, "targets": { "keyids": ["$TARGETS_KEY_ID"], "threshold": 1 } }, "consistent_snapshot": true }, "signatures": [] } EOF # Create targets.json cat > "$OUTPUT_DIR/targets.json" << EOF { "signed": { "_type": "targets", "spec_version": "1.0.0", "version": 1, "expires": "$TARGETS_EXPIRES", "targets": {} }, "signatures": [] } EOF # Create snapshot.json cat > "$OUTPUT_DIR/snapshot.json" << EOF { "signed": { "_type": "snapshot", "spec_version": "1.0.0", "version": 1, "expires": "$SNAPSHOT_EXPIRES", "meta": { "targets.json": { "version": 1 } } }, "signatures": [] } EOF # Create timestamp.json cat > "$OUTPUT_DIR/timestamp.json" << EOF { "signed": { "_type": "timestamp", "spec_version": "1.0.0", "version": 1, "expires": "$TIMESTAMP_EXPIRES", "meta": { "snapshot.json": { "version": 1 } } }, "signatures": [] } EOF # Create sample service map cat > "$OUTPUT_DIR/targets/sigstore-services-v1.json" << EOF { "version": 1, "rekor": { "url": "https://rekor.sigstore.dev", "log_id": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d", "public_key_target": "rekor-key-v1" }, "fulcio": { "url": "https://fulcio.sigstore.dev", "root_cert_target": "fulcio-chain.pem" }, "ct_log": { "url": "https://ctfe.sigstore.dev" }, "overrides": { "staging": { "rekor_url": "https://rekor.sigstage.dev", "fulcio_url": "https://fulcio.sigstage.dev" } }, "metadata": { "updated_at": "$NOW", "note": "Production Sigstore endpoints" } } EOF # Copy scripts cp "$TEMPLATE_DIR/scripts/add-target.sh" "$OUTPUT_DIR/scripts/" 2>/dev/null || true cp "$TEMPLATE_DIR/scripts/update-timestamp.sh" "$OUTPUT_DIR/scripts/" 2>/dev/null || true mkdir -p "$OUTPUT_DIR/scripts" log_info "" log_info "TUF repository initialized successfully!" log_info "" log_info "Directory structure:" log_info " $OUTPUT_DIR/" log_info " ├── keys/ # Signing keys (keep root key offline!)" log_info " ├── targets/ # Target files" log_info " ├── root.json # Root metadata" log_info " ├── snapshot.json # Snapshot metadata" log_info " ├── timestamp.json # Timestamp metadata" log_info " └── targets.json # Targets metadata" log_info "" log_warn "IMPORTANT: The metadata files are NOT YET SIGNED." log_warn "Run the signing script before publishing:" log_warn " ./scripts/sign-metadata.sh $OUTPUT_DIR" log_info "" log_warn "SECURITY: Move the root key to offline storage after signing!"