Skip to content

Commit

Permalink
Support TLS 1.3 in fipstls mode
Browse files Browse the repository at this point in the history
  • Loading branch information
qmuntal committed Feb 2, 2024
1 parent db51141 commit 7a0550c
Show file tree
Hide file tree
Showing 3 changed files with 341 additions and 12 deletions.
26 changes: 15 additions & 11 deletions eng/doc/fips/UserGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ The Go crypto documentation is available online at https://pkg.go.dev/crypto.
- [crypto/sha512](#cryptosha512)
- [func New](#func-new-3)
- [func New384](#func-new384)
- [func New512_224](#func-new512_224)
- [func New512_256](#func-new512_256)
- [func New512\_224](#func-new512_224)
- [func New512\_256](#func-new512_256)
- [func Sum384](#func-sum384)
- [func Sum512](#func-sum512)
- [func Sum512_224](#func-sum512_224)
- [func Sum512_256](#func-sum512_256)
- [func Sum512\_224](#func-sum512_224)
- [func Sum512\_256](#func-sum512_256)
- [crypto/rsa](#cryptorsa)
- [func DecryptOAEP](#func-decryptoaep)
- [func DecryptPKCS1v15](#func-decryptpkcs1v15)
Expand Down Expand Up @@ -1235,18 +1235,22 @@ Package tls will automatically use FIPS compliant primitives implemented in othe

When using TLS in FIPS-only mode the TLS handshake has the following restrictions:

- TLS versions: `tls.VersionTLS12`
- TLS versions:
- `tls.VersionTLS12`
- `tls.VersionTLS13`
- ECDSA elliptic curves:
- `tls.CurveP256`
- `tls.CurveP384`
- `tls.CurveP521`
- Cipher suites:
- `tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`
- `tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384`
- `tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256`
- `tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384`
- `tls.TLS_RSA_WITH_AES_128_GCM_SHA256`
- `tls.TLS_RSA_WITH_AES_256_GCM_SHA384`
- TLS 1.2:
- `tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256`
- `tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384`
- `tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`
- `tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384`
- TLS 1.3:
- `tls.TLS_AES_128_GCM_SHA256`
- `tls.TLS_AES_256_GCM_SHA384`
- x509 certificate public key:
- `rsa.PublicKey` with a bit length of 2048 or 3072. Bit length of 4096 is still not supported, see [this issue](https://github.com/golang/go/issues/41147) for more info.
- `ecdsa.PublicKey` with a supported elliptic curve.
Expand Down
2 changes: 1 addition & 1 deletion patches/0004-Add-BoringSSL-crypto-backend.patch
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ index 00000000000000..ba4f290a8e394d
+
+func NewAESCipher(key []byte) (cipher.Block, error) { return boring.NewAESCipher(key) }
+func NewGCMTLS(c cipher.Block) (cipher.AEAD, error) { return boring.NewGCMTLS(c) }
+func NewGCMTLS13(c cipher.Block) (cipher.AEAD, error) { return boring.NewGCMTLS13(c) }
+func NewGCMTLS13(c cipher.Block) (cipher.AEAD, error) { panic("cryptobackend: not available") }
+
+type PublicKeyECDSA = boring.PublicKeyECDSA
+type PrivateKeyECDSA = boring.PrivateKeyECDSA
Expand Down
325 changes: 325 additions & 0 deletions patches/0008-Support-TLS-1.3-in-fipstls-mode.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: qmuntal <qmuntaldiaz@microsoft.com>
Date: Fri, 2 Feb 2024 09:25:38 +0100
Subject: [PATCH] Support TLS 1.3 in fipstls mode

---
src/crypto/tls/boring.go | 12 +++--
src/crypto/tls/boring_test.go | 56 +++++++++++++++++++-----
src/crypto/tls/cipher_suites.go | 13 +++++-
src/crypto/tls/handshake_client.go | 4 +-
src/crypto/tls/handshake_client_tls13.go | 4 --
src/crypto/tls/handshake_server_test.go | 28 ++++++++----
src/crypto/tls/handshake_server_tls13.go | 7 ++-
src/crypto/tls/notboring.go | 2 +
8 files changed, 93 insertions(+), 33 deletions(-)

diff --git a/src/crypto/tls/boring.go b/src/crypto/tls/boring.go
index ecd0f5a7b3e9ed..35808a3acd5403 100644
--- a/src/crypto/tls/boring.go
+++ b/src/crypto/tls/boring.go
@@ -17,14 +17,14 @@ func needFIPS() bool {

// fipsMinVersion replaces c.minVersion in FIPS-only mode.
func fipsMinVersion(c *Config) uint16 {
- // FIPS requires TLS 1.2.
+ // FIPS requires TLS 1.2 or TLS 1.3.
return VersionTLS12
}

// fipsMaxVersion replaces c.maxVersion in FIPS-only mode.
func fipsMaxVersion(c *Config) uint16 {
- // FIPS requires TLS 1.2.
- return VersionTLS12
+ // FIPS requires TLS 1.2 or TLS 1.3.
+ return VersionTLS13
}

// default defaultFIPSCurvePreferences is the FIPS-allowed curves,
@@ -75,6 +75,12 @@ func fipsCipherSuites(c *Config) []uint16 {
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
// defaultSupportedSignatureAlgorithms without Ed25519 and SHA-1.
var fipsSupportedSignatureAlgorithms = []SignatureScheme{
diff --git a/src/crypto/tls/boring_test.go b/src/crypto/tls/boring_test.go
index 3e63ba6a053c42..e278f8ad1a8118 100644
--- a/src/crypto/tls/boring_test.go
+++ b/src/crypto/tls/boring_test.go
@@ -25,6 +25,31 @@ import (
"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) {
test := func(name string, v uint16, msg string) {
t.Run(name, func(t *testing.T) {
@@ -33,8 +58,11 @@ func TestBoringServerProtocolVersion(t *testing.T) {
clientHello := &clientHelloMsg{
vers: v,
random: make([]byte, 32),
- cipherSuites: allCipherSuites(),
+ cipherSuites: allCipherSuitesIncludingTLS13(),
compressionMethods: []uint8{compressionNone},
+ supportedCurves: defaultCurvePreferences,
+ keyShares: []keyShare{generateKeyShare(CurveP256)},
+ supportedPoints: []uint8{pointFormatUncompressed},
supportedVersions: []uint16{v},
}
testClientHelloFailure(t, serverConfig, clientHello, msg)
@@ -48,15 +76,15 @@ func TestBoringServerProtocolVersion(t *testing.T) {

fipstls.Force()
defer fipstls.Abandon()
- test("VersionSSL30", VersionSSL30, "client offered only unsupported versions")
- test("VersionTLS10", VersionTLS10, "client offered only unsupported versions")
- test("VersionTLS11", VersionTLS11, "client offered only unsupported versions")
- test("VersionTLS12", VersionTLS12, "")
- test("VersionTLS13", VersionTLS13, "client offered only unsupported versions")
+ test("VersionSSL30/fipstls", VersionSSL30, "client offered only unsupported versions")
+ test("VersionTLS10/fipstls", VersionTLS10, "client offered only unsupported versions")
+ test("VersionTLS11/fipstls", VersionTLS11, "client offered only unsupported versions")
+ test("VersionTLS12/fipstls", VersionTLS12, "")
+ test("VersionTLS13/fipstls", VersionTLS13, "")
}

func isBoringVersion(v uint16) bool {
- return v == VersionTLS12
+ return v == VersionTLS12 || v == VersionTLS13
}

func isBoringCipherSuite(id uint16) bool {
@@ -86,7 +114,7 @@ func isECDSA(id uint16) bool {
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 {
@@ -109,10 +137,9 @@ func isBoringSignatureScheme(alg SignatureScheme) bool {

func TestBoringServerCipherSuites(t *testing.T) {
serverConfig := testConfig.Clone()
- serverConfig.CipherSuites = allCipherSuites()
serverConfig.Certificates = make([]Certificate, 1)

- for _, id := range allCipherSuites() {
+ for _, id := range allCipherSuitesIncludingTLS13() {
if isECDSA(id) {
serverConfig.Certificates[0].Certificate = [][]byte{testECDSACertificate}
serverConfig.Certificates[0].PrivateKey = testECDSAPrivateKey
@@ -128,7 +155,12 @@ func TestBoringServerCipherSuites(t *testing.T) {
cipherSuites: []uint16{id},
compressionMethods: []uint8{compressionNone},
supportedCurves: defaultCurvePreferences,
+ keyShares: []keyShare{generateKeyShare(CurveP256)},
supportedPoints: []uint8{pointFormatUncompressed},
+ supportedVersions: []uint16{VersionTLS12},
+ }
+ if isTLS13CipherSuite(id) {
+ clientHello.supportedVersions = []uint16{VersionTLS13}
}

testClientHello(t, serverConfig, clientHello)
@@ -160,7 +192,9 @@ func TestBoringServerCurves(t *testing.T) {
cipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
compressionMethods: []uint8{compressionNone},
supportedCurves: []CurveID{curveid},
+ keyShares: []keyShare{generateKeyShare(curveid)},
supportedPoints: []uint8{pointFormatUncompressed},
+ supportedVersions: []uint16{VersionTLS12},
}

testClientHello(t, serverConfig, clientHello)
@@ -279,7 +313,7 @@ func TestBoringClientHello(t *testing.T) {
}

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 {
if !isBoringVersion(v) {
diff --git a/src/crypto/tls/cipher_suites.go b/src/crypto/tls/cipher_suites.go
index 68598961fda6e4..8c86b8ad998559 100644
--- a/src/crypto/tls/cipher_suites.go
+++ b/src/crypto/tls/cipher_suites.go
@@ -17,6 +17,7 @@ import (
"fmt"
"hash"
"internal/cpu"
+ "internal/goexperiment"
"runtime"

"golang.org/x/crypto/chacha20poly1305"
@@ -540,7 +541,17 @@ func aeadAESGCMTLS13(key, nonceMask []byte) aead {
if err != nil {
panic(err)
}
- aead, err := cipher.NewGCM(aes)
+ var aead cipher.AEAD
+ if boring.Enabled {
+ if goexperiment.BoringCrypto {
+ aead, err = cipher.NewGCM(aes)
+ } else {
+ aead, err = boring.NewGCMTLS13(aes)
+ }
+ } else {
+ boring.Unreachable()
+ aead, err = cipher.NewGCM(aes)
+ }
if err != nil {
panic(err)
}
diff --git a/src/crypto/tls/handshake_client.go b/src/crypto/tls/handshake_client.go
index 0bb6a0aaef2fff..aee451ad7aa10f 100644
--- a/src/crypto/tls/handshake_client.go
+++ b/src/crypto/tls/handshake_client.go
@@ -129,7 +129,9 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *ecdh.PrivateKey, error) {

var key *ecdh.PrivateKey
if hello.supportedVersions[0] == VersionTLS13 {
- if hasAESGCMHardwareSupport {
+ if needFIPS() {
+ hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13FIPS...)
+ } else if hasAESGCMHardwareSupport {
hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13...)
} else {
hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13NoAES...)
diff --git a/src/crypto/tls/handshake_client_tls13.go b/src/crypto/tls/handshake_client_tls13.go
index 4a8661085ebf57..87fe11de5c5be4 100644
--- a/src/crypto/tls/handshake_client_tls13.go
+++ b/src/crypto/tls/handshake_client_tls13.go
@@ -41,10 +41,6 @@ type clientHandshakeStateTLS13 struct {
func (hs *clientHandshakeStateTLS13) handshake() error {
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,
// sections 4.1.2 and 4.1.3.
if c.handshakes > 0 {
diff --git a/src/crypto/tls/handshake_server_test.go b/src/crypto/tls/handshake_server_test.go
index 04abdcca89040d..778972d38dbcf1 100644
--- a/src/crypto/tls/handshake_server_test.go
+++ b/src/crypto/tls/handshake_server_test.go
@@ -27,6 +27,7 @@ import (
)

func testClientHello(t *testing.T, serverConfig *Config, m handshakeMessage) {
+ t.Helper()
testClientHelloFailure(t, serverConfig, m, "")
}

@@ -52,23 +53,32 @@ func testClientHelloFailure(t *testing.T, serverConfig *Config, m handshakeMessa
ctx := context.Background()
conn := Server(s, serverConfig)
ch, err := conn.readClientHello(ctx)
- hs := serverHandshakeState{
- c: conn,
- ctx: ctx,
- clientHello: ch,
- }
- if err == nil {
+ if err == nil && conn.vers == VersionTLS13 {
+ hs := serverHandshakeStateTLS13{
+ c: conn,
+ ctx: ctx,
+ clientHello: ch,
+ }
err = hs.processClientHello()
- }
- if err == nil {
- err = hs.pickCipherSuite()
+ } else if err == nil {
+ hs := serverHandshakeState{
+ c: conn,
+ ctx: ctx,
+ clientHello: ch,
+ }
+ err = hs.processClientHello()
+ if err == nil {
+ err = hs.pickCipherSuite()
+ }
}
s.Close()
if len(expectedSubStr) == 0 {
if err != nil && err != io.EOF {
+ t.Helper()
t.Errorf("Got error: %s; expected to succeed", err)
}
} else if err == nil || !strings.Contains(err.Error(), expectedSubStr) {
+ t.Helper()
t.Errorf("Got error: %v; expected to match substring '%s'", err, expectedSubStr)
}
}
diff --git a/src/crypto/tls/handshake_server_tls13.go b/src/crypto/tls/handshake_server_tls13.go
index d1c5780533bf6a..2980ff58e65d31 100644
--- a/src/crypto/tls/handshake_server_tls13.go
+++ b/src/crypto/tls/handshake_server_tls13.go
@@ -45,10 +45,6 @@ type serverHandshakeStateTLS13 struct {
func (hs *serverHandshakeStateTLS13) handshake() error {
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.
if err := hs.processClientHello(); err != nil {
return err
@@ -158,6 +154,9 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error {
if !hasAESGCMHardwareSupport || !aesgcmPreferred(hs.clientHello.cipherSuites) {
preferenceList = defaultCipherSuitesTLS13NoAES
}
+ if needFIPS() {
+ preferenceList = defaultCipherSuitesTLS13FIPS
+ }
for _, suiteID := range preferenceList {
hs.suite = mutualCipherSuiteTLS13(hs.clientHello.cipherSuites, suiteID)
if hs.suite != nil {
diff --git a/src/crypto/tls/notboring.go b/src/crypto/tls/notboring.go
index 5a133c9b2f94c7..7625ccb867dd92 100644
--- a/src/crypto/tls/notboring.go
+++ b/src/crypto/tls/notboring.go
@@ -18,3 +18,5 @@ func fipsCurvePreferences(c *Config) []CurveID { panic("fipsCurvePreferences") }
func fipsCipherSuites(c *Config) []uint16 { panic("fipsCipherSuites") }

var fipsSupportedSignatureAlgorithms []SignatureScheme
+
+var defaultCipherSuitesTLS13FIPS []uint16

0 comments on commit 7a0550c

Please sign in to comment.