Skip to content

Commit 1baeb1c

Browse files
committed
ssh: don't advertise rsa-sha2 algorithms if we can't use them
The server implementation looks at the HostKeys to advertise and negotiate host key signature algorithms. A fundamental issue of the Signer and AlgorithmSigner interfaces is that they don't expose the supported signature algorithms, so really the server has to guess. Currently, it would guess exclusively based on the PublicKey.Type, regardless of whether the host key implemented AlgorithmSigner. This means that a legacy Signer that only supports ssh-rsa still led the server to negotiate rsa-sha2 algorithms. The server would then fail to find a suitable host key to make the signature and crash. This won't happen if only Signers from this package are used, but if a custom Signer that doesn't support SignWithAlgorithm() but returns "ssh-rsa" from PublicKey().Type() is used as a HostKey, the server is vulnerable to DoS. The only workable rules to determine what to advertise seems to be: 1. a pure Signer will always Sign with the PublicKey.Type 2. an AlgorithmSigner supports all algorithms associated with the PublicKey.Type Rule number two means that we can't add new supported algorithms in the future, which is not great, but it's too late to fix that. rsaSigner was breaking rule number one, and although it would have been fine where it's used, I didn't want to break our own interface contract. It's unclear why we had separate test key entries for rsa-sha2 algorithms, since we can use the ssh-rsa key for those. The only test that used them, TestCertTypes, seemed broken: the init was actually failing at making the corresponding signers rsaSigners, and indeed the test for the SHA-256 signer expected and checked a SHA-512 signature. Pending CVE For golang/go#49952 Change-Id: Ie658eefcadd87906e63fc7faae8249376aa96c79 Reviewed-on: https://go-review.googlesource.com/c/crypto/+/392355 Trust: Filippo Valsorda <filippo@golang.org> Run-TryBot: Filippo Valsorda <filippo@golang.org> Reviewed-by: Roland Shoemaker <roland@golang.org> TryBot-Result: Gopher Robot <gobot@golang.org>
1 parent fcc990c commit 1baeb1c

12 files changed

+233
-232
lines changed

ssh/certs.go

+33-28
Original file line numberDiff line numberDiff line change
@@ -440,10 +440,14 @@ func (c *Certificate) SignCert(rand io.Reader, authority Signer) error {
440440
}
441441
c.SignatureKey = authority.PublicKey()
442442

443-
if v, ok := authority.(AlgorithmSigner); ok {
444-
if v.PublicKey().Type() == KeyAlgoRSA {
445-
authority = &rsaSigner{v, KeyAlgoRSASHA512}
443+
// Default to KeyAlgoRSASHA512 for ssh-rsa signers.
444+
if v, ok := authority.(AlgorithmSigner); ok && v.PublicKey().Type() == KeyAlgoRSA {
445+
sig, err := v.SignWithAlgorithm(rand, c.bytesForSigning(), KeyAlgoRSASHA512)
446+
if err != nil {
447+
return err
446448
}
449+
c.Signature = sig
450+
return nil
447451
}
448452

449453
sig, err := authority.Sign(rand, c.bytesForSigning())
@@ -454,30 +458,29 @@ func (c *Certificate) SignCert(rand io.Reader, authority Signer) error {
454458
return nil
455459
}
456460

457-
// certAlgoNames includes a mapping from signature algorithms to the
458-
// corresponding certificate signature algorithm.
459-
var certAlgoNames = map[string]string{
460-
KeyAlgoRSA: CertAlgoRSAv01,
461-
KeyAlgoRSASHA256: CertAlgoRSASHA256v01,
462-
KeyAlgoRSASHA512: CertAlgoRSASHA512v01,
463-
KeyAlgoDSA: CertAlgoDSAv01,
464-
KeyAlgoECDSA256: CertAlgoECDSA256v01,
465-
KeyAlgoECDSA384: CertAlgoECDSA384v01,
466-
KeyAlgoECDSA521: CertAlgoECDSA521v01,
467-
KeyAlgoSKECDSA256: CertAlgoSKECDSA256v01,
468-
KeyAlgoED25519: CertAlgoED25519v01,
469-
KeyAlgoSKED25519: CertAlgoSKED25519v01,
461+
// certKeyAlgoNames is a mapping from known certificate algorithm names to the
462+
// corresponding public key signature algorithm.
463+
var certKeyAlgoNames = map[string]string{
464+
CertAlgoRSAv01: KeyAlgoRSA,
465+
CertAlgoRSASHA256v01: KeyAlgoRSASHA256,
466+
CertAlgoRSASHA512v01: KeyAlgoRSASHA512,
467+
CertAlgoDSAv01: KeyAlgoDSA,
468+
CertAlgoECDSA256v01: KeyAlgoECDSA256,
469+
CertAlgoECDSA384v01: KeyAlgoECDSA384,
470+
CertAlgoECDSA521v01: KeyAlgoECDSA521,
471+
CertAlgoSKECDSA256v01: KeyAlgoSKECDSA256,
472+
CertAlgoED25519v01: KeyAlgoED25519,
473+
CertAlgoSKED25519v01: KeyAlgoSKED25519,
470474
}
471475

472-
// certToPrivAlgo returns the underlying algorithm for a certificate algorithm.
473-
// Panics if a non-certificate algorithm is passed.
474-
func certToPrivAlgo(algo string) string {
475-
for privAlgo, pubAlgo := range certAlgoNames {
476-
if pubAlgo == algo {
477-
return privAlgo
478-
}
476+
// underlyingAlgo returns the signature algorithm associated with algo (which is
477+
// an advertised or negotiated public key or host key algorithm). These are
478+
// usually the same, except for certificate algorithms.
479+
func underlyingAlgo(algo string) string {
480+
if a, ok := certKeyAlgoNames[algo]; ok {
481+
return a
479482
}
480-
panic("unknown cert algorithm")
483+
return algo
481484
}
482485

483486
func (cert *Certificate) bytesForSigning() []byte {
@@ -523,11 +526,13 @@ func (c *Certificate) Marshal() []byte {
523526

524527
// Type returns the certificate algorithm name. It is part of the PublicKey interface.
525528
func (c *Certificate) Type() string {
526-
algo, ok := certAlgoNames[c.Key.Type()]
527-
if !ok {
528-
panic("unknown cert key type " + c.Key.Type())
529+
keyType := c.Key.Type()
530+
for certName, keyName := range certKeyAlgoNames {
531+
if keyName == keyType {
532+
return certName
533+
}
529534
}
530-
return algo
535+
panic("unknown certificate type for key type " + keyType)
531536
}
532537

533538
// Verify verifies a signature against the certificate's public

ssh/certs_test.go

+3-5
Original file line numberDiff line numberDiff line change
@@ -216,12 +216,12 @@ func TestHostKeyCert(t *testing.T) {
216216
_, _, _, err = NewClientConn(c2, test.addr, config)
217217

218218
if (err == nil) != test.succeed {
219-
t.Fatalf("NewClientConn(%q): %v", test.addr, err)
219+
t.Errorf("NewClientConn(%q): %v", test.addr, err)
220220
}
221221

222222
err = <-errc
223223
if (err == nil) != test.succeed {
224-
t.Fatalf("NewServerConn(%q): %v", test.addr, err)
224+
t.Errorf("NewServerConn(%q): %v", test.addr, err)
225225
}
226226
}
227227
}
@@ -249,9 +249,7 @@ func TestCertTypes(t *testing.T) {
249249
{CertAlgoECDSA521v01, testSigners["ecdsap521"], ""},
250250
{CertAlgoED25519v01, testSigners["ed25519"], ""},
251251
{CertAlgoRSAv01, testSigners["rsa"], KeyAlgoRSASHA512},
252-
{CertAlgoRSAv01, &legacyRSASigner{testSigners["rsa"]}, KeyAlgoRSA},
253-
{CertAlgoRSAv01, testSigners["rsa-sha2-256"], KeyAlgoRSASHA512},
254-
{CertAlgoRSAv01, testSigners["rsa-sha2-512"], KeyAlgoRSASHA512},
252+
{"legacyRSASigner", &legacyRSASigner{testSigners["rsa"]}, KeyAlgoRSA},
255253
{CertAlgoDSAv01, testSigners["dsa"], ""},
256254
}
257255

ssh/client.go

+4-13
Original file line numberDiff line numberDiff line change
@@ -113,25 +113,16 @@ func (c *connection) clientHandshake(dialAddress string, config *ClientConfig) e
113113
return c.clientAuthenticate(config)
114114
}
115115

116-
// verifyHostKeySignature verifies the host key obtained in the key
117-
// exchange.
116+
// verifyHostKeySignature verifies the host key obtained in the key exchange.
117+
// algo is the negotiated algorithm, and may be a certificate type.
118118
func verifyHostKeySignature(hostKey PublicKey, algo string, result *kexResult) error {
119119
sig, rest, ok := parseSignatureBody(result.Signature)
120120
if len(rest) > 0 || !ok {
121121
return errors.New("ssh: signature parse error")
122122
}
123123

124-
// For keys, underlyingAlgo is exactly algo. For certificates,
125-
// we have to look up the underlying key algorithm that SSH
126-
// uses to evaluate signatures.
127-
underlyingAlgo := algo
128-
for sigAlgo, certAlgo := range certAlgoNames {
129-
if certAlgo == algo {
130-
underlyingAlgo = sigAlgo
131-
}
132-
}
133-
if sig.Format != underlyingAlgo {
134-
return fmt.Errorf("ssh: invalid signature algorithm %q, expected %q", sig.Format, underlyingAlgo)
124+
if a := underlyingAlgo(algo); sig.Format != a {
125+
return fmt.Errorf("ssh: invalid signature algorithm %q, expected %q", sig.Format, a)
135126
}
136127

137128
return hostKey.Verify(result.H, sig)

ssh/common.go

+26-16
Original file line numberDiff line numberDiff line change
@@ -89,23 +89,33 @@ var supportedMACs = []string{
8989

9090
var supportedCompressions = []string{compressionNone}
9191

92-
// hashFuncs keeps the mapping of supported algorithms to their respective
93-
// hashes needed for signature verification.
92+
// hashFuncs keeps the mapping of supported signature algorithms to their
93+
// respective hashes needed for signing and verification.
9494
var hashFuncs = map[string]crypto.Hash{
95-
KeyAlgoRSA: crypto.SHA1,
96-
KeyAlgoRSASHA256: crypto.SHA256,
97-
KeyAlgoRSASHA512: crypto.SHA512,
98-
KeyAlgoDSA: crypto.SHA1,
99-
KeyAlgoECDSA256: crypto.SHA256,
100-
KeyAlgoECDSA384: crypto.SHA384,
101-
KeyAlgoECDSA521: crypto.SHA512,
102-
CertAlgoRSAv01: crypto.SHA1,
103-
CertAlgoRSASHA256v01: crypto.SHA256,
104-
CertAlgoRSASHA512v01: crypto.SHA512,
105-
CertAlgoDSAv01: crypto.SHA1,
106-
CertAlgoECDSA256v01: crypto.SHA256,
107-
CertAlgoECDSA384v01: crypto.SHA384,
108-
CertAlgoECDSA521v01: crypto.SHA512,
95+
KeyAlgoRSA: crypto.SHA1,
96+
KeyAlgoRSASHA256: crypto.SHA256,
97+
KeyAlgoRSASHA512: crypto.SHA512,
98+
KeyAlgoDSA: crypto.SHA1,
99+
KeyAlgoECDSA256: crypto.SHA256,
100+
KeyAlgoECDSA384: crypto.SHA384,
101+
KeyAlgoECDSA521: crypto.SHA512,
102+
// KeyAlgoED25519 doesn't pre-hash.
103+
KeyAlgoSKECDSA256: crypto.SHA256,
104+
KeyAlgoSKED25519: crypto.SHA256,
105+
}
106+
107+
// algorithmsForKeyFormat returns the supported signature algorithms for a given
108+
// public key format (PublicKey.Type), in order of preference. See RFC 8332,
109+
// Section 2. See also the note in sendKexInit on backwards compatibility.
110+
func algorithmsForKeyFormat(keyFormat string) []string {
111+
switch keyFormat {
112+
case KeyAlgoRSA:
113+
return []string{KeyAlgoRSASHA256, KeyAlgoRSASHA512, KeyAlgoRSA}
114+
case CertAlgoRSAv01:
115+
return []string{CertAlgoRSASHA256v01, CertAlgoRSASHA512v01, CertAlgoRSAv01}
116+
default:
117+
return []string{keyFormat}
118+
}
109119
}
110120

111121
// unexpectedMessageError results when the SSH message that we received didn't

ssh/handshake.go

+59-32
Original file line numberDiff line numberDiff line change
@@ -455,21 +455,29 @@ func (t *handshakeTransport) sendKexInit() error {
455455
}
456456
io.ReadFull(rand.Reader, msg.Cookie[:])
457457

458-
if len(t.hostKeys) > 0 {
458+
isServer := len(t.hostKeys) > 0
459+
if isServer {
459460
for _, k := range t.hostKeys {
460-
algo := k.PublicKey().Type()
461-
switch algo {
462-
case KeyAlgoRSA:
463-
msg.ServerHostKeyAlgos = append(msg.ServerHostKeyAlgos, []string{KeyAlgoRSASHA512, KeyAlgoRSASHA256, KeyAlgoRSA}...)
464-
case CertAlgoRSAv01:
465-
msg.ServerHostKeyAlgos = append(msg.ServerHostKeyAlgos, []string{CertAlgoRSASHA512v01, CertAlgoRSASHA256v01, CertAlgoRSAv01}...)
466-
default:
467-
msg.ServerHostKeyAlgos = append(msg.ServerHostKeyAlgos, algo)
461+
// If k is an AlgorithmSigner, presume it supports all signature algorithms
462+
// associated with the key format. (Ideally AlgorithmSigner would have a
463+
// method to advertise supported algorithms, but it doesn't. This means that
464+
// adding support for a new algorithm is a breaking change, as we will
465+
// immediately negotiate it even if existing implementations don't support
466+
// it. If that ever happens, we'll have to figure something out.)
467+
// If k is not an AlgorithmSigner, we can only assume it only supports the
468+
// algorithms that matches the key format. (This means that Sign can't pick
469+
// a different default.)
470+
keyFormat := k.PublicKey().Type()
471+
if _, ok := k.(AlgorithmSigner); ok {
472+
msg.ServerHostKeyAlgos = append(msg.ServerHostKeyAlgos, algorithmsForKeyFormat(keyFormat)...)
473+
} else {
474+
msg.ServerHostKeyAlgos = append(msg.ServerHostKeyAlgos, keyFormat)
468475
}
469476
}
470477
} else {
471478
msg.ServerHostKeyAlgos = t.hostKeyAlgorithms
472479
}
480+
473481
packet := Marshal(msg)
474482

475483
// writePacket destroys the contents, so save a copy.
@@ -589,9 +597,9 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
589597

590598
var result *kexResult
591599
if len(t.hostKeys) > 0 {
592-
result, err = t.server(kex, t.algorithms, &magics)
600+
result, err = t.server(kex, &magics)
593601
} else {
594-
result, err = t.client(kex, t.algorithms, &magics)
602+
result, err = t.client(kex, &magics)
595603
}
596604

597605
if err != nil {
@@ -618,33 +626,52 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
618626
return nil
619627
}
620628

621-
func (t *handshakeTransport) server(kex kexAlgorithm, algs *algorithms, magics *handshakeMagics) (*kexResult, error) {
622-
var hostKey Signer
623-
for _, k := range t.hostKeys {
624-
kt := k.PublicKey().Type()
625-
if kt == algs.hostKey {
626-
hostKey = k
627-
} else if signer, ok := k.(AlgorithmSigner); ok {
628-
// Some signature algorithms don't show up as key types
629-
// so we have to manually check for a compatible host key.
630-
switch kt {
631-
case KeyAlgoRSA:
632-
if algs.hostKey == KeyAlgoRSASHA256 || algs.hostKey == KeyAlgoRSASHA512 {
633-
hostKey = &rsaSigner{signer, algs.hostKey}
634-
}
635-
case CertAlgoRSAv01:
636-
if algs.hostKey == CertAlgoRSASHA256v01 || algs.hostKey == CertAlgoRSASHA512v01 {
637-
hostKey = &rsaSigner{signer, certToPrivAlgo(algs.hostKey)}
638-
}
629+
// algorithmSignerWrapper is an AlgorithmSigner that only supports the default
630+
// key format algorithm.
631+
//
632+
// This is technically a violation of the AlgorithmSigner interface, but it
633+
// should be unreachable given where we use this. Anyway, at least it returns an
634+
// error instead of panicing or producing an incorrect signature.
635+
type algorithmSignerWrapper struct {
636+
Signer
637+
}
638+
639+
func (a algorithmSignerWrapper) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*Signature, error) {
640+
if algorithm != underlyingAlgo(a.PublicKey().Type()) {
641+
return nil, errors.New("ssh: internal error: algorithmSignerWrapper invoked with non-default algorithm")
642+
}
643+
return a.Sign(rand, data)
644+
}
645+
646+
func pickHostKey(hostKeys []Signer, algo string) AlgorithmSigner {
647+
for _, k := range hostKeys {
648+
if algo == k.PublicKey().Type() {
649+
return algorithmSignerWrapper{k}
650+
}
651+
k, ok := k.(AlgorithmSigner)
652+
if !ok {
653+
continue
654+
}
655+
for _, a := range algorithmsForKeyFormat(k.PublicKey().Type()) {
656+
if algo == a {
657+
return k
639658
}
640659
}
641660
}
661+
return nil
662+
}
663+
664+
func (t *handshakeTransport) server(kex kexAlgorithm, magics *handshakeMagics) (*kexResult, error) {
665+
hostKey := pickHostKey(t.hostKeys, t.algorithms.hostKey)
666+
if hostKey == nil {
667+
return nil, errors.New("ssh: internal error: negotiated unsupported signature type")
668+
}
642669

643-
r, err := kex.Server(t.conn, t.config.Rand, magics, hostKey)
670+
r, err := kex.Server(t.conn, t.config.Rand, magics, hostKey, t.algorithms.hostKey)
644671
return r, err
645672
}
646673

647-
func (t *handshakeTransport) client(kex kexAlgorithm, algs *algorithms, magics *handshakeMagics) (*kexResult, error) {
674+
func (t *handshakeTransport) client(kex kexAlgorithm, magics *handshakeMagics) (*kexResult, error) {
648675
result, err := kex.Client(t.conn, t.config.Rand, magics)
649676
if err != nil {
650677
return nil, err
@@ -655,7 +682,7 @@ func (t *handshakeTransport) client(kex kexAlgorithm, algs *algorithms, magics *
655682
return nil, err
656683
}
657684

658-
if err := verifyHostKeySignature(hostKey, algs.hostKey, result); err != nil {
685+
if err := verifyHostKeySignature(hostKey, t.algorithms.hostKey, result); err != nil {
659686
return nil, err
660687
}
661688

ssh/handshake_test.go

+35
Original file line numberDiff line numberDiff line change
@@ -583,3 +583,38 @@ func TestHandshakeAEADCipherNoMAC(t *testing.T) {
583583
<-checker.called
584584
}
585585
}
586+
587+
// TestNoSHA2Support tests a host key Signer that is not an AlgorithmSigner and
588+
// therefore can't do SHA-2 signatures. Ensures the server does not advertise
589+
// support for them in this case.
590+
func TestNoSHA2Support(t *testing.T) {
591+
c1, c2, err := netPipe()
592+
if err != nil {
593+
t.Fatalf("netPipe: %v", err)
594+
}
595+
defer c1.Close()
596+
defer c2.Close()
597+
598+
serverConf := &ServerConfig{
599+
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
600+
return &Permissions{}, nil
601+
},
602+
}
603+
serverConf.AddHostKey(&legacyRSASigner{testSigners["rsa"]})
604+
go func() {
605+
_, _, _, err := NewServerConn(c1, serverConf)
606+
if err != nil {
607+
t.Error(err)
608+
}
609+
}()
610+
611+
clientConf := &ClientConfig{
612+
User: "test",
613+
Auth: []AuthMethod{Password("testpw")},
614+
HostKeyCallback: FixedHostKey(testSigners["rsa"].PublicKey()),
615+
}
616+
617+
if _, _, _, err := NewClientConn(c2, "", clientConf); err != nil {
618+
t.Fatal(err)
619+
}
620+
}

0 commit comments

Comments
 (0)