From ce0b19cfc673f5fad6a2696e8127c3d48add177d Mon Sep 17 00:00:00 2001 From: Matthew Gamble Date: Tue, 17 May 2022 01:28:12 +1000 Subject: [PATCH] Update DUO130 to support hashlib constructor usedforsecurity=False param (#42) Support for this arrived in Python 3.9, but this rule should be fully backwards-compatible with older versions of Python. --- CHANGELOG.md | 2 ++ dlint/linters/bad_hashlib_use.py | 30 +++++++++++++------ docs/linters/DUO130.md | 5 ++++ tests/test_bad_hashlib_use.py | 50 ++++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2665ca..654d457 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed +- Support `usedforsecurity=False` parameter to hashlib constructors ([#39](https://github.com/dlint-py/dlint/issues/39)) ## [0.12.0] - 2021-10-27 ### Added diff --git a/dlint/linters/bad_hashlib_use.py b/dlint/linters/bad_hashlib_use.py index 2ec6c2f..94556c4 100644 --- a/dlint/linters/bad_hashlib_use.py +++ b/dlint/linters/bad_hashlib_use.py @@ -7,10 +7,12 @@ unicode_literals, ) -from .helpers import bad_module_attribute_use +from .helpers import bad_kwarg_use +from .. import tree -class BadHashlibUseLinter(bad_module_attribute_use.BadModuleAttributeUseLinter): + +class BadHashlibUseLinter(bad_kwarg_use.BadKwargUseLinter): """This linter looks for unsafe use of the Python "hashlib" module. Use of md5|sha1 is known to have hash collision weaknesses. """ @@ -20,10 +22,20 @@ class BadHashlibUseLinter(bad_module_attribute_use.BadModuleAttributeUseLinter): _error_tmpl = 'DUO130 insecure use of "hashlib" module' @property - def illegal_module_attributes(self): - return { - 'hashlib': [ - 'md5', - 'sha1', - ], - } + def kwargs(self): + def missing_or_true(call, kwarg_name): + return ( + tree.kwarg_not_present(call, kwarg_name) + or tree.kwarg_true(call, kwarg_name) + ) + + bad_hash_algorithms = {"md5", "sha1"} + + return [ + { + "module_path": f"hashlib.{hash_algorithm}", + "kwarg_name": "usedforsecurity", + "predicate": missing_or_true, + } + for hash_algorithm in bad_hash_algorithms + ] diff --git a/docs/linters/DUO130.md b/docs/linters/DUO130.md index 90c5f78..3bd3faf 100644 --- a/docs/linters/DUO130.md +++ b/docs/linters/DUO130.md @@ -25,6 +25,10 @@ import hashlib sha256_hashed = hashlib.sha256(b"data").hexdigest() sha512_hashed = hashlib.sha512(b"data").hexdigest() blake2b_hashed = hashlib.blake2b(b"data").hexdigest() + +# Only supported in Python 3.9 and above +md5_hashed = hashlib.md5(b"data", usedforsecurity=False).hexdigest() +sha1_hashed = hashlib.sha1(b"data", usedforsecurity=False).hexdigest() # ... ``` @@ -35,3 +39,4 @@ Some algorithms have known hash collision weaknesses. ## Exceptions * Compatibility with systems that can only use MD5 or SHA1 and are not under your control +* Use cases that are related to checksumming, rather than cryptography. diff --git a/tests/test_bad_hashlib_use.py b/tests/test_bad_hashlib_use.py index c38e15d..2318b7d 100644 --- a/tests/test_bad_hashlib_use.py +++ b/tests/test_bad_hashlib_use.py @@ -45,6 +45,56 @@ def test_bad_hashlib_usage(self): assert result == expected + def test_hashlib_used_for_security(self): + python_node = self.get_ast_node( + """ + import hashlib + + var = 'echo "TEST"' + + m1 = hashlib.md5(usedforsecurity=True) + m2 = hashlib.sha1(usedforsecurity=True) + """ + ) + + linter = dlint.linters.BadHashlibUseLinter() + linter.visit(python_node) + + result = linter.get_results() + expected = [ + dlint.linters.base.Flake8Result( + lineno=6, + col_offset=5, + message=dlint.linters.BadHashlibUseLinter._error_tmpl + ), + dlint.linters.base.Flake8Result( + lineno=7, + col_offset=5, + message=dlint.linters.BadHashlibUseLinter._error_tmpl + ), + ] + + assert result == expected + + def test_hashlib_not_used_for_security(self): + python_node = self.get_ast_node( + """ + import hashlib + + var = 'echo "TEST"' + + m1 = hashlib.md5(usedforsecurity=False) + m2 = hashlib.sha1(usedforsecurity=False) + """ + ) + + linter = dlint.linters.BadHashlibUseLinter() + linter.visit(python_node) + + result = linter.get_results() + + assert len(result) == 0 + if __name__ == "__main__": unittest.main()