2018-10-05 21:48:36 +00:00
|
|
|
package authority
|
|
|
|
|
|
|
|
import (
|
2022-09-21 01:52:47 +00:00
|
|
|
"bytes"
|
2019-12-10 07:14:56 +00:00
|
|
|
"context"
|
2020-11-03 13:26:46 +00:00
|
|
|
"crypto"
|
2023-05-26 21:52:24 +00:00
|
|
|
"crypto/rsa"
|
2018-10-05 21:48:36 +00:00
|
|
|
"crypto/sha256"
|
2019-01-05 01:51:32 +00:00
|
|
|
"crypto/x509"
|
2018-10-05 21:48:36 +00:00
|
|
|
"encoding/hex"
|
2020-05-08 01:59:30 +00:00
|
|
|
"log"
|
2022-09-30 00:16:26 +00:00
|
|
|
"net/http"
|
2021-07-20 02:28:06 +00:00
|
|
|
"strings"
|
2018-10-05 21:48:36 +00:00
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
2019-08-01 22:04:56 +00:00
|
|
|
"github.com/pkg/errors"
|
2022-03-31 14:12:29 +00:00
|
|
|
"golang.org/x/crypto/ssh"
|
|
|
|
|
2022-08-09 00:58:18 +00:00
|
|
|
"go.step.sm/crypto/kms"
|
|
|
|
kmsapi "go.step.sm/crypto/kms/apiv1"
|
|
|
|
"go.step.sm/crypto/kms/sshagentkms"
|
2022-03-31 14:12:29 +00:00
|
|
|
"go.step.sm/crypto/pemutil"
|
|
|
|
"go.step.sm/linkedca"
|
|
|
|
|
2021-05-18 04:07:25 +00:00
|
|
|
"github.com/smallstep/certificates/authority/admin"
|
2021-05-03 19:48:20 +00:00
|
|
|
adminDBNosql "github.com/smallstep/certificates/authority/admin/db/nosql"
|
|
|
|
"github.com/smallstep/certificates/authority/administrator"
|
2021-05-03 19:48:20 +00:00
|
|
|
"github.com/smallstep/certificates/authority/config"
|
2022-09-21 01:52:47 +00:00
|
|
|
"github.com/smallstep/certificates/authority/internal/constraints"
|
2022-03-08 12:26:07 +00:00
|
|
|
"github.com/smallstep/certificates/authority/policy"
|
2019-03-06 23:00:23 +00:00
|
|
|
"github.com/smallstep/certificates/authority/provisioner"
|
2021-08-11 00:30:33 +00:00
|
|
|
"github.com/smallstep/certificates/cas"
|
2020-09-09 02:26:32 +00:00
|
|
|
casapi "github.com/smallstep/certificates/cas/apiv1"
|
2019-03-05 08:07:13 +00:00
|
|
|
"github.com/smallstep/certificates/db"
|
2021-08-11 00:30:33 +00:00
|
|
|
"github.com/smallstep/certificates/scep"
|
2019-11-21 01:01:31 +00:00
|
|
|
"github.com/smallstep/certificates/templates"
|
2021-05-03 19:48:20 +00:00
|
|
|
"github.com/smallstep/nosql"
|
2018-10-05 21:48:36 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Authority implements the Certificate Authority internal interface.
|
|
|
|
type Authority struct {
|
2021-07-20 02:28:06 +00:00
|
|
|
config *config.Config
|
|
|
|
keyManager kms.KeyManager
|
|
|
|
provisioners *provisioner.Collection
|
|
|
|
admins *administrator.Collection
|
|
|
|
db db.AuthDB
|
|
|
|
adminDB admin.DB
|
|
|
|
templates *templates.Templates
|
|
|
|
linkedCAToken string
|
2022-09-30 00:16:26 +00:00
|
|
|
webhookClient *http.Client
|
2020-01-11 02:33:48 +00:00
|
|
|
|
|
|
|
// X509 CA
|
2022-09-21 01:52:47 +00:00
|
|
|
password []byte
|
|
|
|
issuerPassword []byte
|
|
|
|
x509CAService cas.CertificateAuthorityService
|
|
|
|
rootX509Certs []*x509.Certificate
|
|
|
|
rootX509CertPool *x509.CertPool
|
|
|
|
federatedX509Certs []*x509.Certificate
|
|
|
|
intermediateX509Certs []*x509.Certificate
|
|
|
|
certificates *sync.Map
|
|
|
|
x509Enforcers []provisioner.CertificateEnforcer
|
2020-01-11 02:33:48 +00:00
|
|
|
|
2021-02-25 23:32:21 +00:00
|
|
|
// SCEP CA
|
2024-04-10 01:37:29 +00:00
|
|
|
scepOptions *scep.Options
|
|
|
|
validateSCEP bool
|
|
|
|
scepAuthority *scep.Authority
|
|
|
|
scepKeyManager provisioner.SCEPKeyManager
|
2021-02-25 23:32:21 +00:00
|
|
|
|
2020-01-11 02:33:48 +00:00
|
|
|
// SSH CA
|
2021-09-16 18:55:41 +00:00
|
|
|
sshHostPassword []byte
|
|
|
|
sshUserPassword []byte
|
2019-10-09 01:09:41 +00:00
|
|
|
sshCAUserCertSignKey ssh.Signer
|
|
|
|
sshCAHostCertSignKey ssh.Signer
|
|
|
|
sshCAUserCerts []ssh.PublicKey
|
|
|
|
sshCAHostCerts []ssh.PublicKey
|
|
|
|
sshCAUserFederatedCerts []ssh.PublicKey
|
|
|
|
sshCAHostFederatedCerts []ssh.PublicKey
|
2020-01-11 02:33:48 +00:00
|
|
|
|
2021-11-04 06:05:07 +00:00
|
|
|
// CRL vars
|
2022-10-27 01:55:24 +00:00
|
|
|
crlTicker *time.Ticker
|
|
|
|
crlStopper chan struct{}
|
|
|
|
crlMutex sync.Mutex
|
2021-11-04 06:05:07 +00:00
|
|
|
|
2022-10-11 10:19:29 +00:00
|
|
|
// If true, do not re-initialize
|
2020-01-11 02:33:48 +00:00
|
|
|
initOnce bool
|
|
|
|
startTime time.Time
|
|
|
|
|
2019-11-15 02:24:58 +00:00
|
|
|
// Custom functions
|
2022-03-10 21:01:08 +00:00
|
|
|
sshBastionFunc func(ctx context.Context, user, hostname string) (*config.Bastion, error)
|
|
|
|
sshCheckHostFunc func(ctx context.Context, principal string, tok string, roots []*x509.Certificate) (bool, error)
|
|
|
|
sshGetHostsFunc func(ctx context.Context, cert *x509.Certificate) ([]config.Host, error)
|
|
|
|
getIdentityFunc provisioner.GetIdentityFunc
|
|
|
|
authorizeRenewFunc provisioner.AuthorizeRenewFunc
|
|
|
|
authorizeSSHRenewFunc provisioner.AuthorizeSSHRenewFunc
|
2021-05-03 19:48:20 +00:00
|
|
|
|
2022-09-21 01:52:47 +00:00
|
|
|
// Constraints and Policy engines
|
|
|
|
constraintsEngine *constraints.Engine
|
|
|
|
policyEngine *policy.Engine
|
2022-03-08 12:26:07 +00:00
|
|
|
|
2021-05-03 19:48:20 +00:00
|
|
|
adminMutex sync.RWMutex
|
2022-05-12 00:04:43 +00:00
|
|
|
|
2022-10-11 10:19:29 +00:00
|
|
|
// If true, do not initialize the authority
|
2022-05-12 00:04:43 +00:00
|
|
|
skipInit bool
|
2022-10-11 10:19:29 +00:00
|
|
|
|
2022-10-21 09:11:42 +00:00
|
|
|
// If true, do not output initialization logs
|
2022-10-11 10:19:29 +00:00
|
|
|
quietInit bool
|
2024-01-26 07:47:27 +00:00
|
|
|
|
|
|
|
// Called whenever applicable, in order to instrument the authority.
|
|
|
|
meter Meter
|
2018-10-05 21:48:36 +00:00
|
|
|
}
|
|
|
|
|
2022-05-12 00:04:43 +00:00
|
|
|
// Info contains information about the authority.
|
2022-04-05 17:59:25 +00:00
|
|
|
type Info struct {
|
2022-03-30 23:08:10 +00:00
|
|
|
StartTime time.Time
|
|
|
|
RootX509Certs []*x509.Certificate
|
|
|
|
SSHCAUserPublicKey []byte
|
|
|
|
SSHCAHostPublicKey []byte
|
2022-04-04 19:16:37 +00:00
|
|
|
DNSNames []string
|
2022-03-30 22:48:42 +00:00
|
|
|
}
|
|
|
|
|
2018-10-05 21:48:36 +00:00
|
|
|
// New creates and initiates a new Authority type.
|
2021-10-08 18:59:57 +00:00
|
|
|
func New(cfg *config.Config, opts ...Option) (*Authority, error) {
|
|
|
|
err := cfg.Validate()
|
2018-10-25 22:40:12 +00:00
|
|
|
if err != nil {
|
2018-10-05 21:48:36 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2018-10-25 22:40:12 +00:00
|
|
|
|
2018-10-05 21:48:36 +00:00
|
|
|
var a = &Authority{
|
2021-10-08 18:59:57 +00:00
|
|
|
config: cfg,
|
2019-03-06 23:00:23 +00:00
|
|
|
certificates: new(sync.Map),
|
2023-09-25 20:24:13 +00:00
|
|
|
validateSCEP: true,
|
2024-01-26 07:47:27 +00:00
|
|
|
meter: noopMeter{},
|
2018-10-05 21:48:36 +00:00
|
|
|
}
|
2020-01-11 02:33:48 +00:00
|
|
|
|
|
|
|
// Apply options.
|
|
|
|
for _, fn := range opts {
|
|
|
|
if err := fn(a); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-05-10 22:58:37 +00:00
|
|
|
}
|
2024-01-26 07:47:27 +00:00
|
|
|
if a.keyManager != nil {
|
2024-04-10 14:38:31 +00:00
|
|
|
a.keyManager = newInstrumentedKeyManager(a.keyManager, a.meter)
|
2024-01-26 07:47:27 +00:00
|
|
|
}
|
2020-01-11 02:33:48 +00:00
|
|
|
|
2022-05-12 00:04:43 +00:00
|
|
|
if !a.skipInit {
|
|
|
|
// Initialize authority from options or configuration.
|
|
|
|
if err := a.init(); err != nil {
|
2022-04-25 17:23:07 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2018-10-05 21:48:36 +00:00
|
|
|
}
|
2020-01-11 02:33:48 +00:00
|
|
|
|
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) {
|
2020-05-05 01:52:18 +00:00
|
|
|
a := &Authority{
|
2021-05-03 19:48:20 +00:00
|
|
|
config: &config.Config{},
|
2020-05-05 01:52:18 +00:00
|
|
|
certificates: new(sync.Map),
|
2024-01-26 07:47:27 +00:00
|
|
|
meter: noopMeter{},
|
2020-05-05 01:52:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Apply options.
|
|
|
|
for _, fn := range opts {
|
|
|
|
if err := fn(a); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2024-01-26 07:47:27 +00:00
|
|
|
if a.keyManager != nil {
|
2024-04-10 14:38:31 +00:00
|
|
|
a.keyManager = newInstrumentedKeyManager(a.keyManager, a.meter)
|
2024-01-26 07:47:27 +00:00
|
|
|
}
|
2020-05-05 01:52:18 +00:00
|
|
|
|
|
|
|
// Validate required options
|
|
|
|
switch {
|
2020-05-06 20:00:42 +00:00
|
|
|
case a.config == nil:
|
|
|
|
return nil, errors.New("cannot create an authority without a configuration")
|
2020-05-05 01:52:18 +00:00
|
|
|
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 == "":
|
2020-05-05 01:52:18 +00:00
|
|
|
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 == "":
|
2020-05-05 01:52:18 +00:00
|
|
|
return nil, errors.New("cannot create an authority without an issuer signer")
|
|
|
|
}
|
|
|
|
|
2020-05-06 20:00:42 +00:00
|
|
|
// Initialize config required fields.
|
2021-05-03 19:48:20 +00:00
|
|
|
a.config.Init()
|
2020-05-06 20:00:42 +00:00
|
|
|
|
2022-05-12 00:04:43 +00:00
|
|
|
if !a.skipInit {
|
|
|
|
// Initialize authority from options or configuration.
|
|
|
|
if err := a.init(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-05-05 01:52:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return a, nil
|
|
|
|
}
|
|
|
|
|
2022-04-26 19:54:54 +00:00
|
|
|
type authorityKey struct{}
|
|
|
|
|
|
|
|
// NewContext adds the given authority to the context.
|
|
|
|
func NewContext(ctx context.Context, a *Authority) context.Context {
|
|
|
|
return context.WithValue(ctx, authorityKey{}, a)
|
|
|
|
}
|
|
|
|
|
|
|
|
// FromContext returns the current authority from the given context.
|
|
|
|
func FromContext(ctx context.Context) (a *Authority, ok bool) {
|
|
|
|
a, ok = ctx.Value(authorityKey{}).(*Authority)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-04-27 18:38:06 +00:00
|
|
|
// MustFromContext returns the current authority from the given context. It will
|
|
|
|
// panic if the authority is not in the context.
|
|
|
|
func MustFromContext(ctx context.Context) *Authority {
|
2023-11-29 04:58:58 +00:00
|
|
|
var (
|
|
|
|
a *Authority
|
|
|
|
ok bool
|
|
|
|
)
|
|
|
|
if a, ok = FromContext(ctx); !ok {
|
2022-04-27 18:38:06 +00:00
|
|
|
panic("authority is not in the context")
|
|
|
|
}
|
2023-11-29 04:58:58 +00:00
|
|
|
return a
|
2022-04-26 19:54:54 +00:00
|
|
|
}
|
|
|
|
|
2022-04-25 17:23:07 +00:00
|
|
|
// ReloadAdminResources reloads admins and provisioners from the DB.
|
|
|
|
func (a *Authority) ReloadAdminResources(ctx context.Context) error {
|
2021-05-03 19:48:20 +00:00
|
|
|
var (
|
|
|
|
provList provisioner.List
|
|
|
|
adminList []*linkedca.Admin
|
|
|
|
)
|
|
|
|
if a.config.AuthorityConfig.EnableAdmin {
|
|
|
|
provs, err := a.adminDB.GetProvisioners(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return admin.WrapErrorISE(err, "error getting provisioners to initialize authority")
|
|
|
|
}
|
|
|
|
provList, err = provisionerListToCertificates(provs)
|
|
|
|
if err != nil {
|
|
|
|
return admin.WrapErrorISE(err, "error converting provisioner list to certificates")
|
|
|
|
}
|
|
|
|
adminList, err = a.adminDB.GetAdmins(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return admin.WrapErrorISE(err, "error getting admins to initialize authority")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
provList = a.config.AuthorityConfig.Provisioners
|
|
|
|
adminList = a.config.AuthorityConfig.Admins
|
2021-05-18 04:07:25 +00:00
|
|
|
}
|
|
|
|
|
2021-05-03 19:48:20 +00:00
|
|
|
provisionerConfig, err := a.generateProvisionerConfig(ctx)
|
2021-05-18 04:07:25 +00:00
|
|
|
if err != nil {
|
2021-05-03 19:48:20 +00:00
|
|
|
return admin.WrapErrorISE(err, "error generating provisioner config")
|
2021-05-18 04:07:25 +00:00
|
|
|
}
|
2021-05-03 19:48:20 +00:00
|
|
|
|
|
|
|
// Create provisioner collection.
|
|
|
|
provClxn := provisioner.NewCollection(provisionerConfig.Audiences)
|
|
|
|
for _, p := range provList {
|
2022-02-28 19:04:40 +00:00
|
|
|
if err := p.Init(provisionerConfig); err != nil {
|
2021-05-18 04:07:25 +00:00
|
|
|
return err
|
|
|
|
}
|
2021-05-03 19:48:20 +00:00
|
|
|
if err := provClxn.Store(p); err != nil {
|
2021-05-18 04:07:25 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2021-05-03 19:48:20 +00:00
|
|
|
// Create admin collection.
|
|
|
|
adminClxn := administrator.NewCollection(provClxn)
|
|
|
|
for _, adm := range adminList {
|
|
|
|
p, ok := provClxn.Load(adm.ProvisionerId)
|
|
|
|
if !ok {
|
|
|
|
return admin.NewErrorISE("provisioner %s not found when loading admin %s",
|
|
|
|
adm.ProvisionerId, adm.Id)
|
|
|
|
}
|
|
|
|
if err := adminClxn.Store(adm, p); err != nil {
|
2021-05-18 04:07:25 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2021-05-03 19:48:20 +00:00
|
|
|
|
|
|
|
a.config.AuthorityConfig.Provisioners = provList
|
|
|
|
a.provisioners = provClxn
|
|
|
|
a.config.AuthorityConfig.Admins = adminList
|
|
|
|
a.admins = adminClxn
|
2022-03-15 14:51:45 +00:00
|
|
|
|
2023-08-02 16:26:50 +00:00
|
|
|
switch {
|
|
|
|
case a.requiresSCEP() && a.GetSCEP() == nil:
|
|
|
|
// TODO(hs): try to initialize SCEP here too? It's a bit
|
|
|
|
// problematic if this method is called as part of an update
|
|
|
|
// via Admin API and a password needs to be provided.
|
|
|
|
case a.requiresSCEP() && a.GetSCEP() != nil:
|
|
|
|
// update the SCEP Authority with the currently active SCEP
|
|
|
|
// provisioner names and revalidate the configuration.
|
2023-06-01 13:46:21 +00:00
|
|
|
a.scepAuthority.UpdateProvisioners(a.getSCEPProvisionerNames())
|
|
|
|
if err := a.scepAuthority.Validate(); err != nil {
|
|
|
|
log.Printf("failed validating SCEP authority: %v\n", err)
|
|
|
|
}
|
2023-08-02 16:26:50 +00:00
|
|
|
case !a.requiresSCEP() && a.GetSCEP() != nil:
|
|
|
|
// TODO(hs): don't remove the authority if we can't also
|
|
|
|
// reload it.
|
|
|
|
//a.scepAuthority = nil
|
2023-06-01 12:43:32 +00:00
|
|
|
}
|
|
|
|
|
2021-05-18 04:07:25 +00:00
|
|
|
return 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
|
2022-05-10 23:51:09 +00:00
|
|
|
ctx := NewContext(context.Background(), a)
|
2020-01-10 02:42:26 +00:00
|
|
|
|
2021-09-16 18:55:41 +00:00
|
|
|
// Set password if they are not set.
|
|
|
|
var configPassword []byte
|
|
|
|
if a.config.Password != "" {
|
|
|
|
configPassword = []byte(a.config.Password)
|
|
|
|
}
|
|
|
|
if configPassword != nil && a.password == nil {
|
|
|
|
a.password = configPassword
|
|
|
|
}
|
|
|
|
if a.sshHostPassword == nil {
|
|
|
|
a.sshHostPassword = a.password
|
|
|
|
}
|
|
|
|
if a.sshUserPassword == nil {
|
|
|
|
a.sshUserPassword = a.password
|
|
|
|
}
|
|
|
|
|
2021-08-02 19:13:39 +00:00
|
|
|
// Automatically enable admin for all linked cas.
|
|
|
|
if a.linkedCAToken != "" {
|
|
|
|
a.config.AuthorityConfig.EnableAdmin = true
|
|
|
|
}
|
|
|
|
|
2020-09-21 22:27:20 +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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-11 02:33:48 +00:00
|
|
|
// 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
|
|
|
|
}
|
2022-05-10 23:51:09 +00:00
|
|
|
a.keyManager, err = kms.New(ctx, options)
|
2020-01-10 02:42:26 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-01-26 07:47:27 +00:00
|
|
|
|
2024-04-10 14:38:31 +00:00
|
|
|
a.keyManager = newInstrumentedKeyManager(a.keyManager, a.meter)
|
2020-01-10 02:42:26 +00:00
|
|
|
}
|
|
|
|
|
2022-03-23 21:56:39 +00:00
|
|
|
// Initialize linkedca client if necessary. On a linked RA, the issuer
|
|
|
|
// configuration might come from majordomo.
|
|
|
|
var linkedcaClient *linkedCaClient
|
|
|
|
if a.config.AuthorityConfig.EnableAdmin && a.linkedCAToken != "" && a.adminDB == nil {
|
|
|
|
linkedcaClient, err = newLinkedCAClient(a.linkedCAToken)
|
2020-01-10 02:42:26 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-03-23 21:56:39 +00:00
|
|
|
// If authorityId is configured make sure it matches the one in the token
|
|
|
|
if id := a.config.AuthorityConfig.AuthorityID; id != "" && !strings.EqualFold(id, linkedcaClient.authorityID) {
|
|
|
|
return errors.New("error initializing linkedca: token authority and configured authority do not match")
|
|
|
|
}
|
2022-08-03 02:28:49 +00:00
|
|
|
a.config.AuthorityConfig.AuthorityID = linkedcaClient.authorityID
|
2022-03-23 21:56:39 +00:00
|
|
|
linkedcaClient.Run()
|
2020-01-10 02:42:26 +00:00
|
|
|
}
|
|
|
|
|
2020-09-21 22:27:20 +00:00
|
|
|
// 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
|
2020-09-21 22:27:20 +00:00
|
|
|
}
|
|
|
|
|
2022-08-03 02:28:49 +00:00
|
|
|
// AuthorityID might be empty. It's always available linked CAs/RAs.
|
|
|
|
options.AuthorityID = a.config.AuthorityConfig.AuthorityID
|
|
|
|
|
2022-03-23 21:56:39 +00:00
|
|
|
// Configure linked RA
|
|
|
|
if linkedcaClient != nil && options.CertificateAuthority == "" {
|
2022-05-10 23:51:09 +00:00
|
|
|
conf, err := linkedcaClient.GetConfiguration(ctx)
|
2022-03-23 21:56:39 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if conf.RaConfig != nil {
|
|
|
|
options.CertificateAuthority = conf.RaConfig.CaUrl
|
|
|
|
options.CertificateAuthorityFingerprint = conf.RaConfig.Fingerprint
|
|
|
|
options.CertificateIssuer = &casapi.CertificateIssuer{
|
|
|
|
Type: conf.RaConfig.Provisioner.Type.String(),
|
|
|
|
Provisioner: conf.RaConfig.Provisioner.Name,
|
|
|
|
}
|
2022-08-16 21:39:02 +00:00
|
|
|
// Configure the RA authority type if needed
|
|
|
|
if options.Type == "" {
|
|
|
|
options.Type = casapi.StepCAS
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Remote configuration is currently only supported on a linked RA
|
|
|
|
if sc := conf.ServerConfig; sc != nil {
|
|
|
|
if a.config.Address == "" {
|
|
|
|
a.config.Address = sc.Address
|
|
|
|
}
|
|
|
|
if len(a.config.DNSNames) == 0 {
|
|
|
|
a.config.DNSNames = sc.DnsNames
|
|
|
|
}
|
2022-03-23 21:56:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-16 18:55:41 +00:00
|
|
|
// Set the issuer password if passed in the flags.
|
|
|
|
if options.CertificateIssuer != nil && a.issuerPassword != nil {
|
|
|
|
options.CertificateIssuer.Password = string(a.issuerPassword)
|
|
|
|
}
|
|
|
|
|
2020-09-21 22:27:20 +00:00
|
|
|
// Read intermediate and create X509 signer for default CAS.
|
|
|
|
if options.Is(casapi.SoftCAS) {
|
2020-12-24 04:41:10 +00:00
|
|
|
options.CertificateChain, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert)
|
2020-09-21 22:27:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
|
|
|
|
SigningKey: a.config.IntermediateKey,
|
2022-08-23 19:43:48 +00:00
|
|
|
Password: a.password,
|
2020-09-21 22:27:20 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-09-23 17:50:44 +00:00
|
|
|
// If not defined with an option, add intermediates to the list of
|
|
|
|
// certificates used for name constraints validation at issuance
|
2022-09-22 18:05:06 +00:00
|
|
|
// time.
|
2022-09-21 01:52:47 +00:00
|
|
|
if len(a.intermediateX509Certs) == 0 {
|
|
|
|
a.intermediateX509Certs = append(a.intermediateX509Certs, options.CertificateChain...)
|
|
|
|
}
|
2020-09-21 22:27:20 +00:00
|
|
|
}
|
2022-05-10 23:51:09 +00:00
|
|
|
a.x509CAService, err = cas.New(ctx, options)
|
2020-09-21 22:27:20 +00:00
|
|
|
if err != nil {
|
2019-05-10 22:58:37 +00:00
|
|
|
return err
|
|
|
|
}
|
2020-09-21 22:27:20 +00:00
|
|
|
|
|
|
|
// 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,
|
2020-09-21 22:27:20 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
a.rootX509Certs = append(a.rootX509Certs, resp.RootCertificate)
|
|
|
|
}
|
2019-03-05 08:07:13 +00:00
|
|
|
}
|
|
|
|
|
2020-01-11 02:33:48 +00:00
|
|
|
// Read root certificates and store them in the certificates map.
|
|
|
|
if len(a.rootX509Certs) == 0 {
|
2022-11-09 01:06:22 +00:00
|
|
|
a.rootX509Certs = make([]*x509.Certificate, 0, len(a.config.Root))
|
|
|
|
for _, path := range a.config.Root {
|
|
|
|
crts, err := pemutil.ReadCertificateBundle(path)
|
2020-01-11 02:33:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-11-09 01:06:22 +00:00
|
|
|
a.rootX509Certs = append(a.rootX509Certs, crts...)
|
2019-01-07 23:30:28 +00:00
|
|
|
}
|
2020-01-11 02:33:48 +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
|
|
|
|
2021-05-03 19:48:20 +00:00
|
|
|
a.rootX509CertPool = x509.NewCertPool()
|
|
|
|
for _, cert := range a.rootX509Certs {
|
|
|
|
a.rootX509CertPool.AddCert(cert)
|
|
|
|
}
|
|
|
|
|
2020-01-11 02:33:48 +00:00
|
|
|
// Read federated certificates and store them in the certificates map.
|
|
|
|
if len(a.federatedX509Certs) == 0 {
|
2022-11-09 01:06:22 +00:00
|
|
|
a.federatedX509Certs = make([]*x509.Certificate, 0, len(a.config.FederatedRoots))
|
|
|
|
for _, path := range a.config.FederatedRoots {
|
|
|
|
crts, err := pemutil.ReadCertificateBundle(path)
|
2020-01-11 02:33:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-11-09 01:06:22 +00:00
|
|
|
a.federatedX509Certs = append(a.federatedX509Certs, crts...)
|
2019-01-05 01:51:32 +00:00
|
|
|
}
|
2020-01-11 02:33:48 +00:00
|
|
|
}
|
|
|
|
for _, crt := range a.federatedX509Certs {
|
2019-01-05 01:51:32 +00:00
|
|
|
sum := sha256.Sum256(crt.Raw)
|
|
|
|
a.certificates.Store(hex.EncodeToString(sum[:]), crt)
|
|
|
|
}
|
|
|
|
|
2019-08-01 22:04:56 +00:00
|
|
|
// Decrypt and load SSH keys
|
2020-06-17 00:26:18 +00:00
|
|
|
var tmplVars templates.Step
|
2019-08-01 22:04:56 +00:00
|
|
|
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,
|
2022-08-23 19:43:48 +00:00
|
|
|
Password: a.sshHostPassword,
|
2020-01-10 02:42:26 +00:00
|
|
|
})
|
2019-08-01 22:04:56 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-11-03 13:26:46 +00:00
|
|
|
// 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:
|
2022-08-09 00:58:18 +00:00
|
|
|
a.sshCAHostCertSignKey = s.Signer
|
2020-11-03 13:26:46 +00:00
|
|
|
case crypto.Signer:
|
|
|
|
a.sshCAHostCertSignKey, err = ssh.NewSignerFromSigner(s)
|
|
|
|
default:
|
|
|
|
return errors.Errorf("unsupported signer type %T", signer)
|
|
|
|
}
|
2019-09-25 02:12:13 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "error creating ssh signer")
|
|
|
|
}
|
2019-10-09 01:09:41 +00:00
|
|
|
// Append public key to list of host certs
|
|
|
|
a.sshCAHostCerts = append(a.sshCAHostCerts, a.sshCAHostCertSignKey.PublicKey())
|
|
|
|
a.sshCAHostFederatedCerts = append(a.sshCAHostFederatedCerts, a.sshCAHostCertSignKey.PublicKey())
|
2019-08-01 22:04:56 +00:00
|
|
|
}
|
|
|
|
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,
|
2022-08-23 19:43:48 +00:00
|
|
|
Password: a.sshUserPassword,
|
2020-01-10 02:42:26 +00:00
|
|
|
})
|
2019-08-01 22:04:56 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-11-03 13:26:46 +00:00
|
|
|
// 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:
|
2022-08-09 00:58:18 +00:00
|
|
|
a.sshCAUserCertSignKey = s.Signer
|
2020-11-03 13:26:46 +00:00
|
|
|
case crypto.Signer:
|
|
|
|
a.sshCAUserCertSignKey, err = ssh.NewSignerFromSigner(s)
|
|
|
|
default:
|
|
|
|
return errors.Errorf("unsupported signer type %T", signer)
|
|
|
|
}
|
2019-09-25 02:12:13 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "error creating ssh signer")
|
|
|
|
}
|
2019-10-09 01:09:41 +00:00
|
|
|
// Append public key to list of user certs
|
2019-10-25 01:36:02 +00:00
|
|
|
a.sshCAUserCerts = append(a.sshCAUserCerts, a.sshCAUserCertSignKey.PublicKey())
|
2019-10-09 01:09:41 +00:00
|
|
|
a.sshCAUserFederatedCerts = append(a.sshCAUserFederatedCerts, a.sshCAUserCertSignKey.PublicKey())
|
|
|
|
}
|
|
|
|
|
2021-09-28 22:07:09 +00:00
|
|
|
// Append other public keys and add them to the template variables.
|
2019-10-09 01:09:41 +00:00
|
|
|
for _, key := range a.config.SSH.Keys {
|
2021-09-28 22:07:09 +00:00
|
|
|
publicKey := key.PublicKey()
|
2019-10-09 01:09:41 +00:00
|
|
|
switch key.Type {
|
|
|
|
case provisioner.SSHHostCert:
|
|
|
|
if key.Federated {
|
2021-09-28 22:07:09 +00:00
|
|
|
a.sshCAHostFederatedCerts = append(a.sshCAHostFederatedCerts, publicKey)
|
2019-10-09 01:09:41 +00:00
|
|
|
} else {
|
2021-09-28 22:07:09 +00:00
|
|
|
a.sshCAHostCerts = append(a.sshCAHostCerts, publicKey)
|
2019-10-09 01:09:41 +00:00
|
|
|
}
|
|
|
|
case provisioner.SSHUserCert:
|
|
|
|
if key.Federated {
|
2021-09-28 22:07:09 +00:00
|
|
|
a.sshCAUserFederatedCerts = append(a.sshCAUserFederatedCerts, publicKey)
|
2019-10-09 01:09:41 +00:00
|
|
|
} else {
|
2021-09-28 22:07:09 +00:00
|
|
|
a.sshCAUserCerts = append(a.sshCAUserCerts, publicKey)
|
2019-10-09 01:09:41 +00:00
|
|
|
}
|
|
|
|
default:
|
|
|
|
return errors.Errorf("unsupported type %s", key.Type)
|
|
|
|
}
|
2019-08-01 22:04:56 +00:00
|
|
|
}
|
|
|
|
}
|
2019-07-24 01:46:43 +00:00
|
|
|
|
2021-09-29 01:50:45 +00:00
|
|
|
// Configure template variables. On the template variables HostFederatedKeys
|
|
|
|
// and UserFederatedKeys we will skip the actual CA that will be available
|
|
|
|
// in HostKey and UserKey.
|
|
|
|
//
|
|
|
|
// We cannot do it in the previous blocks because this configuration can be
|
|
|
|
// injected using options.
|
|
|
|
if a.sshCAHostCertSignKey != nil {
|
|
|
|
tmplVars.SSH.HostKey = a.sshCAHostCertSignKey.PublicKey()
|
|
|
|
tmplVars.SSH.HostFederatedKeys = append(tmplVars.SSH.HostFederatedKeys, a.sshCAHostFederatedCerts[1:]...)
|
|
|
|
} else {
|
|
|
|
tmplVars.SSH.HostFederatedKeys = append(tmplVars.SSH.HostFederatedKeys, a.sshCAHostFederatedCerts...)
|
|
|
|
}
|
|
|
|
if a.sshCAUserCertSignKey != nil {
|
|
|
|
tmplVars.SSH.UserKey = a.sshCAUserCertSignKey.PublicKey()
|
|
|
|
tmplVars.SSH.UserFederatedKeys = append(tmplVars.SSH.UserFederatedKeys, a.sshCAUserFederatedCerts[1:]...)
|
|
|
|
} else {
|
|
|
|
tmplVars.SSH.UserFederatedKeys = append(tmplVars.SSH.UserFederatedKeys, a.sshCAUserFederatedCerts...)
|
|
|
|
}
|
|
|
|
|
2021-08-02 19:13:39 +00:00
|
|
|
if a.config.AuthorityConfig.EnableAdmin {
|
2021-05-03 19:48:20 +00:00
|
|
|
// Initialize step-ca Admin Database if it's not already initialized using
|
|
|
|
// WithAdminDB.
|
|
|
|
if a.adminDB == nil {
|
2022-03-23 21:56:39 +00:00
|
|
|
if linkedcaClient != nil {
|
|
|
|
a.adminDB = linkedcaClient
|
2021-07-12 16:11:00 +00:00
|
|
|
} else {
|
2022-03-23 21:56:39 +00:00
|
|
|
a.adminDB, err = adminDBNosql.New(a.db.(nosql.DB), admin.DefaultAuthorityID)
|
2021-07-12 16:11:00 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-05-03 19:48:20 +00:00
|
|
|
}
|
2019-10-28 18:50:43 +00:00
|
|
|
}
|
2021-05-03 19:48:20 +00:00
|
|
|
|
2022-05-10 23:51:09 +00:00
|
|
|
provs, err := a.adminDB.GetProvisioners(ctx)
|
2021-05-03 19:48:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return admin.WrapErrorISE(err, "error loading provisioners to initialize authority")
|
2018-10-05 21:48:36 +00:00
|
|
|
}
|
2021-08-11 00:30:33 +00:00
|
|
|
if len(provs) == 0 && !strings.EqualFold(a.config.AuthorityConfig.DeploymentType, "linked") {
|
2022-10-21 09:11:42 +00:00
|
|
|
// Migration will currently only be kicked off once, because either one or more provisioners
|
|
|
|
// are migrated or a default JWK provisioner will be created in the DB. It won't run for
|
|
|
|
// linked or hosted deployments. Not for linked, because that case is explicitly checked
|
|
|
|
// for above. Not for hosted, because there'll be at least an existing OIDC provisioner.
|
2022-10-06 15:14:02 +00:00
|
|
|
var firstJWKProvisioner *linkedca.Provisioner
|
|
|
|
if len(a.config.AuthorityConfig.Provisioners) > 0 {
|
2022-10-21 09:11:42 +00:00
|
|
|
// Existing provisioners detected; try migrating them to DB storage.
|
2022-10-11 12:12:06 +00:00
|
|
|
a.initLogf("Starting migration of provisioners")
|
2022-10-06 15:14:02 +00:00
|
|
|
for _, p := range a.config.AuthorityConfig.Provisioners {
|
|
|
|
lp, err := ProvisionerToLinkedca(p)
|
|
|
|
if err != nil {
|
|
|
|
return admin.WrapErrorISE(err, "error transforming provisioner %q while migrating", p.GetName())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Store the provisioner to be migrated
|
|
|
|
if err := a.adminDB.CreateProvisioner(ctx, lp); err != nil {
|
|
|
|
return admin.WrapErrorISE(err, "error creating provisioner %q while migrating", p.GetName())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mark the first JWK provisioner, so that it can be used for administration purposes
|
|
|
|
if firstJWKProvisioner == nil && lp.Type == linkedca.Provisioner_JWK {
|
|
|
|
firstJWKProvisioner = lp
|
2022-10-21 09:11:42 +00:00
|
|
|
a.initLogf("Migrated JWK provisioner %q with admin permissions", p.GetName())
|
2022-10-06 15:14:02 +00:00
|
|
|
} else {
|
2022-10-11 10:19:29 +00:00
|
|
|
a.initLogf("Migrated %s provisioner %q", p.GetType(), p.GetName())
|
2022-10-06 15:14:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-11 12:12:06 +00:00
|
|
|
c := a.config
|
|
|
|
if c.WasLoadedFromFile() {
|
2022-10-24 12:51:27 +00:00
|
|
|
// The provisioners in the configuration file can be deleted from
|
|
|
|
// the file by editing it. Automatic rewriting of the file was considered
|
|
|
|
// to be too surprising for users and not the right solution for all
|
|
|
|
// use cases, so we leave it up to users to this themselves.
|
2022-10-24 13:33:48 +00:00
|
|
|
a.initLogf("Provisioners that were migrated can now be removed from `ca.json` by editing it")
|
2022-10-11 12:12:06 +00:00
|
|
|
}
|
2022-10-11 10:19:29 +00:00
|
|
|
|
|
|
|
a.initLogf("Finished migrating provisioners")
|
2021-05-03 19:48:20 +00:00
|
|
|
}
|
|
|
|
|
2022-10-06 15:14:02 +00:00
|
|
|
// Create first JWK provisioner for remote administration purposes if none exists yet
|
|
|
|
if firstJWKProvisioner == nil {
|
|
|
|
firstJWKProvisioner, err = CreateFirstProvisioner(ctx, a.adminDB, string(a.password))
|
|
|
|
if err != nil {
|
|
|
|
return admin.WrapErrorISE(err, "error creating first provisioner")
|
|
|
|
}
|
2022-10-21 09:11:42 +00:00
|
|
|
a.initLogf("Created JWK provisioner %q with admin permissions", firstJWKProvisioner.GetName())
|
2021-05-03 19:48:20 +00:00
|
|
|
}
|
|
|
|
|
2022-10-06 15:14:02 +00:00
|
|
|
// Create first super admin, belonging to the first JWK provisioner
|
2022-10-14 14:01:18 +00:00
|
|
|
// TODO(hs): pass a user-provided first super admin subject to here. With `ca init` it's
|
|
|
|
// added to the DB immediately if using remote management. But when migrating from
|
|
|
|
// ca.json to the DB, this option doesn't exist. Adding a flag just to do it during
|
|
|
|
// migration isn't nice. We could opt for a user to change it afterwards. There exist
|
|
|
|
// cases in which creation of `step` could lock out a user from API access. This is the
|
|
|
|
// case if `step` isn't allowed to be signed by Name Constraints or the X.509 policy.
|
|
|
|
// We have protection for that when creating and updating a policy, but if a policy or
|
|
|
|
// Name Constraints are in use at the time of migration, that could lock the user out.
|
2022-10-24 12:51:27 +00:00
|
|
|
superAdminSubject := "step"
|
2022-05-10 23:51:09 +00:00
|
|
|
if err := a.adminDB.CreateAdmin(ctx, &linkedca.Admin{
|
2022-10-06 15:14:02 +00:00
|
|
|
ProvisionerId: firstJWKProvisioner.Id,
|
2022-10-24 12:51:27 +00:00
|
|
|
Subject: superAdminSubject,
|
2021-05-03 19:48:20 +00:00
|
|
|
Type: linkedca.Admin_SUPER_ADMIN,
|
|
|
|
}); err != nil {
|
|
|
|
return admin.WrapErrorISE(err, "error creating first admin")
|
|
|
|
}
|
2022-10-06 15:14:02 +00:00
|
|
|
|
2022-10-24 12:51:27 +00:00
|
|
|
a.initLogf("Created super admin %q for JWK provisioner %q", superAdminSubject, firstJWKProvisioner.GetName())
|
2021-05-18 04:07:25 +00:00
|
|
|
}
|
|
|
|
}
|
2018-10-05 21:48:36 +00:00
|
|
|
|
2021-05-03 19:48:20 +00:00
|
|
|
// Load Provisioners and Admins
|
2022-05-23 22:31:43 +00:00
|
|
|
if err := a.ReloadAdminResources(ctx); err != nil {
|
2021-05-03 19:48:20 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-06-01 10:10:54 +00:00
|
|
|
// The SCEP functionality is provided through an instance of
|
2023-08-02 16:26:50 +00:00
|
|
|
// scep.Authority. It is initialized when the CA is started and
|
|
|
|
// if it doesn't exist yet. It gets refreshed if it already
|
|
|
|
// exists. If the SCEP authority is no longer required on reload,
|
|
|
|
// it gets removed.
|
|
|
|
// TODO(hs): reloading through SIGHUP doesn't hit these cases. This
|
|
|
|
// is because an entirely new authority.Authority is created, including
|
|
|
|
// a new scep.Authority. Look into this to see if we want this to
|
|
|
|
// keep working like that, or want to reuse a single instance and
|
|
|
|
// update that.
|
|
|
|
switch {
|
|
|
|
case a.requiresSCEP() && a.GetSCEP() == nil:
|
2023-09-25 20:24:13 +00:00
|
|
|
if a.scepOptions == nil {
|
|
|
|
options := &scep.Options{
|
|
|
|
Roots: a.rootX509Certs,
|
|
|
|
Intermediates: a.intermediateX509Certs,
|
|
|
|
SignerCert: a.intermediateX509Certs[0],
|
|
|
|
}
|
2023-09-25 19:55:19 +00:00
|
|
|
if options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
|
|
|
|
SigningKey: a.config.IntermediateKey,
|
|
|
|
Password: a.password,
|
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// TODO(hs): instead of creating the decrypter here, pass the
|
|
|
|
// intermediate key + chain down to the SCEP authority,
|
|
|
|
// and only instantiate it when required there. Is that possible?
|
|
|
|
// Also with entering passwords?
|
|
|
|
// TODO(hs): if moving the logic, try improving the logic for the
|
|
|
|
// decrypter password too? Right now it needs to be entered multiple
|
|
|
|
// times; I've observed it to be three times maximum, every time
|
|
|
|
// the intermediate key is read.
|
|
|
|
_, isRSA := options.Signer.Public().(*rsa.PublicKey)
|
|
|
|
if km, ok := a.keyManager.(kmsapi.Decrypter); ok && isRSA {
|
|
|
|
if decrypter, err := km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{
|
|
|
|
DecryptionKey: a.config.IntermediateKey,
|
|
|
|
Password: a.password,
|
|
|
|
}); err == nil {
|
|
|
|
// only pass the decrypter down when it was successfully created,
|
|
|
|
// meaning it's an RSA key, and `CreateDecrypter` did not fail.
|
|
|
|
options.Decrypter = decrypter
|
|
|
|
options.DecrypterCert = options.Intermediates[0]
|
|
|
|
}
|
2023-05-01 21:59:48 +00:00
|
|
|
}
|
|
|
|
|
2023-09-25 20:24:13 +00:00
|
|
|
a.scepOptions = options
|
|
|
|
}
|
2023-06-01 12:43:32 +00:00
|
|
|
|
2023-09-25 22:00:08 +00:00
|
|
|
// provide the current SCEP provisioner names, so that the provisioners
|
|
|
|
// can be validated when the CA is started.
|
|
|
|
a.scepOptions.SCEPProvisionerNames = a.getSCEPProvisionerNames()
|
|
|
|
|
2023-06-01 13:46:21 +00:00
|
|
|
// create a new SCEP authority
|
2023-09-25 20:24:13 +00:00
|
|
|
scepAuthority, err := scep.New(a, *a.scepOptions)
|
2023-05-01 21:59:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-06-01 13:46:21 +00:00
|
|
|
|
2023-09-25 20:24:13 +00:00
|
|
|
if a.validateSCEP {
|
|
|
|
// validate the SCEP authority
|
|
|
|
if err := scepAuthority.Validate(); err != nil {
|
|
|
|
a.initLogf("failed validating SCEP authority: %v", err)
|
|
|
|
}
|
2023-06-01 13:46:21 +00:00
|
|
|
}
|
2023-08-02 16:26:50 +00:00
|
|
|
|
|
|
|
// set the SCEP authority
|
|
|
|
a.scepAuthority = scepAuthority
|
|
|
|
case !a.requiresSCEP() && a.GetSCEP() != nil:
|
|
|
|
// clear the SCEP authority if it's no longer required
|
|
|
|
a.scepAuthority = nil
|
|
|
|
case a.requiresSCEP() && a.GetSCEP() != nil:
|
|
|
|
// update the SCEP Authority with the currently active SCEP
|
|
|
|
// provisioner names and revalidate the configuration.
|
|
|
|
a.scepAuthority.UpdateProvisioners(a.getSCEPProvisionerNames())
|
|
|
|
if err := a.scepAuthority.Validate(); err != nil {
|
|
|
|
log.Printf("failed validating SCEP authority: %v\n", err)
|
|
|
|
}
|
2023-05-01 21:59:48 +00:00
|
|
|
}
|
|
|
|
|
2022-09-21 01:52:47 +00:00
|
|
|
// Load X509 constraints engine.
|
|
|
|
//
|
|
|
|
// This is currently only available in CA mode.
|
|
|
|
if size := len(a.intermediateX509Certs); size > 0 {
|
|
|
|
last := a.intermediateX509Certs[size-1]
|
|
|
|
constraintCerts := make([]*x509.Certificate, 0, size+1)
|
|
|
|
constraintCerts = append(constraintCerts, a.intermediateX509Certs...)
|
|
|
|
for _, root := range a.rootX509Certs {
|
|
|
|
if bytes.Equal(last.RawIssuer, root.RawSubject) && bytes.Equal(last.AuthorityKeyId, root.SubjectKeyId) {
|
|
|
|
constraintCerts = append(constraintCerts, root)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
a.constraintsEngine = constraints.New(constraintCerts...)
|
|
|
|
}
|
|
|
|
|
2022-03-21 14:53:59 +00:00
|
|
|
// Load x509 and SSH Policy Engines
|
2022-05-10 23:51:09 +00:00
|
|
|
if err := a.reloadPolicyEngines(ctx); err != nil {
|
2021-05-03 19:48:20 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-06-17 00:26:18 +00:00
|
|
|
// Configure templates, currently only ssh templates are supported.
|
|
|
|
if a.sshCAHostCertSignKey != nil || a.sshCAUserCertSignKey != nil {
|
2020-06-17 00:57:35 +00:00
|
|
|
a.templates = a.config.Templates
|
|
|
|
if a.templates == nil {
|
|
|
|
a.templates = templates.DefaultTemplates()
|
2019-10-04 02:03:38 +00:00
|
|
|
}
|
2020-06-17 00:57:35 +00:00
|
|
|
if a.templates.Data == nil {
|
|
|
|
a.templates.Data = make(map[string]interface{})
|
2019-10-04 02:03:38 +00:00
|
|
|
}
|
2020-06-17 00:57:35 +00:00
|
|
|
a.templates.Data["Step"] = tmplVars
|
2019-10-04 02:03:38 +00:00
|
|
|
}
|
|
|
|
|
2022-10-27 01:55:24 +00:00
|
|
|
// Start the CRL generator, we can assume the configuration is validated.
|
|
|
|
if a.config.CRL.IsEnabled() {
|
|
|
|
// Default cache duration to the default one
|
|
|
|
if v := a.config.CRL.CacheDuration; v == nil || v.Duration <= 0 {
|
|
|
|
a.config.CRL.CacheDuration = config.DefaultCRLCacheDuration
|
2022-10-07 02:30:00 +00:00
|
|
|
}
|
2022-10-27 01:55:24 +00:00
|
|
|
// Start CRL generator
|
|
|
|
if err := a.startCRLGenerator(); err != nil {
|
2022-10-07 02:30:00 +00:00
|
|
|
return err
|
2021-11-04 06:05:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-27 18:38:30 +00:00
|
|
|
// JWT numeric dates are seconds.
|
|
|
|
a.startTime = time.Now().Truncate(time.Second)
|
|
|
|
// Set flag indicating that initialization has been completed, and should
|
|
|
|
// not be repeated.
|
|
|
|
a.initOnce = true
|
|
|
|
|
2018-10-05 21:48:36 +00:00
|
|
|
return nil
|
|
|
|
}
|
2019-03-05 08:07:13 +00:00
|
|
|
|
2022-10-11 10:19:29 +00:00
|
|
|
// initLogf is used to log initialization information. The output
|
|
|
|
// can be disabled by starting the CA with the `--quiet` flag.
|
|
|
|
func (a *Authority) initLogf(format string, v ...any) {
|
|
|
|
if !a.quietInit {
|
|
|
|
log.Printf(format, v...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-10 23:51:09 +00:00
|
|
|
// GetID returns the define authority id or a zero uuid.
|
|
|
|
func (a *Authority) GetID() string {
|
|
|
|
const zeroUUID = "00000000-0000-0000-0000-000000000000"
|
|
|
|
if id := a.config.AuthorityConfig.AuthorityID; id != "" {
|
|
|
|
return id
|
|
|
|
}
|
|
|
|
return zeroUUID
|
|
|
|
}
|
|
|
|
|
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.
|
2019-05-10 22:58:37 +00:00
|
|
|
func (a *Authority) GetDatabase() db.AuthDB {
|
|
|
|
return a.db
|
|
|
|
}
|
|
|
|
|
2021-05-03 19:48:20 +00:00
|
|
|
// GetAdminDatabase returns the admin database, if one exists.
|
|
|
|
func (a *Authority) GetAdminDatabase() admin.DB {
|
2021-05-20 20:01:58 +00:00
|
|
|
return a.adminDB
|
2021-05-07 00:03:12 +00:00
|
|
|
}
|
|
|
|
|
2022-04-25 17:23:07 +00:00
|
|
|
// GetConfig returns the config.
|
|
|
|
func (a *Authority) GetConfig() *config.Config {
|
|
|
|
return a.config
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetInfo returns information about the authority.
|
2022-04-05 17:59:25 +00:00
|
|
|
func (a *Authority) GetInfo() Info {
|
|
|
|
ai := Info{
|
2022-03-30 23:08:10 +00:00
|
|
|
StartTime: a.startTime,
|
2022-03-30 22:48:42 +00:00
|
|
|
RootX509Certs: a.rootX509Certs,
|
2022-04-04 19:16:37 +00:00
|
|
|
DNSNames: a.config.DNSNames,
|
2022-03-30 22:48:42 +00:00
|
|
|
}
|
2022-03-30 23:05:26 +00:00
|
|
|
if a.sshCAUserCertSignKey != nil {
|
|
|
|
ai.SSHCAUserPublicKey = ssh.MarshalAuthorizedKey(a.sshCAUserCertSignKey.PublicKey())
|
|
|
|
}
|
|
|
|
if a.sshCAHostCertSignKey != nil {
|
|
|
|
ai.SSHCAHostPublicKey = ssh.MarshalAuthorizedKey(a.sshCAHostCertSignKey.PublicKey())
|
|
|
|
}
|
|
|
|
return ai
|
2022-03-30 22:48:42 +00:00
|
|
|
}
|
|
|
|
|
2021-07-07 00:14:13 +00:00
|
|
|
// IsAdminAPIEnabled returns a boolean indicating whether the Admin API has
|
|
|
|
// been enabled.
|
|
|
|
func (a *Authority) IsAdminAPIEnabled() bool {
|
|
|
|
return a.config.AuthorityConfig.EnableAdmin
|
|
|
|
}
|
|
|
|
|
2019-03-05 08:07:13 +00:00
|
|
|
// Shutdown safely shuts down any clients, databases, etc. held by the Authority.
|
|
|
|
func (a *Authority) Shutdown() error {
|
2022-04-05 03:19:13 +00:00
|
|
|
if a.crlTicker != nil {
|
|
|
|
a.crlTicker.Stop()
|
2022-10-27 01:55:24 +00:00
|
|
|
close(a.crlStopper)
|
2022-04-05 03:19:13 +00:00
|
|
|
}
|
|
|
|
|
2020-05-08 01:59:30 +00:00
|
|
|
if err := a.keyManager.Close(); err != nil {
|
|
|
|
log.Printf("error closing the key manager: %v", err)
|
|
|
|
}
|
2019-03-05 08:07:13 +00:00
|
|
|
return a.db.Shutdown()
|
|
|
|
}
|
2021-02-02 01:14:44 +00:00
|
|
|
|
|
|
|
// CloseForReload closes internal services, to allow a safe reload.
|
|
|
|
func (a *Authority) CloseForReload() {
|
2022-04-05 03:19:13 +00:00
|
|
|
if a.crlTicker != nil {
|
|
|
|
a.crlTicker.Stop()
|
2022-10-27 01:55:24 +00:00
|
|
|
close(a.crlStopper)
|
2022-04-05 03:19:13 +00:00
|
|
|
}
|
|
|
|
|
2021-02-02 01:14:44 +00:00
|
|
|
if err := a.keyManager.Close(); err != nil {
|
|
|
|
log.Printf("error closing the key manager: %v", err)
|
|
|
|
}
|
2021-07-20 02:28:06 +00:00
|
|
|
if client, ok := a.adminDB.(*linkedCaClient); ok {
|
|
|
|
client.Stop()
|
|
|
|
}
|
2021-02-02 01:14:44 +00:00
|
|
|
}
|
2021-02-25 23:32:21 +00:00
|
|
|
|
2021-11-26 16:27:42 +00:00
|
|
|
// IsRevoked returns whether or not a certificate has been
|
|
|
|
// revoked before.
|
|
|
|
func (a *Authority) IsRevoked(sn string) (bool, error) {
|
|
|
|
// Check the passive revocation table.
|
|
|
|
if lca, ok := a.adminDB.(interface {
|
|
|
|
IsRevoked(string) (bool, error)
|
|
|
|
}); ok {
|
|
|
|
return lca.IsRevoked(sn)
|
|
|
|
}
|
|
|
|
|
|
|
|
return a.db.IsRevoked(sn)
|
|
|
|
}
|
|
|
|
|
2023-06-01 14:22:00 +00:00
|
|
|
// requiresSCEP iterates over the configured provisioners
|
|
|
|
// and determines if at least one of them is a SCEP provisioner.
|
2023-06-01 10:10:54 +00:00
|
|
|
func (a *Authority) requiresSCEP() bool {
|
2021-03-12 13:18:36 +00:00
|
|
|
for _, p := range a.config.AuthorityConfig.Provisioners {
|
|
|
|
if p.GetType() == provisioner.TypeSCEP {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-08-02 16:26:50 +00:00
|
|
|
// getSCEPProvisionerNames returns the names of the SCEP provisioners
|
|
|
|
// that are currently available in the CA.
|
2023-06-01 12:43:32 +00:00
|
|
|
func (a *Authority) getSCEPProvisionerNames() (names []string) {
|
|
|
|
for _, p := range a.config.AuthorityConfig.Provisioners {
|
|
|
|
if p.GetType() == provisioner.TypeSCEP {
|
|
|
|
names = append(names, p.GetName())
|
|
|
|
}
|
|
|
|
}
|
2023-08-02 16:26:50 +00:00
|
|
|
|
2023-06-01 12:43:32 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-06-01 13:46:21 +00:00
|
|
|
// GetSCEP returns the configured SCEP Authority
|
|
|
|
func (a *Authority) GetSCEP() *scep.Authority {
|
|
|
|
return a.scepAuthority
|
2021-02-25 23:32:21 +00:00
|
|
|
}
|
2021-11-04 06:05:07 +00:00
|
|
|
|
|
|
|
func (a *Authority) startCRLGenerator() error {
|
2022-10-27 01:55:24 +00:00
|
|
|
if !a.config.CRL.IsEnabled() {
|
2022-03-29 00:51:39 +00:00
|
|
|
return nil
|
|
|
|
}
|
2021-11-04 06:05:07 +00:00
|
|
|
|
2022-03-29 00:51:39 +00:00
|
|
|
// Check that there is a valid CRL in the DB right now. If it doesn't exist
|
|
|
|
// or is expired, generate one now
|
|
|
|
_, ok := a.db.(db.CertificateRevocationListDB)
|
|
|
|
if !ok {
|
|
|
|
return errors.Errorf("CRL Generation requested, but database does not support CRL generation")
|
|
|
|
}
|
2021-11-04 06:05:07 +00:00
|
|
|
|
2022-10-27 01:55:24 +00:00
|
|
|
// Always create a new CRL on startup in case the CA has been down and the
|
|
|
|
// time to next expected CRL update is less than the cache duration.
|
|
|
|
if err := a.GenerateCertificateRevocationList(); err != nil {
|
2022-03-29 00:51:39 +00:00
|
|
|
return errors.Wrap(err, "could not generate a CRL")
|
|
|
|
}
|
2021-11-04 06:05:07 +00:00
|
|
|
|
2022-10-27 01:55:24 +00:00
|
|
|
a.crlStopper = make(chan struct{}, 1)
|
|
|
|
a.crlTicker = time.NewTicker(a.config.CRL.TickerDuration())
|
2021-11-04 06:05:07 +00:00
|
|
|
|
2022-03-29 00:51:39 +00:00
|
|
|
go func() {
|
|
|
|
for {
|
2022-10-27 01:55:24 +00:00
|
|
|
select {
|
|
|
|
case <-a.crlTicker.C:
|
|
|
|
log.Println("Regenerating CRL")
|
|
|
|
if err := a.GenerateCertificateRevocationList(); err != nil {
|
|
|
|
log.Printf("error regenerating the CRL: %v", err)
|
|
|
|
}
|
|
|
|
case <-a.crlStopper:
|
|
|
|
return
|
2021-11-04 06:05:07 +00:00
|
|
|
}
|
2022-03-29 00:51:39 +00:00
|
|
|
}
|
|
|
|
}()
|
2021-11-04 06:05:07 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|