Go to file
zen6 1c02d6a4ab feat: add certificate export functionality (PEM, DER, with private key, chain) 2025-12-07 09:28:48 +01:00
.github feat: PKI API Go avec persistance MongoDB et abstraction storage 2025-12-06 23:11:50 +01:00
cmd feat: PKI API Go avec persistance MongoDB et abstraction storage 2025-12-06 23:11:50 +01:00
internal feat: add certificate export functionality (PEM, DER, with private key, chain) 2025-12-07 09:28:48 +01:00
tests test: add smoke test result 2025-12-06 23:48:56 +01:00
.env.example docs: Ajouter guide de déploiement et fichier .env.example 2025-12-06 23:12:22 +01:00
.gitignore feat: PKI API Go avec persistance MongoDB et abstraction storage 2025-12-06 23:11:50 +01:00
DEPLOYMENT.md last from journey 2025-12-07 01:05:49 +01:00
Dockerfile feat: PKI API Go avec persistance MongoDB et abstraction storage 2025-12-06 23:11:50 +01:00
Makefile feat: PKI API Go avec persistance MongoDB et abstraction storage 2025-12-06 23:11:50 +01:00
README.md feat: add certificate export functionality (PEM, DER, with private key, chain) 2025-12-07 09:28:48 +01:00
compose.yaml feat: PKI API Go avec persistance MongoDB et abstraction storage 2025-12-06 23:11:50 +01:00
go.mod last from journey 2025-12-07 01:05:49 +01:00
test_export.sh feat: add certificate export functionality (PEM, DER, with private key, chain) 2025-12-07 09:28:48 +01:00

README.md

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

go mod download
go build -o pkiapi ./cmd/main.go

2. Démarrer le serveur

Mode développement (MemoryStore):

export STORAGE_TYPE=memory
export PORT=8080
./pkiapi
# Serveur lancé sur http://localhost:8080

Mode production (MongoDB):

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

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 :

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
}

🔑 Autorités de Certification (Authentifiés)

GET /api/v1/ca

Liste toutes les autorités de certification.

Requête :

TOKEN="<your_token>"
curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8080/api/v1/ca

Réponse :

{
  "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 :

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 :

{
  "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 :

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 :

{
  "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 :

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 :

{
  "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 :

TOKEN="<your_token>"
curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8080/api/v1/certificates

Réponse :

{
  "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 :

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 :

{
  "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 :

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 :

{
  "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 :

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 :

{
  "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 :

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 :

{
  "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 :

TOKEN="<your_token>"
curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8080/api/v1/crl

Réponse :

{
  "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 :

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 :

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 :

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 :

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)
export JWT_SECRET_KEY="your-secure-secret-key"
./pkiapi

Exemple de flux complet

# 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

  • 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