forked from TrueCloudLab/certificates
Add tests for stepcas.
This commit is contained in:
parent
bcf70206ac
commit
dbb48ecf8d
3 changed files with 890 additions and 4 deletions
668
cas/stepcas/stepcas_test.go
Normal file
668
cas/stepcas/stepcas_test.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -125,8 +125,8 @@ func newX5CSigner(certFile, keyFile string) (jose.Signer, error) {
|
||||||
|
|
||||||
func newJoseSigner(key crypto.Signer, so *jose.SignerOptions) (jose.Signer, error) {
|
func newJoseSigner(key crypto.Signer, so *jose.SignerOptions) (jose.Signer, error) {
|
||||||
var alg jose.SignatureAlgorithm
|
var alg jose.SignatureAlgorithm
|
||||||
switch k := key.(type) {
|
switch k := key.Public().(type) {
|
||||||
case *ecdsa.PrivateKey:
|
case *ecdsa.PublicKey:
|
||||||
switch k.Curve.Params().Name {
|
switch k.Curve.Params().Name {
|
||||||
case "P-256":
|
case "P-256":
|
||||||
alg = jose.ES256
|
alg = jose.ES256
|
||||||
|
@ -137,9 +137,9 @@ func newJoseSigner(key crypto.Signer, so *jose.SignerOptions) (jose.Signer, erro
|
||||||
default:
|
default:
|
||||||
return nil, errors.Errorf("unsupported elliptic curve %s", k.Curve.Params().Name)
|
return nil, errors.Errorf("unsupported elliptic curve %s", k.Curve.Params().Name)
|
||||||
}
|
}
|
||||||
case ed25519.PrivateKey:
|
case ed25519.PublicKey:
|
||||||
alg = jose.EdDSA
|
alg = jose.EdDSA
|
||||||
case *rsa.PrivateKey:
|
case *rsa.PublicKey:
|
||||||
alg = jose.DefaultRSASigAlgorithm
|
alg = jose.DefaultRSASigAlgorithm
|
||||||
default:
|
default:
|
||||||
return nil, errors.Errorf("unsupported key type %T", k)
|
return nil, errors.Errorf("unsupported key type %T", k)
|
||||||
|
|
218
cas/stepcas/x5c_issuer_test.go
Normal file
218
cas/stepcas/x5c_issuer_test.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue