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>
diff --git a/ssh/client.go b/ssh/client.go
index 72bd27f..0b9fbe5 100644
--- a/ssh/client.go
+++ b/ssh/client.go
@@ -203,4 +203,11 @@
// 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
}
diff --git a/ssh/handshake.go b/ssh/handshake.go
index 4acc1a0..50461be 100644
--- a/ssh/handshake.go
+++ b/ssh/handshake.go
@@ -59,7 +59,14 @@
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 @@
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 @@
msg.ServerHostKeyAlgos, k.PublicKey().Type())
}
} else {
- msg.ServerHostKeyAlgos = supportedHostKeyAlgos
+ msg.ServerHostKeyAlgos = t.hostKeyAlgorithms
}
packet := Marshal(msg)
diff --git a/ssh/handshake_test.go b/ssh/handshake_test.go
index f690627..3202371 100644
--- a/ssh/handshake_test.go
+++ b/ssh/handshake_test.go
@@ -69,6 +69,7 @@
serverConf := &ServerConfig{}
serverConf.AddHostKey(testSigners["ecdsa"])
+ serverConf.AddHostKey(testSigners["rsa"])
serverConf.SetDefaults()
server = newServerTransport(trS, v, v, serverConf)
diff --git a/ssh/session_test.go b/ssh/session_test.go
index 7ce44f5..f7f0f76 100644
--- a/ssh/session_test.go
+++ b/ssh/session_test.go
@@ -718,3 +718,57 @@
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")
+ }
+}