Skip to content

Commit

Permalink
crypto/tls: make cipher suite preference ordering automatic
Browse files Browse the repository at this point in the history
We now have a (well, two, depending on AES hardware support) universal
cipher suite preference order, based on their security and performance.
Peer and application lists are now treated as filters (and AES hardware
support hints) that are applied to this universal order.

This removes a complex and nuanced decision from the application's
responsibilities, one which we are better equipped to make and which
applications usually don't need to have an opinion about. It also lets
us worry less about what suites we support or enable, because we can be
confident that bad ones won't be selected over good ones.

This also moves 3DES suites to InsecureCipherSuites(), even if they are
not disabled by default. Just because we can keep them as a last resort
it doesn't mean they are secure. Thankfully we had not promised that
Insecure means disabled by default.

Notable test changes:

  - TestCipherSuiteCertPreferenceECDSA was testing that we'd pick the
    right certificate regardless of CipherSuite ordering, which is now
    completely ignored, as tested by TestCipherSuitePreference. Removed.

  - The openssl command of TestHandshakeServerExportKeyingMaterial was
    broken for TLS 1.0 in CL 262857, but its golden file was not
    regenerated, so the test kept passing. It now broke because the
    selected suite from the ones in the golden file changed.

  - In TestAESCipherReordering, "server strongly prefers AES-GCM" is
    removed because there is no way for a server to express a strong
    preference anymore; "client prefers AES-GCM and AES-CBC over ChaCha"
    switched to ChaCha20 when the server lacks AES hardware; and finally
    "client supports multiple AES-GCM" changed to always prefer AES-128
    per the universal preference list.

    * this is going back on an explicit decision from CL 262857, and
      while that client order is weird and does suggest a strong dislike
      for ChaCha20, we have a strong dislike for software AES, so it
      didn't feel worth making the logic more complex

  - All Client-* golden files had to be regenerated because the
    ClientHello cipher suites have changed.
    (Even when Config.CipherSuites was limited to one suite, the TLS 1.3
    default order changed.)

Fixes #45430
Fixes #41476 (as 3DES is now always the last resort)

Change-Id: If5f5d356c0f8d1f1c7542fb06644a478d6bad1e5
Reviewed-on: https://go-review.googlesource.com/c/go/+/314609
Run-TryBot: Filippo Valsorda <filippo@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Trust: Filippo Valsorda <filippo@golang.org>
  • Loading branch information
FiloSottile committed May 8, 2021
1 parent 02ce411 commit 9d0819b
Show file tree
Hide file tree
Showing 68 changed files with 3,754 additions and 3,815 deletions.
259 changes: 216 additions & 43 deletions src/crypto/tls/cipher_suites.go

Large diffs are not rendered by default.

161 changes: 16 additions & 145 deletions src/crypto/tls/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,8 @@ import (
"crypto/x509"
"errors"
"fmt"
"internal/cpu"
"io"
"net"
"runtime"
"sort"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -648,17 +645,21 @@ type Config struct {
// testing or in combination with VerifyConnection or VerifyPeerCertificate.
InsecureSkipVerify bool

// CipherSuites is a list of supported cipher suites for TLS versions up to
// TLS 1.2. If CipherSuites is nil, a default list of secure cipher suites
// is used, with a preference order based on hardware performance. The
// default cipher suites might change over Go versions. Note that TLS 1.3
// ciphersuites are not configurable.
// CipherSuites is a list of enabled TLS 1.0–1.2 cipher suites. The order of
// the list is ignored. Note that TLS 1.3 ciphersuites are not configurable.
//
// If CipherSuites is nil, a safe default list is used. The default cipher
// suites might change over time.
CipherSuites []uint16

// PreferServerCipherSuites controls whether the server selects the
// client's most preferred ciphersuite, or the server's most preferred
// ciphersuite. If true then the server's preference, as expressed in
// the order of elements in CipherSuites, is used.
// PreferServerCipherSuites is a legacy field and has no effect.
//
// It used to control whether the server would follow the client's or the
// server's preference. Servers now select the best mutually supported
// cipher suite based on logic that takes into account inferred client
// hardware, server hardware, and security.
//
// Deprected: PreferServerCipherSuites is ignored.
PreferServerCipherSuites bool

// SessionTicketsDisabled may be set to true to disable session ticket and
Expand Down Expand Up @@ -950,11 +951,10 @@ func (c *Config) time() time.Time {
}

func (c *Config) cipherSuites() []uint16 {
s := c.CipherSuites
if s == nil {
s = defaultCipherSuites()
if c.CipherSuites != nil {
return c.CipherSuites
}
return s
return defaultCipherSuites
}

var supportedVersions = []uint16{
Expand Down Expand Up @@ -1444,87 +1444,6 @@ func defaultConfig() *Config {
return &emptyConfig
}

var (
once sync.Once
varDefaultCipherSuites []uint16
varDefaultCipherSuitesTLS13 []uint16
)

func defaultCipherSuites() []uint16 {
once.Do(initDefaultCipherSuites)
return varDefaultCipherSuites
}

func defaultCipherSuitesTLS13() []uint16 {
once.Do(initDefaultCipherSuites)
return varDefaultCipherSuitesTLS13
}

var (
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
// Keep in sync with crypto/aes/cipher_s390x.go.
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR && (cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)

hasAESGCMHardwareSupport = runtime.GOARCH == "amd64" && hasGCMAsmAMD64 ||
runtime.GOARCH == "arm64" && hasGCMAsmARM64 ||
runtime.GOARCH == "s390x" && hasGCMAsmS390X
)

func initDefaultCipherSuites() {
var topCipherSuites []uint16

if hasAESGCMHardwareSupport {
// If AES-GCM hardware is provided then prioritise AES-GCM
// cipher suites.
topCipherSuites = []uint16{
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
}
varDefaultCipherSuitesTLS13 = []uint16{
TLS_AES_128_GCM_SHA256,
TLS_CHACHA20_POLY1305_SHA256,
TLS_AES_256_GCM_SHA384,
}
} else {
// Without AES-GCM hardware, we put the ChaCha20-Poly1305
// cipher suites first.
topCipherSuites = []uint16{
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
}
varDefaultCipherSuitesTLS13 = []uint16{
TLS_CHACHA20_POLY1305_SHA256,
TLS_AES_128_GCM_SHA256,
TLS_AES_256_GCM_SHA384,
}
}

varDefaultCipherSuites = make([]uint16, 0, len(cipherSuites))
varDefaultCipherSuites = append(varDefaultCipherSuites, topCipherSuites...)

NextCipherSuite:
for _, suite := range cipherSuites {
if suite.flags&suiteDefaultOff != 0 {
continue
}
for _, existing := range varDefaultCipherSuites {
if existing == suite.id {
continue NextCipherSuite
}
}
varDefaultCipherSuites = append(varDefaultCipherSuites, suite.id)
}
}

func unexpectedMessageError(wanted, got interface{}) error {
return fmt.Errorf("tls: received unexpected handshake message of type %T when waiting for %T", got, wanted)
}
Expand All @@ -1537,51 +1456,3 @@ func isSupportedSignatureAlgorithm(sigAlg SignatureScheme, supportedSignatureAlg
}
return false
}

var aesgcmCiphers = map[uint16]bool{
// 1.2
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: true,
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: true,
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: true,
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: true,
// 1.3
TLS_AES_128_GCM_SHA256: true,
TLS_AES_256_GCM_SHA384: true,
}

var nonAESGCMAEADCiphers = map[uint16]bool{
// 1.2
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305: true,
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305: true,
// 1.3
TLS_CHACHA20_POLY1305_SHA256: true,
}

// aesgcmPreferred returns whether the first valid cipher in the preference list
// is an AES-GCM cipher, implying the peer has hardware support for it.
func aesgcmPreferred(ciphers []uint16) bool {
for _, cID := range ciphers {
c := cipherSuiteByID(cID)
if c == nil {
c13 := cipherSuiteTLS13ByID(cID)
if c13 == nil {
continue
}
return aesgcmCiphers[cID]
}
return aesgcmCiphers[cID]
}
return false
}

// deprioritizeAES reorders cipher preference lists by rearranging
// adjacent AEAD ciphers such that AES-GCM based ciphers are moved
// after other AEAD ciphers. It returns a fresh slice.
func deprioritizeAES(ciphers []uint16) []uint16 {
reordered := make([]uint16, len(ciphers))
copy(reordered, ciphers)
sort.SliceStable(reordered, func(i, j int) bool {
return nonAESGCMAEADCiphers[reordered[i]] && aesgcmCiphers[reordered[j]]
})
return reordered
}
36 changes: 21 additions & 15 deletions src/crypto/tls/handshake_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,22 +84,24 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, ecdheParameters, error) {
hello.secureRenegotiation = c.clientFinished[:]
}

possibleCipherSuites := config.cipherSuites()
hello.cipherSuites = make([]uint16, 0, len(possibleCipherSuites))
preferenceOrder := cipherSuitesPreferenceOrder
if !hasAESGCMHardwareSupport {
preferenceOrder = cipherSuitesPreferenceOrderNoAES
}
configCipherSuites := config.cipherSuites()
hello.cipherSuites = make([]uint16, 0, len(configCipherSuites))

for _, suiteId := range possibleCipherSuites {
for _, suite := range cipherSuites {
if suite.id != suiteId {
continue
}
// Don't advertise TLS 1.2-only cipher suites unless
// we're attempting TLS 1.2.
if hello.vers < VersionTLS12 && suite.flags&suiteTLS12 != 0 {
break
}
hello.cipherSuites = append(hello.cipherSuites, suiteId)
break
for _, suiteId := range preferenceOrder {
suite := mutualCipherSuite(configCipherSuites, suiteId)
if suite == nil {
continue
}
// Don't advertise TLS 1.2-only cipher suites unless
// we're attempting TLS 1.2.
if hello.vers < VersionTLS12 && suite.flags&suiteTLS12 != 0 {
continue
}
hello.cipherSuites = append(hello.cipherSuites, suiteId)
}

_, err := io.ReadFull(config.rand(), hello.random)
Expand All @@ -120,7 +122,11 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, ecdheParameters, error) {

var params ecdheParameters
if hello.supportedVersions[0] == VersionTLS13 {
hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13()...)
if hasAESGCMHardwareSupport {
hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13...)
} else {
hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13NoAES...)
}

curveID := config.curvePreferences()[0]
if _, ok := curveForCurveID(curveID); curveID != X25519 && !ok {
Expand Down
35 changes: 14 additions & 21 deletions src/crypto/tls/handshake_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,30 +302,23 @@ func supportsECDHE(c *Config, supportedCurves []CurveID, supportedPoints []uint8
func (hs *serverHandshakeState) pickCipherSuite() error {
c := hs.c

var preferenceList, supportedList []uint16
if c.config.PreferServerCipherSuites {
preferenceList = c.config.cipherSuites()
supportedList = hs.clientHello.cipherSuites

// If the client does not seem to have hardware support for AES-GCM,
// and the application did not specify a cipher suite preference order,
// prefer other AEAD ciphers even if we prioritized AES-GCM ciphers
// by default.
if c.config.CipherSuites == nil && !aesgcmPreferred(hs.clientHello.cipherSuites) {
preferenceList = deprioritizeAES(preferenceList)
}
} else {
preferenceList = hs.clientHello.cipherSuites
supportedList = c.config.cipherSuites()

// If we don't have hardware support for AES-GCM, prefer other AEAD
// ciphers even if the client prioritized AES-GCM.
if !hasAESGCMHardwareSupport {
preferenceList = deprioritizeAES(preferenceList)
preferenceOrder := cipherSuitesPreferenceOrder
if !hasAESGCMHardwareSupport || !aesgcmPreferred(hs.clientHello.cipherSuites) {
preferenceOrder = cipherSuitesPreferenceOrderNoAES
}

configCipherSuites := c.config.cipherSuites()
preferenceList := make([]uint16, 0, len(configCipherSuites))
for _, suiteID := range preferenceOrder {
for _, id := range configCipherSuites {
if id == suiteID {
preferenceList = append(preferenceList, id)
break
}
}
}

hs.suite = selectCipherSuite(preferenceList, supportedList, hs.cipherSuiteOk)
hs.suite = selectCipherSuite(preferenceList, hs.clientHello.cipherSuites, hs.cipherSuiteOk)
if hs.suite == nil {
c.sendAlert(alertHandshakeFailure)
return errors.New("tls: no cipher suite supported by both client and server")
Expand Down
Loading

0 comments on commit 9d0819b

Please sign in to comment.