diff --git a/command/ca/certificate.go b/command/ca/certificate.go index d7ccd337c..0017f9b79 100644 --- a/command/ca/certificate.go +++ b/command/ca/certificate.go @@ -109,6 +109,13 @@ Request a new certificate with an X5C provisioner: $ step ca certificate foo.internal foo.crt foo.key --x5c-cert x5c.cert --x5c-key x5c.key ''' +Request a new certificate with an X5C using an certificate from a Yubikey: +''' +$ step ca certificate joe@example.com joe.crt joe.key \ + --x5c-cert yubikey:slot-id=9a \ + --x5c-key 'yubikey:slot-id=9a?pin=value=123456' +''' + **Certificate Templates** - With a provisioner configured with a custom template we can use the **--set** flag to pass user variables: ''' diff --git a/command/ca/rekey.go b/command/ca/rekey.go index cd58fbf7d..aa80dcac0 100644 --- a/command/ca/rekey.go +++ b/command/ca/rekey.go @@ -83,12 +83,22 @@ Rekey a certificate forcing the overwrite of the previous certificate and key $ step ca rekey --force internal.crt internal.key ''' -Rekey a certificate which key is in a KMS, with another from the same KMS: +Rekey a certificate using a KMS, with another from the same KMS: +''' +$ step ca rekey --private-key 'yubikey:slot-id=9a?pin-value=123456' \ + yubikey.crt 'yubikey:slot-id=82?pin-value=123456' +''' + +Rekey a certificate using a KMS with the <--kms> flag: ''' $ step ca rekey \ --kms 'pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=smallstep?pin-value=password' \ - --private-key 'pkcs11:id=4002' - pkcs11.crt 'pkcs11:id=4001' + --private-key 'pkcs11:id=4002' pkcs11.crt 'pkcs11:id=4001' +''' + +''' +$ step ca rekey --key yubikey:pin-value=123456 --private-key yubikey:slot-id=9a \ + yubikey.crt 'yubikey:slot-id=82 ''' Rekey a certificate providing the <--ca-url> and <--root> flags: @@ -239,7 +249,7 @@ func rekeyCertificateAction(ctx *cli.Context) error { // For now, if the --kms flag is given, do not allow to generate a new key // and write it on disk. We can't use the daemon mode because we // cannot generate new keys. - if kmsURI != "" { + if kmsURI != "" || cryptoutil.IsKMS(keyFile) { switch { case givenPrivate == "": return errs.RequiredWithFlag(ctx, "kms", "private-key") diff --git a/command/ca/renew.go b/command/ca/renew.go index 0eaa4e6a9..b26b6be8c 100644 --- a/command/ca/renew.go +++ b/command/ca/renew.go @@ -107,6 +107,11 @@ $ step ca renew --mtls=false --force internal.crt internal.key Renew a certificate which key is in a KMS: ''' +$ step ca renew yubikey.crt 'yubikey:slot-id=9a?pin-value=123456' +''' + +Renew a certificate which key is in a KMS, using the <--kms> flag: +''' $ step ca renew \ --kms 'pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=smallstep?pin-value=password' \ pkcs11.crt 'pkcs11:id=4001' diff --git a/command/ca/token.go b/command/ca/token.go index 91a115897..e074171c2 100644 --- a/command/ca/token.go +++ b/command/ca/token.go @@ -173,8 +173,9 @@ Generate an X5C provisioner token using a certificate in a YubiKey. Note that a YubiKey does not support storing a certificate bundle. To make it work, you must add the intermediate and the root in the provisioner configuration: ''' -$ step ca token --kms yubikey:pin-value=123456 \ - --x5c-cert yubikey:slot-id=82 --x5c-key yubikey:slot-id=82 \ +$ step ca token \ + --x5c-cert yubikey:slot-id=82 \ + --x5c-key 'yubikey:slot-id=82?pin=value=123456' \ internal.example.com ''' diff --git a/command/certificate/create.go b/command/certificate/create.go index ed33493f1..83dc73920 100644 --- a/command/certificate/create.go +++ b/command/certificate/create.go @@ -330,8 +330,18 @@ $ step certificate create --csr --template csr.tpl --san coyote@acme.corp \ "Wile E. Coyote" coyote.csr coyote.key ''' +Create a CSR using : +''' +$ step certificate create --csr --key 'yubikey:slot-id=9a?pin=value=123456' coyote@acme.corp coyote.csr +''' + Create a root certificate using : ''' +$ step certificate create --profile root-ca --key 'yubikey:slot-id=9a?pin=value=123456' 'KMS Root' root_ca.crt +''' + +Create a root certificate using and the <--kms> flag: +''' $ step kms create \ --kms 'pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=smallstep?pin-value=password' \ 'pkcs11:id=4000;object=root-key' diff --git a/command/certificate/sign.go b/command/certificate/sign.go index 628a391cf..a220def33 100644 --- a/command/certificate/sign.go +++ b/command/certificate/sign.go @@ -167,7 +167,11 @@ $ step certificate sign \ --kms 'pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=smallstep?pin-value=password' \ leaf.csr issuer.crt 'pkcs11:id=4001' ''' -`, + +Sign a CSR using a certificate and a key stored in a KMS: +''' +$ step certificate sign leaf.csr yubikey-slot-id=9a 'yubikey-slot-id=9a?pin-value=123456' +'''`, Flags: []cli.Flag{ flags.KMSUri, cli.StringFlag{ @@ -238,6 +242,7 @@ func signAction(ctx *cli.Context) error { csrFile := ctx.Args().Get(0) crtFile := ctx.Args().Get(1) keyFile := ctx.Args().Get(2) + kms := ctx.String("kms") // Parse certificate request csr, err := pemutil.ReadCertificateRequest(csrFile) @@ -249,7 +254,7 @@ func signAction(ctx *cli.Context) error { } // Parse issuer and issuer key (at least one should be present) - issuers, err := pemutil.ReadCertificateBundle(crtFile) + issuers, err := cryptoutil.LoadCertificate(kms, crtFile) if err != nil { return err } @@ -265,7 +270,7 @@ func signAction(ctx *cli.Context) error { opts = append(opts, pemutil.WithPasswordFile(passFile)) } - signer, err := cryptoutil.CreateSigner(ctx.String("kms"), keyFile, opts...) + signer, err := cryptoutil.CreateSigner(kms, keyFile, opts...) if err != nil { return err } diff --git a/internal/cryptoutil/cryptoutil.go b/internal/cryptoutil/cryptoutil.go index 005e74685..3b0a74a7e 100644 --- a/internal/cryptoutil/cryptoutil.go +++ b/internal/cryptoutil/cryptoutil.go @@ -11,6 +11,7 @@ import ( "errors" "fmt" "io" + "os" "os/exec" "strconv" "strings" @@ -22,8 +23,13 @@ import ( "go.step.sm/crypto/pemutil" ) -// IsKMS returns true if the given uri is a KMS URI. +// IsKMS returns true if the given uri is a KMS URI. It will return false if a +// file exists with the same name, even if the path matches a KMS uri pattern. func IsKMS(rawuri string) bool { + if _, err := os.Stat(rawuri); err == nil { + return false + } + typ, err := kms.TypeOf(rawuri) if err != nil || typ == apiv1.DefaultKMS { return false @@ -31,6 +37,10 @@ func IsKMS(rawuri string) bool { return true } +func isFilename(kmsURI, name string) bool { + return kmsURI == "" && !IsKMS(name) +} + // Attestor is the interface implemented by step-kms-plugin using the key, sign, // and attest commands. type Attestor interface { @@ -39,7 +49,7 @@ type Attestor interface { } func PublicKey(kmsURI, name string, opts ...pemutil.Options) (crypto.PublicKey, error) { - if kmsURI == "" { + if isFilename(kmsURI, name) { s, err := pemutil.Read(name, opts...) if err != nil { return nil, err @@ -61,7 +71,7 @@ func PublicKey(kmsURI, name string, opts ...pemutil.Options) (crypto.PublicKey, // CreateSigner reads a key from a file with a given name or creates a signer // with the given kms and name uri. func CreateSigner(kmsURI, name string, opts ...pemutil.Options) (crypto.Signer, error) { - if kmsURI == "" || isSoftKMS(kmsURI) { + if isFilename(kmsURI, name) { s, err := pemutil.Read(name, opts...) if err != nil { return nil, err @@ -75,13 +85,9 @@ func CreateSigner(kmsURI, name string, opts ...pemutil.Options) (crypto.Signer, return newKMSSigner(kmsURI, name) } -func isSoftKMS(kmsURI string) bool { - return strings.HasPrefix(strings.ToLower(strings.TrimSpace(kmsURI)), "softkms") -} - // LoadCertificate returns a x509.Certificate from a kms or file func LoadCertificate(kmsURI, certPath string) ([]*x509.Certificate, error) { - if kmsURI == "" { + if isFilename(kmsURI, certPath) { s, err := pemutil.ReadCertificateBundle(certPath) if err != nil { return nil, fmt.Errorf("file %s does not contain a valid certificate: %w", certPath, err) @@ -117,7 +123,7 @@ func LoadCertificate(kmsURI, certPath string) ([]*x509.Certificate, error) { // LoadJSONWebKey returns a jose.JSONWebKey from a KMS or a file. func LoadJSONWebKey(kmsURI, name string, opts ...jose.Option) (*jose.JSONWebKey, error) { - if kmsURI == "" { + if isFilename(kmsURI, name) { return jose.ReadKey(name, opts...) }