| Index: third_party/twisted_8_1/twisted/conch/ssh/transport.py
|
| diff --git a/third_party/twisted_8_1/twisted/conch/ssh/transport.py b/third_party/twisted_8_1/twisted/conch/ssh/transport.py
|
| deleted file mode 100644
|
| index d1e2ad60d826bfdd2120a2cee378a16ee2ca9908..0000000000000000000000000000000000000000
|
| --- a/third_party/twisted_8_1/twisted/conch/ssh/transport.py
|
| +++ /dev/null
|
| @@ -1,1408 +0,0 @@
|
| -# -*- test-case-name: twisted.conch.test.test_transport -*-
|
| -#
|
| -# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
|
| -# See LICENSE for details.
|
| -
|
| -"""
|
| -The lowest level SSH protocol. This handles the key negotiation, the
|
| -encryption and the compression. The transport layer is described in
|
| -RFC 4253.
|
| -
|
| -Maintainer: U{Paul Swartz<mailto:z3p@twistedmatrix.com>}
|
| -"""
|
| -
|
| -# base library imports
|
| -import struct
|
| -import md5
|
| -import sha
|
| -import zlib
|
| -import array
|
| -
|
| -# external library imports
|
| -from Crypto import Util
|
| -from Crypto.Cipher import XOR
|
| -
|
| -# twisted imports
|
| -from twisted.internet import protocol, defer
|
| -from twisted.conch import error
|
| -from twisted.python import log, randbytes
|
| -
|
| -# sibling imports
|
| -from twisted.conch.ssh import keys
|
| -from twisted.conch.ssh.common import NS, getNS, MP, getMP, _MPpow, ffs
|
| -
|
| -
|
| -
|
| -class SSHTransportBase(protocol.Protocol):
|
| - """
|
| - Protocol supporting basic SSH functionality: sending/receiving packets
|
| - and message dispatch. To connect to or run a server, you must use
|
| - SSHClientTransport or SSHServerTransport.
|
| -
|
| - @ivar protocolVersion: A string representing the version of the SSH
|
| - protocol we support. Currently defaults to '2.0'.
|
| -
|
| - @ivar version: A string representing the version of the server or client.
|
| - Currently defaults to 'Twisted'.
|
| -
|
| - @ivar comment: An optional string giving more information about the
|
| - server or client.
|
| -
|
| - @ivar supportedCiphers: A list of strings representing the encryption
|
| - algorithms supported, in order from most-preferred to least.
|
| -
|
| - @ivar supportedMACs: A list of strings representing the message
|
| - authentication codes (hashes) supported, in order from most-preferred
|
| - to least. Both this and supportedCiphers can include 'none' to use
|
| - no encryption or authentication, but that must be done manually,
|
| -
|
| - @ivar supportedKeyExchanges: A list of strings representing the
|
| - key exchanges supported, in order from most-preferred to least.
|
| -
|
| - @ivar supportedPublicKeys: A list of strings representing the
|
| - public key types supported, in order from most-preferred to least.
|
| -
|
| - @ivar supportedCompressions: A list of strings representing compression
|
| - types supported, from most-preferred to least.
|
| -
|
| - @ivar supportedLanguages: A list of strings representing languages
|
| - supported, from most-preferred to least.
|
| -
|
| - @ivar isClient: A boolean indicating whether this is a client or server.
|
| -
|
| - @ivar gotVersion: A boolean indicating whether we have receieved the
|
| - version string from the other side.
|
| -
|
| - @ivar buf: Data we've received but hasn't been parsed into a packet.
|
| -
|
| - @ivar outgoingPacketSequence: the sequence number of the next packet we
|
| - will send.
|
| -
|
| - @ivar incomingPacketSequence: the sequence number of the next packet we
|
| - are expecting from the other side.
|
| -
|
| - @ivar outgoingCompression: an object supporting the .compress(str) and
|
| - .flush() methods, or None if there is no outgoing compression. Used to
|
| - compress outgoing data.
|
| -
|
| - @ivar outgoingCompressionType: A string representing the outgoing
|
| - compression type.
|
| -
|
| - @ivar incomingCompression: an object supporting the .decompress(str)
|
| - method, or None if there is no incoming compression. Used to
|
| - decompress incoming data.
|
| -
|
| - @ivar incomingCompressionType: A string representing the incoming
|
| - compression type.
|
| -
|
| - @ivar ourVersionString: the version string that we sent to the other side.
|
| - Used in the key exchange.
|
| -
|
| - @ivar otherVersionString: the version string sent by the other side. Used
|
| - in the key exchange.
|
| -
|
| - @ivar ourKexInitPayload: the MSG_KEXINIT payload we sent. Used in the key
|
| - exchange.
|
| -
|
| - @ivar otherKexInitPayload: the MSG_KEXINIT payload we received. Used in
|
| - the key exchange
|
| -
|
| - @ivar sessionID: a string that is unique to this SSH session. Created as
|
| - part of the key exchange, sessionID is used to generate the various
|
| - encryption and authentication keys.
|
| -
|
| - @ivar service: an SSHService instance, or None. If it's set to an object,
|
| - it's the currently running service.
|
| -
|
| - @ivar kexAlg: the agreed-upon key exchange algorithm.
|
| -
|
| - @ivar keyAlg: the agreed-upon public key type for the key exchange.
|
| -
|
| - @ivar currentEncryptions: an SSHCiphers instance. It represents the
|
| - current encryption and authentication options for the transport.
|
| -
|
| - @ivar nextEncryptions: an SSHCiphers instance. Held here until the
|
| - MSG_NEWKEYS messages are exchanged, when nextEncryptions is
|
| - transitioned to currentEncryptions.
|
| -
|
| - @ivar first: the first bytes of the next packet. In order to avoid
|
| - decrypting data twice, the first bytes are decrypted and stored until
|
| - the whole packet is available.
|
| -
|
| - """
|
| -
|
| -
|
| - protocolVersion = '2.0'
|
| - version = 'Twisted'
|
| - comment = ''
|
| - ourVersionString = ('SSH-' + protocolVersion + '-' + version + ' '
|
| - + comment).strip()
|
| - supportedCiphers = ['aes256-ctr', 'aes256-cbc', 'aes192-ctr', 'aes192-cbc',
|
| - 'aes128-ctr', 'aes128-cbc', 'cast128-ctr',
|
| - 'cast128-cbc', 'blowfish-ctr', 'blowfish-cbc',
|
| - '3des-ctr', '3des-cbc'] # ,'none']
|
| - supportedMACs = ['hmac-sha1', 'hmac-md5'] # , 'none']
|
| - # both of the above support 'none', but for security are disabled by
|
| - # default. to enable them, subclass this class and add it, or do:
|
| - # SSHTransportBase.supportedCiphers.append('none')
|
| - supportedKeyExchanges = ['diffie-hellman-group-exchange-sha1',
|
| - 'diffie-hellman-group1-sha1']
|
| - supportedPublicKeys = ['ssh-rsa', 'ssh-dss']
|
| - supportedCompressions = ['none', 'zlib']
|
| - supportedLanguages = ()
|
| - isClient = False
|
| - gotVersion = False
|
| - buf = ''
|
| - outgoingPacketSequence = 0
|
| - incomingPacketSequence = 0
|
| - outgoingCompression = None
|
| - incomingCompression = None
|
| - sessionID = None
|
| - service = None
|
| -
|
| -
|
| - def connectionLost(self, reason):
|
| - if self.service:
|
| - self.service.serviceStopped()
|
| - if hasattr(self, 'avatar'):
|
| - self.logoutFunction()
|
| - log.msg('connection lost')
|
| -
|
| -
|
| - def connectionMade(self):
|
| - """
|
| - Called when the connection is made to the other side. We sent our
|
| - version and the MSG_KEXINIT packet.
|
| - """
|
| - self.transport.write('%s\r\n' % (self.ourVersionString,))
|
| - self.currentEncryptions = SSHCiphers('none', 'none', 'none', 'none')
|
| - self.currentEncryptions.setKeys('', '', '', '', '', '')
|
| - self.sendKexInit()
|
| -
|
| -
|
| - def sendKexInit(self):
|
| - self.ourKexInitPayload = (chr(MSG_KEXINIT) +
|
| - randbytes.secureRandom(16) +
|
| - NS(','.join(self.supportedKeyExchanges)) +
|
| - NS(','.join(self.supportedPublicKeys)) +
|
| - NS(','.join(self.supportedCiphers)) +
|
| - NS(','.join(self.supportedCiphers)) +
|
| - NS(','.join(self.supportedMACs)) +
|
| - NS(','.join(self.supportedMACs)) +
|
| - NS(','.join(self.supportedCompressions)) +
|
| - NS(','.join(self.supportedCompressions)) +
|
| - NS(','.join(self.supportedLanguages)) +
|
| - NS(','.join(self.supportedLanguages)) +
|
| - '\000' + '\000\000\000\000')
|
| - self.sendPacket(MSG_KEXINIT, self.ourKexInitPayload[1:])
|
| -
|
| -
|
| - def sendPacket(self, messageType, payload):
|
| - """
|
| - Sends a packet. If it's been set up, compress the data, encrypt it,
|
| - and authenticate it before sending.
|
| -
|
| - @param messageType: The type of the packet; generally one of the
|
| - MSG_* values.
|
| - @type messageType: C{int}
|
| - @param payload: The payload for the message.
|
| - @type payload: C{str}
|
| - """
|
| - payload = chr(messageType) + payload
|
| - if self.outgoingCompression:
|
| - payload = (self.outgoingCompression.compress(payload)
|
| - + self.outgoingCompression.flush(2))
|
| - bs = self.currentEncryptions.encBlockSize
|
| - # 4 for the packet length and 1 for the padding length
|
| - totalSize = 5 + len(payload)
|
| - lenPad = bs - (totalSize % bs)
|
| - if lenPad < 4:
|
| - lenPad = lenPad + bs
|
| - packet = (struct.pack('!LB',
|
| - totalSize + lenPad - 4, lenPad) +
|
| - payload + randbytes.secureRandom(lenPad))
|
| - encPacket = (
|
| - self.currentEncryptions.encrypt(packet) +
|
| - self.currentEncryptions.makeMAC(
|
| - self.outgoingPacketSequence, packet))
|
| - self.transport.write(encPacket)
|
| - self.outgoingPacketSequence += 1
|
| -
|
| -
|
| - def getPacket(self):
|
| - """
|
| - Try to return a decrypted, authenticated, and decompressed packet
|
| - out of the buffer. If there is not enough data, return None.
|
| -
|
| - @rtype: C{str}/C{None}
|
| - """
|
| - bs = self.currentEncryptions.decBlockSize
|
| - ms = self.currentEncryptions.verifyDigestSize
|
| - if len(self.buf) < bs: return # not enough data
|
| - if not hasattr(self, 'first'):
|
| - first = self.currentEncryptions.decrypt(self.buf[:bs])
|
| - else:
|
| - first = self.first
|
| - del self.first
|
| - packetLen, paddingLen = struct.unpack('!LB', first[:5])
|
| - if packetLen > 1048576: # 1024 ** 2
|
| - self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR,
|
| - 'bad packet length %s' % packetLen)
|
| - return
|
| - if len(self.buf) < packetLen + 4 + ms:
|
| - self.first = first
|
| - return # not enough packet
|
| - if(packetLen + 4) % bs != 0:
|
| - self.sendDisconnect(
|
| - DISCONNECT_PROTOCOL_ERROR,
|
| - 'bad packet mod (%i%%%i == %i)' % (packetLen + 4, bs,
|
| - (packetLen + 4) % bs))
|
| - return
|
| - encData, self.buf = self.buf[:4 + packetLen], self.buf[4 + packetLen:]
|
| - packet = first + self.currentEncryptions.decrypt(encData[bs:])
|
| - if len(packet) != 4 + packetLen:
|
| - self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR,
|
| - 'bad decryption')
|
| - return
|
| - if ms:
|
| - macData, self.buf = self.buf[:ms], self.buf[ms:]
|
| - if not self.currentEncryptions.verify(self.incomingPacketSequence,
|
| - packet, macData):
|
| - self.sendDisconnect(DISCONNECT_MAC_ERROR, 'bad MAC')
|
| - return
|
| - payload = packet[5:-paddingLen]
|
| - if self.incomingCompression:
|
| - try:
|
| - payload = self.incomingCompression.decompress(payload)
|
| - except: # bare except, because who knows what kind of errors
|
| - # decompression can raise
|
| - log.err()
|
| - self.sendDisconnect(DISCONNECT_COMPRESSION_ERROR,
|
| - 'compression error')
|
| - return
|
| - self.incomingPacketSequence += 1
|
| - return payload
|
| -
|
| -
|
| - def dataReceived(self, data):
|
| - """
|
| - First, check for the version string (SSH-2.0-*). After that has been
|
| - received, this method adds data to the buffer, and pulls out any
|
| - packets.
|
| -
|
| - @type data: C{str}
|
| - """
|
| - self.buf = self.buf + data
|
| - if not self.gotVersion:
|
| - if self.buf.find('\n', self.buf.find('SSH-')) == -1:
|
| - return
|
| - lines = self.buf.split('\n')
|
| - for p in lines:
|
| - if p.startswith('SSH-'):
|
| - self.gotVersion = True
|
| - self.otherVersionString = p.strip()
|
| - if p.split('-')[1] not in ('1.99', '2.0'): # bad version
|
| - self.sendDisconnect(
|
| - DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED,
|
| - 'bad version ' + p.split('-')[1])
|
| - return
|
| - i = lines.index(p)
|
| - self.buf = '\n'.join(lines[i + 1:])
|
| - packet = self.getPacket()
|
| - while packet:
|
| - messageNum = ord(packet[0])
|
| - self.dispatchMessage(messageNum, packet[1:])
|
| - packet = self.getPacket()
|
| -
|
| -
|
| - def dispatchMessage(self, messageNum, payload):
|
| - """
|
| - Send a received message to the appropriate method.
|
| -
|
| - @type messageNum: C{int}
|
| - @type payload: c{str}
|
| - """
|
| - if messageNum < 50 and messageNum in messages:
|
| - messageType = messages[messageNum][4:]
|
| - f = getattr(self, 'ssh_%s' % messageType, None)
|
| - if f is not None:
|
| - f(payload)
|
| - else:
|
| - log.msg("couldn't handle %s" % messageType)
|
| - log.msg(repr(payload))
|
| - self.sendUnimplemented()
|
| - elif self.service:
|
| - log.callWithLogger(self.service, self.service.packetReceived,
|
| - messageNum, payload)
|
| - else:
|
| - log.msg("couldn't handle %s" % messageNum)
|
| - log.msg(repr(payload))
|
| - self.sendUnimplemented()
|
| -
|
| -
|
| - def ssh_KEXINIT(self, packet):
|
| - """
|
| - Called when we receive a MSG_KEXINIT message. Payload::
|
| - bytes[16] cookie
|
| - string keyExchangeAlgorithms
|
| - string keyAlgorithms
|
| - string incomingEncryptions
|
| - string outgoingEncryptions
|
| - string incomingAuthentications
|
| - string outgoingAuthentications
|
| - string incomingCompressions
|
| - string outgoingCompressions
|
| - string incomingLanguages
|
| - string outgoingLanguages
|
| - bool firstPacketFollows
|
| - unit32 0 (reserved)
|
| -
|
| - Starts setting up the key exchange, keys, encryptions, and
|
| - authentications. Extended by ssh_KEXINIT in SSHServerTransport and
|
| - SSHClientTransport.
|
| - """
|
| - self.otherKexInitPayload = chr(MSG_KEXINIT) + packet
|
| - #cookie = packet[: 16] # taking this is useless
|
| - k = getNS(packet[16:], 10)
|
| - strings, rest = k[:-1], k[-1]
|
| - (kexAlgs, keyAlgs, encCS, encSC, macCS, macSC, compCS, compSC, langCS,
|
| - langSC) = [s.split(',') for s in strings]
|
| - # these are the server directions
|
| - outs = [encSC, macSC, compSC]
|
| - ins = [encCS, macSC, compCS]
|
| - if self.isClient:
|
| - outs, ins = ins, outs # switch directions
|
| - server = (self.supportedKeyExchanges, self.supportedPublicKeys,
|
| - self.supportedCiphers, self.supportedCiphers,
|
| - self.supportedMACs, self.supportedMACs,
|
| - self.supportedCompressions, self.supportedCompressions)
|
| - client = (kexAlgs, keyAlgs, outs[0], ins[0], outs[1], ins[1],
|
| - outs[2], ins[2])
|
| - if self.isClient:
|
| - server, client = client, server
|
| - self.kexAlg = ffs(client[0], server[0])
|
| - self.keyAlg = ffs(client[1], server[1])
|
| - self.nextEncryptions = SSHCiphers(
|
| - ffs(client[2], server[2]),
|
| - ffs(client[3], server[3]),
|
| - ffs(client[4], server[4]),
|
| - ffs(client[5], server[5]))
|
| - self.outgoingCompressionType = ffs(client[6], server[6])
|
| - self.incomingCompressionType = ffs(client[7], server[7])
|
| - if None in (self.kexAlg, self.keyAlg, self.outgoingCompressionType,
|
| - self.incomingCompressionType):
|
| - self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED,
|
| - "couldn't match all kex parts")
|
| - return
|
| - if None in self.nextEncryptions.__dict__.values():
|
| - self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED,
|
| - "couldn't match all kex parts")
|
| - return
|
| - log.msg('kex alg, key alg: %s %s' % (self.kexAlg, self.keyAlg))
|
| - log.msg('outgoing: %s %s %s' % (self.nextEncryptions.outCipType,
|
| - self.nextEncryptions.outMACType,
|
| - self.outgoingCompressionType))
|
| - log.msg('incoming: %s %s %s' % (self.nextEncryptions.inCipType,
|
| - self.nextEncryptions.inMACType,
|
| - self.incomingCompressionType))
|
| - return kexAlgs, keyAlgs, rest # for SSHServerTransport to use
|
| -
|
| -
|
| - def ssh_DISCONNECT(self, packet):
|
| - """
|
| - Called when we receive a MSG_DISCONNECT message. Payload::
|
| - long code
|
| - string description
|
| -
|
| - This means that the other side has disconnected. Pass the message up
|
| - and disconnect ourselves.
|
| - """
|
| - reasonCode = struct.unpack('>L', packet[: 4])[0]
|
| - description, foo = getNS(packet[4:])
|
| - self.receiveError(reasonCode, description)
|
| - self.transport.loseConnection()
|
| -
|
| -
|
| - def ssh_IGNORE(self, packet):
|
| - """
|
| - Called when we receieve a MSG_IGNORE message. No payload.
|
| - This means nothing; we simply return.
|
| - """
|
| -
|
| -
|
| - def ssh_UNIMPLEMENTED(self, packet):
|
| - """
|
| - Called when we receieve a MSG_UNIMPLEMENTED message. Payload::
|
| - long packet
|
| -
|
| - This means that the other side did not implement one of our packets.
|
| - """
|
| - seqnum, = struct.unpack('>L', packet)
|
| - self.receiveUnimplemented(seqnum)
|
| -
|
| -
|
| - def ssh_DEBUG(self, packet):
|
| - """
|
| - Called when we receieve a MSG_DEBUG message. Payload::
|
| - bool alwaysDisplay
|
| - string message
|
| - string language
|
| -
|
| - This means the other side has passed along some debugging info.
|
| - """
|
| - alwaysDisplay = bool(packet[0])
|
| - message, lang, foo = getNS(packet[1:], 2)
|
| - self.receiveDebug(alwaysDisplay, message, lang)
|
| -
|
| -
|
| - def setService(self, service):
|
| - """
|
| - Set our service to service and start it running. If we were
|
| - running a service previously, stop it first.
|
| -
|
| - @type service: C{SSHService}
|
| - """
|
| - log.msg('starting service %s' % service.name)
|
| - if self.service:
|
| - self.service.serviceStopped()
|
| - self.service = service
|
| - service.transport = self
|
| - self.service.serviceStarted()
|
| -
|
| -
|
| - def sendDebug(self, message, alwaysDisplay=False, language=''):
|
| - """
|
| - Send a debug message to the other side.
|
| -
|
| - @param message: the message to send.
|
| - @type message: C{str}
|
| - @param alwaysDisplay: if True, tell the other side to always
|
| - display this message.
|
| - @type alwaysDisplay: C{bool}
|
| - @param language: optionally, the language the message is in.
|
| - @type language: C{str}
|
| - """
|
| - self.sendPacket(MSG_DEBUG, chr(alwaysDisplay) + NS(message) +
|
| - NS(language))
|
| -
|
| -
|
| - def sendIgnore(self, message):
|
| - """
|
| - Send a message that will be ignored by the other side. This is
|
| - useful to fool attacks based on guessing packet sizes in the
|
| - encrypted stream.
|
| -
|
| - @param message: data to send with the message
|
| - @type message: C{str}
|
| - """
|
| - self.sendPacket(MSG_IGNORE, NS(message))
|
| -
|
| -
|
| - def sendUnimplemented(self):
|
| - """
|
| - Send a message to the other side that the last packet was not
|
| - understood.
|
| - """
|
| - seqnum = self.incomingPacketSequence
|
| - self.sendPacket(MSG_UNIMPLEMENTED, struct.pack('!L', seqnum))
|
| -
|
| -
|
| - def sendDisconnect(self, reason, desc):
|
| - """
|
| - Send a disconnect message to the other side and then disconnect.
|
| -
|
| - @param reason: the reason for the disconnect. Should be one of the
|
| - DISCONNECT_* values.
|
| - @type reason: C{int}
|
| - @param desc: a descrption of the reason for the disconnection.
|
| - @type desc: C{str}
|
| - """
|
| - self.sendPacket(
|
| - MSG_DISCONNECT, struct.pack('>L', reason) + NS(desc) + NS(''))
|
| - log.msg('Disconnecting with error, code %s\nreason: %s' % (reason,
|
| - desc))
|
| - self.transport.loseConnection()
|
| -
|
| -
|
| - def _getKey(self, c, sharedSecret, exchangeHash):
|
| - """
|
| - Get one of the keys for authentication/encryption.
|
| -
|
| - @type c: C{str}
|
| - @type sharedSecret: C{str}
|
| - @type exchangeHash: C{str}
|
| - """
|
| - k1 = sha.new(sharedSecret + exchangeHash + c + self.sessionID)
|
| - k1 = k1.digest()
|
| - k2 = sha.new(sharedSecret + exchangeHash + k1).digest()
|
| - return k1 + k2
|
| -
|
| -
|
| - def _keySetup(self, sharedSecret, exchangeHash):
|
| - """
|
| - Set up the keys for the connection and sends MSG_NEWKEYS when
|
| - finished,
|
| -
|
| - @param sharedSecret: a secret string agreed upon using a Diffie-
|
| - Hellman exchange, so it is only shared between
|
| - the server and the client.
|
| - @type sharedSecret: C{str}
|
| - @param exchangeHash: A hash of various data known by both sides.
|
| - @type exchangeHash: C{str}
|
| - """
|
| - if not self.sessionID:
|
| - self.sessionID = exchangeHash
|
| - initIVCS = self._getKey('A', sharedSecret, exchangeHash)
|
| - initIVSC = self._getKey('B', sharedSecret, exchangeHash)
|
| - encKeyCS = self._getKey('C', sharedSecret, exchangeHash)
|
| - encKeySC = self._getKey('D', sharedSecret, exchangeHash)
|
| - integKeyCS = self._getKey('E', sharedSecret, exchangeHash)
|
| - integKeySC = self._getKey('F', sharedSecret, exchangeHash)
|
| - outs = [initIVSC, encKeySC, integKeySC]
|
| - ins = [initIVCS, encKeyCS, integKeyCS]
|
| - if self.isClient: # reverse for the client
|
| - log.msg('REVERSE')
|
| - outs, ins = ins, outs
|
| - self.nextEncryptions.setKeys(outs[0], outs[1], ins[0], ins[1],
|
| - outs[2], ins[2])
|
| - self.sendPacket(MSG_NEWKEYS, '')
|
| -
|
| -
|
| - def isEncrypted(self, direction="out"):
|
| - """
|
| - Return True if the connection is encrypted in the given direction.
|
| - Direction must be one of ["out", "in", "both"].
|
| - """
|
| - if direction == "out":
|
| - return self.currentEncryptions.outCipType != 'none'
|
| - elif direction == "in":
|
| - return self.currentEncryptions.inCipType != 'none'
|
| - elif direction == "both":
|
| - return self.isEncrypted("in") and self.isEncrypted("out")
|
| - else:
|
| - raise TypeError('direction must be "out", "in", or "both"')
|
| -
|
| -
|
| - def isVerified(self, direction="out"):
|
| - """
|
| - Return True if the connecction is verified/authenticated in the
|
| - given direction. Direction must be one of ["out", "in", "both"].
|
| - """
|
| - if direction == "out":
|
| - return self.currentEncryptions.outMACType != 'none'
|
| - elif direction == "in":
|
| - return self.currentEncryptions.inMACType != 'none'
|
| - elif direction == "both":
|
| - return self.isVerified("in")and self.isVerified("out")
|
| - else:
|
| - raise TypeError('direction must be "out", "in", or "both"')
|
| -
|
| -
|
| - def loseConnection(self):
|
| - """
|
| - Lose the connection to the other side, sending a
|
| - DISCONNECT_CONNECTION_LOST message.
|
| - """
|
| - self.sendDisconnect(DISCONNECT_CONNECTION_LOST,
|
| - "user closed connection")
|
| -
|
| -
|
| - # client methods
|
| - def receiveError(self, reasonCode, description):
|
| - """
|
| - Called when we receive a disconnect error message from the other
|
| - side.
|
| -
|
| - @param reasonCode: the reason for the disconnect, one of the
|
| - DISCONNECT_ values.
|
| - @type reasonCode: C{int}
|
| - @param description: a human-readable description of the
|
| - disconnection.
|
| - @type description: C{str}
|
| - """
|
| - log.msg('Got remote error, code %s\nreason: %s' % (reasonCode,
|
| - description))
|
| -
|
| -
|
| - def receiveUnimplemented(self, seqnum):
|
| - """
|
| - Called when we receive an unimplemented packet message from the other
|
| - side.
|
| -
|
| - @param seqnum: the sequence number that was not understood.
|
| - @type seqnum: C{int}
|
| - """
|
| - log.msg('other side unimplemented packet #%s' % seqnum)
|
| -
|
| -
|
| - def receiveDebug(self, alwaysDisplay, message, lang):
|
| - """
|
| - Called when we receive a debug message from the other side.
|
| -
|
| - @param alwaysDisplay: if True, this message should always be
|
| - displayed.
|
| - @type alwaysDisplay: C{bool}
|
| - @param message: the debug message
|
| - @type message: C{str}
|
| - @param lang: optionally the language the message is in.
|
| - @type lang: C{str}
|
| - """
|
| - if alwaysDisplay:
|
| - log.msg('Remote Debug Message: %s' % message)
|
| -
|
| -
|
| -
|
| -class SSHServerTransport(SSHTransportBase):
|
| - """
|
| - SSHServerTransport implements the server side of the SSH protocol.
|
| -
|
| - @ivar isClient: since we are never the client, this is always False.
|
| -
|
| - @ivar ignoreNextPacket: if True, ignore the next key exchange packet. This
|
| - is set when the client sends a guessed key exchange packet but with
|
| - an incorrect guess.
|
| -
|
| - @ivar dhGexRequest: the KEX_DH_GEX_REQUEST(_OLD) that the client sent.
|
| - The key generation needs this to be stored.
|
| -
|
| - @ivar g: the Diffie-Hellman group generator.
|
| -
|
| - @ivar p: the Diffie-Hellman group prime.
|
| - """
|
| - isClient = False
|
| - ignoreNextPacket = 0
|
| -
|
| -
|
| - def ssh_KEXINIT(self, packet):
|
| - """
|
| - Called when we receive a MSG_KEXINIT message. For a description
|
| - of the packet, see SSHTransportBase.ssh_KEXINIT(). Additionally,
|
| - this method checks if a guessed key exchange packet was sent. If
|
| - it was sent, and it guessed incorrectly, the next key exchange
|
| - packet MUST be ignored.
|
| - """
|
| - retval = SSHTransportBase.ssh_KEXINIT(self, packet)
|
| - if not retval: # disconnected
|
| - return
|
| - else:
|
| - kexAlgs, keyAlgs, rest = retval
|
| - if ord(rest[0]): # first_kex_packet_follows
|
| - if (kexAlgs[0] != self.supportedKeyExchanges[0] or
|
| - keyAlgs[0] != self.supportedPublicKeys[0]):
|
| - self.ignoreNextPacket = True # guess was wrong
|
| -
|
| -
|
| - def ssh_KEX_DH_GEX_REQUEST_OLD(self, packet):
|
| - """
|
| - This represents two different key exchange methods that share the
|
| - same integer value.
|
| -
|
| - KEXDH_INIT (for diffie-hellman-group1-sha1 exchanges) payload::
|
| -
|
| - integer e (the client's Diffie-Hellman public key)
|
| -
|
| - We send the KEXDH_REPLY with our host key and signature.
|
| -
|
| - KEX_DH_GEX_REQUEST_OLD (for diffie-hellman-group-exchange-sha1)
|
| - payload::
|
| -
|
| - integer ideal (ideal size for the Diffie-Hellman prime)
|
| -
|
| - We send the KEX_DH_GEX_GROUP message with the group that is
|
| - closest in size to ideal.
|
| -
|
| - If we were told to ignore the next key exchange packet by
|
| - ssh_KEXINIT, drop it on the floor and return.
|
| - """
|
| - if self.ignoreNextPacket:
|
| - self.ignoreNextPacket = 0
|
| - return
|
| - if self.kexAlg == 'diffie-hellman-group1-sha1':
|
| - # this is really KEXDH_INIT
|
| - clientDHpublicKey, foo = getMP(packet)
|
| - y = Util.number.getRandomNumber(512, randbytes.secureRandom)
|
| - serverDHpublicKey = _MPpow(DH_GENERATOR, y, DH_PRIME)
|
| - sharedSecret = _MPpow(clientDHpublicKey, y, DH_PRIME)
|
| - h = sha.new()
|
| - h.update(NS(self.otherVersionString))
|
| - h.update(NS(self.ourVersionString))
|
| - h.update(NS(self.otherKexInitPayload))
|
| - h.update(NS(self.ourKexInitPayload))
|
| - h.update(NS(self.factory.publicKeys[self.keyAlg].blob()))
|
| - h.update(MP(clientDHpublicKey))
|
| - h.update(serverDHpublicKey)
|
| - h.update(sharedSecret)
|
| - exchangeHash = h.digest()
|
| - self.sendPacket(
|
| - MSG_KEXDH_REPLY,
|
| - NS(self.factory.publicKeys[self.keyAlg].blob()) +
|
| - serverDHpublicKey +
|
| - NS(self.factory.privateKeys[self.keyAlg].sign(exchangeHash)))
|
| - self._keySetup(sharedSecret, exchangeHash)
|
| - elif self.kexAlg == 'diffie-hellman-group-exchange-sha1':
|
| - self.dhGexRequest = packet
|
| - ideal = struct.unpack('>L', packet)[0]
|
| - self.g, self.p = self.factory.getDHPrime(ideal)
|
| - self.sendPacket(MSG_KEX_DH_GEX_GROUP, MP(self.p) + MP(self.g))
|
| - else:
|
| - raise error.ConchError('bad kexalg: %s' % self.kexAlg)
|
| -
|
| -
|
| - def ssh_KEX_DH_GEX_REQUEST(self, packet):
|
| - """
|
| - Called when we receive a MSG_KEX_DH_GEX_REQUEST message. Payload::
|
| - integer minimum
|
| - integer ideal
|
| - integer maximum
|
| -
|
| - The client is asking for a Diffie-Hellman group between minimum and
|
| - maximum size, and close to ideal if possible. We reply with a
|
| - MSG_KEX_DH_GEX_GROUP message.
|
| -
|
| - If we were told to ignore the next key exchange packekt by
|
| - ssh_KEXINIT, drop it on the floor and return.
|
| - """
|
| - if self.ignoreNextPacket:
|
| - self.ignoreNextPacket = 0
|
| - return
|
| - self.dhGexRequest = packet
|
| - min, ideal, max = struct.unpack('>3L', packet)
|
| - self.g, self.p = self.factory.getDHPrime(ideal)
|
| - self.sendPacket(MSG_KEX_DH_GEX_GROUP, MP(self.p) + MP(self.g))
|
| -
|
| -
|
| - def ssh_KEX_DH_GEX_INIT(self, packet):
|
| - """
|
| - Called when we get a MSG_KEX_DH_GEX_INIT message. Payload::
|
| - integer e (client DH public key)
|
| -
|
| - We send the MSG_KEX_DH_GEX_REPLY message with our host key and
|
| - signature.
|
| - """
|
| - clientDHpublicKey, foo = getMP(packet)
|
| - # TODO: we should also look at the value they send to us and reject
|
| - # insecure values of f (if g==2 and f has a single '1' bit while the
|
| - # rest are '0's, then they must have used a small y also).
|
| -
|
| - # TODO: This could be computed when self.p is set up
|
| - # or do as openssh does and scan f for a single '1' bit instead
|
| -
|
| - pSize = Util.number.size(self.p)
|
| - y = Util.number.getRandomNumber(pSize, randbytes.secureRandom)
|
| -
|
| - serverDHpublicKey = _MPpow(self.g, y, self.p)
|
| - sharedSecret = _MPpow(clientDHpublicKey, y, self.p)
|
| - h = sha.new()
|
| - h.update(NS(self.otherVersionString))
|
| - h.update(NS(self.ourVersionString))
|
| - h.update(NS(self.otherKexInitPayload))
|
| - h.update(NS(self.ourKexInitPayload))
|
| - h.update(NS(self.factory.publicKeys[self.keyAlg].blob()))
|
| - h.update(self.dhGexRequest)
|
| - h.update(MP(self.p))
|
| - h.update(MP(self.g))
|
| - h.update(MP(clientDHpublicKey))
|
| - h.update(serverDHpublicKey)
|
| - h.update(sharedSecret)
|
| - exchangeHash = h.digest()
|
| - self.sendPacket(
|
| - MSG_KEX_DH_GEX_REPLY,
|
| - NS(self.factory.publicKeys[self.keyAlg].blob()) +
|
| - serverDHpublicKey +
|
| - NS(self.factory.privateKeys[self.keyAlg].sign(exchangeHash)))
|
| - self._keySetup(sharedSecret, exchangeHash)
|
| -
|
| -
|
| - def ssh_NEWKEYS(self, packet):
|
| - """
|
| - Called when we get a MSG_NEWKEYS message. No payload.
|
| - When we get this, the keys have been set on both sides, and we
|
| - start using them to encrypt and authenticate the connection.
|
| - """
|
| - log.msg('NEW KEYS')
|
| - if packet != '':
|
| - self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR,
|
| - "NEWKEYS takes no data")
|
| - return
|
| - self.currentEncryptions = self.nextEncryptions
|
| - if self.outgoingCompressionType == 'zlib':
|
| - self.outgoingCompression = zlib.compressobj(6)
|
| - if self.incomingCompressionType == 'zlib':
|
| - self.incomingCompression = zlib.decompressobj()
|
| -
|
| -
|
| - def ssh_SERVICE_REQUEST(self, packet):
|
| - """
|
| - Called when we get a MSG_SERVICE_REQUEST message. Payload::
|
| - string serviceName
|
| -
|
| - The client has requested a service. If we can start the service,
|
| - start it; otherwise, disconnect with
|
| - DISCONNECT_SERVICE_NOT_AVAILABLE.
|
| - """
|
| - service, rest = getNS(packet)
|
| - cls = self.factory.getService(self, service)
|
| - if not cls:
|
| - self.sendDisconnect(DISCONNECT_SERVICE_NOT_AVAILABLE,
|
| - "don't have service %s" % service)
|
| - return
|
| - else:
|
| - self.sendPacket(MSG_SERVICE_ACCEPT, NS(service))
|
| - self.setService(cls())
|
| -
|
| -
|
| -
|
| -class SSHClientTransport(SSHTransportBase):
|
| - """
|
| - SSHClientTransport implements the client side of the SSH protocol.
|
| -
|
| - @ivar isClient: since we are always the client, this is always True.
|
| -
|
| - @ivar _gotNewKeys: if we receive a MSG_NEWKEYS message before we are
|
| - ready to transition to the new keys, this is set to True so we
|
| - can transition when the keys are ready locally.
|
| -
|
| - @ivar x: our Diffie-Hellman private key.
|
| -
|
| - @ivar e: our Diffie-Hellman public key.
|
| -
|
| - @ivar g: the Diffie-Hellman group generator.
|
| -
|
| - @ivar p: the Diffie-Hellman group prime
|
| -
|
| - @ivar instance: the SSHService object we are requesting.
|
| - """
|
| - isClient = True
|
| -
|
| -
|
| - def connectionMade(self):
|
| - """
|
| - Called when the connection is started with the server. Just sets
|
| - up a private instance variable.
|
| - """
|
| - SSHTransportBase.connectionMade(self)
|
| - self._gotNewKeys = 0
|
| -
|
| -
|
| - def ssh_KEXINIT(self, packet):
|
| - """
|
| - Called when we receive a MSG_KEXINIT message. For a description
|
| - of the packet, see SSHTransportBase.ssh_KEXINIT(). Additionally,
|
| - this method sends the first key exchange packet. If the agreed-upon
|
| - exchange is diffie-hellman-group1-sha1, generate a public key
|
| - and send it in a MSG_KEXDH_INIT message. If the exchange is
|
| - diffie-hellman-group-exchange-sha1, ask for a 2048 bit group with a
|
| - MSG_KEX_DH_GEX_REQUEST_OLD message.
|
| - """
|
| - if SSHTransportBase.ssh_KEXINIT(self, packet) is None:
|
| - return # we disconnected
|
| - if self.kexAlg == 'diffie-hellman-group1-sha1':
|
| - self.x = Util.number.getRandomNumber(512, randbytes.secureRandom)
|
| - self.e = _MPpow(DH_GENERATOR, self.x, DH_PRIME)
|
| - self.sendPacket(MSG_KEXDH_INIT, self.e)
|
| - elif self.kexAlg == 'diffie-hellman-group-exchange-sha1':
|
| - self.sendPacket(MSG_KEX_DH_GEX_REQUEST_OLD, '\x00\x00\x08\x00')
|
| - else:
|
| - raise error.ConchError("somehow, the kexAlg has been set "
|
| - "to something we don't support")
|
| -
|
| -
|
| - def ssh_KEX_DH_GEX_GROUP(self, packet):
|
| - """
|
| - This handles two different message which share an integer value.
|
| - If the key exchange is diffie-hellman-group1-sha1, this is
|
| - MSG_KEXDH_REPLY. Payload::
|
| - string serverHostKey
|
| - integer f (server Diffie-Hellman public key)
|
| - string signature
|
| -
|
| - We verify the host key by calling verifyHostKey, then continue in
|
| - _continueKEXDH_REPLY.
|
| -
|
| - If the key exchange is diffie-hellman-group-exchange-sha1, this is
|
| - MSG_KEX_DH_GEX_GROUP. Payload::
|
| - string g (group generator)
|
| - string p (group prime)
|
| -
|
| - We generate a Diffie-Hellman public key and send it in a
|
| - MSG_KEX_DH_GEX_INIT message.
|
| - """
|
| - if self.kexAlg == 'diffie-hellman-group1-sha1':
|
| - # actually MSG_KEXDH_REPLY
|
| - pubKey, packet = getNS(packet)
|
| - f, packet = getMP(packet)
|
| - signature, packet = getNS(packet)
|
| - fingerprint = ':'.join([ch.encode('hex') for ch in
|
| - md5.new(pubKey).digest()])
|
| - d = self.verifyHostKey(pubKey, fingerprint)
|
| - d.addCallback(self._continueKEXDH_REPLY, pubKey, f, signature)
|
| - d.addErrback(
|
| - lambda unused: self.sendDisconnect(
|
| - DISCONNECT_HOST_KEY_NOT_VERIFIABLE, 'bad host key'))
|
| - return d
|
| - else:
|
| - self.p, rest = getMP(packet)
|
| - self.g, rest = getMP(rest)
|
| - self.x = Util.number.getRandomNumber(320, randbytes.secureRandom)
|
| - self.e = _MPpow(self.g, self.x, self.p)
|
| - self.sendPacket(MSG_KEX_DH_GEX_INIT, self.e)
|
| -
|
| -
|
| - def _continueKEXDH_REPLY(self, ignored, pubKey, f, signature):
|
| - """
|
| - The host key has been verified, so we generate the keys.
|
| -
|
| - @param pubKey: the public key blob for the server's public key.
|
| - @type pubKey: C{str}
|
| - @param f: the server's Diffie-Hellman public key.
|
| - @type f: C{long}
|
| - @param signature: the server's signature, verifying that it has the
|
| - correct private key.
|
| - @type signature: C{str}
|
| - """
|
| - serverKey = keys.Key.fromString(pubKey)
|
| - sharedSecret = _MPpow(f, self.x, DH_PRIME)
|
| - h = sha.new()
|
| - h.update(NS(self.ourVersionString))
|
| - h.update(NS(self.otherVersionString))
|
| - h.update(NS(self.ourKexInitPayload))
|
| - h.update(NS(self.otherKexInitPayload))
|
| - h.update(NS(pubKey))
|
| - h.update(self.e)
|
| - h.update(MP(f))
|
| - h.update(sharedSecret)
|
| - exchangeHash = h.digest()
|
| - if not serverKey.verify(signature, exchangeHash):
|
| - self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED,
|
| - 'bad signature')
|
| - return
|
| - self._keySetup(sharedSecret, exchangeHash)
|
| -
|
| -
|
| - def ssh_KEX_DH_GEX_REPLY(self, packet):
|
| - """
|
| - Called when we receieve a MSG_KEX_DH_GEX_REPLY message. Payload::
|
| - string server host key
|
| - integer f (server DH public key)
|
| -
|
| - We verify the host key by calling verifyHostKey, then continue in
|
| - _continueGEX_REPLY.
|
| - """
|
| - pubKey, packet = getNS(packet)
|
| - f, packet = getMP(packet)
|
| - signature, packet = getNS(packet)
|
| - fingerprint = ':'.join(map(lambda c: '%02x'%ord(c),
|
| - md5.new(pubKey).digest()))
|
| - d = self.verifyHostKey(pubKey, fingerprint)
|
| - d.addCallback(self._continueGEX_REPLY, pubKey, f, signature)
|
| - d.addErrback(
|
| - lambda unused: self.sendDisconnect(
|
| - DISCONNECT_HOST_KEY_NOT_VERIFIABLE, 'bad host key'))
|
| - return d
|
| -
|
| -
|
| - def _continueGEX_REPLY(self, ignored, pubKey, f, signature):
|
| - """
|
| - The host key has been verified, so we generate the keys.
|
| -
|
| - @param pubKey: the public key blob for the server's public key.
|
| - @type pubKey: C{str}
|
| - @param f: the server's Diffie-Hellman public key.
|
| - @type f: C{long}
|
| - @param signature: the server's signature, verifying that it has the
|
| - correct private key.
|
| - @type signature: C{str}
|
| - """
|
| - serverKey = keys.Key.fromString(pubKey)
|
| - sharedSecret = _MPpow(f, self.x, self.p)
|
| - h = sha.new()
|
| - h.update(NS(self.ourVersionString))
|
| - h.update(NS(self.otherVersionString))
|
| - h.update(NS(self.ourKexInitPayload))
|
| - h.update(NS(self.otherKexInitPayload))
|
| - h.update(NS(pubKey))
|
| - h.update('\x00\x00\x08\x00')
|
| - h.update(MP(self.p))
|
| - h.update(MP(self.g))
|
| - h.update(self.e)
|
| - h.update(MP(f))
|
| - h.update(sharedSecret)
|
| - exchangeHash = h.digest()
|
| - if not serverKey.verify(signature, exchangeHash):
|
| - self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED,
|
| - 'bad signature')
|
| - return
|
| - self._keySetup(sharedSecret, exchangeHash)
|
| -
|
| -
|
| - def _keySetup(self, sharedSecret, exchangeHash):
|
| - """
|
| - See SSHTransportBase._keySetup().
|
| - """
|
| - SSHTransportBase._keySetup(self, sharedSecret, exchangeHash)
|
| - if self._gotNewKeys:
|
| - self.ssh_NEWKEYS('')
|
| -
|
| -
|
| - def ssh_NEWKEYS(self, packet):
|
| - """
|
| - Called when we receieve a MSG_NEWKEYS message. No payload.
|
| - If we've finished setting up our own keys, start using them.
|
| - Otherwise, remeber that we've receieved this message.
|
| - """
|
| - if packet != '':
|
| - self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR,
|
| - "NEWKEYS takes no data")
|
| - return
|
| - if not self.nextEncryptions.encBlockSize:
|
| - self._gotNewKeys = 1
|
| - return
|
| - log.msg('NEW KEYS')
|
| - self.currentEncryptions = self.nextEncryptions
|
| - if self.outgoingCompressionType == 'zlib':
|
| - self.outgoingCompression = zlib.compressobj(6)
|
| - if self.incomingCompressionType == 'zlib':
|
| - self.incomingCompression = zlib.decompressobj()
|
| - self.connectionSecure()
|
| -
|
| -
|
| - def ssh_SERVICE_ACCEPT(self, packet):
|
| - """
|
| - Called when we receieve a MSG_SERVICE_ACCEPT message. Payload::
|
| - string service name
|
| -
|
| - Start the service we requested.
|
| - """
|
| - name = getNS(packet)[0]
|
| - if name != self.instance.name:
|
| - self.sendDisconnect(
|
| - DISCONNECT_PROTOCOL_ERROR,
|
| - "received accept for service we did not request")
|
| - self.setService(self.instance)
|
| -
|
| -
|
| - def requestService(self, instance):
|
| - """
|
| - Request that a service be run over this transport.
|
| -
|
| - @type instance: subclass of L{twisted.conch.ssh.service.SSHService}
|
| - """
|
| - self.sendPacket(MSG_SERVICE_REQUEST, NS(instance.name))
|
| - self.instance = instance
|
| -
|
| -
|
| - # client methods
|
| - def verifyHostKey(self, hostKey, fingerprint):
|
| - """
|
| - Returns a Deferred that gets a callback if it is a valid key, or
|
| - an errback if not.
|
| -
|
| - @type hostKey: C{str}
|
| - @type fingerprint: C{str}
|
| - @rtype: L{twisted.internet.defer.Deferred}
|
| - """
|
| - # return if it's good
|
| - return defer.fail(NotImplementedError())
|
| -
|
| -
|
| - def connectionSecure(self):
|
| - """
|
| - Called when the encryption has been set up. Generally,
|
| - requestService() is called to run another service over the transport.
|
| - """
|
| - raise NotImplementedError()
|
| -
|
| -
|
| -
|
| -class _DummyCipher:
|
| - """
|
| - A cipher for the none encryption method.
|
| -
|
| - @ivar block_size: the block size of the encryption. In the case of the
|
| - none cipher, this is 8 bytes.
|
| - """
|
| - block_size = 8
|
| -
|
| -
|
| - def encrypt(self, x):
|
| - return x
|
| -
|
| -
|
| - decrypt = encrypt
|
| -
|
| -
|
| -class SSHCiphers:
|
| - """
|
| - SSHCiphers represents all the encryption operations that need to occur
|
| - to encrypt and authenticate the SSH connection.
|
| -
|
| - @cvar cipherMap: A dictionary mapping SSH encryption names to 3-tuples of
|
| - (<Crypto.Cipher.* name>, <block size>, <counter mode>)
|
| - @cvar macMap: A dictionary mapping SSH MAC names to hash modules.
|
| -
|
| - @ivar outCipType: the string type of the outgoing cipher.
|
| - @ivar inCipType: the string type of the incoming cipher.
|
| - @ivar outMACType: the string type of the incoming MAC.
|
| - @ivar inMACType: the string type of the incoming MAC.
|
| - @ivar encBlockSize: the block size of the outgoing cipher.
|
| - @ivar decBlockSize: the block size of the incoming cipher.
|
| - @ivar verifyDigestSize: the size of the incoming MAC.
|
| - @ivar outMAC: a tuple of (<hash module>, <inner key>, <outer key>,
|
| - <digest size>) representing the outgoing MAC.
|
| - @ivar inMAc: see outMAC, but for the incoming MAC.
|
| - """
|
| -
|
| -
|
| - cipherMap = {
|
| - '3des-cbc':('DES3', 24, 0),
|
| - 'blowfish-cbc':('Blowfish', 16,0 ),
|
| - 'aes256-cbc':('AES', 32, 0),
|
| - 'aes192-cbc':('AES', 24, 0),
|
| - 'aes128-cbc':('AES', 16, 0),
|
| - 'cast128-cbc':('CAST', 16, 0),
|
| - 'aes128-ctr':('AES', 16, 1),
|
| - 'aes192-ctr':('AES', 24, 1),
|
| - 'aes256-ctr':('AES', 32, 1),
|
| - '3des-ctr':('DES3', 24, 1),
|
| - 'blowfish-ctr':('Blowfish', 16, 1),
|
| - 'cast128-ctr':('CAST', 16, 1),
|
| - 'none':(None, 0, 0),
|
| - }
|
| - macMap = {
|
| - 'hmac-sha1': sha,
|
| - 'hmac-md5': md5,
|
| - 'none':None
|
| - }
|
| -
|
| -
|
| - def __init__(self, outCip, inCip, outMac, inMac):
|
| - self.outCipType = outCip
|
| - self.inCipType = inCip
|
| - self.outMACType = outMac
|
| - self.inMACType = inMac
|
| - self.encBlockSize = 0
|
| - self.decBlockSize = 0
|
| - self.verifyDigestSize = 0
|
| - self.outMAC = (None, '', '', 0)
|
| - self.inMAC = (None, '', '', 0)
|
| -
|
| -
|
| - def setKeys(self, outIV, outKey, inIV, inKey, outInteg, inInteg):
|
| - """
|
| - Set up the ciphers and hashes using the given keys,
|
| -
|
| - @param outIV: the outgoing initialization vector
|
| - @param outKey: the outgoing encryption key
|
| - @param inIV: the incoming initialization vector
|
| - @param inKey: the incoming encryption key
|
| - @param outInteg: the outgoing integrity key
|
| - @param inInteg: the incoming integrity key.
|
| - """
|
| - o = self._getCipher(self.outCipType, outIV, outKey)
|
| - self.encrypt = o.encrypt
|
| - self.encBlockSize = o.block_size
|
| - o = self._getCipher(self.inCipType, inIV, inKey)
|
| - self.decrypt = o.decrypt
|
| - self.decBlockSize = o.block_size
|
| - self.outMAC = self._getMAC(self.outMACType, outInteg)
|
| - self.inMAC = self._getMAC(self.inMACType, inInteg)
|
| - if self.inMAC:
|
| - self.verifyDigestSize = self.inMAC[3]
|
| -
|
| -
|
| - def _getCipher(self, cip, iv, key):
|
| - """
|
| - Creates an initialized cipher object.
|
| -
|
| - @param cip: the name of the cipher: maps into Crypto.Cipher.*
|
| - @param iv: the initialzation vector
|
| - @param key: the encryption key
|
| - """
|
| - modName, keySize, counterMode = self.cipherMap[cip]
|
| - if not modName: # no cipher
|
| - return _DummyCipher()
|
| - mod = __import__('Crypto.Cipher.%s'%modName, {}, {}, 'x')
|
| - if counterMode:
|
| - return mod.new(key[:keySize], mod.MODE_CTR, iv[:mod.block_size],
|
| - counter=_Counter(iv, mod.block_size))
|
| - else:
|
| - return mod.new(key[:keySize], mod.MODE_CBC, iv[:mod.block_size])
|
| -
|
| -
|
| - def _getMAC(self, mac, key):
|
| - """
|
| - Gets a 4-tuple representing the message authentication code.
|
| - (<hash module>, <inner hash value>, <outer hash value>,
|
| - <digest size>)
|
| -
|
| - @param mac: a key mapping into macMap
|
| - @type mac: C{str}
|
| - @param key: the MAC key.
|
| - @type key: C{str}
|
| - """
|
| - mod = self.macMap[mac]
|
| - if not mod:
|
| - return (None, '', '', 0)
|
| - #if not hasattr(mod, 'digest_size'):
|
| - # ds = len(mod.new().digest())
|
| - #else:
|
| - ds = mod.digest_size
|
| - key = key[:ds] + '\x00' * (64 - ds)
|
| - i = XOR.new('\x36').encrypt(key)
|
| - o = XOR.new('\x5c').encrypt(key)
|
| - return mod, i, o, ds
|
| -
|
| -
|
| - def encrypt(self, blocks):
|
| - """
|
| - Encrypt blocks. Overridden by the encrypt method of a
|
| - Crypto.Cipher.* object in setKeys().
|
| -
|
| - @type blocks: C{str}
|
| - """
|
| - raise NotImplementedError()
|
| -
|
| -
|
| - def decrypt(self, blocks):
|
| - """
|
| - Decrypt blocks. See encrypt().
|
| -
|
| - @type blocks: C{str}
|
| - """
|
| - raise NotImplementedError()
|
| -
|
| -
|
| - def makeMAC(self, seqid, data):
|
| - """
|
| - Create a message authentication code (MAC) for the given packet using
|
| - the outgoing MAC values.
|
| -
|
| - @param seqid: the sequence ID of the outgoing packet
|
| - @type seqid: C{int}
|
| - @param data: the data to create a MAC for
|
| - @type data: C{str}
|
| - @rtype: C{str}
|
| - """
|
| - if not self.outMAC[0]:
|
| - return ''
|
| - data = struct.pack('>L', seqid) + data
|
| - mod, i, o, ds = self.outMAC
|
| - inner = mod.new(i + data)
|
| - outer = mod.new(o + inner.digest())
|
| - return outer.digest()
|
| -
|
| -
|
| - def verify(self, seqid, data, mac):
|
| - """
|
| - Verify an incoming MAC using the incoming MAC values. Return True
|
| - if the MAC is valid.
|
| -
|
| - @param seqid: the sequence ID of the incoming packet
|
| - @type seqid: C{int}
|
| - @param data: the packet data to verify
|
| - @type data: C{str}
|
| - @param mac: the MAC sent with the packet
|
| - @type mac: C{str}
|
| - @rtype: C{bool}
|
| - """
|
| - if not self.inMAC[0]:
|
| - return mac == ''
|
| - data = struct.pack('>L', seqid) + data
|
| - mod, i, o, ds = self.inMAC
|
| - inner = mod.new(i + data)
|
| - outer = mod.new(o + inner.digest())
|
| - return mac == outer.digest()
|
| -
|
| -
|
| -
|
| -class _Counter:
|
| - """
|
| - Stateful counter which returns results packed in a byte string
|
| - """
|
| -
|
| -
|
| - def __init__(self, initialVector, blockSize):
|
| - """
|
| - @type initialVector: C{str}
|
| - @param initialVector: A byte string representing the initial counter
|
| - value.
|
| - @type blockSize: C{int}
|
| - @param blockSize: The length of the output buffer, as well as the
|
| - number of bytes at the beginning of C{initialVector} to consider.
|
| - """
|
| - initialVector = initialVector[:blockSize]
|
| - self.count = getMP('\xff\xff\xff\xff' + initialVector)[0]
|
| - self.blockSize = blockSize
|
| - self.count = Util.number.long_to_bytes(self.count - 1)
|
| - self.count = '\x00' * (self.blockSize - len(self.count)) + self.count
|
| - self.count = array.array('c', self.count)
|
| - self.len = len(self.count) - 1
|
| -
|
| -
|
| - def __call__(self):
|
| - """
|
| - Increment the counter and return the new value.
|
| - """
|
| - i = self.len
|
| - while i > -1:
|
| - self.count[i] = n = chr((ord(self.count[i]) + 1) % 256)
|
| - if n == '\x00':
|
| - i -= 1
|
| - else:
|
| - return self.count.tostring()
|
| -
|
| - self.count = array.array('c', '\x00' * self.blockSize)
|
| - return self.count.tostring()
|
| -
|
| -
|
| -
|
| -# Diffie-Hellman primes from Oakley Group 2 [RFC 2409]
|
| -DH_PRIME = long('17976931348623159077083915679378745319786029604875601170644'
|
| -'442368419718021615851936894783379586492554150218056548598050364644054819923'
|
| -'910005079287700335581663922955313623907650873575991482257486257500742530207'
|
| -'744771258955095793777842444242661733472762929938766870920560605027081084290'
|
| -'7692932019128194467627007L')
|
| -DH_GENERATOR = 2L
|
| -
|
| -
|
| -
|
| -MSG_DISCONNECT = 1
|
| -MSG_IGNORE = 2
|
| -MSG_UNIMPLEMENTED = 3
|
| -MSG_DEBUG = 4
|
| -MSG_SERVICE_REQUEST = 5
|
| -MSG_SERVICE_ACCEPT = 6
|
| -MSG_KEXINIT = 20
|
| -MSG_NEWKEYS = 21
|
| -MSG_KEXDH_INIT = 30
|
| -MSG_KEXDH_REPLY = 31
|
| -MSG_KEX_DH_GEX_REQUEST_OLD = 30
|
| -MSG_KEX_DH_GEX_REQUEST = 34
|
| -MSG_KEX_DH_GEX_GROUP = 31
|
| -MSG_KEX_DH_GEX_INIT = 32
|
| -MSG_KEX_DH_GEX_REPLY = 33
|
| -
|
| -
|
| -
|
| -DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT = 1
|
| -DISCONNECT_PROTOCOL_ERROR = 2
|
| -DISCONNECT_KEY_EXCHANGE_FAILED = 3
|
| -DISCONNECT_RESERVED = 4
|
| -DISCONNECT_MAC_ERROR = 5
|
| -DISCONNECT_COMPRESSION_ERROR = 6
|
| -DISCONNECT_SERVICE_NOT_AVAILABLE = 7
|
| -DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED = 8
|
| -DISCONNECT_HOST_KEY_NOT_VERIFIABLE = 9
|
| -DISCONNECT_CONNECTION_LOST = 10
|
| -DISCONNECT_BY_APPLICATION = 11
|
| -DISCONNECT_TOO_MANY_CONNECTIONS = 12
|
| -DISCONNECT_AUTH_CANCELLED_BY_USER = 13
|
| -DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 14
|
| -DISCONNECT_ILLEGAL_USER_NAME = 15
|
| -
|
| -
|
| -
|
| -messages = {}
|
| -for name, value in globals().items():
|
| - if name.startswith('MSG_'):
|
| - messages[value] = name
|
|
|