From 5bf846b35c1113b30f33078fc8a39f7ede4a5358 Mon Sep 17 00:00:00 2001 From: Roland Shoemaker Date: Wed, 15 May 2024 13:46:38 -0700 Subject: [PATCH] crypto/tls: populate Leaf in X509KeyPair Fixes #67065 Change-Id: I189e194de8aa94523eb64e1dd294a70cb81cbdf6 Reviewed-on: https://go-review.googlesource.com/c/go/+/585856 Auto-Submit: Roland Shoemaker LUCI-TryBot-Result: Go LUCI Reviewed-by: Filippo Valsorda Reviewed-by: Damien Neil --- tls.go | 29 ++++++++++++++++++++------- tls_test.go | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 7 deletions(-) diff --git a/tls.go b/tls.go index 8509b7d..b30f0b8 100644 --- a/tls.go +++ b/tls.go @@ -22,6 +22,7 @@ import ( "encoding/pem" "errors" "fmt" + "internal/godebug" "net" "os" "strings" @@ -222,11 +223,14 @@ func (d *Dialer) DialContext(ctx context.Context, network, addr string) (net.Con return c, nil } -// LoadX509KeyPair reads and parses a public/private key pair from a pair -// of files. The files must contain PEM encoded data. The certificate file -// may contain intermediate certificates following the leaf certificate to -// form a certificate chain. On successful return, Certificate.Leaf will -// be nil because the parsed form of the certificate is not retained. +// LoadX509KeyPair reads and parses a public/private key pair from a pair of +// files. The files must contain PEM encoded data. The certificate file may +// contain intermediate certificates following the leaf certificate to form a +// certificate chain. On successful return, Certificate.Leaf will be populated. +// +// Before Go 1.23 Certificate.Leaf was left nil, and the parsed certificate was +// discarded. This behavior can be re-enabled by setting "x509keypairleaf=0" +// in the GODEBUG environment variable. func LoadX509KeyPair(certFile, keyFile string) (Certificate, error) { certPEMBlock, err := os.ReadFile(certFile) if err != nil { @@ -239,9 +243,14 @@ func LoadX509KeyPair(certFile, keyFile string) (Certificate, error) { return X509KeyPair(certPEMBlock, keyPEMBlock) } +var x509keypairleaf = godebug.New("x509keypairleaf") + // X509KeyPair parses a public/private key pair from a pair of -// PEM encoded data. On successful return, Certificate.Leaf will be nil because -// the parsed form of the certificate is not retained. +// PEM encoded data. On successful return, Certificate.Leaf will be populated. +// +// Before Go 1.23 Certificate.Leaf was left nil, and the parsed certificate was +// discarded. This behavior can be re-enabled by setting "x509keypairleaf=0" +// in the GODEBUG environment variable. func X509KeyPair(certPEMBlock, keyPEMBlock []byte) (Certificate, error) { fail := func(err error) (Certificate, error) { return Certificate{}, err } @@ -296,6 +305,12 @@ func X509KeyPair(certPEMBlock, keyPEMBlock []byte) (Certificate, error) { return fail(err) } + if x509keypairleaf.Value() != "0" { + cert.Leaf = x509Cert + } else { + x509keypairleaf.IncNonDefault() + } + cert.PrivateKey, err = parsePrivateKey(keyDERBlock.Bytes) if err != nil { return fail(err) diff --git a/tls_test.go b/tls_test.go index 69b57de..158b459 100644 --- a/tls_test.go +++ b/tls_test.go @@ -8,13 +8,19 @@ import ( "bytes" "context" "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" "crypto/x509" + "crypto/x509/pkix" "encoding/json" + "encoding/pem" "errors" "fmt" "internal/testenv" "io" "math" + "math/big" "net" "os" "reflect" @@ -1945,3 +1951,54 @@ func TestHandshakeKyber(t *testing.T) { }) } } + +func TestX509KeyPairPopulateCertificate(t *testing.T) { + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + keyDER, err := x509.MarshalPKCS8PrivateKey(key) + if err != nil { + t.Fatal(err) + } + keyPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}) + tmpl := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{CommonName: "test"}, + } + certDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key) + if err != nil { + t.Fatal(err) + } + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) + + t.Run("x509keypairleaf=0", func(t *testing.T) { + t.Setenv("GODEBUG", "x509keypairleaf=0") + cert, err := X509KeyPair(certPEM, keyPEM) + if err != nil { + t.Fatal(err) + } + if cert.Leaf != nil { + t.Fatal("Leaf should not be populated") + } + }) + t.Run("x509keypairleaf=1", func(t *testing.T) { + t.Setenv("GODEBUG", "x509keypairleaf=1") + cert, err := X509KeyPair(certPEM, keyPEM) + if err != nil { + t.Fatal(err) + } + if cert.Leaf == nil { + t.Fatal("Leaf should be populated") + } + }) + t.Run("GODEBUG unset", func(t *testing.T) { + cert, err := X509KeyPair(certPEM, keyPEM) + if err != nil { + t.Fatal(err) + } + if cert.Leaf == nil { + t.Fatal("Leaf should be populated") + } + }) +}