Repository: smallstep/crypto
Pull request #12 (2020-11-17 19:29:38)

Set basic constraints if they are in the template
### Description

This PR adds the basic constraints extension if they are defined in the template, even if IsCA is set to false. Apparently, this might be a requirement for OPC UA (OPC Unified Architecture) servers. See https://github.com/smallstep/certificates#426


Patch:
diff --git a/x509util/extensions.go b/x509util/extensions.go
index 661d1a8..b0d6f5b 100644
--- a/x509util/extensions.go
+++ b/x509util/extensions.go
@@ -404,9 +404,9 @@ type BasicConstraints struct {
 
 // Set sets the basic constraints to the given certificate.
 func (b BasicConstraints) Set(c *x509.Certificate) {
+	c.BasicConstraintsValid = true
 	c.IsCA = b.IsCA
 	if c.IsCA {
-		c.BasicConstraintsValid = true
 		switch {
 		case b.MaxPathLen == 0:
 			c.MaxPathLen = 0
@@ -419,7 +419,6 @@ func (b BasicConstraints) Set(c *x509.Certificate) {
 			c.MaxPathLenZero = false
 		}
 	} else {
-		c.BasicConstraintsValid = false
 		c.MaxPathLen = 0
 		c.MaxPathLenZero = false
 	}


Test patch:
diff --git a/x509util/certificate_test.go b/x509util/certificate_test.go
index 65c7dae..5c1a3bc 100644
--- a/x509util/certificate_test.go
+++ b/x509util/certificate_test.go
@@ -214,6 +214,29 @@ func TestNewCertificate(t *testing.T) {
 			PublicKeyAlgorithm: x509.Ed25519,
 		},
 			false},
+		{"okOPCUA", args{cr, []Option{WithTemplateFile("./testdata/opcua.tpl", TemplateData{
+			SANsKey: []SubjectAlternativeName{
+				{Type: "dns", Value: "foo.com"},
+			},
+			TokenKey: map[string]interface{}{
+				"iss": "https://iss",
+				"sub": "sub",
+			},
+		})}}, &Certificate{
+			Subject:  Subject{CommonName: ""},
+			SANs:     []SubjectAlternativeName{{Type: DNSType, Value: "foo.com"}},
+			KeyUsage: KeyUsage(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement | x509.KeyUsageCertSign),
+			BasicConstraints: &BasicConstraints{
+				IsCA:       false,
+				MaxPathLen: 0,
+			},
+			ExtKeyUsage: ExtKeyUsage([]x509.ExtKeyUsage{
+				x509.ExtKeyUsageServerAuth,
+				x509.ExtKeyUsageClientAuth,
+			}),
+			PublicKey:          priv.Public(),
+			PublicKeyAlgorithm: x509.Ed25519,
+		}, false},
 		{"badSignature", args{crBadSignateure, nil}, nil, true},
 		{"failTemplate", args{cr, []Option{WithTemplate(`{{ fail "fatal error }}`, CreateTemplateData("commonName", []string{"foo.com"}))}}, nil, true},
 		{"missingTemplate", args{cr, []Option{WithTemplateFile("./testdata/missing.tpl", CreateTemplateData("commonName", []string{"foo.com"}))}}, nil, true},
diff --git a/x509util/extensions_test.go b/x509util/extensions_test.go
index 17e5da5..0b106c6 100644
--- a/x509util/extensions_test.go
+++ b/x509util/extensions_test.go
@@ -778,13 +778,13 @@ func TestBasicConstraints_Set(t *testing.T) {
 		args   args
 		want   *x509.Certificate
 	}{
-		{"IsCAFalse", fields{false, 0}, args{&x509.Certificate{}}, &x509.Certificate{}},
-		{"IsCAFalseWithPathLen", fields{false, 1}, args{&x509.Certificate{}}, &x509.Certificate{}},
-		{"IsCAFalseWithAnyPathLen", fields{false, -1}, args{&x509.Certificate{}}, &x509.Certificate{}},
+		{"IsCAFalse", fields{false, 0}, args{&x509.Certificate{}}, &x509.Certificate{IsCA: false, BasicConstraintsValid: true}},
+		{"IsCAFalseWithPathLen", fields{false, 1}, args{&x509.Certificate{}}, &x509.Certificate{IsCA: false, BasicConstraintsValid: true}},
+		{"IsCAFalseWithAnyPathLen", fields{false, -1}, args{&x509.Certificate{}}, &x509.Certificate{IsCA: false, BasicConstraintsValid: true}},
 		{"IsCATrue", fields{true, 0}, args{&x509.Certificate{}}, &x509.Certificate{IsCA: true, MaxPathLen: 0, MaxPathLenZero: true, BasicConstraintsValid: true}},
 		{"IsCATrueWithPathLen", fields{true, 1}, args{&x509.Certificate{}}, &x509.Certificate{IsCA: true, MaxPathLen: 1, MaxPathLenZero: false, BasicConstraintsValid: true}},
 		{"IsCATrueWithAnyPathLen", fields{true, -1}, args{&x509.Certificate{}}, &x509.Certificate{IsCA: true, MaxPathLen: -1, MaxPathLenZero: false, BasicConstraintsValid: true}},
-		{"overwriteToFalse", fields{false, 0}, args{&x509.Certificate{IsCA: true, MaxPathLen: 0, MaxPathLenZero: true, BasicConstraintsValid: true}}, &x509.Certificate{}},
+		{"overwriteToFalse", fields{false, 0}, args{&x509.Certificate{IsCA: true, MaxPathLen: 0, MaxPathLenZero: true, BasicConstraintsValid: true}}, &x509.Certificate{IsCA: false, BasicConstraintsValid: true}},
 		{"overwriteToTrue", fields{true, -100}, args{&x509.Certificate{IsCA: true, MaxPathLen: 0, MaxPathLenZero: true, BasicConstraintsValid: true}}, &x509.Certificate{IsCA: true, MaxPathLen: -1, MaxPathLenZero: false, BasicConstraintsValid: true}},
 	}
 	for _, tt := range tests {
diff --git a/x509util/testdata/opcua.tpl b/x509util/testdata/opcua.tpl
new file mode 100644
index 0000000..5f58f86
--- /dev/null
+++ b/x509util/testdata/opcua.tpl
@@ -0,0 +1,9 @@
+{
+      "subject": {{ toJson .Subject }},
+      "sans": {{ toJson .SANs }},
+      "basicConstraints": {
+            "isCA": false
+      },
+      "keyUsage": ["digitalSignature", "keyEncipherment", "keyAgreement", "certSign"],
+      "extKeyUsage": ["serverAuth", "clientAuth"]
+}