package ldap import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/pem" "math/big" "net" "os" "path/filepath" "testing" "time" ) // writeSelfSignedTLSMaterial creates a throwaway certificate and key // on disk for the TLS serve tests. func writeSelfSignedTLSMaterial(t *testing.T) (certPath, keyPath string) { t.Helper() key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { t.Fatalf("generating key: %s", err) } tmpl := &x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{CommonName: "ldap-bridge-test"}, NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().Add(time.Hour), } der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key) if err != nil { t.Fatalf("creating certificate: %s", err) } keyDER, err := x509.MarshalECPrivateKey(key) if err != nil { t.Fatalf("marshaling key: %s", err) } certPath = filepath.Join(t.TempDir(), "cert.pem") keyPath = filepath.Join(t.TempDir(), "key.pem") if err := os.WriteFile(certPath, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), 0600); err != nil { t.Fatalf("writing cert: %s", err) } if err := os.WriteFile(keyPath, pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER}), 0600); err != nil { t.Fatalf("writing key: %s", err) } return certPath, keyPath } // freeAddr reserves an ephemeral port on loopback and returns its // address. The listener is closed immediately; the caller is expected // to hand the address to the server under test. func freeAddr(t *testing.T) string { t.Helper() ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("reserving a port: %s", err) } addr := ln.Addr().String() if err := ln.Close(); err != nil { t.Fatalf("releasing port: %s", err) } return addr } func TestServeListensAndServeStops(t *testing.T) { s := New(WithLogger(testLogger()), WithNetAuth(&fakeClient{})) if err := s.SetDomain("example.com"); err != nil { t.Fatalf("SetDomain: %s", err) } addr := freeAddr(t) if err := s.Serve(addr); err != nil { t.Fatalf("Serve: %s", err) } // Closing the listener stops the serve loop. The embedded // server's own Stop cannot be used under -race because it // closes a done channel that the accept loop reads without a // synchronising edge. defer s.Listener.Close() conn, err := net.Dial("tcp", addr) if err != nil { t.Fatalf("dialing server: %s", err) } conn.Close() } func TestServeReportsBadAddress(t *testing.T) { s := New(WithLogger(testLogger()), WithNetAuth(&fakeClient{})) if err := s.Serve("256.256.256.256:0"); err == nil { t.Fatal("expected an error for an invalid bind address") } } func TestServeTLSRequiresUsableKeyPair(t *testing.T) { s := New(WithLogger(testLogger()), WithNetAuth(&fakeClient{})) if err := s.ServeTLS("127.0.0.1:0", "/nope/not-here.key", "/nope/not-here.crt"); err == nil { t.Fatal("expected an error for a missing key pair") } } // TestServeTLSCapableNegotiation checks that the bridge negotiates a // modern TLS version and refuses legacy ones, which the vendored // server made impossible by hard coding an SSL 3.0 floor. func TestServeTLSNegotiatesModernVersions(t *testing.T) { certPath, keyPath := writeSelfSignedTLSMaterial(t) s := New(WithLogger(testLogger()), WithNetAuth(&fakeClient{})) if err := s.SetDomain("example.com"); err != nil { t.Fatalf("SetDomain: %s", err) } addr := freeAddr(t) if err := s.ServeTLS(addr, keyPath, certPath); err != nil { t.Fatalf("ServeTLS: %s", err) } defer s.Listener.Close() conn, err := tls.Dial("tcp", addr, &tls.Config{ InsecureSkipVerify: true, MinVersion: tls.VersionTLS12, }) if err != nil { t.Fatalf("TLS 1.2 handshake: %s", err) } if v := conn.ConnectionState().Version; v < tls.VersionTLS12 { t.Errorf("negotiated TLS version %x, want >= TLS 1.2", v) } conn.Close() for _, ver := range []uint16{tls.VersionSSL30, tls.VersionTLS10, tls.VersionTLS11} { conn, err := tls.Dial("tcp", addr, &tls.Config{ InsecureSkipVerify: true, MinVersion: ver, MaxVersion: ver, }) if err == nil { conn.Close() t.Errorf("legacy version %x was accepted, want rejection", ver) } } }