Exemplo n.º 1
0
    def upstream_recv(self, data):
        if self.encryptor:
            buf = Buffer()

            ldata = len(data)
            buf.write(self.encryptor.encrypt(struct.pack('<I', ldata)))
            _, nw = data.write_to(buf, modificator=self.encryptor.encrypt, n=ldata)
            d = self.update_encryptor()
            buf.write(d)

            buf.write_to(self.downstream)
        else:
            data.write_to(self.up_buffer)
Exemplo n.º 2
0
class EC4Transport(BasePupyTransport):

    __slots__ = ('encryptor', 'decryptor', 'up_buffer')

    privkey = None
    pubkey = None

    def __init__(self, *args, **kwargs):
        super(EC4Transport, self).__init__(*args, **kwargs)
        if not self.pubkey and not self.privkey:
            raise ValueError('Public or Private key required for EC4')

        if self.pubkey:
            self.encoder = ECPV(curve='brainpoolP384r1',
                                public_key=self.pubkey,
                                hash=SHA384)
        else:
            self.encoder = ECPV(curve='brainpoolP384r1',
                                private_key=self.privkey,
                                hash=SHA384)

        self.encryptor = None
        self.decryptor = None
        self.up_buffer = Buffer()

    def kex(self, data):
        if len(data) < 2:
            return False

        length, = struct.unpack_from('H', data.peek(2))
        if len(data) < 2 + length:
            return False

        request = data.read(2 + length)

        if self.privkey:
            try:
                response, key = self.encoder.process_kex_request(request[2:],
                                                                 0,
                                                                 key_size=128)
            except ValueError as e:
                raise EOFError(str(e))

            # Add jitter, tinyec is quite horrible
            time.sleep(random.random())
            self.downstream.write(struct.pack('H', len(response)) + response)
        else:
            try:
                key = self.encoder.process_kex_response(request[2:],
                                                        0,
                                                        key_size=128)
            except ValueError as e:
                raise EOFError(str(e))

        self.encryptor = RC4(key=key[0])
        self.decryptor = RC4(key=key[1])

        # https://wikileaks.org/ciav7p1/cms/files/NOD%20Cryptographic%20Requirements%20v1.1%20TOP%20SECRET.pdf
        # Okay...
        self.encryptor.encrypt('\x00' * 3072)
        self.decryptor.decrypt('\x00' * 3072)
        return True

    def downstream_recv(self, data):
        if self.encryptor:
            data.write_to(self.upstream, modificator=self.decryptor.decrypt)

        elif self.kex(data):
            if self.up_buffer:
                self.up_buffer.write_to(self.downstream,
                                        modificator=self.encryptor.encrypt)
                self.up_buffer = None

            if len(data):
                data.write_to(self.upstream,
                              modificator=self.decryptor.decrypt)

    def upstream_recv(self, data):
        if self.encryptor:
            data.write_to(self.downstream, modificator=self.encryptor.encrypt)
        else:
            data.write_to(self.up_buffer)
Exemplo n.º 3
0
class PupySocketStream(SocketStream):
    def __init__(self, sock, transport_class, transport_kwargs):
        super(PupySocketStream, self).__init__(sock)

        self.MAX_IO_CHUNK = 32000
        self.KEEP_ALIVE_REQUIRED = False
        self.compress = True

        #buffers for transport
        self.upstream = Buffer(
            transport_func=addGetPeer(("127.0.0.1", 443)),
            shared=True
        )

        if sock is None:
            peername = '127.0.0.1', 0
        elif type(sock) is tuple:
            peername = sock[0], sock[1]
        else:
            peername = sock.getpeername()

        self.downstream = Buffer(
            on_write=self._upstream_recv,
            transport_func=addGetPeer(peername),
            shared=True
        )

        self.upstream_lock = threading.Lock()
        self.downstream_lock = threading.Lock()

        self.transport = transport_class(self, **transport_kwargs)

        #buffers for streams
        self.buf_in = Buffer()
        self.buf_out = Buffer()

        self.on_connect()

    def on_connect(self):
        self.transport.on_connect()
        self._upstream_recv()

    def _read(self):
        try:
            buf = self.sock.recv(self.MAX_IO_CHUNK)
            if __debug__:
                logger.debug('stream: read={}'.format(len(buf) if buf else None))

        except socket.timeout:
            return

        except socket.error:
            ex = sys.exc_info()[1]
            if get_exc_errno(ex) in (errno.EAGAIN, errno.EWOULDBLOCK):
                # windows just has to be a b**ch
                # edit: some politeness please ;)
                return
            self.close()
            raise EOFError(ex)

        if not buf:
            self.close()
            raise EOFError("connection closed by peer")

        self.buf_in.write(buf)

    # The root of evil
    def poll(self, timeout):
        if self.closed:
            raise EOFError('polling on already closed connection')
        result = ( len(self.upstream)>0 or self.sock_poll(timeout) )
        return result

    def sock_poll(self, timeout):
        with self.downstream_lock:
            to_close = None
            to_read = None

            while not (to_close or to_read or self.closed):
                try:
                    to_read, _, to_close = select([self.sock], [], [self.sock], timeout)
                except select_error as r:
                    if not r.args[0] == errno.EINTR:
                        to_close = True
                    continue

                break

            if to_close:
                raise EOFError('sock_poll error')

            if to_read:
                self._read()
                self.transport.downstream_recv(self.buf_in)
                return True
            else:
                return False

    def _upstream_recv(self):
        """ called as a callback on the downstream.write """
        if len(self.downstream)>0:
            if __debug__:
                logger.debug('stream: send={}'.format(len(self.downstream)))

            self.downstream.write_to(super(PupySocketStream, self))

    def waitfor(self, count):
        if __debug__:
            logger.debug('stream: waitfor={}'.format(count))

        try:
            while len(self.upstream)<count:
                if not self.sock_poll(None) and self.closed:
                    return None

            return self.upstream

        except (EOFError, socket.error):
            self.close()
            raise

        except Exception as e:
            logger.debug(traceback.format_exc())
            self.close()
            raise

    def read(self, count):
        promise = self.waitfor(count)
        if promise:
            return promise.read(count)

    def insert(self, data):
        with self.upstream_lock:
            self.buf_out.insert(data)

    def flush(self):
        self.buf_out.flush()

    def write(self, data, notify=True):
        if __debug__:
            logger.debug('stream: write={} / n={}'.format(
                len(data) if data else None, notify))

        try:
            with self.upstream_lock:
                self.buf_out.write(data, notify)
                del data
                if notify:
                    self.transport.upstream_recv(self.buf_out)
            #The write will be done by the _upstream_recv callback on the downstream buffer

        except (EOFError, socket.error):
            self.close()
            raise

        except Exception as e:
            logger.debug(traceback.format_exc())
            self.close()
            raise
Exemplo n.º 4
0
class ECMTransport(BasePupyTransport):
    __slots__ = ('encryptor', 'decryptor', 'up_buffer', 'dec_buffer', 'nonce',
                 'key', 'chunk_len', 'need_validation', 'encoder')

    privkey = None
    pubkey = None

    def __init__(self, *args, **kwargs):
        super(ECMTransport, self).__init__(*args, **kwargs)
        if not self.pubkey and not self.privkey:
            raise ValueError('Public or Private key required for ECM')

        if self.pubkey:
            self.encoder = ECPV(curve='brainpoolP384r1',
                                public_key=self.pubkey,
                                hash=SHA384)
        else:
            self.encoder = ECPV(curve='brainpoolP384r1',
                                public_key=self.privkey,
                                hash=SHA384)

        self.encryptor = None
        self.decryptor = None
        self.up_buffer = Buffer()
        self.dec_buffer = Buffer()
        self.nonce = None
        self.key = None
        self.chunk_len = 0
        self.need_validation = False

    def update_encryptor(self):
        vblock = self.encryptor.digest()

        h = SHA3_224.new()
        h.update(self.key[0])
        h.update(vblock)
        self.encryptor = AES.new(key=self.key[0],
                                 mode=AES.MODE_GCM,
                                 nonce=h.digest())

        return vblock

    def update_decryptor(self, vblock):
        self.decryptor.verify(vblock)

        h = SHA3_224.new()
        h.update(self.key[1])
        h.update(vblock)
        self.decryptor = AES.new(key=self.key[1],
                                 mode=AES.MODE_GCM,
                                 nonce=h.digest())

    def kex(self, data):
        if len(data) < 4:
            return False

        reqlen, noncelen = struct.unpack_from('<HH', data.peek(4))
        if len(data) < 4 + reqlen + noncelen:
            return False

        data.drain(4)

        request = data.read(reqlen)
        remote_nonce = data.read(noncelen)

        if self.privkey:
            response, key = self.encoder.process_kex_request(request, 0)
            # Add jitter, tinyec is quite horrible
            time.sleep(random.random())
            nonce = get_random_bytes(16)
            self.downstream.write(
                struct.pack('<HH', len(response), len(nonce)) + response +
                nonce)
        else:
            key = self.encoder.process_kex_response(request, 0)
            nonce = self.nonce

        eh = SHA3_224.new()
        eh.update(key[0])
        eh.update(remote_nonce)
        ek = eh.digest()[:16]

        dh = SHA3_224.new()
        dh.update(key[1])
        dh.update(nonce)
        dk = dh.digest()[:16]

        self.key = (ek, dk)

        self.encryptor = AES.new(key=self.key[0],
                                 mode=AES.MODE_GCM,
                                 nonce=nonce)
        self.decryptor = AES.new(key=self.key[1],
                                 mode=AES.MODE_GCM,
                                 nonce=remote_nonce)

        return True

    def downstream_recv(self, data):
        if self.decryptor:
            while len(data):
                if not self.chunk_len:
                    if self.need_validation:
                        if len(data) < 16:
                            return

                        vblock = data.read(16)
                        self.update_decryptor(vblock)
                        self.need_validation = False
                        self.dec_buffer.write_to(self.upstream)

                    if len(data) < 4:
                        break

                    raw_chunk_len = self.decryptor.decrypt(data.read(4))
                    self.chunk_len, = struct.unpack('<I', raw_chunk_len)

                if not len(data):
                    break

                nr, nw = data.write_to(self.dec_buffer,
                                       modificator=self.decryptor.decrypt,
                                       n=self.chunk_len)

                self.chunk_len -= nr
                if not self.chunk_len:
                    self.need_validation = True

        elif self.kex(data):
            if len(self.up_buffer):
                self.upstream_recv(self.up_buffer)

            if len(data):
                self.downstream_recv(data)

    def upstream_recv(self, data):
        if self.encryptor:
            buf = Buffer()

            ldata = len(data)
            buf.write(self.encryptor.encrypt(struct.pack('<I', ldata)))
            _, nw = data.write_to(buf,
                                  modificator=self.encryptor.encrypt,
                                  n=ldata)
            d = self.update_encryptor()
            buf.write(d)

            buf.write_to(self.downstream)
        else:
            data.write_to(self.up_buffer)
Exemplo n.º 5
0
class PupyHTTPWrapperServer(BasePupyTransport):
    path = '/index.php?d='
    allowed_methods = ('GET')
    server = None
    headers = {
        'Content-Type': 'text/html; charset=utf-8',
        'Server': 'Apache',
        'Connection': 'close',
    }

    __slots__ = ('parser', 'is_http', 'body', 'downstream_buffer',
                 'well_known', 'omit', 'probe_len')

    def __init__(self, *args, **kwargs):
        super(PupyHTTPWrapperServer, self).__init__(*args, **kwargs)

        self.parser = HttpParser()
        self.is_http = None
        self.body = []
        self.downstream_buffer = Buffer()

        self.well_known = ('GET', 'POST', 'OPTIONS', 'HEAD', 'PUT', 'DELETE')
        self.omit = tuple('{} {}'.format(x, y) for x in self.well_known
                          for y in (self.path, '/ws/', 'ws/'))
        self.probe_len = max(len(x) for x in self.omit)

    def _http_response(self,
                       code,
                       status,
                       headers=None,
                       datasize=None,
                       content=None):
        headers = {}
        headers.update(self.headers)

        if headers:
            headers.update(headers)

        if datasize:
            headers.update({
                'Content-Length': datasize,
                'Content-Type': 'application/octet-stream',
            })

        data = '\r\n'.join([
            'HTTP/1.1 {} {}'.format(code, status), '\r\n'.join([
                '{}: {}'.format(key, value)
                for key, value in headers.iteritems()
            ])
        ]) + '\r\n\r\n'

        self.downstream.write(data)

    def _handle_file(self, filepath):
        try:
            with open(filepath) as infile:
                size = stat(filepath).st_size
                self._http_response(200, 'OK', datasize=size)

                while True:
                    data = infile.read(65535)
                    if data:
                        self.downstream.write(data)
                    else:
                        break

        except:
            self._http_response(404, 'Not found', 'Not found')

    def _handle_not_found(self):
        self._http_response(404, 'Not found', 'Not found')

    def _handle_http(self, data):
        self.parser.execute(data, len(data))

        if self.parser.is_headers_complete():
            try:
                if not self.parser.get_method() in self.allowed_methods:
                    self._http_response(405, 'Method Not Allowed')

                else:
                    urlpath = self.parser.get_path()
                    urlpath = [
                        x.strip() for x in urlpath.split('/')
                        if (x and not str(x) in ('.', '..'))
                    ]

                    root = self.server.config.get_folder('wwwroot')
                    secret = self.server.config.getboolean('httpd', 'secret')
                    log = self.server.config.getboolean('httpd', 'log')

                    if secret:
                        wwwsecret = self.server.config.get('randoms',
                                                           'wwwsecret',
                                                           random=5)
                        if not (urlpath and urlpath[0] == wwwsecret):
                            self._handle_not_found()
                            if log:
                                self.server.info(
                                    '{}: GET {} | SECRET = {}'.format(
                                        '{}:{}'.format(*self.downstream.
                                                       transport.peer[:2]),
                                        urlpath, wwwsecret),
                                    error=True)
                            return

                        urlpath = urlpath[1:]

                    urlpath = path.sep.join([
                        self.server.config.get('randoms', x, new=False) or x
                        for x in urlpath
                    ])

                    if not urlpath:
                        urlpath = 'index.html'

                    filepath = path.join(root, urlpath)

                    if path.exists(filepath):
                        self._handle_file(filepath)
                        if log:
                            message = urlpath
                            if filepath in self.server.served_content:
                                message = message + ' <' + self.server.served_content[
                                    filepath] + '>'

                            self.server.info('{}: GET /{}'.format(
                                '{}:{}'.format(
                                    *self.downstream.transport.peer[:2]),
                                message))

                    else:
                        self._handle_not_found()
                        if log:
                            self.server.info('{}: GET {}'.format(
                                '{}:{}'.format(
                                    *self.downstream.transport.peer[:2]),
                                urlpath),
                                             error=True)
            finally:
                self.close()

    def downstream_recv(self, data):
        header = data.peek(self.probe_len)

        if __debug__:
            logger.debug('Recv: len=%d // header = %s', len(data), header)

        if self.server and self.is_http is None:
            self.is_http = header.startswith(self.well_known) and \
              not header.startswith(self.omit)

            if __debug__:
                logger.debug('Http: %s', self.is_http)

        if self.is_http:
            self._handle_http(data.read())
        else:
            if __debug__:
                logger.debug('Write to upstream: len=%d, handler=%s',
                             len(data), self.upstream.on_write_f)

            data.write_to(self.upstream)

            if self.downstream_buffer:
                if __debug__:
                    logger.debug(
                        'Flush buffer to downstream: len=%d, handler=%s',
                        len(self.downstream_buffer),
                        self.downstream.on_write_f)

                self.downstream_buffer.write_to(self.downstream)

            if __debug__:
                logger.debug('Release transport')

            raise ReleaseChainedTransport()

    def upstream_recv(self, data):
        if __debug__:
            logger.debug('Send intent: len=%d', len(data))

        if self.is_http is None:
            data.write_to(self.downstream_buffer)

            if __debug__:
                logger.debug('HTTP? Append to pending buffer: total len=%d',
                             len(self.downstream_buffer))

        elif not self.is_http:
            if __debug__:
                logger.debug('Non-HTTP: Direct pass (handler=%s)',
                             self.downstream.on_write_f)

            if self.downstream_buffer:
                self.downstream_buffer.write_to(self.downstream)

            data.write_to(self.downstream)
        else:
            if __debug__:
                logger.debug('HTTP: Omit data')

            pass
Exemplo n.º 6
0
class PupyWebSocketServer(PupyWebSocketTransport):
    __slots__ = (
        'user_agent',
        'path',
        'offset',
        'upgraded_buf',
        'missing_bytes',
        'upgraded',
        'decoded',
        'mask',
    )

    def __init__(self, *args, **kwargs):
        PupyWebSocketTransport.__init__(self, *args, **kwargs)
        self.upgraded = False
        self.user_agent = kwargs.pop('user-agent')
        self.path = kwargs.pop('path')
        self.mask = True
        self.offset = 0
        self.missing_bytes = 0
        self.decoded = Buffer()
        self.upgraded_buf = Buffer()

        if __debug__:
            logger.debug('WS Server, path=%s, user-agent=%s', self.path,
                         self.user_agent)

    def calculate_response_key(self, key):
        GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
        hsh = sha1(key.encode() + GUID.encode())
        response_key = base64.b64encode(hsh.digest()).strip()
        return response_key.decode('ASCII')

    def bad_request(self, msg):
        if __debug__:
            logger.debug(msg)

        self.downstream.write(error_response)
        self.close()

    def upstream_recv(self, data):
        """
            Encoding server -> client messages
            Messsages shouldn't be masked
        """
        try:
            if self.upgraded:
                add_ws_encapsulation(data, self.downstream)
            else:
                add_ws_encapsulation(data, self.upgraded_buf)

        except Exception as e:
            raise EOFError(str(e))

    def downstream_recv(self, data):
        """
            Decoding client -> server messages
            Message should be masked coming from client
        """

        if not self.upgraded:
            if __debug__:
                logger.debug('WS: Wait for upgrade requet')

            d = data.peek()
            # Handle HTTP GET requests, strip websocket keys, verify UA etc
            if not d.startswith('GET '):
                self.bad_request('Invalid HTTP method or data ({})'.format(
                    repr(d)))

            if '\r\n\r\n' not in d:
                if __debug__:
                    logger.debug('Short read, incomplete header')
                return

            _, path, _ = d.split(' ', 2)
            if path != self.path:
                self.bad_request('Path does not match ({} != {})!'.format(
                    path, self.path))
                return

            wskey = None

            key = re.search(
                r'\n[sS]ec-[wW]eb[sS]ocket-[kK]ey[\s]*:[\s]*(.*)\r\n', d)
            if key:
                wskey = key.group(1)
            else:
                if __debug__:
                    logger.debug('Unable to get WebSocketKey')

            if self.user_agent:
                ua = re.search(r'\n[uU]ser-[aA]gent:[\s]*(.*)\r\n', d)
                if ua:
                    ua = ua.group(1)
                else:
                    self.bad_request('No User-Agent provided')
                    return

                if ua != self.user_agent:
                    self.bad_request(
                        'Bad User-Agent provided. May be counter-intel ({} != {})'
                        .format(ua, self.user_agent))
                    return

            payload = 'HTTP/1.1 101 Switching Protocols\r\n'
            payload += 'Upgrade: websocket\r\n'
            payload += 'Connection: Upgrade\r\n'

            if wskey:
                payload += 'Sec-WebSocket-Accept: %s\r\n' % (
                    self.calculate_response_key(wskey))

            payload += '\r\n'

            data.drain(d.index('\r\n\r\n') + 4)

            if __debug__:
                logger.debug('Flush upgrade response')

            self.downstream.write(payload)

            if self.upgraded_buf:
                if __debug__:
                    logger.debug('Flush buffer %d', len(self.upgraded_buf))

                self.upgraded_buf.write_to(self.downstream)

            self.upgraded = True

        while data:
            msg_len, self.offset, self.missing_bytes, self.mask = remove_ws_encapsulation(
                data, self.upstream, self.decoded, self.offset,
                self.missing_bytes, self.mask)

            if __debug__:
                logger.debug('Parsed: %d, offset: %d, missing: %d, left: %d',
                             msg_len, self.offset, self.missing_bytes,
                             len(data))

            if not msg_len:
                break
Exemplo n.º 7
0
class Obfs3Transport(BaseTransport):
    """
    Obfs3Transport implements the obfs3 protocol.
    """

    __slots__ = ('state', 'dh', 'shared_secret', 'scanned_padding',
                 'last_padding_chunk', 'other_magic_value', 'send_crypto',
                 'recv_crypto', 'queued_data', 'send_keytype', 'recv_keytype',
                 'send_magic_const', 'recv_magic_const', 'we_are_initiator')

    def __init__(self, *args, **kwargs):
        """Initialize the obfs3 pluggable transport."""
        super(Obfs3Transport, self).__init__(*args, **kwargs)

        # Our state.
        self.state = ST_WAIT_FOR_KEY

        # Uniform-DH object
        self.dh = obfs3_dh.UniformDH()

        # DH shared secret
        self.shared_secret = None

        # Bytes of padding scanned so far.
        self.scanned_padding = 0
        # Last padding bytes scanned.
        self.last_padding_chunk = ''

        # Magic value that the other party is going to send
        # (initialized after deriving shared secret)
        self.other_magic_value = None
        # Crypto to encrypt outgoing data.
        self.send_crypto = None
        # Crypto to decrypt incoming data.
        self.recv_crypto = None

        # Buffer for the first data, Tor is trying to send but can't right now
        # because we have to handle the DH handshake first.
        self.queued_data = Buffer()

        # Attributes below are filled by classes that inherit Obfs3Transport.
        self.send_keytype = None
        self.recv_keytype = None
        self.send_magic_const = None
        self.recv_magic_const = None
        self.we_are_initiator = None

    def circuitConnected(self):
        """
        Do the obfs3 handshake:
        PUBKEY | WR(PADLEN)
        """
        padding_length = random.randint(0, MAX_PADDING / 2)

        public_key = self.dh.get_public()

        handshake_message = public_key + get_random(padding_length)

        if __debug__:
            logger.debug(
                "obfs3 handshake: %s queued %d bytes (padding_length: %d) (public key: %s).",
                "initiator" if self.we_are_initiator else "responder",
                len(handshake_message), padding_length, repr(public_key))

        self.downstream.write(handshake_message)

    def receivedUpstream(self, data):
        """
        Got data from upstream. We need to obfuscated and proxy them downstream.
        """

        if self.state != ST_OPEN:
            if __debug__:
                logger.debug(
                    "Got upstream data before doing handshake [STATE=%d]. Caching.",
                    self.state)

            data.write_to(self.queued_data)
            return

        if __debug__:
            logger.debug("obfs3 receivedUpstream: Transmitting %d bytes.",
                         len(data))

        if self.queued_data:
            if __debug__:
                logger.debug("Flush %d bytes of queued data (???) ",
                             len(self.queued_data))

            self.queued_data.write_to(self.downstream,
                                      modificator=self.send_crypto.encrypt)

        # Proxy encrypted message.
        data.write_to(self.downstream, modificator=self.send_crypto.encrypt)

    def receivedDownstream(self, data):
        """
        Got data from downstream. We need to de-obfuscate them and
        proxy them upstream.
        """

        if self.state == ST_WAIT_FOR_KEY:  # Looking for the other peer's pubkey
            if __debug__:
                logger.debug("Wait for key")

            self._read_handshake(data)

        if self.state == ST_WAIT_FOR_HANDSHAKE:  # Doing the exp mod
            if __debug__:
                logger.debug("Wait for handshake")

            return

        if self.state == ST_SEARCHING_MAGIC:  # Looking for the magic string
            if __debug__:
                logger.debug("Search magic")

            if self._scan_for_magic(data) and self.queued_data:
                if __debug__:
                    logger.debug('Flush queued data: %d',
                                 len(self.queued_data))

                self.queued_data.write_to(self.downstream,
                                          modificator=self.send_crypto.encrypt)

        if self.state == ST_OPEN:  # Handshake is done. Just decrypt and read application data.
            if __debug__:
                logger.debug(
                    "obfs3 receivedDownstream: Processing %d bytes of application data."
                    % (len(data)))

            if not data:
                return

            data.write_to(self.upstream, modificator=self.recv_crypto.encrypt)

    def _read_handshake(self, data):
        """
        Read handshake message, parse the other peer's public key and
        schedule the key exchange for execution outside of the event loop.
        """

        if len(data) < PUBKEY_LEN:
            if __debug__:
                logger.debug("Read handshake - short read")

            return

        # Get the public key from the handshake message, do the DH and
        # get the shared secret.
        other_pubkey = data.read(PUBKEY_LEN)

        if __debug__:
            logger.debug("Other pubkey: %s", repr(other_pubkey))

        self.state = ST_WAIT_FOR_HANDSHAKE

        try:
            kex = self.dh.get_secret(other_pubkey)
            self._read_handshake_post_dh(kex, data)
        except Exception, e:
            if __debug__:
                logger.debug('DH Exception: %s', e)

            self._uniform_dh_errback(e, other_pubkey)