A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io
docker container atproto go
at loom 501 lines 17 kB view raw view rendered
1# ATProto Signatures for Container Images 2 3## Overview 4 5ATCR container images are **already cryptographically signed** through ATProto's repository commit system. Every manifest stored in a user's PDS is signed with the user's ATProto signing key, providing cryptographic proof of authorship and integrity. 6 7This document explains: 8- How ATProto signing works 9- Why additional signing tools aren't needed 10- How to bridge ATProto signatures to the OCI/ORAS ecosystem 11- Trust model and security considerations 12 13## Key Insight: Manifests Are Already Signed 14 15When you push an image to ATCR: 16 17```bash 18docker push atcr.io/alice/myapp:latest 19``` 20 21The following happens: 22 231. **AppView stores manifest** as an `io.atcr.manifest` record in alice's PDS 242. **PDS creates repository commit** containing the manifest record 253. **PDS signs the commit** with alice's ATProto signing key (ECDSA K-256) 264. **Signature is stored** in the repository commit object 27 28**Result:** The manifest is cryptographically signed with alice's private key, and anyone can verify it using alice's public key from her DID document. 29 30## ATProto Signing Mechanism 31 32### Repository Commit Signing 33 34ATProto uses a Merkle Search Tree (MST) to store records, and every modification creates a signed commit: 35 36``` 37┌─────────────────────────────────────────────┐ 38│ Repository Commit │ 39├─────────────────────────────────────────────┤ 40│ DID: did:plc:alice123 │ 41│ Version: 3jzfkjqwdwa2a │ 42│ Previous: bafyreig7... (parent commit) │ 43│ Data CID: bafyreih8... (MST root) │ 44│ ┌───────────────────────────────────────┐ │ 45│ │ Signature (ECDSA K-256 + SHA-256) │ │ 46│ │ Signed with: alice's private key │ │ 47│ │ Value: 0x3045022100... (DER format) │ │ 48│ └───────────────────────────────────────┘ │ 49└─────────────────────────────────────────────┘ 50 51 52 ┌─────────────────────┐ 53 │ Merkle Search Tree │ 54 │ (contains records) │ 55 └─────────────────────┘ 56 57 58 ┌────────────────────────────┐ 59 │ io.atcr.manifest record │ 60 │ Repository: myapp │ 61 │ Digest: sha256:abc123... │ 62 │ Layers: [...] │ 63 └────────────────────────────┘ 64``` 65 66### Signature Algorithm 67 68**Algorithm:** ECDSA with K-256 (secp256k1) curve + SHA-256 hash 69- **Curve:** secp256k1 (same as Bitcoin, Ethereum) 70- **Hash:** SHA-256 71- **Format:** DER-encoded signature bytes 72- **Variant:** "low-S" signatures (per BIP-0062) 73 74**Signing process:** 751. Serialize commit data as DAG-CBOR 762. Hash with SHA-256 773. Sign hash with ECDSA K-256 private key 784. Store signature in commit object 79 80### Public Key Distribution 81 82Public keys are distributed via DID documents, accessible through DID resolution: 83 84**DID Resolution Flow:** 85``` 86did:plc:alice123 87 88Query PLC directory: https://plc.directory/did:plc:alice123 89 90DID Document: 91{ 92 "@context": ["https://www.w3.org/ns/did/v1"], 93 "id": "did:plc:alice123", 94 "verificationMethod": [{ 95 "id": "did:plc:alice123#atproto", 96 "type": "Multikey", 97 "controller": "did:plc:alice123", 98 "publicKeyMultibase": "zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDdo1Ko4Z" 99 }], 100 "service": [{ 101 "id": "#atproto_pds", 102 "type": "AtprotoPersonalDataServer", 103 "serviceEndpoint": "https://bsky.social" 104 }] 105} 106``` 107 108**Public key format:** 109- **Encoding:** Multibase (base58btc with `z` prefix) 110- **Codec:** Multicodec `0xE701` for K-256 keys 111- **Example:** `zQ3sh...` decodes to 33-byte compressed public key 112 113## Verification Process 114 115To verify a manifest's signature: 116 117### Step 1: Resolve Image to Manifest Digest 118 119```bash 120# Get manifest digest 121DIGEST=$(crane digest atcr.io/alice/myapp:latest) 122# Result: sha256:abc123... 123``` 124 125### Step 2: Fetch Manifest Record from PDS 126 127```bash 128# Extract repository name from image reference 129REPO="myapp" 130 131# Query PDS for manifest record 132curl "https://bsky.social/xrpc/com.atproto.repo.listRecords?\ 133 repo=did:plc:alice123&\ 134 collection=io.atcr.manifest&\ 135 limit=100" | jq -r '.records[] | select(.value.digest == "sha256:abc123...")' 136``` 137 138Response includes: 139```json 140{ 141 "uri": "at://did:plc:alice123/io.atcr.manifest/abc123", 142 "cid": "bafyreig7...", 143 "value": { 144 "$type": "io.atcr.manifest", 145 "repository": "myapp", 146 "digest": "sha256:abc123...", 147 ... 148 } 149} 150``` 151 152### Step 3: Fetch Repository Commit 153 154```bash 155# Get current repository state 156curl "https://bsky.social/xrpc/com.atproto.sync.getRepo?\ 157 did=did:plc:alice123" --output repo.car 158 159# Extract commit from CAR file (requires ATProto tools) 160# Commit includes signature over repository state 161``` 162 163### Step 4: Resolve DID to Public Key 164 165```bash 166# Resolve DID document 167curl "https://plc.directory/did:plc:alice123" | jq -r '.verificationMethod[0].publicKeyMultibase' 168# Result: zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDdo1Ko4Z 169``` 170 171### Step 5: Verify Signature 172 173```go 174// Pseudocode for verification 175import "github.com/bluesky-social/indigo/atproto/crypto" 176 177// 1. Parse commit 178commit := parseCommitFromCAR(repoCAR) 179 180// 2. Extract signature bytes 181signature := commit.Sig 182 183// 3. Get bytes that were signed 184bytesToVerify := commit.Unsigned().BytesForSigning() 185 186// 4. Decode public key from multibase 187pubKey := decodeMultibasePublicKey(publicKeyMultibase) 188 189// 5. Verify ECDSA signature 190valid := crypto.VerifySignature(pubKey, bytesToVerify, signature) 191``` 192 193### Step 6: Verify Manifest Integrity 194 195```bash 196# Verify the manifest record's CID matches the content 197# CID is content-addressed, so tampering changes the CID 198``` 199 200## Bridging to OCI/ORAS Ecosystem 201 202While ATProto signatures are cryptographically sound, the OCI ecosystem doesn't understand ATProto records. To make signatures discoverable, we create **ORAS signature artifacts** that reference the ATProto signature. 203 204### ORAS Signature Artifact Format 205 206```json 207{ 208 "schemaVersion": 2, 209 "mediaType": "application/vnd.oci.image.manifest.v1+json", 210 "artifactType": "application/vnd.atproto.signature.v1+json", 211 "config": { 212 "mediaType": "application/vnd.oci.empty.v1+json", 213 "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a", 214 "size": 2 215 }, 216 "subject": { 217 "mediaType": "application/vnd.oci.image.manifest.v1+json", 218 "digest": "sha256:abc123...", 219 "size": 1234 220 }, 221 "layers": [ 222 { 223 "mediaType": "application/vnd.atproto.signature.v1+json", 224 "digest": "sha256:sig789...", 225 "size": 512, 226 "annotations": { 227 "org.opencontainers.image.title": "atproto-signature.json" 228 } 229 } 230 ], 231 "annotations": { 232 "io.atcr.atproto.did": "did:plc:alice123", 233 "io.atcr.atproto.pds": "https://bsky.social", 234 "io.atcr.atproto.recordUri": "at://did:plc:alice123/io.atcr.manifest/abc123", 235 "io.atcr.atproto.commitCid": "bafyreih8...", 236 "io.atcr.atproto.signedAt": "2025-10-31T12:34:56.789Z", 237 "io.atcr.atproto.keyId": "did:plc:alice123#atproto" 238 } 239} 240``` 241 242**Key elements:** 243 2441. **artifactType**: `application/vnd.atproto.signature.v1+json` - identifies this as an ATProto signature 2452. **subject**: Links to the image manifest being signed 2463. **layers**: Contains signature metadata blob 2474. **annotations**: Quick-access metadata for verification 248 249### Signature Metadata Blob 250 251The layer blob contains detailed verification information: 252 253```json 254{ 255 "$type": "io.atcr.atproto.signature", 256 "version": "1.0", 257 "subject": { 258 "digest": "sha256:abc123...", 259 "mediaType": "application/vnd.oci.image.manifest.v1+json" 260 }, 261 "atproto": { 262 "did": "did:plc:alice123", 263 "handle": "alice.bsky.social", 264 "pdsEndpoint": "https://bsky.social", 265 "recordUri": "at://did:plc:alice123/io.atcr.manifest/abc123", 266 "recordCid": "bafyreig7...", 267 "commitCid": "bafyreih8...", 268 "commitRev": "3jzfkjqwdwa2a", 269 "signedAt": "2025-10-31T12:34:56.789Z" 270 }, 271 "signature": { 272 "algorithm": "ECDSA-K256-SHA256", 273 "keyId": "did:plc:alice123#atproto", 274 "publicKeyMultibase": "zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDdo1Ko4Z" 275 }, 276 "verification": { 277 "method": "atproto-repo-commit", 278 "instructions": "Fetch repository commit from PDS and verify signature using public key from DID document" 279 } 280} 281``` 282 283### Discovery via Referrers API 284 285ORAS artifacts are discoverable via the OCI Referrers API: 286 287```bash 288# Query for signature artifacts 289curl "https://atcr.io/v2/alice/myapp/referrers/sha256:abc123?\ 290 artifactType=application/vnd.atproto.signature.v1+json" 291``` 292 293Response: 294```json 295{ 296 "schemaVersion": 2, 297 "mediaType": "application/vnd.oci.image.index.v1+json", 298 "manifests": [ 299 { 300 "mediaType": "application/vnd.oci.image.manifest.v1+json", 301 "digest": "sha256:sig789...", 302 "size": 1234, 303 "artifactType": "application/vnd.atproto.signature.v1+json", 304 "annotations": { 305 "io.atcr.atproto.did": "did:plc:alice123", 306 "io.atcr.atproto.signedAt": "2025-10-31T12:34:56.789Z" 307 } 308 } 309 ] 310} 311``` 312 313## Trust Model 314 315### What ATProto Signatures Prove 316 317**Authenticity**: Image was published by the DID owner 318**Integrity**: Image manifest hasn't been tampered with since signing 319**Non-repudiation**: Only the DID owner could have created this signature 320**Timestamp**: When the image was signed (commit timestamp) 321 322### What ATProto Signatures Don't Prove 323 324**Safety**: Image doesn't contain vulnerabilities (use vulnerability scanning) 325**DID trustworthiness**: Whether the DID owner is trustworthy (trust policy decision) 326**Key security**: Private key wasn't compromised (same limitation as all PKI) 327**PDS honesty**: PDS operator serves correct data (verify across multiple sources) 328 329### Trust Dependencies 330 3311. **DID Resolution**: Must correctly resolve DID to public key 332 - **Mitigation**: Use multiple resolvers, cache DID documents 333 3342. **PDS Availability**: Must query PDS to verify signatures 335 - **Mitigation**: Embed signature bytes in ORAS blob for offline verification 336 3373. **PDS Honesty**: PDS could serve fake/unsigned records 338 - **Mitigation**: Signature verification prevents this (can't forge signature) 339 3404. **Key Security**: User's private key could be compromised 341 - **Mitigation**: Key rotation via DID document updates, short-lived credentials 342 3435. **Algorithm Security**: ECDSA K-256 must remain secure 344 - **Status**: Well-studied, same as Bitcoin/Ethereum (widely trusted) 345 346### Comparison with Other Signing Systems 347 348| Aspect | ATProto Signatures | Cosign (Keyless) | Notary v2 | 349|--------|-------------------|------------------|-----------| 350| **Identity** | DID (decentralized) | OIDC (federated) | X.509 (PKI) | 351| **Key Management** | PDS signing keys | Ephemeral (Fulcio) | User-managed | 352| **Trust Anchor** | DID resolution | Fulcio CA + Rekor | Certificate chain | 353| **Transparency Log** | ATProto firehose | Rekor | Optional | 354| **Offline Verification** | Limited* | No | Yes | 355| **Decentralization** | High | Medium | Low | 356| **Complexity** | Low | High | Medium | 357 358*Can be improved by embedding signature bytes in ORAS blob 359 360### Security Considerations 361 362**Threat: Man-in-the-Middle Attack** 363- **Attack**: Intercept PDS queries, serve fake records 364- **Defense**: TLS for PDS communication, verify signature with public key from DID document 365- **Result**: Attacker can't forge signature without private key 366 367**Threat: Compromised PDS** 368- **Attack**: PDS operator serves unsigned/fake manifests 369- **Defense**: Signature verification fails (PDS can't sign without user's private key) 370- **Result**: Protected 371 372**Threat: Key Compromise** 373- **Attack**: Attacker steals user's ATProto signing key 374- **Defense**: Key rotation via DID document, revoke old keys 375- **Result**: Same as any PKI system (rotate keys quickly) 376 377**Threat: Replay Attack** 378- **Attack**: Replay old signed manifest to rollback to vulnerable version 379- **Defense**: Check commit timestamp, verify commit is in current repository DAG 380- **Result**: Protected (commits form immutable chain) 381 382**Threat: DID Takeover** 383- **Attack**: Attacker gains control of user's DID (rotation keys) 384- **Defense**: Monitor DID document changes, verify key history 385- **Result**: Serious but requires compromising rotation keys (harder than signing keys) 386 387## Implementation Strategy 388 389### Automatic Signature Artifact Creation 390 391When AppView stores a manifest in a user's PDS: 392 3931. **Store manifest record** (existing behavior) 3942. **Get commit response** with commit CID and revision 3953. **Create ORAS signature artifact**: 396 - Build metadata blob (JSON) 397 - Upload blob to hold storage 398 - Create ORAS manifest with subject = image manifest 399 - Store ORAS manifest (creates referrer link) 400 401### Storage Location 402 403Signature artifacts follow the same pattern as SBOMs: 404- **Metadata blobs**: Stored in hold's blob storage 405- **ORAS manifests**: Stored in hold's embedded PDS 406- **Discovery**: Via OCI Referrers API 407 408### Verification Tools 409 410**Option 1: Custom CLI tool (`atcr-verify`)** 411```bash 412atcr-verify atcr.io/alice/myapp:latest 413# → Queries referrers API 414# → Fetches signature metadata 415# → Resolves DID → public key 416# → Queries PDS for commit 417# → Verifies signature 418``` 419 420**Option 2: Shell script (curl + jq)** 421- See `docs/SIGNATURE_INTEGRATION.md` for examples 422 423**Option 3: Kubernetes admission controller** 424- Custom webhook that runs verification 425- Rejects pods with unsigned/invalid signatures 426 427## Benefits of ATProto Signatures 428 429### Compared to No Signing 430 431**Cryptographic proof** of image authorship 432**Tamper detection** for manifests 433**Identity binding** via DIDs 434**Audit trail** via ATProto repository history 435 436### Compared to Cosign/Notary 437 438**No additional signing required** (already signed by PDS) 439**Decentralized identity** (DIDs, not CAs) 440**Simpler infrastructure** (no Fulcio, no Rekor, no TUF) 441**Consistent with ATCR's architecture** (ATProto-native) 442**Lower operational overhead** (reuse existing PDS infrastructure) 443 444### Trade-offs 445 446⚠️ **Custom verification tools required** (standard tools won't work) 447⚠️ **Online verification preferred** (need to query PDS) 448⚠️ **Different trust model** (trust DIDs, not CAs) 449⚠️ **Ecosystem maturity** (newer approach, less tooling) 450 451## Future Enhancements 452 453### Short-term 454 4551. **Offline verification**: Embed signature bytes in ORAS blob 4562. **Multi-PDS verification**: Check signature across multiple PDSs 4573. **Key rotation support**: Handle historical key validity 458 459### Medium-term 460 4614. **Timestamp service**: RFC 3161 timestamps for long-term validity 4625. **Multi-signature**: Require N signatures from M DIDs 4636. **Transparency log integration**: Record verifications in public log 464 465### Long-term 466 4677. **IANA registration**: Register `application/vnd.atproto.signature.v1+json` 4688. **Standards proposal**: ATProto signature spec to ORAS/OCI 4699. **Cross-ecosystem bridges**: Convert to Cosign/Notary formats 470 471## Conclusion 472 473ATCR images are already cryptographically signed through ATProto's repository commit system. By creating ORAS signature artifacts that reference these existing signatures, we can: 474 475- ✅ Make signatures discoverable to OCI tooling 476- ✅ Maintain ATProto as the source of truth 477- ✅ Provide verification tools for users and clusters 478- ✅ Avoid duplicating signing infrastructure 479 480This approach leverages ATProto's strengths (decentralized identity, built-in signing) while bridging to the OCI ecosystem through standard ORAS artifacts. 481 482## References 483 484### ATProto Specifications 485- [ATProto Repository Specification](https://atproto.com/specs/repository) 486- [ATProto Data Model](https://atproto.com/specs/data-model) 487- [ATProto DID Methods](https://atproto.com/specs/did) 488 489### OCI/ORAS Specifications 490- [OCI Distribution Specification](https://github.com/opencontainers/distribution-spec) 491- [OCI Referrers API](https://github.com/opencontainers/distribution-spec/blob/main/spec.md#listing-referrers) 492- [ORAS Artifacts](https://oras.land/docs/) 493 494### Cryptography 495- [ECDSA (secp256k1)](https://en.bitcoin.it/wiki/Secp256k1) 496- [Multibase Encoding](https://github.com/multiformats/multibase) 497- [Multicodec](https://github.com/multiformats/multicodec) 498 499### Related Documentation 500- [SBOM Scanning](./SBOM_SCANNING.md) - Similar ORAS artifact pattern 501- [Signature Integration](./SIGNATURE_INTEGRATION.md) - Practical integration examples