github.com/glycerine/xcryptossh@v7.0.4+incompatible/agent/client_test.go (about)

     1  // Copyright 2012 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package agent
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"crypto/rand"
    11  	"errors"
    12  	"net"
    13  	"os"
    14  	"os/exec"
    15  	"path/filepath"
    16  	"strconv"
    17  	"testing"
    18  	"time"
    19  
    20  	ssh "github.com/glycerine/xcryptossh"
    21  )
    22  
    23  // startOpenSSHAgent executes ssh-agent, and returns a Agent interface to it.
    24  func startOpenSSHAgent(t *testing.T) (client Agent, socket string, cleanup func()) {
    25  	if testing.Short() {
    26  		// ssh-agent is not always available, and the key
    27  		// types supported vary by platform.
    28  		t.Skip("skipping test due to -short")
    29  	}
    30  
    31  	bin, err := exec.LookPath("ssh-agent")
    32  	if err != nil {
    33  		t.Skip("could not find ssh-agent")
    34  	}
    35  
    36  	cmd := exec.Command(bin, "-s")
    37  	out, err := cmd.Output()
    38  	if err != nil {
    39  		t.Fatalf("cmd.Output: %v", err)
    40  	}
    41  
    42  	/* Output looks like:
    43  
    44  		   SSH_AUTH_SOCK=/tmp/ssh-P65gpcqArqvH/agent.15541; export SSH_AUTH_SOCK;
    45  	           SSH_AGENT_PID=15542; export SSH_AGENT_PID;
    46  	           echo Agent pid 15542;
    47  	*/
    48  	fields := bytes.Split(out, []byte(";"))
    49  	line := bytes.SplitN(fields[0], []byte("="), 2)
    50  	line[0] = bytes.TrimLeft(line[0], "\n")
    51  	if string(line[0]) != "SSH_AUTH_SOCK" {
    52  		t.Fatalf("could not find key SSH_AUTH_SOCK in %q", fields[0])
    53  	}
    54  	socket = string(line[1])
    55  
    56  	line = bytes.SplitN(fields[2], []byte("="), 2)
    57  	line[0] = bytes.TrimLeft(line[0], "\n")
    58  	if string(line[0]) != "SSH_AGENT_PID" {
    59  		t.Fatalf("could not find key SSH_AGENT_PID in %q", fields[2])
    60  	}
    61  	pidStr := line[1]
    62  	pid, err := strconv.Atoi(string(pidStr))
    63  	if err != nil {
    64  		t.Fatalf("Atoi(%q): %v", pidStr, err)
    65  	}
    66  
    67  	conn, err := net.Dial("unix", string(socket))
    68  	if err != nil {
    69  		t.Fatalf("net.Dial: %v", err)
    70  	}
    71  
    72  	ac := NewClient(conn)
    73  	return ac, socket, func() {
    74  		proc, _ := os.FindProcess(pid)
    75  		if proc != nil {
    76  			proc.Kill()
    77  		}
    78  		conn.Close()
    79  		os.RemoveAll(filepath.Dir(socket))
    80  	}
    81  }
    82  
    83  // startKeyringAgent uses Keyring to simulate a ssh-agent Server and returns a client.
    84  func startKeyringAgent(t *testing.T) (client Agent, cleanup func()) {
    85  	c1, c2, err := netPipe()
    86  	if err != nil {
    87  		t.Fatalf("netPipe: %v", err)
    88  	}
    89  	go ServeAgent(NewKeyring(), c2)
    90  
    91  	return NewClient(c1), func() {
    92  		c1.Close()
    93  		c2.Close()
    94  	}
    95  }
    96  
    97  func testOpenSSHAgent(t *testing.T, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) {
    98  	agent, _, cleanup := startOpenSSHAgent(t)
    99  	defer cleanup()
   100  
   101  	testAgentInterface(t, agent, key, cert, lifetimeSecs)
   102  }
   103  
   104  func testKeyringAgent(t *testing.T, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) {
   105  	agent, cleanup := startKeyringAgent(t)
   106  	defer cleanup()
   107  
   108  	testAgentInterface(t, agent, key, cert, lifetimeSecs)
   109  }
   110  
   111  func testAgentInterface(t *testing.T, agent Agent, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) {
   112  	signer, err := ssh.NewSignerFromKey(key)
   113  	if err != nil {
   114  		t.Fatalf("NewSignerFromKey(%T): %v", key, err)
   115  	}
   116  	// The agent should start up empty.
   117  	if keys, err := agent.List(); err != nil {
   118  		t.Fatalf("RequestIdentities: %v", err)
   119  	} else if len(keys) > 0 {
   120  		t.Fatalf("got %d keys, want 0: %v", len(keys), keys)
   121  	}
   122  
   123  	// Attempt to insert the key, with certificate if specified.
   124  	var pubKey ssh.PublicKey
   125  	if cert != nil {
   126  		err = agent.Add(AddedKey{
   127  			PrivateKey:   key,
   128  			Certificate:  cert,
   129  			Comment:      "comment",
   130  			LifetimeSecs: lifetimeSecs,
   131  		})
   132  		pubKey = cert
   133  	} else {
   134  		err = agent.Add(AddedKey{PrivateKey: key, Comment: "comment", LifetimeSecs: lifetimeSecs})
   135  		pubKey = signer.PublicKey()
   136  	}
   137  	if err != nil {
   138  		t.Fatalf("insert(%T): %v", key, err)
   139  	}
   140  
   141  	// Did the key get inserted successfully?
   142  	if keys, err := agent.List(); err != nil {
   143  		t.Fatalf("List: %v", err)
   144  	} else if len(keys) != 1 {
   145  		t.Fatalf("got %v, want 1 key", keys)
   146  	} else if keys[0].Comment != "comment" {
   147  		t.Fatalf("key comment: got %v, want %v", keys[0].Comment, "comment")
   148  	} else if !bytes.Equal(keys[0].Blob, pubKey.Marshal()) {
   149  		t.Fatalf("key mismatch")
   150  	}
   151  
   152  	// Can the agent make a valid signature?
   153  	data := []byte("hello")
   154  	sig, err := agent.Sign(pubKey, data)
   155  	if err != nil {
   156  		t.Fatalf("Sign(%s): %v", pubKey.Type(), err)
   157  	}
   158  
   159  	if err := pubKey.Verify(data, sig); err != nil {
   160  		t.Fatalf("Verify(%s): %v", pubKey.Type(), err)
   161  	}
   162  
   163  	// If the key has a lifetime, is it removed when it should be?
   164  	if lifetimeSecs > 0 {
   165  		time.Sleep(time.Second*time.Duration(lifetimeSecs) + 100*time.Millisecond)
   166  		keys, err := agent.List()
   167  		if err != nil {
   168  			t.Fatalf("List: %v", err)
   169  		}
   170  		if len(keys) > 0 {
   171  			t.Fatalf("key not expired")
   172  		}
   173  	}
   174  
   175  }
   176  
   177  func TestAgent(t *testing.T) {
   178  	for _, keyType := range []string{"rsa", "dsa", "ecdsa", "ed25519"} {
   179  		testOpenSSHAgent(t, testPrivateKeys[keyType], nil, 0)
   180  		testKeyringAgent(t, testPrivateKeys[keyType], nil, 0)
   181  	}
   182  }
   183  
   184  func TestCert(t *testing.T) {
   185  	cert := &ssh.Certificate{
   186  		Key:         testPublicKeys["rsa"],
   187  		ValidBefore: ssh.CertTimeInfinity,
   188  		CertType:    ssh.UserCert,
   189  	}
   190  	cert.SignCert(rand.Reader, testSigners["ecdsa"])
   191  
   192  	testOpenSSHAgent(t, testPrivateKeys["rsa"], cert, 0)
   193  	testKeyringAgent(t, testPrivateKeys["rsa"], cert, 0)
   194  }
   195  
   196  // netPipe is analogous to net.Pipe, but it uses a real net.Conn, and
   197  // therefore is buffered (net.Pipe deadlocks if both sides start with
   198  // a write.)
   199  func netPipe() (net.Conn, net.Conn, error) {
   200  	listener, err := net.Listen("tcp", "127.0.0.1:0")
   201  	if err != nil {
   202  		listener, err = net.Listen("tcp", "[::1]:0")
   203  		if err != nil {
   204  			return nil, nil, err
   205  		}
   206  	}
   207  	defer listener.Close()
   208  	c1, err := net.Dial("tcp", listener.Addr().String())
   209  	if err != nil {
   210  		return nil, nil, err
   211  	}
   212  
   213  	c2, err := listener.Accept()
   214  	if err != nil {
   215  		c1.Close()
   216  		return nil, nil, err
   217  	}
   218  
   219  	return c1, c2, nil
   220  }
   221  
   222  func TestAuth(t *testing.T) {
   223  	ctx, cancelctx := context.WithCancel(context.Background())
   224  	defer cancelctx()
   225  	agent, _, cleanup := startOpenSSHAgent(t)
   226  	defer cleanup()
   227  
   228  	a, b, err := netPipe()
   229  	if err != nil {
   230  		t.Fatalf("netPipe: %v", err)
   231  	}
   232  
   233  	defer a.Close()
   234  	defer b.Close()
   235  
   236  	if err := agent.Add(AddedKey{PrivateKey: testPrivateKeys["rsa"], Comment: "comment"}); err != nil {
   237  		t.Errorf("Add: %v", err)
   238  	}
   239  
   240  	serverConf := ssh.ServerConfig{}
   241  	serverConf.AddHostKey(testSigners["rsa"])
   242  	serverConf.PublicKeyCallback = func(c ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
   243  		if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
   244  			return nil, nil
   245  		}
   246  
   247  		return nil, errors.New("pubkey rejected")
   248  	}
   249  
   250  	go func() {
   251  		conn, _, _, err := ssh.NewServerConn(ctx, a, &serverConf)
   252  		if err != nil {
   253  			t.Fatalf("Server: %v", err)
   254  		}
   255  		conn.Close()
   256  	}()
   257  
   258  	conf := ssh.ClientConfig{
   259  		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
   260  	}
   261  	conf.Auth = append(conf.Auth, ssh.PublicKeysCallback(agent.Signers))
   262  	conn, _, _, err := ssh.NewClientConn(ctx, b, "", &conf)
   263  	if err != nil {
   264  		t.Fatalf("NewClientConn: %v", err)
   265  	}
   266  	conn.Close()
   267  }
   268  
   269  func TestLockOpenSSHAgent(t *testing.T) {
   270  	agent, _, cleanup := startOpenSSHAgent(t)
   271  	defer cleanup()
   272  	testLockAgent(agent, t)
   273  }
   274  
   275  func TestLockKeyringAgent(t *testing.T) {
   276  	agent, cleanup := startKeyringAgent(t)
   277  	defer cleanup()
   278  	testLockAgent(agent, t)
   279  }
   280  
   281  func testLockAgent(agent Agent, t *testing.T) {
   282  	if err := agent.Add(AddedKey{PrivateKey: testPrivateKeys["rsa"], Comment: "comment 1"}); err != nil {
   283  		t.Errorf("Add: %v", err)
   284  	}
   285  	if err := agent.Add(AddedKey{PrivateKey: testPrivateKeys["dsa"], Comment: "comment dsa"}); err != nil {
   286  		t.Errorf("Add: %v", err)
   287  	}
   288  	if keys, err := agent.List(); err != nil {
   289  		t.Errorf("List: %v", err)
   290  	} else if len(keys) != 2 {
   291  		t.Errorf("Want 2 keys, got %v", keys)
   292  	}
   293  
   294  	passphrase := []byte("secret")
   295  	if err := agent.Lock(passphrase); err != nil {
   296  		t.Errorf("Lock: %v", err)
   297  	}
   298  
   299  	if keys, err := agent.List(); err != nil {
   300  		t.Errorf("List: %v", err)
   301  	} else if len(keys) != 0 {
   302  		t.Errorf("Want 0 keys, got %v", keys)
   303  	}
   304  
   305  	signer, _ := ssh.NewSignerFromKey(testPrivateKeys["rsa"])
   306  	if _, err := agent.Sign(signer.PublicKey(), []byte("hello")); err == nil {
   307  		t.Fatalf("Sign did not fail")
   308  	}
   309  
   310  	if err := agent.Remove(signer.PublicKey()); err == nil {
   311  		t.Fatalf("Remove did not fail")
   312  	}
   313  
   314  	if err := agent.RemoveAll(); err == nil {
   315  		t.Fatalf("RemoveAll did not fail")
   316  	}
   317  
   318  	if err := agent.Unlock(nil); err == nil {
   319  		t.Errorf("Unlock with wrong passphrase succeeded")
   320  	}
   321  	if err := agent.Unlock(passphrase); err != nil {
   322  		t.Errorf("Unlock: %v", err)
   323  	}
   324  
   325  	if err := agent.Remove(signer.PublicKey()); err != nil {
   326  		t.Fatalf("Remove: %v", err)
   327  	}
   328  
   329  	if keys, err := agent.List(); err != nil {
   330  		t.Errorf("List: %v", err)
   331  	} else if len(keys) != 1 {
   332  		t.Errorf("Want 1 keys, got %v", keys)
   333  	}
   334  }
   335  
   336  func testOpenSSHAgentLifetime(t *testing.T) {
   337  	agent, _, cleanup := startOpenSSHAgent(t)
   338  	defer cleanup()
   339  	testAgentLifetime(t, agent)
   340  }
   341  
   342  func testKeyringAgentLifetime(t *testing.T) {
   343  	agent, cleanup := startKeyringAgent(t)
   344  	defer cleanup()
   345  	testAgentLifetime(t, agent)
   346  }
   347  
   348  func testAgentLifetime(t *testing.T, agent Agent) {
   349  	for _, keyType := range []string{"rsa", "dsa", "ecdsa"} {
   350  		// Add private keys to the agent.
   351  		err := agent.Add(AddedKey{
   352  			PrivateKey:   testPrivateKeys[keyType],
   353  			Comment:      "comment",
   354  			LifetimeSecs: 1,
   355  		})
   356  		if err != nil {
   357  			t.Fatalf("add: %v", err)
   358  		}
   359  		// Add certs to the agent.
   360  		cert := &ssh.Certificate{
   361  			Key:         testPublicKeys[keyType],
   362  			ValidBefore: ssh.CertTimeInfinity,
   363  			CertType:    ssh.UserCert,
   364  		}
   365  		cert.SignCert(rand.Reader, testSigners[keyType])
   366  		err = agent.Add(AddedKey{
   367  			PrivateKey:   testPrivateKeys[keyType],
   368  			Certificate:  cert,
   369  			Comment:      "comment",
   370  			LifetimeSecs: 1,
   371  		})
   372  		if err != nil {
   373  			t.Fatalf("add: %v", err)
   374  		}
   375  	}
   376  	time.Sleep(1100 * time.Millisecond)
   377  	if keys, err := agent.List(); err != nil {
   378  		t.Errorf("List: %v", err)
   379  	} else if len(keys) != 0 {
   380  		t.Errorf("Want 0 keys, got %v", len(keys))
   381  	}
   382  }