From b379ce43aec06420019284acdf7466b3a410bd0b Mon Sep 17 00:00:00 2001 From: Raphael Nogueira Date: Tue, 1 Jul 2025 21:08:49 +0100 Subject: [PATCH 1/4] "gh-136134: Fallback to next auth method when CRAM-MD5 fails due to unsupported hash (e.g. FIPS) --- Lib/smtplib.py | 4 +++ Lib/test/test_smtplib.py | 57 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/Lib/smtplib.py b/Lib/smtplib.py index 84d6d858e7dec1..b3d4691b5290f7 100644 --- a/Lib/smtplib.py +++ b/Lib/smtplib.py @@ -743,6 +743,10 @@ def login(self, user, password, *, initial_response_ok=True): return (code, resp) except SMTPAuthenticationError as e: last_exception = e + except ValueError as e: + last_exception = e + if 'unsupported' in str(e).lower(): + continue # We could not login successfully. Return result of last attempt. raise last_exception diff --git a/Lib/test/test_smtplib.py b/Lib/test/test_smtplib.py index 4c9fc14bd43f54..bc9ec50d6d4ea1 100644 --- a/Lib/test/test_smtplib.py +++ b/Lib/test/test_smtplib.py @@ -23,7 +23,7 @@ from test.support import threading_helper from test.support import asyncore from test.support import smtpd -from unittest.mock import Mock +from unittest.mock import Mock, patch support.requires_working_socket(module=True) @@ -1570,5 +1570,60 @@ def testAUTH_PLAIN_initial_response_auth(self): self.assertEqual(code, 235) +class TestSMTPLoginValueError(unittest.TestCase): + def broken_hmac(*args, **kwargs): + raise ValueError("[digital envelope routines] unsupported") + + def test_login_raises_valueerror_when_cram_md5_fails(self): + with patch("hmac.HMAC", self.broken_hmac): + class FakeSMTP(smtplib.SMTP): + def __init__(self): + super().__init__(host='', port=0) + self.esmtp_features = {"auth": "CRAM-MD5"} + self._host = "localhost" + + def ehlo_or_helo_if_needed(self): + pass + + def has_extn(self, ext): + return ext.lower() == "auth" + + def docmd(self, *args, **kwargs): + # Retorna uma challenge base64 vĂ¡lida + return 334, b"Y2hhbGxlbmdl" + + smtp = FakeSMTP() + with self.assertRaises(ValueError) as ctx: + smtp.login("user", "pass") + self.assertIn("unsupported", str(ctx.exception).lower()) + + def test_login_fallbacks_when_cram_md5_raises_valueerror(self): + with patch("hmac.HMAC", self.broken_hmac): + class FakeSMTP(smtplib.SMTP): + def __init__(self): + super().__init__(host='', port=0) + self.esmtp_features = {"auth": "CRAM-MD5 LOGIN"} + self._host = "localhost" + + def ehlo_or_helo_if_needed(self): + pass + + def has_extn(self, ext): + return ext.lower() == "auth" + + def docmd(self, *args, **kwargs): + if args[0] == "AUTH" and args[1].startswith("CRAM-MD5"): + return 334, b"Y2hhbGxlbmdl" # base64('challenge') + return 235, b"Authentication successful" + + def auth_login(self, challenge=None): + return "login response" + + smtp = FakeSMTP() + code, resp = smtp.login("user", "pass") + self.assertEqual(code, 235) + self.assertEqual(resp, b"Authentication successful") + + if __name__ == '__main__': unittest.main() From 73014d5e2a0eda7b6bd86517400028350be3bbb6 Mon Sep 17 00:00:00 2001 From: Raphael Nogueira Date: Tue, 1 Jul 2025 21:39:20 +0100 Subject: [PATCH 2/4] gh-136134: Add code comment explaining CRAM-MD5 fallback and NEWS entry --- Lib/smtplib.py | 3 +++ .../Library/2025-07-01-21-37-17.gh-issue-136134.NhFpu3.rst | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-07-01-21-37-17.gh-issue-136134.NhFpu3.rst diff --git a/Lib/smtplib.py b/Lib/smtplib.py index b3d4691b5290f7..d5ab7c42c0eac0 100644 --- a/Lib/smtplib.py +++ b/Lib/smtplib.py @@ -744,6 +744,9 @@ def login(self, user, password, *, initial_response_ok=True): except SMTPAuthenticationError as e: last_exception = e except ValueError as e: + # Some environments (e.g., FIPS) disable certain hashing algorithms like MD5, + # which are required by CRAM-MD5. This raises a ValueError when trying to use HMAC. + # If this happens, we catch the exception and continue trying the next auth method. last_exception = e if 'unsupported' in str(e).lower(): continue diff --git a/Misc/NEWS.d/next/Library/2025-07-01-21-37-17.gh-issue-136134.NhFpu3.rst b/Misc/NEWS.d/next/Library/2025-07-01-21-37-17.gh-issue-136134.NhFpu3.rst new file mode 100644 index 00000000000000..e53bbafd092277 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-07-01-21-37-17.gh-issue-136134.NhFpu3.rst @@ -0,0 +1,2 @@ +Make smtplib fallback to next auth method if CRAM-MD5 fails due to +unsupported hash (e.g. FIPS). Contributed by Raphael Rodrigues. From d2cc3729f9ad7523a1326980391eac5059a93520 Mon Sep 17 00:00:00 2001 From: Raphael Nogueira Date: Wed, 2 Jul 2025 20:52:46 +0100 Subject: [PATCH 3/4] bpo-136134: Raise SMTPAuthHashUnsupportedError when HMAC fails in CRAM-MD5 Wraps the HMAC call in auth_cram_md5 and raises a dedicated exception to avoid relying on error message content. Also updates the relevant tests. --- Lib/smtplib.py | 18 ++++++++++++------ Lib/test/test_smtplib.py | 4 ++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Lib/smtplib.py b/Lib/smtplib.py index d5ab7c42c0eac0..9f11d27478ece4 100644 --- a/Lib/smtplib.py +++ b/Lib/smtplib.py @@ -54,7 +54,7 @@ __all__ = ["SMTPException", "SMTPNotSupportedError", "SMTPServerDisconnected", "SMTPResponseException", "SMTPSenderRefused", "SMTPRecipientsRefused", "SMTPDataError", - "SMTPConnectError", "SMTPHeloError", "SMTPAuthenticationError", + "SMTPConnectError", "SMTPHeloError", "SMTPAuthenticationError", "SMTPAuthHashUnsupportedError", "quoteaddr", "quotedata", "SMTP"] SMTP_PORT = 25 @@ -141,6 +141,10 @@ class SMTPAuthenticationError(SMTPResponseException): combination provided. """ +class SMTPAuthHashUnsupportedError(SMTPException): + """Raised when the authentication mechanism uses a hash algorithm unsupported by the system.""" + + def quoteaddr(addrstring): """Quote a subset of the email addresses defined by RFC 821. @@ -665,8 +669,11 @@ def auth_cram_md5(self, challenge=None): # CRAM-MD5 does not support initial-response. if challenge is None: return None - return self.user + " " + hmac.HMAC( - self.password.encode('ascii'), challenge, 'md5').hexdigest() + try: + return self.user + " " + hmac.HMAC( + self.password.encode('ascii'), challenge, 'md5').hexdigest() + except ValueError as e: + raise SMTPAuthHashUnsupportedError(f'CRAM-MD5 failed: {e}') from e def auth_plain(self, challenge=None): """ Authobject to use with PLAIN authentication. Requires self.user and @@ -743,13 +750,12 @@ def login(self, user, password, *, initial_response_ok=True): return (code, resp) except SMTPAuthenticationError as e: last_exception = e - except ValueError as e: + except SMTPAuthHashUnsupportedError as e: # Some environments (e.g., FIPS) disable certain hashing algorithms like MD5, # which are required by CRAM-MD5. This raises a ValueError when trying to use HMAC. # If this happens, we catch the exception and continue trying the next auth method. last_exception = e - if 'unsupported' in str(e).lower(): - continue + continue # We could not login successfully. Return result of last attempt. raise last_exception diff --git a/Lib/test/test_smtplib.py b/Lib/test/test_smtplib.py index bc9ec50d6d4ea1..acbd1a1db24652 100644 --- a/Lib/test/test_smtplib.py +++ b/Lib/test/test_smtplib.py @@ -1572,7 +1572,7 @@ def testAUTH_PLAIN_initial_response_auth(self): class TestSMTPLoginValueError(unittest.TestCase): def broken_hmac(*args, **kwargs): - raise ValueError("[digital envelope routines] unsupported") + raise smtplib.SMTPAuthHashUnsupportedError("CRAM-MD5 failed: [digital envelope routines] unsupported") def test_login_raises_valueerror_when_cram_md5_fails(self): with patch("hmac.HMAC", self.broken_hmac): @@ -1593,7 +1593,7 @@ def docmd(self, *args, **kwargs): return 334, b"Y2hhbGxlbmdl" smtp = FakeSMTP() - with self.assertRaises(ValueError) as ctx: + with self.assertRaises(smtplib.SMTPAuthHashUnsupportedError) as ctx: smtp.login("user", "pass") self.assertIn("unsupported", str(ctx.exception).lower()) From ba5c9917f49a4fbd83d8288918de2d38d616b5c1 Mon Sep 17 00:00:00 2001 From: Raphael Nogueira Date: Wed, 2 Jul 2025 21:13:36 +0100 Subject: [PATCH 4/4] bpo-136134: Clarify comment on handling unsupported hash in CRAM-MD5 --- Lib/smtplib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/smtplib.py b/Lib/smtplib.py index 9f11d27478ece4..4316df93bbca5d 100644 --- a/Lib/smtplib.py +++ b/Lib/smtplib.py @@ -752,7 +752,7 @@ def login(self, user, password, *, initial_response_ok=True): last_exception = e except SMTPAuthHashUnsupportedError as e: # Some environments (e.g., FIPS) disable certain hashing algorithms like MD5, - # which are required by CRAM-MD5. This raises a ValueError when trying to use HMAC. + # which are required by CRAM-MD5. This raises a SMTPAuthHashUnsupportedError when trying to use HMAC. # If this happens, we catch the exception and continue trying the next auth method. last_exception = e continue pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy