// Package sslutil provides SSL/TLS utility functions. package sslutil import ( "crypto/tls" "fmt" "slices" ) // TLS version string constants const ( tlsV12Lower = "TLSv1.2" tlsV13Lower = "TLSv1.3" tlsV12Upper = "TLSV1.2" tlsV13Upper = "TLSV1.3" ) // ParseTLSVersions parses TLS protocol version strings. // // Returns minimum and maximum TLS versions. // // Parameters: // - protocols: Protocol name list (e.g., "TLSv1.2", "TLSv1.3") // // Returns: // - uint16: Minimum version // - uint16: Maximum version // - error: Invalid protocol returns error func ParseTLSVersions(protocols []string) (uint16, uint16, error) { var minVer, maxVer uint16 minVer = tls.VersionTLS13 // Default to highest version maxVer = tls.VersionTLS13 for _, p := range protocols { switch p { case tlsV12Lower, tlsV12Upper: if minVer > tls.VersionTLS12 { minVer = tls.VersionTLS12 } case tlsV13Lower, tlsV13Upper: maxVer = tls.VersionTLS13 case "TLSv1.0", "TLSV1.0", "TLSv1.1", "TLSV1.1": return 0, 0, fmt.Errorf("insecure TLS version %s is not supported", p) default: return 0, 0, fmt.Errorf("unknown TLS version: %s", p) } } return minVer, maxVer, nil } // ParseMinTLSVersion parses the minimum TLS version from protocol list. // // Parameters: // - protocols: Protocol version list // // Returns: // - uint16: TLS version constant func ParseMinTLSVersion(protocols []string) uint16 { for _, p := range protocols { switch p { case tlsV13Lower, tlsV13Upper: return tls.VersionTLS13 case tlsV12Lower, tlsV12Upper: return tls.VersionTLS12 } } return tls.VersionTLS12 } // cipherNameToID maps cipher suite names to TLS IDs. // Supports both OpenSSL-style names and Go standard names. var cipherNameToID = map[string]uint16{ // OpenSSL-style names (ECDHE-RSA-AES128-GCM-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, "ECDHE-RSA-CHACHA20-POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, "ECDHE-ECDSA-CHACHA20-POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, "AES128-GCM-SHA256": tls.TLS_AES_128_GCM_SHA256, "AES256-GCM-SHA384": tls.TLS_AES_256_GCM_SHA384, "CHACHA20-POLY1305": tls.TLS_CHACHA20_POLY1305_SHA256, "ECDHE-RSA-AES128-CBC-SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, "ECDHE-RSA-AES256-CBC-SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, "ECDHE-ECDSA-AES128-CBC-SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, "ECDHE-ECDSA-AES256-CBC-SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, "RSA-AES128-GCM-SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256, "RSA-AES256-GCM-SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384, "RSA-AES128-CBC-SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA, "RSA-AES256-CBC-SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA, "ECDHE-RSA-3DES-EDE-CBC-SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, "RSA-3DES-EDE-CBC-SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, // Go standard names (TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) "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_RSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, "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_ECDSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, "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_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA, "TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA, } // insecureCipherIDs contains cipher suite IDs considered insecure. var insecureCipherIDs = []uint16{ tls.TLS_RSA_WITH_RC4_128_SHA, tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, tls.TLS_RSA_WITH_AES_128_CBC_SHA256, tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, } // IsInsecureCipher checks if a cipher suite is insecure. // // Parameters: // - id: Cipher suite ID // // Returns: // - bool: True if insecure func IsInsecureCipher(id uint16) bool { return slices.Contains(insecureCipherIDs, id) } // ParseCipherSuites parses cipher suite name strings to TLS IDs. // // Parameters: // - ciphers: Cipher suite name list // // Returns: // - []uint16: Cipher suite ID list // - error: Unknown or insecure cipher suite returns error func ParseCipherSuites(ciphers []string) ([]uint16, error) { result := make([]uint16, 0, len(ciphers)) for _, c := range ciphers { id, ok := cipherNameToID[c] if !ok { return nil, fmt.Errorf("unknown cipher suite: %s", c) } // Check for insecure cipher suites if IsInsecureCipher(id) { return nil, fmt.Errorf("insecure cipher suite %s is not allowed", c) } result = append(result, id) } return result, nil } // ParseCipherSuitesLenient parses cipher suites without error on unknown names. // Returns nil if no valid suites found, allowing fallback to defaults. // // Parameters: // - ciphers: Cipher suite name list // // Returns: // - []uint16: Cipher suite ID list (nil if none valid) func ParseCipherSuitesLenient(ciphers []string) []uint16 { var suites []uint16 for _, c := range ciphers { if id, ok := cipherNameToID[c]; ok && !IsInsecureCipher(id) { suites = append(suites, id) } } if len(suites) == 0 { return nil // Use defaults } return suites } // DefaultCipherSuites returns recommended cipher suites for TLS 1.2. // // Prioritizes forward secrecy and AEAD encryption algorithms. // // Returns: // - []uint16: Cipher suite ID list func DefaultCipherSuites() []uint16 { return []uint16{ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, } } // TLSVersionMap maps TLS version strings to tls constants. // Supports TLSv1.0, TLSv1.1, TLSv1.2, TLSv1.3 formats (case insensitive). // Empty string means use Go standard library default. var TLSVersionMap = map[string]uint16{ "TLSV1.0": tls.VersionTLS10, "TLSV1.1": tls.VersionTLS11, "TLSV1.2": tls.VersionTLS12, "TLSV1.3": tls.VersionTLS13, "": 0, // Empty string means use default }