From 7aac3512f2e1704625eafa59ff372c57946b422d Mon Sep 17 00:00:00 2001 From: Eric Brown Date: Fri, 10 Nov 2023 17:41:04 -0800 Subject: [PATCH] Add rule for PyOpenSSL RSA and DSA key generation (#147) * Checks function OpenSSL.crypto.PKey().generate_key * Looks for weak keys being initialized --------- Signed-off-by: Eric Brown Signed-off-by: Eric Brown --- .../python/third_party/PyYAML/yaml_load.py | 2 +- .../pycrypto/pycrypto_weak_cipher.py | 4 +- .../third_party/pycrypto/pycrypto_weak_key.py | 2 +- .../pycryptodomex_weak_cipher.py | 16 +-- .../pycryptodomex/pycryptodomex_weak_key.py | 6 +- .../{pyOpenSSL => pyopenssl}/__init__.py | 0 .../pyopenssl/pyopenssl_weak_key.py | 135 ++++++++++++++++++ .../requests/no_certificate_verify.py | 2 +- setup.cfg | 7 +- .../third_party/PyYAML/test_yaml_load.py | 2 +- .../examples/generate_key_dsa_1024.py | 4 + .../examples/generate_key_dsa_2048.py | 4 + .../examples/generate_key_dsa_4096.py | 4 + .../examples/generate_key_rsa_1024.py | 4 + .../examples/generate_key_rsa_2048.py | 4 + .../examples/generate_key_rsa_4096.py | 4 + .../requests/test_no_certificate_verify.py | 2 +- 17 files changed, 182 insertions(+), 20 deletions(-) rename precli/rules/python/third_party/{pyOpenSSL => pyopenssl}/__init__.py (100%) create mode 100644 precli/rules/python/third_party/pyopenssl/pyopenssl_weak_key.py create mode 100644 tests/unit/rules/python/third_party/pyopenssl/examples/generate_key_dsa_1024.py create mode 100644 tests/unit/rules/python/third_party/pyopenssl/examples/generate_key_dsa_2048.py create mode 100644 tests/unit/rules/python/third_party/pyopenssl/examples/generate_key_dsa_4096.py create mode 100644 tests/unit/rules/python/third_party/pyopenssl/examples/generate_key_rsa_1024.py create mode 100644 tests/unit/rules/python/third_party/pyopenssl/examples/generate_key_rsa_2048.py create mode 100644 tests/unit/rules/python/third_party/pyopenssl/examples/generate_key_rsa_4096.py diff --git a/precli/rules/python/third_party/PyYAML/yaml_load.py b/precli/rules/python/third_party/PyYAML/yaml_load.py index 564e826b..b18ea84e 100644 --- a/precli/rules/python/third_party/PyYAML/yaml_load.py +++ b/precli/rules/python/third_party/PyYAML/yaml_load.py @@ -44,7 +44,7 @@ .. seealso:: - - `Deserialization of Untrusted Data in the PyYAML Module `_ + - `Deserialization of Untrusted Data in the PyYAML Module `_ - `PyYAML Documentation `_ - `CWE-502: Deserialization of Untrusted Data `_ diff --git a/precli/rules/python/third_party/pycrypto/pycrypto_weak_cipher.py b/precli/rules/python/third_party/pycrypto/pycrypto_weak_cipher.py index c6f234d2..cce94e2a 100644 --- a/precli/rules/python/third_party/pycrypto/pycrypto_weak_cipher.py +++ b/precli/rules/python/third_party/pycrypto/pycrypto_weak_cipher.py @@ -75,7 +75,7 @@ key = b'Very long and confidential key' nonce = Random.new().read(16) - tempkey = SHA.new(key+nonce).digest() + tempkey = SHA.new(key + nonce).digest() cipher = ARC4.new(tempkey) msg = nonce + cipher.encrypt(b'Open the pod bay doors, HAL') @@ -97,7 +97,7 @@ key = b'Very long and confidential key' nonce = Random.new().read(16) - tempkey = SHA.new(key+nonce).digest() + tempkey = SHA.new(key + nonce).digest() cipher = AES.new(tempkey) msg = nonce + cipher.encrypt(b'Open the pod bay doors, HAL') diff --git a/precli/rules/python/third_party/pycrypto/pycrypto_weak_key.py b/precli/rules/python/third_party/pycrypto/pycrypto_weak_key.py index 9e776824..0540bb2f 100644 --- a/precli/rules/python/third_party/pycrypto/pycrypto_weak_key.py +++ b/precli/rules/python/third_party/pycrypto/pycrypto_weak_key.py @@ -124,7 +124,7 @@ def analyze(self, context: dict, **kwargs: dict) -> Result: message=self.message.format("DSA", 2048), fixes=fixes, ) - elif call.name_qualified in "Crypto.PublicKey.RSA.generate": + elif call.name_qualified == "Crypto.PublicKey.RSA.generate": argument = call.get_argument(position=0, name="bits") bits = argument.value diff --git a/precli/rules/python/third_party/pycryptodomex/pycryptodomex_weak_cipher.py b/precli/rules/python/third_party/pycryptodomex/pycryptodomex_weak_cipher.py index e6575222..55fed7aa 100644 --- a/precli/rules/python/third_party/pycryptodomex/pycryptodomex_weak_cipher.py +++ b/precli/rules/python/third_party/pycryptodomex/pycryptodomex_weak_cipher.py @@ -68,14 +68,14 @@ :linenos: :emphasize-lines: 9 - from Crypto.Cipher import ARC4 - from Crypto.Hash import SHA - from Crypto import Random + from Cryptodome.Cipher import ARC4 + from Cryptodome.Hash import SHA + from Cryptodome import Random key = b'Very long and confidential key' nonce = Random.new().read(16) - tempkey = SHA.new(key+nonce).digest() + tempkey = SHA.new(key + nonce).digest() cipher = ARC4.new(tempkey) msg = nonce + cipher.encrypt(b'Open the pod bay doors, HAL') @@ -90,14 +90,14 @@ :linenos: :emphasize-lines: 1,9 - from Crypto.Cipher import AES - from Crypto.Hash import SHA - from Crypto import Random + from Cryptodome.Cipher import AES + from Cryptodome.Hash import SHA + from Cryptodome import Random key = b'Very long and confidential key' nonce = Random.new().read(16) - tempkey = SHA.new(key+nonce).digest() + tempkey = SHA.new(key + nonce).digest() cipher = AES.new(tempkey) msg = nonce + cipher.encrypt(b'Open the pod bay doors, HAL') diff --git a/precli/rules/python/third_party/pycryptodomex/pycryptodomex_weak_key.py b/precli/rules/python/third_party/pycryptodomex/pycryptodomex_weak_key.py index 4f821f99..5197b020 100644 --- a/precli/rules/python/third_party/pycryptodomex/pycryptodomex_weak_key.py +++ b/precli/rules/python/third_party/pycryptodomex/pycryptodomex_weak_key.py @@ -39,7 +39,7 @@ :linenos: :emphasize-lines: 4 - from Crypto.PublicKey import DSA + from Cryptodome.PublicKey import DSA key = DSA.generate(1024) @@ -55,7 +55,7 @@ :linenos: :emphasize-lines: 4 - from Crypto.PublicKey import DSA + from Cryptodome.PublicKey import DSA key = DSA.generate(2048) @@ -124,7 +124,7 @@ def analyze(self, context: dict, **kwargs: dict) -> Result: message=self.message.format("DSA", 2048), fixes=fixes, ) - elif call.name_qualified in "Crypto.PublicKey.RSA.generate": + elif call.name_qualified == "Cryptodome.PublicKey.RSA.generate": argument = call.get_argument(position=0, name="bits") bits = argument.value diff --git a/precli/rules/python/third_party/pyOpenSSL/__init__.py b/precli/rules/python/third_party/pyopenssl/__init__.py similarity index 100% rename from precli/rules/python/third_party/pyOpenSSL/__init__.py rename to precli/rules/python/third_party/pyopenssl/__init__.py diff --git a/precli/rules/python/third_party/pyopenssl/pyopenssl_weak_key.py b/precli/rules/python/third_party/pyopenssl/pyopenssl_weak_key.py new file mode 100644 index 00000000..3a578080 --- /dev/null +++ b/precli/rules/python/third_party/pyopenssl/pyopenssl_weak_key.py @@ -0,0 +1,135 @@ +# Copyright 2023 Secure Saurce LLC +r""" +================================================================== +Inadequate Encryption Strength Using Weak Keys in PyOpenSSL Module +================================================================== + +Using weak key sizes for cryptographic algorithms like RSA and DSA can +compromise the security of your encryption and digital signatures. Here's +a brief overview of the risks associated with weak key sizes for these +algorithms: + +RSA (Rivest-Shamir-Adleman): +RSA is widely used for both encryption and digital signatures. Weak key sizes +in RSA can be vulnerable to factorization attacks, such as the famous RSA-129 +challenge, which was factored in 1994 after 17 years of effort. Using small +key sizes makes it easier for attackers to factor the modulus and recover +the private key. + +It's generally recommended to use RSA key sizes of 2048 bits or more for +security in the present day, with 3072 bits or higher being increasingly +preferred for long-term security. + +DSA (Digital Signature Algorithm): +DSA is used for digital signatures and relies on the discrete logarithm +problem. Using weak key sizes in DSA can make it susceptible to attacks that +involve solving the discrete logarithm problem, like the GNFS (General +Number Field Sieve) algorithm. + +For DSA, key sizes of 2048 bits or more are recommended for modern security. +Note that DSA is not as commonly used as RSA or ECC for new applications, and +ECDSA (Elliptic Curve Digital Signature Algorithm) is often preferred due to +its efficiency and strong security properties. + +------- +Example +------- + +.. code-block:: python + :linenos: + :emphasize-lines: 4 + + from Crypto.PublicKey import DSA + + + key = DSA.generate(1024) + +----------- +Remediation +----------- + +Its recommended to increase the key size to at least 2048 for DSA and RSA +algorithms. + +.. code-block:: python + :linenos: + :emphasize-lines: 4 + + from Crypto.PublicKey import DSA + + + key = DSA.generate(2048) + +.. seealso:: + + - `Inadequate Encryption Strength Using Weak Keys in PyOpenSSL Module `_ + - `crypto — Generic cryptographic module — pyOpenSSL documentation `_ + - `CWE-326: Inadequate Encryption Strength `_ + +.. versionadded:: 1.0.0 + +""" # noqa: E501 +from precli.core.config import Config +from precli.core.level import Level +from precli.core.location import Location +from precli.core.result import Result +from precli.rules import Rule + + +class PyopensslWeakKey(Rule): + def __init__(self, id: str): + super().__init__( + id=id, + name="inadequate_encryption_strength", + full_descr=__doc__, + cwe_id=326, + message="Using {} key sizes less than {} bits is considered " + "vulnerable to attacks.", + targets=("call"), + wildcards={ + "OpenSSL.crypto.*": [ + "PKey", + "TYPE_DSA", + ], + "OpenSSL.*": [ + "crypto.PKey", + "crypto.TYPE_DSA", + ], + }, + config=Config(enabled=False), + ) + + def analyze(self, context: dict, **kwargs: dict) -> Result: + call = kwargs.get("call") + + if call.name_qualified == "OpenSSL.crypto.PKey.generate_key": + arg0 = call.get_argument(position=0, name="type") + key_type = arg0.value + + if key_type == "OpenSSL.crypto.TYPE_DSA": + key = "DSA" + elif key_type == "OpenSSL.crypto.TYPE_RSA": + key = "RSA" + + arg1 = call.get_argument(position=1, name="bits") + bits = arg1.value + + if key in ["DSA", "RSA"] and bits < 2048: + fixes = Rule.get_fixes( + context=context, + deleted_location=Location(node=arg1.node), + description=f"Use a minimum key size of 2048 for " + f"{key_type} keys.", + inserted_content="2048", + ) + + return Result( + rule_id=self.id, + location=Location( + file_name=context["file_name"], + node=arg1.node, + ), + level=Level.ERROR if bits <= 1024 else Level.WARNING, + message=self.message.format(key_type, 2048), + fixes=fixes, + ) diff --git a/precli/rules/python/third_party/requests/no_certificate_verify.py b/precli/rules/python/third_party/requests/no_certificate_verify.py index d086df04..650c449b 100644 --- a/precli/rules/python/third_party/requests/no_certificate_verify.py +++ b/precli/rules/python/third_party/requests/no_certificate_verify.py @@ -45,7 +45,7 @@ .. seealso:: - - `Improper Certificate Validation Using Requests Module `_ + - `Improper Certificate Validation Using Requests Module `_ - `Requests HTTP for Humans™ `_ - `CWE-295: Improper Certificate Validation `_ diff --git a/setup.cfg b/setup.cfg index 0f28492e..5d94aa9d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -138,11 +138,14 @@ precli.rules.python = # precli/rules/python/third_party/pyopenssl/insecure_tls_method.py PRE0517 = precli.rules.python.third_party.pyopenssl.insecure_tls_method:InsecureTlsMethod + # precli/rules/python/third_party/pyopenssl/pyopenssl_weak_key.py + PRE0518 = precli.rules.python.third_party.pyopenssl.pyopenssl_weak_key:PyopensslWeakKey + # precli/rules/python/third_party/PyYAML/yaml_load.py - PRE0518 = precli.rules.python.third_party.PyYAML.yaml_load:YamlLoad + PRE0519 = precli.rules.python.third_party.PyYAML.yaml_load:YamlLoad # precli/rules/python/third_party/requests/no_certificate_verify.py - PRE0519 = precli.rules.python.third_party.requests.no_certificate_verify:NoCertificateVerify + PRE0520 = precli.rules.python.third_party.requests.no_certificate_verify:NoCertificateVerify [build_sphinx] all_files = 1 diff --git a/tests/unit/rules/python/third_party/PyYAML/test_yaml_load.py b/tests/unit/rules/python/third_party/PyYAML/test_yaml_load.py index 5d68450e..30025ebd 100644 --- a/tests/unit/rules/python/third_party/PyYAML/test_yaml_load.py +++ b/tests/unit/rules/python/third_party/PyYAML/test_yaml_load.py @@ -7,7 +7,7 @@ from tests.unit.rules.python import test_case -RULE_ID = "PRE0518" +RULE_ID = "PRE0519" class YamlLoadTests(test_case.TestCase): diff --git a/tests/unit/rules/python/third_party/pyopenssl/examples/generate_key_dsa_1024.py b/tests/unit/rules/python/third_party/pyopenssl/examples/generate_key_dsa_1024.py new file mode 100644 index 00000000..d29a3000 --- /dev/null +++ b/tests/unit/rules/python/third_party/pyopenssl/examples/generate_key_dsa_1024.py @@ -0,0 +1,4 @@ +from OpenSSL import crypto + + +crypto.PKey().generate_key(type=crypto.TYPE_DSA, bits=1024) diff --git a/tests/unit/rules/python/third_party/pyopenssl/examples/generate_key_dsa_2048.py b/tests/unit/rules/python/third_party/pyopenssl/examples/generate_key_dsa_2048.py new file mode 100644 index 00000000..a2d4c61c --- /dev/null +++ b/tests/unit/rules/python/third_party/pyopenssl/examples/generate_key_dsa_2048.py @@ -0,0 +1,4 @@ +from OpenSSL import crypto + + +crypto.PKey().generate_key(type=crypto.TYPE_DSA, bits=2048) diff --git a/tests/unit/rules/python/third_party/pyopenssl/examples/generate_key_dsa_4096.py b/tests/unit/rules/python/third_party/pyopenssl/examples/generate_key_dsa_4096.py new file mode 100644 index 00000000..f6d4d6bf --- /dev/null +++ b/tests/unit/rules/python/third_party/pyopenssl/examples/generate_key_dsa_4096.py @@ -0,0 +1,4 @@ +from OpenSSL import crypto + + +crypto.PKey().generate_key(type=crypto.TYPE_DSA, bits=4096) diff --git a/tests/unit/rules/python/third_party/pyopenssl/examples/generate_key_rsa_1024.py b/tests/unit/rules/python/third_party/pyopenssl/examples/generate_key_rsa_1024.py new file mode 100644 index 00000000..54654e7c --- /dev/null +++ b/tests/unit/rules/python/third_party/pyopenssl/examples/generate_key_rsa_1024.py @@ -0,0 +1,4 @@ +from OpenSSL import crypto + + +crypto.PKey().generate_key(type=crypto.TYPE_RSA, bits=1024) diff --git a/tests/unit/rules/python/third_party/pyopenssl/examples/generate_key_rsa_2048.py b/tests/unit/rules/python/third_party/pyopenssl/examples/generate_key_rsa_2048.py new file mode 100644 index 00000000..f8d97718 --- /dev/null +++ b/tests/unit/rules/python/third_party/pyopenssl/examples/generate_key_rsa_2048.py @@ -0,0 +1,4 @@ +from OpenSSL import crypto + + +crypto.PKey().generate_key(type=crypto.TYPE_RSA, bits=2048) diff --git a/tests/unit/rules/python/third_party/pyopenssl/examples/generate_key_rsa_4096.py b/tests/unit/rules/python/third_party/pyopenssl/examples/generate_key_rsa_4096.py new file mode 100644 index 00000000..84c7063c --- /dev/null +++ b/tests/unit/rules/python/third_party/pyopenssl/examples/generate_key_rsa_4096.py @@ -0,0 +1,4 @@ +from OpenSSL import crypto + + +crypto.PKey().generate_key(type=crypto.TYPE_RSA, bits=4096) diff --git a/tests/unit/rules/python/third_party/requests/test_no_certificate_verify.py b/tests/unit/rules/python/third_party/requests/test_no_certificate_verify.py index 9b6f132f..0689028d 100644 --- a/tests/unit/rules/python/third_party/requests/test_no_certificate_verify.py +++ b/tests/unit/rules/python/third_party/requests/test_no_certificate_verify.py @@ -7,7 +7,7 @@ from tests.unit.rules.python import test_case -RULE_ID = "PRE0519" +RULE_ID = "PRE0520" class NoCertificateVerifyTests(test_case.TestCase):