mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-04-20 08:30:29 +00:00
757 lines
27 KiB
Markdown
757 lines
27 KiB
Markdown
# Hold-as-Certificate-Authority Architecture
|
|
|
|
## ⚠️ Important Notice
|
|
|
|
This 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.
|
|
|
|
**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.
|
|
|
|
## Overview
|
|
|
|
The 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.
|
|
|
|
### The Problem
|
|
|
|
- **ATProto signatures** use K-256 (secp256k1) elliptic curve
|
|
- **Notation** only supports P-256, P-384, P-521 elliptic curves
|
|
- **Cannot convert** K-256 signatures to P-256 (different cryptographic curves)
|
|
- **Must re-sign** with P-256 keys for Notation compatibility
|
|
|
|
### The Solution
|
|
|
|
Hold services act as trusted Certificate Authorities (CAs):
|
|
|
|
1. User pushes image → Manifest signed by PDS with K-256 (ATProto)
|
|
2. Hold verifies ATProto signature is valid
|
|
3. Hold generates ephemeral P-256 key pair for user
|
|
4. Hold issues X.509 certificate to user's DID
|
|
5. Hold signs manifest with P-256 key
|
|
6. Hold creates Notation signature envelope (JWS format)
|
|
7. Stores both ATProto and Notation signatures
|
|
|
|
**Result:** Images have two signatures:
|
|
- **ATProto signature** (K-256) - Decentralized, DID-based
|
|
- **Notation signature** (P-256) - Centralized, X.509 PKI
|
|
|
|
## Architecture
|
|
|
|
### Certificate Chain
|
|
|
|
```
|
|
Hold Root CA Certificate (self-signed, P-256)
|
|
└── User Certificate (issued to DID, P-256)
|
|
└── Image Manifest Signature
|
|
```
|
|
|
|
**Hold Root CA:**
|
|
```
|
|
Subject: CN=ATCR Hold CA - did:web:hold01.atcr.io
|
|
Issuer: Self (self-signed)
|
|
Key Usage: Digital Signature, Certificate Sign
|
|
Basic Constraints: CA=true, pathLen=1
|
|
Algorithm: ECDSA P-256
|
|
Validity: 10 years
|
|
```
|
|
|
|
**User Certificate:**
|
|
```
|
|
Subject: CN=did:plc:alice123
|
|
SAN: URI:did:plc:alice123
|
|
Issuer: Hold Root CA
|
|
Key Usage: Digital Signature
|
|
Extended Key Usage: Code Signing
|
|
Algorithm: ECDSA P-256
|
|
Validity: 24 hours (short-lived)
|
|
```
|
|
|
|
### Push Flow
|
|
|
|
```
|
|
┌──────────────────────────────────────────────────────┐
|
|
│ 1. User: docker push atcr.io/alice/myapp:latest │
|
|
└────────────────────┬─────────────────────────────────┘
|
|
↓
|
|
┌──────────────────────────────────────────────────────┐
|
|
│ 2. AppView stores manifest in alice's PDS │
|
|
│ - PDS signs with K-256 (ATProto standard) │
|
|
│ - Signature stored in repository commit │
|
|
└────────────────────┬─────────────────────────────────┘
|
|
↓
|
|
┌──────────────────────────────────────────────────────┐
|
|
│ 3. AppView requests hold to co-sign │
|
|
│ POST /xrpc/io.atcr.hold.coSignManifest │
|
|
│ { │
|
|
│ "userDid": "did:plc:alice123", │
|
|
│ "manifestDigest": "sha256:abc123...", │
|
|
│ "atprotoSignature": {...} │
|
|
│ } │
|
|
└────────────────────┬─────────────────────────────────┘
|
|
↓
|
|
┌──────────────────────────────────────────────────────┐
|
|
│ 4. Hold verifies ATProto signature │
|
|
│ a. Resolve alice's DID → public key │
|
|
│ b. Fetch commit from alice's PDS │
|
|
│ c. Verify K-256 signature │
|
|
│ d. Ensure signature is valid │
|
|
│ │
|
|
│ If verification fails → REJECT │
|
|
└────────────────────┬─────────────────────────────────┘
|
|
↓
|
|
┌──────────────────────────────────────────────────────┐
|
|
│ 5. Hold generates ephemeral P-256 key pair │
|
|
│ privateKey := ecdsa.GenerateKey(elliptic.P256()) │
|
|
└────────────────────┬─────────────────────────────────┘
|
|
↓
|
|
┌──────────────────────────────────────────────────────┐
|
|
│ 6. Hold issues X.509 certificate │
|
|
│ Subject: CN=did:plc:alice123 │
|
|
│ SAN: URI:did:plc:alice123 │
|
|
│ Issuer: Hold CA │
|
|
│ NotBefore: now │
|
|
│ NotAfter: now + 24 hours │
|
|
│ KeyUsage: Digital Signature │
|
|
│ ExtKeyUsage: Code Signing │
|
|
│ │
|
|
│ Sign certificate with hold's CA private key │
|
|
└────────────────────┬─────────────────────────────────┘
|
|
↓
|
|
┌──────────────────────────────────────────────────────┐
|
|
│ 7. Hold signs manifest digest │
|
|
│ hash := SHA256(manifestBytes) │
|
|
│ signature := ECDSA_P256(hash, privateKey) │
|
|
└────────────────────┬─────────────────────────────────┘
|
|
↓
|
|
┌──────────────────────────────────────────────────────┐
|
|
│ 8. Hold creates Notation JWS envelope │
|
|
│ { │
|
|
│ "protected": {...}, │
|
|
│ "payload": "base64(manifestDigest)", │
|
|
│ "signature": "base64(p256Signature)", │
|
|
│ "header": { │
|
|
│ "x5c": [ │
|
|
│ "base64(userCert)", │
|
|
│ "base64(holdCACert)" │
|
|
│ ] │
|
|
│ } │
|
|
│ } │
|
|
└────────────────────┬─────────────────────────────────┘
|
|
↓
|
|
┌──────────────────────────────────────────────────────┐
|
|
│ 9. Hold returns signature to AppView │
|
|
└────────────────────┬─────────────────────────────────┘
|
|
↓
|
|
┌──────────────────────────────────────────────────────┐
|
|
│ 10. AppView stores Notation signature │
|
|
│ - Create ORAS artifact manifest │
|
|
│ - Upload JWS envelope as layer blob │
|
|
│ - Link to image via subject field │
|
|
│ - artifactType: application/vnd.cncf.notary... │
|
|
└──────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Verification Flow
|
|
|
|
```
|
|
┌──────────────────────────────────────────────────────┐
|
|
│ User: notation verify atcr.io/alice/myapp:latest │
|
|
└────────────────────┬─────────────────────────────────┘
|
|
↓
|
|
┌──────────────────────────────────────────────────────┐
|
|
│ 1. Notation queries Referrers API │
|
|
│ GET /v2/alice/myapp/referrers/sha256:abc123 │
|
|
│ → Discovers Notation signature artifact │
|
|
└────────────────────┬─────────────────────────────────┘
|
|
↓
|
|
┌──────────────────────────────────────────────────────┐
|
|
│ 2. Notation downloads JWS envelope │
|
|
│ - Parses JSON Web Signature │
|
|
│ - Extracts certificate chain from x5c header │
|
|
└────────────────────┬─────────────────────────────────┘
|
|
↓
|
|
┌──────────────────────────────────────────────────────┐
|
|
│ 3. Notation validates certificate chain │
|
|
│ a. User cert issued by Hold CA? ✓ │
|
|
│ b. Hold CA cert in trust store? ✓ │
|
|
│ c. Certificate not expired? ✓ │
|
|
│ d. Key usage correct? ✓ │
|
|
│ e. Subject matches policy? ✓ │
|
|
└────────────────────┬─────────────────────────────────┘
|
|
↓
|
|
┌──────────────────────────────────────────────────────┐
|
|
│ 4. Notation verifies signature │
|
|
│ a. Extract public key from user certificate │
|
|
│ b. Compute manifest hash: SHA256(manifest) │
|
|
│ c. Verify: ECDSA_P256(hash, sig, pubKey) ✓ │
|
|
└────────────────────┬─────────────────────────────────┘
|
|
↓
|
|
┌──────────────────────────────────────────────────────┐
|
|
│ 5. Success: Image verified ✓ │
|
|
│ Signed by: did:plc:alice123 (via Hold CA) │
|
|
└──────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## Implementation
|
|
|
|
### Hold CA Certificate Generation
|
|
|
|
```go
|
|
// cmd/hold/main.go - CA initialization
|
|
func (h *Hold) initializeCA(ctx context.Context) error {
|
|
caKeyPath := filepath.Join(h.config.DataDir, "ca-private-key.pem")
|
|
caCertPath := filepath.Join(h.config.DataDir, "ca-certificate.pem")
|
|
|
|
// Load existing CA or generate new one
|
|
if exists(caKeyPath) && exists(caCertPath) {
|
|
h.caKey = loadPrivateKey(caKeyPath)
|
|
h.caCert = loadCertificate(caCertPath)
|
|
return nil
|
|
}
|
|
|
|
// Generate P-256 key pair for CA
|
|
caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to generate CA key: %w", err)
|
|
}
|
|
|
|
// Create CA certificate template
|
|
serialNumber, _ := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
|
|
|
template := &x509.Certificate{
|
|
SerialNumber: serialNumber,
|
|
Subject: pkix.Name{
|
|
CommonName: fmt.Sprintf("ATCR Hold CA - %s", h.DID),
|
|
},
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().AddDate(10, 0, 0), // 10 years
|
|
|
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
|
BasicConstraintsValid: true,
|
|
IsCA: true,
|
|
MaxPathLen: 1, // Can only issue end-entity certificates
|
|
}
|
|
|
|
// Self-sign
|
|
certDER, err := x509.CreateCertificate(
|
|
rand.Reader,
|
|
template,
|
|
template, // Self-signed: issuer = subject
|
|
&caKey.PublicKey,
|
|
caKey,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create CA certificate: %w", err)
|
|
}
|
|
|
|
caCert, _ := x509.ParseCertificate(certDER)
|
|
|
|
// Save to disk (0600 permissions)
|
|
savePrivateKey(caKeyPath, caKey)
|
|
saveCertificate(caCertPath, caCert)
|
|
|
|
h.caKey = caKey
|
|
h.caCert = caCert
|
|
|
|
log.Info("Generated new CA certificate", "did", h.DID, "expires", caCert.NotAfter)
|
|
return nil
|
|
}
|
|
```
|
|
|
|
### User Certificate Issuance
|
|
|
|
```go
|
|
// pkg/hold/cosign.go
|
|
func (h *Hold) issueUserCertificate(userDID string) (*x509.Certificate, *ecdsa.PrivateKey, error) {
|
|
// Generate ephemeral P-256 key for user
|
|
userKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to generate user key: %w", err)
|
|
}
|
|
|
|
serialNumber, _ := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
|
|
|
// Parse DID for SAN
|
|
sanURI, _ := url.Parse(userDID)
|
|
|
|
template := &x509.Certificate{
|
|
SerialNumber: serialNumber,
|
|
Subject: pkix.Name{
|
|
CommonName: userDID,
|
|
},
|
|
URIs: []*url.URL{sanURI}, // Subject Alternative Name
|
|
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().Add(24 * time.Hour), // Short-lived: 24 hours
|
|
|
|
KeyUsage: x509.KeyUsageDigitalSignature,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning},
|
|
BasicConstraintsValid: true,
|
|
IsCA: false,
|
|
}
|
|
|
|
// Sign with hold's CA key
|
|
certDER, err := x509.CreateCertificate(
|
|
rand.Reader,
|
|
template,
|
|
h.caCert, // Issuer: Hold CA
|
|
&userKey.PublicKey,
|
|
h.caKey, // Sign with CA private key
|
|
)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to create user certificate: %w", err)
|
|
}
|
|
|
|
userCert, _ := x509.ParseCertificate(certDER)
|
|
|
|
return userCert, userKey, nil
|
|
}
|
|
```
|
|
|
|
### Co-Signing XRPC Endpoint
|
|
|
|
```go
|
|
// pkg/hold/oci/xrpc.go
|
|
func (s *Server) handleCoSignManifest(ctx context.Context, req *CoSignRequest) (*CoSignResponse, error) {
|
|
// 1. Verify caller is authenticated
|
|
did, err := s.auth.VerifyToken(ctx, req.Token)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("authentication failed: %w", err)
|
|
}
|
|
|
|
// 2. Verify ATProto signature
|
|
valid, err := s.verifyATProtoSignature(ctx, req.UserDID, req.ManifestDigest, req.ATProtoSignature)
|
|
if err != nil || !valid {
|
|
return nil, fmt.Errorf("ATProto signature verification failed: %w", err)
|
|
}
|
|
|
|
// 3. Issue certificate for user
|
|
userCert, userKey, err := s.hold.issueUserCertificate(req.UserDID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to issue certificate: %w", err)
|
|
}
|
|
|
|
// 4. Sign manifest with user's key
|
|
manifestHash := sha256.Sum256([]byte(req.ManifestDigest))
|
|
signature, err := ecdsa.SignASN1(rand.Reader, userKey, manifestHash[:])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to sign manifest: %w", err)
|
|
}
|
|
|
|
// 5. Create JWS envelope
|
|
jws, err := s.createJWSEnvelope(signature, userCert, s.hold.caCert, req.ManifestDigest)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create JWS: %w", err)
|
|
}
|
|
|
|
return &CoSignResponse{
|
|
JWS: jws,
|
|
Certificate: encodeCertificate(userCert),
|
|
CACertificate: encodeCertificate(s.hold.caCert),
|
|
}, nil
|
|
}
|
|
```
|
|
|
|
## Trust Model
|
|
|
|
### Centralization Analysis
|
|
|
|
**ATProto Model (Decentralized):**
|
|
- Each PDS is independent
|
|
- User controls which PDS to use
|
|
- Trust user's DID, not specific infrastructure
|
|
- PDS compromise affects only that PDS's users
|
|
- Multiple PDSs provide redundancy
|
|
|
|
**Hold-as-CA Model (Centralized):**
|
|
- Hold acts as single Certificate Authority
|
|
- All users must trust hold's CA certificate
|
|
- Hold compromise = attacker can issue certificates for ANY user
|
|
- Hold becomes single point of failure
|
|
- Users depend on hold operator honesty
|
|
|
|
### What Hold Vouches For
|
|
|
|
When hold issues a certificate, it attests:
|
|
|
|
✅ **"I verified that [DID] signed this manifest with ATProto"**
|
|
- Hold validated ATProto signature
|
|
- Hold confirmed signature matches user's DID
|
|
- Hold checked signature at specific time
|
|
|
|
❌ **"This image is safe"**
|
|
- Hold does NOT audit image contents
|
|
- Certificate ≠ vulnerability scan
|
|
- Signature ≠ security guarantee
|
|
|
|
❌ **"I control this DID"**
|
|
- Hold does NOT control user's DID
|
|
- DID ownership is independent
|
|
- Hold cannot revoke DIDs
|
|
|
|
### Threat Model
|
|
|
|
**Scenario 1: Hold Private Key Compromise**
|
|
|
|
**Attack:**
|
|
- Attacker steals hold's CA private key
|
|
- Can issue certificates for any DID
|
|
- Can sign malicious images as any user
|
|
|
|
**Impact:**
|
|
- **CRITICAL** - All users affected
|
|
- Attacker can impersonate any user
|
|
- All signatures become untrustworthy
|
|
|
|
**Detection:**
|
|
- Certificate Transparency logs (if implemented)
|
|
- Unusual certificate issuance patterns
|
|
- Users report unexpected signatures
|
|
|
|
**Mitigation:**
|
|
- Store CA key in Hardware Security Module (HSM)
|
|
- Strict access controls
|
|
- Audit logging
|
|
- Regular key rotation
|
|
|
|
**Recovery:**
|
|
- Revoke compromised CA certificate
|
|
- Generate new CA certificate
|
|
- Re-issue all active certificates
|
|
- Notify all users
|
|
- Update trust stores
|
|
|
|
---
|
|
|
|
**Scenario 2: Malicious Hold Operator**
|
|
|
|
**Attack:**
|
|
- Hold operator issues certificates without verifying ATProto signatures
|
|
- Hold operator signs malicious images
|
|
- Hold operator backdates certificates
|
|
|
|
**Impact:**
|
|
- **HIGH** - Trust model broken
|
|
- Users receive signed malicious images
|
|
- Difficult to detect without ATProto cross-check
|
|
|
|
**Detection:**
|
|
- Compare Notation signature timestamp with ATProto commit time
|
|
- Verify ATProto signature exists independently
|
|
- Monitor hold's signing patterns
|
|
|
|
**Mitigation:**
|
|
- Audit trail linking certificates to ATProto signatures
|
|
- Public transparency logs
|
|
- Multi-signature requirements
|
|
- Periodically verify ATProto signatures
|
|
|
|
**Recovery:**
|
|
- Identify malicious certificates
|
|
- Revoke hold's CA trust
|
|
- Switch to different hold
|
|
- Re-verify all images
|
|
|
|
---
|
|
|
|
**Scenario 3: Certificate Theft**
|
|
|
|
**Attack:**
|
|
- Attacker steals issued user certificate + private key
|
|
- Uses it to sign malicious images
|
|
|
|
**Impact:**
|
|
- **LOW-MEDIUM** - Limited scope
|
|
- Affects only specific user/image
|
|
- Short validity period (24 hours)
|
|
|
|
**Detection:**
|
|
- Unexpected signature timestamps
|
|
- Images signed from unknown locations
|
|
|
|
**Mitigation:**
|
|
- Short certificate validity (24 hours)
|
|
- Ephemeral keys (not stored long-term)
|
|
- Certificate revocation if detected
|
|
|
|
**Recovery:**
|
|
- Wait for certificate expiration (24 hours)
|
|
- Revoke specific certificate
|
|
- Investigate compromise source
|
|
|
|
## Certificate Management
|
|
|
|
### Expiration Strategy
|
|
|
|
**Short-Lived Certificates (24 hours):**
|
|
|
|
**Pros:**
|
|
- ✅ Minimal revocation infrastructure needed
|
|
- ✅ Compromise window is tiny
|
|
- ✅ Automatic cleanup
|
|
- ✅ Lower CRL/OCSP overhead
|
|
|
|
**Cons:**
|
|
- ❌ Old images become unverifiable quickly
|
|
- ❌ Requires re-signing for historical verification
|
|
- ❌ Storage: multiple signatures for same image
|
|
|
|
**Solution: On-Demand Re-Signing**
|
|
```
|
|
User pulls old image → Notation verification fails (expired cert)
|
|
→ User requests re-signing: POST /xrpc/io.atcr.hold.reSignManifest
|
|
→ Hold verifies ATProto signature still valid
|
|
→ Hold issues new certificate (24 hours)
|
|
→ Hold creates new Notation signature
|
|
→ User can verify with fresh certificate
|
|
```
|
|
|
|
### Revocation
|
|
|
|
**Certificate Revocation List (CRL):**
|
|
```
|
|
Hold publishes CRL at: https://hold01.atcr.io/ca.crl
|
|
|
|
Notation configured to check CRL:
|
|
{
|
|
"trustPolicies": [{
|
|
"name": "atcr-images",
|
|
"signatureVerification": {
|
|
"verificationLevel": "strict",
|
|
"override": {
|
|
"revocationValidation": "strict"
|
|
}
|
|
}
|
|
}]
|
|
}
|
|
```
|
|
|
|
**OCSP (Online Certificate Status Protocol):**
|
|
- Hold runs OCSP responder: `https://hold01.atcr.io/ocsp`
|
|
- Real-time certificate status checks
|
|
- Lower overhead than CRL downloads
|
|
|
|
**Revocation Triggers:**
|
|
- Key compromise detected
|
|
- Malicious signing detected
|
|
- User request
|
|
- DID ownership change
|
|
|
|
### CA Key Rotation
|
|
|
|
**Rotation Procedure:**
|
|
|
|
1. **Generate new CA key pair**
|
|
2. **Create new CA certificate**
|
|
3. **Cross-sign old CA with new CA** (transition period)
|
|
4. **Distribute new CA certificate** to all users
|
|
5. **Begin issuing with new CA** for new signatures
|
|
6. **Grace period** (30 days): Accept both old and new CA
|
|
7. **Retire old CA** after grace period
|
|
|
|
**Frequency:** Every 2-3 years (longer than short-lived certs)
|
|
|
|
## Trust Store Distribution
|
|
|
|
### Problem
|
|
|
|
Users must add hold's CA certificate to their Notation trust store for verification to work.
|
|
|
|
### Manual Distribution
|
|
|
|
```bash
|
|
# 1. Download hold's CA certificate
|
|
curl https://hold01.atcr.io/ca.crt -o hold01-ca.crt
|
|
|
|
# 2. Verify fingerprint (out-of-band)
|
|
openssl x509 -in hold01-ca.crt -fingerprint -noout
|
|
# Compare with published fingerprint
|
|
|
|
# 3. Add to Notation trust store
|
|
notation cert add --type ca --store atcr-holds hold01-ca.crt
|
|
```
|
|
|
|
### Automated Distribution
|
|
|
|
**ATCR CLI tool:**
|
|
```bash
|
|
atcr trust add hold01.atcr.io
|
|
# → Fetches CA certificate
|
|
# → Verifies via HTTPS + DNSSEC
|
|
# → Adds to Notation trust store
|
|
# → Configures trust policy
|
|
|
|
atcr trust list
|
|
# → Shows trusted holds with fingerprints
|
|
```
|
|
|
|
### System-Wide Trust
|
|
|
|
**For enterprise deployments:**
|
|
|
|
**Debian/Ubuntu:**
|
|
```bash
|
|
# Install CA certificate system-wide
|
|
cp hold01-ca.crt /usr/local/share/ca-certificates/atcr-hold01.crt
|
|
update-ca-certificates
|
|
```
|
|
|
|
**RHEL/CentOS:**
|
|
```bash
|
|
cp hold01-ca.crt /etc/pki/ca-trust/source/anchors/
|
|
update-ca-trust
|
|
```
|
|
|
|
**Container images:**
|
|
```dockerfile
|
|
FROM ubuntu:22.04
|
|
COPY hold01-ca.crt /usr/local/share/ca-certificates/
|
|
RUN update-ca-certificates
|
|
```
|
|
|
|
## Configuration
|
|
|
|
### Hold Service
|
|
|
|
**Environment variables:**
|
|
```bash
|
|
# Enable co-signing feature
|
|
HOLD_COSIGN_ENABLED=true
|
|
|
|
# CA certificate and key paths
|
|
HOLD_CA_CERT_PATH=/var/lib/atcr/hold/ca-certificate.pem
|
|
HOLD_CA_KEY_PATH=/var/lib/atcr/hold/ca-private-key.pem
|
|
|
|
# Certificate validity
|
|
HOLD_CERT_VALIDITY_HOURS=24
|
|
|
|
# OCSP responder
|
|
HOLD_OCSP_ENABLED=true
|
|
HOLD_OCSP_URL=https://hold01.atcr.io/ocsp
|
|
|
|
# CRL distribution
|
|
HOLD_CRL_ENABLED=true
|
|
HOLD_CRL_URL=https://hold01.atcr.io/ca.crl
|
|
```
|
|
|
|
### Notation Trust Policy
|
|
|
|
```json
|
|
{
|
|
"version": "1.0",
|
|
"trustPolicies": [{
|
|
"name": "atcr-images",
|
|
"registryScopes": ["atcr.io/*/*"],
|
|
"signatureVerification": {
|
|
"level": "strict",
|
|
"override": {
|
|
"revocationValidation": "strict"
|
|
}
|
|
},
|
|
"trustStores": ["ca:atcr-holds"],
|
|
"trustedIdentities": [
|
|
"x509.subject: CN=did:plc:*",
|
|
"x509.subject: CN=did:web:*"
|
|
]
|
|
}]
|
|
}
|
|
```
|
|
|
|
## When to Use Hold-as-CA
|
|
|
|
### ✅ Use When
|
|
|
|
**Enterprise X.509 PKI Compliance:**
|
|
- Organization requires standard X.509 certificates
|
|
- Existing security policies mandate PKI
|
|
- Audit requirements for certificate chains
|
|
- Integration with existing CA infrastructure
|
|
|
|
**Tool Compatibility:**
|
|
- Must use standard Notation without plugins
|
|
- Cannot deploy custom verification tools
|
|
- Existing tooling expects X.509 signatures
|
|
|
|
**Centralized Trust Acceptable:**
|
|
- Organization already uses centralized trust model
|
|
- Hold operator is internal/trusted team
|
|
- Centralization risk is acceptable trade-off
|
|
|
|
### ❌ Don't Use When
|
|
|
|
**Default Deployment:**
|
|
- Most users should use [plugin-based approach](./INTEGRATION_STRATEGY.md)
|
|
- Plugins maintain decentralization
|
|
- Plugins reuse existing ATProto signatures
|
|
|
|
**Small Teams / Startups:**
|
|
- Certificate management overhead too high
|
|
- Don't need X.509 compliance
|
|
- Prefer simpler architecture
|
|
|
|
**Maximum Decentralization Required:**
|
|
- Cannot accept hold as single trust point
|
|
- Must maintain pure ATProto model
|
|
- Centralization contradicts project goals
|
|
|
|
## Comparison: Hold-as-CA vs. Plugins
|
|
|
|
| Aspect | Hold-as-CA | Plugin Approach |
|
|
|--------|------------|----------------|
|
|
| **Standard compliance** | ✅ Full X.509/PKI | ⚠️ Custom verification |
|
|
| **Tool compatibility** | ✅ Notation works unchanged | ❌ Requires plugin install |
|
|
| **Decentralization** | ❌ Centralized (hold CA) | ✅ Decentralized (DIDs) |
|
|
| **ATProto alignment** | ❌ Against philosophy | ✅ ATProto-native |
|
|
| **Signature reuse** | ❌ Must re-sign (P-256) | ✅ Reuses ATProto (K-256) |
|
|
| **Certificate mgmt** | 🔴 High overhead | 🟢 None |
|
|
| **Trust distribution** | 🔴 Must distribute CA cert | 🟢 DID resolution |
|
|
| **Hold compromise** | 🔴 All users affected | 🟢 Metadata only |
|
|
| **Operational cost** | 🔴 High | 🟢 Low |
|
|
| **Use case** | Enterprise PKI | General purpose |
|
|
|
|
## Recommendations
|
|
|
|
### Default Approach: Plugins
|
|
|
|
For most deployments, use plugin-based verification:
|
|
- **Ratify plugin** for Kubernetes
|
|
- **OPA Gatekeeper provider** for policy enforcement
|
|
- **Containerd verifier** for runtime checks
|
|
- **atcr-verify CLI** for general purpose
|
|
|
|
See [Integration Strategy](./INTEGRATION_STRATEGY.md) for details.
|
|
|
|
### Optional: Hold-as-CA for Enterprise
|
|
|
|
Only implement hold-as-CA if you have specific requirements:
|
|
- Enterprise X.509 PKI mandates
|
|
- Cannot use plugins (restricted environments)
|
|
- Accept centralization trade-off
|
|
|
|
**Implement as opt-in feature:**
|
|
```bash
|
|
# Users explicitly enable co-signing
|
|
docker push atcr.io/alice/myapp:latest --sign=notation
|
|
|
|
# Or via environment variable
|
|
export ATCR_ENABLE_COSIGN=true
|
|
docker push atcr.io/alice/myapp:latest
|
|
```
|
|
|
|
### Security Best Practices
|
|
|
|
**If implementing hold-as-CA:**
|
|
|
|
1. **Store CA key in HSM** - Never on filesystem
|
|
2. **Audit all certificate issuance** - Log every cert
|
|
3. **Public transparency log** - Publish all certificates
|
|
4. **Short certificate validity** - 24 hours max
|
|
5. **Monitor unusual patterns** - Alert on anomalies
|
|
6. **Regular CA key rotation** - Every 2-3 years
|
|
7. **Cross-check ATProto** - Verify both signatures match
|
|
8. **Incident response plan** - Prepare for compromise
|
|
|
|
## See Also
|
|
|
|
- [ATProto Signatures](./ATPROTO_SIGNATURES.md) - How ATProto signing works
|
|
- [Integration Strategy](./INTEGRATION_STRATEGY.md) - Overview of integration approaches
|
|
- [Signature Integration](./SIGNATURE_INTEGRATION.md) - Tool-specific integration guides
|