1
# -*- test-case-name: twisted.conch.test.test_conch -*-
3
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
4
# See LICENSE for details.
8
"""The lowest level SSH protocol. This handles the key negotiation, the encryption and the compression.
10
This module is unstable.
12
Maintainer: U{Paul Swartz<mailto:z3p@twistedmatrix.com>}
15
from __future__ import nested_scopes
17
# base library imports
22
import math # for math.log
25
# external library imports
26
from Crypto import Util
27
from Crypto.Cipher import XOR
28
from Crypto.PublicKey import RSA
29
from Crypto.Util import randpool
32
from twisted.conch import error
33
from twisted.internet import protocol, defer
34
from twisted.python import log
37
from common import NS, getNS, MP, getMP, _MPpow, ffs, entropy # ease of use
41
class SSHTransportBase(protocol.Protocol):
42
protocolVersion = '2.0'
45
ourVersionString = ('SSH-'+protocolVersion+'-'+version+' '+comment).strip()
47
supportedCiphers = ['aes256-ctr', 'aes256-cbc', 'aes192-ctr', 'aes192-cbc',
48
'aes128-ctr', 'aes128-cbc', 'cast128-ctr',
49
'cast128-cbc', 'blowfish-ctr', 'blowfish', 'idea-ctr'
50
'idea-cbc', '3des-ctr', '3des-cbc'] # ,'none']
51
supportedMACs = ['hmac-sha1', 'hmac-md5'] # , 'none']
53
# both of the above support 'none', but for security are disabled by
54
# default. to enable them, subclass this class and add it, or do:
55
# SSHTransportBase.supportedCiphers.append('none')
57
supportedKeyExchanges = ['diffie-hellman-group-exchange-sha1',
58
'diffie-hellman-group1-sha1']
59
supportedPublicKeys = ['ssh-rsa', 'ssh-dss']
60
supportedCompressions = ['none', 'zlib']
61
supportedLanguages = ()
66
outgoingPacketSequence = 0
67
incomingPacketSequence = 0
68
currentEncryptions = None
69
outgoingCompression = None
70
incomingCompression = None
75
def connectionLost(self, reason):
77
self.service.serviceStopped()
78
if hasattr(self, 'avatar'):
80
log.msg('connection lost')
82
def connectionMade(self):
83
self.transport.write('%s\r\n'%(self.ourVersionString))
86
def sendKexInit(self):
87
self.ourKexInitPayload = chr(MSG_KEXINIT)+entropy.get_bytes(16)+ \
88
NS(','.join(self.supportedKeyExchanges))+ \
89
NS(','.join(self.supportedPublicKeys))+ \
90
NS(','.join(self.supportedCiphers))+ \
91
NS(','.join(self.supportedCiphers))+ \
92
NS(','.join(self.supportedMACs))+ \
93
NS(','.join(self.supportedMACs))+ \
94
NS(','.join(self.supportedCompressions))+ \
95
NS(','.join(self.supportedCompressions))+ \
96
NS(','.join(self.supportedLanguages))+ \
97
NS(','.join(self.supportedLanguages))+ \
98
'\000'+'\000\000\000\000'
99
self.sendPacket(MSG_KEXINIT, self.ourKexInitPayload[1:])
101
def sendPacket(self, messageType, payload):
102
payload = chr(messageType)+payload
103
if self.outgoingCompression:
104
payload = self.outgoingCompression.compress(payload) + self.outgoingCompression.flush(2)
105
if self.currentEncryptions:
106
bs = self.currentEncryptions.enc_block_size
109
totalSize = 5+len(payload)
110
lenPad = bs-(totalSize%bs)
113
packet = struct.pack('!LB', totalSize+lenPad-4, lenPad)+ \
114
payload+entropy.get_bytes(lenPad)
115
assert len(packet)%bs == 0, '%s extra bytes in packet'%(len(packet)%bs)
116
if self.currentEncryptions:
117
encPacket = self.currentEncryptions.encrypt(packet) + self.currentEncryptions.makeMAC(self.outgoingPacketSequence, packet)
120
self.transport.write(encPacket)
121
self.outgoingPacketSequence+=1
124
bs = self.currentEncryptions and self.currentEncryptions.dec_block_size or 8
125
ms = self.currentEncryptions and self.currentEncryptions.verify_digest_size or 0
126
if len(self.buf) < bs: return # not enough data
127
if not hasattr(self, 'first'):
128
if self.currentEncryptions:
129
first = self.currentEncryptions.decrypt(self.buf[: bs])
131
first = self.buf[: bs]
135
packetLen, randomLen = struct.unpack('!LB', first[: 5])
136
if packetLen > 1048576: # 1024 ** 2
137
self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR, 'bad packet length %s'%packetLen)
139
if len(self.buf) < packetLen+4+ms:
141
return # not enough packet
142
if(packetLen+4)%bs != 0:
143
self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR, 'bad packet mod (%s%%%s == %s'%(packetLen+4, bs, (packetLen+4)%bs))
145
encData, self.buf = self.buf[: 4+packetLen], self.buf[4+packetLen:]
146
if self.currentEncryptions:
147
packet = first+self.currentEncryptions.decrypt(encData[bs:])
150
if len(packet) != 4+packetLen:
151
self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR, 'bad packet length')
154
macData, self.buf = self.buf[:ms], self.buf[ms:]
155
if not self.currentEncryptions.verify(self.incomingPacketSequence, packet, macData):
156
self.sendDisconnect(DISCONNECT_MAC_ERROR, 'bad MAC')
158
payload = packet[5: 4+packetLen-randomLen]
159
if self.incomingCompression:
161
payload = self.incomingCompression.decompress(payload)
163
self.sendDisconnect(DISCONNECT_COMPRESSION_ERROR, 'compression error')
165
self.incomingPacketSequence+=1
168
def dataReceived(self, data):
169
self.buf = self.buf+data
170
if not self.gotVersion:
171
parts = self.buf.split('\n')
175
self.otherVersionString = p.strip()
176
if p.split('-')[1]not in('1.99', '2.0'): # bad version
177
self.sendDisconnect(DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED, 'bad version %s'%p.split('-')[1])
180
self.buf = '\n'.join(parts[i+1:])
181
packet = self.getPacket()
183
messageNum = ord(packet[0])
185
messageType = messages[messageNum][4:]
186
f = getattr(self, 'ssh_%s'%messageType, None)
190
log.msg("couldn't handle %s"%messageType)
191
log.msg(repr(packet[1:]))
192
self.sendUnimplemented()
194
log.callWithLogger(self.service, self.service.packetReceived,
195
ord(packet[0]), packet[1:])
197
log.msg("couldn't handle %s"%messageNum)
198
log.msg(repr(packet[1:]))
199
self.sendUnimplemented()
200
packet = self.getPacket()
202
def ssh_DISCONNECT(self, packet):
203
reasonCode = struct.unpack('>L', packet[: 4])[0]
204
description, foo = getNS(packet[4:])
205
self.receiveError(reasonCode, description)
206
self.transport.loseConnection()
208
def ssh_IGNORE(self, packet): pass
210
def ssh_UNIMPLEMENTED(self, packet):
211
seqnum = struct.unpack('>L', packet)
212
self.receiveUnimplemented(seqnum)
214
def ssh_DEBUG(self, packet):
215
alwaysDisplay = ord(packet[0])
216
message, lang, foo = getNS(packet, 2)
217
self.receiveDebug(alwaysDisplay, message, lang)
219
def setService(self, service):
220
log.msg('starting service %s'%service.name)
222
self.service.serviceStopped()
223
self.service = service
224
service.transport = self
225
self.service.serviceStarted()
227
def sendDebug(self, message, alwaysDisplay = 0, language = ''):
228
self.sendPacket(MSG_DEBUG, chr(alwaysDisplay)+NS(message)+NS(language))
230
def sendIgnore(self, message):
231
self.sendPacket(MSG_IGNORE, NS(message))
233
def sendUnimplemented(self):
234
seqnum = self.incomingPacketSequence
235
self.sendPacket(MSG_UNIMPLEMENTED, struct.pack('!L', seqnum))
237
def sendDisconnect(self, reason, desc):
238
self.sendPacket(MSG_DISCONNECT, struct.pack('>L', reason)+NS(desc)+NS(''))
239
log.msg('Disconnecting with error, code %s\nreason: %s'%(reason, desc))
240
self.transport.loseConnection()
243
def receiveError(self, reasonCode, description):
244
log.msg('Got remote error, code %s\nreason: %s'%(reasonCode, description))
246
def receiveUnimplemented(self, seqnum):
247
log.msg('other side unimplemented packet #%s'%seqnum)
249
def receiveDebug(self, alwaysDisplay, message, lang):
251
log.msg('Remote Debug Message:', message)
253
def isEncrypted(self, direction = "out"):
254
"""direction must be in ["out", "in", "both"]
256
if self.currentEncryptions == None:
258
elif direction == "out":
259
return bool(self.currentEncryptions.enc_block_size)
260
elif direction == "in":
261
return bool(self.currentEncryptions.dec_block_size)
262
elif direction == "both":
263
return self.isEncrypted("in") and self.isEncrypted("out")
265
raise TypeError, 'direction must be "out", "in", or "both"'
267
def isVerified(self, direction = "out"):
268
"""direction must be in ["out", "in", "both"]
270
if self.currentEncryptions == None:
272
elif direction == "out":
273
return self.currentEncryptions.outMAC != None
274
elif direction == "in":
275
return self.currentEncryptions.outCMAC != None
276
elif direction == "both":
277
return self.isVerified("in")and self.isVerified("out")
279
raise TypeError, 'direction must be "out", "in", or "both"'
281
def loseConnection(self):
282
self.sendDisconnect(DISCONNECT_CONNECTION_LOST, "user closed connection")
284
class SSHServerTransport(SSHTransportBase):
286
def ssh_KEXINIT(self, packet):
287
self.clientKexInitPayload = chr(MSG_KEXINIT)+packet
288
#cookie = packet[: 16] # taking this is useless
289
k = getNS(packet[16:], 10)
290
strings, rest = k[:-1], k[-1]
291
kexAlgs, keyAlgs, encCS, encSC, macCS, macSC, compCS, compSC, langCS, langSC = \
292
[s.split(',')for s in strings]
293
if ord(rest[0]): # first_kex_packet_follows
294
if kexAlgs[0] != self.supportedKeyExchanges[0]or \
295
keyAlgs[0] != self.supportedPublicKeys[0]or \
296
not ffs(encSC, self.supportedCiphers)or \
297
not ffs(encCS, self.supportedCiphers)or \
298
not ffs(macSC, self.supportedMACs)or \
299
not ffs(macCS, self.supportedMACs)or \
300
not ffs(compCS, self.supportedCompressions)or \
301
not ffs(compSC, self.supportedCompressions):
302
self.ignoreNextPacket = 1 # guess was wrong
303
self.kexAlg = ffs(kexAlgs, self.supportedKeyExchanges)
304
self.keyAlg = ffs(keyAlgs, self.supportedPublicKeys)
305
self.nextEncryptions = SSHCiphers(
306
ffs(encSC, self.supportedCiphers),
307
ffs(encCS, self.supportedCiphers),
308
ffs(macSC, self.supportedMACs),
309
ffs(macCS, self.supportedMACs),
311
self.outgoingCompressionType = ffs(compSC, self.supportedCompressions)
312
self.incomingCompressionType = ffs(compCS, self.supportedCompressions)
313
if None in(self.kexAlg, self.keyAlg, self.outgoingCompressionType, self.incomingCompressionType):
314
self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED, "couldn't match all kex parts")
316
if None in self.nextEncryptions.__dict__.values():
317
self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED, "couldn't match all kex parts")
319
log.msg('kex alg, key alg: %s %s'%(self.kexAlg, self.keyAlg))
320
log.msg('server->client: %s %s %s'%(self.nextEncryptions.outCipType,
321
self.nextEncryptions.outMacType,
322
self.outgoingCompressionType))
323
log.msg('client->server: %s %s %s'%(self.nextEncryptions.inCipType,
324
self.nextEncryptions.inMacType,
325
self.incomingCompressionType))
328
def ssh_KEX_DH_GEX_REQUEST_OLD(self, packet):
329
if self.ignoreNextPacket:
330
self.ignoreNextPacket = 0
332
if self.kexAlg == 'diffie-hellman-group1-sha1': # this is really KEXDH_INIT
333
clientDHPubKey, foo = getMP(packet)
334
y = Util.number.getRandomNumber(16, entropy.get_bytes)
335
f = pow(DH_GENERATOR, y, DH_PRIME)
336
sharedSecret = _MPpow(clientDHPubKey, y, DH_PRIME)
338
h.update(NS(self.otherVersionString))
339
h.update(NS(self.ourVersionString))
340
h.update(NS(self.clientKexInitPayload))
341
h.update(NS(self.ourKexInitPayload))
342
h.update(NS(self.factory.publicKeys[self.keyAlg]))
343
h.update(MP(clientDHPubKey))
345
h.update(sharedSecret)
346
exchangeHash = h.digest()
347
self.sendPacket(MSG_KEXDH_REPLY, NS(self.factory.publicKeys[self.keyAlg])+ \
348
MP(f)+NS(keys.signData(self.factory.privateKeys[self.keyAlg], exchangeHash)))
349
self._keySetup(sharedSecret, exchangeHash)
350
elif self.kexAlg == 'diffie-hellman-group-exchange-sha1':
351
self.kexAlg = 'diffie-hellman-group-exchange-sha1-old'
352
self.ideal = struct.unpack('>L', packet)[0]
353
self.g, self.p = self.factory.getDHPrime(self.ideal)
354
self.sendPacket(MSG_KEX_DH_GEX_GROUP, MP(self.p)+MP(self.g))
356
raise error.ConchError('bad kexalg: %s'%self.kexAlg)
358
def ssh_KEX_DH_GEX_REQUEST(self, packet):
359
if self.ignoreNextPacket:
360
self.ignoreNextPacket = 0
362
self.min, self.ideal, self.max = struct.unpack('>3L', packet)
363
self.g, self.p = self.factory.getDHPrime(self.ideal)
364
self.sendPacket(MSG_KEX_DH_GEX_GROUP, MP(self.p)+MP(self.g))
366
def ssh_KEX_DH_GEX_INIT(self, packet):
367
clientDHPubKey, foo = getMP(packet)
369
# if y < 1024, openssh will reject us: "bad server public DH value".
370
# y<1024 means f will be short, and of the form 2^y, so an observer
371
# could trivially derive our secret y from f. Openssh detects this
372
# and complains, so avoid creating such values by requiring y to be
373
# larger than ln2(self.p)
375
# TODO: we should also look at the value they send to us and reject
376
# insecure values of f (if g==2 and f has a single '1' bit while the
377
# rest are '0's, then they must have used a small y also).
379
# TODO: This could be computed when self.p is set up
380
# or do as openssh does and scan f for a single '1' bit instead
382
minimum = long(math.floor(math.log(self.p) / math.log(2)) + 1)
384
pSize = Util.number.size(self.p)
385
y = Util.number.getRandomNumber(pSize, entropy.get_bytes)
386
while tries < 10 and y < minimum:
388
y = Util.number.getRandomNumber(pSize, entropy.get_bytes)
389
assert(y >= minimum) # TODO: test_conch just hangs if this is hit
390
# the chance of it being hit are really really low
392
f = pow(self.g, y, self.p)
393
sharedSecret = _MPpow(clientDHPubKey, y, self.p)
395
h.update(NS(self.otherVersionString))
396
h.update(NS(self.ourVersionString))
397
h.update(NS(self.clientKexInitPayload))
398
h.update(NS(self.ourKexInitPayload))
399
h.update(NS(self.factory.publicKeys[self.keyAlg]))
400
if self.kexAlg == 'diffie-hellman-group-exchange-sha1':
401
h.update(struct.pack('>3L', self.min, self.ideal, self.max))
403
h.update(struct.pack('>L', self.ideal))
406
h.update(MP(clientDHPubKey))
408
h.update(sharedSecret)
409
exchangeHash = h.digest()
410
self.sendPacket(MSG_KEX_DH_GEX_REPLY, NS(self.factory.publicKeys[self.keyAlg])+ \
411
MP(f)+NS(keys.signData(self.factory.privateKeys[self.keyAlg], exchangeHash)))
412
self._keySetup(sharedSecret, exchangeHash)
414
def ssh_NEWKEYS(self, packet):
416
self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR, "NEWKEYS takes no data")
417
self.currentEncryptions = self.nextEncryptions
418
if self.outgoingCompressionType == 'zlib':
419
self.outgoingCompression = zlib.compressobj(6)
420
#self.outgoingCompression.compress = lambda x: self.outgoingCompression.compress(x) + self.outgoingCompression.flush(zlib.Z_SYNC_FLUSH)
421
if self.incomingCompressionType == 'zlib':
422
self.incomingCompression = zlib.decompressobj()
424
def ssh_SERVICE_REQUEST(self, packet):
425
service, rest = getNS(packet)
426
cls = self.factory.getService(self, service)
428
self.sendDisconnect(DISCONNECT_SERVICE_NOT_AVAILABLE, "don't have service %s"%service)
431
self.sendPacket(MSG_SERVICE_ACCEPT, NS(service))
432
self.setService(cls())
434
def _keySetup(self, sharedSecret, exchangeHash):
435
if not self.sessionID:
436
self.sessionID = exchangeHash
437
initIVCS = self._getKey('A', sharedSecret, exchangeHash)
438
initIVSC = self._getKey('B', sharedSecret, exchangeHash)
439
encKeyCS = self._getKey('C', sharedSecret, exchangeHash)
440
encKeySC = self._getKey('D', sharedSecret, exchangeHash)
441
integKeyCS = self._getKey('E', sharedSecret, exchangeHash)
442
integKeySC = self._getKey('F', sharedSecret, exchangeHash)
443
self.nextEncryptions.setKeys(initIVSC, encKeySC, initIVCS, encKeyCS, integKeySC, integKeyCS)
444
self.sendPacket(MSG_NEWKEYS, '')
446
def _getKey(self, c, sharedSecret, exchangeHash):
447
k1 = sha.new(sharedSecret+exchangeHash+c+self.sessionID).digest()
448
k2 = sha.new(sharedSecret+exchangeHash+k1).digest()
451
class SSHClientTransport(SSHTransportBase):
454
def connectionMade(self):
455
SSHTransportBase.connectionMade(self)
458
def ssh_KEXINIT(self, packet):
459
self.serverKexInitPayload = chr(MSG_KEXINIT)+packet
460
#cookie = packet[: 16] # taking this is unimportant
461
k = getNS(packet[16:], 10)
462
strings, rest = k[:-1], k[-1]
463
kexAlgs, keyAlgs, encCS, encSC, macCS, macSC, compCS, compSC, langCS, langSC = \
464
[s.split(',')for s in strings]
465
self.kexAlg = ffs(self.supportedKeyExchanges, kexAlgs)
466
self.keyAlg = ffs(self.supportedPublicKeys, keyAlgs)
467
self.nextEncryptions = SSHCiphers(
468
ffs(self.supportedCiphers, encCS),
469
ffs(self.supportedCiphers, encSC),
470
ffs(self.supportedMACs, macCS),
471
ffs(self.supportedMACs, macSC),
473
self.outgoingCompressionType = ffs(self.supportedCompressions, compCS)
474
self.incomingCompressionType = ffs(self.supportedCompressions, compSC)
475
if None in(self.kexAlg, self.keyAlg, self.outgoingCompressionType, self.incomingCompressionType):
476
self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED, "couldn't match all kex parts")
478
if None in self.nextEncryptions.__dict__.values():
479
self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED, "couldn't match all kex parts")
481
log.msg('kex alg, key alg: %s %s'%(self.kexAlg, self.keyAlg))
482
log.msg('client->server: %s %s %s'%(self.nextEncryptions.outCipType,
483
self.nextEncryptions.outMacType,
484
self.outgoingCompressionType))
485
log.msg('server->client: %s %s %s'%(self.nextEncryptions.inCipType,
486
self.nextEncryptions.inMacType,
487
self.incomingCompressionType))
489
if self.kexAlg == 'diffie-hellman-group1-sha1':
490
self.x = Util.number.getRandomNumber(512, entropy.get_bytes)
491
self.DHpubKey = pow(DH_GENERATOR, self.x, DH_PRIME)
492
self.sendPacket(MSG_KEXDH_INIT, MP(self.DHpubKey))
494
self.sendPacket(MSG_KEX_DH_GEX_REQUEST_OLD, '\x00\x00\x08\x00')
496
def ssh_KEX_DH_GEX_GROUP(self, packet):
497
if self.kexAlg == 'diffie-hellman-group1-sha1':
498
pubKey, packet = getNS(packet)
499
f, packet = getMP(packet)
500
signature, packet = getNS(packet)
501
fingerprint = ':'.join(map(lambda c: '%02x'%ord(c), md5.new(pubKey).digest()))
502
d = self.verifyHostKey(pubKey, fingerprint)
503
d.addCallback(self._continueGEX_GROUP, pubKey, f, signature)
504
d.addErrback(lambda unused,self=self:self.sendDisconnect(DISCONNECT_HOST_KEY_NOT_VERIFIABLE, 'bad host key'))
506
self.p, rest = getMP(packet)
507
self.g, rest = getMP(rest)
508
self.x = getMP('\x00\x00\x00\x40'+entropy.get_bytes(64))[0]
509
self.DHpubKey = pow(self.g, self.x, self.p)
510
self.sendPacket(MSG_KEX_DH_GEX_INIT, MP(self.DHpubKey))
512
def _continueGEX_GROUP(self, ignored, pubKey, f, signature):
513
serverKey = keys.getPublicKeyObject(pubKey)
514
sharedSecret = _MPpow(f, self.x, DH_PRIME)
516
h.update(NS(self.ourVersionString))
517
h.update(NS(self.otherVersionString))
518
h.update(NS(self.ourKexInitPayload))
519
h.update(NS(self.serverKexInitPayload))
521
h.update(MP(self.DHpubKey))
523
h.update(sharedSecret)
524
exchangeHash = h.digest()
525
if not keys.verifySignature(serverKey, signature, exchangeHash):
526
self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED, 'bad signature')
528
self._keySetup(sharedSecret, exchangeHash)
530
def ssh_KEX_DH_GEX_REPLY(self, packet):
531
pubKey, packet = getNS(packet)
532
f, packet = getMP(packet)
533
signature, packet = getNS(packet)
534
fingerprint = ':'.join(map(lambda c: '%02x'%ord(c), md5.new(pubKey).digest()))
535
d = self.verifyHostKey(pubKey, fingerprint)
536
d.addCallback(self._continueGEX_REPLY, pubKey, f, signature)
537
d.addErrback(lambda unused, self=self: self.sendDisconnect(DISCONNECT_HOST_KEY_NOT_VERIFIABLE, 'bad host key'))
539
def _continueGEX_REPLY(self, ignored, pubKey, f, signature):
540
serverKey = keys.getPublicKeyObject(pubKey)
541
sharedSecret = _MPpow(f, self.x, self.p)
543
h.update(NS(self.ourVersionString))
544
h.update(NS(self.otherVersionString))
545
h.update(NS(self.ourKexInitPayload))
546
h.update(NS(self.serverKexInitPayload))
548
h.update('\x00\x00\x08\x00')
551
h.update(MP(self.DHpubKey))
553
h.update(sharedSecret)
554
exchangeHash = h.digest()
555
if not keys.verifySignature(serverKey, signature, exchangeHash):
556
self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED, 'bad signature')
558
self._keySetup(sharedSecret, exchangeHash)
560
def _keySetup(self, sharedSecret, exchangeHash):
561
if not self.sessionID:
562
self.sessionID = exchangeHash
563
initIVCS = self._getKey('A', sharedSecret, exchangeHash)
564
initIVSC = self._getKey('B', sharedSecret, exchangeHash)
565
encKeyCS = self._getKey('C', sharedSecret, exchangeHash)
566
encKeySC = self._getKey('D', sharedSecret, exchangeHash)
567
integKeyCS = self._getKey('E', sharedSecret, exchangeHash)
568
integKeySC = self._getKey('F', sharedSecret, exchangeHash)
569
self.nextEncryptions.setKeys(initIVCS, encKeyCS, initIVSC, encKeySC, integKeyCS, integKeySC)
570
self.sendPacket(MSG_NEWKEYS, '')
574
def _getKey(self, c, sharedSecret, exchangeHash):
575
k1 = sha.new(sharedSecret+exchangeHash+c+self.sessionID).digest()
576
k2 = sha.new(sharedSecret+exchangeHash+k1).digest()
579
def ssh_NEWKEYS(self, packet):
581
self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR, "NEWKEYS takes no data")
582
if not self.nextEncryptions.enc_block_size:
585
self.currentEncryptions = self.nextEncryptions
586
if self.outgoingCompressionType == 'zlib':
587
self.outgoingCompression = zlib.compressobj(6)
588
#self.outgoingCompression.compress = lambda x: self.outgoingCompression.compress(x) + self.outgoingCompression.flush(zlib.Z_SYNC_FLUSH)
589
if self.incomingCompressionType == 'zlib':
590
self.incomingCompression = zlib.decompressobj()
591
self.connectionSecure()
593
def ssh_SERVICE_ACCEPT(self, packet):
594
name = getNS(packet)[0]
595
if name != self.instance.name:
596
self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR, "received accept for service we did not request")
597
self.setService(self.instance)
599
def requestService(self, instance):
601
Request that a service be run over this transport.
603
@type instance: subclass of L{twisted.conch.ssh.service.SSHService}
605
self.sendPacket(MSG_SERVICE_REQUEST, NS(instance.name))
606
self.instance = instance
609
def verifyHostKey(self, hostKey, fingerprint):
610
"""Returns a Deferred that gets a callback if it is a valid key, or
613
@type hostKey: C{str}
614
@type fingerprint: C{str}
617
# return if it's good
618
return defer.fail(NotImplementedError)
620
def connectionSecure(self):
622
Called when the encryption has been set up. Generally,
623
requestService() is called to run another service over the transport.
625
raise NotImplementedError
630
def encrypt(self, x):
637
'3des-cbc':('DES3', 24, 0),
638
'blowfish-cbc':('Blowfish', 16,0 ),
639
'aes256-cbc':('AES', 32, 0),
640
'aes192-cbc':('AES', 24, 0),
641
'aes128-cbc':('AES', 16, 0),
642
'arcfour':('ARC4', 16, 0),
643
'idea-cbc':('IDEA', 16, 0),
644
'cast128-cbc':('CAST', 16, 0),
645
'aes128-ctr':('AES', 16, 1),
646
'aes192-ctr':('AES', 24, 1),
647
'aes256-ctr':('AES', 32, 1),
648
'3des-ctr':('DES3', 24, 1),
649
'blowfish-ctr':('Blowfish', 16, 1),
650
'idea-ctr':('IDEA', 16, 1),
651
'cast128-ctr':('CAST', 16, 1),
660
def __init__(self, outCip, inCip, outMac, inMac):
661
self.outCipType = outCip
662
self.inCipType = inCip
663
self.outMacType = outMac
664
self.inMacType = inMac
665
self.enc_block_size = 0
666
self.dec_block_size = 0
668
def setKeys(self, outIV, outKey, inIV, inKey, outInteg, inInteg):
669
o = self._getCipher(self.outCipType, outIV, outKey)
670
self.encrypt = o.encrypt
671
self.enc_block_size = o.block_size
672
o = self._getCipher(self.inCipType, inIV, inKey)
673
self.decrypt = o.decrypt
674
self.dec_block_size = o.block_size
675
self.outMAC = self._getMAC(self.outMacType, outInteg)
676
self.inMAC = self._getMAC(self.inMacType, inInteg)
677
self.verify_digest_size = self.inMAC[3]
679
def _getCipher(self, cip, iv, key):
680
modName, keySize, counterMode = self.cipherMap[cip]
681
if not modName: # no cipher
682
return _DummyCipher()
683
mod = __import__('Crypto.Cipher.%s'%modName, {}, {}, 'x')
685
return mod.new(key[:keySize], mod.MODE_CTR, iv[:mod.block_size], counter=_Counter(iv, mod.block_size))
687
return mod.new(key[: keySize], mod.MODE_CBC, iv[: mod.block_size])
689
def _getMAC(self, mac, key):
690
modName = self.macMap[mac]
693
mod = __import__(modName, {}, {}, '')
694
if not hasattr(mod, 'digest_size'):
695
ds = len(mod.new().digest())
698
key = key[: ds]+'\x00'*(64-ds)
699
i = XOR.new('\x36').encrypt(key)
700
o = XOR.new('\x5c').encrypt(key)
703
def encrypt(self, blocks):
706
def decrypt(self, blocks):
709
def makeMAC(self, seqid, data):
710
if not self.outMAC: return ''
711
data = struct.pack('>L', seqid)+data
712
mod, i, o, ds = self.outMAC
713
inner = mod.new(i+data)
714
outer = mod.new(o+inner.digest())
715
return outer.digest()
717
def verify(self, seqid, data, mac):
720
data = struct.pack('>L', seqid)+data
721
mod, i,o, ds = self.inMAC
722
inner = mod.new(i+data)
723
outer = mod.new(o+inner.digest())
724
return mac == outer.digest()
728
Stateful counter which returns results packed in a byte string
730
def __init__(self, initialVector, blockSize):
732
@type initialVector: C{str}
733
@param initialVector: A byte string representing the initial counter value.
735
@type blockSize: C{int}
736
@param blockSize: The length of the output buffer, as well as the
737
number of bytes at the beginning of C{initialVector} to consider.
739
initialVector = initialVector[:blockSize]
740
self.count = getMP('\xff\xff\xff\xff' + initialVector)[0]
741
self.blockSize = blockSize
742
self.count = Util.number.long_to_bytes(self.count - 1)
743
self.count = '\x00' * (self.blockSize - len(self.count)) + self.count
744
self.count = array.array('c', self.count)
745
self.len = len(self.count) - 1
750
Increment the counter and return the new value.
754
self.count[i] = n = chr((ord(self.count[i]) + 1) % 256)
758
return self.count.tostring()
760
self.count = array.array('c', '\x00' * self.blockSize)
761
return self.count.tostring()
765
def buffer_dump(b, title = None):
768
c, b = b[: 16], b[16:]
772
r = r+'%02x%02x '%(ord(a[0]), ord(a[1]))
774
r = r+'%02x'%ord(a[0])
778
DH_PRIME = 179769313486231590770839156793787453197860296048756011706444423684197180216158519368947833795864925541502180565485980503646440548199239100050792877003355816639229553136239076508735759914822574862575007425302077447712589550957937778424442426617334727629299387668709205606050270810842907692932019128194467627007L
783
MSG_UNIMPLEMENTED = 3
785
MSG_SERVICE_REQUEST = 5
786
MSG_SERVICE_ACCEPT = 6
791
MSG_KEX_DH_GEX_REQUEST_OLD = 30
792
MSG_KEX_DH_GEX_REQUEST = 34
793
MSG_KEX_DH_GEX_GROUP = 31
794
MSG_KEX_DH_GEX_INIT = 32
795
MSG_KEX_DH_GEX_REPLY = 33
797
DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT = 1
798
DISCONNECT_PROTOCOL_ERROR = 2
799
DISCONNECT_KEY_EXCHANGE_FAILED = 3
800
DISCONNECT_RESERVED = 4
801
DISCONNECT_MAC_ERROR = 5
802
DISCONNECT_COMPRESSION_ERROR = 6
803
DISCONNECT_SERVICE_NOT_AVAILABLE = 7
804
DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED = 8
805
DISCONNECT_HOST_KEY_NOT_VERIFIABLE = 9
806
DISCONNECT_CONNECTION_LOST = 10
807
DISCONNECT_BY_APPLICATION = 11
808
DISCONNECT_TOO_MANY_CONNECTIONS = 12
809
DISCONNECT_AUTH_CANCELLED_BY_USER = 13
810
DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 14
811
DISCONNECT_ILLEGAL_USER_NAME = 15
814
for name, value in globals().items():
815
if name.startswith('MSG_'):
816
messages[value] = name