Repository: smallstep/crypto
Pull request #64 (2022-09-09 22:31:12)

Convert x509 to certificate
### Description
Add utility to convert an x509.Certificate to an x509util.Certificate.

💔Thank you!


maraino: @areed It looks like a change is now necessary in the workflows too.

Patch:
diff --git a/x509util/certificate.go b/x509util/certificate.go
index a3c1956..e4938b1 100644
--- a/x509util/certificate.go
+++ b/x509util/certificate.go
@@ -55,7 +55,7 @@ func NewCertificate(cr *x509.CertificateRequest, opts ...Option) (*Certificate,
 	// If no template use only the certificate request with the default leaf key
 	// usages.
 	if o.CertBuffer == nil {
-		return newCertificateRequest(cr).GetLeafCertificate(), nil
+		return NewCertificateRequestFromX509(cr).GetLeafCertificate(), nil
 	}
 
 	// With templates
diff --git a/x509util/certificate_request.go b/x509util/certificate_request.go
index 3f0b955..161ff3e 100644
--- a/x509util/certificate_request.go
+++ b/x509util/certificate_request.go
@@ -60,15 +60,15 @@ func NewCertificateRequest(signer crypto.Signer, opts ...Option) (*CertificateRe
 	return &cr, nil
 }
 
-// newCertificateRequest is an internal method that creates a CertificateRequest
-// from an x509.CertificateRequest.
+// NewCertificateRequestFromX509 creates a CertificateRequest from an
+// x509.CertificateRequest.
 //
 // This method is used to create the template variable .Insecure.CR or to
-// initialize the Certificate when no templates are used. newCertificateRequest
-// will always ignore the SignatureAlgorithm because we cannot guarantee that
-// the signer will be able to sign a certificate template if
-// Certificate.SignatureAlgorithm is set.
-func newCertificateRequest(cr *x509.CertificateRequest) *CertificateRequest {
+// initialize the Certificate when no templates are used.
+// NewCertificateRequestFromX509 will always ignore the SignatureAlgorithm
+// because we cannot guarantee that the signer will be able to sign a
+// certificate template if Certificate.SignatureAlgorithm is set.
+func NewCertificateRequestFromX509(cr *x509.CertificateRequest) *CertificateRequest {
 	// Set SubjectAltName extension as critical if Subject is empty.
 	fixSubjectAltName(cr)
 	return &CertificateRequest{
diff --git a/x509util/extensions.go b/x509util/extensions.go
index 11f8e7f..3bbdd4b 100644
--- a/x509util/extensions.go
+++ b/x509util/extensions.go
@@ -545,6 +545,46 @@ func (k *KeyUsage) UnmarshalJSON(data []byte) error {
 	return nil
 }
 
+// MarshalJSON implements the json.Marshaler interface and converts a key usage
+// into a list of strings.
+func (k KeyUsage) MarshalJSON() ([]byte, error) {
+	var usages []string
+
+	if x509.KeyUsage(k)&x509.KeyUsageDigitalSignature != 0 {
+		usages = append(usages, KeyUsageDigitalSignature)
+	}
+	if x509.KeyUsage(k)&x509.KeyUsageContentCommitment != 0 {
+		usages = append(usages, KeyUsageContentCommitment)
+	}
+	if x509.KeyUsage(k)&x509.KeyUsageKeyEncipherment != 0 {
+		usages = append(usages, KeyUsageKeyEncipherment)
+	}
+	if x509.KeyUsage(k)&x509.KeyUsageDataEncipherment != 0 {
+		usages = append(usages, KeyUsageDataEncipherment)
+	}
+	if x509.KeyUsage(k)&x509.KeyUsageKeyAgreement != 0 {
+		usages = append(usages, KeyUsageKeyAgreement)
+	}
+	if x509.KeyUsage(k)&x509.KeyUsageCertSign != 0 {
+		usages = append(usages, KeyUsageCertSign)
+	}
+	if x509.KeyUsage(k)&x509.KeyUsageCRLSign != 0 {
+		usages = append(usages, KeyUsageCRLSign)
+	}
+	if x509.KeyUsage(k)&x509.KeyUsageEncipherOnly != 0 {
+		usages = append(usages, KeyUsageEncipherOnly)
+	}
+	if x509.KeyUsage(k)&x509.KeyUsageDecipherOnly != 0 {
+		usages = append(usages, KeyUsageDecipherOnly)
+	}
+
+	if len(usages) == 0 && k != 0 {
+		return nil, fmt.Errorf("cannot marshal key usage %v", k)
+	}
+
+	return json.Marshal(usages)
+}
+
 // ExtKeyUsage represents a JSON array of extended key usages.
 type ExtKeyUsage []x509.ExtKeyUsage
 
@@ -603,6 +643,49 @@ func (k *ExtKeyUsage) UnmarshalJSON(data []byte) error {
 	return nil
 }
 
+// MarshalJSON implements the json.Marshaler interface and converts a list of
+// extended key usages to a list of strings
+func (k ExtKeyUsage) MarshalJSON() ([]byte, error) {
+	usages := make([]string, len(k))
+
+	for i, eku := range k {
+		switch eku {
+		case x509.ExtKeyUsageAny:
+			usages[i] = ExtKeyUsageAny
+		case x509.ExtKeyUsageServerAuth:
+			usages[i] = ExtKeyUsageServerAuth
+		case x509.ExtKeyUsageClientAuth:
+			usages[i] = ExtKeyUsageClientAuth
+		case x509.ExtKeyUsageCodeSigning:
+			usages[i] = ExtKeyUsageCodeSigning
+		case x509.ExtKeyUsageEmailProtection:
+			usages[i] = ExtKeyUsageEmailProtection
+		case x509.ExtKeyUsageIPSECEndSystem:
+			usages[i] = ExtKeyUsageIPSECEndSystem
+		case x509.ExtKeyUsageIPSECTunnel:
+			usages[i] = ExtKeyUsageIPSECTunnel
+		case x509.ExtKeyUsageIPSECUser:
+			usages[i] = ExtKeyUsageIPSECUser
+		case x509.ExtKeyUsageTimeStamping:
+			usages[i] = ExtKeyUsageTimeStamping
+		case x509.ExtKeyUsageOCSPSigning:
+			usages[i] = ExtKeyUsageOCSPSigning
+		case x509.ExtKeyUsageMicrosoftServerGatedCrypto:
+			usages[i] = ExtKeyUsageMicrosoftServerGatedCrypto
+		case x509.ExtKeyUsageNetscapeServerGatedCrypto:
+			usages[i] = ExtKeyUsageNetscapeServerGatedCrypto
+		case x509.ExtKeyUsageMicrosoftCommercialCodeSigning:
+			usages[i] = ExtKeyUsageMicrosoftCommercialCodeSigning
+		case x509.ExtKeyUsageMicrosoftKernelCodeSigning:
+			usages[i] = ExtKeyUsageMicrosoftKernelCodeSigning
+		default:
+			return nil, fmt.Errorf("unsupported extKeyUsage %v", eku)
+		}
+	}
+
+	return json.Marshal(usages)
+}
+
 // UnknownExtKeyUsage represents the list of OIDs of extended key usages unknown
 // to crypto/x509.
 type UnknownExtKeyUsage MultiObjectIdentifier
diff --git a/x509util/templates.go b/x509util/templates.go
index 059cf7f..3199d22 100644
--- a/x509util/templates.go
+++ b/x509util/templates.go
@@ -123,7 +123,7 @@ func (t TemplateData) SetAuthorizationCertificateChain(chain interface{}) {
 // SetCertificateRequest sets the given certificate request in the insecure
 // template data.
 func (t TemplateData) SetCertificateRequest(cr *x509.CertificateRequest) {
-	t.SetInsecure(CertificateRequestKey, newCertificateRequest(cr))
+	t.SetInsecure(CertificateRequestKey, NewCertificateRequestFromX509(cr))
 }
 
 // SetWebhook sets the given webhook response in the webhooks template data.


Test patch:
diff --git a/x509util/certificate_request_test.go b/x509util/certificate_request_test.go
index dae1826..6e200e6 100644
--- a/x509util/certificate_request_test.go
+++ b/x509util/certificate_request_test.go
@@ -90,7 +90,7 @@ func Test_newCertificateRequest(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			if got := newCertificateRequest(tt.args.cr); !reflect.DeepEqual(got, tt.want) {
+			if got := NewCertificateRequestFromX509(tt.args.cr); !reflect.DeepEqual(got, tt.want) {
 				t.Errorf("newCertificateRequest() = %v, want %v", got, tt.want)
 			}
 		})
diff --git a/x509util/certificate_test.go b/x509util/certificate_test.go
index af31ff0..9bae360 100644
--- a/x509util/certificate_test.go
+++ b/x509util/certificate_test.go
@@ -438,23 +438,23 @@ func TestCreateCertificate(t *testing.T) {
 	}
 
 	cr1, priv1 := createCertificateRequest(t, "commonName", []string{"foo.com"})
-	crt1 := newCertificateRequest(cr1).GetLeafCertificate().GetCertificate()
+	crt1 := NewCertificateRequestFromX509(cr1).GetLeafCertificate().GetCertificate()
 	crt1.SerialNumber = mustSerialNumber()
 	crt1.SubjectKeyId = mustSubjectKeyID(priv1.Public())
 
 	cr2, priv2 := createCertificateRequest(t, "commonName", []string{"foo.com"})
-	crt2 := newCertificateRequest(cr2).GetLeafCertificate().GetCertificate()
+	crt2 := NewCertificateRequestFromX509(cr2).GetLeafCertificate().GetCertificate()
 	crt2.SerialNumber = mustSerialNumber()
 
 	cr3, priv3 := createCertificateRequest(t, "commonName", []string{"foo.com"})
-	crt3 := newCertificateRequest(cr3).GetLeafCertificate().GetCertificate()
+	crt3 := NewCertificateRequestFromX509(cr3).GetLeafCertificate().GetCertificate()
 	crt3.SubjectKeyId = mustSubjectKeyID(priv1.Public())
 
 	cr4, priv4 := createCertificateRequest(t, "commonName", []string{"foo.com"})
-	crt4 := newCertificateRequest(cr4).GetLeafCertificate().GetCertificate()
+	crt4 := NewCertificateRequestFromX509(cr4).GetLeafCertificate().GetCertificate()
 
 	cr5, _ := createCertificateRequest(t, "commonName", []string{"foo.com"})
-	crt5 := newCertificateRequest(cr5).GetLeafCertificate().GetCertificate()
+	crt5 := NewCertificateRequestFromX509(cr5).GetLeafCertificate().GetCertificate()
 
 	badSigner := createBadSigner(t)
 
diff --git a/x509util/extensions_test.go b/x509util/extensions_test.go
index f0f9ce4..ab6e6ed 100644
--- a/x509util/extensions_test.go
+++ b/x509util/extensions_test.go
@@ -409,6 +409,46 @@ func TestKeyUsage_Set(t *testing.T) {
 	}
 }
 
+func TestKeyUsage_MarshalJSON(t *testing.T) {
+	tests := []struct {
+		name    string
+		k       KeyUsage
+		want    string
+		wantErr bool
+	}{
+		{"DigitalSignature", KeyUsage(x509.KeyUsageDigitalSignature), `["digitalsignature"]`, false},
+		{"ContentCommitment", KeyUsage(x509.KeyUsageContentCommitment), `["contentcommitment"]`, false},
+		{"KeyEncipherment", KeyUsage(x509.KeyUsageKeyEncipherment), `["keyencipherment"]`, false},
+		{"DataEncipherment", KeyUsage(x509.KeyUsageDataEncipherment), `["dataencipherment"]`, false},
+		{"KeyAgreement", KeyUsage(x509.KeyUsageKeyAgreement), `["keyagreement"]`, false},
+		{"CertSign", KeyUsage(x509.KeyUsageCertSign), `["certsign"]`, false},
+		{"CRLSign", KeyUsage(x509.KeyUsageCRLSign), `["crlsign"]`, false},
+		{"EncipherOnly", KeyUsage(x509.KeyUsageEncipherOnly), `["encipheronly"]`, false},
+		{"DecipherOnly", KeyUsage(x509.KeyUsageDecipherOnly), `["decipheronly"]`, false},
+		{"DigitalSignature + KeyEncipherment", KeyUsage(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment), `["digitalsignature","keyencipherment"]`, false},
+		{"Error", KeyUsage(x509.KeyUsageDecipherOnly << 1), "", true},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := tt.k.MarshalJSON()
+			if (err != nil) != tt.wantErr {
+				t.Fatalf("KeyUsage.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
+			}
+			if tt.wantErr {
+				return
+			}
+			if tt.want != string(got) {
+				t.Errorf("KeyUsage.MarshalJSON() = %q, want %q", string(got), tt.want)
+			}
+			var unmarshaled KeyUsage
+			unmarshaled.UnmarshalJSON(got)
+			if unmarshaled != tt.k {
+				t.Errorf("KeyUsage.UnmarshalJSON(keyUsage.MarshalJSON) = %v, want %v", unmarshaled, tt.k)
+			}
+		})
+	}
+}
+
 func TestKeyUsage_UnmarshalJSON(t *testing.T) {
 	type args struct {
 		data []byte
@@ -491,6 +531,51 @@ func TestExtKeyUsage_Set(t *testing.T) {
 	}
 }
 
+func TestExtKeyUsage_MarshalJSON(t *testing.T) {
+	tests := []struct {
+		name    string
+		eku     ExtKeyUsage
+		want    string
+		wantErr bool
+	}{
+		{"Any", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageAny}), `["any"]`, false},
+		{"ServerAuth", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}), `["serverauth"]`, false},
+		{"ClientAuth", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}), `["clientauth"]`, false},
+		{"CodeSigning", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}), `["codesigning"]`, false},
+		{"EmailProtection", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageEmailProtection}), `["emailprotection"]`, false},
+		{"IPSECEndSystem", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageIPSECEndSystem}), `["ipsecendsystem"]`, false},
+		{"IPSECTunnel", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageIPSECTunnel}), `["ipsectunnel"]`, false},
+		{"IPSECUser", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageIPSECUser}), `["ipsecuser"]`, false},
+		{"TimeStamping", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageTimeStamping}), `["timestamping"]`, false},
+		{"OCSPSigning", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageOCSPSigning}), `["ocspsigning"]`, false},
+		{"MicrosoftServerGatedCrypto", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageMicrosoftServerGatedCrypto}), `["microsoftservergatedcrypto"]`, false},
+		{"NetscapeServerGatedCrypto", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageNetscapeServerGatedCrypto}), `["netscapeservergatedcrypto"]`, false},
+		{"MicrosoftCommercialCodeSigning", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageMicrosoftCommercialCodeSigning}), `["microsoftcommercialcodesigning"]`, false},
+		{"MicrosoftKernelCodeSigning", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageMicrosoftKernelCodeSigning}), `["microsoftkernelcodesigning"]`, false},
+		{"ServerAuth + ClientAuth", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}), `["serverauth","clientauth"]`, false},
+		{"Error", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageMicrosoftKernelCodeSigning + 1}), "", true},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := tt.eku.MarshalJSON()
+			if (err != nil) != tt.wantErr {
+				t.Fatalf("ExtKeyUsage.MarshalJSON() = error = %v, wantErr %v", err, tt.wantErr)
+			}
+			if tt.wantErr {
+				return
+			}
+			if tt.want != string(got) {
+				t.Errorf("ExtKeyUsage.MarshalJSON() = %q, want %q", string(got), tt.want)
+			}
+			var unmarshaled ExtKeyUsage
+			unmarshaled.UnmarshalJSON(got)
+			if !reflect.DeepEqual(unmarshaled, tt.eku) {
+				t.Errorf("ExtKeyUsage.UnmarshalJSON(ExtKeyUsage.MarshalJSON) = %v, want %v", unmarshaled, tt.eku)
+			}
+		})
+	}
+}
+
 func TestExtKeyUsage_UnmarshalJSON(t *testing.T) {
 	type args struct {
 		data []byte