# PKI Root CA Rotation — Contingency Plan (NOT CURRENTLY NEEDED)
## Status: NOT NEEDED
Verified 2026-03-22: the root CA (`CN=KuiperDesk Internal CA`, valid 2026-2036)
has **NO name constraints**. Custom domains work without any rotation.
```
$ openssl x509 -in root-ca.crt -text | grep "Name Constraints"
(no output — unconstrained)
```
The `*.kuiperdesk.ai` scope mentioned in early CLAUDE.md was design intent,
never enforced. This plan is retained as a contingency if constraints are
ever added to a future root.
## Contingency: If Name Constraints Were Present
If the root HAD been issued with name constraints:
## Solution: Cross-Sign (Zero Downtime)
Issue a new root alongside the old one. Cross-sign the existing hub intermediate with the new root. Both chains are valid simultaneously. Nothing breaks.
```
BEFORE (constrained):
Root-A (*.kuiperdesk.ai, *.arivaran.ai)
└─ Hub Intermediate
└─ Company certs (*.arivaran.ai) ✓
└─ Product certs (*.kuiperdesk.ai) ✓
└─ Custom domain (*.megacorp.com) ✗ BLOCKED
AFTER (cross-signed, both valid):
Root-A (*.kuiperdesk.ai, *.arivaran.ai) ← OLD, stays valid
└─ Hub Intermediate ← SAME cert, two parents
└─ Company certs (*.arivaran.ai) ✓ validates via Root-A
Root-B (NO constraints) ← NEW, unconstrained
└─ Hub Intermediate (cross-signed) ← SAME key, new signature from Root-B
└─ Satellite Intermediates
└─ Custom domain certs ✓ validates via Root-B
└─ *.kuiperdesk.ai certs ✓ validates via either root
```
## Step-by-Step Rotation
### Phase 1: Generate New Root (CloudHSM Ceremony — 1 hour)
```bash
# On air-gapped ceremony laptop with CloudHSM access
# Same ceremony as original root, but WITHOUT name constraints
# Generate new ECDSA P-384 root key on CloudHSM
pkcs11-tool --module /opt/cloudhsm/lib/libcloudhsm_pkcs11.so \
--keypairgen --key-type EC:secp384r1 \
--label "kuiperdesk-root-b" --id 02
# Create self-signed root cert (20 year, NO name constraints)
openssl req -new -x509 -engine cloudhsm -keyform engine \
-key "pkcs11:token=hsm;object=kuiperdesk-root-b" \
-sha384 -days 7300 \
-subj "/CN=KuiperDesk Root CA B/O=Arivaran Inc/C=US" \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,keyCertSign,cRLSign" \
-out root-b.crt
# NOTE: no nameConstraints extension = unconstrained
```
### Phase 2: Cross-Sign Hub Intermediate (5 minutes, no downtime)
```bash
# Extract CSR from existing hub intermediate (OpenBao has the key)
vault read -field=csr pki_int/intermediate/generate/existing \
> hub-intermediate.csr
# Sign with NEW root (Root-B, unconstrained)
openssl x509 -req -in hub-intermediate.csr \
-CA root-b.crt -CAkey "pkcs11:..." \
-CAcreateserial -sha384 -days 3650 \
-extfile <(echo "basicConstraints=critical,CA:TRUE,pathlen:2
keyUsage=critical,keyCertSign,cRLSign
authorityKeyIdentifier=keyid:always
subjectKeyIdentifier=hash") \
-out hub-intermediate-cross-signed.crt
# Import cross-signed cert into OpenBao (alongside existing)
vault write pki_int/intermediate/set-signed \
certificate=@hub-intermediate-cross-signed.crt
```
### Phase 3: Distribute Root-B (Gradual, No Rush)
The old root (Root-A) still works for everything. Root-B is needed ONLY for:
- New satellite intermediates with custom domains
- ca-bundle.pem in air-gap bundles
Distribution:
1. **Hub services**: Already trust Root-B after OpenBao import
2. **Satellites**: New satellites get Root-B in bootstrap. Existing satellites get it at next cert renewal.
3. **Agents**: New agents get Root-B in ca-bundle.pem. Existing agents continue working with Root-A chain.
4. **Company infra**: No change needed — Root-A chain still valid for `*.arivaran.ai`
5. **Browsers**: CATrust.cpp installs Root-B alongside Root-A (both in ca-bundle.pem)
### Phase 4: Retire Root-A (Optional, Years Later)
Once ALL certs have been re-issued under the Root-B chain (after natural 90-day rotation cycles):
1. Stop including Root-A in new ca-bundle.pem files
2. Remove Root-A from OpenBao trust store
3. Root-A expires naturally (20-year lifetime)
**No rush.** Both roots can coexist indefinitely. Root-A handles company infra. Root-B handles product + custom domains.
## Impact Assessment
| System | Impact | Action Needed |
|--------|--------|---------------|
| Company infra | **NONE** | Root-A still valid, certs don't change |
| Hub product services | **NONE** | Hub intermediate has two valid chains |
| Existing satellites | **NONE** | Current certs valid via Root-A chain |
| Existing agents | **NONE** | System CA store has Root-A, certs still verify |
| New satellites | Root-B used | ca-bundle.pem includes both roots |
| New air-gap bundles | Root-B used | Bundle generator includes both roots |
| Custom domains | **ENABLED** | Satellite intermediate issued under Root-B chain |
## ca-bundle.pem During Transition
```pem
-----BEGIN CERTIFICATE-----
<Root-B cert - unconstrained, new>
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
<Root-A cert - constrained, original>
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
<Hub Intermediate - cross-signed by Root-B>
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
<Satellite Intermediate - issued by Hub Intermediate>
-----END CERTIFICATE-----
```
curl/libcurl and browsers try each root until one validates the chain. Including both roots in the bundle ensures backward + forward compatibility.
## Cost
| Item | Cost | Time |
|------|------|------|
| CloudHSM cluster restore | ~$1.50/hr × 2 hrs | 2 hours |
| Ceremony (2 custodians) | Staff time | 1 hour |
| Cross-signing | $0 (OpenBao CLI) | 5 minutes |
| Distribution | $0 (automated) | Gradual |
| **Total** | **~$3 + staff time** | **< 1 day** |
## Decision Record
- **Why now:** Custom domains for Tier A customers are blocked by root name constraints
- **Why cross-sign (not replace):** Zero disruption to company infra and existing products
- **Why keep Root-A:** Company infra at `*.arivaran.ai` doesn't need custom domain support
- **When to retire Root-A:** After all product certs naturally rotate (3-6 months)
## Revision History
| Date | Change | Author |
|------|--------|--------|
| 2026-03-22 | Initial — cross-sign strategy, ceremony steps, impact assessment | Claire Dubois (Security), Tariq Hassan (Platform) |