Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make SSL cipher suite configurable #17440

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions cmd/web_graceful.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
package cmd

import (
"crypto/tls"
"net"
"net/http"
"net/http/fcgi"
Expand All @@ -20,14 +19,6 @@ func runHTTP(network, listenAddr, name string, m http.Handler) error {
return graceful.HTTPListenAndServe(network, listenAddr, name, m)
}

func runHTTPS(network, listenAddr, name, certFile, keyFile string, m http.Handler) error {
return graceful.HTTPListenAndServeTLS(network, listenAddr, name, certFile, keyFile, m)
}

func runHTTPSWithTLSConfig(network, listenAddr, name string, tlsConfig *tls.Config, m http.Handler) error {
return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m)
}

// NoHTTPRedirector tells our cleanup routine that we will not be using a fallback http redirector
func NoHTTPRedirector() {
graceful.GetManager().InformCleanup()
Expand Down
219 changes: 219 additions & 0 deletions cmd/web_https.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package cmd

import (
"crypto/tls"
"net/http"
"os"
"strings"

"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/klauspost/cpuid/v2"
)

func toTLSVersion(version string) uint16 {
version = strings.TrimSpace(strings.ToLower(version))
switch version {
case "tls10":
zeripath marked this conversation as resolved.
Show resolved Hide resolved
return tls.VersionTLS10
case "tls11":
return tls.VersionTLS11
case "tls12":
return tls.VersionTLS12
case "tls13":
return tls.VersionTLS13
default:
log.Warn("Unknown tls version: %s", version)
return 0
}
}

func toCurvePreferences(preferences []string) []tls.CurveID {
ids := make([]tls.CurveID, 0, len(preferences))
for _, pref := range preferences {
pref = strings.TrimSpace(strings.ToLower(pref))
var id tls.CurveID
switch pref {
case "x25519":
id = tls.X25519
case "p256":
id = tls.CurveP256
case "p384":
id = tls.CurveP384
case "p521":
id = tls.CurveP521
default:
log.Warn("Unknown curve: %s", pref)
}
if id != 0 {
ids = append(ids, id)
}
}
return ids
}

func toTLSCiphers(cipherStrings []string) []uint16 {
ciphers := make([]uint16, 0, len(cipherStrings))
for _, cipherString := range cipherStrings {
cipherString = strings.TrimSpace(strings.ToLower(cipherString))
var cipher uint16
switch cipherString {
case "rsa_with_rc4_128_sha":
cipher = tls.TLS_RSA_WITH_RC4_128_SHA
case "rsa_with_3des_ede_cbc_sha":
cipher = tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA
case "rsa_with_aes_128_cbc_sha":
cipher = tls.TLS_RSA_WITH_AES_128_CBC_SHA
case "rsa_with_aes_256_cbc_sha":
cipher = tls.TLS_RSA_WITH_AES_256_CBC_SHA
case "rsa_with_aes_128_cbc_sha256":
cipher = tls.TLS_RSA_WITH_AES_128_CBC_SHA256
case "rsa_with_aes_128_gcm_sha256":
cipher = tls.TLS_RSA_WITH_AES_128_GCM_SHA256
case "rsa_with_aes_256_gcm_sha384":
cipher = tls.TLS_RSA_WITH_AES_256_GCM_SHA384
case "ecdhe_ecdsa_with_rc4_128_sha":
cipher = tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA
case "ecdhe_ecdsa_with_aes_128_cbc_sha":
cipher = tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
case "ecdhe_ecdsa_with_aes_256_cbc_sha":
cipher = tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
case "ecdhe_rsa_with_rc4_128_sha":
cipher = tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA
case "ecdhe_rsa_with_3des_ede_cbc_sha":
cipher = tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
case "ecdhe_rsa_with_aes_128_cbc_sha":
cipher = tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
case "ecdhe_rsa_with_aes_256_cbc_sha":
cipher = tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
case "ecdhe_ecdsa_with_aes_128_cbc_sha256":
cipher = tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
case "ecdhe_rsa_with_aes_128_cbc_sha256":
cipher = tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
case "ecdhe_rsa_with_aes_128_gcm_sha256":
cipher = tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
case "ecdhe_ecdsa_with_aes_128_gcm_sha256":
cipher = tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
case "ecdhe_rsa_with_aes_256_gcm_sha384":
cipher = tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
case "ecdhe_ecdsa_with_aes_256_gcm_sha384":
cipher = tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
case "ecdhe_rsa_with_chacha20_poly1305_sha256":
cipher = tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
case "ecdhe_ecdsa_with_chacha20_poly1305_sha256":
cipher = tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
case "ecdhe_rsa_with_chacha20_poly1305":
cipher = tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
case "ecdhe_ecdsa_with_chacha20_poly1305":
cipher = tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
case "aes_128_gcm_sha256":
cipher = tls.TLS_AES_128_GCM_SHA256
case "aes_256_gcm_sha384":
cipher = tls.TLS_AES_256_GCM_SHA384
case "chacha20_poly1305_sha256":
cipher = tls.TLS_CHACHA20_POLY1305_SHA256
default:
log.Warn("Unknown cipher: %s", cipherString)
}
if cipher != 0 {
ciphers = append(ciphers, cipher)
}
}

return ciphers
}

// defaultCiphers uses hardware support to check if AES is specifically
// supported by the CPU.
//
// If it is AES ciphers will be preferred over ChaCha based ciphers
func defaultCiphers() []uint16 {
if cpuid.CPU.Supports(cpuid.AESNI) {
return defaultCiphersAESfirst
}
return defaultCiphersChaChaFirst
}

var (
defaultCiphersAES = []uint16{
Copy link
Contributor

@Gusted Gusted Nov 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to note that these default ciphers are recommended for TLS v1.0-v.1.2

TLS v1.3 has another default cipher recommendation IIRC.

IIRC(please check it up) AES support:
AES-128 GCM with SHA256
AES-256 GCM with SHA384

No-AES support:
ChaCha20-Poly1305 with SHA256

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To give another note on TLS v1.3 - all chiper suites are secure enough that TLS v1.3 supports, thus golang currently will disregard custom cipher suites when TLS v1.3 is used - Might be worth noting somewhere to avoid confusion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have copied these defaults from

https://github.com/caddyserver/certmagic/blob/f83201861a3710cd9e062ea86954928854a0b57b/crypto.go#L289-L318

They represent a reasonable choice of default of ciphers when we default to MinTLSVersion 1.2. If users want to change their TLS version they need to know how to set things themselves.

tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
}

defaultCiphersChaCha = []uint16{
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
}

defaultCiphersAESfirst = append(defaultCiphersAES, defaultCiphersChaCha...)
defaultCiphersChaChaFirst = append(defaultCiphersChaCha, defaultCiphersAES...)
Comment on lines +127 to +128
Copy link
Contributor

@Gusted Gusted Nov 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to add the whole list as well which golang uses as well. https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L271 (Skip the first 6 as they are "defaultCiphersChaCha" and "defaultCiphersAES")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have copied these defaults from

https://github.com/caddyserver/certmagic/blob/f83201861a3710cd9e062ea86954928854a0b57b/crypto.go#L289-L318

They represent a reasonable choice of default of ciphers when we default to MinTLSVersion 1.2.

)

// runHTTPs listens on the provided network address and then calls
// Serve to handle requests on incoming TLS connections.
//
// Filenames containing a certificate and matching private key for the server must
// be provided. If the certificate is signed by a certificate authority, the
// certFile should be the concatenation of the server's certificate followed by the
// CA's certificate.
func runHTTPS(network, listenAddr, name, certFile, keyFile string, m http.Handler) error {
tlsConfig := &tls.Config{}
if tlsConfig.NextProtos == nil {
tlsConfig.NextProtos = []string{"h2", "http/1.1"}
}

if version := toTLSVersion(setting.SSLMinimumVersion); version != 0 {
tlsConfig.MinVersion = version
}
zeripath marked this conversation as resolved.
Show resolved Hide resolved
if version := toTLSVersion(setting.SSLMaximumVersion); version != 0 {
tlsConfig.MaxVersion = version
}

// Set curve preferences
tlsConfig.CurvePreferences = []tls.CurveID{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Golang will by default set a good curvePreference which is bit less restrictive than this one, not sure if it would make sense to set our own then:
https://github.com/golang/go/blob/e8cda0a6c925668972ada40602ada08468fa90dc/src/crypto/tls/common.go#L1025-L1032

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have copied these defaults from

https://github.com/caddyserver/certmagic/blob/f83201861a3710cd9e062ea86954928854a0b57b/crypto.go#L289-L318

They represent a reasonable choice of default of ciphers when we default to MinTLSVersion 1.2. If users want to change their TLS version they need to know how to set things themselves.

tls.X25519,
tls.CurveP256,
}
if curves := toCurvePreferences(setting.SSLCurvePreferences); len(curves) > 0 {
tlsConfig.CurvePreferences = curves
}

// Set cipher suites
tlsConfig.CipherSuites = defaultCiphers()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto as above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The less we say the better.

if ciphers := toTLSCiphers(setting.SSLCipherSuites); len(ciphers) > 0 {
tlsConfig.CipherSuites = ciphers
}

tlsConfig.Certificates = make([]tls.Certificate, 1)

certPEMBlock, err := os.ReadFile(certFile)
if err != nil {
log.Error("Failed to load https cert file %s for %s:%s: %v", certFile, network, listenAddr, err)
return err
}

keyPEMBlock, err := os.ReadFile(keyFile)
if err != nil {
log.Error("Failed to load https key file %s for %s:%s: %v", keyFile, network, listenAddr, err)
return err
}

tlsConfig.Certificates[0], err = tls.X509KeyPair(certPEMBlock, keyPEMBlock)
if err != nil {
log.Error("Failed to create certificate from cert file %s and key file %s for %s:%s: %v", certFile, keyFile, network, listenAddr, err)
return err
}

return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m)
}

func runHTTPSWithTLSConfig(network, listenAddr, name string, tlsConfig *tls.Config, m http.Handler) error {
return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m)
}
17 changes: 17 additions & 0 deletions cmd/web_letsencrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,23 @@ func runLetsEncrypt(listenAddr, domain, directory, email string, m http.Handler)
tlsConfig := magic.TLSConfig()
tlsConfig.NextProtos = append(tlsConfig.NextProtos, "h2")

if version := toTLSVersion(setting.SSLMinimumVersion); version != 0 {
tlsConfig.MinVersion = version
}
if version := toTLSVersion(setting.SSLMaximumVersion); version != 0 {
tlsConfig.MaxVersion = version
}

// Set curve preferences
if curves := toCurvePreferences(setting.SSLCurvePreferences); len(curves) > 0 {
tlsConfig.CurvePreferences = curves
}

// Set cipher suites
if ciphers := toTLSCiphers(setting.SSLCipherSuites); len(ciphers) > 0 {
tlsConfig.CipherSuites = ciphers
}

if enableHTTPChallenge {
go func() {
log.Info("Running Let's Encrypt handler on %s", setting.HTTPAddr+":"+setting.PortToRedirect)
Expand Down
10 changes: 10 additions & 0 deletions custom/conf/app.example.ini
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@ RUN_MODE = ; prod
;REDIRECT_OTHER_PORT = false
;PORT_TO_REDIRECT = 80
;;
;; Minimum and maximum supported TLS versions
;SSL_MIN_VERSION=tls12
;SSL_MAX_VERSION=
;;
;; SSL Curve Preferences
;SSL_CURVE_PREFERENCES=X25519,P256
;;
;; SSL Cipher Suites
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be noted here about TLSv1.3

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree. The less we say the better.

;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.
;;
;; Timeout for any write to the connection. (Set to 0 to disable all timeouts.)
;PER_WRITE_TIMEOUT = 30s
;;
Expand Down
36 changes: 36 additions & 0 deletions docs/content/doc/advanced/config-cheat-sheet.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,42 @@ The following configuration set `Content-Type: application/vnd.android.package-a

- `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.
- `PORT_TO_REDIRECT`: **80**: Port for the http redirection service to listen on. Used when `REDIRECT_OTHER_PORT` is true.
- `SSL_MIN_VERSION`: **tls12**: Set the minimum version of ssl support.
- `SSL_MAX_VERSION`: **\<empty\>**: Set the maximum version of ssl support.
- `SSL_CURVE_PREFERENCES`: **X25519,P256**: Set the prefered curves,
- `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.
- If there is not hardware support for AES suites by default the cha cha suites will be preferred over the AES suites
- supported suites as of go 1.17 are:
- TLS 1.0 - 1.2 cipher suites
- "rsa_with_rc4_128_sha"
- "rsa_with_3des_ede_cbc_sha"
- "rsa_with_aes_128_cbc_sha"
- "rsa_with_aes_256_cbc_sha"
- "rsa_with_aes_128_cbc_sha256"
- "rsa_with_aes_128_gcm_sha256"
- "rsa_with_aes_256_gcm_sha384"
- "ecdhe_ecdsa_with_rc4_128_sha"
- "ecdhe_ecdsa_with_aes_128_cbc_sha"
- "ecdhe_ecdsa_with_aes_256_cbc_sha"
- "ecdhe_rsa_with_rc4_128_sha"
- "ecdhe_rsa_with_3des_ede_cbc_sha"
- "ecdhe_rsa_with_aes_128_cbc_sha"
- "ecdhe_rsa_with_aes_256_cbc_sha"
- "ecdhe_ecdsa_with_aes_128_cbc_sha256"
- "ecdhe_rsa_with_aes_128_cbc_sha256"
- "ecdhe_rsa_with_aes_128_gcm_sha256"
- "ecdhe_ecdsa_with_aes_128_gcm_sha256"
- "ecdhe_rsa_with_aes_256_gcm_sha384"
- "ecdhe_ecdsa_with_aes_256_gcm_sha384"
- "ecdhe_rsa_with_chacha20_poly1305_sha256"
- "ecdhe_ecdsa_with_chacha20_poly1305_sha256"
- TLS 1.3 cipher suites
- "aes_128_gcm_sha256"
- "aes_256_gcm_sha384"
- "chacha20_poly1305_sha256"
- Aliased names
- "ecdhe_rsa_with_chacha20_poly1305" is an alias for "ecdhe_rsa_with_chacha20_poly1305_sha256"
- "ecdhe_ecdsa_with_chacha20_poly1305" is alias for "ecdhe_ecdsa_with_chacha20_poly1305_sha256"
- `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).
By using Lets Encrypt **you must consent** to their [terms of service](https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf).
- `LETSENCRYPT_ACCEPTTOS`: **false**: This is an explicit check that you accept the terms of service for Let's Encrypt.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ require (
github.com/kevinburke/ssh_config v1.1.0 // indirect
github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4
github.com/klauspost/compress v1.13.1
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/klauspost/cpuid/v2 v2.0.9
github.com/klauspost/pgzip v1.2.5 // indirect
github.com/lafriks/xormstore v1.4.0
github.com/lib/pq v1.10.2
Expand Down
36 changes: 0 additions & 36 deletions modules/graceful/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,42 +95,6 @@ func (srv *Server) ListenAndServe(serve ServeFunction) error {
return srv.Serve(serve)
}

// ListenAndServeTLS listens on the provided network address and then calls
// Serve to handle requests on incoming TLS connections.
//
// Filenames containing a certificate and matching private key for the server must
// be provided. If the certificate is signed by a certificate authority, the
// certFile should be the concatenation of the server's certificate followed by the
// CA's certificate.
func (srv *Server) ListenAndServeTLS(certFile, keyFile string, serve ServeFunction) error {
config := &tls.Config{}
if config.NextProtos == nil {
config.NextProtos = []string{"h2", "http/1.1"}
}

config.Certificates = make([]tls.Certificate, 1)

certPEMBlock, err := os.ReadFile(certFile)
if err != nil {
log.Error("Failed to load https cert file %s for %s:%s: %v", certFile, srv.network, srv.address, err)
return err
}

keyPEMBlock, err := os.ReadFile(keyFile)
if err != nil {
log.Error("Failed to load https key file %s for %s:%s: %v", keyFile, srv.network, srv.address, err)
return err
}

config.Certificates[0], err = tls.X509KeyPair(certPEMBlock, keyPEMBlock)
if err != nil {
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)
return err
}

return srv.ListenAndServeTLSConfig(config, serve)
}

// ListenAndServeTLSConfig listens on the provided network address and then calls
// Serve to handle requests on incoming TLS connections.
func (srv *Server) ListenAndServeTLSConfig(tlsConfig *tls.Config, serve ServeFunction) error {
Expand Down
7 changes: 0 additions & 7 deletions modules/graceful/server_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,6 @@ func HTTPListenAndServe(network, address, name string, handler http.Handler) err
return server.ListenAndServe(lHandler)
}

// HTTPListenAndServeTLS listens on the provided network address and then calls Serve
// to handle requests on incoming connections.
func HTTPListenAndServeTLS(network, address, name, certFile, keyFile string, handler http.Handler) error {
server, lHandler := newHTTPServer(network, address, name, handler)
return server.ListenAndServeTLS(certFile, keyFile, lHandler)
}

// HTTPListenAndServeTLSConfig listens on the provided network address and then calls Serve
// to handle requests on incoming connections.
func HTTPListenAndServeTLSConfig(network, address, name string, tlsConfig *tls.Config, handler http.Handler) error {
Expand Down
Loading