1// Copyright 2021 The Gitea Authors. All rights reserved.
2// SPDX-License-Identifier: MIT
3
4package cmd
5
6import (
7 "crypto/tls"
8 "net/http"
9 "os"
10 "strings"
11
12 "forgejo.org/modules/graceful"
13 "forgejo.org/modules/log"
14 "forgejo.org/modules/setting"
15
16 "github.com/klauspost/cpuid/v2"
17)
18
19var 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
27func 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
36var curveStringMap = map[string]tls.CurveID{
37 "x25519": tls.X25519,
38 "p256": tls.CurveP256,
39 "p384": tls.CurveP384,
40 "p521": tls.CurveP521,
41}
42
43func 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
57var 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
87func 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.)
107func defaultCiphers() []uint16 {
108 if cpuid.CPU.Supports(cpuid.AESNI) {
109 return defaultCiphersAESfirst
110 }
111 return defaultCiphersChaChaFirst
112}
113
114var (
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.
138func runHTTPS(network, listenAddr, name, certFile, keyFile string, m http.Handler, useProxyProtocol, proxyProtocolTLSBridging bool) 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, useProxyProtocol, proxyProtocolTLSBridging)
187}
188
189func runHTTPSWithTLSConfig(network, listenAddr, name string, tlsConfig *tls.Config, m http.Handler, useProxyProtocol, proxyProtocolTLSBridging bool) error {
190 return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m, useProxyProtocol, proxyProtocolTLSBridging)
191}