Skip to content

Commit 63a0e8c

Browse files
committed
[3.13] pythongh-136134: imaplib: fix CRAM-MD5 on FIPS-only environments (pythonGH-136615)
(cherry picked from commit 4519b8a) Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
1 parent a73515e commit 63a0e8c

File tree

4 files changed

+34
-29
lines changed

4 files changed

+34
-29
lines changed

Doc/library/imaplib.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,9 @@ An :class:`IMAP4` instance has the following methods:
325325
the password. Will only work if the server ``CAPABILITY`` response includes the
326326
phrase ``AUTH=CRAM-MD5``.
327327

328+
.. versionchanged:: next
329+
An :exc:`IMAP4.error` is raised if MD5 support is not available.
330+
328331

329332
.. method:: IMAP4.logout()
330333

Lib/imaplib.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -635,9 +635,17 @@ def login_cram_md5(self, user, password):
635635
def _CRAM_MD5_AUTH(self, challenge):
636636
""" Authobject to use with CRAM-MD5 authentication. """
637637
import hmac
638-
pwd = (self.password.encode('utf-8') if isinstance(self.password, str)
639-
else self.password)
640-
return self.user + " " + hmac.HMAC(pwd, challenge, 'md5').hexdigest()
638+
639+
if isinstance(self.password, str):
640+
password = self.password.encode('utf-8')
641+
else:
642+
password = self.password
643+
644+
try:
645+
authcode = hmac.HMAC(password, challenge, 'md5')
646+
except ValueError: # HMAC-MD5 is not available
647+
raise self.error("CRAM-MD5 authentication is not supported")
648+
return f"{self.user} {authcode.hexdigest()}"
641649

642650

643651
def logout(self):

Lib/test/test_imaplib.py

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -391,40 +391,31 @@ def cmd_AUTHENTICATE(self, tag, args):
391391

392392
@hashlib_helper.requires_hashdigest('md5', openssl=True)
393393
def test_login_cram_md5_bytes(self):
394-
class AuthHandler(SimpleIMAPHandler):
395-
capabilities = 'LOGINDISABLED AUTH=CRAM-MD5'
396-
def cmd_AUTHENTICATE(self, tag, args):
397-
self._send_textline('+ PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2Uucm'
398-
'VzdG9uLm1jaS5uZXQ=')
399-
r = yield
400-
if (r == b'dGltIGYxY2E2YmU0NjRiOWVmYT'
401-
b'FjY2E2ZmZkNmNmMmQ5ZjMy\r\n'):
402-
self._send_tagged(tag, 'OK', 'CRAM-MD5 successful')
403-
else:
404-
self._send_tagged(tag, 'NO', 'No access')
405-
client, _ = self._setup(AuthHandler)
406-
self.assertTrue('AUTH=CRAM-MD5' in client.capabilities)
394+
client, _ = self._setup(AuthHandler_CRAM_MD5)
395+
self.assertIn('AUTH=CRAM-MD5', client.capabilities)
407396
ret, _ = client.login_cram_md5("tim", b"tanstaaftanstaaf")
408397
self.assertEqual(ret, "OK")
409398

410399
@hashlib_helper.requires_hashdigest('md5', openssl=True)
411400
def test_login_cram_md5_plain_text(self):
412-
class AuthHandler(SimpleIMAPHandler):
413-
capabilities = 'LOGINDISABLED AUTH=CRAM-MD5'
414-
def cmd_AUTHENTICATE(self, tag, args):
415-
self._send_textline('+ PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2Uucm'
416-
'VzdG9uLm1jaS5uZXQ=')
417-
r = yield
418-
if (r == b'dGltIGYxY2E2YmU0NjRiOWVmYT'
419-
b'FjY2E2ZmZkNmNmMmQ5ZjMy\r\n'):
420-
self._send_tagged(tag, 'OK', 'CRAM-MD5 successful')
421-
else:
422-
self._send_tagged(tag, 'NO', 'No access')
423-
client, _ = self._setup(AuthHandler)
424-
self.assertTrue('AUTH=CRAM-MD5' in client.capabilities)
401+
client, _ = self._setup(AuthHandler_CRAM_MD5)
402+
self.assertIn('AUTH=CRAM-MD5', client.capabilities)
425403
ret, _ = client.login_cram_md5("tim", "tanstaaftanstaaf")
426404
self.assertEqual(ret, "OK")
427405

406+
def test_login_cram_md5_blocked(self):
407+
def side_effect(*a, **kw):
408+
raise ValueError
409+
410+
client, _ = self._setup(AuthHandler_CRAM_MD5)
411+
self.assertIn('AUTH=CRAM-MD5', client.capabilities)
412+
msg = re.escape("CRAM-MD5 authentication is not supported")
413+
with (
414+
mock.patch("hmac.HMAC", side_effect=side_effect),
415+
self.assertRaisesRegex(imaplib.IMAP4.error, msg)
416+
):
417+
client.login_cram_md5("tim", b"tanstaaftanstaaf")
418+
428419
def test_aborted_authentication(self):
429420
class MyServer(SimpleIMAPHandler):
430421
def cmd_AUTHENTICATE(self, tag, args):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:meth:`IMAP4.login_cram_md5 <imaplib.IMAP4.login_cram_md5>` now raises an
2+
:exc:`IMAP4.error <imaplib.IMAP4.error>` if CRAM-MD5 authentication is not
3+
supported. Patch by Bénédikt Tran.

0 commit comments

Comments
 (0)