Skip to content

Commit 87a147f

Browse files
zeripathStelios Malathouras
authored and
Stelios Malathouras
committed
Make SSL cipher suite configurable (go-gitea#17440)
1 parent 142cd71 commit 87a147f

File tree

9 files changed

+266
-54
lines changed

9 files changed

+266
-54
lines changed

cmd/web_graceful.go

-9
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
package cmd
66

77
import (
8-
"crypto/tls"
98
"net"
109
"net/http"
1110
"net/http/fcgi"
@@ -20,14 +19,6 @@ func runHTTP(network, listenAddr, name string, m http.Handler) error {
2019
return graceful.HTTPListenAndServe(network, listenAddr, name, m)
2120
}
2221

23-
func runHTTPS(network, listenAddr, name, certFile, keyFile string, m http.Handler) error {
24-
return graceful.HTTPListenAndServeTLS(network, listenAddr, name, certFile, keyFile, m)
25-
}
26-
27-
func runHTTPSWithTLSConfig(network, listenAddr, name string, tlsConfig *tls.Config, m http.Handler) error {
28-
return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m)
29-
}
30-
3122
// NoHTTPRedirector tells our cleanup routine that we will not be using a fallback http redirector
3223
func NoHTTPRedirector() {
3324
graceful.GetManager().InformCleanup()

cmd/web_https.go

+191
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
// Copyright 2021 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package cmd
6+
7+
import (
8+
"crypto/tls"
9+
"net/http"
10+
"os"
11+
"strings"
12+
13+
"code.gitea.io/gitea/modules/graceful"
14+
"code.gitea.io/gitea/modules/log"
15+
"code.gitea.io/gitea/modules/setting"
16+
"github.com/klauspost/cpuid/v2"
17+
)
18+
19+
var tlsVersionStringMap = map[string]uint16{
20+
"": tls.VersionTLS12, // Default to tls.VersionTLS12
21+
"tlsv1.0": tls.VersionTLS10,
22+
"tlsv1.1": tls.VersionTLS11,
23+
"tlsv1.2": tls.VersionTLS12,
24+
"tlsv1.3": tls.VersionTLS13,
25+
}
26+
27+
func toTLSVersion(version string) uint16 {
28+
tlsVersion, ok := tlsVersionStringMap[strings.TrimSpace(strings.ToLower(version))]
29+
if !ok {
30+
log.Warn("Unknown tls version: %s", version)
31+
return 0
32+
}
33+
return tlsVersion
34+
}
35+
36+
var curveStringMap = map[string]tls.CurveID{
37+
"x25519": tls.X25519,
38+
"p256": tls.CurveP256,
39+
"p384": tls.CurveP384,
40+
"p521": tls.CurveP521,
41+
}
42+
43+
func toCurvePreferences(preferences []string) []tls.CurveID {
44+
ids := make([]tls.CurveID, 0, len(preferences))
45+
for _, pref := range preferences {
46+
id, ok := curveStringMap[strings.TrimSpace(strings.ToLower(pref))]
47+
if !ok {
48+
log.Warn("Unknown curve: %s", pref)
49+
}
50+
if id != 0 {
51+
ids = append(ids, id)
52+
}
53+
}
54+
return ids
55+
}
56+
57+
var cipherStringMap = map[string]uint16{
58+
"rsa_with_rc4_128_sha": tls.TLS_RSA_WITH_RC4_128_SHA,
59+
"rsa_with_3des_ede_cbc_sha": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
60+
"rsa_with_aes_128_cbc_sha": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
61+
"rsa_with_aes_256_cbc_sha": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
62+
"rsa_with_aes_128_cbc_sha256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
63+
"rsa_with_aes_128_gcm_sha256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
64+
"rsa_with_aes_256_gcm_sha384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
65+
"ecdhe_ecdsa_with_rc4_128_sha": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
66+
"ecdhe_ecdsa_with_aes_128_cbc_sha": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
67+
"ecdhe_ecdsa_with_aes_256_cbc_sha": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
68+
"ecdhe_rsa_with_rc4_128_sha": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
69+
"ecdhe_rsa_with_3des_ede_cbc_sha": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
70+
"ecdhe_rsa_with_aes_128_cbc_sha": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
71+
"ecdhe_rsa_with_aes_256_cbc_sha": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
72+
"ecdhe_ecdsa_with_aes_128_cbc_sha256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
73+
"ecdhe_rsa_with_aes_128_cbc_sha256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
74+
"ecdhe_rsa_with_aes_128_gcm_sha256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
75+
"ecdhe_ecdsa_with_aes_128_gcm_sha256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
76+
"ecdhe_rsa_with_aes_256_gcm_sha384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
77+
"ecdhe_ecdsa_with_aes_256_gcm_sha384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
78+
"ecdhe_rsa_with_chacha20_poly1305_sha256": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
79+
"ecdhe_ecdsa_with_chacha20_poly1305_sha256": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
80+
"ecdhe_rsa_with_chacha20_poly1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
81+
"ecdhe_ecdsa_with_chacha20_poly1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
82+
"aes_128_gcm_sha256": tls.TLS_AES_128_GCM_SHA256,
83+
"aes_256_gcm_sha384": tls.TLS_AES_256_GCM_SHA384,
84+
"chacha20_poly1305_sha256": tls.TLS_CHACHA20_POLY1305_SHA256,
85+
}
86+
87+
func toTLSCiphers(cipherStrings []string) []uint16 {
88+
ciphers := make([]uint16, 0, len(cipherStrings))
89+
for _, cipherString := range cipherStrings {
90+
cipher, ok := cipherStringMap[strings.TrimSpace(strings.ToLower(cipherString))]
91+
if !ok {
92+
log.Warn("Unknown cipher: %s", cipherString)
93+
}
94+
if cipher != 0 {
95+
ciphers = append(ciphers, cipher)
96+
}
97+
}
98+
99+
return ciphers
100+
}
101+
102+
// defaultCiphers uses hardware support to check if AES is specifically
103+
// supported by the CPU.
104+
//
105+
// If AES is supported AES ciphers will be preferred over ChaCha based ciphers
106+
// (This code is directly inspired by the certmagic code.)
107+
func defaultCiphers() []uint16 {
108+
if cpuid.CPU.Supports(cpuid.AESNI) {
109+
return defaultCiphersAESfirst
110+
}
111+
return defaultCiphersChaChaFirst
112+
}
113+
114+
var (
115+
defaultCiphersAES = []uint16{
116+
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
117+
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
118+
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
119+
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
120+
}
121+
122+
defaultCiphersChaCha = []uint16{
123+
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
124+
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
125+
}
126+
127+
defaultCiphersAESfirst = append(defaultCiphersAES, defaultCiphersChaCha...)
128+
defaultCiphersChaChaFirst = append(defaultCiphersChaCha, defaultCiphersAES...)
129+
)
130+
131+
// runHTTPs listens on the provided network address and then calls
132+
// Serve to handle requests on incoming TLS connections.
133+
//
134+
// Filenames containing a certificate and matching private key for the server must
135+
// be provided. If the certificate is signed by a certificate authority, the
136+
// certFile should be the concatenation of the server's certificate followed by the
137+
// CA's certificate.
138+
func runHTTPS(network, listenAddr, name, certFile, keyFile string, m http.Handler) error {
139+
tlsConfig := &tls.Config{}
140+
if tlsConfig.NextProtos == nil {
141+
tlsConfig.NextProtos = []string{"h2", "http/1.1"}
142+
}
143+
144+
if version := toTLSVersion(setting.SSLMinimumVersion); version != 0 {
145+
tlsConfig.MinVersion = version
146+
}
147+
if version := toTLSVersion(setting.SSLMaximumVersion); version != 0 {
148+
tlsConfig.MaxVersion = version
149+
}
150+
151+
// Set curve preferences
152+
tlsConfig.CurvePreferences = []tls.CurveID{
153+
tls.X25519,
154+
tls.CurveP256,
155+
}
156+
if curves := toCurvePreferences(setting.SSLCurvePreferences); len(curves) > 0 {
157+
tlsConfig.CurvePreferences = curves
158+
}
159+
160+
// Set cipher suites
161+
tlsConfig.CipherSuites = defaultCiphers()
162+
if ciphers := toTLSCiphers(setting.SSLCipherSuites); len(ciphers) > 0 {
163+
tlsConfig.CipherSuites = ciphers
164+
}
165+
166+
tlsConfig.Certificates = make([]tls.Certificate, 1)
167+
168+
certPEMBlock, err := os.ReadFile(certFile)
169+
if err != nil {
170+
log.Error("Failed to load https cert file %s for %s:%s: %v", certFile, network, listenAddr, err)
171+
return err
172+
}
173+
174+
keyPEMBlock, err := os.ReadFile(keyFile)
175+
if err != nil {
176+
log.Error("Failed to load https key file %s for %s:%s: %v", keyFile, network, listenAddr, err)
177+
return err
178+
}
179+
180+
tlsConfig.Certificates[0], err = tls.X509KeyPair(certPEMBlock, keyPEMBlock)
181+
if err != nil {
182+
log.Error("Failed to create certificate from cert file %s and key file %s for %s:%s: %v", certFile, keyFile, network, listenAddr, err)
183+
return err
184+
}
185+
186+
return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m)
187+
}
188+
189+
func runHTTPSWithTLSConfig(network, listenAddr, name string, tlsConfig *tls.Config, m http.Handler) error {
190+
return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m)
191+
}

cmd/web_letsencrypt.go

+17
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,23 @@ func runLetsEncrypt(listenAddr, domain, directory, email string, m http.Handler)
5555
tlsConfig := magic.TLSConfig()
5656
tlsConfig.NextProtos = append(tlsConfig.NextProtos, "h2")
5757

58+
if version := toTLSVersion(setting.SSLMinimumVersion); version != 0 {
59+
tlsConfig.MinVersion = version
60+
}
61+
if version := toTLSVersion(setting.SSLMaximumVersion); version != 0 {
62+
tlsConfig.MaxVersion = version
63+
}
64+
65+
// Set curve preferences
66+
if curves := toCurvePreferences(setting.SSLCurvePreferences); len(curves) > 0 {
67+
tlsConfig.CurvePreferences = curves
68+
}
69+
70+
// Set cipher suites
71+
if ciphers := toTLSCiphers(setting.SSLCipherSuites); len(ciphers) > 0 {
72+
tlsConfig.CipherSuites = ciphers
73+
}
74+
5875
if enableHTTPChallenge {
5976
go func() {
6077
log.Info("Running Let's Encrypt handler on %s", setting.HTTPAddr+":"+setting.PortToRedirect)

custom/conf/app.example.ini

+10
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@ RUN_MODE = ; prod
5151
;REDIRECT_OTHER_PORT = false
5252
;PORT_TO_REDIRECT = 80
5353
;;
54+
;; Minimum and maximum supported TLS versions
55+
;SSL_MIN_VERSION=TLSv1.2
56+
;SSL_MAX_VERSION=
57+
;;
58+
;; SSL Curve Preferences
59+
;SSL_CURVE_PREFERENCES=X25519,P256
60+
;;
61+
;; SSL Cipher Suites
62+
;SSL_CIPHER_SUITES=; Will default to "ecdhe_ecdsa_with_aes_256_gcm_sha384,ecdhe_rsa_with_aes_256_gcm_sha384,ecdhe_ecdsa_with_aes_128_gcm_sha256,ecdhe_rsa_with_aes_128_gcm_sha256,ecdhe_ecdsa_with_chacha20_poly1305,ecdhe_rsa_with_chacha20_poly1305" if aes is supported by hardware, otherwise chacha will be first.
63+
;;
5464
;; Timeout for any write to the connection. (Set to 0 to disable all timeouts.)
5565
;PER_WRITE_TIMEOUT = 30s
5666
;;

docs/content/doc/advanced/config-cheat-sheet.en-us.md

+36
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,42 @@ The following configuration set `Content-Type: application/vnd.android.package-a
310310

311311
- `REDIRECT_OTHER_PORT`: **false**: If true and `PROTOCOL` is https, allows redirecting http requests on `PORT_TO_REDIRECT` to the https port Gitea listens on.
312312
- `PORT_TO_REDIRECT`: **80**: Port for the http redirection service to listen on. Used when `REDIRECT_OTHER_PORT` is true.
313+
- `SSL_MIN_VERSION`: **TLSv1.2**: Set the minimum version of ssl support.
314+
- `SSL_MAX_VERSION`: **\<empty\>**: Set the maximum version of ssl support.
315+
- `SSL_CURVE_PREFERENCES`: **X25519,P256**: Set the prefered curves,
316+
- `SSL_CIPHER_SUITES`: **ecdhe_ecdsa_with_aes_256_gcm_sha384,ecdhe_rsa_with_aes_256_gcm_sha384,ecdhe_ecdsa_with_aes_128_gcm_sha256,ecdhe_rsa_with_aes_128_gcm_sha256,ecdhe_ecdsa_with_chacha20_poly1305,ecdhe_rsa_with_chacha20_poly1305**: Set the preferred cipher suites.
317+
- If there is not hardware support for AES suites by default the cha cha suites will be preferred over the AES suites
318+
- supported suites as of go 1.17 are:
319+
- TLS 1.0 - 1.2 cipher suites
320+
- "rsa_with_rc4_128_sha"
321+
- "rsa_with_3des_ede_cbc_sha"
322+
- "rsa_with_aes_128_cbc_sha"
323+
- "rsa_with_aes_256_cbc_sha"
324+
- "rsa_with_aes_128_cbc_sha256"
325+
- "rsa_with_aes_128_gcm_sha256"
326+
- "rsa_with_aes_256_gcm_sha384"
327+
- "ecdhe_ecdsa_with_rc4_128_sha"
328+
- "ecdhe_ecdsa_with_aes_128_cbc_sha"
329+
- "ecdhe_ecdsa_with_aes_256_cbc_sha"
330+
- "ecdhe_rsa_with_rc4_128_sha"
331+
- "ecdhe_rsa_with_3des_ede_cbc_sha"
332+
- "ecdhe_rsa_with_aes_128_cbc_sha"
333+
- "ecdhe_rsa_with_aes_256_cbc_sha"
334+
- "ecdhe_ecdsa_with_aes_128_cbc_sha256"
335+
- "ecdhe_rsa_with_aes_128_cbc_sha256"
336+
- "ecdhe_rsa_with_aes_128_gcm_sha256"
337+
- "ecdhe_ecdsa_with_aes_128_gcm_sha256"
338+
- "ecdhe_rsa_with_aes_256_gcm_sha384"
339+
- "ecdhe_ecdsa_with_aes_256_gcm_sha384"
340+
- "ecdhe_rsa_with_chacha20_poly1305_sha256"
341+
- "ecdhe_ecdsa_with_chacha20_poly1305_sha256"
342+
- TLS 1.3 cipher suites
343+
- "aes_128_gcm_sha256"
344+
- "aes_256_gcm_sha384"
345+
- "chacha20_poly1305_sha256"
346+
- Aliased names
347+
- "ecdhe_rsa_with_chacha20_poly1305" is an alias for "ecdhe_rsa_with_chacha20_poly1305_sha256"
348+
- "ecdhe_ecdsa_with_chacha20_poly1305" is alias for "ecdhe_ecdsa_with_chacha20_poly1305_sha256"
313349
- `ENABLE_LETSENCRYPT`: **false**: If enabled you must set `DOMAIN` to valid internet facing domain (ensure DNS is set and port 80 is accessible by letsencrypt validation server).
314350
By using Lets Encrypt **you must consent** to their [terms of service](https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf).
315351
- `LETSENCRYPT_ACCEPTTOS`: **false**: This is an explicit check that you accept the terms of service for Let's Encrypt.

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ require (
6969
github.com/kevinburke/ssh_config v1.1.0 // indirect
7070
github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4
7171
github.com/klauspost/compress v1.13.1
72-
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
72+
github.com/klauspost/cpuid/v2 v2.0.9
7373
github.com/klauspost/pgzip v1.2.5 // indirect
7474
github.com/lib/pq v1.10.2
7575
github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96

modules/graceful/server.go

+3-37
Original file line numberDiff line numberDiff line change
@@ -95,48 +95,14 @@ func (srv *Server) ListenAndServe(serve ServeFunction) error {
9595
return srv.Serve(serve)
9696
}
9797

98-
// ListenAndServeTLS listens on the provided network address and then calls
99-
// Serve to handle requests on incoming TLS connections.
100-
//
101-
// Filenames containing a certificate and matching private key for the server must
102-
// be provided. If the certificate is signed by a certificate authority, the
103-
// certFile should be the concatenation of the server's certificate followed by the
104-
// CA's certificate.
105-
func (srv *Server) ListenAndServeTLS(certFile, keyFile string, serve ServeFunction) error {
106-
config := &tls.Config{}
107-
if config.NextProtos == nil {
108-
config.NextProtos = []string{"h2", "http/1.1"}
109-
}
110-
111-
config.Certificates = make([]tls.Certificate, 1)
112-
113-
certPEMBlock, err := os.ReadFile(certFile)
114-
if err != nil {
115-
log.Error("Failed to load https cert file %s for %s:%s: %v", certFile, srv.network, srv.address, err)
116-
return err
117-
}
118-
119-
keyPEMBlock, err := os.ReadFile(keyFile)
120-
if err != nil {
121-
log.Error("Failed to load https key file %s for %s:%s: %v", keyFile, srv.network, srv.address, err)
122-
return err
123-
}
124-
125-
config.Certificates[0], err = tls.X509KeyPair(certPEMBlock, keyPEMBlock)
126-
if err != nil {
127-
log.Error("Failed to create certificate from cert file %s and key file %s for %s:%s: %v", certFile, keyFile, srv.network, srv.address, err)
128-
return err
129-
}
130-
131-
return srv.ListenAndServeTLSConfig(config, serve)
132-
}
133-
13498
// ListenAndServeTLSConfig listens on the provided network address and then calls
13599
// Serve to handle requests on incoming TLS connections.
136100
func (srv *Server) ListenAndServeTLSConfig(tlsConfig *tls.Config, serve ServeFunction) error {
137101
go srv.awaitShutdown()
138102

139-
tlsConfig.MinVersion = tls.VersionTLS12
103+
if tlsConfig.MinVersion == 0 {
104+
tlsConfig.MinVersion = tls.VersionTLS12
105+
}
140106

141107
l, err := GetListener(srv.network, srv.address)
142108
if err != nil {

modules/graceful/server_http.go

-7
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,6 @@ func HTTPListenAndServe(network, address, name string, handler http.Handler) err
3333
return server.ListenAndServe(lHandler)
3434
}
3535

36-
// HTTPListenAndServeTLS listens on the provided network address and then calls Serve
37-
// to handle requests on incoming connections.
38-
func HTTPListenAndServeTLS(network, address, name, certFile, keyFile string, handler http.Handler) error {
39-
server, lHandler := newHTTPServer(network, address, name, handler)
40-
return server.ListenAndServeTLS(certFile, keyFile, lHandler)
41-
}
42-
4336
// HTTPListenAndServeTLSConfig listens on the provided network address and then calls Serve
4437
// to handle requests on incoming connections.
4538
func HTTPListenAndServeTLSConfig(network, address, name string, tlsConfig *tls.Config, handler http.Handler) error {

modules/setting/setting.go

+8
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ var (
114114
LetsEncryptTOS bool
115115
LetsEncryptDirectory string
116116
LetsEncryptEmail string
117+
SSLMinimumVersion string
118+
SSLMaximumVersion string
119+
SSLCurvePreferences []string
120+
SSLCipherSuites []string
117121
GracefulRestartable bool
118122
GracefulHammerTime time.Duration
119123
StartupTimeout time.Duration
@@ -618,6 +622,10 @@ func NewContext() {
618622
}
619623
LetsEncryptDirectory = sec.Key("LETSENCRYPT_DIRECTORY").MustString("https")
620624
LetsEncryptEmail = sec.Key("LETSENCRYPT_EMAIL").MustString("")
625+
SSLMinimumVersion = sec.Key("SSL_MIN_VERSION").MustString("")
626+
SSLMaximumVersion = sec.Key("SSL_MAX_VERSION").MustString("")
627+
SSLCurvePreferences = sec.Key("SSL_CURVE_PREFERENCES").Strings(",")
628+
SSLCipherSuites = sec.Key("SSL_CIPHER_SUITES").Strings(",")
621629
Domain = sec.Key("DOMAIN").MustString("localhost")
622630
HTTPAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0")
623631
HTTPPort = sec.Key("HTTP_PORT").MustString("3000")

0 commit comments

Comments
 (0)