Skip to content

Commit

Permalink
Merge branch 'master' into hs/scep
Browse files Browse the repository at this point in the history
  • Loading branch information
hslatman committed Mar 26, 2021
2 parents 65aab96 + 5249ce7 commit c5e4ea0
Show file tree
Hide file tree
Showing 23 changed files with 2,300 additions and 39 deletions.
1 change: 0 additions & 1 deletion authority/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,6 @@ func (c *Config) Validate() error {

// Options holds the RA/CAS configuration.
ra := c.AuthorityConfig.Options

// The default RA/CAS requires root, crt and key.
if ra.Is(cas.SoftCAS) {
switch {
Expand Down
21 changes: 12 additions & 9 deletions authority/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
lifetime := leaf.NotAfter.Sub(leaf.NotBefore.Add(signOpts.Backdate))
resp, err := a.x509CAService.CreateCertificate(&casapi.CreateCertificateRequest{
Template: leaf,
CSR: csr,
Lifetime: lifetime,
Backdate: signOpts.Backdate,
})
Expand Down Expand Up @@ -333,22 +334,21 @@ func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error
if !ok {
return errs.InternalServer("authority.Revoke; provisioner not found", opts...)
}
rci.ProvisionerID = p.GetID()
rci.TokenID, err = p.GetTokenID(revokeOpts.OTT)
if err != nil {
return errs.Wrap(http.StatusInternalServerError, err,
"authority.Revoke; could not get ID for token")
}
opts = append(opts, errs.WithKeyVal("provisionerID", rci.ProvisionerID))
opts = append(opts, errs.WithKeyVal("tokenID", rci.TokenID))
} else {
// Load the Certificate provisioner if one exists.
p, err = a.LoadProvisionerByCertificate(revokeOpts.Crt)
if err != nil {
return errs.Wrap(http.StatusUnauthorized, err,
"authority.Revoke: unable to load certificate provisioner", opts...)
if p, err = a.LoadProvisionerByCertificate(revokeOpts.Crt); err == nil {
rci.ProvisionerID = p.GetID()
opts = append(opts, errs.WithKeyVal("provisionerID", rci.ProvisionerID))
}
}
rci.ProvisionerID = p.GetID()
opts = append(opts, errs.WithKeyVal("provisionerID", rci.ProvisionerID))

if provisioner.MethodFromContext(ctx) == provisioner.SSHRevokeMethod {
err = a.db.RevokeSSH(rci)
Expand All @@ -367,9 +367,11 @@ func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error
// CAS operation, note that SoftCAS (default) is a noop.
// The revoke happens when this is stored in the db.
_, err = a.x509CAService.RevokeCertificate(&casapi.RevokeCertificateRequest{
Certificate: revokedCert,
Reason: rci.Reason,
ReasonCode: rci.ReasonCode,
Certificate: revokedCert,
SerialNumber: rci.Serial,
Reason: rci.Reason,
ReasonCode: rci.ReasonCode,
PassiveOnly: revokeOpts.PassiveOnly,
})
if err != nil {
return errs.Wrap(http.StatusInternalServerError, err, "authority.Revoke", opts...)
Expand Down Expand Up @@ -427,6 +429,7 @@ func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) {

resp, err := a.x509CAService.CreateCertificate(&casapi.CreateCertificateRequest{
Template: certTpl,
CSR: cr,
Lifetime: 24 * time.Hour,
Backdate: 1 * time.Minute,
})
Expand Down
24 changes: 24 additions & 0 deletions authority/tls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1231,6 +1231,30 @@ func TestAuthority_Revoke(t *testing.T) {
crt, err := pemutil.ReadCertificate("./testdata/certs/foo.crt")
assert.FatalError(t, err)

return test{
auth: _a,
opts: &RevokeOptions{
Crt: crt,
Serial: "102012593071130646873265215610956555026",
ReasonCode: reasonCode,
Reason: reason,
MTLS: true,
},
}
},
"ok/mTLS-no-provisioner": func() test {
_a := testAuthority(t, WithDatabase(&db.MockAuthDB{}))

crt, err := pemutil.ReadCertificate("./testdata/certs/foo.crt")
assert.FatalError(t, err)
// Filter out provisioner extension.
for i, ext := range crt.Extensions {
if ext.Id.Equal(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64, 1}) {
crt.Extensions = append(crt.Extensions[:i], crt.Extensions[i+1:]...)
break
}
}

return test{
auth: _a,
opts: &RevokeOptions{
Expand Down
26 changes: 22 additions & 4 deletions ca/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ import (
)

type options struct {
configFile string
password []byte
database db.AuthDB
configFile string
password []byte
issuerPassword []byte
database db.AuthDB
}

func (o *options) apply(opts []Option) {
Expand Down Expand Up @@ -55,6 +56,14 @@ func WithPassword(password []byte) Option {
}
}

// WithIssuerPassword sets the given password as the configured certificate
// issuer password in the CA options.
func WithIssuerPassword(password []byte) Option {
return func(o *options) {
o.issuerPassword = password
}
}

// WithDatabase sets the given authority database to the CA options.
func WithDatabase(db db.AuthDB) Option {
return func(o *options) {
Expand Down Expand Up @@ -85,10 +94,18 @@ func New(config *authority.Config, opts ...Option) (*CA, error) {

// Init initializes the CA with the given configuration.
func (ca *CA) Init(config *authority.Config) (*CA, error) {
if l := len(ca.opts.password); l > 0 {
// Intermediate Password.
if len(ca.opts.password) > 0 {
ca.config.Password = string(ca.opts.password)
}

// Certificate issuer password for RA mode.
if len(ca.opts.issuerPassword) > 0 {
if ca.config.AuthorityConfig != nil && ca.config.AuthorityConfig.CertificateIssuer != nil {
ca.config.AuthorityConfig.CertificateIssuer.Password = string(ca.opts.issuerPassword)
}
}

var opts []authority.Option
if ca.opts.database != nil {
opts = append(opts, authority.WithDatabase(ca.opts.database))
Expand Down Expand Up @@ -269,6 +286,7 @@ func (ca *CA) Reload() error {

newCA, err := New(config,
WithPassword(ca.opts.password),
WithIssuerPassword(ca.opts.issuerPassword),
WithConfigFile(ca.opts.configFile),
WithDatabase(ca.auth.GetDatabase()),
)
Expand Down
40 changes: 31 additions & 9 deletions cas/apiv1/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,29 @@ type Options struct {
// The type of the CAS to use.
Type string `json:"type"`

// Path to the credentials file used in CloudCAS
CredentialsFile string `json:"credentialsFile"`
// CertificateAuthority reference:
// In StepCAS the value is the CA url, e.g. "https://ca.smallstep.com:9000".
// In CloudCAS the format is "projects/*/locations/*/certificateAuthorities/*".
CertificateAuthority string `json:"certificateAuthority,omitempty"`

// CertificateAuthority reference. In CloudCAS the format is
// `projects/*/locations/*/certificateAuthorities/*`.
CertificateAuthority string `json:"certificateAuthority"`
// CertificateAuthorityFingerprint is the root fingerprint used to
// authenticate the connection to the CA when using StepCAS.
CertificateAuthorityFingerprint string `json:"certificateAuthorityFingerprint,omitempty"`

// Certificate and signer are the issuer certificate,along with any other bundled certificates to be returned in the chain for consumers, and signer used in SoftCAS.
// They are configured in ca.json crt and key properties.
CertificateChain []*x509.Certificate
Signer crypto.Signer `json:"-"`
// CertificateIssuer contains the configuration used in StepCAS.
CertificateIssuer *CertificateIssuer `json:"certificateIssuer,omitempty"`

// Path to the credentials file used in CloudCAS. If not defined the default
// authentication mechanism provided by Google SDK will be used. See
// https://cloud.google.com/docs/authentication.
CredentialsFile string `json:"credentialsFile,omitempty"`

// Certificate and signer are the issuer certificate, along with any other
// bundled certificates to be returned in the chain for consumers, and
// signer used in SoftCAS. They are configured in ca.json crt and key
// properties.
CertificateChain []*x509.Certificate `json:"-"`
Signer crypto.Signer `json:"-"`

// IsCreator is set to true when we're creating a certificate authority. Is
// used to skip some validations when initializing a CertificateAuthority.
Expand All @@ -39,6 +51,16 @@ type Options struct {
Location string `json:"-"`
}

// CertificateIssuer contains the properties used to use the StepCAS certificate
// authority service.
type CertificateIssuer struct {
Type string `json:"type"`
Provisioner string `json:"provisioner,omitempty"`
Certificate string `json:"crt,omitempty"`
Key string `json:"key,omitempty"`
Password string `json:"password,omitempty"`
}

// Validate checks the fields in Options.
func (o *Options) Validate() error {
var typ Type
Expand Down
12 changes: 8 additions & 4 deletions cas/apiv1/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const (
// CreateCertificateRequest is the request used to sign a new certificate.
type CreateCertificateRequest struct {
Template *x509.Certificate
CSR *x509.CertificateRequest
Lifetime time.Duration
Backdate time.Duration
RequestID string
Expand All @@ -67,6 +68,7 @@ type CreateCertificateResponse struct {
// RenewCertificateRequest is the request used to re-sign a certificate.
type RenewCertificateRequest struct {
Template *x509.Certificate
CSR *x509.CertificateRequest
Lifetime time.Duration
Backdate time.Duration
RequestID string
Expand All @@ -80,10 +82,12 @@ type RenewCertificateResponse struct {

// RevokeCertificateRequest is the request used to revoke a certificate.
type RevokeCertificateRequest struct {
Certificate *x509.Certificate
Reason string
ReasonCode int
RequestID string
Certificate *x509.Certificate
SerialNumber string
Reason string
ReasonCode int
PassiveOnly bool
RequestID string
}

// RevokeCertificateResponse is the response to a revoke certificate request.
Expand Down
23 changes: 23 additions & 0 deletions cas/apiv1/services.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package apiv1

import (
"net/http"
"strings"
)

Expand Down Expand Up @@ -35,6 +36,8 @@ const (
SoftCAS = "softcas"
// CloudCAS is a CertificateAuthorityService using Google Cloud CAS.
CloudCAS = "cloudcas"
// StepCAS is a CertificateAuthorityService using another step-ca instance.
StepCAS = "stepcas"
)

// String returns a string from the type. It will always return the lower case
Expand All @@ -46,3 +49,23 @@ func (t Type) String() string {
}
return strings.ToLower(string(t))
}

// ErrNotImplemented is the type of error returned if an operation is not
// implemented.
type ErrNotImplemented struct {
Message string
}

// ErrNotImplemented implements the error interface.
func (e ErrNotImplemented) Error() string {
if e.Message != "" {
return e.Message
}
return "not implemented"
}

// StatusCode implements the StatusCoder interface and returns the HTTP 501
// error.
func (e ErrNotImplemented) StatusCode() int {
return http.StatusNotImplemented
}
52 changes: 51 additions & 1 deletion cas/apiv1/services_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package apiv1

import "testing"
import (
"testing"
)

func TestType_String(t *testing.T) {
tests := []struct {
Expand All @@ -21,3 +23,51 @@ func TestType_String(t *testing.T) {
})
}
}

func TestErrNotImplemented_Error(t *testing.T) {
type fields struct {
Message string
}
tests := []struct {
name string
fields fields
want string
}{
{"default", fields{""}, "not implemented"},
{"with message", fields{"method not supported"}, "method not supported"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
e := ErrNotImplemented{
Message: tt.fields.Message,
}
if got := e.Error(); got != tt.want {
t.Errorf("ErrNotImplemented.Error() = %v, want %v", got, tt.want)
}
})
}
}

func TestErrNotImplemented_StatusCode(t *testing.T) {
type fields struct {
Message string
}
tests := []struct {
name string
fields fields
want int
}{
{"default", fields{""}, 501},
{"with message", fields{"method not supported"}, 501},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := ErrNotImplemented{
Message: tt.fields.Message,
}
if got := s.StatusCode(); got != tt.want {
t.Errorf("ErrNotImplemented.StatusCode() = %v, want %v", got, tt.want)
}
})
}
}
1 change: 0 additions & 1 deletion cas/cloudcas/cloudcas.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,6 @@ func (c *CloudCAS) GetCertificateAuthority(req *apiv1.GetCertificateAuthorityReq
Name: name,
})
if err != nil {
println(name)
return nil, errors.Wrap(err, "cloudCAS GetCertificateAuthority failed")
}
if len(resp.PemCaCertificates) == 0 {
Expand Down

0 comments on commit c5e4ea0

Please sign in to comment.