Skip to content

Commit ee7db40

Browse files
committed
change sign + authorize authority api | add provisioners
* authorize returns []interface{} - operators in this list can conform to any interface the user decides - our implementation has a combination of certificate claim validators and certificate template modifiers. * provisioners can set and enforce tls cert options
1 parent d7c31c3 commit ee7db40

20 files changed

+612
-422
lines changed

api/api.go

+12-59
Original file line numberDiff line numberDiff line change
@@ -11,43 +11,19 @@ import (
1111

1212
"github.com/go-chi/chi"
1313
"github.com/pkg/errors"
14-
"github.com/smallstep/ca-component/provisioner"
14+
"github.com/smallstep/ca-component/authority"
1515
"github.com/smallstep/cli/crypto/tlsutil"
16-
"github.com/smallstep/cli/crypto/x509util"
1716
"github.com/smallstep/cli/jose"
1817
)
1918

20-
// Minimum and maximum validity of an end-entity (not root or intermediate) certificate.
21-
// They will be overwritten with the values configured in the authority
22-
var (
23-
minCertDuration = 5 * time.Minute
24-
maxCertDuration = 24 * time.Hour
25-
)
26-
27-
// Claim interface is implemented by types used to validate specific claims in a
28-
// certificate request.
29-
// TODO(mariano): Rename?
30-
type Claim interface {
31-
Valid(cr *x509.CertificateRequest) error
32-
}
33-
34-
// SignOptions contains the options that can be passed to the Authority.Sign
35-
// method.
36-
type SignOptions struct {
37-
NotAfter time.Time `json:"notAfter"`
38-
NotBefore time.Time `json:"notBefore"`
39-
}
40-
4119
// Authority is the interface implemented by a CA authority.
4220
type Authority interface {
43-
Authorize(ott string) ([]Claim, error)
21+
Authorize(ott string) ([]interface{}, error)
4422
GetTLSOptions() *tlsutil.TLSOptions
45-
GetMinDuration() time.Duration
46-
GetMaxDuration() time.Duration
4723
Root(shasum string) (*x509.Certificate, error)
48-
Sign(cr *x509.CertificateRequest, opts SignOptions, claims ...Claim) (*x509.Certificate, *x509.Certificate, error)
24+
Sign(cr *x509.CertificateRequest, signOpts authority.SignOptions, extraOpts ...interface{}) (*x509.Certificate, *x509.Certificate, error)
4925
Renew(cert *x509.Certificate) (*x509.Certificate, *x509.Certificate, error)
50-
GetProvisioners() ([]*provisioner.Provisioner, error)
26+
GetProvisioners() ([]*authority.Provisioner, error)
5127
GetEncryptedKey(kid string) (string, error)
5228
}
5329

@@ -176,7 +152,7 @@ type SignRequest struct {
176152
// ProvisionersResponse is the response object that returns the list of
177153
// provisioners.
178154
type ProvisionersResponse struct {
179-
Provisioners []*provisioner.Provisioner `json:"provisioners"`
155+
Provisioners []*authority.Provisioner `json:"provisioners"`
180156
}
181157

182158
// JWKSetByIssuerResponse is the response object that returns the map of
@@ -204,27 +180,6 @@ func (s *SignRequest) Validate() error {
204180
return BadRequest(errors.New("missing ott"))
205181
}
206182

207-
now := time.Now()
208-
if s.NotBefore.IsZero() {
209-
s.NotBefore = now
210-
}
211-
if s.NotAfter.IsZero() {
212-
s.NotAfter = now.Add(x509util.DefaultCertValidity)
213-
}
214-
215-
if s.NotAfter.Before(now) {
216-
return BadRequest(errors.New("notAfter < now"))
217-
}
218-
if s.NotAfter.Before(s.NotBefore) {
219-
return BadRequest(errors.New("notAfter < notBefore"))
220-
}
221-
requestedDuration := s.NotAfter.Sub(s.NotBefore)
222-
if requestedDuration < minCertDuration {
223-
return BadRequest(errors.New("requested certificate validity duration is too short"))
224-
}
225-
if requestedDuration > maxCertDuration {
226-
return BadRequest(errors.New("requested certificate validity duration is too long"))
227-
}
228183
return nil
229184
}
230185

@@ -243,8 +198,6 @@ type caHandler struct {
243198

244199
// New creates a new RouterHandler with the CA endpoints.
245200
func New(authority Authority) RouterHandler {
246-
minCertDuration = authority.GetMinDuration()
247-
maxCertDuration = authority.GetMaxDuration()
248201
return &caHandler{
249202
Authority: authority,
250203
}
@@ -296,18 +249,18 @@ func (h *caHandler) Sign(w http.ResponseWriter, r *http.Request) {
296249
return
297250
}
298251

299-
claims, err := h.Authority.Authorize(body.OTT)
252+
signOpts := authority.SignOptions{
253+
NotBefore: body.NotBefore,
254+
NotAfter: body.NotAfter,
255+
}
256+
257+
extraOpts, err := h.Authority.Authorize(body.OTT)
300258
if err != nil {
301259
WriteError(w, Unauthorized(err))
302260
return
303261
}
304262

305-
opts := SignOptions{
306-
NotBefore: body.NotBefore,
307-
NotAfter: body.NotAfter,
308-
}
309-
310-
cert, root, err := h.Authority.Sign(body.CsrPEM.CertificateRequest, opts, claims...)
263+
cert, root, err := h.Authority.Sign(body.CsrPEM.CertificateRequest, signOpts, extraOpts...)
311264
if err != nil {
312265
WriteError(w, Forbidden(err))
313266
return

api/api_test.go

+22-38
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import (
1717
"time"
1818

1919
"github.com/go-chi/chi"
20-
"github.com/smallstep/ca-component/provisioner"
20+
"github.com/smallstep/ca-component/authority"
2121
"github.com/smallstep/cli/crypto/tlsutil"
2222
"github.com/smallstep/cli/jose"
2323
)
@@ -349,7 +349,6 @@ func TestCertificateRequest_UnmarshalJSON_json(t *testing.T) {
349349
}
350350

351351
func TestSignRequest_Validate(t *testing.T) {
352-
now := time.Now()
353352
csr := parseCertificateRequest(csrPEM)
354353
bad := parseCertificateRequest(csrPEM)
355354
bad.Signature[0]++
@@ -364,16 +363,9 @@ func TestSignRequest_Validate(t *testing.T) {
364363
fields fields
365364
wantErr bool
366365
}{
367-
{"ok", fields{CertificateRequest{csr}, "foobarzar", time.Time{}, time.Time{}}, false},
368-
{"ok 5m", fields{CertificateRequest{csr}, "foobarzar", now, now.Add(5 * time.Minute)}, false},
369-
{"ok 24h", fields{CertificateRequest{csr}, "foobarzar", now, now.Add(24 * time.Hour)}, false},
370366
{"missing csr", fields{CertificateRequest{}, "foobarzar", time.Time{}, time.Time{}}, true},
371367
{"invalid csr", fields{CertificateRequest{bad}, "foobarzar", time.Time{}, time.Time{}}, true},
372368
{"missing ott", fields{CertificateRequest{csr}, "", time.Time{}, time.Time{}}, true},
373-
{"notAfter < now", fields{CertificateRequest{csr}, "foobarzar", now, now.Add(-5 * time.Minute)}, true},
374-
{"notAfter < notBefore", fields{CertificateRequest{csr}, "foobarzar", now.Add(5 * time.Minute), now.Add(4 * time.Minute)}, true},
375-
{"too short", fields{CertificateRequest{csr}, "foobarzar", now, now.Add(4 * time.Minute)}, true},
376-
{"too long", fields{CertificateRequest{csr}, "foobarzar", now, now.Add(24 * time.Hour).Add(1 * time.Minute)}, true},
377369
}
378370
for _, tt := range tests {
379371
t.Run(tt.name, func(t *testing.T) {
@@ -393,20 +385,20 @@ func TestSignRequest_Validate(t *testing.T) {
393385
type mockAuthority struct {
394386
ret1, ret2 interface{}
395387
err error
396-
authorize func(ott string) ([]Claim, error)
388+
authorize func(ott string) ([]interface{}, error)
397389
getTLSOptions func() *tlsutil.TLSOptions
398390
root func(shasum string) (*x509.Certificate, error)
399-
sign func(cr *x509.CertificateRequest, opts SignOptions, claims ...Claim) (*x509.Certificate, *x509.Certificate, error)
391+
sign func(cr *x509.CertificateRequest, signOpts authority.SignOptions, extraOpts ...interface{}) (*x509.Certificate, *x509.Certificate, error)
400392
renew func(cert *x509.Certificate) (*x509.Certificate, *x509.Certificate, error)
401-
getProvisioners func() ([]*provisioner.Provisioner, error)
393+
getProvisioners func() ([]*authority.Provisioner, error)
402394
getEncryptedKey func(kid string) (string, error)
403395
}
404396

405-
func (m *mockAuthority) Authorize(ott string) ([]Claim, error) {
397+
func (m *mockAuthority) Authorize(ott string) ([]interface{}, error) {
406398
if m.authorize != nil {
407399
return m.authorize(ott)
408400
}
409-
return m.ret1.([]Claim), m.err
401+
return m.ret1.([]interface{}), m.err
410402
}
411403

412404
func (m *mockAuthority) GetTLSOptions() *tlsutil.TLSOptions {
@@ -416,24 +408,16 @@ func (m *mockAuthority) GetTLSOptions() *tlsutil.TLSOptions {
416408
return m.ret1.(*tlsutil.TLSOptions)
417409
}
418410

419-
func (m *mockAuthority) GetMinDuration() time.Duration {
420-
return minCertDuration
421-
}
422-
423-
func (m *mockAuthority) GetMaxDuration() time.Duration {
424-
return maxCertDuration
425-
}
426-
427411
func (m *mockAuthority) Root(shasum string) (*x509.Certificate, error) {
428412
if m.root != nil {
429413
return m.root(shasum)
430414
}
431415
return m.ret1.(*x509.Certificate), m.err
432416
}
433417

434-
func (m *mockAuthority) Sign(cr *x509.CertificateRequest, opts SignOptions, claims ...Claim) (*x509.Certificate, *x509.Certificate, error) {
418+
func (m *mockAuthority) Sign(cr *x509.CertificateRequest, signOpts authority.SignOptions, extraOpts ...interface{}) (*x509.Certificate, *x509.Certificate, error) {
435419
if m.sign != nil {
436-
return m.sign(cr, opts, claims...)
420+
return m.sign(cr, signOpts, extraOpts...)
437421
}
438422
return m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate), m.err
439423
}
@@ -445,11 +429,11 @@ func (m *mockAuthority) Renew(cert *x509.Certificate) (*x509.Certificate, *x509.
445429
return m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate), m.err
446430
}
447431

448-
func (m *mockAuthority) GetProvisioners() ([]*provisioner.Provisioner, error) {
432+
func (m *mockAuthority) GetProvisioners() ([]*authority.Provisioner, error) {
449433
if m.getProvisioners != nil {
450434
return m.getProvisioners()
451435
}
452-
return m.ret1.([]*provisioner.Provisioner), m.err
436+
return m.ret1.([]*authority.Provisioner), m.err
453437
}
454438

455439
func (m *mockAuthority) GetEncryptedKey(kid string) (string, error) {
@@ -567,14 +551,14 @@ func Test_caHandler_Sign(t *testing.T) {
567551
}
568552

569553
tests := []struct {
570-
name string
571-
input string
572-
claims []Claim
573-
autherr error
574-
cert *x509.Certificate
575-
root *x509.Certificate
576-
signErr error
577-
statusCode int
554+
name string
555+
input string
556+
certAttrOpts []interface{}
557+
autherr error
558+
cert *x509.Certificate
559+
root *x509.Certificate
560+
signErr error
561+
statusCode int
578562
}{
579563
{"ok", string(valid), nil, nil, parseCertificate(certPEM), parseCertificate(rootPEM), nil, http.StatusCreated},
580564
{"json read error", "{", nil, nil, nil, nil, nil, http.StatusBadRequest},
@@ -589,8 +573,8 @@ func Test_caHandler_Sign(t *testing.T) {
589573
t.Run(tt.name, func(t *testing.T) {
590574
h := New(&mockAuthority{
591575
ret1: tt.cert, ret2: tt.root, err: tt.signErr,
592-
authorize: func(ott string) ([]Claim, error) {
593-
return tt.claims, tt.autherr
576+
authorize: func(ott string) ([]interface{}, error) {
577+
return tt.certAttrOpts, tt.autherr
594578
},
595579
getTLSOptions: func() *tlsutil.TLSOptions {
596580
return nil
@@ -690,7 +674,7 @@ func Test_caHandler_JWKSetByIssuer(t *testing.T) {
690674
t.Fatal(err)
691675
}
692676

693-
p := []*provisioner.Provisioner{
677+
p := []*authority.Provisioner{
694678
{
695679
Issuer: "p1",
696680
Key: &key,
@@ -766,7 +750,7 @@ func Test_caHandler_Provisioners(t *testing.T) {
766750
t.Fatal(err)
767751
}
768752

769-
p := []*provisioner.Provisioner{
753+
p := []*authority.Provisioner{
770754
{
771755
Type: "JWK",
772756
Issuer: "max",

authority/authority_test.go

+3-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77

88
"github.com/pkg/errors"
99
"github.com/smallstep/assert"
10-
"github.com/smallstep/ca-component/provisioner"
1110
stepJOSE "github.com/smallstep/cli/jose"
1211
)
1312

@@ -16,7 +15,7 @@ func testAuthority(t *testing.T) *Authority {
1615
assert.FatalError(t, err)
1716
clijwk, err := stepJOSE.ParseKey("testdata/secrets/step_cli_key_pub.jwk")
1817
assert.FatalError(t, err)
19-
p := []*provisioner.Provisioner{
18+
p := []*Provisioner{
2019
{
2120
Issuer: "Max",
2221
Type: "JWK",
@@ -29,11 +28,11 @@ func testAuthority(t *testing.T) *Authority {
2928
},
3029
}
3130
c := &Config{
32-
Address: "127.0.0.1",
31+
Address: "127.0.0.1:443",
3332
Root: "testdata/secrets/root_ca.crt",
3433
IntermediateCert: "testdata/secrets/intermediate_ca.crt",
3534
IntermediateKey: "testdata/secrets/intermediate_ca_key",
36-
DNSNames: []string{"test.smallstep.com"},
35+
DNSNames: []string{"test.ca.smallstep.com"},
3736
Password: "pass",
3837
AuthorityConfig: &AuthConfig{
3938
Provisioners: p,

0 commit comments

Comments
 (0)