913 lines
27 KiB
Go
913 lines
27 KiB
Go
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/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 == "/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(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 := ioutil.TempDir(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{ProvisionerID: "provisioner-id", ProvisionerType: "ACME"},
|
|
}}, &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)
|
|
x5c := testX5CIssuer(t, caURL, "")
|
|
jwk := testJWKIssuer(t, caURL, "")
|
|
|
|
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
|
|
}{
|
|
{"not implemented", fields{x5c, client, testRootFingerprint}, args{&apiv1.RenewCertificateRequest{
|
|
CSR: testCR,
|
|
Lifetime: time.Hour,
|
|
}}, nil, true},
|
|
{"not implemented jwk", fields{jwk, client, testRootFingerprint}, args{&apiv1.RenewCertificateRequest{
|
|
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,
|
|
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 := 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)
|
|
}
|
|
})
|
|
}
|
|
}
|