mirror of
https://github.com/tailscale/tailscale.git
synced 2025-06-08 16:58:35 +00:00

This fixes the implementation and test from #15208 which apparently never worked. Ignore the metacert when counting the number of expected certs presented. And fix the test, pulling out the TLSConfig setup code into something shared between the real cmd/derper and the test. Fixes #15579 Change-Id: I90526e38e59f89b480629b415f00587b107de10a Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
172 lines
4.2 KiB
Go
172 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()
|
|
ds.ModifyTLSConfigToAddMetaCert(hs.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()
|
|
}
|