ssh: fix certificate authentication with OpenSSH 7.2-7.7

OpenSSH 7.2-7.7 advertises support for rsa-sha2-256 and rsa-sha2-512
in the "server-sig-algs" extension but doesn't support these
algorithms for certificate authentication, so if the server rejects
the key try to use the obtained algorithm as if "server-sig-algs" had
not been implemented.

Fixes golang/go#58371

Change-Id: Id49960d3dedd32a21e2c6c2689b1696e05398286
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/510155
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Run-TryBot: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Nicola Murino <nicola.murino@gmail.com>
diff --git a/ssh/client_auth.go b/ssh/client_auth.go
index 5c3bc25..34bf089 100644
--- a/ssh/client_auth.go
+++ b/ssh/client_auth.go
@@ -307,7 +307,10 @@
 	}
 	var methods []string
 	var errSigAlgo error
-	for _, signer := range signers {
+
+	origSignersLen := len(signers)
+	for idx := 0; idx < len(signers); idx++ {
+		signer := signers[idx]
 		pub := signer.PublicKey()
 		as, algo, err := pickSignatureAlgorithm(signer, extensions)
 		if err != nil && errSigAlgo == nil {
@@ -321,6 +324,21 @@
 		if err != nil {
 			return authFailure, nil, err
 		}
+		// OpenSSH 7.2-7.7 advertises support for rsa-sha2-256 and rsa-sha2-512
+		// in the "server-sig-algs" extension but doesn't support these
+		// algorithms for certificate authentication, so if the server rejects
+		// the key try to use the obtained algorithm as if "server-sig-algs" had
+		// not been implemented if supported from the algorithm signer.
+		if !ok && idx < origSignersLen && isRSACert(algo) && algo != CertAlgoRSAv01 {
+			if contains(as.Algorithms(), KeyAlgoRSA) {
+				// We retry using the compat algorithm after all signers have
+				// been tried normally.
+				signers = append(signers, &multiAlgorithmSigner{
+					AlgorithmSigner:     as,
+					supportedAlgorithms: []string{KeyAlgoRSA},
+				})
+			}
+		}
 		if !ok {
 			continue
 		}
diff --git a/ssh/common.go b/ssh/common.go
index dd2ab0d..7e9c2cb 100644
--- a/ssh/common.go
+++ b/ssh/common.go
@@ -127,6 +127,14 @@
 	return contains(algos, underlyingAlgo(algo))
 }
 
+func isRSACert(algo string) bool {
+	_, ok := certKeyAlgoNames[algo]
+	if !ok {
+		return false
+	}
+	return isRSA(algo)
+}
+
 // supportedPubKeyAuthAlgos specifies the supported client public key
 // authentication algorithms. Note that this doesn't include certificate types
 // since those use the underlying algorithm. This list is sent to the client if