cmd/*, lib/tlsutil: Refactor TLS stuff (fixes #5256) (#5276)

This changes the TLS and certificate handling in a few ways:

- We always use TLS 1.2, both for sync connections (as previously) and
  the GUI/REST/discovery stuff. This is a tightening of the requirements
  on the GUI. AS far as I can tell from caniusethis.com every browser from
  2013 and forward supports TLS 1.2, so I think we should be fine.

- We always greate ECDSA certificates. Previously we'd create
  ECDSA-with-RSA certificates for sync connections and pure RSA
  certificates for the web stuff. The new default is more modern and the
  same everywhere. These certificates are OK in TLS 1.2.

- We use the Go CPU detection stuff to choose the cipher suites to use,
  indirectly. The TLS package uses CPU capabilities probing to select
  either AES-GCM (fast if we have AES-NI) or ChaCha20 (faster if we
  don't). These CPU detection things aren't exported though, so the tlsutil
  package now does a quick TLS handshake with itself as part of init().
  If the chosen cipher suite was AES-GCM we prioritize that, otherwise we
  prefer ChaCha20. Some might call this ugly. I think it's awesome.
This commit is contained in:
Jakob Borg
2018-10-21 14:17:50 +09:00
committed by GitHub
parent c0be9987d0
commit 8519a24ba6
8 changed files with 217 additions and 69 deletions

View File

@@ -27,17 +27,74 @@ var (
ErrIdentificationFailed = fmt.Errorf("failed to identify socket type")
)
// NewCertificate generates and returns a new TLS certificate. If tlsRSABits
// is greater than zero we generate an RSA certificate with the specified
// number of bits. Otherwise we create a 384 bit ECDSA certificate.
func NewCertificate(certFile, keyFile, tlsDefaultCommonName string, tlsRSABits int) (tls.Certificate, error) {
var priv interface{}
var err error
if tlsRSABits > 0 {
priv, err = rsa.GenerateKey(rand.Reader, tlsRSABits)
} else {
priv, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
var (
// The list of cipher suites we will use / suggest for TLS connections.
// This is built based on the component slices below, depending on what
// the hardware prefers.
cipherSuites []uint16
// Suites that are good and fast on hardware with AES-NI. These are
// reordered from the Go default to put the 256 bit ciphers above the
// 128 bit ones - because that looks cooler, even though there is
// probably no relevant difference in strength yet.
gcmSuites = []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
}
// Suites that are good and fast on hardware *without* AES-NI.
chaChaSuites = []uint16{
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
}
// The rest of the suites, minus DES stuff.
otherSuites = []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
}
)
func init() {
// Creates the list of ciper suites that SecureDefault uses.
cipherSuites = buildCipherSuites()
}
// SecureDefault returns a tls.Config with reasonable, secure defaults set.
func SecureDefault() *tls.Config {
// paranoia
cs := make([]uint16, len(cipherSuites))
copy(cs, cipherSuites)
return &tls.Config{
// TLS 1.2 is the minimum we accept
MinVersion: tls.VersionTLS12,
// We want the longer curves at the front, because that's more
// secure (so the web tells me, don't ask me to explain the
// details).
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
// The cipher suite lists built above.
CipherSuites: cs,
// We've put some thought into this choice and would like it to
// matter.
PreferServerCipherSuites: true,
}
}
// NewCertificate generates and returns a new TLS certificate.
func NewCertificate(certFile, keyFile, commonName string) (tls.Certificate, error) {
priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
if err != nil {
return tls.Certificate{}, fmt.Errorf("generate key: %s", err)
}
@@ -48,11 +105,11 @@ func NewCertificate(certFile, keyFile, tlsDefaultCommonName string, tlsRSABits i
template := x509.Certificate{
SerialNumber: new(big.Int).SetInt64(rand.Int63()),
Subject: pkix.Name{
CommonName: tlsDefaultCommonName,
CommonName: commonName,
},
NotBefore: notBefore,
NotAfter: notAfter,
NotBefore: notBefore,
NotAfter: notAfter,
SignatureAlgorithm: x509.ECDSAWithSHA256,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
BasicConstraintsValid: true,
@@ -185,3 +242,79 @@ func pemBlockForKey(priv interface{}) (*pem.Block, error) {
return nil, fmt.Errorf("unknown key type")
}
}
// buildCipherSuites returns a list of cipher suites with either AES-GCM or
// ChaCha20 at the top. This takes advantage of the CPU detection that the
// TLS package does to create an optimal cipher suite list for the current
// hardware.
func buildCipherSuites() []uint16 {
pref := preferredCipherSuite()
for _, suite := range gcmSuites {
if suite == pref {
// Go preferred an AES-GCM suite. Use those first.
return append(gcmSuites, append(chaChaSuites, otherSuites...)...)
}
}
// Use ChaCha20 at the top, then AES-GCM etc.
return append(chaChaSuites, append(gcmSuites, otherSuites...)...)
}
// preferredCipherSuite returns the cipher suite that is selected for a TLS
// connection made with the Go defaults to ourselves. This is (currently,
// probably) either a ChaCha20 suite or an AES-GCM suite, depending on what
// the CPU detection has decided is fastest on this hardware.
//
// The function will return zero if something odd happens, and there's no
// guarantee what cipher suite would be chosen anyway, so the return value
// should be taken with a grain of salt.
func preferredCipherSuite() uint16 {
// This is one of our certs from NewCertificate above, to avoid having
// to generate one at init time just for this function.
crtBs := []byte(`-----BEGIN CERTIFICATE-----
MIIBXDCCAQOgAwIBAgIIQUODl2/bE4owCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ
c3luY3RoaW5nMB4XDTE4MTAxNDA2MjU0M1oXDTQ5MTIzMTIzNTk1OVowFDESMBAG
A1UEAxMJc3luY3RoaW5nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMqP+1lL4
0s/xtI3ygExzYc/GvLHr0qetpBrUVHaDwS/cR1yXDsYaJpJcUNtrf1XK49IlpWW1
Ds8seQsSg7/9BaM/MD0wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUF
BwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMAoGCCqGSM49BAMCA0cAMEQCIFxY
MDBA92FKqZYSZjmfdIbT1OI6S9CnAFvL/pJZJwNuAiAV7osre2NiCHtXABOvsGrH
vKWqDvXcHr6Tlo+LmTAdyg==
-----END CERTIFICATE-----
`)
keyBs := []byte(`-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIHtPxVHlj6Bhi9RgSR2/lAtIQ7APM9wmpaJAcds6TD2CoAoGCCqGSM49
AwEHoUQDQgAEMqP+1lL40s/xtI3ygExzYc/GvLHr0qetpBrUVHaDwS/cR1yXDsYa
JpJcUNtrf1XK49IlpWW1Ds8seQsSg7/9BQ==
-----END EC PRIVATE KEY-----
`)
cert, err := tls.X509KeyPair(crtBs, keyBs)
if err != nil {
return 0
}
serverCfg := &tls.Config{
MinVersion: tls.VersionTLS12,
PreferServerCipherSuites: true,
Certificates: []tls.Certificate{cert},
}
clientCfg := &tls.Config{
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: true,
}
c0, c1 := net.Pipe()
c := tls.Client(c0, clientCfg)
go func() {
c.Handshake()
}()
s := tls.Server(c1, serverCfg)
if err := s.Handshake(); err != nil {
return 0
}
return c.ConnectionState().CipherSuite
}