diff --git a/x509util/algorithms_test.go b/x509util/algorithms_test.go new file mode 100644 index 00000000..a735a0c4 --- /dev/null +++ b/x509util/algorithms_test.go @@ -0,0 +1,76 @@ +package x509util + +import ( + "crypto/x509" + "reflect" + "testing" +) + +func TestSignatureAlgorithm_Set(t *testing.T) { + type args struct { + c *x509.Certificate + } + tests := []struct { + name string + s SignatureAlgorithm + args args + want *x509.Certificate + }{ + {"ok", SignatureAlgorithm(x509.ECDSAWithSHA256), args{&x509.Certificate{}}, &x509.Certificate{SignatureAlgorithm: x509.ECDSAWithSHA256}}, + {"ok", SignatureAlgorithm(x509.PureEd25519), args{&x509.Certificate{}}, &x509.Certificate{SignatureAlgorithm: x509.PureEd25519}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.s.Set(tt.args.c) + if !reflect.DeepEqual(tt.args.c, tt.want) { + t.Errorf("SignatureAlgorithm.Set() = %v, want %v", tt.args.c, tt.want) + } + }) + } +} + +func TestSignatureAlgorithm_UnmarshalJSON(t *testing.T) { + type args struct { + data []byte + } + tests := []struct { + name string + args args + want SignatureAlgorithm + wantErr bool + }{ + {"MD2_RSA", args{[]byte(`"MD2-RSA"`)}, SignatureAlgorithm(x509.MD2WithRSA), false}, + {"MD5-RSA", args{[]byte(`"MD5-RSA"`)}, SignatureAlgorithm(x509.MD5WithRSA), false}, + {"SHA1-RSA", args{[]byte(`"SHA1-RSA"`)}, SignatureAlgorithm(x509.SHA1WithRSA), false}, + {"SHA256-RSA", args{[]byte(`"SHA256-RSA"`)}, SignatureAlgorithm(x509.SHA256WithRSA), false}, + {"SHA384-RSA", args{[]byte(`"SHA384-RSA"`)}, SignatureAlgorithm(x509.SHA384WithRSA), false}, + {"SHA512-RSA", args{[]byte(`"SHA512-RSA"`)}, SignatureAlgorithm(x509.SHA512WithRSA), false}, + {"SHA256-RSAPSS", args{[]byte(`"SHA256-RSAPSS"`)}, SignatureAlgorithm(x509.SHA256WithRSAPSS), false}, + {"SHA384-RSAPSS", args{[]byte(`"SHA384-RSAPSS"`)}, SignatureAlgorithm(x509.SHA384WithRSAPSS), false}, + {"SHA512-RSAPSS", args{[]byte(`"SHA512-RSAPSS"`)}, SignatureAlgorithm(x509.SHA512WithRSAPSS), false}, + {"DSA-SHA1", args{[]byte(`"DSA-SHA1"`)}, SignatureAlgorithm(x509.DSAWithSHA1), false}, + {"DSA-SHA256", args{[]byte(`"DSA-SHA256"`)}, SignatureAlgorithm(x509.DSAWithSHA256), false}, + {"ECDSA-SHA1", args{[]byte(`"ECDSA-SHA1"`)}, SignatureAlgorithm(x509.ECDSAWithSHA1), false}, + {"ECDSA-SHA256", args{[]byte(`"ECDSA-SHA256"`)}, SignatureAlgorithm(x509.ECDSAWithSHA256), false}, + {"ECDSA-SHA384", args{[]byte(`"ECDSA-SHA384"`)}, SignatureAlgorithm(x509.ECDSAWithSHA384), false}, + {"ECDSA-SHA512", args{[]byte(`"ECDSA-SHA512"`)}, SignatureAlgorithm(x509.ECDSAWithSHA512), false}, + {"Ed25519", args{[]byte(`"Ed25519"`)}, SignatureAlgorithm(x509.PureEd25519), false}, + {"lowercase", args{[]byte(`"ecdsa-sha256"`)}, SignatureAlgorithm(x509.ECDSAWithSHA256), false}, + {"empty", args{[]byte(`""`)}, SignatureAlgorithm(0), true}, + {"unknown", args{[]byte(`"unknown"`)}, SignatureAlgorithm(0), true}, + {"null", args{[]byte(`null`)}, SignatureAlgorithm(0), true}, + {"number", args{[]byte(`0`)}, SignatureAlgorithm(0), true}, + {"object", args{[]byte(`{}`)}, SignatureAlgorithm(0), true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var got SignatureAlgorithm + if err := got.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr { + t.Errorf("SignatureAlgorithm.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("SignatureAlgorithm.UnmarshalJSON() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/x509util/certificate.go b/x509util/certificate.go index 17fcecea..2668c256 100644 --- a/x509util/certificate.go +++ b/x509util/certificate.go @@ -93,6 +93,7 @@ func (c *Certificate) GetCertificate() *x509.Certificate { c.AuthorityKeyID.Set(cert) c.OCSPServer.Set(cert) c.IssuingCertificateURL.Set(cert) + c.CRLDistributionPoints.Set(cert) c.PolicyIdentifiers.Set(cert) if c.BasicConstraints != nil { c.BasicConstraints.Set(cert) diff --git a/x509util/certificate_request.go b/x509util/certificate_request.go index 54238dd3..c14338c9 100644 --- a/x509util/certificate_request.go +++ b/x509util/certificate_request.go @@ -20,10 +20,6 @@ type CertificateRequest struct { } func newCertificateRequest(cr *x509.CertificateRequest) *CertificateRequest { - extensions := make([]Extension, len(cr.Extensions)) - for i, e := range cr.Extensions { - extensions[i] = newExtension(e) - } return &CertificateRequest{ Version: cr.Version, Subject: newSubject(cr.Subject), @@ -31,7 +27,7 @@ func newCertificateRequest(cr *x509.CertificateRequest) *CertificateRequest { EmailAddresses: cr.EmailAddresses, IPAddresses: cr.IPAddresses, URIs: cr.URIs, - Extensions: extensions, + Extensions: newExtensions(cr.Extensions), PublicKey: cr.PublicKey, PublicKeyAlgorithm: cr.PublicKeyAlgorithm, Signature: cr.Signature, diff --git a/x509util/certificate_request_test.go b/x509util/certificate_request_test.go new file mode 100644 index 00000000..f09faabb --- /dev/null +++ b/x509util/certificate_request_test.go @@ -0,0 +1,212 @@ +package x509util + +import ( + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "net" + "net/url" + "reflect" + "testing" +) + +func Test_newCertificateRequest(t *testing.T) { + + type args struct { + cr *x509.CertificateRequest + } + tests := []struct { + name string + args args + want *CertificateRequest + }{ + {"ok", args{&x509.CertificateRequest{}}, &CertificateRequest{}}, + {"complex", args{&x509.CertificateRequest{ + Extensions: []pkix.Extension{{Id: []int{1, 2, 3}, Critical: true, Value: []byte{3, 2, 1}}}, + Subject: pkix.Name{Province: []string{"CA"}, CommonName: "commonName"}, + DNSNames: []string{"foo"}, + PublicKey: []byte("publicKey"), + }}, &CertificateRequest{ + Extensions: []Extension{{ID: []int{1, 2, 3}, Critical: true, Value: []byte{3, 2, 1}}}, + Subject: Subject{Province: []string{"CA"}, CommonName: "commonName"}, + DNSNames: []string{"foo"}, + PublicKey: []byte("publicKey"), + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := newCertificateRequest(tt.args.cr); !reflect.DeepEqual(got, tt.want) { + t.Errorf("newCertificateRequest() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCertificateRequest_GetCertificate(t *testing.T) { + type fields struct { + Version int + Subject Subject + DNSNames MultiString + EmailAddresses MultiString + IPAddresses MultiIP + URIs MultiURL + Extensions []Extension + PublicKey interface{} + PublicKeyAlgorithm x509.PublicKeyAlgorithm + Signature []byte + SignatureAlgorithm x509.SignatureAlgorithm + } + tests := []struct { + name string + fields fields + want *Certificate + }{ + {"ok", + fields{ + Version: 2, + Subject: Subject{CommonName: "foo"}, + DNSNames: []string{"foo"}, + EmailAddresses: []string{"foo@bar.com"}, + IPAddresses: []net.IP{net.ParseIP("::1")}, + URIs: []*url.URL{{Scheme: "https", Host: "foo.bar"}}, + Extensions: []Extension{{ID: []int{1, 2, 3}, Critical: true, Value: []byte{3, 2, 1}}}, + PublicKey: []byte("publicKey"), + PublicKeyAlgorithm: x509.Ed25519, + Signature: []byte("signature"), + SignatureAlgorithm: x509.PureEd25519, + }, + &Certificate{ + Subject: Subject{CommonName: "foo"}, + DNSNames: []string{"foo"}, + EmailAddresses: []string{"foo@bar.com"}, + IPAddresses: []net.IP{net.ParseIP("::1")}, + URIs: []*url.URL{{Scheme: "https", Host: "foo.bar"}}, + Extensions: []Extension{{ID: []int{1, 2, 3}, Critical: true, Value: []byte{3, 2, 1}}}, + PublicKey: []byte("publicKey"), + PublicKeyAlgorithm: x509.Ed25519, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &CertificateRequest{ + Version: tt.fields.Version, + Subject: tt.fields.Subject, + DNSNames: tt.fields.DNSNames, + EmailAddresses: tt.fields.EmailAddresses, + IPAddresses: tt.fields.IPAddresses, + URIs: tt.fields.URIs, + Extensions: tt.fields.Extensions, + PublicKey: tt.fields.PublicKey, + PublicKeyAlgorithm: tt.fields.PublicKeyAlgorithm, + Signature: tt.fields.Signature, + SignatureAlgorithm: tt.fields.SignatureAlgorithm, + } + if got := c.GetCertificate(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("CertificateRequest.GetCertificate() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCertificateRequest_GetLeafCertificate(t *testing.T) { + type fields struct { + Version int + Subject Subject + DNSNames MultiString + EmailAddresses MultiString + IPAddresses MultiIP + URIs MultiURL + Extensions []Extension + PublicKey interface{} + PublicKeyAlgorithm x509.PublicKeyAlgorithm + Signature []byte + SignatureAlgorithm x509.SignatureAlgorithm + } + tests := []struct { + name string + fields fields + want *Certificate + }{ + {"ok", + fields{ + Version: 2, + Subject: Subject{CommonName: "foo"}, + DNSNames: []string{"foo"}, + EmailAddresses: []string{"foo@bar.com"}, + IPAddresses: []net.IP{net.ParseIP("::1")}, + URIs: []*url.URL{{Scheme: "https", Host: "foo.bar"}}, + Extensions: []Extension{{ID: []int{1, 2, 3}, Critical: true, Value: []byte{3, 2, 1}}}, + PublicKey: []byte("publicKey"), + PublicKeyAlgorithm: x509.Ed25519, + Signature: []byte("signature"), + SignatureAlgorithm: x509.PureEd25519, + }, + &Certificate{ + Subject: Subject{CommonName: "foo"}, + DNSNames: []string{"foo"}, + EmailAddresses: []string{"foo@bar.com"}, + IPAddresses: []net.IP{net.ParseIP("::1")}, + URIs: []*url.URL{{Scheme: "https", Host: "foo.bar"}}, + Extensions: []Extension{{ID: []int{1, 2, 3}, Critical: true, Value: []byte{3, 2, 1}}}, + KeyUsage: KeyUsage(x509.KeyUsageDigitalSignature), + ExtKeyUsage: ExtKeyUsage([]x509.ExtKeyUsage{ + x509.ExtKeyUsageServerAuth, + x509.ExtKeyUsageClientAuth, + }), + PublicKey: []byte("publicKey"), + PublicKeyAlgorithm: x509.Ed25519, + }, + }, + {"rsa", + fields{ + Version: 2, + Subject: Subject{CommonName: "foo"}, + DNSNames: []string{"foo"}, + EmailAddresses: []string{"foo@bar.com"}, + IPAddresses: []net.IP{net.ParseIP("::1")}, + URIs: []*url.URL{{Scheme: "https", Host: "foo.bar"}}, + Extensions: []Extension{{ID: []int{1, 2, 3}, Critical: true, Value: []byte{3, 2, 1}}}, + PublicKey: &rsa.PublicKey{}, + PublicKeyAlgorithm: x509.RSA, + Signature: []byte("signature"), + SignatureAlgorithm: x509.SHA256WithRSA, + }, + &Certificate{ + Subject: Subject{CommonName: "foo"}, + DNSNames: []string{"foo"}, + EmailAddresses: []string{"foo@bar.com"}, + IPAddresses: []net.IP{net.ParseIP("::1")}, + URIs: []*url.URL{{Scheme: "https", Host: "foo.bar"}}, + Extensions: []Extension{{ID: []int{1, 2, 3}, Critical: true, Value: []byte{3, 2, 1}}}, + KeyUsage: KeyUsage(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment), + ExtKeyUsage: ExtKeyUsage([]x509.ExtKeyUsage{ + x509.ExtKeyUsageServerAuth, + x509.ExtKeyUsageClientAuth, + }), + PublicKey: &rsa.PublicKey{}, + PublicKeyAlgorithm: x509.RSA, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &CertificateRequest{ + Version: tt.fields.Version, + Subject: tt.fields.Subject, + DNSNames: tt.fields.DNSNames, + EmailAddresses: tt.fields.EmailAddresses, + IPAddresses: tt.fields.IPAddresses, + URIs: tt.fields.URIs, + Extensions: tt.fields.Extensions, + PublicKey: tt.fields.PublicKey, + PublicKeyAlgorithm: tt.fields.PublicKeyAlgorithm, + Signature: tt.fields.Signature, + SignatureAlgorithm: tt.fields.SignatureAlgorithm, + } + if got := c.GetLeafCertificate(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("CertificateRequest.GetLeafCertificate() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/x509util/certificate_test.go b/x509util/certificate_test.go new file mode 100644 index 00000000..ea08de28 --- /dev/null +++ b/x509util/certificate_test.go @@ -0,0 +1,224 @@ +package x509util + +import ( + "crypto" + "crypto/ed25519" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "math/big" + "net" + "net/url" + "reflect" + "testing" +) + +func createCertificateRequest(t *testing.T, commonName string, sans []string) (*x509.CertificateRequest, crypto.Signer) { + dnsNames, ips, emails, uris := SplitSANs(sans) + t.Helper() + _, priv, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + asn1Data, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{ + Subject: pkix.Name{CommonName: commonName}, + DNSNames: dnsNames, + IPAddresses: ips, + EmailAddresses: emails, + URIs: uris, + SignatureAlgorithm: x509.PureEd25519, + }, priv) + if err != nil { + t.Fatal(err) + } + cr, err := x509.ParseCertificateRequest(asn1Data) + if err != nil { + t.Fatal(err) + } + return cr, priv +} + +func TestNewCertificate(t *testing.T) { + cr, priv := createCertificateRequest(t, "commonName", []string{"foo.com"}) + crBadSignateure, _ := createCertificateRequest(t, "fail", []string{"foo.com"}) + crBadSignateure.PublicKey = priv.Public() + + type args struct { + cr *x509.CertificateRequest + opts []Option + } + tests := []struct { + name string + args args + want *Certificate + wantErr bool + }{ + {"okSimple", args{cr, nil}, &Certificate{ + Subject: Subject{CommonName: "commonName"}, + DNSNames: []string{"foo.com"}, + KeyUsage: KeyUsage(x509.KeyUsageDigitalSignature), + ExtKeyUsage: ExtKeyUsage([]x509.ExtKeyUsage{ + x509.ExtKeyUsageServerAuth, + x509.ExtKeyUsageClientAuth, + }), + Extensions: newExtensions(cr.Extensions), + PublicKey: priv.Public(), + PublicKeyAlgorithm: x509.Ed25519, + }, false}, + {"okDefaultTemplate", args{cr, []Option{WithTemplate(DefaultLeafTemplate, CreateTemplateData("commonName", []string{"foo.com"}))}}, &Certificate{ + Subject: Subject{CommonName: "commonName"}, + SANs: []SubjectAlternativeName{{Type: DNSType, Value: "foo.com"}}, + KeyUsage: KeyUsage(x509.KeyUsageDigitalSignature), + 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}, + {"badJson", args{cr, []Option{WithTemplate(`"this is not a json object"`, CreateTemplateData("commonName", []string{"foo.com"}))}}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewCertificate(tt.args.cr, tt.args.opts...) + if (err != nil) != tt.wantErr { + t.Errorf("NewCertificate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewCertificate() = \n%v, want \n%v", got, tt.want) + } + }) + } +} + +func TestCertificate_GetCertificate(t *testing.T) { + type fields struct { + Version int + Subject Subject + Issuer Issuer + SerialNumber SerialNumber + DNSNames MultiString + EmailAddresses MultiString + IPAddresses MultiIP + URIs MultiURL + SANs []SubjectAlternativeName + Extensions []Extension + KeyUsage KeyUsage + ExtKeyUsage ExtKeyUsage + SubjectKeyID SubjectKeyID + AuthorityKeyID AuthorityKeyID + OCSPServer OCSPServer + IssuingCertificateURL IssuingCertificateURL + CRLDistributionPoints CRLDistributionPoints + PolicyIdentifiers PolicyIdentifiers + BasicConstraints *BasicConstraints + NameConstaints *NameConstraints + SignatureAlgorithm SignatureAlgorithm + PublicKeyAlgorithm x509.PublicKeyAlgorithm + PublicKey interface{} + } + tests := []struct { + name string + fields fields + want *x509.Certificate + }{ + {"ok", fields{ + Version: 3, + Subject: Subject{CommonName: "commonName", Organization: []string{"smallstep"}}, + Issuer: Issuer{CommonName: "issuer", Organization: []string{"smallstep"}}, + SerialNumber: SerialNumber{big.NewInt(123)}, + DNSNames: []string{"foo.bar"}, + EmailAddresses: []string{"root@foo.com"}, + IPAddresses: []net.IP{net.ParseIP("::1")}, + URIs: []*url.URL{{Scheme: "mailto", Opaque: "root@foo.com"}}, + SANs: []SubjectAlternativeName{ + {Type: DNSType, Value: "www.foo.bar"}, + {Type: IPType, Value: "127.0.0.1"}, + {Type: EmailType, Value: "admin@foo.com"}, + {Type: URIType, Value: "mailto:admin@foo.com"}, + }, + Extensions: []Extension{{ID: []int{1, 2, 3, 4}, Critical: true, Value: []byte("custom extension")}}, + KeyUsage: KeyUsage(x509.KeyUsageDigitalSignature), + ExtKeyUsage: ExtKeyUsage([]x509.ExtKeyUsage{ + x509.ExtKeyUsageServerAuth, + x509.ExtKeyUsageClientAuth, + }), + SubjectKeyID: []byte("subject-key-id"), + AuthorityKeyID: []byte("authority-key-id"), + OCSPServer: []string{"https://oscp.server"}, + IssuingCertificateURL: []string{"https://ca.com"}, + CRLDistributionPoints: []string{"https://ca.com/crl"}, + PolicyIdentifiers: []asn1.ObjectIdentifier{[]int{1, 2, 3, 4}}, + BasicConstraints: &BasicConstraints{IsCA: true, MaxPathLen: 0}, + NameConstaints: &NameConstraints{PermittedDNSDomains: []string{"foo.bar"}}, + SignatureAlgorithm: SignatureAlgorithm(x509.PureEd25519), + PublicKeyAlgorithm: x509.Ed25519, + PublicKey: ed25519.PublicKey("public key"), + }, &x509.Certificate{ + Version: 0, + Subject: pkix.Name{CommonName: "commonName", Organization: []string{"smallstep"}}, + Issuer: pkix.Name{}, + SerialNumber: big.NewInt(123), + DNSNames: []string{"foo.bar", "www.foo.bar"}, + EmailAddresses: []string{"root@foo.com", "admin@foo.com"}, + IPAddresses: []net.IP{net.ParseIP("::1"), net.ParseIP("127.0.0.1")}, + URIs: []*url.URL{{Scheme: "mailto", Opaque: "root@foo.com"}, {Scheme: "mailto", Opaque: "admin@foo.com"}}, + ExtraExtensions: []pkix.Extension{{Id: []int{1, 2, 3, 4}, Critical: true, Value: []byte("custom extension")}}, + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{ + x509.ExtKeyUsageServerAuth, + x509.ExtKeyUsageClientAuth, + }, + SubjectKeyId: []byte("subject-key-id"), + AuthorityKeyId: []byte("authority-key-id"), + OCSPServer: []string{"https://oscp.server"}, + IssuingCertificateURL: []string{"https://ca.com"}, + CRLDistributionPoints: []string{"https://ca.com/crl"}, + PolicyIdentifiers: []asn1.ObjectIdentifier{[]int{1, 2, 3, 4}}, + IsCA: true, + MaxPathLen: 0, + MaxPathLenZero: true, + BasicConstraintsValid: true, + PermittedDNSDomains: []string{"foo.bar"}, + SignatureAlgorithm: x509.PureEd25519, + PublicKeyAlgorithm: x509.Ed25519, + PublicKey: ed25519.PublicKey("public key"), + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Certificate{ + Version: tt.fields.Version, + Subject: tt.fields.Subject, + Issuer: tt.fields.Issuer, + SerialNumber: tt.fields.SerialNumber, + DNSNames: tt.fields.DNSNames, + EmailAddresses: tt.fields.EmailAddresses, + IPAddresses: tt.fields.IPAddresses, + URIs: tt.fields.URIs, + SANs: tt.fields.SANs, + Extensions: tt.fields.Extensions, + KeyUsage: tt.fields.KeyUsage, + ExtKeyUsage: tt.fields.ExtKeyUsage, + SubjectKeyID: tt.fields.SubjectKeyID, + AuthorityKeyID: tt.fields.AuthorityKeyID, + OCSPServer: tt.fields.OCSPServer, + IssuingCertificateURL: tt.fields.IssuingCertificateURL, + CRLDistributionPoints: tt.fields.CRLDistributionPoints, + PolicyIdentifiers: tt.fields.PolicyIdentifiers, + BasicConstraints: tt.fields.BasicConstraints, + NameConstaints: tt.fields.NameConstaints, + SignatureAlgorithm: tt.fields.SignatureAlgorithm, + PublicKeyAlgorithm: tt.fields.PublicKeyAlgorithm, + PublicKey: tt.fields.PublicKey, + } + if got := c.GetCertificate(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Certificate.GetCertificate() = \n%+v, want \n%+v", got, tt.want) + } + }) + } +} diff --git a/x509util/extensions.go b/x509util/extensions.go index 4f3cb13b..ab6ee86b 100644 --- a/x509util/extensions.go +++ b/x509util/extensions.go @@ -73,6 +73,19 @@ func newExtension(e pkix.Extension) Extension { } } +// newExtensions creates a slice of Extension from a slice of pkix.Exntesion. +func newExtensions(extensions []pkix.Extension) []Extension { + if extensions == nil { + return nil + } + ret := make([]Extension, len(extensions)) + for i, e := range extensions { + ret[i] = newExtension(e) + } + return ret + +} + // Set adds the extension to the given X509 certificate. func (e Extension) Set(c *x509.Certificate) { c.ExtraExtensions = append(c.ExtraExtensions, pkix.Extension{ @@ -322,7 +335,7 @@ func (b BasicConstraints) Set(c *x509.Certificate) { c.BasicConstraintsValid = true switch { case b.MaxPathLen == 0: - c.MaxPathLen = b.MaxPathLen + c.MaxPathLen = 0 c.MaxPathLenZero = true case b.MaxPathLen < 0: c.MaxPathLen = -1 diff --git a/x509util/templates.go b/x509util/templates.go index 84e48e0c..12d516e9 100644 --- a/x509util/templates.go +++ b/x509util/templates.go @@ -13,10 +13,14 @@ const ( CertificateRequestKey = "CR" ) +// TemplateError represents an error in a template produced by the fail +// function. type TemplateError struct { Message string } +// Error implements the error interface and returns the error string when a +// template executes the `fail "message"` function. func (e *TemplateError) Error() string { return e.Message } diff --git a/x509util/templates_test.go b/x509util/templates_test.go new file mode 100644 index 00000000..c5f66a97 --- /dev/null +++ b/x509util/templates_test.go @@ -0,0 +1,260 @@ +package x509util + +import ( + "crypto/x509" + "reflect" + "testing" +) + +func TestTemplateError_Error(t *testing.T) { + type fields struct { + Message string + } + tests := []struct { + name string + fields fields + want string + }{ + {"ok", fields{"an error"}, "an error"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := &TemplateError{ + Message: tt.fields.Message, + } + if got := e.Error(); got != tt.want { + t.Errorf("TemplateError.Error() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNewTemplateData(t *testing.T) { + tests := []struct { + name string + want TemplateData + }{ + {"ok", TemplateData{}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NewTemplateData(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewTemplateData() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCreateTemplateData(t *testing.T) { + type args struct { + commonName string + sans []string + } + tests := []struct { + name string + args args + want TemplateData + }{ + {"ok", args{"jane.doe.com", []string{"jane.doe.com", "jane@doe.com", "1.1.1.1", "mailto:jane@doe.com"}}, TemplateData{ + SubjectKey: Subject{CommonName: "jane.doe.com"}, + SANsKey: []SubjectAlternativeName{ + {Type: DNSType, Value: "jane.doe.com"}, + {Type: IPType, Value: "1.1.1.1"}, + {Type: EmailType, Value: "jane@doe.com"}, + {Type: URIType, Value: "mailto:jane@doe.com"}, + }, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := CreateTemplateData(tt.args.commonName, tt.args.sans); !reflect.DeepEqual(got, tt.want) { + t.Errorf("CreateTemplateData() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestTemplateData_SetInsecure(t *testing.T) { + type args struct { + key string + v interface{} + } + tests := []struct { + name string + td TemplateData + args args + want TemplateData + }{ + {"empty", TemplateData{}, args{"foo", "bar"}, TemplateData{InsecureKey: TemplateData{"foo": "bar"}}}, + {"overwrite", TemplateData{InsecureKey: TemplateData{"foo": "bar"}}, args{"foo", "zar"}, TemplateData{InsecureKey: TemplateData{"foo": "zar"}}}, + {"existing", TemplateData{InsecureKey: TemplateData{"foo": "bar"}}, args{"bar", "foo"}, TemplateData{InsecureKey: TemplateData{"foo": "bar", "bar": "foo"}}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.td.SetInsecure(tt.args.key, tt.args.v) + if !reflect.DeepEqual(tt.td, tt.want) { + t.Errorf("TemplateData.SetInsecure() = %v, want %v", tt.td, tt.want) + } + }) + } +} + +func TestTemplateData_SetSubject(t *testing.T) { + type args struct { + v Subject + } + tests := []struct { + name string + td TemplateData + args args + want TemplateData + }{ + {"ok", TemplateData{}, args{Subject{CommonName: "foo"}}, TemplateData{SubjectKey: Subject{CommonName: "foo"}}}, + {"overwrite", TemplateData{SubjectKey: Subject{CommonName: "foo"}}, args{Subject{Province: []string{"CA"}}}, TemplateData{SubjectKey: Subject{Province: []string{"CA"}}}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.td.SetSubject(tt.args.v) + if !reflect.DeepEqual(tt.td, tt.want) { + t.Errorf("TemplateData.SetSubject() = %v, want %v", tt.td, tt.want) + } + }) + } +} + +func TestTemplateData_SetCommonName(t *testing.T) { + type args struct { + cn string + } + tests := []struct { + name string + td TemplateData + args args + want TemplateData + }{ + {"ok", TemplateData{}, args{"commonName"}, TemplateData{SubjectKey: Subject{CommonName: "commonName"}}}, + {"overwrite", TemplateData{SubjectKey: Subject{CommonName: "foo", Province: []string{"CA"}}}, args{"commonName"}, TemplateData{SubjectKey: Subject{CommonName: "commonName", Province: []string{"CA"}}}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.td.SetCommonName(tt.args.cn) + if !reflect.DeepEqual(tt.td, tt.want) { + t.Errorf("TemplateData.SetCommonName() = %v, want %v", tt.td, tt.want) + } + }) + } +} + +func TestTemplateData_SetSANs(t *testing.T) { + type args struct { + sans []string + } + tests := []struct { + name string + td TemplateData + args args + want TemplateData + }{ + {"ok", TemplateData{}, args{[]string{"jane.doe.com", "jane@doe.com", "1.1.1.1", "mailto:jane@doe.com"}}, TemplateData{ + SANsKey: []SubjectAlternativeName{ + {Type: DNSType, Value: "jane.doe.com"}, + {Type: IPType, Value: "1.1.1.1"}, + {Type: EmailType, Value: "jane@doe.com"}, + {Type: URIType, Value: "mailto:jane@doe.com"}, + }}, + }, + {"overwrite", TemplateData{}, args{[]string{"jane.doe.com"}}, TemplateData{ + SANsKey: []SubjectAlternativeName{ + {Type: DNSType, Value: "jane.doe.com"}, + }}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.td.SetSANs(tt.args.sans) + if !reflect.DeepEqual(tt.td, tt.want) { + t.Errorf("TemplateData.SetSANs() = %v, want %v", tt.td, tt.want) + } + }) + } +} + +func TestTemplateData_SetToken(t *testing.T) { + type args struct { + v interface{} + } + tests := []struct { + name string + td TemplateData + args args + want TemplateData + }{ + {"ok", TemplateData{}, args{"token"}, TemplateData{TokenKey: "token"}}, + {"overwrite", TemplateData{TokenKey: "foo"}, args{"token"}, TemplateData{TokenKey: "token"}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.td.SetToken(tt.args.v) + if !reflect.DeepEqual(tt.td, tt.want) { + t.Errorf("TemplateData.SetToken() = %v, want %v", tt.td, tt.want) + } + }) + } +} + +func TestTemplateData_SetUserData(t *testing.T) { + type args struct { + v interface{} + } + tests := []struct { + name string + td TemplateData + args args + want TemplateData + }{ + {"ok", TemplateData{}, args{"userData"}, TemplateData{InsecureKey: TemplateData{UserKey: "userData"}}}, + {"overwrite", TemplateData{InsecureKey: TemplateData{UserKey: "foo"}}, args{"userData"}, TemplateData{InsecureKey: TemplateData{UserKey: "userData"}}}, + {"existing", TemplateData{InsecureKey: TemplateData{"foo": "bar"}}, args{"userData"}, TemplateData{InsecureKey: TemplateData{"foo": "bar", UserKey: "userData"}}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.td.SetUserData(tt.args.v) + if !reflect.DeepEqual(tt.td, tt.want) { + t.Errorf("TemplateData.SetUserData() = %v, want %v", tt.td, tt.want) + } + }) + } +} + +func TestTemplateData_SetCertificateRequest(t *testing.T) { + cr := &x509.CertificateRequest{ + DNSNames: []string{"foo", "bar"}, + } + cr1 := &CertificateRequest{ + DNSNames: []string{"foo", "bar"}, + } + cr2 := &CertificateRequest{ + EmailAddresses: []string{"foo@bar.com"}, + } + type args struct { + cr *x509.CertificateRequest + } + tests := []struct { + name string + td TemplateData + args args + want TemplateData + }{ + {"ok", TemplateData{}, args{cr}, TemplateData{InsecureKey: TemplateData{CertificateRequestKey: cr1}}}, + {"overwrite", TemplateData{InsecureKey: TemplateData{CertificateRequestKey: cr2}}, args{cr}, TemplateData{InsecureKey: TemplateData{CertificateRequestKey: cr1}}}, + {"existing", TemplateData{InsecureKey: TemplateData{"foo": "bar"}}, args{cr}, TemplateData{InsecureKey: TemplateData{"foo": "bar", CertificateRequestKey: cr1}}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.td.SetCertificateRequest(tt.args.cr) + if !reflect.DeepEqual(tt.td, tt.want) { + t.Errorf("TemplateData.SetCertificateRequest() = %v, want %v", tt.td, tt.want) + } + }) + } +}