Skip to content

Repository files navigation

tufcli

Go Reference

A Go implementation of tuftool - a command-line utility for creating and managing The Update Framework (TUF) repositories.

Overview

tufcli is a reimplementation of the Rust-based tuftool in Go, providing the same functionality for managing TUF repositories. TUF (The Update Framework) is a framework for securing software update systems.

Installation

go build -o tufcli .

Usage

# Show help
./tufcli --help

# Show version
./tufcli --version

# Set log level
./tufcli --log-level debug <command>

Commands

Repository Management

  • create - Create a new TUF repository
  • clone - Clone a TUF repository, including metadata and targets
  • update - Update a TUF repository's metadata and optionally add targets
  • download - Download a TUF repository's targets
  • transfer-metadata - Transfer metadata from one root of trust to another

Metadata Management

  • root - Manipulate root.json metadata file
    • init - Initialize a new root.json file
    • add-key - Add a key to root.json
    • remove-key - Remove keys from roles
    • expire - Set the expiration date
    • set-threshold - Set signature threshold for a role
    • bump-version - Increment version number
    • set-version - Set specific version number
    • gen-rsa-key - Generate RSA keypair and add to roles
    • sign - Sign root.json with private keys

Delegation Management

  • delegation - Manage delegated roles (requires --signing-role flag)
    • create-role - Create a delegated role
    • add-role - Add delegated role
    • add-key - Add a key to a delegated role
    • remove-key - Remove a key from a delegated role
    • remove - Remove a role
    • update-delegated-targets - Update delegated targets

Signing Config Management

  • signing-config - Manage Sigstore signing configuration files (signing_config.v0.2.json)
    • create - Create a new signing config (empty, from --base-config, or --with-default-services)
    • add-url - Add a service URL (ca, oidc, rekor, tsa) with replace-or-append semantics
    • remove-url - Remove a service URL by exact match
    • set-config - Set service selection policy (ALL, ANY, EXACT) for rekor or tsa
    • inspect - Display signing config contents (text or JSON format)

RHTAS (Red Hat Trusted Artifact Signer)

  • rhtas - Manage RHTAS TUF repositories with Sigstore-specific targets

    Handles Fulcio, CTLog, Rekor, and TSA targets, and generates the associated trusted_root.json and signing_config.v0.2.json metadata bundles.

    Required flags: --root (-r), --key (-k), --outdir (-o)

    Flag Description
    --set-fulcio-target Add Fulcio certificate chain (with --fulcio-uri, --fulcio-status, --oidc-uri)
    --set-ctlog-target Add CTLog public key (with --ctlog-uri, --ctlog-status)
    --set-rekor-target Add Rekor public key (with --rekor-uri, --rekor-status)
    --set-tsa-target Add TSA certificate chain (with --tsa-uri, --tsa-status)
    --delete-fulcio-target Remove a Fulcio target (repeatable)
    --delete-ctlog-target Remove a CTLog target (repeatable)
    --delete-rekor-target Remove a Rekor target (repeatable)
    --delete-tsa-target Remove a TSA target (repeatable)
    --force-version Enable explicit version overrides
    --targets-version Set targets.json version (requires --force-version)
    --snapshot-version Set snapshot.json version (requires --force-version)
    --timestamp-version Set timestamp.json version (requires --force-version)
    --targets-expires Set targets metadata expiration
    --snapshot-expires Set snapshot metadata expiration
    --timestamp-expires Set timestamp metadata expiration
    --operator Operator name for signing config (default: sigstore.dev)
    --metadata-url (-m) Base URL of existing TUF repo to load metadata from (file:// or https://)
    --allow-expired-repo Allow loading expired metadata (unsafe, for testing)
    --follow (-f) Follow symbolic links when copying target files
    --target-path-exists Behavior when target exists: skip (default), replace, or fail
    --incoming-metadata (-i) Path or URL to incoming delegated targets metadata
    --role Delegated role name (requires --incoming-metadata)

Create

Create a new TUF repository with signed metadata and target files.

Required flags: --root (-r), --key (-k), --outdir (-o), --add-targets (-t)

Flag Description
--root (-r) Path to root.json file for the repository
--key (-k) Key files to sign with (repeatable)
--outdir (-o) Output directory for the repository
--add-targets (-t) Directory of targets to add
--targets-expires Expiration of targets.json (RFC 3339 or relative like in 7 days)
--targets-version Version of targets.json
--snapshot-expires Expiration of snapshot.json
--snapshot-version Version of snapshot.json
--timestamp-expires Expiration of timestamp.json
--timestamp-version Version of timestamp.json
--follow (-f) Follow symbolic links when adding targets
--target-path-exists Behavior when target exists: skip (default), replace, or fail
# Create a TUF repo with targets
./tufcli create \
  --root root.json \
  --key keys/root.pem \
  --key keys/snapshot.pem \
  --key keys/targets.pem \
  --key keys/timestamp.pem \
  --add-targets input/ \
  --targets-expires 'in 3 weeks' \
  --targets-version 1 \
  --snapshot-expires 'in 3 weeks' \
  --snapshot-version 1 \
  --timestamp-expires 'in 1 week' \
  --timestamp-version 1 \
  --outdir repo/

# Create with empty targets directory (metadata-only bootstrap)
./tufcli create \
  --root root.json \
  --key key.pem \
  --add-targets empty-dir/ \
  --targets-expires 'in 52 weeks' \
  --targets-version 1 \
  --snapshot-expires 'in 52 weeks' \
  --snapshot-version 1 \
  --timestamp-expires 'in 52 weeks' \
  --timestamp-version 1 \
  --outdir repo/

Download

Download targets from a TUF repository after verifying metadata integrity through the full TUF client workflow (root rotation, timestamp, snapshot, and targets verification).

The output directory must not already exist.

Required flags: --metadata-url (-m), --targets-url (-t)

Flag Description
--root (-r) Path to root.json file for the repository
--metadata-url (-m) TUF repository metadata base URL (required)
--targets-url (-t) TUF repository targets base URL (required)
--target-name (-n) Download only these targets (repeatable)
--root-version (-v) Remote root.json version number (default: 1)
--allow-expired-repo Allow download for expired metadata (unsafe, for testing only)
--allow-root-download Allow downloading root.json from the repository (unsafe)
# Download all targets
./tufcli download /tmp/outdir \
  -r root.json \
  -m https://tuf.example.com/metadata \
  -t https://tuf.example.com/targets

# Download specific targets
./tufcli download /tmp/outdir \
  -r root.json \
  -m https://tuf.example.com/metadata \
  -t https://tuf.example.com/targets \
  -n trusted_root.json -n signing_config.v0.2.json

# Download without a local root (unsafe, for testing)
./tufcli download /tmp/outdir \
  -m https://tuf.example.com/metadata \
  -t https://tuf.example.com/targets \
  --allow-root-download

# Download with expired metadata (unsafe, for testing)
./tufcli download /tmp/outdir \
  -r root.json \
  -m https://tuf.example.com/metadata \
  -t https://tuf.example.com/targets \
  --allow-expired-repo

Signing Config

Create and manage Sigstore signing configuration files standalone, without requiring a TUF repository. Output is byte-identical to cosign signing-config create.

# Create an empty signing config
./tufcli signing-config create -o signing_config.v0.2.json

# Add service endpoints
./tufcli signing-config add-url -c signing_config.v0.2.json \
  -t ca -u https://fulcio.example.com \
  --start-time 2025-01-01T00:00:00Z --operator example.com

./tufcli signing-config add-url -c signing_config.v0.2.json \
  -t rekor -u https://rekor.example.com \
  --start-time 2025-01-01T00:00:00Z --operator example.com

./tufcli signing-config add-url -c signing_config.v0.2.json \
  -t oidc -u https://oauth2.example.com \
  --start-time 2025-01-01T00:00:00Z --operator example.com

./tufcli signing-config add-url -c signing_config.v0.2.json \
  -t tsa -u https://tsa.example.com/api/v1/timestamp \
  --start-time 2025-01-01T00:00:00Z --operator example.com

# Set service selection policies
./tufcli signing-config set-config -c signing_config.v0.2.json -t rekor -s ANY
./tufcli signing-config set-config -c signing_config.v0.2.json -t tsa -s EXACT -n 1

# Inspect
./tufcli signing-config inspect -c signing_config.v0.2.json
./tufcli signing-config inspect -c signing_config.v0.2.json -f json

# Clone and modify a deployed config
./tufcli signing-config create -o modified.json --base-config deployed_config.json
./tufcli signing-config remove-url -c modified.json -t oidc -u https://old-provider.com

# Fetch public Sigstore defaults
./tufcli signing-config create -o defaults.json --with-default-services

Development Status

Root metadata commands are complete and tested. RHTAS commands are complete and tested. Download command is complete and tested. Create command is complete and tested. Clone command is complete and tested. Update command is complete and tested. Transfer-metadata command is complete and tested. Signing config commands are complete and tested. Delegation commands are not yet implemented.

Library Usage

tufcli can be used as a Go library. Every CLI command has a corresponding public API under pkg/.

Install

go get github.com/securesign/tufcli

Available packages

Package Import path Description
rootmeta github.com/securesign/tufcli/pkg/rootmeta Root.json lifecycle: init, keys, thresholds, expiration, signing
create github.com/securesign/tufcli/pkg/repo/create Create new TUF repositories
update github.com/securesign/tufcli/pkg/repo/update Update existing repositories (targets, versions, expirations)
clone github.com/securesign/tufcli/pkg/repo/clone Clone remote TUF repositories
download github.com/securesign/tufcli/pkg/repo/download Download targets with full TUF verification
transfer github.com/securesign/tufcli/pkg/repo/transfer Transfer metadata between roots of trust
rhtas github.com/securesign/tufcli/pkg/rhtas RHTAS Sigstore target management (Fulcio, Rekor, TSA)
signingconfig github.com/securesign/tufcli/pkg/signingconfig Sigstore signing config management (cosign-compatible)

Example: Create a TUF repository

package main

import (
	"fmt"
	"os"
	"time"

	"github.com/securesign/tufcli/pkg/repo/create"
	"github.com/securesign/tufcli/pkg/rootmeta"
)

func main() {
	// 1. Initialize root.json
	rootmeta.Init(rootmeta.InitOptions{Path: "root.json"})
	rootmeta.Expire(rootmeta.ExpireOptions{Path: "root.json", Expires: time.Now().AddDate(1, 0, 0)})
	for _, role := range []string{"root", "snapshot", "targets", "timestamp"} {
		rootmeta.SetThreshold(rootmeta.SetThresholdOptions{Path: "root.json", Role: role, Threshold: 1})
	}

	// 2. Generate a signing key
	keyID, _ := rootmeta.GenRsaKey(rootmeta.GenRsaKeyOptions{
		Path:    "root.json",
		KeyPath: "key.pem",
		Bits:    2048,
		Roles:   []string{"root", "snapshot", "targets", "timestamp"},
	})
	fmt.Printf("Generated key: %s\n", keyID)

	// 3. Sign root.json
	rootmeta.Sign(rootmeta.SignOptions{Path: "root.json", KeyPaths: []string{"key.pem"}})

	// 4. Create a repository with targets
	os.MkdirAll("targets-input", 0755)
	os.WriteFile("targets-input/artifact.txt", []byte("hello"), 0644)

	create.Create(&create.Options{
		RootPath:         "root.json",
		KeyPaths:         []string{"key.pem"},
		OutDir:           "my-repo",
		AddTargetsDir:    "targets-input",
		TargetsExpires:   time.Now().AddDate(0, 0, 21),
		TargetsVersion:   1,
		SnapshotExpires:  time.Now().AddDate(0, 0, 21),
		SnapshotVersion:  1,
		TimestampExpires: time.Now().AddDate(0, 0, 7),
		TimestampVersion: 1,
	})
	fmt.Println("Repository created at my-repo/")
}

Example: Create a Sigstore signing config

package main

import (
	"fmt"
	"time"

	"github.com/securesign/tufcli/pkg/signingconfig"
)

func main() {
	signingconfig.Create(signingconfig.CreateOptions{Output: "signing_config.v0.2.json"})

	signingconfig.AddURL(signingconfig.AddURLOptions{
		ConfigPath: "signing_config.v0.2.json",
		Type:       "ca",
		URL:        "https://fulcio.example.com",
		APIVersion: 1,
		Operator:   "example.com",
		StartTime:  time.Now(),
	})

	output, _ := signingconfig.Inspect(signingconfig.InspectOptions{
		ConfigPath: "signing_config.v0.2.json",
		Format:     "text",
	})
	fmt.Print(output)
}

Suppressing output

The clone, download, and transfer commands accept an Output io.Writer field for controlling progress messages. Library consumers can suppress output or redirect it:

import (
	"bytes"
	"io"

	"github.com/securesign/tufcli/pkg/repo/clone"
)

// Silent mode — no progress output
clone.Clone(&clone.Options{Output: io.Discard, ...})

// Capture output into a buffer
var buf bytes.Buffer
clone.Clone(&clone.Options{Output: &buf, ...})

// Default (Output not set) — writes to os.Stderr, same as CLI
clone.Clone(&clone.Options{...})

TUF Specification

This implementation targets TUF specification version 1.0.0.

License

MIT OR Apache-2.0

Original Implementation

This is a Go port of the original Rust implementation available at: https://github.com/awslabs/tough

Examples

Quick start (using gen-rsa-key shortcut)

The following is an example of how you can create and download a TUF repository using tufcli.

Create a root.json and signing Key

export WRK="${HOME}/examples"
mkdir -p "${WRK}"

# we will store our root.json in $WRK/root
mkdir "${WRK}/root"
# save the path to the root.json we are about to create, we will use it a lot
export ROOT="${WRK}/root/root.json"

# we will store our signing keys in $WRK/keys
mkdir "${WRK}/keys"

# instantiate a new root.json
./tufcli root init --path "${ROOT}"

# set the root file's expiration date
./tufcli root expire --path "${ROOT}" --time "in 1 year"

# set the signing threshold for each of the standard signing roles.Each of the following roles must have at least 1 valid signature
./tufcli root set-threshold --path "${ROOT}" --role root --threshold 1
./tufcli root set-threshold --path "${ROOT}" --role snapshot --threshold 1
./tufcli root set-threshold --path "${ROOT}" --role targets --threshold 1
./tufcli root set-threshold --path "${ROOT}" --role timestamp --threshold 1

# create an RSA key and store it as a file. this requires openssl on your system
# this command both creates the key and adds it to root.json for the root role
# for this example we will re-use the same key for the other standard roles
./tufcli root gen-rsa-key --path "${ROOT}" --output "${WRK}/keys/root.pem"  --role root --role snapshot --role targets --role timestamp --bits 2048

# sign root.json
./tufcli root sign --path "${ROOT}" --key "${WRK}/keys/root.pem" 

Create a new TUF Repo

Now that we have a root.json file, we can create and sign a TUF repository.

# create a directory to hold the targets that we will sign. we call this the
# 'input' directory because these are the targets that we want to put into
# our TUF repo
mkdir -p "${WRK}/input"

# create an empty TUF repo
./tufcli create \
  --root "${ROOT}" \
  --key "${WRK}/keys/root.pem" \
  --add-targets "${WRK}/input" \
  --targets-expires 'in 3 weeks' \
  --targets-version 1 \
  --snapshot-expires 'in 3 weeks' \
  --snapshot-version 1 \
  --timestamp-expires 'in 1 week' \
  --timestamp-version 1 \
  --outdir "${WRK}/tuf-repo"

Setting up an RHTAS repository

# Create Rekor public key
openssl ecparam -genkey -name prime256v1 -noout -out ${WRK}/input/rekor.pem
openssl ec -in ${WRK}/input/rekor.pem -pubout -out ${WRK}/input/rekor.pub
rm ${WRK}/input/rekor.pem

# Add a Rekor transparency log
./tufcli rhtas \
  --root "${ROOT}" \
  --key "${WRK}/keys/root.pem" \
  --outdir "${WRK}/tuf-repo" \
  --set-rekor-target "${WRK}/input/rekor.pub" \
  --rekor-uri https://rekor.sigstore.dev \
  --metadata-url file:///$WRK/tuf-repo/

# Delete a target
./tufcli rhtas \
   --root "${ROOT}" \
   --key "${WRK}/keys/root.pem" \
   --delete-fulcio-target "fulcio-chain.pem" \
   --outdir "${WRK}/tuf-repo" \
   --metadata-url file:///$WRK/tuf-repo/

# Custom expiration and version
./tufcli rhtas \
  -r root.json -k key.pem -o repo/ \
  --set-fulcio-target fulcio-chain.pem \
  --targets-expires "in 365 days" \
  --snapshot-expires "in 90 days" \
  --timestamp-expires "in 1 day"

Update TUF repo (update metadata expiration)

# Update metadata expiration dates
./tufcli update \
  --root "${ROOT}" \
  --key "${WRK}/keys/root.pem" \
  --targets-expires 'in 3 weeks' \
  --snapshot-expires 'in 3 weeks' \
  --timestamp-expires 'in 1 week' \
  --outdir "${WRK}/tuf-repo" \
  --metadata-url file:///$WRK/tuf-repo

Clone TUF Repo

Clone copies both metadata and targets into separate directories. Use --metadata-only to skip target downloads.

# Clone metadata and targets
./tufcli clone \
  --root "${WRK}/tuf-repo/root.json" \
  --metadata-url "file://${WRK}/tuf-repo/" \
  --targets-url "file://${WRK}/tuf-repo/targets" \
  --metadata-dir "${WRK}/tuf-clone/metadata" \
  --targets-dir "${WRK}/tuf-clone/targets"

# Clone metadata only (no targets)
./tufcli clone \
  --root "${WRK}/tuf-repo/root.json" \
  --metadata-url "file://${WRK}/tuf-repo/" \
  --metadata-dir "${WRK}/tuf-clone-metadata" \
  --metadata-only

Transfer Metadata to a New Root

Transfer metadata from an existing repository to a new root of trust. This copies all target entries (metadata only, not files) and signs under the new root's keys.

# Create a new root for the transfer destination
export NEW_ROOT="${WRK}/new-root/root.json"
mkdir -p "${WRK}/new-root"
./tufcli root init --path "${NEW_ROOT}"
./tufcli root expire --path "${NEW_ROOT}" --time "in 1 year"
./tufcli root set-threshold --path "${NEW_ROOT}" --role root --threshold 1
./tufcli root set-threshold --path "${NEW_ROOT}" --role snapshot --threshold 1
./tufcli root set-threshold --path "${NEW_ROOT}" --role targets --threshold 1
./tufcli root set-threshold --path "${NEW_ROOT}" --role timestamp --threshold 1
./tufcli root gen-rsa-key --path "${NEW_ROOT}" --output "${WRK}/new-root/key.pem" \
  --role root --role snapshot --role targets --role timestamp --bits 2048
./tufcli root sign --path "${NEW_ROOT}" --key "${WRK}/new-root/key.pem"

# Transfer metadata from old repo to new root
./tufcli transfer-metadata \
  --current-root "${WRK}/tuf-repo/root.json" \
  --new-root "${NEW_ROOT}" \
  --key "${WRK}/new-root/key.pem" \
  --metadata-url "file://${WRK}/tuf-repo/" \
  --targets-url "file://${WRK}/tuf-repo/targets" \
  --targets-expires 'in 3 weeks' \
  --targets-version 1 \
  --snapshot-expires 'in 3 weeks' \
  --snapshot-version 1 \
  --timestamp-expires 'in 1 week' \
  --timestamp-version 1 \
  --outdir "${WRK}/transferred-repo"

Download TUF Repo

Now that we have created TUF repo, we can inspect it using download command. Download command is usually used to download a remote repo using HTTP/S url, but for this example we will use a file based url to download from local repo.

# download tuf repo
./tufcli download \
   --root "${WRK}/tuf-repo/root.json" \
   -t "file://${WRK}/tuf-repo/targets" \
   -m "file://${WRK}/tuf-repo/" \
   "${WRK}/tuf-download"

Using existing keys

# Initialize root metadata
./tufcli root init --path "${ROOT}"

# Add existing key to all roles
./tufcli root add-key --path "${ROOT}" --key "${WRK}/keys/root.pem"  --role root --role snapshot --role targets --role timestamp

# Set thresholds
./tufcli root set-threshold --path "${ROOT}" --role root --threshold 1
./tufcli root set-threshold --path "${ROOT}" --role snapshot --threshold 1
./tufcli root set-threshold --path "${ROOT}" --role targets --threshold 1
./tufcli root set-threshold --path "${ROOT}" --role timestamp --threshold 1

# Set expiration and sign
./tufcli root expire --path "${ROOT}" --time "in 1 year"
./tufcli root sign --path "${ROOT}" --key "${WRK}/keys/root.pem" 

About

A Go implementation of tuftool - a command-line utility for creating and managing The Update Framework (TUF) repositories.

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages