Просмотр исходного кода

Merge commit 'f2de0de225d22a9a081c7b418b12540001f36716'

- Squash update x/crypto/ssh fork subtree to 5770296d904e90f15f38f77dfc2e43fdf5efc083
Rod Hynes 4 лет назад
Родитель
Сommit
a251558a7d

+ 22 - 2
psiphon/common/crypto/ssh/certs.go

@@ -14,7 +14,7 @@ import (
 	"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 @@ const (
 	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 @@ func (c *Certificate) SignCert(rand io.Reader, authority Signer) error {
 	}
 	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 @@ func (c *Certificate) SignCert(rand io.Reader, authority Signer) error {
 	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,

+ 31 - 47
psiphon/common/crypto/ssh/certs_test.go

@@ -9,12 +9,12 @@ import (
 	"crypto/ecdsa"
 	"crypto/elliptic"
 	"crypto/rand"
+	"fmt"
+	"io"
 	"net"
 	"reflect"
 	"testing"
 	"time"
-
-	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/crypto/ssh/testdata"
 )
 
 // Cert generated by ssh-keygen 6.0p1 Debian-4.
@@ -226,53 +226,33 @@ func TestHostKeyCert(t *testing.T) {
 	}
 }
 
+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 @@ func TestCertTypes(t *testing.T) {
 
 			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 @@ func TestCertTypes(t *testing.T) {
 				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 },

+ 8 - 0
psiphon/common/crypto/ssh/cipher.go

@@ -394,6 +394,10 @@ func (c *gcmCipher) readCipherPacket(seqNum uint32, r io.Reader) ([]byte, error)
 	}
 	c.incIV()
 
+	if len(plain) == 0 {
+		return nil, errors.New("ssh: empty packet")
+	}
+
 	padding := plain[0]
 	if padding < 4 {
 		// padding is a byte, so it automatically satisfies
@@ -710,6 +714,10 @@ func (c *chacha20Poly1305Cipher) readCipherPacket(seqNum uint32, r io.Reader) ([
 	plain := c.buf[4:contentEnd]
 	s.XORKeyStream(plain, plain)
 
+	if len(plain) == 0 {
+		return nil, errors.New("ssh: empty packet")
+	}
+
 	padding := plain[0]
 	if padding < 4 {
 		// padding is a byte, so it automatically satisfies

+ 100 - 0
psiphon/common/crypto/ssh/cipher_test.go

@@ -8,7 +8,12 @@ import (
 	"bytes"
 	"crypto"
 	"crypto/rand"
+	"encoding/binary"
+	"io"
 	"testing"
+
+	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/crypto/internal/poly1305"
+	"golang.org/x/crypto/chacha20"
 )
 
 func TestDefaultCiphersExist(t *testing.T) {
@@ -129,3 +134,98 @@ func TestCBCOracleCounterMeasure(t *testing.T) {
 		lastRead = bytesRead
 	}
 }
+
+func TestCVE202143565(t *testing.T) {
+	tests := []struct {
+		cipher          string
+		constructPacket func(packetCipher) io.Reader
+	}{
+		{
+			cipher: gcmCipherID,
+			constructPacket: func(client packetCipher) io.Reader {
+				internalCipher := client.(*gcmCipher)
+				b := &bytes.Buffer{}
+				prefix := [4]byte{}
+				if _, err := b.Write(prefix[:]); err != nil {
+					t.Fatal(err)
+				}
+				internalCipher.buf = internalCipher.aead.Seal(internalCipher.buf[:0], internalCipher.iv, []byte{}, prefix[:])
+				if _, err := b.Write(internalCipher.buf); err != nil {
+					t.Fatal(err)
+				}
+				internalCipher.incIV()
+
+				return b
+			},
+		},
+		{
+			cipher: chacha20Poly1305ID,
+			constructPacket: func(client packetCipher) io.Reader {
+				internalCipher := client.(*chacha20Poly1305Cipher)
+				b := &bytes.Buffer{}
+
+				nonce := make([]byte, 12)
+				s, err := chacha20.NewUnauthenticatedCipher(internalCipher.contentKey[:], nonce)
+				if err != nil {
+					t.Fatal(err)
+				}
+				var polyKey, discardBuf [32]byte
+				s.XORKeyStream(polyKey[:], polyKey[:])
+				s.XORKeyStream(discardBuf[:], discardBuf[:]) // skip the next 32 bytes
+
+				internalCipher.buf = make([]byte, 4+poly1305.TagSize)
+				binary.BigEndian.PutUint32(internalCipher.buf, 0)
+				ls, err := chacha20.NewUnauthenticatedCipher(internalCipher.lengthKey[:], nonce)
+				if err != nil {
+					t.Fatal(err)
+				}
+				ls.XORKeyStream(internalCipher.buf, internalCipher.buf[:4])
+				if _, err := io.ReadFull(rand.Reader, internalCipher.buf[4:4]); err != nil {
+					t.Fatal(err)
+				}
+
+				s.XORKeyStream(internalCipher.buf[4:], internalCipher.buf[4:4])
+
+				var tag [poly1305.TagSize]byte
+				poly1305.Sum(&tag, internalCipher.buf[:4], &polyKey)
+
+				copy(internalCipher.buf[4:], tag[:])
+
+				if _, err := b.Write(internalCipher.buf); err != nil {
+					t.Fatal(err)
+				}
+
+				return b
+			},
+		},
+	}
+
+	for _, tc := range tests {
+		mac := "hmac-sha2-256"
+
+		kr := &kexResult{Hash: crypto.SHA1}
+		algs := directionAlgorithms{
+			Cipher:      tc.cipher,
+			MAC:         mac,
+			Compression: "none",
+		}
+		client, err := newPacketCipher(clientKeys, algs, kr)
+		if err != nil {
+			t.Fatalf("newPacketCipher(client, %q, %q): %v", tc.cipher, mac, err)
+		}
+		server, err := newPacketCipher(clientKeys, algs, kr)
+		if err != nil {
+			t.Fatalf("newPacketCipher(client, %q, %q): %v", tc.cipher, mac, err)
+		}
+
+		b := tc.constructPacket(client)
+
+		wantErr := "ssh: empty packet"
+		_, err = server.readCipherPacket(0, b)
+		if err == nil {
+			t.Fatalf("readCipherPacket(%q, %q): didn't fail with empty packet", tc.cipher, mac)
+		} else if err.Error() != wantErr {
+			t.Fatalf("readCipherPacket(%q, %q): unexpected error, got %q, want %q", tc.cipher, mac, err, wantErr)
+		}
+	}
+}

+ 14 - 1
psiphon/common/crypto/ssh/client.go

@@ -115,12 +115,25 @@ func (c *connection) clientHandshake(dialAddress string, config *ClientConfig) e
 
 // 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)
 }
 

+ 41 - 0
psiphon/common/crypto/ssh/client_test.go

@@ -5,6 +5,8 @@
 package ssh
 
 import (
+	"bytes"
+	"crypto/rand"
 	"strings"
 	"testing"
 )
@@ -116,6 +118,45 @@ func TestHostKeyCheck(t *testing.T) {
 	}
 }
 
+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 {

+ 18 - 12
psiphon/common/crypto/ssh/common.go

@@ -78,11 +78,13 @@ var preferredKexAlgos = []string{
 // 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,
 }
@@ -99,16 +101,20 @@ var supportedCompressions = []string{compressionNone}
 // 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

+ 25 - 4
psiphon/common/crypto/ssh/handshake.go

@@ -460,8 +460,15 @@ func (t *handshakeTransport) sendKexInit() error {
 
 	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
@@ -770,8 +777,22 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
 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)}
+				}
+			}
 		}
 	}
 
@@ -790,7 +811,7 @@ func (t *handshakeTransport) client(kex kexAlgorithm, algs *algorithms, magics *
 		return nil, err
 	}
 
-	if err := verifyHostKeySignature(hostKey, result); err != nil {
+	if err := verifyHostKeySignature(hostKey, algs.hostKey, result); err != nil {
 		return nil, err
 	}
 

+ 9 - 0
psiphon/common/crypto/ssh/keys.go

@@ -939,6 +939,15 @@ func newDSAPrivateKey(key *dsa.PrivateKey) (Signer, error) {
 	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

+ 1 - 1
psiphon/common/crypto/ssh/server.go

@@ -284,7 +284,7 @@ func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error)
 
 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
 	}

+ 7 - 1
psiphon/common/crypto/ssh/session_test.go

@@ -757,7 +757,13 @@ func TestHostKeyAlgorithms(t *testing.T) {
 	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()

+ 1 - 1
psiphon/common/crypto/ssh/test/test_unix_test.go

@@ -35,7 +35,7 @@ Banner {{.Dir}}/banner
 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

+ 36 - 0
psiphon/common/crypto/ssh/testdata/keys.go

@@ -60,6 +60,38 @@ NDvRS0rjwt6lJGv7zPZoqDc65VfrK2aNyHx2PgFyzwrEOtuF57bu7pnvEIxpLTeM
 z26i6XVMeYXAWZMTloMCQBbpGgEERQpeUknLBqUHhg/wXF6+lFA+vEGnkY+Dwab2
 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
@@ -191,6 +223,10 @@ var SSHCertificates = map[string][]byte{
 	// 2. Assumes "ca" key above in file named "ca", sign a cert for "rsa.pub":
 	//    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
 `),
 }
 

+ 8 - 0
psiphon/common/crypto/ssh/testdata_test.go

@@ -34,6 +34,14 @@ func init() {
 			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))
 		}