mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-02 22:35:59 +00:00

For people who can't use LetsEncrypt because it's banned. Per https://github.com/tailscale/tailscale/issues/11776#issuecomment-2520955317 This does two things: 1) if you run derper with --certmode=manual and --hostname=$IP_ADDRESS we previously permitted, but now we also: * auto-generate the self-signed cert for you if it doesn't yet exist on disk * print out the derpmap configuration you need to use that self-signed cert 2) teaches derp/derphttp's derp dialer to verify the signature of self-signed TLS certs, if so declared in the existing DERPNode.CertName field, which previously existed for domain fronting, separating out the dial hostname from how certs are validates, so it's not overloaded much; that's what it was meant for. Fixes #11776 Change-Id: Ie72d12f209416bb7e8325fe0838cd2c66342c5cf Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
171 lines
4.2 KiB
Go
171 lines
4.2 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"math/big"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"tailscale.com/derp"
|
|
"tailscale.com/derp/derphttp"
|
|
"tailscale.com/net/netmon"
|
|
"tailscale.com/tailcfg"
|
|
"tailscale.com/types/key"
|
|
)
|
|
|
|
// Verify that in --certmode=manual mode, we can use a bare IP address
|
|
// as the --hostname and that GetCertificate will return it.
|
|
func TestCertIP(t *testing.T) {
|
|
dir := t.TempDir()
|
|
const hostname = "1.2.3.4"
|
|
|
|
priv, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
|
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ip := net.ParseIP(hostname)
|
|
if ip == nil {
|
|
t.Fatalf("invalid IP address %q", hostname)
|
|
}
|
|
template := &x509.Certificate{
|
|
SerialNumber: serialNumber,
|
|
Subject: pkix.Name{
|
|
Organization: []string{"Tailscale Test Corp"},
|
|
},
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().Add(30 * 24 * time.Hour),
|
|
|
|
KeyUsage: x509.KeyUsageDigitalSignature,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
BasicConstraintsValid: true,
|
|
IPAddresses: []net.IP{ip},
|
|
}
|
|
derBytes, err := x509.CreateCertificate(rand.Reader, template, template, &priv.PublicKey, priv)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
certOut, err := os.Create(filepath.Join(dir, hostname+".crt"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
|
|
t.Fatalf("Failed to write data to cert.pem: %v", err)
|
|
}
|
|
if err := certOut.Close(); err != nil {
|
|
t.Fatalf("Error closing cert.pem: %v", err)
|
|
}
|
|
|
|
keyOut, err := os.OpenFile(filepath.Join(dir, hostname+".key"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
|
|
if err != nil {
|
|
t.Fatalf("Unable to marshal private key: %v", err)
|
|
}
|
|
if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil {
|
|
t.Fatalf("Failed to write data to key.pem: %v", err)
|
|
}
|
|
if err := keyOut.Close(); err != nil {
|
|
t.Fatalf("Error closing key.pem: %v", err)
|
|
}
|
|
|
|
cp, err := certProviderByCertMode("manual", dir, hostname)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
back, err := cp.TLSConfig().GetCertificate(&tls.ClientHelloInfo{
|
|
ServerName: "", // no SNI
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("GetCertificate: %v", err)
|
|
}
|
|
if back == nil {
|
|
t.Fatalf("GetCertificate returned nil")
|
|
}
|
|
}
|
|
|
|
// Test that we can dial a raw IP without using a hostname and without a WebPKI
|
|
// cert, validating the cert against the signature of the cert in the DERP map's
|
|
// DERPNode.
|
|
//
|
|
// See https://github.com/tailscale/tailscale/issues/11776.
|
|
func TestPinnedCertRawIP(t *testing.T) {
|
|
td := t.TempDir()
|
|
cp, err := NewManualCertManager(td, "127.0.0.1")
|
|
if err != nil {
|
|
t.Fatalf("NewManualCertManager: %v", err)
|
|
}
|
|
|
|
cert, err := cp.TLSConfig().GetCertificate(&tls.ClientHelloInfo{
|
|
ServerName: "127.0.0.1",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("GetCertificate: %v", err)
|
|
}
|
|
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("Listen: %v", err)
|
|
}
|
|
defer ln.Close()
|
|
|
|
ds := derp.NewServer(key.NewNode(), t.Logf)
|
|
|
|
derpHandler := derphttp.Handler(ds)
|
|
mux := http.NewServeMux()
|
|
mux.Handle("/derp", derpHandler)
|
|
|
|
var hs http.Server
|
|
hs.Handler = mux
|
|
hs.TLSConfig = cp.TLSConfig()
|
|
go hs.ServeTLS(ln, "", "")
|
|
|
|
lnPort := ln.Addr().(*net.TCPAddr).Port
|
|
|
|
reg := &tailcfg.DERPRegion{
|
|
RegionID: 900,
|
|
Nodes: []*tailcfg.DERPNode{
|
|
{
|
|
RegionID: 900,
|
|
HostName: "127.0.0.1",
|
|
CertName: fmt.Sprintf("sha256-raw:%-02x", sha256.Sum256(cert.Leaf.Raw)),
|
|
DERPPort: lnPort,
|
|
},
|
|
},
|
|
}
|
|
|
|
netMon := netmon.NewStatic()
|
|
dc := derphttp.NewRegionClient(key.NewNode(), t.Logf, netMon, func() *tailcfg.DERPRegion {
|
|
return reg
|
|
})
|
|
defer dc.Close()
|
|
|
|
_, connClose, _, err := dc.DialRegionTLS(context.Background(), reg)
|
|
if err != nil {
|
|
t.Fatalf("DialRegionTLS: %v", err)
|
|
}
|
|
defer connClose.Close()
|
|
}
|