Skip to content

Commit 6fef765

Browse files
committed
ssh: expose negotiated algorithms
Fixes golang/go#58523 Fixes golang/go#46638 Change-Id: Ic64bd2fdd6e9ec96acac3ed4be842e2fbb15231d
1 parent 1710f18 commit 6fef765

File tree

10 files changed

+138
-63
lines changed

10 files changed

+138
-63
lines changed

ssh/cipher.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ func newRC4(key, iv []byte) (cipher.Stream, error) {
5858
type cipherMode struct {
5959
keySize int
6060
ivSize int
61-
create func(key, iv []byte, macKey []byte, algs directionAlgorithms) (packetCipher, error)
61+
create func(key, iv []byte, macKey []byte, algs DirectionAlgorithms) (packetCipher, error)
6262
}
6363

64-
func streamCipherMode(skip int, createFunc func(key, iv []byte) (cipher.Stream, error)) func(key, iv []byte, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
65-
return func(key, iv, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
64+
func streamCipherMode(skip int, createFunc func(key, iv []byte) (cipher.Stream, error)) func(key, iv []byte, macKey []byte, algs DirectionAlgorithms) (packetCipher, error) {
65+
return func(key, iv, macKey []byte, algs DirectionAlgorithms) (packetCipher, error) {
6666
stream, err := createFunc(key, iv)
6767
if err != nil {
6868
return nil, err
@@ -307,7 +307,7 @@ type gcmCipher struct {
307307
buf []byte
308308
}
309309

310-
func newGCMCipher(key, iv, unusedMacKey []byte, unusedAlgs directionAlgorithms) (packetCipher, error) {
310+
func newGCMCipher(key, iv, unusedMacKey []byte, unusedAlgs DirectionAlgorithms) (packetCipher, error) {
311311
c, err := aes.NewCipher(key)
312312
if err != nil {
313313
return nil, err
@@ -429,7 +429,7 @@ type cbcCipher struct {
429429
oracleCamouflage uint32
430430
}
431431

432-
func newCBCCipher(c cipher.Block, key, iv, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
432+
func newCBCCipher(c cipher.Block, key, iv, macKey []byte, algs DirectionAlgorithms) (packetCipher, error) {
433433
cbc := &cbcCipher{
434434
mac: macModes[algs.MAC].new(macKey),
435435
decrypter: cipher.NewCBCDecrypter(c, iv),
@@ -443,7 +443,7 @@ func newCBCCipher(c cipher.Block, key, iv, macKey []byte, algs directionAlgorith
443443
return cbc, nil
444444
}
445445

446-
func newAESCBCCipher(key, iv, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
446+
func newAESCBCCipher(key, iv, macKey []byte, algs DirectionAlgorithms) (packetCipher, error) {
447447
c, err := aes.NewCipher(key)
448448
if err != nil {
449449
return nil, err
@@ -457,7 +457,7 @@ func newAESCBCCipher(key, iv, macKey []byte, algs directionAlgorithms) (packetCi
457457
return cbc, nil
458458
}
459459

460-
func newTripleDESCBCCipher(key, iv, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
460+
func newTripleDESCBCCipher(key, iv, macKey []byte, algs DirectionAlgorithms) (packetCipher, error) {
461461
c, err := des.NewTripleDESCipher(key)
462462
if err != nil {
463463
return nil, err
@@ -646,7 +646,7 @@ type chacha20Poly1305Cipher struct {
646646
buf []byte
647647
}
648648

649-
func newChaCha20Cipher(key, unusedIV, unusedMACKey []byte, unusedAlgs directionAlgorithms) (packetCipher, error) {
649+
func newChaCha20Cipher(key, unusedIV, unusedMACKey []byte, unusedAlgs DirectionAlgorithms) (packetCipher, error) {
650650
if len(key) != 64 {
651651
panic(len(key))
652652
}

ssh/cipher_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ func TestPacketCiphers(t *testing.T) {
4444

4545
func testPacketCipher(t *testing.T, cipher, mac string) {
4646
kr := &kexResult{Hash: crypto.SHA1}
47-
algs := directionAlgorithms{
47+
algs := DirectionAlgorithms{
4848
Cipher: cipher,
4949
MAC: mac,
50-
Compression: compressionNone,
50+
compression: compressionNone,
5151
}
5252
client, err := newPacketCipher(clientKeys, algs, kr)
5353
if err != nil {
@@ -77,10 +77,10 @@ func testPacketCipher(t *testing.T, cipher, mac string) {
7777

7878
func TestCBCOracleCounterMeasure(t *testing.T) {
7979
kr := &kexResult{Hash: crypto.SHA1}
80-
algs := directionAlgorithms{
80+
algs := DirectionAlgorithms{
8181
Cipher: InsecureCipherAES128CBC,
8282
MAC: InsecureHMACSHA1,
83-
Compression: compressionNone,
83+
compression: compressionNone,
8484
}
8585
client, err := newPacketCipher(clientKeys, algs, kr)
8686
if err != nil {
@@ -204,10 +204,10 @@ func TestCVE202143565(t *testing.T) {
204204
mac := HMACSHA256
205205

206206
kr := &kexResult{Hash: crypto.SHA1}
207-
algs := directionAlgorithms{
207+
algs := DirectionAlgorithms{
208208
Cipher: tc.cipher,
209209
MAC: mac,
210-
Compression: compressionNone,
210+
compression: compressionNone,
211211
}
212212
client, err := newPacketCipher(clientKeys, algs, kr)
213213
if err != nil {

ssh/client.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ func (c *connection) clientHandshake(dialAddress string, config *ClientConfig) e
110110
}
111111

112112
c.sessionID = c.transport.getSessionID()
113+
c.algorithms = c.transport.getAlgorithms()
113114
return c.clientAuthenticate(config)
114115
}
115116

ssh/common.go

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,14 @@ var (
168168
insecurePubKeyAuthAlgos = []string{KeyAlgoRSA, InsecureKeyAlgoDSA}
169169
)
170170

171+
// NegotiatedAlgorithms defines algorithms negotiated between client and server.
172+
type NegotiatedAlgorithms struct {
173+
KeyExchange string
174+
HostKey string
175+
Read DirectionAlgorithms
176+
Write DirectionAlgorithms
177+
}
178+
171179
// Algorithms defines a set of algorithms that can be configured in the client
172180
// or server config for negotiation during a handshake.
173181
type Algorithms struct {
@@ -278,15 +286,16 @@ func findCommon(what string, client []string, server []string) (common string, e
278286
return "", fmt.Errorf("ssh: no common algorithm for %s; client offered: %v, server offered: %v", what, client, server)
279287
}
280288

281-
// directionAlgorithms records algorithm choices in one direction (either read or write)
282-
type directionAlgorithms struct {
289+
// DirectionAlgorithms defines the algorithms negotiated in one direction
290+
// (either read or write).
291+
type DirectionAlgorithms struct {
283292
Cipher string
284293
MAC string
285-
Compression string
294+
compression string
286295
}
287296

288297
// rekeyBytes returns a rekeying intervals in bytes.
289-
func (a *directionAlgorithms) rekeyBytes() int64 {
298+
func (a *DirectionAlgorithms) rekeyBytes() int64 {
290299
// According to RFC 4344 block ciphers should rekey after
291300
// 2^(BLOCKSIZE/4) blocks. For all AES flavors BLOCKSIZE is
292301
// 128.
@@ -306,27 +315,20 @@ var aeadCiphers = map[string]bool{
306315
CipherChacha20Poly1305: true,
307316
}
308317

309-
type algorithms struct {
310-
kex string
311-
hostKey string
312-
w directionAlgorithms
313-
r directionAlgorithms
314-
}
315-
316-
func findAgreedAlgorithms(isClient bool, clientKexInit, serverKexInit *kexInitMsg) (algs *algorithms, err error) {
317-
result := &algorithms{}
318+
func findAgreedAlgorithms(isClient bool, clientKexInit, serverKexInit *kexInitMsg) (algs *NegotiatedAlgorithms, err error) {
319+
result := &NegotiatedAlgorithms{}
318320

319-
result.kex, err = findCommon("key exchange", clientKexInit.KexAlgos, serverKexInit.KexAlgos)
321+
result.KeyExchange, err = findCommon("key exchange", clientKexInit.KexAlgos, serverKexInit.KexAlgos)
320322
if err != nil {
321323
return
322324
}
323325

324-
result.hostKey, err = findCommon("host key", clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos)
326+
result.HostKey, err = findCommon("host key", clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos)
325327
if err != nil {
326328
return
327329
}
328330

329-
stoc, ctos := &result.w, &result.r
331+
stoc, ctos := &result.Write, &result.Read
330332
if isClient {
331333
ctos, stoc = stoc, ctos
332334
}
@@ -355,12 +357,12 @@ func findAgreedAlgorithms(isClient bool, clientKexInit, serverKexInit *kexInitMs
355357
}
356358
}
357359

358-
ctos.Compression, err = findCommon("client to server compression", clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer)
360+
ctos.compression, err = findCommon("client to server compression", clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer)
359361
if err != nil {
360362
return
361363
}
362364

363-
stoc.Compression, err = findCommon("server to client compression", clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient)
365+
stoc.compression, err = findCommon("server to client compression", clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient)
364366
if err != nil {
365367
return
366368
}

ssh/common_test.go

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -51,33 +51,33 @@ func TestFindAgreedAlgorithms(t *testing.T) {
5151
}
5252
}
5353

54-
initDirAlgs := func(a *directionAlgorithms) {
54+
initDirAlgs := func(a *DirectionAlgorithms) {
5555
if a.Cipher == "" {
5656
a.Cipher = "cipher1"
5757
}
5858
if a.MAC == "" {
5959
a.MAC = "mac1"
6060
}
61-
if a.Compression == "" {
62-
a.Compression = "compression1"
61+
if a.compression == "" {
62+
a.compression = "compression1"
6363
}
6464
}
6565

66-
initAlgs := func(a *algorithms) {
67-
if a.kex == "" {
68-
a.kex = "kex1"
66+
initAlgs := func(a *NegotiatedAlgorithms) {
67+
if a.KeyExchange == "" {
68+
a.KeyExchange = "kex1"
6969
}
70-
if a.hostKey == "" {
71-
a.hostKey = "hostkey1"
70+
if a.HostKey == "" {
71+
a.HostKey = "hostkey1"
7272
}
73-
initDirAlgs(&a.r)
74-
initDirAlgs(&a.w)
73+
initDirAlgs(&a.Read)
74+
initDirAlgs(&a.Write)
7575
}
7676

7777
type testcase struct {
7878
name string
7979
clientIn, serverIn kexInitMsg
80-
wantClient, wantServer algorithms
80+
wantClient, wantServer NegotiatedAlgorithms
8181
wantErr bool
8282
}
8383

@@ -120,19 +120,19 @@ func TestFindAgreedAlgorithms(t *testing.T) {
120120
CiphersClientServer: []string{"cipher2", "cipher1"},
121121
CiphersServerClient: []string{"cipher3", "cipher2"},
122122
},
123-
wantClient: algorithms{
124-
r: directionAlgorithms{
123+
wantClient: NegotiatedAlgorithms{
124+
Read: DirectionAlgorithms{
125125
Cipher: "cipher3",
126126
},
127-
w: directionAlgorithms{
127+
Write: DirectionAlgorithms{
128128
Cipher: "cipher2",
129129
},
130130
},
131-
wantServer: algorithms{
132-
w: directionAlgorithms{
131+
wantServer: NegotiatedAlgorithms{
132+
Write: DirectionAlgorithms{
133133
Cipher: "cipher3",
134134
},
135-
r: directionAlgorithms{
135+
Read: DirectionAlgorithms{
136136
Cipher: "cipher2",
137137
},
138138
},

ssh/connection.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ type Conn interface {
7474
// Disconnect
7575
}
7676

77+
// AlgorithmsConnMetadata is a ConnMetadata that can return the algorithms
78+
// negotiated between client and server.
79+
type AlgorithmsConnMetadata interface {
80+
ConnMetadata
81+
Algorithms() NegotiatedAlgorithms
82+
}
83+
7784
// DiscardRequests consumes and rejects all requests from the
7885
// passed-in channel.
7986
func DiscardRequests(in <-chan *Request) {
@@ -106,6 +113,7 @@ type sshConn struct {
106113
sessionID []byte
107114
clientVersion []byte
108115
serverVersion []byte
116+
algorithms NegotiatedAlgorithms
109117
}
110118

111119
func dup(src []byte) []byte {
@@ -141,3 +149,7 @@ func (c *sshConn) ClientVersion() []byte {
141149
func (c *sshConn) ServerVersion() []byte {
142150
return dup(c.serverVersion)
143151
}
152+
153+
func (c *sshConn) Algorithms() NegotiatedAlgorithms {
154+
return c.algorithms
155+
}

ssh/handshake.go

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ type keyingTransport interface {
3939
// prepareKeyChange sets up a key change. The key change for a
4040
// direction will be effected if a msgNewKeys message is sent
4141
// or received.
42-
prepareKeyChange(*algorithms, *kexResult) error
42+
prepareKeyChange(*NegotiatedAlgorithms, *kexResult) error
4343

4444
// setStrictMode sets the strict KEX mode, notably triggering
4545
// sequence number resets on sending or receiving msgNewKeys.
@@ -116,7 +116,7 @@ type handshakeTransport struct {
116116
bannerCallback BannerCallback
117117

118118
// Algorithms agreed in the last key exchange.
119-
algorithms *algorithms
119+
algorithms *NegotiatedAlgorithms
120120

121121
// Counters exclusively owned by readLoop.
122122
readPacketsLeft uint32
@@ -185,6 +185,10 @@ func (t *handshakeTransport) getSessionID() []byte {
185185
return t.sessionID
186186
}
187187

188+
func (t *handshakeTransport) getAlgorithms() NegotiatedAlgorithms {
189+
return *t.algorithms
190+
}
191+
188192
// waitSession waits for the session to be established. This should be
189193
// the first thing to call after instantiating handshakeTransport.
190194
func (t *handshakeTransport) waitSession() error {
@@ -291,7 +295,7 @@ func (t *handshakeTransport) resetWriteThresholds() {
291295
if t.config.RekeyThreshold > 0 {
292296
t.writeBytesLeft = int64(t.config.RekeyThreshold)
293297
} else if t.algorithms != nil {
294-
t.writeBytesLeft = t.algorithms.w.rekeyBytes()
298+
t.writeBytesLeft = t.algorithms.Write.rekeyBytes()
295299
} else {
296300
t.writeBytesLeft = 1 << 30
297301
}
@@ -408,7 +412,7 @@ func (t *handshakeTransport) resetReadThresholds() {
408412
if t.config.RekeyThreshold > 0 {
409413
t.readBytesLeft = int64(t.config.RekeyThreshold)
410414
} else if t.algorithms != nil {
411-
t.readBytesLeft = t.algorithms.r.rekeyBytes()
415+
t.readBytesLeft = t.algorithms.Read.rekeyBytes()
412416
} else {
413417
t.readBytesLeft = 1 << 30
414418
}
@@ -701,9 +705,9 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
701705
}
702706
}
703707

704-
kex, ok := kexAlgoMap[t.algorithms.kex]
708+
kex, ok := kexAlgoMap[t.algorithms.KeyExchange]
705709
if !ok {
706-
return fmt.Errorf("ssh: unexpected key exchange algorithm %v", t.algorithms.kex)
710+
return fmt.Errorf("ssh: unexpected key exchange algorithm %v", t.algorithms.KeyExchange)
707711
}
708712

709713
var result *kexResult
@@ -810,12 +814,12 @@ func pickHostKey(hostKeys []Signer, algo string) AlgorithmSigner {
810814
}
811815

812816
func (t *handshakeTransport) server(kex kexAlgorithm, magics *handshakeMagics) (*kexResult, error) {
813-
hostKey := pickHostKey(t.hostKeys, t.algorithms.hostKey)
817+
hostKey := pickHostKey(t.hostKeys, t.algorithms.HostKey)
814818
if hostKey == nil {
815819
return nil, errors.New("ssh: internal error: negotiated unsupported signature type")
816820
}
817821

818-
r, err := kex.Server(t.conn, t.config.Rand, magics, hostKey, t.algorithms.hostKey)
822+
r, err := kex.Server(t.conn, t.config.Rand, magics, hostKey, t.algorithms.HostKey)
819823
return r, err
820824
}
821825

@@ -830,7 +834,7 @@ func (t *handshakeTransport) client(kex kexAlgorithm, magics *handshakeMagics) (
830834
return nil, err
831835
}
832836

833-
if err := verifyHostKeySignature(hostKey, t.algorithms.hostKey, result); err != nil {
837+
if err := verifyHostKeySignature(hostKey, t.algorithms.HostKey, result); err != nil {
834838
return nil, err
835839
}
836840

0 commit comments

Comments
 (0)