浏览代码

go.crypto/ssh: never negotiate unsupported ciphers

Fixes golang/go#4285.

Adding a new cipher that is supported by the remote end, but not supported by our client causes that cipher to be considered a valid candidate. This fails later in setupKeys when there is no cipherModes configuration.

In summary, unsupported ciphers cannot be willed into existence by adding them to the client config. This change enforces this.

R=golang-dev, agl
CC=golang-dev
https://golang.org/cl/6780047
Dave Cheney 13 年之前
父节点
当前提交
1582bf0781
共有 6 个文件被更改,包括 73 次插入29 次删除
  1. 3 0
      ssh/cipher.go
  2. 20 0
      ssh/client_auth_test.go
  3. 16 5
      ssh/common.go
  4. 6 6
      ssh/test/session_test.go
  5. 1 1
      ssh/test/tcpip_test.go
  6. 27 17
      ssh/test/test_unix_test.go

+ 3 - 0
ssh/cipher.go

@@ -74,6 +74,9 @@ var DefaultCipherOrder = []string{
 	"arcfour256", "arcfour128",
 	"arcfour256", "arcfour128",
 }
 }
 
 
+// cipherModes documents properties of supported ciphers. Ciphers not included
+// are not supported and will not be negotiated, even if explicitly requested in
+// ClientConfig.Crypto.Ciphers.
 var cipherModes = map[string]*cipherMode{
 var cipherModes = map[string]*cipherMode{
 	// Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms
 	// Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms
 	// are defined in the order specified in the RFC.
 	// are defined in the order specified in the RFC.

+ 20 - 0
ssh/client_auth_test.go

@@ -276,3 +276,23 @@ func TestClientHMAC(t *testing.T) {
 		c.Close()
 		c.Close()
 	}
 	}
 }
 }
+
+// issue 4285.
+func TestClientUnsupportedCipher(t *testing.T) {
+	kc := new(keychain)
+	kc.keys = append(kc.keys, rsakey)
+	config := &ClientConfig{
+		User: "testuser",
+		Auth: []ClientAuth{
+			ClientAuthKeyring(kc),
+		},
+		Crypto: CryptoConfig{
+			Ciphers: []string{"aes128-cbc"}, // not currently supported
+		},
+	}
+	c, err := Dial("tcp", newMockAuthServer(t), config)
+	if err == nil {
+		t.Errorf("expected no ciphers in common")
+		c.Close()
+	}
+}

+ 16 - 5
ssh/common.go

@@ -8,8 +8,8 @@ import (
 	"crypto/dsa"
 	"crypto/dsa"
 	"crypto/rsa"
 	"crypto/rsa"
 	"errors"
 	"errors"
+	"fmt"
 	"math/big"
 	"math/big"
-	"strconv"
 	"sync"
 	"sync"
 )
 )
 
 
@@ -77,7 +77,7 @@ type UnexpectedMessageError struct {
 }
 }
 
 
 func (u UnexpectedMessageError) Error() string {
 func (u UnexpectedMessageError) Error() string {
-	return "ssh: unexpected message type " + strconv.Itoa(int(u.got)) + " (expected " + strconv.Itoa(int(u.expected)) + ")"
+	return fmt.Sprintf("ssh: unexpected message type %d (expected %d)", u.got, u.expected)
 }
 }
 
 
 // ParseError results from a malformed SSH message.
 // ParseError results from a malformed SSH message.
@@ -86,7 +86,7 @@ type ParseError struct {
 }
 }
 
 
 func (p ParseError) Error() string {
 func (p ParseError) Error() string {
-	return "ssh: parse error in message type " + strconv.Itoa(int(p.msgType))
+	return fmt.Sprintf("ssh: parse error in message type %d", p.msgType)
 }
 }
 
 
 type handshakeMagics struct {
 type handshakeMagics struct {
@@ -102,7 +102,18 @@ func findCommonAlgorithm(clientAlgos []string, serverAlgos []string) (commonAlgo
 			}
 			}
 		}
 		}
 	}
 	}
+	return
+}
 
 
+func findCommonCipher(clientCiphers []string, serverCiphers []string) (commonCipher string, ok bool) {
+	for _, clientCipher := range clientCiphers {
+		for _, serverCipher := range serverCiphers {
+			// reject the cipher if we have no cipherModes definition
+			if clientCipher == serverCipher && cipherModes[clientCipher] != nil {
+				return clientCipher, true
+			}
+		}
+	}
 	return
 	return
 }
 }
 
 
@@ -117,12 +128,12 @@ func findAgreedAlgorithms(transport *transport, clientKexInit, serverKexInit *ke
 		return
 		return
 	}
 	}
 
 
-	transport.writer.cipherAlgo, ok = findCommonAlgorithm(clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer)
+	transport.writer.cipherAlgo, ok = findCommonCipher(clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer)
 	if !ok {
 	if !ok {
 		return
 		return
 	}
 	}
 
 
-	transport.reader.cipherAlgo, ok = findCommonAlgorithm(clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient)
+	transport.reader.cipherAlgo, ok = findCommonCipher(clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient)
 	if !ok {
 	if !ok {
 		return
 		return
 	}
 	}

+ 6 - 6
ssh/test/session_test.go

@@ -20,7 +20,7 @@ import (
 func TestRunCommandSuccess(t *testing.T) {
 func TestRunCommandSuccess(t *testing.T) {
 	server := newServer(t)
 	server := newServer(t)
 	defer server.Shutdown()
 	defer server.Shutdown()
-	conn := server.Dial()
+	conn := server.Dial(clientConfig())
 	defer conn.Close()
 	defer conn.Close()
 
 
 	session, err := conn.NewSession()
 	session, err := conn.NewSession()
@@ -37,7 +37,7 @@ func TestRunCommandSuccess(t *testing.T) {
 func TestRunCommandFailed(t *testing.T) {
 func TestRunCommandFailed(t *testing.T) {
 	server := newServer(t)
 	server := newServer(t)
 	defer server.Shutdown()
 	defer server.Shutdown()
-	conn := server.Dial()
+	conn := server.Dial(clientConfig())
 	defer conn.Close()
 	defer conn.Close()
 
 
 	session, err := conn.NewSession()
 	session, err := conn.NewSession()
@@ -54,7 +54,7 @@ func TestRunCommandFailed(t *testing.T) {
 func TestRunCommandWeClosed(t *testing.T) {
 func TestRunCommandWeClosed(t *testing.T) {
 	server := newServer(t)
 	server := newServer(t)
 	defer server.Shutdown()
 	defer server.Shutdown()
-	conn := server.Dial()
+	conn := server.Dial(clientConfig())
 	defer conn.Close()
 	defer conn.Close()
 
 
 	session, err := conn.NewSession()
 	session, err := conn.NewSession()
@@ -74,7 +74,7 @@ func TestRunCommandWeClosed(t *testing.T) {
 func TestFuncLargeRead(t *testing.T) {
 func TestFuncLargeRead(t *testing.T) {
 	server := newServer(t)
 	server := newServer(t)
 	defer server.Shutdown()
 	defer server.Shutdown()
-	conn := server.Dial()
+	conn := server.Dial(clientConfig())
 	defer conn.Close()
 	defer conn.Close()
 
 
 	session, err := conn.NewSession()
 	session, err := conn.NewSession()
@@ -106,7 +106,7 @@ func TestFuncLargeRead(t *testing.T) {
 func TestInvalidTerminalMode(t *testing.T) {
 func TestInvalidTerminalMode(t *testing.T) {
 	server := newServer(t)
 	server := newServer(t)
 	defer server.Shutdown()
 	defer server.Shutdown()
-	conn := server.Dial()
+	conn := server.Dial(clientConfig())
 	defer conn.Close()
 	defer conn.Close()
 
 
 	session, err := conn.NewSession()
 	session, err := conn.NewSession()
@@ -123,7 +123,7 @@ func TestInvalidTerminalMode(t *testing.T) {
 func TestValidTerminalMode(t *testing.T) {
 func TestValidTerminalMode(t *testing.T) {
 	server := newServer(t)
 	server := newServer(t)
 	defer server.Shutdown()
 	defer server.Shutdown()
-	conn := server.Dial()
+	conn := server.Dial(clientConfig())
 	defer conn.Close()
 	defer conn.Close()
 
 
 	session, err := conn.NewSession()
 	session, err := conn.NewSession()

+ 1 - 1
ssh/test/tcpip_test.go

@@ -27,7 +27,7 @@ func TestTCPIPHTTPS(t *testing.T) {
 func doTest(t *testing.T, url string) {
 func doTest(t *testing.T, url string) {
 	server := newServer(t)
 	server := newServer(t)
 	defer server.Shutdown()
 	defer server.Shutdown()
-	conn := server.Dial()
+	conn := server.Dial(clientConfig())
 	defer conn.Close()
 	defer conn.Close()
 
 
 	tr := &http.Transport{
 	tr := &http.Transport{

+ 27 - 17
ssh/test/test_unix_test.go

@@ -9,6 +9,7 @@ package test
 // functional test harness for unix.
 // functional test harness for unix.
 
 
 import (
 import (
+	"bytes"
 	"crypto"
 	"crypto"
 	"crypto/dsa"
 	"crypto/dsa"
 	"crypto/rsa"
 	"crypto/rsa"
@@ -40,7 +41,7 @@ Pidfile {{.Dir}}/sshd.pid
 KeyRegenerationInterval 3600
 KeyRegenerationInterval 3600
 ServerKeyBits 768
 ServerKeyBits 768
 SyslogFacility AUTH
 SyslogFacility AUTH
-LogLevel INFO
+LogLevel DEBUG2
 LoginGraceTime 120
 LoginGraceTime 120
 PermitRootLogin no
 PermitRootLogin no
 StrictModes no
 StrictModes no
@@ -146,9 +147,26 @@ type server struct {
 	cleanup    func() // executed during Shutdown
 	cleanup    func() // executed during Shutdown
 	configfile string
 	configfile string
 	cmd        *exec.Cmd
 	cmd        *exec.Cmd
+	output     bytes.Buffer // holds stderr from sshd process
 }
 }
 
 
-func (s *server) Dial() *ssh.ClientConn {
+func clientConfig() *ssh.ClientConfig {
+	user, err := user.Current()
+	if err != nil {
+		panic(err)
+	}
+	kc := new(keychain)
+	kc.keys = append(kc.keys, rsakey)
+	config := &ssh.ClientConfig{
+		User: user.Username,
+		Auth: []ssh.ClientAuth{
+			ssh.ClientAuthKeyring(kc),
+		},
+	}
+	return config
+}
+
+func (s *server) Dial(config *ssh.ClientConfig) *ssh.ClientConn {
 	s.cmd = exec.Command("sshd", "-f", s.configfile, "-i")
 	s.cmd = exec.Command("sshd", "-f", s.configfile, "-i")
 	stdin, err := s.cmd.StdinPipe()
 	stdin, err := s.cmd.StdinPipe()
 	if err != nil {
 	if err != nil {
@@ -158,28 +176,16 @@ func (s *server) Dial() *ssh.ClientConn {
 	if err != nil {
 	if err != nil {
 		s.t.Fatal(err)
 		s.t.Fatal(err)
 	}
 	}
-	s.cmd.Stderr = os.Stderr
+	s.cmd.Stderr = os.Stderr // &s.output
 	err = s.cmd.Start()
 	err = s.cmd.Start()
 	if err != nil {
 	if err != nil {
+		s.t.FailNow()
 		s.Shutdown()
 		s.Shutdown()
 		s.t.Fatal(err)
 		s.t.Fatal(err)
 	}
 	}
-
-	user, err := user.Current()
-	if err != nil {
-		s.Shutdown()
-		s.t.Fatal(err)
-	}
-	kc := new(keychain)
-	kc.keys = append(kc.keys, rsakey)
-	config := &ssh.ClientConfig{
-		User: user.Username,
-		Auth: []ssh.ClientAuth{
-			ssh.ClientAuthKeyring(kc),
-		},
-	}
 	conn, err := ssh.Client(&client{stdin, stdout}, config)
 	conn, err := ssh.Client(&client{stdin, stdout}, config)
 	if err != nil {
 	if err != nil {
+		s.t.FailNow()
 		s.Shutdown()
 		s.Shutdown()
 		s.t.Fatal(err)
 		s.t.Fatal(err)
 	}
 	}
@@ -190,6 +196,10 @@ func (s *server) Shutdown() {
 	if err := s.cmd.Process.Kill(); err != nil {
 	if err := s.cmd.Process.Kill(); err != nil {
 		s.t.Error(err)
 		s.t.Error(err)
 	}
 	}
+	if s.t.Failed() {
+		// log any output from sshd process
+		s.t.Log(s.output.String())
+	}
 	s.cleanup()
 	s.cleanup()
 }
 }