Add support for kms in pki package.

Adding support to kms in the pki packages opens the door to use
kms implementations in `step ca init`
This commit is contained in:
Mariano Cano 2021-10-07 17:28:39 -07:00
parent 822a1e3bdb
commit ece67fefff

View file

@ -26,13 +26,14 @@ import (
"github.com/smallstep/certificates/cas" "github.com/smallstep/certificates/cas"
"github.com/smallstep/certificates/cas/apiv1" "github.com/smallstep/certificates/cas/apiv1"
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/kms"
kmsapi "github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/nosql" "github.com/smallstep/nosql"
"go.step.sm/cli-utils/config" "go.step.sm/cli-utils/config"
"go.step.sm/cli-utils/errs" "go.step.sm/cli-utils/errs"
"go.step.sm/cli-utils/fileutil" "go.step.sm/cli-utils/fileutil"
"go.step.sm/cli-utils/ui" "go.step.sm/cli-utils/ui"
"go.step.sm/crypto/jose" "go.step.sm/crypto/jose"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/pemutil" "go.step.sm/crypto/pemutil"
"go.step.sm/linkedca" "go.step.sm/linkedca"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
@ -176,6 +177,10 @@ type options struct {
noDB bool noDB bool
isHelm bool isHelm bool
deploymentType DeploymentType deploymentType DeploymentType
rootKeyURI string
intermediateKeyURI string
hostKeyURI string
userKeyURI string
} }
// Option is the type of a configuration option on the pki constructor. // Option is the type of a configuration option on the pki constructor.
@ -258,6 +263,26 @@ func WithDeploymentType(dt DeploymentType) Option {
} }
} }
// WithKMS enabled the kms with the given name.
func WithKMS(name string) Option {
return func(p *PKI) {
typ := linkedca.KMS_Type_value[strings.ToUpper(name)]
p.Configuration.Kms = &linkedca.KMS{
Type: linkedca.KMS_Type(typ),
}
}
}
// WithKeyURIs defines the key uris for X.509 and SSH keys.
func WithKeyURIs(rootKey, intermediateKey, hostKey, userKey string) Option {
return func(p *PKI) {
p.options.rootKeyURI = rootKey
p.options.intermediateKeyURI = intermediateKey
p.options.hostKeyURI = hostKey
p.options.userKeyURI = userKey
}
}
// PKI represents the Public Key Infrastructure used by a certificate authority. // PKI represents the Public Key Infrastructure used by a certificate authority.
type PKI struct { type PKI struct {
linkedca.Configuration linkedca.Configuration
@ -265,6 +290,7 @@ type PKI struct {
casOptions apiv1.Options casOptions apiv1.Options
caService apiv1.CertificateAuthorityService caService apiv1.CertificateAuthorityService
caCreator apiv1.CertificateAuthorityCreator caCreator apiv1.CertificateAuthorityCreator
keyManager kmsapi.KeyManager
config string config string
defaults string defaults string
ottPublicKey *jose.JSONWebKey ottPublicKey *jose.JSONWebKey
@ -303,8 +329,9 @@ func New(o apiv1.Options, opts ...Option) (*PKI, error) {
Files: make(map[string][]byte), Files: make(map[string][]byte),
}, },
casOptions: o, casOptions: o,
caCreator: caCreator,
caService: caService, caService: caService,
caCreator: caCreator,
keyManager: o.KeyManager,
options: &options{ options: &options{
provisioner: "step-cli", provisioner: "step-cli",
}, },
@ -313,6 +340,11 @@ func New(o apiv1.Options, opts ...Option) (*PKI, error) {
fn(p) fn(p)
} }
// Use default key manager
if p.keyManager != nil {
p.keyManager = kms.Default
}
// Use /home/step as the step path in helm configurations. // Use /home/step as the step path in helm configurations.
// Use the current step path when creating pki in files. // Use the current step path when creating pki in files.
var public, private, config string var public, private, config string
@ -448,11 +480,18 @@ func (p *PKI) GenerateKeyPairs(pass []byte) error {
// GenerateRootCertificate generates a root certificate with the given name // GenerateRootCertificate generates a root certificate with the given name
// and using the default key type. // and using the default key type.
func (p *PKI) GenerateRootCertificate(name, org, resource string, pass []byte) (*apiv1.CreateCertificateAuthorityResponse, error) { func (p *PKI) GenerateRootCertificate(name, org, resource string, pass []byte) (*apiv1.CreateCertificateAuthorityResponse, error) {
if uri := p.options.rootKeyURI; uri != "" {
p.RootKey[0] = uri
}
resp, err := p.caCreator.CreateCertificateAuthority(&apiv1.CreateCertificateAuthorityRequest{ resp, err := p.caCreator.CreateCertificateAuthority(&apiv1.CreateCertificateAuthorityRequest{
Name: resource + "-Root-CA", Name: resource + "-Root-CA",
Type: apiv1.RootCA, Type: apiv1.RootCA,
Lifetime: 10 * 365 * 24 * time.Hour, Lifetime: 10 * 365 * 24 * time.Hour,
CreateKey: nil, // use default CreateKey: &apiv1.CreateKeyRequest{
Name: p.RootKey[0],
SignatureAlgorithm: kmsapi.UnspecifiedSignAlgorithm,
},
Template: &x509.Certificate{ Template: &x509.Certificate{
Subject: pkix.Name{ Subject: pkix.Name{
CommonName: name + " Root CA", CommonName: name + " Root CA",
@ -469,6 +508,13 @@ func (p *PKI) GenerateRootCertificate(name, org, resource string, pass []byte) (
return nil, err return nil, err
} }
// Replace key name with the one from the key manager if available. On
// softcas this will be the original filename, on any other kms will be the
// uri to the key.
if resp.KeyName != "" {
p.RootKey[0] = resp.KeyName
}
// PrivateKey will only be set if we have access to it (SoftCAS). // PrivateKey will only be set if we have access to it (SoftCAS).
if err := p.WriteRootCertificate(resp.Certificate, resp.PrivateKey, pass); err != nil { if err := p.WriteRootCertificate(resp.Certificate, resp.PrivateKey, pass); err != nil {
return nil, err return nil, err
@ -495,11 +541,18 @@ func (p *PKI) WriteRootCertificate(rootCrt *x509.Certificate, rootKey interface{
// GenerateIntermediateCertificate generates an intermediate certificate with // GenerateIntermediateCertificate generates an intermediate certificate with
// the given name and using the default key type. // the given name and using the default key type.
func (p *PKI) GenerateIntermediateCertificate(name, org, resource string, parent *apiv1.CreateCertificateAuthorityResponse, pass []byte) error { func (p *PKI) GenerateIntermediateCertificate(name, org, resource string, parent *apiv1.CreateCertificateAuthorityResponse, pass []byte) error {
if uri := p.options.intermediateKeyURI; uri != "" {
p.IntermediateKey = uri
}
resp, err := p.caCreator.CreateCertificateAuthority(&apiv1.CreateCertificateAuthorityRequest{ resp, err := p.caCreator.CreateCertificateAuthority(&apiv1.CreateCertificateAuthorityRequest{
Name: resource + "-Intermediate-CA", Name: resource + "-Intermediate-CA",
Type: apiv1.IntermediateCA, Type: apiv1.IntermediateCA,
Lifetime: 10 * 365 * 24 * time.Hour, Lifetime: 10 * 365 * 24 * time.Hour,
CreateKey: nil, // use default CreateKey: &apiv1.CreateKeyRequest{
Name: p.IntermediateKey,
SignatureAlgorithm: kmsapi.UnspecifiedSignAlgorithm,
},
Template: &x509.Certificate{ Template: &x509.Certificate{
Subject: pkix.Name{ Subject: pkix.Name{
CommonName: name + " Intermediate CA", CommonName: name + " Intermediate CA",
@ -519,7 +572,19 @@ func (p *PKI) GenerateIntermediateCertificate(name, org, resource string, parent
p.casOptions.CertificateAuthority = resp.Name p.casOptions.CertificateAuthority = resp.Name
p.Files[p.Intermediate] = encodeCertificate(resp.Certificate) p.Files[p.Intermediate] = encodeCertificate(resp.Certificate)
// Replace the key name with the one from the key manager. On softcas this
// will be the original filename, on any other kms will be the uri to the
// key.
if resp.KeyName != "" {
p.IntermediateKey = resp.KeyName
}
// If a kms is used it will not have the private key
if resp.PrivateKey != nil {
p.Files[p.IntermediateKey], err = encodePrivateKey(resp.PrivateKey, pass) p.Files[p.IntermediateKey], err = encodePrivateKey(resp.PrivateKey, pass)
}
return err return err
} }
@ -564,27 +629,63 @@ func (p *PKI) GetCertificateAuthority() error {
// GenerateSSHSigningKeys generates and encrypts a private key used for signing // GenerateSSHSigningKeys generates and encrypts a private key used for signing
// SSH user certificates and a private key used for signing host certificates. // SSH user certificates and a private key used for signing host certificates.
func (p *PKI) GenerateSSHSigningKeys(password []byte) error { func (p *PKI) GenerateSSHSigningKeys(password []byte) error {
var pubNames = []string{p.Ssh.HostPublicKey, p.Ssh.UserPublicKey} // Enable SSH
var privNames = []string{p.Ssh.HostKey, p.Ssh.UserKey} p.options.enableSSH = true
for i := 0; i < 2; i++ {
pub, priv, err := keyutil.GenerateDefaultKeyPair() // Create SSH key used to sign host certificates. Using
// kmsapi.UnspecifiedSignAlgorithm will default to the default algorithm.
name := p.Ssh.HostPublicKey
if uri := p.options.hostKeyURI; uri != "" {
name = uri
}
resp, err := p.keyManager.CreateKey(&kmsapi.CreateKeyRequest{
Name: name,
SignatureAlgorithm: kmsapi.UnspecifiedSignAlgorithm,
})
if err != nil { if err != nil {
return err return err
} }
if _, ok := priv.(crypto.Signer); !ok { sshKey, err := ssh.NewPublicKey(resp.PublicKey)
return errors.Errorf("key of type %T is not a crypto.Signer", priv)
}
sshKey, err := ssh.NewPublicKey(pub)
if err != nil { if err != nil {
return errors.Wrapf(err, "error converting public key") return errors.Wrapf(err, "error converting public key")
} }
p.Files[pubNames[i]] = ssh.MarshalAuthorizedKey(sshKey) p.Files[resp.Name] = ssh.MarshalAuthorizedKey(sshKey)
p.Files[privNames[i]], err = encodePrivateKey(priv, password)
// On softkms we will have the private key
if resp.PrivateKey != nil {
p.Files[p.Ssh.HostKey], err = encodePrivateKey(resp.PrivateKey, password)
if err != nil { if err != nil {
return err return err
} }
} }
p.options.enableSSH = true
// Create SSH key used to sign user certificates. Using
// kmsapi.UnspecifiedSignAlgorithm will default to the default algorithm.
name = p.Ssh.UserPublicKey
if uri := p.options.userKeyURI; uri != "" {
name = uri
}
resp, err = p.keyManager.CreateKey(&kmsapi.CreateKeyRequest{
Name: name,
SignatureAlgorithm: kmsapi.UnspecifiedSignAlgorithm,
})
if err != nil {
return err
}
sshKey, err = ssh.NewPublicKey(resp.PublicKey)
if err != nil {
return errors.Wrapf(err, "error converting public key")
}
p.Files[resp.Name] = ssh.MarshalAuthorizedKey(sshKey)
// On softkms we will have the private key
if resp.PrivateKey != nil {
p.Files[p.Ssh.UserKey], err = encodePrivateKey(resp.PrivateKey, password)
if err != nil {
return err
}
}
return nil return nil
} }
@ -684,6 +785,13 @@ func (p *PKI) GenerateConfig(opt ...ConfigOption) (*authconfig.Config, error) {
config.AuthorityConfig.DeploymentType = LinkedDeployment.String() config.AuthorityConfig.DeploymentType = LinkedDeployment.String()
} }
// Enable KMS if necessary
if p.Kms != nil {
config.KMS = &kmsapi.Options{
Type: strings.ToLower(p.Kms.Type.String()),
}
}
// On standalone deployments add the provisioners to either the ca.json or // On standalone deployments add the provisioners to either the ca.json or
// the database. // the database.
var provisioners []provisioner.Interface var provisioners []provisioner.Interface