smallstep-certificates/cas/stepcas/stepcas_test.go
2022-12-21 16:02:26 +01:00

951 lines
28 KiB
Go

package stepcas
import (
"bytes"
"context"
"crypto"
"crypto/ed25519"
"crypto/rand"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"reflect"
"testing"
"time"
"github.com/smallstep/certificates/api"
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/ca"
"github.com/smallstep/certificates/cas/apiv1"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/pemutil"
"go.step.sm/crypto/randutil"
"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
testPassword, testEncryptedKeyPath string
testKeyID, testEncryptedJWKKey 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 := os.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 := os.WriteFile(filename, b, 0600); err != nil {
panic(err)
}
}
func mustEncryptKey(filename string, key crypto.Signer) {
_, err := pemutil.Serialize(key,
pemutil.ToFile(filename, 0600),
pemutil.WithPKCS8(true),
pemutil.WithPassword([]byte(testPassword)))
if 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 == "/renew":
if r.Header.Get("Authorization") == "Bearer fail" {
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",
})
case r.RequestURI == "/provisioners":
w.WriteHeader(http.StatusOK)
writeJSON(w, api.ProvisionersResponse{
NextCursor: "cursor",
Provisioners: []provisioner.Interface{
&provisioner.JWK{
Type: "JWK",
Name: "ra@doe.org",
Key: &jose.JSONWebKey{KeyID: testKeyID, Key: testX5CKey.Public()},
EncryptedKey: testEncryptedJWKKey,
},
&provisioner.JWK{
Type: "JWK",
Name: "empty@doe.org",
Key: &jose.JSONWebKey{KeyID: testKeyID, Key: testX5CKey.Public()},
},
},
})
case r.RequestURI == "/provisioners?cursor=cursor":
w.WriteHeader(http.StatusOK)
writeJSON(w, api.ProvisionersResponse{})
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 testX5CIssuer(t *testing.T, caURL *url.URL, password string) *x5cIssuer {
t.Helper()
key, givenPassword := testX5CKeyPath, password
if password != "" {
key = testEncryptedKeyPath
password = testPassword
}
x5c, err := newX5CIssuer(caURL, &apiv1.CertificateIssuer{
Type: "x5c",
Provisioner: "X5C",
Certificate: testX5CPath,
Key: key,
Password: password,
})
if err != nil {
t.Fatal(err)
}
x5c.password = givenPassword
return x5c
}
func testJWKIssuer(t *testing.T, caURL *url.URL, password string) *jwkIssuer {
t.Helper()
client, err := ca.NewClient(caURL.String(), ca.WithTransport(http.DefaultTransport))
if err != nil {
t.Fatal(err)
}
key := testX5CKeyPath
if password != "" {
key = testEncryptedKeyPath
password = testPassword
}
jwk, err := newJWKIssuer(context.TODO(), caURL, client, &apiv1.CertificateIssuer{
Type: "jwk",
Provisioner: "ra@doe.org",
Key: key,
Password: password,
})
if err != nil {
t.Fatal(err)
}
return jwk
}
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)
testRootFingerprint = x509util.Fingerprint(testRootCrt)
// Final certificate.
var err error
sans := []string{"doe.org", "jane@doe.org", "127.0.0.1", "::1", "localhost", "uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6;name=value"}
testCrt, testKey = mustSignCertificate("Test Certificate", sans, x509util.DefaultLeafTemplate, testIssCrt, testIssKey)
testCR, err = x509util.CreateCertificateRequest("Test Certificate", sans, testKey)
if err != nil {
panic(err)
}
// CR used in errors.
testFailCR, err = x509util.CreateCertificateRequest("", []string{"fail.doe.org"}, testKey)
if err != nil {
panic(err)
}
// Password used to encrypt the key.
testPassword, err = randutil.Hex(32)
if err != nil {
panic(err)
}
// Encrypted JWK key used when the key is downloaded from the CA.
jwe, err := jose.EncryptJWK(&jose.JSONWebKey{Key: testX5CKey}, []byte(testPassword))
if err != nil {
panic(err)
}
testEncryptedJWKKey, err = jwe.CompactSerialize()
if err != nil {
panic(err)
}
testKeyID, err = jose.Thumbprint(&jose.JSONWebKey{Key: testX5CKey})
if err != nil {
panic(err)
}
// Create test files.
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)
testEncryptedKeyPath = filepath.Join(path, "x5c.enc.key")
mustEncryptKey(testEncryptedKeyPath, 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)
signer, err := newJWKSignerFromEncryptedKey(testKeyID, testEncryptedJWKKey, testPassword)
if err != nil {
t.Fatal(err)
}
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{
iss: &x5cIssuer{
caURL: caURL,
certFile: testX5CPath,
keyFile: testX5CKeyPath,
issuer: "X5C",
},
client: client,
fingerprint: testRootFingerprint,
}, false},
{"ok jwk", args{context.TODO(), apiv1.Options{
CertificateAuthority: caURL.String(),
CertificateAuthorityFingerprint: testRootFingerprint,
CertificateIssuer: &apiv1.CertificateIssuer{
Type: "jwk",
Provisioner: "ra@doe.org",
Key: testX5CKeyPath,
},
}}, &StepCAS{
iss: &jwkIssuer{
caURL: caURL,
issuer: "ra@doe.org",
signer: signer,
},
client: client,
fingerprint: testRootFingerprint,
}, false},
{"ok jwk provisioners", args{context.TODO(), apiv1.Options{
CertificateAuthority: caURL.String(),
CertificateAuthorityFingerprint: testRootFingerprint,
CertificateIssuer: &apiv1.CertificateIssuer{
Type: "jwk",
Provisioner: "ra@doe.org",
Password: testPassword,
},
}}, &StepCAS{
iss: &jwkIssuer{
caURL: caURL,
issuer: "ra@doe.org",
signer: signer,
},
client: client,
fingerprint: testRootFingerprint,
}, false},
{"ok ca getter", args{context.TODO(), apiv1.Options{
IsCAGetter: true,
CertificateAuthority: caURL.String(),
CertificateAuthorityFingerprint: testRootFingerprint,
CertificateIssuer: &apiv1.CertificateIssuer{
Type: "jwk",
Provisioner: "ra@doe.org",
},
}}, &StepCAS{
iss: nil,
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 provisioner jwk", args{context.TODO(), apiv1.Options{
CertificateAuthority: caURL.String(),
CertificateAuthorityFingerprint: testRootFingerprint,
CertificateIssuer: &apiv1.CertificateIssuer{
Type: "jwk",
Provisioner: "",
Key: testX5CKeyPath,
},
}}, nil, true},
{"fail provisioner not found", args{context.TODO(), apiv1.Options{
CertificateAuthority: caURL.String(),
CertificateAuthorityFingerprint: testRootFingerprint,
CertificateIssuer: &apiv1.CertificateIssuer{
Type: "jwk",
Provisioner: "notfound@doe.org",
Password: testPassword,
},
}}, nil, true},
{"fail invalid password", args{context.TODO(), apiv1.Options{
CertificateAuthority: caURL.String(),
CertificateAuthorityFingerprint: testRootFingerprint,
CertificateIssuer: &apiv1.CertificateIssuer{
Type: "jwk",
Provisioner: "ra@doe.org",
Password: "bad-password",
},
}}, nil, true},
{"fail no key", args{context.TODO(), apiv1.Options{
CertificateAuthority: caURL.String(),
CertificateAuthorityFingerprint: testRootFingerprint,
CertificateIssuer: &apiv1.CertificateIssuer{
Type: "jwk",
Provisioner: "empty@doe.org",
Password: testPassword,
},
}}, 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},
{"fail key jwk", args{context.TODO(), apiv1.Options{
CertificateAuthority: caURL.String(),
CertificateAuthorityFingerprint: testRootFingerprint,
CertificateIssuer: &apiv1.CertificateIssuer{
Type: "jwk",
Provisioner: "ra@smallstep.com",
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},
{"fail new jwk issuer", args{context.TODO(), apiv1.Options{
CertificateAuthority: caURL.String(),
CertificateAuthorityFingerprint: testRootFingerprint,
CertificateIssuer: &apiv1.CertificateIssuer{
Type: "jwk",
Provisioner: "ra@doe.org",
Key: testX5CKeyPath + ".missing",
},
}}, 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 neither the client nor the signer.
if got != nil && tt.want != nil {
got.client = tt.want.client
if jwk, ok := got.iss.(*jwkIssuer); ok {
jwk.signer = signer
}
}
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 := testX5CIssuer(t, caURL, "")
jwk := testJWKIssuer(t, caURL, "")
x5cEnc := testX5CIssuer(t, caURL, testPassword)
jwkEnc := testJWKIssuer(t, caURL, testPassword)
x5cBad := testX5CIssuer(t, caURL, "bad-password")
type fields struct {
iss stepIssuer
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},
{"ok with password", fields{x5cEnc, client, testRootFingerprint}, args{&apiv1.CreateCertificateRequest{
CSR: testCR,
Lifetime: time.Hour,
}}, &apiv1.CreateCertificateResponse{
Certificate: testCrt,
CertificateChain: []*x509.Certificate{testIssCrt},
}, false},
{"ok jwk", fields{jwk, client, testRootFingerprint}, args{&apiv1.CreateCertificateRequest{
CSR: testCR,
Lifetime: time.Hour,
}}, &apiv1.CreateCertificateResponse{
Certificate: testCrt,
CertificateChain: []*x509.Certificate{testIssCrt},
}, false},
{"ok jwk with password", fields{jwkEnc, client, testRootFingerprint}, args{&apiv1.CreateCertificateRequest{
CSR: testCR,
Lifetime: time.Hour,
}}, &apiv1.CreateCertificateResponse{
Certificate: testCrt,
CertificateChain: []*x509.Certificate{testIssCrt},
}, false},
{"ok with provisioner", fields{jwk, client, testRootFingerprint}, args{&apiv1.CreateCertificateRequest{
CSR: testCR,
Lifetime: time.Hour,
Provisioner: &apiv1.ProvisionerInfo{ID: "provisioner-id", Type: "ACME"},
}}, &apiv1.CreateCertificateResponse{
Certificate: testCrt,
CertificateChain: []*x509.Certificate{testIssCrt},
}, false},
{"ok with server cert", fields{jwk, client, testRootFingerprint}, args{&apiv1.CreateCertificateRequest{
CSR: testCR,
Lifetime: time.Hour,
IsCAServerCert: true,
}}, &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{mockErrIssuer{}, 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},
{"fail password", fields{x5cBad, client, testRootFingerprint}, args{&apiv1.CreateCertificateRequest{
CSR: testCR,
Lifetime: time.Hour,
}}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &StepCAS{
iss: tt.fields.iss,
client: tt.fields.client,
authorityID: "authority-id",
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)
jwk := testJWKIssuer(t, caURL, "")
tokenIssuer := testX5CIssuer(t, caURL, "")
token, err := tokenIssuer.SignToken("test", []string{"test.example.com"}, nil)
if err != nil {
t.Fatal(err)
}
type fields struct {
iss stepIssuer
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{jwk, client, testRootFingerprint}, args{&apiv1.RenewCertificateRequest{
Template: &x509.Certificate{},
Backdate: time.Minute,
Lifetime: time.Hour,
Token: token,
}}, &apiv1.RenewCertificateResponse{
Certificate: testCrt,
CertificateChain: []*x509.Certificate{testIssCrt},
}, false},
{"fail no token", fields{jwk, client, testRootFingerprint}, args{&apiv1.RenewCertificateRequest{
Template: &x509.Certificate{},
Backdate: time.Minute,
Lifetime: time.Hour,
}}, nil, true},
{"fail bad token", fields{jwk, client, testRootFingerprint}, args{&apiv1.RenewCertificateRequest{
Template: &x509.Certificate{},
Backdate: time.Minute,
Lifetime: time.Hour,
Token: "fail",
}}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &StepCAS{
iss: tt.fields.iss,
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.Error(reflect.DeepEqual(got.Certificate, tt.want.Certificate))
t.Error(reflect.DeepEqual(got.CertificateChain, tt.want.CertificateChain))
t.Errorf("StepCAS.RenewCertificate() = %v, want %v", got.Certificate.Subject, tt.want.Certificate.Subject)
}
})
}
}
func TestStepCAS_RevokeCertificate(t *testing.T) {
caURL, client := testCAHelper(t)
x5c := testX5CIssuer(t, caURL, "")
jwk := testJWKIssuer(t, caURL, "")
x5cEnc := testX5CIssuer(t, caURL, testPassword)
jwkEnc := testJWKIssuer(t, caURL, testPassword)
x5cBad := testX5CIssuer(t, caURL, "bad-password")
type fields struct {
iss stepIssuer
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},
{"ok with password", fields{x5cEnc, client, testRootFingerprint}, args{&apiv1.RevokeCertificateRequest{
SerialNumber: "ok",
Certificate: nil,
}}, &apiv1.RevokeCertificateResponse{}, false},
{"ok serial number jwk", fields{jwk, client, testRootFingerprint}, args{&apiv1.RevokeCertificateRequest{
SerialNumber: "ok",
Certificate: nil,
}}, &apiv1.RevokeCertificateResponse{}, false},
{"ok certificate jwk", fields{jwk, client, testRootFingerprint}, args{&apiv1.RevokeCertificateRequest{
SerialNumber: "",
Certificate: testCrt,
}}, &apiv1.RevokeCertificateResponse{
Certificate: testCrt,
}, false},
{"ok both jwk", fields{jwk, client, testRootFingerprint}, args{&apiv1.RevokeCertificateRequest{
SerialNumber: "ok",
Certificate: testCrt,
}}, &apiv1.RevokeCertificateResponse{
Certificate: testCrt,
}, false},
{"ok jwk with password", fields{jwkEnc, client, testRootFingerprint}, args{&apiv1.RevokeCertificateRequest{
SerialNumber: "ok",
Certificate: nil,
}}, &apiv1.RevokeCertificateResponse{}, false},
{"fail request", fields{x5c, client, testRootFingerprint}, args{&apiv1.RevokeCertificateRequest{
SerialNumber: "",
Certificate: nil,
}}, nil, true},
{"fail revoke token", fields{mockErrIssuer{}, client, testRootFingerprint}, args{&apiv1.RevokeCertificateRequest{
SerialNumber: "ok",
}}, nil, true},
{"fail client revoke", fields{x5c, client, testRootFingerprint}, args{&apiv1.RevokeCertificateRequest{
SerialNumber: "fail",
}}, nil, true},
{"fail password", fields{x5cBad, client, testRootFingerprint}, args{&apiv1.RevokeCertificateRequest{
SerialNumber: "ok",
Certificate: nil,
}}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &StepCAS{
iss: tt.fields.iss,
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 := testX5CIssuer(t, caURL, "")
jwk := testJWKIssuer(t, caURL, "")
type fields struct {
iss stepIssuer
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},
{"ok jwk", fields{jwk, 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{
iss: tt.fields.iss,
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)
}
})
}
}