Skip to content

Commit bca16d9

Browse files
authored
Merge pull request #57 from lealem47/F7859
F7859: wolfx509: honor BasicConstraintsValid and mark it critical
2 parents fcfebe3 + b98ad8f commit bca16d9

3 files changed

Lines changed: 93 additions & 2 deletions

File tree

wolfx509/certgen.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ func CreateCertificate(template, parent *Certificate, pubKey, signer KeyHandle)
8383
DNSNames: template.DNSNames,
8484
IPAddresses: template.IPAddresses,
8585
AcmeKeyAuth: template.AcmeKeyAuth,
86+
BasicConstraintsValid: template.BasicConstraintsValid,
8687
}
8788

8889
if template == parent && pubKey == signer {

wolfx509/certgen_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ package wolfx509
2222

2323
import (
2424
"bytes"
25+
"encoding/asn1"
26+
"encoding/hex"
2527
"math/big"
2628
"net"
2729
"testing"
@@ -513,3 +515,74 @@ func TestCreateCertificateValidDays(t *testing.T) {
513515
t.Errorf("NotAfter = %s, want within [%s, %s]", parsed.NotAfter, naLo, naHi)
514516
}
515517
}
518+
519+
// oidBasicConstraints is id-ce-basicConstraints (RFC 5280 4.2.1.9).
520+
var oidBasicConstraints = asn1.ObjectIdentifier{2, 5, 29, 19}
521+
522+
// TestBasicConstraintsMatrix pins the BasicConstraints extension that is
523+
// emitted for a given template:
524+
// - emitted only when BasicConstraintsValid is set,
525+
// - always critical,
526+
// - present but with the cA boolean omitted (an empty SEQUENCE) when IsCA
527+
// is false.
528+
func TestBasicConstraintsMatrix(t *testing.T) {
529+
for _, tc := range []struct {
530+
name string
531+
bcValid bool
532+
isCA bool
533+
wantPresent bool
534+
wantValue string // hex of the extension value
535+
}{
536+
{"valid+CA", true, true, true, "30030101ff"},
537+
{"valid+notCA", true, false, true, "3000"},
538+
{"notValid+CA", false, true, false, ""},
539+
{"notValid+notCA", false, false, false, ""},
540+
} {
541+
t.Run(tc.name, func(t *testing.T) {
542+
k, err := GenerateP256Key()
543+
if err != nil {
544+
t.Fatalf("GenerateP256Key: %v", err)
545+
}
546+
defer k.Free()
547+
548+
tmpl := &Certificate{
549+
SerialNumber: big.NewInt(1),
550+
Subject: Name{CommonName: "bc test"},
551+
NotBefore: time.Now().Add(-time.Hour),
552+
NotAfter: time.Now().Add(24 * time.Hour),
553+
BasicConstraintsValid: tc.bcValid,
554+
IsCA: tc.isCA,
555+
}
556+
der, err := CreateCertificate(tmpl, tmpl, k, k)
557+
if err != nil {
558+
t.Fatalf("CreateCertificate: %v", err)
559+
}
560+
parsed, err := stdlibParseCert(der)
561+
if err != nil {
562+
t.Fatalf("stdlibParseCert: %v", err)
563+
}
564+
565+
for _, ext := range parsed.Extensions {
566+
if !ext.Id.Equal(oidBasicConstraints) {
567+
continue
568+
}
569+
if !tc.wantPresent {
570+
t.Fatalf("BasicConstraints emitted for BasicConstraintsValid=false")
571+
}
572+
if !ext.Critical {
573+
t.Error("BasicConstraints is not critical; RFC 5280 4.2.1.9 requires it of CA certs and crypto/x509 always marks it")
574+
}
575+
if got := hex.EncodeToString(ext.Value); got != tc.wantValue {
576+
t.Errorf("BasicConstraints value = %s, want %s", got, tc.wantValue)
577+
}
578+
if parsed.IsCA != tc.isCA {
579+
t.Errorf("parsed IsCA = %v, want %v", parsed.IsCA, tc.isCA)
580+
}
581+
return
582+
}
583+
if tc.wantPresent {
584+
t.Fatal("BasicConstraints extension missing")
585+
}
586+
})
587+
}
588+
}

wolfx509/certgen_wolfcrypt.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ package wolfx509
5151
// return -174;
5252
// }
5353
// #endif
54+
//
55+
// /* Cert.basicConstSet is a C bitfield, which cgo cannot address
56+
// * without a setter. */
57+
// static void wolfx509_set_basic_const(Cert* cert, int toggle) {
58+
// cert->basicConstSet = (toggle != 0);
59+
// }
5460
import "C"
5561
import (
5662
"encoding/asn1"
@@ -120,6 +126,9 @@ type certBuildOpts struct {
120126
DNSNames []string // SubjectAltName dNSName entries
121127
IPAddresses []net.IP // SubjectAltName iPAddress entries
122128

129+
// BasicConstraintsValid requests the BasicConstraints extension.
130+
BasicConstraintsValid bool
131+
123132
// AcmeKeyAuth, if non-empty, sets the RFC 8737 id-pe-acmeIdentifier
124133
// (1.3.6.1.5.5.7.1.31) extension on the cert. Pass the raw keyAuth
125134
// bytes (token "." JWK_thumbprint per RFC 8555 §8.1); wolfCrypt
@@ -185,8 +194,16 @@ func buildAndSignCert(opts certBuildOpts, parentDER []byte, pubKey, signerKey Ke
185194
}
186195
cert.version = 2
187196
cert.sigType = C.int(sigType)
188-
if opts.IsCA {
189-
cert.isCA = 1
197+
// BasicConstraints: emitted only when BasicConstraintsValid is set, has to
198+
// be marked CRITICAL if set.
199+
// When IsCA is TRUE, wolfSSL will automatically encode the extension.
200+
if opts.BasicConstraintsValid {
201+
cert.basicConstCrit = 1
202+
if opts.IsCA {
203+
cert.isCA = 1
204+
} else {
205+
C.wolfx509_set_basic_const(&cert, 1)
206+
}
190207
}
191208
cert.keyUsage = C.word16(opts.KeyUsage)
192209
cert.extKeyUsage = C.byte(opts.ExtKeyUsage)

0 commit comments

Comments
 (0)