A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io
docker container atproto go

Image Signing with ATProto#

ATCR provides cryptographic verification of container images through ATProto's native signature system. Every manifest stored in a PDS is cryptographically signed, providing tamper-proof image verification.

Overview#

Key Fact: Every image pushed to ATCR is automatically signed via ATProto's repository commit signing. No additional signing tools or steps are required.

When you push an image:

  1. Manifest stored in your PDS as an io.atcr.manifest record
  2. PDS signs the repository commit containing the manifest (ECDSA K-256)
  3. Signature is part of the ATProto repository chain
  4. Verification proves the manifest came from your DID and hasn't been tampered with

This document explains:

  • How ATProto signatures work for ATCR images
  • How to verify signatures using standard and custom tools
  • Integration options for different use cases
  • When to use optional X.509 certificates (Hold-as-CA)

ATProto Signature Model#

How It Works#

ATProto uses a repository commit signing model similar to Git:

1. docker push atcr.io/alice/myapp:latest
   ↓
2. AppView stores manifest in alice's PDS as io.atcr.manifest record
   ↓
3. PDS creates repository commit containing the new record
   ↓
4. PDS signs commit with alice's private key (ECDSA K-256)
   ↓
5. Commit becomes part of alice's cryptographically signed repo chain

What this proves:

  • ✅ Manifest came from alice's PDS (DID-based identity)
  • ✅ Manifest content hasn't been tampered with
  • ✅ Manifest was created at a specific time (commit timestamp)
  • ✅ Manifest is part of alice's verifiable repository history

Trust model:

  • Public keys distributed via DID documents (PLC directory, did:web)
  • Signatures use ECDSA K-256 (secp256k1)
  • Verification is decentralized (no central CA required)
  • Users control their own DIDs and can rotate keys

Signature Metadata#

In addition to ATProto's native commit signatures, ATCR creates ORAS signature artifacts that bridge ATProto signatures to the OCI ecosystem:

{
  "$type": "io.atcr.atproto.signature",
  "version": "1.0",
  "subject": {
    "digest": "sha256:abc123...",
    "mediaType": "application/vnd.oci.image.manifest.v1+json"
  },
  "atproto": {
    "did": "did:plc:alice123",
    "handle": "alice.bsky.social",
    "pdsEndpoint": "https://bsky.social",
    "recordUri": "at://did:plc:alice123/io.atcr.manifest/abc123",
    "commitCid": "bafyreih8...",
    "signedAt": "2025-10-31T12:34:56.789Z"
  },
  "signature": {
    "algorithm": "ECDSA-K256-SHA256",
    "keyId": "did:plc:alice123#atproto",
    "publicKeyMultibase": "zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDdo1Ko4Z"
  }
}

Stored as:

  • OCI artifact with artifactType: application/vnd.atproto.signature.v1+json
  • Linked to image manifest via OCI Referrers API
  • Discoverable by standard OCI tools (ORAS, Cosign, Crane)

Verification#

Quick Verification (Shell Script)#

For manual verification, use the provided shell scripts:

# Verify an image
./examples/verification/atcr-verify.sh atcr.io/alice/myapp:latest

# Output shows:
# - DID and handle of signer
# - PDS endpoint
# - ATProto record URI
# - Signature verification status

See: examples/verification/README.md for complete examples including:

  • Standalone verification script
  • Secure pull wrapper (verify before pull)
  • Kubernetes webhook deployment
  • CI/CD integration examples

Standard Tools (Discovery Only)#

Standard OCI tools can discover ATProto signature artifacts but cannot verify them (different signature format):

# Discover signatures with ORAS
oras discover atcr.io/alice/myapp:latest \
  --artifact-type application/vnd.atproto.signature.v1+json

# Fetch signature metadata
oras pull atcr.io/alice/myapp@sha256:sig789...

# View with Cosign (discovery only)
cosign tree atcr.io/alice/myapp:latest

Note: Cosign/Notary cannot verify ATProto signatures directly because they use a different signature format and trust model. Use integration plugins or the atcr-verify CLI tool instead.

Integration Options#

ATCR supports multiple integration approaches depending on your use case:

Build plugins for existing policy/verification engines:

Ratify Verifier Plugin:

  • Integrates with OPA Gatekeeper
  • Verifies ATProto signatures using Ratify's plugin interface
  • Policy-based enforcement for Kubernetes

OPA Gatekeeper External Provider:

  • HTTP service that verifies ATProto signatures
  • Rego policies call external provider
  • Flexible and easy to deploy

Containerd 2.0 Bindir Plugin:

  • Verifies signatures at containerd level
  • Works with any CRI-compatible runtime
  • No Kubernetes required

See: docs/SIGNATURE_INTEGRATION.md for complete plugin implementation examples

2. CLI Tool (atcr-verify)#

Standalone CLI tool for signature verification:

# Install
go install github.com/atcr-io/atcr/cmd/atcr-verify@latest

# Verify image
atcr-verify atcr.io/alice/myapp:latest --policy trust-policy.yaml

# Use in CI/CD
atcr-verify $IMAGE --quiet && kubectl apply -f deployment.yaml

Features:

  • Trust policy management (which DIDs to trust)
  • Multiple output formats (text, JSON, SARIF)
  • Offline verification with cached DID documents
  • Library usage for custom integrations

See: docs/ATCR_VERIFY_CLI.md for complete CLI specification

3. External Services#

Deploy verification as a service:

GitHub Actions:

- name: Verify image signature
  uses: atcr-io/atcr-verify-action@v1
  with:
    image: atcr.io/alice/myapp:${{ github.sha }}
    policy: .atcr/trust-policy.yaml

GitLab CI, Jenkins, CircleCI:

  • Use atcr-verify CLI in pipeline
  • Fail build if verification fails
  • Enforce signature requirements before deployment

4. X.509 Certificates (Hold-as-CA) ⚠️#

Optional approach where hold services issue X.509 certificates based on ATProto signatures:

Use cases:

  • Enterprise environments requiring PKI compliance
  • Tools that only support X.509 (legacy systems)
  • Notation integration (P-256 certificates)

Trade-offs:

  • ❌ Introduces centralization (hold acts as CA)
  • ❌ Trust shifts from DIDs to hold operator
  • ❌ Requires hold service infrastructure

See: docs/HOLD_AS_CA.md for complete architecture and security considerations

Integration Strategy Decision Matrix#

Choose the right integration approach:

Use Case Recommended Approach Priority
Kubernetes admission control Ratify plugin or Gatekeeper provider HIGH
CI/CD verification atcr-verify CLI or GitHub Actions HIGH
Docker/containerd Containerd bindir plugin MEDIUM
Policy enforcement OPA Gatekeeper + external provider HIGH
Manual verification Shell scripts or atcr-verify CLI LOW
Enterprise PKI compliance Hold-as-CA (X.509 certificates) OPTIONAL
Legacy tool support Hold-as-CA or external bridge service OPTIONAL

See: docs/INTEGRATION_STRATEGY.md for complete integration planning guide including:

  • Architecture layers and data flow
  • Tool compatibility matrix (16+ tools)
  • Implementation roadmap (4 phases)
  • When to use each approach

Trust Policies#

Define which signatures you trust:

# trust-policy.yaml
version: 1.0

trustedDIDs:
  did:plc:alice123:
    name: "Alice (DevOps Lead)"
    validFrom: "2024-01-01T00:00:00Z"
    expiresAt: null

  did:plc:bob456:
    name: "Bob (Security Team)"
    validFrom: "2024-06-01T00:00:00Z"
    expiresAt: "2025-12-31T23:59:59Z"

policies:
  - name: production-images
    scope: "atcr.io/*/prod-*"
    require:
      signature: true
      trustedDIDs:
        - did:plc:alice123
        - did:plc:bob456
      minSignatures: 1
    action: enforce  # reject if policy fails

  - name: dev-images
    scope: "atcr.io/*/dev-*"
    require:
      signature: false
    action: audit  # log but don't reject

Use with:

  • atcr-verify CLI: atcr-verify IMAGE --policy trust-policy.yaml
  • Kubernetes webhooks: ConfigMap with policy
  • CI/CD pipelines: Fail build if policy not met

Security Considerations#

What ATProto Signatures Prove#

Identity: Manifest signed by specific DID (e.g., did:plc:alice123) ✅ Integrity: Manifest content hasn't been tampered with ✅ Timestamp: When the manifest was signed ✅ Authenticity: Signature created with private key for that DID

What They Don't Prove#

Vulnerability-free: Signature doesn't mean image is safe ❌ Authorization: DID ownership doesn't imply permission to deploy ❌ Key security: Private key could be compromised ❌ PDS trustworthiness: Malicious PDS could create fake records

Trust Dependencies#

When verifying signatures, you're trusting:

  1. DID resolution (PLC directory, did:web) - public key is correct for DID
  2. PDS integrity - PDS serves correct records and doesn't forge signatures
  3. Cryptographic primitives - ECDSA K-256 remains secure
  4. Your trust policy - DIDs you've chosen to trust are legitimate

Best Practices#

1. Use Trust Policies Don't blindly trust all signatures - define which DIDs you trust:

trustedDIDs:
  - did:plc:your-org-team
  - did:plc:your-ci-system

2. Monitor Signature Coverage Track which images have signatures:

atcr-verify --check-coverage namespace/production

3. Enforce in Production Use Kubernetes admission control to block unsigned images:

# Ratify + Gatekeeper or custom webhook
enforceSignatures: true
failurePolicy: Fail

4. Verify in CI/CD Never deploy unsigned images:

# GitHub Actions
- name: Verify signature
  run: atcr-verify $IMAGE || exit 1

5. Plan for Compromised Keys

  • Rotate DID keys periodically
  • Monitor DID documents for unexpected key changes
  • Have incident response plan for key compromise

Implementation Status#

✅ Available Now#

  • ATProto signatures: All manifests automatically signed by PDS
  • ORAS artifacts: Signature metadata stored as OCI artifacts
  • OCI Referrers API: Discovery via standard OCI endpoints
  • Shell scripts: Manual verification examples
  • Documentation: Complete integration guides

🔄 In Development#

  • atcr-verify CLI: Standalone verification tool
  • Ratify plugin: Kubernetes integration
  • Gatekeeper provider: OPA policy enforcement
  • GitHub Actions: CI/CD integration

📋 Planned#

  • Containerd plugin: Runtime-level verification
  • Hold-as-CA: X.509 certificate generation (optional)
  • Web UI: Signature viewer in AppView
  • Offline bundles: Air-gapped verification

Comparison with Other Signing Solutions#

Feature ATCR (ATProto) Cosign (Sigstore) Notation (Notary v2)
Signing Automatic (PDS) Manual or keyless Manual
Keys K-256 (secp256k1) P-256 or RSA P-256, P-384, P-521
Trust DID-based OIDC + Fulcio CA X.509 PKI
Storage ATProto PDS OCI registry OCI registry
Centralization Decentralized Centralized (Fulcio) Configurable
Transparency Log ATProto firehose Rekor Configurable
Verification Custom tools/plugins Cosign CLI Notation CLI
Kubernetes Plugins (Ratify) Policy Controller Policy Controller

ATCR advantages:

  • ✅ Decentralized trust (no CA required)
  • ✅ Automatic signing (no extra tools)
  • ✅ DID-based identity (portable, self-sovereign)
  • ✅ Transparent via ATProto firehose

ATCR trade-offs:

  • ⚠️ Requires custom verification tools/plugins
  • ⚠️ K-256 not supported by Notation (needs Hold-as-CA)
  • ⚠️ Smaller ecosystem than Cosign/Notation

Why Not Use Cosign Directly?#

Question: Why not just integrate with Cosign's keyless signing (OIDC + Fulcio)?

Answer: ATProto and Cosign use incompatible authentication models:

Requirement Cosign Keyless ATProto
Identity protocol OIDC ATProto OAuth + DPoP
Token format JWT from OIDC provider DPoP-bound access token
CA Fulcio (Sigstore CA) None (DID-based PKI)
Infrastructure Fulcio + Rekor + TUF PDS + DID resolver

To make Cosign work, we'd need to:

  1. Deploy Fulcio (certificate authority)
  2. Deploy Rekor (transparency log)
  3. Deploy TUF (metadata distribution)
  4. Build OIDC provider bridge for ATProto OAuth
  5. Maintain all this infrastructure

Instead: We leverage ATProto's existing signatures and build lightweight plugins/tools for verification. This is simpler, more decentralized, and aligns with ATCR's design philosophy.

For tools that need X.509 certificates: See Hold-as-CA for an optional centralized approach.

Getting Started#

Verify Your First Image#

# 1. Check if image has ATProto signature
oras discover atcr.io/alice/myapp:latest \
  --artifact-type application/vnd.atproto.signature.v1+json

# 2. Pull signature metadata
oras pull atcr.io/alice/myapp@sha256:sig789...

# 3. Verify with shell script
./examples/verification/atcr-verify.sh atcr.io/alice/myapp:latest

# 4. Use atcr-verify CLI (when available)
atcr-verify atcr.io/alice/myapp:latest --policy trust-policy.yaml

Deploy Kubernetes Verification#

# 1. Choose an approach
# Option A: Ratify plugin (recommended)
# Option B: Gatekeeper external provider
# Option C: Custom admission webhook

# 2. Follow integration guide
# See docs/SIGNATURE_INTEGRATION.md for step-by-step

# 3. Enable for namespace
kubectl label namespace production atcr-verify=enabled

# 4. Test with sample pod
kubectl run test --image=atcr.io/alice/myapp:latest -n production

Integrate with CI/CD#

# GitHub Actions
- name: Verify signature
  run: |
    curl -LO https://github.com/atcr-io/atcr/releases/latest/download/atcr-verify
    chmod +x atcr-verify
    ./atcr-verify ${{ env.IMAGE }} --policy .atcr/trust-policy.yaml

# GitLab CI
verify_image:
  script:
    - wget https://github.com/atcr-io/atcr/releases/latest/download/atcr-verify
    - chmod +x atcr-verify
    - ./atcr-verify $IMAGE --policy .atcr/trust-policy.yaml

Documentation#

Core Documentation#

Examples#

External References#

Support#

For questions or issues:

Summary#

Key Points:

  1. Automatic signing: Every ATCR image is automatically signed via ATProto's native signature system
  2. No additional tools: Signing happens transparently when you push images
  3. Decentralized trust: DID-based signatures, no central CA required
  4. Standard discovery: ORAS artifacts and OCI Referrers API for signature metadata
  5. Custom verification: Use plugins, CLI tools, or shell scripts (not Cosign directly)
  6. Multiple integrations: Kubernetes (Ratify, Gatekeeper), CI/CD (atcr-verify), containerd
  7. Optional X.509: Hold-as-CA for enterprise PKI compliance (centralized)

Next Steps:

  1. Read examples/verification/README.md for practical examples
  2. Choose integration approach from INTEGRATION_STRATEGY.md
  3. Implement plugin or deploy CLI tool from SIGNATURE_INTEGRATION.md
  4. Define trust policy for your organization
  5. Deploy to test environment first, then production