Skip to content

Commit eb61739

Browse files
drakkangopherbot
authored andcommitted
ssh: allow to configure public key auth algorithms on the server side
Fixes golang/go#61244 Change-Id: I29b43e379cf0cdb07b0d6935666491b997157e73 Reviewed-on: https://go-review.googlesource.com/c/crypto/+/510775 TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Bryan Mills <bcmills@google.com> Commit-Queue: Nicola Murino <nicola.murino@gmail.com> Run-TryBot: Nicola Murino <nicola.murino@gmail.com> Auto-Submit: Nicola Murino <nicola.murino@gmail.com> Reviewed-by: Han-Wen Nienhuys <hanwen@google.com>
1 parent 42c83ff commit eb61739

File tree

4 files changed

+110
-5
lines changed

4 files changed

+110
-5
lines changed

ssh/common.go

-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"fmt"
1111
"io"
1212
"math"
13-
"strings"
1413
"sync"
1514

1615
_ "crypto/sha1"
@@ -140,8 +139,6 @@ var supportedPubKeyAuthAlgos = []string{
140139
KeyAlgoDSA,
141140
}
142141

143-
var supportedPubKeyAuthAlgosList = strings.Join(supportedPubKeyAuthAlgos, ",")
144-
145142
// unexpectedMessageError results when the SSH message that we received didn't
146143
// match what we wanted.
147144
func unexpectedMessageError(expected, got uint8) error {

ssh/handshake.go

+7
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"io"
1212
"log"
1313
"net"
14+
"strings"
1415
"sync"
1516
)
1617

@@ -50,6 +51,10 @@ type handshakeTransport struct {
5051
// connection.
5152
hostKeys []Signer
5253

54+
// publicKeyAuthAlgorithms is non-empty if we are the server. In that case,
55+
// it contains the supported client public key authentication algorithms.
56+
publicKeyAuthAlgorithms []string
57+
5358
// hostKeyAlgorithms is non-empty if we are the client. In that case,
5459
// we accept these key types from the server as host key.
5560
hostKeyAlgorithms []string
@@ -141,6 +146,7 @@ func newClientTransport(conn keyingTransport, clientVersion, serverVersion []byt
141146
func newServerTransport(conn keyingTransport, clientVersion, serverVersion []byte, config *ServerConfig) *handshakeTransport {
142147
t := newHandshakeTransport(conn, &config.Config, clientVersion, serverVersion)
143148
t.hostKeys = config.hostKeys
149+
t.publicKeyAuthAlgorithms = config.PublicKeyAuthAlgorithms
144150
go t.readLoop()
145151
go t.kexLoop()
146152
return t
@@ -649,6 +655,7 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
649655
// message with the server-sig-algs extension if the client supports it. See
650656
// RFC 8308, Sections 2.4 and 3.1, and [PROTOCOL], Section 1.9.
651657
if !isClient && firstKeyExchange && contains(clientInit.KexAlgos, "ext-info-c") {
658+
supportedPubKeyAuthAlgosList := strings.Join(t.publicKeyAuthAlgorithms, ",")
652659
extInfo := &extInfoMsg{
653660
NumExtensions: 2,
654661
Payload: make([]byte, 0, 4+15+4+len(supportedPubKeyAuthAlgosList)+4+16+4+1),

ssh/server.go

+18-2
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ type ServerConfig struct {
6464
// Config contains configuration shared between client and server.
6565
Config
6666

67+
// PublicKeyAuthAlgorithms specifies the supported client public key
68+
// authentication algorithms. Note that this should not include certificate
69+
// types since those use the underlying algorithm. This list is sent to the
70+
// client if it supports the server-sig-algs extension. Order is irrelevant.
71+
// If unspecified then a default set of algorithms is used.
72+
PublicKeyAuthAlgorithms []string
73+
6774
hostKeys []Signer
6875

6976
// NoClientAuth is true if clients are allowed to connect without
@@ -201,6 +208,15 @@ func NewServerConn(c net.Conn, config *ServerConfig) (*ServerConn, <-chan NewCha
201208
if fullConf.MaxAuthTries == 0 {
202209
fullConf.MaxAuthTries = 6
203210
}
211+
if len(fullConf.PublicKeyAuthAlgorithms) == 0 {
212+
fullConf.PublicKeyAuthAlgorithms = supportedPubKeyAuthAlgos
213+
} else {
214+
for _, algo := range fullConf.PublicKeyAuthAlgorithms {
215+
if !contains(supportedPubKeyAuthAlgos, algo) {
216+
return nil, nil, nil, fmt.Errorf("ssh: unsupported public key authentication algorithm %s", algo)
217+
}
218+
}
219+
}
204220
// Check if the config contains any unsupported key exchanges
205221
for _, kex := range fullConf.KeyExchanges {
206222
if _, ok := serverForbiddenKexAlgos[kex]; ok {
@@ -524,7 +540,7 @@ userAuthLoop:
524540
return nil, parseError(msgUserAuthRequest)
525541
}
526542
algo := string(algoBytes)
527-
if !contains(supportedPubKeyAuthAlgos, underlyingAlgo(algo)) {
543+
if !contains(config.PublicKeyAuthAlgorithms, underlyingAlgo(algo)) {
528544
authErr = fmt.Errorf("ssh: algorithm %q not accepted", algo)
529545
break
530546
}
@@ -591,7 +607,7 @@ userAuthLoop:
591607
// algorithm name that corresponds to algo with
592608
// sig.Format. This is usually the same, but
593609
// for certs, the names differ.
594-
if !contains(supportedPubKeyAuthAlgos, sig.Format) {
610+
if !contains(config.PublicKeyAuthAlgorithms, sig.Format) {
595611
authErr = fmt.Errorf("ssh: algorithm %q not accepted", sig.Format)
596612
break
597613
}

ssh/server_test.go

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package ssh
6+
7+
import (
8+
"testing"
9+
)
10+
11+
func TestClientAuthRestrictedPublicKeyAlgos(t *testing.T) {
12+
for _, tt := range []struct {
13+
name string
14+
key Signer
15+
wantError bool
16+
}{
17+
{"rsa", testSigners["rsa"], false},
18+
{"dsa", testSigners["dsa"], true},
19+
{"ed25519", testSigners["ed25519"], true},
20+
} {
21+
c1, c2, err := netPipe()
22+
if err != nil {
23+
t.Fatalf("netPipe: %v", err)
24+
}
25+
defer c1.Close()
26+
defer c2.Close()
27+
serverConf := &ServerConfig{
28+
PublicKeyAuthAlgorithms: []string{KeyAlgoRSASHA256, KeyAlgoRSASHA512},
29+
PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
30+
return nil, nil
31+
},
32+
}
33+
serverConf.AddHostKey(testSigners["ecdsap256"])
34+
35+
done := make(chan struct{})
36+
go func() {
37+
defer close(done)
38+
NewServerConn(c1, serverConf)
39+
}()
40+
41+
clientConf := ClientConfig{
42+
User: "user",
43+
Auth: []AuthMethod{
44+
PublicKeys(tt.key),
45+
},
46+
HostKeyCallback: InsecureIgnoreHostKey(),
47+
}
48+
49+
_, _, _, err = NewClientConn(c2, "", &clientConf)
50+
if err != nil {
51+
if !tt.wantError {
52+
t.Errorf("%s: got unexpected error %q", tt.name, err.Error())
53+
}
54+
} else if tt.wantError {
55+
t.Errorf("%s: succeeded, but want error", tt.name)
56+
}
57+
<-done
58+
}
59+
}
60+
61+
func TestNewServerConnValidationErrors(t *testing.T) {
62+
c1, c2, err := netPipe()
63+
if err != nil {
64+
t.Fatalf("netPipe: %v", err)
65+
}
66+
defer c1.Close()
67+
defer c2.Close()
68+
69+
serverConf := &ServerConfig{
70+
PublicKeyAuthAlgorithms: []string{CertAlgoRSAv01},
71+
}
72+
_, _, _, err = NewServerConn(c1, serverConf)
73+
if err == nil {
74+
t.Fatal("NewServerConn with invalid public key auth algorithms succeeded")
75+
}
76+
serverConf = &ServerConfig{
77+
Config: Config{
78+
KeyExchanges: []string{kexAlgoDHGEXSHA256},
79+
},
80+
}
81+
_, _, _, err = NewServerConn(c1, serverConf)
82+
if err == nil {
83+
t.Fatal("NewServerConn with unsupported key exchange succeeded")
84+
}
85+
}

0 commit comments

Comments
 (0)