From 8bd4c2dccd2372fc89aa491e19b558fd63be08a6 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Fri, 16 Sep 2011 15:41:43 -0400 Subject: [PATCH 4/4] _hashlib: Add selftest for FIPS mode and "usedforsecurity=True" --- Lib/test/test_hashlib.py | 109 ++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 109 insertions(+), 0 deletions(-) diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py index 17d752b..a33cee3 100644 --- a/Lib/test/test_hashlib.py +++ b/Lib/test/test_hashlib.py @@ -9,6 +9,7 @@ import array import hashlib import itertools +import os import sys try: import threading @@ -22,6 +23,51 @@ from test.support import _4G, precisionbigmemtest # Were we compiled --with-pydebug or with #define Py_DEBUG? COMPILED_WITH_PYDEBUG = hasattr(sys, 'gettotalrefcount') +def make_fips_env(): + env = os.environ.copy() + env['OPENSSL_FORCE_FIPS_MODE']='1' + return env + +def openssl_can_enforce_fips(): + # Use the "openssl" command (if present) to try to determine if the local + # OpenSSL can be configured to enforce FIPS, by setting + # OPENSSL_FORCE_FIPS_MODE=1 + # in the environment of one of the command-line tools, and seeing if + # "md5" emits an "unknown cipher" error: + from subprocess import Popen, PIPE + + try: + p = Popen(['openssl', 'md5'], + stdin=PIPE, stdout=PIPE, stderr=PIPE, + env=make_fips_env()) + except OSError: + # "openssl" command not found + return False + stdout, stderr = p.communicate(input=b'abc') + return b'unknown cipher' in stderr +OPENSSL_CAN_ENFORCE_FIPS = openssl_can_enforce_fips() + +def run_command_with_fips_enforcement(pycmd): + """ + Run the given python code in a subprocess, with FIPS + enforcement enabled + + Return a (stdout, stderr) pair + """ + from subprocess import Popen, PIPE + + env = os.environ.copy() + env['OPENSSL_FORCE_FIPS_MODE']='1' + + try: + p = Popen([sys.executable, '-c', pycmd], + stdin=PIPE, stdout=PIPE, stderr=PIPE, + env=make_fips_env()) + except OSError: + # "openssl" command not found + return False + stdout, stderr = p.communicate(input=b'abc') + return stdout, stderr def hexstr(s): assert isinstance(s, bytes), repr(s) @@ -370,6 +416,69 @@ class HashLibTestCase(unittest.TestCase): self.assertEqual(expected_hash, hasher.hexdigest()) + def assertUnknownCipherInFipsMode(self, pycmd): + """ + Domain-specific assertion for verifying that the given command fails + gracefully when run in an environment that enforces FIPS compliance + """ + out, err = run_command_with_fips_enforcement(pycmd) + self.assertRegexpMatches(err, b'ValueError: .+unknown cipher') + + def assertSuccessInFipsMode(self, pycmd, expout): + """ + Verify that the given command succeeds in FIPS mode + """ + from test.support import strip_python_stderr + + out, err = run_command_with_fips_enforcement(pycmd) + err = strip_python_stderr(err) + self.assertEqual(err, b"") + self.assertEqual(out, expout) + + @unittest.skipUnless(OPENSSL_CAN_ENFORCE_FIPS, + 'FIPS enforcement required for this test.') + def test_hashopenssl_fips_mode(self): + # Verify the _hashlib module's handling of md5: + _hashlib = self._conditional_import_module('_hashlib') + + assert hasattr(_hashlib, 'openssl_md5') + + # Verify that in an environment in which OpenSSL forbids the usage of + # MD5 that _hashlib.openssl_md5 fails gracefully: + self.assertUnknownCipherInFipsMode( + 'import _hashlib; _hashlib.openssl_md5()') + + # Verify that we can override this using "usedforsecurity=False": + self.assertSuccessInFipsMode( + ('import _hashlib;\n' + 'm = _hashlib.openssl_md5(b"abc\\n",\n' + ' usedforsecurity=False)\n' + 'print(m.hexdigest())'), + b"0bee89b07a248e27c83fc3d5951213c1\n") + + self.assertSuccessInFipsMode( + ('import _hashlib;\n' + 'm = _hashlib.openssl_md5(usedforsecurity=False)\n' + 'm.update(b"abc\\n")\n' + 'print(m.hexdigest())'), + b"0bee89b07a248e27c83fc3d5951213c1\n") + + # Similarly for _hashlib.new('md5'): + self.assertUnknownCipherInFipsMode( + 'import _hashlib; _hashlib.new("md5")') + + self.assertSuccessInFipsMode( + ('import _hashlib;\n' + 'm = _hashlib.new("md5", b"abc\\n", usedforsecurity=False)\n' + 'print(m.hexdigest())'), + b"0bee89b07a248e27c83fc3d5951213c1\n") + self.assertSuccessInFipsMode( + ('import _hashlib;\n' + 'm = _hashlib.new("md5", usedforsecurity=False)\n' + 'm.update(b"abc\\n")\n' + 'print(m.hexdigest())'), + b"0bee89b07a248e27c83fc3d5951213c1\n") + def test_main(): support.run_unittest(HashLibTestCase) -- 1.7.6