first commit
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
// Package tlsutil provides certificate loading: LoadOrGenerate for the
|
||||
// file/self-signed paths (this file), and ACMEManager (acme_manager.go) for
|
||||
// real Let's Encrypt-style issuance via internal/acme. The self-signed
|
||||
// generator here remains the fallback for tls.mode "off" or "file" without
|
||||
// a cert on disk yet — genuinely necessary for local dev/testing, not a
|
||||
// placeholder for a missing feature.
|
||||
package tlsutil
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"math/big"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LoadOrGenerate returns a tls.Config for the given mode:
|
||||
// - "file": load cert/key from disk paths
|
||||
// - anything else ("acme" not yet implemented, "off"): generate a self-signed
|
||||
// cert so STARTTLS/IMAPS/etc. still work during development. Logs a loud
|
||||
// warning since this is never appropriate for production.
|
||||
func LoadOrGenerate(mode, hostname, certFile, keyFile string, minVersion uint16) (*tls.Config, error) {
|
||||
var cert tls.Certificate
|
||||
var err error
|
||||
|
||||
switch mode {
|
||||
case "file":
|
||||
cert, err = tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("loading TLS cert/key: %w", err)
|
||||
}
|
||||
case "acme":
|
||||
// Reaching here (rather than the real ACMEManager path in main.go)
|
||||
// means mode=="acme" but no acme_domains were configured — a real
|
||||
// ACME client exists (internal/acme, wired in main.go), it's just
|
||||
// not usable without knowing which domain(s) to request a cert for.
|
||||
slog.Warn("TLS mode is 'acme' but no acme_domains are configured — "+
|
||||
"generating a SELF-SIGNED certificate instead. Set tls.acme_domains "+
|
||||
"in config.yaml to enable real Let's Encrypt issuance.",
|
||||
"hostname", hostname)
|
||||
cert, err = generateSelfSigned(hostname)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("generating self-signed cert: %w", err)
|
||||
}
|
||||
default:
|
||||
slog.Warn("TLS mode is 'off' — generating a SELF-SIGNED certificate. "+
|
||||
"This is fine for local testing but MUST NOT be used in production; "+
|
||||
"set tls.mode to 'acme' (with acme_domains configured) or 'file'.",
|
||||
"mode", mode, "hostname", hostname)
|
||||
cert, err = generateSelfSigned(hostname)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("generating self-signed cert: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
MinVersion: minVersion,
|
||||
ServerName: hostname,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ParseMinVersion converts the config string ("TLS12"/"TLS13") to the
|
||||
// crypto/tls constant.
|
||||
func ParseMinVersion(s string) uint16 {
|
||||
if s == "TLS13" {
|
||||
return tls.VersionTLS13
|
||||
}
|
||||
return tls.VersionTLS12
|
||||
}
|
||||
|
||||
func generateSelfSigned(hostname string) (tls.Certificate, error) {
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
|
||||
serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serial,
|
||||
Subject: pkix.Name{CommonName: hostname, Organization: []string{"GoMail (self-signed, dev only)"}},
|
||||
NotBefore: time.Now().Add(-time.Hour),
|
||||
NotAfter: time.Now().AddDate(1, 0, 0),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
IsCA: true,
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
if ip := net.ParseIP(hostname); ip != nil {
|
||||
template.IPAddresses = []net.IP{ip}
|
||||
} else {
|
||||
template.DNSNames = []string{hostname}
|
||||
}
|
||||
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
|
||||
return tls.Certificate{
|
||||
Certificate: [][]byte{derBytes},
|
||||
PrivateKey: priv,
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user