diff --git a/pkg/busauth/tls.go b/pkg/busauth/tls.go new file mode 100644 index 0000000..47e4fcf --- /dev/null +++ b/pkg/busauth/tls.go @@ -0,0 +1,37 @@ +package busauth + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "os" +) + +// LoadCATLSConfig builds a *tls.Config that trusts ONLY the given CA certificate +// (PEM file), for a bus client pinning the project's self-signed CA. Because the +// bus uses a private CA rather than a public one, clients must pin it explicitly; +// trusting the system roots would reject the server cert. This is the single +// helper every client (Go peers, the mobile binding, the gateway) uses to turn a +// ca.crt path into a connection config. +func LoadCATLSConfig(caPEMPath string) (*tls.Config, error) { + pem, err := os.ReadFile(caPEMPath) + if err != nil { + return nil, fmt.Errorf("busauth: read CA %q: %w", caPEMPath, err) + } + pool := x509.NewCertPool() + if !pool.AppendCertsFromPEM(pem) { + return nil, fmt.Errorf("busauth: CA %q contains no valid PEM certificate", caPEMPath) + } + return &tls.Config{RootCAs: pool, MinVersion: tls.VersionTLS12}, nil +} + +// ServerTLSConfig loads the bus NATS server's certificate and private key (PEM +// files) into a *tls.Config to present to clients. The private key never leaves +// the host; only the CA cert travels to clients. +func ServerTLSConfig(certPEMPath, keyPEMPath string) (*tls.Config, error) { + cert, err := tls.LoadX509KeyPair(certPEMPath, keyPEMPath) + if err != nil { + return nil, fmt.Errorf("busauth: load server keypair: %w", err) + } + return &tls.Config{Certificates: []tls.Certificate{cert}, MinVersion: tls.VersionTLS12}, nil +} diff --git a/pkg/client/client.go b/pkg/client/client.go index 3d84db2..8e3a878 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -16,6 +16,7 @@ import ( "bytes" "context" "crypto/rand" + "crypto/tls" "encoding/base64" "encoding/hex" "encoding/json" @@ -66,6 +67,10 @@ type Options struct { // with an nkey to a server that does not advertise nkey auth ("nkeys not // supported by the server"), so this is opt-in rather than always-on. UseNkey bool + // TLS, when non-nil, secures the NATS connection and pins the server to this + // config's RootCAs (the bus's self-signed CA). Build it with + // busauth.LoadCATLSConfig(caPath). Nil keeps the connection plaintext. + TLS *tls.Config } // New connects to NATS and records the control-plane URL with default Options @@ -87,6 +92,9 @@ func NewWithOptions(natsURL, ctrlURL string, id cs.Identity, opts Options) (*Cli } natsOpts = append(natsOpts, nats.Nkey(nkeyPub, nkeySign)) } + if opts.TLS != nil { + natsOpts = append(natsOpts, nats.Secure(opts.TLS)) + } nc, err := nats.Connect(natsURL, natsOpts...) if err != nil { return nil, fmt.Errorf("client: connect nats %q: %w", natsURL, err)