go.crypto/ssh: import gosshnew.
See https://groups.google.com/d/msg/Golang-nuts/AoVxQ4bB5XQ/i8kpMxdbVlEJ
R=hanwen
CC=golang-codereviews
https://golang.org/cl/86190043
diff --git a/ssh/test/agent_unix_test.go b/ssh/test/agent_unix_test.go
new file mode 100644
index 0000000..26c88eb
--- /dev/null
+++ b/ssh/test/agent_unix_test.go
@@ -0,0 +1,50 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin freebsd linux netbsd openbsd
+
+package test
+
+import (
+ "bytes"
+ "testing"
+
+ "code.google.com/p/go.crypto/ssh"
+ "code.google.com/p/go.crypto/ssh/agent"
+)
+
+func TestAgentForward(t *testing.T) {
+ server := newServer(t)
+ defer server.Shutdown()
+ conn := server.Dial(clientConfig())
+ defer conn.Close()
+
+ keyring := agent.NewKeyring()
+ keyring.Add(testPrivateKeys["dsa"], nil, "")
+ pub := testPublicKeys["dsa"]
+
+ sess, err := conn.NewSession()
+ if err != nil {
+ t.Fatalf("NewSession: %v", err)
+ }
+ if err := agent.RequestAgentForwarding(sess); err != nil {
+ t.Fatalf("RequestAgentForwarding: %v", err)
+ }
+
+ if err := agent.ForwardToAgent(conn, keyring); err != nil {
+ t.Fatalf("SetupForwardKeyring: %v", err)
+ }
+ out, err := sess.CombinedOutput("ssh-add -L")
+ if err != nil {
+ t.Fatalf("running ssh-add: %v, out %s", err, out)
+ }
+ key, _, _, _, err := ssh.ParseAuthorizedKey(out)
+ if err != nil {
+ t.Fatalf("ParseAuthorizedKey(%q): %v", out, err)
+ }
+
+ if !bytes.Equal(key.Marshal(), pub.Marshal()) {
+ t.Fatalf("got key %s, want %s", ssh.MarshalAuthorizedKey(key), ssh.MarshalAuthorizedKey(pub))
+ }
+}
diff --git a/ssh/test/cert_test.go b/ssh/test/cert_test.go
new file mode 100644
index 0000000..d4f7226
--- /dev/null
+++ b/ssh/test/cert_test.go
@@ -0,0 +1,47 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin freebsd linux netbsd openbsd
+
+package test
+
+import (
+ "crypto/rand"
+ "testing"
+
+ "code.google.com/p/go.crypto/ssh"
+)
+
+func TestCertLogin(t *testing.T) {
+ s := newServer(t)
+ defer s.Shutdown()
+
+ // Use a key different from the default.
+ clientKey := testSigners["dsa"]
+ caAuthKey := testSigners["ecdsa"]
+ cert := &ssh.Certificate{
+ Key: clientKey.PublicKey(),
+ ValidPrincipals: []string{username()},
+ CertType: ssh.UserCert,
+ ValidBefore: ssh.CertTimeInfinity,
+ }
+ if err := cert.SignCert(rand.Reader, caAuthKey); err != nil {
+ t.Fatalf("SetSignature: %v", err)
+ }
+
+ certSigner, err := ssh.NewCertSigner(cert, clientKey)
+ if err != nil {
+ t.Fatalf("NewCertSigner: %v", err)
+ }
+
+ conf := &ssh.ClientConfig{
+ User: username(),
+ }
+ conf.Auth = append(conf.Auth, ssh.PublicKeys(certSigner))
+ client, err := s.TryDial(conf)
+ if err != nil {
+ t.Fatalf("TryDial: %v", err)
+ }
+ client.Close()
+}
diff --git a/ssh/test/forward_unix_test.go b/ssh/test/forward_unix_test.go
index 3a57c10..881a9da 100644
--- a/ssh/test/forward_unix_test.go
+++ b/ssh/test/forward_unix_test.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build darwin freebsd linux netbsd openbsd plan9
+// +build darwin freebsd linux netbsd openbsd
package test
diff --git a/ssh/test/session_test.go b/ssh/test/session_test.go
index bd7307d..d8d35a5 100644
--- a/ssh/test/session_test.go
+++ b/ssh/test/session_test.go
@@ -11,6 +11,7 @@
import (
"bytes"
"code.google.com/p/go.crypto/ssh"
+ "errors"
"io"
"strings"
"testing"
@@ -38,12 +39,13 @@
defer server.Shutdown()
conf := clientConfig()
- k := conf.HostKeyChecker.(*storedHostKey)
+ hostDB := hostKeyDB()
+ conf.HostKeyCallback = hostDB.Check
// change the keys.
- k.keys[ssh.KeyAlgoRSA][25]++
- k.keys[ssh.KeyAlgoDSA][25]++
- k.keys[ssh.KeyAlgoECDSA256][25]++
+ hostDB.keys[ssh.KeyAlgoRSA][25]++
+ hostDB.keys[ssh.KeyAlgoDSA][25]++
+ hostDB.keys[ssh.KeyAlgoECDSA256][25]++
conn, err := server.TryDial(conf)
if err == nil {
@@ -54,6 +56,53 @@
}
}
+func TestRunCommandStdin(t *testing.T) {
+ server := newServer(t)
+ defer server.Shutdown()
+ conn := server.Dial(clientConfig())
+ defer conn.Close()
+
+ session, err := conn.NewSession()
+ if err != nil {
+ t.Fatalf("session failed: %v", err)
+ }
+ defer session.Close()
+
+ r, w := io.Pipe()
+ defer r.Close()
+ defer w.Close()
+ session.Stdin = r
+
+ err = session.Run("true")
+ if err != nil {
+ t.Fatalf("session failed: %v", err)
+ }
+}
+
+func TestRunCommandStdinError(t *testing.T) {
+ server := newServer(t)
+ defer server.Shutdown()
+ conn := server.Dial(clientConfig())
+ defer conn.Close()
+
+ session, err := conn.NewSession()
+ if err != nil {
+ t.Fatalf("session failed: %v", err)
+ }
+ defer session.Close()
+
+ r, w := io.Pipe()
+ defer r.Close()
+ session.Stdin = r
+ pipeErr := errors.New("closing write end of pipe")
+ w.CloseWithError(pipeErr)
+
+ err = session.Run("true")
+ if err != pipeErr {
+ t.Fatalf("expected %v, found %v", pipeErr, err)
+ }
+}
+
func TestRunCommandFailed(t *testing.T) {
server := newServer(t)
defer server.Shutdown()
@@ -107,7 +156,7 @@
t.Fatalf("unable to acquire stdout pipe: %s", err)
}
- err = session.Start("dd if=/dev/urandom bs=2048 count=1")
+ err = session.Start("dd if=/dev/urandom bs=2048 count=1024")
if err != nil {
t.Fatalf("unable to execute remote command: %s", err)
}
@@ -118,11 +167,53 @@
t.Fatalf("error reading from remote stdout: %s", err)
}
- if n != 2048 {
+ if n != 2048*1024 {
t.Fatalf("Expected %d bytes but read only %d from remote command", 2048, n)
}
}
+func TestKeyChange(t *testing.T) {
+ server := newServer(t)
+ defer server.Shutdown()
+ conf := clientConfig()
+ hostDB := hostKeyDB()
+ conf.HostKeyCallback = hostDB.Check
+ conf.RekeyThreshold = 1024
+ conn := server.Dial(conf)
+ defer conn.Close()
+
+ for i := 0; i < 4; i++ {
+ session, err := conn.NewSession()
+ if err != nil {
+ t.Fatalf("unable to create new session: %s", err)
+ }
+
+ stdout, err := session.StdoutPipe()
+ if err != nil {
+ t.Fatalf("unable to acquire stdout pipe: %s", err)
+ }
+
+ err = session.Start("dd if=/dev/urandom bs=1024 count=1")
+ if err != nil {
+ t.Fatalf("unable to execute remote command: %s", err)
+ }
+ buf := new(bytes.Buffer)
+ n, err := io.Copy(buf, stdout)
+ if err != nil {
+ t.Fatalf("error reading from remote stdout: %s", err)
+ }
+
+ want := int64(1024)
+ if n != want {
+ t.Fatalf("Expected %d bytes but read only %d from remote command", want, n)
+ }
+ }
+
+ if changes := hostDB.checkCount; changes < 4 {
+ t.Errorf("got %d key changes, want 4", changes)
+ }
+}
+
func TestInvalidTerminalMode(t *testing.T) {
server := newServer(t)
defer server.Shutdown()
@@ -183,3 +274,44 @@
t.Fatalf("terminal mode failure: expected -echo in stty output, got %s", sttyOutput)
}
}
+
+func TestCiphers(t *testing.T) {
+ var config ssh.Config
+ config.SetDefaults()
+ cipherOrder := config.Ciphers
+
+ for _, ciph := range cipherOrder {
+ server := newServer(t)
+ defer server.Shutdown()
+ conf := clientConfig()
+ conf.Ciphers = []string{ciph}
+ // Don't fail if sshd doesnt have the cipher.
+ conf.Ciphers = append(conf.Ciphers, cipherOrder...)
+ conn, err := server.TryDial(conf)
+ if err == nil {
+ conn.Close()
+ } else {
+ t.Fatalf("failed for cipher %q", ciph)
+ }
+ }
+}
+
+func TestMACs(t *testing.T) {
+ var config ssh.Config
+ config.SetDefaults()
+ macOrder := config.MACs
+
+ for _, mac := range macOrder {
+ server := newServer(t)
+ defer server.Shutdown()
+ conf := clientConfig()
+ conf.MACs = []string{mac}
+ // Don't fail if sshd doesnt have the MAC.
+ conf.MACs = append(conf.MACs, macOrder...)
+ if conn, err := server.TryDial(conf); err == nil {
+ conn.Close()
+ } else {
+ t.Fatalf("failed for MAC %q", mac)
+ }
+ }
+}
diff --git a/ssh/test/tcpip_test.go b/ssh/test/tcpip_test.go
index ee06b60..a2eb935 100644
--- a/ssh/test/tcpip_test.go
+++ b/ssh/test/tcpip_test.go
@@ -9,39 +9,38 @@
// direct-tcpip functional tests
import (
+ "io"
"net"
- "net/http"
"testing"
)
-func TestTCPIPHTTP(t *testing.T) {
- // google.com will generate at least one redirect, possibly three
- // depending on your location.
- doTest(t, "http://google.com")
-}
-
-func TestTCPIPHTTPS(t *testing.T) {
- doTest(t, "https://encrypted.google.com/")
-}
-
-func doTest(t *testing.T, url string) {
+func TestDial(t *testing.T) {
server := newServer(t)
defer server.Shutdown()
- conn := server.Dial(clientConfig())
- defer conn.Close()
+ sshConn := server.Dial(clientConfig())
+ defer sshConn.Close()
- tr := &http.Transport{
- Dial: func(n, addr string) (net.Conn, error) {
- return conn.Dial(n, addr)
- },
- }
- client := &http.Client{
- Transport: tr,
- }
- resp, err := client.Get(url)
+ l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
- t.Fatalf("unable to proxy: %s", err)
+ t.Fatalf("Listen: %v", err)
}
- // got a body without error
- t.Log(resp)
+ defer l.Close()
+
+ go func() {
+ for {
+ c, err := l.Accept()
+ if err != nil {
+ break
+ }
+
+ io.WriteString(c, c.RemoteAddr().String())
+ c.Close()
+ }
+ }()
+
+ conn, err := sshConn.Dial("tcp", l.Addr().String())
+ if err != nil {
+ t.Fatalf("Dial: %v", err)
+ }
+ defer conn.Close()
}
diff --git a/ssh/test/test_unix_test.go b/ssh/test/test_unix_test.go
index 86df3f4..f44c65d 100644
--- a/ssh/test/test_unix_test.go
+++ b/ssh/test/test_unix_test.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build darwin freebsd linux netbsd openbsd plan9
+// +build darwin freebsd linux netbsd openbsd
package test
@@ -11,7 +11,6 @@
import (
"bytes"
"fmt"
- "io"
"io/ioutil"
"log"
"net"
@@ -23,13 +22,14 @@
"text/template"
"code.google.com/p/go.crypto/ssh"
+ "code.google.com/p/go.crypto/ssh/testdata"
)
const sshd_config = `
Protocol 2
-HostKey {{.Dir}}/ssh_host_rsa_key
-HostKey {{.Dir}}/ssh_host_dsa_key
-HostKey {{.Dir}}/ssh_host_ecdsa_key
+HostKey {{.Dir}}/id_rsa
+HostKey {{.Dir}}/id_dsa
+HostKey {{.Dir}}/id_ecdsa
Pidfile {{.Dir}}/sshd.pid
#UsePrivilegeSeparation no
KeyRegenerationInterval 3600
@@ -41,41 +41,14 @@
StrictModes no
RSAAuthentication yes
PubkeyAuthentication yes
-AuthorizedKeysFile {{.Dir}}/authorized_keys
+AuthorizedKeysFile {{.Dir}}/id_user.pub
+TrustedUserCAKeys {{.Dir}}/id_ecdsa.pub
IgnoreRhosts yes
RhostsRSAAuthentication no
HostbasedAuthentication no
`
-var (
- configTmpl template.Template
- privateKey ssh.Signer
- hostKeyRSA ssh.Signer
- hostKeyECDSA ssh.Signer
- hostKeyDSA ssh.Signer
-)
-
-func init() {
- template.Must(configTmpl.Parse(sshd_config))
-
- for n, k := range map[string]*ssh.Signer{
- "ssh_host_ecdsa_key": &hostKeyECDSA,
- "ssh_host_rsa_key": &hostKeyRSA,
- "ssh_host_dsa_key": &hostKeyDSA,
- } {
- var err error
- *k, err = ssh.ParsePrivateKey([]byte(keys[n]))
- if err != nil {
- panic(fmt.Sprintf("ParsePrivateKey(%q): %v", n, err))
- }
- }
-
- var err error
- privateKey, err = ssh.ParsePrivateKey([]byte(testClientPrivateKey))
- if err != nil {
- panic(fmt.Sprintf("ParsePrivateKey: %v", err))
- }
-}
+var configTmpl = template.Must(template.New("").Parse(sshd_config))
type server struct {
t *testing.T
@@ -107,36 +80,44 @@
type storedHostKey struct {
// keys map from an algorithm string to binary key data.
keys map[string][]byte
+
+ // checkCount counts the Check calls. Used for testing
+ // rekeying.
+ checkCount int
}
func (k *storedHostKey) Add(key ssh.PublicKey) {
if k.keys == nil {
k.keys = map[string][]byte{}
}
- k.keys[key.PublicKeyAlgo()] = ssh.MarshalPublicKey(key)
+ k.keys[key.Type()] = key.Marshal()
}
-func (k *storedHostKey) Check(addr string, remote net.Addr, algo string, key []byte) error {
- if k.keys == nil || bytes.Compare(key, k.keys[algo]) != 0 {
+func (k *storedHostKey) Check(addr string, remote net.Addr, key ssh.PublicKey) error {
+ k.checkCount++
+ algo := key.Type()
+
+ if k.keys == nil || bytes.Compare(key.Marshal(), k.keys[algo]) != 0 {
return fmt.Errorf("host key mismatch. Got %q, want %q", key, k.keys[algo])
}
return nil
}
-func clientConfig() *ssh.ClientConfig {
- keyChecker := storedHostKey{}
- keyChecker.Add(hostKeyECDSA.PublicKey())
- keyChecker.Add(hostKeyRSA.PublicKey())
- keyChecker.Add(hostKeyDSA.PublicKey())
+func hostKeyDB() *storedHostKey {
+ keyChecker := &storedHostKey{}
+ keyChecker.Add(testPublicKeys["ecdsa"])
+ keyChecker.Add(testPublicKeys["rsa"])
+ keyChecker.Add(testPublicKeys["dsa"])
+ return keyChecker
+}
- kc := new(keychain)
- kc.keys = append(kc.keys, privateKey)
+func clientConfig() *ssh.ClientConfig {
config := &ssh.ClientConfig{
User: username(),
- Auth: []ssh.ClientAuth{
- ssh.ClientAuthKeyring(kc),
+ Auth: []ssh.AuthMethod{
+ ssh.PublicKeys(testSigners["user"]),
},
- HostKeyChecker: &keyChecker,
+ HostKeyCallback: hostKeyDB().Check,
}
return config
}
@@ -171,7 +152,7 @@
return c1.(*net.UnixConn), c2.(*net.UnixConn), nil
}
-func (s *server) TryDial(config *ssh.ClientConfig) (*ssh.ClientConn, error) {
+func (s *server) TryDial(config *ssh.ClientConfig) (*ssh.Client, error) {
sshd, err := exec.LookPath("sshd")
if err != nil {
s.t.Skipf("skipping test: %v", err)
@@ -197,10 +178,14 @@
s.t.Fatalf("s.cmd.Start: %v", err)
}
s.clientConn = c1
- return ssh.Client(c1, config)
+ conn, chans, reqs, err := ssh.NewClientConn(c1, "", config)
+ if err != nil {
+ return nil, err
+ }
+ return ssh.NewClient(conn, chans, reqs), nil
}
-func (s *server) Dial(config *ssh.ClientConfig) *ssh.ClientConn {
+func (s *server) Dial(config *ssh.ClientConfig) *ssh.Client {
conn, err := s.TryDial(config)
if err != nil {
s.t.Fail()
@@ -226,6 +211,17 @@
s.cleanup()
}
+func writeFile(path string, contents []byte) {
+ f, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600)
+ if err != nil {
+ panic(err)
+ }
+ defer f.Close()
+ if _, err := f.Write(contents); err != nil {
+ panic(err)
+ }
+}
+
// newServer returns a new mock ssh server.
func newServer(t *testing.T) *server {
dir, err := ioutil.TempDir("", "sshtest")
@@ -244,15 +240,10 @@
}
f.Close()
- for k, v := range keys {
- f, err := os.OpenFile(filepath.Join(dir, k), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600)
- if err != nil {
- t.Fatal(err)
- }
- if _, err := f.Write([]byte(v)); err != nil {
- t.Fatal(err)
- }
- f.Close()
+ for k, v := range testdata.PEMBytes {
+ filename := "id_" + k
+ writeFile(filepath.Join(dir, filename), v)
+ writeFile(filepath.Join(dir, filename+".pub"), ssh.MarshalAuthorizedKey(testPublicKeys[k]))
}
return &server{
@@ -265,32 +256,3 @@
},
}
}
-
-// keychain implements the ClientKeyring interface.
-type keychain struct {
- keys []ssh.Signer
-}
-
-func (k *keychain) Key(i int) (ssh.PublicKey, error) {
- if i < 0 || i >= len(k.keys) {
- return nil, nil
- }
- return k.keys[i].PublicKey(), nil
-}
-
-func (k *keychain) Sign(i int, rand io.Reader, data []byte) (sig []byte, err error) {
- return k.keys[i].Sign(rand, data)
-}
-
-func (k *keychain) loadPEM(file string) error {
- buf, err := ioutil.ReadFile(file)
- if err != nil {
- return err
- }
- key, err := ssh.ParsePrivateKey(buf)
- if err != nil {
- return err
- }
- k.keys = append(k.keys, key)
- return nil
-}
diff --git a/ssh/test/testdata_test.go b/ssh/test/testdata_test.go
new file mode 100644
index 0000000..7f50fbe
--- /dev/null
+++ b/ssh/test/testdata_test.go
@@ -0,0 +1,64 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// IMPLEMENTOR NOTE: To avoid a package loop, this file is in three places:
+// ssh/, ssh/agent, and ssh/test/. It should be kept in sync across all three
+// instances.
+
+package test
+
+import (
+ "crypto/rand"
+ "fmt"
+
+ "code.google.com/p/go.crypto/ssh"
+ "code.google.com/p/go.crypto/ssh/testdata"
+)
+
+var (
+ testPrivateKeys map[string]interface{}
+ testSigners map[string]ssh.Signer
+ testPublicKeys map[string]ssh.PublicKey
+)
+
+func init() {
+ var err error
+
+ n := len(testdata.PEMBytes)
+ testPrivateKeys = make(map[string]interface{}, n)
+ testSigners = make(map[string]ssh.Signer, n)
+ testPublicKeys = make(map[string]ssh.PublicKey, n)
+ for t, k := range testdata.PEMBytes {
+ testPrivateKeys[t], err = ssh.ParseRawPrivateKey(k)
+ if err != nil {
+ panic(fmt.Sprintf("Unable to parse test key %s: %v", t, err))
+ }
+ testSigners[t], err = ssh.NewSignerFromKey(testPrivateKeys[t])
+ if err != nil {
+ panic(fmt.Sprintf("Unable to create signer for test key %s: %v", t, err))
+ }
+ testPublicKeys[t] = testSigners[t].PublicKey()
+ }
+
+ // Create a cert and sign it for use in tests.
+ testCert := &ssh.Certificate{
+ Nonce: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil
+ ValidPrincipals: []string{"gopher1", "gopher2"}, // increases test coverage
+ ValidAfter: 0, // unix epoch
+ ValidBefore: ssh.CertTimeInfinity, // The end of currently representable time.
+ Reserved: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil
+ Key: testPublicKeys["ecdsa"],
+ SignatureKey: testPublicKeys["rsa"],
+ Permissions: ssh.Permissions{
+ CriticalOptions: map[string]string{},
+ Extensions: map[string]string{},
+ },
+ }
+ testCert.SignCert(rand.Reader, testSigners["rsa"])
+ testPrivateKeys["cert"] = testPrivateKeys["ecdsa"]
+ testSigners["cert"], err = ssh.NewCertSigner(testCert, testSigners["ecdsa"])
+ if err != nil {
+ panic(fmt.Sprintf("Unable to create certificate signer: %v", err))
+ }
+}