#!/bin/bash # ----------------------------------------------------------------------------- # add-target.sh # Sprint: SPRINT_20260125_001_Attestor_tuf_trust_foundation # Task: TUF-006 - Create TUF repository structure template # Description: Add a new target file to the TUF repository # ----------------------------------------------------------------------------- set -euo pipefail RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' usage() { echo "Usage: $0 [options]" echo "" echo "Add a target file to the TUF repository." echo "" echo "Options:" echo " --repo DIR Repository directory (default: current directory)" echo " --custom-hash HASH Override SHA256 hash (for testing only)" echo " -h, --help Show this help message" echo "" echo "Example:" echo " $0 /path/to/rekor-key.pub rekor-key-v1" echo " $0 /path/to/services.json sigstore-services-v1 --repo /var/lib/tuf" 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"; } SOURCE_FILE="" TARGET_NAME="" REPO_DIR="." CUSTOM_HASH="" while [[ $# -gt 0 ]]; do case $1 in --repo) REPO_DIR="$2" shift 2 ;; --custom-hash) CUSTOM_HASH="$2" shift 2 ;; -h|--help) usage ;; *) if [[ -z "$SOURCE_FILE" ]]; then SOURCE_FILE="$1" elif [[ -z "$TARGET_NAME" ]]; then TARGET_NAME="$1" else log_error "Unknown argument: $1" usage fi shift ;; esac done if [[ -z "$SOURCE_FILE" ]] || [[ -z "$TARGET_NAME" ]]; then log_error "Source file and target name are required" usage fi if [[ ! -f "$SOURCE_FILE" ]]; then log_error "Source file not found: $SOURCE_FILE" exit 1 fi if [[ ! -f "$REPO_DIR/targets.json" ]]; then log_error "Not a TUF repository: $REPO_DIR (targets.json not found)" exit 1 fi # Calculate file hash and size FILE_SIZE=$(stat -f%z "$SOURCE_FILE" 2>/dev/null || stat -c%s "$SOURCE_FILE") if [[ -n "$CUSTOM_HASH" ]]; then FILE_HASH="$CUSTOM_HASH" else FILE_HASH=$(openssl dgst -sha256 -hex "$SOURCE_FILE" | awk '{print $2}') fi log_info "Adding target: $TARGET_NAME" log_info " Source: $SOURCE_FILE" log_info " Size: $FILE_SIZE bytes" log_info " SHA256: $FILE_HASH" # Copy file to targets directory TARGETS_DIR="$REPO_DIR/targets" mkdir -p "$TARGETS_DIR" cp "$SOURCE_FILE" "$TARGETS_DIR/$TARGET_NAME" # Update targets.json # This is a simplified implementation - production should use proper JSON manipulation TARGETS_JSON="$REPO_DIR/targets.json" # Read current version CURRENT_VERSION=$(grep -o '"version"[[:space:]]*:[[:space:]]*[0-9]*' "$TARGETS_JSON" | head -1 | grep -o '[0-9]*') NEW_VERSION=$((CURRENT_VERSION + 1)) # Calculate new expiry (30 days from now) NEW_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) log_info "Updating targets.json (version $CURRENT_VERSION -> $NEW_VERSION)" # Create new targets entry python3 - "$TARGETS_JSON" "$TARGET_NAME" "$FILE_SIZE" "$FILE_HASH" "$NEW_VERSION" "$NEW_EXPIRES" << 'PYTHON_SCRIPT' import json import sys targets_file = sys.argv[1] target_name = sys.argv[2] file_size = int(sys.argv[3]) file_hash = sys.argv[4] new_version = int(sys.argv[5]) new_expires = sys.argv[6] with open(targets_file, 'r') as f: data = json.load(f) data['signed']['version'] = new_version data['signed']['expires'] = new_expires data['signed']['targets'][target_name] = { 'length': file_size, 'hashes': { 'sha256': file_hash } } # Clear signatures (need to re-sign) data['signatures'] = [] with open(targets_file, 'w') as f: json.dump(data, f, indent=2) print(f"Updated {targets_file}") PYTHON_SCRIPT log_info "" log_info "Target added successfully!" log_warn "IMPORTANT: targets.json signatures have been cleared." log_warn "Run the signing script to re-sign metadata before publishing."