ssh: support RSA SHA-2 (RFC8332) signatures

This change adds support for RSA SHA-2 based signatures for host keys and certificates. It also switches the default certificate signature algorithm for RSA to use SHA-512. This is implemented by treating ssh.Signer specially when the key type is `ssh-rsa` by also allowing SHA-256 and SHA-512 signatures.

Fixes golang/go#37278

Change-Id: I2ee1ac4ae4c9c1de441a2d6cf1e806357ef18910
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/220037
Trust: Jason A. Donenfeld <Jason@zx2c4.com>
Run-TryBot: Jason A. Donenfeld <Jason@zx2c4.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Jason A. Donenfeld <Jason@zx2c4.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
diff --git a/ssh/certs.go b/ssh/certs.go
index 916c840..6605bf6 100644
--- a/ssh/certs.go
+++ b/ssh/certs.go
@@ -14,7 +14,7 @@
 	"time"
 )
 
-// These constants from [PROTOCOL.certkeys] represent the algorithm names
+// These constants from [PROTOCOL.certkeys] represent the key algorithm names
 // for certificate types supported by this package.
 const (
 	CertAlgoRSAv01        = "ssh-rsa-cert-v01@openssh.com"
@@ -27,6 +27,14 @@
 	CertAlgoSKED25519v01  = "sk-ssh-ed25519-cert-v01@openssh.com"
 )
 
+// These constants from [PROTOCOL.certkeys] represent additional signature
+// algorithm names for certificate types supported by this package.
+const (
+	CertSigAlgoRSAv01        = "ssh-rsa-cert-v01@openssh.com"
+	CertSigAlgoRSASHA2256v01 = "rsa-sha2-256-cert-v01@openssh.com"
+	CertSigAlgoRSASHA2512v01 = "rsa-sha2-512-cert-v01@openssh.com"
+)
+
 // Certificate types distinguish between host and user
 // certificates. The values can be set in the CertType field of
 // Certificate.
@@ -423,6 +431,12 @@
 	}
 	c.SignatureKey = authority.PublicKey()
 
+	if v, ok := authority.(AlgorithmSigner); ok {
+		if v.PublicKey().Type() == KeyAlgoRSA {
+			authority = &rsaSigner{v, SigAlgoRSASHA2512}
+		}
+	}
+
 	sig, err := authority.Sign(rand, c.bytesForSigning())
 	if err != nil {
 		return err
@@ -431,8 +445,14 @@
 	return nil
 }
 
+// certAlgoNames includes a mapping from signature algorithms to the
+// corresponding certificate signature algorithm. When a key type (such
+// as ED25516) is associated with only one algorithm, the KeyAlgo
+// constant is used instead of the SigAlgo.
 var certAlgoNames = map[string]string{
-	KeyAlgoRSA:        CertAlgoRSAv01,
+	SigAlgoRSA:        CertSigAlgoRSAv01,
+	SigAlgoRSASHA2256: CertSigAlgoRSASHA2256v01,
+	SigAlgoRSASHA2512: CertSigAlgoRSASHA2512v01,
 	KeyAlgoDSA:        CertAlgoDSAv01,
 	KeyAlgoECDSA256:   CertAlgoECDSA256v01,
 	KeyAlgoECDSA384:   CertAlgoECDSA384v01,
diff --git a/ssh/certs_test.go b/ssh/certs_test.go
index c8e7cf5..bae7f7e 100644
--- a/ssh/certs_test.go
+++ b/ssh/certs_test.go
@@ -9,12 +9,12 @@
 	"crypto/ecdsa"
 	"crypto/elliptic"
 	"crypto/rand"
+	"fmt"
+	"io"
 	"net"
 	"reflect"
 	"testing"
 	"time"
-
-	"golang.org/x/crypto/ssh/testdata"
 )
 
 // Cert generated by ssh-keygen 6.0p1 Debian-4.
@@ -226,53 +226,33 @@
 	}
 }
 
+type legacyRSASigner struct {
+	Signer
+}
+
+func (s *legacyRSASigner) Sign(rand io.Reader, data []byte) (*Signature, error) {
+	v, ok := s.Signer.(AlgorithmSigner)
+	if !ok {
+		return nil, fmt.Errorf("invalid signer")
+	}
+	return v.SignWithAlgorithm(rand, data, SigAlgoRSA)
+}
+
 func TestCertTypes(t *testing.T) {
 	var testVars = []struct {
-		name string
-		keys func() Signer
+		name   string
+		signer Signer
+		algo   string
 	}{
-		{
-			name: CertAlgoECDSA256v01,
-			keys: func() Signer {
-				s, _ := ParsePrivateKey(testdata.PEMBytes["ecdsap256"])
-				return s
-			},
-		},
-		{
-			name: CertAlgoECDSA384v01,
-			keys: func() Signer {
-				s, _ := ParsePrivateKey(testdata.PEMBytes["ecdsap384"])
-				return s
-			},
-		},
-		{
-			name: CertAlgoECDSA521v01,
-			keys: func() Signer {
-				s, _ := ParsePrivateKey(testdata.PEMBytes["ecdsap521"])
-				return s
-			},
-		},
-		{
-			name: CertAlgoED25519v01,
-			keys: func() Signer {
-				s, _ := ParsePrivateKey(testdata.PEMBytes["ed25519"])
-				return s
-			},
-		},
-		{
-			name: CertAlgoRSAv01,
-			keys: func() Signer {
-				s, _ := ParsePrivateKey(testdata.PEMBytes["rsa"])
-				return s
-			},
-		},
-		{
-			name: CertAlgoDSAv01,
-			keys: func() Signer {
-				s, _ := ParsePrivateKey(testdata.PEMBytes["dsa"])
-				return s
-			},
-		},
+		{CertAlgoECDSA256v01, testSigners["ecdsap256"], ""},
+		{CertAlgoECDSA384v01, testSigners["ecdsap384"], ""},
+		{CertAlgoECDSA521v01, testSigners["ecdsap521"], ""},
+		{CertAlgoED25519v01, testSigners["ed25519"], ""},
+		{CertAlgoRSAv01, testSigners["rsa"], SigAlgoRSASHA2512},
+		{CertAlgoRSAv01, &legacyRSASigner{testSigners["rsa"]}, SigAlgoRSA},
+		{CertAlgoRSAv01, testSigners["rsa-sha2-256"], SigAlgoRSASHA2512},
+		{CertAlgoRSAv01, testSigners["rsa-sha2-512"], SigAlgoRSASHA2512},
+		{CertAlgoDSAv01, testSigners["dsa"], ""},
 	}
 
 	k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
@@ -304,7 +284,7 @@
 
 			go NewServerConn(c1, conf)
 
-			priv := m.keys()
+			priv := m.signer
 			if err != nil {
 				t.Fatalf("error generating ssh pubkey: %v", err)
 			}
@@ -320,6 +300,10 @@
 				t.Fatalf("error generating cert signer: %v", err)
 			}
 
+			if m.algo != "" && cert.Signature.Format != m.algo {
+				t.Errorf("expected %q signature format, got %q", m.algo, cert.Signature.Format)
+			}
+
 			config := &ClientConfig{
 				User:            "user",
 				HostKeyCallback: func(h string, r net.Addr, k PublicKey) error { return nil },
diff --git a/ssh/client.go b/ssh/client.go
index 99f68bd..ba8621a 100644
--- a/ssh/client.go
+++ b/ssh/client.go
@@ -115,12 +115,25 @@
 
 // verifyHostKeySignature verifies the host key obtained in the key
 // exchange.
-func verifyHostKeySignature(hostKey PublicKey, result *kexResult) error {
+func verifyHostKeySignature(hostKey PublicKey, algo string, result *kexResult) error {
 	sig, rest, ok := parseSignatureBody(result.Signature)
 	if len(rest) > 0 || !ok {
 		return errors.New("ssh: signature parse error")
 	}
 
+	// For keys, underlyingAlgo is exactly algo. For certificates,
+	// we have to look up the underlying key algorithm that SSH
+	// uses to evaluate signatures.
+	underlyingAlgo := algo
+	for sigAlgo, certAlgo := range certAlgoNames {
+		if certAlgo == algo {
+			underlyingAlgo = sigAlgo
+		}
+	}
+	if sig.Format != underlyingAlgo {
+		return fmt.Errorf("ssh: invalid signature algorithm %q, expected %q", sig.Format, underlyingAlgo)
+	}
+
 	return hostKey.Verify(result.H, sig)
 }
 
diff --git a/ssh/client_test.go b/ssh/client_test.go
index 6aaa003..3063433 100644
--- a/ssh/client_test.go
+++ b/ssh/client_test.go
@@ -5,6 +5,8 @@
 package ssh
 
 import (
+	"bytes"
+	"crypto/rand"
 	"strings"
 	"testing"
 )
@@ -116,6 +118,45 @@
 	}
 }
 
+func TestVerifyHostKeySignature(t *testing.T) {
+	for _, tt := range []struct {
+		key        string
+		signAlgo   string
+		verifyAlgo string
+		wantError  string
+	}{
+		{"rsa", SigAlgoRSA, SigAlgoRSA, ""},
+		{"rsa", SigAlgoRSASHA2256, SigAlgoRSASHA2256, ""},
+		{"rsa", SigAlgoRSA, SigAlgoRSASHA2512, `ssh: invalid signature algorithm "ssh-rsa", expected "rsa-sha2-512"`},
+		{"ed25519", KeyAlgoED25519, KeyAlgoED25519, ""},
+	} {
+		key := testSigners[tt.key].PublicKey()
+		s, ok := testSigners[tt.key].(AlgorithmSigner)
+		if !ok {
+			t.Fatalf("needed an AlgorithmSigner")
+		}
+		sig, err := s.SignWithAlgorithm(rand.Reader, []byte("test"), tt.signAlgo)
+		if err != nil {
+			t.Fatalf("couldn't sign: %q", err)
+		}
+
+		b := bytes.Buffer{}
+		writeString(&b, []byte(sig.Format))
+		writeString(&b, sig.Blob)
+
+		result := kexResult{Signature: b.Bytes(), H: []byte("test")}
+
+		err = verifyHostKeySignature(key, tt.verifyAlgo, &result)
+		if err != nil {
+			if tt.wantError == "" || !strings.Contains(err.Error(), tt.wantError) {
+				t.Errorf("got error %q, expecting %q", err.Error(), tt.wantError)
+			}
+		} else if tt.wantError != "" {
+			t.Errorf("succeeded, but want error string %q", tt.wantError)
+		}
+	}
+}
+
 func TestBannerCallback(t *testing.T) {
 	c1, c2, err := netPipe()
 	if err != nil {
diff --git a/ssh/common.go b/ssh/common.go
index 290382d..5ae2275 100644
--- a/ssh/common.go
+++ b/ssh/common.go
@@ -69,11 +69,13 @@
 // supportedHostKeyAlgos specifies the supported host-key algorithms (i.e. methods
 // of authenticating servers) in preference order.
 var supportedHostKeyAlgos = []string{
-	CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01,
+	CertSigAlgoRSASHA2512v01, CertSigAlgoRSASHA2256v01,
+	CertSigAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01,
 	CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoED25519v01,
 
 	KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
-	KeyAlgoRSA, KeyAlgoDSA,
+	SigAlgoRSASHA2512, SigAlgoRSASHA2256,
+	SigAlgoRSA, KeyAlgoDSA,
 
 	KeyAlgoED25519,
 }
@@ -90,16 +92,20 @@
 // hashFuncs keeps the mapping of supported algorithms to their respective
 // hashes needed for signature verification.
 var hashFuncs = map[string]crypto.Hash{
-	KeyAlgoRSA:          crypto.SHA1,
-	KeyAlgoDSA:          crypto.SHA1,
-	KeyAlgoECDSA256:     crypto.SHA256,
-	KeyAlgoECDSA384:     crypto.SHA384,
-	KeyAlgoECDSA521:     crypto.SHA512,
-	CertAlgoRSAv01:      crypto.SHA1,
-	CertAlgoDSAv01:      crypto.SHA1,
-	CertAlgoECDSA256v01: crypto.SHA256,
-	CertAlgoECDSA384v01: crypto.SHA384,
-	CertAlgoECDSA521v01: crypto.SHA512,
+	SigAlgoRSA:               crypto.SHA1,
+	SigAlgoRSASHA2256:        crypto.SHA256,
+	SigAlgoRSASHA2512:        crypto.SHA512,
+	KeyAlgoDSA:               crypto.SHA1,
+	KeyAlgoECDSA256:          crypto.SHA256,
+	KeyAlgoECDSA384:          crypto.SHA384,
+	KeyAlgoECDSA521:          crypto.SHA512,
+	CertSigAlgoRSAv01:        crypto.SHA1,
+	CertSigAlgoRSASHA2256v01: crypto.SHA256,
+	CertSigAlgoRSASHA2512v01: crypto.SHA512,
+	CertAlgoDSAv01:           crypto.SHA1,
+	CertAlgoECDSA256v01:      crypto.SHA256,
+	CertAlgoECDSA384v01:      crypto.SHA384,
+	CertAlgoECDSA521v01:      crypto.SHA512,
 }
 
 // unexpectedMessageError results when the SSH message that we received didn't
diff --git a/ssh/handshake.go b/ssh/handshake.go
index 2b10b05..05ad49c 100644
--- a/ssh/handshake.go
+++ b/ssh/handshake.go
@@ -457,8 +457,15 @@
 
 	if len(t.hostKeys) > 0 {
 		for _, k := range t.hostKeys {
-			msg.ServerHostKeyAlgos = append(
-				msg.ServerHostKeyAlgos, k.PublicKey().Type())
+			algo := k.PublicKey().Type()
+			switch algo {
+			case KeyAlgoRSA:
+				msg.ServerHostKeyAlgos = append(msg.ServerHostKeyAlgos, []string{SigAlgoRSASHA2512, SigAlgoRSASHA2256, SigAlgoRSA}...)
+			case CertAlgoRSAv01:
+				msg.ServerHostKeyAlgos = append(msg.ServerHostKeyAlgos, []string{CertSigAlgoRSASHA2512v01, CertSigAlgoRSASHA2256v01, CertSigAlgoRSAv01}...)
+			default:
+				msg.ServerHostKeyAlgos = append(msg.ServerHostKeyAlgos, algo)
+			}
 		}
 	} else {
 		msg.ServerHostKeyAlgos = t.hostKeyAlgorithms
@@ -614,8 +621,22 @@
 func (t *handshakeTransport) server(kex kexAlgorithm, algs *algorithms, magics *handshakeMagics) (*kexResult, error) {
 	var hostKey Signer
 	for _, k := range t.hostKeys {
-		if algs.hostKey == k.PublicKey().Type() {
+		kt := k.PublicKey().Type()
+		if kt == algs.hostKey {
 			hostKey = k
+		} else if signer, ok := k.(AlgorithmSigner); ok {
+			// Some signature algorithms don't show up as key types
+			// so we have to manually check for a compatible host key.
+			switch kt {
+			case KeyAlgoRSA:
+				if algs.hostKey == SigAlgoRSASHA2256 || algs.hostKey == SigAlgoRSASHA2512 {
+					hostKey = &rsaSigner{signer, algs.hostKey}
+				}
+			case CertAlgoRSAv01:
+				if algs.hostKey == CertSigAlgoRSASHA2256v01 || algs.hostKey == CertSigAlgoRSASHA2512v01 {
+					hostKey = &rsaSigner{signer, certToPrivAlgo(algs.hostKey)}
+				}
+			}
 		}
 	}
 
@@ -634,7 +655,7 @@
 		return nil, err
 	}
 
-	if err := verifyHostKeySignature(hostKey, result); err != nil {
+	if err := verifyHostKeySignature(hostKey, algs.hostKey, result); err != nil {
 		return nil, err
 	}
 
diff --git a/ssh/keys.go b/ssh/keys.go
index 31f2634..c67d3a3 100644
--- a/ssh/keys.go
+++ b/ssh/keys.go
@@ -939,6 +939,15 @@
 	return &dsaPrivateKey{key}, nil
 }
 
+type rsaSigner struct {
+	AlgorithmSigner
+	defaultAlgorithm string
+}
+
+func (s *rsaSigner) Sign(rand io.Reader, data []byte) (*Signature, error) {
+	return s.AlgorithmSigner.SignWithAlgorithm(rand, data, s.defaultAlgorithm)
+}
+
 type wrappedSigner struct {
 	signer crypto.Signer
 	pubKey PublicKey
diff --git a/ssh/server.go b/ssh/server.go
index b6911e8..6a58e12 100644
--- a/ssh/server.go
+++ b/ssh/server.go
@@ -284,7 +284,7 @@
 
 func isAcceptableAlgo(algo string) bool {
 	switch algo {
-	case KeyAlgoRSA, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521, KeyAlgoSKECDSA256, KeyAlgoED25519, KeyAlgoSKED25519,
+	case SigAlgoRSA, SigAlgoRSASHA2256, SigAlgoRSASHA2512, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521, KeyAlgoSKECDSA256, KeyAlgoED25519, KeyAlgoSKED25519,
 		CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoSKECDSA256v01, CertAlgoED25519v01, CertAlgoSKED25519v01:
 		return true
 	}
diff --git a/ssh/session_test.go b/ssh/session_test.go
index 39853bf..fbec952 100644
--- a/ssh/session_test.go
+++ b/ssh/session_test.go
@@ -757,7 +757,13 @@
 	connect(clientConf, KeyAlgoECDSA256)
 
 	// Client asks for RSA explicitly.
-	clientConf.HostKeyAlgorithms = []string{KeyAlgoRSA}
+	clientConf.HostKeyAlgorithms = []string{SigAlgoRSA}
+	connect(clientConf, KeyAlgoRSA)
+
+	// Client asks for RSA-SHA2-512 explicitly.
+	clientConf.HostKeyAlgorithms = []string{SigAlgoRSASHA2512}
+	// We get back an "ssh-rsa" key but the verification happened
+	// with an RSA-SHA2-512 signature.
 	connect(clientConf, KeyAlgoRSA)
 
 	c1, c2, err := netPipe()
diff --git a/ssh/test/test_unix_test.go b/ssh/test/test_unix_test.go
index dbd02c7..804163c 100644
--- a/ssh/test/test_unix_test.go
+++ b/ssh/test/test_unix_test.go
@@ -35,7 +35,7 @@
 HostKey {{.Dir}}/id_rsa
 HostKey {{.Dir}}/id_dsa
 HostKey {{.Dir}}/id_ecdsa
-HostCertificate {{.Dir}}/id_rsa-cert.pub
+HostCertificate {{.Dir}}/id_rsa-sha2-512-cert.pub
 Pidfile {{.Dir}}/sshd.pid
 #UsePrivilegeSeparation no
 KeyRegenerationInterval 3600
diff --git a/ssh/testdata/keys.go b/ssh/testdata/keys.go
index f1e2fc5..4f2f3a4 100644
--- a/ssh/testdata/keys.go
+++ b/ssh/testdata/keys.go
@@ -61,6 +61,38 @@
 KCXFGd+SQ5GdUcEMe9isUH6DYj/6/yCDoFrXXmpQb+M=
 -----END RSA PRIVATE KEY-----
 `),
+	"rsa-sha2-256": []byte(`-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQC8A6FGHDiWCSREAXCq6yBfNVr0xCVG2CzvktFNRpue+RXrGs/2
+a6ySEJQb3IYquw7HlJgu6fg3WIWhOmHCjfpG0PrL4CRwbqQ2LaPPXhJErWYejcD8
+Di00cF3677+G10KMZk9RXbmHtuBFZT98wxg8j+ZsBMqGM1+7yrWUvynswQIDAQAB
+AoGAJMCk5vqfSRzyXOTXLGIYCuR4Kj6pdsbNSeuuRGfYBeR1F2c/XdFAg7D/8s5R
+38p/Ih52/Ty5S8BfJtwtvgVY9ecf/JlU/rl/QzhG8/8KC0NG7KsyXklbQ7gJT8UT
+Ojmw5QpMk+rKv17ipDVkQQmPaj+gJXYNAHqImke5mm/K/h0CQQDciPmviQ+DOhOq
+2ZBqUfH8oXHgFmp7/6pXw80DpMIxgV3CwkxxIVx6a8lVH9bT/AFySJ6vXq4zTuV9
+6QmZcZzDAkEA2j/UXJPIs1fQ8z/6sONOkU/BjtoePFIWJlRxdN35cZjXnBraX5UR
+fFHkePv4YwqmXNqrBOvSu+w2WdSDci+IKwJAcsPRc/jWmsrJW1q3Ha0hSf/WG/Bu
+X7MPuXaKpP/DkzGoUmb8ks7yqj6XWnYkPNLjCc8izU5vRwIiyWBRf4mxMwJBAILa
+NDvRS0rjwt6lJGv7zPZoqDc65VfrK2aNyHx2PgFyzwrEOtuF57bu7pnvEIxpLTeM
+z26i6XVMeYXAWZMTloMCQBbpGgEERQpeUknLBqUHhg/wXF6+lFA+vEGnkY+Dwab2
+KCXFGd+SQ5GdUcEMe9isUH6DYj/6/yCDoFrXXmpQb+M=
+-----END RSA PRIVATE KEY-----
+`),
+	"rsa-sha2-512": []byte(`-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQC8A6FGHDiWCSREAXCq6yBfNVr0xCVG2CzvktFNRpue+RXrGs/2
+a6ySEJQb3IYquw7HlJgu6fg3WIWhOmHCjfpG0PrL4CRwbqQ2LaPPXhJErWYejcD8
+Di00cF3677+G10KMZk9RXbmHtuBFZT98wxg8j+ZsBMqGM1+7yrWUvynswQIDAQAB
+AoGAJMCk5vqfSRzyXOTXLGIYCuR4Kj6pdsbNSeuuRGfYBeR1F2c/XdFAg7D/8s5R
+38p/Ih52/Ty5S8BfJtwtvgVY9ecf/JlU/rl/QzhG8/8KC0NG7KsyXklbQ7gJT8UT
+Ojmw5QpMk+rKv17ipDVkQQmPaj+gJXYNAHqImke5mm/K/h0CQQDciPmviQ+DOhOq
+2ZBqUfH8oXHgFmp7/6pXw80DpMIxgV3CwkxxIVx6a8lVH9bT/AFySJ6vXq4zTuV9
+6QmZcZzDAkEA2j/UXJPIs1fQ8z/6sONOkU/BjtoePFIWJlRxdN35cZjXnBraX5UR
+fFHkePv4YwqmXNqrBOvSu+w2WdSDci+IKwJAcsPRc/jWmsrJW1q3Ha0hSf/WG/Bu
+X7MPuXaKpP/DkzGoUmb8ks7yqj6XWnYkPNLjCc8izU5vRwIiyWBRf4mxMwJBAILa
+NDvRS0rjwt6lJGv7zPZoqDc65VfrK2aNyHx2PgFyzwrEOtuF57bu7pnvEIxpLTeM
+z26i6XVMeYXAWZMTloMCQBbpGgEERQpeUknLBqUHhg/wXF6+lFA+vEGnkY+Dwab2
+KCXFGd+SQ5GdUcEMe9isUH6DYj/6/yCDoFrXXmpQb+M=
+-----END RSA PRIVATE KEY-----
+`),
 	"pkcs8": []byte(`-----BEGIN PRIVATE KEY-----
 MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCitzS2KiRQTccf
 VApb0mbPpo1lt29JjeLBYAehXHWfQ+w8sXpd8e04n/020spx1R94yg+v0NjXyh2R
@@ -192,6 +224,10 @@
 	//    ssh-keygen -s ca -h -n host.example.com -V +500w -I host.example.com-key rsa.pub
 	"rsa": []byte(`ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgLjYqmmuTSEmjVhSfLQphBSTJMLwIZhRgmpn8FHKLiEIAAAADAQABAAAAgQC8A6FGHDiWCSREAXCq6yBfNVr0xCVG2CzvktFNRpue+RXrGs/2a6ySEJQb3IYquw7HlJgu6fg3WIWhOmHCjfpG0PrL4CRwbqQ2LaPPXhJErWYejcD8Di00cF3677+G10KMZk9RXbmHtuBFZT98wxg8j+ZsBMqGM1+7yrWUvynswQAAAAAAAAAAAAAAAgAAABRob3N0LmV4YW1wbGUuY29tLWtleQAAABQAAAAQaG9zdC5leGFtcGxlLmNvbQAAAABZHN8UAAAAAGsjIYUAAAAAAAAAAAAAAAAAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQC+D11D0hEbn2Vglv4YRJ8pZNyHjIGmvth3DWOQrq++2vH2MujmGQDxfr4SVE9GpMBlKU3lwGbpgIBxAg6yZcNSfo6PWVU9ACg6NMFO+yMzc2MaG+/naQdNjSewywF5j2rkNO2XOaViRVSrZroe2B/aY2LTV0jDl8nu5NOjwRs1/s7SLe5z1rw/X0dpmXk0qJY3gQhmR8HZZ1dhEkJUGwaBCPd0T8asSYf1Ag2rUD4aQ28r3q69mbwfWOOa6rMemVZruUV5dzHwVNVNtVv+ImtnYtz8m8g+K0plaGptHn3KsaOnASkh3tujhaE7kvc4HR9Igli9+76jhZie3h/dTN5zAAABDwAAAAdzc2gtcnNhAAABALeDea+60H6xJGhktAyosHaSY7AYzLocaqd8hJQjEIDifBwzoTlnBmcK9CxGhKuaoJFThdCLdaevCeOSuquh8HTkf+2ebZZc/G5T+2thPvPqmcuEcmMosWo+SIjYhbP3S6KD49aLC1X0kz8IBQeauFvURhkZ5ZjhA1L4aQYt9NjL73nqOl8PplRui+Ov5w8b4ldul4zOvYAFrzfcP6wnnXk3c1Zzwwf5wynD5jakO8GpYKBuhM7Z4crzkKSQjU3hla7xqgfomC5Gz4XbR2TNjcQiRrJQ0UlKtX3X3ObRCEhuvG0Kzjklhv+Ddw6txrhKjMjiSi/Yyius/AE8TmC1p4U= host.example.com
 `),
+	"rsa-sha2-256": []byte(`ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgOyK28gunJkM60qp4EbsYAjgbUsyjS8u742OLjipIgc0AAAADAQABAAAAgQC8A6FGHDiWCSREAXCq6yBfNVr0xCVG2CzvktFNRpue+RXrGs/2a6ySEJQb3IYquw7HlJgu6fg3WIWhOmHCjfpG0PrL4CRwbqQ2LaPPXhJErWYejcD8Di00cF3677+G10KMZk9RXbmHtuBFZT98wxg8j+ZsBMqGM1+7yrWUvynswQAAAAAAAAAAAAAAAgAAABRob3N0LmV4YW1wbGUuY29tLWtleQAAABQAAAAQaG9zdC5leGFtcGxlLmNvbQAAAABeSMJ4AAAAAHBPBLwAAAAAAAAAAAAAAAAAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQC+D11D0hEbn2Vglv4YRJ8pZNyHjIGmvth3DWOQrq++2vH2MujmGQDxfr4SVE9GpMBlKU3lwGbpgIBxAg6yZcNSfo6PWVU9ACg6NMFO+yMzc2MaG+/naQdNjSewywF5j2rkNO2XOaViRVSrZroe2B/aY2LTV0jDl8nu5NOjwRs1/s7SLe5z1rw/X0dpmXk0qJY3gQhmR8HZZ1dhEkJUGwaBCPd0T8asSYf1Ag2rUD4aQ28r3q69mbwfWOOa6rMemVZruUV5dzHwVNVNtVv+ImtnYtz8m8g+K0plaGptHn3KsaOnASkh3tujhaE7kvc4HR9Igli9+76jhZie3h/dTN5zAAABFAAAAAxyc2Etc2hhMi0yNTYAAAEAbG4De/+QiqopPS3O1H7ySeEUCY56qmdgr02sFErnihdXPDaWXUXxacvJHaEtLrSTSaPL/3v3iKvjLWDOHaQ5c+cN9J7Tqzso7RQCXZD2nK9bwCUyBoiDyBCRe8w4DQEtfL5okpVzQsSAiojQ8hBohMOpy3gFfXrdm4PVC1ZKqlZh4fAc7ajieRq/Tpq2xOLdHwxkcgPNR83WVHva6K9/xjev/5n227/gkHo0qbGs8YYDOFXIEhENi+B23IzxdNVieWdyQpYpe0C2i95Jhyo0wJmaFY2ArruTS+D1jGQQpMPvAQRy26/A5hI83GLhpwyhrN/M8wCxzAhyPL6Ieuh5tQ== host.example.com
+`),
+       "rsa-sha2-512": []byte(`ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgFGv4IpXfs4L/Y0b3rmUdPFhWoUrVnXuPxXr6aHGs7wgAAAADAQABAAAAgQC8A6FGHDiWCSREAXCq6yBfNVr0xCVG2CzvktFNRpue+RXrGs/2a6ySEJQb3IYquw7HlJgu6fg3WIWhOmHCjfpG0PrL4CRwbqQ2LaPPXhJErWYejcD8Di00cF3677+G10KMZk9RXbmHtuBFZT98wxg8j+ZsBMqGM1+7yrWUvynswQAAAAAAAAAAAAAAAgAAABRob3N0LmV4YW1wbGUuY29tLWtleQAAABQAAAAQaG9zdC5leGFtcGxlLmNvbQAAAABeSMRYAAAAAHBPBp4AAAAAAAAAAAAAAAAAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQC+D11D0hEbn2Vglv4YRJ8pZNyHjIGmvth3DWOQrq++2vH2MujmGQDxfr4SVE9GpMBlKU3lwGbpgIBxAg6yZcNSfo6PWVU9ACg6NMFO+yMzc2MaG+/naQdNjSewywF5j2rkNO2XOaViRVSrZroe2B/aY2LTV0jDl8nu5NOjwRs1/s7SLe5z1rw/X0dpmXk0qJY3gQhmR8HZZ1dhEkJUGwaBCPd0T8asSYf1Ag2rUD4aQ28r3q69mbwfWOOa6rMemVZruUV5dzHwVNVNtVv+ImtnYtz8m8g+K0plaGptHn3KsaOnASkh3tujhaE7kvc4HR9Igli9+76jhZie3h/dTN5zAAABFAAAAAxyc2Etc2hhMi01MTIAAAEAnF4fVj6mm+UFeNCIf9AKJCv9WzymjjPvzzmaMWWkPWqoV0P0m5SiYfvbY9SbA73Blpv8SOr0DmpublF183kodREia4KyVuC8hLhSCV2Y16hy9MBegOZMepn80w+apj7Rn9QCz5OfEakDdztp6OWTBtqxnZFcTQ4XrgFkNWeWRElGdEvAVNn2WHwHi4EIdz0mdv48Imv5SPlOuW862ZdFG4Do1dUfDIiGsBofLlgcyIYlf+eNHul6sBeUkuwFxisMpI5DQzNp8PX1g/QJA2wzwT674PTqDXNttKjyh50Fdr4sXxm9Gz1+jVLoESvFNa55ERdSyAqNu4wTy11MZsWwSA== host.example.com
+`),
 }
 
 var PEMEncryptedKeys = []struct {
diff --git a/ssh/testdata_test.go b/ssh/testdata_test.go
index 2da8c79..83aa51b 100644
--- a/ssh/testdata_test.go
+++ b/ssh/testdata_test.go
@@ -34,6 +34,14 @@
 			panic(fmt.Sprintf("Unable to parse test key %s: %v", t, err))
 		}
 		testSigners[t], err = NewSignerFromKey(testPrivateKeys[t])
+		if v, ok := testSigners[t].(*rsaSigner); ok {
+			switch t {
+			case "rsa-sha2-256":
+				testSigners[t] = &rsaSigner{v, SigAlgoRSASHA2256}
+			case "rsa-sha2-512":
+				testSigners[t] = &rsaSigner{v, SigAlgoRSASHA2512}
+			}
+		}
 		if err != nil {
 			panic(fmt.Sprintf("Unable to create signer for test key %s: %v", t, err))
 		}