556 lines
14 KiB
Markdown
556 lines
14 KiB
Markdown
# 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
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 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 ci‑dessus. 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" ci‑dessus
|
||
```
|
||
|
||
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
|
||
|
||
- [ ] Persistance en base de données (PostgreSQL)
|
||
- [ ] Support OCSP (Online Certificate Status Protocol)
|
||
- [ ] Interface web pour gérer les CAs
|
||
- [ ] Export des certificats (PEM, DER)
|
||
- [ ] Support des chaînes intermédiaires
|
||
- [ ] Auditing et logging
|
||
- [ ] Rate limiting et throttling
|
||
|
||
---
|
||
|
||
## Licence
|
||
|
||
MIT
|