Skip to content

Commit

Permalink
Add rule for PyOpenSSL RSA and DSA key generation (#147)
Browse files Browse the repository at this point in the history
* Checks function OpenSSL.crypto.PKey().generate_key
* Looks for weak keys being initialized

---------

Signed-off-by: Eric Brown <[email protected]>
Signed-off-by: Eric Brown <[email protected]>
  • Loading branch information
ericwb authored Nov 11, 2023
1 parent fa4fd0f commit 7aac351
Show file tree
Hide file tree
Showing 17 changed files with 182 additions and 20 deletions.
2 changes: 1 addition & 1 deletion precli/rules/python/third_party/PyYAML/yaml_load.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
.. seealso::
- `Deserialization of Untrusted Data in the PyYAML Module <https://docs.securesauce.dev/rules/PRE0518>`_
- `Deserialization of Untrusted Data in the PyYAML Module <https://docs.securesauce.dev/rules/PRE0519>`_
- `PyYAML Documentation <https://pyyaml.org/wiki/PyYAMLDocumentation>`_
- `CWE-502: Deserialization of Untrusted Data <https://cwe.mitre.org/data/definitions/502.html>`_
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
:linenos:
:emphasize-lines: 4
from Crypto.PublicKey import DSA
from Cryptodome.PublicKey import DSA
key = DSA.generate(1024)
Expand All @@ -55,7 +55,7 @@
:linenos:
:emphasize-lines: 4
from Crypto.PublicKey import DSA
from Cryptodome.PublicKey import DSA
key = DSA.generate(2048)
Expand Down Expand Up @@ -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

Expand Down
135 changes: 135 additions & 0 deletions precli/rules/python/third_party/pyopenssl/pyopenssl_weak_key.py
Original file line number Diff line number Diff line change
@@ -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 <https://docs.securesauce.dev/rules/PRE0518>`_
- `crypto — Generic cryptographic module — pyOpenSSL documentation <https://www.pyopenssl.org/en/latest/api/crypto.html#pkey-objects>`_
- `CWE-326: Inadequate Encryption Strength <https://cwe.mitre.org/data/definitions/326.html>`_
.. 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,
)
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
.. seealso::
- `Improper Certificate Validation Using Requests Module <https://docs.securesauce.dev/rules/PRE0519>`_
- `Improper Certificate Validation Using Requests Module <https://docs.securesauce.dev/rules/PRE0520>`_
- `Requests HTTP for Humans™ <https://requests.readthedocs.io/en/latest/>`_
- `CWE-295: Improper Certificate Validation <https://cwe.mitre.org/data/definitions/295.html>`_
Expand Down
7 changes: 5 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from tests.unit.rules.python import test_case


RULE_ID = "PRE0518"
RULE_ID = "PRE0519"


class YamlLoadTests(test_case.TestCase):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from OpenSSL import crypto


crypto.PKey().generate_key(type=crypto.TYPE_DSA, bits=1024)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from OpenSSL import crypto


crypto.PKey().generate_key(type=crypto.TYPE_DSA, bits=2048)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from OpenSSL import crypto


crypto.PKey().generate_key(type=crypto.TYPE_DSA, bits=4096)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from OpenSSL import crypto


crypto.PKey().generate_key(type=crypto.TYPE_RSA, bits=1024)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from OpenSSL import crypto


crypto.PKey().generate_key(type=crypto.TYPE_RSA, bits=2048)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from OpenSSL import crypto


crypto.PKey().generate_key(type=crypto.TYPE_RSA, bits=4096)
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from tests.unit.rules.python import test_case


RULE_ID = "PRE0519"
RULE_ID = "PRE0520"


class NoCertificateVerifyTests(test_case.TestCase):
Expand Down

0 comments on commit 7aac351

Please sign in to comment.