"crypto/ed25519"
"crypto/elliptic"
"crypto/rsa"
- "crypto/tls/internal/fips140tls"
"errors"
"fmt"
"hash"
"io"
+ "slices"
)
// verifyHandshakeSignature verifies a signature against pre-hashed
// signatureSchemesForCertificate returns the list of supported SignatureSchemes
// for a given certificate, based on the public key and the protocol version,
// and optionally filtered by its explicit SupportedSignatureAlgorithms.
-//
-// This function must be kept in sync with supportedSignatureAlgorithms.
-// FIPS filtering is applied in the caller, selectSignatureScheme.
func signatureSchemesForCertificate(version uint16, cert *Certificate) []SignatureScheme {
priv, ok := cert.PrivateKey.(crypto.Signer)
if !ok {
}
if cert.SupportedSignatureAlgorithms != nil {
- var filteredSigAlgs []SignatureScheme
- for _, sigAlg := range sigAlgs {
- if isSupportedSignatureAlgorithm(sigAlg, cert.SupportedSignatureAlgorithms) {
- filteredSigAlgs = append(filteredSigAlgs, sigAlg)
- }
- }
- return filteredSigAlgs
+ sigAlgs = slices.DeleteFunc(sigAlgs, func(sigAlg SignatureScheme) bool {
+ return !isSupportedSignatureAlgorithm(sigAlg, cert.SupportedSignatureAlgorithms)
+ })
}
+
+ // Filter out any unsupported signature algorithms, for example due to
+ // FIPS 140-3 policy, or any downstream changes to defaults.go.
+ supportedAlgs := supportedSignatureAlgorithms()
+ sigAlgs = slices.DeleteFunc(sigAlgs, func(sigAlg SignatureScheme) bool {
+ return !isSupportedSignatureAlgorithm(sigAlg, supportedAlgs)
+ })
+
return sigAlgs
}
// Pick signature scheme in the peer's preference order, as our
// preference order is not configurable.
for _, preferredAlg := range peerAlgs {
- if fips140tls.Required() && !isSupportedSignatureAlgorithm(preferredAlg, defaultSupportedSignatureAlgorithmsFIPS) {
- continue
- }
if isSupportedSignatureAlgorithm(preferredAlg, supportedAlgs) {
return preferredAlg, nil
}
// Most applications should not use the cipher suites in this list, and should
// only use those returned by [CipherSuites].
func InsecureCipherSuites() []*CipherSuite {
- // This list includes RC4, CBC_SHA256, and 3DES cipher suites. See
- // cipherSuitesPreferenceOrder for details.
+ // This list includes legacy RSA kex, RC4, CBC_SHA256, and 3DES cipher
+ // suites. See cipherSuitesPreferenceOrder for details.
return []*CipherSuite{
{TLS_RSA_WITH_RC4_128_SHA, "TLS_RSA_WITH_RC4_128_SHA", supportedUpToTLS12, true},
{TLS_RSA_WITH_3DES_EDE_CBC_SHA, "TLS_RSA_WITH_3DES_EDE_CBC_SHA", supportedUpToTLS12, true},
TLS_AES_256_GCM_SHA384: true,
}
-// aesgcmPreferred returns whether the first known cipher in the preference list
-// is an AES-GCM cipher, implying the peer has hardware support for it.
-func aesgcmPreferred(ciphers []uint16) bool {
+// isAESGCMPreferred returns whether we have hardware support for AES-GCM, and the
+// first known cipher in the peer's preference list is an AES-GCM cipher,
+// implying the peer also has hardware support for it.
+func isAESGCMPreferred(ciphers []uint16) bool {
+ if !hasAESGCMHardwareSupport {
+ return false
+ }
for _, cID := range ciphers {
if c := cipherSuiteByID(cID); c != nil {
return aesgcmCiphers[cID]
return t()
}
-func (c *Config) cipherSuites() []uint16 {
+func (c *Config) cipherSuites(aesGCMPreferred bool) []uint16 {
+ var cipherSuites []uint16
if c.CipherSuites == nil {
- if fips140tls.Required() {
- return defaultCipherSuitesFIPS
- }
- return defaultCipherSuites()
+ cipherSuites = defaultCipherSuites(aesGCMPreferred)
+ } else {
+ cipherSuites = supportedCipherSuites(aesGCMPreferred)
+ cipherSuites = slices.DeleteFunc(cipherSuites, func(id uint16) bool {
+ return !slices.Contains(c.CipherSuites, id)
+ })
}
if fips140tls.Required() {
- cipherSuites := slices.Clone(c.CipherSuites)
- return slices.DeleteFunc(cipherSuites, func(id uint16) bool {
- return !slices.Contains(defaultCipherSuitesFIPS, id)
+ cipherSuites = slices.DeleteFunc(cipherSuites, func(id uint16) bool {
+ return !slices.Contains(allowedCipherSuitesFIPS, id)
})
}
- return c.CipherSuites
+ return cipherSuites
+}
+
+// supportedCipherSuites returns the supported TLS 1.0–1.2 cipher suites in an
+// undefined order. For preference ordering, use [Config.cipherSuites].
+func (c *Config) supportedCipherSuites() []uint16 {
+ return c.cipherSuites(false)
}
var supportedVersions = []uint16{
func (c *Config) supportedVersions(isClient bool) []uint16 {
versions := make([]uint16, 0, len(supportedVersions))
for _, v := range supportedVersions {
- if fips140tls.Required() && !slices.Contains(defaultSupportedVersionsFIPS, v) {
+ if fips140tls.Required() && !slices.Contains(allowedSupportedVersionsFIPS, v) {
continue
}
if (c == nil || c.MinVersion == 0) && v < VersionTLS12 {
}
func (c *Config) curvePreferences(version uint16) []CurveID {
- var curvePreferences []CurveID
+ curvePreferences := defaultCurvePreferences()
if fips140tls.Required() {
- curvePreferences = slices.Clone(defaultCurvePreferencesFIPS)
- } else {
- curvePreferences = defaultCurvePreferences()
+ curvePreferences = slices.DeleteFunc(curvePreferences, func(x CurveID) bool {
+ return !slices.Contains(allowedCurvePreferencesFIPS, x)
+ })
}
if c != nil && len(c.CurvePreferences) != 0 {
curvePreferences = slices.DeleteFunc(curvePreferences, func(x CurveID) bool {
}
func (c *Config) supportsCurve(version uint16, curve CurveID) bool {
- for _, cc := range c.curvePreferences(version) {
- if cc == curve {
- return true
- }
- }
- return false
+ return slices.Contains(c.curvePreferences(version), curve)
}
// mutualVersion returns the protocol version to use given the advertised
// versions of the peer. Priority is given to the peer preference order.
func (c *Config) mutualVersion(isClient bool, peerVersions []uint16) (uint16, bool) {
supportedVersions := c.supportedVersions(isClient)
- for _, peerVersion := range peerVersions {
- for _, v := range supportedVersions {
- if v == peerVersion {
- return v, true
- }
+ for _, v := range peerVersions {
+ if slices.Contains(supportedVersions, v) {
+ return v, true
}
}
return 0, false
}
// Finally, there needs to be a mutual cipher suite that uses the static
// RSA key exchange instead of ECDHE.
- rsaCipherSuite := selectCipherSuite(chi.CipherSuites, config.cipherSuites(), func(c *cipherSuite) bool {
+ rsaCipherSuite := selectCipherSuite(chi.CipherSuites, config.supportedCipherSuites(), func(c *cipherSuite) bool {
if c.flags&suiteECDHE != 0 {
return false
}
// Make sure that there is a mutually supported cipher suite that works with
// this certificate. Cipher suite selection will then apply the logic in
// reverse to pick it. See also serverHandshakeState.cipherSuiteOk.
- cipherSuite := selectCipherSuite(chi.CipherSuites, config.cipherSuites(), func(c *cipherSuite) bool {
+ cipherSuite := selectCipherSuite(chi.CipherSuites, config.supportedCipherSuites(), func(c *cipherSuite) bool {
if c.flags&suiteECDHE == 0 {
return false
}
// supportedSignatureAlgorithms returns the supported signature algorithms.
func supportedSignatureAlgorithms() []SignatureScheme {
- if !fips140tls.Required() {
- return defaultSupportedSignatureAlgorithms
+ if fips140tls.Required() {
+ return allowedSupportedSignatureAlgorithmsFIPS
}
- return defaultSupportedSignatureAlgorithmsFIPS
+ return defaultSupportedSignatureAlgorithms
}
func isSupportedSignatureAlgorithm(sigAlg SignatureScheme, supportedSignatureAlgorithms []SignatureScheme) bool {
- for _, s := range supportedSignatureAlgorithms {
- if s == sigAlg {
- return true
- }
- }
- return false
+ return slices.Contains(supportedSignatureAlgorithms, sigAlg)
}
// CertificateVerificationError is returned when certificate verification fails during the handshake.
}
for _, cert := range chain {
- if !fipsAllowCert(cert) {
+ if !isCertificateAllowedFIPS(cert) {
return false
}
}
return true
}
-
-func fipsAllowCert(c *x509.Certificate) bool {
- // The key must be RSA 2048, RSA 3072, RSA 4096,
- // or ECDSA P-256, P-384, P-521.
- switch k := c.PublicKey.(type) {
- case *rsa.PublicKey:
- size := k.N.BitLen()
- return size == 2048 || size == 3072 || size == 4096
- case *ecdsa.PublicKey:
- return k.Curve == elliptic.P256() || k.Curve == elliptic.P384() || k.Curve == elliptic.P521()
- }
-
- return false
-}
var tlsrsakex = godebug.New("tlsrsakex")
var tls3des = godebug.New("tls3des")
-func defaultCipherSuites() []uint16 {
- suites := slices.Clone(cipherSuitesPreferenceOrder)
- return slices.DeleteFunc(suites, func(c uint16) bool {
+func supportedCipherSuites(aesGCMPreferred bool) []uint16 {
+ if aesGCMPreferred {
+ return slices.Clone(cipherSuitesPreferenceOrder)
+ } else {
+ return slices.Clone(cipherSuitesPreferenceOrderNoAES)
+ }
+}
+
+func defaultCipherSuites(aesGCMPreferred bool) []uint16 {
+ cipherSuites := supportedCipherSuites(aesGCMPreferred)
+ return slices.DeleteFunc(cipherSuites, func(c uint16) bool {
return disabledCipherSuites[c] ||
tlsrsakex.Value() != "1" && rsaKexCiphers[c] ||
tls3des.Value() != "1" && tdesCiphers[c]
TLS_AES_128_GCM_SHA256,
TLS_AES_256_GCM_SHA384,
}
-
-// The FIPS-only policies below match BoringSSL's
-// ssl_compliance_policy_fips_202205, which is based on NIST SP 800-52r2, with
-// minor changes per https://go.dev/issue/71757.
-// https://cs.opensource.google/boringssl/boringssl/+/master:ssl/ssl_lib.cc;l=3289;drc=ea7a88fa
-
-var defaultSupportedVersionsFIPS = []uint16{
- VersionTLS12,
- VersionTLS13,
-}
-
-// defaultCurvePreferencesFIPS are the FIPS-allowed curves,
-// in preference order (most preferable first).
-var defaultCurvePreferencesFIPS = []CurveID{CurveP256, CurveP384, CurveP521}
-
-// defaultSupportedSignatureAlgorithmsFIPS currently are a subset of
-// defaultSupportedSignatureAlgorithms without Ed25519 and SHA-1.
-var defaultSupportedSignatureAlgorithmsFIPS = []SignatureScheme{
- PSSWithSHA256,
- PSSWithSHA384,
- PSSWithSHA512,
- PKCS1WithSHA256,
- ECDSAWithP256AndSHA256,
- PKCS1WithSHA384,
- ECDSAWithP384AndSHA384,
- PKCS1WithSHA512,
- ECDSAWithP521AndSHA512,
-}
-
-// defaultCipherSuitesFIPS are the FIPS-allowed cipher suites.
-var defaultCipherSuitesFIPS = []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,
-}
-
-// defaultCipherSuitesTLS13FIPS are the FIPS-allowed cipher suites for TLS 1.3.
-var defaultCipherSuitesTLS13FIPS = []uint16{
- TLS_AES_128_GCM_SHA256,
- TLS_AES_256_GCM_SHA384,
-}
--- /dev/null
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package tls
+
+import (
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rsa"
+ "crypto/x509"
+)
+
+// These Go+BoringCrypto policies mostly match BoringSSL's
+// ssl_compliance_policy_fips_202205, which is based on NIST SP 800-52r2.
+// https://cs.opensource.google/boringssl/boringssl/+/master:ssl/ssl_lib.cc;l=3289;drc=ea7a88fa
+//
+// P-521 is allowed per https://go.dev/issue/71757.
+//
+// They are applied when crypto/tls/fipsonly is imported with GOEXPERIMENT=boringcrypto.
+
+var (
+ allowedSupportedVersionsFIPS = []uint16{
+ VersionTLS12,
+ VersionTLS13,
+ }
+ allowedCurvePreferencesFIPS = []CurveID{
+ CurveP256,
+ CurveP384,
+ CurveP521,
+ }
+ allowedSupportedSignatureAlgorithmsFIPS = []SignatureScheme{
+ PSSWithSHA256,
+ PSSWithSHA384,
+ PSSWithSHA512,
+ PKCS1WithSHA256,
+ ECDSAWithP256AndSHA256,
+ PKCS1WithSHA384,
+ ECDSAWithP384AndSHA384,
+ PKCS1WithSHA512,
+ ECDSAWithP521AndSHA512,
+ }
+ allowedCipherSuitesFIPS = []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,
+ }
+ allowedCipherSuitesTLS13FIPS = []uint16{
+ TLS_AES_128_GCM_SHA256,
+ TLS_AES_256_GCM_SHA384,
+ }
+)
+
+func isCertificateAllowedFIPS(c *x509.Certificate) bool {
+ // The key must be RSA 2048, RSA 3072, RSA 4096,
+ // or ECDSA P-256, P-384, P-521.
+ switch k := c.PublicKey.(type) {
+ case *rsa.PublicKey:
+ size := k.N.BitLen()
+ return size == 2048 || size == 3072 || size == 4096
+ case *ecdsa.PublicKey:
+ return k.Curve == elliptic.P256() || k.Curve == elliptic.P384() || k.Curve == elliptic.P521()
+ }
+
+ return false
+}
}
func isFIPSCipherSuite(id uint16) bool {
+ name := CipherSuiteName(id)
+ if isTLS13CipherSuite(id) {
+ switch id {
+ case TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384:
+ return true
+ case TLS_CHACHA20_POLY1305_SHA256:
+ return false
+ default:
+ panic("unknown TLS 1.3 cipher suite: " + name)
+ }
+ }
switch id {
- case TLS_AES_128_GCM_SHA256,
- TLS_AES_256_GCM_SHA384,
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ case 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:
return true
+ default:
+ return false
}
- return false
}
func isFIPSCurve(id CurveID) bool {
switch id {
case CurveP256, CurveP384, CurveP521:
return true
+ case X25519, X25519MLKEM768:
+ return false
+ default:
+ panic("unknown curve: " + id.String())
}
- return false
}
func isECDSA(id uint16) bool {
func isFIPSSignatureScheme(alg SignatureScheme) bool {
switch alg {
- default:
- return false
case PKCS1WithSHA256,
ECDSAWithP256AndSHA256,
PKCS1WithSHA384,
PSSWithSHA256,
PSSWithSHA384,
PSSWithSHA512:
- // ok
+ return true
+ case Ed25519, PKCS1WithSHA1, ECDSAWithSHA1:
+ return false
+ default:
+ panic("unknown signature scheme: " + alg.String())
}
- return true
}
func TestFIPSServerCipherSuites(t *testing.T) {
keyShares: []keyShare{generateKeyShare(CurveP256)},
supportedPoints: []uint8{pointFormatUncompressed},
supportedVersions: []uint16{VersionTLS12},
- supportedSignatureAlgorithms: defaultSupportedSignatureAlgorithmsFIPS,
+ supportedSignatureAlgorithms: allowedSupportedSignatureAlgorithmsFIPS,
}
if isTLS13CipherSuite(id) {
clientHello.supportedVersions = []uint16{VersionTLS13}
serverConfig.BuildNameToCertificate()
for _, curveid := range defaultCurvePreferences() {
- t.Run(fmt.Sprintf("curve=%d", curveid), func(t *testing.T) {
+ t.Run(fmt.Sprintf("curve=%v", curveid), func(t *testing.T) {
clientConfig := testConfig.Clone()
clientConfig.CurvePreferences = []CurveID{curveid}
runWithFIPSDisabled(t, func(t *testing.T) {
clientErr, serverErr := fipsHandshake(t, testConfig, serverConfig)
if clientErr != nil {
- t.Fatalf("expected handshake with %#x to succeed; client error: %v; server error: %v", sigHash, clientErr, serverErr)
+ t.Fatalf("expected handshake with %v to succeed; client error: %v; server error: %v", sigHash, clientErr, serverErr)
}
})
clientErr, _ := fipsHandshake(t, testConfig, serverConfig)
if isFIPSSignatureScheme(sigHash) {
if clientErr != nil {
- t.Fatalf("expected handshake with %#x to succeed; err=%v", sigHash, clientErr)
+ t.Fatalf("expected handshake with %v to succeed; err=%v", sigHash, clientErr)
}
} else {
if clientErr == nil {
- t.Fatalf("expected handshake with %#x to fail, but it succeeded", sigHash)
+ t.Fatalf("expected handshake with %v to fail, but it succeeded", sigHash)
}
}
})
}
for _, id := range hello.cipherSuites {
if !isFIPSCipherSuite(id) {
- t.Errorf("client offered disallowed suite %#x", id)
+ t.Errorf("client offered disallowed suite %v", CipherSuiteName(id))
}
}
for _, id := range hello.supportedCurves {
if !isFIPSCurve(id) {
- t.Errorf("client offered disallowed curve %d", id)
+ t.Errorf("client offered disallowed curve %v", id)
}
}
for _, sigHash := range hello.supportedSignatureAlgorithms {
fipsOK := mode&fipsCertFIPSOK != 0
runWithFIPSEnabled(t, func(t *testing.T) {
- if fipsAllowCert(cert) != fipsOK {
+ if isCertificateAllowedFIPS(cert) != fipsOK {
t.Errorf("fipsAllowCert(cert with %s key) = %v, want %v", desc, !fipsOK, fipsOK)
}
})
hello.secureRenegotiation = c.clientFinished[:]
}
- preferenceOrder := cipherSuitesPreferenceOrder
- if !hasAESGCMHardwareSupport {
- preferenceOrder = cipherSuitesPreferenceOrderNoAES
- }
- configCipherSuites := config.cipherSuites()
- hello.cipherSuites = make([]uint16, 0, len(configCipherSuites))
-
- 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 maxVersion < VersionTLS12 && suite.flags&suiteTLS12 != 0 {
- continue
- }
- hello.cipherSuites = append(hello.cipherSuites, suiteId)
+ hello.cipherSuites = config.cipherSuites(hasAESGCMHardwareSupport)
+ // Don't advertise TLS 1.2-only cipher suites unless we're attempting TLS 1.2.
+ if maxVersion < VersionTLS12 {
+ hello.cipherSuites = slices.DeleteFunc(hello.cipherSuites, func(id uint16) bool {
+ return cipherSuiteByID(id).flags&suiteTLS12 != 0
+ })
}
_, err := io.ReadFull(config.rand(), hello.random)
hello.cipherSuites = nil
}
if fips140tls.Required() {
- hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13FIPS...)
+ hello.cipherSuites = append(hello.cipherSuites, allowedCipherSuitesTLS13FIPS...)
} else if hasAESGCMHardwareSupport {
hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13...)
} else {
GetConfigForClient: func(chi *ClientHelloInfo) (*Config, error) {
expectedCiphersuites := defaultCipherSuitesTLS13NoAES
if fips140tls.Required() {
- expectedCiphersuites = defaultCipherSuitesTLS13FIPS
+ expectedCiphersuites = allowedCipherSuitesTLS13FIPS
}
if len(chi.CipherSuites) != len(expectedCiphersuites) {
t.Errorf("only TLS 1.3 suites should be advertised, got=%x", chi.CipherSuites)
func (hs *serverHandshakeState) pickCipherSuite() error {
c := hs.c
- 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
- }
- }
- }
+ preferenceList := c.config.cipherSuites(isAESGCMPreferred(hs.clientHello.cipherSuites))
hs.suite = selectCipherSuite(preferenceList, hs.clientHello.cipherSuites, hs.cipherSuiteOk)
if hs.suite == nil {
// Check that we also support the ciphersuite from the session.
suite := selectCipherSuite([]uint16{sessionState.cipherSuite},
- c.config.cipherSuites(), hs.cipherSuiteOk)
+ c.config.supportedCipherSuites(), hs.cipherSuiteOk)
if suite == nil {
return nil
}
hs.hello.compressionMethod = compressionNone
preferenceList := defaultCipherSuitesTLS13
- if !hasAESGCMHardwareSupport || !aesgcmPreferred(hs.clientHello.cipherSuites) {
+ if !hasAESGCMHardwareSupport || !isAESGCMPreferred(hs.clientHello.cipherSuites) {
preferenceList = defaultCipherSuitesTLS13NoAES
}
if fips140tls.Required() {
- preferenceList = defaultCipherSuitesTLS13FIPS
+ preferenceList = allowedCipherSuitesTLS13FIPS
}
for _, suiteID := range preferenceList {
hs.suite = mutualCipherSuiteTLS13(hs.clientHello.cipherSuites, suiteID)
}
if cc.Insecure {
- if slices.Contains(defaultCipherSuites(), c.id) {
+ if slices.Contains(defaultCipherSuites(false), c.id) {
t.Errorf("%#04x: insecure suite in default list", c.id)
}
} else {
- if !slices.Contains(defaultCipherSuites(), c.id) {
+ if !slices.Contains(defaultCipherSuites(false), c.id) {
t.Errorf("%#04x: secure suite not in default list", c.id)
}
}