From 9e0dfa5506d978791d3f9489c1a1b7f9ccaa51f6 Mon Sep 17 00:00:00 2001 From: Ivan Valdes Date: Tue, 12 Dec 2023 14:41:13 -0800 Subject: [PATCH] client: implement TLS CRL tests Signed-off-by: Ivan Valdes --- client/pkg/transport/listener.go | 3 +- client/pkg/transport/listener_test.go | 147 ++++++++++++++++++++++++++ 2 files changed, 149 insertions(+), 1 deletion(-) diff --git a/client/pkg/transport/listener.go b/client/pkg/transport/listener.go index ec69f4e2ad3d..69c23e50d35a 100644 --- a/client/pkg/transport/listener.go +++ b/client/pkg/transport/listener.go @@ -264,9 +264,10 @@ func SelfCert(lg *zap.Logger, dirpath string, hosts []string, selfSignedCertVali NotBefore: time.Now(), NotAfter: time.Now().Add(time.Duration(selfSignedCertValidity) * 365 * (24 * time.Hour)), - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCRLSign, ExtKeyUsage: append([]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, additionalUsages...), BasicConstraintsValid: true, + IsCA: true, } if info.Logger != nil { diff --git a/client/pkg/transport/listener_test.go b/client/pkg/transport/listener_test.go index 836dca998f8e..f063ec5950a2 100644 --- a/client/pkg/transport/listener_test.go +++ b/client/pkg/transport/listener_test.go @@ -15,12 +15,17 @@ package transport import ( + "crypto/rand" "crypto/tls" "crypto/x509" + "encoding/pem" "errors" + "math/big" "net" "net/http" "os" + "path/filepath" + "sync" "testing" "time" @@ -573,3 +578,145 @@ func TestSocktOptsEmpty(t *testing.T) { } } } + +// TestNewListenerWithACRLFile tests when a revocation list is present. +func TestNewListenerWithACRLFile(t *testing.T) { + clientTLSInfo, err := createSelfCertEx(t, "127.0.0.1", x509.ExtKeyUsageClientAuth) + if err != nil { + t.Fatalf("unable to create client cert: %v", err) + } + + loadFileAsPEM := func(fileName string) []byte { + loaded, readErr := os.ReadFile(fileName) + if readErr != nil { + t.Fatalf("unable to read file %q: %v", fileName, readErr) + } + block, _ := pem.Decode(loaded) + return block.Bytes + } + + clientCert, err := x509.ParseCertificate(loadFileAsPEM(clientTLSInfo.CertFile)) + if err != nil { + t.Fatalf("unable to parse client cert: %v", err) + } + + tests := map[string]struct { + expectHandshakeError bool + revokedCertificateEntries []x509.RevocationListEntry + revocationListContents []byte + }{ + "empty revocation list": { + expectHandshakeError: false, + }, + "client cert is revoked": { + expectHandshakeError: true, + revokedCertificateEntries: []x509.RevocationListEntry{ + { + SerialNumber: clientCert.SerialNumber, + RevocationTime: time.Now(), + }, + }, + }, + "invalid CRL file content": { + expectHandshakeError: true, + revocationListContents: []byte("@invalidcontent"), + }, + } + + for testName, test := range tests { + t.Run(testName, func(t *testing.T) { + tmpdir := t.TempDir() + tlsInfo, err := createSelfCert(t) + if err != nil { + t.Fatalf("unable to create server cert: %v", err) + } + tlsInfo.TrustedCAFile = clientTLSInfo.CertFile + tlsInfo.CRLFile = filepath.Join(tmpdir, "revoked.r0") + + cert, err := x509.ParseCertificate(loadFileAsPEM(tlsInfo.CertFile)) + if err != nil { + t.Fatalf("unable to decode server cert: %v", err) + } + + key, err := x509.ParseECPrivateKey(loadFileAsPEM(tlsInfo.KeyFile)) + if err != nil { + t.Fatalf("unable to parse server key: %v", err) + } + + revocationListContents := test.revocationListContents + if len(revocationListContents) == 0 { + tmpl := &x509.RevocationList{ + RevokedCertificateEntries: test.revokedCertificateEntries, + ThisUpdate: time.Now(), + NextUpdate: time.Now().Add(time.Hour), + Number: big.NewInt(1), + } + revocationListContents, err = x509.CreateRevocationList(rand.Reader, tmpl, cert, key) + if err != nil { + t.Fatalf("unable to create revocation list: %v", err) + } + } + + if err = os.WriteFile(tlsInfo.CRLFile, revocationListContents, 0600); err != nil { + t.Fatalf("unable to write revocation list: %v", err) + } + + chHandshakeFailure := make(chan error, 1) + tlsInfo.HandshakeFailure = func(_ *tls.Conn, err error) { + if err != nil { + chHandshakeFailure <- err + } + } + + rootCAs := x509.NewCertPool() + rootCAs.AddCert(cert) + + clientCert, err := tls.LoadX509KeyPair(clientTLSInfo.CertFile, clientTLSInfo.KeyFile) + if err != nil { + t.Fatalf("unable to create peer cert: %v", err) + } + + ln, err := NewListener("127.0.0.1:0", "https", tlsInfo) + if err != nil { + t.Fatalf("unable to start listener: %v", err) + } + + tlsConfig := &tls.Config{} + tlsConfig.InsecureSkipVerify = false + tlsConfig.Certificates = []tls.Certificate{clientCert} + tlsConfig.RootCAs = rootCAs + + tr := &http.Transport{TLSClientConfig: tlsConfig} + cli := &http.Client{Transport: tr} + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + cli.Get("https://" + ln.Addr().String()) + }() + + chAcceptConn := make(chan net.Conn, 1) + go func() { + defer wg.Done() + conn, err := ln.Accept() + if err == nil { + chAcceptConn <- conn + } + }() + + select { + case err := <-chHandshakeFailure: + if !test.expectHandshakeError { + t.Errorf("expecting no handshake error, got: %v", err) + } + case conn := <-chAcceptConn: + if test.expectHandshakeError { + t.Errorf("expecting handshake error, got nothing") + } + conn.Close() + } + ln.Close() + wg.Wait() + }) + } +}