Skip to content

Commit d2cc372

Browse files
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.
1 parent 73014d5 commit d2cc372

File tree

2 files changed

+14
-8
lines changed

2 files changed

+14
-8
lines changed

Lib/smtplib.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454

5555
__all__ = ["SMTPException", "SMTPNotSupportedError", "SMTPServerDisconnected", "SMTPResponseException",
5656
"SMTPSenderRefused", "SMTPRecipientsRefused", "SMTPDataError",
57-
"SMTPConnectError", "SMTPHeloError", "SMTPAuthenticationError",
57+
"SMTPConnectError", "SMTPHeloError", "SMTPAuthenticationError", "SMTPAuthHashUnsupportedError",
5858
"quoteaddr", "quotedata", "SMTP"]
5959

6060
SMTP_PORT = 25
@@ -141,6 +141,10 @@ class SMTPAuthenticationError(SMTPResponseException):
141141
combination provided.
142142
"""
143143

144+
class SMTPAuthHashUnsupportedError(SMTPException):
145+
"""Raised when the authentication mechanism uses a hash algorithm unsupported by the system."""
146+
147+
144148
def quoteaddr(addrstring):
145149
"""Quote a subset of the email addresses defined by RFC 821.
146150
@@ -665,8 +669,11 @@ def auth_cram_md5(self, challenge=None):
665669
# CRAM-MD5 does not support initial-response.
666670
if challenge is None:
667671
return None
668-
return self.user + " " + hmac.HMAC(
669-
self.password.encode('ascii'), challenge, 'md5').hexdigest()
672+
try:
673+
return self.user + " " + hmac.HMAC(
674+
self.password.encode('ascii'), challenge, 'md5').hexdigest()
675+
except ValueError as e:
676+
raise SMTPAuthHashUnsupportedError(f'CRAM-MD5 failed: {e}') from e
670677

671678
def auth_plain(self, challenge=None):
672679
""" Authobject to use with PLAIN authentication. Requires self.user and
@@ -743,13 +750,12 @@ def login(self, user, password, *, initial_response_ok=True):
743750
return (code, resp)
744751
except SMTPAuthenticationError as e:
745752
last_exception = e
746-
except ValueError as e:
753+
except SMTPAuthHashUnsupportedError as e:
747754
# Some environments (e.g., FIPS) disable certain hashing algorithms like MD5,
748755
# which are required by CRAM-MD5. This raises a ValueError when trying to use HMAC.
749756
# If this happens, we catch the exception and continue trying the next auth method.
750757
last_exception = e
751-
if 'unsupported' in str(e).lower():
752-
continue
758+
continue
753759

754760
# We could not login successfully. Return result of last attempt.
755761
raise last_exception

Lib/test/test_smtplib.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1572,7 +1572,7 @@ def testAUTH_PLAIN_initial_response_auth(self):
15721572

15731573
class TestSMTPLoginValueError(unittest.TestCase):
15741574
def broken_hmac(*args, **kwargs):
1575-
raise ValueError("[digital envelope routines] unsupported")
1575+
raise smtplib.SMTPAuthHashUnsupportedError("CRAM-MD5 failed: [digital envelope routines] unsupported")
15761576

15771577
def test_login_raises_valueerror_when_cram_md5_fails(self):
15781578
with patch("hmac.HMAC", self.broken_hmac):
@@ -1593,7 +1593,7 @@ def docmd(self, *args, **kwargs):
15931593
return 334, b"Y2hhbGxlbmdl"
15941594

15951595
smtp = FakeSMTP()
1596-
with self.assertRaises(ValueError) as ctx:
1596+
with self.assertRaises(smtplib.SMTPAuthHashUnsupportedError) as ctx:
15971597
smtp.login("user", "pass")
15981598
self.assertIn("unsupported", str(ctx.exception).lower())
15991599

0 commit comments

Comments
 (0)