Skip to content

Commit c214e18

Browse files
authored
feat(tls): allow configuring TLS versions and cipher suites (#575)
* feat(tls): allow configuring TLS versions and cipher suites Expose configuration options for TLS MinVersion, MaxVersion, and CipherSuites backed by crypto/tls. This allows LiveKit to interoperate with providers that require non-default or legacy TLS configurations. * fix(tls): Change the accepted format of TLS version
1 parent 2476708 commit c214e18

File tree

4 files changed

+223
-0
lines changed

4 files changed

+223
-0
lines changed

pkg/config/config.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ type TLSConfig struct {
5454
ListenPort int `yaml:"port_listen"` // SIP signaling port to listen on
5555
Certs []TLSCert `yaml:"certs"`
5656
KeyLog string `yaml:"key_log"`
57+
58+
MinVersion string `yaml:"min_version"` // min TLS version, accepts: "tls1.0", "tls1.1", "tls1.2", "tls1.3"
59+
MaxVersion string `yaml:"max_version"` // max TLS version, accepts: "tls1.0", "tls1.1", "tls1.2", "tls1.3"
60+
61+
// CipherSuites is an optional list of cipher suite names.
62+
// If not provided, Go's secure defaults are used.
63+
// Note: Only applies to TLS 1.0-1.2; TLS 1.3 cipher suites are not configurable.
64+
CipherSuites []string `yaml:"cipher_suites"`
5765
}
5866

5967
type TCPConfig struct {

pkg/sip/service.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,29 @@ func (s *Service) Start() error {
259259
Certificates: certs,
260260
KeyLogWriter: keyLog,
261261
}
262+
263+
if len(tconf.CipherSuites) > 0 {
264+
suits, err := parseCipherSuites(s.log, tconf.CipherSuites)
265+
if err != nil {
266+
return err
267+
}
268+
tlsConf.CipherSuites = suits
269+
}
270+
if tconf.MinVersion != "" {
271+
minVer, err := parseTLSVersion(tconf.MinVersion)
272+
if err != nil {
273+
return err
274+
}
275+
tlsConf.MinVersion = minVer
276+
}
277+
if tconf.MaxVersion != "" {
278+
maxVer, err := parseTLSVersion(tconf.MaxVersion)
279+
if err != nil {
280+
return err
281+
}
282+
tlsConf.MaxVersion = maxVer
283+
}
284+
262285
ConfigureTLS(tlsConf)
263286
opts = append(opts, sipgo.WithUserAgenTLSConfig(tlsConf))
264287
}

pkg/sip/tls.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,18 @@ import (
1818
"crypto/tls"
1919
"crypto/x509"
2020
"errors"
21+
22+
"github.com/livekit/protocol/logger"
2123
)
2224

25+
func makeTLSCipherMap(CipherSuites []*tls.CipherSuite) map[string]*tls.CipherSuite {
26+
cipherSuitesMap := make(map[string]*tls.CipherSuite)
27+
for _, c := range CipherSuites {
28+
cipherSuitesMap[c.Name] = c
29+
}
30+
return cipherSuitesMap
31+
}
32+
2333
func ConfigureTLS(c *tls.Config) {
2434
// We can't use default cert verification, because SIP headers usually specify IP address instead of a hostname.
2535
// At least, we could validate certificate chain using VerifyPeerCertificate and ignore the server name for now.
@@ -49,3 +59,47 @@ func ConfigureTLS(c *tls.Config) {
4959
return nil
5060
}
5161
}
62+
63+
// parseCipherSuites parses cipher suite names to uint16 IDs.
64+
// Logs a warning for each insecure cipher suite configured.
65+
func parseCipherSuites(log logger.Logger, suites []string) ([]uint16, error) {
66+
if len(suites) == 0 {
67+
return nil, nil
68+
}
69+
70+
parsedCipherSuites := []uint16{}
71+
cipherSuite := makeTLSCipherMap(tls.CipherSuites())
72+
insecureCipherSuite := makeTLSCipherMap(tls.InsecureCipherSuites())
73+
74+
for _, suite := range suites {
75+
if cipher, ok := cipherSuite[suite]; ok {
76+
parsedCipherSuites = append(parsedCipherSuites, cipher.ID)
77+
} else if cipher, ok := insecureCipherSuite[suite]; ok {
78+
parsedCipherSuites = append(parsedCipherSuites, cipher.ID)
79+
log.Warnw("using insecure TLS cipher suite", nil, "cipherSuite", suite)
80+
} else {
81+
return nil, errors.New("unknown cipher suite: " + suite)
82+
}
83+
}
84+
85+
return parsedCipherSuites, nil
86+
}
87+
88+
// parseTLSVersion parses a TLS version string to its uint16 constant.
89+
// Accepts formats: "tls1.0", "tls1.1", "tls1.2", "tls1.3" or "TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3".
90+
func parseTLSVersion(version string) (uint16, error) {
91+
switch version {
92+
case "":
93+
return 0, nil
94+
case "tls1.0", "TLS1.0":
95+
return tls.VersionTLS10, nil
96+
case "tls1.1", "TLS1.1":
97+
return tls.VersionTLS11, nil
98+
case "tls1.2", "TLS1.2":
99+
return tls.VersionTLS12, nil
100+
case "tls1.3", "TLS1.3":
101+
return tls.VersionTLS13, nil
102+
default:
103+
return 0, errors.New("unknown TLS version: " + version)
104+
}
105+
}

pkg/sip/tls_test.go

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package sip
2+
3+
import (
4+
"crypto/tls"
5+
"testing"
6+
7+
"github.com/livekit/protocol/logger"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestParseCipherSuites(t *testing.T) {
12+
log := logger.GetLogger()
13+
14+
t.Run("valid cipher suites - secure", func(t *testing.T) {
15+
cipherSuites := []string{
16+
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
17+
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
18+
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
19+
}
20+
21+
suites, err := parseCipherSuites(log, cipherSuites)
22+
23+
require.NoError(t, err)
24+
require.Equal(t, len(cipherSuites), len(suites))
25+
require.Equal(t, uint16(tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256), suites[0])
26+
require.Equal(t, uint16(tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA), suites[1])
27+
require.Equal(t, uint16(tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256), suites[2])
28+
})
29+
30+
t.Run("valid cipher suites - insecure", func(t *testing.T) {
31+
cipherSuites := []string{
32+
"TLS_RSA_WITH_RC4_128_SHA",
33+
"TLS_RSA_WITH_3DES_EDE_CBC_SHA",
34+
"TLS_ECDHE_RSA_WITH_RC4_128_SHA",
35+
}
36+
37+
suites, err := parseCipherSuites(log, cipherSuites)
38+
39+
require.NoError(t, err)
40+
require.Equal(t, len(cipherSuites), len(suites))
41+
require.Equal(t, uint16(tls.TLS_RSA_WITH_RC4_128_SHA), suites[0])
42+
require.Equal(t, uint16(tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA), suites[1])
43+
require.Equal(t, uint16(tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA), suites[2])
44+
})
45+
46+
t.Run("cipher suite - mixed", func(t *testing.T) {
47+
cipherSuites := []string{
48+
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
49+
"TLS_ECDHE_RSA_WITH_RC4_128_SHA",
50+
}
51+
52+
suites, err := parseCipherSuites(log, cipherSuites)
53+
54+
require.NoError(t, err)
55+
require.Equal(t, len(cipherSuites), len(suites))
56+
require.Equal(t, uint16(tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256), suites[0])
57+
require.Equal(t, uint16(tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA), suites[1])
58+
})
59+
60+
t.Run("invalid cipher site", func(t *testing.T) {
61+
cipherSuites := []string{
62+
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
63+
"INVALID_CIPHER_SUITE",
64+
}
65+
66+
_, err := parseCipherSuites(log, cipherSuites)
67+
68+
require.Error(t, err)
69+
require.Contains(t, err.Error(), "unknown cipher suite: INVALID_CIPHER_SUITE")
70+
})
71+
}
72+
func TestParseTLSVersion(t *testing.T) {
73+
t.Run("empty string", func(t *testing.T) {
74+
version, err := parseTLSVersion("")
75+
require.NoError(t, err)
76+
require.Equal(t, uint16(0), version)
77+
})
78+
79+
t.Run("TLS 1.0 - lowercase format", func(t *testing.T) {
80+
version, err := parseTLSVersion("tls1.0")
81+
require.NoError(t, err)
82+
require.Equal(t, uint16(tls.VersionTLS10), version)
83+
})
84+
85+
t.Run("TLS 1.0 - uppercase format", func(t *testing.T) {
86+
version, err := parseTLSVersion("TLS1.0")
87+
require.NoError(t, err)
88+
require.Equal(t, uint16(tls.VersionTLS10), version)
89+
})
90+
91+
t.Run("TLS 1.1 - lowercase format", func(t *testing.T) {
92+
version, err := parseTLSVersion("tls1.1")
93+
require.NoError(t, err)
94+
require.Equal(t, uint16(tls.VersionTLS11), version)
95+
})
96+
97+
t.Run("TLS 1.1 - uppercase format", func(t *testing.T) {
98+
version, err := parseTLSVersion("TLS1.1")
99+
require.NoError(t, err)
100+
require.Equal(t, uint16(tls.VersionTLS11), version)
101+
})
102+
103+
t.Run("TLS 1.2 - lowercase format", func(t *testing.T) {
104+
version, err := parseTLSVersion("tls1.2")
105+
require.NoError(t, err)
106+
require.Equal(t, uint16(tls.VersionTLS12), version)
107+
})
108+
109+
t.Run("TLS 1.2 - uppercase format", func(t *testing.T) {
110+
version, err := parseTLSVersion("TLS1.2")
111+
require.NoError(t, err)
112+
require.Equal(t, uint16(tls.VersionTLS12), version)
113+
})
114+
115+
t.Run("TLS 1.3 - lowercase format", func(t *testing.T) {
116+
version, err := parseTLSVersion("tls1.3")
117+
require.NoError(t, err)
118+
require.Equal(t, uint16(tls.VersionTLS13), version)
119+
})
120+
121+
t.Run("TLS 1.3 - uppercase format", func(t *testing.T) {
122+
version, err := parseTLSVersion("TLS1.3")
123+
require.NoError(t, err)
124+
require.Equal(t, uint16(tls.VersionTLS13), version)
125+
})
126+
127+
t.Run("invalid version", func(t *testing.T) {
128+
_, err := parseTLSVersion("tls1.4")
129+
require.Error(t, err)
130+
require.Contains(t, err.Error(), "unknown TLS version: tls1.4")
131+
})
132+
133+
t.Run("invalid format", func(t *testing.T) {
134+
_, err := parseTLSVersion("TLS 1.2")
135+
require.Error(t, err)
136+
require.Contains(t, err.Error(), "unknown TLS version: TLS 1.2")
137+
})
138+
}

0 commit comments

Comments
 (0)