#!/bin/sh # AgentPKI bootstrap (POSIX). Runs three real scenarios against the live # issuer + verifier: # 1. happy path -> verdict: allow # 2. tampered signature -> verdict: deny (failure_reason: bad_signature) # 3. revoked-key sign -> verdict: deny (failure_reason: revoked_key) # # Nothing destructive. Six HTTP calls total. The point: you watch the same # verifier accept a real token and reject two different attacks, and the # different reason codes tell you which attack happened. # # Source: https://github.com/agentpki/web/blob/main/public/bootstrap # Inspect without executing: # curl -fsSL https://agentpki.dev/bootstrap set -eu # Required tools for cmd in curl sed awk cut; do if ! command -v "$cmd" >/dev/null 2>&1; then printf ' [fail] missing required tool: %s\n\n' "$cmd" >&2 exit 1 fi done # ─── Helpers ─────────────────────────────────────────────────────────── # Extract a JSON string field at the top level. Handles simple strings # that don't contain escaped quotes. For failure_detail (which DOES # contain escaped quotes), use json_str_detail below. json_str() { # $1 = json blob, $2 = field name printf '%s' "$1" | sed -n 's/.*"'"$2"'"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' } json_num() { printf '%s' "$1" | sed -n 's/.*"'"$2"'"[[:space:]]*:[[:space:]]*\([0-9]*\).*/\1/p' } # failure_detail string can contain \" inside. Look for the trailing # `","next_field"` boundary instead of just `"`. json_detail() { # $1 = json blob; extracts content of "failure_detail":"..." printf '%s' "$1" | awk ' { s = $0 i = index(s, "\"failure_detail\":\"") if (i == 0) { exit } s = substr(s, i + length("\"failure_detail\":\"")) # Find unescaped closing quote: "," j = index(s, "\",\"") if (j == 0) { print s; exit } print substr(s, 1, j - 1) } ' } # ─── Banner ──────────────────────────────────────────────────────────── printf '\nAgentPKI bootstrap . v1.0 (3-scenario trust demo)\n' printf -- '----------------------------------------------------\n\n' # ─── Step 0: collect email ───────────────────────────────────────────── if [ -n "${AGENTPKI_BOOTSTRAP_EMAIL:-}" ]; then EMAIL=$AGENTPKI_BOOTSTRAP_EMAIL printf ' Email? %s (from env)\n' "$EMAIL" elif [ -r /dev/tty ]; then printf ' Email? ' IFS= read -r EMAIL &2 printf ' If running in CI, set AGENTPKI_BOOTSTRAP_EMAIL=you@example.com\n\n' >&2 exit 1 fi case "$EMAIL" in *@*.*) : ;; *) printf '\n [fail] "%s" does not look like an email.\n\n' "$EMAIL" >&2 exit 1 ;; esac printf '\n' # ════════════════════════════════════════════════════════════════════════ # SCENARIO 1 / 3 . happy path # ════════════════════════════════════════════════════════════════════════ printf ' Scenario 1 of 3 . happy path\n' printf ' --------------------------------------------------\n' printf ' Claiming subdomain + minting + verifying + storing ... ' REQ_BODY=$(printf '{"email":"%s"}' "$EMAIL") S1=$(curl -fsS -X POST https://agentpki.dev/api/v1/bootstrap-claim \ -H 'content-type: application/json' \ -d "$REQ_BODY" 2>/dev/null || true) if [ -z "$S1" ]; then printf 'fail\n\n No response from agentpki.dev. Check your network and try again.\n\n' >&2 exit 1 fi case "$S1" in *'"error"'*) ERR=$(json_str "$S1" error) DETAIL=$(json_str "$S1" detail) printf 'fail (%s)\n\n %s\n\n' "$ERR" "$DETAIL" >&2 exit 1 ;; esac S1_ELAPSED=$(json_num "$S1" bootstrap_elapsed_ms) S1_SUBDOMAIN=$(json_str "$S1" subdomain) S1_TOKEN=$(json_str "$S1" passport_token) S1_TOKEN_LEN=$(json_num "$S1" passport_token_length) S1_VERDICT=$(json_str "$S1" verdict) S1_VERIFIER_MS=$(json_num "$S1" verifier_elapsed_ms) S1_CHECK_URL=$(json_str "$S1" check_url) S1_TOK_HEAD=$(printf '%s' "$S1_TOKEN" | cut -c1-24) printf 'ok in %sms\n' "$S1_ELAPSED" printf ' issuer %s\n' "$S1_SUBDOMAIN" printf ' passport %s... (%s chars)\n' "$S1_TOK_HEAD" "$S1_TOKEN_LEN" printf ' verdict %s (verifier elapsed %sms)\n' "$S1_VERDICT" "$S1_VERIFIER_MS" if [ -n "$S1_CHECK_URL" ]; then printf ' share %s\n' "$S1_CHECK_URL" fi printf '\n' # ════════════════════════════════════════════════════════════════════════ # SCENARIO 2 / 3 . tampered signature # Mint a clean token, flip last 4 chars of parts[2] (which is the # Ed25519 signature blob), verify. Payload JSON stays valid -- the # signature no longer matches. # ════════════════════════════════════════════════════════════════════════ printf ' Scenario 2 of 3 . tampered signature\n' printf ' --------------------------------------------------\n' printf ' Minting a fresh token, flipping 4 chars of its Ed25519 signature, verifying ... ' CLEAN_MINT=$(curl -fsS https://demo.agentpki.dev/mint 2>/dev/null) CLEAN_TOK=$(json_str "$CLEAN_MINT" token) if [ -z "$CLEAN_TOK" ]; then printf 'fail (mint returned no token)\n\n' >&2 exit 1 fi # Split token on '.' into 4 fields. POSIX-safe. P0=$(printf '%s' "$CLEAN_TOK" | awk -F. '{print $1}') P1=$(printf '%s' "$CLEAN_TOK" | awk -F. '{print $2}') P2=$(printf '%s' "$CLEAN_TOK" | awk -F. '{print $3}') P3=$(printf '%s' "$CLEAN_TOK" | awk -F. '{print $4}') P2_LEN=$(printf '%s' "$P2" | awk '{print length}') P2_HEAD_LEN=$((P2_LEN - 4)) P2_HEAD=$(printf '%s' "$P2" | cut -c1-"$P2_HEAD_LEN") TAMPERED_TOK="${P0}.${P1}.${P2_HEAD}AAAA.${P3}" S2=$(curl -fsS -X POST https://verify.agentpki.dev/v1/verify \ -H 'content-type: application/json' \ -d "$(printf '{"token":"%s"}' "$TAMPERED_TOK")" 2>/dev/null) S2_VERDICT=$(json_str "$S2" verdict) S2_REASON=$(json_str "$S2" failure_reason) S2_ELAPSED=$(json_num "$S2" elapsed_ms) printf 'done\n' printf ' verdict %s in %sms\n' "$S2_VERDICT" "$S2_ELAPSED" printf ' reason %s\n' "$S2_REASON" printf ' why The payload JSON was still valid -- but the Ed25519\n' printf ' signature no longer matched. No network needed: this\n' printf ' fails on pure crypto math, locally, before any CRL\n' printf ' fetch even gets queued.\n\n' # ════════════════════════════════════════════════════════════════════════ # SCENARIO 3 / 3 . revoked-key signing # demo.agentpki.dev/mint?revoked=1 signs with a kid that the directory # lists as revoked. Signature is real; key is dead. # ════════════════════════════════════════════════════════════════════════ printf ' Scenario 3 of 3 . revoked-key signing\n' printf ' --------------------------------------------------\n' printf ' Minting via /mint?revoked=1 (signs with rotated kid), verifying ... ' REV_MINT=$(curl -fsS 'https://demo.agentpki.dev/mint?revoked=1' 2>/dev/null) REV_TOK=$(json_str "$REV_MINT" token) if [ -z "$REV_TOK" ]; then printf 'fail (revoked mint returned no token)\n\n' >&2 exit 1 fi S3=$(curl -fsS -X POST https://verify.agentpki.dev/v1/verify \ -H 'content-type: application/json' \ -d "$(printf '{"token":"%s"}' "$REV_TOK")" 2>/dev/null) S3_VERDICT=$(json_str "$S3" verdict) S3_REASON=$(json_str "$S3" failure_reason) S3_DETAIL=$(json_detail "$S3") S3_ELAPSED=$(json_num "$S3" elapsed_ms) printf 'done\n' printf ' verdict %s in %sms\n' "$S3_VERDICT" "$S3_ELAPSED" printf ' reason %s\n' "$S3_REASON" if [ -n "$S3_DETAIL" ]; then printf ' detail %s\n' "$S3_DETAIL" fi printf ' why Signature was mathematically valid -- the rotated\n' printf ' kid really did sign this token. The verifier consulted\n' printf " the issuer's CRL (cached after first lookup) and saw\n" printf ' the kid is now revoked. Different reason field from\n' printf ' scenario 2 -- bad_signature means "this is a fake",\n' printf ' revoked_key means "this was real, the key is dead".\n\n' # ─── Summary ─────────────────────────────────────────────────────────── printf -- ' ----------------------------------------------------\n' printf ' All 3 scenarios behaved as expected:\n' printf ' [scenario 1] real signed token -> %s\n' "$S1_VERDICT" printf ' [scenario 2] tampered signature -> %s (%s)\n' "$S2_VERDICT" "$S2_REASON" printf ' [scenario 3] revoked-key signing -> %s (%s)\n' "$S3_VERDICT" "$S3_REASON" printf '\n' printf ' AgentPKI distinguishes a forged token from a revoked one --\n' printf ' and the verifier tells you, accurately, which one happened.\n\n' printf ' About your demo subdomain:\n' printf ' %s\n' "$S1_SUBDOMAIN" printf ' is a deterministic hash of your email. Re-running gives the same\n' printf ' one. It identifies you in the demo only -- you do NOT own it,\n' printf ' and it is not a real DNS-resolvable issuer.\n\n' printf ' Next steps:\n' printf ' - issue real passports from YOUR OWN domain + keys:\n' printf ' https://agentpki.dev/dashboard (magic-link, ~3 min)\n' printf ' - install the SDK in your agent: npm i @agentpki/sdk\n' printf ' - read the v0.2 spec: https://agentpki.dev/spec\n' if [ -n "$S1_CHECK_URL" ]; then printf ' - share the allow scenario:\n' printf ' %s\n' "$S1_CHECK_URL" fi printf '\n'