forked from TrueCloudLab/certificates
commit
2bc69d3edd
17 changed files with 780 additions and 23 deletions
|
@ -7,6 +7,7 @@ addons:
|
||||||
- debhelper
|
- debhelper
|
||||||
- fakeroot
|
- fakeroot
|
||||||
- bash-completion
|
- bash-completion
|
||||||
|
- libpcsclite-dev
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- V=1
|
- V=1
|
||||||
|
|
14
Makefile
14
Makefile
|
@ -2,6 +2,8 @@ PKG?=github.com/smallstep/certificates/cmd/step-ca
|
||||||
BINNAME?=step-ca
|
BINNAME?=step-ca
|
||||||
CLOUDKMS_BINNAME?=step-cloudkms-init
|
CLOUDKMS_BINNAME?=step-cloudkms-init
|
||||||
CLOUDKMS_PKG?=github.com/smallstep/certificates/cmd/step-cloudkms-init
|
CLOUDKMS_PKG?=github.com/smallstep/certificates/cmd/step-cloudkms-init
|
||||||
|
YUBIKEY_BINNAME?=step-yubikey-init
|
||||||
|
YUBIKEY_PKG?=github.com/smallstep/certificates/cmd/step-yubikey-init
|
||||||
|
|
||||||
# Set V to 1 for verbose output from the Makefile
|
# Set V to 1 for verbose output from the Makefile
|
||||||
Q=$(if $V,,@)
|
Q=$(if $V,,@)
|
||||||
|
@ -64,7 +66,7 @@ GOFLAGS := CGO_ENABLED=0
|
||||||
download:
|
download:
|
||||||
$Q go mod download
|
$Q go mod download
|
||||||
|
|
||||||
build: $(PREFIX)bin/$(BINNAME) $(PREFIX)bin/$(CLOUDKMS_BINNAME)
|
build: $(PREFIX)bin/$(BINNAME) $(PREFIX)bin/$(CLOUDKMS_BINNAME) $(PREFIX)bin/$(YUBIKEY_BINNAME)
|
||||||
@echo "Build Complete!"
|
@echo "Build Complete!"
|
||||||
|
|
||||||
$(PREFIX)bin/$(BINNAME): download $(call rwildcard,*.go)
|
$(PREFIX)bin/$(BINNAME): download $(call rwildcard,*.go)
|
||||||
|
@ -75,12 +77,12 @@ $(PREFIX)bin/$(CLOUDKMS_BINNAME): download $(call rwildcard,*.go)
|
||||||
$Q mkdir -p $(@D)
|
$Q mkdir -p $(@D)
|
||||||
$Q $(GOOS_OVERRIDE) $(GOFLAGS) go build -v -o $(PREFIX)bin/$(CLOUDKMS_BINNAME) $(LDFLAGS) $(CLOUDKMS_PKG)
|
$Q $(GOOS_OVERRIDE) $(GOFLAGS) go build -v -o $(PREFIX)bin/$(CLOUDKMS_BINNAME) $(LDFLAGS) $(CLOUDKMS_PKG)
|
||||||
|
|
||||||
|
$(PREFIX)bin/$(YUBIKEY_BINNAME): download $(call rwildcard,*.go)
|
||||||
|
$Q mkdir -p $(@D)
|
||||||
|
$Q $(GOOS_OVERRIDE) $(GOFLAGS) go build -v -o $(PREFIX)bin/$(YUBIKEY_BINNAME) $(LDFLAGS) $(YUBIKEY_PKG)
|
||||||
|
|
||||||
# Target to force a build of step-ca without running tests
|
# Target to force a build of step-ca without running tests
|
||||||
simple:
|
simple: build
|
||||||
$Q mkdir -p $(PREFIX)bin
|
|
||||||
$Q $(GOOS_OVERRIDE) $(GOFLAGS) go build -v -o $(PREFIX)bin/$(BINNAME) $(LDFLAGS) $(PKG)
|
|
||||||
$Q $(GOOS_OVERRIDE) $(GOFLAGS) go build -v -o $(PREFIX)bin/$(CLOUDKMS_BINNAME) $(LDFLAGS) $(CLOUDKMS_PKG)
|
|
||||||
@echo "Build Complete!"
|
|
||||||
|
|
||||||
.PHONY: download build simple
|
.PHONY: download build simple
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -327,5 +328,8 @@ func (a *Authority) GetDatabase() db.AuthDB {
|
||||||
|
|
||||||
// Shutdown safely shuts down any clients, databases, etc. held by the Authority.
|
// Shutdown safely shuts down any clients, databases, etc. held by the Authority.
|
||||||
func (a *Authority) Shutdown() error {
|
func (a *Authority) Shutdown() error {
|
||||||
|
if err := a.keyManager.Close(); err != nil {
|
||||||
|
log.Printf("error closing the key manager: %v", err)
|
||||||
|
}
|
||||||
return a.db.Shutdown()
|
return a.db.Shutdown()
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,7 @@ func NewTLSRenewer(cert *tls.Certificate, fn RenewFunc, opts ...tlsRenewerOption
|
||||||
r := &TLSRenewer{
|
r := &TLSRenewer{
|
||||||
RenewCertificate: fn,
|
RenewCertificate: fn,
|
||||||
cert: cert,
|
cert: cert,
|
||||||
|
certNotAfter: cert.Leaf.NotAfter.Add(-1 * time.Minute),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, f := range opts {
|
for _, f := range opts {
|
||||||
|
|
|
@ -138,6 +138,7 @@ func createPKI(c *cloudkms.CloudKMS, project, location, keyRing string, protecti
|
||||||
Subject: pkix.Name{CommonName: "Smallstep Root"},
|
Subject: pkix.Name{CommonName: "Smallstep Root"},
|
||||||
SerialNumber: mustSerialNumber(),
|
SerialNumber: mustSerialNumber(),
|
||||||
SubjectKeyId: mustSubjectKeyID(resp.PublicKey),
|
SubjectKeyId: mustSubjectKeyID(resp.PublicKey),
|
||||||
|
AuthorityKeyId: mustSubjectKeyID(resp.PublicKey),
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := x509.CreateCertificate(rand.Reader, root, root, resp.PublicKey, signer)
|
b, err := x509.CreateCertificate(rand.Reader, root, root, resp.PublicKey, signer)
|
||||||
|
|
323
cmd/step-yubikey-init/main.go
Normal file
323
cmd/step-yubikey-init/main.go
Normal file
|
@ -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[:]
|
||||||
|
}
|
75
docs/kms.md
75
docs/kms.md
|
@ -6,7 +6,7 @@ private keys and sign certificates.
|
||||||
Support for multiple KMS are planned, but currently the only supported one is
|
Support for multiple KMS are planned, but currently the only supported one is
|
||||||
Google's Cloud KMS.
|
Google's Cloud KMS.
|
||||||
|
|
||||||
## Google's Cloud KMS.
|
## Google's Cloud KMS
|
||||||
|
|
||||||
[Cloud KMS](https://cloud.google.com/kms) is the Google's cloud-hosted KMS that
|
[Cloud KMS](https://cloud.google.com/kms) is the Google's cloud-hosted KMS that
|
||||||
allows you to store the cryptographic keys, and sign certificates using their
|
allows you to store the cryptographic keys, and sign certificates using their
|
||||||
|
@ -65,3 +65,76 @@ Creating SSH Keys ...
|
||||||
```
|
```
|
||||||
|
|
||||||
See `step-cloudkms-init --help` for more options.
|
See `step-cloudkms-init --help` for more options.
|
||||||
|
|
||||||
|
## YubiKey
|
||||||
|
|
||||||
|
And incomplete and experimental support for [YubiKeys](https://www.yubico.com)
|
||||||
|
is also available. Support for YubiKeys is not enabled by default and only TLS
|
||||||
|
signing can be configured.
|
||||||
|
|
||||||
|
The YubiKey implementation requires cgo, and our build system does not produce
|
||||||
|
binaries with it. To enable YubiKey download the source code and run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make build GOFLAGS=""
|
||||||
|
```
|
||||||
|
|
||||||
|
The implementation uses [piv-go](https://github.com/go-piv/piv-go), and it
|
||||||
|
requires PCSC support, this is available by default on macOS and Windows
|
||||||
|
operating systems, but on Linux piv-go requires PCSC lite.
|
||||||
|
|
||||||
|
To install on Debian-based distributions, run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo apt-get install libpcsclite-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
On Fedora:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo yum install pcsc-lite-devel
|
||||||
|
```
|
||||||
|
|
||||||
|
On CentOS:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo yum install 'dnf-command(config-manager)'
|
||||||
|
sudo yum config-manager --set-enabled PowerTools
|
||||||
|
sudo yum install pcsc-lite-devel
|
||||||
|
```
|
||||||
|
|
||||||
|
The initialization of the public key infrastructure (PKI) for YubiKeys, is not
|
||||||
|
currently integrated into [step](https://github.com/smallstep/cli), but an
|
||||||
|
experimental tool named `step-yubikey-init` is available for this use case. At
|
||||||
|
some point this tool will be integrated into `step` and it will be deleted.
|
||||||
|
|
||||||
|
To configure your YubiKey just run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ bin/step-yubikey-init
|
||||||
|
What is the YubiKey PIN?:
|
||||||
|
Creating PKI ...
|
||||||
|
✔ Root Key: yubikey:slot-id=9a
|
||||||
|
✔ Root Certificate: root_ca.crt
|
||||||
|
✔ Intermediate Key: yubikey:slot-id=9c
|
||||||
|
✔ Intermediate Certificate: intermediate_ca.crt
|
||||||
|
```
|
||||||
|
|
||||||
|
See `step-yubikey-init --help` for more options.
|
||||||
|
|
||||||
|
Finally to enable it in the ca.json, point the `root` and `crt` to the generated
|
||||||
|
certificates, set the `key` with the yubikey URI generated in the previous step
|
||||||
|
and configure the `kms` property with the `type` and your `pin` in it.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"root": "/path/to/root_ca.crt",
|
||||||
|
"crt": "/path/to/intermediate_ca.crt",
|
||||||
|
"key": "yubikey:slot-id=9c",
|
||||||
|
"kms": {
|
||||||
|
"type": "yubikey",
|
||||||
|
"pin": "123456"
|
||||||
|
},
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -6,6 +6,7 @@ require (
|
||||||
cloud.google.com/go v0.51.0
|
cloud.google.com/go v0.51.0
|
||||||
github.com/Masterminds/sprig/v3 v3.0.0
|
github.com/Masterminds/sprig/v3 v3.0.0
|
||||||
github.com/go-chi/chi v4.0.2+incompatible
|
github.com/go-chi/chi v4.0.2+incompatible
|
||||||
|
github.com/go-piv/piv-go v1.5.0
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5
|
github.com/googleapis/gax-go/v2 v2.0.5
|
||||||
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a // indirect
|
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a // indirect
|
||||||
github.com/lunixbochs/vtclean v1.0.0 // indirect
|
github.com/lunixbochs/vtclean v1.0.0 // indirect
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -124,6 +124,8 @@ github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTD
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
|
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
|
||||||
|
github.com/go-piv/piv-go v1.5.0 h1:UtHPfrJsZKY+Z3UIjmJLh6DY+KtmNOl/9b/zt4N81pM=
|
||||||
|
github.com/go-piv/piv-go v1.5.0/go.mod h1:ON2WvQncm7dIkCQ7kYJs+nc3V4jHGfrrJnSF8HKy7Gk=
|
||||||
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
||||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
|
|
@ -1,11 +1,28 @@
|
||||||
package apiv1
|
package apiv1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/x509"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// KeyManager is the interface implemented by all the KMS.
|
||||||
|
type KeyManager interface {
|
||||||
|
GetPublicKey(req *GetPublicKeyRequest) (crypto.PublicKey, error)
|
||||||
|
CreateKey(req *CreateKeyRequest) (*CreateKeyResponse, error)
|
||||||
|
CreateSigner(req *CreateSignerRequest) (crypto.Signer, error)
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// CertificateManager is the interface implemented by the KMS that can load and
|
||||||
|
// store x509.Certificates.
|
||||||
|
type CertificateManager interface {
|
||||||
|
LoadCerticate(req *LoadCertificateRequest) (*x509.Certificate, error)
|
||||||
|
StoreCertificate(req *StoreCertificateRequest) error
|
||||||
|
}
|
||||||
|
|
||||||
// ErrNotImplemented
|
// ErrNotImplemented
|
||||||
type ErrNotImplemented struct {
|
type ErrNotImplemented struct {
|
||||||
msg string
|
msg string
|
||||||
|
@ -32,6 +49,8 @@ const (
|
||||||
AmazonKMS Type = "awskms"
|
AmazonKMS Type = "awskms"
|
||||||
// PKCS11 is a KMS implementation using the PKCS11 standard.
|
// PKCS11 is a KMS implementation using the PKCS11 standard.
|
||||||
PKCS11 Type = "pkcs11"
|
PKCS11 Type = "pkcs11"
|
||||||
|
// YubiKey is a KMS implementation using a YubiKey PIV.
|
||||||
|
YubiKey Type = "yubikey"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
|
@ -56,6 +75,7 @@ func (o *Options) Validate() error {
|
||||||
|
|
||||||
switch Type(strings.ToLower(o.Type)) {
|
switch Type(strings.ToLower(o.Type)) {
|
||||||
case DefaultKMS, SoftKMS, CloudKMS:
|
case DefaultKMS, SoftKMS, CloudKMS:
|
||||||
|
case YubiKey:
|
||||||
case AmazonKMS:
|
case AmazonKMS:
|
||||||
return ErrNotImplemented{"support for AmazonKMS is not yet implemented"}
|
return ErrNotImplemented{"support for AmazonKMS is not yet implemented"}
|
||||||
case PKCS11:
|
case PKCS11:
|
||||||
|
|
27
kms/apiv1/registry.go
Normal file
27
kms/apiv1/registry.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package apiv1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -124,3 +125,16 @@ type CreateSignerRequest struct {
|
||||||
PublicKeyPEM []byte
|
PublicKeyPEM []byte
|
||||||
Password []byte
|
Password []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadCertificateRequest is the parameter used in the LoadCertificate method of
|
||||||
|
// a CertificateManager.
|
||||||
|
type LoadCertificateRequest struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreCertificateRequest is the parameter used in the StoreCertificate method
|
||||||
|
// of a CertificateManager.
|
||||||
|
type StoreCertificateRequest struct {
|
||||||
|
Name string
|
||||||
|
Certificate *x509.Certificate
|
||||||
|
}
|
||||||
|
|
|
@ -93,6 +93,12 @@ func New(ctx context.Context, opts apiv1.Options) (*CloudKMS, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
apiv1.Register(apiv1.CloudKMS, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) {
|
||||||
|
return New(ctx, opts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// NewCloudKMS creates a CloudKMS with a given client.
|
// NewCloudKMS creates a CloudKMS with a given client.
|
||||||
func NewCloudKMS(client KeyManagementClient) *CloudKMS {
|
func NewCloudKMS(client KeyManagementClient) *CloudKMS {
|
||||||
return &CloudKMS{
|
return &CloudKMS{
|
||||||
|
|
37
kms/kms.go
37
kms/kms.go
|
@ -2,22 +2,25 @@ package kms
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/smallstep/certificates/kms/apiv1"
|
"github.com/smallstep/certificates/kms/apiv1"
|
||||||
"github.com/smallstep/certificates/kms/cloudkms"
|
|
||||||
"github.com/smallstep/certificates/kms/softkms"
|
// Enabled kms interfaces.
|
||||||
|
_ "github.com/smallstep/certificates/kms/cloudkms"
|
||||||
|
_ "github.com/smallstep/certificates/kms/softkms"
|
||||||
|
|
||||||
|
// Experimental kms interfaces.
|
||||||
|
_ "github.com/smallstep/certificates/kms/yubikey"
|
||||||
)
|
)
|
||||||
|
|
||||||
// KeyManager is the interface implemented by all the KMS.
|
// KeyManager is the interface implemented by all the KMS.
|
||||||
type KeyManager interface {
|
type KeyManager = apiv1.KeyManager
|
||||||
GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error)
|
|
||||||
CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error)
|
// CertificateManager is the interface implemented by the KMS that can load and
|
||||||
CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error)
|
// store x509.Certificates.
|
||||||
Close() error
|
type CertificateManager = apiv1.CertificateManager
|
||||||
}
|
|
||||||
|
|
||||||
// New initializes a new KMS from the given type.
|
// New initializes a new KMS from the given type.
|
||||||
func New(ctx context.Context, opts apiv1.Options) (KeyManager, error) {
|
func New(ctx context.Context, opts apiv1.Options) (KeyManager, error) {
|
||||||
|
@ -25,12 +28,14 @@ func New(ctx context.Context, opts apiv1.Options) (KeyManager, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch apiv1.Type(strings.ToLower(opts.Type)) {
|
t := apiv1.Type(strings.ToLower(opts.Type))
|
||||||
case apiv1.DefaultKMS, apiv1.SoftKMS:
|
if t == apiv1.DefaultKMS {
|
||||||
return softkms.New(ctx, opts)
|
t = apiv1.SoftKMS
|
||||||
case apiv1.CloudKMS:
|
|
||||||
return cloudkms.New(ctx, opts)
|
|
||||||
default:
|
|
||||||
return nil, errors.Errorf("unsupported kms type '%s'", opts.Type)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn, ok := apiv1.LoadKeyManagerNewFunc(t)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.Errorf("unsupported kms type '%s'", t)
|
||||||
|
}
|
||||||
|
return fn(ctx, opts)
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,12 @@ func New(ctx context.Context, opts apiv1.Options) (*SoftKMS, error) {
|
||||||
return &SoftKMS{}, nil
|
return &SoftKMS{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
apiv1.Register(apiv1.SoftKMS, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) {
|
||||||
|
return New(ctx, opts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Close is a noop that just returns nil.
|
// Close is a noop that just returns nil.
|
||||||
func (k *SoftKMS) Close() error {
|
func (k *SoftKMS) Close() error {
|
||||||
return nil
|
return nil
|
||||||
|
|
252
kms/yubikey/yubikey.go
Normal file
252
kms/yubikey/yubikey.go
Normal file
|
@ -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
|
||||||
|
}
|
19
kms/yubikey/yubikey_no_cgo.go
Normal file
19
kms/yubikey/yubikey_no_cgo.go
Normal file
|
@ -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 a new issue