def _parse_kexdh_reply(self, m): # The client runs this function. host_key = m.host_key server_f = self.dh.f = m.f if (server_f < 1) or (server_f > self.dh.P - 1): raise SshException('Server kex "f" is out of range') K = self.dh.calculate_k() if log.isEnabledFor(logging.DEBUG): log.debug("K=[{}].".format(K)) # H = (V_C || V_S || I_C || I_S || K_S || e || f || K). hm = bytearray() hm += sshtype.encodeString(self.protocol.local_banner) hm += sshtype.encodeString(self.protocol.remote_banner) hm += sshtype.encodeBinary(self.protocol.local_kex_init_message) hm += sshtype.encodeBinary(self.protocol.remote_kex_init_message) hm += sshtype.encodeBinary(host_key) hm += sshtype.encodeMpint(self.dh.e) hm += sshtype.encodeMpint(server_f) hm += sshtype.encodeMpint(K) H = sha1(hm).digest() self.protocol.set_K_H(K, H) log.info("Verifying signature...") r = yield from self.protocol.verify_server_key(host_key, m.signature) return r
def verify_server_key(self, key_data, sig): if self.server_key: if self.server_key.asbytes() != key_data: raise SshException("Key provided by server differs from that"\ " which we were expecting (address=[{}])."\ .format(self.address)) else: self.server_key = rsakey.RsaKey(key_data) if not self.server_key.verify_ssh_sig(self.h, sig): raise SshException("Signature verification failed (address=[{}])."\ .format(self.address)) log.info("Signature validated correctly!") r = yield from self.connection_handler.peer_authenticated(self) return r
def _process_encrypted_buffer(self): blksize = 16 if self.inCipher != None: # if len(self.buf) > 20: # max(blksize, 20): bs, hmacSize if len(self.buf) < blksize: return False if len(self.cbuf) == 0: out = self.inCipher.decrypt(self.buf[:blksize]) if log.isEnabledFor(logging.DEBUG): log.debug("Decrypted [\n{}] to [\n{}]."\ .format(hex_dump(self.buf[:blksize]), hex_dump(out))) self.cbuf += out packet_length = struct.unpack(">L", out[:4])[0] log.debug("packet_length=[{}].".format(packet_length)) if packet_length > MAX_PACKET_LENGTH: errmsg = "Illegal packet_length [{}] received."\ .format(packet_length) log.warning(errmsg) raise SshException(errmsg) # Add size of packet_length as we leave it in buf. self.bpLength = packet_length + 4 self.buf = self.buf[blksize:] if self.bpLength == blksize: return True if len(self.buf) < min(\ 1024, self.bpLength - len(self.cbuf) + self.inHmacSize): return True l = min(len(self.buf), self.bpLength - len(self.cbuf)) if not l: return True dsize = l - (l % blksize) blks = self.buf[:dsize] self.buf = self.buf[dsize:] assert len(blks) % blksize == 0,\ "len(blks)=[{}], dsize=[{}], l=[{}],"\ " len(self.buf)=[{}], len(self.cbuf)=[{}], blksize=[{}],"\ " self.bpLength=[{}], type(blks)=[{}]."\ .format(len(blks), dsize, l, len(self.buf),\ len(self.cbuf), blksize, self.bpLength, type(blks)) out = self.inCipher.decrypt(blks) self.cbuf += out if log.isEnabledFor(logging.DEBUG): log.debug("Decrypted [\n{}] to [\n{}].".format(hex_dump(blks), hex_dump(out))) log.debug("len(cbuf)={}, cbuf=[\n{}]".format(len(self.cbuf), hex_dump(self.cbuf))) else: self.cbuf = self.buf return True
def read_packet(self, require_connected=True): if self.status is Status.disconnected: return None if require_connected and self.status is Status.closed: errstr = "ProtocolHandler closed, refusing read_packet(..)!" log.debug(errstr) raise SshException(errstr) if self.packet != None: packet = self.packet self.packet = None if packet[0] == 0x01: yield from\ self._peer_disconnected(\ mnetpacket.SshDisconnectMessage(packet)) return None log.info("P: Returning next packet.") #asyncio.call_soon(self.process_buffer()) # For now, call process_buffer in this event. if len(self.buf) > 0: self.process_buffer() return packet if self.status is Status.closed or self.status is Status.disconnected: return None log.info("P: Waiting for packet.") yield from self.do_wait() if self.status is Status.closed or self.status is Status.disconnected: return None assert self.packet != None packet = self.packet self.packet = None log.info("P: Notified of packet.") if packet[0] == 0x01: yield from\ self._peer_disconnected(\ mnetpacket.SshDisconnectMessage(packet)) return None # For now, call process_buffer in this event. if len(self.buf) > 0: self.process_buffer() return packet
def connectTaskInsecure(protocol, server_mode): m = mnetpacket.SshKexdhReplyMessage() if server_mode: m.host_key = protocol.server_key.asbytes() else: m.host_key = protocol.client_key.asbytes() m.f = 42 m.signature = b"test" m.encode() protocol.write_packet(m) pkt = yield from protocol.read_packet() if not pkt: return False m = mnetpacket.SshKexdhReplyMessage(pkt) if server_mode: if protocol.client_key: if protocol.client_key.asbytes() != m.host_key: raise SshException("Key provided by client differs from that which we were expecting.") else: protocol.client_key = rsakey.RsaKey(m.host_key) else: if protocol.server_key: if protocol.server_key.asbytes() != m.host_key: raise SshException("Key provided by server differs from that which we were expecting.") else: protocol.server_key = rsakey.RsaKey(m.host_key) r = yield from protocol.connection_handler.peer_authenticated(protocol) if not r: # Peer is rejected for some reason by higher level. protocol.close() return False return True
def _parse_kexdh_init(self, m): # The server runs this function. client_e = self.dh.f = m.e if (client_e < 1) or (client_e > self.dh.P - 1): raise SshException("Client kex 'e' is out of range") K = self.dh.calculate_k() if log.isEnabledFor(logging.DEBUG): log.debug("K=[{}].".format(K)) key = self.protocol.server_key.asbytes() # H = (V_C || V_S || I_C || I_S || K_S || e || f || K). hm = bytearray() hm += sshtype.encodeString(self.protocol.remote_banner) hm += sshtype.encodeString(self.protocol.local_banner) hm += sshtype.encodeBinary(self.protocol.remote_kex_init_message) hm += sshtype.encodeBinary(self.protocol.local_kex_init_message) hm += sshtype.encodeBinary(key) hm += sshtype.encodeMpint(client_e) hm += sshtype.encodeMpint(self.dh.e) hm += sshtype.encodeMpint(K) H = sha1(hm).digest() self.protocol.set_K_H(K, H) # Sign it. sig = self.protocol.server_key.sign_ssh_data(H) # Send reply. m = mnp.SshKexdhReplyMessage() m.host_key = key m.f = self.dh.e m.signature = sig m.encode() self.protocol.write_packet(m)
def open_channel(self, channel_type, block=False): "Returns the channel queue for the new channel." if self.status is Status.new: if not block: raise SshException("Connection is not ready yet.") waiter = asyncio.futures.Future(loop=self.loop) self.ready_waiters.append(waiter) yield from waiter if self.status is not Status.ready: # Ignore if it is closed or disconnected. if log.isEnabledFor(logging.INFO): log.info("open_channel(..) called on a closed connection.") return None, None if self._implicit_channels_enabled: local_cid = self._open_implicit_channel(channel_type) else: local_cid = self._open_channel(channel_type) queue = self._create_channel_queue() self.channel_queues[local_cid] = queue if self._implicit_channels_enabled: yield from self.channel_handler.channel_opened(\ self, None, local_cid, queue) elif block: r = yield from queue.get() if not r: # Could be None for disconnect or False for rejected. return local_cid, r assert r == True, "r=[{}]!".format(r) return local_cid, queue
def _process_buffer(self): if log.isEnabledFor(logging.DEBUG): log.debug("P: process_buffer(): called (binaryMode={}), buf=[\n{}].".format(self.binaryMode, hex_dump(self.buf))) assert self.binaryMode r = self._process_encrypted_buffer() if not r: return # cbuf is clear text buf. while True: if self.bpLength is None: assert not self.inCipher if len(self.cbuf) < 4: return if log.isEnabledFor(logging.DEBUG): log.debug("t=[{}].".format(self.cbuf[:4])) packet_length = struct.unpack(">L", self.cbuf[:4])[0] if log.isEnabledFor(logging.DEBUG): log.debug("packet_length=[{}].".format(packet_length)) if packet_length > MAX_PACKET_LENGTH: errmsg = "Illegal packet_length [{}] received."\ .format(packet_length) log.warning(errmsg) raise SshException(errmsg) self.bpLength = packet_length + 4 # Add size of packet_length as we leave it in buf. else: if len(self.cbuf) < self.bpLength or len(self.buf) < self.inHmacSize: return; if log.isEnabledFor(logging.DEBUG): log.debug("PACKET READ (bpLength={}, inHmacSize={}, len(self.cbuf)={}, len(self.buf)={})".format(self.bpLength, self.inHmacSize, len(self.cbuf), len(self.buf))) padding_length = struct.unpack("B", self.cbuf[4:5])[0] log.debug("padding_length=[{}].".format(padding_length)) padding_offset = self.bpLength - padding_length payload = self.cbuf[5:padding_offset] padding = self.cbuf[padding_offset:self.bpLength] # mac = self.cbuf[self.bpLength:self.bpLength + self.inHmacSize] mac = self.buf[:self.inHmacSize] if log.isEnabledFor(logging.DEBUG): log.debug("payload=[\n{}], padding=[\n{}], mac=[\n{}] len(mac)={}.".format(hex_dump(payload), hex_dump(padding), hex_dump(mac), len(mac))) if self.inHmacSize != 0: self.buf = self.buf[self.inHmacSize:] mbuf = struct.pack(">L", self.inPacketId) tmac = hmac.new(self.inHmacKey, digestmod=sha1) tmac.update(mbuf) tmac.update(self.cbuf) cmac = tmac.digest() if log.isEnabledFor(logging.DEBUG): log.debug("inPacketId={} len(cmac)={}, cmac=[\n{}].".format(self.inPacketId, len(cmac), hex_dump(cmac))) r = hmac.compare_digest(cmac, mac) log.info("HMAC check result: [{}].".format(r)) if not r: raise SshException("HMAC check failure, packetId={}.".format(self.inPacketId)) newbuf = self.cbuf[self.bpLength + self.inHmacSize:] if self.cbuf == self.buf: self.cbuf = bytearray() self.buf = newbuf else: self.cbuf = newbuf if self.waitingForNewKeys: packet_type = mnetpacket.SshPacket.parse_type(payload) if packet_type == mnetpacket.SSH_MSG_NEWKEYS: if self.server_mode: self.init_inbound_encryption() else: # Disable further processing until inbound # encryption is setup. It may not have yet as # parameters and newkeys may have come in same tcp # packet. self.set_inbound_enabled(False) self.waitingForNewKeys = False self.packet = payload self.inPacketId = (self.inPacketId + 1) & 0xFFFFFFFF self.bpLength = None if self.waiter != None: self.waiter.set_result(False) self.waiter = None break;
def _process_ssh_packet(self, packet, offset=0): t = mnetpacket.SshPacket.parse_type(packet, offset) if log.isEnabledFor(logging.INFO): log.info("Received packet, type=[{}].".format(t)) if t == mnetpacket.SSH_MSG_CHANNEL_OPEN: msg = mnetpacket.SshChannelOpenMessage(packet) if log.isEnabledFor(logging.INFO): log.info("P: Received CHANNEL_OPEN: channel_type=[{}],"\ " sender_channel=[{}]."\ .format(msg.channel_type, msg.sender_channel)) if self._implicit_channels_enabled: if msg.data_packet is None: raise SshException() if self._reverse_channel_map.get(msg.sender_channel): log.warning("Remote end sent a CHANNEL_OPEN request with an already open remote id; ignoring.") return r = yield from\ self.channel_handler.request_open_channel(self, msg) if r: local_cid = self._accept_channel_open(msg) if log.isEnabledFor(logging.INFO): log.info("Channel [{}] opened (address=[{}])."\ .format(local_cid, self.address)) queue = self._create_channel_queue() self.channel_queues[local_cid] = queue yield from self.channel_handler.channel_opened(\ self, msg.channel_type, local_cid, queue) if self._implicit_channels_enabled: yield from self._process_ssh_packet(msg.data_packet) elif not self._implicit_channels_enabled: self._open_channel_reject(msg) elif t == mnetpacket.SSH_MSG_CHANNEL_OPEN_CONFIRMATION: if self._implicit_channels_enabled: raise SshException() msg = mnetpacket.SshChannelOpenConfirmationMessage(packet) log.info("P: Received CHANNEL_OPEN_CONFIRMATION:"\ " sender_channel=[{}], recipient_channel=[{}]."\ .format(msg.sender_channel, msg.recipient_channel)) rcid = self._channel_map.get(msg.recipient_channel) if rcid == None: log.warning("Received a CHANNEL_OPEN_CONFIRMATION for a local channel that was not started; ignoring.") return if rcid == ChannelStatus.closing: log.warning("Received a CHANNEL_OPEN_CONFIRMATION for a local channel that was closed; ignoring.") return if rcid != ChannelStatus.opening: log.warning("Received a CHANNEL_OPEN_CONFIRMATION for a local channel that was already open; ignoring.") return lcid = self._reverse_channel_map\ .setdefault(msg.sender_channel, msg.recipient_channel) if lcid is not msg.recipient_channel: log.warning("Received a CHANNEL_OPEN_CONFIRMATION for a remote channel that is already open; ignoring.") return self._channel_map[msg.recipient_channel] = msg.sender_channel if log.isEnabledFor(logging.INFO): log.info("Channel [{}] opened (address=[{}])."\ .format(msg.recipient_channel, self.address)) # First 'packet' is a True, signaling the channel is open to # those yielding from the queue. queue = self.channel_queues[msg.recipient_channel] yield from queue.put(True) yield from self.channel_handler\ .channel_opened(self, None, msg.recipient_channel, queue) elif t == mnetpacket.SSH_MSG_CHANNEL_OPEN_FAILURE: msg = mnetpacket.SshChannelOpenFailureMessage(packet) log.info("P: Received CHANNEL_OPEN_FAILURE recipient_channel=[{}].".format(msg.recipient_channel)) queue = self.channel_queues[msg.recipient_channel] yield from queue.put(False) if (yield from self._close_channel(msg.recipient_channel, True)): yield from\ self.channel_handler.channel_open_failed(self, msg) elif t == mnetpacket.SSH_MSG_CHANNEL_IMPLICIT_WRAPPER: msg = mnetpacket.SshChannelImplicitWrapper(packet, offset) offset += mnetpacket.SshChannelImplicitWrapper.data_offset yield from self._process_ssh_packet(packet, offset) elif t == mnetpacket.SSH_MSG_CHANNEL_EXTENDED_DATA: raise SshException("Unimplemented.") elif t == mnetpacket.SSH_MSG_CHANNEL_DATA: msg = mnetpacket.SshChannelDataMessage(packet, offset) if offset: remote_cid = self._fix_implicit_msg(msg) else: remote_cid = self._channel_map[msg.recipient_channel] log.info("P: Received CHANNEL_DATA recipient_channel=[{}]."\ .format(msg.recipient_channel)) if remote_cid is None: raise SshException(\ "Received data for unmapped channel.") r = yield from self.channel_handler.channel_data(\ self, msg.recipient_channel, msg.data) if not r: log.info(\ "Adding protocol (address={}) channel [{}] data"\ " to queue (remote_cid=[{}])."\ .format(self.address, msg.recipient_channel, remote_cid)) yield from self.channel_queues[msg.recipient_channel]\ .put(msg.data) elif t == mnetpacket.SSH_MSG_CHANNEL_CLOSE: msg = mnetpacket.SshChannelCloseMessage(packet) if log.isEnabledFor(logging.INFO): log.info("P: Received CHANNEL_CLOSE (recipient_channel=[{}],"\ " implicit_channel=[{}])."\ .format(msg.recipient_channel, msg.implicit_channel)) local_cid = msg.recipient_channel if self._implicit_channels_enabled: if msg.implicit_channel: local_cid = self._reverse_channel_map[local_cid] if log.isEnabledFor(logging.INFO): log.info("implicit_channel, local_cid=[{}]."\ .format(local_cid)) else: if msg.implicit_channel: raise SshException() if (yield from self._close_channel(local_cid)): yield from self.channel_handler.channel_closed(\ self, local_cid) elif t == mnetpacket.SSH_MSG_CHANNEL_REQUEST: msg = mnetpacket.SshChannelRequest(packet, offset) if offset: self._fix_implicit_msg(msg) if log.isEnabledFor(logging.INFO): log.info("Received SSH_MSG_CHANNEL_REQUEST:"\ " recipient_channel=[{}], request_type=[{}],"\ " want_reply=[{}]."\ .format(msg.recipient_channel, msg.request_type,\ msg.want_reply)) yield from self.channel_handler.channel_request(self, msg) else: log.warning("Unhandled packet of type [{}].".format(t))
def connectTaskSecure(protocol, server_mode): # Send KexInit packet. opobj = mnetpacket.SshKexInitMessage() opobj.cookie = os.urandom(16) # opobj.kex_algorithms = "diffie-hellman-group-exchange-sha256" opobj.kex_algorithms = "diffie-hellman-group14-sha1" opobj.server_host_key_algorithms = "ssh-rsa" opobj.encryption_algorithms_client_to_server = "aes256-cbc" opobj.encryption_algorithms_server_to_client = "aes256-cbc" # opobj.mac_algorithms_client_to_server = "hmac-sha2-512" # opobj.mac_algorithms_server_to_client = "hmac-sha2-512" opobj.mac_algorithms_client_to_server = "hmac-sha1" opobj.mac_algorithms_server_to_client = "hmac-sha1" opobj.compression_algorithms_client_to_server = "none" opobj.compression_algorithms_server_to_client = "none" opobj.encode() protocol.local_kex_init_message = opobj.buf protocol.write_packet(opobj) # Read KexInit packet. packet = yield from protocol.read_packet() if not packet: return False if log.isEnabledFor(logging.DEBUG): log.debug("X: Received packet [{}].".format(hex_dump(packet))) packet_type = mnetpacket.SshPacket.parse_type(packet) if log.isEnabledFor(logging.INFO): log.info("packet_type=[{}].".format(packet_type)) if packet_type != 20: log.warning("Peer sent unexpected packet_type[{}], disconnecting.".format(packet_type)) protocol.close() return False protocol.remote_kex_init_message = packet pobj = mnetpacket.SshKexInitMessage(packet) if log.isEnabledFor(logging.DEBUG): log.debug("cookie=[{}].".format(pobj.cookie)) if log.isEnabledFor(logging.INFO): log.info("keyExchangeAlgorithms=[{}].".format(pobj.kex_algorithms)) protocol.waitingForNewKeys = True # ke = kex.KexGroup14(protocol) # log.info("Calling start_kex()...") # r = yield from ke.do_kex() ke = kexdhgroup14sha1.KexDhGroup14Sha1(protocol) log.info("Calling kex->run()...") r = yield from ke.run() if not r: # Client is rejected for some reason by higher level. protocol.close() return False # Setup encryption now that keys are exchanged. protocol.init_outbound_encryption() if not protocol.server_mode: """ Server gets done automatically since parameters are always there before NEWKEYS is received, but client the parameters and NEWKEYS message may come in the same tcppacket, so the auto part just turns off inbound processing and waits for us to call init_inbound_encryption when we have the parameters ready. """ protocol.init_inbound_encryption() protocol.set_inbound_enabled(True) packet = yield from protocol.read_packet() if not packet: return False m = mnetpacket.SshNewKeysMessage(packet) log.debug("Received SSH_MSG_NEWKEYS.") if protocol.server_mode: packet = yield from protocol.read_packet() if not packet: return False # m = mnetpacket.SshPacket(None, packet) # log.info("X: Received packet (type={}) [{}].".format(m.packet_type, packet)) m = mnetpacket.SshServiceRequestMessage(packet) log.info("Service requested [{}].".format(m.service_name)) if m.service_name != "ssh-userauth": raise SshException("Remote end requested unexpected service (name=[{}]).".format(m.service_name)) mr = mnetpacket.SshServiceAcceptMessage() mr.service_name = "ssh-userauth" mr.encode() protocol.write_packet(mr) packet = yield from protocol.read_packet() if not packet: return False m = mnetpacket.SshUserauthRequestMessage(packet) log.info("Userauth requested with method=[{}].".format(m.method_name)) if m.method_name == "none": mr = mnetpacket.SshUserauthFailureMessage() mr.auths = "publickey" mr.partial_success = False mr.encode() protocol.write_packet(mr) packet = yield from protocol.read_packet() if not packet: return False m = mnetpacket.SshUserauthRequestMessage(packet) log.info("Userauth requested with method=[{}].".format(m.method_name)) if m.method_name != "publickey": raise SshException("Unhandled client auth method [{}].".format(m.method_name)) if m.algorithm_name != "ssh-rsa": raise SshException("Unhandled client auth algorithm [{}].".format(m.algorithm_name)) log.debug("m.signature_present()={}.".format(m.signature_present)) if not m.signature_present: mr = mnetpacket.SshUserauthPkOkMessage() mr.algorithm_name = m.algorithm_name mr.public_key = m.public_key mr.encode() protocol.write_packet(mr) packet = yield from protocol.read_packet() if not packet: return False m = mnetpacket.SshUserauthRequestMessage(packet) log.info("Userauth requested with method=[{}].".format(m.method_name)) if m.method_name != "publickey": raise SshException("Unhandled client auth method [{}].".format(m.method_name)) if m.algorithm_name != "ssh-rsa": raise SshException("Unhandled client auth algorithm [{}].".format(m.algorithm_name)) if log.isEnabledFor(logging.DEBUG): log.debug("signature=[{}].".format(hex_dump(m.signature))) if protocol.client_key: if protocol.client_key.asbytes() != m.public_key: raise SshException("Key provided by client differs from that which we were expecting.") else: protocol.client_key = rsakey.RsaKey(m.public_key) buf = bytearray() buf += sshtype.encodeBinary(protocol.session_id) buf += packet[:-m.signature_length] r = protocol.client_key.verify_ssh_sig(buf, m.signature) log.info("Userauth signature check result: [{}].".format(r)) if not r: raise SshException("Signature and key provided by client did not match.") r = yield from protocol.connection_handler.peer_authenticated(protocol) if not r: # Client is rejected for some reason by higher level. protocol.close() return False mr = mnetpacket.SshUserauthSuccessMessage() mr.encode() protocol.write_packet(mr) else: # client mode. m = mnetpacket.SshServiceRequestMessage() m.service_name = "ssh-userauth" m.encode() protocol.write_packet(m) packet = yield from protocol.read_packet() if not packet: return False m = mnetpacket.SshServiceAcceptMessage(packet) log.info("Service request accepted [{}].".format(m.service_name)) mr = mnetpacket.SshUserauthRequestMessage() mr.user_name = "dev" mr.service_name = "ssh-connection" mr.method_name = "publickey" mr.signature_present = True mr.algorithm_name = "ssh-rsa" ckey = protocol.client_key mr.public_key = ckey.asbytes() mr.encode() mrb = bytearray() mrb += sshtype.encodeBinary(protocol.session_id) mrb += mr.buf sig = sshtype.encodeBinary(ckey.sign_ssh_data(mrb)) mrb = mr.buf assert mr.buf == mrb mrb += sig protocol.write_packet(mr) packet = yield from protocol.read_packet() if not packet: return False m = mnetpacket.SshUserauthSuccessMessage(packet) log.info("Userauth accepted.") log.info("Connect task done (server={}).".format(server_mode)) # if not server_mode: # protocol.close() return True