Repository: smallstep/crypto
Pull request #767 (2025-05-07 20:07:22)

Add new extension fields on certificate requests
### Description

This commit adds support for specifying on certificate requests the key usage, the extended key usage and the basic constraints.

Fixes: smallstep/cli#1415, smallstep/certificates#2260



Patch:
diff --git a/x509util/certificate_request.go b/x509util/certificate_request.go
index f0ecbf0..4af5ec7 100644
--- a/x509util/certificate_request.go
+++ b/x509util/certificate_request.go
@@ -8,8 +8,9 @@ import (
 	"crypto/x509/pkix"
 	"encoding/asn1"
 	"encoding/json"
+	"errors"
+	"fmt"
 
-	"github.com/pkg/errors"
 	"go.step.sm/crypto/internal/utils"
 	"golang.org/x/crypto/cryptobyte"
 	cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1"
@@ -53,6 +54,10 @@ type CertificateRequest struct {
 	URIs               MultiURL                 `json:"uris"`
 	SANs               []SubjectAlternativeName `json:"sans"`
 	Extensions         []Extension              `json:"extensions"`
+	KeyUsage           KeyUsage                 `json:"keyUsage"`
+	ExtKeyUsage        ExtKeyUsage              `json:"extKeyUsage"`
+	UnknownExtKeyUsage UnknownExtKeyUsage       `json:"unknownExtKeyUsage"`
+	BasicConstraints   *BasicConstraints        `json:"basicConstraints"`
 	SignatureAlgorithm SignatureAlgorithm       `json:"signatureAlgorithm"`
 	ChallengePassword  string                   `json:"-"`
 	PublicKey          interface{}              `json:"-"`
@@ -83,7 +88,7 @@ func NewCertificateRequest(signer crypto.Signer, opts ...Option) (*CertificateRe
 	// With templates
 	var cr CertificateRequest
 	if err := json.NewDecoder(o.CertBuffer).Decode(&cr); err != nil {
-		return nil, errors.Wrap(err, "error unmarshaling certificate")
+		return nil, fmt.Errorf("error unmarshaling certificate: %w", err)
 	}
 	cr.PublicKey = pub
 	cr.Signer = signer
@@ -100,6 +105,33 @@ func NewCertificateRequest(signer crypto.Signer, opts ...Option) (*CertificateRe
 		cr.Extensions = append([]Extension{ext}, cr.Extensions...)
 	}
 
+	// Add KeyUsage extension if necessary.
+	if cr.KeyUsage != 0 && !cr.hasExtension(oidExtensionKeyUsage) {
+		ext, err := cr.KeyUsage.Extension()
+		if err != nil {
+			return nil, err
+		}
+		cr.Extensions = append([]Extension{ext}, cr.Extensions...)
+	}
+
+	// Add ExtKeyUsage extension if necessary.
+	if len(cr.ExtKeyUsage) > 0 || len(cr.UnknownExtKeyUsage) > 0 {
+		ext, err := cr.ExtKeyUsage.Extension(cr.UnknownExtKeyUsage)
+		if err != nil {
+			return nil, err
+		}
+		cr.Extensions = append([]Extension{ext}, cr.Extensions...)
+	}
+
+	// Add BasicConstraints extension if necessary.
+	if cr.BasicConstraints != nil {
+		ext, err := cr.BasicConstraints.Extension()
+		if err != nil {
+			return nil, err
+		}
+		cr.Extensions = append([]Extension{ext}, cr.Extensions...)
+	}
+
 	return &cr, nil
 }
 
@@ -114,6 +146,12 @@ func NewCertificateRequest(signer crypto.Signer, opts ...Option) (*CertificateRe
 func NewCertificateRequestFromX509(cr *x509.CertificateRequest) *CertificateRequest {
 	// Set SubjectAltName extension as critical if Subject is empty.
 	fixSubjectAltName(cr)
+	// Extracts key usage, extended key usage, and basic constraints from the
+	// certificate extensions. For backward compatibility, this method does not
+	// return an error if an extension is improperly encoded or cannot be
+	// decoded. In such cases, the extension is simply ignored.
+	parsed, _ := parseCertificateRequestExtensions(cr.Extensions)
+
 	return &CertificateRequest{
 		Version:            cr.Version,
 		Subject:            newSubject(cr.Subject),
@@ -123,6 +161,10 @@ func NewCertificateRequestFromX509(cr *x509.CertificateRequest) *CertificateRequ
 		IPAddresses:        cr.IPAddresses,
 		URIs:               cr.URIs,
 		Extensions:         newExtensions(cr.Extensions),
+		KeyUsage:           parsed.KeyUsage,
+		ExtKeyUsage:        parsed.ExtKeyUsage,
+		UnknownExtKeyUsage: parsed.UnknownExtKeyUsage,
+		BasicConstraints:   parsed.BasicConstraints,
 		PublicKey:          cr.PublicKey,
 		PublicKeyAlgorithm: cr.PublicKeyAlgorithm,
 		Signature:          cr.Signature,
@@ -146,7 +188,7 @@ func (c *CertificateRequest) GetCertificateRequest() (*x509.CertificateRequest,
 		SignatureAlgorithm: x509.SignatureAlgorithm(c.SignatureAlgorithm),
 	}, c.Signer)
 	if err != nil {
-		return nil, errors.Wrap(err, "error creating certificate request")
+		return nil, fmt.Errorf("error creating certificate request: %w", err)
 	}
 
 	// If a challenge password is provided, encode and prepend it as a challenge
@@ -193,7 +235,7 @@ func (c *CertificateRequest) addChallengePassword(asn1Data []byte) ([]byte, erro
 
 	b, err := builder.Bytes()
 	if err != nil {
-		return nil, errors.Wrap(err, "error marshaling challenge password")
+		return nil, fmt.Errorf("error marshaling challenge password: %w", err)
 	}
 	challengePasswordAttr := asn1.RawValue{
 		FullBytes: b,
@@ -223,7 +265,7 @@ func (c *CertificateRequest) addChallengePassword(asn1Data []byte) ([]byte, erro
 	// Marshal tbsCertificateRequest
 	tbsCSRContents, err := asn1.Marshal(tbsCSR)
 	if err != nil {
-		return nil, errors.Wrap(err, "error creating certificate request")
+		return nil, fmt.Errorf("error creating certificate request: %w", err)
 	}
 	tbsCSR.Raw = tbsCSRContents
 
@@ -239,7 +281,7 @@ func (c *CertificateRequest) addChallengePassword(asn1Data []byte) ([]byte, erro
 		}
 	}
 	if !found {
-		return nil, errors.Errorf("error creating certificate request: unsupported signature algorithm %s", sigAlgoOID)
+		return nil, fmt.Errorf("error creating certificate request: unsupported signature algorithm %q", sigAlgoOID)
 	}
 
 	// Sign tbsCertificateRequest
@@ -253,7 +295,7 @@ func (c *CertificateRequest) addChallengePassword(asn1Data []byte) ([]byte, erro
 	var signature []byte
 	signature, err = c.Signer.Sign(rand.Reader, signed, hashFunc)
 	if err != nil {
-		return nil, errors.Wrap(err, "error creating certificate request")
+		return nil, fmt.Errorf("error creating certificate request: %w", err)
 	}
 
 	// Build new certificate request and marshal
@@ -266,7 +308,7 @@ func (c *CertificateRequest) addChallengePassword(asn1Data []byte) ([]byte, erro
 		},
 	})
 	if err != nil {
-		return nil, errors.Wrap(err, "error creating certificate request")
+		return nil, fmt.Errorf("error creating certificate request: %w", err)
 	}
 	return asn1Data, nil
 }
@@ -351,7 +393,7 @@ func CreateCertificateRequest(commonName string, sans []string, signer crypto.Si
 		URIs:           uris,
 	}, signer)
 	if err != nil {
-		return nil, errors.Wrap(err, "error creating certificate request")
+		return nil, fmt.Errorf("error creating certificate request: %w", err)
 	}
 	// This should not fail
 	return x509.ParseCertificateRequest(asn1Data)
@@ -368,3 +410,91 @@ func fixSubjectAltName(cr *x509.CertificateRequest) {
 		}
 	}
 }
+
+type certificateRequestParsedExtensions struct {
+	KeyUsage           KeyUsage
+	ExtKeyUsage        ExtKeyUsage
+	UnknownExtKeyUsage UnknownExtKeyUsage
+	BasicConstraints   *BasicConstraints
+}
+
+func parseCertificateRequestExtensions(exts []pkix.Extension) (cr certificateRequestParsedExtensions, errs error) {
+	var err error
+	for _, ext := range exts {
+		switch {
+		case ext.Id.Equal(oidExtensionKeyUsage):
+			if cr.KeyUsage, err = parseKeyUsageExtension(ext.Value); err != nil {
+				errs = errors.Join(errs, err)
+			}
+		case ext.Id.Equal(oidExtensionExtendedKeyUsage):
+			if cr.ExtKeyUsage, cr.UnknownExtKeyUsage, err = parseExtKeyUsageExtension(ext.Value); err != nil {
+				errs = errors.Join(errs, err)
+			}
+		case ext.Id.Equal(oidExtensionBasicConstraints):
+			if cr.BasicConstraints, err = parseBasicConstraintsExtension(ext.Value); err != nil {
+				errs = errors.Join(errs, err)
+			}
+		}
+	}
+
+	return
+}
+
+func parseKeyUsageExtension(der cryptobyte.String) (KeyUsage, error) {
+	var usageBits asn1.BitString
+	if !der.ReadASN1BitString(&usageBits) {
+		return 0, errors.New("invalid key usage")
+	}
+
+	var usage int
+	for i := 0; i < 9; i++ {
+		if usageBits.At(i) != 0 {
+			usage |= 1 << uint(i)
+		}
+	}
+
+	return KeyUsage(usage), nil
+}
+
+func parseExtKeyUsageExtension(der cryptobyte.String) (ExtKeyUsage, UnknownExtKeyUsage, error) {
+	var extKeyUsages ExtKeyUsage
+	var unknownUsages UnknownExtKeyUsage
+	if !der.ReadASN1(&der, cryptobyte_asn1.SEQUENCE) {
+		return nil, nil, errors.New("invalid extended key usages")
+	}
+	for !der.Empty() {
+		var eku asn1.ObjectIdentifier
+		if !der.ReadASN1ObjectIdentifier(&eku) {
+			return nil, nil, errors.New("invalid extended key usages")
+		}
+		if extKeyUsage, ok := extKeyUsageFromOID(eku); ok {
+			extKeyUsages = append(extKeyUsages, extKeyUsage)
+		} else {
+			unknownUsages = append(unknownUsages, eku)
+		}
+	}
+
+	return extKeyUsages, unknownUsages, nil
+}
+
+func parseBasicConstraintsExtension(der cryptobyte.String) (*BasicConstraints, error) {
+	var isCA bool
+	if !der.ReadASN1(&der, cryptobyte_asn1.SEQUENCE) {
+		return nil, errors.New("invalid basic constraints")
+	}
+	if der.PeekASN1Tag(cryptobyte_asn1.BOOLEAN) {
+		if !der.ReadASN1Boolean(&isCA) {
+			return nil, errors.New("invalid basic constraints")
+		}
+	}
+	maxPathLen := -1
+	if der.PeekASN1Tag(cryptobyte_asn1.INTEGER) {
+		if !der.ReadASN1Integer(&maxPathLen) {
+			return nil, errors.New("invalid basic constraints")
+		}
+	}
+
+	return &BasicConstraints{
+		IsCA: isCA, MaxPathLen: maxPathLen,
+	}, nil
+}
diff --git a/x509util/extensions.go b/x509util/extensions.go
index 4c92aa4..57b3780 100644
--- a/x509util/extensions.go
+++ b/x509util/extensions.go
@@ -23,6 +23,12 @@ func convertName(s string) string {
 	return strings.ReplaceAll(strings.ToLower(s), "_", "")
 }
 
+var (
+	oidExtensionKeyUsage         = []int{2, 5, 29, 15}
+	oidExtensionExtendedKeyUsage = []int{2, 5, 29, 37}
+	oidExtensionBasicConstraints = []int{2, 5, 29, 19}
+)
+
 // Names used for key usages.
 const (
 	KeyUsageDigitalSignature  = "digitalSignature"
@@ -54,6 +60,74 @@ const (
 	ExtKeyUsageMicrosoftKernelCodeSigning     = "microsoftKernelCodeSigning"
 )
 
+// RFC 5280, 4.2.1.12  Extended Key Usage
+//
+//	anyExtendedKeyUsage OBJECT IDENTIFIER ::= { id-ce-extKeyUsage 0 }
+//
+//	id-kp OBJECT IDENTIFIER ::= { id-pkix 3 }
+//
+//	id-kp-serverAuth             OBJECT IDENTIFIER ::= { id-kp 1 }
+//	id-kp-clientAuth             OBJECT IDENTIFIER ::= { id-kp 2 }
+//	id-kp-codeSigning            OBJECT IDENTIFIER ::= { id-kp 3 }
+//	id-kp-emailProtection        OBJECT IDENTIFIER ::= { id-kp 4 }
+//	id-kp-timeStamping           OBJECT IDENTIFIER ::= { id-kp 8 }
+//	id-kp-OCSPSigning            OBJECT IDENTIFIER ::= { id-kp 9 }
+var (
+	oidExtKeyUsageAny                            = asn1.ObjectIdentifier{2, 5, 29, 37, 0}
+	oidExtKeyUsageServerAuth                     = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 1}
+	oidExtKeyUsageClientAuth                     = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 2}
+	oidExtKeyUsageCodeSigning                    = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 3}
+	oidExtKeyUsageEmailProtection                = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 4}
+	oidExtKeyUsageIPSECEndSystem                 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 5}
+	oidExtKeyUsageIPSECTunnel                    = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 6}
+	oidExtKeyUsageIPSECUser                      = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 7}
+	oidExtKeyUsageTimeStamping                   = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 8}
+	oidExtKeyUsageOCSPSigning                    = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 9}
+	oidExtKeyUsageMicrosoftServerGatedCrypto     = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 10, 3, 3}
+	oidExtKeyUsageNetscapeServerGatedCrypto      = asn1.ObjectIdentifier{2, 16, 840, 1, 113730, 4, 1}
+	oidExtKeyUsageMicrosoftCommercialCodeSigning = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 2, 1, 22}
+	oidExtKeyUsageMicrosoftKernelCodeSigning     = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 61, 1, 1}
+)
+
+// extKeyUsageOIDs contains the mapping between an ExtKeyUsage and its OID.
+var extKeyUsageOIDs = []struct {
+	extKeyUsage x509.ExtKeyUsage
+	oid         asn1.ObjectIdentifier
+}{
+	{x509.ExtKeyUsageAny, oidExtKeyUsageAny},
+	{x509.ExtKeyUsageServerAuth, oidExtKeyUsageServerAuth},
+	{x509.ExtKeyUsageClientAuth, oidExtKeyUsageClientAuth},
+	{x509.ExtKeyUsageCodeSigning, oidExtKeyUsageCodeSigning},
+	{x509.ExtKeyUsageEmailProtection, oidExtKeyUsageEmailProtection},
+	{x509.ExtKeyUsageIPSECEndSystem, oidExtKeyUsageIPSECEndSystem},
+	{x509.ExtKeyUsageIPSECTunnel, oidExtKeyUsageIPSECTunnel},
+	{x509.ExtKeyUsageIPSECUser, oidExtKeyUsageIPSECUser},
+	{x509.ExtKeyUsageTimeStamping, oidExtKeyUsageTimeStamping},
+	{x509.ExtKeyUsageOCSPSigning, oidExtKeyUsageOCSPSigning},
+	{x509.ExtKeyUsageMicrosoftServerGatedCrypto, oidExtKeyUsageMicrosoftServerGatedCrypto},
+	{x509.ExtKeyUsageNetscapeServerGatedCrypto, oidExtKeyUsageNetscapeServerGatedCrypto},
+	{x509.ExtKeyUsageMicrosoftCommercialCodeSigning, oidExtKeyUsageMicrosoftCommercialCodeSigning},
+	{x509.ExtKeyUsageMicrosoftKernelCodeSigning, oidExtKeyUsageMicrosoftKernelCodeSigning},
+}
+
+func extKeyUsageFromOID(oid asn1.ObjectIdentifier) (eku x509.ExtKeyUsage, ok bool) {
+	for _, pair := range extKeyUsageOIDs {
+		if oid.Equal(pair.oid) {
+			return pair.extKeyUsage, true
+		}
+	}
+	return
+}
+
+func oidFromExtKeyUsage(eku x509.ExtKeyUsage) (oid asn1.ObjectIdentifier, ok bool) {
+	for _, pair := range extKeyUsageOIDs {
+		if eku == pair.extKeyUsage {
+			return pair.oid, true
+		}
+	}
+	return
+}
+
 // Names used and SubjectAlternativeNames types.
 const (
 	AutoType                = "auto"
@@ -201,13 +275,15 @@ func newExtensions(extensions []pkix.Extension) []Extension {
 	return ret
 }
 
-// Set adds the extension to the given X509 certificate.
+// Set adds a non empty extension to the given X509 certificate.
 func (e Extension) Set(c *x509.Certificate) {
-	c.ExtraExtensions = append(c.ExtraExtensions, pkix.Extension{
-		Id:       asn1.ObjectIdentifier(e.ID),
-		Critical: e.Critical,
-		Value:    e.Value,
-	})
+	if len(e.ID) > 0 {
+		c.ExtraExtensions = append(c.ExtraExtensions, pkix.Extension{
+			Id:       asn1.ObjectIdentifier(e.ID),
+			Critical: e.Critical,
+			Value:    e.Value,
+		})
+	}
 }
 
 // ObjectIdentifier represents a JSON strings that unmarshals into an ASN1
@@ -604,6 +680,38 @@ func (k KeyUsage) Set(c *x509.Certificate) {
 	c.KeyUsage = x509.KeyUsage(k)
 }
 
+// Extension marshals the key usage to an [Extension].  It will return an empty
+// extension if key usages is empty.
+func (k KeyUsage) Extension() (Extension, error) {
+	if k == 0 {
+		return Extension{}, nil
+	}
+
+	var b [2]byte
+	b[0] = reverseBitsInAByte(byte(k))
+	b[1] = reverseBitsInAByte(byte(k >> 8))
+
+	l := 1
+	if b[1] != 0 {
+		l = 2
+	}
+
+	bitString := b[:l]
+	value, err := asn1.Marshal(asn1.BitString{
+		Bytes:     bitString,
+		BitLength: asn1BitLength(bitString),
+	})
+	if err != nil {
+		return Extension{}, fmt.Errorf("error marshaling keyUsage extension to ASN1: %w", err)
+	}
+
+	return Extension{
+		ID:       oidExtensionKeyUsage,
+		Critical: true,
+		Value:    value,
+	}, nil
+}
+
 // UnmarshalJSON implements the json.Unmarshaler interface and coverts a string
 // or a list of strings into a key usage.
 func (k *KeyUsage) UnmarshalJSON(data []byte) error {
@@ -692,6 +800,36 @@ func (k ExtKeyUsage) Set(c *x509.Certificate) {
 	c.ExtKeyUsage = []x509.ExtKeyUsage(k)
 }
 
+// Extension marshals the extended key usages to an [Extension]. It will return
+// an empty extension if there are no extended key usages.
+func (k ExtKeyUsage) Extension(unknownUsages UnknownExtKeyUsage) (Extension, error) {
+	size := len(k) + len(unknownUsages)
+	if size == 0 {
+		return Extension{}, nil
+	}
+
+	oids := make([]asn1.ObjectIdentifier, size)
+	for i, u := range k {
+		if oid, ok := oidFromExtKeyUsage(u); ok {
+			oids[i] = oid
+		} else {
+			return Extension{}, errors.New("unknown extended key usage")
+		}
+	}
+
+	copy(oids[len(k):], unknownUsages)
+
+	value, err := asn1.Marshal(oids)
+	if err != nil {
+		return Extension{}, fmt.Errorf("error marshaling extKeyUsage extension to ASN1: %w", err)
+	}
+
+	return Extension{
+		ID:    oidExtensionExtendedKeyUsage,
+		Value: value,
+	}, nil
+}
+
 // UnmarshalJSON implements the json.Unmarshaler interface and coverts a string
 // or a list of strings into a list of extended key usages.
 func (k *ExtKeyUsage) UnmarshalJSON(data []byte) error {
@@ -929,8 +1067,8 @@ func (p PolicyIdentifiers) Set(c *x509.Certificate) {
 // self-issued intermediate CA certificates may follow in a valid certification
 // path. To do not impose a limit the MaxPathLen should be set to -1.
 type BasicConstraints struct {
-	IsCA       bool `json:"isCA"`
-	MaxPathLen int  `json:"maxPathLen"`
+	IsCA       bool `json:"isCA" asn1:"optional"`
+	MaxPathLen int  `json:"maxPathLen" asn1:"optional,default:-1"`
 }
 
 // Set sets the basic constraints to the given certificate.
@@ -955,6 +1093,25 @@ func (b BasicConstraints) Set(c *x509.Certificate) {
 	}
 }
 
+// Extension marshals the basic constraints to an [Extension].
+func (b BasicConstraints) Extension() (Extension, error) {
+	// When IsCA is false the MaxPathLen must be the default -1.
+	if !b.IsCA || b.MaxPathLen < 0 {
+		b.MaxPathLen = -1
+	}
+
+	value, err := asn1.Marshal(b)
+	if err != nil {
+		return Extension{}, fmt.Errorf("error marshaling basicConstraints extension to ASN1: %w", err)
+	}
+
+	return Extension{
+		ID:       oidExtensionBasicConstraints,
+		Critical: true,
+		Value:    value,
+	}, nil
+}
+
 // NameConstraints represents the X509 Name constraints extension and defines a
 // names space within which all subject names in subsequent certificates in a
 // certificate path must be located. The name constraints extension must be used
@@ -1286,3 +1443,30 @@ func forEachSAN(extension []byte, callback func(ext asn1.RawValue) error) error
 
 	return nil
 }
+
+func reverseBitsInAByte(in byte) byte {
+	b1 := in>>4 | in<<4
+	b2 := b1>>2&0x33 | b1<<2&0xcc
+	b3 := b2>>1&0x55 | b2<<1&0xaa
+	return b3
+}
+
+// asn1BitLength returns the bit-length of bitString by considering the
+// most-significant bit in a byte to be the "first" bit. This convention matches
+// ASN.1, but differs from almost everything else.
+func asn1BitLength(bitString []byte) int {
+	bitLen := len(bitString) * 8
+
+	for i := range bitString {
+		b := bitString[len(bitString)-i-1]
+
+		for bit := uint(0); bit < 8; bit++ {
+			if (b>>bit)&1 == 1 {
+				return bitLen
+			}
+			bitLen--
+		}
+	}
+
+	return 0
+}


Test patch:
diff --git a/x509util/certificate_request_test.go b/x509util/certificate_request_test.go
index 8712d90..2beb609 100644
--- a/x509util/certificate_request_test.go
+++ b/x509util/certificate_request_test.go
@@ -19,13 +19,13 @@ import (
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	"golang.org/x/crypto/cryptobyte"
+	cbasn1 "golang.org/x/crypto/cryptobyte/asn1"
 )
 
 func TestNewCertificateRequest(t *testing.T) {
 	_, signer, err := ed25519.GenerateKey(rand.Reader)
-	if err != nil {
-		t.Fatal(err)
-	}
+	require.NoError(t, err)
 
 	// ok extended sans
 	sans := []SubjectAlternativeName{
@@ -38,9 +38,7 @@ func TestNewCertificateRequest(t *testing.T) {
 	extendedSANs := CreateTemplateData("123456789", nil)
 	extendedSANs.SetSubjectAlternativeNames(sans...)
 	extendedSANsExtension, err := createSubjectAltNameExtension(nil, nil, nil, nil, sans, false)
-	if err != nil {
-		t.Fatal(err)
-	}
+	require.NoError(t, err)
 
 	// ok extended sans and extension
 	extendedSANsAndExtensionsTemplate := fmt.Sprintf(`{
@@ -62,9 +60,34 @@ func TestNewCertificateRequest(t *testing.T) {
 	permanentIdentifierTemplateExtension, err := createSubjectAltNameExtension(nil, nil, nil, nil, []SubjectAlternativeName{
 		{Type: PermanentIdentifierType, Value: "123456789"},
 	}, false)
-	if err != nil {
-		t.Fatal(err)
-	}
+	require.NoError(t, err)
+
+	// ok with key usage and basic constraints
+	caTemplate := `{ 
+		"subject": {{ toJson .Subject }},
+		"keyUsage": ["certSign", "crlSign"],
+		"basicConstraints": {
+			"isCA": true,
+			"maxPathLen": 0
+		}
+	}`
+
+	// ok with key usage and extended key usage
+	leafTemplate := `{ 
+		"subject": {{ toJson .Subject }},
+		"sans": {{ toJson .SANs }},
+		"keyUsage": ["digitalSignature"],
+		"extKeyUsage": ["serverAuth", "clientAuth"]
+	}`
+
+	caKeyUsageExtension, err := KeyUsage(x509.KeyUsageCertSign | x509.KeyUsageCRLSign).Extension()
+	require.NoError(t, err)
+	leafKeyUsageExtension, err := KeyUsage(x509.KeyUsageDigitalSignature).Extension()
+	require.NoError(t, err)
+	basicConstraintsExtension, err := BasicConstraints{IsCA: true, MaxPathLen: 0}.Extension()
+	require.NoError(t, err)
+	extKeyUsageExtension, err := ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}.Extension(nil)
+	require.NoError(t, err)
 
 	// fail extended sans
 	failExtendedSANs := CreateTemplateData("123456789", nil)
@@ -138,6 +161,40 @@ func TestNewCertificateRequest(t *testing.T) {
 			PublicKey:  signer.Public(),
 			Signer:     signer,
 		}, false},
+		{"ok with key usage and basic constraints", args{signer, []Option{
+			WithTemplate(caTemplate, extendedSANs),
+		}}, &CertificateRequest{
+			Subject:          Subject{CommonName: "123456789"},
+			KeyUsage:         KeyUsage(x509.KeyUsageCertSign | x509.KeyUsageCRLSign),
+			BasicConstraints: &BasicConstraints{IsCA: true, MaxPathLen: 0},
+			Extensions: []Extension{
+				basicConstraintsExtension,
+				caKeyUsageExtension,
+			},
+			PublicKey: signer.Public(),
+			Signer:    signer,
+		}, false},
+		{"ok with key usage and extended key usage", args{signer, []Option{
+			WithTemplate(leafTemplate, extendedSANs),
+		}}, &CertificateRequest{
+			Subject: Subject{CommonName: "123456789"},
+			SANs: []SubjectAlternativeName{
+				{Type: "dns", Value: "foo.com"},
+				{Type: "email", Value: "root@foo.com"},
+				{Type: "ip", Value: "3.14.15.92"},
+				{Type: "uri", Value: "mailto:root@foo.com"},
+				{Type: "permanentIdentifier", Value: "123456789"},
+			},
+			KeyUsage:    KeyUsage(x509.KeyUsageDigitalSignature),
+			ExtKeyUsage: ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
+			Extensions: []Extension{
+				extKeyUsageExtension,
+				leafKeyUsageExtension,
+				extendedSANsExtension,
+			},
+			PublicKey: signer.Public(),
+			Signer:    signer,
+		}, false},
 		{"fail apply", args{signer, []Option{WithTemplateFile("testdata/missing.tpl", NewTemplateData())}}, nil, true},
 		{"fail unmarshal", args{signer, []Option{WithTemplate("{badjson", NewTemplateData())}}, nil, true},
 		{"fail extended sans", args{signer, []Option{WithTemplate(DefaultCertificateRequestTemplate, failExtendedSANs)}}, nil, true},
@@ -157,6 +214,15 @@ func TestNewCertificateRequest(t *testing.T) {
 }
 
 func Test_newCertificateRequest(t *testing.T) {
+	ku, err := KeyUsage(x509.KeyUsageCertSign | x509.KeyUsageCRLSign).Extension()
+	require.NoError(t, err)
+	eku, err := ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageCodeSigning}.Extension(
+		UnknownExtKeyUsage{{1, 2, 4, 8}, {1, 3, 5, 9}},
+	)
+	require.NoError(t, err)
+	bc, err := BasicConstraints{IsCA: true, MaxPathLen: 0}.Extension()
+	require.NoError(t, err)
+
 	type args struct {
 		cr *x509.CertificateRequest
 	}
@@ -179,6 +245,54 @@ func Test_newCertificateRequest(t *testing.T) {
 			PublicKey:          []byte("publicKey"),
 			SignatureAlgorithm: SignatureAlgorithm(x509.UnknownSignatureAlgorithm),
 		}},
+		{"with known extensions", args{&x509.CertificateRequest{
+			Extensions: []pkix.Extension{
+				{Id: []int{1, 2, 3}, Critical: true, Value: []byte{3, 2, 1}},
+				{Id: asn1.ObjectIdentifier(ku.ID), Critical: ku.Critical, Value: ku.Value},
+				{Id: asn1.ObjectIdentifier(eku.ID), Critical: eku.Critical, Value: eku.Value},
+				{Id: asn1.ObjectIdentifier(bc.ID), Critical: bc.Critical, Value: bc.Value},
+			},
+			Subject:            pkix.Name{Province: []string{"CA"}, CommonName: "commonName"},
+			DNSNames:           []string{"foo"},
+			PublicKey:          []byte("publicKey"),
+			SignatureAlgorithm: x509.PureEd25519,
+		}}, &CertificateRequest{
+			Extensions: []Extension{
+				{ID: []int{1, 2, 3}, Critical: true, Value: []byte{3, 2, 1}},
+				ku, eku, bc,
+			},
+			Subject:            Subject{Province: []string{"CA"}, CommonName: "commonName"},
+			DNSNames:           []string{"foo"},
+			KeyUsage:           KeyUsage(x509.KeyUsageCertSign | x509.KeyUsageCRLSign),
+			ExtKeyUsage:        ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageCodeSigning},
+			UnknownExtKeyUsage: UnknownExtKeyUsage{{1, 2, 4, 8}, {1, 3, 5, 9}},
+			BasicConstraints:   &BasicConstraints{IsCA: true, MaxPathLen: 0},
+			PublicKey:          []byte("publicKey"),
+			SignatureAlgorithm: SignatureAlgorithm(x509.UnknownSignatureAlgorithm),
+		}},
+		{"with ignored errors", args{&x509.CertificateRequest{
+			Extensions: []pkix.Extension{
+				{Id: []int{1, 2, 3}, Critical: true, Value: []byte{3, 2, 1}},
+				{Id: asn1.ObjectIdentifier(ku.ID), Critical: ku.Critical, Value: []byte("garbage")},
+				{Id: asn1.ObjectIdentifier(eku.ID), Critical: eku.Critical, Value: []byte("garbage")},
+				{Id: asn1.ObjectIdentifier(bc.ID), Critical: bc.Critical, Value: []byte("garbage")},
+			},
+			Subject:            pkix.Name{Province: []string{"CA"}, CommonName: "commonName"},
+			DNSNames:           []string{"foo"},
+			PublicKey:          []byte("publicKey"),
+			SignatureAlgorithm: x509.PureEd25519,
+		}}, &CertificateRequest{
+			Extensions: []Extension{
+				{ID: []int{1, 2, 3}, Critical: true, Value: []byte{3, 2, 1}},
+				{ID: ku.ID, Critical: ku.Critical, Value: []byte("garbage")},
+				{ID: eku.ID, Critical: eku.Critical, Value: []byte("garbage")},
+				{ID: bc.ID, Critical: bc.Critical, Value: []byte("garbage")},
+			},
+			Subject:            Subject{Province: []string{"CA"}, CommonName: "commonName"},
+			DNSNames:           []string{"foo"},
+			PublicKey:          []byte("publicKey"),
+			SignatureAlgorithm: SignatureAlgorithm(x509.UnknownSignatureAlgorithm),
+		}},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -754,3 +868,212 @@ func TestCreateCertificateRequest(t *testing.T) {
 		})
 	}
 }
+
+func TestSignCertificateRequestTemplates(t *testing.T) {
+	iss, issPriv := createIssuerCertificate(t, "issuer")
+	pub, priv, err := ed25519.GenerateKey(rand.Reader)
+	require.NoError(t, err)
+
+	t.Run("sign ca certificate maxPathLen 1", func(t *testing.T) {
+		template := `{ 
+			"subject": {{ toJson .Subject }},
+			"keyUsage": ["certSign", "crlSign"],
+			"basicConstraints": {
+				"isCA": true,
+				"maxPathLen": 1
+			}
+		}`
+		csr, err := NewCertificateRequest(priv, WithTemplate(template, TemplateData{SubjectKey: Subject{
+			CommonName: "CA Intermediate MaxPathLen 1",
+		}}))
+		require.NoError(t, err)
+
+		crt, err := CreateCertificate(csr.GetCertificate().GetCertificate(), iss, pub, issPriv)
+		require.NoError(t, err)
+		assert.Equal(t, "CA Intermediate MaxPathLen 1", crt.Subject.CommonName)
+		assert.Equal(t, "issuer", crt.Issuer.CommonName)
+		assert.Equal(t, x509.KeyUsageCertSign|x509.KeyUsageCRLSign, crt.KeyUsage)
+		assert.True(t, crt.BasicConstraintsValid)
+		assert.True(t, crt.IsCA)
+		assert.False(t, false, crt.MaxPathLenZero)
+		assert.Equal(t, 1, crt.MaxPathLen)
+	})
+
+	t.Run("sign ca certificate maxPathLen 0", func(t *testing.T) {
+		template := `{ 
+			"subject": {{ toJson .Subject }},
+			"keyUsage": ["certSign", "crlSign"],
+			"basicConstraints": {
+				"isCA": true,
+				"maxPathLen": 0
+			}
+		}`
+		csr, err := NewCertificateRequest(priv, WithTemplate(template, TemplateData{SubjectKey: Subject{
+			CommonName: "CA Intermediate MaxPathLen 0",
+		}}))
+		require.NoError(t, err)
+
+		crt, err := CreateCertificate(csr.GetCertificate().GetCertificate(), iss, pub, issPriv)
+		require.NoError(t, err)
+		assert.Equal(t, "CA Intermediate MaxPathLen 0", crt.Subject.CommonName)
+		assert.Equal(t, "issuer", crt.Issuer.CommonName)
+		assert.Equal(t, x509.KeyUsageCertSign|x509.KeyUsageCRLSign, crt.KeyUsage)
+		assert.True(t, crt.BasicConstraintsValid)
+		assert.True(t, crt.IsCA)
+		assert.True(t, crt.MaxPathLenZero)
+		assert.Equal(t, 0, crt.MaxPathLen)
+	})
+
+	t.Run("sign leaf certificate", func(t *testing.T) {
+		template := `{ 
+			"subject": {{ toJson .Subject }},
+			"sans": {{ toJson .SANs }},
+			{{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }}
+			"keyUsage": ["keyEncipherment", "digitalSignature"],
+			{{- else }}
+			"keyUsage": ["digitalSignature"],
+			{{- end }}
+			"extKeyUsage": ["serverAuth", "clientAuth"]
+		}`
+
+		csr, err := NewCertificateRequest(priv, WithTemplate(template, CreateTemplateData("leaf", []string{"leaf.example.com"})))
+		require.NoError(t, err)
+
+		crt, err := CreateCertificate(csr.GetCertificate().GetCertificate(), iss, pub, issPriv)
+		require.NoError(t, err)
+		assert.Equal(t, "leaf", crt.Subject.CommonName)
+		assert.Equal(t, "issuer", crt.Issuer.CommonName)
+		assert.Equal(t, []string{"leaf.example.com"}, crt.DNSNames)
+		assert.Equal(t, x509.KeyUsageDigitalSignature, crt.KeyUsage)
+		assert.Equal(t, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, crt.ExtKeyUsage)
+	})
+
+}
+
+func Test_parseKeyUsageExtension(t *testing.T) {
+	mustValue := func(ku KeyUsage) cryptobyte.String {
+		ext, err := ku.Extension()
+		require.NoError(t, err)
+		return ext.Value
+	}
+
+	type args struct {
+		der cryptobyte.String
+	}
+	tests := []struct {
+		name      string
+		args      args
+		want      KeyUsage
+		assertion assert.ErrorAssertionFunc
+	}{
+		{"ok", args{mustValue(KeyUsage(x509.KeyUsageCertSign | x509.KeyUsageCRLSign))}, KeyUsage(x509.KeyUsageCertSign | x509.KeyUsageCRLSign), assert.NoError},
+		{"ok", args{mustValue(KeyUsage(x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature))}, KeyUsage(x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature), assert.NoError},
+		{"fail", args{cryptobyte.String("garbage")}, KeyUsage(0), assert.Error},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := parseKeyUsageExtension(tt.args.der)
+			tt.assertion(t, err)
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func Test_parseExtKeyUsageExtension(t *testing.T) {
+	mustValue := func(eku ExtKeyUsage, ueku UnknownExtKeyUsage) cryptobyte.String {
+		ext, err := eku.Extension(ueku)
+		require.NoError(t, err)
+		return ext.Value
+	}
+
+	b64OID, err := asn1Encode("oid:1.2.3.4")
+	require.NoError(t, err)
+
+	b64Int, err := asn1Encode("int:10")
+	require.NoError(t, err)
+
+	b64Seq, err := asn1Sequence(b64OID, b64Int)
+	require.NoError(t, err)
+
+	badParse, err := base64.StdEncoding.DecodeString(b64Seq)
+	require.NoError(t, err)
+
+	type args struct {
+		der cryptobyte.String
+	}
+	tests := []struct {
+		name      string
+		args      args
+		want      ExtKeyUsage
+		want1     UnknownExtKeyUsage
+		assertion assert.ErrorAssertionFunc
+	}{
+		{"fail parse", args{cryptobyte.String(badParse)}, nil, nil, assert.Error},
+		{"ok", args{mustValue(ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, nil)},
+			ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, nil, assert.NoError},
+		{"ok unhandled", args{mustValue(ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, UnknownExtKeyUsage{{1, 2, 3, 4}, {1, 4, 6, 8}})},
+			ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, UnknownExtKeyUsage{{1, 2, 3, 4}, {1, 4, 6, 8}}, assert.NoError},
+		{"ok unhandled only", args{mustValue(ExtKeyUsage{}, UnknownExtKeyUsage{{1, 2, 3, 4}, {1, 4, 6, 8}})},
+			nil, UnknownExtKeyUsage{{1, 2, 3, 4}, {1, 4, 6, 8}}, assert.NoError},
+		{"fail", args{cryptobyte.String("garbage")}, nil, nil, assert.Error},
+		{"fail parse", args{cryptobyte.String(badParse)}, nil, nil, assert.Error},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, got1, err := parseExtKeyUsageExtension(tt.args.der)
+			tt.assertion(t, err)
+			assert.Equal(t, tt.want, got)
+			assert.Equal(t, tt.want1, got1)
+		})
+	}
+}
+
+func Test_parseBasicConstraintsExtension(t *testing.T) {
+	mustValue := func(bc BasicConstraints) cryptobyte.String {
+		ext, err := bc.Extension()
+		require.NoError(t, err)
+		return ext.Value
+	}
+
+	var b1 cryptobyte.Builder
+	b1.AddASN1(cbasn1.SEQUENCE, func(child *cryptobyte.Builder) {
+		// Tag 1 boolean and garbage
+		child.AddBytes([]byte{1, 2, 3})
+	})
+	failParseBool, err := b1.Bytes()
+	require.NoError(t, err)
+
+	var b2 cryptobyte.Builder
+	b2.AddASN1(cbasn1.SEQUENCE, func(child *cryptobyte.Builder) {
+		child.AddASN1Boolean(true)
+		// Tag 2 integer and nothing
+		child.AddBytes([]byte{2})
+	})
+	failParseInt, err := b2.Bytes()
+	require.NoError(t, err)
+
+	type args struct {
+		der cryptobyte.String
+	}
+	tests := []struct {
+		name      string
+		args      args
+		want      *BasicConstraints
+		assertion assert.ErrorAssertionFunc
+	}{
+		{"ok 0", args{mustValue(BasicConstraints{IsCA: true, MaxPathLen: 0})}, &BasicConstraints{IsCA: true, MaxPathLen: 0}, assert.NoError},
+		{"ok 1", args{mustValue(BasicConstraints{IsCA: true, MaxPathLen: 1})}, &BasicConstraints{IsCA: true, MaxPathLen: 1}, assert.NoError},
+		{"ok -1", args{mustValue(BasicConstraints{IsCA: true, MaxPathLen: -1})}, &BasicConstraints{IsCA: true, MaxPathLen: -1}, assert.NoError},
+		{"ok no ca", args{mustValue(BasicConstraints{IsCA: false, MaxPathLen: 0})}, &BasicConstraints{IsCA: false, MaxPathLen: -1}, assert.NoError},
+		{"fail", args{cryptobyte.String("garbage")}, nil, assert.Error},
+		{"fail parse bool", args{failParseBool}, nil, assert.Error},
+		{"fail parse int", args{failParseInt}, nil, assert.Error},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := parseBasicConstraintsExtension(tt.args.der)
+			tt.assertion(t, err)
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
diff --git a/x509util/certificate_test.go b/x509util/certificate_test.go
index 4649292..b775394 100644
--- a/x509util/certificate_test.go
+++ b/x509util/certificate_test.go
@@ -96,8 +96,8 @@ func createIssuerCertificate(t *testing.T, commonName string) (*x509.Certificate
 		NotAfter:              now.Add(time.Hour),
 		KeyUsage:              x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
 		BasicConstraintsValid: true,
-		MaxPathLen:            0,
-		MaxPathLenZero:        true,
+		MaxPathLen:            -1,
+		MaxPathLenZero:        false,
 		Issuer:                pkix.Name{CommonName: "issuer"},
 		Subject:               pkix.Name{CommonName: "issuer"},
 		SerialNumber:          sn,
diff --git a/x509util/extensions_test.go b/x509util/extensions_test.go
index a28c9c6..49edd01 100644
--- a/x509util/extensions_test.go
+++ b/x509util/extensions_test.go
@@ -2,6 +2,8 @@ package x509util
 
 import (
 	"bytes"
+	"crypto/ed25519"
+	"crypto/rand"
 	"crypto/x509"
 	"crypto/x509/pkix"
 	"encoding/asn1"
@@ -1665,3 +1667,162 @@ func TestPolicyIdentifiers(t *testing.T) {
 		})
 	}
 }
+
+func TestKeyUsage_Extension(t *testing.T) {
+	pub, _, err := ed25519.GenerateKey(rand.Reader)
+	require.NoError(t, err)
+
+	iss, issPriv := createIssuerCertificate(t, "issuer")
+	mustExtension := func(ku x509.KeyUsage) Extension {
+		crt, err := CreateCertificate(&x509.Certificate{
+			Subject:   pkix.Name{CommonName: "leaf"},
+			PublicKey: pub,
+			KeyUsage:  ku,
+		}, iss, pub, issPriv)
+		require.NoError(t, err)
+
+		for _, ext := range crt.Extensions {
+			if ext.Id.Equal(oidExtensionKeyUsage) {
+				return newExtension(ext)
+			}
+		}
+		return Extension{}
+	}
+
+	all := x509.KeyUsageDigitalSignature |
+		x509.KeyUsageContentCommitment |
+		x509.KeyUsageKeyEncipherment |
+		x509.KeyUsageDataEncipherment |
+		x509.KeyUsageKeyAgreement |
+		x509.KeyUsageCertSign |
+		x509.KeyUsageCRLSign |
+		x509.KeyUsageEncipherOnly |
+		x509.KeyUsageDecipherOnly
+
+	tests := []struct {
+		name      string
+		k         KeyUsage
+		want      Extension
+		assertion assert.ErrorAssertionFunc
+	}{
+		{"ca", KeyUsage(x509.KeyUsageCertSign | x509.KeyUsageCRLSign), mustExtension(x509.KeyUsageCertSign | x509.KeyUsageCRLSign), assert.NoError},
+		{"leaf", KeyUsage(x509.KeyUsageDigitalSignature), mustExtension(x509.KeyUsageDigitalSignature), assert.NoError},
+		{"all", KeyUsage(all), mustExtension(all), assert.NoError},
+		{"empty", KeyUsage(0), Extension{}, assert.NoError},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := tt.k.Extension()
+			tt.assertion(t, err)
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func TestExtKeyUsage_Extension(t *testing.T) {
+	pub, _, err := ed25519.GenerateKey(rand.Reader)
+	require.NoError(t, err)
+
+	iss, issPriv := createIssuerCertificate(t, "issuer")
+	mustExtension := func(eku []x509.ExtKeyUsage, unhandledEKUs ...asn1.ObjectIdentifier) Extension {
+		crt, err := CreateCertificate(&x509.Certificate{
+			Subject:            pkix.Name{CommonName: "leaf"},
+			PublicKey:          pub,
+			KeyUsage:           x509.KeyUsageDigitalSignature,
+			ExtKeyUsage:        eku,
+			UnknownExtKeyUsage: unhandledEKUs,
+		}, iss, pub, issPriv)
+		require.NoError(t, err)
+
+		for _, ext := range crt.Extensions {
+			if ext.Id.Equal(oidExtensionExtendedKeyUsage) {
+				return newExtension(ext)
+			}
+		}
+		return Extension{}
+	}
+
+	type args struct {
+		unknownUsages UnknownExtKeyUsage
+	}
+	tests := []struct {
+		name      string
+		k         ExtKeyUsage
+		args      args
+		want      Extension
+		assertion assert.ErrorAssertionFunc
+	}{
+		{"ok", ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, args{nil}, mustExtension([]x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}), assert.NoError},
+		{"ok unhandled", ExtKeyUsage{x509.ExtKeyUsageClientAuth}, args{[]asn1.ObjectIdentifier{
+			{1, 2, 3, 4}, {1, 5, 6, 7},
+		}}, mustExtension([]x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, asn1.ObjectIdentifier{1, 2, 3, 4}, asn1.ObjectIdentifier{1, 5, 6, 7}), assert.NoError},
+		{"ok unhandled only", ExtKeyUsage{}, args{[]asn1.ObjectIdentifier{
+			{1, 2, 3, 4}, {1, 5, 6, 7},
+		}}, mustExtension([]x509.ExtKeyUsage{}, asn1.ObjectIdentifier{1, 2, 3, 4}, asn1.ObjectIdentifier{1, 5, 6, 7}), assert.NoError},
+		{"empty", ExtKeyUsage{}, args{UnknownExtKeyUsage{}}, Extension{}, assert.NoError},
+		{"empty nil", ExtKeyUsage{}, args{nil}, Extension{}, assert.NoError},
+		{"fail extKeyUsage", ExtKeyUsage{x509.ExtKeyUsage(100)}, args{nil}, Extension{}, assert.Error},
+		{"fail unhandled", ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, args{[]asn1.ObjectIdentifier{{1, 2, 3, 4}, {5, 6, 7, 8}}}, Extension{}, assert.Error},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := tt.k.Extension(tt.args.unknownUsages)
+			tt.assertion(t, err)
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func TestBasicConstraints_Extension(t *testing.T) {
+	pub, _, err := ed25519.GenerateKey(rand.Reader)
+	require.NoError(t, err)
+
+	iss, issPriv := createIssuerCertificate(t, "issuer")
+	mustExtension := func(isCA bool, maxPathLen int, maxPathLenZero bool) Extension {
+		crt, err := CreateCertificate(&x509.Certificate{
+			Subject:               pkix.Name{CommonName: "ca"},
+			PublicKey:             pub,
+			KeyUsage:              x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
+			IsCA:                  isCA,
+			MaxPathLen:            maxPathLen,
+			MaxPathLenZero:        maxPathLenZero,
+			BasicConstraintsValid: true,
+		}, iss, pub, issPriv)
+		require.NoError(t, err)
+
+		for _, ext := range crt.Extensions {
+			if ext.Id.Equal(oidExtensionBasicConstraints) {
+				return newExtension(ext)
+			}
+		}
+		return Extension{}
+	}
+
+	type fields struct {
+		IsCA       bool
+		MaxPathLen int
+	}
+	tests := []struct {
+		name      string
+		fields    fields
+		want      Extension
+		assertion assert.ErrorAssertionFunc
+	}{
+		{"ca 1", fields{true, 1}, mustExtension(true, 1, false), assert.NoError},
+		{"ca 0", fields{true, 0}, mustExtension(true, 0, true), assert.NoError},
+		{"ca -1", fields{true, -1}, mustExtension(true, -1, false), assert.NoError},
+		{"ca -2", fields{true, -2}, mustExtension(true, -1, false), assert.NoError},
+		{"no ca", fields{false, 0}, mustExtension(false, 0, false), assert.NoError},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			b := BasicConstraints{
+				IsCA:       tt.fields.IsCA,
+				MaxPathLen: tt.fields.MaxPathLen,
+			}
+			got, err := b.Extension()
+			tt.assertion(t, err)
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
diff --git a/x509util/templates_test.go b/x509util/templates_test.go
index a3d1653..0d6262c 100644
--- a/x509util/templates_test.go
+++ b/x509util/templates_test.go
@@ -2,8 +2,12 @@ package x509util
 
 import (
 	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/asn1"
 	"reflect"
 	"testing"
+
+	"github.com/stretchr/testify/require"
 )
 
 func TestTemplateError_Error(t *testing.T) {
@@ -349,11 +353,28 @@ func TestTemplateData_SetAuthorizationCertificateChain(t *testing.T) {
 }
 
 func TestTemplateData_SetCertificateRequest(t *testing.T) {
+	ku, err := KeyUsage(x509.KeyUsageDigitalSignature).Extension()
+	require.NoError(t, err)
+	eku, err := ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageCodeSigning}.Extension(nil)
+	require.NoError(t, err)
+
 	cr := &x509.CertificateRequest{
 		DNSNames: []string{"foo", "bar"},
+		Extensions: []pkix.Extension{{
+			Id:       asn1.ObjectIdentifier(eku.ID),
+			Critical: eku.Critical,
+			Value:    eku.Value,
+		}, {
+			Id:       asn1.ObjectIdentifier(ku.ID),
+			Critical: ku.Critical,
+			Value:    ku.Value,
+		}},
 	}
 	cr1 := &CertificateRequest{
-		DNSNames: []string{"foo", "bar"},
+		DNSNames:    []string{"foo", "bar"},
+		KeyUsage:    KeyUsage(x509.KeyUsageDigitalSignature),
+		ExtKeyUsage: ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageCodeSigning},
+		Extensions:  []Extension{eku, ku},
 	}
 	cr2 := &CertificateRequest{
 		EmailAddresses: []string{"foo@bar.com"},