-
Notifications
You must be signed in to change notification settings - Fork 456
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[#680]Plugin for Extended Master Secret support
- Loading branch information
1 parent
90e88f3
commit fe56979
Showing
6 changed files
with
210 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
from dataclasses import dataclass | ||
from typing import List, Optional | ||
|
||
from nassl.ssl_client import SslClient, ExtendedMasterSecretSupportEnum | ||
|
||
from sslyze.json.pydantic_utils import BaseModelWithOrmModeAndForbid | ||
from sslyze.json.scan_attempt_json import ScanCommandAttemptAsJson | ||
from sslyze.plugins.plugin_base import ( | ||
ScanCommandResult, | ||
ScanCommandImplementation, | ||
ScanCommandExtraArgument, | ||
ScanJob, | ||
ScanCommandWrongUsageError, | ||
ScanCommandCliConnector, | ||
ScanJobResult, | ||
) | ||
from sslyze.server_connectivity import ServerConnectivityInfo, TlsVersionEnum | ||
|
||
|
||
@dataclass(frozen=True) | ||
class EmsExtensionScanResult(ScanCommandResult): | ||
"""The result of testing a server for TLS Extended Master Secret extension support. | ||
Attributes: | ||
supports_ems_extension: True if the server supports the TLS Extended Master Secret extension. | ||
""" | ||
|
||
supports_ems_extension: bool | ||
|
||
|
||
class EmsExtensionScanResultAsJson(BaseModelWithOrmModeAndForbid): | ||
supports_ems_extension: bool | ||
|
||
|
||
class EmsExtensionScanAttemptAsJson(ScanCommandAttemptAsJson): | ||
result: Optional[EmsExtensionScanResultAsJson] | ||
|
||
|
||
class _EmsExtensionCliConnector(ScanCommandCliConnector[EmsExtensionScanResult, None]): | ||
_cli_option = "ems" | ||
_cli_description = "Test a server for TLS Extended Master Secret extension support." | ||
|
||
@classmethod | ||
def result_to_console_output(cls, result: EmsExtensionScanResult) -> List[str]: | ||
result_as_txt = [cls._format_title("TLS Extended Master Secret Extension")] | ||
downgrade_txt = "OK - Supported" if result.supports_ems_extension else "VULNERABLE - EMS not supported" | ||
result_as_txt.append(cls._format_field("", downgrade_txt)) | ||
return result_as_txt | ||
|
||
|
||
class EmsExtensionImplementation(ScanCommandImplementation[EmsExtensionScanResult, None]): | ||
"""Test a server for TLS Extended Master Secret extension support.""" | ||
|
||
cli_connector_cls = _EmsExtensionCliConnector | ||
|
||
@classmethod | ||
def scan_jobs_for_scan_command( | ||
cls, server_info: ServerConnectivityInfo, extra_arguments: Optional[ScanCommandExtraArgument] = None | ||
) -> List[ScanJob]: | ||
if extra_arguments: | ||
raise ScanCommandWrongUsageError("This plugin does not take extra arguments") | ||
|
||
return [ScanJob(function_to_call=_test_ems, function_arguments=[server_info])] | ||
|
||
@classmethod | ||
def result_for_completed_scan_jobs( | ||
cls, server_info: ServerConnectivityInfo, scan_job_results: List[ScanJobResult] | ||
) -> EmsExtensionScanResult: | ||
if len(scan_job_results) != 1: | ||
raise RuntimeError(f"Unexpected number of scan jobs received: {scan_job_results}") | ||
|
||
return EmsExtensionScanResult(supports_ems_extension=scan_job_results[0].get_result()) | ||
|
||
|
||
def _test_ems(server_info: ServerConnectivityInfo) -> bool: | ||
# The Extended Master Secret extension is not relevant to TLS 1.3 and later | ||
if server_info.tls_probing_result.highest_tls_version_supported.value >= TlsVersionEnum.TLS_1_3.value: | ||
return True | ||
|
||
ssl_connection = server_info.get_preconfigured_tls_connection( | ||
# Only the modern client has EMS support | ||
should_use_legacy_openssl=False, | ||
) | ||
if not isinstance(ssl_connection.ssl_client, SslClient): | ||
raise RuntimeError("Should never happen") | ||
|
||
# Perform the SSL handshake | ||
try: | ||
ssl_connection.connect() | ||
ems_support_enum = ssl_connection.ssl_client.get_extended_master_secret_support() | ||
finally: | ||
ssl_connection.close() | ||
|
||
# Return the result | ||
if ems_support_enum == ExtendedMasterSecretSupportEnum.NOT_USED_IN_CURRENT_SESSION: | ||
return False | ||
elif ems_support_enum == ExtendedMasterSecretSupportEnum.USED_IN_CURRENT_SESSION: | ||
return True | ||
else: | ||
raise ValueError("Could not determine Extended Master Secret Extension support") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
from nassl.ssl_client import ClientCertificateRequested | ||
|
||
from sslyze.plugins.ems_extension_plugin import ( | ||
EmsExtensionImplementation, | ||
EmsExtensionScanResult, | ||
EmsExtensionScanResultAsJson, | ||
) | ||
|
||
from sslyze.server_setting import ( | ||
ServerNetworkLocation, | ||
ServerNetworkConfiguration, | ||
ClientAuthenticationCredentials, | ||
) | ||
from tests.connectivity_utils import check_connectivity_to_server_and_return_info | ||
from tests.markers import can_only_run_on_linux_64 | ||
from tests.openssl_server import LegacyOpenSslServer, ClientAuthConfigEnum | ||
import pytest | ||
|
||
|
||
class TestFallbackScsvPlugin: | ||
def test_good(self) -> None: | ||
# Given a server that supports Extended Master Secret | ||
server_location = ServerNetworkLocation("www.google.com", 443) | ||
server_info = check_connectivity_to_server_and_return_info(server_location) | ||
|
||
# When testing for EMS support, it succeeds with the expected result | ||
result: EmsExtensionScanResult = EmsExtensionImplementation.scan_server(server_info) | ||
assert result.supports_ems_extension | ||
|
||
# And a CLI output can be generated | ||
assert EmsExtensionImplementation.cli_connector_cls.result_to_console_output(result) | ||
|
||
# And the result can be converted to JSON | ||
result_as_json = EmsExtensionScanResultAsJson.model_validate(result).model_dump_json() | ||
assert result_as_json | ||
|
||
@can_only_run_on_linux_64 | ||
def test_bad(self) -> None: | ||
# Given a server that does NOT support EMS | ||
with LegacyOpenSslServer() as server: | ||
server_location = ServerNetworkLocation( | ||
hostname=server.hostname, ip_address=server.ip_address, port=server.port | ||
) | ||
server_info = check_connectivity_to_server_and_return_info(server_location) | ||
|
||
# When testing for EMS, it succeeds | ||
result: EmsExtensionScanResult = EmsExtensionImplementation.scan_server(server_info) | ||
|
||
# And the server is reported as NOT supporting it | ||
assert not result.supports_ems_extension | ||
|
||
@can_only_run_on_linux_64 | ||
def test_fails_when_client_auth_failed(self) -> None: | ||
# Given a server that does NOT support EMS and that requires client authentication | ||
with LegacyOpenSslServer(client_auth_config=ClientAuthConfigEnum.REQUIRED) as server: | ||
# And sslyze does NOT provide a client certificate | ||
server_location = ServerNetworkLocation( | ||
hostname=server.hostname, ip_address=server.ip_address, port=server.port | ||
) | ||
server_info = check_connectivity_to_server_and_return_info(server_location) | ||
|
||
# When testing, it fails as a client cert was not supplied | ||
with pytest.raises(ClientCertificateRequested): | ||
EmsExtensionImplementation.scan_server(server_info) | ||
|
||
@can_only_run_on_linux_64 | ||
def test_works_when_client_auth_succeeded(self) -> None: | ||
# Given a server that does NOT support EMS and that requires client authentication | ||
with LegacyOpenSslServer(client_auth_config=ClientAuthConfigEnum.REQUIRED) as server: | ||
server_location = ServerNetworkLocation( | ||
hostname=server.hostname, ip_address=server.ip_address, port=server.port | ||
) | ||
# And sslyze provides a client certificate | ||
network_config = ServerNetworkConfiguration( | ||
tls_server_name_indication=server.hostname, | ||
tls_client_auth_credentials=ClientAuthenticationCredentials( | ||
certificate_chain_path=server.get_client_certificate_path(), key_path=server.get_client_key_path() | ||
), | ||
) | ||
server_info = check_connectivity_to_server_and_return_info(server_location, network_config) | ||
|
||
# When testing for EMS, it succeeds | ||
result: EmsExtensionScanResult = EmsExtensionImplementation.scan_server(server_info) | ||
|
||
# And the server is reported as NOT supporting EMS | ||
assert not result.supports_ems_extension |