From 936b845cf4cccf7e214f09b8bdce8bc40a754838 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Tue, 9 Jul 2024 16:28:31 -0700 Subject: [PATCH] Add support for setting the raw subject in the templates This commit allows setting the raw subject of the certificate. This allows for example copying the subject for the CR without changing the types of some subject fields from UTF8String to PrintableString. Fixes smallstep/certificates#1917 --- x509util/certificate.go | 2 ++ x509util/certificate_request.go | 3 ++ x509util/certificate_test.go | 50 ++++++++++++++++++++++++++++++++ x509util/testdata/rawSubject.csr | 9 ++++++ x509util/testdata/rawSubject.key | 5 ++++ x509util/testdata/rawSubject.tpl | 10 +++++++ 6 files changed, 79 insertions(+) create mode 100644 x509util/testdata/rawSubject.csr create mode 100644 x509util/testdata/rawSubject.key create mode 100644 x509util/testdata/rawSubject.tpl diff --git a/x509util/certificate.go b/x509util/certificate.go index 69eb5378..a1d8fe18 100644 --- a/x509util/certificate.go +++ b/x509util/certificate.go @@ -17,6 +17,7 @@ import ( type Certificate struct { Version int `json:"version"` Subject Subject `json:"subject"` + RawSubject []byte `json:"rawSubject"` Issuer Issuer `json:"issuer"` SerialNumber SerialNumber `json:"serialNumber"` DNSNames MultiString `json:"dnsNames"` @@ -128,6 +129,7 @@ func (c *Certificate) GetCertificate() *x509.Certificate { // Unparsed data cert.PublicKey = c.PublicKey cert.PublicKeyAlgorithm = c.PublicKeyAlgorithm + cert.RawSubject = c.RawSubject // Subject c.Subject.Set(cert) diff --git a/x509util/certificate_request.go b/x509util/certificate_request.go index 24861e04..b2ade54a 100644 --- a/x509util/certificate_request.go +++ b/x509util/certificate_request.go @@ -45,6 +45,7 @@ type certificateRequest struct { type CertificateRequest struct { Version int `json:"version"` Subject Subject `json:"subject"` + RawSubject []byte `json:"rawSubject"` DNSNames MultiString `json:"dnsNames"` EmailAddresses MultiString `json:"emailAddresses"` IPAddresses MultiIP `json:"ipAddresses"` @@ -115,6 +116,7 @@ func NewCertificateRequestFromX509(cr *x509.CertificateRequest) *CertificateRequ return &CertificateRequest{ Version: cr.Version, Subject: newSubject(cr.Subject), + RawSubject: cr.RawSubject, DNSNames: cr.DNSNames, EmailAddresses: cr.EmailAddresses, IPAddresses: cr.IPAddresses, @@ -134,6 +136,7 @@ func (c *CertificateRequest) GetCertificateRequest() (*x509.CertificateRequest, cert := c.GetCertificate().GetCertificate() asn1Data, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{ Subject: cert.Subject, + RawSubject: cert.RawSubject, DNSNames: cert.DNSNames, IPAddresses: cert.IPAddresses, EmailAddresses: cert.EmailAddresses, diff --git a/x509util/certificate_test.go b/x509util/certificate_test.go index 7c04eaac..fa4963c9 100644 --- a/x509util/certificate_test.go +++ b/x509util/certificate_test.go @@ -22,6 +22,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.step.sm/crypto/pemutil" ) func createCertificateRequest(t *testing.T, commonName string, sans []string) (*x509.CertificateRequest, crypto.Signer) { @@ -49,6 +50,21 @@ func createCertificateRequest(t *testing.T, commonName string, sans []string) (* return cr, priv } +func readCertificateRequest(t *testing.T, filename, keyFilename string) (*x509.CertificateRequest, crypto.Signer) { + t.Helper() + + cr, err := pemutil.ReadCertificateRequest(filename) + require.NoError(t, err) + + key, err := pemutil.Read(keyFilename) + require.NoError(t, err) + + signer, ok := key.(crypto.Signer) + require.True(t, ok) + + return cr, signer +} + func createIssuerCertificate(t *testing.T, commonName string) (*x509.Certificate, crypto.Signer) { t.Helper() now := time.Now() @@ -135,6 +151,8 @@ func TestNewCertificate(t *testing.T) { return ipNet } + rawSubjectCR, rawSubjectKey := readCertificateRequest(t, "testdata/rawSubject.csr", "testdata/rawSubject.key") + type args struct { cr *x509.CertificateRequest opts []Option @@ -283,6 +301,38 @@ func TestNewCertificate(t *testing.T) { PublicKey: priv.Public(), PublicKeyAlgorithm: x509.Ed25519, }, false}, + {"okRawSubject", args{rawSubjectCR, []Option{WithTemplateFile("./testdata/rawSubject.tpl", TemplateData{ + SANsKey: []SubjectAlternativeName{ + {Type: "dns", Value: "foo.com"}, + }, + CertificateRequestKey: NewCertificateRequestFromX509(rawSubjectCR), + })}}, &Certificate{ + Subject: Subject{}, + RawSubject: []byte{ + 0x30, 0x68, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, + 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, + 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, + 0x0c, 0x0a, 0x43, 0x61, 0x6c, 0x69, 0x66, 0x6f, + 0x72, 0x6e, 0x69, 0x61, 0x31, 0x16, 0x30, 0x14, + 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x0d, 0x53, + 0x61, 0x6e, 0x20, 0x46, 0x72, 0x61, 0x6e, 0x63, + 0x69, 0x73, 0x63, 0x6f, 0x31, 0x1d, 0x30, 0x1b, + 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x14, 0x53, + 0x6d, 0x61, 0x6c, 0x6c, 0x73, 0x74, 0x65, 0x70, + 0x20, 0x4c, 0x61, 0x62, 0x73, 0x2c, 0x20, 0x49, + 0x6e, 0x63, 0x2e, 0x31, 0x0d, 0x30, 0x0b, 0x06, + 0x03, 0x55, 0x04, 0x03, 0x0c, 0x04, 0x54, 0x65, + 0x73, 0x74, + }, + SANs: []SubjectAlternativeName{{Type: DNSType, Value: "foo.com"}}, + KeyUsage: KeyUsage(x509.KeyUsageDigitalSignature), + ExtKeyUsage: ExtKeyUsage([]x509.ExtKeyUsage{ + x509.ExtKeyUsageServerAuth, + x509.ExtKeyUsageClientAuth, + }), + PublicKey: rawSubjectKey.Public(), + PublicKeyAlgorithm: x509.ECDSA, + }, 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/testdata/rawSubject.csr b/x509util/testdata/rawSubject.csr new file mode 100644 index 00000000..233fcf84 --- /dev/null +++ b/x509util/testdata/rawSubject.csr @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBIjCBygIBADBoMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEW +MBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEdMBsGA1UECgwUU21hbGxzdGVwIExhYnMs +IEluYy4xDTALBgNVBAMMBFRlc3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATG +NLZtzv5dcl2Nz66XBV+tq5jKCH9WGPlsuBEq48NNUb1LzecjaVTZsEwe+ZpQbnzk +wTdYXFI68HbovxWc9fDxoAAwCgYIKoZIzj0EAwIDRwAwRAIgODOqVqdeZzSwZOFB +VZuSLoMVvCVoiFng8/2hXfJWJi4CIBNHEt9batonI4Z6Z0kij/qNTeTTWFd5yawX +2DP2+6EP +-----END CERTIFICATE REQUEST----- \ No newline at end of file diff --git a/x509util/testdata/rawSubject.key b/x509util/testdata/rawSubject.key new file mode 100644 index 00000000..768c3067 --- /dev/null +++ b/x509util/testdata/rawSubject.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIK9FBgwqr6Zjsbf2uPmvnDz341AhiJbPLTuq8+/BLbd/oAoGCCqGSM49 +AwEHoUQDQgAExjS2bc7+XXJdjc+ulwVfrauYygh/Vhj5bLgRKuPDTVG9S83nI2lU +2bBMHvmaUG585ME3WFxSOvB26L8VnPXw8Q== +-----END EC PRIVATE KEY----- diff --git a/x509util/testdata/rawSubject.tpl b/x509util/testdata/rawSubject.tpl new file mode 100644 index 00000000..d87f5b2c --- /dev/null +++ b/x509util/testdata/rawSubject.tpl @@ -0,0 +1,10 @@ +{ + "rawSubject": {{ toJson .Insecure.CR.RawSubject }}, + "sans": {{ toJson .SANs }}, +{{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }} + "keyUsage": ["keyEncipherment", "digitalSignature"], +{{- else }} + "keyUsage": ["digitalSignature"], +{{- end }} + "extKeyUsage": ["serverAuth", "clientAuth"] +} \ No newline at end of file