From 35b918f574464eeca89fcf0d5f3a07a58a4b5a38 Mon Sep 17 00:00:00 2001
From: zeripath <art27@cantab.net>
Date: Wed, 20 Oct 2021 20:55:33 +0100
Subject: [PATCH] Offer rsa-sha2-512 and rsa-sha2-256 algorithms in internal
 SSH (#17281)

* Offer rsa-sha2-512 and rsa-sha2-256 algorithms in internal SSH

There is a subtle bug in the SSH library x/crypto/ssh which makes the incorrect
assumption that the public key type is the same as the signature algorithm type.

This means that only ssh-rsa signatures are offered by default.

This PR adds a workaround around this problem.

Fix #17175

Signed-off-by: Andrew Thornton <art27@cantab.net>

* as per review

Signed-off-by: Andrew Thornton <art27@cantab.net>
---
 modules/ssh/ssh.go | 56 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 56 insertions(+)

diff --git a/modules/ssh/ssh.go b/modules/ssh/ssh.go
index efe9525345..909ed32c5e 100644
--- a/modules/ssh/ssh.go
+++ b/modules/ssh/ssh.go
@@ -316,10 +316,66 @@ func Listen(host string, port int, ciphers []string, keyExchanges []string, macs
 		}
 	}
 
+	// Workaround slightly broken behaviour in x/crypto/ssh/handshake.go:458-463
+	//
+	// Fundamentally the issue here is that HostKeyAlgos make the incorrect assumption
+	// that the PublicKey().Type() matches the signature algorithm.
+	//
+	// Therefore we need to add duplicates for the RSA with different signing algorithms.
+	signers := make([]ssh.Signer, 0, len(srv.HostSigners))
+	for _, signer := range srv.HostSigners {
+		if signer.PublicKey().Type() == "ssh-rsa" {
+			signers = append(signers,
+				&wrapSigner{
+					Signer:    signer,
+					algorithm: gossh.SigAlgoRSASHA2512,
+				},
+				&wrapSigner{
+					Signer:    signer,
+					algorithm: gossh.SigAlgoRSASHA2256,
+				},
+			)
+		}
+		signers = append(signers, signer)
+	}
+	srv.HostSigners = signers
+
 	go listen(&srv)
 
 }
 
+// wrapSigner wraps a signer and overrides its public key type with the provided algorithm
+type wrapSigner struct {
+	ssh.Signer
+	algorithm string
+}
+
+// PublicKey returns an associated PublicKey instance.
+func (s *wrapSigner) PublicKey() gossh.PublicKey {
+	return &wrapPublicKey{
+		PublicKey: s.Signer.PublicKey(),
+		algorithm: s.algorithm,
+	}
+}
+
+// Sign returns raw signature for the given data. This method
+// will apply the hash specified for the keytype to the data using
+// the algorithm assigned for this key
+func (s *wrapSigner) Sign(rand io.Reader, data []byte) (*gossh.Signature, error) {
+	return s.Signer.(gossh.AlgorithmSigner).SignWithAlgorithm(rand, data, s.algorithm)
+}
+
+// wrapPublicKey wraps a PublicKey and overrides its type
+type wrapPublicKey struct {
+	gossh.PublicKey
+	algorithm string
+}
+
+// Type returns the algorithm
+func (k *wrapPublicKey) Type() string {
+	return k.algorithm
+}
+
 // GenKeyPair make a pair of public and private keys for SSH access.
 // Public key is encoded in the format for inclusion in an OpenSSH authorized_keys file.
 // Private Key generated is PEM encoded