Skip to content

crypto/x509: Verify fails to find valid path when some paths have EKU constraint violations #48869

@JackOfMostTrades

Description

@JackOfMostTrades

What version of Go are you using (go version)?

1.17.2

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/ihaken/.cache/go-build"
GOENV="/home/ihaken/.config/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/home/ihaken/go/pkg/mod"
GONOPROXY="stash.corp.netflix.com"
GONOSUMDB="stash.corp.netflix.com"
GOOS="linux"
GOPATH="/home/ihaken/go"
GOPRIVATE="stash.corp.netflix.com"
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/home/ihaken/bin/go1.17.2-linux-amd64"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/home/ihaken/bin/go1.17.2-linux-amd64/pkg/tool/linux_amd64"
GOVCS=""
GOVERSION="go1.17.2"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/dev/null"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3893857195=/tmp/go-build -gno-record-gcc-switches"

What did you do?

The path building in x509/verify.go appears to fail to find a valid path when some paths are invalid due to EKU constraints on intermediate CAs.

The certificates in this test case form the following graph (lifted from RFC 4158, figure 7):

     +---------+
     |  Trust  |
     | Anchor  |
     +---------+
      |       |
      v       v
   +---+    +---+
   | A |<-->| C |
   +---+    +---+
    |         |
    |  +---+  |
    +->| B |<-+
       +---+
         |
         v
       +----+
       | EE |
       +----+

There are seven certificates (besides the self-signed certificate for the trust anchor), but the certificate "C => B" has an "Email Protection" EKU which causes it to be considered invalid for this x509.Verify call (which requires CAs to allow for Server Authentication EKU). Despite this certificate being invalid, x509.Verify should be able to find a path such as "Trust Anchor => C => A => B => EE".

A test case reproducing this is below. Note that simply excluding the first intermediate cert below allows x509.Verify to find the expected path.

package foo

import (
	"crypto/x509"
	"encoding/base64"
	"testing"
	"time"
)

func mustParse(s string) *x509.Certificate {
	certBytes, err := base64.StdEncoding.DecodeString(s)
	if err != nil {
		panic(err)
	}
	cert, err := x509.ParseCertificate(certBytes)
	if err != nil {
		panic(err)
	}
	return cert
}

func TestEkuPathBuilding(t *testing.T) {
	truststore := x509.NewCertPool()
	truststore.AddCert(mustParse("MIICDTCCAbSgAwIBAgISAYNv3E86soSvTqnYh7AQuZp1MAoGCCqGSM49BAMCMGYxFjAUBgNVBAoTDWJldHRlcnRscy5jb20xHTAbBgNVBAMMFGJldHRlcnRsc190cnVzdF9yb290MS0wKwYDVQQFEyQ3ZTUwODA0ZC03YTg0LTRiMmUtYTkyOC03MDI3MjFjYzU0MDMwHhcNMjExMDA3MTcyMjM0WhcNMjIxMDA5MTcyMjM0WjBmMRYwFAYDVQQKEw1iZXR0ZXJ0bHMuY29tMR0wGwYDVQQDDBRiZXR0ZXJ0bHNfdHJ1c3Rfcm9vdDEtMCsGA1UEBRMkN2U1MDgwNGQtN2E4NC00YjJlLWE5MjgtNzAyNzIxY2M1NDAzMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENGXYZwSkdK6+vAvj7/il3Ny7TKLlEVoyMfy9OeJkxzWEwDT1yziP/qLrq9lyczJTSkf89boGIfYWtkulJhGufqNCMEAwDgYDVR0PAQH/BAQDAgIEMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKLkJSmoYL7BBE+YTsJrYerI6BpkMAoGCCqGSM49BAMCA0cAMEQCICfcFvAkR8A6a7kSercJ0097QhufwlLOulxDurchFRzjAiBqImnlB5ba5ZoosJMGb9tdOQpyB3P35L8DxBOwBtvdJA=="))
	
	leafCert := mustParse("MIIB/jCCAaSgAwIBAgISAY7+auYZRhGK+2ljtZkwdohXMAoGCCqGSM49BAMCMFMxFjAUBgNVBAoTDWJldHRlcnRscy5jb20xCjAIBgNVBAMTAUIxLTArBgNVBAUTJGVkNmNkMWNiLTFiOWEtNGMyZC04Zjk3LWVjMjYyY2VjNWNmMzAeFw0yMTEwMDcxNzIyMzRaFw0yMjEwMDkxNzIyMzRaMFQxFjAUBgNVBAoTDWJldHRlcnRscy5jb20xCzAJBgNVBAMTAkVFMS0wKwYDVQQFEyQxOTY2NDc4Yi01YWQ4LTRmZDAtODFhYy03YmJlODczYmNiZjAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQFChvHbqUXsBfGrWEojOk0csKDBOs5Ad8v/QnXuq+a8GYlamS+zy9XvCNC13nGgPX4isJLzTXw5ptYf333Ex+lo1cwVTAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBTtMMD8JlKX+vA33cElQe2GOlECgTAUBgNVHREEDTALgglsb2NhbGhvc3QwCgYIKoZIzj0EAwIDSAAwRQIgJPo8+u371jzPFkG5YQkOhCCE8B8TS7oHlXiaKCxc7wgCIQDJoXSBzvm5f/OUojj/PdxnZtY1Vznf0oDP9hI8LvSFpA==")
		
	intermediates := x509.NewCertPool()
	for _, cert := range []string{
		"MIICHzCCAcSgAwIBAgISAZ/vGwdCCamGv0KKXppr4dc1MAoGCCqGSM49BAMCMFMxFjAUBgNVBAoTDWJldHRlcnRscy5jb20xCjAIBgNVBAMTAUMxLTArBgNVBAUTJDBjMWZiZDZkLWRmYTktNDA0Zi1iNGE4LTg4MTlhYjIxNTkyNTAeFw0yMTEwMDcxNzIyMzRaFw0yMjEwMDkxNzIyMzRaMFMxFjAUBgNVBAoTDWJldHRlcnRscy5jb20xCjAIBgNVBAMTAUIxLTArBgNVBAUTJGVkNmNkMWNiLTFiOWEtNGMyZC04Zjk3LWVjMjYyY2VjNWNmMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOYibTGILHFXfbH3DOKJjodZ3v6UuPu3F8rT9m37Xa1O3tCvIecBE8VZ1+migbzeRxUV3LUGPxXed9bE60ENvO2jeDB2MA4GA1UdDwEB/wQEAwICBDATBgNVHSUEDDAKBggrBgEFBQcDBDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTtMMD8JlKX+vA33cElQe2GOlECgTAfBgNVHSMEGDAWgBSzLYGv7LzSVT+vq38dh28P45qy9DAKBggqhkjOPQQDAgNJADBGAiEApfI8g4xzvRThwztZYEdVV+bLZw4dk7lV2/pVI0S3HVgCIQCxpPenGZY9myDBAC4B9bYfhxGo6ZxVmYFQLuFuJOoEcQ==",
		"MIICCTCCAa+gAwIBAgISAby+6XTq4ojH1RUoePd9hHlJMAoGCCqGSM49BAMCMFMxFjAUBgNVBAoTDWJldHRlcnRscy5jb20xCjAIBgNVBAMTAUExLTArBgNVBAUTJGI5YzRlMWFlLTgyNTEtNDgzZC04OWMzLTQ1YTc1MjUwNzc5ZDAeFw0yMTEwMDcxNzIyMzRaFw0yMjEwMDkxNzIyMzRaMFMxFjAUBgNVBAoTDWJldHRlcnRscy5jb20xCjAIBgNVBAMTAUIxLTArBgNVBAUTJGVkNmNkMWNiLTFiOWEtNGMyZC04Zjk3LWVjMjYyY2VjNWNmMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOYibTGILHFXfbH3DOKJjodZ3v6UuPu3F8rT9m37Xa1O3tCvIecBE8VZ1+migbzeRxUV3LUGPxXed9bE60ENvO2jYzBhMA4GA1UdDwEB/wQEAwICBDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTtMMD8JlKX+vA33cElQe2GOlECgTAfBgNVHSMEGDAWgBSTUb2mpweQtatmY5bbxTHqUJfwrDAKBggqhkjOPQQDAgNIADBFAiAYmqcbTJPPE2aJhTjTSr49yKQSHEFKffCi2wTIr15MxgIhAKT1XlaFHrX3zlyzaE3Bq9KTT89hdYyYXpRyucK+eG3F",
		"MIICCDCCAa+gAwIBAgISAWiJ6wlu8cnqlmE8mo/NwxErMAoGCCqGSM49BAMCMFMxFjAUBgNVBAoTDWJldHRlcnRscy5jb20xCjAIBgNVBAMTAUMxLTArBgNVBAUTJDBjMWZiZDZkLWRmYTktNDA0Zi1iNGE4LTg4MTlhYjIxNTkyNTAeFw0yMTEwMDcxNzIyMzRaFw0yMjEwMDkxNzIyMzRaMFMxFjAUBgNVBAoTDWJldHRlcnRscy5jb20xCjAIBgNVBAMTAUExLTArBgNVBAUTJGI5YzRlMWFlLTgyNTEtNDgzZC04OWMzLTQ1YTc1MjUwNzc5ZDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABFyCj4kv2+ksqHeAH1Oc9vkaWfNFvlgdnjw2k3Sgdyld+7wnxYmqtIA8KqVUMR7Xk3TcKpP3Wq62yMvZ/KmoE1SjYzBhMA4GA1UdDwEB/wQEAwICBDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSTUb2mpweQtatmY5bbxTHqUJfwrDAfBgNVHSMEGDAWgBSzLYGv7LzSVT+vq38dh28P45qy9DAKBggqhkjOPQQDAgNHADBEAiBxTzv5TZYlSzUZDI1TxCMMIExd0t80Cf43MaP1p+31mgIgJ/MOjtCSconc2IJE66fUwg3UpttPZIKkfrQgVsc4u2I=",
		"MIICCDCCAa+gAwIBAgISATAvFW8FOPawt5+dEiyysohYMAoGCCqGSM49BAMCMFMxFjAUBgNVBAoTDWJldHRlcnRscy5jb20xCjAIBgNVBAMTAUExLTArBgNVBAUTJGI5YzRlMWFlLTgyNTEtNDgzZC04OWMzLTQ1YTc1MjUwNzc5ZDAeFw0yMTEwMDcxNzIyMzRaFw0yMjEwMDkxNzIyMzRaMFMxFjAUBgNVBAoTDWJldHRlcnRscy5jb20xCjAIBgNVBAMTAUMxLTArBgNVBAUTJDBjMWZiZDZkLWRmYTktNDA0Zi1iNGE4LTg4MTlhYjIxNTkyNTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOtbqq4w2iVAgrzIB1cOYZcJ+3l3IhHNAaHUaStOaic5cKhJZngYW/etsKsBTEeGf85QZZkK9GTnGgz0Jy3fygujYzBhMA4GA1UdDwEB/wQEAwICBDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSzLYGv7LzSVT+vq38dh28P45qy9DAfBgNVHSMEGDAWgBSTUb2mpweQtatmY5bbxTHqUJfwrDAKBggqhkjOPQQDAgNHADBEAiBL5hGkVTDYq/MfGICTeJUveAAznZUSZuTObCPYFbE04gIgA50pAT434f/DkQYtzTR5cuEn5evJlmAr354Kepcg5VU=",
		"MIICGzCCAcKgAwIBAgISAcZvyHEGNKO8tB6ha1i4TmvgMAoGCCqGSM49BAMCMGYxFjAUBgNVBAoTDWJldHRlcnRscy5jb20xHTAbBgNVBAMMFGJldHRlcnRsc190cnVzdF9yb290MS0wKwYDVQQFEyQ3ZTUwODA0ZC03YTg0LTRiMmUtYTkyOC03MDI3MjFjYzU0MDMwHhcNMjExMDA3MTcyMjM0WhcNMjIxMDA5MTcyMjM0WjBTMRYwFAYDVQQKEw1iZXR0ZXJ0bHMuY29tMQowCAYDVQQDEwFDMS0wKwYDVQQFEyQwYzFmYmQ2ZC1kZmE5LTQwNGYtYjRhOC04ODE5YWIyMTU5MjUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATrW6quMNolQIK8yAdXDmGXCft5dyIRzQGh1GkrTmonOXCoSWZ4GFv3rbCrAUxHhn/OUGWZCvRk5xoM9Cct38oLo2MwYTAOBgNVHQ8BAf8EBAMCAgQwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUsy2Br+y80lU/r6t/HYdvD+OasvQwHwYDVR0jBBgwFoAUouQlKahgvsEET5hOwmth6sjoGmQwCgYIKoZIzj0EAwIDRwAwRAIgDSQ75/Ak83lhe636BxoL0PHvn6k54rjSlx+VrrqZ03wCIBNeVrWX2yBvgDHVwS9q+CNfckH+yhZjEOSBTW3k0XxJ",
		"MIICHTCCAcKgAwIBAgISAX9H6Ko+SZUQe21jNfmjU/KjMAoGCCqGSM49BAMCMGYxFjAUBgNVBAoTDWJldHRlcnRscy5jb20xHTAbBgNVBAMMFGJldHRlcnRsc190cnVzdF9yb290MS0wKwYDVQQFEyQ3ZTUwODA0ZC03YTg0LTRiMmUtYTkyOC03MDI3MjFjYzU0MDMwHhcNMjExMDA3MTcyMjM0WhcNMjIxMDA5MTcyMjM0WjBTMRYwFAYDVQQKEw1iZXR0ZXJ0bHMuY29tMQowCAYDVQQDEwFBMS0wKwYDVQQFEyRiOWM0ZTFhZS04MjUxLTQ4M2QtODljMy00NWE3NTI1MDc3OWQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARcgo+JL9vpLKh3gB9TnPb5GlnzRb5YHZ48NpN0oHcpXfu8J8WJqrSAPCqlVDEe15N03CqT91qutsjL2fypqBNUo2MwYTAOBgNVHQ8BAf8EBAMCAgQwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUk1G9pqcHkLWrZmOW28Ux6lCX8KwwHwYDVR0jBBgwFoAUouQlKahgvsEET5hOwmth6sjoGmQwCgYIKoZIzj0EAwIDSQAwRgIhALawfPf6YECsKcbKn46wFABqalUAF1TTQoYiuupSfs08AiEAuP9jGMiUzrMet9TJDpvS1daX8gfMGfQUrPM08SykJgY=",
	} {
		intermediates.AddCert(mustParse(cert))
	}
	
	chains, err := leafCert.Verify(x509.VerifyOptions{
		DNSName:                   "localhost",
		Intermediates:             intermediates,
		Roots:                     truststore,
		CurrentTime:               time.Unix(1633713295, 0),
		KeyUsages:                 []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
	})
	if err != nil {
		t.Error(err)
	} else {
		if len(chains) == 0 {
			t.Error("Unable to discover valid chain.")
		}
	}
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    FrozenDueToAgeNeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions