Skip to content

crypto/x509: CreateCertificate creates invalid serial number field #33310

Description

@azsn

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

go1.12.7 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
GOARCH="amd64"
GOBIN=""
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPROXY=""
GORACE=""
GOROOT="/usr/local/Cellar/go/1.12.7/libexec"
GOTMPDIR=""
GOTOOLDIR="/usr/local/Cellar/go/1.12.7/libexec/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"

What did you do?

I created an x509 certificate with x509.CreateCertificate from crypto/x509 with the following code (modified from Martian's mitm NewAuthority function):

package main

import (
	"bytes"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha1"
	"crypto/x509"
	"crypto/x509/pkix"
	"fmt"
	"log"
	"math/big"
	"time"
)

var MaxSerialNumber = big.NewInt(0).SetBytes(bytes.Repeat([]byte{255}, 20))

func main() {
	name := "Test name"
	organization := "Test Org"
	validity := time.Duration(24 * time.Hour)

	priv, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		log.Fatal(err)
	}
	pub := priv.Public()

	// Subject Key Identifier support for end entity certificate.
	// https://www.ietf.org/rfc/rfc3280.txt (section 4.2.1.2)
	pkixpub, err := x509.MarshalPKIXPublicKey(pub)
	if err != nil {
		log.Fatal(err)
	}
	h := sha1.New()
	h.Write(pkixpub)
	keyID := h.Sum(nil)

	serial := MaxSerialNumber

	tmpl := &x509.Certificate{
		SerialNumber: serial,
		Subject: pkix.Name{
			CommonName:   name,
			Organization: []string{organization},
		},
		SubjectKeyId:          keyID,
		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		BasicConstraintsValid: true,
		NotBefore:             time.Now().Add(-validity),
		NotAfter:              time.Now().Add(validity),
		DNSNames:              []string{name},
		IsCA:                  true,
	}

	fmt.Println(MaxSerialNumber)
	fmt.Println(serial)

	raw, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, pub, priv)
	fmt.Println(err, len(raw), raw)
}

(Unfortunately, this won't run in play.golang.org; it errors with process took too long.)

What did you expect to see?

A certificate with 20 bytes of serial number (which is the maximum allowed) where all 20 bytes are 255.

What did you see instead?

A certificate with 21 bytes of serial number, specifically, a 0 byte followed by 20 255 bytes. The field length correctly indicates 21 bytes, but some software will refuse to read a certificate with that many bytes.

The leading 0 byte is added whenever the big-end byte is >= 128. This can be seen by changing serial := MaxSerialNumber to serial := big.NewInt(127) and then serial := big.NewInt(128). The 127 serial number will have a field length of 1 byte, followed by the single byte 127. The 128 serial number will have a field length of 2 bytes, followed by the the two bytes 0 128.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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