add /csr endpoint for CSR signing
Some checks failed
CI / Lint & Format (push) Failing after 16s
CI / Unit Tests (push) Has been skipped
CI / Memory Leak Check (push) Has been skipped
CI / SBOM Generation (push) Has been skipped
CI / Security Scan (push) Successful in 20s
CI / Security Tests (push) Has been skipped
CI / Advanced Security Tests (push) Has been skipped
Some checks failed
CI / Lint & Format (push) Failing after 16s
CI / Unit Tests (push) Has been skipped
CI / Memory Leak Check (push) Has been skipped
CI / SBOM Generation (push) Has been skipped
CI / Security Scan (push) Successful in 20s
CI / Security Tests (push) Has been skipped
CI / Advanced Security Tests (push) Has been skipped
Allow clients to submit Certificate Signing Requests instead of having the server generate private keys. Client keeps key local. - sign_csr() in pki.py validates and signs CSRs - POST /csr endpoint with PoW protection - 10 new tests for CSR functionality - API documentation updated
This commit is contained in:
@@ -1229,6 +1229,122 @@ curl -H "X-SSL-Client-SHA1: $(openssl x509 -in client.crt -fingerprint -sha1 -no
|
||||
4. Server signs CSR and returns the certificate
|
||||
5. Client combines their private key with the signed certificate
|
||||
|
||||
**Request (with PoW):**
|
||||
```http
|
||||
POST /csr HTTP/1.1
|
||||
Host: localhost:5000
|
||||
Content-Type: application/x-pem-file
|
||||
X-PoW-Token: a1b2c3d4...:1700000300:24:signature
|
||||
X-PoW-Solution: 12345678
|
||||
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIBhDCB7gIBADBFMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExEjAQBgNVBAcM
|
||||
...
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
```
|
||||
|
||||
**Request (PoW disabled):**
|
||||
```http
|
||||
POST /csr HTTP/1.1
|
||||
Host: localhost:5000
|
||||
Content-Type: application/x-pem-file
|
||||
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
...
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
```
|
||||
|
||||
**Response (200 OK):**
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/x-pem-file
|
||||
Content-Disposition: attachment; filename="alice.crt"
|
||||
X-Fingerprint-SHA1: b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3
|
||||
X-Certificate-Expires: 1731533400
|
||||
X-Certificate-Serial: 00000000000000000000000000000002
|
||||
X-Is-Admin: 0
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICxDCCAaygAwIBAgIUY...
|
||||
-----END CERTIFICATE-----
|
||||
```
|
||||
|
||||
**Response Headers:**
|
||||
| Header | Description |
|
||||
|--------|-------------|
|
||||
| `X-Fingerprint-SHA1` | SHA1 fingerprint for `X-SSL-Client-SHA1` header |
|
||||
| `X-Certificate-Expires` | Unix timestamp when certificate expires |
|
||||
| `X-Certificate-Serial` | Certificate serial number |
|
||||
| `X-Is-Admin` | `1` if first user (admin), `0` otherwise |
|
||||
|
||||
**Errors:**
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | CSR required (empty body) |
|
||||
| 400 | Invalid CSR format |
|
||||
| 400 | CSR must contain a Common Name (CN) |
|
||||
| 400 | CSR signature is invalid |
|
||||
| 400 | Proof-of-work required (when enabled) |
|
||||
| 400 | Proof-of-work failed (invalid/expired challenge) |
|
||||
| 503 | PKI_CA_PASSWORD not configured |
|
||||
|
||||
**Generating a CSR:**
|
||||
|
||||
```bash
|
||||
# Generate key pair and CSR with OpenSSL
|
||||
openssl ecparam -genkey -name secp384r1 -out client.key
|
||||
openssl req -new -key client.key -out client.csr -subj "/CN=alice"
|
||||
|
||||
# Submit CSR to server
|
||||
curl -X POST --data-binary @client.csr \
|
||||
-H "Content-Type: application/x-pem-file" \
|
||||
https://paste.example.com/csr > client.crt
|
||||
|
||||
# Verify the certificate
|
||||
openssl x509 -in client.crt -noout -subject -issuer
|
||||
```
|
||||
|
||||
**Creating a PKCS#12 bundle:**
|
||||
|
||||
```bash
|
||||
# Combine key and certificate for browser/curl use
|
||||
openssl pkcs12 -export -out client.p12 \
|
||||
-inkey client.key -in client.crt \
|
||||
-certfile ca.crt -passout pass:
|
||||
|
||||
# Use with curl
|
||||
curl --cert client.crt --key client.key https://paste.example.com/
|
||||
```
|
||||
|
||||
**Comparison with /register:**
|
||||
|
||||
| Feature | /register | /csr |
|
||||
|---------|-----------|------|
|
||||
| Private key | Generated server-side | Generated client-side |
|
||||
| Response format | PKCS#12 bundle | PEM certificate only |
|
||||
| Key security | Transmitted over network | Never leaves client |
|
||||
| Complexity | Simple (one step) | More steps required |
|
||||
|
||||
**Notes:**
|
||||
- Uses same PoW difficulty as `/register` (`FLASKPASTE_REGISTER_POW`)
|
||||
- CA is auto-generated on first CSR if not present
|
||||
- First user (via `/register` or `/csr`) becomes admin
|
||||
- CSR must be signed (server validates signature)
|
||||
- Common Name (CN) in CSR becomes certificate subject
|
||||
|
||||
---
|
||||
|
||||
## Audit Logging
|
||||
|
||||
FlaskPaste logs PKI certificate lifecycle events for compliance and forensics.
|
||||
|
||||
**Logged Events:**
|
||||
|
||||
| Event | Trigger | Details |
|
||||
|-------|---------|---------|
|
||||
| `cert_issued` | Certificate registration or issuance | Type, CN, fingerprint, expiry |
|
||||
| `cert_revoked` | Certificate revocation | Serial, fingerprint |
|
||||
| `auth_failure` | Revoked/expired certificate used | Fingerprint, reason |
|
||||
|
||||
**Log Format (production):**
|
||||
```json
|
||||
|
||||
Reference in New Issue
Block a user