ssh: ignore invalid MACs and KEXs just like we do for ciphers

Tighter validation could cause backwards incompatibility issues, eg
configurations with valid and invalid MACs, KEXs, ciphers currently work
if a supported algorithm is negotiated and that's also the scenario of
removing support for an existing algorithm.

Fixes golang/go#39397

Change-Id: If90253ba89e1d8f732cc1e1c3d24fe0a1e2dac71
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/512175
Run-TryBot: Han-Wen Nienhuys <hanwen@google.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Han-Wen Nienhuys <hanwen@google.com>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
Reviewed-by: David Chase <drchase@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/ssh/client_test.go b/ssh/client_test.go
index 2814755..c114573 100644
--- a/ssh/client_test.go
+++ b/ssh/client_test.go
@@ -254,3 +254,93 @@
 		})
 	}
 }
+
+func TestUnsupportedAlgorithm(t *testing.T) {
+	for _, tt := range []struct {
+		name      string
+		config    Config
+		wantError string
+	}{
+		{
+			"unsupported KEX",
+			Config{
+				KeyExchanges: []string{"unsupported"},
+			},
+			"no common algorithm",
+		},
+		{
+			"unsupported and supported KEXs",
+			Config{
+				KeyExchanges: []string{"unsupported", kexAlgoCurve25519SHA256},
+			},
+			"",
+		},
+		{
+			"unsupported cipher",
+			Config{
+				Ciphers: []string{"unsupported"},
+			},
+			"no common algorithm",
+		},
+		{
+			"unsupported and supported ciphers",
+			Config{
+				Ciphers: []string{"unsupported", chacha20Poly1305ID},
+			},
+			"",
+		},
+		{
+			"unsupported MAC",
+			Config{
+				MACs: []string{"unsupported"},
+				// MAC is used for non AAED ciphers.
+				Ciphers: []string{"aes256-ctr"},
+			},
+			"no common algorithm",
+		},
+		{
+			"unsupported and supported MACs",
+			Config{
+				MACs: []string{"unsupported", "hmac-sha2-256-etm@openssh.com"},
+				// MAC is used for non AAED ciphers.
+				Ciphers: []string{"aes256-ctr"},
+			},
+			"",
+		},
+	} {
+		t.Run(tt.name, func(t *testing.T) {
+			c1, c2, err := netPipe()
+			if err != nil {
+				t.Fatalf("netPipe: %v", err)
+			}
+			defer c1.Close()
+			defer c2.Close()
+
+			serverConf := &ServerConfig{
+				Config: tt.config,
+				PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
+					return &Permissions{}, nil
+				},
+			}
+			serverConf.AddHostKey(testSigners["rsa"])
+			go NewServerConn(c1, serverConf)
+
+			clientConf := &ClientConfig{
+				User:   "testuser",
+				Config: tt.config,
+				Auth: []AuthMethod{
+					Password("testpw"),
+				},
+				HostKeyCallback: InsecureIgnoreHostKey(),
+			}
+			_, _, _, err = NewClientConn(c2, "", clientConf)
+			if err != nil {
+				if tt.wantError == "" || !strings.Contains(err.Error(), tt.wantError) {
+					t.Errorf("%s: got error %q, missing %q", tt.name, err.Error(), tt.wantError)
+				}
+			} else if tt.wantError != "" {
+				t.Errorf("%s: succeeded, but want error string %q", tt.name, tt.wantError)
+			}
+		})
+	}
+}
diff --git a/ssh/common.go b/ssh/common.go
index 44f71de..3862ec8 100644
--- a/ssh/common.go
+++ b/ssh/common.go
@@ -269,16 +269,16 @@
 	// unspecified, a size suitable for the chosen cipher is used.
 	RekeyThreshold uint64
 
-	// The allowed key exchanges algorithms. If unspecified then a
-	// default set of algorithms is used.
+	// The allowed key exchanges algorithms. If unspecified then a default set
+	// of algorithms is used. Unsupported values are silently ignored.
 	KeyExchanges []string
 
-	// The allowed cipher algorithms. If unspecified then a sensible
-	// default is used.
+	// The allowed cipher algorithms. If unspecified then a sensible default is
+	// used. Unsupported values are silently ignored.
 	Ciphers []string
 
-	// The allowed MAC algorithms. If unspecified then a sensible default
-	// is used.
+	// The allowed MAC algorithms. If unspecified then a sensible default is
+	// used. Unsupported values are silently ignored.
 	MACs []string
 }
 
@@ -295,7 +295,7 @@
 	var ciphers []string
 	for _, c := range c.Ciphers {
 		if cipherModes[c] != nil {
-			// reject the cipher if we have no cipherModes definition
+			// Ignore the cipher if we have no cipherModes definition.
 			ciphers = append(ciphers, c)
 		}
 	}
@@ -304,10 +304,26 @@
 	if c.KeyExchanges == nil {
 		c.KeyExchanges = preferredKexAlgos
 	}
+	var kexs []string
+	for _, k := range c.KeyExchanges {
+		if kexAlgoMap[k] != nil {
+			// Ignore the KEX if we have no kexAlgoMap definition.
+			kexs = append(kexs, k)
+		}
+	}
+	c.KeyExchanges = kexs
 
 	if c.MACs == nil {
 		c.MACs = supportedMACs
 	}
+	var macs []string
+	for _, m := range c.MACs {
+		if macModes[m] != nil {
+			// Ignore the MAC if we have no macModes definition.
+			macs = append(macs, m)
+		}
+	}
+	c.MACs = macs
 
 	if c.RekeyThreshold == 0 {
 		// cipher specific default