- Remove sslutil.ParseTLSVersion (singular): unused, ParseTLSVersions (plural) is used - Remove TestSetPidFile and TestSetLogFile: trivial setter tests
208 lines
7.1 KiB
Go
208 lines
7.1 KiB
Go
// 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
|
|
}
|