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

crypto/x509: verify checks root certificate #28971

Open
vit3k opened this issue Nov 27, 2018 · 3 comments

Comments

@vit3k
Copy link

commented Nov 27, 2018

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

$ go version
go version go1.11.2 darwin/amd64

Does this issue reproduce with the latest release?

Yes

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

go env Output
$ go env
GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/witek/Library/Caches/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/witek/go"
GOPROXY=""
GORACE=""
GOROOT="/usr/local/Cellar/go/1.11.2/libexec"
GOTMPDIR=""
GOTOOLDIR="/usr/local/Cellar/go/1.11.2/libexec/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD=""
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 -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/k6/n2t1dlcx5b3_h_2hgys27n940000gn/T/go-build556083463=/tmp/go-build -gno-record-gcc-switches -fno-common"

What did you do?

package main

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"fmt"
	"log"
	"math/big"
	"os"
	"time"
)

func saveCertificate(cert *x509.Certificate, path string) {
	certOut, _ := os.Create(path)
	pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
	certOut.Close()
}

func savePrivateKey(key *rsa.PrivateKey, path string) {
	keyOut, _ := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
	pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
	keyOut.Close()
}
func main() {
	rootCertTemplate := &x509.Certificate{
		SerialNumber: big.NewInt(1653),
		Subject: pkix.Name{
			CommonName: "Root",
		},
		NotBefore:             time.Now(),
		NotAfter:              time.Now().AddDate(10, 0, 0),
		IsCA:                  true,
		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
		BasicConstraintsValid: true,
		MaxPathLen:            0,
		MaxPathLenZero:        true,
	}

	rootPriv, _ := rsa.GenerateKey(rand.Reader, 2048)
	rootPub := &rootPriv.PublicKey
	rootCertBytes, err := x509.CreateCertificate(rand.Reader, rootCertTemplate, rootCertTemplate, rootPub, rootPriv)
	if err != nil {
		log.Println("create ca failed", err)
		return
	}
	rootCert, _ := x509.ParseCertificate(rootCertBytes)

	saveCertificate(rootCert, "root.crt")
	savePrivateKey(rootPriv, "root.key")

	intermediateCertTemplate := &x509.Certificate{
		SerialNumber: big.NewInt(1658),
		Subject: pkix.Name{
			CommonName: "Intermediate",
		},
		NotBefore:             time.Now(),
		NotAfter:              time.Now().AddDate(10, 0, 0),
		SubjectKeyId:          []byte{1, 2, 3, 4, 6},
		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
		IsCA:                  true,
		BasicConstraintsValid: true,
		MaxPathLen:            0,
		MaxPathLenZero:        true,
	}
	intermediatePriv, _ := rsa.GenerateKey(rand.Reader, 2048)
	intermediatePub := &intermediatePriv.PublicKey

	intermediateCertBytes, err := x509.CreateCertificate(rand.Reader, intermediateCertTemplate, rootCert, intermediatePub, rootPriv)
	intermediateCert, _ := x509.ParseCertificate(intermediateCertBytes)

	saveCertificate(intermediateCert, "intermediate.crt")
	savePrivateKey(intermediatePriv, "intermediate.key")

	leafCertTemplate := &x509.Certificate{
		SerialNumber: big.NewInt(1680),
		Subject: pkix.Name{
			CommonName: "Leaf",
		},
		NotBefore:    time.Now(),
		NotAfter:     time.Now().AddDate(10, 0, 0),
		SubjectKeyId: []byte{1, 2, 3, 4, 7},
		KeyUsage:     x509.KeyUsageDigitalSignature,
	}
	leafPriv, _ := rsa.GenerateKey(rand.Reader, 2048)
	leafPub := &leafPriv.PublicKey

	leafCertBytes, err := x509.CreateCertificate(rand.Reader, leafCertTemplate, intermediateCert, leafPub, intermediatePriv)
	leafCert, _ := x509.ParseCertificate(leafCertBytes)

	saveCertificate(leafCert, "leaf.crt")
	savePrivateKey(leafPriv, "leaf.key")

	roots := x509.NewCertPool()
	roots.AddCert(rootCert)

	intermediates := x509.NewCertPool()
	intermediates.AddCert(intermediateCert)

	opts := x509.VerifyOptions{
		Roots:         roots,
		Intermediates: intermediates,
	}
	_, err = leafCert.Verify(opts)
	if err != nil {
		fmt.Println("Verification failed: ", err.Error())
		return
	}
	fmt.Printf("Success!\n")
}

Example saves root, intermediate and leaf certificate to files so it can be verified with openssl.

What did you expect to see?

Verification should be successful.

What did you see instead?

Verification fails because MaxPathLen is set to 0 on root certificate. According to x509 specification root certs should not be part of chain and should not be validated at all.

RFC5280 6.1

The primary goal of path validation is to verify the binding between a subject distinguished name or a subject alternative name and subject public key, as represented in the target certificate, based on the public key of the trust anchor.
...
To meet this goal, the path validation process verifies, among other things, that a prospective certification path (a sequence of n certificates) satisfies the following conditions:

(a) for all x in {1, ..., n-1}, the subject of certificate x is the issuer of certificate x+1;
(b) certificate 1 is issued by the trust anchor;
(c) certificate n is the certificate to be validated (i.e., the target certificate); and
(d) for all x in {1, ..., n}, the certificate was valid at the time in question.

As far as I understand it means that trust anchor (which in example is a root CA self-signed certificate) is not in this list of certificates to be validated because the first one should be the one that trust anchor issued.

When the trust anchor is provided in the form of a self-signed certificate, this self-signed certificate is not included as part of the prospective certification path. Information about trust anchors is provided as inputs to the certification path validation algorithm (Section 6.1.1).

Trust anchor is defined in section 6.1.1 as:

(d) trust anchor information, describing a CA that serves as a trust anchor for the certification path. The trust anchor information includes:

(1) the trusted issuer name,
(2) the trusted public key algorithm,
(3) the trusted public key, and
(4) optionally, the trusted public key parameters associated with the public key.

The trust anchor information may be provided to the path processing procedure in the form of a self-signed certificate.

In fact it doesn't need to be a certificate at all.

Also in verification algorithm there is:

(l) If the certificate was not self-issued, verify that max_path_length is greater than zero and decrement max_path_length by 1.

The root in example is self signed but anyway - in my opinion it should not be even a part of verification chain. It should be only a trust anchor.

Openssl verification is successful on such chain

openssl verify -trusted root.crt -untrusted intermediate.crt leaf.crt
leaf.crt: OK
@agnivade

This comment has been minimized.

Copy link
Member

commented Nov 28, 2018

@imakewebthings

This comment has been minimized.

Copy link

commented Jan 3, 2019

Openssl verification is successful on such chain

@vit3k I'm curious what version of openssl you're using that the chain you described succeeds. I'm dealing with similar issues with a similar chain where openssl 1.1.0f verified as ok, but 1.1.0j fails the path length constraint. I suspect openssl/openssl#7353 is the cause there. It's unclear to me from the spec what the correct behavior even is.

@blaufish

This comment has been minimized.

Copy link

commented Mar 19, 2019

  1. openssl/openssl#7353 will cause changes for any test-case that relies on self-issued certificates (including self-signed trust anchors). This is because it fixed a bug for which any self-issued certificate would be ignored in openssl's equivalent to 6.1.4. Preparation for Certificate i+1 section (m)
(m)  If pathLenConstraint is present in the certificate and is
           less than max_path_length, set max_path_length to the value
           of pathLenConstraint.
  1. Regarding Trust Anchor, there was no rule in openssl prior that treated Trust Anchor any differently than the other parts of the certificate chain. This experienced difference is likely due to self-signed trust anchors being one instance of self-issued certificates (see bug description above)

  2. Regarding Trust Anchor, rfc5280 actually does suggests that processing trust anchor certificate is implementation defined ("implementations ... are free to process or ignore such information") but recommended. Also, this aligns with other SSL stacks that do process the trust anchor certificate, so any certificate chain intentionally breaking the trust anchors path length constraint should not be expected to process OK.

Where a CA distributes self-signed certificates to specify trust
   anchor information, certificate extensions can be used to specify
   recommended inputs to path validation.  For example, a policy
   constraints extension could be included in the self-signed
   certificate to indicate that paths beginning with this trust anchor
   should be trusted only for the specified policies.  Similarly, a name
   constraints extension could be included to indicate that paths
   beginning with this trust anchor should be trusted only for the
   specified name spaces.  The path validation algorithm presented in
   Section 6.1 does not assume that trust anchor information is provided
   in self-signed certificates and does not specify processing rules for
   additional information included in such certificates.
   Implementations that use self-signed certificates to specify trust
   anchor information are free to process or ignore such information.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants
You can’t perform that action at this time.