Пример #1
0
def _test_early_data_support(server_info: ServerConnectivityInfo) -> bool:
    session = None
    is_early_data_supported = False
    ssl_connection = server_info.get_preconfigured_tls_connection(
        override_tls_version=TlsVersionEnum.TLS_1_3)
    try:
        # Perform an SSL handshake and keep the session
        ssl_connection.connect()
        # Send and receive data for the TLS session to be created
        ssl_connection.ssl_client.write(
            HttpRequestGenerator.get_request(
                host=server_info.server_location.hostname))
        ssl_connection.ssl_client.read(2048)
        session = ssl_connection.ssl_client.get_session()
    except ServerRejectedTlsHandshake:
        # TLS 1.3 not supported
        is_early_data_supported = False
    except TlsHandshakeTimedOut:
        # Sometimes triggered by servers that don't support TLS 1.3 at all, such as Amazon Cloudfront
        is_early_data_supported = False
    except socket.timeout:
        # Some servers just don't answer the read() call
        is_early_data_supported = False
    finally:
        ssl_connection.close()

    # Then try to re-use the session and send early data
    if session is not None:
        ssl_connection2 = server_info.get_preconfigured_tls_connection(
            override_tls_version=TlsVersionEnum.TLS_1_3)
        if not isinstance(ssl_connection2.ssl_client, SslClient):
            raise RuntimeError("Should never happen")

        ssl_connection2.ssl_client.set_session(session)

        try:
            # Open a socket to the server but don't do the actual TLS handshake
            ssl_connection2._do_pre_handshake()

            # Send one byte of early data
            ssl_connection2.ssl_client.write_early_data(b"E")
            ssl_connection2.ssl_client.do_handshake()
            if ssl_connection2.ssl_client.get_early_data_status(
            ) == OpenSslEarlyDataStatusEnum.ACCEPTED:
                is_early_data_supported = True
            else:
                is_early_data_supported = False

        except OpenSSLError as e:
            if "function you should not call" in e.args[0]:
                # This is what OpenSSL returns when the server did not enable early data
                is_early_data_supported = False
            else:
                raise

        finally:
            ssl_connection2.close()

    return is_early_data_supported
Пример #2
0
def get_certificate_chain(
    server_info: ServerConnectivityInfo,
    custom_ca_file: Optional[Path],
    tls_version: Optional[TlsVersionEnum],
    openssl_cipher_string: Optional[str],
) -> Tuple[List[str], Optional[nassl.ocsp_response.OcspResponse],
           Optional[Path]]:
    ssl_connection = server_info.get_preconfigured_tls_connection(
        override_tls_version=tls_version)
    if openssl_cipher_string:
        ssl_connection.ssl_client.set_cipher_list(openssl_cipher_string)

    # Enable OCSP stapling
    ssl_connection.ssl_client.set_tlsext_status_ocsp()

    try:
        ssl_connection.connect()
        ocsp_response = ssl_connection.ssl_client.get_tlsext_status_ocsp_resp()
        received_chain_as_pem = ssl_connection.ssl_client.get_received_chain()

    except ClientCertificateRequested:
        ocsp_response = ssl_connection.ssl_client.get_tlsext_status_ocsp_resp()
        received_chain_as_pem = ssl_connection.ssl_client.get_received_chain()

    finally:
        ssl_connection.close()

    return received_chain_as_pem, ocsp_response, custom_ca_file
Пример #3
0
def _test_heartbleed(server_info: ServerConnectivityInfo) -> bool:
    if server_info.tls_probing_result.highest_tls_version_supported.value >= TlsVersionEnum.TLS_1_3.value:
        # The server uses a recent version of OpenSSL and it cannot be vulnerable to Heartbleed
        return False

    # Disable SNI for this check because some legacy servers don't support sending the heartbleed payload and SNI
    # See https://github.com/nabla-c0d3/sslyze/issues/202
    ssl_connection = server_info.get_preconfigured_tls_connection(
        should_enable_server_name_indication=False)

    # Replace nassl.sslClient.do_handshake() with a heartbleed checking SSL handshake so that all the SSLyze options
    # (startTLS, proxy, etc.) still work
    ssl_connection.ssl_client.do_handshake = types.MethodType(  # type: ignore
        _do_handshake_with_heartbleed, ssl_connection.ssl_client)

    is_vulnerable_to_heartbleed = False
    try:
        # Start the SSL handshake
        ssl_connection.connect()
    except _VulnerableToHeartbleed:
        # The test was completed and the server is vulnerable
        is_vulnerable_to_heartbleed = True
    except _NotVulnerableToHeartbleed:
        # The test was completed and the server is NOT vulnerable
        pass
    finally:
        ssl_connection.close()

    return is_vulnerable_to_heartbleed
def _test_for_ccs_injection(server_info: ServerConnectivityInfo) -> bool:
    if server_info.tls_probing_result.highest_tls_version_supported.value >= TlsVersionEnum.TLS_1_3.value:
        # The server uses a recent version of OpenSSL and it cannot be vulnerable to CCS Injection
        return False

    ssl_connection = server_info.get_preconfigured_tls_connection()
    # Replace nassl.sslClient.do_handshake() with a CCS checking SSL handshake so that all the SSLyze options
    # (startTLS, proxy, etc.) still work
    ssl_connection.ssl_client.do_handshake = types.MethodType(  # type: ignore
        _do_handshake_with_ccs_injection, ssl_connection.ssl_client)

    is_vulnerable = False
    try:
        # Start the SSL handshake
        ssl_connection.connect()
    except _VulnerableToCcsInjection:
        # The test was completed and the server is vulnerable
        is_vulnerable = True
    except _NotVulnerableToCcsInjection:
        # The test was completed and the server is NOT vulnerable
        pass
    finally:
        ssl_connection.close()

    return is_vulnerable
Пример #5
0
def _get_rsa_parameters(
    server_info: ServerConnectivityInfo, tls_version: TlsVersionEnum, openssl_cipher_string: str
) -> Optional[RSAPublicNumbers]:
    ssl_connection = server_info.get_preconfigured_tls_connection(
        override_tls_version=tls_version, should_use_legacy_openssl=True,
    )
    ssl_connection.ssl_client.set_cipher_list(openssl_cipher_string)
    parsed_cert = None
    try:
        # Perform the SSL handshake
        ssl_connection.connect()
        cert_as_pem = ssl_connection.ssl_client.get_received_chain()[0]
        parsed_cert = load_pem_x509_certificate(cert_as_pem.encode("ascii"), backend=default_backend())
    except ServerRejectedTlsHandshake:
        # Server does not support RSA cipher suites?
        pass
    except ClientCertificateRequested:
        # AD: The server asked for a client cert. We could still retrieve the server certificate, but it is unclear
        # to me if the ROBOT check is supposed to work even if we do not provide a client cert. My guess is that
        # it should not work since it requires completing a full handshake, which we can't without a client cert.
        # Hence, propagate the error to make the check fail.
        raise
    finally:
        ssl_connection.close()

    if parsed_cert:
        public_key = parsed_cert.public_key()
        if isinstance(public_key, RSAPublicKey):
            return public_key.public_numbers()
        else:
            return None
    else:
        return None
Пример #6
0
def resume_tls_session(
    server_info: ServerConnectivityInfo,
    tls_version_to_use: OpenSslVersionEnum,
    tls_session: Optional[nassl._nassl.SSL_SESSION] = None,
    should_enable_tls_ticket: bool = False,
) -> nassl._nassl.SSL_SESSION:
    """Connect to the server and returns the session object that was assigned for that connection.
    If ssl_session is given, tries to resume that session.
    """
    ssl_connection = server_info.get_preconfigured_tls_connection(
        override_tls_version=tls_version_to_use)
    if not should_enable_tls_ticket:
        # Need to disable TLS tickets to test session IDs, according to rfc5077:
        # If a ticket is presented by the client, the server MUST NOT attempt
        # to use the Session ID in the ClientHello for stateful session resumption
        ssl_connection.ssl_client.disable_stateless_session_resumption(
        )  # Turning off TLS tickets.

    if tls_session:
        ssl_connection.ssl_client.set_session(tls_session)

    try:
        # Perform the SSL handshake
        ssl_connection.connect()
        new_session = ssl_connection.ssl_client.get_session(
        )  # Get session data
    finally:
        ssl_connection.close()

    return new_session
Пример #7
0
def _test_secure_renegotiation(server_info: ServerConnectivityInfo) -> Tuple[_ScanJobResultEnum, bool]:
    """Check whether the server supports secure renegotiation.
    """
    # Try with TLS 1.2 even if the server supports TLS 1.3 or higher as there is no reneg with TLS 1.3
    if server_info.tls_probing_result.highest_tls_version_supported.value >= TlsVersionEnum.TLS_1_3.value:
        tls_version_to_use = TlsVersionEnum.TLS_1_2
        downgraded_from_tls_1_3 = True
    else:
        tls_version_to_use = server_info.tls_probing_result.highest_tls_version_supported
        downgraded_from_tls_1_3 = False

    ssl_connection = server_info.get_preconfigured_tls_connection(
        override_tls_version=tls_version_to_use,
        should_use_legacy_openssl=True,  # Only the legacy SSL client has methods to check for secure reneg
    )
    if not isinstance(ssl_connection.ssl_client, LegacySslClient):
        raise RuntimeError("Should never happen")

    try:
        # Perform the TLS handshake
        ssl_connection.connect()
        supports_secure_renegotiation = ssl_connection.ssl_client.get_secure_renegotiation_support()

    # Should only happen when the server only supports TLS 1.3
    except ServerRejectedTlsHandshake:
        if downgraded_from_tls_1_3:
            supports_secure_renegotiation = True  # Technically TLS 1.3 has no renegotiation therefore it is secure
        else:
            raise

    finally:
        ssl_connection.close()

    return _ScanJobResultEnum.SUPPORTS_SECURE_RENEG, supports_secure_renegotiation
def _test_client_renegotiation(
        server_info: ServerConnectivityInfo,
        tls_version_to_use: TlsVersionEnum) -> Tuple[_ScanJobResultEnum, bool]:
    """Check whether the server honors session renegotiation requests.
    """
    ssl_connection = server_info.get_preconfigured_tls_connection(
        override_tls_version=tls_version_to_use,
        should_use_legacy_openssl=True)
    if not isinstance(ssl_connection.ssl_client, LegacySslClient):
        raise RuntimeError("Should never happen")

    try:
        # Perform the SSL handshake
        ssl_connection.connect()

        try:
            # Let's try to renegotiate
            ssl_connection.ssl_client.do_renegotiate()
            accepts_client_renegotiation = True

        # Errors caused by a server rejecting the renegotiation
        except socket.timeout:
            # This is how Netty rejects a renegotiation - https://github.com/nabla-c0d3/sslyze/issues/114
            accepts_client_renegotiation = False
        except ConnectionError:
            accepts_client_renegotiation = False
        except OSError as e:
            # OSError is the parent of all (non-TLS) socket/connection errors so it should be last
            if "Nassl SSL handshake failed" in e.args[0]:
                # Special error returned by nassl
                accepts_client_renegotiation = False
            else:
                raise
        except OpenSSLError as e:
            if "handshake failure" in e.args[0]:
                accepts_client_renegotiation = False
            elif "no renegotiation" in e.args[0]:
                accepts_client_renegotiation = False
            elif "tlsv1 unrecognized name" in e.args[0]:
                # Yahoo's very own way of rejecting a renegotiation
                accepts_client_renegotiation = False
            elif "tlsv1 alert internal error" in e.args[0]:
                # Jetty server: https://github.com/nabla-c0d3/sslyze/issues/290
                accepts_client_renegotiation = False
            elif "decryption failed or bad record mac" in e.args[0]:
                # Some servers such as reddit.com
                accepts_client_renegotiation = False
            elif "sslv3 alert unexpected message" in e.args[0]:
                # traefik https://github.com/nabla-c0d3/sslyze/issues/422
                accepts_client_renegotiation = False

            else:
                raise

    finally:
        ssl_connection.close()

    return _ScanJobResultEnum.ACCEPTS_CLIENT_RENEG, accepts_client_renegotiation
Пример #9
0
def _send_robot_payload(
    server_info: ServerConnectivityInfo,
    tls_version_to_use: TlsVersionEnum,
    rsa_cipher_string: str,
    robot_payload_enum: RobotPmsPaddingPayloadEnum,
    robot_should_finish_handshake: bool,
    rsa_modulus: int,
    rsa_exponent: int,
) -> str:
    # Do a handshake which each record and keep track of what the server returned
    ssl_connection = server_info.get_preconfigured_tls_connection(override_tls_version=tls_version_to_use)

    # Replace nassl.sslClient.do_handshake() with a ROBOT checking SSL handshake so that all the SSLyze
    # options (startTLS, proxy, etc.) still work
    ssl_connection.ssl_client.do_handshake = types.MethodType(  # type: ignore
        do_handshake_with_robot, ssl_connection.ssl_client
    )
    ssl_connection.ssl_client.set_cipher_list(rsa_cipher_string)

    # Compute the  payload
    tls_parser_tls_version: tls_parser.tls_version.TlsVersionEnum
    if tls_version_to_use == TlsVersionEnum.SSL_3_0:
        tls_parser_tls_version = tls_parser.tls_version.TlsVersionEnum.SSLV3
    elif tls_version_to_use == TlsVersionEnum.TLS_1_0:
        tls_parser_tls_version = tls_parser.tls_version.TlsVersionEnum.TLSV1
    elif tls_version_to_use == TlsVersionEnum.TLS_1_1:
        tls_parser_tls_version = tls_parser.tls_version.TlsVersionEnum.TLSV1_1
    elif tls_version_to_use == TlsVersionEnum.TLS_1_2:
        tls_parser_tls_version = tls_parser.tls_version.TlsVersionEnum.TLSV1_2
    else:
        raise ValueError("Should never happen")

    cke_payload = _RobotTlsRecordPayloads.get_client_key_exchange_record(
        robot_payload_enum, tls_parser_tls_version, rsa_modulus, rsa_exponent
    )

    # H4ck: we need to pass some arguments to the handshake but there is no simple way to do it; we use an attribute
    ssl_connection.ssl_client._robot_cke_record = cke_payload  # type: ignore
    ssl_connection.ssl_client._robot_should_finish_handshake = robot_should_finish_handshake  # type: ignore

    server_response = ""
    try:
        # Start the SSL handshake
        ssl_connection.connect()
    except ServerResponseToRobot as e:
        # Should always be thrown
        server_response = e.server_response
    except socket.timeout:
        # https://github.com/nabla-c0d3/sslyze/issues/361
        server_response = "Connection timed out"
    finally:
        ssl_connection.close()

    return server_response
Пример #10
0
def connect_with_cipher_suite(
    server_connectivity_info: ServerConnectivityInfo,
    tls_version: TlsVersionEnum, cipher_suite: CipherSuite
) -> Union[CipherSuiteAcceptedByServer, CipherSuiteRejectedByServer]:
    """Initiates a SSL handshake with the server using the SSL version and the cipher suite specified.
    """
    requires_legacy_openssl = True
    if tls_version == TlsVersionEnum.TLS_1_2:
        # For TLS 1.2, we need to pick the right version of OpenSSL depending on which cipher suite
        requires_legacy_openssl = WorkaroundForTls12ForCipherSuites.requires_legacy_openssl(
            cipher_suite.openssl_name)
    elif tls_version == TlsVersionEnum.TLS_1_3:
        requires_legacy_openssl = False

    ssl_connection = server_connectivity_info.get_preconfigured_tls_connection(
        override_tls_version=tls_version,
        should_use_legacy_openssl=requires_legacy_openssl)
    _set_cipher_suite_string(tls_version, cipher_suite.openssl_name,
                             ssl_connection.ssl_client)

    ephemeral_key = None
    try:
        # Perform the SSL handshake
        ssl_connection.connect()
        ephemeral_key = ssl_connection.ssl_client.get_ephemeral_key()

    except ServerTlsConfigurationNotSupported:
        # SSLyze rejected the handshake because the server's DH config was too insecure; this means the
        # cipher suite is actually supported
        pass

    except ClientCertificateRequested:
        # When the handshake failed due to ClientCertificateRequested
        ephemeral_key = ssl_connection.ssl_client.get_ephemeral_key()
        pass

    except ServerRejectedTlsHandshake as e:
        return CipherSuiteRejectedByServer(cipher_suite=cipher_suite,
                                           error_message=e.error_message)

    except TlsHandshakeTimedOut as e:
        # Sometimes triggered by servers that don't support (at all) a specific version of TLS
        # Amazon Cloudfront does that with TLS 1.3
        # There's no easy way to differentiate this error from a network glitch/timeout
        return CipherSuiteRejectedByServer(cipher_suite=cipher_suite,
                                           error_message=e.error_message)

    finally:
        ssl_connection.close()

    return CipherSuiteAcceptedByServer(cipher_suite=cipher_suite,
                                       ephemeral_key=ephemeral_key)
def _test_client_renegotiation(
        server_info: ServerConnectivityInfo,
        tls_version_to_use: OpenSslVersionEnum
) -> Tuple[_ScanJobResultEnum, bool]:
    """Check whether the server honors session renegotiation requests.
    """
    ssl_connection = server_info.get_preconfigured_tls_connection(
        override_tls_version=tls_version_to_use,
        should_use_legacy_openssl=True)
    if not isinstance(ssl_connection.ssl_client, LegacySslClient):
        raise RuntimeError("Should never happen")

    try:
        # Perform the SSL handshake
        ssl_connection.connect()

        try:
            # Let's try to renegotiate
            ssl_connection.ssl_client.do_renegotiate()
            accepts_client_renegotiation = True

        # Errors caused by a server rejecting the renegotiation
        except socket.timeout:
            # This is how Netty rejects a renegotiation - https://github.com/nabla-c0d3/sslyze/issues/114
            accepts_client_renegotiation = False
        except socket.error as e:
            if "connection was forcibly closed" in str(e.args):
                accepts_client_renegotiation = False
            elif "reset by peer" in str(e.args):
                accepts_client_renegotiation = False
            elif "Nassl SSL handshake failed" in str(e.args):
                accepts_client_renegotiation = False
            else:
                raise
        except OpenSSLError as e:
            if "handshake failure" in str(e.args):
                accepts_client_renegotiation = False
            elif "no renegotiation" in str(e.args):
                accepts_client_renegotiation = False
            elif "tlsv1 unrecognized name" in str(e.args):
                # Yahoo's very own way of rejecting a renegotiation
                accepts_client_renegotiation = False
            elif "tlsv1 alert internal error" in str(e.args):
                # Jetty server: https://github.com/nabla-c0d3/sslyze/issues/290
                accepts_client_renegotiation = False
            else:
                raise

    finally:
        ssl_connection.close()

    return _ScanJobResultEnum.ACCEPTS_CLIENT_RENEG, accepts_client_renegotiation
Пример #12
0
def _get_selected_cipher_suite(server_connectivity: ServerConnectivityInfo,
                               tls_version: OpenSslVersionEnum,
                               openssl_cipher_string: str) -> str:
    ssl_connection = server_connectivity.get_preconfigured_tls_connection(
        override_tls_version=tls_version)
    ssl_connection.ssl_client.set_cipher_list(openssl_cipher_string)

    # Perform the SSL handshake
    try:
        ssl_connection.connect()
        return ssl_connection.ssl_client.get_current_cipher_name()
    except ClientCertificateRequested:
        # TODO(AD): Sometimes get_current_cipher_name() called in from_ongoing_ssl_connection() will return None
        return ssl_connection.ssl_client.get_current_cipher_name()
    finally:
        ssl_connection.close()
Пример #13
0
def _retrieve_and_analyze_http_response(
        server_info: ServerConnectivityInfo) -> HttpHeadersScanResult:
    # Send HTTP requests until we no longer received an HTTP redirection, but allow only 4 redirections max
    redirections_count = 0
    next_location_path: Optional[str] = "/"
    while next_location_path and redirections_count < 4:
        ssl_connection = server_info.get_preconfigured_tls_connection()
        try:
            # Perform the TLS handshake
            ssl_connection.connect()

            # Send an HTTP GET request to the server
            ssl_connection.ssl_client.write(
                HttpRequestGenerator.get_request(
                    host=server_info.network_configuration.
                    tls_server_name_indication,
                    path=next_location_path))
            http_response = HttpResponseParser.parse_from_ssl_connection(
                ssl_connection.ssl_client)
        finally:
            ssl_connection.close()

        if http_response.version == 9:
            # HTTP 0.9 => Probably not an HTTP response
            raise ValueError("Server did not return an HTTP response")

        # Handle redirection if there is one
        next_location_path = _detect_http_redirection(
            http_response=http_response,
            server_host_name=server_info.network_configuration.
            tls_server_name_indication,
            server_port=server_info.server_location.port,
        )
        redirections_count += 1

    # Parse and return each header
    return HttpHeadersScanResult(
        strict_transport_security_header=_parse_hsts_header_from_http_response(
            http_response),
        public_key_pins_header=_parse_hpkp_header_from_http_response(
            http_response),
        public_key_pins_report_only_header=
        _parse_hpkp_report_only_header_from_http_response(http_response),
        expect_ct_header=_parse_expect_ct_header_from_http_response(
            http_response),
    )
Пример #14
0
def _test_compression_support(server_info: ServerConnectivityInfo) -> bool:
    # Try with TLS 1.2 even if the server supports TLS 1.3 or higher as there is no compression with TLS 1.3
    if server_info.tls_probing_result.highest_tls_version_supported.value >= TlsVersionEnum.TLS_1_3.value:
        tls_version_to_use = TlsVersionEnum.TLS_1_2
        downgraded_from_tls_1_3 = True
    else:
        tls_version_to_use = server_info.tls_probing_result.highest_tls_version_supported
        downgraded_from_tls_1_3 = False

    ssl_connection = server_info.get_preconfigured_tls_connection(
        override_tls_version=tls_version_to_use,
        should_use_legacy_openssl=
        True,  # Only the legacy SSL client has methods to check for compression support
    )
    if not isinstance(ssl_connection.ssl_client, LegacySslClient):
        raise RuntimeError("Should never happen")

    # Make sure OpenSSL was built with support for compression to avoid false negatives
    if "zlib compression" not in ssl_connection.ssl_client.get_available_compression_methods(
    ):
        raise RuntimeError(
            "OpenSSL was not built with support for zlib / compression. Did you build nassl yourself ?"
        )

    compression_name: Optional[str]
    try:
        # Perform the TLS handshake
        ssl_connection.connect()
        compression_name = ssl_connection.ssl_client.get_current_compression_method(
        )

    except ClientCertificateRequested:
        compression_name = ssl_connection.ssl_client.get_current_compression_method(
        )

    # Should only happen when the server only supports TLS 1.3, which does not support compression
    except ServerRejectedTlsHandshake:
        if downgraded_from_tls_1_3:
            compression_name = None
        else:
            raise

    finally:
        ssl_connection.close()

    return True if compression_name else False
Пример #15
0
def _test_scsv(server_info: ServerConnectivityInfo) -> bool:
    # Try with TLS 1.2 even if the server supports TLS 1.3 or higher as there is no downgrade possible with TLS 1.3
    if server_info.tls_probing_result.highest_tls_version_supported.value >= TlsVersionEnum.TLS_1_3.value:
        ssl_version_to_use = TlsVersionEnum.TLS_1_2
    else:
        ssl_version_to_use = server_info.tls_probing_result.highest_tls_version_supported

    # Try to connect using a lower TLS version with the fallback cipher suite enabled
    ssl_version_downgrade = TlsVersionEnum(ssl_version_to_use.value - 1)
    ssl_connection = server_info.get_preconfigured_tls_connection(
        override_tls_version=ssl_version_downgrade,
        # Only the legacy client has enable_fallback_scsv()
        should_use_legacy_openssl=True,
    )
    if not isinstance(ssl_connection.ssl_client, LegacySslClient):
        raise RuntimeError("Should never happen")

    ssl_connection.ssl_client.enable_fallback_scsv()

    supports_fallback_scsv = False
    try:
        # Perform the SSL handshake
        ssl_connection.connect()

    except _nassl.OpenSSLError as e:
        # This is the right, specific alert the server should return
        if "tlsv1 alert inappropriate fallback" in str(e.args):
            supports_fallback_scsv = True
        else:
            raise

    except ServerRejectedTlsHandshake:
        # If the handshake is rejected, we assume downgrade attacks are prevented (this is how F5 balancers do it)
        # although it could also be because the server does not support this version of TLS
        # https://github.com/nabla-c0d3/sslyze/issues/119
        supports_fallback_scsv = True

    except TlsHandshakeTimedOut:
        # Sometimes triggered by servers that don't support (at all) a specific version of TLS
        # Amazon Cloudfront does that with TLS 1.3
        supports_fallback_scsv = True

    finally:
        ssl_connection.close()

    return supports_fallback_scsv
Пример #16
0
def retrieve_tls_session(
    server_info: ServerConnectivityInfo,
    session_to_resume: Optional[nassl._nassl.SSL_SESSION] = None,
    should_enable_tls_ticket: bool = False,
) -> nassl._nassl.SSL_SESSION:
    """Connect to the server and returns the session object that was assigned for that connection.

    If ssl_session is given, tries to resume that session.
    """
    # Try with TLS 1.2 even if the server supports TLS 1.3 or higher as there is no session resumption (with IDs or
    # tickets) with TLS 1.3
    if server_info.tls_probing_result.highest_tls_version_supported.value >= TlsVersionEnum.TLS_1_3.value:
        tls_version_to_use = TlsVersionEnum.TLS_1_2
        downgraded_from_tls_1_3 = True
    else:
        tls_version_to_use = server_info.tls_probing_result.highest_tls_version_supported
        downgraded_from_tls_1_3 = False

    ssl_connection = server_info.get_preconfigured_tls_connection(override_tls_version=tls_version_to_use)
    if not should_enable_tls_ticket:
        # Need to disable TLS tickets to test session IDs, according to rfc5077:
        # If a ticket is presented by the client, the server MUST NOT attempt
        # to use the Session ID in the ClientHello for stateful session resumption
        ssl_connection.ssl_client.disable_stateless_session_resumption()  # Turning off TLS tickets.

    if session_to_resume:
        ssl_connection.ssl_client.set_session(session_to_resume)

    try:
        # Perform the TLS handshake
        ssl_connection.connect()
        new_session = ssl_connection.ssl_client.get_session()  # Get session data

    except ServerRejectedTlsHandshake:
        if downgraded_from_tls_1_3:
            raise ServerOnlySupportsTls13()
        else:
            raise

    finally:
        ssl_connection.close()

    return new_session
def _test_secure_renegotiation(
        server_info: ServerConnectivityInfo,
        tls_version_to_use: TlsVersionEnum) -> Tuple[_ScanJobResultEnum, bool]:
    """Check whether the server supports secure renegotiation.
    """
    ssl_connection = server_info.get_preconfigured_tls_connection(
        override_tls_version=tls_version_to_use,
        should_use_legacy_openssl=True)
    if not isinstance(ssl_connection.ssl_client, LegacySslClient):
        raise RuntimeError("Should never happen")

    try:
        # Perform the SSL handshake
        ssl_connection.connect()
        supports_secure_renegotiation = ssl_connection.ssl_client.get_secure_renegotiation_support(
        )

    finally:
        ssl_connection.close()

    return _ScanJobResultEnum.SUPPORTS_SECURE_RENEG, supports_secure_renegotiation
Пример #18
0
def _retrieve_and_analyze_http_response(
        server_info: ServerConnectivityInfo) -> HttpHeadersScanResult:
    # Perform the TLS handshake
    ssl_connection = server_info.get_preconfigured_tls_connection()
    try:
        ssl_connection.connect()

        # Send an HTTP GET request to the server
        ssl_connection.ssl_client.write(
            HttpRequestGenerator.get_request(
                host=server_info.network_configuration.
                tls_server_name_indication))

        # We do not follow redirections because the security headers must be set on the first page according to
        # https://hstspreload.appspot.com/:
        # "If you are serving an additional redirect from your HTTPS site, that redirect must still have the HSTS
        # header (rather than the page it redirects to)."
        http_response = HttpResponseParser.parse_from_ssl_connection(
            ssl_connection.ssl_client)
    finally:
        ssl_connection.close()

    if http_response.version == 9:
        # HTTP 0.9 => Probably not an HTTP response
        raise ValueError("Server did not return an HTTP response")

    # Parse and return each header
    return HttpHeadersScanResult(
        strict_transport_security_header=_parse_hsts_header_from_http_response(
            http_response),
        public_key_pins_header=_parse_hpkp_header_from_http_response(
            http_response),
        public_key_pins_report_only_header=
        _parse_hpkp_report_only_header_from_http_response(http_response),
        expect_ct_header=_parse_expect_ct_header_from_http_response(
            http_response),
    )
Пример #19
0
def _test_compression_support(server_info: ServerConnectivityInfo) -> bool:
    # Try with TLS 1.2 even if the server supports TLS 1.3 or higher as there is no compression with TLS 1.3
    if server_info.tls_probing_result.highest_tls_version_supported >= OpenSslVersionEnum.TLSV1_3:
        ssl_version_to_use = OpenSslVersionEnum.TLSV1_2
    else:
        ssl_version_to_use = server_info.tls_probing_result.highest_tls_version_supported

    ssl_connection = server_info.get_preconfigured_tls_connection(
        override_tls_version=ssl_version_to_use,
        should_use_legacy_openssl=True)
    if not isinstance(ssl_connection.ssl_client, LegacySslClient):
        raise RuntimeError("Should never happen")

    # Make sure OpenSSL was built with support for compression to avoid false negatives
    if "zlib compression" not in ssl_connection.ssl_client.get_available_compression_methods(
    ):
        raise RuntimeError(
            "OpenSSL was not built with support for zlib / compression. Did you build nassl yourself ?"
        )

    compression_name: Optional[str] = None
    try:
        # Perform the SSL handshake
        ssl_connection.connect()
        compression_name = ssl_connection.ssl_client.get_current_compression_method(
        )
    except ClientCertificateRequested:
        # The server asked for a client cert
        compression_name = ssl_connection.ssl_client.get_current_compression_method(
        )
    except ServerRejectedTlsHandshake:
        # Should only happen when the server only supports TLS 1.3, which does not support compression
        pass
    finally:
        ssl_connection.close()

    return True if compression_name else False
Пример #20
0
def _test_client_renegotiation(
        server_info: ServerConnectivityInfo
) -> Tuple[_ScanJobResultEnum, bool]:
    """Check whether the server honors session renegotiation requests.
    """
    # Try with TLS 1.2 even if the server supports TLS 1.3 or higher as there is no reneg with TLS 1.3
    if server_info.tls_probing_result.highest_tls_version_supported.value >= TlsVersionEnum.TLS_1_3.value:
        tls_version_to_use = TlsVersionEnum.TLS_1_2
        downgraded_from_tls_1_3 = True
    else:
        tls_version_to_use = server_info.tls_probing_result.highest_tls_version_supported
        downgraded_from_tls_1_3 = False

    ssl_connection = server_info.get_preconfigured_tls_connection(
        override_tls_version=tls_version_to_use,
        should_use_legacy_openssl=
        True,  # Only the legacy SSL client has methods to trigger a reneg
    )
    if not isinstance(ssl_connection.ssl_client, LegacySslClient):
        raise RuntimeError("Should never happen")

    try:
        # Perform the TLS handshake
        ssl_connection.connect()

    # Should only happen when the server only supports TLS 1.3
    except ServerRejectedTlsHandshake:
        if downgraded_from_tls_1_3:
            accepts_client_renegotiation = False  # Technically TLS 1.3 has no renegotiation therefore it is secure
        else:
            raise

    # The initial TLS handshake went well; let's try to do a renegotiation
    else:
        try:
            # Do a reneg multiple times in a row to be 100% sure that the server has no mitigations in place
            # https://github.com/nabla-c0d3/sslyze/issues/473
            for i in range(10):
                ssl_connection.ssl_client.do_renegotiate()
            accepts_client_renegotiation = True

        # Errors caused by a server rejecting the renegotiation
        except socket.timeout:
            # This is how Netty rejects a renegotiation - https://github.com/nabla-c0d3/sslyze/issues/114
            accepts_client_renegotiation = False
        except ConnectionError:
            accepts_client_renegotiation = False
        except OSError as e:
            # OSError is the parent of all (non-TLS) socket/connection errors so it should be last
            if "Nassl SSL handshake failed" in e.args[0]:
                # Special error returned by nassl
                accepts_client_renegotiation = False
            else:
                raise
        except OpenSSLError as e:
            if "handshake failure" in e.args[0]:
                accepts_client_renegotiation = False
            elif "no renegotiation" in e.args[0]:
                accepts_client_renegotiation = False
            elif "tlsv1 unrecognized name" in e.args[0]:
                # Yahoo's very own way of rejecting a renegotiation
                accepts_client_renegotiation = False
            elif "tlsv1 alert internal error" in e.args[0]:
                # Jetty server: https://github.com/nabla-c0d3/sslyze/issues/290
                accepts_client_renegotiation = False
            elif "decryption failed or bad record mac" in e.args[0]:
                # Some servers such as reddit.com
                accepts_client_renegotiation = False
            elif "sslv3 alert unexpected message" in e.args[0]:
                # traefik https://github.com/nabla-c0d3/sslyze/issues/422
                accepts_client_renegotiation = False
            elif "shut down by peer" in e.args[0]:
                # Cloudfront
                accepts_client_renegotiation = False
            elif "unexpected record" in e.args[0]:
                # Indy TCP Server with special RSA Token authentication https://github.com/nabla-c0d3/sslyze/issues/483
                accepts_client_renegotiation = False

            else:
                raise

    finally:
        ssl_connection.close()

    return _ScanJobResultEnum.IS_VULNERABLE_TO_CLIENT_RENEG_DOS, accepts_client_renegotiation
Пример #21
0
def _test_curve(server_info: ServerConnectivityInfo,
                curve_nid: OpenSslEcNidEnum) -> _EllipticCurveResult:
    if not server_info.tls_probing_result.supports_ecdh_key_exchange:
        raise RuntimeError("Should never happen")

    tls_version = server_info.tls_probing_result.highest_tls_version_supported
    ssl_connection = server_info.get_preconfigured_tls_connection(
        override_tls_version=tls_version, should_use_legacy_openssl=False)
    if not isinstance(ssl_connection.ssl_client, SslClient):
        raise RuntimeError(
            "Should never happen: specified should_use_legacy_openssl=False but didn't get the modern SSL client"
        )

    # Set curve to test whether it is supported by the server
    enable_ecdh_cipher_suites(tls_version, ssl_connection.ssl_client)
    ssl_connection.ssl_client.set_groups([curve_nid])

    try:
        ssl_connection.connect()
        negotiated_ephemeral_key = ssl_connection.ssl_client.get_ephemeral_key(
        )

    # Error handling here mis similar to test_cipher_suite.py
    except ClientCertificateRequested:
        negotiated_ephemeral_key = ssl_connection.ssl_client.get_ephemeral_key(
        )

    except (TlsHandshakeTimedOut, ServerRejectedTlsHandshake):
        negotiated_ephemeral_key = None

    except OpenSSLError as e:
        # The following errors can be triggered by some servers when they don't support the specific curve enabled
        # in the client
        if "ossl_statem_client_read_transition:unexpected message" in e.args[
                0]:
            # Related to https://github.com/nabla-c0d3/sslyze/issues/466
            negotiated_ephemeral_key = None
        elif "tls_process_ske_ecdhe:wrong curve" in e.args[0]:
            # https://github.com/nabla-c0d3/sslyze/issues/490
            negotiated_ephemeral_key = None
        elif "sslv3 alert unexpected message" in e.args[0]:
            # https://github.com/nabla-c0d3/sslyze/issues/490
            negotiated_ephemeral_key = None
        else:
            raise

    finally:
        ssl_connection.close()

        # If no error occurred check if the curve was really used
        try:
            curve_name = _OPENSSL_NID_TO_SECG_ANSI_X9_62[
                curve_nid]  # TODO(AD): Make this public in nassl
        except KeyError:
            curve_name = f"unknown-curve-with-openssl-id-{curve_nid.value}"

    if negotiated_ephemeral_key:
        if isinstance(negotiated_ephemeral_key, EcDhEphemeralKeyInfo):
            if negotiated_ephemeral_key.curve != curve_nid:
                raise RuntimeError("Should never happen")

            return _EllipticCurveResult(
                curve=EllipticCurve(name=curve_name,
                                    openssl_nid=curve_nid.value),
                was_accepted_by_server=True,
            )

    return _EllipticCurveResult(
        curve=EllipticCurve(name=curve_name, openssl_nid=curve_nid.value),
        was_accepted_by_server=False,
    )
Пример #22
0
def _retrieve_and_analyze_http_response(server_info: ServerConnectivityInfo) -> HttpHeadersScanResult:
    # Send HTTP requests until we no longer received an HTTP redirection, but allow only 4 redirections max
    _logger.info(f"Retrieving HTTP headers from {server_info}")
    redirections_count = 0
    next_location_path: Optional[str] = "/"
    http_error_trace = None

    while next_location_path and redirections_count < 4:
        _logger.info(f"Sending HTTP request to {next_location_path}")
        http_path_redirected_to = next_location_path

        # Perform the TLS handshake
        ssl_connection = server_info.get_preconfigured_tls_connection()
        ssl_connection.connect()

        try:
            # Send an HTTP GET request to the server
            ssl_connection.ssl_client.write(
                HttpRequestGenerator.get_request(
                    host=server_info.network_configuration.tls_server_name_indication, path=next_location_path
                )
            )
            http_response = HttpResponseParser.parse_from_ssl_connection(ssl_connection.ssl_client)

        except (OSError, NotAValidHttpResponseError, SslError) as e:
            # The server closed/rejected the connection, or didn't return a valid HTTP response
            http_error_trace = TracebackException.from_exception(e)

        finally:
            ssl_connection.close()

        if http_error_trace:
            break

        # Handle redirection if there is one
        next_location_path = _detect_http_redirection(
            http_response=http_response,
            server_host_name=server_info.network_configuration.tls_server_name_indication,
            server_port=server_info.server_location.port,
        )
        redirections_count += 1

    # Prepare the results
    initial_http_request = HttpRequestGenerator.get_request(
        host=server_info.network_configuration.tls_server_name_indication, path="/"
    ).decode("ascii")

    if http_error_trace:
        # If the server errored when receiving an HTTP request, return the error as the result
        return HttpHeadersScanResult(
            http_request_sent=initial_http_request,
            http_error_trace=http_error_trace,
            http_path_redirected_to=None,
            strict_transport_security_header=None,
            expect_ct_header=None,
        )
    else:
        # If no HTTP error happened, parse and return each header
        return HttpHeadersScanResult(
            http_request_sent=initial_http_request,
            http_path_redirected_to=http_path_redirected_to,
            http_error_trace=None,
            strict_transport_security_header=_parse_hsts_header_from_http_response(http_response),
            expect_ct_header=_parse_expect_ct_header_from_http_response(http_response),
        )
Пример #23
0
def connect_with_cipher_suite(
    server_connectivity_info: ServerConnectivityInfo,
    tls_version: OpenSslVersionEnum, cipher_suite: CipherSuite
) -> Union[CipherSuiteAcceptedByServer, CipherSuiteRejectedByServer]:
    """Initiates a SSL handshake with the server using the SSL version and the cipher suite specified.
    """
    requires_legacy_openssl = True
    if tls_version == OpenSslVersionEnum.TLSV1_2:
        # For TLS 1.2, we need to pick the right version of OpenSSL depending on which cipher suite
        requires_legacy_openssl = WorkaroundForTls12ForCipherSuites.requires_legacy_openssl(
            cipher_suite.openssl_name)
    elif tls_version == OpenSslVersionEnum.TLSV1_3:
        requires_legacy_openssl = False

    ssl_connection = server_connectivity_info.get_preconfigured_tls_connection(
        override_tls_version=tls_version,
        should_use_legacy_openssl=requires_legacy_openssl)

    # Only enable the cipher suite to test; not trivial anymore since OpenSSL 1.1.1 and TLS 1.3
    if isinstance(ssl_connection.ssl_client, SslClient):
        # With the modern OpenSSL client we have to manage TLS 1.3-specific cipher functions
        if tls_version == OpenSslVersionEnum.TLSV1_3:
            legacy_openssl_cipher_string = ""
            tls1_3_openssl_cipher_string = cipher_suite.openssl_name
        else:
            legacy_openssl_cipher_string = cipher_suite.openssl_name
            tls1_3_openssl_cipher_string = ""

        ssl_connection.ssl_client.set_ciphersuites(
            tls1_3_openssl_cipher_string)  # TLS 1.3 method
        ssl_connection.ssl_client.set_cipher_list(
            legacy_openssl_cipher_string)  # Legacy method
    elif isinstance(ssl_connection.ssl_client, LegacySslClient):
        # With the legacy OpenSSL client, nothing special to do
        ssl_connection.ssl_client.set_cipher_list(cipher_suite.openssl_name)
    else:
        raise RuntimeError("Should never happen")

    if len(ssl_connection.ssl_client.get_cipher_list()) != 1:
        raise ValueError(
            f'Passed an OpenSSL string for multiple cipher suites: "{cipher_suite.openssl_name}": '
            f"{str(ssl_connection.ssl_client.get_cipher_list())}")

    ephemeral_key = None
    try:
        # Perform the SSL handshake
        ssl_connection.connect()
        ephemeral_key = ssl_connection.ssl_client.get_ephemeral_key()

    except ServerTlsConfigurationNotSupported:
        # SSLyze rejected the handshake because the server's DH config was too insecure; this means the
        # cipher suite is actually supported
        pass

    except ClientCertificateRequested:
        # When the handshake failed due to ClientCertificateRequested
        ephemeral_key = ssl_connection.ssl_client.get_ephemeral_key()
        pass

    except ServerRejectedTlsHandshake as e:
        return CipherSuiteRejectedByServer(cipher_suite=cipher_suite,
                                           error_message=e.error_message)
    finally:
        ssl_connection.close()

    return CipherSuiteAcceptedByServer(cipher_suite=cipher_suite,
                                       ephemeral_key=ephemeral_key)