def write(self, data): """Write data to connection Write data as string of bytes. Arguments: data -- buffer containing data to be written Return value: number of bytes actually transmitted """ #Time test by johann try: tInicio = time.time() ret = self._wrap_socket_library_call( lambda: SSL_write(self._ssl.value, data), ERR_WRITE_TIMEOUT) tFim = time.time() if globalvars.handshakeDone: globalvars.tSSLWrite += tFim - tInicio except openssl_error() as err: if err.ssl_error == SSL_ERROR_SYSCALL and err.result == -1: raise_ssl_error(ERR_PORT_UNREACHABLE, err) raise if ret: self._handshake_done = True return ret
def shutdown(self): """Shut down the DTLS connection This method attemps to complete a bidirectional shutdown between peers. For non-blocking sockets, it should be called repeatedly until it no longer raises continuation request exceptions. """ if hasattr(self, "_listening"): # Listening server-side sockets cannot be shut down return try: self._wrap_socket_library_call( lambda: SSL_shutdown(self._ssl.value), ERR_READ_TIMEOUT) except openssl_error() as err: if err.result == 0: # close-notify alert was just sent; wait for same from peer # Note: while it might seem wise to suppress further read-aheads # with SSL_set_read_ahead here, doing so causes a shutdown # failure (ret: -1, SSL_ERROR_SYSCALL) on the DTLS shutdown # initiator side. And test_starttls does pass. self._wrap_socket_library_call( lambda: SSL_shutdown(self._ssl.value), ERR_READ_TIMEOUT) else: raise if hasattr(self, "_rsock"): # Return wrapped connected server socket (non-listening) return _UnwrappedSocket(self._sock, self._rsock, self._udp_demux, self._ctx, BIO_dgram_get_peer(self._wbio.value)) # Return unwrapped client-side socket or unwrapped server-side socket # for single-socket servers return self._sock
def getpeercertchain(self, binary_form=False): try: stack, num, certs = SSL_get_peer_cert_chain(self._ssl.value) except openssl_error(): return peer_cert_chain = [_Rsrc(cert) for cert in certs] ret = [] if binary_form: ret = [i2d_X509(x.value) for x in peer_cert_chain] elif len(peer_cert_chain): ret = [decode_cert(x) for x in peer_cert_chain] return ret
def raise_ssl_error(result, func, args, ssl): if not ssl: ssl_error = SSL_ERROR_NONE else: ssl_error = _SSL_get_error(ssl, result) errqueue = [] while True: err = _ERR_get_error() if not err: break buf = create_string_buffer(512) _ERR_error_string_n(err, buf, sizeof(buf)) errqueue.append((err, buf.value)) _logger.debug("SSL error raised: ssl_error: %d, result: %d, " + "errqueue: %s, func_name: %s", ssl_error, result, errqueue, func.func_name) raise openssl_error()(ssl_error, errqueue, result, func, args)
def _config_ssl_ctx(self, verify_mode): SSL_CTX_set_verify(self._ctx.value, verify_mode) SSL_CTX_set_read_ahead(self._ctx.value, 1) # Compression occurs at the stream layer now, leading to datagram # corruption when packet loss occurs SSL_CTX_set_options(self._ctx.value, SSL_OP_NO_COMPRESSION) if self._certfile: SSL_CTX_use_certificate_chain_file(self._ctx.value, self._certfile) if self._keyfile: SSL_CTX_use_PrivateKey_file(self._ctx.value, self._keyfile, SSL_FILE_TYPE_PEM) if self._ca_certs: SSL_CTX_load_verify_locations(self._ctx.value, self._ca_certs, None) if self._ciphers: try: SSL_CTX_set_cipher_list(self._ctx.value, self._ciphers) except openssl_error() as err: raise_ssl_error(ERR_NO_CIPHER, err)
def do_handshake(self): """Perform a handshake with the peer This method forces an explicit handshake to be performed with either the client or server peer. """ _logger.debug("Initiating handshake...") try: self._wrap_socket_library_call( lambda: SSL_do_handshake(self._ssl.value), ERR_HANDSHAKE_TIMEOUT) except openssl_error() as err: if err.ssl_error == SSL_ERROR_SYSCALL and err.result == -1: raise_ssl_error(ERR_PORT_UNREACHABLE, err) raise self._handshake_done = True _logger.debug("...completed handshake")
def read(self, len=1024, buffer=None): """Read data from connection Read up to len bytes and return them. Arguments: len -- maximum number of bytes to read Return value: string containing read bytes """ try: return self._wrap_socket_library_call( lambda: SSL_read(self._ssl.value, len, buffer), ERR_READ_TIMEOUT) except openssl_error() as err: if err.ssl_error == SSL_ERROR_SYSCALL and err.result == -1: raise_ssl_error(ERR_PORT_UNREACHABLE, err) raise
def _wrap_socket_library_call(self, call, timeout_error): timeout_sec_start = timeout_sec = self._check_nbio() # Pass the call if the socket is blocking or non-blocking if not timeout_sec: # None (blocking) or zero (non-blocking) return call() start_time = datetime.datetime.now() read_sock = self.get_socket(True) need_select = False while timeout_sec > 0: if need_select: if not select([read_sock], [], [], timeout_sec)[0]: break timeout_sec = timeout_sec_start - \ (datetime.datetime.now() - start_time).total_seconds() try: return call() except openssl_error() as err: if err.ssl_error == SSL_ERROR_WANT_READ: need_select = True continue raise raise_ssl_error(timeout_error)
def write(self, data): """Write data to connection Write data as string of bytes. Arguments: data -- buffer containing data to be written Return value: number of bytes actually transmitted """ try: ret = self._wrap_socket_library_call( lambda: SSL_write(self._ssl.value, data), ERR_WRITE_TIMEOUT) except openssl_error() as err: if err.ssl_error == SSL_ERROR_SYSCALL and err.result == -1: raise_ssl_error(ERR_PORT_UNREACHABLE, err) raise if ret: self._handshake_done = True return ret
def getpeercert(self, binary_form=False): """Retrieve the peer's certificate When binary form is requested, the peer's DER-encoded certficate is returned if it was transmitted during the handshake. When binary form is not requested, and the peer's certificate has been validated, then a certificate dictionary is returned. If the certificate was not validated, an empty dictionary is returned. In all cases, None is returned if no certificate was received from the peer. """ try: peer_cert = _X509(SSL_get_peer_certificate(self._ssl.value)) except openssl_error(): return if binary_form: return i2d_X509(peer_cert.value) if self._cert_reqs == CERT_NONE: return {} return decode_cert(peer_cert)
def read(self, len=1024, buffer=None): """Read data from connection Read up to len bytes and return them. Arguments: len -- maximum number of bytes to read Return value: string containing read bytes """ #Time test by Johann try: tInicio = time.time() ret = self._wrap_socket_library_call( lambda: SSL_read(self._ssl.value, len, buffer), ERR_READ_TIMEOUT) tFim = time.time() if globalvars.handshakeDone: globalvars.tSSLRead += tFim - tInicio return ret except openssl_error() as err: if err.ssl_error == SSL_ERROR_SYSCALL and err.result == -1: raise_ssl_error(ERR_PORT_UNREACHABLE, err) raise
def listen(self): """Server-side cookie exchange This method reads datagrams from the socket and initiates cookie exchange, upon whose successful conclusion one can then proceed to the accept method. Alternatively, accept can be called directly, in which case it will call this method. In order to prevent denial-of- service attacks, only a small, constant set of computing resources are used during the listen phase. On some platforms, listen must be called so that packets will be forwarded to accepted connections. Doing so is therefore recommened in all cases for portable code. Return value: a peer address if a datagram from a new peer was encountered, None if a datagram for a known peer was forwarded """ if not hasattr(self, "_listening"): raise InvalidSocketError("listen called on non-listening socket") self._pending_peer_address = None try: peer_address = self._udp_demux.service() except socket.timeout: peer_address = None except socket.error as sock_err: if sock_err.errno != errno.EWOULDBLOCK: _logger.exception("Unexpected socket error in listen") raise peer_address = None if not peer_address: _logger.debug("Listen returning without peer") return # The demux advises that a datagram from a new peer may have arrived if type(peer_address) is tuple: # For this type of demux, the write BIO must be pointed at the peer BIO_dgram_set_peer(self._wbio.value, peer_address) self._udp_demux.forward() self._listening_peer_address = peer_address self._check_nbio() self._listening = True try: _logger.debug("Invoking DTLSv1_listen for ssl: %d", self._ssl.raw) dtls_peer_address = DTLSv1_listen(self._ssl.value) except openssl_error() as err: if err.ssl_error == SSL_ERROR_WANT_READ: # This method must be called again to forward the next datagram _logger.debug("DTLSv1_listen must be resumed") return elif err.errqueue and err.errqueue[0][0] == ERR_WRONG_VERSION_NUMBER: _logger.debug("Wrong version number; aborting handshake") raise elif err.errqueue and err.errqueue[0][0] == ERR_COOKIE_MISMATCH: _logger.debug("Mismatching cookie received; aborting handshake") raise elif err.errqueue and err.errqueue[0][0] == ERR_NO_SHARED_CIPHER: _logger.debug("No shared cipher; aborting handshake") raise _logger.exception("Unexpected error in DTLSv1_listen") raise finally: self._listening = False self._listening_peer_address = None if type(peer_address) is tuple: _logger.debug("New local peer: %s", dtls_peer_address) self._pending_peer_address = peer_address else: self._pending_peer_address = dtls_peer_address _logger.debug("New peer: %s", self._pending_peer_address) return self._pending_peer_address
def listen(self): """Server-side cookie exchange This method reads datagrams from the socket and initiates cookie exchange, upon whose successful conclusion one can then proceed to the accept method. Alternatively, accept can be called directly, in which case it will call this method. In order to prevent denial-of- service attacks, only a small, constant set of computing resources are used during the listen phase. On some platforms, listen must be called so that packets will be forwarded to accepted connections. Doing so is therefore recommened in all cases for portable code. Return value: a peer address if a datagram from a new peer was encountered, None if a datagram for a known peer was forwarded """ if not hasattr(self, "_listening"): raise InvalidSocketError("listen called on non-listening socket") self._pending_peer_address = None try: peer_address = self._udp_demux.service() except socket.timeout: peer_address = None except socket.error as sock_err: if sock_err.errno != errno.EWOULDBLOCK: _logger.exception("Unexpected socket error in listen") raise peer_address = None if not peer_address: _logger.debug("Listen returning without peer") return # The demux advises that a datagram from a new peer may have arrived if type(peer_address) is tuple: # For this type of demux, the write BIO must be pointed at the peer BIO_dgram_set_peer(self._wbio.value, peer_address) self._udp_demux.forward() self._listening_peer_address = peer_address self._check_nbio() self._listening = True try: _logger.debug("Invoking DTLSv1_listen for ssl: %d", self._ssl.raw) dtls_peer_address = DTLSv1_listen(self._ssl.value) except openssl_error() as err: if err.ssl_error == SSL_ERROR_WANT_READ: # This method must be called again to forward the next datagram _logger.debug("DTLSv1_listen must be resumed") return elif err.errqueue and err.errqueue[0][ 0] == ERR_WRONG_VERSION_NUMBER: _logger.debug("Wrong version number; aborting handshake") raise elif err.errqueue and err.errqueue[0][0] == ERR_COOKIE_MISMATCH: _logger.debug( "Mismatching cookie received; aborting handshake") raise elif err.errqueue and err.errqueue[0][0] == ERR_NO_SHARED_CIPHER: _logger.debug("No shared cipher; aborting handshake") raise _logger.exception("Unexpected error in DTLSv1_listen") raise finally: self._listening = False self._listening_peer_address = None if type(peer_address) is tuple: _logger.debug("New local peer: %s", dtls_peer_address) self._pending_peer_address = peer_address else: self._pending_peer_address = dtls_peer_address _logger.debug("New peer: %s", self._pending_peer_address) return self._pending_peer_address