package proxy import ( "crypto/tls" "strconv" "strings" ) func buildTLSConfig(config TLSConfig) *tls.Config { tlsConfig := &tls.Config{} if versions := parseTLSVersions(config.Protocols); versions != nil { tlsConfig.MinVersion = versions.min tlsConfig.MaxVersion = versions.max } if suites := parseCipherSuites(config.Ciphers); len(suites) > 0 { tlsConfig.CipherSuites = suites } return tlsConfig } type tlsVersionBounds struct { min uint16 max uint16 } func parseTLSVersions(value string) *tlsVersionBounds { if strings.TrimSpace(value) == "" { return nil } bounds := tlsVersionBounds{} for _, token := range splitTLSList(value) { version, ok := parseTLSVersionToken(token) if !ok { continue } if bounds.min == 0 || version < bounds.min { bounds.min = version } if version > bounds.max { bounds.max = version } } if bounds.min == 0 || bounds.max == 0 { return nil } return &bounds } func parseTLSVersionToken(token string) (uint16, bool) { switch strings.ToLower(strings.TrimSpace(token)) { case "tls1.0", "tlsv1.0", "tls1", "tlsv1", "1.0", "tls10": return tls.VersionTLS10, true case "tls1.1", "tlsv1.1", "1.1", "tls11": return tls.VersionTLS11, true case "tls1.2", "tlsv1.2", "1.2", "tls12": return tls.VersionTLS12, true case "tls1.3", "tlsv1.3", "1.3", "tls13": return tls.VersionTLS13, true } if raw, errorValue := strconv.ParseUint(strings.TrimSpace(token), 10, 16); errorValue == nil { switch uint16(raw) { case tls.VersionTLS10, tls.VersionTLS11, tls.VersionTLS12, tls.VersionTLS13: return uint16(raw), true } } return 0, false } func parseCipherSuites(value string) []uint16 { if strings.TrimSpace(value) == "" { return nil } var suites []uint16 for _, token := range splitTLSList(value) { if suite, ok := tlsCipherSuiteNames[strings.ToUpper(strings.TrimSpace(token))]; ok { suites = append(suites, suite) } } return suites } func splitTLSList(value string) []string { return strings.FieldsFunc(value, func(r rune) bool { switch r { case ':', ',', ' ', ';': return true default: return false } }) } var tlsCipherSuiteNames = map[string]uint16{ "TLS_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256, "TLS_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, "TLS_AES_128_GCM_SHA256": tls.TLS_AES_128_GCM_SHA256, "TLS_AES_256_GCM_SHA384": tls.TLS_AES_256_GCM_SHA384, "TLS_CHACHA20_POLY1305_SHA256": tls.TLS_CHACHA20_POLY1305_SHA256, "ECDHE-RSA-AES128-GCM-SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, "ECDHE-RSA-AES256-GCM-SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, "ECDHE-ECDSA-AES128-GCM-SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, "ECDHE-ECDSA-AES256-GCM-SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, "AES128-GCM-SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256, "AES256-GCM-SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384, "ECDHE-RSA-CHACHA20-POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, "ECDHE-ECDSA-CHACHA20-POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, "CHACHA20-POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, }