Skip to content

Commit 130ef9b

Browse files
authored
Merge pull request #767 from smallstep/mariano/csr-templates
Add new extension fields on certificate requests
2 parents a4855a3 + 07b1e2a commit 130ef9b

File tree

6 files changed

+848
-29
lines changed

6 files changed

+848
-29
lines changed

x509util/certificate_request.go

Lines changed: 139 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ import (
88
"crypto/x509/pkix"
99
"encoding/asn1"
1010
"encoding/json"
11+
"errors"
12+
"fmt"
1113

12-
"github.com/pkg/errors"
1314
"go.step.sm/crypto/internal/utils"
1415
"golang.org/x/crypto/cryptobyte"
1516
cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1"
@@ -53,6 +54,10 @@ type CertificateRequest struct {
5354
URIs MultiURL `json:"uris"`
5455
SANs []SubjectAlternativeName `json:"sans"`
5556
Extensions []Extension `json:"extensions"`
57+
KeyUsage KeyUsage `json:"keyUsage"`
58+
ExtKeyUsage ExtKeyUsage `json:"extKeyUsage"`
59+
UnknownExtKeyUsage UnknownExtKeyUsage `json:"unknownExtKeyUsage"`
60+
BasicConstraints *BasicConstraints `json:"basicConstraints"`
5661
SignatureAlgorithm SignatureAlgorithm `json:"signatureAlgorithm"`
5762
ChallengePassword string `json:"-"`
5863
PublicKey interface{} `json:"-"`
@@ -83,7 +88,7 @@ func NewCertificateRequest(signer crypto.Signer, opts ...Option) (*CertificateRe
8388
// With templates
8489
var cr CertificateRequest
8590
if err := json.NewDecoder(o.CertBuffer).Decode(&cr); err != nil {
86-
return nil, errors.Wrap(err, "error unmarshaling certificate")
91+
return nil, fmt.Errorf("error unmarshaling certificate: %w", err)
8792
}
8893
cr.PublicKey = pub
8994
cr.Signer = signer
@@ -100,6 +105,33 @@ func NewCertificateRequest(signer crypto.Signer, opts ...Option) (*CertificateRe
100105
cr.Extensions = append([]Extension{ext}, cr.Extensions...)
101106
}
102107

108+
// Add KeyUsage extension if necessary.
109+
if cr.KeyUsage != 0 && !cr.hasExtension(oidExtensionKeyUsage) {
110+
ext, err := cr.KeyUsage.Extension()
111+
if err != nil {
112+
return nil, err
113+
}
114+
cr.Extensions = append([]Extension{ext}, cr.Extensions...)
115+
}
116+
117+
// Add ExtKeyUsage extension if necessary.
118+
if len(cr.ExtKeyUsage) > 0 || len(cr.UnknownExtKeyUsage) > 0 {
119+
ext, err := cr.ExtKeyUsage.Extension(cr.UnknownExtKeyUsage)
120+
if err != nil {
121+
return nil, err
122+
}
123+
cr.Extensions = append([]Extension{ext}, cr.Extensions...)
124+
}
125+
126+
// Add BasicConstraints extension if necessary.
127+
if cr.BasicConstraints != nil {
128+
ext, err := cr.BasicConstraints.Extension()
129+
if err != nil {
130+
return nil, err
131+
}
132+
cr.Extensions = append([]Extension{ext}, cr.Extensions...)
133+
}
134+
103135
return &cr, nil
104136
}
105137

@@ -114,6 +146,12 @@ func NewCertificateRequest(signer crypto.Signer, opts ...Option) (*CertificateRe
114146
func NewCertificateRequestFromX509(cr *x509.CertificateRequest) *CertificateRequest {
115147
// Set SubjectAltName extension as critical if Subject is empty.
116148
fixSubjectAltName(cr)
149+
// Extracts key usage, extended key usage, and basic constraints from the
150+
// certificate extensions. For backward compatibility, this method does not
151+
// return an error if an extension is improperly encoded or cannot be
152+
// decoded. In such cases, the extension is simply ignored.
153+
parsed, _ := parseCertificateRequestExtensions(cr.Extensions)
154+
117155
return &CertificateRequest{
118156
Version: cr.Version,
119157
Subject: newSubject(cr.Subject),
@@ -123,6 +161,10 @@ func NewCertificateRequestFromX509(cr *x509.CertificateRequest) *CertificateRequ
123161
IPAddresses: cr.IPAddresses,
124162
URIs: cr.URIs,
125163
Extensions: newExtensions(cr.Extensions),
164+
KeyUsage: parsed.KeyUsage,
165+
ExtKeyUsage: parsed.ExtKeyUsage,
166+
UnknownExtKeyUsage: parsed.UnknownExtKeyUsage,
167+
BasicConstraints: parsed.BasicConstraints,
126168
PublicKey: cr.PublicKey,
127169
PublicKeyAlgorithm: cr.PublicKeyAlgorithm,
128170
Signature: cr.Signature,
@@ -146,7 +188,7 @@ func (c *CertificateRequest) GetCertificateRequest() (*x509.CertificateRequest,
146188
SignatureAlgorithm: x509.SignatureAlgorithm(c.SignatureAlgorithm),
147189
}, c.Signer)
148190
if err != nil {
149-
return nil, errors.Wrap(err, "error creating certificate request")
191+
return nil, fmt.Errorf("error creating certificate request: %w", err)
150192
}
151193

152194
// 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
193235

194236
b, err := builder.Bytes()
195237
if err != nil {
196-
return nil, errors.Wrap(err, "error marshaling challenge password")
238+
return nil, fmt.Errorf("error marshaling challenge password: %w", err)
197239
}
198240
challengePasswordAttr := asn1.RawValue{
199241
FullBytes: b,
@@ -223,7 +265,7 @@ func (c *CertificateRequest) addChallengePassword(asn1Data []byte) ([]byte, erro
223265
// Marshal tbsCertificateRequest
224266
tbsCSRContents, err := asn1.Marshal(tbsCSR)
225267
if err != nil {
226-
return nil, errors.Wrap(err, "error creating certificate request")
268+
return nil, fmt.Errorf("error creating certificate request: %w", err)
227269
}
228270
tbsCSR.Raw = tbsCSRContents
229271

@@ -239,7 +281,7 @@ func (c *CertificateRequest) addChallengePassword(asn1Data []byte) ([]byte, erro
239281
}
240282
}
241283
if !found {
242-
return nil, errors.Errorf("error creating certificate request: unsupported signature algorithm %s", sigAlgoOID)
284+
return nil, fmt.Errorf("error creating certificate request: unsupported signature algorithm %q", sigAlgoOID)
243285
}
244286

245287
// Sign tbsCertificateRequest
@@ -253,7 +295,7 @@ func (c *CertificateRequest) addChallengePassword(asn1Data []byte) ([]byte, erro
253295
var signature []byte
254296
signature, err = c.Signer.Sign(rand.Reader, signed, hashFunc)
255297
if err != nil {
256-
return nil, errors.Wrap(err, "error creating certificate request")
298+
return nil, fmt.Errorf("error creating certificate request: %w", err)
257299
}
258300

259301
// Build new certificate request and marshal
@@ -266,7 +308,7 @@ func (c *CertificateRequest) addChallengePassword(asn1Data []byte) ([]byte, erro
266308
},
267309
})
268310
if err != nil {
269-
return nil, errors.Wrap(err, "error creating certificate request")
311+
return nil, fmt.Errorf("error creating certificate request: %w", err)
270312
}
271313
return asn1Data, nil
272314
}
@@ -351,7 +393,7 @@ func CreateCertificateRequest(commonName string, sans []string, signer crypto.Si
351393
URIs: uris,
352394
}, signer)
353395
if err != nil {
354-
return nil, errors.Wrap(err, "error creating certificate request")
396+
return nil, fmt.Errorf("error creating certificate request: %w", err)
355397
}
356398
// This should not fail
357399
return x509.ParseCertificateRequest(asn1Data)
@@ -368,3 +410,91 @@ func fixSubjectAltName(cr *x509.CertificateRequest) {
368410
}
369411
}
370412
}
413+
414+
type certificateRequestParsedExtensions struct {
415+
KeyUsage KeyUsage
416+
ExtKeyUsage ExtKeyUsage
417+
UnknownExtKeyUsage UnknownExtKeyUsage
418+
BasicConstraints *BasicConstraints
419+
}
420+
421+
func parseCertificateRequestExtensions(exts []pkix.Extension) (cr certificateRequestParsedExtensions, errs error) {
422+
var err error
423+
for _, ext := range exts {
424+
switch {
425+
case ext.Id.Equal(oidExtensionKeyUsage):
426+
if cr.KeyUsage, err = parseKeyUsageExtension(ext.Value); err != nil {
427+
errs = errors.Join(errs, err)
428+
}
429+
case ext.Id.Equal(oidExtensionExtendedKeyUsage):
430+
if cr.ExtKeyUsage, cr.UnknownExtKeyUsage, err = parseExtKeyUsageExtension(ext.Value); err != nil {
431+
errs = errors.Join(errs, err)
432+
}
433+
case ext.Id.Equal(oidExtensionBasicConstraints):
434+
if cr.BasicConstraints, err = parseBasicConstraintsExtension(ext.Value); err != nil {
435+
errs = errors.Join(errs, err)
436+
}
437+
}
438+
}
439+
440+
return
441+
}
442+
443+
func parseKeyUsageExtension(der cryptobyte.String) (KeyUsage, error) {
444+
var usageBits asn1.BitString
445+
if !der.ReadASN1BitString(&usageBits) {
446+
return 0, errors.New("invalid key usage")
447+
}
448+
449+
var usage int
450+
for i := 0; i < 9; i++ {
451+
if usageBits.At(i) != 0 {
452+
usage |= 1 << uint(i)
453+
}
454+
}
455+
456+
return KeyUsage(usage), nil
457+
}
458+
459+
func parseExtKeyUsageExtension(der cryptobyte.String) (ExtKeyUsage, UnknownExtKeyUsage, error) {
460+
var extKeyUsages ExtKeyUsage
461+
var unknownUsages UnknownExtKeyUsage
462+
if !der.ReadASN1(&der, cryptobyte_asn1.SEQUENCE) {
463+
return nil, nil, errors.New("invalid extended key usages")
464+
}
465+
for !der.Empty() {
466+
var eku asn1.ObjectIdentifier
467+
if !der.ReadASN1ObjectIdentifier(&eku) {
468+
return nil, nil, errors.New("invalid extended key usages")
469+
}
470+
if extKeyUsage, ok := extKeyUsageFromOID(eku); ok {
471+
extKeyUsages = append(extKeyUsages, extKeyUsage)
472+
} else {
473+
unknownUsages = append(unknownUsages, eku)
474+
}
475+
}
476+
477+
return extKeyUsages, unknownUsages, nil
478+
}
479+
480+
func parseBasicConstraintsExtension(der cryptobyte.String) (*BasicConstraints, error) {
481+
var isCA bool
482+
if !der.ReadASN1(&der, cryptobyte_asn1.SEQUENCE) {
483+
return nil, errors.New("invalid basic constraints")
484+
}
485+
if der.PeekASN1Tag(cryptobyte_asn1.BOOLEAN) {
486+
if !der.ReadASN1Boolean(&isCA) {
487+
return nil, errors.New("invalid basic constraints")
488+
}
489+
}
490+
maxPathLen := -1
491+
if der.PeekASN1Tag(cryptobyte_asn1.INTEGER) {
492+
if !der.ReadASN1Integer(&maxPathLen) {
493+
return nil, errors.New("invalid basic constraints")
494+
}
495+
}
496+
497+
return &BasicConstraints{
498+
IsCA: isCA, MaxPathLen: maxPathLen,
499+
}, nil
500+
}

0 commit comments

Comments
 (0)