package tls import ( "crypto/tls" "crypto/x509" "fmt" "log/slog" "net" "os" "path/filepath" "time" ) // certDir returns the directory for storing certificates. func certDir() string { home, _ := os.UserHomeDir() dir := filepath.Join(home, ".voicepaste", "certs") os.MkdirAll(dir, 0700) return dir } // GetTLSConfig returns a tls.Config for the given LAN IP. // It tries to load cached self-signed certs, or generates new ones. func GetTLSConfig(lanIP string) (*tls.Config, error) { dir := certDir() certFile := filepath.Join(dir, "cert.pem") keyFile := filepath.Join(dir, "key.pem") // Try loading existing cert if cert, err := tls.LoadX509KeyPair(certFile, keyFile); err == nil { // Check if cert covers this IP and is not expired if leaf, err := x509.ParseCertificate(cert.Certificate[0]); err == nil { if time.Now().Before(leaf.NotAfter) && certCoversIP(leaf, lanIP) { slog.Info("using cached TLS certificate", "expires", leaf.NotAfter.Format("2006-01-02")) return &tls.Config{Certificates: []tls.Certificate{cert}}, nil } } } // Generate new self-signed cert slog.Info("generating self-signed TLS certificate", "ip", lanIP) cert, err := generateSelfSigned(lanIP, certFile, keyFile) if err != nil { return nil, fmt.Errorf("generate TLS cert: %w", err) } return &tls.Config{Certificates: []tls.Certificate{cert}}, nil } // certCoversIP checks if the certificate covers the given IP. func certCoversIP(cert *x509.Certificate, ip string) bool { target := net.ParseIP(ip) if target == nil { return false } for _, certIP := range cert.IPAddresses { if certIP.Equal(target) { return true } } return false }