Merge branch 'master' into hs/scep-master

This commit is contained in:
Herman Slatman 2021-02-19 10:16:39 +01:00
commit 9e43dc85d8
No known key found for this signature in database
GPG key ID: F4D8A44EA0A75A4F
35 changed files with 2727 additions and 114 deletions

View file

@ -18,7 +18,7 @@ env:
before_script: before_script:
- make bootstrap - make bootstrap
script: script:
- make - make travis
- make artifacts - make artifacts
after_success: after_success:
- bash <(curl -s https://codecov.io/bash) -t "$CODECOV_TOKEN" || echo "Codecov did - bash <(curl -s https://codecov.io/bash) -t "$CODECOV_TOKEN" || echo "Codecov did

View file

@ -6,6 +6,8 @@ AWSKMS_BINNAME?=step-awskms-init
AWSKMS_PKG?=github.com/smallstep/certificates/cmd/step-awskms-init AWSKMS_PKG?=github.com/smallstep/certificates/cmd/step-awskms-init
YUBIKEY_BINNAME?=step-yubikey-init YUBIKEY_BINNAME?=step-yubikey-init
YUBIKEY_PKG?=github.com/smallstep/certificates/cmd/step-yubikey-init YUBIKEY_PKG?=github.com/smallstep/certificates/cmd/step-yubikey-init
PKCS11_BINNAME?=step-pkcs11-init
PKCS11_PKG?=github.com/smallstep/certificates/cmd/step-pkcs11-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,,@)
@ -16,7 +18,9 @@ OUTPUT_ROOT=output/
all: lint test build all: lint test build
.PHONY: all travis: lintcgo testcgo build
.PHONY: all travis
######################################### #########################################
# Bootstrapping # Bootstrapping
@ -76,7 +80,7 @@ GOFLAGS := CGO_ENABLED=0
download: download:
$Q go mod download $Q go mod download
build: $(PREFIX)bin/$(BINNAME) $(PREFIX)bin/$(CLOUDKMS_BINNAME) $(PREFIX)bin/$(AWSKMS_BINNAME) $(PREFIX)bin/$(YUBIKEY_BINNAME) build: $(PREFIX)bin/$(BINNAME) $(PREFIX)bin/$(CLOUDKMS_BINNAME) $(PREFIX)bin/$(AWSKMS_BINNAME) $(PREFIX)bin/$(YUBIKEY_BINNAME) $(PREFIX)bin/$(PKCS11_BINNAME)
@echo "Build Complete!" @echo "Build Complete!"
$(PREFIX)bin/$(BINNAME): download $(call rwildcard,*.go) $(PREFIX)bin/$(BINNAME): download $(call rwildcard,*.go)
@ -95,6 +99,10 @@ $(PREFIX)bin/$(YUBIKEY_BINNAME): download $(call rwildcard,*.go)
$Q mkdir -p $(@D) $Q mkdir -p $(@D)
$Q $(GOOS_OVERRIDE) $(GOFLAGS) go build -v -o $(PREFIX)bin/$(YUBIKEY_BINNAME) $(LDFLAGS) $(YUBIKEY_PKG) $Q $(GOOS_OVERRIDE) $(GOFLAGS) go build -v -o $(PREFIX)bin/$(YUBIKEY_BINNAME) $(LDFLAGS) $(YUBIKEY_PKG)
$(PREFIX)bin/$(PKCS11_BINNAME): download $(call rwildcard,*.go)
$Q mkdir -p $(@D)
$Q $(GOOS_OVERRIDE) $(GOFLAGS) go build -v -o $(PREFIX)bin/$(PKCS11_BINNAME) $(LDFLAGS) $(PKCS11_PKG)
# Target to force a build of step-ca without running tests # Target to force a build of step-ca without running tests
simple: build simple: build
@ -115,7 +123,10 @@ generate:
test: test:
$Q $(GOFLAGS) go test -short -coverprofile=coverage.out ./... $Q $(GOFLAGS) go test -short -coverprofile=coverage.out ./...
.PHONY: test testcgo:
$Q go test -short -coverprofile=coverage.out ./...
.PHONY: test testcgo
integrate: integration integrate: integration
@ -132,9 +143,12 @@ fmt:
$Q gofmt -l -w $(SRC) $Q gofmt -l -w $(SRC)
lint: lint:
$Q $(GOFLAGS) LOG_LEVEL=error golangci-lint run --timeout=30m
lintcgo:
$Q LOG_LEVEL=error golangci-lint run --timeout=30m $Q LOG_LEVEL=error golangci-lint run --timeout=30m
.PHONY: lint fmt .PHONY: fmt lint lintcgo
######################################### #########################################
# Install # Install
@ -171,6 +185,9 @@ endif
ifneq ($(YUBIKEY_BINNAME),"") ifneq ($(YUBIKEY_BINNAME),"")
$Q rm -f bin/$(YUBIKEY_BINNAME) $Q rm -f bin/$(YUBIKEY_BINNAME)
endif endif
ifneq ($(PKCS11_BINNAME),"")
$Q rm -f bin/$(PKCS11_BINNAME)
endif
.PHONY: clean .PHONY: clean

View file

@ -387,3 +387,10 @@ func (a *Authority) Shutdown() error {
} }
return a.db.Shutdown() return a.db.Shutdown()
} }
// CloseForReload closes internal services, to allow a safe reload.
func (a *Authority) CloseForReload() {
if err := a.keyManager.Close(); err != nil {
log.Printf("error closing the key manager: %v", err)
}
}

View file

@ -306,3 +306,17 @@ func TestNewEmbedded_GetTLSCertificate(t *testing.T) {
assert.True(t, cert.Leaf.IPAddresses[0].Equal(net.ParseIP("127.0.0.1"))) assert.True(t, cert.Leaf.IPAddresses[0].Equal(net.ParseIP("127.0.0.1")))
assert.True(t, cert.Leaf.IPAddresses[1].Equal(net.ParseIP("::1"))) assert.True(t, cert.Leaf.IPAddresses[1].Equal(net.ParseIP("::1")))
} }
func TestAuthority_CloseForReload(t *testing.T) {
tests := []struct {
name string
auth *Authority
}{
{"ok", testAuthority(t)},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.auth.CloseForReload()
})
}
}

View file

@ -250,9 +250,11 @@ func (ca *CA) Reload() error {
} }
// 1. Stop previous renewer // 1. Stop previous renewer
// 2. Replace ca properties // 2. Safely shutdown any internal resources (e.g. key manager)
// 3. Replace ca properties
// Do not replace ca.srv // Do not replace ca.srv
ca.renewer.Stop() ca.renewer.Stop()
ca.auth.CloseForReload()
ca.auth = newCA.auth ca.auth = newCA.auth
ca.config = newCA.config ca.config = newCA.config
ca.opts = newCA.opts ca.opts = newCA.opts

View file

@ -31,6 +31,7 @@ import (
_ "github.com/smallstep/certificates/kms/sshagentkms" _ "github.com/smallstep/certificates/kms/sshagentkms"
// Experimental kms interfaces. // Experimental kms interfaces.
_ "github.com/smallstep/certificates/kms/pkcs11"
_ "github.com/smallstep/certificates/kms/yubikey" _ "github.com/smallstep/certificates/kms/yubikey"
// Enabled cas interfaces. // Enabled cas interfaces.

View file

@ -234,7 +234,7 @@ func createSSH(c *cloudkms.CloudKMS, project, location, keyRing string, protecti
resp, err = c.CreateKey(&apiv1.CreateKeyRequest{ resp, err = c.CreateKey(&apiv1.CreateKeyRequest{
Name: parent + "/ssh-host-key", Name: parent + "/ssh-host-key",
SignatureAlgorithm: apiv1.ECDSAWithSHA256, SignatureAlgorithm: apiv1.ECDSAWithSHA256,
ProtectionLevel: apiv1.Software, ProtectionLevel: protectionLevel,
}) })
if err != nil { if err != nil {
return err return err

View file

@ -0,0 +1,455 @@
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"
"runtime"
"time"
"github.com/pkg/errors"
"github.com/smallstep/certificates/kms"
"github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/certificates/kms/uri"
"go.step.sm/cli-utils/fileutil"
"go.step.sm/cli-utils/ui"
"go.step.sm/crypto/pemutil"
// Enable pkcs11.
_ "github.com/smallstep/certificates/kms/pkcs11"
)
// Config is a mapping of the cli flags.
type Config struct {
KMS string
RootOnly bool
RootObject string
RootKeyObject string
CrtObject string
CrtKeyObject string
SSHHostKeyObject string
SSHUserKeyObject string
RootFile string
KeyFile string
Pin string
NoCerts bool
EnableSSH bool
Force bool
}
// Validate checks the flags in the config.
func (c *Config) Validate() error {
switch {
case c.KMS == "":
return errors.New("flag `--kms` is required")
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.RootFile == "" && c.RootObject == "":
return errors.New("one of flag `--root` or `--root-cert` is required")
case c.RootFile == "" && c.RootKeyObject == "":
return errors.New("one of flag `--root` or `--root-key` is required")
default:
if c.RootFile != "" {
c.RootObject = ""
c.RootKeyObject = ""
}
if c.RootOnly {
c.CrtObject = ""
c.CrtKeyObject = ""
}
if !c.EnableSSH {
c.SSHHostKeyObject = ""
c.SSHUserKeyObject = ""
}
return nil
}
}
func main() {
var kmsuri string
switch runtime.GOOS {
case "darwin":
kmsuri = "pkcs11:module-path=/usr/local/lib/pkcs11/yubihsm_pkcs11.dylib;token=YubiHSM"
case "linux":
kmsuri = "pkcs11:module-path=/usr/lib/x86_64-linux-gnu/pkcs11/yubihsm_pkcs11.so;token=YubiHSM"
case "windows":
if home, err := os.UserHomeDir(); err == nil {
kmsuri = "pkcs11:module-path=" + home + "\\yubihsm2-sdk\\bin\\yubihsm_pkcs11.dll" + ";token=YubiHSM"
}
}
var c Config
flag.StringVar(&c.KMS, "kms", kmsuri, "PKCS #11 URI with the module-path and token to connect to the module.")
flag.StringVar(&c.RootObject, "root-cert", "pkcs11:id=7330;object=root-cert", "PKCS #11 URI with object id and label to store the root certificate.")
flag.StringVar(&c.RootKeyObject, "root-key", "pkcs11:id=7330;object=root-key", "PKCS #11 URI with object id and label to store the root key.")
flag.StringVar(&c.CrtObject, "crt-cert", "pkcs11:id=7331;object=intermediate-cert", "PKCS #11 URI with object id and label to store the intermediate certificate.")
flag.StringVar(&c.CrtKeyObject, "crt-key", "pkcs11:id=7331;object=intermediate-key", "PKCS #11 URI with object id and label to store the intermediate certificate.")
flag.StringVar(&c.SSHHostKeyObject, "ssh-host-key", "pkcs11:id=7332;object=ssh-host-key", "PKCS #11 URI with object id and label to store the key used to sign SSH host certificates.")
flag.StringVar(&c.SSHUserKeyObject, "ssh-user-key", "pkcs11:id=7333;object=ssh-user-key", "PKCS #11 URI with object id and label to store the key used to sign SSH user certificates.")
flag.BoolVar(&c.RootOnly, "root-only", false, "Store only only the root certificate and sign and intermediate.")
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.EnableSSH, "ssh", false, "Enable the creation of ssh keys.")
flag.BoolVar(&c.NoCerts, "no-certs", false, "Do not store certificates in the module.")
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)
}
u, err := uri.ParseWithScheme("pkcs11", c.KMS)
if err != nil {
fatal(err)
}
if u.Pin() == "" {
pin, err := ui.PromptPassword("What is the PKCS#11 PIN?")
if err != nil {
fatal(err)
}
c.Pin = string(pin)
}
k, err := kms.New(context.Background(), apiv1.Options{
Type: string(apiv1.PKCS11),
URI: c.KMS,
Pin: c.Pin,
})
if err != nil {
fatal(err)
}
defer func() {
_ = k.Close()
}()
// Check if the slots are empty, fail if they are not
certUris := []string{
c.RootObject, c.CrtObject,
}
keyUris := []string{
c.RootKeyObject, c.CrtKeyObject,
c.SSHHostKeyObject, c.SSHUserKeyObject,
}
if !c.Force {
for _, u := range certUris {
if u != "" && !c.NoCerts {
checkObject(k, u)
checkCertificate(k, u)
}
}
for _, u := range keyUris {
if u != "" {
checkObject(k, u)
}
}
} else {
deleter, ok := k.(interface {
DeleteKey(uri string) error
DeleteCertificate(uri string) error
})
if ok {
for _, u := range certUris {
if u != "" && !c.NoCerts {
// Some HSMs like Nitrokey will overwrite the key with the
// certificate label.
if err := deleter.DeleteKey(u); err != nil {
fatalClose(err, k)
}
if err := deleter.DeleteCertificate(u); err != nil {
fatalClose(err, k)
}
}
}
for _, u := range keyUris {
if u != "" {
if err := deleter.DeleteKey(u); err != nil {
fatalClose(err, k)
}
}
}
}
}
if err := createPKI(k, c); err != nil {
fatalClose(err, k)
}
}
func fatal(err error) {
if os.Getenv("STEPDEBUG") == "1" {
fmt.Fprintf(os.Stderr, "%+v\n", err)
} else {
fmt.Fprintln(os.Stderr, err)
}
os.Exit(1)
}
func fatalClose(err error, k kms.KeyManager) {
_ = k.Close()
fatal(err)
}
func usage() {
fmt.Fprintln(os.Stderr, "Usage: step-pkcs11-init")
fmt.Fprintln(os.Stderr, `
The step-pkcs11-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-2021 Smallstep Labs, Inc.`)
os.Exit(1)
}
func checkCertificate(k kms.KeyManager, rawuri string) {
if cm, ok := k.(kms.CertificateManager); ok {
if _, err := cm.LoadCertificate(&apiv1.LoadCertificateRequest{
Name: rawuri,
}); err == nil {
fmt.Fprintf(os.Stderr, "⚠️ Your PKCS #11 module already has a certificate on %s.\n", rawuri)
fmt.Fprintln(os.Stderr, " If you want to delete it and start fresh, use `--force`.")
_ = k.Close()
os.Exit(1)
}
}
}
func checkObject(k kms.KeyManager, rawuri string) {
if _, err := k.GetPublicKey(&apiv1.GetPublicKeyRequest{
Name: rawuri,
}); err == nil {
fmt.Fprintf(os.Stderr, "⚠️ Your PKCS #11 module already has a key on %s.\n", rawuri)
fmt.Fprintln(os.Stderr, " If you want to delete it and start fresh, use `--force`.")
_ = k.Close()
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.RootKeyObject,
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: "PKCS #11 Smallstep Root"},
Subject: pkix.Name{CommonName: "PKCS #11 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 && !c.NoCerts {
if err = cm.StoreCertificate(&apiv1.StoreCertificateRequest{
Name: c.RootObject,
Certificate: root,
}); err != nil {
return err
}
}
if err = fileutil.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.CrtKeyObject,
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 && !c.NoCerts {
if err = cm.StoreCertificate(&apiv1.StoreCertificateRequest{
Name: c.CrtObject,
Certificate: intermediate,
}); err != nil {
return err
}
}
if err = fileutil.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")
if c.SSHHostKeyObject != "" {
resp, err := k.CreateKey(&apiv1.CreateKeyRequest{
Name: c.SSHHostKeyObject,
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
})
if err != nil {
return err
}
ui.PrintSelected("SSH Host Key", resp.Name)
}
if c.SSHUserKeyObject != "" {
resp, err := k.CreateKey(&apiv1.CreateKeyRequest{
Name: c.SSHUserKeyObject,
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
})
if err != nil {
return err
}
ui.PrintSelected("SSH User Key", resp.Name)
}
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[:]
}

View file

@ -16,42 +16,20 @@ e.g. `v1.0.2`
`-rc*` suffix. e.g. `v1.0.2-rc` or `v1.0.2-rc.4` `-rc*` suffix. e.g. `v1.0.2-rc` or `v1.0.2-rc.4`
--- ---
1. **Release `cli` first**
If you plan to release [`cli`](https://github.com/smallstep/cli) as part of 1. **Tag it!**
this release, `cli` must be released first. The `certificates` docker container
depends on the `cli` container. Make certain to wait until the `cli` travis
build has completed.
2. **Update the version of step/cli** 1. Find the most recent tag.
<pre><code> ```
<b>$ go get -u github.com/smallstep/cli</b> $> git fetch --tags
</code></pre> $> git tag
```
3. **Commit all changes.**
Make sure that the local checkout is up to date with the remote origin and
that all local changes have been pushed.
<pre><code>
<b>$ git pull --rebase origin master</b>
<b>$ git push</b>
</code></pre>
4. **Tag it!**
1. **Find the most recent tag.**
<pre><code>
<b>$ git fetch --tags</b>
<b>$ git tag</b>
</code></pre>
The new tag needs to be the logical successor of the most recent existing tag. The new tag needs to be the logical successor of the most recent existing tag.
See [versioning](#versioning) section for more information on version numbers. See [versioning](#versioning) section for more information on version numbers.
2. **Select the type and value of the next tag.** 2. Select the type and value of the next tag.
Is the new release a *release candidate* or a *standard release*? Is the new release a *release candidate* or a *standard release*?
@ -62,7 +40,7 @@ e.g. `v1.0.2`
is a release candidate, say `v1.0.2-rc.3`, then the version of the next is a release candidate, say `v1.0.2-rc.3`, then the version of the next
release candidate should be `v1.0.2-rc.4`. release candidate should be `v1.0.2-rc.4`.
2. **Standard Release** 2. Standard Release
If the most recent tag is a standard release, say `v1.0.2`, then the version If the most recent tag is a standard release, say `v1.0.2`, then the version
of the next standard release should be `v1.0.3`. If the most recent tag of the next standard release should be `v1.0.3`. If the most recent tag
@ -70,27 +48,27 @@ e.g. `v1.0.2`
standard release should be `v1.0.3`. standard release should be `v1.0.3`.
3. **Create a local tag.** 3. Create a local tag.
<pre><code> ```
# standard release # standard release
<b>$ git tag v1.0.3</b> $> git tag v1.0.3
...or ...or
# release candidate # release candidate
<b>$ git tag v1.0.3-rc.1</b> $> git tag v1.0.3-rc.1
</code></pre> ```
4. **Push the new tag to the remote origin.** 4. Push the new tag to the remote origin.
<pre><code> ```
# standard release # standard release
<b>$ git push origin tag v1.0.3</b> $> git push origin tag v1.0.3
...or ...or
# release candidate # release candidate
<b>$ git push origin tag v1.0.3-rc.1</b> $> git push origin tag v1.0.3-rc.1
</code></pre> ```
5. **Check the build status at** 2. **Check the build status at**
[Travis-CI](https://travis-ci.com/smallstep/certificates/builds/). [Travis-CI](https://travis-ci.com/smallstep/certificates/builds/).
Travis will begin by verifying that there are no compilation or linting errors Travis will begin by verifying that there are no compilation or linting errors
@ -105,7 +83,7 @@ e.g. `v1.0.2`
* **step-certificates_1.0.3_darwin_amd64.tar.gz**: tarball containing a statically compiled darwin binary. * **step-certificates_1.0.3_darwin_amd64.tar.gz**: tarball containing a statically compiled darwin binary.
* **step-certificates.tar.gz**: tarball containing a git archive of the full repo. * **step-certificates.tar.gz**: tarball containing a git archive of the full repo.
6. **Update the AUR Arch Linux package** 3. **Update the AUR Arch Linux package**
> **NOTE**: if you plan to release `cli` next then you can skip this step. > **NOTE**: if you plan to release `cli` next then you can skip this step.
@ -119,7 +97,7 @@ e.g. `v1.0.2`
<b>$ ./update --ca v1.0.3</b> <b>$ ./update --ca v1.0.3</b>
</code></pre> </code></pre>
7. **Update the Helm packages** 4. **Update the Helm packages**
> **NOTE**: This is an optional step, only necessary if we want to release a > **NOTE**: This is an optional step, only necessary if we want to release a
> new helm package. > new helm package.

5
go.mod
View file

@ -5,6 +5,7 @@ go 1.14
require ( require (
cloud.google.com/go v0.70.0 cloud.google.com/go v0.70.0
github.com/Masterminds/sprig/v3 v3.1.0 github.com/Masterminds/sprig/v3 v3.1.0
github.com/ThalesIgnite/crypto11 v1.2.4
github.com/aws/aws-sdk-go v1.30.29 github.com/aws/aws-sdk-go v1.30.29
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.7.0 github.com/go-piv/piv-go v1.7.0
@ -18,12 +19,12 @@ require (
github.com/rs/xid v1.2.1 github.com/rs/xid v1.2.1
github.com/sirupsen/logrus v1.4.2 github.com/sirupsen/logrus v1.4.2
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262
github.com/smallstep/nosql v0.3.0 github.com/smallstep/nosql v0.3.6
github.com/urfave/cli v1.22.4 github.com/urfave/cli v1.22.4
go.step.sm/cli-utils v0.1.0 go.step.sm/cli-utils v0.1.0
go.step.sm/crypto v0.7.3 go.step.sm/crypto v0.7.3
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
golang.org/x/net v0.0.0-20201021035429-f5854403a974 golang.org/x/net v0.0.0-20210119194325-5f4716e94777
google.golang.org/api v0.33.0 google.golang.org/api v0.33.0
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154 google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154
google.golang.org/grpc v1.32.0 google.golang.org/grpc v1.32.0

61
go.sum
View file

@ -34,13 +34,15 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9 h1:HD8gA2tkByhMAwYaFAX9w2l7vxvBQ5NMoxDrkhqhtn4= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM= github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM=
github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk= github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
@ -49,6 +51,8 @@ github.com/Masterminds/sprig/v3 v3.1.0 h1:j7GpgZ7PdFqNsmncycTHsLmVPf5/3wJtlgW9TN
github.com/Masterminds/sprig/v3 v3.1.0/go.mod h1:ONGMf7UfYGAbMXCZmQLy8x3lCDIPrEZE/rU8pmrbihA= github.com/Masterminds/sprig/v3 v3.1.0/go.mod h1:ONGMf7UfYGAbMXCZmQLy8x3lCDIPrEZE/rU8pmrbihA=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/ThalesIgnite/crypto11 v1.2.4 h1:3MebRK/U0mA2SmSthXAIZAdUA9w8+ZuKem2O6HuR1f8=
github.com/ThalesIgnite/crypto11 v1.2.4/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/aws/aws-sdk-go v1.30.29 h1:NXNqBS9hjOCpDL8SyCyl38gZX3LLLunKOJc5E7vJ8P0= github.com/aws/aws-sdk-go v1.30.29 h1:NXNqBS9hjOCpDL8SyCyl38gZX3LLLunKOJc5E7vJ8P0=
github.com/aws/aws-sdk-go v1.30.29/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/aws/aws-sdk-go v1.30.29/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
@ -75,14 +79,17 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/badger v1.5.3 h1:5oWIuRvwn93cie+OSt1zSnkaIQ1JFQM8bGlIv6O6Sts= github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8=
github.com/dgraph-io/badger v1.5.3/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE=
github.com/dgraph-io/badger/v2 v2.0.1-rc1.0.20200413122845-09dd2e1a4195 h1:n8KbImHW5qZCXv1y3tHjz5yz418/eTxeRJZ2ZuDm1ZU= github.com/dgraph-io/badger/v2 v2.0.1-rc1.0.20201003150343-5d1bab4fc658 h1:/WBjuutuivOA02gpDtrvrWKw01ugkyt3QnimB7enbtI=
github.com/dgraph-io/badger/v2 v2.0.1-rc1.0.20200413122845-09dd2e1a4195/go.mod h1:3KY8+bsP8wI0OEnQJAKpd4wIJW/Mm32yw2j/9FUVnIM= github.com/dgraph-io/badger/v2 v2.0.1-rc1.0.20201003150343-5d1bab4fc658/go.mod h1:2uGEvGm+JSDLd5UAaKIFSbXDcYyeH0fWJP4N2HMMYMI=
github.com/dgraph-io/ristretto v0.0.2-0.20200115201040-8f368f2f2ab3 h1:MQLRM35Pp0yAyBYksjbj1nZI/w6eyRY/mWoM1sFf4kU= github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgraph-io/ristretto v0.0.2-0.20200115201040-8f368f2f2ab3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.0.4-0.20200906165740-41ebdbffecfd h1:KoJOtZf+6wpQaDTuOWGuo61GxcPBIfhwRxRTaTWGCTc=
github.com/dgraph-io/ristretto v0.0.4-0.20200906165740-41ebdbffecfd/go.mod h1:YylP9MpCYGVZQrly/j/diqcdUetCRRePeBB0c2VGXsA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@ -98,8 +105,6 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-piv/piv-go v1.7.0 h1:rfjdFdASfGV5KLJhSjgpGJ5lzVZVtRWn8ovy/H9HQ/U= github.com/go-piv/piv-go v1.7.0 h1:rfjdFdASfGV5KLJhSjgpGJ5lzVZVtRWn8ovy/H9HQ/U=
github.com/go-piv/piv-go v1.7.0/go.mod h1:ON2WvQncm7dIkCQ7kYJs+nc3V4jHGfrrJnSF8HKy7Gk= github.com/go-piv/piv-go v1.7.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/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
@ -139,6 +144,8 @@ github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw=
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@ -197,6 +204,8 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
@ -216,18 +225,17 @@ github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGe
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/micromdm/scep v1.0.0 h1:ai//kcZnxZPq1YE/MatiE2bIRD94KOAwZRpN1fhVQXY= github.com/micromdm/scep v1.0.0 h1:ai//kcZnxZPq1YE/MatiE2bIRD94KOAwZRpN1fhVQXY=
github.com/micromdm/scep v1.0.0/go.mod h1:CID2SixSr5FvoauZdAFUSpQkn5MAuSy9oyURMGOJbag= github.com/micromdm/scep v1.0.0/go.mod h1:CID2SixSr5FvoauZdAFUSpQkn5MAuSy9oyURMGOJbag=
github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f h1:eVB9ELsoq5ouItQBr5Tj334bhPJG/MX+m7rTchmzVUQ=
github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/newrelic/go-agent v2.15.0+incompatible h1:IB0Fy+dClpBq9aEoIrLyQXzU34JyI1xVTanPLB/+jvU= github.com/newrelic/go-agent v2.15.0+incompatible h1:IB0Fy+dClpBq9aEoIrLyQXzU34JyI1xVTanPLB/+jvU=
github.com/newrelic/go-agent v2.15.0+incompatible/go.mod h1:a8Fv1b/fYhFSReoTU6HDkTYIMZeSVNffmoS726Y0LzQ= github.com/newrelic/go-agent v2.15.0+incompatible/go.mod h1:a8Fv1b/fYhFSReoTU6HDkTYIMZeSVNffmoS726Y0LzQ=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -247,12 +255,11 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5I
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5 h1:lX6ybsQW9Agn3qK/W1Z39Z4a6RyEMGem/gXUYW0axYk=
github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5/go.mod h1:TC9A4+RjIOS+HyTH7wG17/gSqVv95uDw2J64dQZx7RE= github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5/go.mod h1:TC9A4+RjIOS+HyTH7wG17/gSqVv95uDw2J64dQZx7RE=
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY= github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY=
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc=
github.com/smallstep/nosql v0.3.0 h1:V1X5vfDsDt89499h3jZFUlR4VnnsYYs5tXaQZ0w8z5U= github.com/smallstep/nosql v0.3.6 h1:cq6a3NwjFJxkVlWU1T4qGskcfEXr0fO1WqQrraDO1Po=
github.com/smallstep/nosql v0.3.0/go.mod h1:QG7gNOpidifn99MjZaiNbm7HPesIyBd97F/OfacNz8Q= github.com/smallstep/nosql v0.3.6/go.mod h1:h1zC/Z54uNHc8euquLED4qJNCrMHd3nytA141ZZh4qQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
@ -267,11 +274,13 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg=
github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA= github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
@ -280,20 +289,17 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk= go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.step.sm/cli-utils v0.1.0 h1:uuQ73MuAh5P5Eg+3zfqwrtlTLx5DWSfGqGCrSSjYqdk= go.step.sm/cli-utils v0.1.0 h1:uuQ73MuAh5P5Eg+3zfqwrtlTLx5DWSfGqGCrSSjYqdk=
go.step.sm/cli-utils v0.1.0/go.mod h1:+t4qCp5NO+080DdGkJxEh3xL5S4TcYC2JTPLMM72b6Y= go.step.sm/cli-utils v0.1.0/go.mod h1:+t4qCp5NO+080DdGkJxEh3xL5S4TcYC2JTPLMM72b6Y=
go.step.sm/crypto v0.6.1 h1:nJoRFGrGNf/mKVVMdWnfLbBfIFt/z4NdJlSL5nipQMQ=
go.step.sm/crypto v0.6.1/go.mod h1:AKS4yMZVZD4EGjpSkY4eibuMenrvKCscb+BpWMet8c0= go.step.sm/crypto v0.6.1/go.mod h1:AKS4yMZVZD4EGjpSkY4eibuMenrvKCscb+BpWMet8c0=
go.step.sm/crypto v0.7.3 h1:uWkT0vsaZVixgn5x6Ojqittry9PiyVn2ihEYG/qOxV8= go.step.sm/crypto v0.7.3 h1:uWkT0vsaZVixgn5x6Ojqittry9PiyVn2ihEYG/qOxV8=
go.step.sm/crypto v0.7.3/go.mod h1:AKS4yMZVZD4EGjpSkY4eibuMenrvKCscb+BpWMet8c0= go.step.sm/crypto v0.7.3/go.mod h1:AKS4yMZVZD4EGjpSkY4eibuMenrvKCscb+BpWMet8c0=
@ -302,10 +308,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 h1:bXoxMPcSLOq08zI3/c5dEBT6lE4eh+jOh886GHrn6V8=
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@ -366,8 +370,8 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -416,8 +420,11 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View file

@ -26,16 +26,29 @@ type CertificateManager interface {
// ErrNotImplemented is the type of error returned if an operation is not // ErrNotImplemented is the type of error returned if an operation is not
// implemented. // implemented.
type ErrNotImplemented struct { type ErrNotImplemented struct {
msg string Message string
} }
func (e ErrNotImplemented) Error() string { func (e ErrNotImplemented) Error() string {
if e.msg != "" { if e.Message != "" {
return e.msg return e.Message
} }
return "not implemented" return "not implemented"
} }
// ErrAlreadyExists is the type of error returned if a key already exists. This
// is currently only implmented on pkcs11.
type ErrAlreadyExists struct {
Message string
}
func (e ErrAlreadyExists) Error() string {
if e.Message != "" {
return e.Message
}
return "key already exists"
}
// Type represents the KMS type used. // Type represents the KMS type used.
type Type string type Type string
@ -64,10 +77,15 @@ type Options struct {
// Path to the credentials file used in CloudKMS and AmazonKMS. // Path to the credentials file used in CloudKMS and AmazonKMS.
CredentialsFile string `json:"credentialsFile"` CredentialsFile string `json:"credentialsFile"`
// Path to the module used with PKCS11 KMS. // URI is based on the PKCS #11 URI Scheme defined in
Module string `json:"module"` // https://tools.ietf.org/html/rfc7512 and represents the configuration used
// to connect to the KMS.
//
// Used by: pkcs11
URI string `json:"uri"`
// Pin used to access the PKCS11 module. // Pin used to access the PKCS11 module. It can be defined in the URI using
// the pin-value or pin-source properties.
Pin string `json:"pin"` Pin string `json:"pin"`
// ManagementKey used in YubiKeys. Default management key is the hexadecimal // ManagementKey used in YubiKeys. Default management key is the hexadecimal
@ -93,10 +111,9 @@ func (o *Options) Validate() error {
} }
switch Type(strings.ToLower(o.Type)) { switch Type(strings.ToLower(o.Type)) {
case DefaultKMS, SoftKMS, CloudKMS, AmazonKMS, SSHAgentKMS: case DefaultKMS, SoftKMS: // Go crypto based kms.
case YubiKey: case CloudKMS, AmazonKMS, SSHAgentKMS: // Cloud based kms.
case PKCS11: case YubiKey, PKCS11: // Hardware based kms.
return ErrNotImplemented{"support for PKCS11 is not yet implemented"}
default: default:
return errors.Errorf("unsupported kms type %s", o.Type) return errors.Errorf("unsupported kms type %s", o.Type)
} }

View file

@ -15,7 +15,7 @@ func TestOptions_Validate(t *testing.T) {
{"cloudkms", &Options{Type: "cloudkms"}, false}, {"cloudkms", &Options{Type: "cloudkms"}, false},
{"awskms", &Options{Type: "awskms"}, false}, {"awskms", &Options{Type: "awskms"}, false},
{"sshagentkms", &Options{Type: "sshagentkms"}, false}, {"sshagentkms", &Options{Type: "sshagentkms"}, false},
{"pkcs11", &Options{Type: "pkcs11"}, true}, {"pkcs11", &Options{Type: "pkcs11"}, false},
{"unsupported", &Options{Type: "unsupported"}, true}, {"unsupported", &Options{Type: "unsupported"}, true},
} }
for _, tt := range tests { for _, tt := range tests {
@ -42,7 +42,7 @@ func TestErrNotImplemented_Error(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
e := ErrNotImplemented{ e := ErrNotImplemented{
msg: tt.fields.msg, Message: tt.fields.msg,
} }
if got := e.Error(); got != tt.want { if got := e.Error(); got != tt.want {
t.Errorf("ErrNotImplemented.Error() = %v, want %v", got, tt.want) t.Errorf("ErrNotImplemented.Error() = %v, want %v", got, tt.want)
@ -50,3 +50,27 @@ func TestErrNotImplemented_Error(t *testing.T) {
}) })
} }
} }
func TestErrAlreadyExists_Error(t *testing.T) {
type fields struct {
msg string
}
tests := []struct {
name string
fields fields
want string
}{
{"default", fields{}, "key already exists"},
{"custom", fields{"custom message: key already exists"}, "custom message: key already exists"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
e := ErrAlreadyExists{
Message: tt.fields.msg,
}
if got := e.Error(); got != tt.want {
t.Errorf("ErrAlreadyExists.Error() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -98,9 +98,16 @@ type GetPublicKeyRequest struct {
// CreateKeyRequest is the parameter used in the kms.CreateKey method. // CreateKeyRequest is the parameter used in the kms.CreateKey method.
type CreateKeyRequest struct { type CreateKeyRequest struct {
Name string // Name represents the key name or label used to identify a key.
//
// Used by: awskms, cloudkms, pkcs11, yubikey.
Name string
// SignatureAlgorithm represents the type of key to create.
SignatureAlgorithm SignatureAlgorithm SignatureAlgorithm SignatureAlgorithm
Bits int
// Bits is the number of bits on RSA keys.
Bits int
// ProtectionLevel specifies how cryptographic operations are performed. // ProtectionLevel specifies how cryptographic operations are performed.
// Used by: cloudkms // Used by: cloudkms

View file

@ -17,6 +17,9 @@ import (
"go.step.sm/crypto/pemutil" "go.step.sm/crypto/pemutil"
) )
// Scheme is the scheme used in uris.
const Scheme = "awskms"
// KMS implements a KMS using AWS Key Management Service. // KMS implements a KMS using AWS Key Management Service.
type KMS struct { type KMS struct {
session *session.Session session *session.Session
@ -69,7 +72,24 @@ var customerMasterKeySpecMapping = map[apiv1.SignatureAlgorithm]interface{}{
// AWS sessions can also be configured with environment variables, see docs at // AWS sessions can also be configured with environment variables, see docs at
// https://docs.aws.amazon.com/sdk-for-go/api/aws/session/ for all the options. // https://docs.aws.amazon.com/sdk-for-go/api/aws/session/ for all the options.
func New(ctx context.Context, opts apiv1.Options) (*KMS, error) { func New(ctx context.Context, opts apiv1.Options) (*KMS, error) {
o := session.Options{} var o session.Options
if opts.URI != "" {
u, err := uri.ParseWithScheme(Scheme, opts.URI)
if err != nil {
return nil, err
}
o.Profile = u.Get("profile")
if v := u.Get("region"); v != "" {
o.Config.Region = new(string)
*o.Config.Region = v
}
if f := u.Get("credentials-file"); f != "" {
o.SharedConfigFiles = []string{opts.CredentialsFile}
}
}
// Deprecated way to set configuration parameters.
if opts.Region != "" { if opts.Region != "" {
o.Config.Region = &opts.Region o.Config.Region = &opts.Region
} }

View file

@ -60,7 +60,13 @@ func TestNew(t *testing.T) {
Profile: "smallstep", Profile: "smallstep",
CredentialsFile: "~/aws/credentials", CredentialsFile: "~/aws/credentials",
}}, expected, false}, }}, expected, false},
{"ok with uri", args{ctx, apiv1.Options{
URI: "awskms:region=us-east-1;profile=smallstep;credentials-file=/var/run/aws/credentials",
}}, expected, false},
{"fail", args{ctx, apiv1.Options{}}, nil, true}, {"fail", args{ctx, apiv1.Options{}}, nil, true},
{"fail uri", args{ctx, apiv1.Options{
URI: "pkcs11:region=us-east-1;profile=smallstep;credentials-file=/var/run/aws/credentials",
}}, nil, true},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View file

@ -14,11 +14,15 @@ import (
gax "github.com/googleapis/gax-go/v2" gax "github.com/googleapis/gax-go/v2"
"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/uri"
"go.step.sm/crypto/pemutil" "go.step.sm/crypto/pemutil"
"google.golang.org/api/option" "google.golang.org/api/option"
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1" kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
) )
// Scheme is the scheme used in uris.
const Scheme = "cloudkms"
const pendingGenerationRetries = 10 const pendingGenerationRetries = 10
// protectionLevelMapping maps step protection levels with cloud kms ones. // protectionLevelMapping maps step protection levels with cloud kms ones.
@ -71,6 +75,10 @@ type KeyManagementClient interface {
CreateCryptoKeyVersion(ctx context.Context, req *kmspb.CreateCryptoKeyVersionRequest, opts ...gax.CallOption) (*kmspb.CryptoKeyVersion, error) CreateCryptoKeyVersion(ctx context.Context, req *kmspb.CreateCryptoKeyVersionRequest, opts ...gax.CallOption) (*kmspb.CryptoKeyVersion, error)
} }
var newKeyManagementClient = func(ctx context.Context, opts ...option.ClientOption) (KeyManagementClient, error) {
return cloudkms.NewKeyManagementClient(ctx, opts...)
}
// CloudKMS implements a KMS using Google's Cloud apiv1. // CloudKMS implements a KMS using Google's Cloud apiv1.
type CloudKMS struct { type CloudKMS struct {
client KeyManagementClient client KeyManagementClient
@ -79,11 +87,23 @@ type CloudKMS struct {
// New creates a new CloudKMS configured with a new client. // New creates a new CloudKMS configured with a new client.
func New(ctx context.Context, opts apiv1.Options) (*CloudKMS, error) { func New(ctx context.Context, opts apiv1.Options) (*CloudKMS, error) {
var cloudOpts []option.ClientOption var cloudOpts []option.ClientOption
if opts.URI != "" {
u, err := uri.ParseWithScheme(Scheme, opts.URI)
if err != nil {
return nil, err
}
if f := u.Get("credentials-file"); f != "" {
cloudOpts = append(cloudOpts, option.WithCredentialsFile(f))
}
}
// Deprecated way to set configuration parameters.
if opts.CredentialsFile != "" { if opts.CredentialsFile != "" {
cloudOpts = append(cloudOpts, option.WithCredentialsFile(opts.CredentialsFile)) cloudOpts = append(cloudOpts, option.WithCredentialsFile(opts.CredentialsFile))
} }
client, err := cloudkms.NewKeyManagementClient(ctx, cloudOpts...) client, err := newKeyManagementClient(ctx, cloudOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -5,13 +5,13 @@ import (
"crypto" "crypto"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os"
"reflect" "reflect"
"testing" "testing"
gax "github.com/googleapis/gax-go/v2" gax "github.com/googleapis/gax-go/v2"
"github.com/smallstep/certificates/kms/apiv1" "github.com/smallstep/certificates/kms/apiv1"
"go.step.sm/crypto/pemutil" "go.step.sm/crypto/pemutil"
"google.golang.org/api/option"
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1" kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
@ -50,26 +50,63 @@ func TestParent(t *testing.T) {
} }
func TestNew(t *testing.T) { func TestNew(t *testing.T) {
tmp := newKeyManagementClient
t.Cleanup(func() {
newKeyManagementClient = tmp
})
newKeyManagementClient = func(ctx context.Context, opts ...option.ClientOption) (KeyManagementClient, error) {
if len(opts) > 0 {
return nil, fmt.Errorf("test error")
}
return &MockClient{}, nil
}
type args struct { type args struct {
ctx context.Context ctx context.Context
opts apiv1.Options opts apiv1.Options
} }
tests := []struct { tests := []struct {
name string name string
skipOnCI bool args args
args args want *CloudKMS
want *CloudKMS wantErr bool
wantErr bool
}{ }{
{"fail authentication", true, args{context.Background(), apiv1.Options{}}, nil, true}, {"ok", args{context.Background(), apiv1.Options{}}, &CloudKMS{client: &MockClient{}}, false},
{"fail credentials", false, args{context.Background(), apiv1.Options{CredentialsFile: "testdata/missing"}}, nil, true}, {"ok with uri", args{context.Background(), apiv1.Options{URI: "cloudkms:"}}, &CloudKMS{client: &MockClient{}}, false},
{"fail credentials", args{context.Background(), apiv1.Options{CredentialsFile: "testdata/missing"}}, nil, true},
{"fail with uri", args{context.Background(), apiv1.Options{URI: "cloudkms:credentials-file=testdata/missing"}}, nil, true},
{"fail schema", args{context.Background(), apiv1.Options{URI: "pkcs11:"}}, nil, true},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if tt.skipOnCI && os.Getenv("CI") == "true" { got, err := New(tt.args.ctx, tt.args.opts)
t.SkipNow() if (err != nil) != tt.wantErr {
t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr)
return
} }
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("New() = %v, want %v", got, tt.want)
}
})
}
}
func TestNew_real(t *testing.T) {
type args struct {
ctx context.Context
opts apiv1.Options
}
tests := []struct {
name string
args args
want *CloudKMS
wantErr bool
}{
{"fail credentials", args{context.Background(), apiv1.Options{CredentialsFile: "testdata/missing"}}, nil, true},
{"fail with uri", args{context.Background(), apiv1.Options{URI: "cloudkms:credentials-file=testdata/missing"}}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := New(tt.args.ctx, tt.args.opts) got, err := New(tt.args.ctx, tt.args.opts)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr)

View file

@ -0,0 +1,82 @@
// +build cgo
package pkcs11
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"testing"
"github.com/smallstep/certificates/kms/apiv1"
)
func benchmarkSign(b *testing.B, signer crypto.Signer, opts crypto.SignerOpts) {
hash := opts.HashFunc()
h := hash.New()
h.Write([]byte("buggy-coheir-RUBRIC-rabbet-liberal-eaglet-khartoum-stagger"))
digest := h.Sum(nil)
b.ResetTimer()
for i := 0; i < b.N; i++ {
signer.Sign(rand.Reader, digest, opts)
}
b.StopTimer()
}
func BenchmarkSignRSA(b *testing.B) {
k := setupPKCS11(b)
signer, err := k.CreateSigner(&apiv1.CreateSignerRequest{
SigningKey: "pkcs11:id=7371;object=rsa-key",
})
if err != nil {
b.Fatalf("PKCS11.CreateSigner() error = %v", err)
}
benchmarkSign(b, signer, crypto.SHA256)
}
func BenchmarkSignRSAPSS(b *testing.B) {
k := setupPKCS11(b)
signer, err := k.CreateSigner(&apiv1.CreateSignerRequest{
SigningKey: "pkcs11:id=7372;object=rsa-pss-key",
})
if err != nil {
b.Fatalf("PKCS11.CreateSigner() error = %v", err)
}
benchmarkSign(b, signer, &rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthEqualsHash,
Hash: crypto.SHA256,
})
}
func BenchmarkSignP256(b *testing.B) {
k := setupPKCS11(b)
signer, err := k.CreateSigner(&apiv1.CreateSignerRequest{
SigningKey: "pkcs11:id=7373;object=ecdsa-p256-key",
})
if err != nil {
b.Fatalf("PKCS11.CreateSigner() error = %v", err)
}
benchmarkSign(b, signer, crypto.SHA256)
}
func BenchmarkSignP384(b *testing.B) {
k := setupPKCS11(b)
signer, err := k.CreateSigner(&apiv1.CreateSignerRequest{
SigningKey: "pkcs11:id=7374;object=ecdsa-p384-key",
})
if err != nil {
b.Fatalf("PKCS11.CreateSigner() error = %v", err)
}
benchmarkSign(b, signer, crypto.SHA384)
}
func BenchmarkSignP521(b *testing.B) {
k := setupPKCS11(b)
signer, err := k.CreateSigner(&apiv1.CreateSignerRequest{
SigningKey: "pkcs11:id=7375;object=ecdsa-p521-key",
})
if err != nil {
b.Fatalf("PKCS11.CreateSigner() error = %v", err)
}
benchmarkSign(b, signer, crypto.SHA512)
}

60
kms/pkcs11/opensc_test.go Normal file
View file

@ -0,0 +1,60 @@
// +build opensc
package pkcs11
import (
"runtime"
"sync"
"github.com/ThalesIgnite/crypto11"
)
var softHSM2Once sync.Once
// mustPKCS11 configures a *PKCS11 KMS to be used with OpenSC, using for example
// a Nitrokey HSM. To initialize these tests we should run:
// sc-hsm-tool --initialize --so-pin 3537363231383830 --pin 123456
// Or:
// pkcs11-tool --module /usr/local/lib/opensc-pkcs11.so \
// --init-token --init-pin \
// --so-pin=3537363231383830 --new-pin=123456 --pin=123456 \
// --label="pkcs11-test"
func mustPKCS11(t TBTesting) *PKCS11 {
t.Helper()
testModule = "OpenSC"
if runtime.GOARCH != "amd64" {
t.Fatalf("opensc test skipped on %s:%s", runtime.GOOS, runtime.GOARCH)
}
var path string
switch runtime.GOOS {
case "darwin":
path = "/usr/local/lib/opensc-pkcs11.so"
case "linux":
path = "/usr/local/lib/opensc-pkcs11.so"
default:
t.Skipf("opensc test skipped on %s", runtime.GOOS)
return nil
}
var zero int
p11, err := crypto11.Configure(&crypto11.Config{
Path: path,
SlotNumber: &zero,
Pin: "123456",
})
if err != nil {
t.Fatalf("failed to configure opensc on %s: %v", runtime.GOOS, err)
}
k := &PKCS11{
p11: p11,
}
// Setup
softHSM2Once.Do(func() {
teardown(t, k)
setup(t, k)
})
return k
}

174
kms/pkcs11/other_test.go Normal file
View file

@ -0,0 +1,174 @@
// +build cgo,!softhsm2,!yubihsm2,!opensc
package pkcs11
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"io"
"math/big"
"github.com/ThalesIgnite/crypto11"
"github.com/pkg/errors"
)
func mustPKCS11(t TBTesting) *PKCS11 {
t.Helper()
testModule = "Golang crypto"
k := &PKCS11{
p11: &stubPKCS11{
signerIndex: make(map[keyType]int),
certIndex: make(map[keyType]int),
},
}
for i := range testCerts {
testCerts[i].Certificates = nil
}
teardown(t, k)
setup(t, k)
return k
}
type keyType struct {
id string
label string
serial string
}
func newKey(id, label []byte, serial *big.Int) keyType {
var serialString string
if serial != nil {
serialString = serial.String()
}
return keyType{
id: string(id),
label: string(label),
serial: serialString,
}
}
type stubPKCS11 struct {
signers []crypto11.Signer
certs []*x509.Certificate
signerIndex map[keyType]int
certIndex map[keyType]int
}
func (s *stubPKCS11) FindKeyPair(id, label []byte) (crypto11.Signer, error) {
if id == nil && label == nil {
return nil, errors.New("id and label cannot both be nil")
}
if i, ok := s.signerIndex[newKey(id, label, nil)]; ok {
return s.signers[i], nil
}
return nil, nil
}
func (s *stubPKCS11) FindCertificate(id, label []byte, serial *big.Int) (*x509.Certificate, error) {
if id == nil && label == nil && serial == nil {
return nil, errors.New("id, label and serial cannot both be nil")
}
if i, ok := s.certIndex[newKey(id, label, serial)]; ok {
return s.certs[i], nil
}
return nil, nil
}
func (s *stubPKCS11) ImportCertificateWithLabel(id, label []byte, cert *x509.Certificate) error {
switch {
case id == nil && label == nil:
return errors.New("id and label cannot both be nil")
case cert == nil:
return errors.New("certificate cannot be nil")
}
i := len(s.certs)
s.certs = append(s.certs, cert)
s.certIndex[newKey(id, label, cert.SerialNumber)] = i
s.certIndex[newKey(id, nil, nil)] = i
s.certIndex[newKey(nil, label, nil)] = i
s.certIndex[newKey(nil, nil, cert.SerialNumber)] = i
s.certIndex[newKey(id, label, nil)] = i
s.certIndex[newKey(id, nil, cert.SerialNumber)] = i
s.certIndex[newKey(nil, label, cert.SerialNumber)] = i
return nil
}
func (s *stubPKCS11) DeleteCertificate(id, label []byte, serial *big.Int) error {
if id == nil && label == nil && serial == nil {
return errors.New("id, label and serial cannot both be nil")
}
if i, ok := s.certIndex[newKey(id, label, serial)]; ok {
s.certs[i] = nil
}
return nil
}
func (s *stubPKCS11) GenerateRSAKeyPairWithLabel(id, label []byte, bits int) (crypto11.SignerDecrypter, error) {
if id == nil && label == nil {
return nil, errors.New("id and label cannot both be nil")
}
p, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return nil, err
}
k := &privateKey{
Signer: p,
index: len(s.signers),
stub: s,
}
s.signers = append(s.signers, k)
s.signerIndex[newKey(id, label, nil)] = k.index
s.signerIndex[newKey(id, nil, nil)] = k.index
s.signerIndex[newKey(nil, label, nil)] = k.index
return k, nil
}
func (s *stubPKCS11) GenerateECDSAKeyPairWithLabel(id, label []byte, curve elliptic.Curve) (crypto11.Signer, error) {
if id == nil && label == nil {
return nil, errors.New("id and label cannot both be nil")
}
p, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return nil, err
}
k := &privateKey{
Signer: p,
index: len(s.signers),
stub: s,
}
s.signers = append(s.signers, k)
s.signerIndex[newKey(id, label, nil)] = k.index
s.signerIndex[newKey(id, nil, nil)] = k.index
s.signerIndex[newKey(nil, label, nil)] = k.index
return k, nil
}
func (s *stubPKCS11) Close() error {
return nil
}
type privateKey struct {
crypto.Signer
index int
stub *stubPKCS11
}
func (s *privateKey) Delete() error {
s.stub.signers[s.index] = nil
return nil
}
func (s *privateKey) Decrypt(rand io.Reader, msg []byte, opts crypto.DecrypterOpts) (plaintext []byte, err error) {
k, ok := s.Signer.(*rsa.PrivateKey)
if !ok {
return nil, errors.New("key is not an rsa key")
}
return k.Decrypt(rand, msg, opts)
}

354
kms/pkcs11/pkcs11.go Normal file
View file

@ -0,0 +1,354 @@
// +build cgo
package pkcs11
import (
"context"
"crypto"
"crypto/elliptic"
"crypto/x509"
"encoding/hex"
"fmt"
"math/big"
"strconv"
"sync"
"github.com/ThalesIgnite/crypto11"
"github.com/pkg/errors"
"github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/certificates/kms/uri"
)
// Scheme is the scheme used in uris.
const Scheme = "pkcs11"
// DefaultRSASize is the number of bits of a new RSA key if no size has been
// specified.
const DefaultRSASize = 3072
// P11 defines the methods on crypto11.Context that this package will use. This
// interface will be used for unit testing.
type P11 interface {
FindKeyPair(id, label []byte) (crypto11.Signer, error)
FindCertificate(id, label []byte, serial *big.Int) (*x509.Certificate, error)
ImportCertificateWithLabel(id, label []byte, cert *x509.Certificate) error
DeleteCertificate(id, label []byte, serial *big.Int) error
GenerateRSAKeyPairWithLabel(id, label []byte, bits int) (crypto11.SignerDecrypter, error)
GenerateECDSAKeyPairWithLabel(id, label []byte, curve elliptic.Curve) (crypto11.Signer, error)
Close() error
}
var p11Configure = func(config *crypto11.Config) (P11, error) {
return crypto11.Configure(config)
}
// PKCS11 is the implementation of a KMS using the PKCS #11 standard.
type PKCS11 struct {
p11 P11
closed sync.Once
}
// New returns a new PKCS11 KMS.
func New(ctx context.Context, opts apiv1.Options) (*PKCS11, error) {
var config crypto11.Config
if opts.URI != "" {
u, err := uri.ParseWithScheme(Scheme, opts.URI)
if err != nil {
return nil, err
}
config.Pin = u.Pin()
config.Path = u.Get("module-path")
config.TokenLabel = u.Get("token")
config.TokenSerial = u.Get("serial")
if v := u.Get("slot-id"); v != "" {
n, err := strconv.Atoi(v)
if err != nil {
return nil, errors.Wrap(err, "kms uri 'slot-id' is not valid")
}
config.SlotNumber = &n
}
}
if config.Pin == "" && opts.Pin != "" {
config.Pin = opts.Pin
}
switch {
case config.Path == "":
return nil, errors.New("kms uri 'module-path' are required")
case config.TokenLabel == "" && config.TokenSerial == "" && config.SlotNumber == nil:
return nil, errors.New("kms uri 'token', 'serial' or 'slot-id' are required")
case config.Pin == "":
return nil, errors.New("kms 'pin' cannot be empty")
case config.TokenLabel != "" && config.TokenSerial != "":
return nil, errors.New("kms uri 'token' and 'serial' are mutually exclusive")
case config.TokenLabel != "" && config.SlotNumber != nil:
return nil, errors.New("kms uri 'token' and 'slot-id' are mutually exclusive")
case config.TokenSerial != "" && config.SlotNumber != nil:
return nil, errors.New("kms uri 'serial' and 'slot-id' are mutually exclusive")
}
p11, err := p11Configure(&config)
if err != nil {
return nil, errors.Wrap(err, "error initializing PKCS#11")
}
return &PKCS11{
p11: p11,
}, nil
}
func init() {
apiv1.Register(apiv1.PKCS11, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) {
return New(ctx, opts)
})
}
// GetPublicKey returns the public key ....
func (k *PKCS11) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) {
if req.Name == "" {
return nil, errors.New("getPublicKeyRequest 'name' cannot be empty")
}
signer, err := findSigner(k.p11, req.Name)
if err != nil {
return nil, errors.Wrap(err, "getPublicKey failed")
}
return signer.Public(), nil
}
// CreateKey generates a new key in the PKCS#11 module and returns the public key.
func (k *PKCS11) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) {
switch {
case req.Name == "":
return nil, errors.New("createKeyRequest 'name' cannot be empty")
case req.Bits < 0:
return nil, errors.New("createKeyRequest 'bits' cannot be negative")
}
signer, err := generateKey(k.p11, req)
if err != nil {
return nil, errors.Wrap(err, "createKey failed")
}
return &apiv1.CreateKeyResponse{
Name: req.Name,
PublicKey: signer.Public(),
CreateSignerRequest: apiv1.CreateSignerRequest{
SigningKey: req.Name,
},
}, nil
}
// CreateSigner creates a signer using the key present in the PKCS#11 MODULE signature
// slot.
func (k *PKCS11) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error) {
switch {
case req.SigningKey == "":
return nil, errors.New("createSignerRequest 'signingKey' cannot be empty")
}
signer, err := findSigner(k.p11, req.SigningKey)
if err != nil {
return nil, errors.Wrap(err, "createSigner failed")
}
return signer, nil
}
// LoadCertificate implements kms.CertificateManager and loads a certificate
// from the YubiKey.
func (k *PKCS11) LoadCertificate(req *apiv1.LoadCertificateRequest) (*x509.Certificate, error) {
if req.Name == "" {
return nil, errors.New("loadCertificateRequest 'name' cannot be nil")
}
cert, err := findCertificate(k.p11, req.Name)
if err != nil {
return nil, errors.Wrap(err, "loadCertificate failed")
}
return cert, nil
}
// StoreCertificate implements kms.CertificateManager and stores a certificate
// in the YubiKey.
func (k *PKCS11) StoreCertificate(req *apiv1.StoreCertificateRequest) error {
switch {
case req.Name == "":
return errors.New("storeCertificateRequest 'name' cannot be empty")
case req.Certificate == nil:
return errors.New("storeCertificateRequest 'Certificate' cannot be nil")
}
id, object, err := parseObject(req.Name)
if err != nil {
return errors.Wrap(err, "storeCertificate failed")
}
cert, err := k.p11.FindCertificate(id, object, nil)
if err != nil {
return errors.Wrap(err, "storeCertificate failed")
}
if cert != nil {
return errors.Wrap(apiv1.ErrAlreadyExists{
Message: req.Name + " already exists",
}, "storeCertificate failed")
}
if err := k.p11.ImportCertificateWithLabel(id, object, req.Certificate); err != nil {
return errors.Wrap(err, "storeCertificate failed")
}
return nil
}
// DeleteKey is a utility function to delete a key given an uri.
func (k *PKCS11) DeleteKey(uri string) error {
id, object, err := parseObject(uri)
if err != nil {
return errors.Wrap(err, "deleteKey failed")
}
signer, err := k.p11.FindKeyPair(id, object)
if err != nil {
return errors.Wrap(err, "deleteKey failed")
}
if signer == nil {
return nil
}
if err := signer.Delete(); err != nil {
return errors.Wrap(err, "deleteKey failed")
}
return nil
}
// DeleteCertificate is a utility function to delete a certificate given an uri.
func (k *PKCS11) DeleteCertificate(uri string) error {
id, object, err := parseObject(uri)
if err != nil {
return errors.Wrap(err, "deleteCertificate failed")
}
if err := k.p11.DeleteCertificate(id, object, nil); err != nil {
return errors.Wrap(err, "deleteCertificate failed")
}
return nil
}
// Close releases the connection to the PKCS#11 module.
func (k *PKCS11) Close() (err error) {
k.closed.Do(func() {
err = errors.Wrap(k.p11.Close(), "error closing pkcs#11 context")
})
return
}
func toByte(s string) []byte {
if s == "" {
return nil
}
return []byte(s)
}
func parseObject(rawuri string) ([]byte, []byte, error) {
u, err := uri.ParseWithScheme(Scheme, rawuri)
if err != nil {
return nil, nil, err
}
id := u.GetEncoded("id")
object := u.Get("object")
if len(id) == 0 && object == "" {
return nil, nil, errors.Errorf("key with uri %s is not valid, id or object are required", rawuri)
}
return id, toByte(object), nil
}
func generateKey(ctx P11, req *apiv1.CreateKeyRequest) (crypto11.Signer, error) {
id, object, err := parseObject(req.Name)
if err != nil {
return nil, err
}
signer, err := ctx.FindKeyPair(id, object)
if err != nil {
return nil, err
}
if signer != nil {
return nil, apiv1.ErrAlreadyExists{
Message: req.Name + " already exists",
}
}
// Enforce the use of both id and labels. This is not strictly necessary in
// PKCS #11, but it's a good practice.
if len(id) == 0 || len(object) == 0 {
return nil, errors.Errorf("key with uri %s is not valid, id and object are required", req.Name)
}
bits := req.Bits
if bits == 0 {
bits = DefaultRSASize
}
switch req.SignatureAlgorithm {
case apiv1.UnspecifiedSignAlgorithm:
return ctx.GenerateECDSAKeyPairWithLabel(id, object, elliptic.P256())
case apiv1.SHA256WithRSA, apiv1.SHA384WithRSA, apiv1.SHA512WithRSA:
return ctx.GenerateRSAKeyPairWithLabel(id, object, bits)
case apiv1.SHA256WithRSAPSS, apiv1.SHA384WithRSAPSS, apiv1.SHA512WithRSAPSS:
return ctx.GenerateRSAKeyPairWithLabel(id, object, bits)
case apiv1.ECDSAWithSHA256:
return ctx.GenerateECDSAKeyPairWithLabel(id, object, elliptic.P256())
case apiv1.ECDSAWithSHA384:
return ctx.GenerateECDSAKeyPairWithLabel(id, object, elliptic.P384())
case apiv1.ECDSAWithSHA512:
return ctx.GenerateECDSAKeyPairWithLabel(id, object, elliptic.P521())
case apiv1.PureEd25519:
return nil, fmt.Errorf("signature algorithm %s is not supported", req.SignatureAlgorithm)
default:
return nil, fmt.Errorf("signature algorithm %s is not supported", req.SignatureAlgorithm)
}
}
func findSigner(ctx P11, rawuri string) (crypto11.Signer, error) {
id, object, err := parseObject(rawuri)
if err != nil {
return nil, err
}
signer, err := ctx.FindKeyPair(id, object)
if err != nil {
return nil, errors.Wrapf(err, "error finding key with uri %s", rawuri)
}
if signer == nil {
return nil, errors.Errorf("key with uri %s not found", rawuri)
}
return signer, nil
}
func findCertificate(ctx P11, rawuri string) (*x509.Certificate, error) {
u, err := uri.ParseWithScheme(Scheme, rawuri)
if err != nil {
return nil, err
}
id, object, serial := u.GetEncoded("id"), u.Get("object"), u.Get("serial")
if len(id) == 0 && object == "" && serial == "" {
return nil, errors.Errorf("key with uri %s is not valid, id, object or serial are required", rawuri)
}
var serialNumber *big.Int
if serial != "" {
b, err := hex.DecodeString(serial)
if err != nil {
return nil, errors.Errorf("key with uri %s is not valid, failed to decode serial", rawuri)
}
serialNumber = new(big.Int).SetBytes(b)
}
cert, err := ctx.FindCertificate(id, toByte(object), serialNumber)
if err != nil {
return nil, errors.Wrapf(err, "error finding certificate with uri %s", rawuri)
}
if cert == nil {
return nil, errors.Errorf("certificate with uri %s not found", rawuri)
}
return cert, nil
}

View file

@ -0,0 +1,57 @@
// +build !cgo
package pkcs11
import (
"context"
"crypto"
"os"
"path/filepath"
"github.com/pkg/errors"
"github.com/smallstep/certificates/kms/apiv1"
)
var errUnsupported error
func init() {
name := filepath.Base(os.Args[0])
errUnsupported = errors.Errorf("unsupported kms type 'pkcs11': %s is compiled without cgo support", name)
apiv1.Register(apiv1.PKCS11, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) {
return nil, errUnsupported
})
}
// PKCS11 is the implementation of a KMS using the PKCS #11 standard.
type PKCS11 struct{}
// New implements the kms.KeyManager interface and without CGO will always
// return an error.
func New(ctx context.Context, opts apiv1.Options) (*PKCS11, error) {
return nil, errUnsupported
}
// GetPublicKey implements the kms.KeyManager interface and without CGO will always
// return an error.
func (*PKCS11) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) {
return nil, errUnsupported
}
// CreateKey implements the kms.KeyManager interface and without CGO will always
// return an error.
func (*PKCS11) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) {
return nil, errUnsupported
}
// CreateSigner implements the kms.KeyManager interface and without CGO will always
// return an error.
func (*PKCS11) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error) {
return nil, errUnsupported
}
// Close implements the kms.KeyManager interface and without CGO will always
// return an error.
func (*PKCS11) Close() error {
return errUnsupported
}

729
kms/pkcs11/pkcs11_test.go Normal file
View file

@ -0,0 +1,729 @@
// +build cgo
package pkcs11
import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"math/big"
"reflect"
"strings"
"testing"
"github.com/ThalesIgnite/crypto11"
"github.com/pkg/errors"
"github.com/smallstep/certificates/kms/apiv1"
"golang.org/x/crypto/cryptobyte"
"golang.org/x/crypto/cryptobyte/asn1"
)
func TestNew(t *testing.T) {
tmp := p11Configure
t.Cleanup(func() {
p11Configure = tmp
})
k := mustPKCS11(t)
t.Cleanup(func() {
k.Close()
})
p11Configure = func(config *crypto11.Config) (P11, error) {
if strings.Contains(config.Path, "fail") {
return nil, errors.New("an error")
}
return k.p11, nil
}
type args struct {
ctx context.Context
opts apiv1.Options
}
tests := []struct {
name string
args args
want *PKCS11
wantErr bool
}{
{"ok", args{context.Background(), apiv1.Options{
Type: "pkcs11",
URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=pkcs11-test?pin-value=password",
}}, k, false},
{"ok with serial", args{context.Background(), apiv1.Options{
Type: "pkcs11",
URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;serial=0123456789?pin-value=password",
}}, k, false},
{"ok with slot-id", args{context.Background(), apiv1.Options{
Type: "pkcs11",
URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;slot-id=0?pin-value=password",
}}, k, false},
{"ok with pin", args{context.Background(), apiv1.Options{
Type: "pkcs11",
URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=pkcs11-test",
Pin: "passowrd",
}}, k, false},
{"fail missing module", args{context.Background(), apiv1.Options{
Type: "pkcs11",
URI: "pkcs11:token=pkcs11-test",
Pin: "passowrd",
}}, nil, true},
{"fail missing pin", args{context.Background(), apiv1.Options{
Type: "pkcs11",
URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=pkcs11-test",
}}, nil, true},
{"fail missing token/serial/slot-id", args{context.Background(), apiv1.Options{
Type: "pkcs11",
URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so",
Pin: "passowrd",
}}, nil, true},
{"fail token+serial+slot-id", args{context.Background(), apiv1.Options{
Type: "pkcs11",
URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=pkcs11-test;serial=0123456789;slot-id=0",
Pin: "passowrd",
}}, nil, true},
{"fail token+serial", args{context.Background(), apiv1.Options{
Type: "pkcs11",
URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=pkcs11-test;serial=0123456789",
Pin: "passowrd",
}}, nil, true},
{"fail token+slot-id", args{context.Background(), apiv1.Options{
Type: "pkcs11",
URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=pkcs11-test;slot-id=0",
Pin: "passowrd",
}}, nil, true},
{"fail serial+slot-id", args{context.Background(), apiv1.Options{
Type: "pkcs11",
URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;serial=0123456789;slot-id=0",
Pin: "passowrd",
}}, nil, true},
{"fail slot-id", args{context.Background(), apiv1.Options{
Type: "pkcs11",
URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;slot-id=x?pin-value=password",
}}, nil, true},
{"fail scheme", args{context.Background(), apiv1.Options{
Type: "pkcs11",
URI: "foo:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=pkcs11-test?pin-value=password",
}}, nil, true},
{"fail configure", args{context.Background(), apiv1.Options{
Type: "pkcs11",
URI: "pkcs11:module-path=/usr/local/lib/fail.so;token=pkcs11-test?pin-value=password",
}}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := New(tt.args.ctx, tt.args.opts)
if (err != nil) != tt.wantErr {
t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("New() = %v, want %v", got, tt.want)
}
})
}
}
func TestPKCS11_GetPublicKey(t *testing.T) {
k := setupPKCS11(t)
type args struct {
req *apiv1.GetPublicKeyRequest
}
tests := []struct {
name string
args args
want crypto.PublicKey
wantErr bool
}{
{"RSA", args{&apiv1.GetPublicKeyRequest{
Name: "pkcs11:id=7371;object=rsa-key",
}}, &rsa.PublicKey{}, false},
{"RSA by id", args{&apiv1.GetPublicKeyRequest{
Name: "pkcs11:id=7371",
}}, &rsa.PublicKey{}, false},
{"RSA by label", args{&apiv1.GetPublicKeyRequest{
Name: "pkcs11:object=rsa-key",
}}, &rsa.PublicKey{}, false},
{"ECDSA", args{&apiv1.GetPublicKeyRequest{
Name: "pkcs11:id=7373;object=ecdsa-p256-key",
}}, &ecdsa.PublicKey{}, false},
{"ECDSA by id", args{&apiv1.GetPublicKeyRequest{
Name: "pkcs11:id=7373",
}}, &ecdsa.PublicKey{}, false},
{"ECDSA by label", args{&apiv1.GetPublicKeyRequest{
Name: "pkcs11:object=ecdsa-p256-key",
}}, &ecdsa.PublicKey{}, false},
{"fail name", args{&apiv1.GetPublicKeyRequest{
Name: "",
}}, nil, true},
{"fail uri", args{&apiv1.GetPublicKeyRequest{
Name: "https:id=9999;object=https",
}}, nil, true},
{"fail missing", args{&apiv1.GetPublicKeyRequest{
Name: "pkcs11:id=9999;object=rsa-key",
}}, nil, true},
{"fail FindKeyPair", args{&apiv1.GetPublicKeyRequest{
Name: "pkcs11:foo=bar",
}}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := k.GetPublicKey(tt.args.req)
if (err != nil) != tt.wantErr {
t.Errorf("PKCS11.GetPublicKey() error = %v, wantErr %v", err, tt.wantErr)
return
}
if reflect.TypeOf(got) != reflect.TypeOf(tt.want) {
t.Errorf("PKCS11.GetPublicKey() = %T, want %T", got, tt.want)
}
})
}
}
func TestPKCS11_CreateKey(t *testing.T) {
k := setupPKCS11(t)
// Make sure to delete the created key
k.DeleteKey(testObject)
type args struct {
req *apiv1.CreateKeyRequest
}
tests := []struct {
name string
args args
want *apiv1.CreateKeyResponse
wantErr bool
}{
{"default", args{&apiv1.CreateKeyRequest{
Name: testObject,
}}, &apiv1.CreateKeyResponse{
Name: testObject,
PublicKey: &ecdsa.PublicKey{},
CreateSignerRequest: apiv1.CreateSignerRequest{
SigningKey: testObject,
},
}, false},
{"RSA SHA256WithRSA", args{&apiv1.CreateKeyRequest{
Name: testObject,
SignatureAlgorithm: apiv1.SHA256WithRSA,
}}, &apiv1.CreateKeyResponse{
Name: testObject,
PublicKey: &rsa.PublicKey{},
CreateSignerRequest: apiv1.CreateSignerRequest{
SigningKey: testObject,
},
}, false},
{"RSA SHA384WithRSA", args{&apiv1.CreateKeyRequest{
Name: testObject,
SignatureAlgorithm: apiv1.SHA384WithRSA,
}}, &apiv1.CreateKeyResponse{
Name: testObject,
PublicKey: &rsa.PublicKey{},
CreateSignerRequest: apiv1.CreateSignerRequest{
SigningKey: testObject,
},
}, false},
{"RSA SHA512WithRSA", args{&apiv1.CreateKeyRequest{
Name: testObject,
SignatureAlgorithm: apiv1.SHA512WithRSA,
}}, &apiv1.CreateKeyResponse{
Name: testObject,
PublicKey: &rsa.PublicKey{},
CreateSignerRequest: apiv1.CreateSignerRequest{
SigningKey: testObject,
},
}, false},
{"RSA SHA256WithRSAPSS", args{&apiv1.CreateKeyRequest{
Name: testObject,
SignatureAlgorithm: apiv1.SHA256WithRSAPSS,
}}, &apiv1.CreateKeyResponse{
Name: testObject,
PublicKey: &rsa.PublicKey{},
CreateSignerRequest: apiv1.CreateSignerRequest{
SigningKey: testObject,
},
}, false},
{"RSA SHA384WithRSAPSS", args{&apiv1.CreateKeyRequest{
Name: testObject,
SignatureAlgorithm: apiv1.SHA384WithRSAPSS,
}}, &apiv1.CreateKeyResponse{
Name: testObject,
PublicKey: &rsa.PublicKey{},
CreateSignerRequest: apiv1.CreateSignerRequest{
SigningKey: testObject,
},
}, false},
{"RSA SHA512WithRSAPSS", args{&apiv1.CreateKeyRequest{
Name: testObject,
SignatureAlgorithm: apiv1.SHA512WithRSAPSS,
}}, &apiv1.CreateKeyResponse{
Name: testObject,
PublicKey: &rsa.PublicKey{},
CreateSignerRequest: apiv1.CreateSignerRequest{
SigningKey: testObject,
},
}, false},
{"RSA 2048", args{&apiv1.CreateKeyRequest{
Name: testObject,
SignatureAlgorithm: apiv1.SHA256WithRSA,
Bits: 2048,
}}, &apiv1.CreateKeyResponse{
Name: testObject,
PublicKey: &rsa.PublicKey{},
CreateSignerRequest: apiv1.CreateSignerRequest{
SigningKey: testObject,
},
}, false},
{"RSA 4096", args{&apiv1.CreateKeyRequest{
Name: testObject,
SignatureAlgorithm: apiv1.SHA256WithRSA,
Bits: 4096,
}}, &apiv1.CreateKeyResponse{
Name: testObject,
PublicKey: &rsa.PublicKey{},
CreateSignerRequest: apiv1.CreateSignerRequest{
SigningKey: testObject,
},
}, false},
{"ECDSA P256", args{&apiv1.CreateKeyRequest{
Name: testObject,
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
}}, &apiv1.CreateKeyResponse{
Name: testObject,
PublicKey: &ecdsa.PublicKey{},
CreateSignerRequest: apiv1.CreateSignerRequest{
SigningKey: testObject,
},
}, false},
{"ECDSA P384", args{&apiv1.CreateKeyRequest{
Name: testObject,
SignatureAlgorithm: apiv1.ECDSAWithSHA384,
}}, &apiv1.CreateKeyResponse{
Name: testObject,
PublicKey: &ecdsa.PublicKey{},
CreateSignerRequest: apiv1.CreateSignerRequest{
SigningKey: testObject,
},
}, false},
{"ECDSA P521", args{&apiv1.CreateKeyRequest{
Name: testObject,
SignatureAlgorithm: apiv1.ECDSAWithSHA512,
}}, &apiv1.CreateKeyResponse{
Name: testObject,
PublicKey: &ecdsa.PublicKey{},
CreateSignerRequest: apiv1.CreateSignerRequest{
SigningKey: testObject,
},
}, false},
{"fail name", args{&apiv1.CreateKeyRequest{
Name: "",
}}, nil, true},
{"fail no id", args{&apiv1.CreateKeyRequest{
Name: "pkcs11:object=create-key",
}}, nil, true},
{"fail no object", args{&apiv1.CreateKeyRequest{
Name: "pkcs11:id=9999",
}}, nil, true},
{"fail schema", args{&apiv1.CreateKeyRequest{
Name: "pkcs12:id=9999;object=create-key",
}}, nil, true},
{"fail bits", args{&apiv1.CreateKeyRequest{
Name: "pkcs11:id=9999;object=create-key",
Bits: -1,
SignatureAlgorithm: apiv1.SHA256WithRSAPSS,
}}, nil, true},
{"fail ed25519", args{&apiv1.CreateKeyRequest{
Name: "pkcs11:id=9999;object=create-key",
SignatureAlgorithm: apiv1.PureEd25519,
}}, nil, true},
{"fail unknown", args{&apiv1.CreateKeyRequest{
Name: "pkcs11:id=9999;object=create-key",
SignatureAlgorithm: apiv1.SignatureAlgorithm(100),
}}, nil, true},
{"fail FindKeyPair", args{&apiv1.CreateKeyRequest{
Name: "pkcs11:foo=bar",
SignatureAlgorithm: apiv1.SHA256WithRSAPSS,
}}, nil, true},
{"fail already exists", args{&apiv1.CreateKeyRequest{
Name: "pkcs11:id=7373;object=ecdsa-p256-key",
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
}}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := k.CreateKey(tt.args.req)
if (err != nil) != tt.wantErr {
t.Errorf("PKCS11.CreateKey() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != nil {
got.PublicKey = tt.want.PublicKey
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("PKCS11.CreateKey() = %v, want %v", got, tt.want)
}
if got != nil {
if err := k.DeleteKey(got.Name); err != nil {
t.Errorf("PKCS11.DeleteKey() error = %v", err)
}
}
})
}
}
func TestPKCS11_CreateSigner(t *testing.T) {
k := setupPKCS11(t)
data := []byte("buggy-coheir-RUBRIC-rabbet-liberal-eaglet-khartoum-stagger")
// VerifyASN1 verifies the ASN.1 encoded signature, sig, of hash using the
// public key, pub. Its return value records whether the signature is valid.
verifyASN1 := func(pub *ecdsa.PublicKey, hash, sig []byte) bool {
var (
r, s = &big.Int{}, &big.Int{}
inner cryptobyte.String
)
input := cryptobyte.String(sig)
if !input.ReadASN1(&inner, asn1.SEQUENCE) ||
!input.Empty() ||
!inner.ReadASN1Integer(r) ||
!inner.ReadASN1Integer(s) ||
!inner.Empty() {
return false
}
return ecdsa.Verify(pub, hash, r, s)
}
type args struct {
req *apiv1.CreateSignerRequest
}
tests := []struct {
name string
args args
algorithm apiv1.SignatureAlgorithm
signerOpts crypto.SignerOpts
wantErr bool
}{
// SoftHSM2
{"RSA", args{&apiv1.CreateSignerRequest{
SigningKey: "pkcs11:id=7371;object=rsa-key",
}}, apiv1.SHA256WithRSA, crypto.SHA256, false},
{"RSA PSS", args{&apiv1.CreateSignerRequest{
SigningKey: "pkcs11:id=7372;object=rsa-pss-key",
}}, apiv1.SHA256WithRSAPSS, &rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthEqualsHash,
Hash: crypto.SHA256,
}, false},
{"ECDSA P256", args{&apiv1.CreateSignerRequest{
SigningKey: "pkcs11:id=7373;object=ecdsa-p256-key",
}}, apiv1.ECDSAWithSHA256, crypto.SHA256, false},
{"ECDSA P384", args{&apiv1.CreateSignerRequest{
SigningKey: "pkcs11:id=7374;object=ecdsa-p384-key",
}}, apiv1.ECDSAWithSHA384, crypto.SHA384, false},
{"ECDSA P521", args{&apiv1.CreateSignerRequest{
SigningKey: "pkcs11:id=7375;object=ecdsa-p521-key",
}}, apiv1.ECDSAWithSHA512, crypto.SHA512, false},
{"fail SigningKey", args{&apiv1.CreateSignerRequest{
SigningKey: "",
}}, 0, nil, true},
{"fail uri", args{&apiv1.CreateSignerRequest{
SigningKey: "https:id=7375;object=ecdsa-p521-key",
}}, 0, nil, true},
{"fail FindKeyPair", args{&apiv1.CreateSignerRequest{
SigningKey: "pkcs11:foo=bar",
}}, 0, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := k.CreateSigner(tt.args.req)
if (err != nil) != tt.wantErr {
t.Errorf("PKCS11.CreateSigner() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != nil {
hash := tt.signerOpts.HashFunc()
h := hash.New()
h.Write(data)
digest := h.Sum(nil)
sig, err := got.Sign(rand.Reader, digest, tt.signerOpts)
if err != nil {
t.Errorf("cyrpto.Signer.Sign() error = %v", err)
}
switch tt.algorithm {
case apiv1.SHA256WithRSA, apiv1.SHA384WithRSA, apiv1.SHA512WithRSA:
pub := got.Public().(*rsa.PublicKey)
if err := rsa.VerifyPKCS1v15(pub, hash, digest, sig); err != nil {
t.Errorf("rsa.VerifyPKCS1v15() error = %v", err)
}
case apiv1.UnspecifiedSignAlgorithm, apiv1.SHA256WithRSAPSS, apiv1.SHA384WithRSAPSS, apiv1.SHA512WithRSAPSS:
pub := got.Public().(*rsa.PublicKey)
if err := rsa.VerifyPSS(pub, hash, digest, sig, tt.signerOpts.(*rsa.PSSOptions)); err != nil {
t.Errorf("rsa.VerifyPSS() error = %v", err)
}
case apiv1.ECDSAWithSHA256, apiv1.ECDSAWithSHA384, apiv1.ECDSAWithSHA512:
pub := got.Public().(*ecdsa.PublicKey)
if !verifyASN1(pub, digest, sig) {
t.Error("ecdsa.VerifyASN1() failed")
}
default:
t.Errorf("signature algorithm %s is not supported", tt.algorithm)
}
}
})
}
}
func TestPKCS11_LoadCertificate(t *testing.T) {
k := setupPKCS11(t)
getCertFn := func(i, j int) func() *x509.Certificate {
return func() *x509.Certificate {
return testCerts[i].Certificates[j]
}
}
type args struct {
req *apiv1.LoadCertificateRequest
}
tests := []struct {
name string
args args
wantFn func() *x509.Certificate
wantErr bool
}{
{"load", args{&apiv1.LoadCertificateRequest{
Name: "pkcs11:id=7376;object=test-root",
}}, getCertFn(0, 0), false},
{"load by id", args{&apiv1.LoadCertificateRequest{
Name: "pkcs11:id=7376",
}}, getCertFn(0, 0), false},
{"load by label", args{&apiv1.LoadCertificateRequest{
Name: "pkcs11:object=test-root",
}}, getCertFn(0, 0), false},
{"load by serial", args{&apiv1.LoadCertificateRequest{
Name: "pkcs11:serial=64",
}}, getCertFn(0, 0), false},
{"fail missing", args{&apiv1.LoadCertificateRequest{
Name: "pkcs11:id=9999;object=test-root",
}}, nil, true},
{"fail name", args{&apiv1.LoadCertificateRequest{
Name: "",
}}, nil, true},
{"fail scheme", args{&apiv1.LoadCertificateRequest{
Name: "foo:id=7376;object=test-root",
}}, nil, true},
{"fail serial", args{&apiv1.LoadCertificateRequest{
Name: "pkcs11:serial=foo",
}}, nil, true},
{"fail FindCertificate", args{&apiv1.LoadCertificateRequest{
Name: "pkcs11:foo=bar",
}}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := k.LoadCertificate(tt.args.req)
if (err != nil) != tt.wantErr {
t.Errorf("PKCS11.LoadCertificate() error = %v, wantErr %v", err, tt.wantErr)
return
}
var want *x509.Certificate
if tt.wantFn != nil {
want = tt.wantFn()
got.Raw, got.RawIssuer, got.RawSubject, got.RawTBSCertificate, got.RawSubjectPublicKeyInfo = nil, nil, nil, nil, nil
want.Raw, want.RawIssuer, want.RawSubject, want.RawTBSCertificate, want.RawSubjectPublicKeyInfo = nil, nil, nil, nil, nil
}
if !reflect.DeepEqual(got, want) {
t.Errorf("PKCS11.LoadCertificate() = %v, want %v", got, want)
}
})
}
}
func TestPKCS11_StoreCertificate(t *testing.T) {
k := setupPKCS11(t)
pub, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("ed25519.GenerateKey() error = %v", err)
}
cert, err := generateCertificate(pub, priv)
if err != nil {
t.Fatalf("x509.CreateCertificate() error = %v", err)
}
// Make sure to delete the created certificate
t.Cleanup(func() {
k.DeleteCertificate(testObject)
})
type args struct {
req *apiv1.StoreCertificateRequest
}
tests := []struct {
name string
args args
wantErr bool
}{
{"ok", args{&apiv1.StoreCertificateRequest{
Name: testObject,
Certificate: cert,
}}, false},
{"fail already exists", args{&apiv1.StoreCertificateRequest{
Name: testObject,
Certificate: cert,
}}, true},
{"fail name", args{&apiv1.StoreCertificateRequest{
Name: "",
Certificate: cert,
}}, true},
{"fail certificate", args{&apiv1.StoreCertificateRequest{
Name: testObject,
Certificate: nil,
}}, true},
{"fail uri", args{&apiv1.StoreCertificateRequest{
Name: "http:id=7770;object=create-cert",
Certificate: cert,
}}, true},
{"fail ImportCertificateWithLabel", args{&apiv1.StoreCertificateRequest{
Name: "pkcs11:foo=bar",
Certificate: cert,
}}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := k.StoreCertificate(tt.args.req); (err != nil) != tt.wantErr {
t.Errorf("PKCS11.StoreCertificate() error = %v, wantErr %v", err, tt.wantErr)
}
if !tt.wantErr {
got, err := k.LoadCertificate(&apiv1.LoadCertificateRequest{
Name: tt.args.req.Name,
})
if err != nil {
t.Errorf("PKCS11.LoadCertificate() error = %v", err)
}
if !reflect.DeepEqual(got, cert) {
t.Errorf("PKCS11.LoadCertificate() = %v, want %v", got, cert)
}
}
})
}
}
func TestPKCS11_DeleteKey(t *testing.T) {
k := setupPKCS11(t)
type args struct {
uri string
}
tests := []struct {
name string
args args
wantErr bool
}{
{"delete", args{testObject}, false},
{"delete by id", args{testObjectByID}, false},
{"delete by label", args{testObjectByLabel}, false},
{"delete missing", args{"pkcs11:id=9999;object=missing-key"}, false},
{"fail name", args{""}, true},
{"fail FindKeyPair", args{"pkcs11:foo=bar"}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if _, err := k.CreateKey(&apiv1.CreateKeyRequest{
Name: testObject,
}); err != nil {
t.Fatalf("PKCS1.CreateKey() error = %v", err)
}
if err := k.DeleteKey(tt.args.uri); (err != nil) != tt.wantErr {
t.Errorf("PKCS11.DeleteKey() error = %v, wantErr %v", err, tt.wantErr)
}
if _, err := k.GetPublicKey(&apiv1.GetPublicKeyRequest{
Name: tt.args.uri,
}); err == nil {
t.Error("PKCS11.GetPublicKey() public key found and not expected")
}
// Make sure to delete the created one.
if err := k.DeleteKey(testObject); err != nil {
t.Errorf("PKCS11.DeleteKey() error = %v", err)
}
})
}
}
func TestPKCS11_DeleteCertificate(t *testing.T) {
k := setupPKCS11(t)
pub, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("ed25519.GenerateKey() error = %v", err)
}
cert, err := generateCertificate(pub, priv)
if err != nil {
t.Fatalf("x509.CreateCertificate() error = %v", err)
}
type args struct {
uri string
}
tests := []struct {
name string
args args
wantErr bool
}{
{"delete", args{testObject}, false},
{"delete by id", args{testObjectByID}, false},
{"delete by label", args{testObjectByLabel}, false},
{"delete missing", args{"pkcs11:id=9999;object=missing-key"}, false},
{"fail name", args{""}, true},
{"fail DeleteCertificate", args{"pkcs11:foo=bar"}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := k.StoreCertificate(&apiv1.StoreCertificateRequest{
Name: testObject,
Certificate: cert,
}); err != nil {
t.Fatalf("PKCS11.StoreCertificate() error = %v", err)
}
if err := k.DeleteCertificate(tt.args.uri); (err != nil) != tt.wantErr {
t.Errorf("PKCS11.DeleteCertificate() error = %v, wantErr %v", err, tt.wantErr)
}
if _, err := k.LoadCertificate(&apiv1.LoadCertificateRequest{
Name: tt.args.uri,
}); err == nil {
t.Error("PKCS11.LoadCertificate() certificate found and not expected")
}
// Make sure to delete the created one.
if err := k.DeleteCertificate(testObject); err != nil {
t.Errorf("PKCS11.DeleteCertificate() error = %v", err)
}
})
}
}
func TestPKCS11_Close(t *testing.T) {
k := mustPKCS11(t)
tests := []struct {
name string
wantErr bool
}{
{"ok", false},
{"second", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := k.Close(); (err != nil) != tt.wantErr {
t.Errorf("PKCS11.Close() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

143
kms/pkcs11/setup_test.go Normal file
View file

@ -0,0 +1,143 @@
// +build cgo
package pkcs11
import (
"crypto"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"math/big"
"time"
"github.com/pkg/errors"
"github.com/smallstep/certificates/kms/apiv1"
)
var (
testModule = ""
testObject = "pkcs11:id=7370;object=test-name"
testObjectByID = "pkcs11:id=7370"
testObjectByLabel = "pkcs11:object=test-name"
testKeys = []struct {
Name string
SignatureAlgorithm apiv1.SignatureAlgorithm
Bits int
}{
{"pkcs11:id=7371;object=rsa-key", apiv1.SHA256WithRSA, 2048},
{"pkcs11:id=7372;object=rsa-pss-key", apiv1.SHA256WithRSAPSS, DefaultRSASize},
{"pkcs11:id=7373;object=ecdsa-p256-key", apiv1.ECDSAWithSHA256, 0},
{"pkcs11:id=7374;object=ecdsa-p384-key", apiv1.ECDSAWithSHA384, 0},
{"pkcs11:id=7375;object=ecdsa-p521-key", apiv1.ECDSAWithSHA512, 0},
}
testCerts = []struct {
Name string
Key string
Certificates []*x509.Certificate
}{
{"pkcs11:id=7376;object=test-root", "pkcs11:id=7373;object=ecdsa-p256-key", nil},
}
)
type TBTesting interface {
Helper()
Cleanup(f func())
Log(args ...interface{})
Errorf(format string, args ...interface{})
Fatalf(format string, args ...interface{})
Skipf(format string, args ...interface{})
}
func generateCertificate(pub crypto.PublicKey, signer crypto.Signer) (*x509.Certificate, error) {
now := time.Now()
template := &x509.Certificate{
Subject: pkix.Name{CommonName: "Test Root Certificate"},
Issuer: pkix.Name{CommonName: "Test Root Certificate"},
IsCA: true,
MaxPathLen: 1,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
NotBefore: now,
NotAfter: now.Add(time.Hour),
SerialNumber: big.NewInt(100),
}
b, err := x509.CreateCertificate(rand.Reader, template, template, pub, signer)
if err != nil {
return nil, err
}
return x509.ParseCertificate(b)
}
func setup(t TBTesting, k *PKCS11) {
t.Log("Running using", testModule)
for _, tk := range testKeys {
_, err := k.CreateKey(&apiv1.CreateKeyRequest{
Name: tk.Name,
SignatureAlgorithm: tk.SignatureAlgorithm,
Bits: tk.Bits,
})
if err != nil && !errors.Is(errors.Cause(err), apiv1.ErrAlreadyExists{
Message: tk.Name + " already exists",
}) {
t.Errorf("PKCS11.GetPublicKey() error = %v", err)
}
}
for i, c := range testCerts {
signer, err := k.CreateSigner(&apiv1.CreateSignerRequest{
SigningKey: c.Key,
})
if err != nil {
t.Errorf("PKCS11.CreateSigner() error = %v", err)
continue
}
cert, err := generateCertificate(signer.Public(), signer)
if err != nil {
t.Errorf("x509.CreateCertificate() error = %v", err)
continue
}
if err := k.StoreCertificate(&apiv1.StoreCertificateRequest{
Name: c.Name,
Certificate: cert,
}); err != nil && !errors.Is(errors.Cause(err), apiv1.ErrAlreadyExists{
Message: c.Name + " already exists",
}) {
t.Errorf("PKCS1.StoreCertificate() error = %+v", err)
continue
}
testCerts[i].Certificates = append(testCerts[i].Certificates, cert)
}
}
func teardown(t TBTesting, k *PKCS11) {
testObjects := []string{testObject, testObjectByID, testObjectByLabel}
for _, name := range testObjects {
if err := k.DeleteKey(name); err != nil {
t.Errorf("PKCS11.DeleteKey() error = %v", err)
}
if err := k.DeleteCertificate(name); err != nil {
t.Errorf("PKCS11.DeleteCertificate() error = %v", err)
}
}
for _, tk := range testKeys {
if err := k.DeleteKey(tk.Name); err != nil {
t.Errorf("PKCS11.DeleteKey() error = %v", err)
}
}
for _, tc := range testCerts {
if err := k.DeleteCertificate(tc.Name); err != nil {
t.Errorf("PKCS11.DeleteCertificate() error = %v", err)
}
}
}
func setupPKCS11(t TBTesting) *PKCS11 {
t.Helper()
k := mustPKCS11(t)
t.Cleanup(func() {
k.Close()
})
return k
}

View file

@ -0,0 +1,59 @@
// +build cgo,softhsm2
package pkcs11
import (
"runtime"
"sync"
"github.com/ThalesIgnite/crypto11"
)
var softHSM2Once sync.Once
// mustPKCS11 configures a *PKCS11 KMS to be used with SoftHSM2. To initialize
// these tests, we should run:
// softhsm2-util --init-token --free \
// --token pkcs11-test --label pkcs11-test \
// --so-pin password --pin password
//
// To delete we should run:
// softhsm2-util --delete-token --token pkcs11-test
func mustPKCS11(t TBTesting) *PKCS11 {
t.Helper()
testModule = "SoftHSM2"
if runtime.GOARCH != "amd64" {
t.Fatalf("softHSM2 test skipped on %s:%s", runtime.GOOS, runtime.GOARCH)
}
var path string
switch runtime.GOOS {
case "darwin":
path = "/usr/local/lib/softhsm/libsofthsm2.so"
case "linux":
path = "/usr/lib/softhsm/libsofthsm2.so"
default:
t.Skipf("softHSM2 test skipped on %s", runtime.GOOS)
return nil
}
p11, err := crypto11.Configure(&crypto11.Config{
Path: path,
TokenLabel: "pkcs11-test",
Pin: "password",
})
if err != nil {
t.Fatalf("failed to configure softHSM2 on %s: %v", runtime.GOOS, err)
}
k := &PKCS11{
p11: p11,
}
// Setup
softHSM2Once.Do(func() {
teardown(t, k)
setup(t, k)
})
return k
}

View file

@ -0,0 +1,54 @@
// +build cgo,yubihsm2
package pkcs11
import (
"runtime"
"sync"
"github.com/ThalesIgnite/crypto11"
)
var yubiHSM2Once sync.Once
// mustPKCS11 configures a *PKCS11 KMS to be used with YubiHSM2. To initialize
// these tests, we should run:
// yubihsm-connector -d
func mustPKCS11(t TBTesting) *PKCS11 {
t.Helper()
testModule = "YubiHSM2"
if runtime.GOARCH != "amd64" {
t.Skipf("yubiHSM2 test skipped on %s:%s", runtime.GOOS, runtime.GOARCH)
}
var path string
switch runtime.GOOS {
case "darwin":
path = "/usr/local/lib/pkcs11/yubihsm_pkcs11.dylib"
case "linux":
path = "/usr/lib/x86_64-linux-gnu/pkcs11/yubihsm_pkcs11.so"
default:
t.Skipf("yubiHSM2 test skipped on %s", runtime.GOOS)
return nil
}
p11, err := crypto11.Configure(&crypto11.Config{
Path: path,
TokenLabel: "YubiHSM",
Pin: "0001password",
})
if err != nil {
t.Fatalf("failed to configure YubiHSM2 on %s: %v", runtime.GOOS, err)
}
k := &PKCS11{
p11: p11,
}
// Setup
yubiHSM2Once.Do(func() {
teardown(t, k)
setup(t, k)
})
return k
}

1
kms/uri/testdata/pin.txt vendored Normal file
View file

@ -0,0 +1 @@
trim-this-pin

View file

@ -1,8 +1,12 @@
package uri package uri
import ( import (
"bytes"
"encoding/hex"
"io/ioutil"
"net/url" "net/url"
"strings" "strings"
"unicode"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -79,8 +83,54 @@ func ParseWithScheme(scheme, rawuri string) (*URI, error) {
return u, nil return u, nil
} }
// Get returns the first value in the uri with the give n key, it will return // Get returns the first value in the uri with the given key, it will return
// empty string if that field is not present. // empty string if that field is not present.
func (u *URI) Get(key string) string { func (u *URI) Get(key string) string {
return u.Values.Get(key) v := u.Values.Get(key)
if v == "" {
v = u.URL.Query().Get(key)
}
return v
}
// GetEncoded returns the first value in the uri with the given key, it will
// return empty nil if that field is not present or is empty. If the return
// value is hex encoded it will decode it and return it.
func (u *URI) GetEncoded(key string) []byte {
v := u.Get(key)
if v == "" {
return nil
}
if len(v)%2 == 0 {
if b, err := hex.DecodeString(v); err == nil {
return b
}
}
return []byte(v)
}
// Pin returns the pin encoded in the url. It will read the pin from the
// pin-value or the pin-source attributes.
func (u *URI) Pin() string {
if value := u.Get("pin-value"); value != "" {
return value
}
if path := u.Get("pin-source"); path != "" {
if b, err := readFile(path); err == nil {
return string(bytes.TrimRightFunc(b, unicode.IsSpace))
}
}
return ""
}
func readFile(path string) ([]byte, error) {
u, err := url.Parse(path)
if err == nil && (u.Scheme == "" || u.Scheme == "file") && u.Path != "" {
path = u.Path
}
b, err := ioutil.ReadFile(path)
if err != nil {
return nil, errors.Wrapf(err, "error reading %s", path)
}
return b, nil
} }

View file

@ -99,6 +99,10 @@ func TestParse(t *testing.T) {
URL: &url.URL{Scheme: "yubikey", Opaque: "slot-id=9a"}, URL: &url.URL{Scheme: "yubikey", Opaque: "slot-id=9a"},
Values: url.Values{"slot-id": []string{"9a"}}, Values: url.Values{"slot-id": []string{"9a"}},
}, false}, }, false},
{"ok schema", args{"cloudkms:"}, &URI{
URL: &url.URL{Scheme: "cloudkms"},
Values: url.Values{},
}, false},
{"ok query", args{"yubikey:slot-id=9a;foo=bar?pin=123456&foo=bar"}, &URI{ {"ok query", args{"yubikey:slot-id=9a;foo=bar?pin=123456&foo=bar"}, &URI{
URL: &url.URL{Scheme: "yubikey", Opaque: "slot-id=9a;foo=bar", RawQuery: "pin=123456&foo=bar"}, URL: &url.URL{Scheme: "yubikey", Opaque: "slot-id=9a;foo=bar", RawQuery: "pin=123456&foo=bar"},
Values: url.Values{"slot-id": []string{"9a"}, "foo": []string{"bar"}}, Values: url.Values{"slot-id": []string{"9a"}, "foo": []string{"bar"}},
@ -115,6 +119,7 @@ func TestParse(t *testing.T) {
URL: &url.URL{Scheme: "file", Host: "tmp", Path: "/ca.cert"}, URL: &url.URL{Scheme: "file", Host: "tmp", Path: "/ca.cert"},
Values: url.Values{}, Values: url.Values{},
}, false}, }, false},
{"fail schema", args{"cloudkms"}, nil, true},
{"fail parse", args{"yubi%key:slot-id=9a"}, nil, true}, {"fail parse", args{"yubi%key:slot-id=9a"}, nil, true},
{"fail scheme", args{"yubikey"}, nil, true}, {"fail scheme", args{"yubikey"}, nil, true},
{"fail parse opaque", args{"yubikey:slot-id=%ZZ"}, nil, true}, {"fail parse opaque", args{"yubikey:slot-id=%ZZ"}, nil, true},
@ -148,12 +153,17 @@ func TestParseWithScheme(t *testing.T) {
URL: &url.URL{Scheme: "yubikey", Opaque: "slot-id=9a"}, URL: &url.URL{Scheme: "yubikey", Opaque: "slot-id=9a"},
Values: url.Values{"slot-id": []string{"9a"}}, Values: url.Values{"slot-id": []string{"9a"}},
}, false}, }, false},
{"ok schema", args{"cloudkms", "cloudkms:"}, &URI{
URL: &url.URL{Scheme: "cloudkms"},
Values: url.Values{},
}, false},
{"ok file", args{"file", "file:///tmp/ca.cert"}, &URI{ {"ok file", args{"file", "file:///tmp/ca.cert"}, &URI{
URL: &url.URL{Scheme: "file", Path: "/tmp/ca.cert"}, URL: &url.URL{Scheme: "file", Path: "/tmp/ca.cert"},
Values: url.Values{}, Values: url.Values{},
}, false}, }, false},
{"fail parse", args{"yubikey", "yubikey"}, nil, true}, {"fail parse", args{"yubikey", "yubikey"}, nil, true},
{"fail scheme", args{"yubikey", "awskms:slot-id=9a"}, nil, true}, {"fail scheme", args{"yubikey", "awskms:slot-id=9a"}, nil, true},
{"fail schema", args{"cloudkms", "cloudkms"}, nil, true},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@ -189,8 +199,9 @@ func TestURI_Get(t *testing.T) {
{"ok", mustParse("yubikey:slot-id=9a"), args{"slot-id"}, "9a"}, {"ok", mustParse("yubikey:slot-id=9a"), args{"slot-id"}, "9a"},
{"ok first", mustParse("yubikey:slot-id=9a;slot-id=9b"), args{"slot-id"}, "9a"}, {"ok first", mustParse("yubikey:slot-id=9a;slot-id=9b"), args{"slot-id"}, "9a"},
{"ok multiple", mustParse("yubikey:slot-id=9a;foo=bar"), args{"foo"}, "bar"}, {"ok multiple", mustParse("yubikey:slot-id=9a;foo=bar"), args{"foo"}, "bar"},
{"ok in query", mustParse("yubikey:slot-id=9a?foo=bar"), args{"foo"}, "bar"},
{"fail missing", mustParse("yubikey:slot-id=9a"), args{"foo"}, ""}, {"fail missing", mustParse("yubikey:slot-id=9a"), args{"foo"}, ""},
{"fail in query", mustParse("yubikey:slot-id=9a?foo=bar"), args{"foo"}, ""}, {"fail missing query", mustParse("yubikey:slot-id=9a?bar=zar"), args{"foo"}, ""},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@ -200,3 +211,66 @@ func TestURI_Get(t *testing.T) {
}) })
} }
} }
func TestURI_GetEncoded(t *testing.T) {
mustParse := func(s string) *URI {
u, err := Parse(s)
if err != nil {
t.Fatal(err)
}
return u
}
type args struct {
key string
}
tests := []struct {
name string
uri *URI
args args
want []byte
}{
{"ok", mustParse("yubikey:slot-id=9a"), args{"slot-id"}, []byte{0x9a}},
{"ok first", mustParse("yubikey:slot-id=9a9b;slot-id=9b"), args{"slot-id"}, []byte{0x9a, 0x9b}},
{"ok percent", mustParse("yubikey:slot-id=9a;foo=%9a%9b%9c"), args{"foo"}, []byte{0x9a, 0x9b, 0x9c}},
{"ok in query", mustParse("yubikey:slot-id=9a?foo=9a"), args{"foo"}, []byte{0x9a}},
{"ok in query percent", mustParse("yubikey:slot-id=9a?foo=%9a"), args{"foo"}, []byte{0x9a}},
{"ok missing", mustParse("yubikey:slot-id=9a"), args{"foo"}, nil},
{"ok missing query", mustParse("yubikey:slot-id=9a?bar=zar"), args{"foo"}, nil},
{"ok no hex", mustParse("yubikey:slot-id=09a?bar=zar"), args{"slot-id"}, []byte{'0', '9', 'a'}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.uri.GetEncoded(tt.args.key)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("URI.GetEncoded() = %v, want %v", got, tt.want)
}
})
}
}
func TestURI_Pin(t *testing.T) {
mustParse := func(s string) *URI {
u, err := Parse(s)
if err != nil {
t.Fatal(err)
}
return u
}
tests := []struct {
name string
uri *URI
want string
}{
{"from value", mustParse("pkcs11:id=%72%73?pin-value=0123456789"), "0123456789"},
{"from source", mustParse("pkcs11:id=%72%73?pin-source=testdata/pin.txt"), "trim-this-pin"},
{"from missing", mustParse("pkcs11:id=%72%73"), ""},
{"from source missing", mustParse("pkcs11:id=%72%73?pin-source=testdata/foo.txt"), ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.uri.Pin(); got != tt.want {
t.Errorf("URI.Pin() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -13,8 +13,12 @@ import (
"github.com/go-piv/piv-go/piv" "github.com/go-piv/piv-go/piv"
"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/uri"
) )
// Scheme is the scheme used in uris.
const Scheme = "yubikey"
// YubiKey implements the KMS interface on a YubiKey. // YubiKey implements the KMS interface on a YubiKey.
type YubiKey struct { type YubiKey struct {
yk *piv.YubiKey yk *piv.YubiKey
@ -26,6 +30,21 @@ type YubiKey struct {
// TODO(mariano): only one card is currently supported. // TODO(mariano): only one card is currently supported.
func New(ctx context.Context, opts apiv1.Options) (*YubiKey, error) { func New(ctx context.Context, opts apiv1.Options) (*YubiKey, error) {
managementKey := piv.DefaultManagementKey managementKey := piv.DefaultManagementKey
if opts.URI != "" {
u, err := uri.ParseWithScheme(Scheme, opts.URI)
if err != nil {
return nil, err
}
if v := u.Pin(); v != "" {
opts.Pin = v
}
if v := u.Get("management-key"); v != "" {
opts.ManagementKey = v
}
}
// Deprecated way to set configuration parameters.
if opts.ManagementKey != "" { if opts.ManagementKey != "" {
b, err := hex.DecodeString(opts.ManagementKey) b, err := hex.DecodeString(opts.ManagementKey)
if err != nil { if err != nil {
@ -106,12 +125,12 @@ func (k *YubiKey) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey
return nil, err return nil, err
} }
cert, err := k.yk.Certificate(slot) pub, err := k.getPublicKey(slot)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "error retrieving certificate") return nil, err
} }
return cert.PublicKey, nil return pub, nil
} }
// CreateKey generates a new key in the YubiKey and returns the public key. // CreateKey generates a new key in the YubiKey and returns the public key.
@ -150,12 +169,12 @@ func (k *YubiKey) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, e
return nil, err return nil, err
} }
cert, err := k.yk.Certificate(slot) pub, err := k.getPublicKey(slot)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "error retrieving certificate") return nil, err
} }
priv, err := k.yk.PrivateKey(slot, cert.PublicKey, piv.KeyAuth{ priv, err := k.yk.PrivateKey(slot, pub, piv.KeyAuth{
PIN: k.pin, PIN: k.pin,
PINPolicy: piv.PINPolicyAlways, PINPolicy: piv.PINPolicyAlways,
}) })
@ -175,6 +194,20 @@ func (k *YubiKey) Close() error {
return errors.Wrap(k.yk.Close(), "error closing yubikey") return errors.Wrap(k.yk.Close(), "error closing yubikey")
} }
// getPublicKey returns the public key on a slot. First it attempts to do
// attestation to get a certificate with the public key in it, if this succeeds
// means that the key was generated in the device. If not we'll try to get the
// key from a stored certificate in the same slot.
func (k *YubiKey) getPublicKey(slot piv.Slot) (crypto.PublicKey, error) {
cert, err := k.yk.Attest(slot)
if err != nil {
if cert, err = k.yk.Certificate(slot); err != nil {
return nil, errors.Wrap(err, "error retrieving public key")
}
}
return cert.PublicKey, nil
}
// signatureAlgorithmMapping is a mapping between the step signature algorithm, // signatureAlgorithmMapping is a mapping between the step signature algorithm,
// and bits for RSA keys, with yubikey ones. // and bits for RSA keys, with yubikey ones.
var signatureAlgorithmMapping = map[apiv1.SignatureAlgorithm]interface{}{ var signatureAlgorithmMapping = map[apiv1.SignatureAlgorithm]interface{}{
@ -228,6 +261,26 @@ var slotMapping = map[string]piv.Slot{
"9c": piv.SlotSignature, "9c": piv.SlotSignature,
"9e": piv.SlotCardAuthentication, "9e": piv.SlotCardAuthentication,
"9d": piv.SlotKeyManagement, "9d": piv.SlotKeyManagement,
"82": {Key: 0x82, Object: 0x5FC10D},
"83": {Key: 0x83, Object: 0x5FC10E},
"84": {Key: 0x84, Object: 0x5FC10F},
"85": {Key: 0x85, Object: 0x5FC110},
"86": {Key: 0x86, Object: 0x5FC111},
"87": {Key: 0x87, Object: 0x5FC112},
"88": {Key: 0x88, Object: 0x5FC113},
"89": {Key: 0x89, Object: 0x5FC114},
"8a": {Key: 0x8a, Object: 0x5FC115},
"8b": {Key: 0x8b, Object: 0x5FC116},
"8c": {Key: 0x8c, Object: 0x5FC117},
"8d": {Key: 0x8d, Object: 0x5FC118},
"8e": {Key: 0x8e, Object: 0x5FC119},
"8f": {Key: 0x8f, Object: 0x5FC11A},
"90": {Key: 0x90, Object: 0x5FC11B},
"91": {Key: 0x91, Object: 0x5FC11C},
"92": {Key: 0x92, Object: 0x5FC11D},
"93": {Key: 0x93, Object: 0x5FC11E},
"94": {Key: 0x94, Object: 0x5FC11F},
"95": {Key: 0x95, Object: 0x5FC120},
} }
func getSlot(name string) (piv.Slot, error) { func getSlot(name string) (piv.Slot, error) {

5
systemd/README.md Normal file
View file

@ -0,0 +1,5 @@
### Systemd unit files for `step-ca`
For documentation on `step-ca.service`, see [Running `step-ca` As A Daemon](https://smallstep.com/docs/step-ca/certificate-authority-server-production#running-step-ca-as-a-daemon).
For documentation on `cert-renewer@.*`, see [Automating Certificate Renewal](https://smallstep.com/docs/step-ca/certificate-authority-server-production#automate-x509-certificate-lifecycle-management)

View file

@ -0,0 +1,31 @@
[Unit]
Description=Certificate renewer for %I
After=network-online.target
Documentation=https://smallstep.com/docs/step-ca/certificate-authority-server-production
StartLimitIntervalSec=0
[Service]
Type=oneshot
User=root
Environment=STEPPATH=/etc/step-ca \
CERT_LOCATION=/etc/step/certs/%i.crt \
KEY_LOCATION=/etc/step/certs/%i.key
; ExecStartPre checks if the certificate is ready for renewal,
; based on the exit status of the command.
; (In systemd 243 and above, you can use ExecCondition= here.)
ExecStartPre=/usr/bin/bash -c \
'step certificate inspect $CERT_LOCATION --format json --roots "$STEPPATH/certs/root_ca.crt" | \
jq -e "(((.validity.start | fromdate) + \
((.validity.end | fromdate) - (.validity.start | fromdate)) * 0.66) \
- now) <= 0" > /dev/null'
; ExecStart renews the certificate, if ExecStartPre was successful.
ExecStart=/usr/bin/step ca renew --force $CERT_LOCATION $KEY_LOCATION
; Try to reload or restart the systemd service that relies on this cert-renewer
ExecStartPost=/usr/bin/bash -c 'systemctl --quiet is-enabled %i && systemctl try-reload-or-restart %i'
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,18 @@
[Unit]
Description=Certificate renewal timer for %I
Documentation=https://smallstep.com/docs/step-ca/certificate-authority-server-production
[Timer]
Persistent=true
; Run the timer unit every 5 minutes.
OnCalendar=*:1/5
; Always run the timer on time.
AccuracySec=1us
; Add jitter to prevent a "thundering hurd" of simultaneous certificate renewals.
RandomizedDelaySec=5m
[Install]
WantedBy=timers.target

56
systemd/step-ca.service Normal file
View file

@ -0,0 +1,56 @@
[Unit]
Description=step-ca service
Documentation=https://smallstep.com/docs/step-ca
Documentation=https://smallstep.com/docs/step-ca/certificate-authority-server-production
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=30
StartLimitBurst=3
ConditionFileNotEmpty=/etc/step-ca/config/ca.json
ConditionFileNotEmpty=/etc/step-ca/password.txt
[Service]
Type=simple
User=step
Group=step
Environment=STEPPATH=/etc/step-ca
WorkingDirectory=/etc/step-ca
ExecStart=/usr/local/bin/step-ca config/ca.json --password-file password.txt
ExecReload=/bin/kill --signal HUP $MAINPID
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
StartLimitInterval=30
StartLimitBurst=3
; Process capabilities & privileges
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
SecureBits=keep-caps
NoNewPrivileges=yes
; Sandboxing
; This sandboxing works with YubiKey PIV (via pcscd HTTP API), but it is likely
; too restrictive for PKCS#11 HSMs.
ProtectSystem=full
ProtectHome=true
RestrictNamespaces=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
PrivateTmp=true
ProtectClock=true
ProtectControlGroups=true
ProtectKernelTunables=true
ProtectKernelLogs=true
ProtectKernelModules=true
LockPersonality=true
RestrictSUIDSGID=true
RemoveIPC=true
RestrictRealtime=true
PrivateDevices=true
SystemCallFilter=@system-service
SystemCallArchitectures=native
MemoryDenyWriteExecute=true
ReadWriteDirectories=/etc/step-ca/db
[Install]
WantedBy=multi-user.target