def peek(self, length): """ Tries to peek into the underlying file object. Returns: Up to the next N bytes if peeking is successful. Raises: exceptions.TcpException if there was an error with the socket TlsException if there was an error with pyOpenSSL. NotImplementedError if the underlying file object is not a [pyOpenSSL] socket """ if isinstance(self.o, socket_fileobject): try: return self.o._sock.recv(length, socket.MSG_PEEK) except socket.error as e: raise exceptions.TcpException(repr(e)) elif isinstance(self.o, SSL.Connection): try: return self.o.recv(length, socket.MSG_PEEK) except SSL.Error as e: six.reraise(exceptions.TlsException, exceptions.TlsException(str(e)), sys.exc_info()[2]) else: raise NotImplementedError("Can only peek into (pyOpenSSL) sockets")
def peek(self, length): """ Tries to peek into the underlying file object. Returns: Up to the next N bytes if peeking is successful. Raises: exceptions.TcpException if there was an error with the socket TlsException if there was an error with pyOpenSSL. NotImplementedError if the underlying file object is not a [pyOpenSSL] socket """ if isinstance(self.o, socket_fileobject): try: return self.o._sock.recv(length, socket.MSG_PEEK) except socket.error as e: raise exceptions.TcpException(repr(e)) elif isinstance(self.o, SSL.Connection): try: if tuple(int(x) for x in OpenSSL.__version__.split(".")[:2]) > (0, 15): return self.o.recv(length, socket.MSG_PEEK) else: # TODO: remove once a new version is released # Polyfill for pyOpenSSL <= 0.15.1 # Taken from https://github.com/pyca/pyopenssl/commit/1d95dea7fea03c7c0df345a5ea30c12d8a0378d2 buf = SSL._ffi.new("char[]", length) result = SSL._lib.SSL_peek(self.o._ssl, buf, length) self.o._raise_ssl_error(self.o._ssl, result) return SSL._ffi.buffer(buf, result)[:] except SSL.Error as e: six.reraise(exceptions.TlsException, exceptions.TlsException(str(e)), sys.exc_info()[2]) else: raise NotImplementedError("Can only peek into (pyOpenSSL) sockets")
def read(self, length): """ If length is -1, we read until connection closes. """ result = b'' start = time.time() while length == -1 or length > 0: if length == -1 or length > self.BLOCKSIZE: rlen = self.BLOCKSIZE else: rlen = length try: data = self.o.read(rlen) except SSL.ZeroReturnError: # TLS connection was shut down cleanly break except (SSL.WantWriteError, SSL.WantReadError): # From the OpenSSL docs: # If the underlying BIO is non-blocking, SSL_read() will also return when the # underlying BIO could not satisfy the needs of SSL_read() to continue the # operation. In this case a call to SSL_get_error with the return value of # SSL_read() will yield SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE. if (time.time() - start) < self.o.gettimeout(): time.sleep(0.1) continue else: raise exceptions.TcpTimeout() except socket.timeout: raise exceptions.TcpTimeout() except socket.error as e: raise exceptions.TcpDisconnect(str(e)) except SSL.SysCallError as e: if e.args == (-1, 'Unexpected EOF'): break raise exceptions.TlsException(str(e)) except SSL.Error as e: raise exceptions.TlsException(str(e)) self.first_byte_timestamp = self.first_byte_timestamp or time.time( ) if not data: break result += data if length != -1: length -= len(data) self.add_log(result) return result
def create_ssl_context(self, cert=None, alpn_protos=None, **sslctx_kwargs): context = self._create_ssl_context(alpn_protos=alpn_protos, **sslctx_kwargs) # Client Certs if cert: try: context.use_privatekey_file(cert) context.use_certificate_file(cert) except SSL.Error as v: raise exceptions.TlsException( "SSL client certificate error: %s" % str(v)) return context
def convert_to_ssl(self, cert, key, **sslctx_kwargs): """ Convert connection to SSL. For a list of parameters, see BaseHandler._create_ssl_context(...) """ context = self.create_ssl_context(cert, key, **sslctx_kwargs) self.connection = SSL.Connection(context, self.connection) self.connection.set_accept_state() try: self.connection.do_handshake() except SSL.Error as v: raise exceptions.TlsException("SSL handshake error: %s" % repr(v)) self.ssl_established = True self.rfile.set_descriptor(self.connection) self.wfile.set_descriptor(self.connection)
def convert_to_ssl(self, sni=None, alpn_protos=None, **sslctx_kwargs): """ cert: Path to a file containing both client cert and private key. options: A bit field consisting of OpenSSL.SSL.OP_* values verify_options: A bit field consisting of OpenSSL.SSL.VERIFY_* values ca_path: Path to a directory of trusted CA certificates prepared using the c_rehash tool ca_pemfile: Path to a PEM formatted trusted CA certificate """ verification_mode = sslctx_kwargs.get('verify_options', None) if verification_mode == SSL.VERIFY_PEER and not sni: raise exceptions.TlsException( "Cannot validate certificate hostname without SNI") context = self.create_ssl_context(alpn_protos=alpn_protos, sni=sni, **sslctx_kwargs) self.connection = SSL.Connection(context, self.connection) if sni: self.sni = sni self.connection.set_tlsext_host_name(sni.encode("idna")) self.connection.set_connect_state() try: self.connection.do_handshake() except SSL.Error as v: if self.ssl_verification_error: raise self.ssl_verification_error else: raise exceptions.TlsException("SSL handshake error: %s" % repr(v)) else: # Fix for pre v1.0 OpenSSL, which doesn't throw an exception on # certificate validation failure if verification_mode == SSL.VERIFY_PEER and self.ssl_verification_error: raise self.ssl_verification_error self.cert = certutils.SSLCert(self.connection.get_peer_certificate()) # Keep all server certificates in a list for i in self.connection.get_peer_cert_chain(): self.server_certs.append(certutils.SSLCert(i)) # Validate TLS Hostname try: crt = dict(subjectAltName=[("DNS", x.decode("ascii", "strict")) for x in self.cert.altnames]) if self.cert.cn: crt["subject"] = [[[ "commonName", self.cert.cn.decode("ascii", "strict") ]]] if sni: hostname = sni else: hostname = "no-hostname" ssl_match_hostname.match_hostname(crt, hostname) except (ValueError, ssl_match_hostname.CertificateError) as e: self.ssl_verification_error = exceptions.InvalidCertificateException( "Certificate Verification Error for {}: {}".format( sni or repr(self.address), str(e))) if verification_mode == SSL.VERIFY_PEER: raise self.ssl_verification_error self.ssl_established = True self.rfile.set_descriptor(self.connection) self.wfile.set_descriptor(self.connection)
def _create_ssl_context( self, method=SSL_DEFAULT_METHOD, options=SSL_DEFAULT_OPTIONS, verify_options=SSL.VERIFY_NONE, ca_path=None, ca_pemfile=None, cipher_list=None, alpn_protos=None, alpn_select=None, alpn_select_callback=None, sni=None, ): """ Creates an SSL Context. :param method: One of SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, TLSv1_METHOD, TLSv1_1_METHOD, or TLSv1_2_METHOD :param options: A bit field consisting of OpenSSL.SSL.OP_* values :param verify_options: A bit field consisting of OpenSSL.SSL.VERIFY_* values :param ca_path: Path to a directory of trusted CA certificates prepared using the c_rehash tool :param ca_pemfile: Path to a PEM formatted trusted CA certificate :param cipher_list: A textual OpenSSL cipher list, see https://www.openssl.org/docs/apps/ciphers.html :rtype : SSL.Context """ context = SSL.Context(method) # Options (NO_SSLv2/3) if options is not None: context.set_options(options) # Verify Options (NONE/PEER and trusted CAs) if verify_options is not None: def verify_cert(conn, x509, errno, err_depth, is_cert_verified): if not is_cert_verified: self.ssl_verification_error = exceptions.InvalidCertificateException( "Certificate Verification Error for {}: {} (errno: {}, depth: {})" .format( sni, strutils.native( SSL._ffi.string( SSL._lib.X509_verify_cert_error_string( errno)), "utf8"), errno, err_depth)) return is_cert_verified context.set_verify(verify_options, verify_cert) if ca_path is None and ca_pemfile is None: ca_pemfile = certifi.where() context.load_verify_locations(ca_pemfile, ca_path) # Workaround for # https://github.com/pyca/pyopenssl/issues/190 # https://github.com/mitmproxy/mitmproxy/issues/472 # Options already set before are not cleared. context.set_mode(SSL._lib.SSL_MODE_AUTO_RETRY) # Cipher List if cipher_list: try: context.set_cipher_list(cipher_list) # TODO: maybe change this to with newer pyOpenSSL APIs context.set_tmp_ecdh( OpenSSL.crypto.get_elliptic_curve('prime256v1')) except SSL.Error as v: raise exceptions.TlsException( "SSL cipher specification error: %s" % str(v)) # SSLKEYLOGFILE if log_ssl_key: context.set_info_callback(log_ssl_key) if HAS_ALPN: if alpn_protos is not None: # advertise application layer protocols context.set_alpn_protos(alpn_protos) elif alpn_select is not None and alpn_select_callback is None: # select application layer protocol def alpn_select_callback(conn_, options): if alpn_select in options: return bytes(alpn_select) else: # pragma no cover return options[0] context.set_alpn_select_callback(alpn_select_callback) elif alpn_select_callback is not None and alpn_select is None: context.set_alpn_select_callback(alpn_select_callback) elif alpn_select_callback is not None and alpn_select is not None: raise exceptions.TlsException( "ALPN error: only define alpn_select (string) OR alpn_select_callback (method)." ) return context