Skip to content

Commit 74e709a

Browse files
drakkangopherbot
authored andcommitted
ssh: add AlgorithmNegotiationError
Fixes golang/go#61536 Change-Id: Id38cc6d46879dbe2bdea04dec061596387ec6cfe Reviewed-on: https://go-review.googlesource.com/c/crypto/+/559056 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Filippo Valsorda <filippo@golang.org> Reviewed-by: David Chase <drchase@google.com> Auto-Submit: Nicola Murino <nicola.murino@gmail.com> Reviewed-by: Carlos Amedee <carlos@golang.org>
1 parent b3790b8 commit 74e709a

File tree

3 files changed

+106
-11
lines changed

3 files changed

+106
-11
lines changed

ssh/client_auth.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ func pickSignatureAlgorithm(signer Signer, extensions map[string][]byte) (MultiA
289289
}
290290
}
291291

292-
algo, err := findCommon("public key signature algorithm", keyAlgos, serverAlgos)
292+
algo, err := findCommon("public key signature algorithm", keyAlgos, serverAlgos, true)
293293
if err != nil {
294294
// If there is no overlap, return the fallback algorithm to support
295295
// servers that fail to list all supported algorithms.

ssh/common.go

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -336,15 +336,40 @@ func parseError(tag uint8) error {
336336
return fmt.Errorf("ssh: parse error in message type %d", tag)
337337
}
338338

339-
func findCommon(what string, client []string, server []string) (common string, err error) {
339+
func findCommon(what string, client []string, server []string, isClient bool) (string, error) {
340340
for _, c := range client {
341341
for _, s := range server {
342342
if c == s {
343343
return c, nil
344344
}
345345
}
346346
}
347-
return "", fmt.Errorf("ssh: no common algorithm for %s; client offered: %v, server offered: %v", what, client, server)
347+
err := &AlgorithmNegotiationError{
348+
What: what,
349+
}
350+
if isClient {
351+
err.SupportedAlgorithms = client
352+
err.RequestedAlgorithms = server
353+
} else {
354+
err.SupportedAlgorithms = server
355+
err.RequestedAlgorithms = client
356+
}
357+
return "", err
358+
}
359+
360+
// AlgorithmNegotiationError defines the error returned if the client and the
361+
// server cannot agree on an algorithm for key exchange, host key, cipher, MAC.
362+
type AlgorithmNegotiationError struct {
363+
What string
364+
// RequestedAlgorithms lists the algorithms supported by the peer.
365+
RequestedAlgorithms []string
366+
// SupportedAlgorithms lists the algorithms supported on our side.
367+
SupportedAlgorithms []string
368+
}
369+
370+
func (a *AlgorithmNegotiationError) Error() string {
371+
return fmt.Sprintf("ssh: no common algorithm for %s; we offered: %v, peer offered: %v",
372+
a.What, a.SupportedAlgorithms, a.RequestedAlgorithms)
348373
}
349374

350375
// DirectionAlgorithms defines the algorithms negotiated in one direction
@@ -379,12 +404,12 @@ var aeadCiphers = map[string]bool{
379404
func findAgreedAlgorithms(isClient bool, clientKexInit, serverKexInit *kexInitMsg) (algs *NegotiatedAlgorithms, err error) {
380405
result := &NegotiatedAlgorithms{}
381406

382-
result.KeyExchange, err = findCommon("key exchange", clientKexInit.KexAlgos, serverKexInit.KexAlgos)
407+
result.KeyExchange, err = findCommon("key exchange", clientKexInit.KexAlgos, serverKexInit.KexAlgos, isClient)
383408
if err != nil {
384409
return
385410
}
386411

387-
result.HostKey, err = findCommon("host key", clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos)
412+
result.HostKey, err = findCommon("host key", clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos, isClient)
388413
if err != nil {
389414
return
390415
}
@@ -394,36 +419,36 @@ func findAgreedAlgorithms(isClient bool, clientKexInit, serverKexInit *kexInitMs
394419
ctos, stoc = stoc, ctos
395420
}
396421

397-
ctos.Cipher, err = findCommon("client to server cipher", clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer)
422+
ctos.Cipher, err = findCommon("client to server cipher", clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer, isClient)
398423
if err != nil {
399424
return
400425
}
401426

402-
stoc.Cipher, err = findCommon("server to client cipher", clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient)
427+
stoc.Cipher, err = findCommon("server to client cipher", clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient, isClient)
403428
if err != nil {
404429
return
405430
}
406431

407432
if !aeadCiphers[ctos.Cipher] {
408-
ctos.MAC, err = findCommon("client to server MAC", clientKexInit.MACsClientServer, serverKexInit.MACsClientServer)
433+
ctos.MAC, err = findCommon("client to server MAC", clientKexInit.MACsClientServer, serverKexInit.MACsClientServer, isClient)
409434
if err != nil {
410435
return
411436
}
412437
}
413438

414439
if !aeadCiphers[stoc.Cipher] {
415-
stoc.MAC, err = findCommon("server to client MAC", clientKexInit.MACsServerClient, serverKexInit.MACsServerClient)
440+
stoc.MAC, err = findCommon("server to client MAC", clientKexInit.MACsServerClient, serverKexInit.MACsServerClient, isClient)
416441
if err != nil {
417442
return
418443
}
419444
}
420445

421-
ctos.compression, err = findCommon("client to server compression", clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer)
446+
ctos.compression, err = findCommon("client to server compression", clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer, isClient)
422447
if err != nil {
423448
return
424449
}
425450

426-
stoc.compression, err = findCommon("server to client compression", clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient)
451+
stoc.compression, err = findCommon("server to client compression", clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient, isClient)
427452
if err != nil {
428453
return
429454
}

ssh/handshake_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1294,3 +1294,73 @@ func TestNegotiatedAlgorithms(t *testing.T) {
12941294
}
12951295
}
12961296
}
1297+
1298+
func TestAlgorithmNegotiationError(t *testing.T) {
1299+
c1, c2, err := netPipe()
1300+
if err != nil {
1301+
t.Fatalf("netPipe: %v", err)
1302+
}
1303+
defer c1.Close()
1304+
defer c2.Close()
1305+
1306+
serverConf := &ServerConfig{
1307+
Config: Config{
1308+
Ciphers: []string{CipherAES128CTR, CipherAES256CTR},
1309+
},
1310+
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
1311+
return &Permissions{}, nil
1312+
},
1313+
}
1314+
serverConf.AddHostKey(testSigners["rsa"])
1315+
1316+
srvErrCh := make(chan error, 1)
1317+
go func() {
1318+
_, _, _, err := NewServerConn(c1, serverConf)
1319+
srvErrCh <- err
1320+
}()
1321+
1322+
clientConf := &ClientConfig{
1323+
Config: Config{
1324+
Ciphers: []string{CipherAES128GCM, CipherAES256GCM},
1325+
},
1326+
User: "test",
1327+
Auth: []AuthMethod{Password("testpw")},
1328+
HostKeyCallback: FixedHostKey(testSigners["rsa"].PublicKey()),
1329+
}
1330+
1331+
_, _, _, err = NewClientConn(c2, "", clientConf)
1332+
if err == nil {
1333+
t.Fatal("client connection succeded expected algorithm negotiation error")
1334+
}
1335+
var negotiationError *AlgorithmNegotiationError
1336+
if !errors.As(err, &negotiationError) {
1337+
t.Fatalf("expected algorithm negotiation error, got %v", err)
1338+
}
1339+
expectedErrorString := fmt.Sprintf("ssh: handshake failed: ssh: no common algorithm for client to server cipher; we offered: %v, peer offered: %v",
1340+
clientConf.Ciphers, serverConf.Ciphers)
1341+
if err.Error() != expectedErrorString {
1342+
t.Fatalf("expected error string %q, got %q", expectedErrorString, err.Error())
1343+
}
1344+
if !reflect.DeepEqual(negotiationError.RequestedAlgorithms, serverConf.Ciphers) {
1345+
t.Fatalf("expected requested algorithms %v, got %v", serverConf.Ciphers, negotiationError.RequestedAlgorithms)
1346+
}
1347+
if !reflect.DeepEqual(negotiationError.SupportedAlgorithms, clientConf.Ciphers) {
1348+
t.Fatalf("expected supported algorithms %v, got %v", clientConf.Ciphers, negotiationError.SupportedAlgorithms)
1349+
}
1350+
err = <-srvErrCh
1351+
negotiationError = nil
1352+
if !errors.As(err, &negotiationError) {
1353+
t.Fatalf("expected algorithm negotiation error, got %v", err)
1354+
}
1355+
expectedErrorString = fmt.Sprintf("ssh: no common algorithm for client to server cipher; we offered: %v, peer offered: %v",
1356+
serverConf.Ciphers, clientConf.Ciphers)
1357+
if err.Error() != expectedErrorString {
1358+
t.Fatalf("expected error string %q, got %q", expectedErrorString, err.Error())
1359+
}
1360+
if !reflect.DeepEqual(negotiationError.RequestedAlgorithms, clientConf.Ciphers) {
1361+
t.Fatalf("expected requested algorithms %v, got %v", clientConf.Ciphers, negotiationError.RequestedAlgorithms)
1362+
}
1363+
if !reflect.DeepEqual(negotiationError.SupportedAlgorithms, serverConf.Ciphers) {
1364+
t.Fatalf("expected supported algorithms %v, got %v", serverConf.Ciphers, negotiationError.SupportedAlgorithms)
1365+
}
1366+
}

0 commit comments

Comments
 (0)