Files
at-container-registry/examples/verification/atcr-verify.sh
T
2025-10-31 21:03:33 -05:00

244 lines
7.9 KiB
Bash
Executable File

#!/bin/bash
# ATProto Signature Verification Script
#
# This script verifies ATProto signatures for container images stored in ATCR.
# It performs all steps except full cryptographic verification (which requires
# the indigo library). For production use, use the atcr-verify CLI tool.
#
# Usage: ./atcr-verify.sh IMAGE_REF
# Example: ./atcr-verify.sh atcr.io/alice/myapp:latest
#
# Requirements:
# - curl
# - jq
# - crane (https://github.com/google/go-containerregistry/releases)
# - oras (https://oras.land/docs/installation)
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Check dependencies
check_dependencies() {
local missing=0
for cmd in curl jq crane oras; do
if ! command -v $cmd &> /dev/null; then
echo -e "${RED}${NC} Missing dependency: $cmd"
missing=1
fi
done
if [ $missing -eq 1 ]; then
echo ""
echo "Install missing dependencies:"
echo " curl: https://curl.se/download.html"
echo " jq: https://stedolan.github.io/jq/download/"
echo " crane: https://github.com/google/go-containerregistry/releases"
echo " oras: https://oras.land/docs/installation"
exit 1
fi
}
# Print with color
print_step() {
echo -e "${BLUE}[$1/${TOTAL_STEPS}]${NC} $2..."
}
print_success() {
echo -e " ${GREEN}${NC} $1"
}
print_error() {
echo -e " ${RED}${NC} $1"
}
print_warning() {
echo -e " ${YELLOW}${NC} $1"
}
# Main verification function
verify_image() {
local image="$1"
if [ -z "$image" ]; then
echo "Usage: $0 IMAGE_REF"
echo "Example: $0 atcr.io/alice/myapp:latest"
exit 1
fi
TOTAL_STEPS=7
echo ""
echo "═══════════════════════════════════════════════════"
echo " ATProto Signature Verification"
echo "═══════════════════════════════════════════════════"
echo " Image: $image"
echo "═══════════════════════════════════════════════════"
echo ""
# Step 1: Resolve image digest
print_step 1 "Resolving image digest"
DIGEST=$(crane digest "$image" 2>&1)
if [ $? -ne 0 ]; then
print_error "Failed to resolve image digest"
echo "$DIGEST"
exit 1
fi
print_success "$DIGEST"
# Extract registry, repository, and tag
REGISTRY=$(echo "$image" | cut -d/ -f1)
REPO=$(echo "$image" | cut -d/ -f2-)
REPO_PATH=$(echo "$REPO" | cut -d: -f1)
# Step 2: Discover ATProto signature artifacts
print_step 2 "Discovering ATProto signature artifacts"
REFERRERS_URL="https://${REGISTRY}/v2/${REPO_PATH}/referrers/${DIGEST}?artifactType=application/vnd.atproto.signature.v1+json"
SIG_ARTIFACTS=$(curl -s -H "Accept: application/vnd.oci.image.index.v1+json" "$REFERRERS_URL")
if [ $? -ne 0 ]; then
print_error "Failed to query referrers API"
exit 1
fi
SIG_COUNT=$(echo "$SIG_ARTIFACTS" | jq '.manifests | length')
if [ "$SIG_COUNT" = "0" ]; then
print_error "No ATProto signature found"
echo ""
echo "This image does not have an ATProto signature."
echo "Signatures are automatically created when you push to ATCR."
exit 1
fi
print_success "Found $SIG_COUNT signature(s)"
# Get first signature digest
SIG_DIGEST=$(echo "$SIG_ARTIFACTS" | jq -r '.manifests[0].digest')
SIG_DID=$(echo "$SIG_ARTIFACTS" | jq -r '.manifests[0].annotations["io.atcr.atproto.did"]')
print_success "Signature digest: $SIG_DIGEST"
print_success "Signed by DID: $SIG_DID"
# Step 3: Fetch signature metadata
print_step 3 "Fetching signature metadata"
TMPDIR=$(mktemp -d)
trap "rm -rf $TMPDIR" EXIT
oras pull "${REGISTRY}/${REPO_PATH}@${SIG_DIGEST}" -o "$TMPDIR" --quiet 2>&1
if [ $? -ne 0 ]; then
print_error "Failed to fetch signature metadata"
exit 1
fi
# Find the JSON file
SIG_FILE=$(find "$TMPDIR" -name "*.json" -type f | head -n 1)
if [ -z "$SIG_FILE" ]; then
print_error "Signature metadata file not found"
exit 1
fi
DID=$(jq -r '.atproto.did' "$SIG_FILE")
HANDLE=$(jq -r '.atproto.handle // "unknown"' "$SIG_FILE")
PDS=$(jq -r '.atproto.pdsEndpoint' "$SIG_FILE")
RECORD_URI=$(jq -r '.atproto.recordUri' "$SIG_FILE")
COMMIT_CID=$(jq -r '.atproto.commitCid' "$SIG_FILE")
SIGNED_AT=$(jq -r '.atproto.signedAt' "$SIG_FILE")
print_success "DID: $DID"
print_success "Handle: $HANDLE"
print_success "PDS: $PDS"
print_success "Record: $RECORD_URI"
print_success "Signed at: $SIGNED_AT"
# Step 4: Resolve DID to public key
print_step 4 "Resolving DID to public key"
DID_DOC=$(curl -s "https://plc.directory/$DID")
if [ $? -ne 0 ]; then
print_error "Failed to resolve DID"
exit 1
fi
PUB_KEY_MB=$(echo "$DID_DOC" | jq -r '.verificationMethod[0].publicKeyMultibase')
if [ "$PUB_KEY_MB" = "null" ] || [ -z "$PUB_KEY_MB" ]; then
print_error "Public key not found in DID document"
exit 1
fi
print_success "Public key: ${PUB_KEY_MB:0:20}...${PUB_KEY_MB: -10}"
# Step 5: Query PDS for signed record
print_step 5 "Querying PDS for signed record"
# Extract collection and rkey from record URI (at://did/collection/rkey)
COLLECTION=$(echo "$RECORD_URI" | sed 's|at://[^/]*/\([^/]*\)/.*|\1|')
RKEY=$(echo "$RECORD_URI" | sed 's|at://.*/||')
RECORD_URL="${PDS}/xrpc/com.atproto.repo.getRecord?repo=${DID}&collection=${COLLECTION}&rkey=${RKEY}"
RECORD=$(curl -s "$RECORD_URL")
if [ $? -ne 0 ]; then
print_error "Failed to fetch record from PDS"
exit 1
fi
RECORD_CID=$(echo "$RECORD" | jq -r '.cid')
if [ "$RECORD_CID" = "null" ] || [ -z "$RECORD_CID" ]; then
print_error "Record not found in PDS"
exit 1
fi
print_success "Record CID: $RECORD_CID"
# Step 6: Verify record matches image manifest
print_step 6 "Verifying record integrity"
RECORD_DIGEST=$(echo "$RECORD" | jq -r '.value.digest')
if [ "$RECORD_DIGEST" != "$DIGEST" ]; then
print_error "Record digest ($RECORD_DIGEST) doesn't match image digest ($DIGEST)"
exit 1
fi
print_success "Record digest matches image digest"
# Step 7: Signature verification status
print_step 7 "Cryptographic signature verification"
print_warning "Full cryptographic verification requires ATProto crypto library"
print_warning "This script verifies:"
echo " • Record exists in PDS"
echo " • DID resolved successfully"
echo " • Public key retrieved from DID document"
echo " • Record digest matches image digest"
echo ""
print_warning "For full cryptographic verification, use: atcr-verify $image"
# Summary
echo ""
echo "═══════════════════════════════════════════════════"
echo -e " ${GREEN}✓ Verification Completed${NC}"
echo "═══════════════════════════════════════════════════"
echo ""
echo " Signed by: $HANDLE ($DID)"
echo " Signed at: $SIGNED_AT"
echo " PDS: $PDS"
echo " Record: $RECORD_URI"
echo " Signature: $SIG_DIGEST"
echo ""
echo "═══════════════════════════════════════════════════"
echo ""
}
# Check dependencies first
check_dependencies
# Run verification
verify_image "$1"