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

Configure Feed

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

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