Skip to content

Commit e218344

Browse files
committed
imaplib: fix CRAM-MD5 on FIPS-only environments
1 parent 9e5cebd commit e218344

File tree

4 files changed

+44
-32
lines changed

4 files changed

+44
-32
lines changed

Doc/library/imaplib.rst

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

416+
.. versionchanged:: next
417+
An :exc:`IMAP4.error` is raised if MD5 support is not available.
418+
416419

417420
.. method:: IMAP4.logout()
418421

Lib/imaplib.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -725,9 +725,17 @@ def login_cram_md5(self, user, password):
725725
def _CRAM_MD5_AUTH(self, challenge):
726726
""" Authobject to use with CRAM-MD5 authentication. """
727727
import hmac
728-
pwd = (self.password.encode('utf-8') if isinstance(self.password, str)
729-
else self.password)
730-
return self.user + " " + hmac.HMAC(pwd, challenge, 'md5').hexdigest()
728+
729+
if isinstance(self.password, str):
730+
password = self.password.encode('utf-8')
731+
else:
732+
password = self.password
733+
734+
try:
735+
authcode = hmac.HMAC(password, challenge, 'md5')
736+
except ValueError: # HMAC-MD5 is not available
737+
raise self.error("CRAM-MD5 authentication is not supported")
738+
return f"{self.user} {authcode.hexdigest()}"
731739

732740

733741
def logout(self):

Lib/test/test_imaplib.py

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@
1212
import socket
1313

1414
from test.support import verbose, run_with_tz, run_with_locale, cpython_only
15-
from test.support import hashlib_helper
16-
from test.support import threading_helper
15+
from test.support import hashlib_helper, threading_helper
1716
import unittest
1817
from unittest import mock
1918
from datetime import datetime, timezone, timedelta
@@ -256,7 +255,20 @@ def cmd_IDLE(self, tag, args):
256255
self._send_tagged(tag, 'BAD', 'Expected DONE')
257256

258257

259-
class NewIMAPTestsMixin():
258+
class AuthHandler_CRAM_MD5(SimpleIMAPHandler):
259+
capabilities = 'LOGINDISABLED AUTH=CRAM-MD5'
260+
def cmd_AUTHENTICATE(self, tag, args):
261+
self._send_textline('+ PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2Uucm'
262+
'VzdG9uLm1jaS5uZXQ=')
263+
r = yield
264+
if (r == b'dGltIGYxY2E2YmU0NjRiOWVmYT'
265+
b'FjY2E2ZmZkNmNmMmQ5ZjMy\r\n'):
266+
self._send_tagged(tag, 'OK', 'CRAM-MD5 successful')
267+
else:
268+
self._send_tagged(tag, 'NO', 'No access')
269+
270+
271+
class NewIMAPTestsMixin:
260272
client = None
261273

262274
def _setup(self, imap_handler, connect=True):
@@ -439,40 +451,26 @@ def cmd_AUTHENTICATE(self, tag, args):
439451

440452
@hashlib_helper.requires_hashdigest('md5', openssl=True)
441453
def test_login_cram_md5_bytes(self):
442-
class AuthHandler(SimpleIMAPHandler):
443-
capabilities = 'LOGINDISABLED AUTH=CRAM-MD5'
444-
def cmd_AUTHENTICATE(self, tag, args):
445-
self._send_textline('+ PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2Uucm'
446-
'VzdG9uLm1jaS5uZXQ=')
447-
r = yield
448-
if (r == b'dGltIGYxY2E2YmU0NjRiOWVmYT'
449-
b'FjY2E2ZmZkNmNmMmQ5ZjMy\r\n'):
450-
self._send_tagged(tag, 'OK', 'CRAM-MD5 successful')
451-
else:
452-
self._send_tagged(tag, 'NO', 'No access')
453-
client, _ = self._setup(AuthHandler)
454-
self.assertTrue('AUTH=CRAM-MD5' in client.capabilities)
454+
client, _ = self._setup(AuthHandler_CRAM_MD5)
455+
self.assertIn('AUTH=CRAM-MD5', client.capabilities)
455456
ret, _ = client.login_cram_md5("tim", b"tanstaaftanstaaf")
456457
self.assertEqual(ret, "OK")
457458

458459
@hashlib_helper.requires_hashdigest('md5', openssl=True)
459460
def test_login_cram_md5_plain_text(self):
460-
class AuthHandler(SimpleIMAPHandler):
461-
capabilities = 'LOGINDISABLED AUTH=CRAM-MD5'
462-
def cmd_AUTHENTICATE(self, tag, args):
463-
self._send_textline('+ PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2Uucm'
464-
'VzdG9uLm1jaS5uZXQ=')
465-
r = yield
466-
if (r == b'dGltIGYxY2E2YmU0NjRiOWVmYT'
467-
b'FjY2E2ZmZkNmNmMmQ5ZjMy\r\n'):
468-
self._send_tagged(tag, 'OK', 'CRAM-MD5 successful')
469-
else:
470-
self._send_tagged(tag, 'NO', 'No access')
471-
client, _ = self._setup(AuthHandler)
472-
self.assertTrue('AUTH=CRAM-MD5' in client.capabilities)
461+
client, _ = self._setup(AuthHandler_CRAM_MD5)
462+
self.assertIn('AUTH=CRAM-MD5', client.capabilities)
473463
ret, _ = client.login_cram_md5("tim", "tanstaaftanstaaf")
474464
self.assertEqual(ret, "OK")
475465

466+
@hashlib_helper.block_algorithm("md5")
467+
def test_login_cram_md5_blocked(self):
468+
client, _ = self._setup(AuthHandler_CRAM_MD5)
469+
self.assertIn('AUTH=CRAM-MD5', client.capabilities)
470+
msg = re.escape("CRAM-MD5 authentication is not supported")
471+
with self.assertRaisesRegex(imaplib.IMAP4.error, msg):
472+
client.login_cram_md5("tim", b"tanstaaftanstaaf")
473+
476474
def test_aborted_authentication(self):
477475
class MyServer(SimpleIMAPHandler):
478476
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy