commit
2bc69d3edd
@ -0,0 +1,323 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/kms"
|
||||
"github.com/smallstep/certificates/kms/apiv1"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"github.com/smallstep/cli/ui"
|
||||
"github.com/smallstep/cli/utils"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
RootOnly bool
|
||||
RootSlot string
|
||||
CrtSlot string
|
||||
RootFile string
|
||||
KeyFile string
|
||||
Pin string
|
||||
Force bool
|
||||
}
|
||||
|
||||
func (c *Config) Validate() error {
|
||||
switch {
|
||||
case c.RootFile != "" && c.KeyFile == "":
|
||||
return errors.New("flag `--root` requires flag `--key`")
|
||||
case c.KeyFile != "" && c.RootFile == "":
|
||||
return errors.New("flag `--key` requires flag `--root`")
|
||||
case c.RootOnly && c.RootFile != "":
|
||||
return errors.New("flag `--root-only` is incompatible with flag `--root`")
|
||||
case c.RootSlot == c.CrtSlot:
|
||||
return errors.New("flag `--root-slot` and flag `--crt-slot` cannot be the same")
|
||||
case c.RootFile == "" && c.RootSlot == "":
|
||||
return errors.New("one of flag `--root` or `--root-slot` is required")
|
||||
default:
|
||||
if c.RootFile != "" {
|
||||
c.RootSlot = ""
|
||||
}
|
||||
if c.RootOnly {
|
||||
c.CrtSlot = ""
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var c Config
|
||||
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.")
|
||||
flag.StringVar(&c.RootFile, "root", "", "Path to the root certificate to use.")
|
||||
flag.StringVar(&c.KeyFile, "key", "", "Path to the root key to use.")
|
||||
flag.BoolVar(&c.Force, "force", false, "Force the delete of previous keys.")
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
|
||||
if err := c.Validate(); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
pin, err := ui.PromptPassword("What is the YubiKey PIN?")
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
c.Pin = string(pin)
|
||||
|
||||
k, err := kms.New(context.Background(), apiv1.Options{
|
||||
Type: string(apiv1.YubiKey),
|
||||
Pin: c.Pin,
|
||||
})
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
// Check if the slots are empty, fail if they are not
|
||||
if !c.Force {
|
||||
switch {
|
||||
case c.RootSlot != "":
|
||||
checkSlot(k, c.RootSlot)
|
||||
case c.CrtSlot != "":
|
||||
checkSlot(k, c.CrtSlot)
|
||||
}
|
||||
}
|
||||
|
||||
if err := createPKI(k, c); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = k.Close()
|
||||
}()
|
||||
}
|
||||
|
||||
func fatal(err error) {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintln(os.Stderr, "Usage: step-yubikey-init")
|
||||
fmt.Fprintln(os.Stderr, `
|
||||
The step-yubikey-init command initializes a public key infrastructure (PKI)
|
||||
to be used by step-ca.
|
||||
|
||||
This tool is experimental and in the future it will be integrated in step cli.
|
||||
|
||||
OPTIONS`)
|
||||
fmt.Fprintln(os.Stderr)
|
||||
flag.PrintDefaults()
|
||||
fmt.Fprintln(os.Stderr, `
|
||||
COPYRIGHT
|
||||
|
||||
(c) 2018-2020 Smallstep Labs, Inc.`)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func checkSlot(k kms.KeyManager, slot string) {
|
||||
if _, err := k.GetPublicKey(&apiv1.GetPublicKeyRequest{
|
||||
Name: slot,
|
||||
}); err == nil {
|
||||
fmt.Fprintf(os.Stderr, "⚠️ Your YubiKey already has a key in the slot %s.\n", slot)
|
||||
fmt.Fprintln(os.Stderr, " If you want to delete it and start fresh, use `--force`.")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func createPKI(k kms.KeyManager, c Config) error {
|
||||
var err error
|
||||
ui.Println("Creating PKI ...")
|
||||
now := time.Now()
|
||||
|
||||
// Root Certificate
|
||||
var signer crypto.Signer
|
||||
var root *x509.Certificate
|
||||
if c.RootFile != "" && c.KeyFile != "" {
|
||||
root, err = pemutil.ReadCertificate(c.RootFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key, err := pemutil.Read(c.KeyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ok bool
|
||||
if signer, ok = key.(crypto.Signer); !ok {
|
||||
return errors.Errorf("key type '%T' does not implement a signer", key)
|
||||
}
|
||||
} else {
|
||||
resp, err := k.CreateKey(&apiv1.CreateKeyRequest{
|
||||
Name: c.RootSlot,
|
||||
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
signer, err = k.CreateSigner(&resp.CreateSignerRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
template := &x509.Certificate{
|
||||
IsCA: true,
|
||||
NotBefore: now,
|
||||
NotAfter: now.Add(time.Hour * 24 * 365 * 10),
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
||||
BasicConstraintsValid: true,
|
||||
MaxPathLen: 1,
|
||||
MaxPathLenZero: false,
|
||||
Issuer: pkix.Name{CommonName: "YubiKey Smallstep Root"},
|
||||
Subject: pkix.Name{CommonName: "YubiKey Smallstep Root"},
|
||||
SerialNumber: mustSerialNumber(),
|
||||
SubjectKeyId: mustSubjectKeyID(resp.PublicKey),
|
||||
AuthorityKeyId: mustSubjectKeyID(resp.PublicKey),
|
||||
}
|
||||
|
||||
b, err := x509.CreateCertificate(rand.Reader, template, template, resp.PublicKey, signer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
root, err = x509.ParseCertificate(b)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error parsing root certificate")
|
||||
}
|
||||
|
||||
if cm, ok := k.(kms.CertificateManager); ok {
|
||||
if err = cm.StoreCertificate(&apiv1.StoreCertificateRequest{
|
||||
Name: c.RootSlot,
|
||||
Certificate: root,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = utils.WriteFile("root_ca.crt", pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: b,
|
||||
}), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ui.PrintSelected("Root Key", resp.Name)
|
||||
ui.PrintSelected("Root Certificate", "root_ca.crt")
|
||||
}
|
||||
|
||||
// Intermediate Certificate
|
||||
var keyName string
|
||||
var publicKey crypto.PublicKey
|
||||
if c.RootOnly {
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error creating intermediate key")
|
||||
}
|
||||
|
||||
pass, err := ui.PromptPasswordGenerate("What do you want your password to be? [leave empty and we'll generate one]",
|
||||
ui.WithRichPrompt())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = pemutil.Serialize(priv, pemutil.WithPassword(pass), pemutil.ToFile("intermediate_ca_key", 0600))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
publicKey = priv.Public()
|
||||
} else {
|
||||
resp, err := k.CreateKey(&apiv1.CreateKeyRequest{
|
||||
Name: c.CrtSlot,
|
||||
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
publicKey = resp.PublicKey
|
||||
keyName = resp.Name
|
||||
}
|
||||
|
||||
template := &x509.Certificate{
|
||||
IsCA: true,
|
||||
NotBefore: now,
|
||||
NotAfter: now.Add(time.Hour * 24 * 365 * 10),
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
||||
BasicConstraintsValid: true,
|
||||
MaxPathLen: 0,
|
||||
MaxPathLenZero: true,
|
||||
Issuer: root.Subject,
|
||||
Subject: pkix.Name{CommonName: "YubiKey Smallstep Intermediate"},
|
||||
SerialNumber: mustSerialNumber(),
|
||||
SubjectKeyId: mustSubjectKeyID(publicKey),
|
||||
}
|
||||
|
||||
b, err := x509.CreateCertificate(rand.Reader, template, root, publicKey, signer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
intermediate, err := x509.ParseCertificate(b)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error parsing intermediate certificate")
|
||||
}
|
||||
|
||||
if cm, ok := k.(kms.CertificateManager); ok {
|
||||
if err = cm.StoreCertificate(&apiv1.StoreCertificateRequest{
|
||||
Name: c.CrtSlot,
|
||||
Certificate: intermediate,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = utils.WriteFile("intermediate_ca.crt", pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: b,
|
||||
}), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.RootOnly {
|
||||
ui.PrintSelected("Intermediate Key", "intermediate_ca_key")
|
||||
} else {
|
||||
ui.PrintSelected("Intermediate Key", keyName)
|
||||
}
|
||||
|
||||
ui.PrintSelected("Intermediate Certificate", "intermediate_ca.crt")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func mustSerialNumber() *big.Int {
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
sn, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sn
|
||||
}
|
||||
|
||||
func mustSubjectKeyID(key crypto.PublicKey) []byte {
|
||||
b, err := x509.MarshalPKIXPublicKey(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
hash := sha1.Sum(b)
|
||||
return hash[:]
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package apiv1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var registry = new(sync.Map)
|
||||
|
||||
// KeyManagerNewFunc is the type that represents the method to initialize a new
|
||||
// KeyManager.
|
||||
type KeyManagerNewFunc func(ctx context.Context, opts Options) (KeyManager, error)
|
||||
|
||||
// Register adds to the registry a method to create a KeyManager of type t.
|
||||
func Register(t Type, fn KeyManagerNewFunc) {
|
||||
registry.Store(t, fn)
|
||||
}
|
||||
|
||||
// LoadKeyManagerNewFunc returns the function initialize a KayManager.
|
||||
func LoadKeyManagerNewFunc(t Type) (KeyManagerNewFunc, bool) {
|
||||
v, ok := registry.Load(t)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
fn, ok := v.(KeyManagerNewFunc)
|
||||
return fn, ok
|
||||
}
|
@ -0,0 +1,252 @@
|
||||
// +build cgo
|
||||
|
||||
package yubikey
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/go-piv/piv-go/piv"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/kms/apiv1"
|
||||
)
|
||||
|
||||
// YubiKey implements the KMS interface on a YubiKey.
|
||||
type YubiKey struct {
|
||||
yk *piv.YubiKey
|
||||
pin string
|
||||
}
|
||||
|
||||
// New initializes a new YubiKey.
|
||||
// TODO(mariano): only one card is currently supported.
|
||||
func New(ctx context.Context, opts apiv1.Options) (*YubiKey, error) {
|
||||
cards, err := piv.Cards()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(cards) == 0 {
|
||||
return nil, errors.New("error detecting yubikey: try removing and reconnecting the device")
|
||||
}
|
||||
|
||||
yk, err := piv.Open(cards[0])
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error opening yubikey")
|
||||
}
|
||||
|
||||
return &YubiKey{
|
||||
yk: yk,
|
||||
pin: opts.Pin,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
apiv1.Register(apiv1.YubiKey, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) {
|
||||
return New(ctx, opts)
|
||||
})
|
||||
}
|
||||
|
||||
// LoadCertificate implements kms.CertificateManager and loads a certificate
|
||||
// from the YubiKey.
|
||||
func (k *YubiKey) LoadCertificate(req *apiv1.LoadCertificateRequest) (*x509.Certificate, error) {
|
||||
slot, err := getSlot(req.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert, err := k.yk.Certificate(slot)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error retrieving certificate")
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// StoreCertificate implements kms.CertificateManager and stores a certificate
|
||||
// in the YubiKey.
|
||||
func (k *YubiKey) StoreCertificate(req *apiv1.StoreCertificateRequest) error {
|
||||
if req.Certificate == nil {
|
||||
return errors.New("storeCertificateRequest 'Certificate' cannot be nil")
|
||||
}
|
||||
|
||||
slot, err := getSlot(req.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = k.yk.SetCertificate(piv.DefaultManagementKey, slot, req.Certificate)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error storing certificate")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPublicKey returns the public key present in the YubiKey signature slot.
|
||||
func (k *YubiKey) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) {
|
||||
slot, err := getSlot(req.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert, err := k.yk.Certificate(slot)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error retrieving certificate")
|
||||
}
|
||||
|
||||
return cert.PublicKey, nil
|
||||
}
|
||||
|
||||
// CreateKey generates a new key in the YubiKey and returns the public key.
|
||||
func (k *YubiKey) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) {
|
||||
alg, err := getSignatureAlgorithm(req.SignatureAlgorithm, req.Bits)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
slot, name, err := getSlotAndName(req.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pub, err := k.yk.GenerateKey(piv.DefaultManagementKey, slot, piv.Key{
|
||||
Algorithm: alg,
|
||||
PINPolicy: piv.PINPolicyAlways,
|
||||
TouchPolicy: piv.TouchPolicyNever,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error generating key")
|
||||
}
|
||||
return &apiv1.CreateKeyResponse{
|
||||
Name: name,
|
||||
PublicKey: pub,
|
||||
CreateSignerRequest: apiv1.CreateSignerRequest{
|
||||
SigningKey: name,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateSigner creates a signer using the key present in the YubiKey signature
|
||||
// slot.
|
||||
func (k *YubiKey) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error) {
|
||||
slot, err := getSlot(req.SigningKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert, err := k.yk.Certificate(slot)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error retrieving certificate")
|
||||
}
|
||||
|
||||
priv, err := k.yk.PrivateKey(slot, cert.PublicKey, piv.KeyAuth{
|
||||
PIN: k.pin,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error retrieving private key")
|
||||
}
|
||||
|
||||
signer, ok := priv.(crypto.Signer)
|
||||
if !ok {
|
||||
return nil, errors.New("private key is not a crypto.Signer")
|
||||
}
|
||||
return signer, nil
|
||||
}
|
||||
|
||||
// Close releases the connection to the YubiKey.
|
||||
func (k *YubiKey) Close() error {
|
||||
return errors.Wrap(k.yk.Close(), "error closing yubikey")
|
||||
}
|
||||
|
||||
// signatureAlgorithmMapping is a mapping between the step signature algorithm,
|
||||
// and bits for RSA keys, with yubikey ones.
|
||||
var signatureAlgorithmMapping = map[apiv1.SignatureAlgorithm]interface{}{
|
||||
apiv1.UnspecifiedSignAlgorithm: piv.AlgorithmEC256,
|
||||
apiv1.SHA256WithRSA: map[int]piv.Algorithm{
|
||||
0: piv.AlgorithmRSA2048,
|
||||
1024: piv.AlgorithmRSA1024,
|
||||
2048: piv.AlgorithmRSA2048,
|
||||
},
|
||||
apiv1.SHA512WithRSA: map[int]piv.Algorithm{
|
||||
0: piv.AlgorithmRSA2048,
|
||||
1024: piv.AlgorithmRSA1024,
|
||||
2048: piv.AlgorithmRSA2048,
|
||||
},
|
||||
apiv1.SHA256WithRSAPSS: map[int]piv.Algorithm{
|
||||
0: piv.AlgorithmRSA2048,
|
||||
1024: piv.AlgorithmRSA1024,
|
||||
2048: piv.AlgorithmRSA2048,
|
||||
},
|
||||
apiv1.SHA512WithRSAPSS: map[int]piv.Algorithm{
|
||||
0: piv.AlgorithmRSA2048,
|
||||
1024: piv.AlgorithmRSA1024,
|
||||
2048: piv.AlgorithmRSA2048,
|
||||
},
|
||||
apiv1.ECDSAWithSHA256: piv.AlgorithmEC256,
|
||||
apiv1.ECDSAWithSHA384: piv.AlgorithmEC384,
|
||||
}
|
||||
|
||||
func getSignatureAlgorithm(alg apiv1.SignatureAlgorithm, bits int) (piv.Algorithm, error) {
|
||||
v, ok := signatureAlgorithmMapping[alg]
|
||||
if !ok {
|
||||
return 0, errors.Errorf("YubiKey does not support signature algorithm '%s'", alg)
|
||||
}
|
||||
|
||||
switch v := v.(type) {
|
||||
case piv.Algorithm:
|
||||
return v, nil
|
||||
case map[int]piv.Algorithm:
|
||||
signatureAlgorithm, ok := v[bits]
|
||||
if !ok {
|
||||
return 0, errors.Errorf("YubiKey does not support signature algorithm '%s' with '%d' bits", alg, bits)
|
||||
}
|
||||
return signatureAlgorithm, nil
|
||||
default:
|
||||
return 0, errors.Errorf("unexpected error: this should not happen")
|
||||
}
|
||||
}
|
||||
|
||||
var slotMapping = map[string]piv.Slot{
|
||||
"9a": piv.SlotAuthentication,
|
||||
"9c": piv.SlotSignature,
|
||||
"9e": piv.SlotCardAuthentication,
|
||||
"9d": piv.SlotKeyManagement,
|
||||
}
|
||||
|
||||
func getSlot(name string) (piv.Slot, error) {
|
||||
slot, _, err := getSlotAndName(name)
|
||||
return slot, err
|
||||
}
|
||||
|
||||
func getSlotAndName(name string) (piv.Slot, string, error) {
|
||||
if name == "" {
|
||||
return piv.SlotSignature, "yubikey:slot-id=9c", nil
|
||||
}
|
||||
|
||||
var slotID string
|
||||
name = strings.ToLower(name)
|
||||
if strings.HasPrefix(name, "yubikey:") {
|
||||
u, err := url.Parse(name)
|
||||
if err != nil {
|
||||
return piv.Slot{}, "", errors.Wrapf(err, "error parsing '%s'", name)
|
||||
}
|
||||
v, err := url.ParseQuery(u.Opaque)
|
||||
if err != nil {
|
||||
return piv.Slot{}, "", errors.Wrapf(err, "error parsing '%s'", name)
|
||||
}
|
||||
if slotID = v.Get("slot-id"); slotID == "" {
|
||||
return piv.Slot{}, "", errors.Wrapf(err, "error parsing '%s': slot-id is missing", name)
|
||||
}
|
||||
} else {
|
||||
slotID = name
|
||||
}
|
||||
|
||||
s, ok := slotMapping[slotID]
|
||||
if !ok {
|
||||
return piv.Slot{}, "", errors.Errorf("usupported slot-id '%s'", name)
|
||||
}
|
||||
|
||||
name = "yubikey:slot-id=" + url.QueryEscape(slotID)
|
||||
return s, name, nil
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
// +build !cgo
|
||||
|
||||
package yubikey
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/kms/apiv1"
|
||||
)
|
||||
|
||||
func init() {
|
||||
apiv1.Register(apiv1.YubiKey, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) {
|
||||
name := filepath.Base(os.Args[0])
|
||||
return nil, errors.Errorf("unsupported kms type 'yubikey': %s is compiled without cgo support", name)
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue