Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve trusted_ca_fingerprint warnings and fix tests #285

Merged
merged 3 commits into from
Mar 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions transport/tlscommon/tls_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,17 +183,24 @@ func trustRootCA(cfg *TLSConfig, peerCerts []*x509.Certificate) error {
return fmt.Errorf("decode 'ca_trusted_fingerprint': %w", err)
}

foundCADigests := []string{}

for _, cert := range peerCerts {

// Compute digest for each certificate.
digest := sha256.Sum256(cert.Raw)

if cert.IsCA {
foundCADigests = append(foundCADigests, hex.EncodeToString(digest[:]))
}

if !bytes.Equal(digest[0:], fingerprint) {
continue
}

// Make sure the fingerprint matches a CA certificate
if !cert.IsCA {
logger.Info("Certificate matching 'ca_trusted_fingerprint' found, but is not a CA certificate")
logger.Warn("Certificate matching 'ca_trusted_fingerprint' found, but it is not a CA certificate. 'ca_trusted_fingerprint' can only be used to trust CA certificates.")
continue
}

Expand All @@ -206,7 +213,13 @@ func trustRootCA(cfg *TLSConfig, peerCerts []*x509.Certificate) error {
return nil
}

logger.Warn("no CA certificate matching the fingerprint")
// if we are here, we didn't find any CA certificate matching the fingerprint
if len(foundCADigests) == 0 {
logger.Warn("The remote server's certificate is presented without its certificate chain. Using 'ca_trusted_fingerprint' requires that the server presents a certificate chain that includes the certificate's issuing certificate authority.")
} else {
logger.Warnf("The provided 'ca_trusted_fingerprint': '%s' does not match the fingerprint of any Certificate Authority present in the server's certificate chain. Found the following CA fingerprints instead: %v", cfg.CATrustedFingerprint, foundCADigests)
}

return nil
}

Expand Down
68 changes: 59 additions & 9 deletions transport/tlscommon/tls_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
package tlscommon

import (
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/hex"
"errors"
"net"
"net/http"
Expand All @@ -29,6 +31,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/elastic/elastic-agent-libs/logp"
"github.com/elastic/elastic-agent-libs/transport/tlscommontest"
)

Expand Down Expand Up @@ -191,37 +194,60 @@ func TestTrustRootCA(t *testing.T) {
nonEmptyCertPool.AddCert(certs["wildcard"])
nonEmptyCertPool.AddCert(certs["unknown_authority"])

fingerprint := tlscommontest.GetCertFingerprint(certs["ca"])
certfingerprint := tlscommontest.GetCertFingerprint(certs["correct"])
cafingerprint := tlscommontest.GetCertFingerprint(certs["ca"])

unknownAuthorityDigest := sha256.Sum256(certs["unknown_authority"].Raw)
unknownAuthoritySha256 := hex.EncodeToString(unknownAuthorityDigest[:])

testCases := []struct {
name string
rootCAs *x509.CertPool
caTrustedFingerprint string
peerCerts []*x509.Certificate
expectingWarnings []string
expectingError bool
expectedRootCAsLen int
}{
{
name: "RootCA cert matches the fingerprint and is added to cfg.RootCAs",
caTrustedFingerprint: fingerprint,
caTrustedFingerprint: cafingerprint,
peerCerts: []*x509.Certificate{certs["correct"], certs["ca"]},
expectedRootCAsLen: 1,
},
{
name: "RootCA cert doesn not matche the fingerprint and is not added to cfg.RootCAs",
caTrustedFingerprint: fingerprint,
peerCerts: []*x509.Certificate{certs["correct"], certs["ca"]},
name: "RootCA cert doesn't match the fingerprint and is not added to cfg.RootCAs",
caTrustedFingerprint: cafingerprint,
peerCerts: []*x509.Certificate{certs["correct"], certs["unknown_authority"]},
expectingWarnings: []string{"The provided 'ca_trusted_fingerprint': '" + cafingerprint + "' does not match the fingerprint of any Certificate Authority present in the server's certificate chain. Found the following CA fingerprints instead: [" + unknownAuthoritySha256 + "]"},
expectedRootCAsLen: 0,
},
{
name: "Peer cert does not include a CA Certificate and is not added to cfg.RootCAs",
caTrustedFingerprint: cafingerprint,
peerCerts: []*x509.Certificate{certs["correct"]},
expectingWarnings: []string{"The remote server's certificate is presented without its certificate chain. Using 'ca_trusted_fingerprint' requires that the server presents a certificate chain that includes the certificate's issuing certificate authority."},
expectedRootCAsLen: 0,
},
{
name: "fingerprint matches peer cert instead of the CA Certificate and is not added to cfg.RootCAs",
caTrustedFingerprint: certfingerprint,
peerCerts: []*x509.Certificate{certs["correct"]},
expectingWarnings: []string{
"Certificate matching 'ca_trusted_fingerprint' found, but it is not a CA certificate. 'ca_trusted_fingerprint' can only be used to trust CA certificates.",
"The remote server's certificate is presented without its certificate chain. Using 'ca_trusted_fingerprint' requires that the server presents a certificate chain that includes the certificate's issuing certificate authority.",
},
expectedRootCAsLen: 0,
},
{
name: "non empty CertPool has the RootCA added",
rootCAs: nonEmptyCertPool,
caTrustedFingerprint: fingerprint,
caTrustedFingerprint: cafingerprint,
peerCerts: []*x509.Certificate{certs["correct"], certs["ca"]},
expectedRootCAsLen: 3,
},
{
name: "invalis HEX encoding",
name: "invalid HEX encoding",
caTrustedFingerprint: "INVALID ENCODING",
expectedRootCAsLen: 0,
expectingError: true,
Expand All @@ -234,6 +260,10 @@ func TestTrustRootCA(t *testing.T) {
RootCAs: tc.rootCAs,
CATrustedFingerprint: tc.caTrustedFingerprint,
}

// Capture the logs
_ = logp.DevelopmentSetup(logp.ToObserverOutput())

err := trustRootCA(&cfg, tc.peerCerts)
if tc.expectingError && err == nil {
t.Fatal("expecting an error when calling trustRootCA")
Expand All @@ -243,9 +273,29 @@ func TestTrustRootCA(t *testing.T) {
t.Fatalf("did not expect an error calling trustRootCA: %v", err)
}

if tc.expectedRootCAsLen != 0 {
if len(tc.expectingWarnings) > 0 {
warnings := logp.ObserverLogs().FilterLevelExact(logp.WarnLevel.ZapLevel()).TakeAll()
if len(warnings) == 0 {
t.Fatal("expecting a warning message")
}
if len(warnings) != len(tc.expectingWarnings) {
t.Fatalf("expecting %d warning messages, got %d", len(tc.expectingWarnings), len(warnings))
}

for i, expectedWarning := range tc.expectingWarnings {
if got := warnings[i].Message; got != expectedWarning {
t.Fatalf("expecting warning message to be '%s', got '%s'", expectedWarning, got)
}
}
}

if tc.expectedRootCAsLen == 0 {
if cfg.RootCAs != nil {
t.Fatal("cfg.RootCAs should be nil")
}
} else {
if cfg.RootCAs == nil {
t.Fatal("cfg.RootCAs cannot be nil")
t.Fatal("cfg.RootCAs should not be nil")
}

// we want to know the number of certificates in the CertPool (RootCAs), as it is not
Expand Down
2 changes: 1 addition & 1 deletion transport/tlscommontest/test_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func GenTestCerts(t *testing.T) map[string]*x509.Certificate {
"unknown_authority": {
ca: unknownCA,
keyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
isCA: false,
isCA: true,
dnsNames: []string{"localhost"},
// IPV4 and IPV6
ips: []net.IP{{127, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
Expand Down
Loading