package db import ( "crypto/tls" "crypto/x509" "fmt" "os" "strings" ) // EffectiveSSLMode resolves ssl / sslMode for drivers. // Checkbox off or mode disable → disable. Legacy ssl:true with empty mode → require. func EffectiveSSLMode(c ConnReq) string { mode := strings.ToLower(strings.TrimSpace(c.SSLMode)) switch mode { case "disable", "require", "skip-verify", "verify-ca": if mode == "disable" { return "disable" } return mode } if c.SSL { return "require" } return "disable" } // SSLEnabled is true when TLS should be negotiated. func SSLEnabled(c ConnReq) bool { return EffectiveSSLMode(c) != "disable" } // tlsConfigFrom builds a *tls.Config from ConnReq paths/mode. // Returns (nil, nil) when SSL is disabled. func tlsConfigFrom(c ConnReq) (*tls.Config, error) { mode := EffectiveSSLMode(c) if mode == "disable" { return nil, nil } cfg := &tls.Config{ MinVersion: tls.VersionTLS12, } if host := strings.TrimSpace(c.Host); host != "" { cfg.ServerName = host } switch mode { case "skip-verify": cfg.InsecureSkipVerify = true case "require": // Encrypt; verify with system / provided CAs when present. case "verify-ca": // Prefer explicit CA; still verify chain (hostname via ServerName when set). default: return nil, fmt.Errorf("未知 SSL 模式: %s", mode) } if path := strings.TrimSpace(c.SSLCA); path != "" { pem, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("读取 CA 证书失败 (%s): %w", path, err) } pool := x509.NewCertPool() if !pool.AppendCertsFromPEM(pem) { return nil, fmt.Errorf("CA 证书不是有效 PEM: %s", path) } cfg.RootCAs = pool if mode == "verify-ca" { // verify-ca: trust given CA but allow hostname mismatch (common for IP hosts) cfg.InsecureSkipVerify = true roots := pool cfg.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) error { if len(rawCerts) == 0 { return fmt.Errorf("服务端未提供证书") } certs := make([]*x509.Certificate, 0, len(rawCerts)) for i, raw := range rawCerts { cert, err := x509.ParseCertificate(raw) if err != nil { return fmt.Errorf("解析服务端证书失败: %w", err) } if i == 0 { // keep leaf for Intermediates below } certs = append(certs, cert) } leaf := certs[0] inter := x509.NewCertPool() for _, cert := range certs[1:] { inter.AddCert(cert) } _, err := leaf.Verify(x509.VerifyOptions{Roots: roots, Intermediates: inter}) return err } } } else if mode == "verify-ca" { return nil, fmt.Errorf("verify-ca 模式需要填写 CA 证书路径") } certPath := strings.TrimSpace(c.SSLCert) keyPath := strings.TrimSpace(c.SSLKey) if certPath != "" || keyPath != "" { if certPath == "" || keyPath == "" { return nil, fmt.Errorf("客户端证书与密钥需同时填写") } cert, err := tls.LoadX509KeyPair(certPath, keyPath) if err != nil { return nil, fmt.Errorf("加载客户端证书失败: %w", err) } cfg.Certificates = []tls.Certificate{cert} } return cfg, nil }