mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-26 12:14:17 +00:00
- diff view gains a Packages tab with added/removed/changed/unchanged package tables and purl-derived type/license/upstream links - captain records verified against the DID's atcr_hold service before caching (processor + batch backfill), preventing forged holds - fix empty-handle updates clobbering cached handles and colliding on the UNIQUE constraint - move fillPrevCIDs into repo.go; DirectRepoOperator is now canonical, repomgr kept as a test oracle - surface read-only crew status in hold selector - reconcile docs
565 lines
23 KiB
Markdown
565 lines
23 KiB
Markdown
# Image Signing with ATProto (Research / POC)
|
|
|
|
> STATUS: RESEARCH / DESIGN PROPOSAL. Last reviewed 2026-06-11.
|
|
>
|
|
> **Nothing in this document is implemented.** As of this review there is no
|
|
> signature artifact creation, no OCI Referrers API endpoint in the AppView, no
|
|
> `cmd/atcr-verify` binary, no `pkg/verify` package, no hold-as-CA code, no
|
|
> `io.atcr.hold.coSignManifest` / `io.atcr.hold.reSignManifest` XRPC endpoints,
|
|
> and no `HOLD_COSIGN_*` configuration. The CLI examples, install instructions,
|
|
> `oras`/`cosign` command transcripts, Kubernetes manifests, and version output
|
|
> shown below are **illustrative of a proposed design**, not working features.
|
|
>
|
|
> This file consolidates what were previously six separate docs
|
|
> (`IMAGE_SIGNING.md`, `ATPROTO_SIGNATURES.md`, `SIGNATURE_INTEGRATION.md`,
|
|
> `HOLD_AS_CA.md`, `ATCR_VERIFY_CLI.md`, `INTEGRATION_STRATEGY.md`) into a single
|
|
> research note. Several of the originals presented features as shipped
|
|
> ("Available Now", install commands, version strings); those claims have been
|
|
> removed or relabeled as proposals. See "Corrections from review" at the end for
|
|
> the specific factual fixes applied during consolidation.
|
|
|
|
## Table of contents
|
|
|
|
1. [What is real today](#1-what-is-real-today)
|
|
2. [Proposed design: ORAS signature artifacts + Referrers API](#2-proposed-design-oras-signature-artifacts--referrers-api)
|
|
3. [Alternative proposal: Hold-as-CA (X.509)](#3-alternative-proposal-hold-as-ca-x509)
|
|
4. [Proposed tool: atcr-verify CLI](#4-proposed-tool-atcr-verify-cli)
|
|
5. [Integration notes (hypothetical examples)](#5-integration-notes-hypothetical-examples)
|
|
6. [Trust model and security considerations](#6-trust-model-and-security-considerations)
|
|
7. [Comparison with other signing solutions](#7-comparison-with-other-signing-solutions)
|
|
8. [Proposed implementation roadmap](#8-proposed-implementation-roadmap)
|
|
9. [Corrections from review](#9-corrections-from-review)
|
|
|
|
---
|
|
|
|
## 1. What is real today
|
|
|
|
This section describes mechanisms that genuinely exist in the protocol layer
|
|
ATCR builds on. It is the accurate core extracted from the original
|
|
`ATPROTO_SIGNATURES.md`.
|
|
|
|
### Manifests are signed by the user's PDS
|
|
|
|
When a manifest is stored in ATCR, it lands in the user's PDS as an
|
|
`io.atcr.manifest` record. ATProto's repository model means that record is part
|
|
of a commit that the PDS signs:
|
|
|
|
1. AppView stores the manifest as an `io.atcr.manifest` record in the user's PDS.
|
|
2. The PDS creates a repository commit containing the new record in its Merkle
|
|
Search Tree (MST).
|
|
3. The PDS signs that commit with the repository's signing key (ECDSA over the
|
|
secp256k1 / K-256 curve).
|
|
4. The signature lives in the commit object and becomes part of the user's
|
|
verifiable repository chain.
|
|
|
|
This is a real, existing property of ATProto repositories. It is **not** an
|
|
ATCR feature that needed to be built; it is inherited from the PDS.
|
|
|
|
### Signature algorithm
|
|
|
|
- Curve: secp256k1 (K-256), the same curve used by Bitcoin/Ethereum.
|
|
- Hash: SHA-256.
|
|
- Signing: serialize commit data as DAG-CBOR, hash with SHA-256, sign with the
|
|
K-256 private key. ATProto uses "low-S" signatures (per BIP-0062).
|
|
|
|
Note that ATProto repository keys are K-256. This is distinct from the **P-256
|
|
(ES256)** key the AppView uses for OAuth, and distinct from the P-256 curve that
|
|
Notation expects (see the Hold-as-CA section for why that matters).
|
|
|
|
### Public key distribution
|
|
|
|
Public keys are published in DID documents and obtained through DID resolution:
|
|
|
|
```
|
|
did:plc:alice123
|
|
-> Query PLC directory: https://plc.directory/did:plc:alice123
|
|
-> DID document verificationMethod (id "#atproto")
|
|
publicKeyMultibase: zQ3sh... (multibase base58btc, multicodec 0xE701 for K-256)
|
|
```
|
|
|
|
For `did:web` identities the document is served from the web origin instead of
|
|
the PLC directory.
|
|
|
|
### What can be verified today, and how
|
|
|
|
Given an image and the manifest record behind it, a verifier can prove the
|
|
manifest came from a specific DID and was not tampered with, using only existing
|
|
ATProto endpoints:
|
|
|
|
1. Resolve the image reference to a manifest digest.
|
|
2. Find the corresponding `io.atcr.manifest` record in the user's PDS
|
|
(`com.atproto.repo.getRecord` / `listRecords`).
|
|
3. Fetch the repository (`com.atproto.sync.getRepo`) and extract the signed
|
|
commit covering that record.
|
|
4. Resolve the DID to its public key from the DID document.
|
|
5. Verify the commit signature against the public key using a K-256 verifier
|
|
(e.g. `github.com/bluesky-social/indigo/atproto/crypto`).
|
|
|
|
Sketch of the verification core (existing libraries, no new ATCR code):
|
|
|
|
```go
|
|
import "github.com/bluesky-social/indigo/atproto/crypto"
|
|
|
|
// pubKey parsed from the DID document's publicKeyMultibase (K-256)
|
|
// commit parsed from the CAR returned by com.atproto.sync.getRepo
|
|
bytesToVerify := commit.Unsigned().BytesForSigning()
|
|
err := pubKey.Verify(bytesToVerify, commit.Sig)
|
|
```
|
|
|
|
This is the foundation every proposal below builds on. None of the bridging,
|
|
discovery, or tooling that would make this convenient for OCI consumers exists
|
|
yet.
|
|
|
|
---
|
|
|
|
## 2. Proposed design: ORAS signature artifacts + Referrers API
|
|
|
|
> Proposal. Not implemented. There is no Referrers API endpoint in the AppView
|
|
> and no code that creates signature artifacts.
|
|
|
|
The OCI ecosystem does not understand ATProto records. The proposal here is to
|
|
publish a small **ORAS signature artifact** that points at the existing ATProto
|
|
signature, so that standard OCI tooling can at least *discover* it via the OCI
|
|
Referrers API, and a custom verifier can then follow the pointer to the real
|
|
ATProto signature.
|
|
|
|
### Proposed artifact format
|
|
|
|
The signature artifact would be an OCI image manifest with a `subject` field
|
|
referencing the image manifest, an `artifactType` of
|
|
`application/vnd.atproto.signature.v1+json`, and a single layer carrying the
|
|
metadata blob:
|
|
|
|
```json
|
|
{
|
|
"schemaVersion": 2,
|
|
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
|
"artifactType": "application/vnd.atproto.signature.v1+json",
|
|
"config": {
|
|
"mediaType": "application/vnd.oci.empty.v1+json",
|
|
"digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
|
|
"size": 2
|
|
},
|
|
"subject": {
|
|
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
|
"digest": "sha256:<image-manifest-digest>",
|
|
"size": 1234
|
|
},
|
|
"layers": [
|
|
{
|
|
"mediaType": "application/vnd.atproto.signature.v1+json",
|
|
"digest": "sha256:<metadata-blob-digest>",
|
|
"size": 512,
|
|
"annotations": { "org.opencontainers.image.title": "atproto-signature.json" }
|
|
}
|
|
],
|
|
"annotations": {
|
|
"io.atcr.atproto.did": "did:plc:alice123",
|
|
"io.atcr.atproto.pds": "https://bsky.social",
|
|
"io.atcr.atproto.recordUri": "at://did:plc:alice123/io.atcr.manifest/<rkey>",
|
|
"io.atcr.atproto.commitCid": "bafyreih8...",
|
|
"io.atcr.atproto.signedAt": "2025-10-31T12:34:56.789Z",
|
|
"io.atcr.atproto.keyId": "did:plc:alice123#atproto"
|
|
}
|
|
}
|
|
```
|
|
|
|
The metadata blob would carry enough to find and verify the underlying ATProto
|
|
signature:
|
|
|
|
```json
|
|
{
|
|
"$type": "io.atcr.atproto.signature",
|
|
"version": "1.0",
|
|
"subject": { "digest": "sha256:<image-manifest-digest>", "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/<rkey>",
|
|
"recordCid": "bafyreig7...",
|
|
"commitCid": "bafyreih8...",
|
|
"commitRev": "3jzfkjqwdwa2a",
|
|
"signedAt": "2025-10-31T12:34:56.789Z"
|
|
},
|
|
"signature": {
|
|
"algorithm": "ECDSA-K256-SHA256",
|
|
"keyId": "did:plc:alice123#atproto",
|
|
"publicKeyMultibase": "zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDdo1Ko4Z"
|
|
},
|
|
"verification": {
|
|
"method": "atproto-repo-commit",
|
|
"instructions": "Fetch repository commit from PDS and verify signature using public key from DID document"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Proposed discovery via the Referrers API
|
|
|
|
If the AppView implemented the OCI Referrers API, discovery would look like:
|
|
|
|
```
|
|
GET /v2/<owner>/<repo>/referrers/sha256:<image-manifest-digest>
|
|
?artifactType=application/vnd.atproto.signature.v1+json
|
|
```
|
|
|
|
returning an OCI image index listing matching signature artifacts. This endpoint
|
|
does **not** exist yet.
|
|
|
|
### Why standard tools cannot verify these
|
|
|
|
Even with discovery in place, standard OCI tools (`cosign verify`, `notation
|
|
verify`) could not *verify* an ATProto signature: they expect their own
|
|
signature formats and trust models. They would only be able to *list* the
|
|
artifact. Verification would require a custom tool (see the proposed
|
|
`atcr-verify` CLI) or a plugin.
|
|
|
|
### Proposed storage approach
|
|
|
|
The proposal is for the AppView, after storing a manifest, to read back the
|
|
commit CID/revision, build the metadata blob, and create the ORAS artifact
|
|
linked via `subject`. This is design intent only; the code path does not exist.
|
|
|
|
> Correction (do not confuse with how SBOMs/scans work today): the original docs
|
|
> claimed signature artifacts would follow "the same pattern as SBOMs" and that
|
|
> SBOMs are stored as ORAS artifacts. That is wrong. SBOMs and scan results are
|
|
> stored as `io.atcr.hold.scan` ATProto records in the hold's CAR store (see
|
|
> `pkg/hold/pds/scan.go`, `CreateScanRecord` -> `repomgr.UpsertRecord`), with the
|
|
> SBOM carried as a blob reference inside the record. There is no ORAS/Referrers
|
|
> machinery behind scans. If signature artifacts were ever built, they would be
|
|
> new infrastructure, not a reuse of the scan pipeline.
|
|
|
|
---
|
|
|
|
## 3. Alternative proposal: Hold-as-CA (X.509)
|
|
|
|
> Alternative proposal, separate from the ORAS-artifact design above. Not
|
|
> implemented. There is no CA code, no `io.atcr.hold.coSignManifest` or
|
|
> `io.atcr.hold.reSignManifest` endpoint, and no `HOLD_COSIGN_*` / `HOLD_CA_*`
|
|
> configuration in the hold.
|
|
|
|
This is a distinct, optional design for environments that require standard X.509
|
|
PKI (for example, tools that only speak Notation/Notary v2). It deliberately
|
|
trades away decentralization, so it is presented as an alternative rather than
|
|
the recommended path.
|
|
|
|
### The problem it addresses
|
|
|
|
- ATProto repository signatures use K-256.
|
|
- Notation supports only P-256/P-384/P-521.
|
|
- A K-256 signature cannot be converted to a P-256 one (different curves), so a
|
|
second signature with a P-256 key would be required.
|
|
|
|
### The proposed mechanism
|
|
|
|
A hold would act as a Certificate Authority:
|
|
|
|
1. User pushes an image; the manifest is signed by the PDS with K-256 (as today).
|
|
2. The AppView asks the hold to co-sign (proposed `io.atcr.hold.coSignManifest`).
|
|
3. The hold verifies the ATProto K-256 signature is valid for the user's DID.
|
|
4. The hold mints an ephemeral P-256 key pair and issues a short-lived X.509
|
|
certificate (subject `CN=<did>`, SAN `URI:<did>`) signed by the hold's CA key.
|
|
5. The hold signs the manifest digest with the P-256 key and wraps it in a
|
|
Notation JWS envelope (with the cert chain in the `x5c` header).
|
|
6. The signature is published as an ORAS artifact
|
|
(`artifactType: application/vnd.cncf.notary.signature`).
|
|
|
|
Proposed certificate chain:
|
|
|
|
```
|
|
Hold Root CA (self-signed, P-256, ~10y, CA=true pathLen=1)
|
|
-> User certificate (CN=<did>, SAN URI:<did>, P-256, ~24h, Code Signing)
|
|
-> Manifest signature
|
|
```
|
|
|
|
### What is signed, clarified
|
|
|
|
> Correction: the original `HOLD_AS_CA.md` was internally inconsistent about what
|
|
> the P-256 signature covers. The intended design is that the hold signs a hash
|
|
> derived from the **manifest content/digest**, producing a Notation-style
|
|
> detached signature whose payload is the manifest descriptor. The illustrative
|
|
> Go in the original that did `SHA256([]byte(req.ManifestDigest))` (hashing the
|
|
> digest *string*) was a sketch, not a spec. A real implementation would sign
|
|
> over the manifest bytes / OCI descriptor per the Notation signing spec, not the
|
|
> ASCII of the digest string.
|
|
|
|
### Proposed (non-existent) configuration
|
|
|
|
These environment variables are **proposed names only**; none are read by the
|
|
hold today:
|
|
|
|
```
|
|
HOLD_COSIGN_ENABLED=true
|
|
HOLD_CA_CERT_PATH=/var/lib/atcr/hold/ca-certificate.pem
|
|
HOLD_CA_KEY_PATH=/var/lib/atcr/hold/ca-private-key.pem
|
|
HOLD_CERT_VALIDITY_HOURS=24
|
|
HOLD_OCSP_ENABLED=true
|
|
HOLD_CRL_ENABLED=true
|
|
```
|
|
|
|
### Why this is the non-default proposal
|
|
|
|
| Aspect | Hold-as-CA | ORAS-artifact + custom verifier |
|
|
|---|---|---|
|
|
| Standard tool compatibility | Notation works unchanged | Requires a custom verifier/plugin |
|
|
| Decentralization | Centralized (hold is the CA) | Decentralized (DID-based) |
|
|
| ATProto alignment | Against the model | Native |
|
|
| Signature reuse | Must re-sign with P-256 | Reuses existing K-256 commit signature |
|
|
| Compromise blast radius | Hold key compromise affects all users | Metadata only |
|
|
| Operational overhead | High (CA, CRL/OCSP, trust distribution) | Low |
|
|
|
|
Threat highlights for the CA design: compromise of the hold's CA private key is
|
|
catastrophic (an attacker could mint certificates for any DID); a malicious hold
|
|
operator could issue certificates without genuinely verifying the ATProto
|
|
signature. Mitigations discussed in the original (HSM-backed CA key, short cert
|
|
lifetimes, transparency logging, cross-checking against the ATProto signature)
|
|
remain valid design considerations but are unbuilt.
|
|
|
|
---
|
|
|
|
## 4. Proposed tool: atcr-verify CLI
|
|
|
|
> Proposal / specification. Not implemented. There is no `cmd/atcr-verify`
|
|
> directory and no `pkg/verify` package. There are no release binaries, no
|
|
> container image, and no version output. The commands, flags, and transcripts
|
|
> below describe a tool that does not exist yet.
|
|
|
|
A standalone verifier is the keystone of the decentralized (non-CA) approach: it
|
|
would discover the signature artifact, follow it to the ATProto record, resolve
|
|
the DID, fetch the commit, and verify the K-256 signature, then apply a trust
|
|
policy.
|
|
|
|
### Proposed verification flow
|
|
|
|
1. Resolve the image reference to a manifest digest.
|
|
2. Query the Referrers API for `application/vnd.atproto.signature.v1+json`
|
|
artifacts and fetch the metadata blob.
|
|
3. Resolve the DID to a public key.
|
|
4. Query the PDS for the manifest record and its commit.
|
|
5. Verify the K-256 commit signature against the public key.
|
|
6. Evaluate a trust policy (trusted DIDs, max age, minimum signatures).
|
|
|
|
### Proposed surface (illustrative only)
|
|
|
|
```bash
|
|
# proposed, not real
|
|
atcr-verify atcr.io/alice/myapp:latest
|
|
atcr-verify atcr.io/alice/myapp:latest --policy trust-policy.yaml
|
|
atcr-verify atcr.io/alice/myapp:latest --output json
|
|
atcr-verify atcr.io/alice/myapp:latest --quiet # exit code only
|
|
```
|
|
|
|
Proposed flags: `--policy`, `--output {text,json,quiet}`, `--cache-dir`,
|
|
`--no-cache`, `--timeout`, `--verbose`. Proposed exit codes: `0` verified,
|
|
`1` failed, `2` bad args, `3` network error, `4` policy violation.
|
|
|
|
Proposed subcommands: `verify` (default), `export` (write a bundle), `trust`
|
|
(manage trusted DIDs), `version`.
|
|
|
|
### Proposed trust policy format
|
|
|
|
```yaml
|
|
version: 1.0
|
|
defaultAction: enforce # enforce | audit | allow
|
|
requireSignature: true
|
|
|
|
policies:
|
|
- name: production-images
|
|
scope: "atcr.io/*/prod-*"
|
|
require:
|
|
signature: true
|
|
trustedDIDs: [did:plc:devops-team, did:plc:security-team]
|
|
minSignatures: 1
|
|
maxAge: 2592000 # 30 days, seconds
|
|
action: enforce
|
|
- name: dev-images
|
|
scope: "atcr.io/*/dev-*"
|
|
require:
|
|
signature: false
|
|
action: audit
|
|
|
|
trustedDIDs:
|
|
did:plc:devops-team:
|
|
name: "DevOps Team"
|
|
validFrom: "2024-01-01T00:00:00Z"
|
|
expiresAt: null
|
|
```
|
|
|
|
### Offline verification is a proposal, not a current capability
|
|
|
|
> Correction: the original docs contradicted each other on offline verification.
|
|
> One described full `--offline --bundle` support as a feature; another listed
|
|
> ATProto offline verification as "Limited" with a footnote that it "can be
|
|
> improved by embedding signature bytes in the ORAS blob". The accurate
|
|
> statement: **offline verification does not exist.** It is *possible in
|
|
> principle* if the signature artifact embeds the commit bytes and the relevant
|
|
> DID document so a verifier need not reach the PLC directory or PDS at run time.
|
|
> Treat any `--offline`/`export bundle` mention as a future design idea.
|
|
|
|
### Proposed library usage
|
|
|
|
If built, the verifier package would import as `atcr.io/pkg/verify` (the module
|
|
is `atcr.io`, not `github.com/atcr-io/atcr`). The original docs used the wrong
|
|
module path throughout; see corrections.
|
|
|
|
---
|
|
|
|
## 5. Integration notes (hypothetical examples)
|
|
|
|
> Everything in this section is hypothetical. The `atcr-verify` binary,
|
|
> container image, Referrers API, and signature artifacts they assume do not
|
|
> exist. The examples are retained as design sketches for the integration
|
|
> surfaces that would matter if the foundation were built. The Ratify and
|
|
> Gatekeeper plugin skeletons referenced here live under `examples/plugins/`
|
|
> and are likewise unbuilt scaffolding.
|
|
|
|
### Tool reality check
|
|
|
|
Standard OCI tools could, *given the proposed artifacts*, discover but not verify
|
|
ATProto signatures:
|
|
|
|
| Tool | Could discover | Could fetch | Could verify |
|
|
|---|---|---|---|
|
|
| `oras discover` | yes | - | no |
|
|
| `oras pull` / `crane manifest` | - | yes | no |
|
|
| `cosign tree` | yes (as artifacts) | - | no |
|
|
| `cosign verify` / `notation verify` | - | - | no (different format) |
|
|
| proposed `atcr-verify` | yes | yes | yes |
|
|
|
|
### Kubernetes (proposed)
|
|
|
|
Several admission-control paths were sketched, all depending on the proposed
|
|
verifier:
|
|
|
|
- A validating admission webhook that runs verification per container image and
|
|
rejects pods on failure.
|
|
- A Ratify verifier plugin (`CanVerify` on
|
|
`application/vnd.atproto.signature.v1+json`, then resolve DID, fetch commit,
|
|
verify K-256, check trust policy). Skeleton under
|
|
`examples/plugins/ratify-verifier/`.
|
|
- An OPA Gatekeeper external data provider that calls a verification service and
|
|
returns `verified: true/false` for Rego policies. Skeleton under
|
|
`examples/plugins/gatekeeper-provider/`.
|
|
- A Containerd 2.0 bindir image-verifier that shells out to `atcr-verify` at pull
|
|
time.
|
|
|
|
### CI/CD (proposed)
|
|
|
|
GitHub Actions / GitLab CI snippets would install the (nonexistent) `atcr-verify`
|
|
binary or use a (nonexistent) `atcr.io/atcr/verify` image and fail the build on a
|
|
bad signature. They are placeholders until the CLI exists.
|
|
|
|
---
|
|
|
|
## 6. Trust model and security considerations
|
|
|
|
This section is accurate at the conceptual level and applies to any design built
|
|
on ATProto signatures.
|
|
|
|
### What an ATProto signature proves
|
|
|
|
- Authenticity: the manifest record was committed by the DID owner's PDS.
|
|
- Integrity: the manifest content has not been altered since signing
|
|
(content-addressed; tampering changes the CID).
|
|
- Timestamp: when the commit was made.
|
|
|
|
### What it does not prove
|
|
|
|
- That the image is free of vulnerabilities (that is what scanning is for).
|
|
- That the DID owner is *authorized* to deploy anywhere.
|
|
- That the signing key was not compromised.
|
|
- That the PDS is honest about anything other than the signed bytes (it cannot
|
|
forge a signature without the private key, but availability and record
|
|
selection still depend on it).
|
|
|
|
### Trust dependencies
|
|
|
|
1. DID resolution returns the correct public key for the DID.
|
|
2. The PDS is reachable to fetch the commit (unless offline verification is
|
|
built per the proposal in section 4).
|
|
3. K-256 remains secure.
|
|
4. The verifier's trust policy lists only legitimately trusted DIDs.
|
|
|
|
### Threat sketches
|
|
|
|
- MITM on PDS queries: defeated by signature verification (attacker cannot forge
|
|
the signature) plus TLS.
|
|
- Compromised/malicious PDS serving fake manifests: signature verification fails.
|
|
- Key compromise: same posture as any PKI; rotate keys via DID document updates.
|
|
- Replay/rollback: check that the commit is in the current repository DAG and
|
|
inspect timestamps.
|
|
- DID takeover via rotation keys: serious but requires compromising rotation
|
|
keys, which is harder than the signing key.
|
|
|
|
---
|
|
|
|
## 7. Comparison with other signing solutions
|
|
|
|
| Feature | ATCR (ATProto) | Cosign (Sigstore) | Notation (Notary v2) |
|
|
|---|---|---|---|
|
|
| Signing | Inherent (PDS commit) | 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 of signature | ATProto commit (today); ORAS artifact (proposed) | OCI registry | OCI registry |
|
|
| Centralization | Decentralized | Centralized (Fulcio) | Configurable |
|
|
| Transparency log | ATProto firehose | Rekor | Configurable |
|
|
| Offline verification | Not built (possible per proposal) | No | Yes |
|
|
|
|
### Why not just use Cosign keyless?
|
|
|
|
ATProto and Cosign keyless use incompatible identity models. Cosign keyless
|
|
needs OIDC + Fulcio + Rekor + TUF; ATProto uses DPoP-bound OAuth and DID-based
|
|
PKI with no CA. Bridging would mean operating Fulcio/Rekor/TUF and an OIDC bridge
|
|
for ATProto OAuth. The ATProto-native approach reuses the existing commit
|
|
signature instead. (For tools that strictly require X.509, the Hold-as-CA
|
|
alternative in section 3 exists as a deliberate, centralized fallback.)
|
|
|
|
---
|
|
|
|
## 8. Proposed implementation roadmap
|
|
|
|
> Aspirational ordering, not a commitment. Effort estimates are from the
|
|
> original design docs and are unvalidated.
|
|
|
|
1. **Foundation**: build the `atcr-verify` CLI (K-256 commit verification, trust
|
|
policy, output formats) and the AppView OCI Referrers API + signature-artifact
|
|
creation. Without these two, nothing else is testable.
|
|
2. **Kubernetes**: OPA Gatekeeper external provider and Ratify verifier plugin.
|
|
3. **Runtime**: Containerd 2.0 bindir verifier.
|
|
4. **Optional/enterprise**: Hold-as-CA (only if X.509 compliance is actually
|
|
demanded), plus advanced features (transparency log, multi-signature,
|
|
offline bundles).
|
|
|
|
---
|
|
|
|
## 9. Corrections from review
|
|
|
|
The following factual problems in the original six docs were fixed during
|
|
consolidation:
|
|
|
|
- **Module path.** Originals used `github.com/atcr-io/atcr` for imports, `go
|
|
install`, `git clone`, and release URLs. The actual Go module is `atcr.io`
|
|
(see `go.mod`). The GitHub-style paths and release-download instructions were
|
|
removed or relabeled as hypothetical.
|
|
- **SBOM storage.** Originals claimed SBOMs are stored as ORAS artifacts and that
|
|
signature artifacts would "follow the same pattern as SBOMs." False. SBOMs and
|
|
scan results are `io.atcr.hold.scan` ATProto records in the hold's CAR store
|
|
(`pkg/hold/pds/scan.go`), with the SBOM as a blob reference inside the record.
|
|
There is no ORAS/Referrers pipeline for scans.
|
|
- **"Available Now" claims.** `IMAGE_SIGNING.md` listed ATProto signature
|
|
artifacts, ORAS artifacts, and the OCI Referrers API under "Available Now."
|
|
None of these exist. All such claims were removed; the only thing real today is
|
|
the PDS commit signature (section 1).
|
|
- **Fabricated CLI artifacts.** `ATCR_VERIFY_CLI.md` shipped install commands,
|
|
`docker pull` instructions, and a `version` output (`atcr-verify version
|
|
1.0.0`, a fake commit hash and build date). Removed; the CLI is a spec.
|
|
- **Offline verification contradiction.** Resolved: offline verification is a
|
|
proposal, not a current capability (section 4).
|
|
- **What the CA signs.** The Hold-as-CA Go sketch hashed the digest *string*; the
|
|
intended design signs over the manifest descriptor/bytes per Notation. Noted in
|
|
section 3.
|
|
- **Cross-references.** Links that pointed at the now-deleted sibling docs were
|
|
consolidated into this single file. External example scaffolding
|
|
(`examples/verification/`, `examples/plugins/`) still exists but is unbuilt;
|
|
its READMEs were updated to point here.
|