1
# -*- test-case-name: twisted.conch.test.test_transport -*-
3
# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
4
# See LICENSE for details.
7
The lowest level SSH protocol. This handles the key negotiation, the
8
encryption and the compression. The transport layer is described in
11
Maintainer: Paul Swartz
14
# base library imports
19
# external library imports
20
from Crypto import Util
21
from Crypto.Cipher import XOR
24
from twisted.internet import protocol, defer
25
from twisted.conch import error
26
from twisted.python import log, randbytes
27
from twisted.python.hashlib import md5, sha1
30
from twisted.conch.ssh import keys
31
from twisted.conch.ssh.common import NS, getNS, MP, getMP, _MPpow, ffs
35
class SSHTransportBase(protocol.Protocol):
37
Protocol supporting basic SSH functionality: sending/receiving packets
38
and message dispatch. To connect to or run a server, you must use
39
SSHClientTransport or SSHServerTransport.
41
@ivar protocolVersion: A string representing the version of the SSH
42
protocol we support. Currently defaults to '2.0'.
44
@ivar version: A string representing the version of the server or client.
45
Currently defaults to 'Twisted'.
47
@ivar comment: An optional string giving more information about the
50
@ivar supportedCiphers: A list of strings representing the encryption
51
algorithms supported, in order from most-preferred to least.
53
@ivar supportedMACs: A list of strings representing the message
54
authentication codes (hashes) supported, in order from most-preferred
55
to least. Both this and supportedCiphers can include 'none' to use
56
no encryption or authentication, but that must be done manually,
58
@ivar supportedKeyExchanges: A list of strings representing the
59
key exchanges supported, in order from most-preferred to least.
61
@ivar supportedPublicKeys: A list of strings representing the
62
public key types supported, in order from most-preferred to least.
64
@ivar supportedCompressions: A list of strings representing compression
65
types supported, from most-preferred to least.
67
@ivar supportedLanguages: A list of strings representing languages
68
supported, from most-preferred to least.
70
@ivar isClient: A boolean indicating whether this is a client or server.
72
@ivar gotVersion: A boolean indicating whether we have receieved the
73
version string from the other side.
75
@ivar buf: Data we've received but hasn't been parsed into a packet.
77
@ivar outgoingPacketSequence: the sequence number of the next packet we
80
@ivar incomingPacketSequence: the sequence number of the next packet we
81
are expecting from the other side.
83
@ivar outgoingCompression: an object supporting the .compress(str) and
84
.flush() methods, or None if there is no outgoing compression. Used to
85
compress outgoing data.
87
@ivar outgoingCompressionType: A string representing the outgoing
90
@ivar incomingCompression: an object supporting the .decompress(str)
91
method, or None if there is no incoming compression. Used to
92
decompress incoming data.
94
@ivar incomingCompressionType: A string representing the incoming
97
@ivar ourVersionString: the version string that we sent to the other side.
98
Used in the key exchange.
100
@ivar otherVersionString: the version string sent by the other side. Used
103
@ivar ourKexInitPayload: the MSG_KEXINIT payload we sent. Used in the key
106
@ivar otherKexInitPayload: the MSG_KEXINIT payload we received. Used in
109
@ivar sessionID: a string that is unique to this SSH session. Created as
110
part of the key exchange, sessionID is used to generate the various
111
encryption and authentication keys.
113
@ivar service: an SSHService instance, or None. If it's set to an object,
114
it's the currently running service.
116
@ivar kexAlg: the agreed-upon key exchange algorithm.
118
@ivar keyAlg: the agreed-upon public key type for the key exchange.
120
@ivar currentEncryptions: an SSHCiphers instance. It represents the
121
current encryption and authentication options for the transport.
123
@ivar nextEncryptions: an SSHCiphers instance. Held here until the
124
MSG_NEWKEYS messages are exchanged, when nextEncryptions is
125
transitioned to currentEncryptions.
127
@ivar first: the first bytes of the next packet. In order to avoid
128
decrypting data twice, the first bytes are decrypted and stored until
129
the whole packet is available.
134
protocolVersion = '2.0'
137
ourVersionString = ('SSH-' + protocolVersion + '-' + version + ' '
139
supportedCiphers = ['aes256-ctr', 'aes256-cbc', 'aes192-ctr', 'aes192-cbc',
140
'aes128-ctr', 'aes128-cbc', 'cast128-ctr',
141
'cast128-cbc', 'blowfish-ctr', 'blowfish-cbc',
142
'3des-ctr', '3des-cbc'] # ,'none']
143
supportedMACs = ['hmac-sha1', 'hmac-md5'] # , 'none']
144
# both of the above support 'none', but for security are disabled by
145
# default. to enable them, subclass this class and add it, or do:
146
# SSHTransportBase.supportedCiphers.append('none')
147
supportedKeyExchanges = ['diffie-hellman-group-exchange-sha1',
148
'diffie-hellman-group1-sha1']
149
supportedPublicKeys = ['ssh-rsa', 'ssh-dss']
150
supportedCompressions = ['none', 'zlib']
151
supportedLanguages = ()
155
outgoingPacketSequence = 0
156
incomingPacketSequence = 0
157
outgoingCompression = None
158
incomingCompression = None
163
def connectionLost(self, reason):
165
self.service.serviceStopped()
166
if hasattr(self, 'avatar'):
167
self.logoutFunction()
168
log.msg('connection lost')
171
def connectionMade(self):
173
Called when the connection is made to the other side. We sent our
174
version and the MSG_KEXINIT packet.
176
self.transport.write('%s\r\n' % (self.ourVersionString,))
177
self.currentEncryptions = SSHCiphers('none', 'none', 'none', 'none')
178
self.currentEncryptions.setKeys('', '', '', '', '', '')
182
def sendKexInit(self):
183
self.ourKexInitPayload = (chr(MSG_KEXINIT) +
184
randbytes.secureRandom(16) +
185
NS(','.join(self.supportedKeyExchanges)) +
186
NS(','.join(self.supportedPublicKeys)) +
187
NS(','.join(self.supportedCiphers)) +
188
NS(','.join(self.supportedCiphers)) +
189
NS(','.join(self.supportedMACs)) +
190
NS(','.join(self.supportedMACs)) +
191
NS(','.join(self.supportedCompressions)) +
192
NS(','.join(self.supportedCompressions)) +
193
NS(','.join(self.supportedLanguages)) +
194
NS(','.join(self.supportedLanguages)) +
195
'\000' + '\000\000\000\000')
196
self.sendPacket(MSG_KEXINIT, self.ourKexInitPayload[1:])
199
def sendPacket(self, messageType, payload):
201
Sends a packet. If it's been set up, compress the data, encrypt it,
202
and authenticate it before sending.
204
@param messageType: The type of the packet; generally one of the
206
@type messageType: C{int}
207
@param payload: The payload for the message.
208
@type payload: C{str}
210
payload = chr(messageType) + payload
211
if self.outgoingCompression:
212
payload = (self.outgoingCompression.compress(payload)
213
+ self.outgoingCompression.flush(2))
214
bs = self.currentEncryptions.encBlockSize
215
# 4 for the packet length and 1 for the padding length
216
totalSize = 5 + len(payload)
217
lenPad = bs - (totalSize % bs)
220
packet = (struct.pack('!LB',
221
totalSize + lenPad - 4, lenPad) +
222
payload + randbytes.secureRandom(lenPad))
224
self.currentEncryptions.encrypt(packet) +
225
self.currentEncryptions.makeMAC(
226
self.outgoingPacketSequence, packet))
227
self.transport.write(encPacket)
228
self.outgoingPacketSequence += 1
233
Try to return a decrypted, authenticated, and decompressed packet
234
out of the buffer. If there is not enough data, return None.
236
@rtype: C{str}/C{None}
238
bs = self.currentEncryptions.decBlockSize
239
ms = self.currentEncryptions.verifyDigestSize
240
if len(self.buf) < bs: return # not enough data
241
if not hasattr(self, 'first'):
242
first = self.currentEncryptions.decrypt(self.buf[:bs])
246
packetLen, paddingLen = struct.unpack('!LB', first[:5])
247
if packetLen > 1048576: # 1024 ** 2
248
self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR,
249
'bad packet length %s' % packetLen)
251
if len(self.buf) < packetLen + 4 + ms:
253
return # not enough packet
254
if(packetLen + 4) % bs != 0:
256
DISCONNECT_PROTOCOL_ERROR,
257
'bad packet mod (%i%%%i == %i)' % (packetLen + 4, bs,
258
(packetLen + 4) % bs))
260
encData, self.buf = self.buf[:4 + packetLen], self.buf[4 + packetLen:]
261
packet = first + self.currentEncryptions.decrypt(encData[bs:])
262
if len(packet) != 4 + packetLen:
263
self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR,
267
macData, self.buf = self.buf[:ms], self.buf[ms:]
268
if not self.currentEncryptions.verify(self.incomingPacketSequence,
270
self.sendDisconnect(DISCONNECT_MAC_ERROR, 'bad MAC')
272
payload = packet[5:-paddingLen]
273
if self.incomingCompression:
275
payload = self.incomingCompression.decompress(payload)
276
except: # bare except, because who knows what kind of errors
277
# decompression can raise
279
self.sendDisconnect(DISCONNECT_COMPRESSION_ERROR,
282
self.incomingPacketSequence += 1
286
def dataReceived(self, data):
288
First, check for the version string (SSH-2.0-*). After that has been
289
received, this method adds data to the buffer, and pulls out any
294
self.buf = self.buf + data
295
if not self.gotVersion:
296
if self.buf.find('\n', self.buf.find('SSH-')) == -1:
298
lines = self.buf.split('\n')
300
if p.startswith('SSH-'):
301
self.gotVersion = True
302
self.otherVersionString = p.strip()
303
if p.split('-')[1] not in ('1.99', '2.0'): # bad version
305
DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED,
306
'bad version ' + p.split('-')[1])
309
self.buf = '\n'.join(lines[i + 1:])
310
packet = self.getPacket()
312
messageNum = ord(packet[0])
313
self.dispatchMessage(messageNum, packet[1:])
314
packet = self.getPacket()
317
def dispatchMessage(self, messageNum, payload):
319
Send a received message to the appropriate method.
321
@type messageNum: C{int}
322
@type payload: c{str}
324
if messageNum < 50 and messageNum in messages:
325
messageType = messages[messageNum][4:]
326
f = getattr(self, 'ssh_%s' % messageType, None)
330
log.msg("couldn't handle %s" % messageType)
331
log.msg(repr(payload))
332
self.sendUnimplemented()
334
log.callWithLogger(self.service, self.service.packetReceived,
337
log.msg("couldn't handle %s" % messageNum)
338
log.msg(repr(payload))
339
self.sendUnimplemented()
342
def ssh_KEXINIT(self, packet):
344
Called when we receive a MSG_KEXINIT message. Payload::
346
string keyExchangeAlgorithms
348
string incomingEncryptions
349
string outgoingEncryptions
350
string incomingAuthentications
351
string outgoingAuthentications
352
string incomingCompressions
353
string outgoingCompressions
354
string incomingLanguages
355
string outgoingLanguages
356
bool firstPacketFollows
359
Starts setting up the key exchange, keys, encryptions, and
360
authentications. Extended by ssh_KEXINIT in SSHServerTransport and
363
self.otherKexInitPayload = chr(MSG_KEXINIT) + packet
364
#cookie = packet[: 16] # taking this is useless
365
k = getNS(packet[16:], 10)
366
strings, rest = k[:-1], k[-1]
367
(kexAlgs, keyAlgs, encCS, encSC, macCS, macSC, compCS, compSC, langCS,
368
langSC) = [s.split(',') for s in strings]
369
# these are the server directions
370
outs = [encSC, macSC, compSC]
371
ins = [encCS, macSC, compCS]
373
outs, ins = ins, outs # switch directions
374
server = (self.supportedKeyExchanges, self.supportedPublicKeys,
375
self.supportedCiphers, self.supportedCiphers,
376
self.supportedMACs, self.supportedMACs,
377
self.supportedCompressions, self.supportedCompressions)
378
client = (kexAlgs, keyAlgs, outs[0], ins[0], outs[1], ins[1],
381
server, client = client, server
382
self.kexAlg = ffs(client[0], server[0])
383
self.keyAlg = ffs(client[1], server[1])
384
self.nextEncryptions = SSHCiphers(
385
ffs(client[2], server[2]),
386
ffs(client[3], server[3]),
387
ffs(client[4], server[4]),
388
ffs(client[5], server[5]))
389
self.outgoingCompressionType = ffs(client[6], server[6])
390
self.incomingCompressionType = ffs(client[7], server[7])
391
if None in (self.kexAlg, self.keyAlg, self.outgoingCompressionType,
392
self.incomingCompressionType):
393
self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED,
394
"couldn't match all kex parts")
396
if None in self.nextEncryptions.__dict__.values():
397
self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED,
398
"couldn't match all kex parts")
400
log.msg('kex alg, key alg: %s %s' % (self.kexAlg, self.keyAlg))
401
log.msg('outgoing: %s %s %s' % (self.nextEncryptions.outCipType,
402
self.nextEncryptions.outMACType,
403
self.outgoingCompressionType))
404
log.msg('incoming: %s %s %s' % (self.nextEncryptions.inCipType,
405
self.nextEncryptions.inMACType,
406
self.incomingCompressionType))
407
return kexAlgs, keyAlgs, rest # for SSHServerTransport to use
410
def ssh_DISCONNECT(self, packet):
412
Called when we receive a MSG_DISCONNECT message. Payload::
416
This means that the other side has disconnected. Pass the message up
417
and disconnect ourselves.
419
reasonCode = struct.unpack('>L', packet[: 4])[0]
420
description, foo = getNS(packet[4:])
421
self.receiveError(reasonCode, description)
422
self.transport.loseConnection()
425
def ssh_IGNORE(self, packet):
427
Called when we receieve a MSG_IGNORE message. No payload.
428
This means nothing; we simply return.
432
def ssh_UNIMPLEMENTED(self, packet):
434
Called when we receieve a MSG_UNIMPLEMENTED message. Payload::
437
This means that the other side did not implement one of our packets.
439
seqnum, = struct.unpack('>L', packet)
440
self.receiveUnimplemented(seqnum)
443
def ssh_DEBUG(self, packet):
445
Called when we receieve a MSG_DEBUG message. Payload::
450
This means the other side has passed along some debugging info.
452
alwaysDisplay = bool(packet[0])
453
message, lang, foo = getNS(packet[1:], 2)
454
self.receiveDebug(alwaysDisplay, message, lang)
457
def setService(self, service):
459
Set our service to service and start it running. If we were
460
running a service previously, stop it first.
462
@type service: C{SSHService}
464
log.msg('starting service %s' % service.name)
466
self.service.serviceStopped()
467
self.service = service
468
service.transport = self
469
self.service.serviceStarted()
472
def sendDebug(self, message, alwaysDisplay=False, language=''):
474
Send a debug message to the other side.
476
@param message: the message to send.
477
@type message: C{str}
478
@param alwaysDisplay: if True, tell the other side to always
479
display this message.
480
@type alwaysDisplay: C{bool}
481
@param language: optionally, the language the message is in.
482
@type language: C{str}
484
self.sendPacket(MSG_DEBUG, chr(alwaysDisplay) + NS(message) +
488
def sendIgnore(self, message):
490
Send a message that will be ignored by the other side. This is
491
useful to fool attacks based on guessing packet sizes in the
494
@param message: data to send with the message
495
@type message: C{str}
497
self.sendPacket(MSG_IGNORE, NS(message))
500
def sendUnimplemented(self):
502
Send a message to the other side that the last packet was not
505
seqnum = self.incomingPacketSequence
506
self.sendPacket(MSG_UNIMPLEMENTED, struct.pack('!L', seqnum))
509
def sendDisconnect(self, reason, desc):
511
Send a disconnect message to the other side and then disconnect.
513
@param reason: the reason for the disconnect. Should be one of the
516
@param desc: a descrption of the reason for the disconnection.
520
MSG_DISCONNECT, struct.pack('>L', reason) + NS(desc) + NS(''))
521
log.msg('Disconnecting with error, code %s\nreason: %s' % (reason,
523
self.transport.loseConnection()
526
def _getKey(self, c, sharedSecret, exchangeHash):
528
Get one of the keys for authentication/encryption.
531
@type sharedSecret: C{str}
532
@type exchangeHash: C{str}
534
k1 = sha1(sharedSecret + exchangeHash + c + self.sessionID)
536
k2 = sha1(sharedSecret + exchangeHash + k1).digest()
540
def _keySetup(self, sharedSecret, exchangeHash):
542
Set up the keys for the connection and sends MSG_NEWKEYS when
545
@param sharedSecret: a secret string agreed upon using a Diffie-
546
Hellman exchange, so it is only shared between
547
the server and the client.
548
@type sharedSecret: C{str}
549
@param exchangeHash: A hash of various data known by both sides.
550
@type exchangeHash: C{str}
552
if not self.sessionID:
553
self.sessionID = exchangeHash
554
initIVCS = self._getKey('A', sharedSecret, exchangeHash)
555
initIVSC = self._getKey('B', sharedSecret, exchangeHash)
556
encKeyCS = self._getKey('C', sharedSecret, exchangeHash)
557
encKeySC = self._getKey('D', sharedSecret, exchangeHash)
558
integKeyCS = self._getKey('E', sharedSecret, exchangeHash)
559
integKeySC = self._getKey('F', sharedSecret, exchangeHash)
560
outs = [initIVSC, encKeySC, integKeySC]
561
ins = [initIVCS, encKeyCS, integKeyCS]
562
if self.isClient: # reverse for the client
564
outs, ins = ins, outs
565
self.nextEncryptions.setKeys(outs[0], outs[1], ins[0], ins[1],
567
self.sendPacket(MSG_NEWKEYS, '')
570
def isEncrypted(self, direction="out"):
572
Return True if the connection is encrypted in the given direction.
573
Direction must be one of ["out", "in", "both"].
575
if direction == "out":
576
return self.currentEncryptions.outCipType != 'none'
577
elif direction == "in":
578
return self.currentEncryptions.inCipType != 'none'
579
elif direction == "both":
580
return self.isEncrypted("in") and self.isEncrypted("out")
582
raise TypeError('direction must be "out", "in", or "both"')
585
def isVerified(self, direction="out"):
587
Return True if the connecction is verified/authenticated in the
588
given direction. Direction must be one of ["out", "in", "both"].
590
if direction == "out":
591
return self.currentEncryptions.outMACType != 'none'
592
elif direction == "in":
593
return self.currentEncryptions.inMACType != 'none'
594
elif direction == "both":
595
return self.isVerified("in")and self.isVerified("out")
597
raise TypeError('direction must be "out", "in", or "both"')
600
def loseConnection(self):
602
Lose the connection to the other side, sending a
603
DISCONNECT_CONNECTION_LOST message.
605
self.sendDisconnect(DISCONNECT_CONNECTION_LOST,
606
"user closed connection")
610
def receiveError(self, reasonCode, description):
612
Called when we receive a disconnect error message from the other
615
@param reasonCode: the reason for the disconnect, one of the
617
@type reasonCode: C{int}
618
@param description: a human-readable description of the
620
@type description: C{str}
622
log.msg('Got remote error, code %s\nreason: %s' % (reasonCode,
626
def receiveUnimplemented(self, seqnum):
628
Called when we receive an unimplemented packet message from the other
631
@param seqnum: the sequence number that was not understood.
634
log.msg('other side unimplemented packet #%s' % seqnum)
637
def receiveDebug(self, alwaysDisplay, message, lang):
639
Called when we receive a debug message from the other side.
641
@param alwaysDisplay: if True, this message should always be
643
@type alwaysDisplay: C{bool}
644
@param message: the debug message
645
@type message: C{str}
646
@param lang: optionally the language the message is in.
650
log.msg('Remote Debug Message: %s' % message)
654
class SSHServerTransport(SSHTransportBase):
656
SSHServerTransport implements the server side of the SSH protocol.
658
@ivar isClient: since we are never the client, this is always False.
660
@ivar ignoreNextPacket: if True, ignore the next key exchange packet. This
661
is set when the client sends a guessed key exchange packet but with
664
@ivar dhGexRequest: the KEX_DH_GEX_REQUEST(_OLD) that the client sent.
665
The key generation needs this to be stored.
667
@ivar g: the Diffie-Hellman group generator.
669
@ivar p: the Diffie-Hellman group prime.
675
def ssh_KEXINIT(self, packet):
677
Called when we receive a MSG_KEXINIT message. For a description
678
of the packet, see SSHTransportBase.ssh_KEXINIT(). Additionally,
679
this method checks if a guessed key exchange packet was sent. If
680
it was sent, and it guessed incorrectly, the next key exchange
681
packet MUST be ignored.
683
retval = SSHTransportBase.ssh_KEXINIT(self, packet)
684
if not retval: # disconnected
687
kexAlgs, keyAlgs, rest = retval
688
if ord(rest[0]): # first_kex_packet_follows
689
if (kexAlgs[0] != self.supportedKeyExchanges[0] or
690
keyAlgs[0] != self.supportedPublicKeys[0]):
691
self.ignoreNextPacket = True # guess was wrong
694
def ssh_KEX_DH_GEX_REQUEST_OLD(self, packet):
696
This represents two different key exchange methods that share the
699
KEXDH_INIT (for diffie-hellman-group1-sha1 exchanges) payload::
701
integer e (the client's Diffie-Hellman public key)
703
We send the KEXDH_REPLY with our host key and signature.
705
KEX_DH_GEX_REQUEST_OLD (for diffie-hellman-group-exchange-sha1)
708
integer ideal (ideal size for the Diffie-Hellman prime)
710
We send the KEX_DH_GEX_GROUP message with the group that is
711
closest in size to ideal.
713
If we were told to ignore the next key exchange packet by
714
ssh_KEXINIT, drop it on the floor and return.
716
if self.ignoreNextPacket:
717
self.ignoreNextPacket = 0
719
if self.kexAlg == 'diffie-hellman-group1-sha1':
720
# this is really KEXDH_INIT
721
clientDHpublicKey, foo = getMP(packet)
722
y = Util.number.getRandomNumber(512, randbytes.secureRandom)
723
serverDHpublicKey = _MPpow(DH_GENERATOR, y, DH_PRIME)
724
sharedSecret = _MPpow(clientDHpublicKey, y, DH_PRIME)
726
h.update(NS(self.otherVersionString))
727
h.update(NS(self.ourVersionString))
728
h.update(NS(self.otherKexInitPayload))
729
h.update(NS(self.ourKexInitPayload))
730
h.update(NS(self.factory.publicKeys[self.keyAlg].blob()))
731
h.update(MP(clientDHpublicKey))
732
h.update(serverDHpublicKey)
733
h.update(sharedSecret)
734
exchangeHash = h.digest()
737
NS(self.factory.publicKeys[self.keyAlg].blob()) +
739
NS(self.factory.privateKeys[self.keyAlg].sign(exchangeHash)))
740
self._keySetup(sharedSecret, exchangeHash)
741
elif self.kexAlg == 'diffie-hellman-group-exchange-sha1':
742
self.dhGexRequest = packet
743
ideal = struct.unpack('>L', packet)[0]
744
self.g, self.p = self.factory.getDHPrime(ideal)
745
self.sendPacket(MSG_KEX_DH_GEX_GROUP, MP(self.p) + MP(self.g))
747
raise error.ConchError('bad kexalg: %s' % self.kexAlg)
750
def ssh_KEX_DH_GEX_REQUEST(self, packet):
752
Called when we receive a MSG_KEX_DH_GEX_REQUEST message. Payload::
757
The client is asking for a Diffie-Hellman group between minimum and
758
maximum size, and close to ideal if possible. We reply with a
759
MSG_KEX_DH_GEX_GROUP message.
761
If we were told to ignore the next key exchange packekt by
762
ssh_KEXINIT, drop it on the floor and return.
764
if self.ignoreNextPacket:
765
self.ignoreNextPacket = 0
767
self.dhGexRequest = packet
768
min, ideal, max = struct.unpack('>3L', packet)
769
self.g, self.p = self.factory.getDHPrime(ideal)
770
self.sendPacket(MSG_KEX_DH_GEX_GROUP, MP(self.p) + MP(self.g))
773
def ssh_KEX_DH_GEX_INIT(self, packet):
775
Called when we get a MSG_KEX_DH_GEX_INIT message. Payload::
776
integer e (client DH public key)
778
We send the MSG_KEX_DH_GEX_REPLY message with our host key and
781
clientDHpublicKey, foo = getMP(packet)
782
# TODO: we should also look at the value they send to us and reject
783
# insecure values of f (if g==2 and f has a single '1' bit while the
784
# rest are '0's, then they must have used a small y also).
786
# TODO: This could be computed when self.p is set up
787
# or do as openssh does and scan f for a single '1' bit instead
789
pSize = Util.number.size(self.p)
790
y = Util.number.getRandomNumber(pSize, randbytes.secureRandom)
792
serverDHpublicKey = _MPpow(self.g, y, self.p)
793
sharedSecret = _MPpow(clientDHpublicKey, y, self.p)
795
h.update(NS(self.otherVersionString))
796
h.update(NS(self.ourVersionString))
797
h.update(NS(self.otherKexInitPayload))
798
h.update(NS(self.ourKexInitPayload))
799
h.update(NS(self.factory.publicKeys[self.keyAlg].blob()))
800
h.update(self.dhGexRequest)
803
h.update(MP(clientDHpublicKey))
804
h.update(serverDHpublicKey)
805
h.update(sharedSecret)
806
exchangeHash = h.digest()
808
MSG_KEX_DH_GEX_REPLY,
809
NS(self.factory.publicKeys[self.keyAlg].blob()) +
811
NS(self.factory.privateKeys[self.keyAlg].sign(exchangeHash)))
812
self._keySetup(sharedSecret, exchangeHash)
815
def ssh_NEWKEYS(self, packet):
817
Called when we get a MSG_NEWKEYS message. No payload.
818
When we get this, the keys have been set on both sides, and we
819
start using them to encrypt and authenticate the connection.
823
self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR,
824
"NEWKEYS takes no data")
826
self.currentEncryptions = self.nextEncryptions
827
if self.outgoingCompressionType == 'zlib':
828
self.outgoingCompression = zlib.compressobj(6)
829
if self.incomingCompressionType == 'zlib':
830
self.incomingCompression = zlib.decompressobj()
833
def ssh_SERVICE_REQUEST(self, packet):
835
Called when we get a MSG_SERVICE_REQUEST message. Payload::
838
The client has requested a service. If we can start the service,
839
start it; otherwise, disconnect with
840
DISCONNECT_SERVICE_NOT_AVAILABLE.
842
service, rest = getNS(packet)
843
cls = self.factory.getService(self, service)
845
self.sendDisconnect(DISCONNECT_SERVICE_NOT_AVAILABLE,
846
"don't have service %s" % service)
849
self.sendPacket(MSG_SERVICE_ACCEPT, NS(service))
850
self.setService(cls())
854
class SSHClientTransport(SSHTransportBase):
856
SSHClientTransport implements the client side of the SSH protocol.
858
@ivar isClient: since we are always the client, this is always True.
860
@ivar _gotNewKeys: if we receive a MSG_NEWKEYS message before we are
861
ready to transition to the new keys, this is set to True so we
862
can transition when the keys are ready locally.
864
@ivar x: our Diffie-Hellman private key.
866
@ivar e: our Diffie-Hellman public key.
868
@ivar g: the Diffie-Hellman group generator.
870
@ivar p: the Diffie-Hellman group prime
872
@ivar instance: the SSHService object we are requesting.
877
def connectionMade(self):
879
Called when the connection is started with the server. Just sets
880
up a private instance variable.
882
SSHTransportBase.connectionMade(self)
886
def ssh_KEXINIT(self, packet):
888
Called when we receive a MSG_KEXINIT message. For a description
889
of the packet, see SSHTransportBase.ssh_KEXINIT(). Additionally,
890
this method sends the first key exchange packet. If the agreed-upon
891
exchange is diffie-hellman-group1-sha1, generate a public key
892
and send it in a MSG_KEXDH_INIT message. If the exchange is
893
diffie-hellman-group-exchange-sha1, ask for a 2048 bit group with a
894
MSG_KEX_DH_GEX_REQUEST_OLD message.
896
if SSHTransportBase.ssh_KEXINIT(self, packet) is None:
897
return # we disconnected
898
if self.kexAlg == 'diffie-hellman-group1-sha1':
899
self.x = Util.number.getRandomNumber(512, randbytes.secureRandom)
900
self.e = _MPpow(DH_GENERATOR, self.x, DH_PRIME)
901
self.sendPacket(MSG_KEXDH_INIT, self.e)
902
elif self.kexAlg == 'diffie-hellman-group-exchange-sha1':
903
self.sendPacket(MSG_KEX_DH_GEX_REQUEST_OLD, '\x00\x00\x08\x00')
905
raise error.ConchError("somehow, the kexAlg has been set "
906
"to something we don't support")
909
def ssh_KEX_DH_GEX_GROUP(self, packet):
911
This handles two different message which share an integer value.
912
If the key exchange is diffie-hellman-group1-sha1, this is
913
MSG_KEXDH_REPLY. Payload::
915
integer f (server Diffie-Hellman public key)
918
We verify the host key by calling verifyHostKey, then continue in
919
_continueKEXDH_REPLY.
921
If the key exchange is diffie-hellman-group-exchange-sha1, this is
922
MSG_KEX_DH_GEX_GROUP. Payload::
923
string g (group generator)
924
string p (group prime)
926
We generate a Diffie-Hellman public key and send it in a
927
MSG_KEX_DH_GEX_INIT message.
929
if self.kexAlg == 'diffie-hellman-group1-sha1':
930
# actually MSG_KEXDH_REPLY
931
pubKey, packet = getNS(packet)
932
f, packet = getMP(packet)
933
signature, packet = getNS(packet)
934
fingerprint = ':'.join([ch.encode('hex') for ch in
935
md5(pubKey).digest()])
936
d = self.verifyHostKey(pubKey, fingerprint)
937
d.addCallback(self._continueKEXDH_REPLY, pubKey, f, signature)
939
lambda unused: self.sendDisconnect(
940
DISCONNECT_HOST_KEY_NOT_VERIFIABLE, 'bad host key'))
943
self.p, rest = getMP(packet)
944
self.g, rest = getMP(rest)
945
self.x = Util.number.getRandomNumber(320, randbytes.secureRandom)
946
self.e = _MPpow(self.g, self.x, self.p)
947
self.sendPacket(MSG_KEX_DH_GEX_INIT, self.e)
950
def _continueKEXDH_REPLY(self, ignored, pubKey, f, signature):
952
The host key has been verified, so we generate the keys.
954
@param pubKey: the public key blob for the server's public key.
956
@param f: the server's Diffie-Hellman public key.
958
@param signature: the server's signature, verifying that it has the
960
@type signature: C{str}
962
serverKey = keys.Key.fromString(pubKey)
963
sharedSecret = _MPpow(f, self.x, DH_PRIME)
965
h.update(NS(self.ourVersionString))
966
h.update(NS(self.otherVersionString))
967
h.update(NS(self.ourKexInitPayload))
968
h.update(NS(self.otherKexInitPayload))
972
h.update(sharedSecret)
973
exchangeHash = h.digest()
974
if not serverKey.verify(signature, exchangeHash):
975
self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED,
978
self._keySetup(sharedSecret, exchangeHash)
981
def ssh_KEX_DH_GEX_REPLY(self, packet):
983
Called when we receieve a MSG_KEX_DH_GEX_REPLY message. Payload::
984
string server host key
985
integer f (server DH public key)
987
We verify the host key by calling verifyHostKey, then continue in
990
pubKey, packet = getNS(packet)
991
f, packet = getMP(packet)
992
signature, packet = getNS(packet)
993
fingerprint = ':'.join(map(lambda c: '%02x'%ord(c),
994
md5(pubKey).digest()))
995
d = self.verifyHostKey(pubKey, fingerprint)
996
d.addCallback(self._continueGEX_REPLY, pubKey, f, signature)
998
lambda unused: self.sendDisconnect(
999
DISCONNECT_HOST_KEY_NOT_VERIFIABLE, 'bad host key'))
1003
def _continueGEX_REPLY(self, ignored, pubKey, f, signature):
1005
The host key has been verified, so we generate the keys.
1007
@param pubKey: the public key blob for the server's public key.
1008
@type pubKey: C{str}
1009
@param f: the server's Diffie-Hellman public key.
1011
@param signature: the server's signature, verifying that it has the
1012
correct private key.
1013
@type signature: C{str}
1015
serverKey = keys.Key.fromString(pubKey)
1016
sharedSecret = _MPpow(f, self.x, self.p)
1018
h.update(NS(self.ourVersionString))
1019
h.update(NS(self.otherVersionString))
1020
h.update(NS(self.ourKexInitPayload))
1021
h.update(NS(self.otherKexInitPayload))
1022
h.update(NS(pubKey))
1023
h.update('\x00\x00\x08\x00')
1024
h.update(MP(self.p))
1025
h.update(MP(self.g))
1028
h.update(sharedSecret)
1029
exchangeHash = h.digest()
1030
if not serverKey.verify(signature, exchangeHash):
1031
self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED,
1034
self._keySetup(sharedSecret, exchangeHash)
1037
def _keySetup(self, sharedSecret, exchangeHash):
1039
See SSHTransportBase._keySetup().
1041
SSHTransportBase._keySetup(self, sharedSecret, exchangeHash)
1042
if self._gotNewKeys:
1043
self.ssh_NEWKEYS('')
1046
def ssh_NEWKEYS(self, packet):
1048
Called when we receieve a MSG_NEWKEYS message. No payload.
1049
If we've finished setting up our own keys, start using them.
1050
Otherwise, remeber that we've receieved this message.
1053
self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR,
1054
"NEWKEYS takes no data")
1056
if not self.nextEncryptions.encBlockSize:
1057
self._gotNewKeys = 1
1060
self.currentEncryptions = self.nextEncryptions
1061
if self.outgoingCompressionType == 'zlib':
1062
self.outgoingCompression = zlib.compressobj(6)
1063
if self.incomingCompressionType == 'zlib':
1064
self.incomingCompression = zlib.decompressobj()
1065
self.connectionSecure()
1068
def ssh_SERVICE_ACCEPT(self, packet):
1070
Called when we receieve a MSG_SERVICE_ACCEPT message. Payload::
1073
Start the service we requested.
1075
name = getNS(packet)[0]
1076
if name != self.instance.name:
1077
self.sendDisconnect(
1078
DISCONNECT_PROTOCOL_ERROR,
1079
"received accept for service we did not request")
1080
self.setService(self.instance)
1083
def requestService(self, instance):
1085
Request that a service be run over this transport.
1087
@type instance: subclass of L{twisted.conch.ssh.service.SSHService}
1089
self.sendPacket(MSG_SERVICE_REQUEST, NS(instance.name))
1090
self.instance = instance
1094
def verifyHostKey(self, hostKey, fingerprint):
1096
Returns a Deferred that gets a callback if it is a valid key, or
1099
@type hostKey: C{str}
1100
@type fingerprint: C{str}
1101
@rtype: L{twisted.internet.defer.Deferred}
1103
# return if it's good
1104
return defer.fail(NotImplementedError())
1107
def connectionSecure(self):
1109
Called when the encryption has been set up. Generally,
1110
requestService() is called to run another service over the transport.
1112
raise NotImplementedError()
1118
A cipher for the none encryption method.
1120
@ivar block_size: the block size of the encryption. In the case of the
1121
none cipher, this is 8 bytes.
1126
def encrypt(self, x):
1135
SSHCiphers represents all the encryption operations that need to occur
1136
to encrypt and authenticate the SSH connection.
1138
@cvar cipherMap: A dictionary mapping SSH encryption names to 3-tuples of
1139
(<Crypto.Cipher.* name>, <block size>, <counter mode>)
1140
@cvar macMap: A dictionary mapping SSH MAC names to hash modules.
1142
@ivar outCipType: the string type of the outgoing cipher.
1143
@ivar inCipType: the string type of the incoming cipher.
1144
@ivar outMACType: the string type of the incoming MAC.
1145
@ivar inMACType: the string type of the incoming MAC.
1146
@ivar encBlockSize: the block size of the outgoing cipher.
1147
@ivar decBlockSize: the block size of the incoming cipher.
1148
@ivar verifyDigestSize: the size of the incoming MAC.
1149
@ivar outMAC: a tuple of (<hash module>, <inner key>, <outer key>,
1150
<digest size>) representing the outgoing MAC.
1151
@ivar inMAc: see outMAC, but for the incoming MAC.
1156
'3des-cbc':('DES3', 24, 0),
1157
'blowfish-cbc':('Blowfish', 16,0 ),
1158
'aes256-cbc':('AES', 32, 0),
1159
'aes192-cbc':('AES', 24, 0),
1160
'aes128-cbc':('AES', 16, 0),
1161
'cast128-cbc':('CAST', 16, 0),
1162
'aes128-ctr':('AES', 16, 1),
1163
'aes192-ctr':('AES', 24, 1),
1164
'aes256-ctr':('AES', 32, 1),
1165
'3des-ctr':('DES3', 24, 1),
1166
'blowfish-ctr':('Blowfish', 16, 1),
1167
'cast128-ctr':('CAST', 16, 1),
1168
'none':(None, 0, 0),
1177
def __init__(self, outCip, inCip, outMac, inMac):
1178
self.outCipType = outCip
1179
self.inCipType = inCip
1180
self.outMACType = outMac
1181
self.inMACType = inMac
1182
self.encBlockSize = 0
1183
self.decBlockSize = 0
1184
self.verifyDigestSize = 0
1185
self.outMAC = (None, '', '', 0)
1186
self.inMAC = (None, '', '', 0)
1189
def setKeys(self, outIV, outKey, inIV, inKey, outInteg, inInteg):
1191
Set up the ciphers and hashes using the given keys,
1193
@param outIV: the outgoing initialization vector
1194
@param outKey: the outgoing encryption key
1195
@param inIV: the incoming initialization vector
1196
@param inKey: the incoming encryption key
1197
@param outInteg: the outgoing integrity key
1198
@param inInteg: the incoming integrity key.
1200
o = self._getCipher(self.outCipType, outIV, outKey)
1201
self.encrypt = o.encrypt
1202
self.encBlockSize = o.block_size
1203
o = self._getCipher(self.inCipType, inIV, inKey)
1204
self.decrypt = o.decrypt
1205
self.decBlockSize = o.block_size
1206
self.outMAC = self._getMAC(self.outMACType, outInteg)
1207
self.inMAC = self._getMAC(self.inMACType, inInteg)
1209
self.verifyDigestSize = self.inMAC[3]
1212
def _getCipher(self, cip, iv, key):
1214
Creates an initialized cipher object.
1216
@param cip: the name of the cipher: maps into Crypto.Cipher.*
1217
@param iv: the initialzation vector
1218
@param key: the encryption key
1220
modName, keySize, counterMode = self.cipherMap[cip]
1221
if not modName: # no cipher
1222
return _DummyCipher()
1223
mod = __import__('Crypto.Cipher.%s'%modName, {}, {}, 'x')
1225
return mod.new(key[:keySize], mod.MODE_CTR, iv[:mod.block_size],
1226
counter=_Counter(iv, mod.block_size))
1228
return mod.new(key[:keySize], mod.MODE_CBC, iv[:mod.block_size])
1231
def _getMAC(self, mac, key):
1233
Gets a 4-tuple representing the message authentication code.
1234
(<hash module>, <inner hash value>, <outer hash value>,
1237
@param mac: a key mapping into macMap
1239
@param key: the MAC key.
1242
mod = self.macMap[mac]
1244
return (None, '', '', 0)
1245
ds = mod().digest_size
1246
key = key[:ds] + '\x00' * (64 - ds)
1247
i = XOR.new('\x36').encrypt(key)
1248
o = XOR.new('\x5c').encrypt(key)
1249
return mod, i, o, ds
1252
def encrypt(self, blocks):
1254
Encrypt blocks. Overridden by the encrypt method of a
1255
Crypto.Cipher.* object in setKeys().
1257
@type blocks: C{str}
1259
raise NotImplementedError()
1262
def decrypt(self, blocks):
1264
Decrypt blocks. See encrypt().
1266
@type blocks: C{str}
1268
raise NotImplementedError()
1271
def makeMAC(self, seqid, data):
1273
Create a message authentication code (MAC) for the given packet using
1274
the outgoing MAC values.
1276
@param seqid: the sequence ID of the outgoing packet
1278
@param data: the data to create a MAC for
1282
if not self.outMAC[0]:
1284
data = struct.pack('>L', seqid) + data
1285
mod, i, o, ds = self.outMAC
1286
inner = mod(i + data)
1287
outer = mod(o + inner.digest())
1288
return outer.digest()
1291
def verify(self, seqid, data, mac):
1293
Verify an incoming MAC using the incoming MAC values. Return True
1294
if the MAC is valid.
1296
@param seqid: the sequence ID of the incoming packet
1298
@param data: the packet data to verify
1300
@param mac: the MAC sent with the packet
1304
if not self.inMAC[0]:
1306
data = struct.pack('>L', seqid) + data
1307
mod, i, o, ds = self.inMAC
1308
inner = mod(i + data)
1309
outer = mod(o + inner.digest())
1310
return mac == outer.digest()
1316
Stateful counter which returns results packed in a byte string
1320
def __init__(self, initialVector, blockSize):
1322
@type initialVector: C{str}
1323
@param initialVector: A byte string representing the initial counter
1325
@type blockSize: C{int}
1326
@param blockSize: The length of the output buffer, as well as the
1327
number of bytes at the beginning of C{initialVector} to consider.
1329
initialVector = initialVector[:blockSize]
1330
self.count = getMP('\xff\xff\xff\xff' + initialVector)[0]
1331
self.blockSize = blockSize
1332
self.count = Util.number.long_to_bytes(self.count - 1)
1333
self.count = '\x00' * (self.blockSize - len(self.count)) + self.count
1334
self.count = array.array('c', self.count)
1335
self.len = len(self.count) - 1
1340
Increment the counter and return the new value.
1344
self.count[i] = n = chr((ord(self.count[i]) + 1) % 256)
1348
return self.count.tostring()
1350
self.count = array.array('c', '\x00' * self.blockSize)
1351
return self.count.tostring()
1355
# Diffie-Hellman primes from Oakley Group 2 [RFC 2409]
1356
DH_PRIME = long('17976931348623159077083915679378745319786029604875601170644'
1357
'442368419718021615851936894783379586492554150218056548598050364644054819923'
1358
'910005079287700335581663922955313623907650873575991482257486257500742530207'
1359
'744771258955095793777842444242661733472762929938766870920560605027081084290'
1360
'7692932019128194467627007L')
1367
MSG_UNIMPLEMENTED = 3
1369
MSG_SERVICE_REQUEST = 5
1370
MSG_SERVICE_ACCEPT = 6
1374
MSG_KEXDH_REPLY = 31
1375
MSG_KEX_DH_GEX_REQUEST_OLD = 30
1376
MSG_KEX_DH_GEX_REQUEST = 34
1377
MSG_KEX_DH_GEX_GROUP = 31
1378
MSG_KEX_DH_GEX_INIT = 32
1379
MSG_KEX_DH_GEX_REPLY = 33
1383
DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT = 1
1384
DISCONNECT_PROTOCOL_ERROR = 2
1385
DISCONNECT_KEY_EXCHANGE_FAILED = 3
1386
DISCONNECT_RESERVED = 4
1387
DISCONNECT_MAC_ERROR = 5
1388
DISCONNECT_COMPRESSION_ERROR = 6
1389
DISCONNECT_SERVICE_NOT_AVAILABLE = 7
1390
DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED = 8
1391
DISCONNECT_HOST_KEY_NOT_VERIFIABLE = 9
1392
DISCONNECT_CONNECTION_LOST = 10
1393
DISCONNECT_BY_APPLICATION = 11
1394
DISCONNECT_TOO_MANY_CONNECTIONS = 12
1395
DISCONNECT_AUTH_CANCELLED_BY_USER = 13
1396
DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 14
1397
DISCONNECT_ILLEGAL_USER_NAME = 15
1402
for name, value in globals().items():
1403
if name.startswith('MSG_'):
1404
messages[value] = name