crypto/tls: align FIPS-only mode with BoringSSL policy

This enables TLS 1.3, disables P-521, and disables non-ECDHE suites.

Fixes #64717
Updates #62372

Change-Id: I3a65b239ef0198bbdbe5e55e0810e7128f90a091
Reviewed-on: https://go-review.googlesource.com/c/go/+/549975
Reviewed-by: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Than McIntosh <thanm@google.com>
This commit is contained in:
Filippo Valsorda 2023-12-14 22:13:29 +01:00 committed by Roland Shoemaker
parent 7383b2a4db
commit 4106de901a
11 changed files with 125 additions and 57 deletions

View File

@ -228,26 +228,41 @@ func (c *aesCipher) NewGCM(nonceSize, tagSize int) (cipher.AEAD, error) {
if tagSize != gcmTagSize { if tagSize != gcmTagSize {
return cipher.NewGCMWithTagSize(&noGCM{c}, tagSize) return cipher.NewGCMWithTagSize(&noGCM{c}, tagSize)
} }
return c.newGCM(false) return c.newGCM(0)
} }
const (
VersionTLS12 = 0x0303
VersionTLS13 = 0x0304
)
func NewGCMTLS(c cipher.Block) (cipher.AEAD, error) { func NewGCMTLS(c cipher.Block) (cipher.AEAD, error) {
return c.(*aesCipher).newGCM(true) return c.(*aesCipher).newGCM(VersionTLS12)
} }
func (c *aesCipher) newGCM(tls bool) (cipher.AEAD, error) { func NewGCMTLS13(c cipher.Block) (cipher.AEAD, error) {
return c.(*aesCipher).newGCM(VersionTLS13)
}
func (c *aesCipher) newGCM(tlsVersion uint16) (cipher.AEAD, error) {
var aead *C.GO_EVP_AEAD var aead *C.GO_EVP_AEAD
switch len(c.key) * 8 { switch len(c.key) * 8 {
case 128: case 128:
if tls { switch tlsVersion {
case VersionTLS12:
aead = C._goboringcrypto_EVP_aead_aes_128_gcm_tls12() aead = C._goboringcrypto_EVP_aead_aes_128_gcm_tls12()
} else { case VersionTLS13:
aead = C._goboringcrypto_EVP_aead_aes_128_gcm_tls13()
default:
aead = C._goboringcrypto_EVP_aead_aes_128_gcm() aead = C._goboringcrypto_EVP_aead_aes_128_gcm()
} }
case 256: case 256:
if tls { switch tlsVersion {
case VersionTLS12:
aead = C._goboringcrypto_EVP_aead_aes_256_gcm_tls12() aead = C._goboringcrypto_EVP_aead_aes_256_gcm_tls12()
} else { case VersionTLS13:
aead = C._goboringcrypto_EVP_aead_aes_256_gcm_tls13()
default:
aead = C._goboringcrypto_EVP_aead_aes_256_gcm() aead = C._goboringcrypto_EVP_aead_aes_256_gcm()
} }
default: default:

View File

@ -50,6 +50,7 @@ func NewHMAC(h func() hash.Hash, key []byte) hash.Hash { panic("boringcrypto: no
func NewAESCipher(key []byte) (cipher.Block, error) { panic("boringcrypto: not available") } func NewAESCipher(key []byte) (cipher.Block, error) { panic("boringcrypto: not available") }
func NewGCMTLS(cipher.Block) (cipher.AEAD, error) { panic("boringcrypto: not available") } func NewGCMTLS(cipher.Block) (cipher.AEAD, error) { panic("boringcrypto: not available") }
func NewGCMTLS13(cipher.Block) (cipher.AEAD, error) { panic("boringcrypto: not available") }
type PublicKeyECDSA struct{ _ int } type PublicKeyECDSA struct{ _ int }
type PrivateKeyECDSA struct{ _ int } type PrivateKeyECDSA struct{ _ int }

View File

@ -6,9 +6,10 @@
package tls package tls
import ( import "crypto/internal/boring/fipstls"
"crypto/internal/boring/fipstls"
) // The FIPS-only policies enforced here currently match BoringSSL's
// ssl_policy_fips_202205.
// needFIPS returns fipstls.Required(); it avoids a new import in common.go. // needFIPS returns fipstls.Required(); it avoids a new import in common.go.
func needFIPS() bool { func needFIPS() bool {
@ -17,19 +18,19 @@ func needFIPS() bool {
// fipsMinVersion replaces c.minVersion in FIPS-only mode. // fipsMinVersion replaces c.minVersion in FIPS-only mode.
func fipsMinVersion(c *Config) uint16 { func fipsMinVersion(c *Config) uint16 {
// FIPS requires TLS 1.2. // FIPS requires TLS 1.2 or TLS 1.3.
return VersionTLS12 return VersionTLS12
} }
// fipsMaxVersion replaces c.maxVersion in FIPS-only mode. // fipsMaxVersion replaces c.maxVersion in FIPS-only mode.
func fipsMaxVersion(c *Config) uint16 { func fipsMaxVersion(c *Config) uint16 {
// FIPS requires TLS 1.2. // FIPS requires TLS 1.2 or TLS 1.3.
return VersionTLS12 return VersionTLS13
} }
// default defaultFIPSCurvePreferences is the FIPS-allowed curves, // default defaultFIPSCurvePreferences is the FIPS-allowed curves,
// in preference order (most preferable first). // in preference order (most preferable first).
var defaultFIPSCurvePreferences = []CurveID{CurveP256, CurveP384, CurveP521} var defaultFIPSCurvePreferences = []CurveID{CurveP256, CurveP384}
// fipsCurvePreferences replaces c.curvePreferences in FIPS-only mode. // fipsCurvePreferences replaces c.curvePreferences in FIPS-only mode.
func fipsCurvePreferences(c *Config) []CurveID { func fipsCurvePreferences(c *Config) []CurveID {
@ -54,8 +55,6 @@ var defaultCipherSuitesFIPS = []uint16{
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
TLS_RSA_WITH_AES_128_GCM_SHA256,
TLS_RSA_WITH_AES_256_GCM_SHA384,
} }
// fipsCipherSuites replaces c.cipherSuites in FIPS-only mode. // fipsCipherSuites replaces c.cipherSuites in FIPS-only mode.
@ -75,8 +74,14 @@ func fipsCipherSuites(c *Config) []uint16 {
return list return list
} }
// defaultCipherSuitesTLS13FIPS are the FIPS-allowed cipher suites for TLS 1.3.
var defaultCipherSuitesTLS13FIPS = []uint16{
TLS_AES_128_GCM_SHA256,
TLS_AES_256_GCM_SHA384,
}
// fipsSupportedSignatureAlgorithms currently are a subset of // fipsSupportedSignatureAlgorithms currently are a subset of
// defaultSupportedSignatureAlgorithms without Ed25519 and SHA-1. // defaultSupportedSignatureAlgorithms without Ed25519, SHA-1, and P-521.
var fipsSupportedSignatureAlgorithms = []SignatureScheme{ var fipsSupportedSignatureAlgorithms = []SignatureScheme{
PSSWithSHA256, PSSWithSHA256,
PSSWithSHA384, PSSWithSHA384,
@ -86,7 +91,6 @@ var fipsSupportedSignatureAlgorithms = []SignatureScheme{
PKCS1WithSHA384, PKCS1WithSHA384,
ECDSAWithP384AndSHA384, ECDSAWithP384AndSHA384,
PKCS1WithSHA512, PKCS1WithSHA512,
ECDSAWithP521AndSHA512,
} }
// supportedSignatureAlgorithms returns the supported signature algorithms. // supportedSignatureAlgorithms returns the supported signature algorithms.

View File

@ -25,6 +25,31 @@ import (
"time" "time"
) )
func allCipherSuitesIncludingTLS13() []uint16 {
s := allCipherSuites()
for _, suite := range cipherSuitesTLS13 {
s = append(s, suite.id)
}
return s
}
func isTLS13CipherSuite(id uint16) bool {
for _, suite := range cipherSuitesTLS13 {
if id == suite.id {
return true
}
}
return false
}
func generateKeyShare(group CurveID) keyShare {
key, err := generateECDHEKey(rand.Reader, group)
if err != nil {
panic(err)
}
return keyShare{group: group, data: key.PublicKey().Bytes()}
}
func TestBoringServerProtocolVersion(t *testing.T) { func TestBoringServerProtocolVersion(t *testing.T) {
test := func(name string, v uint16, msg string) { test := func(name string, v uint16, msg string) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
@ -33,8 +58,11 @@ func TestBoringServerProtocolVersion(t *testing.T) {
clientHello := &clientHelloMsg{ clientHello := &clientHelloMsg{
vers: v, vers: v,
random: make([]byte, 32), random: make([]byte, 32),
cipherSuites: allCipherSuites(), cipherSuites: allCipherSuitesIncludingTLS13(),
compressionMethods: []uint8{compressionNone}, compressionMethods: []uint8{compressionNone},
supportedCurves: defaultCurvePreferences,
keyShares: []keyShare{generateKeyShare(CurveP256)},
supportedPoints: []uint8{pointFormatUncompressed},
supportedVersions: []uint16{v}, supportedVersions: []uint16{v},
} }
testClientHelloFailure(t, serverConfig, clientHello, msg) testClientHelloFailure(t, serverConfig, clientHello, msg)
@ -48,25 +76,25 @@ func TestBoringServerProtocolVersion(t *testing.T) {
fipstls.Force() fipstls.Force()
defer fipstls.Abandon() defer fipstls.Abandon()
test("VersionSSL30", VersionSSL30, "client offered only unsupported versions") test("VersionSSL30/fipstls", VersionSSL30, "client offered only unsupported versions")
test("VersionTLS10", VersionTLS10, "client offered only unsupported versions") test("VersionTLS10/fipstls", VersionTLS10, "client offered only unsupported versions")
test("VersionTLS11", VersionTLS11, "client offered only unsupported versions") test("VersionTLS11/fipstls", VersionTLS11, "client offered only unsupported versions")
test("VersionTLS12", VersionTLS12, "") test("VersionTLS12/fipstls", VersionTLS12, "")
test("VersionTLS13", VersionTLS13, "client offered only unsupported versions") test("VersionTLS13/fipstls", VersionTLS13, "")
} }
func isBoringVersion(v uint16) bool { func isBoringVersion(v uint16) bool {
return v == VersionTLS12 return v == VersionTLS12 || v == VersionTLS13
} }
func isBoringCipherSuite(id uint16) bool { func isBoringCipherSuite(id uint16) bool {
switch id { switch id {
case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, case TLS_AES_128_GCM_SHA256,
TLS_AES_256_GCM_SHA384,
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
TLS_RSA_WITH_AES_128_GCM_SHA256,
TLS_RSA_WITH_AES_256_GCM_SHA384:
return true return true
} }
return false return false
@ -74,7 +102,7 @@ func isBoringCipherSuite(id uint16) bool {
func isBoringCurve(id CurveID) bool { func isBoringCurve(id CurveID) bool {
switch id { switch id {
case CurveP256, CurveP384, CurveP521: case CurveP256, CurveP384:
return true return true
} }
return false return false
@ -86,7 +114,7 @@ func isECDSA(id uint16) bool {
return suite.flags&suiteECSign == suiteECSign return suite.flags&suiteECSign == suiteECSign
} }
} }
panic(fmt.Sprintf("unknown cipher suite %#x", id)) return false // TLS 1.3 cipher suites are not tied to the signature algorithm.
} }
func isBoringSignatureScheme(alg SignatureScheme) bool { func isBoringSignatureScheme(alg SignatureScheme) bool {
@ -98,7 +126,6 @@ func isBoringSignatureScheme(alg SignatureScheme) bool {
PKCS1WithSHA384, PKCS1WithSHA384,
ECDSAWithP384AndSHA384, ECDSAWithP384AndSHA384,
PKCS1WithSHA512, PKCS1WithSHA512,
ECDSAWithP521AndSHA512,
PSSWithSHA256, PSSWithSHA256,
PSSWithSHA384, PSSWithSHA384,
PSSWithSHA512: PSSWithSHA512:
@ -109,10 +136,9 @@ func isBoringSignatureScheme(alg SignatureScheme) bool {
func TestBoringServerCipherSuites(t *testing.T) { func TestBoringServerCipherSuites(t *testing.T) {
serverConfig := testConfig.Clone() serverConfig := testConfig.Clone()
serverConfig.CipherSuites = allCipherSuites()
serverConfig.Certificates = make([]Certificate, 1) serverConfig.Certificates = make([]Certificate, 1)
for _, id := range allCipherSuites() { for _, id := range allCipherSuitesIncludingTLS13() {
if isECDSA(id) { if isECDSA(id) {
serverConfig.Certificates[0].Certificate = [][]byte{testECDSACertificate} serverConfig.Certificates[0].Certificate = [][]byte{testECDSACertificate}
serverConfig.Certificates[0].PrivateKey = testECDSAPrivateKey serverConfig.Certificates[0].PrivateKey = testECDSAPrivateKey
@ -121,14 +147,19 @@ func TestBoringServerCipherSuites(t *testing.T) {
serverConfig.Certificates[0].PrivateKey = testRSAPrivateKey serverConfig.Certificates[0].PrivateKey = testRSAPrivateKey
} }
serverConfig.BuildNameToCertificate() serverConfig.BuildNameToCertificate()
t.Run(fmt.Sprintf("suite=%#x", id), func(t *testing.T) { t.Run(fmt.Sprintf("suite=%s", CipherSuiteName(id)), func(t *testing.T) {
clientHello := &clientHelloMsg{ clientHello := &clientHelloMsg{
vers: VersionTLS12, vers: VersionTLS12,
random: make([]byte, 32), random: make([]byte, 32),
cipherSuites: []uint16{id}, cipherSuites: []uint16{id},
compressionMethods: []uint8{compressionNone}, compressionMethods: []uint8{compressionNone},
supportedCurves: defaultCurvePreferences, supportedCurves: defaultCurvePreferences,
keyShares: []keyShare{generateKeyShare(CurveP256)},
supportedPoints: []uint8{pointFormatUncompressed}, supportedPoints: []uint8{pointFormatUncompressed},
supportedVersions: []uint16{VersionTLS12},
}
if isTLS13CipherSuite(id) {
clientHello.supportedVersions = []uint16{VersionTLS13}
} }
testClientHello(t, serverConfig, clientHello) testClientHello(t, serverConfig, clientHello)
@ -160,7 +191,9 @@ func TestBoringServerCurves(t *testing.T) {
cipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, cipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
compressionMethods: []uint8{compressionNone}, compressionMethods: []uint8{compressionNone},
supportedCurves: []CurveID{curveid}, supportedCurves: []CurveID{curveid},
keyShares: []keyShare{generateKeyShare(curveid)},
supportedPoints: []uint8{pointFormatUncompressed}, supportedPoints: []uint8{pointFormatUncompressed},
supportedVersions: []uint16{VersionTLS12},
} }
testClientHello(t, serverConfig, clientHello) testClientHello(t, serverConfig, clientHello)
@ -279,7 +312,7 @@ func TestBoringClientHello(t *testing.T) {
} }
if !isBoringVersion(hello.vers) { if !isBoringVersion(hello.vers) {
t.Errorf("client vers=%#x, want %#x (TLS 1.2)", hello.vers, VersionTLS12) t.Errorf("client vers=%#x", hello.vers)
} }
for _, v := range hello.supportedVersions { for _, v := range hello.supportedVersions {
if !isBoringVersion(v) { if !isBoringVersion(v) {

View File

@ -556,7 +556,13 @@ func aeadAESGCMTLS13(key, nonceMask []byte) aead {
if err != nil { if err != nil {
panic(err) panic(err)
} }
aead, err := cipher.NewGCM(aes) var aead cipher.AEAD
if boring.Enabled {
aead, err = boring.NewGCMTLS13(aes)
} else {
boring.Unreachable()
aead, err = cipher.NewGCM(aes)
}
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -139,7 +139,9 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *ecdh.PrivateKey, error) {
if len(hello.supportedVersions) == 1 { if len(hello.supportedVersions) == 1 {
hello.cipherSuites = nil hello.cipherSuites = nil
} }
if hasAESGCMHardwareSupport { if needFIPS() {
hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13FIPS...)
} else if hasAESGCMHardwareSupport {
hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13...) hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13...)
} else { } else {
hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13NoAES...) hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13NoAES...)

View File

@ -41,10 +41,6 @@ type clientHandshakeStateTLS13 struct {
func (hs *clientHandshakeStateTLS13) handshake() error { func (hs *clientHandshakeStateTLS13) handshake() error {
c := hs.c c := hs.c
if needFIPS() {
return errors.New("tls: internal error: TLS 1.3 reached in FIPS mode")
}
// The server must not select TLS 1.3 in a renegotiation. See RFC 8446, // The server must not select TLS 1.3 in a renegotiation. See RFC 8446,
// sections 4.1.2 and 4.1.3. // sections 4.1.2 and 4.1.3.
if c.handshakes > 0 { if c.handshakes > 0 {

View File

@ -27,6 +27,7 @@ import (
) )
func testClientHello(t *testing.T, serverConfig *Config, m handshakeMessage) { func testClientHello(t *testing.T, serverConfig *Config, m handshakeMessage) {
t.Helper()
testClientHelloFailure(t, serverConfig, m, "") testClientHelloFailure(t, serverConfig, m, "")
} }
@ -52,23 +53,32 @@ func testClientHelloFailure(t *testing.T, serverConfig *Config, m handshakeMessa
ctx := context.Background() ctx := context.Background()
conn := Server(s, serverConfig) conn := Server(s, serverConfig)
ch, err := conn.readClientHello(ctx) ch, err := conn.readClientHello(ctx)
hs := serverHandshakeState{ if err == nil && conn.vers == VersionTLS13 {
c: conn, hs := serverHandshakeStateTLS13{
ctx: ctx, c: conn,
clientHello: ch, ctx: ctx,
} clientHello: ch,
if err == nil { }
err = hs.processClientHello() err = hs.processClientHello()
} } else if err == nil {
if err == nil { hs := serverHandshakeState{
err = hs.pickCipherSuite() c: conn,
ctx: ctx,
clientHello: ch,
}
err = hs.processClientHello()
if err == nil {
err = hs.pickCipherSuite()
}
} }
s.Close() s.Close()
if len(expectedSubStr) == 0 { if len(expectedSubStr) == 0 {
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
t.Helper()
t.Errorf("Got error: %s; expected to succeed", err) t.Errorf("Got error: %s; expected to succeed", err)
} }
} else if err == nil || !strings.Contains(err.Error(), expectedSubStr) { } else if err == nil || !strings.Contains(err.Error(), expectedSubStr) {
t.Helper()
t.Errorf("Got error: %v; expected to match substring '%s'", err, expectedSubStr) t.Errorf("Got error: %v; expected to match substring '%s'", err, expectedSubStr)
} }
} }

View File

@ -45,10 +45,6 @@ type serverHandshakeStateTLS13 struct {
func (hs *serverHandshakeStateTLS13) handshake() error { func (hs *serverHandshakeStateTLS13) handshake() error {
c := hs.c c := hs.c
if needFIPS() {
return errors.New("tls: internal error: TLS 1.3 reached in FIPS mode")
}
// For an overview of the TLS 1.3 handshake, see RFC 8446, Section 2. // For an overview of the TLS 1.3 handshake, see RFC 8446, Section 2.
if err := hs.processClientHello(); err != nil { if err := hs.processClientHello(); err != nil {
return err return err
@ -163,6 +159,9 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error {
if !hasAESGCMHardwareSupport || !aesgcmPreferred(hs.clientHello.cipherSuites) { if !hasAESGCMHardwareSupport || !aesgcmPreferred(hs.clientHello.cipherSuites) {
preferenceList = defaultCipherSuitesTLS13NoAES preferenceList = defaultCipherSuitesTLS13NoAES
} }
if needFIPS() {
preferenceList = defaultCipherSuitesTLS13FIPS
}
for _, suiteID := range preferenceList { for _, suiteID := range preferenceList {
hs.suite = mutualCipherSuiteTLS13(hs.clientHello.cipherSuites, suiteID) hs.suite = mutualCipherSuiteTLS13(hs.clientHello.cipherSuites, suiteID)
if hs.suite != nil { if hs.suite != nil {

View File

@ -18,3 +18,5 @@ func fipsCurvePreferences(c *Config) []CurveID { panic("fipsCurvePreferences") }
func fipsCipherSuites(c *Config) []uint16 { panic("fipsCipherSuites") } func fipsCipherSuites(c *Config) []uint16 { panic("fipsCipherSuites") }
var fipsSupportedSignatureAlgorithms []SignatureScheme var fipsSupportedSignatureAlgorithms []SignatureScheme
var defaultCipherSuitesTLS13FIPS []uint16

View File

@ -22,7 +22,7 @@ func boringAllowCert(c *Certificate) bool {
} }
// The key must be RSA 2048, RSA 3072, RSA 4096, // The key must be RSA 2048, RSA 3072, RSA 4096,
// or ECDSA P-256, P-384, P-521. // or ECDSA P-256 or P-384.
switch k := c.PublicKey.(type) { switch k := c.PublicKey.(type) {
default: default:
return false return false
@ -31,7 +31,7 @@ func boringAllowCert(c *Certificate) bool {
return false return false
} }
case *ecdsa.PublicKey: case *ecdsa.PublicKey:
if k.Curve != elliptic.P256() && k.Curve != elliptic.P384() && k.Curve != elliptic.P521() { if k.Curve != elliptic.P256() && k.Curve != elliptic.P384() {
return false return false
} }
} }