Skip to content

Commit 8af7f56

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

File tree

3 files changed

+40
-32
lines changed

3 files changed

+40
-32
lines changed

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: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:mod:`imaplib`: raise an error if CRAM-MD5 authentication is not supported.
2+
Patch by Bénédikt Tran.

0 commit comments

Comments
 (0)