forked from TrueCloudLab/certificates
Add support for using ssh-agent as a KMS
This adds a new KMS, SSHAgentKMS, which is a KMS to provide signing keys for issuing ssh certificates signed by a key managed by a ssh-agent. It uses the golang.org/x/crypto package to get a native Go implementation to talk to a ssh-agent. This was primarly written to be able to use gpg-agent to provide the keys stored in a YubiKeys openpgp interface, but can be used for other setups like proxying a ssh-agent over network. That way the signing key for ssh certificates can be kept in a "sign-only" hsm. This code was written for my employer Intinor AB, but for simplicity sake gifted to me to contribute upstream. Signed-off-by: Anton Lundin <glance@acc.umu.se>
This commit is contained in:
parent
5a1e44a399
commit
3e6137110b
13 changed files with 938 additions and 3 deletions
|
@ -2,6 +2,7 @@ package authority
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
@ -17,6 +18,7 @@ import (
|
||||||
"github.com/smallstep/certificates/db"
|
"github.com/smallstep/certificates/db"
|
||||||
"github.com/smallstep/certificates/kms"
|
"github.com/smallstep/certificates/kms"
|
||||||
kmsapi "github.com/smallstep/certificates/kms/apiv1"
|
kmsapi "github.com/smallstep/certificates/kms/apiv1"
|
||||||
|
"github.com/smallstep/certificates/kms/sshagentkms"
|
||||||
"github.com/smallstep/certificates/templates"
|
"github.com/smallstep/certificates/templates"
|
||||||
"go.step.sm/crypto/pemutil"
|
"go.step.sm/crypto/pemutil"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
|
@ -237,7 +239,17 @@ func (a *Authority) init() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.sshCAHostCertSignKey, err = ssh.NewSignerFromSigner(signer)
|
// If our signer is from sshagentkms, just unwrap it instead of
|
||||||
|
// wrapping it in another layer, and this prevents crypto from
|
||||||
|
// erroring out with: ssh: unsupported key type *agent.Key
|
||||||
|
switch s := signer.(type) {
|
||||||
|
case *sshagentkms.WrappedSSHSigner:
|
||||||
|
a.sshCAHostCertSignKey = s.Sshsigner
|
||||||
|
case crypto.Signer:
|
||||||
|
a.sshCAHostCertSignKey, err = ssh.NewSignerFromSigner(s)
|
||||||
|
default:
|
||||||
|
return errors.Errorf("unsupported signer type %T", signer)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "error creating ssh signer")
|
return errors.Wrap(err, "error creating ssh signer")
|
||||||
}
|
}
|
||||||
|
@ -253,7 +265,17 @@ func (a *Authority) init() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.sshCAUserCertSignKey, err = ssh.NewSignerFromSigner(signer)
|
// If our signer is from sshagentkms, just unwrap it instead of
|
||||||
|
// wrapping it in another layer, and this prevents crypto from
|
||||||
|
// erroring out with: ssh: unsupported key type *agent.Key
|
||||||
|
switch s := signer.(type) {
|
||||||
|
case *sshagentkms.WrappedSSHSigner:
|
||||||
|
a.sshCAUserCertSignKey = s.Sshsigner
|
||||||
|
case crypto.Signer:
|
||||||
|
a.sshCAUserCertSignKey, err = ssh.NewSignerFromSigner(s)
|
||||||
|
default:
|
||||||
|
return errors.Errorf("unsupported signer type %T", signer)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "error creating ssh signer")
|
return errors.Wrap(err, "error creating ssh signer")
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import (
|
||||||
_ "github.com/smallstep/certificates/kms/awskms"
|
_ "github.com/smallstep/certificates/kms/awskms"
|
||||||
_ "github.com/smallstep/certificates/kms/cloudkms"
|
_ "github.com/smallstep/certificates/kms/cloudkms"
|
||||||
_ "github.com/smallstep/certificates/kms/softkms"
|
_ "github.com/smallstep/certificates/kms/softkms"
|
||||||
|
_ "github.com/smallstep/certificates/kms/sshagentkms"
|
||||||
|
|
||||||
// Experimental kms interfaces.
|
// Experimental kms interfaces.
|
||||||
_ "github.com/smallstep/certificates/kms/yubikey"
|
_ "github.com/smallstep/certificates/kms/yubikey"
|
||||||
|
|
22
docs/kms.md
22
docs/kms.md
|
@ -213,3 +213,25 @@ and configure the `kms` property with the `type` and your `pin` in it.
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## SSHAgentKMS
|
||||||
|
|
||||||
|
SSHAgentKMS is a KMS that wrapps a ssh-agent which has access to the keys to
|
||||||
|
sign ssh certificates. This was primarly written to be able to use gpg-agent
|
||||||
|
to provide the keys stored in a YubiKeys openpgp interface.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kms": {
|
||||||
|
"type": "sshagentkms"
|
||||||
|
},
|
||||||
|
"ssh": {
|
||||||
|
"hostKey": "sshagentkms:cardno:000123456789",
|
||||||
|
"userKey": "sshagentkms:cardno:000123456789",
|
||||||
|
},
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This KMS requires that "root", "crt" and "key" are stored in plain files as for
|
||||||
|
SoftKMS.
|
||||||
|
|
|
@ -52,6 +52,8 @@ const (
|
||||||
PKCS11 Type = "pkcs11"
|
PKCS11 Type = "pkcs11"
|
||||||
// YubiKey is a KMS implementation using a YubiKey PIV.
|
// YubiKey is a KMS implementation using a YubiKey PIV.
|
||||||
YubiKey Type = "yubikey"
|
YubiKey Type = "yubikey"
|
||||||
|
// SSHAgentKMS is a KMS implementation using ssh-agent to access keys.
|
||||||
|
SSHAgentKMS Type = "sshagentkms"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Options are the KMS options. They represent the kms object in the ca.json.
|
// Options are the KMS options. They represent the kms object in the ca.json.
|
||||||
|
@ -91,7 +93,7 @@ func (o *Options) Validate() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch Type(strings.ToLower(o.Type)) {
|
switch Type(strings.ToLower(o.Type)) {
|
||||||
case DefaultKMS, SoftKMS, CloudKMS, AmazonKMS:
|
case DefaultKMS, SoftKMS, CloudKMS, AmazonKMS, SSHAgentKMS:
|
||||||
case YubiKey:
|
case YubiKey:
|
||||||
case PKCS11:
|
case PKCS11:
|
||||||
return ErrNotImplemented{"support for PKCS11 is not yet implemented"}
|
return ErrNotImplemented{"support for PKCS11 is not yet implemented"}
|
||||||
|
|
|
@ -14,6 +14,7 @@ func TestOptions_Validate(t *testing.T) {
|
||||||
{"softkms", &Options{Type: "softkms"}, false},
|
{"softkms", &Options{Type: "softkms"}, false},
|
||||||
{"cloudkms", &Options{Type: "cloudkms"}, false},
|
{"cloudkms", &Options{Type: "cloudkms"}, false},
|
||||||
{"awskms", &Options{Type: "awskms"}, false},
|
{"awskms", &Options{Type: "awskms"}, false},
|
||||||
|
{"sshagentkms", &Options{Type: "sshagentkms"}, false},
|
||||||
{"pkcs11", &Options{Type: "pkcs11"}, true},
|
{"pkcs11", &Options{Type: "pkcs11"}, true},
|
||||||
{"unsupported", &Options{Type: "unsupported"}, true},
|
{"unsupported", &Options{Type: "unsupported"}, true},
|
||||||
}
|
}
|
||||||
|
|
201
kms/sshagentkms/sshagentkms.go
Normal file
201
kms/sshagentkms/sshagentkms.go
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
package sshagentkms
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
"golang.org/x/crypto/ssh/agent"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/smallstep/certificates/kms/apiv1"
|
||||||
|
|
||||||
|
"go.step.sm/crypto/pemutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SSHAgentKMS is a key manager that uses keys provided by ssh-agent
|
||||||
|
type SSHAgentKMS struct {
|
||||||
|
agentClient agent.Agent
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new SSHAgentKMS.
|
||||||
|
func New(ctx context.Context, opts apiv1.Options) (*SSHAgentKMS, error) {
|
||||||
|
socket := os.Getenv("SSH_AUTH_SOCK")
|
||||||
|
conn, err := net.Dial("unix", socket)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to open SSH_AUTH_SOCK: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
agentClient := agent.NewClient(conn)
|
||||||
|
|
||||||
|
return &SSHAgentKMS{
|
||||||
|
agentClient: agentClient,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// For testing
|
||||||
|
func NewFromAgent(ctx context.Context, opts apiv1.Options, agentClient agent.Agent) (*SSHAgentKMS, error) {
|
||||||
|
return &SSHAgentKMS{
|
||||||
|
agentClient: agentClient,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
apiv1.Register(apiv1.SSHAgentKMS, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) {
|
||||||
|
return New(ctx, opts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *SSHAgentKMS) Close() error {
|
||||||
|
// TODO: Is there any cleanup in Agent we can do?
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility class to wrap a ssh.Signer as a crypto.Signer
|
||||||
|
type WrappedSSHSigner struct {
|
||||||
|
Sshsigner ssh.Signer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *WrappedSSHSigner) Public() crypto.PublicKey {
|
||||||
|
return s.Sshsigner.PublicKey()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *WrappedSSHSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
||||||
|
sig, err := s.Sshsigner.Sign(rand, digest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return sig.Blob, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWrappedSignerFromSSHSigner(signer ssh.Signer) crypto.Signer {
|
||||||
|
return &WrappedSSHSigner{signer}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *SSHAgentKMS) findKey(signingKey string) (target int, err error) {
|
||||||
|
if strings.HasPrefix(signingKey, "sshagentkms:") {
|
||||||
|
var key = strings.TrimPrefix(signingKey, "sshagentkms:")
|
||||||
|
|
||||||
|
l, err := k.agentClient.List()
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
for i, s := range l {
|
||||||
|
if s.Comment == key {
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1, errors.Errorf("SSHAgentKMS couldn't find %s", signingKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSigner returns a new signer configured with the given signing key.
|
||||||
|
func (k *SSHAgentKMS) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error) {
|
||||||
|
if req.Signer != nil {
|
||||||
|
return req.Signer, nil
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(req.SigningKey, "sshagentkms:") {
|
||||||
|
target, err := k.findKey(req.SigningKey)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s, err := k.agentClient.Signers()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewWrappedSignerFromSSHSigner(s[target]), nil
|
||||||
|
}
|
||||||
|
// OK: We don't actually care about non-ssh certificates,
|
||||||
|
// but we can't disable it in step-ca so this code is copy-pasted from
|
||||||
|
// softkms just to keep step-ca happy.
|
||||||
|
var opts []pemutil.Options
|
||||||
|
if req.Password != nil {
|
||||||
|
opts = append(opts, pemutil.WithPassword(req.Password))
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateKey generates a new key and returns both public and private key.
|
||||||
|
func (k *SSHAgentKMS) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) {
|
||||||
|
return nil, errors.Errorf("SSHAgentKMS doesn't support generating keys")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPublicKey returns the public key from the file passed in the request name.
|
||||||
|
func (k *SSHAgentKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) {
|
||||||
|
var v crypto.PublicKey
|
||||||
|
if strings.HasPrefix(req.Name, "sshagentkms:") {
|
||||||
|
target, err := k.findKey(req.Name)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := k.agentClient.Signers()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sshPub := s[target].PublicKey()
|
||||||
|
|
||||||
|
sshPubBytes := sshPub.Marshal()
|
||||||
|
|
||||||
|
parsed, err := ssh.ParsePublicKey(sshPubBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedCryptoKey := parsed.(ssh.CryptoPublicKey)
|
||||||
|
|
||||||
|
// Then, we can call CryptoPublicKey() to get the actual crypto.PublicKey
|
||||||
|
v = parsedCryptoKey.CryptoPublicKey()
|
||||||
|
} else {
|
||||||
|
var err 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)
|
||||||
|
}
|
||||||
|
}
|
608
kms/sshagentkms/sshagentkms_test.go
Normal file
608
kms/sshagentkms/sshagentkms_test.go
Normal file
|
@ -0,0 +1,608 @@
|
||||||
|
package sshagentkms
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/smallstep/certificates/kms/apiv1"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
"golang.org/x/crypto/ssh/agent"
|
||||||
|
|
||||||
|
"go.step.sm/crypto/pemutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Some helpers with inspiration from crypto/ssh/agent/client_test.go
|
||||||
|
|
||||||
|
// startOpenSSHAgent executes ssh-agent, and returns an Agent interface to it.
|
||||||
|
func startOpenSSHAgent(t *testing.T) (client agent.Agent, socket string, cleanup func()) {
|
||||||
|
/* Always test with OpenSSHAgent
|
||||||
|
if testing.Short() {
|
||||||
|
// ssh-agent is not always available, and the key
|
||||||
|
// types supported vary by platform.
|
||||||
|
t.Skip("skipping test due to -short")
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
bin, err := exec.LookPath("ssh-agent")
|
||||||
|
if err != nil {
|
||||||
|
t.Skip("could not find ssh-agent")
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(bin, "-s")
|
||||||
|
cmd.Env = []string{} // Do not let the user's environment influence ssh-agent behavior.
|
||||||
|
cmd.Stderr = new(bytes.Buffer)
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s failed: %v\n%s", strings.Join(cmd.Args, " "), err, cmd.Stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output looks like:
|
||||||
|
//
|
||||||
|
// SSH_AUTH_SOCK=/tmp/ssh-P65gpcqArqvH/agent.15541; export SSH_AUTH_SOCK;
|
||||||
|
// SSH_AGENT_PID=15542; export SSH_AGENT_PID;
|
||||||
|
// echo Agent pid 15542;
|
||||||
|
|
||||||
|
fields := bytes.Split(out, []byte(";"))
|
||||||
|
line := bytes.SplitN(fields[0], []byte("="), 2)
|
||||||
|
line[0] = bytes.TrimLeft(line[0], "\n")
|
||||||
|
if string(line[0]) != "SSH_AUTH_SOCK" {
|
||||||
|
t.Fatalf("could not find key SSH_AUTH_SOCK in %q", fields[0])
|
||||||
|
}
|
||||||
|
socket = string(line[1])
|
||||||
|
|
||||||
|
line = bytes.SplitN(fields[2], []byte("="), 2)
|
||||||
|
line[0] = bytes.TrimLeft(line[0], "\n")
|
||||||
|
if string(line[0]) != "SSH_AGENT_PID" {
|
||||||
|
t.Fatalf("could not find key SSH_AGENT_PID in %q", fields[2])
|
||||||
|
}
|
||||||
|
pidStr := line[1]
|
||||||
|
pid, err := strconv.Atoi(string(pidStr))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Atoi(%q): %v", pidStr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := net.Dial("unix", string(socket))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("net.Dial: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ac := agent.NewClient(conn)
|
||||||
|
return ac, socket, func() {
|
||||||
|
proc, _ := os.FindProcess(pid)
|
||||||
|
if proc != nil {
|
||||||
|
proc.Kill()
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
os.RemoveAll(filepath.Dir(socket))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func startAgent(t *testing.T, sshagent agent.Agent) (client agent.Agent, cleanup func()) {
|
||||||
|
c1, c2, err := netPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("netPipe: %v", err)
|
||||||
|
}
|
||||||
|
go agent.ServeAgent(sshagent, c2)
|
||||||
|
|
||||||
|
return agent.NewClient(c1), func() {
|
||||||
|
c1.Close()
|
||||||
|
c2.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// startKeyringAgent uses Keyring to simulate a ssh-agent Server and returns a client.
|
||||||
|
func startKeyringAgent(t *testing.T) (client agent.Agent, cleanup func()) {
|
||||||
|
return startAgent(t, agent.NewKeyring())
|
||||||
|
}
|
||||||
|
|
||||||
|
type startTestAgentFunc func(t *testing.T, keysToAdd ...agent.AddedKey) (sshagent agent.Agent)
|
||||||
|
|
||||||
|
func startTestOpenSSHAgent(t *testing.T, keysToAdd ...agent.AddedKey) (sshagent agent.Agent) {
|
||||||
|
sshagent, _, cleanup := startOpenSSHAgent(t)
|
||||||
|
for _, keyToAdd := range keysToAdd {
|
||||||
|
err := sshagent.Add(keyToAdd)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("sshagent.add: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Cleanup(cleanup)
|
||||||
|
|
||||||
|
//testAgentInterface(t, sshagent, key, cert, lifetimeSecs)
|
||||||
|
return sshagent
|
||||||
|
}
|
||||||
|
|
||||||
|
func startTestKeyringAgent(t *testing.T, keysToAdd ...agent.AddedKey) (sshagent agent.Agent) {
|
||||||
|
sshagent, cleanup := startKeyringAgent(t)
|
||||||
|
for _, keyToAdd := range keysToAdd {
|
||||||
|
err := sshagent.Add(keyToAdd)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("sshagent.add: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Cleanup(cleanup)
|
||||||
|
|
||||||
|
//testAgentInterface(t, agent, key, cert, lifetimeSecs)
|
||||||
|
return sshagent
|
||||||
|
}
|
||||||
|
|
||||||
|
// netPipe is analogous to net.Pipe, but it uses a real net.Conn, and
|
||||||
|
// therefore is buffered (net.Pipe deadlocks if both sides start with
|
||||||
|
// a write.)
|
||||||
|
func netPipe() (net.Conn, net.Conn, error) {
|
||||||
|
listener, err := netListener()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
defer listener.Close()
|
||||||
|
c1, err := net.Dial("tcp", listener.Addr().String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c2, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
c1.Close()
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c1, c2, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// netListener creates a localhost network listener.
|
||||||
|
func netListener() (net.Listener, error) {
|
||||||
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
listener, err = net.Listen("tcp", "[::1]:0")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return listener, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
comment := "Key from OpenSSHAgent"
|
||||||
|
// Ensure we don't "inherit" any SSH_AUTH_SOCK
|
||||||
|
os.Unsetenv("SSH_AUTH_SOCK")
|
||||||
|
|
||||||
|
sshagent, socket, cleanup := startOpenSSHAgent(t)
|
||||||
|
|
||||||
|
os.Setenv("SSH_AUTH_SOCK", socket)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
os.Unsetenv("SSH_AUTH_SOCK")
|
||||||
|
cleanup()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test that we can't find any signers in the agent before we have loaded them
|
||||||
|
t.Run("No keys with OpenSSHAgent", func(t *testing.T) {
|
||||||
|
kms, err := New(context.Background(), apiv1.Options{})
|
||||||
|
if kms == nil || err != nil {
|
||||||
|
t.Errorf("New() = %v, %v", kms, err)
|
||||||
|
}
|
||||||
|
signer, err := kms.CreateSigner(&apiv1.CreateSignerRequest{SigningKey: "sshagentkms:" + comment})
|
||||||
|
if err == nil || signer != nil {
|
||||||
|
t.Errorf("SSHAgentKMS.CreateSigner() error = \"%v\", signer = \"%v\"", err, signer)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Load ssh test fixtures
|
||||||
|
b, err := ioutil.ReadFile("testdata/ssh")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
privateKey, err := ssh.ParseRawPrivateKey(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// And add that key to the agent
|
||||||
|
err = sshagent.Add(agent.AddedKey{PrivateKey: privateKey, Comment: comment})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("sshagent.add: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// And test that we can find it when it's loaded
|
||||||
|
t.Run("Keys with OpenSSHAgent", func(t *testing.T) {
|
||||||
|
kms, err := New(context.Background(), apiv1.Options{})
|
||||||
|
if kms == nil || err != nil {
|
||||||
|
t.Errorf("New() = %v, %v", kms, err)
|
||||||
|
}
|
||||||
|
signer, err := kms.CreateSigner(&apiv1.CreateSignerRequest{SigningKey: "sshagentkms:" + comment})
|
||||||
|
if err != nil || signer == nil {
|
||||||
|
t.Errorf("SSHAgentKMS.CreateSigner() error = \"%v\", signer = \"%v\"", err, signer)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewFromAgent(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
opts apiv1.Options
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
sshagentstarter startTestAgentFunc
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"ok OpenSSHAgent", args{context.Background(), apiv1.Options{}}, startTestOpenSSHAgent, false},
|
||||||
|
{"ok KeyringAgent", args{context.Background(), apiv1.Options{}}, startTestKeyringAgent, false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := NewFromAgent(tt.args.ctx, tt.args.opts, tt.sshagentstarter(t))
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("NewFromAgent() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if got == nil {
|
||||||
|
t.Errorf("NewFromAgent() = %v", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSSHAgentKMS_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 := &SSHAgentKMS{}
|
||||||
|
if err := k.Close(); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("SSHAgentKMS.Close() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSSHAgentKMS_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,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Load ssh test fixtures
|
||||||
|
sshPubKeyStr, err := ioutil.ReadFile("testdata/ssh.pub")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, comment, _, _, err := ssh.ParseAuthorizedKey(sshPubKeyStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
b, err = ioutil.ReadFile("testdata/ssh")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
privateKey, err := ssh.ParseRawPrivateKey(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
sshPrivateKey, err := ssh.NewSignerFromKey(privateKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
wrappedSSHPrivateKey := NewWrappedSignerFromSSHSigner(sshPrivateKey)
|
||||||
|
|
||||||
|
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},
|
||||||
|
{"sshagent", args{&apiv1.CreateSignerRequest{SigningKey: "sshagentkms:" + comment}}, wrappedSSHPrivateKey, false},
|
||||||
|
{"sshagent Nonexistant", args{&apiv1.CreateSignerRequest{SigningKey: "sshagentkms:Nonexistant"}}, nil, true},
|
||||||
|
{"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},
|
||||||
|
}
|
||||||
|
starters := []struct {
|
||||||
|
name string
|
||||||
|
starter startTestAgentFunc
|
||||||
|
}{
|
||||||
|
{"startTestOpenSSHAgent", startTestOpenSSHAgent},
|
||||||
|
{"startTestKeyringAgent", startTestKeyringAgent},
|
||||||
|
}
|
||||||
|
for _, starter := range starters {
|
||||||
|
k, err := NewFromAgent(context.Background(), apiv1.Options{}, starter.starter(t, agent.AddedKey{PrivateKey: privateKey, Comment: comment}))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(starter.name+"/"+tt.name, func(t *testing.T) {
|
||||||
|
got, err := k.CreateSigner(tt.args.req)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("SSHAgentKMS.CreateSigner() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch s := got.(type) {
|
||||||
|
case *WrappedSSHSigner:
|
||||||
|
gotPkS := s.Sshsigner.PublicKey().(*agent.Key).String() + "\n"
|
||||||
|
wantPkS := string(sshPubKeyStr)
|
||||||
|
if !reflect.DeepEqual(gotPkS, wantPkS) {
|
||||||
|
t.Errorf("SSHAgentKMS.CreateSigner() = %T, want %T", gotPkS, wantPkS)
|
||||||
|
t.Errorf("SSHAgentKMS.CreateSigner() = %v, want %v", gotPkS, wantPkS)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("SSHAgentKMS.CreateSigner() = %T, want %T", got, tt.want)
|
||||||
|
t.Errorf("SSHAgentKMS.CreateSigner() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func restoreGenerateKey() func() {
|
||||||
|
oldGenerateKey := generateKey
|
||||||
|
return func() {
|
||||||
|
generateKey = oldGenerateKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
func TestSSHAgentKMS_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 := &SSHAgentKMS{}
|
||||||
|
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("SSHAgentKMS.CreateKey() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("SSHAgentKMS.CreateKey() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
func TestSSHAgentKMS_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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load ssh test fixtures
|
||||||
|
b, err = ioutil.ReadFile("testdata/ssh.pub")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
sshPubKey, comment, _, _, err := ssh.ParseAuthorizedKey(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
b, err = ioutil.ReadFile("testdata/ssh")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// crypto.PrivateKey
|
||||||
|
sshPrivateKey, err := ssh.ParseRawPrivateKey(b)
|
||||||
|
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},
|
||||||
|
{"sshagent", args{&apiv1.GetPublicKeyRequest{Name: "sshagentkms:" + comment}}, sshPubKey, false},
|
||||||
|
{"sshagent Nonexistant", args{&apiv1.GetPublicKeyRequest{Name: "sshagentkms:Nonexistant"}}, nil, true},
|
||||||
|
{"fail not exists", args{&apiv1.GetPublicKeyRequest{Name: "testdata/missing"}}, nil, true},
|
||||||
|
{"fail type", args{&apiv1.GetPublicKeyRequest{Name: "testdata/cert.key"}}, nil, true},
|
||||||
|
}
|
||||||
|
starters := []struct {
|
||||||
|
name string
|
||||||
|
starter startTestAgentFunc
|
||||||
|
}{
|
||||||
|
{"startTestOpenSSHAgent", startTestOpenSSHAgent},
|
||||||
|
{"startTestKeyringAgent", startTestKeyringAgent},
|
||||||
|
}
|
||||||
|
for _, starter := range starters {
|
||||||
|
k, err := NewFromAgent(context.Background(), apiv1.Options{}, starter.starter(t, agent.AddedKey{PrivateKey: sshPrivateKey, Comment: comment}))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(starter.name+"/"+tt.name, func(t *testing.T) {
|
||||||
|
got, err := k.GetPublicKey(tt.args.req)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("SSHAgentKMS.GetPublicKey() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch tt.want.(type) {
|
||||||
|
case ssh.PublicKey:
|
||||||
|
// If we want a ssh.PublicKey, protote got to a
|
||||||
|
got, err = ssh.NewPublicKey(got)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("SSHAgentKMS.GetPublicKey() = %T, want %T", got, tt.want)
|
||||||
|
t.Errorf("SSHAgentKMS.GetPublicKey() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSSHAgentKMS_CreateKey(t *testing.T) {
|
||||||
|
starters := []struct {
|
||||||
|
name string
|
||||||
|
starter startTestAgentFunc
|
||||||
|
}{
|
||||||
|
{"startTestOpenSSHAgent", startTestOpenSSHAgent},
|
||||||
|
{"startTestKeyringAgent", startTestKeyringAgent},
|
||||||
|
}
|
||||||
|
for _, starter := range starters {
|
||||||
|
k, err := NewFromAgent(context.Background(), apiv1.Options{}, starter.starter(t))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Run(starter.name+"/CreateKey", func(t *testing.T) {
|
||||||
|
got, err := k.CreateKey(&apiv1.CreateKeyRequest{
|
||||||
|
Name: "sshagentkms:0",
|
||||||
|
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||||
|
})
|
||||||
|
if got != nil {
|
||||||
|
t.Error("SSHAgentKMS.CreateKey() shoudn't return a value")
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Error("SSHAgentKMS.CreateKey() didn't return a value")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
11
kms/sshagentkms/testdata/cert.crt
vendored
Normal file
11
kms/sshagentkms/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/sshagentkms/testdata/cert.key
vendored
Normal file
5
kms/sshagentkms/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/sshagentkms/testdata/priv.pem
vendored
Normal file
8
kms/sshagentkms/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/sshagentkms/testdata/pub.pem
vendored
Normal file
4
kms/sshagentkms/testdata/pub.pem
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5VPD/W5RXn0lrs2MdoNteTSZ+sh1
|
||||||
|
veT13hakPZF9YzaNVZgujqK3d1nt+4jPECU+ED/WQ1GgFZiVGUo3flvB/w==
|
||||||
|
-----END PUBLIC KEY-----
|
49
kms/sshagentkms/testdata/ssh
vendored
Normal file
49
kms/sshagentkms/testdata/ssh
vendored
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
-----END OPENSSH PRIVATE KEY-----
|
1
kms/sshagentkms/testdata/ssh.pub
vendored
Normal file
1
kms/sshagentkms/testdata/ssh.pub
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC2H93vNENu/nrujjopPphioL8RV0OlnybSvMh2ugkv5EdUNc1DWlTeWsL0hZ+TKNf5jA72McZuton4KCicCSXfqa0vM0LjrjlAZLb9Z0WpFHqZALPu/gbqc5oU1IJ4SsyGDR+fh/uN2pel3SFijMimS1FU1hymLZEyavx78pp2vkRVB9thmB0oiXU7paz07dDUGEsaL0aKkSy1RO+OrFnMXuhSBYofniPl4c1d1sWoqqcrw01CbKk0tgYOxMFAcb3NGz6jTsLZ+g7110g7Y4JqtwFZl8aVjTmT9mESasQ442HatIuEnCrD4QmBB5TQ+h78iqMXLuOdNGSL6157Z6UFhW8dg9Kh9loGZfuLbWzx4FGX9mIXHeKnoTczniZe8pntK4Daqr9hbQh4rInkNK4vT6xEDKJJDYhyzJvdgq/oteiO4lOYn0eD2/j5o4/E5CG81+HdvuwiH9ybPV2r9tpsvI45GtscoPEgkwTtU8yymZ11X3AmAHga7V5Y6JH8K6/qOphZN+JYkMiiplEPmcXW0Woq1nmICqR+W/gpujXS2/qdOVdHpNa4+7Zn+lmnH4037tDFVRKVGDFvtFNVwdG+mB3RM0RcZ25HxqtZUOU17A6Z/koWuhbUhf/JHU68TlGwOLHFxfs1765KWr2GijL1X8inR99wX8qDQ0mg/Cucgw== ssh.test.smallstep.com
|
Loading…
Reference in a new issue