4.0 KiB
4.0 KiB
Asymmetric Cryptography (RSA) Authentication & E2E Secrets Sync
This replaces static tokens with an asymmetric key pair (RSA or Ed25519) generated on each client device. This provides challenge response authentication and e2e encryption for synchronized secrets.
1. Sequence Diagram: Device Registration & Challenge-Response Auth
sequenceDiagram
autonumber
actor User as User
participant DevB as Device B (New Machine)
participant Server as Auth Server (Rust)
participant DevA as Device A (Trusted Machine)
Note over DevB: 1. Generate RSA key pair locally if missing<br/>(~/.config/bootstrap/id_rsa)
User->>DevB: Runs `b me`
DevB->>Server: POST /api/register (sends hostname, os, DevB_pubkey)
Server-->>DevB: Returns user_code (ABCD) & challenge_nonce
DevB->>User: Prints: "Run 'b me approve ABCD' on Device A"
DevB->>Server: Starts polling POST /api/challenge/poll (with user_code)
User->>DevA: Runs `b me approve ABCD`
DevA->>Server: GET /api/pending/ABCD (Fetches DevB_pubkey)
Server-->>DevA: Returns DevB_pubkey
Note over DevA: DevA signs DevB_pubkey with its own private key:<br/>Signature = Sign(DevA_privkey, DevB_pubkey)
DevA->>Server: POST /api/approve (sends user_code, DevA_pubkey, Signature)
Note over Server: Server verifies Signature using stored DevA_pubkey.<br/>If valid, adds DevB_pubkey to authorized_keys.
Server-->>DevA: Returns "Approved!"
DevA->>User: Prints: "Approved ABCD"
Note over DevB,Server: Next polling interval
DevB->>Server: POST /api/challenge/poll (with user_code)
Note over Server: Server sees DevB is approved.<br/>Encrypts secrets payload using DevB_pubkey:<br/>EncryptedSecrets = Encrypt(DevB_pubkey, secrets)
Server-->>DevB: Returns EncryptedSecrets
Note over DevB: Decrypts secrets using its private key:<br/>secrets = Decrypt(DevB_privkey, EncryptedSecrets)
DevB->>DevB: Saves secrets locally (0600 permissions)
DevB->>User: Prints: "Successfully authenticated!"
2. Key Cryptographic Concepts
-
Identity & Authentication (Challenge-Response): Instead of storing a static bearer token in
~/.config/bootstrap/.auth_token, the client proves its identity by solving a cryptographic challenge.- The server sends a random string (nonce).
- The client signs the nonce with its private key and returns the signature.
- The server verifies the signature using the client's registered public key.
-
End-to-End Encryption (E2E): Secrets are stored in plaintext on the server (or encrypted at rest) but are encrypted using the client's public key before transmission. Only the client holding the corresponding private key can decrypt and read them.
-
Offline Fallback (What if Device A is offline?):
- Option A (Master Key Signatures): You keep a "Master Key Pair" offline. You can sign Device B's public key offline and send the signature to the server to authorize it.
- Option B (Server SSH Access): Since the server's authorized public keys are stored in a simple configuration file (e.g.
authorized_keys.jsonor text file), you can SSH into the server and append Device B's public key directly.
3. API Endpoints
A. Register New Device
- Endpoint:
POST /api/register - Request:
{ "hostname": "my-new-laptop", "os": "linux", "public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..." } - Response:
{ "user_code": "ABCD", "expires_in": 300 }
B. Approve Pending Device
- Endpoint:
POST /api/approve - Request:
{ "user_code": "ABCD", "approver_public_key_fingerprint": "SHA256:...", "signature": "base64_encoded_signature_of_new_device_public_key" }
C. Challenge Poll
- Endpoint:
POST /api/challenge/poll - Request:
{ "user_code": "ABCD", "signature": "base64_encoded_signature_of_challenge_nonce" } - Response (when approved):
{ "encrypted_secrets": "base64_encrypted_payload" }