Aller au contenu
servercert

servercert — d'où vient le certificat TLS d'un démon

go-authn/servercert sert un certificat de serveur TLS issu de fichiers relus quand ils changent, ou d'une AC ACME via autocert, External Account Binding compris.

D’où vient le certificat de serveur TLS d’un démon ? Un certificat et une clé sur disque, relus quand ils changent, ou une AC (autorité de certification) ACME via autocert, External Account Binding compris.

go get github.com/go-authn/servercert@v0.5.2

Une Config nomme exactement une source — CertFile et KeyFile, ou ACME — et New renvoie une *Source dont TLSConfig() la sert, avec MinVersion TLS 1.2.

Exemple

Depuis des fichiers :

package main

import (
	"log"
	"net/http"

	"github.com/go-authn/servercert"
)

func main() {
	src, err := servercert.New(servercert.Config{
		CertFile: "/etc/fileshare/tls/fullchain.pem",
		KeyFile:  "/etc/fileshare/tls/privkey.pem",
		OnError:  func(err error) { log.Print(err) }, // a renewal that did not load
	})
	if err != nil {
		log.Fatal(err) // unreadable, unparsable, or a key that is not the certificate's
	}
	srv := &http.Server{Addr: ":443", TLSConfig: src.TLSConfig()}
	log.Fatal(srv.ListenAndServeTLS("", ""))
}

Config.Check refuse une configuration qui ne peut pas fonctionner, et ne touche à rien — aucun fichier lu, aucun répertoire créé :

package main

import (
	"fmt"

	"github.com/go-authn/servercert"
)

func main() {
	// Check touches nothing: no file is read, no directory created.
	for _, c := range []servercert.Config{
		{ACME: &servercert.ACME{Domains: []string{"*.example.org"}, CacheDir: "/var/lib/fileshare/acme"}},
		{ACME: &servercert.ACME{Domains: []string{"192.0.2.10"}, CacheDir: "/var/lib/fileshare/acme"}},
		{ACME: &servercert.ACME{Domains: []string{"files.example.org"}, CacheDir: "acme"}},
		{CertFile: "/etc/fileshare/tls/fullchain.pem"},
		{ACME: &servercert.ACME{Domains: []string{"files.example.org"}, CacheDir: "/var/lib/fileshare/acme",
			DirectoryURL: "http://acme.example.org/directory"}},
		{ACME: &servercert.ACME{Domains: []string{"files.example.org"}, CacheDir: "/var/lib/fileshare/acme"}},
	} {
		fmt.Println(c.Check())
	}
}
servercert: ACME domain "*.example.org" is a wildcard; that needs dns-01, which autocert does not do
servercert: ACME domain "192.0.2.10" is an IP address; autocert issues only for DNS names
servercert: ACME.CacheDir "acme" is not an absolute path
servercert: CertFile and KeyFile go together; one of them is empty
servercert: ACME.DirectoryURL "http://acme.example.org/directory" is not an https URL (RFC 8555 §6.1)
<nil>

La dernière est la production de Let’s Encrypt : DirectoryURL vide.

Fichiers

New lit la paire et échoue tout de suite sur une paire illisible, qui ne s’analyse pas, ou dont la clé n’est pas celle du certificat. Ensuite, au plus une fois toutes les 10 secondes et pendant une poignée de main, les fichiers sont relus ; une paire modifiée est analysée sans tenir de verrou puis mise en place. Une paire modifiée qui ne se charge pas — le certificat écrit avant sa clé, un fichier tronqué — est signalée une fois à OnError, et la dernière bonne paire reste en service : une tâche de renouvellement qui écrit les deux fichiers l’un après l’autre ne fait donc jamais tomber le serveur.

ACME

autocert répond à deux types de défi et à aucun autre : tls-alpn-01 (RFC 8737, port 443, ALPN acme-tls/1) et, avec HTTPChallenge, http-01 (RFC 8555 §8.3, port 80, servi par Source.HTTPHandler). Il n’y a pas de dns-01, donc ni joker ni certificat pour une adresse IP.

Les certificats sont obtenus à la première poignée de main dont le SNI nomme l’un des Domains, ou avant tout client par Source.Prefetch, et renouvelés par autocert avant leur expiration. Un client qui se connecte par adresse IP n’envoie pas de SNI et reçoit le certificat du premier domaine.

Ajouter à NextProtos, ne jamais le remplacer

Avec ACME, TLSConfig().NextProtos contient acme-tls/1, sans quoi la validation tls-alpn-01 de l’AC échoue. Et il faut bien ajouter : un serveur TLS Go qui annonce des protocoles ALPN refuse un client dont l’offre n’en partage aucun ; un serveur NFS sur TLS ajoute donc sunrpc (RFC 9289). net/http ajoute http/1.1 et h2 de lui-même.

Une AC qui n’exige aucun défi. autocert rend la main aussitôt quand une nouvelle commande est déjà ready, ce que répond une AC aux domaines prévalidés : GÉANT TCS, servi par HARICA, pour un compte Enterprise Admin. Un serveur interne joignable depuis Internet ni sur le port 80 ni sur le 443 peut tout de même obtenir un certificat reconnu publiquement. La clé HMAC de l’EAB est un secret : la configuration nomme donc un fichier qui la contient (EABHMACKeyFile), jamais la clé.

Un défaut de x/crypto/acme est contourné (golang/go#77704) : après la finalisation d’une commande que l’AC traite encore, le client interroge l’URL de l’en-tête Location de la réponse de finalisation, que RFC 8555 n’y place pas. servercert donne au client ACME un transport qui ajoute cet en-tête, avec l’URL de la commande tirée de la réponse newOrder reçue plus tôt.

Ce qu’elle refuse, et pourquoi

Check refuse : aucune source ou les deux ; une moitié de paire de fichiers ; ACME sans domaines ou sans répertoire de cache ; un chemin relatif ; une moitié d’EAB ; un domaine qui est une adresse IP, un joker, une seule étiquette, se termine par un point, ou n’est pas un nom d’hôte valide ; une URL de répertoire qui n’est pas en https (RFC 8555 §6.1).

New refuse aussi un répertoire de cache — il contient des clés privées — qui est un lien symbolique, est accessible au groupe ou aux autres, appartient à un autre utilisateur que l’utilisateur effectif du processus, ou n’est pas accessible en écriture ; un répertoire absent est créé en 0700 puis vérifié. Chaque répertoire au-dessus ne doit être modifiable que par root ou par l’utilisateur effectif, comme le StrictModes de sshd l’exige du chemin vers authorized_keys, et un lien symbolique au-dessus est refusé sauf s’il appartient à root. Sous Windows, le mode et le propriétaire ne sont pas vérifiés — les ACL décident des deux, et les restreindre vous revient — et un lien symbolique n’importe où dans le chemin est refusé.

Contre quoi elle est vérifiée

pebble, l’AC de test de Let’s Encrypt, exécutée dans le processus à partir de ses paquets ca, db, va et wfe avec un bouchon DNS, délivrant de vrais certificats que crypto/tls vérifie contre la racine de pebble : tls-alpn-01 ; http-01 via HTTPHandler et son témoin avec HTTPChallenge désactivé ; EAB exigé, absent, faux, puis correct ; un domaine prévalidé délivré sans aucun port de défi ouvert, et son témoin. Un test exécute la même délivrance sans le contournement de golang/go#77704 et exige qu’elle échoue. Le plancher de couverture est de 100 %.

Référence

pkg.go.dev/github.com/go-authn/servercert@v0.5.2

Créée le par tannevaled

Modifiée le par tannevaled