// tlscert.go: the hub's self-signed TLS certificate for the peer link. // // There's no certificate authority that can issue for a Tailscale/LAN IP, // so this isn't meant to be CA-trusted — it's meant to be PINNED (see // pin.go). The hub generates one ed25519 keypair + cert on first run and // reuses it after that; the peer verifies the connection by comparing the // certificate's fingerprint against the one from the invite code, not by // checking who signed it. package main import ( "crypto/ed25519" "crypto/rand" "crypto/sha256" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/hex" "encoding/pem" "fmt" "math/big" "os" "path/filepath" "time" ) func hubCertPath() string { dir, ok := exeDir() if !ok { return "hub-cert.pem" } return filepath.Join(dir, "hub-cert.pem") } func hubKeyPath() string { dir, ok := exeDir() if !ok { return "hub-key.pem" } return filepath.Join(dir, "hub-key.pem") } // certFingerprint is the hex SHA-256 of the certificate's raw DER bytes — // what gets pinned on the peer side. func certFingerprint(der []byte) string { sum := sha256.Sum256(der) return hex.EncodeToString(sum[:]) } // generateHubCert creates a fresh ed25519 keypair and a self-signed // certificate around it. No SANs: nothing ever validates a hostname // against this cert, pinning replaces that entirely. func generateHubCert() (certDER, keyDER []byte, err error) { pub, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { return nil, nil, fmt.Errorf("couldn't generate a keypair: %w", err) } serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) if err != nil { return nil, nil, fmt.Errorf("couldn't generate a serial number: %w", err) } tmpl := &x509.Certificate{ SerialNumber: serial, Subject: pkix.Name{CommonName: "deathwatch-hub"}, NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().AddDate(10, 0, 0), KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, } certDER, err = x509.CreateCertificate(rand.Reader, tmpl, tmpl, pub, priv) if err != nil { return nil, nil, fmt.Errorf("couldn't create the certificate: %w", err) } keyDER, err = x509.MarshalPKCS8PrivateKey(priv) if err != nil { return nil, nil, fmt.Errorf("couldn't encode the private key: %w", err) } return certDER, keyDER, nil } func writePEM(path, blockType string, der []byte) error { return os.WriteFile(path, pem.EncodeToMemory(&pem.Block{Type: blockType, Bytes: der}), 0600) } func readPEM(path, wantType string) ([]byte, error) { data, err := os.ReadFile(path) if err != nil { return nil, err } block, _ := pem.Decode(data) if block == nil || block.Type != wantType { return nil, fmt.Errorf("%s doesn't look like a valid %s", path, wantType) } return block.Bytes, nil } // loadOrCreateHubCert loads the hub's certificate and key if both are // already on disk, or generates and persists a new pair if neither is. // A partial pair (one file present, one missing, or one that doesn't // parse) is treated as a broken installation, not something to silently // regenerate around: delete both and restart to get a fresh pair. func loadOrCreateHubCert() (cert tls.Certificate, fingerprint string, generated bool, err error) { certPath, keyPath := hubCertPath(), hubKeyPath() certDER, certErr := readPEM(certPath, "CERTIFICATE") keyDER, keyErr := readPEM(keyPath, "PRIVATE KEY") switch { case certErr == nil && keyErr == nil: // both present, fall through to build the tls.Certificate below case os.IsNotExist(certErr) && os.IsNotExist(keyErr): certDER, keyDER, err = generateHubCert() if err != nil { return tls.Certificate{}, "", false, err } if err := writePEM(certPath, "CERTIFICATE", certDER); err != nil { return tls.Certificate{}, "", false, fmt.Errorf("couldn't save %s: %w", certPath, err) } if err := writePEM(keyPath, "PRIVATE KEY", keyDER); err != nil { return tls.Certificate{}, "", false, fmt.Errorf("couldn't save %s: %w", keyPath, err) } generated = true default: return tls.Certificate{}, "", false, fmt.Errorf( "%s and %s should both exist or both be missing (got cert: %v, key: %v) — delete both to generate a fresh pair", certPath, keyPath, certErr, keyErr) } priv, err := x509.ParsePKCS8PrivateKey(keyDER) if err != nil { return tls.Certificate{}, "", false, fmt.Errorf("%s doesn't parse as a private key: %w", keyPath, err) } leaf, err := x509.ParseCertificate(certDER) if err != nil { return tls.Certificate{}, "", false, fmt.Errorf("%s doesn't parse as a certificate: %w", certPath, err) } cert = tls.Certificate{ Certificate: [][]byte{certDER}, PrivateKey: priv, Leaf: leaf, } return cert, certFingerprint(certDER), generated, nil }