mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-11-04 00:55:11 +00:00 
			
		
		
		
	So we can probe load balancers by their unique DNS name but without asking for that cert name. Updates tailscale/corp#13050 Change-Id: Ie4c0a2f951328df64281ed1602b4e624e3c8cf2e Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
		
			
				
	
	
		
			236 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			236 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright (c) Tailscale Inc & AUTHORS
 | 
						|
// SPDX-License-Identifier: BSD-3-Clause
 | 
						|
 | 
						|
package prober
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"context"
 | 
						|
	"crypto"
 | 
						|
	"crypto/rand"
 | 
						|
	"crypto/rsa"
 | 
						|
	"crypto/tls"
 | 
						|
	"crypto/x509"
 | 
						|
	"crypto/x509/pkix"
 | 
						|
	"encoding/pem"
 | 
						|
	"math/big"
 | 
						|
	"net"
 | 
						|
	"net/http"
 | 
						|
	"net/http/httptest"
 | 
						|
	"strings"
 | 
						|
	"testing"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"golang.org/x/crypto/ocsp"
 | 
						|
)
 | 
						|
 | 
						|
var leafCert = x509.Certificate{
 | 
						|
	SerialNumber:       big.NewInt(10001),
 | 
						|
	Subject:            pkix.Name{CommonName: "tlsprobe.test"},
 | 
						|
	SignatureAlgorithm: x509.SHA256WithRSA,
 | 
						|
	PublicKeyAlgorithm: x509.RSA,
 | 
						|
	Version:            3,
 | 
						|
	IPAddresses:        []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback},
 | 
						|
	NotBefore:          time.Now().Add(-5 * time.Minute),
 | 
						|
	NotAfter:           time.Now().Add(60 * 24 * time.Hour),
 | 
						|
	SubjectKeyId:       []byte{1, 2, 3},
 | 
						|
	AuthorityKeyId:     []byte{1, 2, 3, 4, 5}, // issuerCert below
 | 
						|
	ExtKeyUsage:        []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
 | 
						|
	KeyUsage:           x509.KeyUsageDigitalSignature,
 | 
						|
}
 | 
						|
 | 
						|
var issuerCertTpl = x509.Certificate{
 | 
						|
	SerialNumber:       big.NewInt(10002),
 | 
						|
	Subject:            pkix.Name{CommonName: "tlsprobe.ca.test"},
 | 
						|
	SignatureAlgorithm: x509.SHA256WithRSA,
 | 
						|
	PublicKeyAlgorithm: x509.RSA,
 | 
						|
	Version:            3,
 | 
						|
	IPAddresses:        []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback},
 | 
						|
	NotBefore:          time.Now().Add(-5 * time.Minute),
 | 
						|
	NotAfter:           time.Now().Add(60 * 24 * time.Hour),
 | 
						|
	SubjectKeyId:       []byte{1, 2, 3, 4, 5},
 | 
						|
	ExtKeyUsage:        []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
 | 
						|
	KeyUsage:           x509.KeyUsageDigitalSignature,
 | 
						|
}
 | 
						|
 | 
						|
func simpleCert() (tls.Certificate, error) {
 | 
						|
	certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
 | 
						|
	if err != nil {
 | 
						|
		return tls.Certificate{}, err
 | 
						|
	}
 | 
						|
	certPrivKeyPEM := new(bytes.Buffer)
 | 
						|
	pem.Encode(certPrivKeyPEM, &pem.Block{
 | 
						|
		Type:  "RSA PRIVATE KEY",
 | 
						|
		Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey),
 | 
						|
	})
 | 
						|
	certBytes, err := x509.CreateCertificate(rand.Reader, &leafCert, &leafCert, &certPrivKey.PublicKey, certPrivKey)
 | 
						|
	if err != nil {
 | 
						|
		return tls.Certificate{}, err
 | 
						|
	}
 | 
						|
	certPEM := new(bytes.Buffer)
 | 
						|
	pem.Encode(certPEM, &pem.Block{
 | 
						|
		Type:  "CERTIFICATE",
 | 
						|
		Bytes: certBytes,
 | 
						|
	})
 | 
						|
	return tls.X509KeyPair(certPEM.Bytes(), certPrivKeyPEM.Bytes())
 | 
						|
}
 | 
						|
 | 
						|
func TestTLSConnection(t *testing.T) {
 | 
						|
	crt, err := simpleCert()
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
 | 
						|
	srv.TLS = &tls.Config{Certificates: []tls.Certificate{crt}}
 | 
						|
	srv.StartTLS()
 | 
						|
	defer srv.Close()
 | 
						|
 | 
						|
	err = probeTLS(context.Background(), "fail.example.com", srv.Listener.Addr().String())
 | 
						|
	// The specific error message here is platform-specific ("certificate is not trusted"
 | 
						|
	// on macOS and "certificate signed by unknown authority" on Linux), so only check
 | 
						|
	// that it contains the word 'certificate'.
 | 
						|
	if err == nil || !strings.Contains(err.Error(), "certificate") {
 | 
						|
		t.Errorf("unexpected error: %q", err)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestCertExpiration(t *testing.T) {
 | 
						|
	for _, tt := range []struct {
 | 
						|
		name    string
 | 
						|
		cert    func() *x509.Certificate
 | 
						|
		wantErr string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			"cert not valid yet",
 | 
						|
			func() *x509.Certificate {
 | 
						|
				c := leafCert
 | 
						|
				c.NotBefore = time.Now().Add(time.Hour)
 | 
						|
				return &c
 | 
						|
			},
 | 
						|
			"one of the certs has NotBefore in the future",
 | 
						|
		},
 | 
						|
		{
 | 
						|
			"cert expiring soon",
 | 
						|
			func() *x509.Certificate {
 | 
						|
				c := leafCert
 | 
						|
				c.NotAfter = time.Now().Add(time.Hour)
 | 
						|
				return &c
 | 
						|
			},
 | 
						|
			"one of the certs expires in",
 | 
						|
		},
 | 
						|
		{
 | 
						|
			"valid duration but no OCSP",
 | 
						|
			func() *x509.Certificate { return &leafCert },
 | 
						|
			"no OCSP server presented in leaf cert for CN=tlsprobe.test",
 | 
						|
		},
 | 
						|
	} {
 | 
						|
		t.Run(tt.name, func(t *testing.T) {
 | 
						|
			cs := &tls.ConnectionState{PeerCertificates: []*x509.Certificate{tt.cert()}}
 | 
						|
			err := validateConnState(context.Background(), cs)
 | 
						|
			if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
 | 
						|
				t.Errorf("unexpected error %q; want %q", err, tt.wantErr)
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type ocspServer struct {
 | 
						|
	issuer        *x509.Certificate
 | 
						|
	responderCert *x509.Certificate
 | 
						|
	template      *ocsp.Response
 | 
						|
	priv          crypto.Signer
 | 
						|
}
 | 
						|
 | 
						|
func (s *ocspServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
						|
	if s.template == nil {
 | 
						|
		w.WriteHeader(http.StatusInternalServerError)
 | 
						|
		return
 | 
						|
	}
 | 
						|
	resp, err := ocsp.CreateResponse(s.issuer, s.responderCert, *s.template, s.priv)
 | 
						|
	if err != nil {
 | 
						|
		panic(err)
 | 
						|
	}
 | 
						|
	w.Write(resp)
 | 
						|
}
 | 
						|
 | 
						|
func TestOCSP(t *testing.T) {
 | 
						|
	issuerKey, err := rsa.GenerateKey(rand.Reader, 4096)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	issuerBytes, err := x509.CreateCertificate(rand.Reader, &issuerCertTpl, &issuerCertTpl, &issuerKey.PublicKey, issuerKey)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	issuerCert, err := x509.ParseCertificate(issuerBytes)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	responderKey, err := rsa.GenerateKey(rand.Reader, 4096)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	// issuer cert template re-used here, but with a different key
 | 
						|
	responderBytes, err := x509.CreateCertificate(rand.Reader, &issuerCertTpl, &issuerCertTpl, &responderKey.PublicKey, responderKey)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	responderCert, err := x509.ParseCertificate(responderBytes)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	handler := &ocspServer{
 | 
						|
		issuer:        issuerCert,
 | 
						|
		responderCert: responderCert,
 | 
						|
		priv:          issuerKey,
 | 
						|
	}
 | 
						|
	srv := httptest.NewUnstartedServer(handler)
 | 
						|
	srv.Start()
 | 
						|
	defer srv.Close()
 | 
						|
 | 
						|
	cert := leafCert
 | 
						|
	cert.OCSPServer = append(cert.OCSPServer, srv.URL)
 | 
						|
	key, err := rsa.GenerateKey(rand.Reader, 4096)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	certBytes, err := x509.CreateCertificate(rand.Reader, &cert, issuerCert, &key.PublicKey, issuerKey)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	parsed, err := x509.ParseCertificate(certBytes)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tt := range []struct {
 | 
						|
		name    string
 | 
						|
		resp    *ocsp.Response
 | 
						|
		wantErr string
 | 
						|
	}{
 | 
						|
		{"good response", &ocsp.Response{Status: ocsp.Good}, ""},
 | 
						|
		{"unknown response", &ocsp.Response{Status: ocsp.Unknown}, "unknown OCSP verification status for CN=tlsprobe.test"},
 | 
						|
		{"revoked response", &ocsp.Response{Status: ocsp.Revoked}, "cert for CN=tlsprobe.test has been revoked"},
 | 
						|
		{"error 500 from ocsp", nil, "non-200 status code from OCSP"},
 | 
						|
	} {
 | 
						|
		t.Run(tt.name, func(t *testing.T) {
 | 
						|
			handler.template = tt.resp
 | 
						|
			if handler.template != nil {
 | 
						|
				handler.template.SerialNumber = big.NewInt(1337)
 | 
						|
			}
 | 
						|
			cs := &tls.ConnectionState{PeerCertificates: []*x509.Certificate{parsed, issuerCert}}
 | 
						|
			err := validateConnState(context.Background(), cs)
 | 
						|
 | 
						|
			if err == nil && tt.wantErr == "" {
 | 
						|
				return
 | 
						|
			}
 | 
						|
 | 
						|
			if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
 | 
						|
				t.Errorf("unexpected error %q; want %q", err, tt.wantErr)
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 |