Files
mailgoserver/internal/tlsutil/tlsutil.go
T
2026-08-12 12:56:22 +01:00

95 lines
2.7 KiB
Go

// Package tlsutil generates the self-signed certificate used by the direct-TLS SMTP
// listener and builds its tls.Config, mirroring email_server/tls_utils.py.
package tlsutil
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"os"
"path/filepath"
"time"
)
// GenerateSelfSignedCert mirrors tls_utils.generate_self_signed_cert: skips generation
// if both files already exist; otherwise writes an RSA-2048/SHA-256, 1-year-valid,
// self-signed cert with the same subject fields as the Python version.
func GenerateSelfSignedCert(certFile, keyFile string) error {
if _, err := os.Stat(certFile); err == nil {
if _, err := os.Stat(keyFile); err == nil {
return nil
}
}
if err := os.MkdirAll(filepath.Dir(certFile), 0o755); err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(keyFile), 0o755); err != nil {
return err
}
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return err
}
subject := pkix.Name{
CommonName: "localhost",
Organization: []string{"PyMTA Server"},
Country: []string{"GB"},
}
template := x509.Certificate{
SerialNumber: big.NewInt(1000),
Subject: subject,
Issuer: subject,
NotBefore: time.Now(),
NotAfter: time.Now().Add(365 * 24 * time.Hour),
SignatureAlgorithm: x509.SHA256WithRSA,
BasicConstraintsValid: true,
}
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
return err
}
certOut, err := os.Create(certFile)
if err != nil {
return err
}
defer certOut.Close()
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certDER}); err != nil {
return err
}
keyOut, err := os.OpenFile(keyFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
if err != nil {
return err
}
defer keyOut.Close()
keyDER, err := x509.MarshalPKCS8PrivateKey(priv)
if err != nil {
return err
}
return pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: keyDER})
}
// CreateSSLContext mirrors tls_utils.create_ssl_context: loads the cert/key pair and
// pins MinVersion to TLS 1.2 (Python's ssl.create_default_context leaves this to the
// environment's OpenSSL defaults, which is typically TLS 1.2+ on modern systems —
// pinning it explicitly here is the closest deterministic equivalent). Cipher suites
// are left at Go's own secure defaults, matching the Python code's "DEFAULT" relaxation.
func CreateSSLContext(certFile, keyFile string) (*tls.Config, error) {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, err
}
return &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS12,
}, nil
}