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 main 756 lines 28 kB view raw view rendered
1# Hold-as-Certificate-Authority Architecture 2 3## ⚠️ Important Notice 4 5This document describes an **optional enterprise feature** for X.509 PKI compliance. The hold-as-CA approach introduces **centralization trade-offs** that contradict ATProto's decentralized philosophy. 6 7**Default Recommendation:** Use [plugin-based integration](./INTEGRATION_STRATEGY.md) instead. Only implement hold-as-CA if your organization has specific X.509 PKI compliance requirements. 8 9## Overview 10 11The hold-as-CA architecture allows ATCR to generate Notation/Notary v2-compatible signatures by having hold services act as Certificate Authorities that issue X.509 certificates for users. 12 13### The Problem 14 15- **ATProto signatures** use K-256 (secp256k1) elliptic curve 16- **Notation** only supports P-256, P-384, P-521 elliptic curves 17- **Cannot convert** K-256 signatures to P-256 (different cryptographic curves) 18- **Must re-sign** with P-256 keys for Notation compatibility 19 20### The Solution 21 22Hold services act as trusted Certificate Authorities (CAs): 23 241. User pushes image → Manifest signed by PDS with K-256 (ATProto) 252. Hold verifies ATProto signature is valid 263. Hold generates ephemeral P-256 key pair for user 274. Hold issues X.509 certificate to user's DID 285. Hold signs manifest with P-256 key 296. Hold creates Notation signature envelope (JWS format) 307. Stores both ATProto and Notation signatures 31 32**Result:** Images have two signatures: 33- **ATProto signature** (K-256) - Decentralized, DID-based 34- **Notation signature** (P-256) - Centralized, X.509 PKI 35 36## Architecture 37 38### Certificate Chain 39 40``` 41Hold Root CA Certificate (self-signed, P-256) 42 └── User Certificate (issued to DID, P-256) 43 └── Image Manifest Signature 44``` 45 46**Hold Root CA:** 47``` 48Subject: CN=ATCR Hold CA - did:web:hold01.atcr.io 49Issuer: Self (self-signed) 50Key Usage: Digital Signature, Certificate Sign 51Basic Constraints: CA=true, pathLen=1 52Algorithm: ECDSA P-256 53Validity: 10 years 54``` 55 56**User Certificate:** 57``` 58Subject: CN=did:plc:alice123 59SAN: URI:did:plc:alice123 60Issuer: Hold Root CA 61Key Usage: Digital Signature 62Extended Key Usage: Code Signing 63Algorithm: ECDSA P-256 64Validity: 24 hours (short-lived) 65``` 66 67### Push Flow 68 69``` 70┌──────────────────────────────────────────────────────┐ 71│ 1. User: docker push atcr.io/alice/myapp:latest │ 72└────────────────────┬─────────────────────────────────┘ 73 74┌──────────────────────────────────────────────────────┐ 75│ 2. AppView stores manifest in alice's PDS │ 76│ - PDS signs with K-256 (ATProto standard) │ 77│ - Signature stored in repository commit │ 78└────────────────────┬─────────────────────────────────┘ 79 80┌──────────────────────────────────────────────────────┐ 81│ 3. AppView requests hold to co-sign │ 82│ POST /xrpc/io.atcr.hold.coSignManifest │ 83│ { │ 84│ "userDid": "did:plc:alice123", │ 85│ "manifestDigest": "sha256:abc123...", │ 86│ "atprotoSignature": {...} │ 87│ } │ 88└────────────────────┬─────────────────────────────────┘ 89 90┌──────────────────────────────────────────────────────┐ 91│ 4. Hold verifies ATProto signature │ 92│ a. Resolve alice's DID → public key │ 93│ b. Fetch commit from alice's PDS │ 94│ c. Verify K-256 signature │ 95│ d. Ensure signature is valid │ 96│ │ 97│ If verification fails → REJECT │ 98└────────────────────┬─────────────────────────────────┘ 99100┌──────────────────────────────────────────────────────┐ 101│ 5. Hold generates ephemeral P-256 key pair │ 102│ privateKey := ecdsa.GenerateKey(elliptic.P256()) │ 103└────────────────────┬─────────────────────────────────┘ 104105┌──────────────────────────────────────────────────────┐ 106│ 6. Hold issues X.509 certificate │ 107│ Subject: CN=did:plc:alice123 │ 108│ SAN: URI:did:plc:alice123 │ 109│ Issuer: Hold CA │ 110│ NotBefore: now │ 111│ NotAfter: now + 24 hours │ 112│ KeyUsage: Digital Signature │ 113│ ExtKeyUsage: Code Signing │ 114│ │ 115│ Sign certificate with hold's CA private key │ 116└────────────────────┬─────────────────────────────────┘ 117118┌──────────────────────────────────────────────────────┐ 119│ 7. Hold signs manifest digest │ 120│ hash := SHA256(manifestBytes) │ 121│ signature := ECDSA_P256(hash, privateKey) │ 122└────────────────────┬─────────────────────────────────┘ 123124┌──────────────────────────────────────────────────────┐ 125│ 8. Hold creates Notation JWS envelope │ 126│ { │ 127│ "protected": {...}, │ 128│ "payload": "base64(manifestDigest)", │ 129│ "signature": "base64(p256Signature)", │ 130│ "header": { │ 131│ "x5c": [ │ 132│ "base64(userCert)", │ 133│ "base64(holdCACert)" │ 134│ ] │ 135│ } │ 136│ } │ 137└────────────────────┬─────────────────────────────────┘ 138139┌──────────────────────────────────────────────────────┐ 140│ 9. Hold returns signature to AppView │ 141└────────────────────┬─────────────────────────────────┘ 142143┌──────────────────────────────────────────────────────┐ 144│ 10. AppView stores Notation signature │ 145│ - Create ORAS artifact manifest │ 146│ - Upload JWS envelope as layer blob │ 147│ - Link to image via subject field │ 148│ - artifactType: application/vnd.cncf.notary... │ 149└──────────────────────────────────────────────────────┘ 150``` 151 152### Verification Flow 153 154``` 155┌──────────────────────────────────────────────────────┐ 156│ User: notation verify atcr.io/alice/myapp:latest │ 157└────────────────────┬─────────────────────────────────┘ 158159┌──────────────────────────────────────────────────────┐ 160│ 1. Notation queries Referrers API │ 161│ GET /v2/alice/myapp/referrers/sha256:abc123 │ 162│ → Discovers Notation signature artifact │ 163└────────────────────┬─────────────────────────────────┘ 164165┌──────────────────────────────────────────────────────┐ 166│ 2. Notation downloads JWS envelope │ 167│ - Parses JSON Web Signature │ 168│ - Extracts certificate chain from x5c header │ 169└────────────────────┬─────────────────────────────────┘ 170171┌──────────────────────────────────────────────────────┐ 172│ 3. Notation validates certificate chain │ 173│ a. User cert issued by Hold CA? ✓ │ 174│ b. Hold CA cert in trust store? ✓ │ 175│ c. Certificate not expired? ✓ │ 176│ d. Key usage correct? ✓ │ 177│ e. Subject matches policy? ✓ │ 178└────────────────────┬─────────────────────────────────┘ 179180┌──────────────────────────────────────────────────────┐ 181│ 4. Notation verifies signature │ 182│ a. Extract public key from user certificate │ 183│ b. Compute manifest hash: SHA256(manifest) │ 184│ c. Verify: ECDSA_P256(hash, sig, pubKey) ✓ │ 185└────────────────────┬─────────────────────────────────┘ 186187┌──────────────────────────────────────────────────────┐ 188│ 5. Success: Image verified ✓ │ 189│ Signed by: did:plc:alice123 (via Hold CA) │ 190└──────────────────────────────────────────────────────┘ 191``` 192 193## Implementation 194 195### Hold CA Certificate Generation 196 197```go 198// cmd/hold/main.go - CA initialization 199func (h *Hold) initializeCA(ctx context.Context) error { 200 caKeyPath := filepath.Join(h.config.DataDir, "ca-private-key.pem") 201 caCertPath := filepath.Join(h.config.DataDir, "ca-certificate.pem") 202 203 // Load existing CA or generate new one 204 if exists(caKeyPath) && exists(caCertPath) { 205 h.caKey = loadPrivateKey(caKeyPath) 206 h.caCert = loadCertificate(caCertPath) 207 return nil 208 } 209 210 // Generate P-256 key pair for CA 211 caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 212 if err != nil { 213 return fmt.Errorf("failed to generate CA key: %w", err) 214 } 215 216 // Create CA certificate template 217 serialNumber, _ := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) 218 219 template := &x509.Certificate{ 220 SerialNumber: serialNumber, 221 Subject: pkix.Name{ 222 CommonName: fmt.Sprintf("ATCR Hold CA - %s", h.DID), 223 }, 224 NotBefore: time.Now(), 225 NotAfter: time.Now().AddDate(10, 0, 0), // 10 years 226 227 KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, 228 BasicConstraintsValid: true, 229 IsCA: true, 230 MaxPathLen: 1, // Can only issue end-entity certificates 231 } 232 233 // Self-sign 234 certDER, err := x509.CreateCertificate( 235 rand.Reader, 236 template, 237 template, // Self-signed: issuer = subject 238 &caKey.PublicKey, 239 caKey, 240 ) 241 if err != nil { 242 return fmt.Errorf("failed to create CA certificate: %w", err) 243 } 244 245 caCert, _ := x509.ParseCertificate(certDER) 246 247 // Save to disk (0600 permissions) 248 savePrivateKey(caKeyPath, caKey) 249 saveCertificate(caCertPath, caCert) 250 251 h.caKey = caKey 252 h.caCert = caCert 253 254 log.Info("Generated new CA certificate", "did", h.DID, "expires", caCert.NotAfter) 255 return nil 256} 257``` 258 259### User Certificate Issuance 260 261```go 262// pkg/hold/cosign.go 263func (h *Hold) issueUserCertificate(userDID string) (*x509.Certificate, *ecdsa.PrivateKey, error) { 264 // Generate ephemeral P-256 key for user 265 userKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 266 if err != nil { 267 return nil, nil, fmt.Errorf("failed to generate user key: %w", err) 268 } 269 270 serialNumber, _ := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) 271 272 // Parse DID for SAN 273 sanURI, _ := url.Parse(userDID) 274 275 template := &x509.Certificate{ 276 SerialNumber: serialNumber, 277 Subject: pkix.Name{ 278 CommonName: userDID, 279 }, 280 URIs: []*url.URL{sanURI}, // Subject Alternative Name 281 282 NotBefore: time.Now(), 283 NotAfter: time.Now().Add(24 * time.Hour), // Short-lived: 24 hours 284 285 KeyUsage: x509.KeyUsageDigitalSignature, 286 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, 287 BasicConstraintsValid: true, 288 IsCA: false, 289 } 290 291 // Sign with hold's CA key 292 certDER, err := x509.CreateCertificate( 293 rand.Reader, 294 template, 295 h.caCert, // Issuer: Hold CA 296 &userKey.PublicKey, 297 h.caKey, // Sign with CA private key 298 ) 299 if err != nil { 300 return nil, nil, fmt.Errorf("failed to create user certificate: %w", err) 301 } 302 303 userCert, _ := x509.ParseCertificate(certDER) 304 305 return userCert, userKey, nil 306} 307``` 308 309### Co-Signing XRPC Endpoint 310 311```go 312// pkg/hold/oci/xrpc.go 313func (s *Server) handleCoSignManifest(ctx context.Context, req *CoSignRequest) (*CoSignResponse, error) { 314 // 1. Verify caller is authenticated 315 did, err := s.auth.VerifyToken(ctx, req.Token) 316 if err != nil { 317 return nil, fmt.Errorf("authentication failed: %w", err) 318 } 319 320 // 2. Verify ATProto signature 321 valid, err := s.verifyATProtoSignature(ctx, req.UserDID, req.ManifestDigest, req.ATProtoSignature) 322 if err != nil || !valid { 323 return nil, fmt.Errorf("ATProto signature verification failed: %w", err) 324 } 325 326 // 3. Issue certificate for user 327 userCert, userKey, err := s.hold.issueUserCertificate(req.UserDID) 328 if err != nil { 329 return nil, fmt.Errorf("failed to issue certificate: %w", err) 330 } 331 332 // 4. Sign manifest with user's key 333 manifestHash := sha256.Sum256([]byte(req.ManifestDigest)) 334 signature, err := ecdsa.SignASN1(rand.Reader, userKey, manifestHash[:]) 335 if err != nil { 336 return nil, fmt.Errorf("failed to sign manifest: %w", err) 337 } 338 339 // 5. Create JWS envelope 340 jws, err := s.createJWSEnvelope(signature, userCert, s.hold.caCert, req.ManifestDigest) 341 if err != nil { 342 return nil, fmt.Errorf("failed to create JWS: %w", err) 343 } 344 345 return &CoSignResponse{ 346 JWS: jws, 347 Certificate: encodeCertificate(userCert), 348 CACertificate: encodeCertificate(s.hold.caCert), 349 }, nil 350} 351``` 352 353## Trust Model 354 355### Centralization Analysis 356 357**ATProto Model (Decentralized):** 358- Each PDS is independent 359- User controls which PDS to use 360- Trust user's DID, not specific infrastructure 361- PDS compromise affects only that PDS's users 362- Multiple PDSs provide redundancy 363 364**Hold-as-CA Model (Centralized):** 365- Hold acts as single Certificate Authority 366- All users must trust hold's CA certificate 367- Hold compromise = attacker can issue certificates for ANY user 368- Hold becomes single point of failure 369- Users depend on hold operator honesty 370 371### What Hold Vouches For 372 373When hold issues a certificate, it attests: 374 375**"I verified that [DID] signed this manifest with ATProto"** 376- Hold validated ATProto signature 377- Hold confirmed signature matches user's DID 378- Hold checked signature at specific time 379 380**"This image is safe"** 381- Hold does NOT audit image contents 382- Certificate ≠ vulnerability scan 383- Signature ≠ security guarantee 384 385**"I control this DID"** 386- Hold does NOT control user's DID 387- DID ownership is independent 388- Hold cannot revoke DIDs 389 390### Threat Model 391 392**Scenario 1: Hold Private Key Compromise** 393 394**Attack:** 395- Attacker steals hold's CA private key 396- Can issue certificates for any DID 397- Can sign malicious images as any user 398 399**Impact:** 400- **CRITICAL** - All users affected 401- Attacker can impersonate any user 402- All signatures become untrustworthy 403 404**Detection:** 405- Certificate Transparency logs (if implemented) 406- Unusual certificate issuance patterns 407- Users report unexpected signatures 408 409**Mitigation:** 410- Store CA key in Hardware Security Module (HSM) 411- Strict access controls 412- Audit logging 413- Regular key rotation 414 415**Recovery:** 416- Revoke compromised CA certificate 417- Generate new CA certificate 418- Re-issue all active certificates 419- Notify all users 420- Update trust stores 421 422--- 423 424**Scenario 2: Malicious Hold Operator** 425 426**Attack:** 427- Hold operator issues certificates without verifying ATProto signatures 428- Hold operator signs malicious images 429- Hold operator backdates certificates 430 431**Impact:** 432- **HIGH** - Trust model broken 433- Users receive signed malicious images 434- Difficult to detect without ATProto cross-check 435 436**Detection:** 437- Compare Notation signature timestamp with ATProto commit time 438- Verify ATProto signature exists independently 439- Monitor hold's signing patterns 440 441**Mitigation:** 442- Audit trail linking certificates to ATProto signatures 443- Public transparency logs 444- Multi-signature requirements 445- Periodically verify ATProto signatures 446 447**Recovery:** 448- Identify malicious certificates 449- Revoke hold's CA trust 450- Switch to different hold 451- Re-verify all images 452 453--- 454 455**Scenario 3: Certificate Theft** 456 457**Attack:** 458- Attacker steals issued user certificate + private key 459- Uses it to sign malicious images 460 461**Impact:** 462- **LOW-MEDIUM** - Limited scope 463- Affects only specific user/image 464- Short validity period (24 hours) 465 466**Detection:** 467- Unexpected signature timestamps 468- Images signed from unknown locations 469 470**Mitigation:** 471- Short certificate validity (24 hours) 472- Ephemeral keys (not stored long-term) 473- Certificate revocation if detected 474 475**Recovery:** 476- Wait for certificate expiration (24 hours) 477- Revoke specific certificate 478- Investigate compromise source 479 480## Certificate Management 481 482### Expiration Strategy 483 484**Short-Lived Certificates (24 hours):** 485 486**Pros:** 487- ✅ Minimal revocation infrastructure needed 488- ✅ Compromise window is tiny 489- ✅ Automatic cleanup 490- ✅ Lower CRL/OCSP overhead 491 492**Cons:** 493- ❌ Old images become unverifiable quickly 494- ❌ Requires re-signing for historical verification 495- ❌ Storage: multiple signatures for same image 496 497**Solution: On-Demand Re-Signing** 498``` 499User pulls old image → Notation verification fails (expired cert) 500→ User requests re-signing: POST /xrpc/io.atcr.hold.reSignManifest 501→ Hold verifies ATProto signature still valid 502→ Hold issues new certificate (24 hours) 503→ Hold creates new Notation signature 504→ User can verify with fresh certificate 505``` 506 507### Revocation 508 509**Certificate Revocation List (CRL):** 510``` 511Hold publishes CRL at: https://hold01.atcr.io/ca.crl 512 513Notation configured to check CRL: 514{ 515 "trustPolicies": [{ 516 "name": "atcr-images", 517 "signatureVerification": { 518 "verificationLevel": "strict", 519 "override": { 520 "revocationValidation": "strict" 521 } 522 } 523 }] 524} 525``` 526 527**OCSP (Online Certificate Status Protocol):** 528- Hold runs OCSP responder: `https://hold01.atcr.io/ocsp` 529- Real-time certificate status checks 530- Lower overhead than CRL downloads 531 532**Revocation Triggers:** 533- Key compromise detected 534- Malicious signing detected 535- User request 536- DID ownership change 537 538### CA Key Rotation 539 540**Rotation Procedure:** 541 5421. **Generate new CA key pair** 5432. **Create new CA certificate** 5443. **Cross-sign old CA with new CA** (transition period) 5454. **Distribute new CA certificate** to all users 5465. **Begin issuing with new CA** for new signatures 5476. **Grace period** (30 days): Accept both old and new CA 5487. **Retire old CA** after grace period 549 550**Frequency:** Every 2-3 years (longer than short-lived certs) 551 552## Trust Store Distribution 553 554### Problem 555 556Users must add hold's CA certificate to their Notation trust store for verification to work. 557 558### Manual Distribution 559 560```bash 561# 1. Download hold's CA certificate 562curl https://hold01.atcr.io/ca.crt -o hold01-ca.crt 563 564# 2. Verify fingerprint (out-of-band) 565openssl x509 -in hold01-ca.crt -fingerprint -noout 566# Compare with published fingerprint 567 568# 3. Add to Notation trust store 569notation cert add --type ca --store atcr-holds hold01-ca.crt 570``` 571 572### Automated Distribution 573 574**ATCR CLI tool:** 575```bash 576atcr trust add hold01.atcr.io 577# → Fetches CA certificate 578# → Verifies via HTTPS + DNSSEC 579# → Adds to Notation trust store 580# → Configures trust policy 581 582atcr trust list 583# → Shows trusted holds with fingerprints 584``` 585 586### System-Wide Trust 587 588**For enterprise deployments:** 589 590**Debian/Ubuntu:** 591```bash 592# Install CA certificate system-wide 593cp hold01-ca.crt /usr/local/share/ca-certificates/atcr-hold01.crt 594update-ca-certificates 595``` 596 597**RHEL/CentOS:** 598```bash 599cp hold01-ca.crt /etc/pki/ca-trust/source/anchors/ 600update-ca-trust 601``` 602 603**Container images:** 604```dockerfile 605FROM ubuntu:22.04 606COPY hold01-ca.crt /usr/local/share/ca-certificates/ 607RUN update-ca-certificates 608``` 609 610## Configuration 611 612### Hold Service 613 614**Environment variables:** 615```bash 616# Enable co-signing feature 617HOLD_COSIGN_ENABLED=true 618 619# CA certificate and key paths 620HOLD_CA_CERT_PATH=/var/lib/atcr/hold/ca-certificate.pem 621HOLD_CA_KEY_PATH=/var/lib/atcr/hold/ca-private-key.pem 622 623# Certificate validity 624HOLD_CERT_VALIDITY_HOURS=24 625 626# OCSP responder 627HOLD_OCSP_ENABLED=true 628HOLD_OCSP_URL=https://hold01.atcr.io/ocsp 629 630# CRL distribution 631HOLD_CRL_ENABLED=true 632HOLD_CRL_URL=https://hold01.atcr.io/ca.crl 633``` 634 635### Notation Trust Policy 636 637```json 638{ 639 "version": "1.0", 640 "trustPolicies": [{ 641 "name": "atcr-images", 642 "registryScopes": ["atcr.io/*/*"], 643 "signatureVerification": { 644 "level": "strict", 645 "override": { 646 "revocationValidation": "strict" 647 } 648 }, 649 "trustStores": ["ca:atcr-holds"], 650 "trustedIdentities": [ 651 "x509.subject: CN=did:plc:*", 652 "x509.subject: CN=did:web:*" 653 ] 654 }] 655} 656``` 657 658## When to Use Hold-as-CA 659 660### ✅ Use When 661 662**Enterprise X.509 PKI Compliance:** 663- Organization requires standard X.509 certificates 664- Existing security policies mandate PKI 665- Audit requirements for certificate chains 666- Integration with existing CA infrastructure 667 668**Tool Compatibility:** 669- Must use standard Notation without plugins 670- Cannot deploy custom verification tools 671- Existing tooling expects X.509 signatures 672 673**Centralized Trust Acceptable:** 674- Organization already uses centralized trust model 675- Hold operator is internal/trusted team 676- Centralization risk is acceptable trade-off 677 678### ❌ Don't Use When 679 680**Default Deployment:** 681- Most users should use [plugin-based approach](./INTEGRATION_STRATEGY.md) 682- Plugins maintain decentralization 683- Plugins reuse existing ATProto signatures 684 685**Small Teams / Startups:** 686- Certificate management overhead too high 687- Don't need X.509 compliance 688- Prefer simpler architecture 689 690**Maximum Decentralization Required:** 691- Cannot accept hold as single trust point 692- Must maintain pure ATProto model 693- Centralization contradicts project goals 694 695## Comparison: Hold-as-CA vs. Plugins 696 697| Aspect | Hold-as-CA | Plugin Approach | 698|--------|------------|----------------| 699| **Standard compliance** | ✅ Full X.509/PKI | ⚠️ Custom verification | 700| **Tool compatibility** | ✅ Notation works unchanged | ❌ Requires plugin install | 701| **Decentralization** | ❌ Centralized (hold CA) | ✅ Decentralized (DIDs) | 702| **ATProto alignment** | ❌ Against philosophy | ✅ ATProto-native | 703| **Signature reuse** | ❌ Must re-sign (P-256) | ✅ Reuses ATProto (K-256) | 704| **Certificate mgmt** | 🔴 High overhead | 🟢 None | 705| **Trust distribution** | 🔴 Must distribute CA cert | 🟢 DID resolution | 706| **Hold compromise** | 🔴 All users affected | 🟢 Metadata only | 707| **Operational cost** | 🔴 High | 🟢 Low | 708| **Use case** | Enterprise PKI | General purpose | 709 710## Recommendations 711 712### Default Approach: Plugins 713 714For most deployments, use plugin-based verification: 715- **Ratify plugin** for Kubernetes 716- **OPA Gatekeeper provider** for policy enforcement 717- **Containerd verifier** for runtime checks 718- **atcr-verify CLI** for general purpose 719 720See [Integration Strategy](./INTEGRATION_STRATEGY.md) for details. 721 722### Optional: Hold-as-CA for Enterprise 723 724Only implement hold-as-CA if you have specific requirements: 725- Enterprise X.509 PKI mandates 726- Cannot use plugins (restricted environments) 727- Accept centralization trade-off 728 729**Implement as opt-in feature:** 730```bash 731# Users explicitly enable co-signing 732docker push atcr.io/alice/myapp:latest --sign=notation 733 734# Or via environment variable 735export ATCR_ENABLE_COSIGN=true 736docker push atcr.io/alice/myapp:latest 737``` 738 739### Security Best Practices 740 741**If implementing hold-as-CA:** 742 7431. **Store CA key in HSM** - Never on filesystem 7442. **Audit all certificate issuance** - Log every cert 7453. **Public transparency log** - Publish all certificates 7464. **Short certificate validity** - 24 hours max 7475. **Monitor unusual patterns** - Alert on anomalies 7486. **Regular CA key rotation** - Every 2-3 years 7497. **Cross-check ATProto** - Verify both signatures match 7508. **Incident response plan** - Prepare for compromise 751 752## See Also 753 754- [ATProto Signatures](./ATPROTO_SIGNATURES.md) - How ATProto signing works 755- [Integration Strategy](./INTEGRATION_STRATEGY.md) - Overview of integration approaches 756- [Signature Integration](./SIGNATURE_INTEGRATION.md) - Tool-specific integration guides