We zullen een Certificate Transparency log systeem bouwen voor interne PKI's met behulp van Kafka als een berichtenwachtrij en Trillian als een alleen-toevoegbare Merkle-boom. Deze opzet maakt efficiënte controle van certificaatuitgiftes mogelijk, detectie van verkeerd uitgegeven certificaten en consistentiecontroles op basis van geruchten.

Waarom Zich Bezighouden met Interne CT Logs?

Voordat we in de technische details duiken, laten we de olifant in de kamer aanpakken: Waarom zouden we ons druk maken over het implementeren van CT logs voor interne PKI's?

  • Detecteer ongeautoriseerde certificaatuitgiftes
  • Zorg voor naleving van interne beleidsregels
  • Verbeter de mogelijkheden voor incidentrespons
  • Verhoog de algehele beveiligingspositie

Zie het als een vertrouw-maar-verifieer aanpak voor je interne CA. Je vertrouwt je CA, maar je wilt ook dat het eerlijk blijft.

De Bouwstenen: Kafka en Trillian

Om ons interne CT log systeem te implementeren, gebruiken we twee krachtige tools:

1. Apache Kafka

Kafka zal dienen als onze berichtenwachtrij, die de hoge doorvoer van certificaatgegevens verwerkt. Het is als een lopende band voor je certificaten, die ervoor zorgt dat ze in volgorde en met hoge betrouwbaarheid worden verwerkt.

2. Trillian

Trillian, ontwikkeld door Google, is onze alleen-toevoegbare Merkle-boom implementatie. Het is de ruggengraat van onze CT log, die cryptografische garanties biedt voor de integriteit van de log en efficiënte bewijzen van opname mogelijk maakt.

Overzicht van de Architectuur

Laten we onze systeemarchitectuur opsplitsen:


+----------------+     +--------+     +----------+     +---------+
|  Internal CA   | --> | Kafka  | --> | Trillian | --> | Auditor |
+----------------+     +--------+     +----------+     +---------+
        |                                 |
        |                                 |
        v                                 v
+----------------+              +--------------------+
| Monitor/Alert  |              | Gossip Participants|
+----------------+              +--------------------+

1. De interne CA dient nieuw uitgegeven certificaten in bij Kafka.

2. Kafka zorgt voor geordende, betrouwbare levering aan Trillian.

3. Trillian voegt de certificaten toe aan zijn Merkle-boom.

4. Auditors kunnen de consistentie van de log verifiëren en verdachte certificaten controleren.

5. Monitoringsystemen waarschuwen bij eventuele afwijkingen.

6. Gossip-deelnemers zorgen voor de consistentie van de log over meerdere instanties.

Het Systeem Implementeren

Stap 1: Kafka Instellen

Laten we eerst onze Kafka-cluster opzetten. We gebruiken Docker voor eenvoud:


version: '3'
services:
  zookeeper:
    image: confluentinc/cp-zookeeper:latest
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000

  kafka:
    image: confluentinc/cp-kafka:latest
    depends_on:
      - zookeeper
    ports:
      - 9092:9092
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1

Voer dit uit met docker-compose up -d, en je hebt een Kafka-cluster klaar om certificaten te verwerken.

Stap 2: Trillian Configureren

Nu gaan we Trillian instellen. We moeten het vanuit de bron compileren:


git clone https://github.com/google/trillian.git
cd trillian
go build ./cmd/trillian_log_server
go build ./cmd/trillian_log_signer

Maak een MySQL-database voor Trillian aan:


CREATE DATABASE trillian;

Initialiseer het databaseschema:


mysql -u root -p trillian < storage/mysql/schema/storage.sql

Start nu de Trillian log server en ondertekenaar:


./trillian_log_server --logtostderr ...
./trillian_log_signer --logtostderr ...

Stap 3: De Certificaataangever Implementeren

We hebben een component nodig om certificaten van onze interne CA naar Kafka te sturen. Hier is een eenvoudige Go-implementatie:


package main

import (
	"context"
	"crypto/x509"
	"encoding/pem"
	"github.com/segmentio/kafka-go"
)

func submitCertificate(cert *x509.Certificate) error {
	w := kafka.NewWriter(kafka.WriterConfig{
		Brokers: []string{"localhost:9092"},
		Topic:   "ct-log-entries",
	})

	pemCert := pem.EncodeToMemory(&pem.Block{
		Type:  "CERTIFICATE",
		Bytes: cert.Raw,
	})

	return w.WriteMessages(context.Background(),
		kafka.Message{
			Key:   []byte(cert.SerialNumber.String()),
			Value: pemCert,
		},
	)
}

Stap 4: Certificaten Verwerken met Trillian

Nu moeten we certificaten van Kafka consumeren en toevoegen aan Trillian:


package main

import (
	"context"
	"github.com/google/trillian"
	"github.com/segmentio/kafka-go"
)

func processCertificates(logID int64) {
	r := kafka.NewReader(kafka.ReaderConfig{
		Brokers: []string{"localhost:9092"},
		Topic:   "ct-log-entries",
		GroupID: "trillian-processor",
	})

	client, err := trillian.NewTrillianLogClient(...)
	if err != nil {
		// Handle error
	}

	for {
		msg, err := r.ReadMessage(context.Background())
		if err != nil {
			// Handle error
			continue
		}

		leaf := &trillian.LogLeaf{
			LeafValue: msg.Value,
		}

		_, err = client.QueueLeaf(context.Background(), &trillian.QueueLeafRequest{
			LogId: logID,
			Leaf:  leaf,
		})
		if err != nil {
			// Handle error
		}
	}
}

Implementeren van Consistentie op Basis van Geruchten

Om de consistentie van onze CT log over meerdere instanties te waarborgen, implementeren we een geruchtenprotocol. Dit stelt verschillende loginstanties in staat om hun weergaven van de log te vergelijken en eventuele discrepanties te detecteren.

Overzicht van het Geruchtenprotocol

  1. Elke loginstantie stuurt periodiek zijn laatste Signed Tree Head (STH) naar een set peers.
  2. Peers vergelijken de ontvangen STH met hun eigen.
  3. Als er verschillen worden gedetecteerd, vragen peers om en verifiëren ze consistentiebewijzen.
  4. Eventuele inconsistenties activeren waarschuwingen voor verder onderzoek.

Hier is een basisimplementatie van het geruchtenprotocol:


package main

import (
	"context"
	"github.com/google/trillian"
	"github.com/google/trillian/client"
	"time"
)

type GossipParticipant struct {
	LogID     int64
	Client    trillian.TrillianLogClient
	Verifier  *client.LogVerifier
	Peers     []string
}

func (g *GossipParticipant) RunGossip() {
	ticker := time.NewTicker(5 * time.Minute)
	for range ticker.C {
		g.gossipRound()
	}
}

func (g *GossipParticipant) gossipRound() {
	ctx := context.Background()
	sth, err := g.Client.GetLatestSignedLogRoot(ctx, &trillian.GetLatestSignedLogRootRequest{LogId: g.LogID})
	if err != nil {
		// Handle error
		return
	}

	for _, peer := range g.Peers {
		peerSTH := getPeerSTH(peer) // Implementeer deze functie om STH van een peer te krijgen
		if !g.Verifier.VerifyRoot(sth.SignedLogRoot, peerSTH.SignedLogRoot) {
			// STH's komen niet overeen, vraag consistentiebewijs aan
			proof, err := g.Client.GetConsistencyProof(ctx, &trillian.GetConsistencyProofRequest{
				LogId:          g.LogID,
				FirstTreeSize:  peerSTH.TreeSize,
				SecondTreeSize: sth.TreeSize,
			})
			if err != nil {
				// Handle error
				continue
			}

			// Verifieer het consistentiebewijs
			if !g.Verifier.VerifyConsistencyProof(proof) {
				// Inconsistentie gedetecteerd! Waarschuwing geven
				raiseInconsistencyAlert(g.LogID, peer)
			}
		}
	}
}

func raiseInconsistencyAlert(logID int64, peer string) {
	// Implementeer waarschuwingsmechanisme (bijv. stuur e-mail, activeer incidentrespons)
}

Auditing en Monitoring

Met ons CT log systeem op zijn plaats, moeten we auditing en monitoring implementeren om verdachte activiteiten of inconsistenties te detecteren.

Een Auditor Implementeren

De taak van de auditor is om periodiek de log te controleren op certificaten die het beleid schenden of verdacht lijken. Hier is een basisimplementatie:


package main

import (
	"context"
	"crypto/x509"
	"encoding/pem"
	"github.com/google/trillian"
	"time"
)

type Auditor struct {
	LogID  int64
	Client trillian.TrillianLogClient
}

func (a *Auditor) AuditLog() {
	ticker := time.NewTicker(1 * time.Hour)
	for range ticker.C {
		a.auditRound()
	}
}

func (a *Auditor) auditRound() {
	ctx := context.Background()
	leaves, err := a.Client.GetLeavesByRange(ctx, &trillian.GetLeavesByRangeRequest{
		LogId: a.LogID,
		StartIndex: 0,
		Count: 1000, // Pas aan indien nodig
	})
	if err != nil {
		// Handle error
		return
	}

	for _, leaf := range leaves.Leaves {
		block, _ := pem.Decode(leaf.LeafValue)
		if block == nil {
			// Handle error
			continue
		}

		cert, err := x509.ParseCertificate(block.Bytes)
		if err != nil {
			// Handle error
			continue
		}

		if isSuspiciousCertificate(cert) {
			raiseSuspiciousCertificateAlert(cert)
		}
	}
}

func isSuspiciousCertificate(cert *x509.Certificate) bool {
	// Implementeer controles voor verdachte certificaten
	// Bijvoorbeeld:
	// - Onverwachte uitgevers
	// - Ongebruikelijke geldigheidsperioden
	// - Verboden sleutelgebruiken
	// - Onverwachte SAN's
	return false
}

func raiseSuspiciousCertificateAlert(cert *x509.Certificate) {
	// Implementeer waarschuwingsmechanisme voor verdachte certificaten
}

Monitoring en Waarschuwen

Om de gezondheid en prestaties van ons CT log systeem in de gaten te houden, moeten we uitgebreide monitoring en waarschuwingen implementeren. Hier zijn enkele belangrijke statistieken om bij te houden:

  • Loggrootte en groeisnelheid
  • Latentie van certificaatindieningen
  • Foutpercentages bij certificaatverwerking
  • Consistentiecontroles van het geruchtenprotocol
  • Bevindingen en waarschuwingen van de auditor

Je kunt tools zoals Prometheus en Grafana gebruiken om deze statistieken te verzamelen en te visualiseren. Hier is een voorbeeld van hoe je enkele basisstatistieken kunt blootstellen met behulp van de Prometheus-clientbibliotheek voor Go:


package main

import (
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
)

var (
	certificatesProcessed = promauto.NewCounter(prometheus.CounterOpts{
		Name: "ct_log_certificates_processed_total",
		Help: "Het totale aantal verwerkte certificaten",
	})

	processingLatency = promauto.NewHistogram(prometheus.HistogramOpts{
		Name: "ct_log_processing_latency_seconds",
		Help: "De latentie van het verwerken van certificaten",
		Buckets: prometheus.DefBuckets,
	})

	gossipInconsistencies = promauto.NewCounter(prometheus.CounterOpts{
		Name: "ct_log_gossip_inconsistencies_total",
		Help: "Het totale aantal gedetecteerde inconsistenties in geruchten",
	})
)

// Gebruik deze statistieken in je code:
// certificatesProcessed.Inc()
// processingLatency.Observe(duration.Seconds())
// gossipInconsistencies.Inc()

Conclusie: Vertrouw, maar Verifieer (en Log)

Het implementeren van een Certificate Transparency log voor je interne PKI lijkt misschien overdreven op het eerste gezicht. Maar in de wereld van cybersecurity, waar vertrouwen van het grootste belang is en de gevolgen van een inbreuk catastrofaal kunnen zijn, is het een kleine prijs voor gemoedsrust.

Door de kracht van Kafka te benutten voor verwerking van berichten met hoge doorvoer en Trillian voor cryptografische integriteit, hebben we een robuust systeem gecreëerd dat kan:

  • Snel ongeautoriseerde of verkeerd uitgegeven certificaten detecteren
  • Een onveranderlijk auditspoor van alle certificaatuitgiftes bieden
  • Consistentie over meerdere loginstanties waarborgen via geruchtenprotocollen
  • Proactieve monitoring en waarschuwingen voor verdachte activiteiten mogelijk maken

Onthoud, in het domein van PKI is vertrouwen goed, maar verificatie beter. Door dit interne CT log systeem te implementeren, verbeter je niet alleen je beveiligingspositie; je bouwt een fundament van verifieerbaar vertrouwen dat de toets van audits en de tand des tijds kan doorstaan.

"In God we trust. All others must bring data." - W. Edwards Deming

Ga nu en log die certificaten! Je toekomstige zelf (en je auditors) zullen je dankbaar zijn.

Verder Lezen