https://github.com/python/cpython/commit/3afc263c41b2a548b55163e398373698c5148d21
commit: 3afc263c41b2a548b55163e398373698c5148d21
branch: 3.13
author: Bénédikt Tran <[email protected]>
committer: picnixz <[email protected]>
date: 2025-08-23T13:01:36+02:00
summary:

[3.13] gh-136134: imaplib: fix CRAM-MD5 on FIPS-only environments (GH-136615) 
(#138055)

(cherry picked from commit 4519b8acb5a65df2af69e60b0d8c0a5df5bfb087)

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 a2dad58b00b9fa..dc4cac277459b2 100644
--- a/Doc/library/imaplib.rst
+++ b/Doc/library/imaplib.rst
@@ -325,6 +325,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 e337fe6471069f..eb440b6fc1ffbd 100644
--- a/Lib/imaplib.py
+++ b/Lib/imaplib.py
@@ -635,9 +635,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 d462a2cda5047a..f3ee57d7af3b16 100644
--- a/Lib/test/test_imaplib.py
+++ b/Lib/test/test_imaplib.py
@@ -208,7 +208,20 @@ def cmd_UNSELECT(self, tag, args):
             self._send_tagged(tag, 'BAD', 'No mailbox selected')
 
 
-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):
@@ -391,40 +404,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]

Reply via email to