tls: consider presented intermediates during server connection tls handshake. (#10964)

* use intermediates when verifying

* extract connection state

* remove useless import

* add changelog entry

* golint

* better error

* wording

* collect errors

* use SAN.DNSName instead of CommonName

* Add test for unknown intermediate

* improve changelog entry
This commit is contained in:
Hans Hasselberg 2021-09-09 21:48:54 +02:00 committed by GitHub
parent 2798b3e02f
commit 24c6ce0be0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 64 additions and 20 deletions

3
.changelog/10964.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:bug
tls: consider presented intermediates during server connection tls handshake.
```

View File

@ -1072,13 +1072,22 @@ func (r isReadRequest) HasTimedOut(since time.Time, rpcHoldTimeout, maxQueryTime
} }
func TestRPC_AuthorizeRaftRPC(t *testing.T) { func TestRPC_AuthorizeRaftRPC(t *testing.T) {
caPEM, pk, err := tlsutil.GenerateCA(tlsutil.CAOpts{Days: 5, Domain: "consul"}) caPEM, caPK, err := tlsutil.GenerateCA(tlsutil.CAOpts{Days: 5, Domain: "consul"})
require.NoError(t, err)
caSigner, err := tlsutil.ParseSigner(caPK)
require.NoError(t, err) require.NoError(t, err)
dir := testutil.TempDir(t, "certs") dir := testutil.TempDir(t, "certs")
err = ioutil.WriteFile(filepath.Join(dir, "ca.pem"), []byte(caPEM), 0600) err = ioutil.WriteFile(filepath.Join(dir, "ca.pem"), []byte(caPEM), 0600)
require.NoError(t, err) require.NoError(t, err)
intermediatePEM, intermediatePK, err := tlsutil.GenerateCert(tlsutil.CertOpts{IsCA: true, CA: caPEM, Signer: caSigner, Days: 5})
require.NoError(t, err)
err = ioutil.WriteFile(filepath.Join(dir, "intermediate.pem"), []byte(intermediatePEM), 0600)
require.NoError(t, err)
newCert := func(t *testing.T, caPEM, pk, node, name string) { newCert := func(t *testing.T, caPEM, pk, node, name string) {
t.Helper() t.Helper()
@ -1101,7 +1110,7 @@ func TestRPC_AuthorizeRaftRPC(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
} }
newCert(t, caPEM, pk, "srv1", "server.dc1.consul") newCert(t, caPEM, caPK, "srv1", "server.dc1.consul")
_, connectCApk, err := connect.GeneratePrivateKey() _, connectCApk, err := connect.GeneratePrivateKey()
require.NoError(t, err) require.NoError(t, err)
@ -1113,7 +1122,7 @@ func TestRPC_AuthorizeRaftRPC(t *testing.T) {
c.TLSConfig.KeyFile = filepath.Join(dir, "srv1-server.dc1.consul.key") c.TLSConfig.KeyFile = filepath.Join(dir, "srv1-server.dc1.consul.key")
c.TLSConfig.VerifyIncoming = true c.TLSConfig.VerifyIncoming = true
c.TLSConfig.VerifyServerHostname = true c.TLSConfig.VerifyServerHostname = true
// Enable Auto-Encrypt so that Conenct CA roots are added to the // Enable Auto-Encrypt so that Connect CA roots are added to the
// tlsutil.Configurator. // tlsutil.Configurator.
c.AutoEncryptAllowTLS = true c.AutoEncryptAllowTLS = true
c.CAConfig = &structs.CAConfiguration{ c.CAConfig = &structs.CAConfiguration{
@ -1159,11 +1168,29 @@ func TestRPC_AuthorizeRaftRPC(t *testing.T) {
setupAgentTLSCert := func(name string) func(t *testing.T) string { setupAgentTLSCert := func(name string) func(t *testing.T) string {
return func(t *testing.T) string { return func(t *testing.T) string {
newCert(t, caPEM, pk, "node1", name) newCert(t, caPEM, caPK, "node1", name)
return filepath.Join(dir, "node1-"+name) return filepath.Join(dir, "node1-"+name)
} }
} }
setupAgentTLSCertWithIntermediate := func(name string) func(t *testing.T) string {
return func(t *testing.T) string {
newCert(t, intermediatePEM, intermediatePK, "node1", name)
certPrefix := filepath.Join(dir, "node1-"+name)
f, err := os.OpenFile(certPrefix+".pem", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
t.Fatal(err)
}
if _, err := f.Write([]byte(intermediatePEM)); err != nil {
t.Fatal(err)
}
if err := f.Close(); err != nil {
t.Fatal(err)
}
return certPrefix
}
}
setupConnectCACert := func(name string) func(t *testing.T) string { setupConnectCACert := func(name string) func(t *testing.T) string {
return func(t *testing.T) string { return func(t *testing.T) string {
_, caRoot := srv.caManager.getCAProvider() _, caRoot := srv.caManager.getCAProvider()
@ -1221,6 +1248,11 @@ func TestRPC_AuthorizeRaftRPC(t *testing.T) {
setupCert: setupAgentTLSCert("server.dc1.consul"), setupCert: setupAgentTLSCert("server.dc1.consul"),
conn: useTLSByte, conn: useTLSByte,
}, },
{
name: "TLS byte with server cert in same DC and with unknown intermediate",
setupCert: setupAgentTLSCertWithIntermediate("server.dc1.consul"),
conn: useTLSByte,
},
{ {
name: "TLS byte with ConnectCA leaf cert", name: "TLS byte with ConnectCA leaf cert",
setupCert: setupConnectCACert("server.dc1.consul"), setupCert: setupConnectCACert("server.dc1.consul"),

View File

@ -15,6 +15,7 @@ import (
"time" "time"
"github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/consul/logging" "github.com/hashicorp/consul/logging"
) )
@ -842,15 +843,11 @@ func (c *Configurator) wrapTLSClient(dc string, conn net.Conn) (net.Conn, error)
Intermediates: x509.NewCertPool(), Intermediates: x509.NewCertPool(),
} }
certs := tlsConn.ConnectionState().PeerCertificates cs := tlsConn.ConnectionState()
for i, cert := range certs { for _, cert := range cs.PeerCertificates[1:] {
if i == 0 {
continue
}
opts.Intermediates.AddCert(cert) opts.Intermediates.AddCert(cert)
} }
_, err = cs.PeerCertificates[0].Verify(opts)
_, err = certs[0].Verify(opts)
if err != nil { if err != nil {
tlsConn.Close() tlsConn.Close()
return nil, err return nil, err
@ -915,22 +912,29 @@ func (c *Configurator) AuthorizeServerConn(dc string, conn *tls.Conn) error {
c.lock.RUnlock() c.lock.RUnlock()
expected := c.ServerSNI(dc, "") expected := c.ServerSNI(dc, "")
for _, chain := range conn.ConnectionState().VerifiedChains { cs := conn.ConnectionState()
var errs error
for _, chain := range cs.VerifiedChains {
if len(chain) == 0 { if len(chain) == 0 {
continue continue
} }
clientCert := chain[0] opts := x509.VerifyOptions{
_, err := clientCert.Verify(x509.VerifyOptions{
DNSName: expected, DNSName: expected,
Intermediates: x509.NewCertPool(),
Roots: caPool, Roots: caPool,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}) }
for _, cert := range cs.PeerCertificates[1:] {
opts.Intermediates.AddCert(cert)
}
_, err := cs.PeerCertificates[0].Verify(opts)
if err == nil { if err == nil {
return nil return nil
} }
c.logger.Debug("AuthorizeServerConn failed certificate validation", "error", err) multierror.Append(errs, err)
} }
return fmt.Errorf("a TLS certificate with a CommonName of %v is required for this operation", expected) return fmt.Errorf("AuthorizeServerConn failed certificate validation for certificate with a SAN.DNSName of %v: %w", expected, errs)
} }
// ParseCiphers parse ciphersuites from the comma-separated string into // ParseCiphers parse ciphersuites from the comma-separated string into

View File

@ -54,6 +54,7 @@ type CertOpts struct {
DNSNames []string DNSNames []string
IPAddresses []net.IP IPAddresses []net.IP
ExtKeyUsage []x509.ExtKeyUsage ExtKeyUsage []x509.ExtKeyUsage
IsCA bool
} }
// GenerateCA generates a new CA for agent TLS (not to be confused with Connect TLS) // GenerateCA generates a new CA for agent TLS (not to be confused with Connect TLS)
@ -177,6 +178,10 @@ func GenerateCert(opts CertOpts) (string, string, error) {
DNSNames: opts.DNSNames, DNSNames: opts.DNSNames,
IPAddresses: opts.IPAddresses, IPAddresses: opts.IPAddresses,
} }
if opts.IsCA {
template.IsCA = true
template.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageDigitalSignature
}
bs, err := x509.CreateCertificate(rand.Reader, &template, parent, signee.Public(), opts.Signer) bs, err := x509.CreateCertificate(rand.Reader, &template, parent, signee.Public(), opts.Signer)
if err != nil { if err != nil {