Skip to content

Commit b95136d

Browse files
drakkaniQQBot
authored andcommitted
ssh: add support for extension negotiation (rfc 8308)
This is a rebase of the following PR golang#197 with some changes and improvements: - added support for client certificate authentication - removed read loop from server handshake - adapted extInfoMsg to upstream changes Signed-off-by: Nicola Murino <[email protected]>
1 parent 9e6db79 commit b95136d

File tree

3 files changed

+99
-1
lines changed

3 files changed

+99
-1
lines changed

ssh/common.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,18 @@ const (
2525
serviceSSH = "ssh-connection"
2626
)
2727

28+
// These are string constants related to extensions and extension negotiation
29+
const (
30+
extInfoServer = "ext-info-s"
31+
extInfoClient = "ext-info-c"
32+
ExtServerSigAlgs = "server-sig-algs"
33+
)
34+
35+
// defaultExtensions lists extensions enabled by default.
36+
var defaultExtensions = []string{
37+
ExtServerSigAlgs,
38+
}
39+
2840
// supportedCiphers lists ciphers we support but might not recommend.
2941
var supportedCiphers = []string{
3042
"aes128-ctr", "aes192-ctr", "aes256-ctr",
@@ -90,6 +102,15 @@ var supportedMACs = []string{
90102

91103
var supportedCompressions = []string{compressionNone}
92104

105+
// supportedServerSigAlgs defines the algorithms supported for pubkey authentication
106+
// in no particular order.
107+
var supportedServerSigAlgs = []string{KeyAlgoRSASHA256,
108+
KeyAlgoRSASHA512, KeyAlgoRSA,
109+
KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
110+
KeyAlgoSKECDSA256, KeyAlgoED25519, KeyAlgoSKED25519,
111+
KeyAlgoDSA,
112+
}
113+
93114
// hashFuncs keeps the mapping of supported signature algorithms to their
94115
// respective hashes needed for signing and verification.
95116
var hashFuncs = map[string]crypto.Hash{
@@ -203,6 +224,10 @@ func findAgreedAlgorithms(isClient bool, clientKexInit, serverKexInit *kexInitMs
203224
result.kex, err = findCommon("key exchange", clientKexInit.KexAlgos, serverKexInit.KexAlgos)
204225
if err != nil {
205226
return
227+
} else if result.kex == extInfoClient || result.kex == extInfoServer {
228+
// According to RFC8308 section 2.2 if either the client or server extension signal
229+
// is chosen as the kex algorithm the parties must disconnect.
230+
return result, fmt.Errorf("ssh: invalid kex algorithm chosen: %s", result.kex)
206231
}
207232

208233
result.hostKey, err = findCommon("host key", clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos)
@@ -280,6 +305,10 @@ type Config struct {
280305
// The allowed MAC algorithms. If unspecified then a sensible default
281306
// is used.
282307
MACs []string
308+
309+
// A list of enabled extensions. If unspecified then a sensible
310+
// default is used
311+
Extensions []string
283312
}
284313

285314
// SetDefaults sets sensible values for unset fields in config. This is
@@ -309,6 +338,10 @@ func (c *Config) SetDefaults() {
309338
c.MACs = supportedMACs
310339
}
311340

341+
if c.Extensions == nil {
342+
c.Extensions = defaultExtensions
343+
}
344+
312345
if c.RekeyThreshold == 0 {
313346
// cipher specific default
314347
} else if c.RekeyThreshold < minRekeyThreshold {

ssh/handshake.go

Lines changed: 36 additions & 1 deletion
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

@@ -95,6 +96,10 @@ type handshakeTransport struct {
9596

9697
// The session ID or nil if first kex did not complete yet.
9798
sessionID []byte
99+
100+
// True if the first ext info message has been sent immediately following
101+
// SSH_MSG_NEWKEYS, false otherwise.
102+
extInfoSent bool
98103
}
99104

100105
type pendingKex struct {
@@ -477,6 +482,9 @@ func (t *handshakeTransport) sendKexInit() error {
477482
msg.ServerHostKeyAlgos = append(msg.ServerHostKeyAlgos, keyFormat)
478483
}
479484
}
485+
if contains(t.config.Extensions, ExtServerSigAlgs) {
486+
msg.KexAlgos = append(msg.KexAlgos, extInfoServer)
487+
}
480488
} else {
481489
msg.ServerHostKeyAlgos = t.hostKeyAlgorithms
482490

@@ -486,7 +494,7 @@ func (t *handshakeTransport) sendKexInit() error {
486494
if firstKeyExchange := t.sessionID == nil; firstKeyExchange {
487495
msg.KexAlgos = make([]string, 0, len(t.config.KeyExchanges)+1)
488496
msg.KexAlgos = append(msg.KexAlgos, t.config.KeyExchanges...)
489-
msg.KexAlgos = append(msg.KexAlgos, "ext-info-c")
497+
msg.KexAlgos = append(msg.KexAlgos, extInfoClient)
490498
}
491499
}
492500

@@ -663,6 +671,33 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
663671
return unexpectedMessageError(msgNewKeys, packet[0])
664672
}
665673

674+
if !isClient {
675+
// We're on the server side, see if the client sent the extension signal
676+
if !t.extInfoSent && contains(clientInit.KexAlgos, extInfoClient) && contains(t.config.Extensions, ExtServerSigAlgs) {
677+
// The other side supports ext info, an ext info message hasn't been sent this session,
678+
// and we have at least one extension enabled, so send an SSH_MSG_EXT_INFO message.
679+
extensions := map[string][]byte{}
680+
// We're the server, the client supports SSH_MSG_EXT_INFO and server-sig-algs
681+
// is enabled. Prepare the server-sig-algos extension message to send.
682+
extensions[ExtServerSigAlgs] = []byte(strings.Join(supportedServerSigAlgs, ","))
683+
var payload []byte
684+
for k, v := range extensions {
685+
payload = appendInt(payload, len(k))
686+
payload = append(payload, k...)
687+
payload = appendInt(payload, len(v))
688+
payload = append(payload, v...)
689+
}
690+
extInfo := extInfoMsg{
691+
NumExtensions: uint32(len(extensions)),
692+
Payload: payload,
693+
}
694+
if err := t.conn.writePacket(Marshal(&extInfo)); err != nil {
695+
return err
696+
}
697+
t.extInfoSent = true
698+
}
699+
}
700+
666701
return nil
667702
}
668703

ssh/server.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,11 +274,41 @@ func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error)
274274
// We just did the key change, so the session ID is established.
275275
s.sessionID = s.transport.getSessionID()
276276

277+
// the client could send a SSH_MSG_EXT_INFO before SSH_MSG_SERVICE_REQUEST
277278
var packet []byte
278279
if packet, err = s.transport.readPacket(); err != nil {
279280
return nil, err
280281
}
281282

283+
// be permissive and don't add contains(s.transport.config.Extensions, ExtServerSigAlgs)
284+
if len(packet) > 0 && packet[0] == msgExtInfo {
285+
// read SSH_MSG_EXT_INFO
286+
var extInfo extInfoMsg
287+
extensions := make(map[string][]byte)
288+
if err := Unmarshal(packet, &extInfo); err != nil {
289+
return nil, err
290+
}
291+
payload := extInfo.Payload
292+
for i := uint32(0); i < extInfo.NumExtensions; i++ {
293+
name, rest, ok := parseString(payload)
294+
if !ok {
295+
return nil, parseError(msgExtInfo)
296+
}
297+
value, rest, ok := parseString(rest)
298+
if !ok {
299+
return nil, parseError(msgExtInfo)
300+
}
301+
extensions[string(name)] = value
302+
payload = rest
303+
}
304+
305+
// read the next packet
306+
packet = nil
307+
if packet, err = s.transport.readPacket(); err != nil {
308+
return nil, err
309+
}
310+
}
311+
282312
var serviceRequest serviceRequestMsg
283313
if err = Unmarshal(packet, &serviceRequest); err != nil {
284314
return nil, err

0 commit comments

Comments
 (0)