315 lines
8.0 KiB
Bash
315 lines
8.0 KiB
Bash
#!/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 <output-directory> [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!"
|