Skip to content

Commit

Permalink
Merge pull request #942 from smallstep/herman/ca-kms
Browse files Browse the repository at this point in the history
Add `--ca-kms` flag to `step certificate create`
  • Loading branch information
hslatman authored Aug 9, 2023
2 parents 1463dba + 9845803 commit 7783ecf
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 23 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Detect OIDC tokens issued by Kubernetes (smallstep/cli#953).
- Add support for Smallstep Managed Endpoint X509 extension
(smallstep/cli#989).
- Support signing a certificate for a private key that can only be used for encryption with the `--skip-csr-signature` flag in `step certificate create`. Some KMSs restrict key usage to a single type of cryptographic operation. This blocks RSA decryption keys from being used to sign a CSR for their public key. Using the `--skip-csr-signature` flag, the public key is used directly with a certificate template, removing the need for the CSR signature.

### Changed

- Increase PBKDF2 iterations to 600k (smallstep/cli#949).
- `--kms` flag is no longer used for the CA (signing) key for `step certificate create`. It was replaced by the `--ca-kms` flag (smallstep/cli#942).

### Fixed

Expand Down
89 changes: 68 additions & 21 deletions command/certificate/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ func createCommand() cli.Command {
[**--kms**=<uri>] [**--csr**] [**--profile**=<profile>]
[**--template**=<file>] [**--set**=<key=value>] [**--set-file**=<file>]
[**--not-before**=<duration>] [**--not-after**=<duration>]
[**--password-file**=<file>] [**--ca**=<issuer-cert>]
[**--password-file**=<file>] [**--ca**=<issuer-cert>]
[**--ca-key**=<issuer-key>] [**--ca-password-file**=<file>]
[**--san**=<SAN>] [**--bundle**] [**--key**=<file>]
[**--ca-kms**=<uri>] [**--san**=<SAN>] [**--bundle**] [**--key**=<file>]
[**--kty**=<type>] [**--curve**=<curve>] [**--size**=<size>]
[**--no-password**] [**--insecure**]`,
[**--skip-csr-signature**] [**--no-password**] [**--insecure**]`,
Description: `**step certificate create** generates a certificate or a
certificate signing request (CSR) that can be signed later using 'step
certificate sign' (or some other tool) to produce a certificate.
Expand Down Expand Up @@ -345,11 +345,34 @@ $ step kms create \
'pkcs11:id=4001;object=intermediate-key'
$ step certificate create \
--profile intermediate-ca \
--kms 'pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=smallstep?pin-value=password' \
--ca-kms 'pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=smallstep?pin-value=password'
--ca root_ca.crt --ca-key 'pkcs11:id=4000' \
--kms 'pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=smallstep?pin-value=password' \
--key 'pkcs11:id=4001' \
'My KMS Intermediate' intermediate_ca.crt
'''
Create an intermediate certificate for an RSA decryption key in Google Cloud KMS, signed by a root stored on disk, using <step-kms-plugin>:
'''
$ step certificate create \
--profile intermediate-ca \
--ca root_ca.crt --ca-key root_ca_key \
--kms cloudkms: \
--key 'projects/myProjectID/locations/global/keyRings/myKeyRing/cryptoKeys/myKey/cryptoKeyVersions/1' \
--skip-csr-signature \
'My RSA Intermediate' intermediate_rsa_ca.crt
'''
Create an intermediate certificate for an RSA signing key in Google Cloud KMS, signed by a root stored in an HSM, using <step-kms-plugin>:
'''
$ step certificate create \
--profile intermediate-ca \
--ca-kms 'pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=smallstep?pin-value=password' \
--ca root_ca.crt --ca-key 'pkcs11:id=4000' \
--kms cloudkms: \
--key 'projects/myProjectID/locations/global/keyRings/myKeyRing/cryptoKeys/myKey/cryptoKeyVersions/1' \
'My RSA Intermediate' intermediate_rsa_ca.crt
'''
`,
Flags: []cli.Flag{
flags.KMSUri,
Expand Down Expand Up @@ -446,6 +469,14 @@ the **--ca** flag.`,
Name: "insecure",
Hidden: true,
},
cli.StringFlag{
Name: "ca-kms",
Usage: "The <uri> to configure the KMS used for signing the certificate",
},
cli.BoolFlag{
Name: "skip-csr-signature",
Usage: "Skip creating and signing a CSR",
},
},
}
}
Expand Down Expand Up @@ -485,17 +516,22 @@ func createAction(ctx *cli.Context) error {
}

var (
sans = ctx.StringSlice("san")
profile = ctx.String("profile")
templateFile = ctx.String("template")
bundle = ctx.Bool("bundle")
subtle = ctx.Bool("subtle")
sans = ctx.StringSlice("san")
profile = ctx.String("profile")
templateFile = ctx.String("template")
bundle = ctx.Bool("bundle")
subtle = ctx.Bool("subtle")
skipCSRSignature = ctx.Bool("skip-csr-signature")
)

if ctx.IsSet("profile") && templateFile != "" {
return errs.IncompatibleFlagWithFlag(ctx, "profile", "template")
}

if ctx.Bool("csr") && skipCSRSignature {
return errs.IncompatibleFlagWithFlag(ctx, "csr", "skip-csr-signature")
}

// Read template if passed
var template string
if templateFile != "" {
Expand Down Expand Up @@ -631,20 +667,31 @@ func createAction(ctx *cli.Context) error {
defaultValidity = defaultTemplatevalidity
}

// Create X.509 certificate used as base for the certificate
cr, err := x509util.CreateCertificateRequest(subject, sans, signer)
if err != nil {
return err
}

// Create X.509 certificate
templateData := x509util.CreateTemplateData(subject, sans)
templateData.SetUserData(userData)
certificate, err := x509util.NewCertificate(cr, x509util.WithTemplate(template, templateData))
if err != nil {
return err

var certTemplate = &x509.Certificate{}
if skipCSRSignature {
certTemplate.PublicKey = pub
certificate, err := x509util.NewCertificateFromX509(certTemplate, x509util.WithTemplate(template, templateData))
if err != nil {
return err
}
certTemplate = certificate.GetCertificate()
} else {
// Create X.509 certificate used as base for the certificate
cr, err := x509util.CreateCertificateRequest(subject, sans, priv)
if err != nil {
return err
}
certificate, err := x509util.NewCertificate(cr, x509util.WithTemplate(template, templateData))
if err != nil {
return err
}
certTemplate = certificate.GetCertificate()
}
certTemplate := certificate.GetCertificate()

if parent == nil {
parent = certTemplate
}
Expand Down Expand Up @@ -766,9 +813,9 @@ func parseSigner(ctx *cli.Context, defaultSigner crypto.Signer) (*x509.Certifica
var (
caCert = ctx.String("ca")
caKey = ctx.String("ca-key")
caKMS = ctx.String("ca-kms")
profile = ctx.String("profile")
template = ctx.String("template")
kms = ctx.String("kms")
)

// Check required flags when profile is used.
Expand Down Expand Up @@ -819,7 +866,7 @@ func parseSigner(ctx *cli.Context, defaultSigner crypto.Signer) (*x509.Certifica
opts = append(opts, pemutil.WithPasswordFile(passFile))
}

signer, err := cryptoutil.CreateSigner(kms, caKey, opts...)
signer, err := cryptoutil.CreateSigner(caKMS, caKey, opts...)
if err != nil {
return nil, nil, err
}
Expand Down
12 changes: 10 additions & 2 deletions internal/cryptoutil/cryptoutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type Attestor interface {
// CreateSigner reads a key from a file with a given name or creates a signer
// with the given kms and name uri.
func CreateSigner(kms, name string, opts ...pemutil.Options) (crypto.Signer, error) {
if kms == "" {
if kms == "" || isSoftKMS(kms) {
s, err := pemutil.Read(name, opts...)
if err != nil {
return nil, err
Expand All @@ -43,6 +43,10 @@ func CreateSigner(kms, name string, opts ...pemutil.Options) (crypto.Signer, err
return newKMSSigner(kms, name)
}

func isSoftKMS(kms string) bool {
return strings.HasPrefix(strings.ToLower(strings.TrimSpace(kms)), "softkms")
}

// LoadCertificate returns a x509.Certificate from a kms or file
func LoadCertificate(kms, certPath string) ([]*x509.Certificate, error) {
if kms == "" {
Expand Down Expand Up @@ -138,9 +142,13 @@ func IsKMSSigner(signer crypto.Signer) (ok bool) {
}

// IsX509Signer returns true if the given signer is supported by Go's
// crypto/x509 package to sign sign X509 certificates. This methods returns true
// crypto/x509 package to sign X509 certificates. This methods returns true
// for ECDSA, RSA and Ed25519 keys, but if the kms is `sshagentkms:` it will
// only return true for Ed25519 keys.
// TODO(hs): introspect the KMS key to verify that it can actually be
// used for signing? E.g. for Google Cloud KMS RSA keys can be used for
// signing or decryption, but only one of those at a time. Trying to use
// a signing key to decrypt data will result in an error from Cloud KMS.
func IsX509Signer(signer crypto.Signer) bool {
pub := signer.Public()
if ks, ok := signer.(*kmsSigner); ok {
Expand Down

0 comments on commit 7783ecf

Please sign in to comment.