docs: Updated Readme
All checks were successful
Deployment Pipeline / deploy (push) Successful in 4m38s

This commit is contained in:
2026-06-25 16:14:16 +05:30
parent 977e5bc2c8
commit 33538537a6

208
README.md
View File

@@ -1,107 +1,119 @@
# Asymmetric Cryptography (RSA) Authentication & E2E Secrets Sync
# Bootstrap Authentication - Asymmetric Crypto (RSA) Auth & E2E Secrets
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.
## Introduction
---
This project is a centralized authentication and secrets provisioning server. It is designed to allow arbitrary client machines to securely request access and retrieve sensitive environment variables or secrets during their initialization phase. It relies on strict cryptographic verification using Ed25519 asymmetric keys to ensure that only explicitly authorized clients receive secrets.
## 1. Sequence Diagram: Device Registration & Challenge-Response Auth
By utilizing modern encryption protocols, this system provides a highly secure mechanism to bootstrap new servers, workstations, or deployment targets without ever transmitting plain text secrets over the network or storing them insecurely.
## Architecture and Cryptography
The system uses a completely zero trust model for secret delivery. The server holds a master key that encrypts all secrets at rest within a local SQLite database using the AES 256 GCM authenticated encryption algorithm.
When a new client machine requests access to the secrets, it generates a local Ed25519 key pair and submits its public key to the server. To prevent unauthorized access, an administrator or an already approved device must cryptographically sign this new public key to authorize the new device.
Once the device is approved, the new client must prove it actually possesses the private key it claimed to own by signing a random challenge nonce generated by the server. Only after this verification succeeds does the server retrieve the encrypted secrets from the database. The server decrypts them in memory, and then immediately encrypts them again. This second encryption is performed using the age encryption protocol, specifically targeting the approved client public key.
This strict protocol guarantees that even if the network transport is entirely compromised, the payload remains mathematically inaccessible to anyone except the exact client device that generated the initial request.
## Deployment and Configuration
The server requires minimal setup. It compiles to a single binary and utilizes SQLite for its persistent storage, eliminating the need for complex database infrastructure.
The application is configured using standard environment variables:
* `SERVER_MASTER_KEY` : A highly secure string used to derive the AES encryption key. This is required for encrypting and decrypting the database secrets.
* `DATABASE_URL` : The connection string for the SQLite database. Defaults to a local file named data.db.
* SERVER_PORT : The network port the Axum server will listen on. Defaults to 3000.
## Initial Secrets Provisioning
When the server starts, it automatically looks for a file named secrets.json in the current working directory. If this file is found, the server parses the key and value pairs, encrypts the values securely using the master key, and stores them persistently in the database.
After successful ingestion, the server renames the file to secrets.json.bak. This automatic cleanup prevents accidental plain text exposure and ensures the secrets are not ingested multiple times.
## Application Programming Interface Endpoints
The server exposes three primary endpoints for the device registration lifecycle.
### POST `/api/register`
Clients send their system hostname, operating system details, and their Ed25519 public key in standard Secure Shell format to the registration endpoint. The server registers the device in a pending state and generates a short user code along with a secure challenge nonce.
**Request Payload:**
A JSON object containing:
* `hostname` (string)
* `os` (string)
* `public_key` (string)
**Response Payload:**
A JSON object containing:
* `user_code` (string)
* `challenge_nonce` (string)
* `expires_in` (integer representing seconds)
### POST `/api/approve`
An administrator submits the user code along with a cryptographic signature. This signature must be the pending device public key signed by the administrator private key. The server verifies this signature against known approved devices to authorize the new client.
**Request Payload:**
A JSON object containing:
* `user_code` (string)
* `approver_public_key_fingerprint` (string)
* `signature` (base64 encoded string)
**Response Payload:**
A JSON string confirming approval.
### POST `/api/challenge/poll`
The pending client polls the server with their user code and a signature of the challenge nonce. This proves the client actually possesses the private key corresponding to their submitted public key. If the device has been approved, the server returns the secrets encrypted via the age protocol for that specific client. If the device is still pending, the server responds with an appropriate status code indicating the client should continue waiting.
**Request Payload:**
A JSON object containing:
* `user_code` (string)
* `signature` (base64 encoded string of the challenge nonce signature)
**Response Payload:**
A JSON object containing:
* `encrypted_secrets` (base64 encoded string containing the age encrypted payload)
## Authentication Sequence
The complete cryptographic handshake and provisioning flow is illustrated below:
```mermaid
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)
participant C as New Client
participant S as Authentication Server
participant DB as SQLite Database
participant A as Administrator Device
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!"
Note over C: Generates Ed25519 Key Pair
C->>S: POST /api/register (Public Key, System Info)
S->>DB: Insert pending device record
S-->>C: Returns User Code and Challenge Nonce
Note over C, A: Out of band communication
C->>A: Displays User Code and Public Key Fingerprint
Note over A: Signs New Client Public Key
A->>S: POST /api/approve (User Code, Cryptographic Signature)
S->>DB: Validates Administrator Key
S->>S: Verifies Signature mathematically
S->>DB: Updates pending device to approved status
S-->>A: Acknowledges approval
loop Polling mechanism
Note over C: Signs Challenge Nonce
C->>S: POST /api/challenge/poll (User Code, Nonce Signature)
S->>S: Verifies Client Signature mathematically
S->>DB: Checks approval status
end
S->>DB: Retrieves AES encrypted secrets
S->>S: Decrypts secrets in memory using Master Key
S->>S: Encrypts secrets using age protocol for Client Public Key
S->>DB: Purges pending request record
S-->>C: Returns age encrypted payload
Note over C: Decrypts payload using local Private Key
```
---
## 2. Key Cryptographic Concepts
1. **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.
2. **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.
3. **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.json` or 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:**
```json
{
"hostname": "my-new-laptop",
"os": "linux",
"public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."
}
```
* **Response:**
```json
{
"user_code": "ABCD",
"expires_in": 300
}
```
### B. Approve Pending Device
* **Endpoint:** `POST /api/approve`
* **Request:**
```json
{
"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:**
```json
{
"user_code": "ABCD",
"signature": "base64_encoded_signature_of_challenge_nonce"
}
```
* **Response (when approved):**
```json
{
"encrypted_secrets": "base64_encrypted_payload"
}
```