Repository: smallstep/crypto
Pull request #8 (2020-09-01 18:55:55)

Add support for UnknownExtKeyUsage.
### Description
This PR adds support for UnknownExtKeyUsage in certificate templates. UnknownExtKeyUsage are extended key usages not known by crypto/x509.

Fixes smallstep/certificates#361


Patch:
diff --git a/x509util/certificate.go b/x509util/certificate.go
index d86dda1..9702741 100644
--- a/x509util/certificate.go
+++ b/x509util/certificate.go
@@ -26,6 +26,7 @@ type Certificate struct {
 	Extensions            []Extension              `json:"extensions"`
 	KeyUsage              KeyUsage                 `json:"keyUsage"`
 	ExtKeyUsage           ExtKeyUsage              `json:"extKeyUsage"`
+	UnknownExtKeyUsage    UnknownExtKeyUsage       `json:"unknownExtKeyUsage"`
 	SubjectKeyID          SubjectKeyID             `json:"subjectKeyId"`
 	AuthorityKeyID        AuthorityKeyID           `json:"authorityKeyId"`
 	OCSPServer            OCSPServer               `json:"ocspServer"`
@@ -98,6 +99,7 @@ func (c *Certificate) GetCertificate() *x509.Certificate {
 	// Defined extensions.
 	c.KeyUsage.Set(cert)
 	c.ExtKeyUsage.Set(cert)
+	c.UnknownExtKeyUsage.Set(cert)
 	c.SubjectKeyID.Set(cert)
 	c.AuthorityKeyID.Set(cert)
 	c.OCSPServer.Set(cert)
diff --git a/x509util/extensions.go b/x509util/extensions.go
index 6d1bdec..661d1a8 100644
--- a/x509util/extensions.go
+++ b/x509util/extensions.go
@@ -265,6 +265,30 @@ func (k *ExtKeyUsage) UnmarshalJSON(data []byte) error {
 	return nil
 }
 
+// UnknownExtKeyUsage represents the list of OIDs of extended key usages unknown
+// to crypto/x509.
+type UnknownExtKeyUsage MultiObjectIdentifier
+
+// MarshalJSON implements the json.Marshaler interface in UnknownExtKeyUsage.
+func (u UnknownExtKeyUsage) MarshalJSON() ([]byte, error) {
+	return MultiObjectIdentifier(u).MarshalJSON()
+}
+
+// UnmarshalJSON implements the json.Unmarshaler interface in UnknownExtKeyUsage.
+func (u *UnknownExtKeyUsage) UnmarshalJSON(data []byte) error {
+	var v MultiObjectIdentifier
+	if err := json.Unmarshal(data, &v); err != nil {
+		return errors.Wrap(err, "error unmarshaling json")
+	}
+	*u = UnknownExtKeyUsage(v)
+	return nil
+}
+
+// Set sets the policy identifiers to the given certificate.
+func (u UnknownExtKeyUsage) Set(c *x509.Certificate) {
+	c.UnknownExtKeyUsage = u
+}
+
 // SubjectKeyID represents the binary value of the subject key identifier
 // extension, this should be the SHA-1 hash of the public key. In JSON this
 // value should be a base64-encoded string, and in most cases it should not be


Test patch:
diff --git a/x509util/certificate_test.go b/x509util/certificate_test.go
index 152dc04..e40c6d0 100644
--- a/x509util/certificate_test.go
+++ b/x509util/certificate_test.go
@@ -187,12 +187,13 @@ func TestNewCertificate(t *testing.T) {
 			Extensions:            []Extension{{ID: []int{1, 2, 3, 4}, Critical: true, Value: []byte("extension")}},
 			KeyUsage:              KeyUsage(x509.KeyUsageDigitalSignature),
 			ExtKeyUsage:           ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}),
+			UnknownExtKeyUsage:    []asn1.ObjectIdentifier{[]int{1, 3, 6, 1, 4, 1, 44924, 1, 6}, []int{1, 3, 6, 1, 4, 1, 44924, 1, 7}},
 			SubjectKeyID:          []byte("subjectKeyId"),
 			AuthorityKeyID:        []byte("authorityKeyId"),
 			OCSPServer:            []string{"https://ocsp.server"},
 			IssuingCertificateURL: []string{"https://ca.com"},
 			CRLDistributionPoints: []string{"https://ca.com/ca.crl"},
-			PolicyIdentifiers:     PolicyIdentifiers{[]int{5, 6, 7, 8, 9, 0}},
+			PolicyIdentifiers:     PolicyIdentifiers{[]int{1, 2, 3, 4, 5, 6}},
 			BasicConstraints: &BasicConstraints{
 				IsCA:       false,
 				MaxPathLen: 0,
@@ -246,6 +247,7 @@ func TestCertificate_GetCertificate(t *testing.T) {
 		Extensions            []Extension
 		KeyUsage              KeyUsage
 		ExtKeyUsage           ExtKeyUsage
+		UnknownExtKeyUsage    UnknownExtKeyUsage
 		SubjectKeyID          SubjectKeyID
 		AuthorityKeyID        AuthorityKeyID
 		OCSPServer            OCSPServer
@@ -284,6 +286,7 @@ func TestCertificate_GetCertificate(t *testing.T) {
 				x509.ExtKeyUsageServerAuth,
 				x509.ExtKeyUsageClientAuth,
 			}),
+			UnknownExtKeyUsage:    []asn1.ObjectIdentifier{[]int{1, 3, 6, 1, 4, 1, 44924, 1, 6}, []int{1, 3, 6, 1, 4, 1, 44924, 1, 7}},
 			SubjectKeyID:          []byte("subject-key-id"),
 			AuthorityKeyID:        []byte("authority-key-id"),
 			OCSPServer:            []string{"https://oscp.server"},
@@ -310,6 +313,7 @@ func TestCertificate_GetCertificate(t *testing.T) {
 				x509.ExtKeyUsageServerAuth,
 				x509.ExtKeyUsageClientAuth,
 			},
+			UnknownExtKeyUsage:    []asn1.ObjectIdentifier{[]int{1, 3, 6, 1, 4, 1, 44924, 1, 6}, []int{1, 3, 6, 1, 4, 1, 44924, 1, 7}},
 			SubjectKeyId:          []byte("subject-key-id"),
 			AuthorityKeyId:        []byte("authority-key-id"),
 			OCSPServer:            []string{"https://oscp.server"},
@@ -341,6 +345,7 @@ func TestCertificate_GetCertificate(t *testing.T) {
 				Extensions:            tt.fields.Extensions,
 				KeyUsage:              tt.fields.KeyUsage,
 				ExtKeyUsage:           tt.fields.ExtKeyUsage,
+				UnknownExtKeyUsage:    tt.fields.UnknownExtKeyUsage,
 				SubjectKeyID:          tt.fields.SubjectKeyID,
 				AuthorityKeyID:        tt.fields.AuthorityKeyID,
 				OCSPServer:            tt.fields.OCSPServer,
diff --git a/x509util/extensions_test.go b/x509util/extensions_test.go
index fb8f23f..17e5da5 100644
--- a/x509util/extensions_test.go
+++ b/x509util/extensions_test.go
@@ -406,6 +406,84 @@ func TestExtKeyUsage_UnmarshalJSON(t *testing.T) {
 	}
 }
 
+func TestUnknownExtKeyUsage_MarshalJSON(t *testing.T) {
+	tests := []struct {
+		name    string
+		m       UnknownExtKeyUsage
+		want    []byte
+		wantErr bool
+	}{
+		{"ok", []asn1.ObjectIdentifier{[]int{1, 2, 3, 4}, []int{5, 6, 7, 8, 9, 0}}, []byte(`["1.2.3.4","5.6.7.8.9.0"]`), false},
+		{"empty", []asn1.ObjectIdentifier{}, []byte(`[]`), false},
+		{"nil", nil, []byte(`null`), false},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := json.Marshal(tt.m)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("UnknownExtKeyUsage.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("UnknownExtKeyUsage.MarshalJSON() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestUnknownExtKeyUsage_UnmarshalJSON(t *testing.T) {
+	type args struct {
+		data []byte
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    UnknownExtKeyUsage
+		wantErr bool
+	}{
+		{"string", args{[]byte(`"1.2.3.4"`)}, []asn1.ObjectIdentifier{[]int{1, 2, 3, 4}}, false},
+		{"array", args{[]byte(`["1.2.3.4", "5.6.7.8.9.0"]`)}, []asn1.ObjectIdentifier{[]int{1, 2, 3, 4}, []int{5, 6, 7, 8, 9, 0}}, false},
+		{"empty", args{[]byte(`[]`)}, []asn1.ObjectIdentifier{}, false},
+		{"null", args{[]byte(`null`)}, nil, false},
+		{"fail", args{[]byte(`":foo:bar"`)}, nil, true},
+		{"failJSON", args{[]byte(`["https://iss#sub"`)}, nil, true},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var got UnknownExtKeyUsage
+			if err := got.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr {
+				t.Errorf("UnknownExtKeyUsage.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("UnknownExtKeyUsage.UnmarshalJSON() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestUnknownExtKeyUsage_Set(t *testing.T) {
+	type args struct {
+		c *x509.Certificate
+	}
+	tests := []struct {
+		name string
+		o    UnknownExtKeyUsage
+		args args
+		want *x509.Certificate
+	}{
+		{"ok", []asn1.ObjectIdentifier{{1, 2, 3, 4}}, args{&x509.Certificate{}}, &x509.Certificate{UnknownExtKeyUsage: []asn1.ObjectIdentifier{{1, 2, 3, 4}}}},
+		{"overwrite", []asn1.ObjectIdentifier{{1, 2, 3, 4}, {4, 3, 2, 1}}, args{&x509.Certificate{UnknownExtKeyUsage: []asn1.ObjectIdentifier{{1, 2, 3, 4}}}}, &x509.Certificate{UnknownExtKeyUsage: []asn1.ObjectIdentifier{{1, 2, 3, 4}, {4, 3, 2, 1}}}},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			tt.o.Set(tt.args.c)
+			if !reflect.DeepEqual(tt.args.c, tt.want) {
+				t.Errorf("UnknownExtKeyUsage.Set() = %v, want %v", tt.args.c, tt.want)
+			}
+		})
+	}
+}
+
 func TestSubjectKeyID_Set(t *testing.T) {
 	type args struct {
 		c *x509.Certificate
diff --git a/x509util/testdata/fullsimple.tpl b/x509util/testdata/fullsimple.tpl
index 0fbf16e..92b02f7 100644
--- a/x509util/testdata/fullsimple.tpl
+++ b/x509util/testdata/fullsimple.tpl
@@ -11,12 +11,13 @@
     "extensions": [{"id":"1.2.3.4","critical":true,"value":"ZXh0ZW5zaW9u"}],
     "keyUsage": ["digitalSignature"],
     "extKeyUsage": ["serverAuth"],
+    "unknownExtKeyUsage": ["1.3.6.1.4.1.44924.1.6", "1.3.6.1.4.1.44924.1.7"],
     "subjectKeyId": "c3ViamVjdEtleUlk",
     "authorityKeyId": "YXV0aG9yaXR5S2V5SWQ=",
     "ocspServer": "https://ocsp.server",
     "issuingCertificateURL": "https://ca.com",
     "crlDistributionPoints": "https://ca.com/ca.crl",
-    "policyIdentifiers": "5.6.7.8.9.0",
+    "policyIdentifiers": "1.2.3.4.5.6",
     "basicConstraints": {
         "isCA": false, 
         "maxPathLen": 0