-
-
Notifications
You must be signed in to change notification settings - Fork 33.2k
gh-136134: Fallback to next auth method when CRAM-MD5 fails due to unsupported hash (e.g. FIPS) #136188
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gh-136134: Fallback to next auth method when CRAM-MD5 fails due to unsupported hash (e.g. FIPS) #136188
Changes from 2 commits
b379ce4
73014d5
d2cc372
ba5c991
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -743,6 +743,13 @@ def login(self, user, password, *, initial_response_ok=True): | |
| return (code, resp) | ||
| 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 | ||
|
|
||
| # We could not login successfully. Return result of last attempt. | ||
| raise last_exception | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make it an external function, and mock the entire class instead. |
||
| 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() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is too verbose. Just write something like "skip unsupported HMAC-HASH algorithms"