pkiapi/README.md

649 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# PKI API
API Go complète pour gérer une Infrastructure à Clé Publique (PKI) avec hiérarchie de certificats et persistance MongoDB.
## Caractéristiques
-**Authentification JWT** : Tous les endpoints protégés par JWT
-**Hiérarchie CA** : Support des Root CA, Intermediate CA (Sub-CA) et certificats finaux
-**Signature de certificats** : Certificats auto-signés ou signés par une CA
-**Gestion des révocations** : Révocation et CRL (Certificate Revocation List)
-**Stockage pluggable** : MemoryStore (développement) ou MongoDB (production)
-**Cryptographie** : X.509, RSA 2048-bit, signatures HS256 pour JWT
## Architecture
```
pkiapi/
├── cmd/main.go # Point d'entrée avec config
├── internal/
│ ├── api/
│ │ ├── router.go # Routage Gin
│ │ ├── auth.go # Login endpoint
│ │ ├── ca.go # Handlers CAs
│ │ └── certificates.go # Handlers certificats
│ ├── auth/
│ │ ├── jwt.go # JWT manager
│ │ └── middleware.go # Middleware d'authentification
│ ├── config/
│ │ └── config.go # Configuration centralisée
│ ├── pki/
│ │ ├── certificate.go # Logique certificats X.509
│ │ └── errors.go # Erreurs PKI
│ └── storage/
│ ├── interface.go # Interface CertificateStore
│ ├── store.go # MemoryStore (en mémoire)
│ ├── mongo.go # MongoStore (persistance)
│ ├── util.go # Helpers sérialisation
│ └── errors.go # Erreurs storage
└── go.mod
```
## Démarrage rapide
### 1. Installer et compiler
```bash
go mod download
go build -o pkiapi ./cmd/main.go
```
### 2. Démarrer le serveur
**Mode développement (MemoryStore):**
```bash
export STORAGE_TYPE=memory
export PORT=8080
./pkiapi
# Serveur lancé sur http://localhost:8080
```
**Mode production (MongoDB):**
```bash
export STORAGE_TYPE=mongodb
export MONGO_URI=mongodb://mongodb-server:27017
export MONGO_DB=pkiapi-prod
export JWT_SECRET_KEY=super-secret-key
./pkiapi
```
### 3. Obtenir un token JWT
```bash
curl -X POST http://localhost:8080/api/v1/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin"}'
# Réponse:
# {
# "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
# "expires_in": 86400
# }
```
## API Endpoints
### 🔐 Authentification (Public)
#### POST /api/v1/login
Obtient un token JWT pour accéder aux autres endpoints.
**Requête :**
```bash
curl -X POST http://localhost:8080/api/v1/login \
-H "Content-Type: application/json" \
-d '{
"username": "admin",
"password": "admin"
}'
```
**Réponse :**
```json
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_in": 86400
}
```
---
### 🔑 Autorités de Certification (Authentifiés)
#### GET /api/v1/ca
Liste toutes les autorités de certification.
**Requête :**
```bash
TOKEN="<your_token>"
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:8080/api/v1/ca
```
**Réponse :**
```json
{
"cas": [
{
"id": "16de28da-f25e-49cd-81de-a929d34dfe08",
"subject": "CN=Root CA,O=Example,C=FR",
"issuer": "CN=Root CA,O=Example,C=FR",
"not_before": "2025-12-06T22:52:48Z",
"not_after": "2035-12-04T22:52:48Z",
"serial_number": "574847517"
}
]
}
```
---
#### POST /api/v1/ca
Crée une nouvelle autorité de certification auto-signée.
**Requête :**
```bash
TOKEN="<your_token>"
curl -X POST http://localhost:8080/api/v1/ca \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"subject": "CN=Root CA,O=Example Inc,C=FR",
"validity_days": 3650
}'
```
**Réponse :**
```json
{
"ca": {
"id": "ff3ac5c5-08d1-401b-9e83-f18eda4c538b",
"subject": "CN=Root CA,O=Example Inc,C=FR",
"not_before": "2025-12-06T21:45:01Z",
"not_after": "2035-12-04T21:45:01Z",
"serial_number": "546965196",
"certificate": "MIIC5zCCAc+gAwIBAgIDCkUz...",
"is_ca": true
},
"created_by": "admin"
}
```
---
#### GET /api/v1/ca/:id
Récupère une autorité de certification par ID.
**Requête :**
```bash
TOKEN="<your_token>"
CA_ID="ff3ac5c5-08d1-401b-9e83-f18eda4c538b"
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:8080/api/v1/ca/$CA_ID
```
**Réponse :**
```json
{
"ca": {
"id": "ff3ac5c5-08d1-401b-9e83-f18eda4c538b",
"subject": "CN=Root CA,O=Example Inc,C=FR",
"not_before": "2025-12-06T21:45:01Z",
"not_after": "2035-12-04T21:45:01Z",
"serial_number": "546965196",
"certificate": "MIIC5zCCAc+gAwIBAgIDCkUz...",
"is_ca": true
}
}
```
---
#### POST /api/v1/ca/sign
Crée une CA intermédiaire (Sub-CA) signée par une CA parent.
**Requête :**
```bash
TOKEN="<your_token>"
PARENT_CA_ID="ff3ac5c5-08d1-401b-9e83-f18eda4c538b"
curl -X POST http://localhost:8080/api/v1/ca/sign \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"parent_ca_id\": \"$PARENT_CA_ID\",
\"subject\": \"CN=Intermediate CA,O=Example Inc,C=FR\",
\"validity_days\": 1825
}"
```
**Réponse :**
```json
{
"ca": {
"id": "b2350d39-53c2-469a-802c-acc39707e352",
"subject": "CN=Intermediate CA,O=Example Inc,C=FR",
"not_before": "2025-12-06T21:45:09Z",
"not_after": "2030-12-05T21:45:09Z",
"serial_number": "576310632",
"certificate": "MIIDOTCCAiGgAwIBAgIEIlnNaD...",
"is_ca": true
},
"created_by": "admin",
"signed_by": "ff3ac5c5-08d1-401b-9e83-f18eda4c538b"
}
```
---
### 📜 Certificats (Authentifiés)
#### GET /api/v1/certificates
Liste tous les certificats.
**Requête :**
```bash
TOKEN="<your_token>"
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:8080/api/v1/certificates
```
**Réponse :**
```json
{
"certificates": [
{
"id": "e12e08a9-adeb-404c-a7b7-a613b77dfe66",
"subject": "CN=server.example.com,O=Example Inc,C=FR",
"issuer": "CN=Intermediate CA,O=Example Inc,C=FR",
"not_before": "2025-12-06T21:45:09Z",
"not_after": "2026-12-06T21:45:09Z",
"serial_number": "46798982",
"revoked": false
}
]
}
```
---
#### POST /api/v1/certificates
Crée un certificat auto-signé.
**Requête :**
```bash
TOKEN="<your_token>"
curl -X POST http://localhost:8080/api/v1/certificates \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"subject": "CN=example.com,O=Example Inc,C=FR",
"validity_days": 365
}'
```
**Réponse :**
```json
{
"certificate": {
"id": "8e77050f-d19f-4607-8c49-b974c5f9cb08",
"subject": "CN=example.com,O=Example Inc,C=FR",
"issuer": "CN=example.com,O=Example Inc,C=FR",
"not_before": "2025-12-06T21:41:38Z",
"not_after": "2026-12-06T21:41:38Z",
"serial_number": "673075",
"certificate": "MIIC5zCCAc+gAwIBAgIDCkUzMA0GCSq...",
"revoked": false
},
"created_by": "admin"
}
```
---
#### POST /api/v1/certificates/sign
Signe un certificat avec une CA.
**Requête :**
```bash
TOKEN="<your_token>"
CA_ID="b2350d39-53c2-469a-802c-acc39707e352"
curl -X POST http://localhost:8080/api/v1/certificates/sign \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"ca_id\": \"$CA_ID\",
\"subject\": \"CN=server.example.com,O=Example Inc,C=FR\",
\"validity_days\": 365
}"
```
**Réponse :**
```json
{
"certificate": {
"id": "e12e08a9-adeb-404c-a7b7-a613b77dfe66",
"subject": "CN=server.example.com,O=Example Inc,C=FR",
"issuer": "CN=Intermediate CA,O=Example Inc,C=FR",
"not_before": "2025-12-06T21:45:09Z",
"not_after": "2026-12-06T21:45:09Z",
"serial_number": "46798982",
"certificate": "MIIDFDCCAfygAwIBAgIEAsoYhjANBg...",
"revoked": false
},
"created_by": "admin",
"signed_by": "b2350d39-53c2-469a-802c-acc39707e352"
}
```
---
#### GET /api/v1/certificates/:id
Récupère un certificat par ID.
**Requête :**
```bash
TOKEN="<your_token>"
CERT_ID="e12e08a9-adeb-404c-a7b7-a613b77dfe66"
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:8080/api/v1/certificates/$CERT_ID
```
**Réponse :**
```json
{
"certificate": {
"id": "e12e08a9-adeb-404c-a7b7-a613b77dfe66",
"subject": "CN=server.example.com,O=Example Inc,C=FR",
"issuer": "CN=Intermediate CA,O=Example Inc,C=FR",
"not_before": "2025-12-06T21:45:09Z",
"not_after": "2026-12-06T21:45:09Z",
"serial_number": "46798982",
"certificate": "MIIDFDCCAfygAwIBAgIEAsoYhjANBg...",
"revoked": false
}
}
```
---
#### POST /api/v1/revoke
Révoque un certificat.
**Requête :**
```bash
TOKEN="<your_token>"
CERT_ID="e12e08a9-adeb-404c-a7b7-a613b77dfe66"
curl -X POST http://localhost:8080/api/v1/revoke \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"certificate_id\": \"$CERT_ID\",
\"reason\": \"Compromised key\"
}"
```
**Réponse :**
```json
{
"message": "certificat révoqué",
"id": "e12e08a9-adeb-404c-a7b7-a613b77dfe66",
"reason": "Compromised key"
}
```
---
#### GET /api/v1/crl
Récupère la Certificate Revocation List (liste des certificats révoqués).
**Requête :**
```bash
TOKEN="<your_token>"
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:8080/api/v1/crl
```
**Réponse :**
```json
{
"crl": [
{
"serial_number": "46798982",
"subject": "CN=server.example.com,O=Example Inc,C=FR"
}
],
"version": 1
}
```
---
### 📥 Export de Certificats (Authentifiés)
#### GET /api/v1/certificates/:id/export/pem
Exporte un certificat au format PEM (binaire/texte).
**Requête :**
```bash
TOKEN="<your_token>"
CERT_ID="e12e08a9-adeb-404c-a7b7-a613b77dfe66"
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:8080/api/v1/certificates/$CERT_ID/export/pem \
-o certificate.pem
```
**Réponse :**
```
-----BEGIN CERTIFICATE-----
MIIDFDCCAfygAwIBAgIEAsoYhjANBg...
...base64...
-----END CERTIFICATE-----
```
---
#### GET /api/v1/certificates/:id/export/der
Exporte un certificat au format DER (binaire).
**Requête :**
```bash
TOKEN="<your_token>"
CERT_ID="e12e08a9-adeb-404c-a7b7-a613b77dfe66"
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:8080/api/v1/certificates/$CERT_ID/export/der \
-o certificate.der
```
**Réponse :**
Format binaire DER directement (données binaires).
---
#### GET /api/v1/certificates/:id/export/pem-with-key
Exporte un certificat avec sa clé privée au format PEM (combiné).
**Requête :**
```bash
TOKEN="<your_token>"
CERT_ID="e12e08a9-adeb-404c-a7b7-a613b77dfe66"
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:8080/api/v1/certificates/$CERT_ID/export/pem-with-key \
-o certificate_with_key.pem
```
**Réponse :**
```
-----BEGIN CERTIFICATE-----
MIIDFDCCAfygAwIBAgIEAsoYhjANBg...
...base64...
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQE...
...base64...
-----END PRIVATE KEY-----
```
---
#### GET /api/v1/certificates/:id/export/chain
Exporte la chaîne de certificats (certificat + CA parent).
**Requête :**
```bash
TOKEN="<your_token>"
CERT_ID="e12e08a9-adeb-404c-a7b7-a613b77dfe66"
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:8080/api/v1/certificates/$CERT_ID/export/chain \
-o certificate_chain.pem
```
**Réponse :**
```
-----BEGIN CERTIFICATE-----
MIIDFDCCAfygAwIBAgIEAsoYhjANBg...
...certificat feuille...
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDOTCCAiGgAwIBAgIEIlnNaD...
...certificat CA parent...
-----END CERTIFICATE-----
```
---
## Variables d'Environnement
- `JWT_SECRET_KEY` : Secret pour signer les tokens JWT (défaut: `your-secret-key-change-in-prod`)
```bash
export JWT_SECRET_KEY="your-secure-secret-key"
./pkiapi
```
---
## Exemple de flux complet
```bash
# 1. Obtenir un token
**Smoke Test Results**
- **Fichier de résultat :** `tests/smoke_result.txt` — sortie brute d'un smoke test automatisé (login → création Root CA → création Sub-CA → signature de certificat → révocation → récupération de la CRL).
- **Résumé :** le test vérifie que le flux complet fonctionne avec `STORAGE_TYPE=mongodb` (création des CA, signature, révocation) et que la CRL liste bien les certificats révoqués.
- **Reproduire localement :** démarrer la stack, puis exécuter les commandes de l'exemple de flux cidessus. Vous pouvez aussi lancer le script temporaire utilisé lors des tests :
```
# reconstruire et démarrer la stack
STORAGE_TYPE=mongodb docker compose up -d --build
# exécuter manuellement l'exemple de flux (ou utiliser jq pour extraire le token)
# voir la section "Exemple de flux complet" cidessus
```
Les résultats complets sont committés dans `tests/smoke_result.txt` pour référence.
TOKEN=$(curl -s -X POST http://localhost:8080/api/v1/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin"}' | jq -r '.token')
echo "Token: $TOKEN"
# 2. Créer une Root CA
ROOT_CA=$(curl -s -X POST http://localhost:8080/api/v1/ca \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"subject":"CN=Root CA,O=Example,C=FR","validity_days":3650}')
ROOT_CA_ID=$(echo $ROOT_CA | jq -r '.ca.id')
echo "Root CA ID: $ROOT_CA_ID"
# 3. Créer une Sub-CA
SUB_CA=$(curl -s -X POST http://localhost:8080/api/v1/ca/sign \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"parent_ca_id\":\"$ROOT_CA_ID\",\"subject\":\"CN=Intermediate CA,O=Example,C=FR\",\"validity_days\":1825}")
SUB_CA_ID=$(echo $SUB_CA | jq -r '.ca.id')
echo "Sub-CA ID: $SUB_CA_ID"
# 4. Signer un certificat avec la Sub-CA
CERT=$(curl -s -X POST http://localhost:8080/api/v1/certificates/sign \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"ca_id\":\"$SUB_CA_ID\",\"subject\":\"CN=app.example.com,O=Example,C=FR\",\"validity_days\":365}")
CERT_ID=$(echo $CERT | jq -r '.certificate.id')
echo "Certificate ID: $CERT_ID"
# 5. Lister toutes les CAs
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/ca | jq .
# 6. Lister tous les certificats
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/certificates | jq .
# 7. Révoquer le certificat
curl -s -X POST http://localhost:8080/api/v1/revoke \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"certificate_id\":\"$CERT_ID\",\"reason\":\"Test\"}" | jq .
# 8. Voir la CRL
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/crl | jq .
```
---
## Structure du projet
```
pkiapi/
├── cmd/main.go # Point d'entrée
├── internal/
│ ├── api/
│ │ ├── router.go # Routes Gin
│ │ ├── auth.go # Login
│ │ ├── ca.go # Handlers CA
│ │ └── certificates.go # Handlers certificats
│ ├── auth/
│ │ ├── jwt.go # JWT manager
│ │ └── middleware.go # Middleware JWT
│ ├── pki/
│ │ ├── certificate.go # Logique X.509
│ │ └── errors.go # Erreurs PKI
│ └── storage/
│ ├── store.go # Store thread-safe
│ └── errors.go # Erreurs storage
├── go.mod
├── go.sum
├── Makefile
├── README.md
└── .gitignore
```
---
## Conventions de code
- **Gestion des erreurs** : Propagation simple sans wrapper
- **Concurrence** : `sync.RWMutex` pour le store
- **Cryptographie** : Stdlib Go (crypto/x509, crypto/rsa, crypto/rand)
- **JWT** : github.com/golang-jwt/jwt/v5
---
## Future améliorations
- [x] Export des certificats (PEM, DER)
- [ ] Persistance en base de données (PostgreSQL)
- [ ] Support OCSP (Online Certificate Status Protocol)
- [ ] Interface web pour gérer les CAs
- [ ] Support des chaînes intermédiaires
- [ ] Auditing et logging
- [ ] Rate limiting et throttling
---
## Licence
MIT