first commit
This commit is contained in:
30
.gitignore
vendored
Normal file
30
.gitignore
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Binaries
|
||||||
|
certigo-dummy-ca
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test files
|
||||||
|
test.key
|
||||||
|
test.csr
|
||||||
|
test.crt
|
||||||
|
root.crt
|
||||||
|
*.pem
|
||||||
|
*.key
|
||||||
|
*.crt
|
||||||
|
*.csr
|
||||||
|
|
||||||
|
# Go
|
||||||
|
*.test
|
||||||
|
*.out
|
||||||
|
go.work
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
386
API.md
Normal file
386
API.md
Normal file
@@ -0,0 +1,386 @@
|
|||||||
|
# Dummy CA - API Dokumentation
|
||||||
|
|
||||||
|
Diese Dokumentation beschreibt die REST-API für die externe Anbindung an die Dummy CA.
|
||||||
|
|
||||||
|
**Base URL:** `http://localhost:8088` (oder die entsprechende Server-Adresse)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Endpunkte
|
||||||
|
|
||||||
|
### 1. Health Check
|
||||||
|
|
||||||
|
Prüft, ob der Server erreichbar ist.
|
||||||
|
|
||||||
|
**Endpoint:** `GET /health`
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ok"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Status Codes:**
|
||||||
|
- `200 OK` - Server ist erreichbar
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. CSR einreichen und signieren
|
||||||
|
|
||||||
|
Reicht einen Certificate Signing Request (CSR) ein und lässt ihn signieren.
|
||||||
|
|
||||||
|
**Endpoint:** `POST /csr`
|
||||||
|
|
||||||
|
**Content-Type:** `application/json`
|
||||||
|
|
||||||
|
**Request Body:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"csr": "BASE64_ENCODED_CSR_PEM",
|
||||||
|
"action": "sign",
|
||||||
|
"validity_days": 365
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameter:**
|
||||||
|
- `csr` (string, erforderlich): Der CSR im PEM-Format, Base64-kodiert
|
||||||
|
- `action` (string, erforderlich): Aktuell nur `"sign"` erlaubt
|
||||||
|
- `validity_days` (integer, optional): Gültigkeitsdauer in Tagen (Standard: 365)
|
||||||
|
|
||||||
|
**Response (Erfolg):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "0202",
|
||||||
|
"status": "success",
|
||||||
|
"message": "CSR erfolgreich signiert",
|
||||||
|
"certificate": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIRAK...\n-----END CERTIFICATE-----\n"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response (Fehler):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": "fehler beim Dekodieren des CSR: illegal base64 data"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Status Codes:**
|
||||||
|
- `200 OK` - CSR erfolgreich signiert
|
||||||
|
- `400 Bad Request` - Ungültige Anfrage (fehlende Parameter, ungültiger CSR, etc.)
|
||||||
|
- `405 Method Not Allowed` - Falsche HTTP-Methode
|
||||||
|
- `500 Internal Server Error` - Server-Fehler beim Signieren
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Zertifikat abrufen
|
||||||
|
|
||||||
|
Ruft ein signiertes Zertifikat anhand der Zertifikat-ID ab.
|
||||||
|
|
||||||
|
**Endpoint:** `GET /certificate/{id}`
|
||||||
|
|
||||||
|
**URL Parameter:**
|
||||||
|
- `id` (string, erforderlich): Die Zertifikat-ID (aus der CSR-Response)
|
||||||
|
|
||||||
|
**Response (Erfolg):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "0202",
|
||||||
|
"certificate": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIRAK...\n-----END CERTIFICATE-----\n",
|
||||||
|
"created_at": "2024-01-15T10:30:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response (Fehler):**
|
||||||
|
```
|
||||||
|
zertifikat mit ID 0202 nicht gefunden
|
||||||
|
```
|
||||||
|
|
||||||
|
**Status Codes:**
|
||||||
|
- `200 OK` - Zertifikat gefunden
|
||||||
|
- `400 Bad Request` - Fehlende Zertifikat-ID
|
||||||
|
- `404 Not Found` - Zertifikat nicht gefunden
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Root-Zertifikat abrufen
|
||||||
|
|
||||||
|
Ruft das Root-Zertifikat der CA ab.
|
||||||
|
|
||||||
|
**Endpoint:** `GET /root`
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDXTCCAkWgAwIBAgIRAK...
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
```
|
||||||
|
|
||||||
|
**Content-Type:** `application/x-pem-file`
|
||||||
|
|
||||||
|
**Status Codes:**
|
||||||
|
- `200 OK` - Root-Zertifikat erfolgreich abgerufen
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Beispiel-Implementierungen
|
||||||
|
|
||||||
|
### Python
|
||||||
|
|
||||||
|
```python
|
||||||
|
import requests
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
|
||||||
|
CA_URL = "http://localhost:8088"
|
||||||
|
|
||||||
|
# 1. CSR aus Datei lesen und Base64 kodieren
|
||||||
|
with open("request.csr", "rb") as f:
|
||||||
|
csr_pem = f.read()
|
||||||
|
csr_b64 = base64.b64encode(csr_pem).decode('utf-8')
|
||||||
|
|
||||||
|
# 2. CSR einreichen
|
||||||
|
response = requests.post(
|
||||||
|
f"{CA_URL}/csr",
|
||||||
|
json={
|
||||||
|
"csr": csr_b64,
|
||||||
|
"action": "sign",
|
||||||
|
"validity_days": 365
|
||||||
|
},
|
||||||
|
headers={"Content-Type": "application/json"}
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
cert_id = data["id"]
|
||||||
|
certificate = data["certificate"]
|
||||||
|
|
||||||
|
# Zertifikat speichern
|
||||||
|
with open("signed.crt", "w") as f:
|
||||||
|
f.write(certificate)
|
||||||
|
|
||||||
|
print(f"Zertifikat-ID: {cert_id}")
|
||||||
|
else:
|
||||||
|
print(f"Fehler: {response.status_code} - {response.text}")
|
||||||
|
|
||||||
|
# 3. Zertifikat später abrufen
|
||||||
|
cert_response = requests.get(f"{CA_URL}/certificate/{cert_id}")
|
||||||
|
if cert_response.status_code == 200:
|
||||||
|
cert_data = cert_response.json()
|
||||||
|
print(f"Zertifikat erstellt am: {cert_data['created_at']}")
|
||||||
|
```
|
||||||
|
|
||||||
|
### cURL
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# CSR einreichen
|
||||||
|
CSR_B64=$(cat request.csr | base64 -w 0)
|
||||||
|
|
||||||
|
curl -X POST http://localhost:8088/csr \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{
|
||||||
|
\"csr\": \"$CSR_B64\",
|
||||||
|
\"action\": \"sign\",
|
||||||
|
\"validity_days\": 365
|
||||||
|
}"
|
||||||
|
|
||||||
|
# Zertifikat abrufen
|
||||||
|
curl http://localhost:8088/certificate/0202
|
||||||
|
|
||||||
|
# Root-Zertifikat abrufen
|
||||||
|
curl http://localhost:8088/root > root.crt
|
||||||
|
```
|
||||||
|
|
||||||
|
### JavaScript/Node.js
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const axios = require('axios');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const CA_URL = 'http://localhost:8088';
|
||||||
|
|
||||||
|
async function submitCSR(csrPath) {
|
||||||
|
// CSR lesen und Base64 kodieren
|
||||||
|
const csrPEM = fs.readFileSync(csrPath, 'utf8');
|
||||||
|
const csrB64 = Buffer.from(csrPEM).toString('base64');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// CSR einreichen
|
||||||
|
const response = await axios.post(`${CA_URL}/csr`, {
|
||||||
|
csr: csrB64,
|
||||||
|
action: 'sign',
|
||||||
|
validity_days: 365
|
||||||
|
});
|
||||||
|
|
||||||
|
const { id, certificate } = response.data;
|
||||||
|
|
||||||
|
// Zertifikat speichern
|
||||||
|
fs.writeFileSync('signed.crt', certificate);
|
||||||
|
|
||||||
|
console.log(`Zertifikat-ID: ${id}`);
|
||||||
|
return id;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler:', error.response?.data || error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getCertificate(certId) {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${CA_URL}/certificate/${certId}`);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler:', error.response?.data || error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verwendung
|
||||||
|
submitCSR('request.csr')
|
||||||
|
.then(certId => getCertificate(certId))
|
||||||
|
.then(data => console.log('Zertifikat erstellt:', data.created_at))
|
||||||
|
.catch(console.error);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Go
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CA_URL = "http://localhost:8088"
|
||||||
|
|
||||||
|
type CSRRequest struct {
|
||||||
|
CSR string `json:"csr"`
|
||||||
|
Action string `json:"action"`
|
||||||
|
ValidityDays int `json:"validity_days"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CSRResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Certificate string `json:"certificate"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func submitCSR(csrPath string) (string, error) {
|
||||||
|
// CSR lesen
|
||||||
|
csrPEM, err := ioutil.ReadFile(csrPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base64 kodieren
|
||||||
|
csrB64 := base64.StdEncoding.EncodeToString(csrPEM)
|
||||||
|
|
||||||
|
// Request erstellen
|
||||||
|
reqBody := CSRRequest{
|
||||||
|
CSR: csrB64,
|
||||||
|
Action: "sign",
|
||||||
|
ValidityDays: 365,
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonData, _ := json.Marshal(reqBody)
|
||||||
|
|
||||||
|
// HTTP Request
|
||||||
|
resp, err := http.Post(CA_URL+"/csr", "application/json", bytes.NewBuffer(jsonData))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return "", fmt.Errorf("fehler: %s", string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
var csrResp CSRResponse
|
||||||
|
json.Unmarshal(body, &csrResp)
|
||||||
|
|
||||||
|
// Zertifikat speichern
|
||||||
|
ioutil.WriteFile("signed.crt", []byte(csrResp.Certificate), 0644)
|
||||||
|
|
||||||
|
return csrResp.ID, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CSR-Format
|
||||||
|
|
||||||
|
Der CSR muss im PEM-Format vorliegen und Base64-kodiert übertragen werden.
|
||||||
|
|
||||||
|
**Beispiel CSR (PEM):**
|
||||||
|
```
|
||||||
|
-----BEGIN CERTIFICATE REQUEST-----
|
||||||
|
MIICVjCCAT4CAQAwEjEQMA4GA1UEAwwHZXhhbXBsZTEwggEiMA0GCSqGSIb3DQEB
|
||||||
|
...
|
||||||
|
-----END CERTIFICATE REQUEST-----
|
||||||
|
```
|
||||||
|
|
||||||
|
**Erstellung eines CSR:**
|
||||||
|
```bash
|
||||||
|
# Private Key generieren
|
||||||
|
openssl genrsa -out private.key 2048
|
||||||
|
|
||||||
|
# CSR erstellen
|
||||||
|
openssl req -new -key private.key -out request.csr \
|
||||||
|
-subj "/CN=example.com/O=Example Org/C=DE"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fehlerbehandlung
|
||||||
|
|
||||||
|
Alle Fehler werden als HTTP-Status-Codes zurückgegeben:
|
||||||
|
|
||||||
|
- **400 Bad Request**: Ungültige Anfrage (fehlende/ungültige Parameter)
|
||||||
|
- **404 Not Found**: Ressource nicht gefunden (z.B. Zertifikat-ID existiert nicht)
|
||||||
|
- **405 Method Not Allowed**: Falsche HTTP-Methode verwendet
|
||||||
|
- **500 Internal Server Error**: Server-seitiger Fehler
|
||||||
|
|
||||||
|
Fehlermeldungen werden im Response-Body als Text zurückgegeben.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Wichtige Hinweise
|
||||||
|
|
||||||
|
1. **Zertifikat-Speicher**: Zertifikate werden nur im Speicher gehalten und gehen nach einem Server-Neustart verloren.
|
||||||
|
|
||||||
|
2. **Keine Authentifizierung**: Die API hat aktuell keine Authentifizierung. Für Produktionsumgebungen sollte dies hinzugefügt werden.
|
||||||
|
|
||||||
|
3. **CSR-Validierung**: Die CA validiert die CSR-Signatur, aber nicht den Inhalt des CSR.
|
||||||
|
|
||||||
|
4. **Serialnummern**: Jedes Zertifikat erhält eine eindeutige, automatisch inkrementierte Serialnummer.
|
||||||
|
|
||||||
|
5. **Gültigkeitsdauer**: Standardmäßig 365 Tage, kann über `validity_days` angepasst werden.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testen der API
|
||||||
|
|
||||||
|
Sie können die API mit dem bereitgestellten Beispiel-Skript testen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./example.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Oder manuell mit cURL:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Health Check
|
||||||
|
curl http://localhost:8088/health
|
||||||
|
|
||||||
|
# Root-Zertifikat
|
||||||
|
curl http://localhost:8088/root
|
||||||
|
```
|
||||||
|
|
||||||
159
README.md
Normal file
159
README.md
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
# Dummy CA - Certificate Authority in Go
|
||||||
|
|
||||||
|
Eine einfache Certificate Authority (CA) in Go, die über eine REST-API Certificate Signing Requests (CSR) entgegennimmt, signiert und Zertifikate bereitstellt.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Automatische Generierung eines Root-Zertifikats beim Start
|
||||||
|
- REST-API zum Einreichen und Signieren von CSRs
|
||||||
|
- Abruf von signierten Zertifikaten über GET-Request
|
||||||
|
- Abruf des Root-Zertifikats
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go mod download
|
||||||
|
```
|
||||||
|
|
||||||
|
## Starten
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go run main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Server läuft standardmäßig auf Port 8088.
|
||||||
|
|
||||||
|
## API-Endpunkte
|
||||||
|
|
||||||
|
### POST /csr
|
||||||
|
Reicht einen CSR ein und lässt ihn signieren.
|
||||||
|
|
||||||
|
**Request Body:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"csr": "BASE64_ENCODED_CSR_PEM",
|
||||||
|
"action": "sign",
|
||||||
|
"validity_days": 365
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "zertifikat-id",
|
||||||
|
"status": "success",
|
||||||
|
"message": "CSR erfolgreich signiert",
|
||||||
|
"certificate": "-----BEGIN CERTIFICATE-----\n..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### GET /certificate/{id}
|
||||||
|
Ruft ein signiertes Zertifikat anhand der ID ab.
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "zertifikat-id",
|
||||||
|
"certificate": "-----BEGIN CERTIFICATE-----\n...",
|
||||||
|
"created_at": "2024-01-01T12:00:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### GET /root
|
||||||
|
Ruft das Root-Zertifikat der CA ab (PEM-Format).
|
||||||
|
|
||||||
|
### GET /health
|
||||||
|
Health-Check-Endpunkt.
|
||||||
|
|
||||||
|
## Beispiel: CSR erstellen und einreichen
|
||||||
|
|
||||||
|
### 1. CSR erstellen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Private Key generieren
|
||||||
|
openssl genrsa -out private.key 2048
|
||||||
|
|
||||||
|
# CSR erstellen
|
||||||
|
openssl req -new -key private.key -out request.csr -subj "/CN=example.com"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. CSR in Base64 kodieren
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# CSR in Base64 kodieren
|
||||||
|
CSR_B64=$(cat request.csr | base64 -w 0)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. CSR an die CA senden
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:8088/csr \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{
|
||||||
|
\"csr\": \"$CSR_B64\",
|
||||||
|
\"action\": \"sign\",
|
||||||
|
\"validity_days\": 365
|
||||||
|
}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Zertifikat abrufen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Mit der ID aus der vorherigen Antwort
|
||||||
|
curl http://localhost:8088/certificate/{id}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Root-Zertifikat abrufen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8088/root > root.crt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Beispiel-Skript
|
||||||
|
|
||||||
|
Ein Beispiel-Skript zum Testen der API:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 1. Private Key und CSR erstellen
|
||||||
|
openssl genrsa -out test.key 2048
|
||||||
|
openssl req -new -key test.key -out test.csr -subj "/CN=test.example.com"
|
||||||
|
|
||||||
|
# 2. CSR kodieren und einreichen
|
||||||
|
CSR_B64=$(cat test.csr | base64 -w 0)
|
||||||
|
RESPONSE=$(curl -s -X POST http://localhost:8088/csr \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{
|
||||||
|
\"csr\": \"$CSR_B64\",
|
||||||
|
\"action\": \"sign\",
|
||||||
|
\"validity_days\": 365
|
||||||
|
}")
|
||||||
|
|
||||||
|
# 3. Zertifikat-ID extrahieren
|
||||||
|
CERT_ID=$(echo $RESPONSE | jq -r '.id')
|
||||||
|
echo "Zertifikat-ID: $CERT_ID"
|
||||||
|
|
||||||
|
# 4. Zertifikat abrufen
|
||||||
|
curl -s http://localhost:8088/certificate/$CERT_ID | jq -r '.certificate' > test.crt
|
||||||
|
|
||||||
|
# 5. Zertifikat verifizieren
|
||||||
|
openssl x509 -in test.crt -text -noout
|
||||||
|
```
|
||||||
|
|
||||||
|
## Technische Details
|
||||||
|
|
||||||
|
- Root-Zertifikat: 2048-bit RSA, 10 Jahre Gültigkeit
|
||||||
|
- Signierte Zertifikate: Standardmäßig 365 Tage Gültigkeit (konfigurierbar)
|
||||||
|
- Zertifikat-Speicher: In-Memory (verloren nach Neustart)
|
||||||
|
- Serialnummern: Automatisch inkrementiert
|
||||||
|
|
||||||
|
## Sicherheitshinweise
|
||||||
|
|
||||||
|
⚠️ **Diese CA ist nur für Test- und Entwicklungszwecke gedacht!**
|
||||||
|
|
||||||
|
- Keine Persistierung der Zertifikate
|
||||||
|
- Keine Authentifizierung der API
|
||||||
|
- Keine Validierung der CSR-Inhalte
|
||||||
|
- Nicht für Produktionsumgebungen geeignet
|
||||||
|
|
||||||
162
api_example.sh
Executable file
162
api_example.sh
Executable file
@@ -0,0 +1,162 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Beispiel-Skript für externe Systeme zur Anbindung an die Dummy CA
|
||||||
|
# Dieses Skript zeigt, wie man die API von einem externen System aus nutzt
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
CA_URL="${CA_URL:-http://localhost:8088}"
|
||||||
|
|
||||||
|
# Farben für Output
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
echo -e "${YELLOW}=== Dummy CA API - Externes System Beispiel ===${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Funktion: Health Check
|
||||||
|
check_health() {
|
||||||
|
echo "1. Prüfe Server-Verfügbarkeit..."
|
||||||
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$CA_URL/health")
|
||||||
|
|
||||||
|
if [ "$HTTP_CODE" = "200" ]; then
|
||||||
|
echo -e "${GREEN}✓ Server ist erreichbar${NC}"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ Server nicht erreichbar (HTTP $HTTP_CODE)${NC}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Funktion: CSR einreichen
|
||||||
|
submit_csr() {
|
||||||
|
local csr_file="$1"
|
||||||
|
local validity_days="${2:-365}"
|
||||||
|
|
||||||
|
if [ ! -f "$csr_file" ]; then
|
||||||
|
echo -e "${RED}✗ CSR-Datei nicht gefunden: $csr_file${NC}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "2. Reiche CSR ein..."
|
||||||
|
|
||||||
|
# CSR Base64 kodieren
|
||||||
|
CSR_B64=$(cat "$csr_file" | base64 -w 0)
|
||||||
|
|
||||||
|
# CSR einreichen
|
||||||
|
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$CA_URL/csr" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{
|
||||||
|
\"csr\": \"$CSR_B64\",
|
||||||
|
\"action\": \"sign\",
|
||||||
|
\"validity_days\": $validity_days
|
||||||
|
}")
|
||||||
|
|
||||||
|
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
||||||
|
BODY=$(echo "$RESPONSE" | sed '$d')
|
||||||
|
|
||||||
|
if [ "$HTTP_CODE" = "200" ]; then
|
||||||
|
CERT_ID=$(echo "$BODY" | jq -r '.id')
|
||||||
|
CERT_PEM=$(echo "$BODY" | jq -r '.certificate')
|
||||||
|
|
||||||
|
# Zertifikat speichern
|
||||||
|
echo "$CERT_PEM" > "certificate_${CERT_ID}.crt"
|
||||||
|
|
||||||
|
echo -e "${GREEN}✓ CSR erfolgreich signiert${NC}"
|
||||||
|
echo " Zertifikat-ID: $CERT_ID"
|
||||||
|
echo " Zertifikat gespeichert in: certificate_${CERT_ID}.crt"
|
||||||
|
echo "$CERT_ID"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ Fehler beim Signieren (HTTP $HTTP_CODE)${NC}"
|
||||||
|
echo "$BODY" | jq . 2>/dev/null || echo "$BODY"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Funktion: Zertifikat abrufen
|
||||||
|
get_certificate() {
|
||||||
|
local cert_id="$1"
|
||||||
|
|
||||||
|
if [ -z "$cert_id" ]; then
|
||||||
|
echo -e "${RED}✗ Zertifikat-ID erforderlich${NC}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "3. Rufe Zertifikat ab (ID: $cert_id)..."
|
||||||
|
|
||||||
|
RESPONSE=$(curl -s -w "\n%{http_code}" "$CA_URL/certificate/$cert_id")
|
||||||
|
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
||||||
|
BODY=$(echo "$RESPONSE" | sed '$d')
|
||||||
|
|
||||||
|
if [ "$HTTP_CODE" = "200" ]; then
|
||||||
|
CERT_PEM=$(echo "$BODY" | jq -r '.certificate')
|
||||||
|
CREATED_AT=$(echo "$BODY" | jq -r '.created_at')
|
||||||
|
|
||||||
|
echo -e "${GREEN}✓ Zertifikat abgerufen${NC}"
|
||||||
|
echo " Erstellt am: $CREATED_AT"
|
||||||
|
echo "$CERT_PEM"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ Zertifikat nicht gefunden (HTTP $HTTP_CODE)${NC}"
|
||||||
|
echo "$BODY"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Funktion: Root-Zertifikat abrufen
|
||||||
|
get_root_certificate() {
|
||||||
|
echo "4. Rufe Root-Zertifikat ab..."
|
||||||
|
|
||||||
|
ROOT_CERT=$(curl -s "$CA_URL/root")
|
||||||
|
|
||||||
|
if [ -n "$ROOT_CERT" ]; then
|
||||||
|
echo "$ROOT_CERT" > "root_ca.crt"
|
||||||
|
echo -e "${GREEN}✓ Root-Zertifikat gespeichert in: root_ca.crt${NC}"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ Fehler beim Abrufen des Root-Zertifikats${NC}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Hauptfunktion
|
||||||
|
main() {
|
||||||
|
# Health Check
|
||||||
|
if ! check_health; then
|
||||||
|
echo ""
|
||||||
|
echo "Bitte starten Sie den Server mit: go run main.go"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Wenn CSR-Datei als Argument übergeben wurde
|
||||||
|
if [ -n "$1" ]; then
|
||||||
|
CERT_ID=$(submit_csr "$1" "${2:-365}")
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ -n "$CERT_ID" ]; then
|
||||||
|
get_certificate "$CERT_ID" > /dev/null
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Verwendung:"
|
||||||
|
echo " $0 <csr-datei> [validity_days]"
|
||||||
|
echo ""
|
||||||
|
echo "Beispiel:"
|
||||||
|
echo " $0 request.csr 365"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Root-Zertifikat abrufen
|
||||||
|
get_root_certificate
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo -e "${YELLOW}=== Fertig ===${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Skript ausführen
|
||||||
|
main "$@"
|
||||||
|
|
||||||
88
example.sh
Executable file
88
example.sh
Executable file
@@ -0,0 +1,88 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Beispiel-Skript zum Testen der Dummy CA API
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
CA_URL="http://localhost:8088"
|
||||||
|
|
||||||
|
echo "=== Dummy CA Test ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 1. Health-Check
|
||||||
|
echo "1. Health-Check..."
|
||||||
|
HEALTH_RESPONSE=$(curl -s -w "\n%{http_code}" "$CA_URL/health")
|
||||||
|
HTTP_CODE=$(echo "$HEALTH_RESPONSE" | tail -n1)
|
||||||
|
BODY=$(echo "$HEALTH_RESPONSE" | sed '$d')
|
||||||
|
|
||||||
|
if [ "$HTTP_CODE" = "200" ]; then
|
||||||
|
echo "$BODY" | jq . 2>/dev/null || echo "$BODY"
|
||||||
|
echo "✓ Server ist erreichbar"
|
||||||
|
else
|
||||||
|
echo "✗ Server nicht erreichbar (HTTP $HTTP_CODE)"
|
||||||
|
echo " Stelle sicher, dass der Server auf Port 8088 läuft:"
|
||||||
|
echo " go run main.go"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 2. Private Key und CSR erstellen
|
||||||
|
echo "2. Erstelle Private Key und CSR..."
|
||||||
|
openssl genrsa -out test.key 2048 2>/dev/null
|
||||||
|
openssl req -new -key test.key -out test.csr -subj "/CN=test.example.com/O=Test Org" 2>/dev/null
|
||||||
|
echo "✓ Private Key und CSR erstellt"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 3. CSR kodieren und einreichen
|
||||||
|
echo "3. Reiche CSR ein..."
|
||||||
|
CSR_B64=$(cat test.csr | base64 -w 0)
|
||||||
|
RESPONSE=$(curl -s -X POST "$CA_URL/csr" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{
|
||||||
|
\"csr\": \"$CSR_B64\",
|
||||||
|
\"action\": \"sign\",
|
||||||
|
\"validity_days\": 365
|
||||||
|
}")
|
||||||
|
|
||||||
|
CERT_ID=$(echo $RESPONSE | jq -r '.id' 2>/dev/null)
|
||||||
|
if [ -z "$CERT_ID" ] || [ "$CERT_ID" = "null" ]; then
|
||||||
|
echo "✗ Fehler beim Signieren des CSR:"
|
||||||
|
echo "$RESPONSE" | jq . 2>/dev/null || echo "$RESPONSE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✓ CSR signiert - Zertifikat-ID: $CERT_ID"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 4. Zertifikat abrufen
|
||||||
|
echo "4. Rufe Zertifikat ab..."
|
||||||
|
curl -s "$CA_URL/certificate/$CERT_ID" | jq -r '.certificate' > test.crt
|
||||||
|
echo "✓ Zertifikat gespeichert in test.crt"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 5. Root-Zertifikat abrufen
|
||||||
|
echo "5. Rufe Root-Zertifikat ab..."
|
||||||
|
curl -s "$CA_URL/root" > root.crt
|
||||||
|
echo "✓ Root-Zertifikat gespeichert in root.crt"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 6. Zertifikat-Details anzeigen
|
||||||
|
echo "6. Zertifikat-Details:"
|
||||||
|
openssl x509 -in test.crt -text -noout | head -20
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 7. Zertifikat mit Root verifizieren
|
||||||
|
echo "7. Verifiziere Zertifikat mit Root-CA..."
|
||||||
|
if openssl verify -CAfile root.crt test.crt > /dev/null 2>&1; then
|
||||||
|
echo "✓ Zertifikat ist gültig!"
|
||||||
|
else
|
||||||
|
echo "✗ Zertifikat-Verifizierung fehlgeschlagen"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== Test abgeschlossen ==="
|
||||||
|
echo "Dateien:"
|
||||||
|
echo " - test.key (Private Key)"
|
||||||
|
echo " - test.csr (Certificate Signing Request)"
|
||||||
|
echo " - test.crt (Signiertes Zertifikat)"
|
||||||
|
echo " - root.crt (Root-Zertifikat)"
|
||||||
|
|
||||||
5
go.mod
Normal file
5
go.mod
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module certigo-dummy-ca
|
||||||
|
|
||||||
|
go 1.21
|
||||||
|
|
||||||
|
require github.com/gorilla/mux v1.8.1
|
||||||
2
go.sum
Normal file
2
go.sum
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||||
|
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||||
429
main.go
Normal file
429
main.go
Normal file
@@ -0,0 +1,429 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math/big"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CertificateStore speichert signierte Zertifikate
|
||||||
|
type CertificateStore struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
certificates map[string]*StoredCertificate
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoredCertificate enthält das signierte Zertifikat und Metadaten
|
||||||
|
type StoredCertificate struct {
|
||||||
|
ID string
|
||||||
|
Certificate *x509.Certificate
|
||||||
|
PEM string
|
||||||
|
CreatedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// CA repräsentiert die Certificate Authority
|
||||||
|
type CA struct {
|
||||||
|
rootCert *x509.Certificate
|
||||||
|
rootKey *rsa.PrivateKey
|
||||||
|
certStore *CertificateStore
|
||||||
|
serialNumber *big.Int
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSRRequest repräsentiert eine CSR-Anfrage
|
||||||
|
type CSRRequest struct {
|
||||||
|
CSR string `json:"csr"` // Base64-kodierter PEM-formatierter CSR
|
||||||
|
Action string `json:"action"` // z.B. "sign"
|
||||||
|
ValidityDays int `json:"validity_days"` // Gültigkeitsdauer in Tagen (optional, default: 365)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSRResponse repräsentiert die Antwort auf eine CSR-Anfrage
|
||||||
|
type CSRResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Certificate string `json:"certificate,omitempty"` // PEM-formatierter Zertifikat
|
||||||
|
}
|
||||||
|
|
||||||
|
// CertificateResponse repräsentiert die Antwort beim Abruf eines Zertifikats
|
||||||
|
type CertificateResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Certificate string `json:"certificate"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCA erstellt eine neue Certificate Authority
|
||||||
|
func NewCA() (*CA, error) {
|
||||||
|
// Root-Zertifikat und Key generieren
|
||||||
|
rootKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("fehler beim Generieren des Root-Keys: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root-Zertifikat erstellen
|
||||||
|
rootTemplate := &x509.Certificate{
|
||||||
|
SerialNumber: big.NewInt(1),
|
||||||
|
Subject: pkix.Name{
|
||||||
|
Organization: []string{"Dummy CA"},
|
||||||
|
Country: []string{"DE"},
|
||||||
|
Province: []string{""},
|
||||||
|
Locality: []string{""},
|
||||||
|
StreetAddress: []string{""},
|
||||||
|
PostalCode: []string{""},
|
||||||
|
CommonName: "Dummy CA Root Certificate",
|
||||||
|
},
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
NotAfter: time.Now().AddDate(10, 0, 0), // 10 Jahre gültig
|
||||||
|
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
IsCA: true,
|
||||||
|
MaxPathLen: 2,
|
||||||
|
MaxPathLenZero: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
rootCertDER, err := x509.CreateCertificate(rand.Reader, rootTemplate, rootTemplate, &rootKey.PublicKey, rootKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("fehler beim Erstellen des Root-Zertifikats: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rootCert, err := x509.ParseCertificate(rootCertDER)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("fehler beim Parsen des Root-Zertifikats: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CA{
|
||||||
|
rootCert: rootCert,
|
||||||
|
rootKey: rootKey,
|
||||||
|
certStore: &CertificateStore{certificates: make(map[string]*StoredCertificate)},
|
||||||
|
serialNumber: big.NewInt(2),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignCSR signiert einen Certificate Signing Request
|
||||||
|
func (ca *CA) SignCSR(csrPEM string, validityDays int) (string, error) {
|
||||||
|
// PEM-Block dekodieren
|
||||||
|
block, _ := pem.Decode([]byte(csrPEM))
|
||||||
|
if block == nil {
|
||||||
|
return "", fmt.Errorf("ungültiger PEM-Block")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSR parsen
|
||||||
|
csr, err := x509.ParseCertificateRequest(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("fehler beim Parsen des CSR: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSR validieren
|
||||||
|
if err := csr.CheckSignature(); err != nil {
|
||||||
|
return "", fmt.Errorf("ungültige CSR-Signatur: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialnummer inkrementieren
|
||||||
|
ca.mu.Lock()
|
||||||
|
serial := new(big.Int).Set(ca.serialNumber)
|
||||||
|
ca.serialNumber.Add(ca.serialNumber, big.NewInt(1))
|
||||||
|
ca.mu.Unlock()
|
||||||
|
|
||||||
|
// Zertifikat-Template erstellen
|
||||||
|
template := &x509.Certificate{
|
||||||
|
SerialNumber: serial,
|
||||||
|
Subject: csr.Subject,
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
NotAfter: time.Now().AddDate(0, 0, validityDays),
|
||||||
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
|
||||||
|
}
|
||||||
|
|
||||||
|
// SANs (Subject Alternative Names) vom CSR kopieren
|
||||||
|
if len(csr.DNSNames) > 0 {
|
||||||
|
template.DNSNames = csr.DNSNames
|
||||||
|
}
|
||||||
|
if len(csr.IPAddresses) > 0 {
|
||||||
|
template.IPAddresses = csr.IPAddresses
|
||||||
|
}
|
||||||
|
if len(csr.EmailAddresses) > 0 {
|
||||||
|
template.EmailAddresses = csr.EmailAddresses
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zertifikat signieren
|
||||||
|
certDER, err := x509.CreateCertificate(rand.Reader, template, ca.rootCert, csr.PublicKey, ca.rootKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("fehler beim Signieren des Zertifikats: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zertifikat parsen
|
||||||
|
cert, err := x509.ParseCertificate(certDER)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("fehler beim Parsen des signierten Zertifikats: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PEM-formatieren
|
||||||
|
certPEM := pem.EncodeToMemory(&pem.Block{
|
||||||
|
Type: "CERTIFICATE",
|
||||||
|
Bytes: certDER,
|
||||||
|
})
|
||||||
|
|
||||||
|
// In Store speichern
|
||||||
|
certID := fmt.Sprintf("%x", cert.SerialNumber.Bytes())
|
||||||
|
ca.certStore.mu.Lock()
|
||||||
|
ca.certStore.certificates[certID] = &StoredCertificate{
|
||||||
|
ID: certID,
|
||||||
|
Certificate: cert,
|
||||||
|
PEM: string(certPEM),
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
ca.certStore.mu.Unlock()
|
||||||
|
|
||||||
|
return certID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCertificate ruft ein Zertifikat anhand der ID ab
|
||||||
|
func (ca *CA) GetCertificate(id string) (*StoredCertificate, error) {
|
||||||
|
ca.certStore.mu.RLock()
|
||||||
|
defer ca.certStore.mu.RUnlock()
|
||||||
|
|
||||||
|
cert, exists := ca.certStore.certificates[id]
|
||||||
|
if !exists {
|
||||||
|
return nil, fmt.Errorf("zertifikat mit ID %s nicht gefunden", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRootCertificate gibt das Root-Zertifikat zurück
|
||||||
|
func (ca *CA) GetRootCertificate() string {
|
||||||
|
rootCertPEM := pem.EncodeToMemory(&pem.Block{
|
||||||
|
Type: "CERTIFICATE",
|
||||||
|
Bytes: ca.rootCert.Raw,
|
||||||
|
})
|
||||||
|
return string(rootCertPEM)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleSubmitCSR behandelt POST-Anfragen zum Einreichen eines CSR
|
||||||
|
func (ca *CA) handleSubmitCSR(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "nur POST erlaubt", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req CSRRequest
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("fehler beim Dekodieren der Anfrage: %v", err), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSR dekodieren (Base64)
|
||||||
|
csrBytes, err := base64.StdEncoding.DecodeString(req.CSR)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("fehler beim Dekodieren des CSR: %v", err), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
csrPEM := string(csrBytes)
|
||||||
|
|
||||||
|
// Validity Days standardmäßig auf 365 setzen
|
||||||
|
validityDays := req.ValidityDays
|
||||||
|
if validityDays <= 0 {
|
||||||
|
validityDays = 365
|
||||||
|
}
|
||||||
|
|
||||||
|
// Action prüfen
|
||||||
|
if req.Action != "sign" {
|
||||||
|
http.Error(w, "ungültige Action. Erlaubt: 'sign'", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSR signieren
|
||||||
|
certID, err := ca.SignCSR(csrPEM, validityDays)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("fehler beim Signieren: %v", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zertifikat abrufen
|
||||||
|
storedCert, err := ca.GetCertificate(certID)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("fehler beim Abrufen des Zertifikats: %v", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Antwort senden
|
||||||
|
response := CSRResponse{
|
||||||
|
ID: certID,
|
||||||
|
Status: "success",
|
||||||
|
Message: "CSR erfolgreich signiert",
|
||||||
|
Certificate: storedCert.PEM,
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleGetCertificate behandelt GET-Anfragen zum Abruf eines Zertifikats
|
||||||
|
func (ca *CA) handleGetCertificate(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
certID := vars["id"]
|
||||||
|
|
||||||
|
if certID == "" {
|
||||||
|
http.Error(w, "zertifikat-ID erforderlich", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
storedCert, err := ca.GetCertificate(certID)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := CertificateResponse{
|
||||||
|
ID: storedCert.ID,
|
||||||
|
Certificate: storedCert.PEM,
|
||||||
|
CreatedAt: storedCert.CreatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleGetRootCertificate gibt das Root-Zertifikat zurück
|
||||||
|
func (ca *CA) handleGetRootCertificate(w http.ResponseWriter, r *http.Request) {
|
||||||
|
rootCert := ca.GetRootCertificate()
|
||||||
|
w.Header().Set("Content-Type", "application/x-pem-file")
|
||||||
|
w.Write([]byte(rootCert))
|
||||||
|
}
|
||||||
|
|
||||||
|
// responseWriter ist ein Wrapper für http.ResponseWriter, um den Status Code zu erfassen
|
||||||
|
type responseWriter struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
statusCode int
|
||||||
|
bytesWritten int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func newResponseWriter(w http.ResponseWriter) *responseWriter {
|
||||||
|
return &responseWriter{w, http.StatusOK, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *responseWriter) WriteHeader(code int) {
|
||||||
|
rw.statusCode = code
|
||||||
|
rw.ResponseWriter.WriteHeader(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *responseWriter) Write(b []byte) (int, error) {
|
||||||
|
n, err := rw.ResponseWriter.Write(b)
|
||||||
|
rw.bytesWritten += int64(n)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// loggingMiddleware loggt alle HTTP-Requests mit Details
|
||||||
|
func loggingMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
// ResponseWriter wrappen, um Status Code zu erfassen
|
||||||
|
wrapped := newResponseWriter(w)
|
||||||
|
|
||||||
|
// Request verarbeiten
|
||||||
|
next.ServeHTTP(wrapped, r)
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
duration := time.Since(start)
|
||||||
|
|
||||||
|
// Farbige Ausgabe für bessere Lesbarkeit
|
||||||
|
statusColor := ""
|
||||||
|
statusReset := ""
|
||||||
|
|
||||||
|
if wrapped.statusCode >= 200 && wrapped.statusCode < 300 {
|
||||||
|
statusColor = "\033[32m" // Grün für Erfolg
|
||||||
|
statusReset = "\033[0m"
|
||||||
|
} else if wrapped.statusCode >= 400 && wrapped.statusCode < 500 {
|
||||||
|
statusColor = "\033[33m" // Gelb für Client-Fehler
|
||||||
|
statusReset = "\033[0m"
|
||||||
|
} else if wrapped.statusCode >= 500 {
|
||||||
|
statusColor = "\033[31m" // Rot für Server-Fehler
|
||||||
|
statusReset = "\033[0m"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log-Format: [Zeit] METHOD Pfad Status Dauer Größe IP User-Agent
|
||||||
|
log.Printf("[%s] %s %s %s%d%s %v %d bytes %s %s",
|
||||||
|
start.Format("2006-01-02 15:04:05"),
|
||||||
|
r.Method,
|
||||||
|
r.URL.Path,
|
||||||
|
statusColor,
|
||||||
|
wrapped.statusCode,
|
||||||
|
statusReset,
|
||||||
|
duration,
|
||||||
|
wrapped.bytesWritten,
|
||||||
|
r.RemoteAddr,
|
||||||
|
r.UserAgent(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bei POST-Requests auch Query-Parameter und Content-Length loggen
|
||||||
|
if r.Method == "POST" {
|
||||||
|
if r.URL.RawQuery != "" {
|
||||||
|
log.Printf(" Query: %s", r.URL.RawQuery)
|
||||||
|
}
|
||||||
|
if r.ContentLength > 0 {
|
||||||
|
log.Printf(" Content-Length: %d bytes", r.ContentLength)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// CA initialisieren
|
||||||
|
ca, err := NewCA()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("fehler beim Initialisieren der CA: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Router erstellen
|
||||||
|
r := mux.NewRouter()
|
||||||
|
|
||||||
|
// API-Endpunkte
|
||||||
|
r.HandleFunc("/csr", ca.handleSubmitCSR).Methods("POST")
|
||||||
|
r.HandleFunc("/certificate/{id}", ca.handleGetCertificate).Methods("GET")
|
||||||
|
r.HandleFunc("/root", ca.handleGetRootCertificate).Methods("GET")
|
||||||
|
|
||||||
|
// Health-Check
|
||||||
|
r.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
|
||||||
|
}).Methods("GET")
|
||||||
|
|
||||||
|
// Logging-Middleware auf alle Routen anwenden
|
||||||
|
loggedRouter := loggingMiddleware(r)
|
||||||
|
|
||||||
|
// Logger konfigurieren für bessere Ausgabe
|
||||||
|
log.SetOutput(os.Stdout)
|
||||||
|
log.SetFlags(0) // Keine Standard-Präfixe, wir formatieren selbst
|
||||||
|
|
||||||
|
log.Println("========================================")
|
||||||
|
log.Println("Dummy CA Server startet auf Port 8088")
|
||||||
|
log.Println("========================================")
|
||||||
|
log.Println("Endpunkte:")
|
||||||
|
log.Println(" POST /csr - CSR einreichen und signieren")
|
||||||
|
log.Println(" GET /certificate/{id} - Zertifikat abrufen")
|
||||||
|
log.Println(" GET /root - Root-Zertifikat abrufen")
|
||||||
|
log.Println(" GET /health - Health-Check")
|
||||||
|
log.Println("")
|
||||||
|
log.Println("Alle API-Anfragen werden geloggt...")
|
||||||
|
log.Println("========================================")
|
||||||
|
log.Println("")
|
||||||
|
|
||||||
|
if err := http.ListenAndServe(":8088", loggedRouter); err != nil {
|
||||||
|
log.Fatalf("fehler beim Starten des Servers: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user