forked from TrueCloudLab/certificates
commit
cf7ef472f7
26 changed files with 2111 additions and 100 deletions
|
@ -12,10 +12,11 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/smallstep/certificates/authority/provisioner"
|
"github.com/smallstep/certificates/authority/provisioner"
|
||||||
"github.com/smallstep/certificates/db"
|
"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/sshutil"
|
||||||
"github.com/smallstep/certificates/templates"
|
"github.com/smallstep/certificates/templates"
|
||||||
"github.com/smallstep/cli/crypto/pemutil"
|
"github.com/smallstep/cli/crypto/pemutil"
|
||||||
"github.com/smallstep/cli/crypto/x509util"
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,21 +26,30 @@ const (
|
||||||
|
|
||||||
// Authority implements the Certificate Authority internal interface.
|
// Authority implements the Certificate Authority internal interface.
|
||||||
type Authority struct {
|
type Authority struct {
|
||||||
config *Config
|
config *Config
|
||||||
rootX509Certs []*x509.Certificate
|
keyManager kms.KeyManager
|
||||||
intermediateIdentity *x509util.Identity
|
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
|
sshCAUserCertSignKey ssh.Signer
|
||||||
sshCAHostCertSignKey ssh.Signer
|
sshCAHostCertSignKey ssh.Signer
|
||||||
sshCAUserCerts []ssh.PublicKey
|
sshCAUserCerts []ssh.PublicKey
|
||||||
sshCAHostCerts []ssh.PublicKey
|
sshCAHostCerts []ssh.PublicKey
|
||||||
sshCAUserFederatedCerts []ssh.PublicKey
|
sshCAUserFederatedCerts []ssh.PublicKey
|
||||||
sshCAHostFederatedCerts []ssh.PublicKey
|
sshCAHostFederatedCerts []ssh.PublicKey
|
||||||
certificates *sync.Map
|
|
||||||
startTime time.Time
|
|
||||||
provisioners *provisioner.Collection
|
|
||||||
db db.AuthDB
|
|
||||||
// Do not re-initialize
|
// Do not re-initialize
|
||||||
initOnce bool
|
initOnce bool
|
||||||
|
startTime time.Time
|
||||||
|
|
||||||
// Custom functions
|
// Custom functions
|
||||||
sshBastionFunc func(user, hostname string) (*Bastion, error)
|
sshBastionFunc func(user, hostname string) (*Bastion, error)
|
||||||
sshCheckHostFunc func(ctx context.Context, principal string, tok string, roots []*x509.Certificate) (bool, 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),
|
certificates: new(sync.Map),
|
||||||
provisioners: provisioner.NewCollection(config.getAudiences()),
|
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 {
|
if err := a.init(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,6 +93,19 @@ func (a *Authority) init() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
var err 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.
|
// 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.config.DB is nil then a simple, barebones in memory DB will be used.
|
||||||
if a.db == nil {
|
if a.db == nil {
|
||||||
|
@ -84,50 +114,62 @@ func (a *Authority) init() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the root certificates and add them to the certificate store
|
// Read root certificates and store them in the certificates map.
|
||||||
a.rootX509Certs = make([]*x509.Certificate, len(a.config.Root))
|
if len(a.rootX509Certs) == 0 {
|
||||||
for i, path := range a.config.Root {
|
a.rootX509Certs = make([]*x509.Certificate, len(a.config.Root))
|
||||||
crt, err := pemutil.ReadCertificate(path)
|
for i, path := range a.config.Root {
|
||||||
if err != nil {
|
crt, err := pemutil.ReadCertificate(path)
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.rootX509Certs[i] = crt
|
||||||
}
|
}
|
||||||
// Add root certificate to the certificate map
|
|
||||||
sum := sha256.Sum256(crt.Raw)
|
|
||||||
a.certificates.Store(hex.EncodeToString(sum[:]), crt)
|
|
||||||
a.rootX509Certs[i] = crt
|
|
||||||
}
|
}
|
||||||
|
for _, crt := range a.rootX509Certs {
|
||||||
// Add federated roots
|
|
||||||
for _, path := range a.config.FederatedRoots {
|
|
||||||
crt, err := pemutil.ReadCertificate(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sum := sha256.Sum256(crt.Raw)
|
sum := sha256.Sum256(crt.Raw)
|
||||||
a.certificates.Store(hex.EncodeToString(sum[:]), crt)
|
a.certificates.Store(hex.EncodeToString(sum[:]), crt)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrypt and load intermediate public / private key pair.
|
// Read federated certificates and store them in the certificates map.
|
||||||
if len(a.config.Password) > 0 {
|
if len(a.federatedX509Certs) == 0 {
|
||||||
a.intermediateIdentity, err = x509util.LoadIdentityFromDisk(
|
a.federatedX509Certs = make([]*x509.Certificate, len(a.config.FederatedRoots))
|
||||||
a.config.IntermediateCert,
|
for i, path := range a.config.FederatedRoots {
|
||||||
a.config.IntermediateKey,
|
crt, err := pemutil.ReadCertificate(path)
|
||||||
pemutil.WithPassword([]byte(a.config.Password)),
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read intermediate and create X509 signer.
|
||||||
|
if a.x509Signer == nil {
|
||||||
|
crt, err := pemutil.ReadCertificate(a.config.IntermediateCert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
signer, err := a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
|
||||||
a.intermediateIdentity, err = x509util.LoadIdentityFromDisk(a.config.IntermediateCert, a.config.IntermediateKey)
|
SigningKey: a.config.IntermediateKey,
|
||||||
|
Password: []byte(a.config.Password),
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
a.x509Signer = signer
|
||||||
|
a.x509Issuer = crt
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrypt and load SSH keys
|
// Decrypt and load SSH keys
|
||||||
if a.config.SSH != nil {
|
if a.config.SSH != nil {
|
||||||
if a.config.SSH.HostKey != "" {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -140,7 +182,10 @@ func (a *Authority) init() error {
|
||||||
a.sshCAHostFederatedCerts = append(a.sshCAHostFederatedCerts, a.sshCAHostCertSignKey.PublicKey())
|
a.sshCAHostFederatedCerts = append(a.sshCAHostFederatedCerts, a.sshCAHostCertSignKey.PublicKey())
|
||||||
}
|
}
|
||||||
if a.config.SSH.UserKey != "" {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -245,19 +290,3 @@ func (a *Authority) GetDatabase() db.AuthDB {
|
||||||
func (a *Authority) Shutdown() error {
|
func (a *Authority) Shutdown() error {
|
||||||
return a.db.Shutdown()
|
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.Equals(t, auth.rootX509Certs[0], root)
|
||||||
|
|
||||||
assert.True(t, auth.initOnce)
|
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 {
|
for _, p := range tc.config.AuthorityConfig.Provisioners {
|
||||||
var _p provisioner.Interface
|
var _p provisioner.Interface
|
||||||
_p, ok = auth.provisioners.Load(p.GetID())
|
_p, ok = auth.provisioners.Load(p.GetID())
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/smallstep/certificates/authority/provisioner"
|
"github.com/smallstep/certificates/authority/provisioner"
|
||||||
"github.com/smallstep/certificates/db"
|
"github.com/smallstep/certificates/db"
|
||||||
|
kms "github.com/smallstep/certificates/kms/apiv1"
|
||||||
"github.com/smallstep/certificates/templates"
|
"github.com/smallstep/certificates/templates"
|
||||||
"github.com/smallstep/cli/crypto/tlsutil"
|
"github.com/smallstep/cli/crypto/tlsutil"
|
||||||
"github.com/smallstep/cli/crypto/x509util"
|
"github.com/smallstep/cli/crypto/x509util"
|
||||||
|
@ -54,6 +55,7 @@ type Config struct {
|
||||||
IntermediateKey string `json:"key"`
|
IntermediateKey string `json:"key"`
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
DNSNames []string `json:"dnsNames"`
|
DNSNames []string `json:"dnsNames"`
|
||||||
|
KMS *kms.Options `json:"kms,omitempty"`
|
||||||
SSH *SSHConfig `json:"ssh,omitempty"`
|
SSH *SSHConfig `json:"ssh,omitempty"`
|
||||||
Logger json.RawMessage `json:"logger,omitempty"`
|
Logger json.RawMessage `json:"logger,omitempty"`
|
||||||
DB *db.Config `json:"db,omitempty"`
|
DB *db.Config `json:"db,omitempty"`
|
||||||
|
@ -179,6 +181,11 @@ func (c *Config) Validate() error {
|
||||||
c.TLS.Renegotiation = c.TLS.Renegotiation || DefaultTLSOptions.Renegotiation
|
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
|
// Validate ssh: nil is ok
|
||||||
if err := c.SSH.Validate(); err != nil {
|
if err := c.SSH.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -2,45 +2,54 @@ package authority
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/smallstep/certificates/authority/provisioner"
|
"github.com/smallstep/certificates/authority/provisioner"
|
||||||
"github.com/smallstep/certificates/db"
|
"github.com/smallstep/certificates/db"
|
||||||
|
"github.com/smallstep/certificates/kms"
|
||||||
"github.com/smallstep/certificates/sshutil"
|
"github.com/smallstep/certificates/sshutil"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Option sets options to the Authority.
|
// 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
|
// WithDatabase sets an already initialized authority database to a new
|
||||||
// authority. This option is intended to be use on graceful reloads.
|
// authority. This option is intended to be use on graceful reloads.
|
||||||
func WithDatabase(db db.AuthDB) Option {
|
func WithDatabase(db db.AuthDB) Option {
|
||||||
return func(a *Authority) {
|
return func(a *Authority) error {
|
||||||
a.db = db
|
a.db = db
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithGetIdentityFunc sets a custom function to retrieve the identity from
|
// WithGetIdentityFunc sets a custom function to retrieve the identity from
|
||||||
// an external resource.
|
// an external resource.
|
||||||
func WithGetIdentityFunc(fn func(p provisioner.Interface, email string) (*provisioner.Identity, error)) Option {
|
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
|
a.getIdentityFunc = fn
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithSSHBastionFunc sets a custom function to get the bastion for a
|
// WithSSHBastionFunc sets a custom function to get the bastion for a
|
||||||
// given user-host pair.
|
// given user-host pair.
|
||||||
func WithSSHBastionFunc(fn func(user, host string) (*Bastion, error)) Option {
|
func WithSSHBastionFunc(fn func(user, host string) (*Bastion, error)) Option {
|
||||||
return func(a *Authority) {
|
return func(a *Authority) error {
|
||||||
a.sshBastionFunc = fn
|
a.sshBastionFunc = fn
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithSSHGetHosts sets a custom function to get the bastion for a
|
// WithSSHGetHosts sets a custom function to get the bastion for a
|
||||||
// given user-host pair.
|
// given user-host pair.
|
||||||
func WithSSHGetHosts(fn func(cert *x509.Certificate) ([]sshutil.Host, error)) Option {
|
func WithSSHGetHosts(fn func(cert *x509.Certificate) ([]sshutil.Host, error)) Option {
|
||||||
return func(a *Authority) {
|
return func(a *Authority) error {
|
||||||
a.sshGetHostsFunc = fn
|
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
|
// step ssh enabled. The token is used to validate the request, while the roots
|
||||||
// are used to validate the token.
|
// are used to validate the token.
|
||||||
func WithSSHCheckHost(fn func(ctx context.Context, principal string, tok string, roots []*x509.Certificate) (bool, error)) Option {
|
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
|
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)}
|
opts = []interface{}{errs.WithKeyVal("csr", csr), errs.WithKeyVal("signOptions", signOpts)}
|
||||||
mods = []x509util.WithOption{withDefaultASN1DN(a.config.AuthorityConfig.Template)}
|
mods = []x509util.WithOption{withDefaultASN1DN(a.config.AuthorityConfig.Template)}
|
||||||
certValidators = []provisioner.CertificateValidator{}
|
certValidators = []provisioner.CertificateValidator{}
|
||||||
issIdentity = a.intermediateIdentity
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Set backdate with the configured value
|
// 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...)
|
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 {
|
if err != nil {
|
||||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign", opts...)
|
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...)
|
"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 = a.db.StoreCertificate(serverCert); err != nil {
|
||||||
if err != db.ErrNotImplemented {
|
if err != db.ErrNotImplemented {
|
||||||
return nil, errs.Wrap(http.StatusInternalServerError, err,
|
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
|
// 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...)
|
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Renew", opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issuer
|
|
||||||
issIdentity := a.intermediateIdentity
|
|
||||||
|
|
||||||
// Durations
|
// Durations
|
||||||
backdate := a.config.AuthorityConfig.Backdate.Duration
|
backdate := a.config.AuthorityConfig.Backdate.Duration
|
||||||
duration := oldCert.NotAfter.Sub(oldCert.NotBefore)
|
duration := oldCert.NotAfter.Sub(oldCert.NotBefore)
|
||||||
|
@ -148,7 +138,7 @@ func (a *Authority) Renew(oldCert *x509.Certificate) ([]*x509.Certificate, error
|
||||||
|
|
||||||
newCert := &x509.Certificate{
|
newCert := &x509.Certificate{
|
||||||
PublicKey: oldCert.PublicKey,
|
PublicKey: oldCert.PublicKey,
|
||||||
Issuer: issIdentity.Crt.Subject,
|
Issuer: a.x509Issuer.Subject,
|
||||||
Subject: oldCert.Subject,
|
Subject: oldCert.Subject,
|
||||||
NotBefore: now.Add(-1 * backdate),
|
NotBefore: now.Add(-1 * backdate),
|
||||||
NotAfter: now.Add(duration - 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,
|
leaf, err := x509util.NewLeafProfileWithTemplate(newCert, a.x509Issuer, a.x509Signer)
|
||||||
issIdentity.Crt, issIdentity.Key)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Renew", opts...)
|
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,
|
return nil, errs.Wrap(http.StatusInternalServerError, err,
|
||||||
"authority.Renew; error parsing new server certificate", opts...)
|
"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 = a.db.StoreCertificate(serverCert); err != nil {
|
||||||
if err != db.ErrNotImplemented {
|
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.
|
// 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.
|
// GetTLSCertificate creates a new leaf certificate to be used by the CA HTTPS server.
|
||||||
func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) {
|
func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) {
|
||||||
profile, err := x509util.NewLeafProfile("Step Online CA",
|
profile, err := x509util.NewLeafProfile("Step Online CA", a.x509Issuer, a.x509Signer,
|
||||||
a.intermediateIdentity.Crt, a.intermediateIdentity.Key,
|
|
||||||
x509util.WithHosts(strings.Join(a.config.DNSNames, ",")))
|
x509util.WithHosts(strings.Join(a.config.DNSNames, ",")))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate")
|
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)
|
// Load the x509 key pair (combining server and intermediate blocks)
|
||||||
// to a tls.Certificate.
|
// to a tls.Certificate.
|
||||||
intermediatePEM, err := pemutil.Serialize(a.intermediateIdentity.Crt)
|
intermediatePEM, err := pemutil.Serialize(a.x509Issuer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate")
|
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate")
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package authority
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
@ -156,7 +157,7 @@ func TestAuthority_Sign(t *testing.T) {
|
||||||
},
|
},
|
||||||
"fail create cert": func(t *testing.T) *signTest {
|
"fail create cert": func(t *testing.T) *signTest {
|
||||||
_a := testAuthority(t)
|
_a := testAuthority(t)
|
||||||
_a.intermediateIdentity.Key = nil
|
_a.x509Signer = nil
|
||||||
csr := getCSR(t, priv)
|
csr := getCSR(t, priv)
|
||||||
return &signTest{
|
return &signTest{
|
||||||
auth: _a,
|
auth: _a,
|
||||||
|
@ -303,7 +304,7 @@ ZYtQ9Ot36qc=
|
||||||
hash := sha1.Sum(pubBytes)
|
hash := sha1.Sum(pubBytes)
|
||||||
assert.Equals(t, leaf.SubjectKeyId, hash[:])
|
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
|
// Verify Provisioner OID
|
||||||
found := 0
|
found := 0
|
||||||
|
@ -322,7 +323,7 @@ ZYtQ9Ot36qc=
|
||||||
}
|
}
|
||||||
assert.Equals(t, found, 1)
|
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.FatalError(t, err)
|
||||||
assert.Equals(t, intermediate, realIntermediate)
|
assert.Equals(t, intermediate, realIntermediate)
|
||||||
}
|
}
|
||||||
|
@ -353,8 +354,7 @@ func TestAuthority_Renew(t *testing.T) {
|
||||||
NotAfter: provisioner.NewTimeDuration(na1),
|
NotAfter: provisioner.NewTimeDuration(na1),
|
||||||
}
|
}
|
||||||
|
|
||||||
leaf, err := x509util.NewLeafProfile("renew", a.intermediateIdentity.Crt,
|
leaf, err := x509util.NewLeafProfile("renew", a.x509Issuer, a.x509Signer,
|
||||||
a.intermediateIdentity.Key,
|
|
||||||
x509util.WithNotBeforeAfterDuration(so.NotBefore.Time(), so.NotAfter.Time(), 0),
|
x509util.WithNotBeforeAfterDuration(so.NotBefore.Time(), so.NotAfter.Time(), 0),
|
||||||
withDefaultASN1DN(a.config.AuthorityConfig.Template),
|
withDefaultASN1DN(a.config.AuthorityConfig.Template),
|
||||||
x509util.WithPublicKey(pub), x509util.WithHosts("test.smallstep.com,test"),
|
x509util.WithPublicKey(pub), x509util.WithHosts("test.smallstep.com,test"),
|
||||||
|
@ -365,8 +365,7 @@ func TestAuthority_Renew(t *testing.T) {
|
||||||
cert, err := x509.ParseCertificate(certBytes)
|
cert, err := x509.ParseCertificate(certBytes)
|
||||||
assert.FatalError(t, err)
|
assert.FatalError(t, err)
|
||||||
|
|
||||||
leafNoRenew, err := x509util.NewLeafProfile("norenew", a.intermediateIdentity.Crt,
|
leafNoRenew, err := x509util.NewLeafProfile("norenew", a.x509Issuer, a.x509Signer,
|
||||||
a.intermediateIdentity.Key,
|
|
||||||
x509util.WithNotBeforeAfterDuration(so.NotBefore.Time(), so.NotAfter.Time(), 0),
|
x509util.WithNotBeforeAfterDuration(so.NotBefore.Time(), so.NotAfter.Time(), 0),
|
||||||
withDefaultASN1DN(a.config.AuthorityConfig.Template),
|
withDefaultASN1DN(a.config.AuthorityConfig.Template),
|
||||||
x509util.WithPublicKey(pub), x509util.WithHosts("test.smallstep.com,test"),
|
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){
|
tests := map[string]func() (*renewTest, error){
|
||||||
"fail-create-cert": func() (*renewTest, error) {
|
"fail-create-cert": func() (*renewTest, error) {
|
||||||
_a := testAuthority(t)
|
_a := testAuthority(t)
|
||||||
_a.intermediateIdentity.Key = nil
|
_a.x509Signer = nil
|
||||||
return &renewTest{
|
return &renewTest{
|
||||||
auth: _a,
|
auth: _a,
|
||||||
cert: cert,
|
cert: cert,
|
||||||
|
@ -425,8 +424,8 @@ func TestAuthority_Renew(t *testing.T) {
|
||||||
assert.FatalError(t, err)
|
assert.FatalError(t, err)
|
||||||
|
|
||||||
_a := testAuthority(t)
|
_a := testAuthority(t)
|
||||||
_a.intermediateIdentity.Key = newIntermediateProfile.SubjectPrivateKey()
|
_a.x509Signer = newIntermediateProfile.SubjectPrivateKey().(crypto.Signer)
|
||||||
_a.intermediateIdentity.Crt = newIntermediateCert
|
_a.x509Issuer = newIntermediateCert
|
||||||
return &renewTest{
|
return &renewTest{
|
||||||
auth: _a,
|
auth: _a,
|
||||||
cert: cert,
|
cert: cert,
|
||||||
|
@ -494,8 +493,8 @@ func TestAuthority_Renew(t *testing.T) {
|
||||||
assert.Equals(t, leaf.SubjectKeyId, hash[:])
|
assert.Equals(t, leaf.SubjectKeyId, hash[:])
|
||||||
|
|
||||||
// We did not change the intermediate before renewing.
|
// We did not change the intermediate before renewing.
|
||||||
if a.intermediateIdentity.Crt.SerialNumber == tc.auth.intermediateIdentity.Crt.SerialNumber {
|
if a.x509Issuer.SerialNumber == tc.auth.x509Issuer.SerialNumber {
|
||||||
assert.Equals(t, leaf.AuthorityKeyId, a.intermediateIdentity.Crt.SubjectKeyId)
|
assert.Equals(t, leaf.AuthorityKeyId, a.x509Issuer.SubjectKeyId)
|
||||||
// Compare extensions: they can be in a different order
|
// Compare extensions: they can be in a different order
|
||||||
for _, ext1 := range tc.cert.Extensions {
|
for _, ext1 := range tc.cert.Extensions {
|
||||||
found := false
|
found := false
|
||||||
|
@ -511,7 +510,7 @@ func TestAuthority_Renew(t *testing.T) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// We did change the intermediate before renewing.
|
// 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
|
// Compare extensions: they can be in a different order
|
||||||
for _, ext1 := range tc.cert.Extensions {
|
for _, ext1 := range tc.cert.Extensions {
|
||||||
// The authority key id extension should be different b/c the intermediates are different.
|
// 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.FatalError(t, err)
|
||||||
assert.Equals(t, intermediate, realIntermediate)
|
assert.Equals(t, intermediate, realIntermediate)
|
||||||
}
|
}
|
||||||
|
|
6
go.mod
6
go.mod
|
@ -3,8 +3,11 @@ module github.com/smallstep/certificates
|
||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
cloud.google.com/go v0.51.0
|
||||||
github.com/Masterminds/sprig/v3 v3.0.0
|
github.com/Masterminds/sprig/v3 v3.0.0
|
||||||
github.com/go-chi/chi v4.0.2+incompatible
|
github.com/go-chi/chi v4.0.2+incompatible
|
||||||
|
github.com/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/newrelic/go-agent v2.15.0+incompatible
|
||||||
github.com/pkg/errors v0.8.1
|
github.com/pkg/errors v0.8.1
|
||||||
github.com/rs/xid v1.2.1
|
github.com/rs/xid v1.2.1
|
||||||
|
@ -15,6 +18,9 @@ require (
|
||||||
github.com/urfave/cli v1.22.2
|
github.com/urfave/cli v1.22.2
|
||||||
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876
|
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876
|
||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553
|
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
|
gopkg.in/square/go-jose.v2 v2.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
68
go.sum
68
go.sum
|
@ -1,12 +1,24 @@
|
||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
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.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.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/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=
|
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 h1:HD8gA2tkByhMAwYaFAX9w2l7vxvBQ5NMoxDrkhqhtn4=
|
||||||
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
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 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic=
|
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 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
|
||||||
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||||
|
@ -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.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 h1:sXD3pix0wDemuPuSlrXpJNNYXlUiKiysLrtPVQmxkzI=
|
||||||
github.com/go-critic/go-critic v0.4.0/go.mod h1:7/14rZGnZbY6E38VEGk2kVhoq6itzc1E68facVDK23g=
|
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.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-kit/kit v0.9.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=
|
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.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 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
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/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-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/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.0.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.1.1/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.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.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.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/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/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-20190606152607-4b11a32b5934/go.mod h1:6NTfaQoUpg5QmPsCUWLR3ig33FHrKXhTtWzF0DVdmuk=
|
||||||
github.com/google/monologue v0.0.0-20191220140058-35abc9683a6c/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-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/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 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=
|
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 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
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.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/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.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
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/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 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0=
|
||||||
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
|
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/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 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
|
||||||
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
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.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/json-iterator/go v1.1.9/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.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/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 h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
|
||||||
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
|
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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
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/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.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.2.0/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/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
|
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/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/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 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
|
||||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||||
|
@ -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.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.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
|
go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs=
|
||||||
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
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.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
go.uber.org/atomic v1.5.1/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-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-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-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 h1:0hQKqeLdqlt5iIwVOBErRisrHJAN57yOiPRQItI20fU=
|
||||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
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=
|
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 h1:sKJQZMuxjOAR/Uo2LBfU90onWEf1dF4C+0hPJCc9Mpc=
|
||||||
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
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-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-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-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-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-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-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-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/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.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/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-20170915142106-8351a756f30f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/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-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-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-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-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 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
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-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-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-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-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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/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-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-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-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-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 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-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 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
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-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 h1:LwyF2AFISC9nVbS6MgzsaQNSUsRXI49GS+YQ5KX/QH0=
|
||||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
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=
|
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-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-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-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-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-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/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-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-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-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-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-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-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-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-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-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-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-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-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-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-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 h1:MR2F33ipDGog0C4eMhU6u9o3q6c3dvYis2aG6Jl12Wg=
|
||||||
golang.org/x/tools v0.0.0-20200106190116-7be0a674c9fc/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
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-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-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.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
google.golang.org/api v0.5.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.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.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.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 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
|
||||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
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-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-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-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-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-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-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-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-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/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.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
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.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
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.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=
|
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/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=
|
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-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 h1:K7wru2CfJGumS5hkiguQ0Rb9ebKM2Jo8s5d4Jm9lFaM=
|
||||||
mvdan.cc/unparam v0.0.0-20191111180625-960b1ec0f2c2/go.mod h1:rCqoQrfAmpTX/h2APczwM7UymU/uvaOluiVPIYCSY/k=
|
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=
|
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 v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||||
sourcegraph.com/sqs/pbtypes v1.0.0 h1:f7lAwqviDEGvON4kRv0o5V7FT/IQK+tbkF664XMbP3o=
|
sourcegraph.com/sqs/pbtypes v1.0.0 h1:f7lAwqviDEGvON4kRv0o5V7FT/IQK+tbkF664XMbP3o=
|
||||||
|
|
68
kms/apiv1/options.go
Normal file
68
kms/apiv1/options.go
Normal file
|
@ -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
|
||||||
|
}
|
51
kms/apiv1/options_test.go
Normal file
51
kms/apiv1/options_test.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
126
kms/apiv1/requests.go
Normal file
126
kms/apiv1/requests.go
Normal file
|
@ -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
|
||||||
|
}
|
51
kms/apiv1/requests_test.go
Normal file
51
kms/apiv1/requests_test.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
280
kms/cloudkms/cloudkms.go
Normal file
280
kms/cloudkms/cloudkms.go
Normal file
|
@ -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:]
|
||||||
|
}
|
||||||
|
}
|
376
kms/cloudkms/cloudkms_test.go
Normal file
376
kms/cloudkms/cloudkms_test.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
46
kms/cloudkms/mock_test.go
Normal file
46
kms/cloudkms/mock_test.go
Normal file
|
@ -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...)
|
||||||
|
}
|
78
kms/cloudkms/signer.go
Normal file
78
kms/cloudkms/signer.go
Normal file
|
@ -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
|
||||||
|
}
|
148
kms/cloudkms/signer_test.go
Normal file
148
kms/cloudkms/signer_test.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
4
kms/cloudkms/testdata/pub.pem
vendored
Normal file
4
kms/cloudkms/testdata/pub.pem
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5VPD/W5RXn0lrs2MdoNteTSZ+sh1
|
||||||
|
veT13hakPZF9YzaNVZgujqK3d1nt+4jPECU+ED/WQ1GgFZiVGUo3flvB/w==
|
||||||
|
-----END PUBLIC KEY-----
|
36
kms/kms.go
Normal file
36
kms/kms.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
51
kms/kms_test.go
Normal file
51
kms/kms_test.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
134
kms/softkms/softkms.go
Normal file
134
kms/softkms/softkms.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
312
kms/softkms/softkms_test.go
Normal file
312
kms/softkms/softkms_test.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
11
kms/softkms/testdata/cert.crt
vendored
Normal file
11
kms/softkms/testdata/cert.crt
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBpzCCAU2gAwIBAgIQWaY8KIDAfak8aYljelf8eTAKBggqhkjOPQQDAjAdMRsw
|
||||||
|
GQYDVQQDExJ0ZXN0LnNtYWxsc3RlcC5jb20wHhcNMjAwMTE2MDAwNDU4WhcNMjAw
|
||||||
|
MTE3MDAwNDU4WjAdMRswGQYDVQQDExJ0ZXN0LnNtYWxsc3RlcC5jb20wWTATBgcq
|
||||||
|
hkjOPQIBBggqhkjOPQMBBwNCAATlU8P9blFefSWuzYx2g215NJn6yHW95PXeFqQ9
|
||||||
|
kX1jNo1VmC6Oord3We37iM8QJT4QP9ZDUaAVmJUZSjd+W8H/o28wbTAOBgNVHQ8B
|
||||||
|
Af8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQW
|
||||||
|
BBTn0wonKkm2lLRNYZrKhUukiynvqzAdBgNVHREEFjAUghJ0ZXN0LnNtYWxsc3Rl
|
||||||
|
cC5jb20wCgYIKoZIzj0EAwIDSAAwRQIhAJ5XqryBIY1X4fl/9l0isV69eQfA0Qo5
|
||||||
|
1mjervUcEnOWAiBsmN4frz5YVw7i4UXChVBeZLZfJOKvn5eyh2gEzoq1+w==
|
||||||
|
-----END CERTIFICATE-----
|
5
kms/softkms/testdata/cert.key
vendored
Normal file
5
kms/softkms/testdata/cert.key
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
-----BEGIN EC PRIVATE KEY-----
|
||||||
|
MHcCAQEEICB6lIrMa9fVQJtdAYS4qmdYQ1BHJsEQDx8zxL38gA8toAoGCCqGSM49
|
||||||
|
AwEHoUQDQgAE5VPD/W5RXn0lrs2MdoNteTSZ+sh1veT13hakPZF9YzaNVZgujqK3
|
||||||
|
d1nt+4jPECU+ED/WQ1GgFZiVGUo3flvB/w==
|
||||||
|
-----END EC PRIVATE KEY-----
|
8
kms/softkms/testdata/priv.pem
vendored
Normal file
8
kms/softkms/testdata/priv.pem
vendored
Normal file
|
@ -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-----
|
4
kms/softkms/testdata/pub.pem
vendored
Normal file
4
kms/softkms/testdata/pub.pem
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5VPD/W5RXn0lrs2MdoNteTSZ+sh1
|
||||||
|
veT13hakPZF9YzaNVZgujqK3d1nt+4jPECU+ED/WQ1GgFZiVGUo3flvB/w==
|
||||||
|
-----END PUBLIC KEY-----
|
Loading…
Reference in a new issue