From e6b08fed84efee194e852ae22e42d6f07dddb4bd Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Fri, 30 Jan 2026 14:13:12 -0800 Subject: [PATCH 1/5] Use KMS URIs without --kms flag This command allows use use KMS URIs without using the `--kms` flag. Those commands using cryptoutils package will read a key from a KMS if the name is not a file and is one of the supported KMS types. --- command/ca/rekey.go | 2 +- internal/cryptoutil/cryptoutil.go | 24 +++++++++++++++--------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/command/ca/rekey.go b/command/ca/rekey.go index cd58fbf7d..2372b3adb 100644 --- a/command/ca/rekey.go +++ b/command/ca/rekey.go @@ -239,7 +239,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/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...) } From 3da5429dc27642608fc51b665c16b719b1423fdf Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Fri, 30 Jan 2026 15:27:23 -0800 Subject: [PATCH 2/5] Add examples of KMS URIs without the --kms flag --- command/ca/rekey.go | 10 +++++++--- command/ca/renew.go | 5 +++++ command/certificate/create.go | 10 ++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/command/ca/rekey.go b/command/ca/rekey.go index 2372b3adb..b3c9ebde3 100644 --- a/command/ca/rekey.go +++ b/command/ca/rekey.go @@ -83,12 +83,16 @@ 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 yubikey.crt yubikey:slot-id=82 +''' + +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' ''' Rekey a certificate providing the <--ca-url> and <--root> flags: diff --git a/command/ca/renew.go b/command/ca/renew.go index 0eaa4e6a9..8a3cbdbd3 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 +''' + +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/certificate/create.go b/command/certificate/create.go index ed33493f1..97bd3ce40 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 coyote@acme.corp coyote.csr +''' + Create a root certificate using : ''' +$ step certificate create --profile root-ca --key yubikey:slot-id=9a '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' From b36ab17d9bc4e8145f13f00cdfd6ffa1772036e3 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Wed, 4 Feb 2026 11:04:31 -0800 Subject: [PATCH 3/5] Add more kms examples --- command/ca/certificate.go | 7 +++++++ command/ca/rekey.go | 8 +++++++- command/ca/renew.go | 2 +- command/ca/token.go | 5 +++-- command/certificate/create.go | 4 ++-- 5 files changed, 20 insertions(+), 6 deletions(-) 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 b3c9ebde3..aa80dcac0 100644 --- a/command/ca/rekey.go +++ b/command/ca/rekey.go @@ -85,7 +85,8 @@ $ step ca rekey --force internal.crt internal.key Rekey a certificate using a KMS, with another from the same KMS: ''' -$ step ca rekey --private-key yubikey:slot-id=9a yubikey.crt yubikey:slot-id=82 +$ 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: @@ -95,6 +96,11 @@ $ step ca rekey \ --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: ''' $ step ca rekey --ca-url https://ca.smallstep.com:9000 \ diff --git a/command/ca/renew.go b/command/ca/renew.go index 8a3cbdbd3..b26b6be8c 100644 --- a/command/ca/renew.go +++ b/command/ca/renew.go @@ -107,7 +107,7 @@ $ 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 +$ 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: 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 97bd3ce40..83dc73920 100644 --- a/command/certificate/create.go +++ b/command/certificate/create.go @@ -332,12 +332,12 @@ $ step certificate create --csr --template csr.tpl --san coyote@acme.corp \ Create a CSR using : ''' -$ step certificate create --csr --key yubikey:slot-id=9a coyote@acme.corp coyote.csr +$ 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 'KMS Root' root_ca.crt +$ 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: From 99c4d7c8ef179a2221537063a934eb90a2a7fa6e Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Wed, 4 Feb 2026 11:22:28 -0800 Subject: [PATCH 4/5] Add sign example --- command/certificate/sign.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/command/certificate/sign.go b/command/certificate/sign.go index 628a391cf..22626968a 100644 --- a/command/certificate/sign.go +++ b/command/certificate/sign.go @@ -167,7 +167,10 @@ $ 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' ''' -`, + +''' +$ step certificate sign leaf.csr issuer.crt 'yubikey-slot-id=9a?pin-value=123456' +'''`, Flags: []cli.Flag{ flags.KMSUri, cli.StringFlag{ From cde778067b860981d7afe500f183fc20077880df Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Wed, 4 Feb 2026 11:36:00 -0800 Subject: [PATCH 5/5] Allow loading a certificate from a KMS --- command/certificate/sign.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/command/certificate/sign.go b/command/certificate/sign.go index 22626968a..a220def33 100644 --- a/command/certificate/sign.go +++ b/command/certificate/sign.go @@ -168,8 +168,9 @@ $ step certificate sign \ 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 issuer.crt 'yubikey-slot-id=9a?pin-value=123456' +$ step certificate sign leaf.csr yubikey-slot-id=9a 'yubikey-slot-id=9a?pin-value=123456' '''`, Flags: []cli.Flag{ flags.KMSUri, @@ -241,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) @@ -252,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 } @@ -268,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 }