121 lines
4.1 KiB
Go
121 lines
4.1 KiB
Go
package tlsreload
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/pem"
|
|
"math/big"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestTrustProviderReloadsRootsAndKeepsLastKnownGoodBundle(t *testing.T) {
|
|
directory := t.TempDir()
|
|
trustPath := filepath.Join(directory, "ca.crt")
|
|
firstAuthority := newTestAuthority(t, "first")
|
|
firstServer := firstAuthority.server(t, "controller")
|
|
writeTrustBundle(t, trustPath, firstAuthority.certificate)
|
|
|
|
provider, err := NewTrustProvider(trustPath)
|
|
if err != nil {
|
|
t.Fatalf("NewTrustProvider() error = %v", err)
|
|
}
|
|
verify := provider.VerifyServer("controller")
|
|
if err := verify(tls.ConnectionState{PeerCertificates: []*x509.Certificate{firstServer}}); err != nil {
|
|
t.Fatalf("verify first server with first root: %v", err)
|
|
}
|
|
|
|
secondAuthority := newTestAuthority(t, "second")
|
|
secondServer := secondAuthority.server(t, "controller")
|
|
writeTrustBundle(t, trustPath, secondAuthority.certificate)
|
|
if err := verify(tls.ConnectionState{PeerCertificates: []*x509.Certificate{firstServer}}); err == nil {
|
|
t.Fatal("verify old server after trust rotation error = nil")
|
|
}
|
|
if err := verify(tls.ConnectionState{PeerCertificates: []*x509.Certificate{secondServer}}); err != nil {
|
|
t.Fatalf("verify second server with second root: %v", err)
|
|
}
|
|
|
|
if err := os.WriteFile(trustPath, []byte("incomplete"), 0o600); err != nil {
|
|
t.Fatalf("break trust bundle: %v", err)
|
|
}
|
|
if err := verify(tls.ConnectionState{PeerCertificates: []*x509.Certificate{secondServer}}); err != nil {
|
|
t.Fatalf("verify with last known good trust bundle: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTrustProviderRejectsEmptyPath(t *testing.T) {
|
|
if _, err := NewTrustProvider(""); err != ErrInvalidTrustPath {
|
|
t.Fatalf("NewTrustProvider(empty path) error = %v, want %v", err, ErrInvalidTrustPath)
|
|
}
|
|
}
|
|
|
|
type testAuthority struct {
|
|
certificate *x509.Certificate
|
|
privateKey *ecdsa.PrivateKey
|
|
}
|
|
|
|
func newTestAuthority(t *testing.T, commonName string) testAuthority {
|
|
t.Helper()
|
|
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
if err != nil {
|
|
t.Fatalf("generate authority key: %v", err)
|
|
}
|
|
now := time.Now()
|
|
template := &x509.Certificate{
|
|
SerialNumber: big.NewInt(now.UnixNano()), Subject: pkix.Name{CommonName: commonName},
|
|
NotBefore: now.Add(-time.Minute), NotAfter: now.Add(time.Hour),
|
|
IsCA: true, BasicConstraintsValid: true,
|
|
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,
|
|
}
|
|
der, err := x509.CreateCertificate(rand.Reader, template, template, &privateKey.PublicKey, privateKey)
|
|
if err != nil {
|
|
t.Fatalf("create authority certificate: %v", err)
|
|
}
|
|
certificate, err := x509.ParseCertificate(der)
|
|
if err != nil {
|
|
t.Fatalf("parse authority certificate: %v", err)
|
|
}
|
|
return testAuthority{certificate: certificate, privateKey: privateKey}
|
|
}
|
|
|
|
func (authority testAuthority) server(t *testing.T, serverName string) *x509.Certificate {
|
|
t.Helper()
|
|
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
if err != nil {
|
|
t.Fatalf("generate server key: %v", err)
|
|
}
|
|
now := time.Now()
|
|
template := &x509.Certificate{
|
|
SerialNumber: big.NewInt(now.UnixNano()), Subject: pkix.Name{CommonName: serverName},
|
|
DNSNames: []string{serverName}, NotBefore: now.Add(-time.Minute), NotAfter: now.Add(time.Hour),
|
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
}
|
|
der, err := x509.CreateCertificate(rand.Reader, template, authority.certificate, &privateKey.PublicKey, authority.privateKey)
|
|
if err != nil {
|
|
t.Fatalf("create server certificate: %v", err)
|
|
}
|
|
certificate, err := x509.ParseCertificate(der)
|
|
if err != nil {
|
|
t.Fatalf("parse server certificate: %v", err)
|
|
}
|
|
return certificate
|
|
}
|
|
|
|
func writeTrustBundle(t *testing.T, path string, certificate *x509.Certificate) {
|
|
t.Helper()
|
|
if certificate == nil {
|
|
t.Fatal("write trust bundle: certificate is nil")
|
|
}
|
|
payload := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certificate.Raw})
|
|
if err := os.WriteFile(path, payload, 0o600); err != nil {
|
|
t.Fatalf("write trust bundle: %v", err)
|
|
}
|
|
}
|