From dbb48ecf8d2e8926912b76cba6c5860e07483b34 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Thu, 18 Mar 2021 18:01:38 -0700 Subject: [PATCH] Add tests for stepcas. --- cas/stepcas/stepcas_test.go | 668 +++++++++++++++++++++++++++++++++ cas/stepcas/x5c_issuer.go | 8 +- cas/stepcas/x5c_issuer_test.go | 218 +++++++++++ 3 files changed, 890 insertions(+), 4 deletions(-) create mode 100644 cas/stepcas/stepcas_test.go create mode 100644 cas/stepcas/x5c_issuer_test.go diff --git a/cas/stepcas/stepcas_test.go b/cas/stepcas/stepcas_test.go new file mode 100644 index 00000000..c10fb5ca --- /dev/null +++ b/cas/stepcas/stepcas_test.go @@ -0,0 +1,668 @@ +package stepcas + +import ( + "bytes" + "context" + "crypto" + "crypto/ed25519" + "crypto/rand" + "crypto/x509" + "encoding/json" + "encoding/pem" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "reflect" + "testing" + "time" + + "github.com/smallstep/certificates/api" + "github.com/smallstep/certificates/ca" + "github.com/smallstep/certificates/cas/apiv1" + "go.step.sm/crypto/x509util" +) + +var ( + testRootCrt *x509.Certificate + testRootKey crypto.Signer + testRootPath, testRootKeyPath string + testRootFingerprint string + + testIssCrt *x509.Certificate + testIssKey crypto.Signer + testIssPath, testIssKeyPath string + + testX5CCrt *x509.Certificate + testX5CKey crypto.Signer + testX5CPath, testX5CKeyPath string + + testCR *x509.CertificateRequest + testCrt *x509.Certificate + testKey crypto.Signer + testFailCR *x509.CertificateRequest +) + +func mustSignCertificate(subject string, sans []string, template string, parent *x509.Certificate, signer crypto.Signer) (*x509.Certificate, crypto.Signer) { + pub, priv, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + panic(err) + } + cr, err := x509util.CreateCertificateRequest(subject, sans, priv) + if err != nil { + panic(err) + } + cert, err := x509util.NewCertificate(cr, x509util.WithTemplate(template, x509util.CreateTemplateData(subject, sans))) + if err != nil { + panic(err) + } + + crt := cert.GetCertificate() + crt.NotBefore = time.Now() + crt.NotAfter = crt.NotBefore.Add(time.Hour) + if parent == nil { + parent = crt + } + if signer == nil { + signer = priv + } + if crt, err = x509util.CreateCertificate(crt, parent, pub, signer); err != nil { + panic(err) + } + return crt, priv +} + +func mustSerializeCrt(filename string, certs ...*x509.Certificate) { + buf := new(bytes.Buffer) + for _, c := range certs { + if err := pem.Encode(buf, &pem.Block{ + Type: "CERTIFICATE", + Bytes: c.Raw, + }); err != nil { + panic(err) + } + } + if err := ioutil.WriteFile(filename, buf.Bytes(), 0600); err != nil { + panic(err) + } +} + +func mustSerializeKey(filename string, key crypto.Signer) { + b, err := x509.MarshalPKCS8PrivateKey(key) + if err != nil { + panic(err) + } + b = pem.EncodeToMemory(&pem.Block{ + Type: "PRIVATE KEY", + Bytes: b, + }) + if err := ioutil.WriteFile(filename, b, 0600); err != nil { + panic(err) + } +} + +func testCAHelper(t *testing.T) (*url.URL, *ca.Client) { + t.Helper() + + writeJSON := func(w http.ResponseWriter, v interface{}) { + _ = json.NewEncoder(w).Encode(v) + } + parseJSON := func(r *http.Request, v interface{}) { + _ = json.NewDecoder(r.Body).Decode(v) + } + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case r.RequestURI == "/root/"+testRootFingerprint: + w.WriteHeader(http.StatusOK) + writeJSON(w, api.RootResponse{ + RootPEM: api.NewCertificate(testRootCrt), + }) + case r.RequestURI == "/sign": + var msg api.SignRequest + parseJSON(r, &msg) + if msg.CsrPEM.DNSNames[0] == "fail.doe.org" { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(w, `{"error":"fail","message":"fail"}`) + return + } + w.WriteHeader(http.StatusOK) + writeJSON(w, api.SignResponse{ + CertChainPEM: []api.Certificate{api.NewCertificate(testCrt), api.NewCertificate(testIssCrt)}, + }) + case r.RequestURI == "/revoke": + var msg api.RevokeRequest + parseJSON(r, &msg) + if msg.Serial == "fail" { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(w, `{"error":"fail","message":"fail"}`) + return + } + w.WriteHeader(http.StatusOK) + writeJSON(w, api.RevokeResponse{ + Status: "ok", + }) + default: + w.WriteHeader(http.StatusNotFound) + fmt.Fprintf(w, `{"error":"not found"}`) + } + })) + t.Cleanup(func() { + srv.Close() + }) + u, err := url.Parse(srv.URL) + if err != nil { + srv.Close() + t.Fatal(err) + } + + client, err := ca.NewClient(srv.URL, ca.WithTransport(http.DefaultTransport)) + if err != nil { + srv.Close() + t.Fatal(err) + } + + return u, client +} + +func TestMain(m *testing.M) { + testRootCrt, testRootKey = mustSignCertificate("Test Root Certificate", nil, x509util.DefaultRootTemplate, nil, nil) + testIssCrt, testIssKey = mustSignCertificate("Test Intermediate Certificate", nil, x509util.DefaultIntermediateTemplate, testRootCrt, testRootKey) + testX5CCrt, testX5CKey = mustSignCertificate("Test X5C Certificate", nil, x509util.DefaultLeafTemplate, testIssCrt, testIssKey) + + // Final certificate. + var err error + testCrt, testKey = mustSignCertificate("Test Certificate", []string{"doe.org"}, x509util.DefaultLeafTemplate, testIssCrt, testIssKey) + testCR, err = x509util.CreateCertificateRequest("Test Certificate", []string{"doe.org"}, testKey) + if err != nil { + panic(err) + } + + // CR used in errors. + testFailCR, err = x509util.CreateCertificateRequest("Test Certificate", []string{"fail.doe.org"}, testKey) + if err != nil { + panic(err) + } + + testRootFingerprint = x509util.Fingerprint(testRootCrt) + + path, err := os.MkdirTemp(os.TempDir(), "stepcas") + if err != nil { + panic(err) + } + + testRootPath = filepath.Join(path, "root_ca.crt") + testRootKeyPath = filepath.Join(path, "root_ca.key") + mustSerializeCrt(testRootPath, testRootCrt) + mustSerializeKey(testRootKeyPath, testRootKey) + + testIssPath = filepath.Join(path, "intermediate_ca.crt") + testIssKeyPath = filepath.Join(path, "intermediate_ca.key") + mustSerializeCrt(testIssPath, testIssCrt) + mustSerializeKey(testIssKeyPath, testIssKey) + + testX5CPath = filepath.Join(path, "x5c.crt") + testX5CKeyPath = filepath.Join(path, "x5c.key") + mustSerializeCrt(testX5CPath, testX5CCrt, testIssCrt) + mustSerializeKey(testX5CKeyPath, testX5CKey) + + code := m.Run() + if err := os.RemoveAll(path); err != nil { + panic(err) + } + os.Exit(code) +} + +func Test_init(t *testing.T) { + caURL, _ := testCAHelper(t) + + fn, ok := apiv1.LoadCertificateAuthorityServiceNewFunc(apiv1.StepCAS) + if !ok { + t.Errorf("apiv1.Register() ok = %v, want true", ok) + return + } + fn(context.Background(), apiv1.Options{ + CertificateAuthority: caURL.String(), + CertificateAuthorityFingerprint: testRootFingerprint, + CertificateIssuer: &apiv1.CertificateIssuer{ + Type: "x5c", + Provisioner: "X5C", + Certificate: testX5CPath, + Key: testX5CKeyPath, + }, + }) +} + +func TestNew(t *testing.T) { + caURL, client := testCAHelper(t) + type args struct { + ctx context.Context + opts apiv1.Options + } + tests := []struct { + name string + args args + want *StepCAS + wantErr bool + }{ + {"ok", args{context.TODO(), apiv1.Options{ + CertificateAuthority: caURL.String(), + CertificateAuthorityFingerprint: testRootFingerprint, + CertificateIssuer: &apiv1.CertificateIssuer{ + Type: "x5c", + Provisioner: "X5C", + Certificate: testX5CPath, + Key: testX5CKeyPath, + }, + }}, &StepCAS{ + x5c: &x5cIssuer{ + caURL: caURL, + certFile: testX5CPath, + keyFile: testX5CKeyPath, + issuer: "X5C", + }, + client: client, + fingerprint: testRootFingerprint, + }, false}, + {"fail authority", args{context.TODO(), apiv1.Options{ + CertificateAuthority: "", + CertificateAuthorityFingerprint: testRootFingerprint, + CertificateIssuer: &apiv1.CertificateIssuer{ + Type: "x5c", + Provisioner: "X5C", + Certificate: testX5CPath, + Key: testX5CKeyPath, + }, + }}, nil, true}, + {"fail fingerprint", args{context.TODO(), apiv1.Options{ + CertificateAuthority: caURL.String(), + CertificateAuthorityFingerprint: "", + CertificateIssuer: &apiv1.CertificateIssuer{ + Type: "x5c", + Provisioner: "X5C", + Certificate: testX5CPath, + Key: testX5CKeyPath, + }, + }}, nil, true}, + {"fail type", args{context.TODO(), apiv1.Options{ + CertificateAuthority: caURL.String(), + CertificateAuthorityFingerprint: testRootFingerprint, + CertificateIssuer: &apiv1.CertificateIssuer{ + Type: "", + Provisioner: "X5C", + Certificate: testX5CPath, + Key: testX5CKeyPath, + }, + }}, nil, true}, + {"fail provisioner", args{context.TODO(), apiv1.Options{ + CertificateAuthority: caURL.String(), + CertificateAuthorityFingerprint: testRootFingerprint, + CertificateIssuer: &apiv1.CertificateIssuer{ + Type: "x5c", + Provisioner: "", + Certificate: testX5CPath, + Key: testX5CKeyPath, + }, + }}, nil, true}, + {"fail certificate", args{context.TODO(), apiv1.Options{ + CertificateAuthority: caURL.String(), + CertificateAuthorityFingerprint: testRootFingerprint, + CertificateIssuer: &apiv1.CertificateIssuer{ + Type: "x5c", + Provisioner: "X5C", + Certificate: "", + Key: testX5CKeyPath, + }, + }}, nil, true}, + {"fail key", args{context.TODO(), apiv1.Options{ + CertificateAuthority: caURL.String(), + CertificateAuthorityFingerprint: testRootFingerprint, + CertificateIssuer: &apiv1.CertificateIssuer{ + Type: "x5c", + Provisioner: "X5C", + Certificate: testX5CPath, + Key: "", + }, + }}, nil, true}, + {"bad authority", args{context.TODO(), apiv1.Options{ + CertificateAuthority: "https://foobar", + CertificateAuthorityFingerprint: testRootFingerprint, + CertificateIssuer: &apiv1.CertificateIssuer{ + Type: "x5c", + Provisioner: "X5C", + Certificate: testX5CPath, + Key: testX5CKeyPath, + }, + }}, nil, true}, + {"fail parse url", args{context.TODO(), apiv1.Options{ + CertificateAuthority: "::failparse", + CertificateAuthorityFingerprint: testRootFingerprint, + CertificateIssuer: &apiv1.CertificateIssuer{ + Type: "x5c", + Provisioner: "X5C", + Certificate: testX5CPath, + Key: testX5CKeyPath, + }, + }}, nil, true}, + {"fail new client", args{context.TODO(), apiv1.Options{ + CertificateAuthority: caURL.String(), + CertificateAuthorityFingerprint: "foobar", + CertificateIssuer: &apiv1.CertificateIssuer{ + Type: "x5c", + Provisioner: "X5C", + Certificate: testX5CPath, + Key: testX5CKeyPath, + }, + }}, nil, true}, + {"fail new x5c issuer", args{context.TODO(), apiv1.Options{ + CertificateAuthority: caURL.String(), + CertificateAuthorityFingerprint: testRootFingerprint, + CertificateIssuer: &apiv1.CertificateIssuer{ + Type: "x5c", + Provisioner: "X5C", + Certificate: testX5CPath + ".missing", + Key: testX5CKeyPath, + }, + }}, nil, true}, + {"bad issuer", args{context.TODO(), apiv1.Options{ + CertificateAuthority: caURL.String(), + CertificateAuthorityFingerprint: testRootFingerprint, + CertificateIssuer: nil}}, nil, true}, + {"bad issuer type", args{context.TODO(), apiv1.Options{ + CertificateAuthority: caURL.String(), + CertificateAuthorityFingerprint: testRootFingerprint, + CertificateIssuer: &apiv1.CertificateIssuer{ + Type: "fail", + Provisioner: "X5C", + Certificate: testX5CPath, + Key: testX5CKeyPath, + }, + }}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := New(tt.args.ctx, tt.args.opts) + if (err != nil) != tt.wantErr { + t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) + return + } + // We cannot compare client + if got != nil && tt.want != nil { + got.client = tt.want.client + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("New() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestStepCAS_CreateCertificate(t *testing.T) { + caURL, client := testCAHelper(t) + x5c, err := newX5CIssuer(caURL, &apiv1.CertificateIssuer{ + Type: "x5c", + Provisioner: "X5C", + Certificate: testX5CPath, + Key: testX5CKeyPath, + }) + if err != nil { + t.Fatal(err) + } + + type fields struct { + x5c *x5cIssuer + client *ca.Client + fingerprint string + } + type args struct { + req *apiv1.CreateCertificateRequest + } + tests := []struct { + name string + fields fields + args args + want *apiv1.CreateCertificateResponse + wantErr bool + }{ + {"ok", fields{x5c, client, testRootFingerprint}, args{&apiv1.CreateCertificateRequest{ + CSR: testCR, + Lifetime: time.Hour, + }}, &apiv1.CreateCertificateResponse{ + Certificate: testCrt, + CertificateChain: []*x509.Certificate{testIssCrt}, + }, false}, + {"fail CSR", fields{x5c, client, testRootFingerprint}, args{&apiv1.CreateCertificateRequest{ + CSR: nil, + Lifetime: time.Hour, + }}, nil, true}, + {"fail lifetime", fields{x5c, client, testRootFingerprint}, args{&apiv1.CreateCertificateRequest{ + CSR: testCR, + Lifetime: 0, + }}, nil, true}, + {"fail sign token", fields{nil, client, testRootFingerprint}, args{&apiv1.CreateCertificateRequest{ + CSR: testCR, + Lifetime: time.Hour, + }}, nil, true}, + {"fail client sign", fields{x5c, client, testRootFingerprint}, args{&apiv1.CreateCertificateRequest{ + CSR: testFailCR, + Lifetime: time.Hour, + }}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &StepCAS{ + x5c: tt.fields.x5c, + client: tt.fields.client, + fingerprint: tt.fields.fingerprint, + } + got, err := s.CreateCertificate(tt.args.req) + if (err != nil) != tt.wantErr { + t.Errorf("StepCAS.CreateCertificate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("StepCAS.CreateCertificate() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestStepCAS_RenewCertificate(t *testing.T) { + caURL, client := testCAHelper(t) + x5c, err := newX5CIssuer(caURL, &apiv1.CertificateIssuer{ + Type: "x5c", + Provisioner: "X5C", + Certificate: testX5CPath, + Key: testX5CKeyPath, + }) + if err != nil { + t.Fatal(err) + } + + type fields struct { + x5c *x5cIssuer + client *ca.Client + fingerprint string + } + type args struct { + req *apiv1.RenewCertificateRequest + } + tests := []struct { + name string + fields fields + args args + want *apiv1.RenewCertificateResponse + wantErr bool + }{ + {"ok", fields{x5c, client, testRootFingerprint}, args{&apiv1.RenewCertificateRequest{ + CSR: testCR, + Lifetime: time.Hour, + }}, &apiv1.RenewCertificateResponse{ + Certificate: testCrt, + CertificateChain: []*x509.Certificate{testIssCrt}, + }, false}, + {"fail CSR", fields{x5c, client, testRootFingerprint}, args{&apiv1.RenewCertificateRequest{ + CSR: nil, + Lifetime: time.Hour, + }}, nil, true}, + {"fail lifetime", fields{x5c, client, testRootFingerprint}, args{&apiv1.RenewCertificateRequest{ + CSR: testCR, + Lifetime: 0, + }}, nil, true}, + {"fail sign token", fields{nil, client, testRootFingerprint}, args{&apiv1.RenewCertificateRequest{ + CSR: testCR, + Lifetime: time.Hour, + }}, nil, true}, + {"fail client sign", fields{x5c, client, testRootFingerprint}, args{&apiv1.RenewCertificateRequest{ + CSR: testFailCR, + Lifetime: time.Hour, + }}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &StepCAS{ + x5c: tt.fields.x5c, + client: tt.fields.client, + fingerprint: tt.fields.fingerprint, + } + got, err := s.RenewCertificate(tt.args.req) + if (err != nil) != tt.wantErr { + t.Errorf("StepCAS.RenewCertificate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("StepCAS.RenewCertificate() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestStepCAS_RevokeCertificate(t *testing.T) { + caURL, client := testCAHelper(t) + x5c, err := newX5CIssuer(caURL, &apiv1.CertificateIssuer{ + Type: "x5c", + Provisioner: "X5C", + Certificate: testX5CPath, + Key: testX5CKeyPath, + }) + if err != nil { + t.Fatal(err) + } + + type fields struct { + x5c *x5cIssuer + client *ca.Client + fingerprint string + } + type args struct { + req *apiv1.RevokeCertificateRequest + } + tests := []struct { + name string + fields fields + args args + want *apiv1.RevokeCertificateResponse + wantErr bool + }{ + {"ok serial number", fields{x5c, client, testRootFingerprint}, args{&apiv1.RevokeCertificateRequest{ + SerialNumber: "ok", + Certificate: nil, + }}, &apiv1.RevokeCertificateResponse{}, false}, + {"ok certificate", fields{x5c, client, testRootFingerprint}, args{&apiv1.RevokeCertificateRequest{ + SerialNumber: "", + Certificate: testCrt, + }}, &apiv1.RevokeCertificateResponse{ + Certificate: testCrt, + }, false}, + {"ok both", fields{x5c, client, testRootFingerprint}, args{&apiv1.RevokeCertificateRequest{ + SerialNumber: "ok", + Certificate: testCrt, + }}, &apiv1.RevokeCertificateResponse{ + Certificate: testCrt, + }, false}, + {"fail request", fields{x5c, client, testRootFingerprint}, args{&apiv1.RevokeCertificateRequest{ + SerialNumber: "", + Certificate: nil, + }}, nil, true}, + {"fail revoke token", fields{nil, client, testRootFingerprint}, args{&apiv1.RevokeCertificateRequest{ + SerialNumber: "ok", + }}, nil, true}, + {"fail client revoke", fields{x5c, client, testRootFingerprint}, args{&apiv1.RevokeCertificateRequest{ + SerialNumber: "fail", + }}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &StepCAS{ + x5c: tt.fields.x5c, + client: tt.fields.client, + fingerprint: tt.fields.fingerprint, + } + got, err := s.RevokeCertificate(tt.args.req) + if (err != nil) != tt.wantErr { + t.Errorf("StepCAS.RevokeCertificate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("StepCAS.RevokeCertificate() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestStepCAS_GetCertificateAuthority(t *testing.T) { + caURL, client := testCAHelper(t) + x5c, err := newX5CIssuer(caURL, &apiv1.CertificateIssuer{ + Type: "x5c", + Provisioner: "X5C", + Certificate: testX5CPath, + Key: testX5CKeyPath, + }) + if err != nil { + t.Fatal(err) + } + + type fields struct { + x5c *x5cIssuer + client *ca.Client + fingerprint string + } + type args struct { + req *apiv1.GetCertificateAuthorityRequest + } + tests := []struct { + name string + fields fields + args args + want *apiv1.GetCertificateAuthorityResponse + wantErr bool + }{ + {"ok", fields{x5c, client, testRootFingerprint}, args{&apiv1.GetCertificateAuthorityRequest{ + Name: caURL.String(), + }}, &apiv1.GetCertificateAuthorityResponse{ + RootCertificate: testRootCrt, + }, false}, + {"fail fingerprint", fields{x5c, client, "fail"}, args{&apiv1.GetCertificateAuthorityRequest{ + Name: caURL.String(), + }}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &StepCAS{ + x5c: tt.fields.x5c, + client: tt.fields.client, + fingerprint: tt.fields.fingerprint, + } + got, err := s.GetCertificateAuthority(tt.args.req) + if (err != nil) != tt.wantErr { + t.Errorf("StepCAS.GetCertificateAuthority() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("StepCAS.GetCertificateAuthority() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/cas/stepcas/x5c_issuer.go b/cas/stepcas/x5c_issuer.go index f7e77a3f..8046b8ae 100644 --- a/cas/stepcas/x5c_issuer.go +++ b/cas/stepcas/x5c_issuer.go @@ -125,8 +125,8 @@ func newX5CSigner(certFile, keyFile string) (jose.Signer, error) { func newJoseSigner(key crypto.Signer, so *jose.SignerOptions) (jose.Signer, error) { var alg jose.SignatureAlgorithm - switch k := key.(type) { - case *ecdsa.PrivateKey: + switch k := key.Public().(type) { + case *ecdsa.PublicKey: switch k.Curve.Params().Name { case "P-256": alg = jose.ES256 @@ -137,9 +137,9 @@ func newJoseSigner(key crypto.Signer, so *jose.SignerOptions) (jose.Signer, erro default: return nil, errors.Errorf("unsupported elliptic curve %s", k.Curve.Params().Name) } - case ed25519.PrivateKey: + case ed25519.PublicKey: alg = jose.EdDSA - case *rsa.PrivateKey: + case *rsa.PublicKey: alg = jose.DefaultRSASigAlgorithm default: return nil, errors.Errorf("unsupported key type %T", k) diff --git a/cas/stepcas/x5c_issuer_test.go b/cas/stepcas/x5c_issuer_test.go new file mode 100644 index 00000000..f8972741 --- /dev/null +++ b/cas/stepcas/x5c_issuer_test.go @@ -0,0 +1,218 @@ +package stepcas + +import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "io" + "net/url" + "reflect" + "testing" + + "go.step.sm/crypto/jose" +) + +type noneSigner []byte + +func (b noneSigner) Public() crypto.PublicKey { + return []byte(b) +} + +func (b noneSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { + return digest, nil +} + +func Test_x5cIssuer_SignToken(t *testing.T) { + caURL, err := url.Parse("https://ca.smallstep.com") + if err != nil { + t.Fatal(err) + } + type fields struct { + caURL *url.URL + certFile string + keyFile string + issuer string + } + type args struct { + subject string + sans []string + } + type claims struct { + Aud []string `json:"aud"` + Sub string `json:"sub"` + Sans []string `json:"sans"` + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + {"ok", fields{caURL, testX5CPath, testX5CKeyPath, "X5C"}, args{"doe", []string{"doe.org"}}, false}, + {"fail crt", fields{caURL, "", testX5CKeyPath, "X5C"}, args{"doe", []string{"doe.org"}}, true}, + {"fail key", fields{caURL, testX5CPath, "", "X5C"}, args{"doe", []string{"doe.org"}}, true}, + {"fail no signer", fields{caURL, testIssKeyPath, testIssPath, "X5C"}, args{"doe", []string{"doe.org"}}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + i := &x5cIssuer{ + caURL: tt.fields.caURL, + certFile: tt.fields.certFile, + keyFile: tt.fields.keyFile, + issuer: tt.fields.issuer, + } + got, err := i.SignToken(tt.args.subject, tt.args.sans) + if (err != nil) != tt.wantErr { + t.Errorf("x5cIssuer.SignToken() error = %v, wantErr %v", err, tt.wantErr) + } + if !tt.wantErr { + jwt, err := jose.ParseSigned(got) + if err != nil { + t.Errorf("jose.ParseSigned() error = %v", err) + } + var c claims + want := claims{ + Aud: []string{tt.fields.caURL.String() + "/1.0/sign#x5c/X5C"}, + Sub: tt.args.subject, + Sans: tt.args.sans, + } + if err := jwt.Claims(testX5CKey.Public(), &c); err != nil { + t.Errorf("jwt.Claims() error = %v", err) + } + if !reflect.DeepEqual(c, want) { + t.Errorf("jwt.Claims() claims = %#v, want %#v", c, want) + } + } + }) + } +} + +func Test_x5cIssuer_RevokeToken(t *testing.T) { + caURL, err := url.Parse("https://ca.smallstep.com") + if err != nil { + t.Fatal(err) + } + type fields struct { + caURL *url.URL + certFile string + keyFile string + issuer string + } + type args struct { + subject string + } + type claims struct { + Aud []string `json:"aud"` + Sub string `json:"sub"` + Sans []string `json:"sans"` + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + {"ok", fields{caURL, testX5CPath, testX5CKeyPath, "X5C"}, args{"doe"}, false}, + {"fail crt", fields{caURL, "", testX5CKeyPath, "X5C"}, args{"doe"}, true}, + {"fail key", fields{caURL, testX5CPath, "", "X5C"}, args{"doe"}, true}, + {"fail no signer", fields{caURL, testIssKeyPath, testIssPath, "X5C"}, args{"doe"}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + i := &x5cIssuer{ + caURL: tt.fields.caURL, + certFile: tt.fields.certFile, + keyFile: tt.fields.keyFile, + issuer: tt.fields.issuer, + } + got, err := i.RevokeToken(tt.args.subject) + if (err != nil) != tt.wantErr { + t.Errorf("x5cIssuer.RevokeToken() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr { + jwt, err := jose.ParseSigned(got) + if err != nil { + t.Errorf("jose.ParseSigned() error = %v", err) + } + var c claims + want := claims{ + Aud: []string{tt.fields.caURL.String() + "/1.0/revoke#x5c/X5C"}, + Sub: tt.args.subject, + } + if err := jwt.Claims(testX5CKey.Public(), &c); err != nil { + t.Errorf("jwt.Claims() error = %v", err) + } + if !reflect.DeepEqual(c, want) { + t.Errorf("jwt.Claims() claims = %#v, want %#v", c, want) + } + } + }) + } +} + +func Test_newJoseSigner(t *testing.T) { + mustSigner := func(args ...interface{}) crypto.Signer { + if err := args[len(args)-1]; err != nil { + t.Fatal(err) + } + for _, a := range args { + if s, ok := a.(crypto.Signer); ok { + return s + } + } + t.Fatal("signer not found") + return nil + } + + p224 := mustSigner(ecdsa.GenerateKey(elliptic.P224(), rand.Reader)) + p256 := mustSigner(ecdsa.GenerateKey(elliptic.P256(), rand.Reader)) + p384 := mustSigner(ecdsa.GenerateKey(elliptic.P384(), rand.Reader)) + p521 := mustSigner(ecdsa.GenerateKey(elliptic.P521(), rand.Reader)) + edKey := mustSigner(ed25519.GenerateKey(rand.Reader)) + rsaKey := mustSigner(rsa.GenerateKey(rand.Reader, 2048)) + + type args struct { + key crypto.Signer + so *jose.SignerOptions + } + tests := []struct { + name string + args args + want []jose.Header + wantErr bool + }{ + {"p256", args{p256, nil}, []jose.Header{{Algorithm: "ES256"}}, false}, + {"p384", args{p384, new(jose.SignerOptions).WithType("JWT")}, []jose.Header{{Algorithm: "ES384", ExtraHeaders: map[jose.HeaderKey]interface{}{"typ": "JWT"}}}, false}, + {"p521", args{p521, new(jose.SignerOptions).WithHeader("kid", "the-kid")}, []jose.Header{{Algorithm: "ES512", KeyID: "the-kid"}}, false}, + {"ed25519", args{edKey, nil}, []jose.Header{{Algorithm: "EdDSA"}}, false}, + {"rsa", args{rsaKey, nil}, []jose.Header{{Algorithm: "RS256"}}, false}, + {"fail p224", args{p224, nil}, nil, true}, + {"fail signer", args{noneSigner{1, 2, 3}, nil}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := newJoseSigner(tt.args.key, tt.args.so) + if (err != nil) != tt.wantErr { + t.Errorf("newJoseSigner() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr { + jws, err := got.Sign([]byte("{}")) + if err != nil { + t.Errorf("jose.Signer.Sign() err = %v", err) + } + jwt, err := jose.ParseSigned(jws.FullSerialize()) + if err != nil { + t.Errorf("jose.ParseSigned() err = %v", err) + } + if !reflect.DeepEqual(jwt.Headers, tt.want) { + t.Errorf("jose.Header got = %v, want = %v", jwt.Headers, tt.want) + } + } + }) + } +}