https://github.com/python/cpython/commit/d574f832061b2ff7ac4ec64234fc81184a30bc2c
commit: d574f832061b2ff7ac4ec64234fc81184a30bc2c
branch: 3.14
author: Bénédikt Tran <[email protected]>
committer: hugovk <[email protected]>
date: 2025-09-04T17:59:49+03:00
summary:
[3.14] gh-136134: imaplib: fix CRAM-MD5 on FIPS-only environments (GH-136615)
(#138054)
files:
A Misc/NEWS.d/next/Library/2025-07-13-11-20-05.gh-issue-136134.xhh0Kq.rst
M Doc/library/imaplib.rst
M Lib/imaplib.py
M Lib/test/test_imaplib.py
diff --git a/Doc/library/imaplib.rst b/Doc/library/imaplib.rst
index 9f198aebcb66b0..2a12a0ca8e960b 100644
--- a/Doc/library/imaplib.rst
+++ b/Doc/library/imaplib.rst
@@ -413,6 +413,9 @@ An :class:`IMAP4` instance has the following methods:
the password. Will only work if the server ``CAPABILITY`` response
includes the
phrase ``AUTH=CRAM-MD5``.
+ .. versionchanged:: next
+ An :exc:`IMAP4.error` is raised if MD5 support is not available.
+
.. method:: IMAP4.logout()
diff --git a/Lib/imaplib.py b/Lib/imaplib.py
index 2c3925958d011b..362d6a2dcf2573 100644
--- a/Lib/imaplib.py
+++ b/Lib/imaplib.py
@@ -21,7 +21,7 @@
# GET/SETANNOTATION contributed by Tomas Lindroos <[email protected]> June 2005.
# IDLE contributed by Forest <[email protected]> August 2024.
-__version__ = "2.59"
+__version__ = "2.60"
import binascii, errno, random, re, socket, subprocess, sys, time, calendar
from datetime import datetime, timezone, timedelta
@@ -725,9 +725,17 @@ def login_cram_md5(self, user, password):
def _CRAM_MD5_AUTH(self, challenge):
""" Authobject to use with CRAM-MD5 authentication. """
import hmac
- pwd = (self.password.encode('utf-8') if isinstance(self.password, str)
- else self.password)
- return self.user + " " + hmac.HMAC(pwd, challenge, 'md5').hexdigest()
+
+ if isinstance(self.password, str):
+ password = self.password.encode('utf-8')
+ else:
+ password = self.password
+
+ try:
+ authcode = hmac.HMAC(password, challenge, 'md5')
+ except ValueError: # HMAC-MD5 is not available
+ raise self.error("CRAM-MD5 authentication is not supported")
+ return f"{self.user} {authcode.hexdigest()}"
def logout(self):
diff --git a/Lib/test/test_imaplib.py b/Lib/test/test_imaplib.py
index a13ee58d650e1b..e0bd1febab88c7 100644
--- a/Lib/test/test_imaplib.py
+++ b/Lib/test/test_imaplib.py
@@ -256,7 +256,20 @@ def cmd_IDLE(self, tag, args):
self._send_tagged(tag, 'BAD', 'Expected DONE')
-class NewIMAPTestsMixin():
+class AuthHandler_CRAM_MD5(SimpleIMAPHandler):
+ capabilities = 'LOGINDISABLED AUTH=CRAM-MD5'
+ def cmd_AUTHENTICATE(self, tag, args):
+ self._send_textline('+ PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2Uucm'
+ 'VzdG9uLm1jaS5uZXQ=')
+ r = yield
+ if (r == b'dGltIGYxY2E2YmU0NjRiOWVmYT'
+ b'FjY2E2ZmZkNmNmMmQ5ZjMy\r\n'):
+ self._send_tagged(tag, 'OK', 'CRAM-MD5 successful')
+ else:
+ self._send_tagged(tag, 'NO', 'No access')
+
+
+class NewIMAPTestsMixin:
client = None
def _setup(self, imap_handler, connect=True):
@@ -439,40 +452,31 @@ def cmd_AUTHENTICATE(self, tag, args):
@hashlib_helper.requires_hashdigest('md5', openssl=True)
def test_login_cram_md5_bytes(self):
- class AuthHandler(SimpleIMAPHandler):
- capabilities = 'LOGINDISABLED AUTH=CRAM-MD5'
- def cmd_AUTHENTICATE(self, tag, args):
- self._send_textline('+ PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2Uucm'
- 'VzdG9uLm1jaS5uZXQ=')
- r = yield
- if (r == b'dGltIGYxY2E2YmU0NjRiOWVmYT'
- b'FjY2E2ZmZkNmNmMmQ5ZjMy\r\n'):
- self._send_tagged(tag, 'OK', 'CRAM-MD5 successful')
- else:
- self._send_tagged(tag, 'NO', 'No access')
- client, _ = self._setup(AuthHandler)
- self.assertTrue('AUTH=CRAM-MD5' in client.capabilities)
+ client, _ = self._setup(AuthHandler_CRAM_MD5)
+ self.assertIn('AUTH=CRAM-MD5', client.capabilities)
ret, _ = client.login_cram_md5("tim", b"tanstaaftanstaaf")
self.assertEqual(ret, "OK")
@hashlib_helper.requires_hashdigest('md5', openssl=True)
def test_login_cram_md5_plain_text(self):
- class AuthHandler(SimpleIMAPHandler):
- capabilities = 'LOGINDISABLED AUTH=CRAM-MD5'
- def cmd_AUTHENTICATE(self, tag, args):
- self._send_textline('+ PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2Uucm'
- 'VzdG9uLm1jaS5uZXQ=')
- r = yield
- if (r == b'dGltIGYxY2E2YmU0NjRiOWVmYT'
- b'FjY2E2ZmZkNmNmMmQ5ZjMy\r\n'):
- self._send_tagged(tag, 'OK', 'CRAM-MD5 successful')
- else:
- self._send_tagged(tag, 'NO', 'No access')
- client, _ = self._setup(AuthHandler)
- self.assertTrue('AUTH=CRAM-MD5' in client.capabilities)
+ client, _ = self._setup(AuthHandler_CRAM_MD5)
+ self.assertIn('AUTH=CRAM-MD5', client.capabilities)
ret, _ = client.login_cram_md5("tim", "tanstaaftanstaaf")
self.assertEqual(ret, "OK")
+ def test_login_cram_md5_blocked(self):
+ def side_effect(*a, **kw):
+ raise ValueError
+
+ client, _ = self._setup(AuthHandler_CRAM_MD5)
+ self.assertIn('AUTH=CRAM-MD5', client.capabilities)
+ msg = re.escape("CRAM-MD5 authentication is not supported")
+ with (
+ mock.patch("hmac.HMAC", side_effect=side_effect),
+ self.assertRaisesRegex(imaplib.IMAP4.error, msg)
+ ):
+ client.login_cram_md5("tim", b"tanstaaftanstaaf")
+
def test_aborted_authentication(self):
class MyServer(SimpleIMAPHandler):
def cmd_AUTHENTICATE(self, tag, args):
diff --git
a/Misc/NEWS.d/next/Library/2025-07-13-11-20-05.gh-issue-136134.xhh0Kq.rst
b/Misc/NEWS.d/next/Library/2025-07-13-11-20-05.gh-issue-136134.xhh0Kq.rst
new file mode 100644
index 00000000000000..619526ab12bee2
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-07-13-11-20-05.gh-issue-136134.xhh0Kq.rst
@@ -0,0 +1,3 @@
+:meth:`IMAP4.login_cram_md5 <imaplib.IMAP4.login_cram_md5>` now raises an
+:exc:`IMAP4.error <imaplib.IMAP4.error>` if CRAM-MD5 authentication is not
+supported. Patch by Bénédikt Tran.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]