tailscale/tstest/tlstest/tlstest.go

168 lines
4.3 KiB
Go
Raw Normal View History

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Package tlstest contains code to help test Tailscale's client proxy support.
package tlstest
import (
"bytes"
"crypto/ecdsa"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
_ "embed"
"encoding/pem"
"fmt"
"math/big"
"sync"
"time"
)
// Some baked-in ECDSA keys to speed up tests, not having to burn CPU to
// generate them each time. We only make the certs (which have expiry times)
// at runtime.
//
// They were made with:
//
// openssl ecparam -name prime256v1 -genkey -noout -out root-ca.key
var (
//go:embed testdata/root-ca.key
rootCAKeyPEM []byte
// TestProxyServerKey is the PEM private key for [TestProxyServerCert].
//
//go:embed testdata/proxy.tstest.key
TestProxyServerKey []byte
// TestControlPlaneKey is the PEM private key for [TestControlPlaneCert].
//
//go:embed testdata/controlplane.tstest.key
TestControlPlaneKey []byte
)
// TestRootCA returns a self-signed ECDSA root CA certificate (as PEM) for
// testing purposes.
func TestRootCA() []byte {
return bytes.Clone(testRootCAOncer())
}
var testRootCAOncer = sync.OnceValue(func() []byte {
key := rootCAKey()
now := time.Now().Add(-time.Hour)
tpl := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
CommonName: "Tailscale Unit Test ECDSA Root",
Organization: []string{"Tailscale Test Org"},
},
NotBefore: now,
NotAfter: now.AddDate(5, 0, 0),
IsCA: true,
BasicConstraintsValid: true,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
SubjectKeyId: mustSKID(&key.PublicKey),
}
der, err := x509.CreateCertificate(rand.Reader, tpl, tpl, &key.PublicKey, key)
if err != nil {
panic(err)
}
return pemCert(der)
})
func pemCert(der []byte) []byte {
var buf bytes.Buffer
if err := pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: der}); err != nil {
panic(fmt.Sprintf("failed to encode PEM: %v", err))
}
return buf.Bytes()
}
var rootCAKey = sync.OnceValue(func() *ecdsa.PrivateKey {
return mustParsePEM(rootCAKeyPEM, x509.ParseECPrivateKey)
})
func mustParsePEM[T any](pemBytes []byte, parse func([]byte) (T, error)) T {
block, rest := pem.Decode(pemBytes)
if block == nil || len(rest) > 0 {
panic("invalid PEM")
}
v, err := parse(block.Bytes)
if err != nil {
panic(fmt.Sprintf("invalid PEM: %v", err))
}
return v
}
// KeyPair is a simple struct to hold a certificate and its private key.
type KeyPair struct {
Domain string
KeyPEM []byte // PEM-encoded private key
}
// ServerTLSConfig returns a TLS configuration suitable for a server
// using the KeyPair's certificate and private key.
func (p KeyPair) ServerTLSConfig() *tls.Config {
cert, err := tls.X509KeyPair(p.CertPEM(), p.KeyPEM)
if err != nil {
panic("invalid TLS key pair: " + err.Error())
}
return &tls.Config{
Certificates: []tls.Certificate{cert},
}
}
// ProxyServerKeyPair is a KeyPair for a test control plane server
// with domain name "proxy.tstest".
var ProxyServerKeyPair = KeyPair{
Domain: "proxy.tstest",
KeyPEM: TestProxyServerKey,
}
// ControlPlaneKeyPair is a KeyPair for a test control plane server
// with domain name "controlplane.tstest".
var ControlPlaneKeyPair = KeyPair{
Domain: "controlplane.tstest",
KeyPEM: TestControlPlaneKey,
}
func (p KeyPair) CertPEM() []byte {
caCert := mustParsePEM(TestRootCA(), x509.ParseCertificate)
caPriv := mustParsePEM(rootCAKeyPEM, x509.ParseECPrivateKey)
leafKey := mustParsePEM(p.KeyPEM, x509.ParseECPrivateKey)
serial, err := rand.Int(rand.Reader, big.NewInt(0).Lsh(big.NewInt(1), 128))
if err != nil {
panic(err)
}
now := time.Now().Add(-time.Hour)
tpl := &x509.Certificate{
SerialNumber: serial,
Subject: pkix.Name{CommonName: p.Domain},
NotBefore: now,
NotAfter: now.AddDate(2, 0, 0),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
DNSNames: []string{p.Domain},
}
der, err := x509.CreateCertificate(rand.Reader, tpl, caCert, &leafKey.PublicKey, caPriv)
if err != nil {
panic(err)
}
return pemCert(der)
}
func mustSKID(pub *ecdsa.PublicKey) []byte {
skid, err := x509.MarshalPKIXPublicKey(pub)
if err != nil {
panic(err)
}
return skid[:20] // same as x509 library
}