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
What version of Go are you using (
go version)?Does this issue reproduce with the latest release?
Yes
What operating system and processor architecture are you using (
go env)?go envOutputWhat did you do?
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
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.
Trust anchor is defined in section 6.1.1 as:
In fact it doesn't need to be a certificate at all.
Also in verification algorithm there is:
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