# CapAuth Cryptographic Specification

### PGP Identity, Key Management, and Challenge-Response Protocol

**Version:** 1.0.0 | **Classification:** Public | **Last Updated:** 2026-02-23

---

## Overview

CapAuth implements sovereign identity using the OpenPGP standard (RFC 4880 / RFC 9580). Every agent and human is identified by a PGP keypair. Authentication is challenge-response — no corporate auth server, no session tokens that expire at someone else's discretion. You ARE your key.

This document covers the implementation-level cryptographic details: key generation, signature schemes, challenge-response protocol, the dual-backend architecture, and integration with SKCapstone's sync layer.

---

## Dual Backend Architecture

CapAuth abstracts the crypto layer behind a `CryptoBackend` interface with two implementations:

| Backend | Module | Library | When to Use |
|---------|--------|---------|-------------|
| **PGPy** | `crypto/pgpy_backend.py` | PGPy (pure Python) | Default. No system deps. Portable. |
| **GnuPG** | `crypto/gnupg_backend.py` | python-gnupg (wraps gpg2) | When system GPG keyring integration needed. |

### Backend Interface (`crypto/base.py`)

```python
class CryptoBackend(ABC):
    @abstractmethod
    def generate_keypair(name: str, email: str, passphrase: str) -> KeyMaterial: ...

    @abstractmethod
    def sign(data: bytes, private_key, passphrase: str) -> bytes: ...

    @abstractmethod
    def verify(data: bytes, signature: bytes, public_key) -> bool: ...

    @abstractmethod
    def encrypt(data: bytes, recipient_pubkey) -> bytes: ...

    @abstractmethod
    def decrypt(data: bytes, private_key, passphrase: str) -> bytes: ...

    @abstractmethod
    def export_public_key(key) -> str: ...

    @abstractmethod
    def load_public_key(armored: str): ...
```

Both backends produce interoperable OpenPGP output. A key generated by PGPy can be verified by GnuPG and vice versa.

### Backend Selection

```python
from capauth.crypto import get_backend

backend = get_backend()          # auto-detect (PGPy default)
backend = get_backend("pgpy")    # force PGPy
backend = get_backend("gnupg")   # force system GPG
```

Auto-detection order:
1. If `CAPAUTH_BACKEND` env var is set, use that
2. If PGPy is importable, use PGPy
3. If `gpg2` is on PATH, use GnuPG
4. Raise `CryptoBackendNotFound`

---

## Key Generation

### Supported Algorithms

| Algorithm | Key Size | Use | Performance |
|-----------|----------|-----|-------------|
| **Ed25519** | 256-bit | Signing + certification | Fast, modern, recommended |
| **Cv25519** | 256-bit | Encryption subkey | Paired with Ed25519 |
| **RSA** | 4096-bit | Signing + encryption | Slower, maximum compatibility |

Default: **Ed25519 + Cv25519** (modern, fast, small keys)
Fallback: **RSA-4096** (when Ed25519 is not supported by peer)

### Key Structure

```
Primary Key: Ed25519 [C] (Certify)
  └── Subkey: Cv25519 [E] (Encrypt)
```

Or for RSA:

```
Primary Key: RSA-4096 [SC] (Sign + Certify)
  └── Subkey: RSA-4096 [E] (Encrypt)
```

### Generation Flow

```
capauth init --name "Opus" --email "opus@skcapstone.local"
  │
  ├─ Backend.generate_keypair("Opus", "opus@skcapstone.local", passphrase)
  │   ├─ Generate Ed25519 primary key [C]
  │   ├─ Generate Cv25519 encryption subkey [E]
  │   ├─ Bind UID: "Opus (SKCapstone Agent) <opus@skcapstone.local>"
  │   └─ Protect private key with passphrase (S2K: Iterated+Salted SHA256)
  │
  ├─ Store private key → ~/.capauth/identity/private.asc (mode 0600)
  ├─ Store public key → ~/.capauth/identity/public.asc (mode 0644)
  ├─ Store profile → ~/.capauth/identity/profile.json
  │   {
  │     "name": "Opus",
  │     "email": "opus@skcapstone.local",
  │     "fingerprint": "9B3AB00F411B064646879B92D10E637B4F8367DA",
  │     "algorithm": "ed25519",
  │     "created_at": "2026-02-23T04:21:56Z",
  │     "backend": "gnupg"
  │   }
  │
  └─ Return SovereignProfile object
```

### Passphrase Requirements

| Property | Requirement |
|----------|-------------|
| Minimum length | 8 characters |
| Recommended | 16+ characters or passphrase (4+ words) |
| S2K algorithm | Iterated+Salted SHA256 |
| S2K iterations | 65536+ (tuned to ~100ms on target hardware) |

---

## Sovereign Profile

The `SovereignProfile` class is the core identity object:

```python
class SovereignProfile:
    name: str
    email: str
    fingerprint: str
    public_key: PGPKey | str
    private_key: PGPKey | str  # encrypted at rest
    created_at: datetime
    backend: CryptoBackend

    def sign(self, data: bytes, passphrase: str) -> bytes: ...
    def verify(self, data: bytes, signature: bytes) -> bool: ...
    def encrypt(self, data: bytes, recipient: SovereignProfile) -> bytes: ...
    def decrypt(self, data: bytes, passphrase: str) -> bytes: ...
    def export(self) -> dict: ...  # JSON-serializable profile
    def challenge(self) -> ChallengePacket: ...
    def respond(self, challenge: ChallengePacket, passphrase: str) -> ResponsePacket: ...
```

### Profile Lifecycle

```
INIT ──▶ ACTIVE ──▶ ROTATED
  │                     │
  │                     └──▶ Old key signed by new key
  │                          (chain of trust preserved)
  └──▶ REVOKED (key compromise)
         └──▶ Revocation cert published
              All tokens signed by this key become invalid
```

---

## Challenge-Response Protocol

Identity verification without revealing secrets. Used for agent-to-agent auth and agent-to-service auth.

### Protocol Flow

```
Verifier                          Prover (Agent)
   │                                   │
   ├── 1. Generate nonce (32 bytes) ──▶│
   │      + timestamp                  │
   │      + verifier fingerprint       │
   │                                   │
   │◀── 2. Sign(nonce ∥ ts ∥ fp) ─────┤
   │      using prover's private key   │
   │                                   │
   ├── 3. Verify signature ───────────▶│
   │      against prover's public key  │
   │                                   │
   ├── 4. Check nonce freshness        │
   │      (reject if > 5 min old)      │
   │                                   │
   └── 5. VERIFIED ✓                   │
```

### Challenge Packet

```json
{
  "capauth_protocol": "challenge-response/1.0",
  "nonce": "base64(32 random bytes)",
  "timestamp": "2026-02-23T04:30:00Z",
  "verifier_fingerprint": "ABCD1234...",
  "purpose": "identity_verification"
}
```

### Response Packet

```json
{
  "capauth_protocol": "challenge-response/1.0",
  "nonce": "base64(same nonce)",
  "prover_fingerprint": "9B3AB00F...",
  "signature": "-----BEGIN PGP SIGNATURE-----\n..."
}
```

### Security Properties

| Property | Guarantee |
|----------|-----------|
| **Replay protection** | Nonce + timestamp (5-min window) |
| **Impersonation resistance** | Requires possession of private key |
| **No secret transmission** | Only signature transmitted, not key |
| **Offline verifiable** | Only needs prover's public key |
| **Bidirectional** | Either party can challenge the other |

---

## Encryption Model

### Seed/Vault Encryption (SKCapstone Integration)

When SKCapstone sync pushes a seed or vault, CapAuth encrypts it:

```
Plaintext seed (JSON)
  │
  ├─ 1. GPG encrypt to recipient's public key
  │     gpg --armor --encrypt --recipient <fingerprint> seed.json
  │     Result: seed.json.gpg
  │
  ├─ 2. GPG sign with sender's private key
  │     gpg --armor --sign seed.json.gpg
  │     Result: seed.json.gpg.sig (or embedded signature)
  │
  └─ 3. Drop in sync outbox
        Syncthing adds TLS 1.3 transport encryption
```

### Encryption Layers

```
Layer 1: PGP at rest (CapAuth)
  ├─ Algorithm: Cv25519 (ECDH) or RSA-4096
  ├─ Session key: AES-256
  └─ Only private key holder can decrypt

Layer 2: TLS in transit (Syncthing)
  ├─ Protocol: TLS 1.3
  ├─ Key exchange: X25519
  └─ No intermediary (P2P direct)

Layer 3: Filesystem permissions
  ├─ ~/.skcapstone/identity/ mode 0700
  └─ Private keys mode 0600

Layer 4: Legal (PMA)
  └─ Fiducia Communitatis private jurisdiction
```

---

## Key Management

### Key Storage

| File | Location | Permissions | Contents |
|------|----------|-------------|----------|
| `private.asc` | `~/.capauth/identity/` | 0600 | ASCII-armored private key (passphrase-protected) |
| `public.asc` | `~/.capauth/identity/` | 0644 | ASCII-armored public key (shareable) |
| `profile.json` | `~/.capauth/identity/` | 0644 | Profile metadata (name, fingerprint, algo) |

### Key Rotation

```
1. Generate new keypair
2. Sign new public key with old private key (transition signature)
3. Publish rotation notice:
   {
     "event": "key_rotation",
     "old_fingerprint": "ABCD...",
     "new_fingerprint": "EFGH...",
     "transition_signature": "...",
     "effective_at": "2026-03-01T00:00:00Z"
   }
4. Peers verify transition signature before trusting new key
5. Grace period: accept both keys for 30 days
6. Old key archived (never deleted -- audit trail)
```

### Key Revocation

```
1. Generate revocation certificate:
   gpg --gen-revoke <fingerprint>
2. Publish to:
   - Sovereign profile (revocations.json)
   - Syncthing mesh (immediate propagation)
   - IPFS (immutable record)
3. All tokens signed by revoked key become immediately invalid
4. Peers checking signatures will see revocation and reject
```

---

## Capability Token Signing

Tokens are PGP-signed JSON documents granting scoped access:

```
Token creation:
  1. AI advocate constructs token JSON
  2. AI advocate signs with own private key
  3. Human countersigns with own private key (dual signature)
  4. Token delivered to recipient

Token verification:
  1. Recipient receives token
  2. Verify AI advocate signature against advocate's public key
  3. Verify human signature against human's public key
  4. Check: both signatures valid? Token not expired? Capability matches request?
  5. Grant or deny access
```

### Dual Signature Model

```
                 Token JSON
                     │
        ┌────────────┴────────────┐
        │                         │
   AI Advocate                Human Owner
   signs with                 countersigns with
   advocate key               owner key
        │                         │
        └────────────┬────────────┘
                     │
              Dual-signed token
              (both signatures required)
```

This prevents:
- AI acting without human consent (human sig missing)
- Human being impersonated (AI sig proves advocate relationship)
- Token forgery (PGP signatures are unforgeable without private key)

---

## Test Coverage

27 pytest tests covering:

| Category | Tests | What's Tested |
|----------|-------|---------------|
| Key generation | 4 | Ed25519, RSA, passphrase protection, backend selection |
| Sign/verify | 5 | Valid signatures, tampered data rejection, wrong key rejection |
| Encrypt/decrypt | 4 | Round-trip, wrong key fails, large payloads |
| Challenge-response | 5 | Full protocol, replay rejection, expired nonce, wrong responder |
| Profile lifecycle | 4 | Init, load, export, serialize |
| CLI commands | 3 | init, verify, export |
| Backend interop | 2 | PGPy-generated key verified by GnuPG and vice versa |

---

## Threat Model

| Threat | Mitigation |
|--------|-----------|
| **Key theft (private key stolen)** | Passphrase protection (S2K), filesystem permissions (0600), revocation certificate |
| **Impersonation (fake agent)** | Challenge-response requires private key possession |
| **Replay attack** | Nonce + timestamp with 5-minute window |
| **Man-in-the-middle** | PGP signatures are end-to-end; MITM cannot forge |
| **Token forgery** | Dual PGP signature (AI + human); unforgeable without both keys |
| **Key compromise propagation** | Revocation certificates; tokens signed by revoked key auto-invalid |
| **Clone agent (identity duplication)** | Same key on two machines detected by nonce tracking + audit log |
| **Weak passphrase** | Minimum 8 chars enforced; S2K iterations tuned for brute-force resistance |
| **Quantum computing** | Ed25519 migration path to post-quantum algorithms when standardized |

---

## Standards Compliance

| Standard | Usage |
|----------|-------|
| **RFC 4880** | OpenPGP message format |
| **RFC 9580** | Updated OpenPGP (Ed25519/Cv25519 support) |
| **RFC 7748** | X25519 / Cv25519 key agreement |
| **RFC 8032** | Ed25519 signature scheme |
| **AES-256** | Symmetric session key encryption |
| **SHA-256** | Hash algorithm for S2K and fingerprints |
| **S2K** | String-to-Key passphrase derivation (Iterated+Salted) |

---

## Integration Points

### SKCapstone Sync

```python
from capauth import SovereignProfile

profile = SovereignProfile.load("~/.capauth/identity/")

encrypted = profile.encrypt(seed_json, recipient=profile)
signed = profile.sign(encrypted, passphrase=passphrase)

# Drop in sync outbox -> Syncthing propagates
Path("~/.skcapstone/sync/outbox/seed.json.gpg").write_bytes(signed)
```

### Hemisphere Sync (Agent-to-Agent)

```python
challenge = remote_agent.challenge()
response = local_profile.respond(challenge, passphrase)
remote_agent.verify_response(response)
# Identity confirmed -- proceed with data exchange
```

---

## License

**GPL-3.0-or-later** -- Cryptography should be free and auditable.

Built by the [smilinTux](https://smilintux.org) ecosystem.

*Your key. Your identity. Your sovereignty.* 

#staycuriousANDkeepsmilin
