| 1 |
|
|---|
| 2 |
|
|---|
| 3 |
|
|---|
| 4 |
|
|---|
| 5 |
|
|---|
| 6 |
""" |
|---|
| 7 |
The lowest level SSH protocol. This handles the key negotiation, the |
|---|
| 8 |
encryption and the compression. The transport layer is described in |
|---|
| 9 |
RFC 4253. |
|---|
| 10 |
|
|---|
| 11 |
Maintainer: Paul Swartz |
|---|
| 12 |
""" |
|---|
| 13 |
|
|---|
| 14 |
|
|---|
| 15 |
import struct |
|---|
| 16 |
import zlib |
|---|
| 17 |
import array |
|---|
| 18 |
|
|---|
| 19 |
|
|---|
| 20 |
from Crypto import Util |
|---|
| 21 |
from Crypto.Cipher import XOR |
|---|
| 22 |
|
|---|
| 23 |
|
|---|
| 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 |
|---|
| 28 |
|
|---|
| 29 |
|
|---|
| 30 |
from twisted.conch.ssh import keys |
|---|
| 31 |
from twisted.conch.ssh.common import NS, getNS, MP, getMP, _MPpow, ffs |
|---|
| 32 |
|
|---|
| 33 |
|
|---|
| 34 |
|
|---|
| 35 |
class SSHTransportBase(protocol.Protocol): |
|---|
| 36 |
""" |
|---|
| 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. |
|---|
| 40 |
|
|---|
| 41 |
@ivar protocolVersion: A string representing the version of the SSH |
|---|
| 42 |
protocol we support. Currently defaults to '2.0'. |
|---|
| 43 |
|
|---|
| 44 |
@ivar version: A string representing the version of the server or client. |
|---|
| 45 |
Currently defaults to 'Twisted'. |
|---|
| 46 |
|
|---|
| 47 |
@ivar comment: An optional string giving more information about the |
|---|
| 48 |
server or client. |
|---|
| 49 |
|
|---|
| 50 |
@ivar supportedCiphers: A list of strings representing the encryption |
|---|
| 51 |
algorithms supported, in order from most-preferred to least. |
|---|
| 52 |
|
|---|
| 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, |
|---|
| 57 |
|
|---|
| 58 |
@ivar supportedKeyExchanges: A list of strings representing the |
|---|
| 59 |
key exchanges supported, in order from most-preferred to least. |
|---|
| 60 |
|
|---|
| 61 |
@ivar supportedPublicKeys: A list of strings representing the |
|---|
| 62 |
public key types supported, in order from most-preferred to least. |
|---|
| 63 |
|
|---|
| 64 |
@ivar supportedCompressions: A list of strings representing compression |
|---|
| 65 |
types supported, from most-preferred to least. |
|---|
| 66 |
|
|---|
| 67 |
@ivar supportedLanguages: A list of strings representing languages |
|---|
| 68 |
supported, from most-preferred to least. |
|---|
| 69 |
|
|---|
| 70 |
@ivar isClient: A boolean indicating whether this is a client or server. |
|---|
| 71 |
|
|---|
| 72 |
@ivar gotVersion: A boolean indicating whether we have receieved the |
|---|
| 73 |
version string from the other side. |
|---|
| 74 |
|
|---|
| 75 |
@ivar buf: Data we've received but hasn't been parsed into a packet. |
|---|
| 76 |
|
|---|
| 77 |
@ivar outgoingPacketSequence: the sequence number of the next packet we |
|---|
| 78 |
will send. |
|---|
| 79 |
|
|---|
| 80 |
@ivar incomingPacketSequence: the sequence number of the next packet we |
|---|
| 81 |
are expecting from the other side. |
|---|
| 82 |
|
|---|
| 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. |
|---|
| 86 |
|
|---|
| 87 |
@ivar outgoingCompressionType: A string representing the outgoing |
|---|
| 88 |
compression type. |
|---|
| 89 |
|
|---|
| 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. |
|---|
| 93 |
|
|---|
| 94 |
@ivar incomingCompressionType: A string representing the incoming |
|---|
| 95 |
compression type. |
|---|
| 96 |
|
|---|
| 97 |
@ivar ourVersionString: the version string that we sent to the other side. |
|---|
| 98 |
Used in the key exchange. |
|---|
| 99 |
|
|---|
| 100 |
@ivar otherVersionString: the version string sent by the other side. Used |
|---|
| 101 |
in the key exchange. |
|---|
| 102 |
|
|---|
| 103 |
@ivar ourKexInitPayload: the MSG_KEXINIT payload we sent. Used in the key |
|---|
| 104 |
exchange. |
|---|
| 105 |
|
|---|
| 106 |
@ivar otherKexInitPayload: the MSG_KEXINIT payload we received. Used in |
|---|
| 107 |
the key exchange |
|---|
| 108 |
|
|---|
| 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. |
|---|
| 112 |
|
|---|
| 113 |
@ivar service: an SSHService instance, or None. If it's set to an object, |
|---|
| 114 |
it's the currently running service. |
|---|
| 115 |
|
|---|
| 116 |
@ivar kexAlg: the agreed-upon key exchange algorithm. |
|---|
| 117 |
|
|---|
| 118 |
@ivar keyAlg: the agreed-upon public key type for the key exchange. |
|---|
| 119 |
|
|---|
| 120 |
@ivar currentEncryptions: an SSHCiphers instance. It represents the |
|---|
| 121 |
current encryption and authentication options for the transport. |
|---|
| 122 |
|
|---|
| 123 |
@ivar nextEncryptions: an SSHCiphers instance. Held here until the |
|---|
| 124 |
MSG_NEWKEYS messages are exchanged, when nextEncryptions is |
|---|
| 125 |
transitioned to currentEncryptions. |
|---|
| 126 |
|
|---|
| 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. |
|---|
| 130 |
|
|---|
| 131 |
""" |
|---|
| 132 |
|
|---|
| 133 |
|
|---|
| 134 |
protocolVersion = '2.0' |
|---|
| 135 |
version = 'Twisted' |
|---|
| 136 |
comment = '' |
|---|
| 137 |
ourVersionString = ('SSH-' + protocolVersion + '-' + version + ' ' |
|---|
| 138 |
+ comment).strip() |
|---|
| 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'] |
|---|
| 143 |
supportedMACs = ['hmac-sha1', 'hmac-md5'] |
|---|
| 144 |
|
|---|
| 145 |
|
|---|
| 146 |
|
|---|
| 147 |
supportedKeyExchanges = ['diffie-hellman-group-exchange-sha1', |
|---|
| 148 |
'diffie-hellman-group1-sha1'] |
|---|
| 149 |
supportedPublicKeys = ['ssh-rsa', 'ssh-dss'] |
|---|
| 150 |
supportedCompressions = ['none', 'zlib'] |
|---|
| 151 |
supportedLanguages = () |
|---|
| 152 |
isClient = False |
|---|
| 153 |
gotVersion = False |
|---|
| 154 |
buf = '' |
|---|
| 155 |
outgoingPacketSequence = 0 |
|---|
| 156 |
incomingPacketSequence = 0 |
|---|
| 157 |
outgoingCompression = None |
|---|
| 158 |
incomingCompression = None |
|---|
| 159 |
sessionID = None |
|---|
| 160 |
service = None |
|---|
| 161 |
|
|---|
| 162 |
|
|---|
| 163 |
def connectionLost(self, reason): |
|---|
| 164 |
if self.service: |
|---|
| 165 |
self.service.serviceStopped() |
|---|
| 166 |
if hasattr(self, 'avatar'): |
|---|
| 167 |
self.logoutFunction() |
|---|
| 168 |
log.msg('connection lost') |
|---|
| 169 |
|
|---|
| 170 |
|
|---|
| 171 |
def connectionMade(self): |
|---|
| 172 |
""" |
|---|
| 173 |
Called when the connection is made to the other side. We sent our |
|---|
| 174 |
version and the MSG_KEXINIT packet. |
|---|
| 175 |
""" |
|---|
| 176 |
self.transport.write('%s\r\n' % (self.ourVersionString,)) |
|---|
| 177 |
self.currentEncryptions = SSHCiphers('none', 'none', 'none', 'none') |
|---|
| 178 |
self.currentEncryptions.setKeys('', '', '', '', '', '') |
|---|
| 179 |
self.sendKexInit() |
|---|
| 180 |
|
|---|
| 181 |
|
|---|
| 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:]) |
|---|
| 197 |
|
|---|
| 198 |
|
|---|
| 199 |
def sendPacket(self, messageType, payload): |
|---|
| 200 |
""" |
|---|
| 201 |
Sends a packet. If it's been set up, compress the data, encrypt it, |
|---|
| 202 |
and authenticate it before sending. |
|---|
| 203 |
|
|---|
| 204 |
@param messageType: The type of the packet; generally one of the |
|---|
| 205 |
MSG_* values. |
|---|
| 206 |
@type messageType: C{int} |
|---|
| 207 |
@param payload: The payload for the message. |
|---|
| 208 |
@type payload: C{str} |
|---|
| 209 |
""" |
|---|
| 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 |
|
|---|
| 216 |
totalSize = 5 + len(payload) |
|---|
| 217 |
lenPad = bs - (totalSize % bs) |
|---|
| 218 |
if lenPad < 4: |
|---|
| 219 |
lenPad = lenPad + bs |
|---|
| 220 |
packet = (struct.pack('!LB', |
|---|
| 221 |
totalSize + lenPad - 4, lenPad) + |
|---|
| 222 |
payload + randbytes.secureRandom(lenPad)) |
|---|
| 223 |
encPacket = ( |
|---|
| 224 |
self.currentEncryptions.encrypt(packet) + |
|---|
| 225 |
self.currentEncryptions.makeMAC( |
|---|
| 226 |
self.outgoingPacketSequence, packet)) |
|---|
| 227 |
self.transport.write(encPacket) |
|---|
| 228 |
self.outgoingPacketSequence += 1 |
|---|
| 229 |
|
|---|
| 230 |
|
|---|
| 231 |
def getPacket(self): |
|---|
| 232 |
""" |
|---|
| 233 |
Try to return a decrypted, authenticated, and decompressed packet |
|---|
| 234 |
out of the buffer. If there is not enough data, return None. |
|---|
| 235 |
|
|---|
| 236 |
@rtype: C{str}/C{None} |
|---|
| 237 |
""" |
|---|
| 238 |
bs = self.currentEncryptions.decBlockSize |
|---|
| 239 |
ms = self.currentEncryptions.verifyDigestSize |
|---|
| 240 |
if len(self.buf) < bs: return |
|---|
| 241 |
if not hasattr(self, 'first'): |
|---|
| 242 |
first = self.currentEncryptions.decrypt(self.buf[:bs]) |
|---|
| 243 |
else: |
|---|
| 244 |
first = self.first |
|---|
| 245 |
del self.first |
|---|
| 246 |
packetLen, paddingLen = struct.unpack('!LB', first[:5]) |
|---|
| 247 |
if packetLen > 1048576: |
|---|
| 248 |
self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR, |
|---|
| 249 |
'bad packet length %s' % packetLen) |
|---|
| 250 |
return |
|---|
| 251 |
if len(self.buf) < packetLen + 4 + ms: |
|---|
| 252 |
self.first = first |
|---|
| 253 |
return |
|---|
| 254 |
if(packetLen + 4) % bs != 0: |
|---|
| 255 |
self.sendDisconnect( |
|---|
| 256 |
DISCONNECT_PROTOCOL_ERROR, |
|---|
| 257 |
'bad packet mod (%i%%%i == %i)' % (packetLen + 4, bs, |
|---|
| 258 |
(packetLen + 4) % bs)) |
|---|
| 259 |
return |
|---|
| 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, |
|---|
| 264 |
'bad decryption') |
|---|
| 265 |
return |
|---|
| 266 |
if ms: |
|---|
| 267 |
macData, self.buf = self.buf[:ms], self.buf[ms:] |
|---|
| 268 |
if not self.currentEncryptions.verify(self.incomingPacketSequence, |
|---|
| 269 |
packet, macData): |
|---|
| 270 |
self.sendDisconnect(DISCONNECT_MAC_ERROR, 'bad MAC') |
|---|
| 271 |
return |
|---|
| 272 |
payload = packet[5:-paddingLen] |
|---|
| 273 |
if self.incomingCompression: |
|---|
| 274 |
try: |
|---|
| 275 |
payload = self.incomingCompression.decompress(payload) |
|---|
| 276 |
except: |
|---|
| 277 |
|
|---|
| 278 |
log.err() |
|---|
| 279 |
self.sendDisconnect(DISCONNECT_COMPRESSION_ERROR, |
|---|
| 280 |
'compression error') |
|---|
| 281 |
return |
|---|
| 282 |
self.incomingPacketSequence += 1 |
|---|
| 283 |
return payload |
|---|
| 284 |
|
|---|
| 285 |
|
|---|
| 286 |
def dataReceived(self, data): |
|---|
| 287 |
""" |
|---|
| 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 |
|---|
| 290 |
packets. |
|---|
| 291 |
|
|---|
| 292 |
@type data: C{str} |
|---|
| 293 |
""" |
|---|
| 294 |
self.buf = self.buf + data |
|---|
| 295 |
if not self.gotVersion: |
|---|
| 296 |
if self.buf.find('\n', self.buf.find('SSH-')) == -1: |
|---|
| 297 |
return |
|---|
| 298 |
lines = self.buf.split('\n') |
|---|
| 299 |
for p in lines: |
|---|
| 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'): |
|---|
| 304 |
self.sendDisconnect( |
|---|
| 305 |
DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED, |
|---|
| 306 |
'bad version ' + p.split('-')[1]) |
|---|
| 307 |
return |
|---|
| 308 |
i = lines.index(p) |
|---|
| 309 |
self.buf = '\n'.join(lines[i + 1:]) |
|---|
| 310 |
packet = self.getPacket() |
|---|
| 311 |
while packet: |
|---|
| 312 |
messageNum = ord(packet[0]) |
|---|
| 313 |
self.dispatchMessage(messageNum, packet[1:]) |
|---|
| 314 |
packet = self.getPacket() |
|---|
| 315 |
|
|---|
| 316 |
|
|---|
| 317 |
def dispatchMessage(self, messageNum, payload): |
|---|
| 318 |
""" |
|---|
| 319 |
Send a received message to the appropriate method. |
|---|
| 320 |
|
|---|
| 321 |
@type messageNum: C{int} |
|---|
| 322 |
@type payload: c{str} |
|---|
| 323 |
""" |
|---|
| 324 |
if messageNum < 50 and messageNum in messages: |
|---|
| 325 |
messageType = messages[messageNum][4:] |
|---|
| 326 |
f = getattr(self, 'ssh_%s' % messageType, None) |
|---|
| 327 |
if f is not None: |
|---|
| 328 |
f(payload) |
|---|
| 329 |
else: |
|---|
| 330 |
log.msg("couldn't handle %s" % messageType) |
|---|
| 331 |
log.msg(repr(payload)) |
|---|
| 332 |
self.sendUnimplemented() |
|---|
| 333 |
elif self.service: |
|---|
| 334 |
log.callWithLogger(self.service, self.service.packetReceived, |
|---|
| 335 |
messageNum, payload) |
|---|
| 336 |
else: |
|---|
| 337 |
log.msg("couldn't handle %s" % messageNum) |
|---|
| 338 |
log.msg(repr(payload)) |
|---|
| 339 |
self.sendUnimplemented() |
|---|
| 340 |
|
|---|
| 341 |
|
|---|
| 342 |
def ssh_KEXINIT(self, packet): |
|---|
| 343 |
""" |
|---|
| 344 |
Called when we receive a MSG_KEXINIT message. Payload:: |
|---|
| 345 |
bytes[16] cookie |
|---|
| 346 |
string keyExchangeAlgorithms |
|---|
| 347 |
string keyAlgorithms |
|---|
| 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 |
|---|
| 357 |
unit32 0 (reserved) |
|---|
| 358 |
|
|---|
| 359 |
Starts setting up the key exchange, keys, encryptions, and |
|---|
| 360 |
authentications. Extended by ssh_KEXINIT in SSHServerTransport and |
|---|
| 361 |
SSHClientTransport. |
|---|
| 362 |
""" |
|---|
| 363 |
self.otherKexInitPayload = chr(MSG_KEXINIT) + packet |
|---|
| 364 |
|
|---|
| 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 |
|
|---|
| 370 |
outs = [encSC, macSC, compSC] |
|---|
| 371 |
ins = [encCS, macSC, compCS] |
|---|
| 372 |
if self.isClient: |
|---|
| 373 |
outs, ins = ins, outs |
|---|
| 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], |
|---|
| 379 |
outs[2], ins[2]) |
|---|
| 380 |
if self.isClient: |
|---|
| 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") |
|---|
| 395 |
return |
|---|
| 396 |
if None in self.nextEncryptions.__dict__.values(): |
|---|
| 397 |
self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED, |
|---|
| 398 |
"couldn't match all kex parts") |
|---|
| 399 |
return |
|---|
| 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 |
|---|
| 408 |
|
|---|
| 409 |
|
|---|
| 410 |
def ssh_DISCONNECT(self, packet): |
|---|
| 411 |
""" |
|---|
| 412 |
Called when we receive a MSG_DISCONNECT message. Payload:: |
|---|
| 413 |
long code |
|---|
| 414 |
string description |
|---|
| 415 |
|
|---|
| 416 |
This means that the other side has disconnected. Pass the message up |
|---|
| 417 |
and disconnect ourselves. |
|---|
| 418 |
""" |
|---|
| 419 |
reasonCode = struct.unpack('>L', packet[: 4])[0] |
|---|
| 420 |
description, foo = getNS(packet[4:]) |
|---|
| 421 |
self.receiveError(reasonCode, description) |
|---|
| 422 |
self.transport.loseConnection() |
|---|
| 423 |
|
|---|
| 424 |
|
|---|
| 425 |
def ssh_IGNORE(self, packet): |
|---|
| 426 |
""" |
|---|
| 427 |
Called when we receieve a MSG_IGNORE message. No payload. |
|---|
| 428 |
This means nothing; we simply return. |
|---|
| 429 |
""" |
|---|
| 430 |
|
|---|
| 431 |
|
|---|
| 432 |
def ssh_UNIMPLEMENTED(self, packet): |
|---|
| 433 |
""" |
|---|
| 434 |
Called when we receieve a MSG_UNIMPLEMENTED message. Payload:: |
|---|
| 435 |
long packet |
|---|
| 436 |
|
|---|
| 437 |
This means that the other side did not implement one of our packets. |
|---|
| 438 |
""" |
|---|
| 439 |
seqnum, = struct.unpack('>L', packet) |
|---|
| 440 |
self.receiveUnimplemented(seqnum) |
|---|
| 441 |
|
|---|
| 442 |
|
|---|
| 443 |
def ssh_DEBUG(self, packet): |
|---|
| 444 |
""" |
|---|
| 445 |
Called when we receieve a MSG_DEBUG message. Payload:: |
|---|
| 446 |
bool alwaysDisplay |
|---|
| 447 |
string message |
|---|
| 448 |
string language |
|---|
| 449 |
|
|---|
| 450 |
This means the other side has passed along some debugging info. |
|---|
| 451 |
""" |
|---|
| 452 |
alwaysDisplay = bool(packet[0]) |
|---|
| 453 |
message, lang, foo = getNS(packet[1:], 2) |
|---|
| 454 |
self.receiveDebug(alwaysDisplay, message, lang) |
|---|
| 455 |
|
|---|
| 456 |
|
|---|
| 457 |
def setService(self, service): |
|---|
| 458 |
""" |
|---|
| 459 |
Set our service to service and start it running. If we were |
|---|
| 460 |
running a service previously, stop it first. |
|---|
| 461 |
|
|---|
| 462 |
@type service: C{SSHService} |
|---|
| 463 |
""" |
|---|
| 464 |
log.msg('starting service %s' % service.name) |
|---|
| 465 |
if self.service: |
|---|
| 466 |
self.service.serviceStopped() |
|---|
| 467 |
self.service = service |
|---|
| 468 |
service.transport = self |
|---|
| 469 |
self.service.serviceStarted() |
|---|
| 470 |
|
|---|
| 471 |
|
|---|
| 472 |
def sendDebug(self, message, alwaysDisplay=False, language=''): |
|---|
| 473 |
""" |
|---|
| 474 |
Send a debug message to the other side. |
|---|
| 475 |
|
|---|
| 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} |
|---|
| 483 |
""" |
|---|
| 484 |
self.sendPacket(MSG_DEBUG, chr(alwaysDisplay) + NS(message) + |
|---|
| 485 |
NS(language)) |
|---|
| 486 |
|
|---|
| 487 |
|
|---|
| 488 |
def sendIgnore(self, message): |
|---|
| 489 |
""" |
|---|
| 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 |
|---|
| 492 |
encrypted stream. |
|---|
| 493 |
|
|---|
| 494 |
@param message: data to send with the message |
|---|
| 495 |
@type message: C{str} |
|---|
| 496 |
""" |
|---|
| 497 |
self.sendPacket(MSG_IGNORE, NS(message)) |
|---|
| 498 |
|
|---|
| 499 |
|
|---|
| 500 |
def sendUnimplemented(self): |
|---|
| 501 |
""" |
|---|
| 502 |
Send a message to the other side that the last packet was not |
|---|
| 503 |
understood. |
|---|
| 504 |
""" |
|---|
| 505 |
seqnum = self.incomingPacketSequence |
|---|
| 506 |
self.sendPacket(MSG_UNIMPLEMENTED, struct.pack('!L', seqnum)) |
|---|
| 507 |
|
|---|
| 508 |
|
|---|
| 509 |
def sendDisconnect(self, reason, desc): |
|---|
| 510 |
""" |
|---|
| 511 |
Send a disconnect message to the other side and then disconnect. |
|---|
| 512 |
|
|---|
| 513 |
@param reason: the reason for the disconnect. Should be one of the |
|---|
| 514 |
DISCONNECT_* values. |
|---|
| 515 |
@type reason: C{int} |
|---|
| 516 |
@param desc: a descrption of the reason for the disconnection. |
|---|
| 517 |
@type desc: C{str} |
|---|
| 518 |
""" |
|---|
| 519 |
self.sendPacket( |
|---|
| 520 |
MSG_DISCONNECT, struct.pack('>L', reason) + NS(desc) + NS('')) |
|---|
| 521 |
log.msg('Disconnecting with error, code %s\nreason: %s' % (reason, |
|---|
| 522 |
desc)) |
|---|
| 523 |
self.transport.loseConnection() |
|---|
| 524 |
|
|---|
| 525 |
|
|---|
| 526 |
def _getKey(self, c, sharedSecret, exchangeHash): |
|---|
| 527 |
""" |
|---|
| 528 |
Get one of the keys for authentication/encryption. |
|---|
| 529 |
|
|---|
| 530 |
@type c: C{str} |
|---|
| 531 |
@type sharedSecret: C{str} |
|---|
| 532 |
@type exchangeHash: C{str} |
|---|
| 533 |
""" |
|---|
| 534 |
k1 = sha1(sharedSecret + exchangeHash + c + self.sessionID) |
|---|
| 535 |
k1 = k1.digest() |
|---|
| 536 |
k2 = sha1(sharedSecret + exchangeHash + k1).digest() |
|---|
| 537 |
return k1 + k2 |
|---|
| 538 |
|
|---|
| 539 |
|
|---|
| 540 |
def _keySetup(self, sharedSecret, exchangeHash): |
|---|
| 541 |
""" |
|---|
| 542 |
Set up the keys for the connection and sends MSG_NEWKEYS when |
|---|
| 543 |
finished, |
|---|
| 544 |
|
|---|
| 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} |
|---|
| 551 |
""" |
|---|
| 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: |
|---|
| 563 |
log.msg('REVERSE') |
|---|
| 564 |
outs, ins = ins, outs |
|---|
| 565 |
self.nextEncryptions.setKeys(outs[0], outs[1], ins[0], ins[1], |
|---|
| 566 |
outs[2], ins[2]) |
|---|
| 567 |
self.sendPacket(MSG_NEWKEYS, '') |
|---|
| 568 |
|
|---|
| 569 |
|
|---|
| 570 |
def isEncrypted(self, direction="out"): |
|---|
| 571 |
""" |
|---|
| 572 |
Return True if the connection is encrypted in the given direction. |
|---|
| 573 |
Direction must be one of ["out", "in", "both"]. |
|---|
| 574 |
""" |
|---|
| 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") |
|---|
| 581 |
else: |
|---|
| 582 |
raise TypeError('direction must be "out", "in", or "both"') |
|---|
| 583 |
|
|---|
| 584 |
|
|---|
| 585 |
def isVerified(self, direction="out"): |
|---|
| 586 |
""" |
|---|
| 587 |
Return True if the connecction is verified/authenticated in the |
|---|
| 588 |
given direction. Direction must be one of ["out", "in", "both"]. |
|---|
| 589 |
""" |
|---|
| 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") |
|---|
| 596 |
else: |
|---|
| 597 |
raise TypeError('direction must be "out", "in", or "both"') |
|---|
| 598 |
|
|---|
| 599 |
|
|---|
| 600 |
def loseConnection(self): |
|---|
| 601 |
""" |
|---|
| 602 |
Lose the connection to the other side, sending a |
|---|
| 603 |
DISCONNECT_CONNECTION_LOST message. |
|---|
| 604 |
""" |
|---|
| 605 |
self.sendDisconnect(DISCONNECT_CONNECTION_LOST, |
|---|
| 606 |
"user closed connection") |
|---|
| 607 |
|
|---|
| 608 |
|
|---|
| 609 |
|
|---|
| 610 |
def receiveError(self, reasonCode, description): |
|---|
| 611 |
""" |
|---|
| 612 |
Called when we receive a disconnect error message from the other |
|---|
| 613 |
side. |
|---|
| 614 |
|
|---|
| 615 |
@param reasonCode: the reason for the disconnect, one of the |
|---|
| 616 |
DISCONNECT_ values. |
|---|
| 617 |
@type reasonCode: C{int} |
|---|
| 618 |
@param description: a human-readable description of the |
|---|
| 619 |
disconnection. |
|---|
| 620 |
@type description: C{str} |
|---|
| 621 |
""" |
|---|
| 622 |
log.msg('Got remote error, code %s\nreason: %s' % (reasonCode, |
|---|
| 623 |
description)) |
|---|
| 624 |
|
|---|
| 625 |
|
|---|
| 626 |
def receiveUnimplemented(self, seqnum): |
|---|
| 627 |
""" |
|---|
| 628 |
Called when we receive an unimplemented packet message from the other |
|---|
| 629 |
side. |
|---|
| 630 |
|
|---|
| 631 |
@param seqnum: the sequence number that was not understood. |
|---|
| 632 |
@type seqnum: C{int} |
|---|
| 633 |
""" |
|---|
| 634 |
log.msg('other side unimplemented packet #%s' % seqnum) |
|---|
| 635 |
|
|---|
| 636 |
|
|---|
| 637 |
def receiveDebug(self, alwaysDisplay, message, lang): |
|---|
| 638 |
""" |
|---|
| 639 |
Called when we receive a debug message from the other side. |
|---|
| 640 |
|
|---|
| 641 |
@param alwaysDisplay: if True, this message should always be |
|---|
| 642 |
displayed. |
|---|
| 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. |
|---|
| 647 |
@type lang: C{str} |
|---|
| 648 |
""" |
|---|
| 649 |
if alwaysDisplay: |
|---|
| 650 |
log.msg('Remote Debug Message: %s' % message) |
|---|
| 651 |
|
|---|
| 652 |
|
|---|
| 653 |
|
|---|
| 654 |
class SSHServerTransport(SSHTransportBase): |
|---|
| 655 |
""" |
|---|
| 656 |
SSHServerTransport implements the server side of the SSH protocol. |
|---|
| 657 |
|
|---|
| 658 |
@ivar isClient: since we are never the client, this is always False. |
|---|
| 659 |
|
|---|
| 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 |
|---|
| 662 |
an incorrect guess. |
|---|
| 663 |
|
|---|
| 664 |
@ivar dhGexRequest: the KEX_DH_GEX_REQUEST(_OLD) that the client sent. |
|---|
| 665 |
The key generation needs this to be stored. |
|---|
| 666 |
|
|---|
| 667 |
@ivar g: the Diffie-Hellman group generator. |
|---|
| 668 |
|
|---|
| 669 |
@ivar p: the Diffie-Hellman group prime. |
|---|
| 670 |
""" |
|---|
| 671 |
isClient = False |
|---|
| 672 |
ignoreNextPacket = 0 |
|---|
| 673 |
|
|---|
| 674 |
|
|---|
| 675 |
def ssh_KEXINIT(self, packet): |
|---|
| 676 |
""" |
|---|
| 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. |
|---|
| 682 |
""" |
|---|
| 683 |
retval = SSHTransportBase.ssh_KEXINIT(self, packet) |
|---|
| 684 |
if not retval: |
|---|
| 685 |
return |
|---|
| 686 |
else: |
|---|
| 687 |
kexAlgs, keyAlgs, rest = retval |
|---|
| 688 |
if ord(rest[0]): |
|---|
| 689 |
if (kexAlgs[0] != self.supportedKeyExchanges[0] or |
|---|
| 690 |
keyAlgs[0] != self.supportedPublicKeys[0]): |
|---|
| 691 |
self.ignoreNextPacket = True |
|---|
| 692 |
|
|---|
| 693 |
|
|---|
| 694 |
def ssh_KEX_DH_GEX_REQUEST_OLD(self, packet): |
|---|
| 695 |
""" |
|---|
| 696 |
This represents two different key exchange methods that share the |
|---|
| 697 |
same integer value. |
|---|
| 698 |
|
|---|
| 699 |
KEXDH_INIT (for diffie-hellman-group1-sha1 exchanges) payload:: |
|---|
| 700 |
|
|---|
| 701 |
integer e (the client's Diffie-Hellman public key) |
|---|
| 702 |
|
|---|
| 703 |
We send the KEXDH_REPLY with our host key and signature. |
|---|
| 704 |
|
|---|
| 705 |
KEX_DH_GEX_REQUEST_OLD (for diffie-hellman-group-exchange-sha1) |
|---|
| 706 |
payload:: |
|---|
| 707 |
|
|---|
| 708 |
integer ideal (ideal size for the Diffie-Hellman prime) |
|---|
| 709 |
|
|---|
| 710 |
We send the KEX_DH_GEX_GROUP message with the group that is |
|---|
| 711 |
closest in size to ideal. |
|---|
| 712 |
|
|---|
| 713 |
If we were told to ignore the next key exchange packet by |
|---|
| 714 |
ssh_KEXINIT, drop it on the floor and return. |
|---|
| 715 |
""" |
|---|
| 716 |
if self.ignoreNextPacket: |
|---|
| 717 |
self.ignoreNextPacket = 0 |
|---|
| 718 |
return |
|---|
| 719 |
if self.kexAlg == 'diffie-hellman-group1-sha1': |
|---|
| 720 |
|
|---|
| 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) |
|---|
| 725 |
h = sha1() |
|---|
| 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() |
|---|
| 735 |
self.sendPacket( |
|---|
| 736 |
MSG_KEXDH_REPLY, |
|---|
| 737 |
NS(self.factory.publicKeys[self.keyAlg].blob()) + |
|---|
| 738 |
serverDHpublicKey + |
|---|
| 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)) |
|---|
| 746 |
else: |
|---|
| 747 |
raise error.ConchError('bad kexalg: %s' % self.kexAlg) |
|---|
| 748 |
|
|---|
| 749 |
|
|---|
| 750 |
def ssh_KEX_DH_GEX_REQUEST(self, packet): |
|---|
| 751 |
""" |
|---|
| 752 |
Called when we receive a MSG_KEX_DH_GEX_REQUEST message. Payload:: |
|---|
| 753 |
integer minimum |
|---|
| 754 |
integer ideal |
|---|
| 755 |
integer maximum |
|---|
| 756 |
|
|---|
| 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. |
|---|
| 760 |
|
|---|
| 761 |
If we were told to ignore the next key exchange packekt by |
|---|
| 762 |
ssh_KEXINIT, drop it on the floor and return. |
|---|
| 763 |
""" |
|---|
| 764 |
if self.ignoreNextPacket: |
|---|
| 765 |
self.ignoreNextPacket = 0 |
|---|
| 766 |
return |
|---|
| 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)) |
|---|
| 771 |
|
|---|
| 772 |
|
|---|
| 773 |
def ssh_KEX_DH_GEX_INIT(self, packet): |
|---|
| 774 |
""" |
|---|
| 775 |
Called when we get a MSG_KEX_DH_GEX_INIT message. Payload:: |
|---|
| 776 |
integer e (client DH public key) |
|---|
| 777 |
|
|---|
| 778 |
We send the MSG_KEX_DH_GEX_REPLY message with our host key and |
|---|
| 779 |
signature. |
|---|
| 780 |
""" |
|---|
| 781 |
clientDHpublicKey, foo = getMP(packet) |
|---|
| 782 |
|
|---|
| 783 |
|
|---|
| 784 |
|
|---|
| 785 |
|
|---|
| 786 |
|
|---|
| 787 |
|
|---|
| 788 |
|
|---|
| 789 |
pSize = Util.number.size(self.p) |
|---|
| 790 |
y = Util.number.getRandomNumber(pSize, randbytes.secureRandom) |
|---|
| 791 |
|
|---|
| 792 |
serverDHpublicKey = _MPpow(self.g, y, self.p) |
|---|
| 793 |
sharedSecret = _MPpow(clientDHpublicKey, y, self.p) |
|---|
| 794 |
h = sha1() |
|---|
| 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) |
|---|
| 801 |
h.update(MP(self.p)) |
|---|
| 802 |
h.update(MP(self.g)) |
|---|
| 803 |
h.update(MP(clientDHpublicKey)) |
|---|
| 804 |
h.update(serverDHpublicKey) |
|---|
| 805 |
h.update(sharedSecret) |
|---|
| 806 |
exchangeHash = h.digest() |
|---|
| 807 |
self.sendPacket( |
|---|
| 808 |
MSG_KEX_DH_GEX_REPLY, |
|---|
| 809 |
NS(self.factory.publicKeys[self.keyAlg].blob()) + |
|---|
| 810 |
serverDHpublicKey + |
|---|
| 811 |
NS(self.factory.privateKeys[self.keyAlg].sign(exchangeHash))) |
|---|
| 812 |
self._keySetup(sharedSecret, exchangeHash) |
|---|
| 813 |
|
|---|
| 814 |
|
|---|
| 815 |
def ssh_NEWKEYS(self, packet): |
|---|
| 816 |
""" |
|---|
| 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. |
|---|
| 820 |
""" |
|---|
| 821 |
log.msg('NEW KEYS') |
|---|
| 822 |
if packet != '': |
|---|
| 823 |
self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR, |
|---|
| 824 |
"NEWKEYS takes no data") |
|---|
| 825 |
return |
|---|
| 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() |
|---|
| 831 |
|
|---|
| 832 |
|
|---|
| 833 |
def ssh_SERVICE_REQUEST(self, packet): |
|---|
| 834 |
""" |
|---|
| 835 |
Called when we get a MSG_SERVICE_REQUEST message. Payload:: |
|---|
| 836 |
string serviceName |
|---|
| 837 |
|
|---|
| 838 |
The client has requested a service. If we can start the service, |
|---|
| 839 |
start it; otherwise, disconnect with |
|---|
| 840 |
DISCONNECT_SERVICE_NOT_AVAILABLE. |
|---|
| 841 |
""" |
|---|
| 842 |
service, rest = getNS(packet) |
|---|
| 843 |
cls = self.factory.getService(self, service) |
|---|
| 844 |
if not cls: |
|---|
| 845 |
self.sendDisconnect(DISCONNECT_SERVICE_NOT_AVAILABLE, |
|---|
| 846 |
"don't have service %s" % service) |
|---|
| 847 |
return |
|---|
| 848 |
else: |
|---|
| 849 |
self.sendPacket(MSG_SERVICE_ACCEPT, NS(service)) |
|---|
| 850 |
self.setService(cls()) |
|---|
| 851 |
|
|---|
| 852 |
|
|---|
| 853 |
|
|---|
| 854 |
class SSHClientTransport(SSHTransportBase): |
|---|
| 855 |
""" |
|---|
| 856 |
SSHClientTransport implements the client side of the SSH protocol. |
|---|
| 857 |
|
|---|
| 858 |
@ivar isClient: since we are always the client, this is always True. |
|---|
| 859 |
|
|---|
| 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. |
|---|
| 863 |
|
|---|
| 864 |
@ivar x: our Diffie-Hellman private key. |
|---|
| 865 |
|
|---|
| 866 |
@ivar e: our Diffie-Hellman public key. |
|---|
| 867 |
|
|---|
| 868 |
@ivar g: the Diffie-Hellman group generator. |
|---|
| 869 |
|
|---|
| 870 |
@ivar p: the Diffie-Hellman group prime |
|---|
| 871 |
|
|---|
| 872 |
@ivar instance: the SSHService object we are requesting. |
|---|
| 873 |
""" |
|---|
| 874 |
isClient = True |
|---|
| 875 |
|
|---|
| 876 |
|
|---|
| 877 |
def connectionMade(self): |
|---|
| 878 |
""" |
|---|
| 879 |
Called when the connection is started with the server. Just sets |
|---|
| 880 |
up a private instance variable. |
|---|
| 881 |
""" |
|---|
| 882 |
SSHTransportBase.connectionMade(self) |
|---|
| 883 |
self._gotNewKeys = 0 |
|---|
| 884 |
|
|---|
| 885 |
|
|---|
| 886 |
def ssh_KEXINIT(self, packet): |
|---|
| 887 |
""" |
|---|
| 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. |
|---|
| 895 |
""" |
|---|
| 896 |
if SSHTransportBase.ssh_KEXINIT(self, packet) is None: |
|---|
| 897 |
return |
|---|
| 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') |
|---|
| 904 |
else: |
|---|
| 905 |
raise error.ConchError("somehow, the kexAlg has been set " |
|---|
| 906 |
"to something we don't support") |
|---|
| 907 |
|
|---|
| 908 |
|
|---|
| 909 |
def ssh_KEX_DH_GEX_GROUP(self, packet): |
|---|
| 910 |
""" |
|---|
| 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:: |
|---|
| 914 |
string serverHostKey |
|---|
| 915 |
integer f (server Diffie-Hellman public key) |
|---|
| 916 |
string signature |
|---|
| 917 |
|
|---|
| 918 |
We verify the host key by calling verifyHostKey, then continue in |
|---|
| 919 |
_continueKEXDH_REPLY. |
|---|
| 920 |
|
|---|
| 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) |
|---|
| 925 |
|
|---|
| 926 |
We generate a Diffie-Hellman public key and send it in a |
|---|
| 927 |
MSG_KEX_DH_GEX_INIT message. |
|---|
| 928 |
""" |
|---|
| 929 |
if self.kexAlg == 'diffie-hellman-group1-sha1': |
|---|
| 930 |
|
|---|
| 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) |
|---|
| 938 |
d.addErrback( |
|---|
| 939 |
lambda unused: self.sendDisconnect( |
|---|
| 940 |
DISCONNECT_HOST_KEY_NOT_VERIFIABLE, 'bad host key')) |
|---|
| 941 |
return d |
|---|
| 942 |
else: |
|---|
| 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) |
|---|
| 948 |
|
|---|
| 949 |
|
|---|
| 950 |
def _continueKEXDH_REPLY(self, ignored, pubKey, f, signature): |
|---|
| 951 |
""" |
|---|
| 952 |
The host key has been verified, so we generate the keys. |
|---|
| 953 |
|
|---|
| 954 |
@param pubKey: the public key blob for the server's public key. |
|---|
| 955 |
@type pubKey: C{str} |
|---|
| 956 |
@param f: the server's Diffie-Hellman public key. |
|---|
| 957 |
@type f: C{long} |
|---|
| 958 |
@param signature: the server's signature, verifying that it has the |
|---|
| 959 |
correct private key. |
|---|
| 960 |
@type signature: C{str} |
|---|
| 961 |
""" |
|---|
| 962 |
serverKey = keys.Key.fromString(pubKey) |
|---|
| 963 |
sharedSecret = _MPpow(f, self.x, DH_PRIME) |
|---|
| 964 |
h = sha1() |
|---|
| 965 |
h.update(NS(self.ourVersionString)) |
|---|
| 966 |
h.update(NS(self.otherVersionString)) |
|---|
| 967 |
h.update(NS(self.ourKexInitPayload)) |
|---|
| 968 |
h.update(NS(self.otherKexInitPayload)) |
|---|
| 969 |
h.update(NS(pubKey)) |
|---|
| 970 |
h.update(self.e) |
|---|
| 971 |
h.update(MP(f)) |
|---|
| 972 |
h.update(sharedSecret) |
|---|
| 973 |
exchangeHash = h.digest() |
|---|
| 974 |
if not serverKey.verify(signature, exchangeHash): |
|---|
| 975 |
self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED, |
|---|
| 976 |
'bad signature') |
|---|
| 977 |
return |
|---|
| 978 |
self._keySetup(sharedSecret, exchangeHash) |
|---|
| 979 |
|
|---|
| 980 |
|
|---|
| 981 |
def ssh_KEX_DH_GEX_REPLY(self, packet): |
|---|
| 982 |
""" |
|---|
| 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) |
|---|
| 986 |
|
|---|
| 987 |
We verify the host key by calling verifyHostKey, then continue in |
|---|
| 988 |
_continueGEX_REPLY. |
|---|
| 989 |
""" |
|---|
| 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) |
|---|
| 997 |
d.addErrback( |
|---|
| 998 |
lambda unused: self.sendDisconnect( |
|---|
| 999 |
DISCONNECT_HOST_KEY_NOT_VERIFIABLE, 'bad host key')) |
|---|
| 1000 |
return d |
|---|
| 1001 |
|
|---|
| 1002 |
|
|---|
| 1003 |
def _continueGEX_REPLY(self, ignored, pubKey, f, signature): |
|---|
| 1004 |
""" |
|---|
| 1005 |
The host key has been verified, so we generate the keys. |
|---|
| 1006 |
|
|---|
| 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. |
|---|
| 1010 |
@type f: C{long} |
|---|
| 1011 |
@param signature: the server's signature, verifying that it has the |
|---|
| 1012 |
correct private key. |
|---|
| 1013 |
@type signature: C{str} |
|---|
| 1014 |
""" |
|---|
| 1015 |
serverKey = keys.Key.fromString(pubKey) |
|---|
| 1016 |
sharedSecret = _MPpow(f, self.x, self.p) |
|---|
| 1017 |
h = sha1() |
|---|
| 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)) |
|---|
| 1026 |
h.update(self.e) |
|---|
| 1027 |
h.update(MP(f)) |
|---|
| 1028 |
h.update(sharedSecret) |
|---|
| 1029 |
exchangeHash = h.digest() |
|---|
| 1030 |
if not serverKey.verify(signature, exchangeHash): |
|---|
| 1031 |
self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED, |
|---|
| 1032 |
'bad signature') |
|---|
| 1033 |
return |
|---|
| 1034 |
self._keySetup(sharedSecret, exchangeHash) |
|---|
| 1035 |
|
|---|
| 1036 |
|
|---|
| 1037 |
def _keySetup(self, sharedSecret, exchangeHash): |
|---|
| 1038 |
""" |
|---|
| 1039 |
See SSHTransportBase._keySetup(). |
|---|
| 1040 |
""" |
|---|
| 1041 |
SSHTransportBase._keySetup(self, sharedSecret, exchangeHash) |
|---|
| 1042 |
if self._gotNewKeys: |
|---|
| 1043 |
self.ssh_NEWKEYS('') |
|---|
| 1044 |
|
|---|
| 1045 |
|
|---|
| 1046 |
def ssh_NEWKEYS(self, packet): |
|---|
| 1047 |
""" |
|---|
| 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. |
|---|
| 1051 |
""" |
|---|
| 1052 |
if packet != '': |
|---|
| 1053 |
self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR, |
|---|
| 1054 |
"NEWKEYS takes no data") |
|---|
| 1055 |
return |
|---|
| 1056 |
if not self.nextEncryptions.encBlockSize: |
|---|
| 1057 |
self._gotNewKeys = 1 |
|---|
| 1058 |
return |
|---|
| 1059 |
log.msg('NEW KEYS') |
|---|
| 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() |
|---|
| 1066 |
|
|---|
| 1067 |
|
|---|
| 1068 |
def ssh_SERVICE_ACCEPT(self, packet): |
|---|
| 1069 |
""" |
|---|
| 1070 |
Called when we receieve a MSG_SERVICE_ACCEPT message. Payload:: |
|---|
| 1071 |
string service name |
|---|
| 1072 |
|
|---|
| 1073 |
Start the service we requested. |
|---|
| 1074 |
""" |
|---|
| 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) |
|---|
| 1081 |
|
|---|
| 1082 |
|
|---|
| 1083 |
def requestService(self, instance): |
|---|
| 1084 |
""" |
|---|
| 1085 |
Request that a service be run over this transport. |
|---|
| 1086 |
|
|---|
| 1087 |
@type instance: subclass of L{twisted.conch.ssh.service.SSHService} |
|---|
| 1088 |
""" |
|---|
| 1089 |
self.sendPacket(MSG_SERVICE_REQUEST, NS(instance.name)) |
|---|
| 1090 |
self.instance = instance |
|---|
| 1091 |
|
|---|
| 1092 |
|
|---|
| 1093 |
|
|---|
| 1094 |
def verifyHostKey(self, hostKey, fingerprint): |
|---|
| 1095 |
""" |
|---|
| 1096 |
Returns a Deferred that gets a callback if it is a valid key, or |
|---|
| 1097 |
an errback if not. |
|---|
| 1098 |
|
|---|
| 1099 |
@type hostKey: C{str} |
|---|
| 1100 |
@type fingerprint: C{str} |
|---|
| 1101 |
@rtype: L{twisted.internet.defer.Deferred} |
|---|
| 1102 |
""" |
|---|
| 1103 |
|
|---|
| 1104 |
return defer.fail(NotImplementedError()) |
|---|
| 1105 |
|
|---|
| 1106 |
|
|---|
| 1107 |
def connectionSecure(self): |
|---|
| 1108 |
""" |
|---|
| 1109 |
Called when the encryption has been set up. Generally, |
|---|
| 1110 |
requestService() is called to run another service over the transport. |
|---|
| 1111 |
""" |
|---|
| 1112 |
raise NotImplementedError() |
|---|
| 1113 |
|
|---|
| 1114 |
|
|---|
| 1115 |
|
|---|
| 1116 |
class _DummyCipher: |
|---|
| 1117 |
""" |
|---|
| 1118 |
A cipher for the none encryption method. |
|---|
| 1119 |
|
|---|
| 1120 |
@ivar block_size: the block size of the encryption. In the case of the |
|---|
| 1121 |
none cipher, this is 8 bytes. |
|---|
| 1122 |
""" |
|---|
| 1123 |
block_size = 8 |
|---|
| 1124 |
|
|---|
| 1125 |
|
|---|
| 1126 |
def encrypt(self, x): |
|---|
| 1127 |
return x |
|---|
| 1128 |
|
|---|
| 1129 |
|
|---|
| 1130 |
decrypt = encrypt |
|---|
| 1131 |
|
|---|
| 1132 |
|
|---|
| 1133 |
class SSHCiphers: |
|---|
| 1134 |
""" |
|---|
| 1135 |
SSHCiphers represents all the encryption operations that need to occur |
|---|
| 1136 |
to encrypt and authenticate the SSH connection. |
|---|
| 1137 |
|
|---|
| 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. |
|---|
| 1141 |
|
|---|
| 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. |
|---|
| 1152 |
""" |
|---|
| 1153 |
|
|---|
| 1154 |
|
|---|
| 1155 |
cipherMap = { |
|---|
| 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), |
|---|
| 1169 |
} |
|---|
| 1170 |
macMap = { |
|---|
| 1171 |
'hmac-sha1': sha1, |
|---|
| 1172 |
'hmac-md5': md5, |
|---|
| 1173 |
'none': None |
|---|
| 1174 |
} |
|---|
| 1175 |
|
|---|
| 1176 |
|
|---|
| 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) |
|---|
| 1187 |
|
|---|
| 1188 |
|
|---|
| 1189 |
def setKeys(self, outIV, outKey, inIV, inKey, outInteg, inInteg): |
|---|
| 1190 |
""" |
|---|
| 1191 |
Set up the ciphers and hashes using the given keys, |
|---|
| 1192 |
|
|---|
| 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. |
|---|
| 1199 |
""" |
|---|
| 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) |
|---|
| 1208 |
if self.inMAC: |
|---|
| 1209 |
self.verifyDigestSize = self.inMAC[3] |
|---|
| 1210 |
|
|---|
| 1211 |
|
|---|
| 1212 |
def _getCipher(self, cip, iv, key): |
|---|
| 1213 |
""" |
|---|
| 1214 |
Creates an initialized cipher object. |
|---|
| 1215 |
|
|---|
| 1216 |
@param cip: the name of the cipher: maps into Crypto.Cipher.* |
|---|
| 1217 |
@param iv: the initialzation vector |
|---|
| 1218 |
@param key: the encryption key |
|---|
| 1219 |
""" |
|---|
| 1220 |
modName, keySize, counterMode = self.cipherMap[cip] |
|---|
| 1221 |
if not modName: |
|---|
| 1222 |
return _DummyCipher() |
|---|
| 1223 |
mod = __import__('Crypto.Cipher.%s'%modName, {}, {}, 'x') |
|---|
| 1224 |
if counterMode: |
|---|
| 1225 |
return mod.new(key[:keySize], mod.MODE_CTR, iv[:mod.block_size], |
|---|
| 1226 |
counter=_Counter(iv, mod.block_size)) |
|---|
| 1227 |
else: |
|---|
| 1228 |
return mod.new(key[:keySize], mod.MODE_CBC, iv[:mod.block_size]) |
|---|
| 1229 |
|
|---|
| 1230 |
|
|---|
| 1231 |
def _getMAC(self, mac, key): |
|---|
| 1232 |
""" |
|---|
| 1233 |
Gets a 4-tuple representing the message authentication code. |
|---|
| 1234 |
(<hash module>, <inner hash value>, <outer hash value>, |
|---|
| 1235 |
<digest size>) |
|---|
| 1236 |
|
|---|
| 1237 |
@param mac: a key mapping into macMap |
|---|
| 1238 |
@type mac: C{str} |
|---|
| 1239 |
@param key: the MAC key. |
|---|
| 1240 |
@type key: C{str} |
|---|
| 1241 |
""" |
|---|
| 1242 |
mod = self.macMap[mac] |
|---|
| 1243 |
if not mod: |
|---|
| 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 |
|---|
| 1250 |
|
|---|
| 1251 |
|
|---|
| 1252 |
def encrypt(self, blocks): |
|---|
| 1253 |
""" |
|---|
| 1254 |
Encrypt blocks. Overridden by the encrypt method of a |
|---|
| 1255 |
Crypto.Cipher.* object in setKeys(). |
|---|
| 1256 |
|
|---|
| 1257 |
@type blocks: C{str} |
|---|
| 1258 |
""" |
|---|
| 1259 |
raise NotImplementedError() |
|---|
| 1260 |
|
|---|
| 1261 |
|
|---|
| 1262 |
def decrypt(self, blocks): |
|---|
| 1263 |
""" |
|---|
| 1264 |
Decrypt blocks. See encrypt(). |
|---|
| 1265 |
|
|---|
| 1266 |
@type blocks: C{str} |
|---|
| 1267 |
""" |
|---|
| 1268 |
raise NotImplementedError() |
|---|
| 1269 |
|
|---|
| 1270 |
|
|---|
| 1271 |
def makeMAC(self, seqid, data): |
|---|
| 1272 |
""" |
|---|
| 1273 |
Create a message authentication code (MAC) for the given packet using |
|---|
| 1274 |
the outgoing MAC values. |
|---|
| 1275 |
|
|---|
| 1276 |
@param seqid: the sequence ID of the outgoing packet |
|---|
| 1277 |
@type seqid: C{int} |
|---|
| 1278 |
@param data: the data to create a MAC for |
|---|
| 1279 |
@type data: C{str} |
|---|
| 1280 |
@rtype: C{str} |
|---|
| 1281 |
""" |
|---|
| 1282 |
if not self.outMAC[0]: |
|---|
| 1283 |
return '' |
|---|
| 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() |
|---|
| 1289 |
|
|---|
| 1290 |
|
|---|
| 1291 |
def verify(self, seqid, data, mac): |
|---|
| 1292 |
""" |
|---|
| 1293 |
Verify an incoming MAC using the incoming MAC values. Return True |
|---|
| 1294 |
if the MAC is valid. |
|---|
| 1295 |
|
|---|
| 1296 |
@param seqid: the sequence ID of the incoming packet |
|---|
| 1297 |
@type seqid: C{int} |
|---|
| 1298 |
@param data: the packet data to verify |
|---|
| 1299 |
@type data: C{str} |
|---|
| 1300 |
@param mac: the MAC sent with the packet |
|---|
| 1301 |
@type mac: C{str} |
|---|
| 1302 |
@rtype: C{bool} |
|---|
| 1303 |
""" |
|---|
| 1304 |
if not self.inMAC[0]: |
|---|
| 1305 |
return mac == '' |
|---|
| 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() |
|---|
| 1311 |
|
|---|
| 1312 |
|
|---|
| 1313 |
|
|---|
| 1314 |
class _Counter: |
|---|
| 1315 |
""" |
|---|
| 1316 |
Stateful counter which returns results packed in a byte string |
|---|
| 1317 |
""" |
|---|
| 1318 |
|
|---|
| 1319 |
|
|---|
| 1320 |
def __init__(self, initialVector, blockSize): |
|---|
| 1321 |
""" |
|---|
| 1322 |
@type initialVector: C{str} |
|---|
| 1323 |
@param initialVector: A byte string representing the initial counter |
|---|
| 1324 |
value. |
|---|
| 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. |
|---|
| 1328 |
""" |
|---|
| 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 |
|---|
| 1336 |
|
|---|
| 1337 |
|
|---|
| 1338 |
def __call__(self): |
|---|
| 1339 |
""" |
|---|
| 1340 |
Increment the counter and return the new value. |
|---|
| 1341 |
""" |
|---|
| 1342 |
i = self.len |
|---|
| 1343 |
while i > -1: |
|---|
| 1344 |
self.count[i] = n = chr((ord(self.count[i]) + 1) % 256) |
|---|
| 1345 |
if n == '\x00': |
|---|
| 1346 |
i -= 1 |
|---|
| 1347 |
else: |
|---|
| 1348 |
return self.count.tostring() |
|---|
| 1349 |
|
|---|
| 1350 |
self.count = array.array('c', '\x00' * self.blockSize) |
|---|
| 1351 |
return self.count.tostring() |
|---|
| 1352 |
|
|---|
| 1353 |
|
|---|
| 1354 |
|
|---|
| 1355 |
|
|---|
| 1356 |
DH_PRIME = long('17976931348623159077083915679378745319786029604875601170644' |
|---|
| 1357 |
'442368419718021615851936894783379586492554150218056548598050364644054819923' |
|---|
| 1358 |
'910005079287700335581663922955313623907650873575991482257486257500742530207' |
|---|
| 1359 |
'744771258955095793777842444242661733472762929938766870920560605027081084290' |
|---|
| 1360 |
'7692932019128194467627007L') |
|---|
| 1361 |
DH_GENERATOR = 2L |
|---|
| 1362 |
|
|---|
| 1363 |
|
|---|
| 1364 |
|
|---|
| 1365 |
MSG_DISCONNECT = 1 |
|---|
| 1366 |
MSG_IGNORE = 2 |
|---|
| 1367 |
MSG_UNIMPLEMENTED = 3 |
|---|
| 1368 |
MSG_DEBUG = 4 |
|---|
| 1369 |
MSG_SERVICE_REQUEST = 5 |
|---|
| 1370 |
MSG_SERVICE_ACCEPT = 6 |
|---|
| 1371 |
MSG_KEXINIT = 20 |
|---|
| 1372 |
MSG_NEWKEYS = 21 |
|---|
| 1373 |
MSG_KEXDH_INIT = 30 |
|---|
| 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 |
|---|
| 1380 |
|
|---|
| 1381 |
|
|---|
| 1382 |
|
|---|
| 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 |
|---|
| 1398 |
|
|---|
| 1399 |
|
|---|
| 1400 |
|
|---|
| 1401 |
messages = {} |
|---|
| 1402 |
for name, value in globals().items(): |
|---|
| 1403 |
if name.startswith('MSG_'): |
|---|
| 1404 |
messages[value] = name |
|---|