blob: 5644d235acf1c6b9eb6065de34fb0cc6134c75b0 [file] [edit]
// Copyright 2017 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/fips140"
"crypto/internal/boring"
"crypto/internal/cryptotest"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"internal/testenv"
"math/big"
"net"
"runtime"
"strings"
"testing"
"time"
)
var testConfigFIPS140 = &Config{
Time: testTime,
Certificates: []Certificate{testECDSAP256Cert, testRSAPSSCert, testEd25519Cert},
RootCAs: testRootCertPool,
ServerName: "test.golang.example",
}
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 {
ke, err := keyExchangeForCurveID(group)
if err != nil {
panic(err)
}
_, shares, err := ke.keyShares(rand.Reader)
if err != nil {
panic(err)
}
return shares[0]
}
func TestFIPSServerProtocolVersion(t *testing.T) {
test := func(t *testing.T, name string, v uint16, msg string) {
t.Run(name, func(t *testing.T) {
serverConfig := testConfigFIPS140.Clone()
serverConfig.MinVersion = VersionSSL30
serverConfig.MaxVersion = VersionTLS13
clientConfig := testConfigFIPS140.Clone()
clientConfig.MinVersion = v
clientConfig.MaxVersion = v
_, _, err := testHandshake(t, clientConfig, serverConfig)
if msg == "" {
if err != nil {
t.Fatalf("got error: %v, expected success", err)
}
} else {
if err == nil {
t.Fatalf("got success, expected error")
}
if !strings.Contains(err.Error(), msg) {
t.Fatalf("got error %v, expected %q", err, msg)
}
}
})
}
runWithFIPSDisabled(t, func(t *testing.T) {
test(t, "VersionTLS10", VersionTLS10, "")
test(t, "VersionTLS11", VersionTLS11, "")
test(t, "VersionTLS12", VersionTLS12, "")
test(t, "VersionTLS13", VersionTLS13, "")
})
runWithFIPSEnabled(t, func(t *testing.T) {
test(t, "VersionTLS10", VersionTLS10, "supported versions")
test(t, "VersionTLS11", VersionTLS11, "supported versions")
test(t, "VersionTLS12", VersionTLS12, "")
test(t, "VersionTLS13", VersionTLS13, "")
})
if !fips140.Enforced() {
cryptotest.RerunWithFIPS140Enforced(t)
}
}
func isFIPSVersion(v uint16) bool {
return v == VersionTLS12 || v == VersionTLS13
}
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_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
case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
// Only for the native module.
return !boring.Enabled
}
switch {
case strings.Contains(name, "CHACHA20"):
return false
case strings.HasSuffix(name, "_SHA"): // SHA-1
return false
case strings.HasPrefix(name, "TLS_RSA"): // RSA kex
return false
default:
panic("unknown cipher suite: " + name)
}
}
func isFIPSCurve(id CurveID) bool {
switch id {
case CurveP256, CurveP384, CurveP521:
return true
case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024:
// Only for the native module.
return !boring.Enabled
case X25519:
return false
default:
panic("unknown curve: " + id.String())
}
}
func isECDSA(id uint16) bool {
for _, suite := range cipherSuites {
if suite.id == id {
return suite.flags&suiteECSign == suiteECSign
}
}
return false // TLS 1.3 cipher suites are not tied to the signature algorithm.
}
func isFIPSSignatureScheme(alg SignatureScheme) bool {
switch alg {
case PKCS1WithSHA256,
ECDSAWithP256AndSHA256,
PKCS1WithSHA384,
ECDSAWithP384AndSHA384,
PKCS1WithSHA512,
ECDSAWithP521AndSHA512,
PSSWithSHA256,
PSSWithSHA384,
PSSWithSHA512:
return true
case Ed25519:
// Only for the native module.
return !boring.Enabled
case PKCS1WithSHA1, ECDSAWithSHA1:
return false
default:
panic("unknown signature scheme: " + alg.String())
}
}
func TestFIPSServerCipherSuites(t *testing.T) {
for _, id := range allCipherSuitesIncludingTLS13() {
t.Run(fmt.Sprintf("suite=%s", CipherSuiteName(id)), func(t *testing.T) {
serverConfig := testConfigFIPS140.Clone()
clientHello := &clientHelloMsg{
vers: VersionTLS12,
random: make([]byte, 32),
cipherSuites: []uint16{id},
compressionMethods: []uint8{compressionNone},
supportedCurves: []CurveID{CurveP256},
keyShares: []keyShare{generateKeyShare(CurveP256)},
supportedPoints: []uint8{pointFormatUncompressed},
supportedVersions: []uint16{VersionTLS12},
supportedSignatureAlgorithms: allowedSignatureAlgorithmsFIPS,
}
if isTLS13CipherSuite(id) {
clientHello.supportedVersions = []uint16{VersionTLS13}
} else {
serverConfig.CipherSuites = []uint16{id}
}
runWithFIPSDisabled(t, func(t *testing.T) {
testClientHello(t, serverConfig, clientHello)
})
runWithFIPSEnabled(t, func(t *testing.T) {
msg := ""
if !isFIPSCipherSuite(id) {
msg = "no cipher suite supported by both client and server"
}
testClientHelloFailure(t, serverConfig, clientHello, msg)
})
})
}
if !fips140.Enforced() {
cryptotest.RerunWithFIPS140Enforced(t)
}
}
func TestFIPSServerCurves(t *testing.T) {
for _, curveid := range defaultCurvePreferences() {
t.Run(fmt.Sprintf("curve=%v", curveid), func(t *testing.T) {
testConfig := testConfigFIPS140.Clone()
testConfig.CurvePreferences = []CurveID{curveid}
runWithFIPSDisabled(t, func(t *testing.T) {
if _, _, err := testHandshake(t, testConfig, testConfig); err != nil {
t.Fatalf("got error: %v, expected success", err)
}
})
// With fipstls forced, bad curves should be rejected.
runWithFIPSEnabled(t, func(t *testing.T) {
_, _, err := testHandshake(t, testConfig, testConfig)
if err != nil && isFIPSCurve(curveid) {
t.Fatalf("got error: %v, expected success", err)
} else if err == nil && !isFIPSCurve(curveid) {
t.Fatalf("got success, expected error")
}
})
})
}
if !fips140.Enforced() {
cryptotest.RerunWithFIPS140Enforced(t)
}
}
func fipsHandshake(t *testing.T, clientConfig, serverConfig *Config) (clientErr, serverErr error) {
c, s := localPipe(t)
client := Client(c, clientConfig)
server := Server(s, serverConfig)
done := make(chan error, 1)
go func() {
done <- client.Handshake()
c.Close()
}()
serverErr = server.Handshake()
s.Close()
clientErr = <-done
return
}
func TestFIPSServerSignatureAndHash(t *testing.T) {
defer func() {
testingOnlySupportedSignatureAlgorithms = nil
}()
testenv.SetGODEBUG(t, "tlssha1=1")
for _, sigHash := range defaultSupportedSignatureAlgorithms() {
t.Run(fmt.Sprintf("%v", sigHash), func(t *testing.T) {
serverConfig := testConfigFIPS140.Clone()
testingOnlySupportedSignatureAlgorithms = []SignatureScheme{sigHash}
// PKCS#1 v1.5 signature algorithms can't be used standalone in TLS
// 1.3, and the ECDSA ones bind to the curve used.
serverConfig.MaxVersion = VersionTLS12
runWithFIPSDisabled(t, func(t *testing.T) {
clientErr, serverErr := fipsHandshake(t, testConfigFIPS140, serverConfig)
if clientErr != nil {
t.Fatalf("expected handshake with %v to succeed; client error: %v; server error: %v", sigHash, clientErr, serverErr)
}
})
// With fipstls forced, bad curves should be rejected.
runWithFIPSEnabled(t, func(t *testing.T) {
clientErr, _ := fipsHandshake(t, testConfigFIPS140, serverConfig)
if isFIPSSignatureScheme(sigHash) {
if clientErr != nil {
t.Fatalf("expected handshake with %v to succeed; err=%v", sigHash, clientErr)
}
} else {
if clientErr == nil {
t.Fatalf("expected handshake with %v to fail, but it succeeded", sigHash)
}
}
})
})
}
if !fips140.Enforced() {
cryptotest.RerunWithFIPS140Enforced(t)
}
}
func TestFIPSClientHello(t *testing.T) {
runWithFIPSEnabled(t, testFIPSClientHello)
}
func testFIPSClientHello(t *testing.T) {
// Test that no matter what we put in the client config,
// the client does not offer non-FIPS configurations.
c, s := net.Pipe()
defer c.Close()
defer s.Close()
clientConfig := testConfigFIPS140.Clone()
// All sorts of traps for the client to avoid.
clientConfig.MinVersion = VersionSSL30
clientConfig.MaxVersion = VersionTLS13
clientConfig.CipherSuites = allCipherSuites()
clientConfig.CurvePreferences = defaultCurvePreferences()
go Client(c, clientConfig).Handshake()
srv := Server(s, testConfigFIPS140)
msg, err := srv.readHandshake(nil)
if err != nil {
t.Fatal(err)
}
hello, ok := msg.(*clientHelloMsg)
if !ok {
t.Fatalf("unexpected message type %T", msg)
}
if !isFIPSVersion(hello.vers) {
t.Errorf("client vers=%#x", hello.vers)
}
for _, v := range hello.supportedVersions {
if !isFIPSVersion(v) {
t.Errorf("client offered disallowed version %#x", v)
}
}
for _, id := range hello.cipherSuites {
if !isFIPSCipherSuite(id) {
t.Errorf("client offered disallowed suite %v", CipherSuiteName(id))
}
}
for _, id := range hello.supportedCurves {
if !isFIPSCurve(id) {
t.Errorf("client offered disallowed curve %v", id)
}
}
for _, sigHash := range hello.supportedSignatureAlgorithms {
if !isFIPSSignatureScheme(sigHash) {
t.Errorf("client offered disallowed signature-and-hash %v", sigHash)
}
}
}
func TestFIPSCertAlgs(t *testing.T) {
// arm and wasm time out generating keys. Nothing in this test is
// architecture-specific, so just don't bother on those.
if testenv.CPUIsSlow() {
t.Skipf("skipping on %s/%s because key generation takes too long", runtime.GOOS, runtime.GOARCH)
}
// Set up some roots, intermediate CAs, and leaf certs with various algorithms.
// X_Y is X signed by Y.
R1 := fipsCert(t, "R1", fipsRSAKey(t, 2048), nil, fipsCertCA|fipsCertFIPSOK)
R2 := fipsCert(t, "R2", fipsRSAKey(t, 1024), nil, fipsCertCA)
R3 := fipsCert(t, "R3", fipsRSAKey(t, 4096), nil, fipsCertCA|fipsCertFIPSOK)
M1_R1 := fipsCert(t, "M1_R1", fipsECDSAKey(t, elliptic.P256()), R1, fipsCertCA|fipsCertFIPSOK)
M2_R1 := fipsCert(t, "M2_R1", fipsECDSAKey(t, elliptic.P224()), R1, fipsCertCA)
I_R1 := fipsCert(t, "I_R1", fipsRSAKey(t, 3072), R1, fipsCertCA|fipsCertFIPSOK)
I_R2 := fipsCert(t, "I_R2", I_R1.key, R2, fipsCertCA|fipsCertFIPSOK)
I_M1 := fipsCert(t, "I_M1", I_R1.key, M1_R1, fipsCertCA|fipsCertFIPSOK)
I_M2 := fipsCert(t, "I_M2", I_R1.key, M2_R1, fipsCertCA|fipsCertFIPSOK)
I_R3 := fipsCert(t, "I_R3", fipsRSAKey(t, 3072), R3, fipsCertCA|fipsCertFIPSOK)
fipsCert(t, "I_R3", I_R3.key, R3, fipsCertCA|fipsCertFIPSOK)
L1_I := fipsCert(t, "L1_I", fipsECDSAKey(t, elliptic.P384()), I_R1, fipsCertLeaf|fipsCertFIPSOK)
L2_I := fipsCert(t, "L2_I", fipsRSAKey(t, 1024), I_R1, fipsCertLeaf)
// client verifying server cert
testServerCert := func(t *testing.T, desc string, pool *x509.CertPool, key any, list [][]byte, ok bool) {
clientConfig := testConfig.Clone()
clientConfig.RootCAs = pool
clientConfig.InsecureSkipVerify = false
clientConfig.ServerName = "example.com"
serverConfig := testConfig.Clone()
serverConfig.Certificates = []Certificate{{Certificate: list, PrivateKey: key}}
clientErr, _ := fipsHandshake(t, clientConfig, serverConfig)
if (clientErr == nil) == ok {
if ok {
t.Logf("%s: accept", desc)
} else {
t.Logf("%s: reject", desc)
}
} else {
if ok {
t.Errorf("%s: BAD reject (%v)", desc, clientErr)
} else {
t.Errorf("%s: BAD accept", desc)
}
}
}
// server verifying client cert
testClientCert := func(t *testing.T, desc string, pool *x509.CertPool, key any, list [][]byte, ok bool) {
clientConfig := testConfig.Clone()
clientConfig.ServerName = "example.com"
clientConfig.Certificates = []Certificate{{Certificate: list, PrivateKey: key}}
serverConfig := testConfig.Clone()
serverConfig.ClientCAs = pool
serverConfig.ClientAuth = RequireAndVerifyClientCert
_, serverErr := fipsHandshake(t, clientConfig, serverConfig)
if (serverErr == nil) == ok {
if ok {
t.Logf("%s: accept", desc)
} else {
t.Logf("%s: reject", desc)
}
} else {
if ok {
t.Errorf("%s: BAD reject (%v)", desc, serverErr)
} else {
t.Errorf("%s: BAD accept", desc)
}
}
}
// Run simple basic test with known answers before proceeding to
// exhaustive test with computed answers.
r1pool := x509.NewCertPool()
r1pool.AddCert(R1.cert)
runWithFIPSDisabled(t, func(t *testing.T) {
testServerCert(t, "basic", r1pool, L2_I.key, [][]byte{L2_I.der, I_R1.der}, true)
testClientCert(t, "basic (client cert)", r1pool, L2_I.key, [][]byte{L2_I.der, I_R1.der}, true)
})
runWithFIPSEnabled(t, func(t *testing.T) {
testServerCert(t, "basic (fips)", r1pool, L2_I.key, [][]byte{L2_I.der, I_R1.der}, false)
testClientCert(t, "basic (fips, client cert)", r1pool, L2_I.key, [][]byte{L2_I.der, I_R1.der}, false)
})
if t.Failed() {
t.Fatal("basic test failed, skipping exhaustive test")
}
if testing.Short() {
t.Logf("basic test passed; skipping exhaustive test in -short mode")
return
}
for l := 1; l <= 2; l++ {
leaf := L1_I
if l == 2 {
leaf = L2_I
}
for i := 0; i < 64; i++ {
reachable := map[string]bool{leaf.parentOrg: true}
reachableFIPS := map[string]bool{leaf.parentOrg: leaf.fipsOK}
list := [][]byte{leaf.der}
listName := leaf.name
addList := func(cond int, c *fipsCertificate) {
if cond != 0 {
list = append(list, c.der)
listName += "," + c.name
if reachable[c.org] {
reachable[c.parentOrg] = true
}
if reachableFIPS[c.org] && c.fipsOK {
reachableFIPS[c.parentOrg] = true
}
}
}
addList(i&1, I_R1)
addList(i&2, I_R2)
addList(i&4, I_M1)
addList(i&8, I_M2)
addList(i&16, M1_R1)
addList(i&32, M2_R1)
for r := 1; r <= 3; r++ {
pool := x509.NewCertPool()
rootName := ","
shouldVerify := false
shouldVerifyFIPS := false
addRoot := func(cond int, c *fipsCertificate) {
if cond != 0 {
rootName += "," + c.name
pool.AddCert(c.cert)
if reachable[c.org] {
shouldVerify = true
}
if reachableFIPS[c.org] && c.fipsOK {
shouldVerifyFIPS = true
}
}
}
addRoot(r&1, R1)
addRoot(r&2, R2)
rootName = rootName[1:] // strip leading comma
runWithFIPSDisabled(t, func(t *testing.T) {
testServerCert(t, listName+"->"+rootName[1:], pool, leaf.key, list, shouldVerify)
testClientCert(t, listName+"->"+rootName[1:]+"(client cert)", pool, leaf.key, list, shouldVerify)
})
runWithFIPSEnabled(t, func(t *testing.T) {
testServerCert(t, listName+"->"+rootName[1:]+" (fips)", pool, leaf.key, list, shouldVerifyFIPS)
testClientCert(t, listName+"->"+rootName[1:]+" (fips, client cert)", pool, leaf.key, list, shouldVerifyFIPS)
})
}
}
}
}
const (
fipsCertCA = iota
fipsCertLeaf
fipsCertFIPSOK = 0x80
)
func fipsRSAKey(t *testing.T, size int) *rsa.PrivateKey {
k, err := rsa.GenerateKey(rand.Reader, size)
if err != nil {
t.Fatal(err)
}
return k
}
func fipsECDSAKey(t *testing.T, curve elliptic.Curve) *ecdsa.PrivateKey {
k, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
t.Fatal(err)
}
return k
}
type fipsCertificate struct {
name string
org string
parentOrg string
der []byte
cert *x509.Certificate
key any
fipsOK bool
}
func fipsCert(t *testing.T, name string, key any, parent *fipsCertificate, mode int) *fipsCertificate {
org := name
parentOrg := ""
if i := strings.Index(org, "_"); i >= 0 {
org = org[:i]
parentOrg = name[i+1:]
}
tmpl := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
Organization: []string{org},
},
NotBefore: time.Unix(0, 0),
NotAfter: time.Unix(0, 0),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
BasicConstraintsValid: true,
}
if mode&^fipsCertFIPSOK == fipsCertLeaf {
tmpl.DNSNames = []string{"example.com"}
} else {
tmpl.IsCA = true
tmpl.KeyUsage |= x509.KeyUsageCertSign
}
var pcert *x509.Certificate
var pkey any
if parent != nil {
pcert = parent.cert
pkey = parent.key
} else {
pcert = tmpl
pkey = key
}
var pub any
var desc string
switch k := key.(type) {
case *rsa.PrivateKey:
pub = &k.PublicKey
desc = fmt.Sprintf("RSA-%d", k.N.BitLen())
case *ecdsa.PrivateKey:
pub = &k.PublicKey
desc = "ECDSA-" + k.Curve.Params().Name
default:
t.Fatalf("invalid key %T", key)
}
der, err := x509.CreateCertificate(rand.Reader, tmpl, pcert, pub, pkey)
if err != nil {
t.Fatal(err)
}
cert, err := x509.ParseCertificate(der)
if err != nil {
t.Fatal(err)
}
fipsOK := mode&fipsCertFIPSOK != 0
runWithFIPSEnabled(t, func(t *testing.T) {
if isCertificateAllowedFIPS(cert) != fipsOK {
t.Errorf("fipsAllowCert(cert with %s key) = %v, want %v", desc, !fipsOK, fipsOK)
}
})
return &fipsCertificate{name, org, parentOrg, der, cert, key, fipsOK}
}