// 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() }