Skip to content

Commit

Permalink
Merge branch 'smtp-improvements' of https://github.com/mxsasha/sslyze
Browse files Browse the repository at this point in the history
…into mxsasha-smtp-improvements

sslyze/plugins/certificate_info/trust_stores/pem_files/apple.pem
sslyze/plugins/certificate_info/trust_stores/pem_files/google_aosp.pem
sslyze/plugins/certificate_info/trust_stores/pem_files/google_aosp.yaml
sslyze/plugins/certificate_info/trust_stores/pem_files/microsoft_windows.pem
sslyze/plugins/certificate_info/trust_stores/pem_files/mozilla_nss.pem
sslyze/plugins/certificate_info/trust_stores/pem_files/mozilla_nss.yaml
sslyze/plugins/certificate_info/trust_stores/pem_files/openjdk.pem
sslyze/plugins/certificate_info/trust_stores/pem_files/oracle_java.pem
  • Loading branch information
nabla-c0d3 committed Dec 26, 2024
2 parents f9c3146 + 01ca04d commit 3e0015c
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 19 deletions.
54 changes: 39 additions & 15 deletions sslyze/connection_helpers/opportunistic_tls_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import struct
from abc import abstractmethod, ABC
from enum import Enum
from smtplib import SMTP, SMTPException
from typing import ClassVar, Optional


Expand Down Expand Up @@ -61,20 +62,41 @@ def prepare_socket_for_tls_handshake(self, sock: socket.socket) -> None:
class _SmtpHelper(_OpportunisticTlsHelper):
"""Perform an SMTP StartTLS negotiation."""

def __init__(self, smtp_ehlo_hostname: str):
self._smtp_ehlo_hostname = smtp_ehlo_hostname

def prepare_socket_for_tls_handshake(self, sock: socket.socket) -> None:
# Get the SMTP banner
sock.recv(2048)
# SMTP parsing has some complicated areas and some unusual but legal
# server behavior - this code uses Python's smtplib to handle the protocol.
smtp = SMTP(local_hostname=self._smtp_ehlo_hostname)
smtp.sock = sock

# Send a EHLO and wait for the 250 status
sock.send(b"EHLO sslyze.scan\r\n")
data = sock.recv(2048)
if b"250 " not in data:
raise OpportunisticTlsError(f"SMTP EHLO was rejected: {repr(data)}")
try:
code, server_reply = smtp.getreply()
message = server_reply.decode()
except SMTPException as exc:
code, message = -1, str(exc)
if code != 220:
raise OpportunisticTlsError(f"Unable to find 220 service ready response: {message}")

# Send a STARTTLS
sock.send(b"STARTTLS\r\n")
if b"220" not in sock.recv(2048):
raise OpportunisticTlsError("SMTP STARTTLS not supported")
try:
code, server_reply = smtp.ehlo()
message = server_reply.decode()
except SMTPException as exc:
code, message = -1, str(exc)
if code != 250:
raise OpportunisticTlsError(f"SMTP EHLO was rejected: {message}")

if not smtp.has_extn("starttls"):
raise OpportunisticTlsError(f"Server does not support STARTTLS: {message}")

try:
code, server_reply = smtp.docmd("STARTTLS")
message = server_reply.decode()
except SMTPException as exc:
code, message = -1, str(exc)
if code != 220:
raise OpportunisticTlsError(f"SMTP STARTTLS rejected: {message}")


class _XmppHelper(_OpportunisticTlsHelper):
Expand Down Expand Up @@ -220,14 +242,16 @@ class _PostgresHelper(_GenericOpportunisticTlsHelper):


def get_opportunistic_tls_helper(
protocol: ProtocolWithOpportunisticTlsEnum, xmpp_to_hostname: Optional[str]
protocol: ProtocolWithOpportunisticTlsEnum, xmpp_to_hostname: Optional[str], smtp_ehlo_hostname: str
) -> _OpportunisticTlsHelper:
helper_cls = _START_TLS_HELPER_CLASSES[protocol]
if protocol not in [ProtocolWithOpportunisticTlsEnum.XMPP, ProtocolWithOpportunisticTlsEnum.XMPP_SERVER]:
opportunistic_tls_helper = helper_cls()
else:
if protocol in [ProtocolWithOpportunisticTlsEnum.XMPP, ProtocolWithOpportunisticTlsEnum.XMPP_SERVER]:
if xmpp_to_hostname is None:
raise ValueError("Received None for xmpp_to_hostname")
opportunistic_tls_helper = helper_cls(xmpp_to=xmpp_to_hostname)
elif protocol == ProtocolWithOpportunisticTlsEnum.SMTP:
opportunistic_tls_helper = helper_cls(smtp_ehlo_hostname)
else:
opportunistic_tls_helper = helper_cls()

return opportunistic_tls_helper
4 changes: 3 additions & 1 deletion sslyze/connection_helpers/tls_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,9 @@ def _do_pre_handshake(self) -> None:
# Do the Opportunistic/StartTLS negotiation if needed
if self._network_configuration.tls_opportunistic_encryption:
opportunistic_tls_helper = get_opportunistic_tls_helper(
self._network_configuration.tls_opportunistic_encryption, self._network_configuration.xmpp_to_hostname
self._network_configuration.tls_opportunistic_encryption,
self._network_configuration.xmpp_to_hostname,
self._network_configuration.smtp_ehlo_hostname,
)
try:
opportunistic_tls_helper.prepare_socket_for_tls_handshake(sock)
Expand Down
20 changes: 17 additions & 3 deletions sslyze/server_setting.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,18 +163,20 @@ class ServerNetworkConfiguration:
Attributes:
tls_server_name_indication: The hostname to set within the Server Name Indication TLS extension.
tls_wrapped_protocol: The protocol wrapped in TLS that the server expects. It allows SSLyze to figure out
tls_opportunistic_encryption: The protocol wrapped in TLS that the server expects. It allows SSLyze to figure out
how to establish a (Start)TLS connection to the server and what kind of "hello" message
(SMTP, XMPP, etc.) to send to the server after the handshake was completed. If not supplied, standard
TLS will be used.
tls_client_auth_credentials: The client certificate and private key needed to perform mutual authentication
with the server. If not supplied, SSLyze will attempt to connect to the server without performing
client authentication.
xmpp_to_hostname: The hostname to set within the `to` attribute of the XMPP stream. If not supplied, the
server's hostname will be used. Should only be set if the supplied `tls_wrapped_protocol` is an
server's hostname will be used. Should only be set if the supplied `tls_opportunistic_encryption` is an
XMPP protocol.
http_user_agent: The User-Agent to send in HTTP requests. If not supplied, a default Chrome-like
is used that includes SSLyze's version.
smtp_ehlo_hostname: The hostname to set in the SMTP EHLO. If not supplied, the default of "sslyze.scan"
will be used. Should only be set if the supplied `tls_opportunistic_encryption` is SMTP.
network_timeout: The timeout (in seconds) to be used when attempting to establish a connection to the
server.
network_max_retries: The number of retries SSLyze will perform when attempting to establish a connection
Expand All @@ -186,6 +188,7 @@ class ServerNetworkConfiguration:
tls_client_auth_credentials: Optional[ClientAuthenticationCredentials] = None

xmpp_to_hostname: Optional[str] = None
smtp_ehlo_hostname: Optional[str] = None
http_user_agent: Optional[str] = None

network_timeout: int = 5
Expand All @@ -203,7 +206,18 @@ def __post_init__(self) -> None:
else:
if self.xmpp_to_hostname:
raise InvalidServerNetworkConfigurationError("Can only specify xmpp_to for the XMPP StartTLS protocol.")


if self.tls_opportunistic_encryption in [
ProtocolWithOpportunisticTlsEnum.SMTP,
]:
if not self.smtp_ehlo_hostname:
object.__setattr__(self, "smtp_ehlo_hostname", "sslyze.scan")
else:
if self.smtp_ehlo_hostname:
raise InvalidServerNetworkConfigurationError(
"Can only specify smtp_ehlo_hostname for the SMTP StartTLS protocol."
)

if self.tls_opportunistic_encryption and self.http_user_agent:
raise InvalidServerNetworkConfigurationError(
"Cannot specify both tls_opportunistic_encryption and http_user_agent"
Expand Down

0 comments on commit 3e0015c

Please sign in to comment.