From 991e44927c8fdaaee7ceed74941af742e762dd9d Mon Sep 17 00:00:00 2001 From: Lealem Amedie Date: Fri, 21 Aug 2026 20:53:51 -0600 Subject: [PATCH 1/2] F7859: wolfx509: honor BasicConstraintsValid and mark BasicConstraints critical --- wolfx509/certgen.go | 1 + wolfx509/certgen_test.go | 72 +++++++++++++++++++++++++++++++++++ wolfx509/certgen_wolfcrypt.go | 21 +++++++++- 3 files changed, 92 insertions(+), 2 deletions(-) diff --git a/wolfx509/certgen.go b/wolfx509/certgen.go index bd37eae..6e525ac 100644 --- a/wolfx509/certgen.go +++ b/wolfx509/certgen.go @@ -83,6 +83,7 @@ func CreateCertificate(template, parent *Certificate, pubKey, signer KeyHandle) DNSNames: template.DNSNames, IPAddresses: template.IPAddresses, AcmeKeyAuth: template.AcmeKeyAuth, + BasicConstraintsValid: template.BasicConstraintsValid, } if template == parent && pubKey == signer { diff --git a/wolfx509/certgen_test.go b/wolfx509/certgen_test.go index 16cf2a7..f584058 100644 --- a/wolfx509/certgen_test.go +++ b/wolfx509/certgen_test.go @@ -22,6 +22,8 @@ package wolfx509 import ( "bytes" + "encoding/asn1" + "encoding/hex" "math/big" "net" "testing" @@ -448,3 +450,73 @@ func TestCreateCertificateValidDays(t *testing.T) { t.Errorf("NotAfter = %s, want within [%s, %s]", parsed.NotAfter, naLo, naHi) } } + +// oidBasicConstraints is id-ce-basicConstraints (RFC 5280 4.2.1.9). +var oidBasicConstraints = asn1.ObjectIdentifier{2, 5, 29, 19} + +// TestBasicConstraintsMatrix pins the BasicConstraints extension that +// emitted for a given template: +// - emitted only when BasicConstraintsValid is set, +// - always critical, +// - CA omitted when IsCA is false, +func TestBasicConstraintsMatrix(t *testing.T) { + for _, tc := range []struct { + name string + bcValid bool + isCA bool + wantPresent bool + wantValue string // hex of the extension value + }{ + {"valid+CA", true, true, true, "30030101ff"}, + {"valid+notCA", true, false, true, "3000"}, + {"notValid+CA", false, true, false, ""}, + {"notValid+notCA", false, false, false, ""}, + } { + t.Run(tc.name, func(t *testing.T) { + k, err := GenerateP256Key() + if err != nil { + t.Fatalf("GenerateP256Key: %v", err) + } + defer k.Free() + + tmpl := &Certificate{ + SerialNumber: big.NewInt(1), + Subject: Name{CommonName: "bc test"}, + NotBefore: time.Now().Add(-time.Hour), + NotAfter: time.Now().Add(24 * time.Hour), + BasicConstraintsValid: tc.bcValid, + IsCA: tc.isCA, + } + der, err := CreateCertificate(tmpl, tmpl, k, k) + if err != nil { + t.Fatalf("CreateCertificate: %v", err) + } + parsed, err := stdlibParseCert(der) + if err != nil { + t.Fatalf("stdlibParseCert: %v", err) + } + + for _, ext := range parsed.Extensions { + if !ext.Id.Equal(oidBasicConstraints) { + continue + } + if !tc.wantPresent { + t.Fatalf("BasicConstraints emitted for BasicConstraintsValid=false") + } + if !ext.Critical { + t.Error("BasicConstraints is not critical; RFC 5280 4.2.1.9 requires it of CA certs and crypto/x509 always marks it") + } + if got := hex.EncodeToString(ext.Value); got != tc.wantValue { + t.Errorf("BasicConstraints value = %s, want %s", got, tc.wantValue) + } + if parsed.IsCA != tc.isCA { + t.Errorf("parsed IsCA = %v, want %v", parsed.IsCA, tc.isCA) + } + return + } + if tc.wantPresent { + t.Fatal("BasicConstraints extension missing") + } + }) + } +} diff --git a/wolfx509/certgen_wolfcrypt.go b/wolfx509/certgen_wolfcrypt.go index b7389fe..fb8d10c 100644 --- a/wolfx509/certgen_wolfcrypt.go +++ b/wolfx509/certgen_wolfcrypt.go @@ -51,6 +51,12 @@ package wolfx509 // return -174; // } // #endif +// +// /* Cert.basicConstSet is a C bitfield, which cgo cannot address +// * without a setter. */ +// static void wolfx509_set_basic_const(Cert* cert, int toggle) { +// cert->basicConstSet = (toggle != 0); +// } import "C" import ( "encoding/asn1" @@ -120,6 +126,9 @@ type certBuildOpts struct { DNSNames []string // SubjectAltName dNSName entries IPAddresses []net.IP // SubjectAltName iPAddress entries + // BasicConstraintsValid requests the BasicConstraints extension. + BasicConstraintsValid bool + // AcmeKeyAuth, if non-empty, sets the RFC 8737 id-pe-acmeIdentifier // (1.3.6.1.5.5.7.1.31) extension on the cert. Pass the raw keyAuth // bytes (token "." JWK_thumbprint per RFC 8555 ยง8.1); wolfCrypt @@ -182,8 +191,16 @@ func buildAndSignCert(opts certBuildOpts, parentDER []byte, pubKey, signerKey Ke } cert.version = 2 cert.sigType = C.int(sigType) - if opts.IsCA { - cert.isCA = 1 + // BasicConstraints: emitted only when BasicConstraintsValid is set, has to + // be marked CRITICAL if set. + // When IsCA is TRUE, wolfSSL will automiatically encode the extension. + if opts.BasicConstraintsValid { + cert.basicConstCrit = 1 + if opts.IsCA { + cert.isCA = 1 + } else { + C.wolfx509_set_basic_const(&cert, 1) + } } cert.keyUsage = C.word16(opts.KeyUsage) cert.extKeyUsage = C.byte(opts.ExtKeyUsage) From b98ad8f8c2ba547fbb906406231961b17e0864b1 Mon Sep 17 00:00:00 2001 From: Lealem Amedie Date: Mon, 24 Aug 2026 11:40:43 -0600 Subject: [PATCH 2/2] Copilot fixes --- wolfx509/certgen_test.go | 5 +++-- wolfx509/certgen_wolfcrypt.go | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/wolfx509/certgen_test.go b/wolfx509/certgen_test.go index f584058..d4b0a05 100644 --- a/wolfx509/certgen_test.go +++ b/wolfx509/certgen_test.go @@ -454,11 +454,12 @@ func TestCreateCertificateValidDays(t *testing.T) { // oidBasicConstraints is id-ce-basicConstraints (RFC 5280 4.2.1.9). var oidBasicConstraints = asn1.ObjectIdentifier{2, 5, 29, 19} -// TestBasicConstraintsMatrix pins the BasicConstraints extension that +// TestBasicConstraintsMatrix pins the BasicConstraints extension that is // emitted for a given template: // - emitted only when BasicConstraintsValid is set, // - always critical, -// - CA omitted when IsCA is false, +// - present but with the cA boolean omitted (an empty SEQUENCE) when IsCA +// is false. func TestBasicConstraintsMatrix(t *testing.T) { for _, tc := range []struct { name string diff --git a/wolfx509/certgen_wolfcrypt.go b/wolfx509/certgen_wolfcrypt.go index fb8d10c..a12302b 100644 --- a/wolfx509/certgen_wolfcrypt.go +++ b/wolfx509/certgen_wolfcrypt.go @@ -193,7 +193,7 @@ func buildAndSignCert(opts certBuildOpts, parentDER []byte, pubKey, signerKey Ke cert.sigType = C.int(sigType) // BasicConstraints: emitted only when BasicConstraintsValid is set, has to // be marked CRITICAL if set. - // When IsCA is TRUE, wolfSSL will automiatically encode the extension. + // When IsCA is TRUE, wolfSSL will automatically encode the extension. if opts.BasicConstraintsValid { cert.basicConstCrit = 1 if opts.IsCA {