A container registry that uses the AT Protocol for manifest storage and S3 for blob storage.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at codeberg-source 243 lines 8.0 kB view raw
1#!/bin/bash 2# ATProto Signature Verification Script 3# 4# This script verifies ATProto signatures for container images stored in ATCR. 5# It performs all steps except full cryptographic verification (which requires 6# the indigo library). For production use, use the atcr-verify CLI tool. 7# 8# Usage: ./atcr-verify.sh IMAGE_REF 9# Example: ./atcr-verify.sh atcr.io/alice/myapp:latest 10# 11# Requirements: 12# - curl 13# - jq 14# - crane (https://github.com/google/go-containerregistry/releases) 15# - oras (https://oras.land/docs/installation) 16 17set -e 18 19# Colors for output 20RED='\033[0;31m' 21GREEN='\033[0;32m' 22YELLOW='\033[1;33m' 23BLUE='\033[0;34m' 24NC='\033[0m' # No Color 25 26# Check dependencies 27check_dependencies() { 28 local missing=0 29 30 for cmd in curl jq crane oras; do 31 if ! command -v $cmd &> /dev/null; then 32 echo -e "${RED}${NC} Missing dependency: $cmd" 33 missing=1 34 fi 35 done 36 37 if [ $missing -eq 1 ]; then 38 echo "" 39 echo "Install missing dependencies:" 40 echo " curl: https://curl.se/download.html" 41 echo " jq: https://stedolan.github.io/jq/download/" 42 echo " crane: https://github.com/google/go-containerregistry/releases" 43 echo " oras: https://oras.land/docs/installation" 44 exit 1 45 fi 46} 47 48# Print with color 49print_step() { 50 echo -e "${BLUE}[$1/${TOTAL_STEPS}]${NC} $2..." 51} 52 53print_success() { 54 echo -e " ${GREEN}${NC} $1" 55} 56 57print_error() { 58 echo -e " ${RED}${NC} $1" 59} 60 61print_warning() { 62 echo -e " ${YELLOW}${NC} $1" 63} 64 65# Main verification function 66verify_image() { 67 local image="$1" 68 69 if [ -z "$image" ]; then 70 echo "Usage: $0 IMAGE_REF" 71 echo "Example: $0 atcr.io/alice/myapp:latest" 72 exit 1 73 fi 74 75 TOTAL_STEPS=7 76 77 echo "" 78 echo "═══════════════════════════════════════════════════" 79 echo " ATProto Signature Verification" 80 echo "═══════════════════════════════════════════════════" 81 echo " Image: $image" 82 echo "═══════════════════════════════════════════════════" 83 echo "" 84 85 # Step 1: Resolve image digest 86 print_step 1 "Resolving image digest" 87 DIGEST=$(crane digest "$image" 2>&1) 88 if [ $? -ne 0 ]; then 89 print_error "Failed to resolve image digest" 90 echo "$DIGEST" 91 exit 1 92 fi 93 print_success "$DIGEST" 94 95 # Extract registry, repository, and tag 96 REGISTRY=$(echo "$image" | cut -d/ -f1) 97 REPO=$(echo "$image" | cut -d/ -f2-) 98 REPO_PATH=$(echo "$REPO" | cut -d: -f1) 99 100 # Step 2: Discover ATProto signature artifacts 101 print_step 2 "Discovering ATProto signature artifacts" 102 REFERRERS_URL="https://${REGISTRY}/v2/${REPO_PATH}/referrers/${DIGEST}?artifactType=application/vnd.atproto.signature.v1+json" 103 104 SIG_ARTIFACTS=$(curl -s -H "Accept: application/vnd.oci.image.index.v1+json" "$REFERRERS_URL") 105 106 if [ $? -ne 0 ]; then 107 print_error "Failed to query referrers API" 108 exit 1 109 fi 110 111 SIG_COUNT=$(echo "$SIG_ARTIFACTS" | jq '.manifests | length') 112 if [ "$SIG_COUNT" = "0" ]; then 113 print_error "No ATProto signature found" 114 echo "" 115 echo "This image does not have an ATProto signature." 116 echo "Signatures are automatically created when you push to ATCR." 117 exit 1 118 fi 119 120 print_success "Found $SIG_COUNT signature(s)" 121 122 # Get first signature digest 123 SIG_DIGEST=$(echo "$SIG_ARTIFACTS" | jq -r '.manifests[0].digest') 124 SIG_DID=$(echo "$SIG_ARTIFACTS" | jq -r '.manifests[0].annotations["io.atcr.atproto.did"]') 125 print_success "Signature digest: $SIG_DIGEST" 126 print_success "Signed by DID: $SIG_DID" 127 128 # Step 3: Fetch signature metadata 129 print_step 3 "Fetching signature metadata" 130 131 TMPDIR=$(mktemp -d) 132 trap "rm -rf $TMPDIR" EXIT 133 134 oras pull "${REGISTRY}/${REPO_PATH}@${SIG_DIGEST}" -o "$TMPDIR" --quiet 2>&1 135 if [ $? -ne 0 ]; then 136 print_error "Failed to fetch signature metadata" 137 exit 1 138 fi 139 140 # Find the JSON file 141 SIG_FILE=$(find "$TMPDIR" -name "*.json" -type f | head -n 1) 142 if [ -z "$SIG_FILE" ]; then 143 print_error "Signature metadata file not found" 144 exit 1 145 fi 146 147 DID=$(jq -r '.atproto.did' "$SIG_FILE") 148 HANDLE=$(jq -r '.atproto.handle // "unknown"' "$SIG_FILE") 149 PDS=$(jq -r '.atproto.pdsEndpoint' "$SIG_FILE") 150 RECORD_URI=$(jq -r '.atproto.recordUri' "$SIG_FILE") 151 COMMIT_CID=$(jq -r '.atproto.commitCid' "$SIG_FILE") 152 SIGNED_AT=$(jq -r '.atproto.signedAt' "$SIG_FILE") 153 154 print_success "DID: $DID" 155 print_success "Handle: $HANDLE" 156 print_success "PDS: $PDS" 157 print_success "Record: $RECORD_URI" 158 print_success "Signed at: $SIGNED_AT" 159 160 # Step 4: Resolve DID to public key 161 print_step 4 "Resolving DID to public key" 162 163 DID_DOC=$(curl -s "https://plc.directory/$DID") 164 if [ $? -ne 0 ]; then 165 print_error "Failed to resolve DID" 166 exit 1 167 fi 168 169 PUB_KEY_MB=$(echo "$DID_DOC" | jq -r '.verificationMethod[0].publicKeyMultibase') 170 if [ "$PUB_KEY_MB" = "null" ] || [ -z "$PUB_KEY_MB" ]; then 171 print_error "Public key not found in DID document" 172 exit 1 173 fi 174 175 print_success "Public key: ${PUB_KEY_MB:0:20}...${PUB_KEY_MB: -10}" 176 177 # Step 5: Query PDS for signed record 178 print_step 5 "Querying PDS for signed record" 179 180 # Extract collection and rkey from record URI (at://did/collection/rkey) 181 COLLECTION=$(echo "$RECORD_URI" | sed 's|at://[^/]*/\([^/]*\)/.*|\1|') 182 RKEY=$(echo "$RECORD_URI" | sed 's|at://.*/||') 183 184 RECORD_URL="${PDS}/xrpc/com.atproto.repo.getRecord?repo=${DID}&collection=${COLLECTION}&rkey=${RKEY}" 185 RECORD=$(curl -s "$RECORD_URL") 186 187 if [ $? -ne 0 ]; then 188 print_error "Failed to fetch record from PDS" 189 exit 1 190 fi 191 192 RECORD_CID=$(echo "$RECORD" | jq -r '.cid') 193 if [ "$RECORD_CID" = "null" ] || [ -z "$RECORD_CID" ]; then 194 print_error "Record not found in PDS" 195 exit 1 196 fi 197 198 print_success "Record CID: $RECORD_CID" 199 200 # Step 6: Verify record matches image manifest 201 print_step 6 "Verifying record integrity" 202 203 RECORD_DIGEST=$(echo "$RECORD" | jq -r '.value.digest') 204 if [ "$RECORD_DIGEST" != "$DIGEST" ]; then 205 print_error "Record digest ($RECORD_DIGEST) doesn't match image digest ($DIGEST)" 206 exit 1 207 fi 208 209 print_success "Record digest matches image digest" 210 211 # Step 7: Signature verification status 212 print_step 7 "Cryptographic signature verification" 213 214 print_warning "Full cryptographic verification requires ATProto crypto library" 215 print_warning "This script verifies:" 216 echo " • Record exists in PDS" 217 echo " • DID resolved successfully" 218 echo " • Public key retrieved from DID document" 219 echo " • Record digest matches image digest" 220 echo "" 221 print_warning "For full cryptographic verification, use: atcr-verify $image" 222 223 # Summary 224 echo "" 225 echo "═══════════════════════════════════════════════════" 226 echo -e " ${GREEN}✓ Verification Completed${NC}" 227 echo "═══════════════════════════════════════════════════" 228 echo "" 229 echo " Signed by: $HANDLE ($DID)" 230 echo " Signed at: $SIGNED_AT" 231 echo " PDS: $PDS" 232 echo " Record: $RECORD_URI" 233 echo " Signature: $SIG_DIGEST" 234 echo "" 235 echo "═══════════════════════════════════════════════════" 236 echo "" 237} 238 239# Check dependencies first 240check_dependencies 241 242# Run verification 243verify_image "$1"