Skip to content

crypto/x509: verify checks root certificate #28971

Description

@vit3k

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    NeedsInvestigationSomeone 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