diff --git a/cmd/step-yubikey-init/main.go b/cmd/step-yubikey-init/main.go index d5e81075..da5e04ac 100644 --- a/cmd/step-yubikey-init/main.go +++ b/cmd/step-yubikey-init/main.go @@ -9,6 +9,7 @@ import ( "crypto/sha1" "crypto/x509" "crypto/x509/pkix" + "encoding/hex" "encoding/pem" "flag" "fmt" @@ -27,18 +28,23 @@ import ( _ "github.com/smallstep/certificates/kms/yubikey" ) +// Config is a mapping of the cli flags. type Config struct { - RootOnly bool - RootSlot string - CrtSlot string - RootFile string - KeyFile string - Pin string - Force bool + RootOnly bool + RootSlot string + CrtSlot string + RootFile string + KeyFile string + Pin string + ManagementKey string + Force bool } +// Validate checks the flags in the config. func (c *Config) Validate() error { switch { + case c.ManagementKey != "" && len(c.ManagementKey) != 48: + return errors.New("flag `--management-key` must be 48 hexadecimal characters (24 bytes)") case c.RootFile != "" && c.KeyFile == "": return errors.New("flag `--root` requires flag `--key`") case c.KeyFile != "" && c.RootFile == "": @@ -56,12 +62,18 @@ func (c *Config) Validate() error { if c.RootOnly { c.CrtSlot = "" } + if c.ManagementKey != "" { + if _, err := hex.DecodeString(c.ManagementKey); err != nil { + return errors.Wrap(err, "flag `--management-key` is not valid") + } + } return nil } } func main() { var c Config + flag.StringVar(&c.ManagementKey, "management-key", "", `Management key to use in hexadecimal format. (default "010203040506070801020304050607080102030405060708")`) flag.BoolVar(&c.RootOnly, "root-only", false, "Slot only the root certificate and sign and intermediate.") flag.StringVar(&c.RootSlot, "root-slot", "9a", "Slot to store the root certificate.") flag.StringVar(&c.CrtSlot, "crt-slot", "9c", "Slot to store the intermediate certificate.") @@ -82,8 +94,9 @@ func main() { c.Pin = string(pin) k, err := kms.New(context.Background(), apiv1.Options{ - Type: string(apiv1.YubiKey), - Pin: c.Pin, + Type: string(apiv1.YubiKey), + Pin: c.Pin, + ManagementKey: c.ManagementKey, }) if err != nil { fatal(err) @@ -109,7 +122,11 @@ func main() { } func fatal(err error) { - fmt.Fprintln(os.Stderr, err) + if os.Getenv("STEPDEBUG") == "1" { + fmt.Fprintf(os.Stderr, "%+v\n", err) + } else { + fmt.Fprintln(os.Stderr, err) + } os.Exit(1) } diff --git a/kms/apiv1/options.go b/kms/apiv1/options.go index 05421bd2..76a79563 100644 --- a/kms/apiv1/options.go +++ b/kms/apiv1/options.go @@ -19,11 +19,12 @@ type KeyManager interface { // CertificateManager is the interface implemented by the KMS that can load and // store x509.Certificates. type CertificateManager interface { - LoadCerticate(req *LoadCertificateRequest) (*x509.Certificate, error) + LoadCertificate(req *LoadCertificateRequest) (*x509.Certificate, error) StoreCertificate(req *StoreCertificateRequest) error } -// ErrNotImplemented +// ErrNotImplemented is the type of error returned if an operation is not +// implemented. type ErrNotImplemented struct { msg string } @@ -53,6 +54,7 @@ const ( YubiKey Type = "yubikey" ) +// Options are the KMS options. They represent the kms object in the ca.json. type Options struct { // The type of the KMS to use. Type string `json:"type"` @@ -66,6 +68,15 @@ type Options struct { // Pin used to access the PKCS11 module. Pin string `json:"pin"` + // ManagementKey used in YubiKeys. Default management key is the hexadecimal + // string 010203040506070801020304050607080102030405060708: + // []byte{ + // 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + // 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + // 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + // } + ManagementKey string `json:"managementKey"` + // Region to use in AmazonKMS. Region string `json:"region"` diff --git a/kms/yubikey/yubikey.go b/kms/yubikey/yubikey.go index 6f2a5c18..3349ea63 100644 --- a/kms/yubikey/yubikey.go +++ b/kms/yubikey/yubikey.go @@ -6,6 +6,7 @@ import ( "context" "crypto" "crypto/x509" + "encoding/hex" "net/url" "strings" @@ -16,13 +17,26 @@ import ( // YubiKey implements the KMS interface on a YubiKey. type YubiKey struct { - yk *piv.YubiKey - pin string + yk *piv.YubiKey + pin string + managementKey [24]byte } // New initializes a new YubiKey. // TODO(mariano): only one card is currently supported. func New(ctx context.Context, opts apiv1.Options) (*YubiKey, error) { + managementKey := piv.DefaultManagementKey + if opts.ManagementKey != "" { + b, err := hex.DecodeString(opts.ManagementKey) + if err != nil { + return nil, errors.Wrap(err, "error decoding managementKey") + } + if len(b) != 24 { + return nil, errors.New("invalid managementKey: length is not 24 bytes") + } + copy(managementKey[:], b[:24]) + } + cards, err := piv.Cards() if err != nil { return nil, err @@ -37,8 +51,9 @@ func New(ctx context.Context, opts apiv1.Options) (*YubiKey, error) { } return &YubiKey{ - yk: yk, - pin: opts.Pin, + yk: yk, + pin: opts.Pin, + managementKey: managementKey, }, nil } @@ -76,7 +91,7 @@ func (k *YubiKey) StoreCertificate(req *apiv1.StoreCertificateRequest) error { return err } - err = k.yk.SetCertificate(piv.DefaultManagementKey, slot, req.Certificate) + err = k.yk.SetCertificate(k.managementKey, slot, req.Certificate) if err != nil { return errors.Wrap(err, "error storing certificate") } @@ -110,7 +125,7 @@ func (k *YubiKey) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyRespon return nil, err } - pub, err := k.yk.GenerateKey(piv.DefaultManagementKey, slot, piv.Key{ + pub, err := k.yk.GenerateKey(k.managementKey, slot, piv.Key{ Algorithm: alg, PINPolicy: piv.PINPolicyAlways, TouchPolicy: piv.TouchPolicyNever,