Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,10 @@ cargo run -p crosspack-cli -- --help

### 2) Bootstrap the trusted default source (`core`)

Before first metadata use, verify the published fingerprint in both channels:

- `docs/trust/core-registry-fingerprint.txt` in this repository.
- Matching GitHub Release note entry for the same `updated_at` and `key_id`.
Before first metadata use, derive the source fingerprint from trusted `registry.pub` bytes in the official registry repository.

```bash
cargo run -p crosspack-cli -- registry add core https://github.com/spiritledsoftware/crosspack-registry.git --kind git --priority 100 --fingerprint 65149d198a39db9ecfea6f63d098858ed3b06c118c1f455f84ab571106b830c2
cargo run -p crosspack-cli -- registry add core https://github.com/spiritledsoftware/crosspack-registry.git --kind git --priority 100 --fingerprint <sha256-of-registry.pub>
cargo run -p crosspack-cli -- update
cargo run -p crosspack-cli -- registry list
```
Expand Down Expand Up @@ -228,7 +225,7 @@ Crosspack verifies both metadata and artifacts:

- Official default source name: `core`.
- Official source kind and URL: `git` at `https://github.com/spiritledsoftware/crosspack-registry.git`.
- Official fingerprint distribution channel: `docs/trust/core-registry-fingerprint.txt` plus a matching GitHub Release note entry.
- Official fingerprint source: SHA-256 digest of `registry.pub` from the official registry repository.
- Bootstrap and rotation troubleshooting: `docs/registry-bootstrap-runbook.md`.

Trust boundary note:
Expand Down
2 changes: 1 addition & 1 deletion crates/crosspack-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ struct Cli {

const NO_ROOT_PACKAGES_TO_UPGRADE: &str = "No root packages installed";
const METADATA_CONFIG_GUIDANCE: &str =
"no configured registry snapshots available; bootstrap trusted source `core` with `crosspack registry add core https://github.com/spiritledsoftware/crosspack-registry.git --kind git --priority 100 --fingerprint <64-hex>` then run `crosspack update` (see https://github.com/spiritledsoftware/crosspack/blob/main/docs/registry-bootstrap-runbook.md and https://github.com/spiritledsoftware/crosspack/blob/main/docs/trust/core-registry-fingerprint.txt)";
"no configured registry snapshots available; bootstrap trusted source `core` with `crosspack registry add core https://github.com/spiritledsoftware/crosspack-registry.git --kind git --priority 100 --fingerprint <64-hex>` then run `crosspack update` (see https://github.com/spiritledsoftware/crosspack/blob/main/docs/registry-bootstrap-runbook.md)";
const SNAPSHOT_ID_MISMATCH_ERROR_CODE: &str = "snapshot-id-mismatch";
const SEARCH_METADATA_GUIDANCE: &str =
"search metadata unavailable; run `crosspack update` to refresh local snapshots and `crosspack registry list` to inspect source status";
Expand Down
3 changes: 3 additions & 0 deletions docs/install-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ The following install-flow extensions are planned in `docs/dependency-policy-spe
- creates or updates a single managed profile block in `~/.bashrc`, `~/.zshrc`, or `~/.config/fish/config.fish`,
- ensures PATH setup and completion sourcing are idempotent.
- Windows installer (`scripts/install.ps1`) writes PowerShell completion script to `<prefix>\share\completions\crosspack.ps1` and updates `$PROFILE.CurrentUserCurrentHost` with one managed block for PATH + completion sourcing.
- Installers resolve the default `core` fingerprint at runtime by downloading `registry.pub` from `https://github.com/spiritledsoftware/crosspack-registry` and hashing it (SHA-256).
- Installers fail closed on fetch/hash/validation errors.
- Installer fingerprint overrides remain available for controlled/offline scenarios (`CROSSPACK_CORE_FINGERPRINT` on Unix, `-CoreFingerprint` on Windows).
- Installer shell setup is best-effort: unsupported shells or profile write failures print warnings and manual commands, but installation still succeeds.
- Opt out of installer shell setup with:
- Unix: `CROSSPACK_NO_SHELL_SETUP=1`
Expand Down
31 changes: 17 additions & 14 deletions docs/registry-bootstrap-runbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,14 @@ This runbook defines support and operator procedures for first-run trust bootstr
- Source name: `core`
- Source kind: `git`
- Source URL: `https://github.com/spiritledsoftware/crosspack-registry.git`
- Fingerprint channel:
- `docs/trust/core-registry-fingerprint.txt`
- Matching GitHub Release note entry
- Fingerprint source: SHA-256 digest of `registry.pub` from `https://github.com/spiritledsoftware/crosspack-registry`

Always verify both channels match on `fingerprint_sha256`, `updated_at`, and `key_id` before bootstrap.
Always verify fingerprint derivation from trusted `registry.pub` bytes before bootstrap.

## First-Run Bootstrap (User)

1. Read `docs/trust/core-registry-fingerprint.txt`.
2. Confirm the same values appear in the latest corresponding GitHub Release note.
3. Add the trusted source and update snapshots:
1. Derive SHA-256 from trusted `registry.pub` bytes.
2. Add the trusted source and update snapshots:

```bash
crosspack registry add core https://github.com/spiritledsoftware/crosspack-registry.git --kind git --priority 100 --fingerprint <fingerprint_sha256>
Expand All @@ -27,14 +24,20 @@ crosspack registry list

Expected state: `core` appears with `snapshot=ready:<snapshot-id>`.

## Installer Behavior

- `scripts/install.sh` and `scripts/install.ps1` fetch `registry.pub` from `https://github.com/spiritledsoftware/crosspack-registry` at install time and compute its SHA-256 fingerprint for `registry add`.
- Installers fail closed on fetch/hash/validation errors.
- Override only when needed for controlled/offline operations:
- Unix: `CROSSPACK_CORE_FINGERPRINT=<64-hex>`
- Windows: `-CoreFingerprint <64-hex>`

## Fingerprint and Key Rotation (Operator Procedure)

1. Prepare new signing keypair and stage new `registry.pub` at planned cutover revision.
2. Compute the new fingerprint from raw `registry.pub` bytes.
3. Update `docs/trust/core-registry-fingerprint.txt` with new `fingerprint_sha256`, `updated_at`, and `key_id`.
4. Publish a GitHub Release note entry with exactly matching values.
5. Announce cutover with user recovery commands.
6. Keep rollback window for the previous key; remove old key after successful migration.
3. Publish cutover communication and user recovery commands.
4. Keep rollback window for the previous key; remove old key after successful migration.

User-facing recovery commands during rotation:

Expand All @@ -61,8 +64,8 @@ Symptoms:
- Error includes mismatch/fingerprint wording.

Actions:
1. Re-check `docs/trust/core-registry-fingerprint.txt` against GitHub Release note.
2. Remove and re-add `core` with the published fingerprint.
1. Fetch trusted `registry.pub` bytes from the official registry repository.
2. Recompute fingerprint from trusted `registry.pub` bytes and compare with local `sources.toml` value.
3. Retry `crosspack update`.

### source sync failed (`source-sync-failed`)
Expand Down Expand Up @@ -92,6 +95,6 @@ Symptoms:
- Update or metadata read fails with signature/metadata validation context.

Actions:
1. Confirm registry key and fingerprint channels still match.
1. Confirm `registry.pub` and configured `fingerprint_sha256` still match.
2. Retry after fresh sync (`crosspack update`).
3. Escalate to registry operators with failing package path and error text.
18 changes: 6 additions & 12 deletions docs/source-management-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,17 @@ Crosspack publishes one official default source for first-run bootstrap:
- Source name: `core`
- Source kind: `git`
- Source URL: `https://github.com/spiritledsoftware/crosspack-registry.git`
- Fingerprint publication channel:
- `docs/trust/core-registry-fingerprint.txt` in this repository
- Matching GitHub Release note entry for the same `updated_at` and `key_id`
- Fingerprint source: SHA-256 digest of `registry.pub` from `https://github.com/spiritledsoftware/crosspack-registry`

Bootstrap sequence:

```text
crosspack registry add core https://github.com/spiritledsoftware/crosspack-registry.git --kind git --priority 100 --fingerprint <fingerprint-from-trust-bulletin>
crosspack registry add core https://github.com/spiritledsoftware/crosspack-registry.git --kind git --priority 100 --fingerprint <sha256-of-registry.pub>
crosspack update
crosspack registry list
```

Users must verify both fingerprint channels match before adding or updating trusted source records.
Users must derive or verify the fingerprint from trusted `registry.pub` bytes before adding or updating trusted source records.

## CLI Contract

Expand Down Expand Up @@ -264,13 +262,9 @@ Rotation is explicit and fail-closed. Operators must complete all steps in order

1. Generate and publish new `registry.pub` in the source root at the target cutover revision.
2. Compute new SHA-256 fingerprint from raw `registry.pub` bytes.
3. Update `docs/trust/core-registry-fingerprint.txt` with:
- `fingerprint_sha256`
- `updated_at`
- `key_id`
4. Publish a matching GitHub Release note entry with the same three values.
5. Announce cutover window and required user action.
6. Keep old key material available only for rollback interval; remove once cutover is complete.
3. Publish rotation notice and required user action in release communications.
4. Announce cutover window and required user action.
5. Keep old key material available only for rollback interval; remove once cutover is complete.

User recovery commands after announced rotation:

Expand Down
11 changes: 0 additions & 11 deletions docs/trust/core-registry-fingerprint.txt

This file was deleted.

48 changes: 46 additions & 2 deletions scripts/install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ param(
[string]$CoreUrl = "https://github.com/spiritledsoftware/crosspack-registry.git",
[string]$CoreKind = "git",
[int]$CorePriority = 100,
[string]$CoreFingerprint = "65149d198a39db9ecfea6f63d098858ed3b06c118c1f455f84ab571106b830c2",
[string]$CoreFingerprint = "",
[Alias("TrustBulletinUrl")]
[string]$CoreRegistryPubUrl = "",
[switch]$NoShellSetup
)

Expand All @@ -15,6 +17,42 @@ $ErrorActionPreference = "Stop"

$tmpDir = Join-Path ([System.IO.Path]::GetTempPath()) ("crosspack-install-" + [guid]::NewGuid().ToString("N"))

function Test-Hex64 {
param([string]$Value)
return $Value -match '^[0-9a-fA-F]{64}$'
}

function Resolve-CoreFingerprint {
param(
[string]$Override,
[string]$RegistryPubUrl,
[string]$TempDirectory
)

if (-not [string]::IsNullOrWhiteSpace($Override)) {
if (-not (Test-Hex64 -Value $Override)) {
throw "-CoreFingerprint must be exactly 64 hex characters"
}
return $Override
}

if ([string]::IsNullOrWhiteSpace($RegistryPubUrl)) {
throw "Core registry.pub URL is empty"
}
if (-not $RegistryPubUrl.StartsWith('https://')) {
throw "Core registry.pub URL must use https: $RegistryPubUrl"
}

$registryPubPath = Join-Path $TempDirectory "registry.pub"
Invoke-WebRequest -Uri $RegistryPubUrl -OutFile $registryPubPath -UseBasicParsing
$fingerprint = (Get-FileHash -Algorithm SHA256 -Path $registryPubPath).Hash.ToLowerInvariant()
if (-not (Test-Hex64 -Value $fingerprint)) {
throw "computed registry key fingerprint is invalid"
}

return $fingerprint
}

function Update-CrosspackManagedProfileBlock {
param(
[string]$ProfilePath,
Expand Down Expand Up @@ -119,6 +157,10 @@ $end
}

try {
if ([string]::IsNullOrWhiteSpace($CoreRegistryPubUrl)) {
$CoreRegistryPubUrl = "https://raw.githubusercontent.com/spiritledsoftware/crosspack-registry/main/registry.pub"
}

if ([string]::IsNullOrWhiteSpace($Version)) {
$release = Invoke-RestMethod -Uri "https://api.github.com/repos/$Repo/releases/latest"
$Version = $release.tag_name
Expand Down Expand Up @@ -163,8 +205,10 @@ try {
Copy-Item (Join-Path $tmpDir "crosspack.exe") (Join-Path $BinDir "cpk.exe") -Force

$crosspackExe = Join-Path $BinDir "crosspack.exe"
$resolvedCoreFingerprint = Resolve-CoreFingerprint -Override $CoreFingerprint -RegistryPubUrl $CoreRegistryPubUrl -TempDirectory $tmpDir

Write-Host "==> Configuring default registry source ($CoreName)"
$addOutput = & $crosspackExe registry add $CoreName $CoreUrl --kind $CoreKind --priority $CorePriority --fingerprint $CoreFingerprint 2>&1
$addOutput = & $crosspackExe registry add $CoreName $CoreUrl --kind $CoreKind --priority $CorePriority --fingerprint $resolvedCoreFingerprint 2>&1
if ($LASTEXITCODE -ne 0) {
$listOutput = & $crosspackExe registry list 2>&1
if ($LASTEXITCODE -ne 0 -or ($listOutput -notmatch [regex]::Escape($CoreName))) {
Expand Down
48 changes: 46 additions & 2 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ CORE_NAME="${CROSSPACK_CORE_NAME:-core}"
CORE_URL="${CROSSPACK_CORE_URL:-https://github.com/spiritledsoftware/crosspack-registry.git}"
CORE_KIND="${CROSSPACK_CORE_KIND:-git}"
CORE_PRIORITY="${CROSSPACK_CORE_PRIORITY:-100}"
CORE_FINGERPRINT="${CROSSPACK_CORE_FINGERPRINT:-65149d198a39db9ecfea6f63d098858ed3b06c118c1f455f84ab571106b830c2}"
CORE_FINGERPRINT="${CROSSPACK_CORE_FINGERPRINT:-}"
CORE_REGISTRY_PUB_URL="${CROSSPACK_CORE_REGISTRY_PUB_URL:-${CROSSPACK_TRUST_BULLETIN_URL:-https://raw.githubusercontent.com/spiritledsoftware/crosspack-registry/main/registry.pub}}"
SHELL_SETUP_OPT_OUT="${CROSSPACK_NO_SHELL_SETUP:-0}"

SHELL_SETUP_BEGIN="# >>> crosspack shell setup >>>"
Expand All @@ -24,6 +25,48 @@ warn() {
echo "warning: $*" >&2
}

is_hex64() {
value="$1"
[ "${#value}" -eq 64 ] || return 1
normalized="$(printf "%s" "$value" | tr 'A-F' 'a-f')"
case "$normalized" in
*[!0-9a-f]*|'') return 1 ;;
esac
return 0
}

assert_https_url() {
url="$1"
case "$url" in
https://*) return 0 ;;
*) err "registry key URL must use https: ${url}" ;;
esac
}

resolve_core_fingerprint() {
if [ -n "$CORE_FINGERPRINT" ]; then
if ! is_hex64 "$CORE_FINGERPRINT"; then
err "CROSSPACK_CORE_FINGERPRINT must be 64 hex characters"
fi
echo "$CORE_FINGERPRINT"
return 0
fi

assert_https_url "$CORE_REGISTRY_PUB_URL"

registry_pub_path="${tmp_dir}/registry.pub"
if ! download "$CORE_REGISTRY_PUB_URL" "$registry_pub_path"; then
err "failed fetching registry key from ${CORE_REGISTRY_PUB_URL}; set CROSSPACK_CORE_FINGERPRINT to override"
fi

computed_fingerprint="$(sha256_of "$registry_pub_path")"
if ! is_hex64 "$computed_fingerprint"; then
err "computed registry key fingerprint is invalid"
fi

echo "$computed_fingerprint"
}

download() {
url="$1"
out="$2"
Expand Down Expand Up @@ -264,7 +307,8 @@ else
fi

echo "==> Configuring default registry source (${CORE_NAME})"
if "${BIN_DIR}/crosspack" registry add "${CORE_NAME}" "${CORE_URL}" --kind "${CORE_KIND}" --priority "${CORE_PRIORITY}" --fingerprint "${CORE_FINGERPRINT}" >/dev/null 2>&1; then
resolved_core_fingerprint="$(resolve_core_fingerprint)"
if "${BIN_DIR}/crosspack" registry add "${CORE_NAME}" "${CORE_URL}" --kind "${CORE_KIND}" --priority "${CORE_PRIORITY}" --fingerprint "${resolved_core_fingerprint}" >/dev/null 2>&1; then
echo "Added registry source '${CORE_NAME}'"
else
if "${BIN_DIR}/crosspack" registry list 2>/dev/null | grep -q "${CORE_NAME}"; then
Expand Down