A container registry that uses the AT Protocol for manifest storage and S3 for blob storage.
0
fork

Configure Feed

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

at refactor 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