Merge pull request #164 from smallstep/kms

Kms
pull/195/head v0.14.0-rc.5
Mariano Cano 5 years ago committed by GitHub
commit cf7ef472f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -12,10 +12,11 @@ import (
"github.com/pkg/errors"
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/kms"
kmsapi "github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/certificates/sshutil"
"github.com/smallstep/certificates/templates"
"github.com/smallstep/cli/crypto/pemutil"
"github.com/smallstep/cli/crypto/x509util"
"golang.org/x/crypto/ssh"
)
@ -25,21 +26,30 @@ const (
// Authority implements the Certificate Authority internal interface.
type Authority struct {
config *Config
rootX509Certs []*x509.Certificate
intermediateIdentity *x509util.Identity
config *Config
keyManager kms.KeyManager
provisioners *provisioner.Collection
db db.AuthDB
// X509 CA
rootX509Certs []*x509.Certificate
federatedX509Certs []*x509.Certificate
x509Signer crypto.Signer
x509Issuer *x509.Certificate
certificates *sync.Map
// SSH CA
sshCAUserCertSignKey ssh.Signer
sshCAHostCertSignKey ssh.Signer
sshCAUserCerts []ssh.PublicKey
sshCAHostCerts []ssh.PublicKey
sshCAUserFederatedCerts []ssh.PublicKey
sshCAHostFederatedCerts []ssh.PublicKey
certificates *sync.Map
startTime time.Time
provisioners *provisioner.Collection
db db.AuthDB
// Do not re-initialize
initOnce bool
initOnce bool
startTime time.Time
// Custom functions
sshBastionFunc func(user, hostname string) (*Bastion, error)
sshCheckHostFunc func(ctx context.Context, principal string, tok string, roots []*x509.Certificate) (bool, error)
@ -59,12 +69,19 @@ func New(config *Config, opts ...Option) (*Authority, error) {
certificates: new(sync.Map),
provisioners: provisioner.NewCollection(config.getAudiences()),
}
for _, opt := range opts {
opt(a)
// Apply options.
for _, fn := range opts {
if err := fn(a); err != nil {
return nil, err
}
}
// Initialize authority from options or configuration.
if err := a.init(); err != nil {
return nil, err
}
return a, nil
}
@ -76,6 +93,19 @@ func (a *Authority) init() error {
}
var err error
// Initialize key manager if it has not been set in the options.
if a.keyManager == nil {
var options kmsapi.Options
if a.config.KMS != nil {
options = *a.config.KMS
}
a.keyManager, err = kms.New(context.Background(), options)
if err != nil {
return err
}
}
// Initialize step-ca Database if it's not already initialized with WithDB.
// If a.config.DB is nil then a simple, barebones in memory DB will be used.
if a.db == nil {
@ -84,50 +114,62 @@ func (a *Authority) init() error {
}
}
// Load the root certificates and add them to the certificate store
a.rootX509Certs = make([]*x509.Certificate, len(a.config.Root))
for i, path := range a.config.Root {
crt, err := pemutil.ReadCertificate(path)
if err != nil {
return err
// Read root certificates and store them in the certificates map.
if len(a.rootX509Certs) == 0 {
a.rootX509Certs = make([]*x509.Certificate, len(a.config.Root))
for i, path := range a.config.Root {
crt, err := pemutil.ReadCertificate(path)
if err != nil {
return err
}
a.rootX509Certs[i] = crt
}
// Add root certificate to the certificate map
}
for _, crt := range a.rootX509Certs {
sum := sha256.Sum256(crt.Raw)
a.certificates.Store(hex.EncodeToString(sum[:]), crt)
a.rootX509Certs[i] = crt
}
// Add federated roots
for _, path := range a.config.FederatedRoots {
crt, err := pemutil.ReadCertificate(path)
if err != nil {
return err
// Read federated certificates and store them in the certificates map.
if len(a.federatedX509Certs) == 0 {
a.federatedX509Certs = make([]*x509.Certificate, len(a.config.FederatedRoots))
for i, path := range a.config.FederatedRoots {
crt, err := pemutil.ReadCertificate(path)
if err != nil {
return err
}
a.federatedX509Certs[i] = crt
}
}
for _, crt := range a.federatedX509Certs {
sum := sha256.Sum256(crt.Raw)
a.certificates.Store(hex.EncodeToString(sum[:]), crt)
}
// Decrypt and load intermediate public / private key pair.
if len(a.config.Password) > 0 {
a.intermediateIdentity, err = x509util.LoadIdentityFromDisk(
a.config.IntermediateCert,
a.config.IntermediateKey,
pemutil.WithPassword([]byte(a.config.Password)),
)
// Read intermediate and create X509 signer.
if a.x509Signer == nil {
crt, err := pemutil.ReadCertificate(a.config.IntermediateCert)
if err != nil {
return err
}
} else {
a.intermediateIdentity, err = x509util.LoadIdentityFromDisk(a.config.IntermediateCert, a.config.IntermediateKey)
signer, err := a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.IntermediateKey,
Password: []byte(a.config.Password),
})
if err != nil {
return err
}
a.x509Signer = signer
a.x509Issuer = crt
}
// Decrypt and load SSH keys
if a.config.SSH != nil {
if a.config.SSH.HostKey != "" {
signer, err := parseCryptoSigner(a.config.SSH.HostKey, a.config.Password)
signer, err := a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.SSH.HostKey,
Password: []byte(a.config.Password),
})
if err != nil {
return err
}
@ -140,7 +182,10 @@ func (a *Authority) init() error {
a.sshCAHostFederatedCerts = append(a.sshCAHostFederatedCerts, a.sshCAHostCertSignKey.PublicKey())
}
if a.config.SSH.UserKey != "" {
signer, err := parseCryptoSigner(a.config.SSH.UserKey, a.config.Password)
signer, err := a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.SSH.UserKey,
Password: []byte(a.config.Password),
})
if err != nil {
return err
}
@ -245,19 +290,3 @@ func (a *Authority) GetDatabase() db.AuthDB {
func (a *Authority) Shutdown() error {
return a.db.Shutdown()
}
func parseCryptoSigner(filename, password string) (crypto.Signer, error) {
var opts []pemutil.Options
if password != "" {
opts = append(opts, pemutil.WithPassword([]byte(password)))
}
key, err := pemutil.Read(filename, opts...)
if err != nil {
return nil, err
}
signer, ok := key.(crypto.Signer)
if !ok {
return nil, errors.Errorf("key %s of type %T cannot be used for signing operations", filename, key)
}
return signer, nil
}

@ -137,7 +137,8 @@ func TestAuthorityNew(t *testing.T) {
assert.Equals(t, auth.rootX509Certs[0], root)
assert.True(t, auth.initOnce)
assert.NotNil(t, auth.intermediateIdentity)
assert.NotNil(t, auth.x509Signer)
assert.NotNil(t, auth.x509Issuer)
for _, p := range tc.config.AuthorityConfig.Provisioners {
var _p provisioner.Interface
_p, ok = auth.provisioners.Load(p.GetID())

@ -10,6 +10,7 @@ import (
"github.com/pkg/errors"
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/db"
kms "github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/certificates/templates"
"github.com/smallstep/cli/crypto/tlsutil"
"github.com/smallstep/cli/crypto/x509util"
@ -54,6 +55,7 @@ type Config struct {
IntermediateKey string `json:"key"`
Address string `json:"address"`
DNSNames []string `json:"dnsNames"`
KMS *kms.Options `json:"kms,omitempty"`
SSH *SSHConfig `json:"ssh,omitempty"`
Logger json.RawMessage `json:"logger,omitempty"`
DB *db.Config `json:"db,omitempty"`
@ -179,6 +181,11 @@ func (c *Config) Validate() error {
c.TLS.Renegotiation = c.TLS.Renegotiation || DefaultTLSOptions.Renegotiation
}
// Validate KMS options, nil is ok.
if err := c.KMS.Validate(); err != nil {
return err
}
// Validate ssh: nil is ok
if err := c.SSH.Validate(); err != nil {
return err

@ -2,45 +2,54 @@ package authority
import (
"context"
"crypto"
"crypto/x509"
"encoding/pem"
"github.com/pkg/errors"
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/kms"
"github.com/smallstep/certificates/sshutil"
"golang.org/x/crypto/ssh"
)
// Option sets options to the Authority.
type Option func(*Authority)
type Option func(*Authority) error
// WithDatabase sets an already initialized authority database to a new
// authority. This option is intended to be use on graceful reloads.
func WithDatabase(db db.AuthDB) Option {
return func(a *Authority) {
return func(a *Authority) error {
a.db = db
return nil
}
}
// WithGetIdentityFunc sets a custom function to retrieve the identity from
// an external resource.
func WithGetIdentityFunc(fn func(p provisioner.Interface, email string) (*provisioner.Identity, error)) Option {
return func(a *Authority) {
return func(a *Authority) error {
a.getIdentityFunc = fn
return nil
}
}
// WithSSHBastionFunc sets a custom function to get the bastion for a
// given user-host pair.
func WithSSHBastionFunc(fn func(user, host string) (*Bastion, error)) Option {
return func(a *Authority) {
return func(a *Authority) error {
a.sshBastionFunc = fn
return nil
}
}
// WithSSHGetHosts sets a custom function to get the bastion for a
// given user-host pair.
func WithSSHGetHosts(fn func(cert *x509.Certificate) ([]sshutil.Host, error)) Option {
return func(a *Authority) {
return func(a *Authority) error {
a.sshGetHostsFunc = fn
return nil
}
}
@ -48,7 +57,127 @@ func WithSSHGetHosts(fn func(cert *x509.Certificate) ([]sshutil.Host, error)) Op
// step ssh enabled. The token is used to validate the request, while the roots
// are used to validate the token.
func WithSSHCheckHost(fn func(ctx context.Context, principal string, tok string, roots []*x509.Certificate) (bool, error)) Option {
return func(a *Authority) {
return func(a *Authority) error {
a.sshCheckHostFunc = fn
return nil
}
}
// WithKeyManager defines the key manager used to get and create keys, and sign
// certificates.
func WithKeyManager(k kms.KeyManager) Option {
return func(a *Authority) error {
a.keyManager = k
return nil
}
}
// WithX509Signer defines the signer used to sign X509 certificates.
func WithX509Signer(crt *x509.Certificate, s crypto.Signer) Option {
return func(a *Authority) error {
a.x509Issuer = crt
a.x509Signer = s
return nil
}
}
// WithSSHUserSigner defines the signer used to sign SSH user certificates.
func WithSSHUserSigner(s crypto.Signer) Option {
return func(a *Authority) error {
signer, err := ssh.NewSignerFromSigner(s)
if err != nil {
return errors.Wrap(err, "error creating ssh user signer")
}
a.sshCAUserCertSignKey = signer
// Append public key to list of user certs
pub := signer.PublicKey()
a.sshCAUserCerts = append(a.sshCAUserCerts, pub)
a.sshCAUserFederatedCerts = append(a.sshCAUserFederatedCerts, pub)
return nil
}
}
// WithSSHHostSigner defines the signer used to sign SSH host certificates.
func WithSSHHostSigner(s crypto.Signer) Option {
return func(a *Authority) error {
signer, err := ssh.NewSignerFromSigner(s)
if err != nil {
return errors.Wrap(err, "error creating ssh host signer")
}
a.sshCAHostCertSignKey = signer
// Append public key to list of host certs
pub := signer.PublicKey()
a.sshCAHostCerts = append(a.sshCAHostCerts, pub)
a.sshCAHostFederatedCerts = append(a.sshCAHostFederatedCerts, pub)
return nil
}
}
// WithX509RootCerts is an option that allows to define the list of root
// certificates to use. This option will replace any root certificate defined
// before.
func WithX509RootCerts(rootCerts ...*x509.Certificate) Option {
return func(a *Authority) error {
a.rootX509Certs = rootCerts
return nil
}
}
// WithX509FederatedCerts is an option that allows to define the list of
// federated certificates. This option will replace any federated certificate
// defined before.
func WithX509FederatedCerts(certs ...*x509.Certificate) Option {
return func(a *Authority) error {
a.federatedX509Certs = certs
return nil
}
}
// WithX509RootBundle is an option that allows to define the list of root
// certificates. This option will replace any root certificate defined before.
func WithX509RootBundle(pemCerts []byte) Option {
return func(a *Authority) error {
certs, err := readCertificateBundle(pemCerts)
if err != nil {
return err
}
a.rootX509Certs = certs
return nil
}
}
// WithX509FederatedBundle is an option that allows to define the list of
// federated certificates. This option will replace any federated certificate
// defined before.
func WithX509FederatedBundle(pemCerts []byte) Option {
return func(a *Authority) error {
certs, err := readCertificateBundle(pemCerts)
if err != nil {
return err
}
a.federatedX509Certs = certs
return nil
}
}
func readCertificateBundle(pemCerts []byte) ([]*x509.Certificate, error) {
var block *pem.Block
var certs []*x509.Certificate
for len(pemCerts) > 0 {
block, pemCerts = pem.Decode(pemCerts)
if block == nil {
break
}
if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
continue
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, err
}
certs = append(certs, cert)
}
return certs, nil
}

@ -64,7 +64,6 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Opti
opts = []interface{}{errs.WithKeyVal("csr", csr), errs.WithKeyVal("signOptions", signOpts)}
mods = []x509util.WithOption{withDefaultASN1DN(a.config.AuthorityConfig.Template)}
certValidators = []provisioner.CertificateValidator{}
issIdentity = a.intermediateIdentity
)
// Set backdate with the configured value
@ -89,7 +88,7 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Opti
return nil, errs.Wrap(http.StatusBadRequest, err, "authority.Sign; invalid certificate request", opts...)
}
leaf, err := x509util.NewLeafProfileWithCSR(csr, issIdentity.Crt, issIdentity.Key, mods...)
leaf, err := x509util.NewLeafProfileWithCSR(csr, a.x509Issuer, a.x509Signer, mods...)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign", opts...)
}
@ -112,12 +111,6 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Opti
"authority.Sign; error parsing new leaf certificate", opts...)
}
caCert, err := x509.ParseCertificate(issIdentity.Crt.Raw)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err,
"authority.Sign; error parsing intermediate certificate", opts...)
}
if err = a.db.StoreCertificate(serverCert); err != nil {
if err != db.ErrNotImplemented {
return nil, errs.Wrap(http.StatusInternalServerError, err,
@ -125,7 +118,7 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Opti
}
}
return []*x509.Certificate{serverCert, caCert}, nil
return []*x509.Certificate{serverCert, a.x509Issuer}, nil
}
// Renew creates a new Certificate identical to the old certificate, except
@ -138,9 +131,6 @@ func (a *Authority) Renew(oldCert *x509.Certificate) ([]*x509.Certificate, error
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Renew", opts...)
}
// Issuer
issIdentity := a.intermediateIdentity
// Durations
backdate := a.config.AuthorityConfig.Backdate.Duration
duration := oldCert.NotAfter.Sub(oldCert.NotBefore)
@ -148,7 +138,7 @@ func (a *Authority) Renew(oldCert *x509.Certificate) ([]*x509.Certificate, error
newCert := &x509.Certificate{
PublicKey: oldCert.PublicKey,
Issuer: issIdentity.Crt.Subject,
Issuer: a.x509Issuer.Subject,
Subject: oldCert.Subject,
NotBefore: now.Add(-1 * backdate),
NotAfter: now.Add(duration - backdate),
@ -188,8 +178,7 @@ func (a *Authority) Renew(oldCert *x509.Certificate) ([]*x509.Certificate, error
}
}
leaf, err := x509util.NewLeafProfileWithTemplate(newCert,
issIdentity.Crt, issIdentity.Key)
leaf, err := x509util.NewLeafProfileWithTemplate(newCert, a.x509Issuer, a.x509Signer)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Renew", opts...)
}
@ -204,11 +193,6 @@ func (a *Authority) Renew(oldCert *x509.Certificate) ([]*x509.Certificate, error
return nil, errs.Wrap(http.StatusInternalServerError, err,
"authority.Renew; error parsing new server certificate", opts...)
}
caCert, err := x509.ParseCertificate(issIdentity.Crt.Raw)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err,
"authority.Renew; error parsing intermediate certificate", opts...)
}
if err = a.db.StoreCertificate(serverCert); err != nil {
if err != db.ErrNotImplemented {
@ -216,7 +200,7 @@ func (a *Authority) Renew(oldCert *x509.Certificate) ([]*x509.Certificate, error
}
}
return []*x509.Certificate{serverCert, caCert}, nil
return []*x509.Certificate{serverCert, a.x509Issuer}, nil
}
// RevokeOptions are the options for the Revoke API.
@ -320,8 +304,7 @@ func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error
// GetTLSCertificate creates a new leaf certificate to be used by the CA HTTPS server.
func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) {
profile, err := x509util.NewLeafProfile("Step Online CA",
a.intermediateIdentity.Crt, a.intermediateIdentity.Key,
profile, err := x509util.NewLeafProfile("Step Online CA", a.x509Issuer, a.x509Signer,
x509util.WithHosts(strings.Join(a.config.DNSNames, ",")))
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate")
@ -344,7 +327,7 @@ func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) {
// Load the x509 key pair (combining server and intermediate blocks)
// to a tls.Certificate.
intermediatePEM, err := pemutil.Serialize(a.intermediateIdentity.Crt)
intermediatePEM, err := pemutil.Serialize(a.x509Issuer)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate")
}

@ -2,6 +2,7 @@ package authority
import (
"context"
"crypto"
"crypto/rand"
"crypto/sha1"
"crypto/x509"
@ -156,7 +157,7 @@ func TestAuthority_Sign(t *testing.T) {
},
"fail create cert": func(t *testing.T) *signTest {
_a := testAuthority(t)
_a.intermediateIdentity.Key = nil
_a.x509Signer = nil
csr := getCSR(t, priv)
return &signTest{
auth: _a,
@ -303,7 +304,7 @@ ZYtQ9Ot36qc=
hash := sha1.Sum(pubBytes)
assert.Equals(t, leaf.SubjectKeyId, hash[:])
assert.Equals(t, leaf.AuthorityKeyId, a.intermediateIdentity.Crt.SubjectKeyId)
assert.Equals(t, leaf.AuthorityKeyId, a.x509Issuer.SubjectKeyId)
// Verify Provisioner OID
found := 0
@ -322,7 +323,7 @@ ZYtQ9Ot36qc=
}
assert.Equals(t, found, 1)
realIntermediate, err := x509.ParseCertificate(a.intermediateIdentity.Crt.Raw)
realIntermediate, err := x509.ParseCertificate(a.x509Issuer.Raw)
assert.FatalError(t, err)
assert.Equals(t, intermediate, realIntermediate)
}
@ -353,8 +354,7 @@ func TestAuthority_Renew(t *testing.T) {
NotAfter: provisioner.NewTimeDuration(na1),
}
leaf, err := x509util.NewLeafProfile("renew", a.intermediateIdentity.Crt,
a.intermediateIdentity.Key,
leaf, err := x509util.NewLeafProfile("renew", a.x509Issuer, a.x509Signer,
x509util.WithNotBeforeAfterDuration(so.NotBefore.Time(), so.NotAfter.Time(), 0),
withDefaultASN1DN(a.config.AuthorityConfig.Template),
x509util.WithPublicKey(pub), x509util.WithHosts("test.smallstep.com,test"),
@ -365,8 +365,7 @@ func TestAuthority_Renew(t *testing.T) {
cert, err := x509.ParseCertificate(certBytes)
assert.FatalError(t, err)
leafNoRenew, err := x509util.NewLeafProfile("norenew", a.intermediateIdentity.Crt,
a.intermediateIdentity.Key,
leafNoRenew, err := x509util.NewLeafProfile("norenew", a.x509Issuer, a.x509Signer,
x509util.WithNotBeforeAfterDuration(so.NotBefore.Time(), so.NotAfter.Time(), 0),
withDefaultASN1DN(a.config.AuthorityConfig.Template),
x509util.WithPublicKey(pub), x509util.WithHosts("test.smallstep.com,test"),
@ -387,7 +386,7 @@ func TestAuthority_Renew(t *testing.T) {
tests := map[string]func() (*renewTest, error){
"fail-create-cert": func() (*renewTest, error) {
_a := testAuthority(t)
_a.intermediateIdentity.Key = nil
_a.x509Signer = nil
return &renewTest{
auth: _a,
cert: cert,
@ -425,8 +424,8 @@ func TestAuthority_Renew(t *testing.T) {
assert.FatalError(t, err)
_a := testAuthority(t)
_a.intermediateIdentity.Key = newIntermediateProfile.SubjectPrivateKey()
_a.intermediateIdentity.Crt = newIntermediateCert
_a.x509Signer = newIntermediateProfile.SubjectPrivateKey().(crypto.Signer)
_a.x509Issuer = newIntermediateCert
return &renewTest{
auth: _a,
cert: cert,
@ -494,8 +493,8 @@ func TestAuthority_Renew(t *testing.T) {
assert.Equals(t, leaf.SubjectKeyId, hash[:])
// We did not change the intermediate before renewing.
if a.intermediateIdentity.Crt.SerialNumber == tc.auth.intermediateIdentity.Crt.SerialNumber {
assert.Equals(t, leaf.AuthorityKeyId, a.intermediateIdentity.Crt.SubjectKeyId)
if a.x509Issuer.SerialNumber == tc.auth.x509Issuer.SerialNumber {
assert.Equals(t, leaf.AuthorityKeyId, a.x509Issuer.SubjectKeyId)
// Compare extensions: they can be in a different order
for _, ext1 := range tc.cert.Extensions {
found := false
@ -511,7 +510,7 @@ func TestAuthority_Renew(t *testing.T) {
}
} else {
// We did change the intermediate before renewing.
assert.Equals(t, leaf.AuthorityKeyId, tc.auth.intermediateIdentity.Crt.SubjectKeyId)
assert.Equals(t, leaf.AuthorityKeyId, tc.auth.x509Issuer.SubjectKeyId)
// Compare extensions: they can be in a different order
for _, ext1 := range tc.cert.Extensions {
// The authority key id extension should be different b/c the intermediates are different.
@ -535,7 +534,7 @@ func TestAuthority_Renew(t *testing.T) {
}
}
realIntermediate, err := x509.ParseCertificate(tc.auth.intermediateIdentity.Crt.Raw)
realIntermediate, err := x509.ParseCertificate(tc.auth.x509Issuer.Raw)
assert.FatalError(t, err)
assert.Equals(t, intermediate, realIntermediate)
}

@ -3,8 +3,11 @@ module github.com/smallstep/certificates
go 1.13
require (
cloud.google.com/go v0.51.0
github.com/Masterminds/sprig/v3 v3.0.0
github.com/go-chi/chi v4.0.2+incompatible
github.com/google/go-cmp v0.4.0 // indirect
github.com/googleapis/gax-go/v2 v2.0.5
github.com/newrelic/go-agent v2.15.0+incompatible
github.com/pkg/errors v0.8.1
github.com/rs/xid v1.2.1
@ -15,6 +18,9 @@ require (
github.com/urfave/cli v1.22.2
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553
google.golang.org/api v0.15.0
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb
google.golang.org/grpc v1.26.0
gopkg.in/square/go-jose.v2 v2.4.0
)

@ -1,12 +1,24 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.51.0 h1:PvKAVQWCtlGUSlZkGW3QLelKaWq7KYv/MW1EboG8bfM=
cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw=
contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA=
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-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
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/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic=
github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
@ -97,6 +109,7 @@ github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxm
github.com/go-critic/go-critic v0.3.5-0.20190526074819-1df300866540/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA=
github.com/go-critic/go-critic v0.4.0 h1:sXD3pix0wDemuPuSlrXpJNNYXlUiKiysLrtPVQmxkzI=
github.com/go-critic/go-critic v0.4.0/go.mod h1:7/14rZGnZbY6E38VEGk2kVhoq6itzc1E68facVDK23g=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-lintpack/lintpack v0.5.2 h1:DI5mA3+eKdWeJ40nU4d6Wc26qmdG8RCi/btYq0TuRN0=
@ -135,8 +148,11 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF2I64qf5Fh8Aa83Q/dnOafMYV0OMwjA=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.0.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
@ -192,11 +208,15 @@ github.com/google/certificate-transparency-go v1.1.0/go.mod h1:i+Q7XY+ArBveOUT36
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/monologue v0.0.0-20190606152607-4b11a32b5934/go.mod h1:6NTfaQoUpg5QmPsCUWLR3ig33FHrKXhTtWzF0DVdmuk=
github.com/google/monologue v0.0.0-20191220140058-35abc9683a6c/go.mod h1:6NTfaQoUpg5QmPsCUWLR3ig33FHrKXhTtWzF0DVdmuk=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/trillian v1.2.2-0.20190612132142-05461f4df60a/go.mod h1:YPmUVn5NGwgnDUgqlVyFGMTgaWlnSvH7W5p+NdOG8UA=
github.com/google/trillian-examples v0.0.0-20190603134952-4e75ba15216c/go.mod h1:WgL3XZ3pA8/9cm7yxqWrZE6iZkESB2ItGxy5Fo6k2lk=
@ -204,6 +224,8 @@ github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
@ -223,6 +245,7 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0=
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGznybq1itEKXj6RYw9I71qK4kH+OGMjRC4KEo=
github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
@ -237,6 +260,7 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
@ -266,6 +290,7 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
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/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/letsencrypt/pkcs11key v2.0.1-0.20170608213348-396559074696+incompatible h1:GfzE+uq7odDW7nOmp1QWuilLEK7kJf8i84XcIfk3mKA=
github.com/letsencrypt/pkcs11key v2.0.1-0.20170608213348-396559074696+incompatible/go.mod h1:iGYXKqDXt0cpBthCHdr9ZdsQwyGlYFh/+8xa4WzIQ34=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@ -299,6 +324,7 @@ github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/pkcs11 v1.0.2 h1:CIBkOawOtzJNE0B+EpRiUBzuVW7JEQAwdwhSS6YhIeg=
github.com/miekg/pkcs11 v1.0.2/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/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
@ -538,6 +564,8 @@ go.etcd.io/etcd v3.3.13+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSF
go.etcd.io/etcd v3.3.18+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI=
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.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
@ -552,6 +580,7 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
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-20190911031432-227b76d455e7 h1:0hQKqeLdqlt5iIwVOBErRisrHJAN57yOiPRQItI20fU=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
@ -560,14 +589,24 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 h1:sKJQZMuxjOAR/Uo2LBfU90onWEf1dF4C+0hPJCc9Mpc=
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20170915142106-8351a756f30f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -584,6 +623,7 @@ golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -595,6 +635,8 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0HISf9D4TzseP40cEA6IGfs=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -620,12 +662,16 @@ golang.org/x/sys v0.0.0-20190424175732-18eb32c0e2f0 h1:V+O002es++Mnym06Rj/S6Fl7V
golang.org/x/sys v0.0.0-20190424175732-18eb32c0e2f0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e h1:LwyF2AFISC9nVbS6MgzsaQNSUsRXI49GS+YQ5KX/QH0=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915090833-1cbadb444a80/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -650,6 +696,7 @@ golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
@ -657,36 +704,55 @@ golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBn
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190909030654-5b82db07426d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911151314-feee8acb394c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113232020-e2727e816f5a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200106190116-7be0a674c9fc h1:MR2F33ipDGog0C4eMhU6u9o3q6c3dvYis2aG6Jl12Wg=
golang.org/x/tools v0.0.0-20200106190116-7be0a674c9fc/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.15.0 h1:yzlyyDW/J0w8yNFJIhiAJy4kq74S+1DOLdawELNxFMA=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190605220351-eb0b1bdb6ae6/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb h1:ADPHZzpzM4tk4V4S5cnCrr5SwzvlrPRmqqCuJDB8UTs=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
@ -694,6 +760,7 @@ google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ij
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
@ -737,6 +804,7 @@ mvdan.cc/unparam v0.0.0-20190209190245-fbb59629db34/go.mod h1:H6SUd1XjIs+qQCyskX
mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw=
mvdan.cc/unparam v0.0.0-20191111180625-960b1ec0f2c2 h1:K7wru2CfJGumS5hkiguQ0Rb9ebKM2Jo8s5d4Jm9lFaM=
mvdan.cc/unparam v0.0.0-20191111180625-960b1ec0f2c2/go.mod h1:rCqoQrfAmpTX/h2APczwM7UymU/uvaOluiVPIYCSY/k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
sourcegraph.com/sqs/pbtypes v1.0.0 h1:f7lAwqviDEGvON4kRv0o5V7FT/IQK+tbkF664XMbP3o=

@ -0,0 +1,68 @@
package apiv1
import (
"strings"
"github.com/pkg/errors"
)
// ErrNotImplemented
type ErrNotImplemented struct {
msg string
}
func (e ErrNotImplemented) Error() string {
if e.msg != "" {
return e.msg
}
return "not implemented"
}
// Type represents the KMS type used.
type Type string
const (
// DefaultKMS is a KMS implementation using software.
DefaultKMS Type = ""
// SoftKMS is a KMS implementation using software.
SoftKMS Type = "softkms"
// CloudKMS is a KMS implementation using Google's Cloud KMS.
CloudKMS Type = "cloudkms"
// AmazonKMS is a KMS implementation using Amazon AWS KMS.
AmazonKMS Type = "awskms"
// PKCS11 is a KMS implementation using the PKCS11 standard.
PKCS11 Type = "pkcs11"
)
type Options struct {
// The type of the KMS to use.
Type string `json:"type"`
// Path to the credentials file used in CloudKMS.
CredentialsFile string `json:"credentialsFile"`
// Path to the module used with PKCS11 KMS.
Module string `json:"module"`
// Pin used to access the PKCS11 module.
Pin string `json:"pin"`
}
// Validate checks the fields in Options.
func (o *Options) Validate() error {
if o == nil {
return nil
}
switch Type(strings.ToLower(o.Type)) {
case DefaultKMS, SoftKMS, CloudKMS:
case AmazonKMS:
return ErrNotImplemented{"support for AmazonKMS is not yet implemented"}
case PKCS11:
return ErrNotImplemented{"support for PKCS11 is not yet implemented"}
default:
return errors.Errorf("unsupported kms type %s", o.Type)
}
return nil
}

@ -0,0 +1,51 @@
package apiv1
import (
"testing"
)
func TestOptions_Validate(t *testing.T) {
tests := []struct {
name string
options *Options
wantErr bool
}{
{"nil", nil, false},
{"softkms", &Options{Type: "softkms"}, false},
{"cloudkms", &Options{Type: "cloudkms"}, false},
{"awskms", &Options{Type: "awskms"}, true},
{"pkcs11", &Options{Type: "pkcs11"}, true},
{"unsupported", &Options{Type: "unsupported"}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.options.Validate(); (err != nil) != tt.wantErr {
t.Errorf("Options.Validate() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestErrNotImplemented_Error(t *testing.T) {
type fields struct {
msg string
}
tests := []struct {
name string
fields fields
want string
}{
{"default", fields{}, "not implemented"},
{"custom", fields{"custom message: not implemented"}, "custom message: not implemented"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
e := ErrNotImplemented{
msg: tt.fields.msg,
}
if got := e.Error(); got != tt.want {
t.Errorf("ErrNotImplemented.Error() = %v, want %v", got, tt.want)
}
})
}
}

@ -0,0 +1,126 @@
package apiv1
import (
"crypto"
"fmt"
)
// ProtectionLevel specifies on some KMS how cryptographic operations are
// performed.
type ProtectionLevel int
const (
// Protection level not specified.
UnspecifiedProtectionLevel ProtectionLevel = iota
// Crypto operations are performed in software.
Software
// Crypto operations are performed in a Hardware Security Module.
HSM
)
// String returns a string representation of p.
func (p ProtectionLevel) String() string {
switch p {
case UnspecifiedProtectionLevel:
return "unspecified"
case Software:
return "software"
case HSM:
return "hsm"
default:
return fmt.Sprintf("unknown(%d)", p)
}
}
// SignatureAlgorithm used for cryptographic signing.
type SignatureAlgorithm int
const (
// Not specified.
UnspecifiedSignAlgorithm SignatureAlgorithm = iota
// RSASSA-PKCS1-v1_5 key and a SHA256 digest.
SHA256WithRSA
// RSASSA-PKCS1-v1_5 key and a SHA384 digest.
SHA384WithRSA
// RSASSA-PKCS1-v1_5 key and a SHA512 digest.
SHA512WithRSA
// RSASSA-PSS key with a SHA256 digest.
SHA256WithRSAPSS
// RSASSA-PSS key with a SHA384 digest.
SHA384WithRSAPSS
// RSASSA-PSS key with a SHA512 digest.
SHA512WithRSAPSS
// ECDSA on the NIST P-256 curve with a SHA256 digest.
ECDSAWithSHA256
// ECDSA on the NIST P-384 curve with a SHA384 digest.
ECDSAWithSHA384
// ECDSA on the NIST P-521 curve with a SHA512 digest.
ECDSAWithSHA512
// EdDSA on Curve25519 with a SHA512 digest.
PureEd25519
)
// String returns a string representation of s.
func (s SignatureAlgorithm) String() string {
switch s {
case UnspecifiedSignAlgorithm:
return "unspecified"
case SHA256WithRSA:
return "SHA256-RSA"
case SHA384WithRSA:
return "SHA384-RSA"
case SHA512WithRSA:
return "SHA512-RSA"
case SHA256WithRSAPSS:
return "SHA256-RSAPSS"
case SHA384WithRSAPSS:
return "SHA384-RSAPSS"
case SHA512WithRSAPSS:
return "SHA512-RSAPSS"
case ECDSAWithSHA256:
return "ECDSA-SHA256"
case ECDSAWithSHA384:
return "ECDSA-SHA384"
case ECDSAWithSHA512:
return "ECDSA-SHA512"
case PureEd25519:
return "Ed25519"
default:
return fmt.Sprintf("unknown(%d)", s)
}
}
// GetPublicKeyRequest is the parameter used in the kms.GetPublicKey method.
type GetPublicKeyRequest struct {
Name string
}
// CreateKeyRequest is the parameter used in the kms.CreateKey method.
type CreateKeyRequest struct {
Name string
SignatureAlgorithm SignatureAlgorithm
Bits int
// ProtectionLevel specifies how cryptographic operations are performed.
// Used by: cloudkms
ProtectionLevel ProtectionLevel
}
// CreateKeyResponse is the response value of the kms.CreateKey method.
type CreateKeyResponse struct {
Name string
PublicKey crypto.PublicKey
PrivateKey crypto.PrivateKey
CreateSignerRequest CreateSignerRequest
}
// CreateSignerRequest is the parameter used in the kms.CreateSigner method.
type CreateSignerRequest struct {
Signer crypto.Signer
SigningKey string
SigningKeyPEM []byte
TokenLabel string
PublicKey string
PublicKeyPEM []byte
Password []byte
}

@ -0,0 +1,51 @@
package apiv1
import "testing"
func TestProtectionLevel_String(t *testing.T) {
tests := []struct {
name string
p ProtectionLevel
want string
}{
{"unspecified", UnspecifiedProtectionLevel, "unspecified"},
{"software", Software, "software"},
{"hsm", HSM, "hsm"},
{"unknown", ProtectionLevel(100), "unknown(100)"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.p.String(); got != tt.want {
t.Errorf("ProtectionLevel.String() = %v, want %v", got, tt.want)
}
})
}
}
func TestSignatureAlgorithm_String(t *testing.T) {
tests := []struct {
name string
s SignatureAlgorithm
want string
}{
{"UnspecifiedSignAlgorithm", UnspecifiedSignAlgorithm, "unspecified"},
{"SHA256WithRSA", SHA256WithRSA, "SHA256-RSA"},
{"SHA384WithRSA", SHA384WithRSA, "SHA384-RSA"},
{"SHA512WithRSA", SHA512WithRSA, "SHA512-RSA"},
{"SHA256WithRSAPSS", SHA256WithRSAPSS, "SHA256-RSAPSS"},
{"SHA384WithRSAPSS", SHA384WithRSAPSS, "SHA384-RSAPSS"},
{"SHA512WithRSAPSS", SHA512WithRSAPSS, "SHA512-RSAPSS"},
{"ECDSAWithSHA256", ECDSAWithSHA256, "ECDSA-SHA256"},
{"ECDSAWithSHA384", ECDSAWithSHA384, "ECDSA-SHA384"},
{"ECDSAWithSHA512", ECDSAWithSHA512, "ECDSA-SHA512"},
{"PureEd25519", PureEd25519, "Ed25519"},
{"unknown", SignatureAlgorithm(100), "unknown(100)"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.s.String(); got != tt.want {
t.Errorf("SignatureAlgorithm.String() = %v, want %v", got, tt.want)
}
})
}
}

@ -0,0 +1,280 @@
package cloudkms
import (
"context"
"crypto"
"strings"
"time"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
cloudkms "cloud.google.com/go/kms/apiv1"
gax "github.com/googleapis/gax-go/v2"
"github.com/pkg/errors"
"github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/cli/crypto/pemutil"
"google.golang.org/api/option"
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
)
// protectionLevelMapping maps step protection levels with cloud kms ones.
var protectionLevelMapping = map[apiv1.ProtectionLevel]kmspb.ProtectionLevel{
apiv1.UnspecifiedProtectionLevel: kmspb.ProtectionLevel_PROTECTION_LEVEL_UNSPECIFIED,
apiv1.Software: kmspb.ProtectionLevel_SOFTWARE,
apiv1.HSM: kmspb.ProtectionLevel_HSM,
}
// signatureAlgorithmMapping is a mapping between the step signature algorithm,
// and bits for RSA keys, with cloud kms one.
//
// Cloud KMS does not support SHA384WithRSA, SHA384WithRSAPSS, SHA384WithRSAPSS,
// ECDSAWithSHA512, and PureEd25519.
var signatureAlgorithmMapping = map[apiv1.SignatureAlgorithm]interface{}{
apiv1.UnspecifiedSignAlgorithm: kmspb.CryptoKeyVersion_CRYPTO_KEY_VERSION_ALGORITHM_UNSPECIFIED,
apiv1.SHA256WithRSA: map[int]kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm{
0: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_3072_SHA256,
2048: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_2048_SHA256,
3072: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_3072_SHA256,
4096: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA256,
},
apiv1.SHA512WithRSA: map[int]kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm{
0: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA256,
4096: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA256,
},
apiv1.SHA256WithRSAPSS: map[int]kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm{
0: kmspb.CryptoKeyVersion_RSA_SIGN_PSS_3072_SHA256,
2048: kmspb.CryptoKeyVersion_RSA_SIGN_PSS_2048_SHA256,
3072: kmspb.CryptoKeyVersion_RSA_SIGN_PSS_3072_SHA256,
4096: kmspb.CryptoKeyVersion_RSA_SIGN_PSS_4096_SHA256,
},
apiv1.SHA512WithRSAPSS: map[int]kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm{
0: kmspb.CryptoKeyVersion_RSA_SIGN_PSS_4096_SHA512,
4096: kmspb.CryptoKeyVersion_RSA_SIGN_PSS_4096_SHA512,
},
apiv1.ECDSAWithSHA256: kmspb.CryptoKeyVersion_EC_SIGN_P256_SHA256,
apiv1.ECDSAWithSHA384: kmspb.CryptoKeyVersion_EC_SIGN_P384_SHA384,
}
// KeyManagementClient defines the methods on KeyManagementClient that this
// package will use. This interface will be used for unit testing.
type KeyManagementClient interface {
Close() error
GetPublicKey(context.Context, *kmspb.GetPublicKeyRequest, ...gax.CallOption) (*kmspb.PublicKey, error)
AsymmetricSign(context.Context, *kmspb.AsymmetricSignRequest, ...gax.CallOption) (*kmspb.AsymmetricSignResponse, error)
CreateCryptoKey(context.Context, *kmspb.CreateCryptoKeyRequest, ...gax.CallOption) (*kmspb.CryptoKey, error)
GetKeyRing(context.Context, *kmspb.GetKeyRingRequest, ...gax.CallOption) (*kmspb.KeyRing, error)
CreateKeyRing(context.Context, *kmspb.CreateKeyRingRequest, ...gax.CallOption) (*kmspb.KeyRing, error)
CreateCryptoKeyVersion(ctx context.Context, req *kmspb.CreateCryptoKeyVersionRequest, opts ...gax.CallOption) (*kmspb.CryptoKeyVersion, error)
}
// CloudKMS implements a KMS using Google's Cloud apiv1.
type CloudKMS struct {
client KeyManagementClient
}
// New creates a new CloudKMS configured with a new client.
func New(ctx context.Context, opts apiv1.Options) (*CloudKMS, error) {
var cloudOpts []option.ClientOption
if opts.CredentialsFile != "" {
cloudOpts = append(cloudOpts, option.WithCredentialsFile(opts.CredentialsFile))
}
client, err := cloudkms.NewKeyManagementClient(ctx, cloudOpts...)
if err != nil {
return nil, err
}
return &CloudKMS{
client: client,
}, nil
}
// NewCloudKMS creates a CloudKMS with a given client.
func NewCloudKMS(client KeyManagementClient) *CloudKMS {
return &CloudKMS{
client: client,
}
}
// Close closes the connection of the Cloud KMS client.
func (k *CloudKMS) Close() error {
if err := k.client.Close(); err != nil {
return errors.Wrap(err, "cloudKMS Close failed")
}
return nil
}
// CreateSigner returns a new cloudkms signer configured with the given signing
// key name.
func (k *CloudKMS) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error) {
if req.SigningKey == "" {
return nil, errors.New("signing key cannot be empty")
}
return NewSigner(k.client, req.SigningKey), nil
}
// CreateKey creates in Google's Cloud KMS a new asymmetric key for signing.
func (k *CloudKMS) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) {
if req.Name == "" {
return nil, errors.New("createKeyRequest 'name' cannot be empty")
}
protectionLevel, ok := protectionLevelMapping[req.ProtectionLevel]
if !ok {
return nil, errors.Errorf("cloudKMS does not support protection level '%s'", req.ProtectionLevel)
}
var signatureAlgorithm kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm
v, ok := signatureAlgorithmMapping[req.SignatureAlgorithm]
if !ok {
return nil, errors.Errorf("cloudKMS does not support signature algorithm '%s'", req.SignatureAlgorithm)
}
switch v := v.(type) {
case kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm:
signatureAlgorithm = v
case map[int]kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm:
if signatureAlgorithm, ok = v[req.Bits]; !ok {
return nil, errors.Errorf("cloudKMS does not support signature algorithm '%s' with '%d' bits", req.SignatureAlgorithm, req.Bits)
}
default:
return nil, errors.Errorf("unexpected error: this should not happen")
}
var crytoKeyName string
// Split `projects/PROJECT_ID/locations/global/keyRings/RING_ID/cryptoKeys/KEY_ID`
// to `projects/PROJECT_ID/locations/global/keyRings/RING_ID` and `KEY_ID`.
keyRing, keyID := Parent(req.Name)
if err := k.createKeyRingIfNeeded(keyRing); err != nil {
return nil, err
}
ctx, cancel := defaultContext()
defer cancel()
// Create private key in CloudKMS.
response, err := k.client.CreateCryptoKey(ctx, &kmspb.CreateCryptoKeyRequest{
Parent: keyRing,
CryptoKeyId: keyID,
CryptoKey: &kmspb.CryptoKey{
Purpose: kmspb.CryptoKey_ASYMMETRIC_SIGN,
VersionTemplate: &kmspb.CryptoKeyVersionTemplate{
ProtectionLevel: protectionLevel,
Algorithm: signatureAlgorithm,
},
},
})
if err != nil {
if status.Code(err) != codes.AlreadyExists {
return nil, errors.Wrap(err, "cloudKMS CreateCryptoKey failed")
}
// Create a new version if the key already exists.
//
// Note that it will have the same purpose, protection level and
// algorithm than as previous one.
req := &kmspb.CreateCryptoKeyVersionRequest{
Parent: req.Name,
CryptoKeyVersion: &kmspb.CryptoKeyVersion{
State: kmspb.CryptoKeyVersion_ENABLED,
},
}
response, err := k.client.CreateCryptoKeyVersion(ctx, req)
if err != nil {
return nil, errors.Wrap(err, "cloudKMS CreateCryptoKeyVersion failed")
}
crytoKeyName = response.Name
} else {
crytoKeyName = response.Name + "/cryptoKeyVersions/1"
}
// Retrieve public key to add it to the response.
pk, err := k.GetPublicKey(&apiv1.GetPublicKeyRequest{
Name: crytoKeyName,
})
if err != nil {
return nil, errors.Wrap(err, "cloudKMS GetPublicKey failed")
}
return &apiv1.CreateKeyResponse{
Name: crytoKeyName,
PublicKey: pk,
CreateSignerRequest: apiv1.CreateSignerRequest{
SigningKey: crytoKeyName,
},
}, nil
}
func (k *CloudKMS) createKeyRingIfNeeded(name string) error {
ctx, cancel := defaultContext()
defer cancel()
_, err := k.client.GetKeyRing(ctx, &kmspb.GetKeyRingRequest{
Name: name,
})
if err == nil {
return nil
}
parent, child := Parent(name)
_, err = k.client.CreateKeyRing(ctx, &kmspb.CreateKeyRingRequest{
Parent: parent,
KeyRingId: child,
})
if err != nil && status.Code(err) != codes.AlreadyExists {
return errors.Wrap(err, "cloudKMS CreateKeyRing failed")
}
return nil
}
// GetPublicKey gets from Google's Cloud KMS a public key by name. Key names
// follow the pattern:
// projects/([^/]+)/locations/([a-zA-Z0-9_-]{1,63})/keyRings/([a-zA-Z0-9_-]{1,63})/cryptoKeys/([a-zA-Z0-9_-]{1,63})/cryptoKeyVersions/([a-zA-Z0-9_-]{1,63})
func (k *CloudKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) {
if req.Name == "" {
return nil, errors.New("createKeyRequest 'name' cannot be empty")
}
ctx, cancel := defaultContext()
defer cancel()
response, err := k.client.GetPublicKey(ctx, &kmspb.GetPublicKeyRequest{
Name: req.Name,
})
if err != nil {
return nil, errors.Wrap(err, "cloudKMS GetPublicKey failed")
}
pk, err := pemutil.ParseKey([]byte(response.Pem))
if err != nil {
return nil, err
}
return pk, nil
}
func defaultContext() (context.Context, context.CancelFunc) {
return context.WithTimeout(context.Background(), 15*time.Second)
}
// Parent splits a string in the format `key/value/key2/value2` in a parent and
// child, for the previous string it will return `key/value` and `value2`.
func Parent(name string) (string, string) {
a, b := parent(name)
a, _ = parent(a)
return a, b
}
func parent(name string) (string, string) {
i := strings.LastIndex(name, "/")
switch i {
case -1:
return "", name
case 0:
return "", name[i+1:]
default:
return name[:i], name[i+1:]
}
}

@ -0,0 +1,376 @@
package cloudkms
import (
"context"
"crypto"
"fmt"
"io/ioutil"
"os"
"reflect"
"testing"
gax "github.com/googleapis/gax-go/v2"
"github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/cli/crypto/pemutil"
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func TestParent(t *testing.T) {
type args struct {
name string
}
tests := []struct {
name string
args args
want string
want1 string
}{
{"zero", args{"child"}, "", "child"},
{"one", args{"parent/child"}, "", "child"},
{"two", args{"grandparent/parent/child"}, "grandparent", "child"},
{"three", args{"great-grandparent/grandparent/parent/child"}, "great-grandparent/grandparent", "child"},
{"empty", args{""}, "", ""},
{"root", args{"/"}, "", ""},
{"child", args{"/child"}, "", "child"},
{"parent", args{"parent/"}, "", ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, got1 := Parent(tt.args.name)
if got != tt.want {
t.Errorf("Parent() got = %v, want %v", got, tt.want)
}
if got1 != tt.want1 {
t.Errorf("Parent() got1 = %v, want %v", got1, tt.want1)
}
})
}
}
func TestNew(t *testing.T) {
type args struct {
ctx context.Context
opts apiv1.Options
}
tests := []struct {
name string
skipOnCI bool
args args
want *CloudKMS
wantErr bool
}{
{"fail authentication", true, args{context.Background(), apiv1.Options{}}, nil, true},
{"fail credentials", false, args{context.Background(), apiv1.Options{CredentialsFile: "testdata/missing"}}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.skipOnCI && os.Getenv("CI") == "true" {
t.SkipNow()
}
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 TestNewCloudKMS(t *testing.T) {
type args struct {
client KeyManagementClient
}
tests := []struct {
name string
args args
want *CloudKMS
}{
{"ok", args{&MockClient{}}, &CloudKMS{&MockClient{}}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewCloudKMS(tt.args.client); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewCloudKMS() = %v, want %v", got, tt.want)
}
})
}
}
func TestCloudKMS_Close(t *testing.T) {
type fields struct {
client KeyManagementClient
}
tests := []struct {
name string
fields fields
wantErr bool
}{
{"ok", fields{&MockClient{close: func() error { return nil }}}, false},
{"fail", fields{&MockClient{close: func() error { return fmt.Errorf("an error") }}}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
k := &CloudKMS{
client: tt.fields.client,
}
if err := k.Close(); (err != nil) != tt.wantErr {
t.Errorf("CloudKMS.Close() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestCloudKMS_CreateSigner(t *testing.T) {
keyName := "projects/p/locations/l/keyRings/k/cryptoKeys/c/cryptoKeyVersions/1"
type fields struct {
client KeyManagementClient
}
type args struct {
req *apiv1.CreateSignerRequest
}
tests := []struct {
name string
fields fields
args args
want crypto.Signer
wantErr bool
}{
{"ok", fields{&MockClient{}}, args{&apiv1.CreateSignerRequest{SigningKey: keyName}}, &Signer{client: &MockClient{}, signingKey: keyName}, false},
{"fail", fields{&MockClient{}}, args{&apiv1.CreateSignerRequest{SigningKey: ""}}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
k := &CloudKMS{
client: tt.fields.client,
}
got, err := k.CreateSigner(tt.args.req)
if (err != nil) != tt.wantErr {
t.Errorf("CloudKMS.CreateSigner() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("CloudKMS.CreateSigner() = %v, want %v", got, tt.want)
}
})
}
}
func TestCloudKMS_CreateKey(t *testing.T) {
keyName := "projects/p/locations/l/keyRings/k/cryptoKeys/c"
testError := fmt.Errorf("an error")
alreadyExists := status.Error(codes.AlreadyExists, "already exists")
pemBytes, err := ioutil.ReadFile("testdata/pub.pem")
if err != nil {
t.Fatal(err)
}
pk, err := pemutil.ParseKey(pemBytes)
if err != nil {
t.Fatal(err)
}
type fields struct {
client KeyManagementClient
}
type args struct {
req *apiv1.CreateKeyRequest
}
tests := []struct {
name string
fields fields
args args
want *apiv1.CreateKeyResponse
wantErr bool
}{
{"ok", fields{
&MockClient{
getKeyRing: func(_ context.Context, _ *kmspb.GetKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) {
return &kmspb.KeyRing{}, nil
},
createCryptoKey: func(_ context.Context, _ *kmspb.CreateCryptoKeyRequest, _ ...gax.CallOption) (*kmspb.CryptoKey, error) {
return &kmspb.CryptoKey{Name: keyName}, nil
},
getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) {
return &kmspb.PublicKey{Pem: string(pemBytes)}, nil
},
}},
args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.HSM, SignatureAlgorithm: apiv1.ECDSAWithSHA256}},
&apiv1.CreateKeyResponse{Name: keyName + "/cryptoKeyVersions/1", PublicKey: pk, CreateSignerRequest: apiv1.CreateSignerRequest{SigningKey: keyName + "/cryptoKeyVersions/1"}}, false},
{"ok new key ring", fields{
&MockClient{
getKeyRing: func(_ context.Context, _ *kmspb.GetKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) {
return nil, testError
},
createKeyRing: func(_ context.Context, _ *kmspb.CreateKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) {
return nil, alreadyExists
},
createCryptoKey: func(_ context.Context, _ *kmspb.CreateCryptoKeyRequest, _ ...gax.CallOption) (*kmspb.CryptoKey, error) {
return &kmspb.CryptoKey{Name: keyName}, nil
},
getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) {
return &kmspb.PublicKey{Pem: string(pemBytes)}, nil
},
}},
args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.Software, SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 3072}},
&apiv1.CreateKeyResponse{Name: keyName + "/cryptoKeyVersions/1", PublicKey: pk, CreateSignerRequest: apiv1.CreateSignerRequest{SigningKey: keyName + "/cryptoKeyVersions/1"}}, false},
{"ok new key version", fields{
&MockClient{
getKeyRing: func(_ context.Context, _ *kmspb.GetKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) {
return &kmspb.KeyRing{}, nil
},
createCryptoKey: func(_ context.Context, _ *kmspb.CreateCryptoKeyRequest, _ ...gax.CallOption) (*kmspb.CryptoKey, error) {
return nil, alreadyExists
},
createCryptoKeyVersion: func(_ context.Context, _ *kmspb.CreateCryptoKeyVersionRequest, _ ...gax.CallOption) (*kmspb.CryptoKeyVersion, error) {
return &kmspb.CryptoKeyVersion{Name: keyName + "/cryptoKeyVersions/2"}, nil
},
getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) {
return &kmspb.PublicKey{Pem: string(pemBytes)}, nil
},
}},
args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.HSM, SignatureAlgorithm: apiv1.ECDSAWithSHA256}},
&apiv1.CreateKeyResponse{Name: keyName + "/cryptoKeyVersions/2", PublicKey: pk, CreateSignerRequest: apiv1.CreateSignerRequest{SigningKey: keyName + "/cryptoKeyVersions/2"}}, false},
{"fail name", fields{&MockClient{}}, args{&apiv1.CreateKeyRequest{}}, nil, true},
{"fail protection level", fields{&MockClient{}}, args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.ProtectionLevel(100)}}, nil, true},
{"fail signature algorithm", fields{&MockClient{}}, args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.Software, SignatureAlgorithm: apiv1.SignatureAlgorithm(100)}}, nil, true},
{"fail number of bits", fields{&MockClient{}}, args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.Software, SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 1024}},
nil, true},
{"fail create key ring", fields{
&MockClient{
getKeyRing: func(_ context.Context, _ *kmspb.GetKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) {
return nil, testError
},
createKeyRing: func(_ context.Context, _ *kmspb.CreateKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) {
return nil, testError
},
}},
args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.HSM, SignatureAlgorithm: apiv1.ECDSAWithSHA256}},
nil, true},
{"fail create key", fields{
&MockClient{
getKeyRing: func(_ context.Context, _ *kmspb.GetKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) {
return &kmspb.KeyRing{}, nil
},
createCryptoKey: func(_ context.Context, _ *kmspb.CreateCryptoKeyRequest, _ ...gax.CallOption) (*kmspb.CryptoKey, error) {
return nil, testError
},
}},
args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.HSM, SignatureAlgorithm: apiv1.ECDSAWithSHA256}},
nil, true},
{"fail create key version", fields{
&MockClient{
getKeyRing: func(_ context.Context, _ *kmspb.GetKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) {
return &kmspb.KeyRing{}, nil
},
createCryptoKey: func(_ context.Context, _ *kmspb.CreateCryptoKeyRequest, _ ...gax.CallOption) (*kmspb.CryptoKey, error) {
return nil, alreadyExists
},
createCryptoKeyVersion: func(_ context.Context, _ *kmspb.CreateCryptoKeyVersionRequest, _ ...gax.CallOption) (*kmspb.CryptoKeyVersion, error) {
return nil, testError
},
}},
args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.HSM, SignatureAlgorithm: apiv1.ECDSAWithSHA256}},
nil, true},
{"fail get public key", fields{
&MockClient{
getKeyRing: func(_ context.Context, _ *kmspb.GetKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) {
return &kmspb.KeyRing{}, nil
},
createCryptoKey: func(_ context.Context, _ *kmspb.CreateCryptoKeyRequest, _ ...gax.CallOption) (*kmspb.CryptoKey, error) {
return &kmspb.CryptoKey{Name: keyName}, nil
},
getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) {
return nil, testError
},
}},
args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.HSM, SignatureAlgorithm: apiv1.ECDSAWithSHA256}},
nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
k := &CloudKMS{
client: tt.fields.client,
}
got, err := k.CreateKey(tt.args.req)
if (err != nil) != tt.wantErr {
t.Errorf("CloudKMS.CreateKey() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("CloudKMS.CreateKey() = %v, want %v", got, tt.want)
}
})
}
}
func TestCloudKMS_GetPublicKey(t *testing.T) {
keyName := "projects/p/locations/l/keyRings/k/cryptoKeys/c/cryptoKeyVersions/1"
testError := fmt.Errorf("an error")
pemBytes, err := ioutil.ReadFile("testdata/pub.pem")
if err != nil {
t.Fatal(err)
}
pk, err := pemutil.ParseKey(pemBytes)
if err != nil {
t.Fatal(err)
}
type fields struct {
client KeyManagementClient
}
type args struct {
req *apiv1.GetPublicKeyRequest
}
tests := []struct {
name string
fields fields
args args
want crypto.PublicKey
wantErr bool
}{
{"ok", fields{
&MockClient{
getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) {
return &kmspb.PublicKey{Pem: string(pemBytes)}, nil
},
}},
args{&apiv1.GetPublicKeyRequest{Name: keyName}}, pk, false},
{"fail name", fields{&MockClient{}}, args{&apiv1.GetPublicKeyRequest{}}, nil, true},
{"fail get public key", fields{
&MockClient{
getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) {
return nil, testError
},
}},
args{&apiv1.GetPublicKeyRequest{Name: keyName}}, nil, true},
{"fail parse pem", fields{
&MockClient{
getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) {
return &kmspb.PublicKey{Pem: string("bad pem")}, nil
},
}},
args{&apiv1.GetPublicKeyRequest{Name: keyName}}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
k := &CloudKMS{
client: tt.fields.client,
}
got, err := k.GetPublicKey(tt.args.req)
if (err != nil) != tt.wantErr {
t.Errorf("CloudKMS.GetPublicKey() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("CloudKMS.GetPublicKey() = %v, want %v", got, tt.want)
}
})
}
}

@ -0,0 +1,46 @@
package cloudkms
import (
"context"
gax "github.com/googleapis/gax-go/v2"
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
)
type MockClient struct {
close func() error
getPublicKey func(context.Context, *kmspb.GetPublicKeyRequest, ...gax.CallOption) (*kmspb.PublicKey, error)
asymmetricSign func(context.Context, *kmspb.AsymmetricSignRequest, ...gax.CallOption) (*kmspb.AsymmetricSignResponse, error)
createCryptoKey func(context.Context, *kmspb.CreateCryptoKeyRequest, ...gax.CallOption) (*kmspb.CryptoKey, error)
getKeyRing func(context.Context, *kmspb.GetKeyRingRequest, ...gax.CallOption) (*kmspb.KeyRing, error)
createKeyRing func(context.Context, *kmspb.CreateKeyRingRequest, ...gax.CallOption) (*kmspb.KeyRing, error)
createCryptoKeyVersion func(context.Context, *kmspb.CreateCryptoKeyVersionRequest, ...gax.CallOption) (*kmspb.CryptoKeyVersion, error)
}
func (m *MockClient) Close() error {
return m.close()
}
func (m *MockClient) GetPublicKey(ctx context.Context, req *kmspb.GetPublicKeyRequest, opts ...gax.CallOption) (*kmspb.PublicKey, error) {
return m.getPublicKey(ctx, req, opts...)
}
func (m *MockClient) AsymmetricSign(ctx context.Context, req *kmspb.AsymmetricSignRequest, opts ...gax.CallOption) (*kmspb.AsymmetricSignResponse, error) {
return m.asymmetricSign(ctx, req, opts...)
}
func (m *MockClient) CreateCryptoKey(ctx context.Context, req *kmspb.CreateCryptoKeyRequest, opts ...gax.CallOption) (*kmspb.CryptoKey, error) {
return m.createCryptoKey(ctx, req, opts...)
}
func (m *MockClient) GetKeyRing(ctx context.Context, req *kmspb.GetKeyRingRequest, opts ...gax.CallOption) (*kmspb.KeyRing, error) {
return m.getKeyRing(ctx, req, opts...)
}
func (m *MockClient) CreateKeyRing(ctx context.Context, req *kmspb.CreateKeyRingRequest, opts ...gax.CallOption) (*kmspb.KeyRing, error) {
return m.createKeyRing(ctx, req, opts...)
}
func (m *MockClient) CreateCryptoKeyVersion(ctx context.Context, req *kmspb.CreateCryptoKeyVersionRequest, opts ...gax.CallOption) (*kmspb.CryptoKeyVersion, error) {
return m.createCryptoKeyVersion(ctx, req, opts...)
}

@ -0,0 +1,78 @@
package cloudkms
import (
"crypto"
"io"
"github.com/pkg/errors"
"github.com/smallstep/cli/crypto/pemutil"
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
)
// Signer implements a crypto.Signer using Google's Cloud KMS.
type Signer struct {
client KeyManagementClient
signingKey string
}
func NewSigner(c KeyManagementClient, signingKey string) *Signer {
return &Signer{
client: c,
signingKey: signingKey,
}
}
// Public returns the public key of this signer or an error.
func (s *Signer) Public() crypto.PublicKey {
ctx, cancel := defaultContext()
defer cancel()
response, err := s.client.GetPublicKey(ctx, &kmspb.GetPublicKeyRequest{
Name: s.signingKey,
})
if err != nil {
return errors.Wrap(err, "cloudKMS GetPublicKey failed")
}
pk, err := pemutil.ParseKey([]byte(response.Pem))
if err != nil {
return err
}
return pk
}
// Sign signs digest with the private key stored in Google's Cloud KMS.
func (s *Signer) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
req := &kmspb.AsymmetricSignRequest{
Name: s.signingKey,
Digest: &kmspb.Digest{},
}
switch h := opts.HashFunc(); h {
case crypto.SHA256:
req.Digest.Digest = &kmspb.Digest_Sha256{
Sha256: digest,
}
case crypto.SHA384:
req.Digest.Digest = &kmspb.Digest_Sha384{
Sha384: digest,
}
case crypto.SHA512:
req.Digest.Digest = &kmspb.Digest_Sha512{
Sha512: digest,
}
default:
return nil, errors.Errorf("unsupported hash function %v", h)
}
ctx, cancel := defaultContext()
defer cancel()
response, err := s.client.AsymmetricSign(ctx, req)
if err != nil {
return nil, errors.Wrap(err, "cloudKMS AsymmetricSign failed")
}
return response.Signature, nil
}

@ -0,0 +1,148 @@
package cloudkms
import (
"context"
"crypto"
"crypto/rand"
"fmt"
"io"
"io/ioutil"
"reflect"
"testing"
gax "github.com/googleapis/gax-go/v2"
"github.com/smallstep/cli/crypto/pemutil"
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
)
func Test_newSigner(t *testing.T) {
type args struct {
c KeyManagementClient
signingKey string
}
tests := []struct {
name string
args args
want *Signer
}{
{"ok", args{&MockClient{}, "signingKey"}, &Signer{client: &MockClient{}, signingKey: "signingKey"}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewSigner(tt.args.c, tt.args.signingKey); !reflect.DeepEqual(got, tt.want) {
t.Errorf("newSigner() = %v, want %v", got, tt.want)
}
})
}
}
func Test_signer_Public(t *testing.T) {
keyName := "projects/p/locations/l/keyRings/k/cryptoKeys/c/cryptoKeyVersions/1"
testError := fmt.Errorf("an error")
pemBytes, err := ioutil.ReadFile("testdata/pub.pem")
if err != nil {
t.Fatal(err)
}
pk, err := pemutil.ParseKey(pemBytes)
if err != nil {
t.Fatal(err)
}
type fields struct {
client KeyManagementClient
signingKey string
}
tests := []struct {
name string
fields fields
want crypto.PublicKey
wantErr bool
}{
{"ok", fields{&MockClient{
getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) {
return &kmspb.PublicKey{Pem: string(pemBytes)}, nil
},
}, keyName}, pk, false},
{"fail get public key", fields{&MockClient{
getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) {
return nil, testError
},
}, keyName}, nil, true},
{"fail parse pem", fields{
&MockClient{
getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) {
return &kmspb.PublicKey{Pem: string("bad pem")}, nil
},
}, keyName}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &Signer{
client: tt.fields.client,
signingKey: tt.fields.signingKey,
}
got := s.Public()
if _, ok := got.(error); ok != tt.wantErr {
t.Errorf("signer.Public() error = %v, wantErr %v", got, tt.wantErr)
return
}
if !tt.wantErr && !reflect.DeepEqual(got, tt.want) {
t.Errorf("signer.Public() = %v, want %v", got, tt.want)
}
})
}
}
func Test_signer_Sign(t *testing.T) {
keyName := "projects/p/locations/l/keyRings/k/cryptoKeys/c/cryptoKeyVersions/1"
okClient := &MockClient{
asymmetricSign: func(_ context.Context, _ *kmspb.AsymmetricSignRequest, _ ...gax.CallOption) (*kmspb.AsymmetricSignResponse, error) {
return &kmspb.AsymmetricSignResponse{Signature: []byte("ok signature")}, nil
},
}
failClient := &MockClient{
asymmetricSign: func(_ context.Context, _ *kmspb.AsymmetricSignRequest, _ ...gax.CallOption) (*kmspb.AsymmetricSignResponse, error) {
return nil, fmt.Errorf("an error")
},
}
type fields struct {
client KeyManagementClient
signingKey string
}
type args struct {
rand io.Reader
digest []byte
opts crypto.SignerOpts
}
tests := []struct {
name string
fields fields
args args
want []byte
wantErr bool
}{
{"ok sha256", fields{okClient, keyName}, args{rand.Reader, []byte("digest"), crypto.SHA256}, []byte("ok signature"), false},
{"ok sha384", fields{okClient, keyName}, args{rand.Reader, []byte("digest"), crypto.SHA384}, []byte("ok signature"), false},
{"ok sha512", fields{okClient, keyName}, args{rand.Reader, []byte("digest"), crypto.SHA512}, []byte("ok signature"), false},
{"fail MD5", fields{okClient, keyName}, args{rand.Reader, []byte("digest"), crypto.MD5}, nil, true},
{"fail asymmetric sign", fields{failClient, keyName}, args{rand.Reader, []byte("digest"), crypto.SHA256}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &Signer{
client: tt.fields.client,
signingKey: tt.fields.signingKey,
}
got, err := s.Sign(tt.args.rand, tt.args.digest, tt.args.opts)
if (err != nil) != tt.wantErr {
t.Errorf("signer.Sign() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("signer.Sign() = %v, want %v", got, tt.want)
}
})
}
}

@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5VPD/W5RXn0lrs2MdoNteTSZ+sh1
veT13hakPZF9YzaNVZgujqK3d1nt+4jPECU+ED/WQ1GgFZiVGUo3flvB/w==
-----END PUBLIC KEY-----

@ -0,0 +1,36 @@
package kms
import (
"context"
"crypto"
"strings"
"github.com/pkg/errors"
"github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/certificates/kms/cloudkms"
"github.com/smallstep/certificates/kms/softkms"
)
// KeyManager is the interface implemented by all the KMS.
type KeyManager interface {
GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error)
CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error)
CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error)
Close() error
}
// New initializes a new KMS from the given type.
func New(ctx context.Context, opts apiv1.Options) (KeyManager, error) {
if err := opts.Validate(); err != nil {
return nil, err
}
switch apiv1.Type(strings.ToLower(opts.Type)) {
case apiv1.DefaultKMS, apiv1.SoftKMS:
return softkms.New(ctx, opts)
case apiv1.CloudKMS:
return cloudkms.New(ctx, opts)
default:
return nil, errors.Errorf("unsupported kms type '%s'", opts.Type)
}
}

@ -0,0 +1,51 @@
package kms
import (
"context"
"os"
"reflect"
"testing"
"github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/certificates/kms/cloudkms"
"github.com/smallstep/certificates/kms/softkms"
)
func TestNew(t *testing.T) {
ctx := context.Background()
type args struct {
ctx context.Context
opts apiv1.Options
}
tests := []struct {
name string
skipOnCI bool
args args
want KeyManager
wantErr bool
}{
{"softkms", false, args{ctx, apiv1.Options{Type: "softkms"}}, &softkms.SoftKMS{}, false},
{"default", false, args{ctx, apiv1.Options{}}, &softkms.SoftKMS{}, false},
{"cloudkms", true, args{ctx, apiv1.Options{Type: "cloudkms"}}, &cloudkms.CloudKMS{}, true}, // fails because not credentials
{"awskms", false, args{ctx, apiv1.Options{Type: "awskms"}}, nil, true}, // not yet supported
{"pkcs11", false, args{ctx, apiv1.Options{Type: "pkcs11"}}, nil, true}, // not yet supported
{"fail validation", false, args{ctx, apiv1.Options{Type: "foobar"}}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.skipOnCI && os.Getenv("CI") == "true" {
t.SkipNow()
}
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.TypeOf(got) != reflect.TypeOf(tt.want) {
t.Errorf("New() = %T, want %T", got, tt.want)
}
})
}
}

@ -0,0 +1,134 @@
package softkms
import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/x509"
"github.com/pkg/errors"
"github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/cli/crypto/keys"
"github.com/smallstep/cli/crypto/pemutil"
)
type algorithmAttributes struct {
Type string
Curve string
}
// DefaultRSAKeySize is the default size for RSA keys.
const DefaultRSAKeySize = 3072
var signatureAlgorithmMapping = map[apiv1.SignatureAlgorithm]algorithmAttributes{
apiv1.UnspecifiedSignAlgorithm: {"EC", "P-256"},
apiv1.SHA256WithRSA: {"RSA", ""},
apiv1.SHA384WithRSA: {"RSA", ""},
apiv1.SHA512WithRSA: {"RSA", ""},
apiv1.SHA256WithRSAPSS: {"RSA", ""},
apiv1.SHA384WithRSAPSS: {"RSA", ""},
apiv1.SHA512WithRSAPSS: {"RSA", ""},
apiv1.ECDSAWithSHA256: {"EC", "P-256"},
apiv1.ECDSAWithSHA384: {"EC", "P-384"},
apiv1.ECDSAWithSHA512: {"EC", "P-521"},
apiv1.PureEd25519: {"OKP", "Ed25519"},
}
// generateKey is used for testing purposes.
var generateKey = func(kty, crv string, size int) (interface{}, interface{}, error) {
if kty == "RSA" && size == 0 {
size = DefaultRSAKeySize
}
return keys.GenerateKeyPair(kty, crv, size)
}
// SoftKMS is a key manager that uses keys stored in disk.
type SoftKMS struct{}
// New returns a new SoftKMS.
func New(ctx context.Context, opts apiv1.Options) (*SoftKMS, error) {
return &SoftKMS{}, nil
}
// Close is a noop that just returns nil.
func (k *SoftKMS) Close() error {
return nil
}
// CreateSigner returns a new signer configured with the given signing key.
func (k *SoftKMS) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error) {
var opts []pemutil.Options
if req.Password != nil {
opts = append(opts, pemutil.WithPassword(req.Password))
}
switch {
case req.Signer != nil:
return req.Signer, nil
case len(req.SigningKeyPEM) != 0:
v, err := pemutil.ParseKey(req.SigningKeyPEM, opts...)
if err != nil {
return nil, err
}
sig, ok := v.(crypto.Signer)
if !ok {
return nil, errors.New("signingKeyPEM is not a crypto.Signer")
}
return sig, nil
case req.SigningKey != "":
v, err := pemutil.Read(req.SigningKey, opts...)
if err != nil {
return nil, err
}
sig, ok := v.(crypto.Signer)
if !ok {
return nil, errors.New("signingKey is not a crypto.Signer")
}
return sig, nil
default:
return nil, errors.New("failed to load softKMS: please define signingKeyPEM or signingKey")
}
}
func (k *SoftKMS) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) {
v, ok := signatureAlgorithmMapping[req.SignatureAlgorithm]
if !ok {
return nil, errors.Errorf("softKMS does not support signature algorithm '%s'", req.SignatureAlgorithm)
}
pub, priv, err := generateKey(v.Type, v.Curve, req.Bits)
if err != nil {
return nil, err
}
signer, ok := priv.(crypto.Signer)
if !ok {
return nil, errors.Errorf("softKMS createKey result is not a crypto.Signer: type %T", priv)
}
return &apiv1.CreateKeyResponse{
Name: req.Name,
PublicKey: pub,
PrivateKey: priv,
CreateSignerRequest: apiv1.CreateSignerRequest{
Signer: signer,
},
}, nil
}
func (k *SoftKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) {
v, err := pemutil.Read(req.Name)
if err != nil {
return nil, err
}
switch vv := v.(type) {
case *x509.Certificate:
return vv.PublicKey, nil
case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey:
return vv, nil
default:
return nil, errors.Errorf("unsupported public key type %T", v)
}
}

@ -0,0 +1,312 @@
package softkms
import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"reflect"
"testing"
"github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/cli/crypto/pemutil"
)
func TestNew(t *testing.T) {
type args struct {
ctx context.Context
opts apiv1.Options
}
tests := []struct {
name string
args args
want *SoftKMS
wantErr bool
}{
{"ok", args{context.Background(), apiv1.Options{}}, &SoftKMS{}, false},
}
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 TestSoftKMS_Close(t *testing.T) {
tests := []struct {
name string
wantErr bool
}{
{"ok", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
k := &SoftKMS{}
if err := k.Close(); (err != nil) != tt.wantErr {
t.Errorf("SoftKMS.Close() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestSoftKMS_CreateSigner(t *testing.T) {
pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
pemBlock, err := pemutil.Serialize(pk)
if err != nil {
t.Fatal(err)
}
pemBlockPassword, err := pemutil.Serialize(pk, pemutil.WithPassword([]byte("pass")))
if err != nil {
t.Fatal(err)
}
// Read and decode file using standard packages
b, err := ioutil.ReadFile("testdata/priv.pem")
if err != nil {
t.Fatal(err)
}
block, _ := pem.Decode(b)
block.Bytes, err = x509.DecryptPEMBlock(block, []byte("pass"))
if err != nil {
t.Fatal(err)
}
pk2, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
t.Fatal(err)
}
// Create a public PEM
b, err = x509.MarshalPKIXPublicKey(pk.Public())
if err != nil {
t.Fatal(err)
}
pub := pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: b,
})
type args struct {
req *apiv1.CreateSignerRequest
}
tests := []struct {
name string
args args
want crypto.Signer
wantErr bool
}{
{"signer", args{&apiv1.CreateSignerRequest{Signer: pk}}, pk, false},
{"pem", args{&apiv1.CreateSignerRequest{SigningKeyPEM: pem.EncodeToMemory(pemBlock)}}, pk, false},
{"pem password", args{&apiv1.CreateSignerRequest{SigningKeyPEM: pem.EncodeToMemory(pemBlockPassword), Password: []byte("pass")}}, pk, false},
{"file", args{&apiv1.CreateSignerRequest{SigningKey: "testdata/priv.pem", Password: []byte("pass")}}, pk2, false},
{"fail", args{&apiv1.CreateSignerRequest{}}, nil, true},
{"fail bad pem", args{&apiv1.CreateSignerRequest{SigningKeyPEM: []byte("bad pem")}}, nil, true},
{"fail bad password", args{&apiv1.CreateSignerRequest{SigningKey: "testdata/priv.pem", Password: []byte("bad-pass")}}, nil, true},
{"fail not a signer", args{&apiv1.CreateSignerRequest{SigningKeyPEM: pub}}, nil, true},
{"fail not a signer from file", args{&apiv1.CreateSignerRequest{SigningKey: "testdata/pub.pem"}}, nil, true},
{"fail missing", args{&apiv1.CreateSignerRequest{SigningKey: "testdata/missing"}}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
k := &SoftKMS{}
got, err := k.CreateSigner(tt.args.req)
if (err != nil) != tt.wantErr {
t.Errorf("SoftKMS.CreateSigner() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("SoftKMS.CreateSigner() = %v, want %v", got, tt.want)
}
})
}
}
func restoreGenerateKey() func() {
oldGenerateKey := generateKey
return func() {
generateKey = oldGenerateKey
}
}
func TestSoftKMS_CreateKey(t *testing.T) {
fn := restoreGenerateKey()
defer fn()
p256, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
rsa2048, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatal(err)
}
edpub, edpriv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatal(err)
}
type args struct {
req *apiv1.CreateKeyRequest
}
type params struct {
kty string
crv string
size int
}
tests := []struct {
name string
args args
generateKey func() (interface{}, interface{}, error)
want *apiv1.CreateKeyResponse
wantParams params
wantErr bool
}{
{"p256", args{&apiv1.CreateKeyRequest{Name: "p256", SignatureAlgorithm: apiv1.ECDSAWithSHA256}}, func() (interface{}, interface{}, error) {
return p256.Public(), p256, nil
}, &apiv1.CreateKeyResponse{Name: "p256", PublicKey: p256.Public(), PrivateKey: p256, CreateSignerRequest: apiv1.CreateSignerRequest{Signer: p256}}, params{"EC", "P-256", 0}, false},
{"rsa", args{&apiv1.CreateKeyRequest{Name: "rsa3072", SignatureAlgorithm: apiv1.SHA256WithRSA}}, func() (interface{}, interface{}, error) {
return rsa2048.Public(), rsa2048, nil
}, &apiv1.CreateKeyResponse{Name: "rsa3072", PublicKey: rsa2048.Public(), PrivateKey: rsa2048, CreateSignerRequest: apiv1.CreateSignerRequest{Signer: rsa2048}}, params{"RSA", "", 0}, false},
{"rsa2048", args{&apiv1.CreateKeyRequest{Name: "rsa2048", SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 2048}}, func() (interface{}, interface{}, error) {
return rsa2048.Public(), rsa2048, nil
}, &apiv1.CreateKeyResponse{Name: "rsa2048", PublicKey: rsa2048.Public(), PrivateKey: rsa2048, CreateSignerRequest: apiv1.CreateSignerRequest{Signer: rsa2048}}, params{"RSA", "", 2048}, false},
{"rsaPSS2048", args{&apiv1.CreateKeyRequest{Name: "rsa2048", SignatureAlgorithm: apiv1.SHA256WithRSAPSS, Bits: 2048}}, func() (interface{}, interface{}, error) {
return rsa2048.Public(), rsa2048, nil
}, &apiv1.CreateKeyResponse{Name: "rsa2048", PublicKey: rsa2048.Public(), PrivateKey: rsa2048, CreateSignerRequest: apiv1.CreateSignerRequest{Signer: rsa2048}}, params{"RSA", "", 2048}, false},
{"ed25519", args{&apiv1.CreateKeyRequest{Name: "ed25519", SignatureAlgorithm: apiv1.PureEd25519}}, func() (interface{}, interface{}, error) {
return edpub, edpriv, nil
}, &apiv1.CreateKeyResponse{Name: "ed25519", PublicKey: edpub, PrivateKey: edpriv, CreateSignerRequest: apiv1.CreateSignerRequest{Signer: edpriv}}, params{"OKP", "Ed25519", 0}, false},
{"default", args{&apiv1.CreateKeyRequest{Name: "default"}}, func() (interface{}, interface{}, error) {
return p256.Public(), p256, nil
}, &apiv1.CreateKeyResponse{Name: "default", PublicKey: p256.Public(), PrivateKey: p256, CreateSignerRequest: apiv1.CreateSignerRequest{Signer: p256}}, params{"EC", "P-256", 0}, false},
{"fail algorithm", args{&apiv1.CreateKeyRequest{Name: "fail", SignatureAlgorithm: apiv1.SignatureAlgorithm(100)}}, func() (interface{}, interface{}, error) {
return p256.Public(), p256, nil
}, nil, params{}, true},
{"fail generate key", args{&apiv1.CreateKeyRequest{Name: "fail", SignatureAlgorithm: apiv1.ECDSAWithSHA256}}, func() (interface{}, interface{}, error) {
return nil, nil, fmt.Errorf("an error")
}, nil, params{"EC", "P-256", 0}, true},
{"fail no signer", args{&apiv1.CreateKeyRequest{Name: "fail", SignatureAlgorithm: apiv1.ECDSAWithSHA256}}, func() (interface{}, interface{}, error) {
return 1, 2, nil
}, nil, params{"EC", "P-256", 0}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
k := &SoftKMS{}
generateKey = func(kty, crv string, size int) (interface{}, interface{}, error) {
if tt.wantParams.kty != kty {
t.Errorf("GenerateKey() kty = %s, want %s", kty, tt.wantParams.kty)
}
if tt.wantParams.crv != crv {
t.Errorf("GenerateKey() crv = %s, want %s", crv, tt.wantParams.crv)
}
if tt.wantParams.size != size {
t.Errorf("GenerateKey() size = %d, want %d", size, tt.wantParams.size)
}
return tt.generateKey()
}
got, err := k.CreateKey(tt.args.req)
if (err != nil) != tt.wantErr {
t.Errorf("SoftKMS.CreateKey() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("SoftKMS.CreateKey() = %v, want %v", got, tt.want)
}
})
}
}
func TestSoftKMS_GetPublicKey(t *testing.T) {
b, err := ioutil.ReadFile("testdata/pub.pem")
if err != nil {
t.Fatal(err)
}
block, _ := pem.Decode(b)
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
t.Fatal(err)
}
type args struct {
req *apiv1.GetPublicKeyRequest
}
tests := []struct {
name string
args args
want crypto.PublicKey
wantErr bool
}{
{"key", args{&apiv1.GetPublicKeyRequest{Name: "testdata/pub.pem"}}, pub, false},
{"cert", args{&apiv1.GetPublicKeyRequest{Name: "testdata/cert.crt"}}, pub, false},
{"fail not exists", args{&apiv1.GetPublicKeyRequest{Name: "testdata/missing"}}, nil, true},
{"fail type", args{&apiv1.GetPublicKeyRequest{Name: "testdata/cert.key"}}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
k := &SoftKMS{}
got, err := k.GetPublicKey(tt.args.req)
if (err != nil) != tt.wantErr {
t.Errorf("SoftKMS.GetPublicKey() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("SoftKMS.GetPublicKey() = %v, want %v", got, tt.want)
}
})
}
}
func Test_generateKey(t *testing.T) {
type args struct {
kty string
crv string
size int
}
tests := []struct {
name string
args args
wantType interface{}
wantType1 interface{}
wantErr bool
}{
{"rsa2048", args{"RSA", "", 0}, &rsa.PublicKey{}, &rsa.PrivateKey{}, false},
{"rsa2048", args{"RSA", "", 2048}, &rsa.PublicKey{}, &rsa.PrivateKey{}, false},
{"p256", args{"EC", "P-256", 0}, &ecdsa.PublicKey{}, &ecdsa.PrivateKey{}, false},
{"ed25519", args{"OKP", "Ed25519", 0}, ed25519.PublicKey{}, ed25519.PrivateKey{}, false},
{"fail kty", args{"FOO", "", 0}, nil, nil, true},
{"fail crv", args{"EC", "P-123", 0}, nil, nil, true},
{"fail size", args{"RSA", "", 1}, nil, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, got1, err := generateKey(tt.args.kty, tt.args.crv, tt.args.size)
if (err != nil) != tt.wantErr {
t.Errorf("generateKey() error = %v, wantErr %v", err, tt.wantErr)
return
}
if reflect.TypeOf(got) != reflect.TypeOf(tt.wantType) {
t.Errorf("generateKey() got = %T, want %T", got, tt.wantType)
}
if reflect.TypeOf(got1) != reflect.TypeOf(tt.wantType1) {
t.Errorf("generateKey() got1 = %T, want %T", got1, tt.wantType1)
}
})
}
}

@ -0,0 +1,11 @@
-----BEGIN CERTIFICATE-----
MIIBpzCCAU2gAwIBAgIQWaY8KIDAfak8aYljelf8eTAKBggqhkjOPQQDAjAdMRsw
GQYDVQQDExJ0ZXN0LnNtYWxsc3RlcC5jb20wHhcNMjAwMTE2MDAwNDU4WhcNMjAw
MTE3MDAwNDU4WjAdMRswGQYDVQQDExJ0ZXN0LnNtYWxsc3RlcC5jb20wWTATBgcq
hkjOPQIBBggqhkjOPQMBBwNCAATlU8P9blFefSWuzYx2g215NJn6yHW95PXeFqQ9
kX1jNo1VmC6Oord3We37iM8QJT4QP9ZDUaAVmJUZSjd+W8H/o28wbTAOBgNVHQ8B
Af8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQW
BBTn0wonKkm2lLRNYZrKhUukiynvqzAdBgNVHREEFjAUghJ0ZXN0LnNtYWxsc3Rl
cC5jb20wCgYIKoZIzj0EAwIDSAAwRQIhAJ5XqryBIY1X4fl/9l0isV69eQfA0Qo5
1mjervUcEnOWAiBsmN4frz5YVw7i4UXChVBeZLZfJOKvn5eyh2gEzoq1+w==
-----END CERTIFICATE-----

@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEICB6lIrMa9fVQJtdAYS4qmdYQ1BHJsEQDx8zxL38gA8toAoGCCqGSM49
AwEHoUQDQgAE5VPD/W5RXn0lrs2MdoNteTSZ+sh1veT13hakPZF9YzaNVZgujqK3
d1nt+4jPECU+ED/WQ1GgFZiVGUo3flvB/w==
-----END EC PRIVATE KEY-----

@ -0,0 +1,8 @@
-----BEGIN EC PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,1fcec5dfbf3327f61bfe5ab6ae8a0626
V39b/pNHMbP80TXSHLsUY6UOTCzf3KwIxvj1e7S9brNMJJc9b3UiloMBJIYBkl00
NKI8JU4jSlcerR58DqsTHIELiX6a+RJLe3/iR2/5Gru+CmmWJ68jQu872WCgh6Ms
o8TzhyGx74ETmdKn5CdtylsnKMa9heW3tBLFAbNCgKc=
-----END EC PRIVATE KEY-----

@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5VPD/W5RXn0lrs2MdoNteTSZ+sh1
veT13hakPZF9YzaNVZgujqK3d1nt+4jPECU+ED/WQ1GgFZiVGUo3flvB/w==
-----END PUBLIC KEY-----
Loading…
Cancel
Save