certificates/authority/authority.go

467 lines
14 KiB
Go
Raw Normal View History

2018-10-05 21:48:36 +00:00
package authority
import (
"context"
"crypto"
2018-10-05 21:48:36 +00:00
"crypto/sha256"
"crypto/x509"
2018-10-05 21:48:36 +00:00
"encoding/hex"
"log"
2018-10-05 21:48:36 +00:00
"sync"
"time"
"github.com/smallstep/certificates/cas"
"github.com/smallstep/certificates/scep"
"github.com/pkg/errors"
"github.com/smallstep/certificates/authority/provisioner"
casapi "github.com/smallstep/certificates/cas/apiv1"
"github.com/smallstep/certificates/db"
2020-01-10 02:42:26 +00:00
"github.com/smallstep/certificates/kms"
"github.com/smallstep/certificates/kms/apiv1"
2020-01-10 02:42:26 +00:00
kmsapi "github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/certificates/kms/sshagentkms"
2019-11-21 01:01:31 +00:00
"github.com/smallstep/certificates/templates"
2020-08-14 22:33:50 +00:00
"go.step.sm/crypto/pemutil"
"golang.org/x/crypto/ssh"
2018-10-05 21:48:36 +00:00
)
2019-05-27 00:41:10 +00:00
const (
legacyAuthority = "step-certificate-authority"
)
2018-10-20 01:25:59 +00:00
2018-10-05 21:48:36 +00:00
// Authority implements the Certificate Authority internal interface.
type Authority struct {
config *Config
keyManager kms.KeyManager
provisioners *provisioner.Collection
db db.AuthDB
templates *templates.Templates
// X509 CA
x509CAService cas.CertificateAuthorityService
rootX509Certs []*x509.Certificate
federatedX509Certs []*x509.Certificate
certificates *sync.Map
// SCEP CA
scepService *scep.Service
// SSH CA
sshCAUserCertSignKey ssh.Signer
sshCAHostCertSignKey ssh.Signer
sshCAUserCerts []ssh.PublicKey
sshCAHostCerts []ssh.PublicKey
sshCAUserFederatedCerts []ssh.PublicKey
sshCAHostFederatedCerts []ssh.PublicKey
2018-10-05 21:48:36 +00:00
// Do not re-initialize
initOnce bool
startTime time.Time
2019-11-15 02:24:58 +00:00
// Custom functions
sshBastionFunc func(ctx context.Context, user, hostname string) (*Bastion, error)
sshCheckHostFunc func(ctx context.Context, principal string, tok string, roots []*x509.Certificate) (bool, error)
sshGetHostsFunc func(ctx context.Context, cert *x509.Certificate) ([]Host, error)
getIdentityFunc provisioner.GetIdentityFunc
2018-10-05 21:48:36 +00:00
}
// New creates and initiates a new Authority type.
func New(config *Config, opts ...Option) (*Authority, error) {
err := config.Validate()
if err != nil {
2018-10-05 21:48:36 +00:00
return nil, err
}
2018-10-05 21:48:36 +00:00
var a = &Authority{
config: config,
certificates: new(sync.Map),
2018-10-05 21:48:36 +00:00
}
// Apply options.
for _, fn := range opts {
if err := fn(a); err != nil {
return nil, err
}
}
// Initialize authority from options or configuration.
2018-10-05 21:48:36 +00:00
if err := a.init(); err != nil {
return nil, err
}
2018-10-05 21:48:36 +00:00
return a, nil
}
2020-05-06 00:46:22 +00:00
// NewEmbedded initializes an authority that can be embedded in a different
// project without the limitations of the config.
func NewEmbedded(opts ...Option) (*Authority, error) {
a := &Authority{
config: &Config{},
certificates: new(sync.Map),
}
// Apply options.
for _, fn := range opts {
if err := fn(a); err != nil {
return nil, err
}
}
// Validate required options
switch {
case a.config == nil:
return nil, errors.New("cannot create an authority without a configuration")
case len(a.rootX509Certs) == 0 && a.config.Root.HasEmpties():
return nil, errors.New("cannot create an authority without a root certificate")
2020-09-16 20:31:26 +00:00
case a.x509CAService == nil && a.config.IntermediateCert == "":
return nil, errors.New("cannot create an authority without an issuer certificate")
2020-09-16 20:31:26 +00:00
case a.x509CAService == nil && a.config.IntermediateKey == "":
return nil, errors.New("cannot create an authority without an issuer signer")
}
// Initialize config required fields.
a.config.init()
// Initialize authority from options or configuration.
if err := a.init(); err != nil {
return nil, err
}
return a, nil
}
2018-10-05 21:48:36 +00:00
// init performs validation and initializes the fields of an Authority struct.
func (a *Authority) init() error {
// Check if handler has already been validated/initialized.
if a.initOnce {
return nil
}
var err error
2020-01-10 02:42:26 +00:00
// Initialize step-ca Database if it's not already initialized with WithDB.
// If a.config.DB is nil then a simple, barebones in memory DB will be used.
if a.db == nil {
if a.db, err = db.New(a.config.DB); err != nil {
return err
}
}
// Initialize key manager if it has not been set in the options.
2020-01-10 02:42:26 +00:00
if a.keyManager == nil {
2020-01-15 02:47:05 +00:00
var options kmsapi.Options
if a.config.KMS != nil {
options = *a.config.KMS
}
a.keyManager, err = kms.New(context.Background(), options)
2020-01-10 02:42:26 +00:00
if err != nil {
return err
}
}
// Initialize the X.509 CA Service if it has not been set in the options.
if a.x509CAService == nil {
var options casapi.Options
2020-10-20 01:08:51 +00:00
if a.config.AuthorityConfig.Options != nil {
options = *a.config.AuthorityConfig.Options
}
// Read intermediate and create X509 signer for default CAS.
if options.Is(casapi.SoftCAS) {
options.CertificateChain, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert)
if err != nil {
return err
}
options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.IntermediateKey,
Password: []byte(a.config.Password),
})
if err != nil {
return err
}
}
a.x509CAService, err = cas.New(context.Background(), options)
if err != nil {
return err
}
// Get root certificate from CAS.
if srv, ok := a.x509CAService.(casapi.CertificateAuthorityGetter); ok {
resp, err := srv.GetCertificateAuthority(&casapi.GetCertificateAuthorityRequest{
2020-10-20 01:08:51 +00:00
Name: options.CertificateAuthority,
})
if err != nil {
return err
}
a.rootX509Certs = append(a.rootX509Certs, resp.RootCertificate)
2020-09-22 20:23:48 +00:00
sum := sha256.Sum256(resp.RootCertificate.Raw)
log.Printf("Using root fingerprint '%s'", hex.EncodeToString(sum[:]))
}
}
// TODO: decide if this is a good approach for providing the SCEP functionality
// It currently mirrors the logic for the x509CAServer
if a.scepService == nil {
var options casapi.Options
if a.config.AuthorityConfig.Options != nil {
options = *a.config.AuthorityConfig.Options
}
// Read intermediate and create X509 signer and decrypter for default CAS.
if options.Is(casapi.SoftCAS) {
options.CertificateChain, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert)
if err != nil {
return err
}
options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.IntermediateKey,
Password: []byte(a.config.Password),
})
if err != nil {
return err
}
if km, ok := a.keyManager.(apiv1.Decrypter); ok {
options.Decrypter, err = km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{
DecryptionKey: a.config.IntermediateKey,
Password: []byte(a.config.Password),
})
if err != nil {
return err
}
2021-03-10 21:39:20 +00:00
}
}
a.scepService = &scep.Service{
Signer: options.Signer,
Decrypter: options.Decrypter,
}
}
// Read root certificates and store them in the certificates map.
if len(a.rootX509Certs) == 0 {
a.rootX509Certs = make([]*x509.Certificate, len(a.config.Root))
for i, path := range a.config.Root {
crt, err := pemutil.ReadCertificate(path)
if err != nil {
return err
}
a.rootX509Certs[i] = crt
2019-01-07 23:30:28 +00:00
}
}
for _, crt := range a.rootX509Certs {
2019-01-07 23:30:28 +00:00
sum := sha256.Sum256(crt.Raw)
a.certificates.Store(hex.EncodeToString(sum[:]), crt)
}
2018-10-05 21:48:36 +00:00
// Read federated certificates and store them in the certificates map.
if len(a.federatedX509Certs) == 0 {
a.federatedX509Certs = make([]*x509.Certificate, len(a.config.FederatedRoots))
for i, path := range a.config.FederatedRoots {
crt, err := pemutil.ReadCertificate(path)
if err != nil {
return err
}
a.federatedX509Certs[i] = crt
}
}
for _, crt := range a.federatedX509Certs {
sum := sha256.Sum256(crt.Raw)
a.certificates.Store(hex.EncodeToString(sum[:]), crt)
}
// Decrypt and load SSH keys
var tmplVars templates.Step
if a.config.SSH != nil {
if a.config.SSH.HostKey != "" {
2020-01-10 02:42:26 +00:00
signer, err := a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.SSH.HostKey,
2020-01-15 02:47:05 +00:00
Password: []byte(a.config.Password),
2020-01-10 02:42:26 +00:00
})
if err != nil {
return err
}
// If our signer is from sshagentkms, just unwrap it instead of
// wrapping it in another layer, and this prevents crypto from
// erroring out with: ssh: unsupported key type *agent.Key
switch s := signer.(type) {
case *sshagentkms.WrappedSSHSigner:
a.sshCAHostCertSignKey = s.Sshsigner
case crypto.Signer:
a.sshCAHostCertSignKey, err = ssh.NewSignerFromSigner(s)
default:
return errors.Errorf("unsupported signer type %T", signer)
}
if err != nil {
return errors.Wrap(err, "error creating ssh signer")
}
// Append public key to list of host certs
a.sshCAHostCerts = append(a.sshCAHostCerts, a.sshCAHostCertSignKey.PublicKey())
a.sshCAHostFederatedCerts = append(a.sshCAHostFederatedCerts, a.sshCAHostCertSignKey.PublicKey())
}
if a.config.SSH.UserKey != "" {
2020-01-10 02:42:26 +00:00
signer, err := a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.SSH.UserKey,
2020-01-15 02:47:05 +00:00
Password: []byte(a.config.Password),
2020-01-10 02:42:26 +00:00
})
if err != nil {
return err
}
// If our signer is from sshagentkms, just unwrap it instead of
// wrapping it in another layer, and this prevents crypto from
// erroring out with: ssh: unsupported key type *agent.Key
switch s := signer.(type) {
case *sshagentkms.WrappedSSHSigner:
a.sshCAUserCertSignKey = s.Sshsigner
case crypto.Signer:
a.sshCAUserCertSignKey, err = ssh.NewSignerFromSigner(s)
default:
return errors.Errorf("unsupported signer type %T", signer)
}
if err != nil {
return errors.Wrap(err, "error creating ssh signer")
}
// Append public key to list of user certs
2019-10-25 01:36:02 +00:00
a.sshCAUserCerts = append(a.sshCAUserCerts, a.sshCAUserCertSignKey.PublicKey())
a.sshCAUserFederatedCerts = append(a.sshCAUserFederatedCerts, a.sshCAUserCertSignKey.PublicKey())
}
// Append other public keys
for _, key := range a.config.SSH.Keys {
switch key.Type {
case provisioner.SSHHostCert:
if key.Federated {
a.sshCAHostFederatedCerts = append(a.sshCAHostFederatedCerts, key.PublicKey())
} else {
a.sshCAHostCerts = append(a.sshCAHostCerts, key.PublicKey())
}
case provisioner.SSHUserCert:
if key.Federated {
a.sshCAUserFederatedCerts = append(a.sshCAUserFederatedCerts, key.PublicKey())
} else {
a.sshCAUserCerts = append(a.sshCAUserCerts, key.PublicKey())
}
default:
return errors.Errorf("unsupported type %s", key.Type)
}
}
2020-06-23 18:14:26 +00:00
// Configure template variables.
tmplVars.SSH.HostKey = a.sshCAHostCertSignKey.PublicKey()
tmplVars.SSH.UserKey = a.sshCAUserCertSignKey.PublicKey()
// On the templates we skip the first one because there's a distinction
// between the main key and federated keys.
tmplVars.SSH.HostFederatedKeys = append(tmplVars.SSH.HostFederatedKeys, a.sshCAHostFederatedCerts[1:]...)
tmplVars.SSH.UserFederatedKeys = append(tmplVars.SSH.UserFederatedKeys, a.sshCAUserFederatedCerts[1:]...)
}
// Merge global and configuration claims
claimer, err := provisioner.NewClaimer(a.config.AuthorityConfig.Claims, globalProvisionerClaims)
if err != nil {
return err
}
// TODO: should we also be combining the ssh federated roots here?
// If we rotate ssh roots keys, sshpop provisioner will lose ability to
// validate old SSH certificates, unless they are added as federated certs.
sshKeys, err := a.GetSSHRoots(context.Background())
if err != nil {
return err
}
// Initialize provisioners
audiences := a.config.getAudiences()
a.provisioners = provisioner.NewCollection(audiences)
config := provisioner.Config{
Claims: claimer.Claims(),
Audiences: audiences,
DB: a.db,
SSHKeys: &provisioner.SSHKeys{
UserKeys: sshKeys.UserKeys,
HostKeys: sshKeys.HostKeys,
},
GetIdentityFunc: a.getIdentityFunc,
}
// Check if a KMS with decryption capability is required and available
if a.requiresDecrypter() {
if _, ok := a.keyManager.(apiv1.Decrypter); !ok {
return errors.New("keymanager doesn't provide crypto.Decrypter")
}
}
// Store all the provisioners
2018-10-05 21:48:36 +00:00
for _, p := range a.config.AuthorityConfig.Provisioners {
if err := p.Init(config); err != nil {
return err
}
if err := a.provisioners.Store(p); err != nil {
return err
2018-10-05 21:48:36 +00:00
}
}
// Configure templates, currently only ssh templates are supported.
if a.sshCAHostCertSignKey != nil || a.sshCAUserCertSignKey != nil {
a.templates = a.config.Templates
if a.templates == nil {
a.templates = templates.DefaultTemplates()
}
if a.templates.Data == nil {
a.templates.Data = make(map[string]interface{})
}
a.templates.Data["Step"] = tmplVars
}
// JWT numeric dates are seconds.
a.startTime = time.Now().Truncate(time.Second)
2018-10-05 21:48:36 +00:00
// Set flag indicating that initialization has been completed, and should
// not be repeated.
a.initOnce = true
return nil
}
2019-05-11 00:54:18 +00:00
// GetDatabase returns the authority database. If the configuration does not
// define a database, GetDatabase will return a db.SimpleDB instance.
func (a *Authority) GetDatabase() db.AuthDB {
return a.db
}
// Shutdown safely shuts down any clients, databases, etc. held by the Authority.
func (a *Authority) Shutdown() error {
if err := a.keyManager.Close(); err != nil {
log.Printf("error closing the key manager: %v", err)
}
return a.db.Shutdown()
}
// CloseForReload closes internal services, to allow a safe reload.
func (a *Authority) CloseForReload() {
if err := a.keyManager.Close(); err != nil {
log.Printf("error closing the key manager: %v", err)
}
}
// requiresDecrypter iterates over the configured provisioners
// and determines if the Authority requires a KMS that provides
// a crypto.Decrypter by implementing the apiv1.Decrypter
// interface. Currently only the SCEP provider requires this,
// but others may be added in the future.
func (a *Authority) requiresDecrypter() bool {
for _, p := range a.config.AuthorityConfig.Provisioners {
if p.GetType() == provisioner.TypeSCEP {
return true
}
}
return false
}
// GetSCEPService returns the configured SCEP Service
// TODO: this function is intended to exist temporarily
// in order to make SCEP work more easily. It can be
// made more correct by using the right interfaces/abstractions
// after it works as expected.
func (a *Authority) GetSCEPService() scep.Service {
return *a.scepService
}