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. # 300 is OpenSSL default timeout timeout = self.o.gettimeout() or 300 if (time.time() - start) < timeout: time.sleep(0.1) continue else: raise exceptions.TcpTimeout() except socket.timeout: raise exceptions.TcpTimeout() except OSError 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 convert_to_tls(self, sni=None, alpn_protos=None, **sslctx_kwargs): context = tls.create_client_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)) self.cert = certs.Cert(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(certs.Cert(i)) self.tls_established = True self.rfile.set_descriptor(self.connection) self.wfile.set_descriptor(self.connection)
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, cert, key, **sslctx_kwargs): """ Convert connection to SSL. For a list of parameters, see tls.create_server_context(...) """ context = tls.create_server_context(cert=cert, key=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 cert = self.connection.get_peer_certificate() if cert: self.clientcert = certs.SSLCert(cert) self.rfile.set_descriptor(self.connection) self.wfile.set_descriptor(self.connection)
def convert_to_tls(self, sni=None, alpn_protos=None, **sslctx_kwargs): context = tls.create_client_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: idle_timeout = self.connection_idle_seconds if idle_timeout != -1: timeout = idle_timeout else: timeout = 60 if self.channel is not None: self.channel.ask("ssl_handshake_started", self) TimeoutHelper.wrap_with_timeout(self.connection.do_handshake, timeout) 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)) finally: if self.channel is not None: self.channel.ask("ssl_handshake_finished", self) self.cert = certs.Cert(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(certs.Cert(i)) self.tls_established = True self.rfile.set_descriptor(self.connection) self.wfile.set_descriptor(self.connection)
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: raise exceptions.TlsException(str(e)) else: raise NotImplementedError("Can only peek into (pyOpenSSL) sockets")
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)) self.cert = certs.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(certs.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: # SNI hostnames allow support of IDN by using ASCII-Compatible Encoding # Conversion algorithm is in RFC 3490 which is implemented by idna codec # https://docs.python.org/3/library/codecs.html#text-encodings # https://tools.ietf.org/html/rfc6066#section-3 # https://tools.ietf.org/html/rfc4985#section-3 hostname = sni.encode("idna").decode("ascii") else: hostname = "no-hostname" match_hostname(crt, hostname) except (ValueError, 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 """ try: context = SSL.Context(method) except ValueError as e: method_name = ssl_method_names.get(method, "unknown") raise exceptions.TlsException( "SSL method \"%s\" is most likely not supported " "or disabled (for security reasons) in your libssl. " "Please refer to https://github.com/mitmproxy/mitmproxy/issues/1101 " "for more details." % method_name) # 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.always_str( 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() try: context.load_verify_locations(ca_pemfile, ca_path) except SSL.Error: raise exceptions.TlsException( "Cannot load trusted certificates ({}, {}).".format( 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.encode()) 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 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: if not callable(alpn_select_callback): raise exceptions.TlsException( "ALPN error: alpn_select_callback must be a function.") 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 (function)." ) return context
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 = certs.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(certs.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" match_hostname(crt, hostname) except (ValueError, 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_client_context( cert: str = None, sni: str = None, address: str = None, verify: int = SSL.VERIFY_NONE, **sslctx_kwargs ) -> SSL.Context: """ Args: cert: Path to a file containing both client cert and private key. sni: Server Name Indication. Required for VERIFY_PEER address: server address, used for expressive error messages only verify: A bit field consisting of OpenSSL.SSL.VERIFY_* values """ if sni is None and verify != SSL.VERIFY_NONE: raise exceptions.TlsException("Cannot validate certificate hostname without SNI") def verify_callback( conn: SSL.Connection, x509: SSL.X509, errno: int, depth: int, is_cert_verified: bool ) -> bool: if is_cert_verified and depth == 0 and not sni: conn.cert_error = exceptions.InvalidCertificateException( f"Certificate verification error for {address}: Cannot validate hostname, SNI missing." ) is_cert_verified = False elif is_cert_verified: pass else: conn.cert_error = exceptions.InvalidCertificateException( "Certificate verification error for {}: {} (errno: {}, depth: {})".format( sni, SSL._ffi.string(SSL._lib.X509_verify_cert_error_string(errno)).decode(), errno, depth ) ) # SSL_VERIFY_NONE: The handshake will be continued regardless of the verification result. return is_cert_verified context = _create_ssl_context( verify=verify, verify_callback=verify_callback, **sslctx_kwargs, ) if sni: # Manually enable hostname verification on the context object. # https://wiki.openssl.org/index.php/Hostname_validation param = SSL._lib.SSL_CTX_get0_param(context._context) # Matching on the CN is disabled in both Chrome and Firefox, so we disable it, too. # https://www.chromestatus.com/feature/4981025180483584 SSL._lib.X509_VERIFY_PARAM_set_hostflags( param, SSL._lib.X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS | SSL._lib.X509_CHECK_FLAG_NEVER_CHECK_SUBJECT ) SSL._openssl_assert( SSL._lib.X509_VERIFY_PARAM_set1_host(param, sni.encode("idna"), 0) == 1 ) # Client Certs if cert: try: context.use_privatekey_file(cert) context.use_certificate_chain_file(cert) except SSL.Error as v: raise exceptions.TlsException("SSL client certificate error: %s" % str(v)) return context
def _create_ssl_context( method: int = DEFAULT_METHOD, options: int = DEFAULT_OPTIONS, ca_path: str = None, ca_pemfile: str = None, cipher_list: str = None, alpn_protos: typing.Iterable[bytes] = None, alpn_select=None, alpn_select_callback: typing.Callable[[typing.Any, typing.Any], bytes] = None, verify: int = SSL.VERIFY_PEER, verify_callback: typing.Optional[ typing.Callable[[SSL.Connection, SSL.X509, int, int, bool], bool] ] = None, ) -> SSL.Context: """ 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: 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 """ try: context = SSL.Context(method) except ValueError: method_name = METHOD_NAMES.get(method, "unknown") raise exceptions.TlsException( "SSL method \"%s\" is most likely not supported " "or disabled (for security reasons) in your libssl. " "Please refer to https://github.com/mitmproxy/mitmproxy/issues/1101 " "for more details." % method_name ) # Options (NO_SSLv2/3) if options is not None: context.set_options(options) # Verify Options (NONE/PEER and trusted CAs) if verify is not None: context.set_verify(verify, verify_callback) if ca_path is None and ca_pemfile is None: ca_pemfile = certifi.where() try: context.load_verify_locations(ca_pemfile, ca_path) except SSL.Error: raise exceptions.TlsException( "Cannot load trusted certificates ({}, {}).".format( 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.encode()) except SSL.Error as v: raise exceptions.TlsException("SSL cipher specification error: %s" % str(v)) # SSLKEYLOGFILE if log_master_secret: context.set_keylog_callback(log_master_secret) 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: if not callable(alpn_select_callback): raise exceptions.TlsException("ALPN error: alpn_select_callback must be a function.") 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 (function).") return context
def create_client_context(cert: str = None, sni: str = None, address: str = None, verify: int = SSL.VERIFY_NONE, **sslctx_kwargs) -> SSL.Context: """ Args: cert: Path to a file containing both client cert and private key. sni: Server Name Indication. Required for VERIFY_PEER address: server address, used for expressive error messages only verify: A bit field consisting of OpenSSL.SSL.VERIFY_* values """ if sni is None and verify != SSL.VERIFY_NONE: raise exceptions.TlsException( "Cannot validate certificate hostname without SNI") def verify_callback(conn: SSL.Connection, x509: SSL.X509, errno: int, depth: int, is_cert_verified: bool) -> bool: if is_cert_verified and depth == 0: # Verify hostname of leaf certificate. cert = certs.Cert(x509) try: crt = dict(subjectAltName=[ ("DNS", x.decode("ascii", "strict")) for x in cert.altnames ]) # type: typing.Dict[str, typing.Any] if cert.cn: crt["subject"] = [[[ "commonName", cert.cn.decode("ascii", "strict") ]]] if sni: # SNI hostnames allow support of IDN by using ASCII-Compatible Encoding # Conversion algorithm is in RFC 3490 which is implemented by idna codec # https://docs.python.org/3/library/codecs.html#text-encodings # https://tools.ietf.org/html/rfc6066#section-3 # https://tools.ietf.org/html/rfc4985#section-3 hostname = sni.encode("idna").decode("ascii") else: hostname = "no-hostname" match_hostname(crt, hostname) except (ValueError, CertificateError) as e: conn.cert_error = exceptions.InvalidCertificateException( "Certificate verification error for {}: {}".format( sni or repr(address), str(e))) is_cert_verified = False elif is_cert_verified: pass else: conn.cert_error = exceptions.InvalidCertificateException( "Certificate verification error for {}: {} (errno: {}, depth: {})" .format( sni, SSL._ffi.string( SSL._lib.X509_verify_cert_error_string( errno)).decode(), errno, depth)) # SSL_VERIFY_NONE: The handshake will be continued regardless of the verification result. return is_cert_verified context = _create_ssl_context( verify=verify, verify_callback=verify_callback, **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 _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