Browse Source

crypto/ssh: allow client to specify host key algorithms.

Fixes golang/go#11722.

Change-Id: I4fa2a1db14050151f9269427ca35cf7ebd21440a
Reviewed-on: https://go-review.googlesource.com/12907
Reviewed-by: Adam Langley <agl@golang.org>
hanwen 10 years ago
parent
commit
2f3083f616
4 changed files with 76 additions and 2 deletions
  1. 7 0
      ssh/client.go
  2. 14 2
      ssh/handshake.go
  3. 1 0
      ssh/handshake_test.go
  4. 54 0
      ssh/session_test.go

+ 7 - 0
ssh/client.go

@@ -203,4 +203,11 @@ type ClientConfig struct {
 	// ClientVersion contains the version identification string that will
 	// be used for the connection. If empty, a reasonable default is used.
 	ClientVersion string
+
+	// HostKeyAlgorithms lists the key types that the client will
+	// accept from the server as host key, in order of
+	// preference. If empty, a reasonable default is used. Any
+	// string returned from PublicKey.Type method may be used, or
+	// any of the CertAlgoXxxx and KeyAlgoXxxx constants.
+	HostKeyAlgorithms []string
 }

+ 14 - 2
ssh/handshake.go

@@ -59,7 +59,14 @@ type handshakeTransport struct {
 	serverVersion []byte
 	clientVersion []byte
 
-	hostKeys []Signer // If hostKeys are given, we are the server.
+	// hostKeys is non-empty if we are the server. In that case,
+	// it contains all host keys that can be used to sign the
+	// connection.
+	hostKeys []Signer
+
+	// hostKeyAlgorithms is non-empty if we are the client. In that case,
+	// we accept these key types from the server as host key.
+	hostKeyAlgorithms []string
 
 	// On read error, incoming is closed, and readError is set.
 	incoming  chan []byte
@@ -98,6 +105,11 @@ func newClientTransport(conn keyingTransport, clientVersion, serverVersion []byt
 	t.dialAddress = dialAddr
 	t.remoteAddr = addr
 	t.hostKeyCallback = config.HostKeyCallback
+	if config.HostKeyAlgorithms != nil {
+		t.hostKeyAlgorithms = config.HostKeyAlgorithms
+	} else {
+		t.hostKeyAlgorithms = supportedHostKeyAlgos
+	}
 	go t.readLoop()
 	return t
 }
@@ -234,7 +246,7 @@ func (t *handshakeTransport) sendKexInitLocked() (*kexInitMsg, []byte, error) {
 				msg.ServerHostKeyAlgos, k.PublicKey().Type())
 		}
 	} else {
-		msg.ServerHostKeyAlgos = supportedHostKeyAlgos
+		msg.ServerHostKeyAlgos = t.hostKeyAlgorithms
 	}
 	packet := Marshal(msg)
 

+ 1 - 0
ssh/handshake_test.go

@@ -69,6 +69,7 @@ func handshakePair(clientConf *ClientConfig, addr string) (client *handshakeTran
 
 	serverConf := &ServerConfig{}
 	serverConf.AddHostKey(testSigners["ecdsa"])
+	serverConf.AddHostKey(testSigners["rsa"])
 	serverConf.SetDefaults()
 	server = newServerTransport(trS, v, v, serverConf)
 

+ 54 - 0
ssh/session_test.go

@@ -718,3 +718,57 @@ func TestInvalidServerConfiguration(t *testing.T) {
 		t.Fatalf("NewServerConn attempted to Read() from Conn while configuration is missing authentication method")
 	}
 }
+
+func TestHostKeyAlgorithms(t *testing.T) {
+	serverConf := &ServerConfig{
+		NoClientAuth: true,
+	}
+	serverConf.AddHostKey(testSigners["rsa"])
+	serverConf.AddHostKey(testSigners["ecdsa"])
+
+	connect := func(clientConf *ClientConfig, want string) {
+		var alg string
+		clientConf.HostKeyCallback = func(h string, a net.Addr, key PublicKey) error {
+			alg = key.Type()
+			return nil
+		}
+		c1, c2, err := netPipe()
+		if err != nil {
+			t.Fatalf("netPipe: %v", err)
+		}
+		defer c1.Close()
+		defer c2.Close()
+
+		go NewServerConn(c1, serverConf)
+		_, _, _, err = NewClientConn(c2, "", clientConf)
+		if err != nil {
+			t.Fatalf("NewClientConn: %v", err)
+		}
+		if alg != want {
+			t.Errorf("selected key algorithm %s, want %s", alg, want)
+		}
+	}
+
+	// By default, we get the preferred algorithm, which is ECDSA 256.
+
+	clientConf := &ClientConfig{}
+	connect(clientConf, KeyAlgoECDSA256)
+
+	// Client asks for RSA explicitly.
+	clientConf.HostKeyAlgorithms = []string{KeyAlgoRSA}
+	connect(clientConf, KeyAlgoRSA)
+
+	c1, c2, err := netPipe()
+	if err != nil {
+		t.Fatalf("netPipe: %v", err)
+	}
+	defer c1.Close()
+	defer c2.Close()
+
+	go NewServerConn(c1, serverConf)
+	clientConf.HostKeyAlgorithms = []string{"nonexistent-hostkey-algo"}
+	_, _, _, err = NewClientConn(c2, "", clientConf)
+	if err == nil {
+		t.Fatal("succeeded connecting with unknown hostkey algorithm")
+	}
+}