ssh/agent: fix non-RSA certificates

The type of ssh.PublicKey.Type can be a certificate type, while the
algorithm passed to SignWithAlgorithm is going to be an underlying
algorithm.

Fixes golang/go#52185

Change-Id: I0f7c46defa83d1fd64a3c1e861734650b20cca21
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/404614
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
Run-TryBot: Filippo Valsorda <filippo@golang.org>
diff --git a/ssh/agent/client.go b/ssh/agent/client.go
index dbc79d5..3c4d18a 100644
--- a/ssh/agent/client.go
+++ b/ssh/agent/client.go
@@ -772,7 +772,7 @@
 }
 
 func (s *agentKeyringSigner) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*ssh.Signature, error) {
-	if algorithm == "" || algorithm == s.pub.Type() {
+	if algorithm == "" || algorithm == underlyingAlgo(s.pub.Type()) {
 		return s.Sign(rand, data)
 	}
 
@@ -791,6 +791,33 @@
 
 var _ ssh.AlgorithmSigner = &agentKeyringSigner{}
 
+// certKeyAlgoNames is a mapping from known certificate algorithm names to the
+// corresponding public key signature algorithm.
+//
+// This map must be kept in sync with the one in certs.go.
+var certKeyAlgoNames = map[string]string{
+	ssh.CertAlgoRSAv01:        ssh.KeyAlgoRSA,
+	ssh.CertAlgoRSASHA256v01:  ssh.KeyAlgoRSASHA256,
+	ssh.CertAlgoRSASHA512v01:  ssh.KeyAlgoRSASHA512,
+	ssh.CertAlgoDSAv01:        ssh.KeyAlgoDSA,
+	ssh.CertAlgoECDSA256v01:   ssh.KeyAlgoECDSA256,
+	ssh.CertAlgoECDSA384v01:   ssh.KeyAlgoECDSA384,
+	ssh.CertAlgoECDSA521v01:   ssh.KeyAlgoECDSA521,
+	ssh.CertAlgoSKECDSA256v01: ssh.KeyAlgoSKECDSA256,
+	ssh.CertAlgoED25519v01:    ssh.KeyAlgoED25519,
+	ssh.CertAlgoSKED25519v01:  ssh.KeyAlgoSKED25519,
+}
+
+// underlyingAlgo returns the signature algorithm associated with algo (which is
+// an advertised or negotiated public key or host key algorithm). These are
+// usually the same, except for certificate algorithms.
+func underlyingAlgo(algo string) string {
+	if a, ok := certKeyAlgoNames[algo]; ok {
+		return a
+	}
+	return algo
+}
+
 // Calls an extension method. It is up to the agent implementation as to whether or not
 // any particular extension is supported and may always return an error. Because the
 // type of the response is up to the implementation, this returns the bytes of the
diff --git a/ssh/certs.go b/ssh/certs.go
index a69e224..4600c20 100644
--- a/ssh/certs.go
+++ b/ssh/certs.go
@@ -460,6 +460,8 @@
 
 // certKeyAlgoNames is a mapping from known certificate algorithm names to the
 // corresponding public key signature algorithm.
+//
+// This map must be kept in sync with the one in agent/client.go.
 var certKeyAlgoNames = map[string]string{
 	CertAlgoRSAv01:        KeyAlgoRSA,
 	CertAlgoRSASHA256v01:  KeyAlgoRSASHA256,