Skip to content

Commit b379ce4

Browse files
"gh-136134: Fallback to next auth method when CRAM-MD5 fails due to unsupported hash (e.g. FIPS)
1 parent f41e9c7 commit b379ce4

File tree

2 files changed

+60
-1
lines changed

2 files changed

+60
-1
lines changed

Lib/smtplib.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,10 @@ def login(self, user, password, *, initial_response_ok=True):
743743
return (code, resp)
744744
except SMTPAuthenticationError as e:
745745
last_exception = e
746+
except ValueError as e:
747+
last_exception = e
748+
if 'unsupported' in str(e).lower():
749+
continue
746750

747751
# We could not login successfully. Return result of last attempt.
748752
raise last_exception

Lib/test/test_smtplib.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from test.support import threading_helper
2424
from test.support import asyncore
2525
from test.support import smtpd
26-
from unittest.mock import Mock
26+
from unittest.mock import Mock, patch
2727

2828

2929
support.requires_working_socket(module=True)
@@ -1570,5 +1570,60 @@ def testAUTH_PLAIN_initial_response_auth(self):
15701570
self.assertEqual(code, 235)
15711571

15721572

1573+
class TestSMTPLoginValueError(unittest.TestCase):
1574+
def broken_hmac(*args, **kwargs):
1575+
raise ValueError("[digital envelope routines] unsupported")
1576+
1577+
def test_login_raises_valueerror_when_cram_md5_fails(self):
1578+
with patch("hmac.HMAC", self.broken_hmac):
1579+
class FakeSMTP(smtplib.SMTP):
1580+
def __init__(self):
1581+
super().__init__(host='', port=0)
1582+
self.esmtp_features = {"auth": "CRAM-MD5"}
1583+
self._host = "localhost"
1584+
1585+
def ehlo_or_helo_if_needed(self):
1586+
pass
1587+
1588+
def has_extn(self, ext):
1589+
return ext.lower() == "auth"
1590+
1591+
def docmd(self, *args, **kwargs):
1592+
# Retorna uma challenge base64 válida
1593+
return 334, b"Y2hhbGxlbmdl"
1594+
1595+
smtp = FakeSMTP()
1596+
with self.assertRaises(ValueError) as ctx:
1597+
smtp.login("user", "pass")
1598+
self.assertIn("unsupported", str(ctx.exception).lower())
1599+
1600+
def test_login_fallbacks_when_cram_md5_raises_valueerror(self):
1601+
with patch("hmac.HMAC", self.broken_hmac):
1602+
class FakeSMTP(smtplib.SMTP):
1603+
def __init__(self):
1604+
super().__init__(host='', port=0)
1605+
self.esmtp_features = {"auth": "CRAM-MD5 LOGIN"}
1606+
self._host = "localhost"
1607+
1608+
def ehlo_or_helo_if_needed(self):
1609+
pass
1610+
1611+
def has_extn(self, ext):
1612+
return ext.lower() == "auth"
1613+
1614+
def docmd(self, *args, **kwargs):
1615+
if args[0] == "AUTH" and args[1].startswith("CRAM-MD5"):
1616+
return 334, b"Y2hhbGxlbmdl" # base64('challenge')
1617+
return 235, b"Authentication successful"
1618+
1619+
def auth_login(self, challenge=None):
1620+
return "login response"
1621+
1622+
smtp = FakeSMTP()
1623+
code, resp = smtp.login("user", "pass")
1624+
self.assertEqual(code, 235)
1625+
self.assertEqual(resp, b"Authentication successful")
1626+
1627+
15731628
if __name__ == '__main__':
15741629
unittest.main()

0 commit comments

Comments
 (0)