From 57341cb39c6644861ef51688f9e83e9d8b69c291 Mon Sep 17 00:00:00 2001 From: Eric Brown Date: Sun, 31 Dec 2023 21:48:13 -0800 Subject: [PATCH] Add Go rule for weak rsa and dsa key sizes (#190) Signed-off-by: Eric Brown --- precli/core/argument.py | 4 +- precli/parsers/go.py | 9 +- precli/rules/go/stdlib/crypto/weak_key.py | 160 ++++++++++++++++++ setup.cfg | 3 + .../stdlib/crypto/examples/weak_cipher_des.go | 5 + .../crypto/examples/weak_hash_md5_new.go | 5 + .../crypto/examples/weak_hash_sha1_new.go | 5 + .../crypto/examples/weak_key_dsa_1024.go | 41 +++++ .../crypto/examples/weak_key_rsa_1024.go | 39 +++++ 9 files changed, 263 insertions(+), 8 deletions(-) create mode 100644 precli/rules/go/stdlib/crypto/weak_key.py create mode 100644 tests/unit/rules/go/stdlib/crypto/examples/weak_key_dsa_1024.go create mode 100644 tests/unit/rules/go/stdlib/crypto/examples/weak_key_rsa_1024.go diff --git a/precli/core/argument.py b/precli/core/argument.py index bb5b9f4b..7553f5d9 100644 --- a/precli/core/argument.py +++ b/precli/core/argument.py @@ -21,9 +21,9 @@ def _get_func_ident(node: Node) -> Node: if node is None: return None # TODO(ericwb): does this function fail with nested calls? - if node.type == "attribute": + if node.type in ["attribute", "selector_expression"]: return Argument._get_func_ident(node.named_children[1]) - if node.type == "identifier": + if node.type in ["identifier", "field_identifier"]: return node @property diff --git a/precli/parsers/go.py b/precli/parsers/go.py index df8e9733..05d773c3 100644 --- a/precli/parsers/go.py +++ b/precli/parsers/go.py @@ -157,19 +157,16 @@ def literal_value(self, node: Node, default=None): value = symbol.value case "interpreted_string_literal": value = ast.literal_eval(nodetext) - case "string": - # TODO: bytes and f-type strings are messed up - value = ast.literal_eval(nodetext) - case "integer": + case "int_literal": # TODO: hex, octal, binary value = ast.literal_eval(nodetext) - case "float": + case "float_literal": value = float(nodetext) case "true": value = True case "false": value = False - case "none": + case "nil": value = None except ValueError: value = None diff --git a/precli/rules/go/stdlib/crypto/weak_key.py b/precli/rules/go/stdlib/crypto/weak_key.py new file mode 100644 index 00000000..60215219 --- /dev/null +++ b/precli/rules/go/stdlib/crypto/weak_key.py @@ -0,0 +1,160 @@ +# Copyright 2023 Secure Saurce LLC +r""" +================================================================ +Inadequate Encryption Strength Using Weak Keys in Crypto Package +================================================================ + +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: 10 + + package main + + import ( + "crypto/rand" + "crypto/rsa" + "log" + ) + + func main() { + privateKey, err := rsa.GenerateKey(rand.Reader, 1024) + if err != nil { + log.Fatalf("Failed to generate key: %v", err) + } + } + +----------- +Remediation +----------- + +Its recommended to increase the key size to at least 2048 for DSA and RSA +algorithms. + +.. code-block:: python + :linenos: + :emphasize-lines: 10 + + package main + + import ( + "crypto/rand" + "crypto/rsa" + "log" + ) + + func main() { + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + log.Fatalf("Failed to generate key: %v", err) + } + } + +.. seealso:: + + - `Inadequate Encryption Strength Using Weak Keys in Crypto Package `_ + - `dsa package - crypto_dsa - Go Packages `_ + - `rsa package - crypto_rsa - Go Packages `_ + - `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 WeakKey(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={}, + config=Config(enabled=False), + ) + + def analyze(self, context: dict, **kwargs: dict) -> Result: + call = kwargs.get("call") + + if call.name_qualified in ["crypto/dsa.GenerateParameters"]: + argument = call.get_argument(position=2) + sizes = argument.value + + if sizes == "crypto/dsa.L1024N160": + fixes = Rule.get_fixes( + context=context, + deleted_location=Location(node=argument.identifier_node), + description="Use a minimum key size of 2048 for DSA keys.", + inserted_content="L2048N224", + ) + + return Result( + rule_id=self.id, + location=Location( + file_name=context["file_name"], + node=argument.identifier_node, + ), + level=Level.ERROR, + message=self.message.format("DSA", 2048), + fixes=fixes, + ) + elif call.name_qualified in ["crypto/rsa.GenerateKey"]: + argument = call.get_argument(position=1) + bits = argument.value + + if bits < 2048: + fixes = Rule.get_fixes( + context=context, + deleted_location=Location(node=argument.node), + description="Use a minimum key size of 2048 for RSA keys.", + inserted_content="2048", + ) + + return Result( + rule_id=self.id, + location=Location( + file_name=context["file_name"], + node=argument.node, + ), + level=Level.ERROR if bits <= 1024 else Level.WARNING, + message=self.message.format("RSA", 2048), + fixes=fixes, + ) diff --git a/setup.cfg b/setup.cfg index 963e2317..85ad1d94 100644 --- a/setup.cfg +++ b/setup.cfg @@ -39,6 +39,9 @@ precli.rules.go = # precli/rules/go/stdlib/crypto/weak_hash.py GO002 = precli.rules.go.stdlib.crypto.weak_hash:WeakHash + # precli/rules/go/stdlib/crypto/weak_key.py + GO003 = precli.rules.go.stdlib.crypto.weak_key:WeakKey + # precli/rules/go/golang_org_x_crypto/ssh_insecure_ignore_hostkey.py GO501 = precli.rules.go.golang_org_x_crypto.ssh_insecure_ignore_hostkey:SshInsecureIgnoreHostKey diff --git a/tests/unit/rules/go/stdlib/crypto/examples/weak_cipher_des.go b/tests/unit/rules/go/stdlib/crypto/examples/weak_cipher_des.go index 9878a560..cf98061d 100644 --- a/tests/unit/rules/go/stdlib/crypto/examples/weak_cipher_des.go +++ b/tests/unit/rules/go/stdlib/crypto/examples/weak_cipher_des.go @@ -1,3 +1,8 @@ +// level: ERROR +// start_line: 19 +// end_line: 19 +// start_column: 38 +// end_column: 41 package main import ( diff --git a/tests/unit/rules/go/stdlib/crypto/examples/weak_hash_md5_new.go b/tests/unit/rules/go/stdlib/crypto/examples/weak_hash_md5_new.go index b12f6ba2..e9bcaaec 100644 --- a/tests/unit/rules/go/stdlib/crypto/examples/weak_hash_md5_new.go +++ b/tests/unit/rules/go/stdlib/crypto/examples/weak_hash_md5_new.go @@ -1,3 +1,8 @@ +// level: ERROR +// start_line: 14 +// end_line: 14 +// start_column: 38 +// end_column: 41 package main import ( diff --git a/tests/unit/rules/go/stdlib/crypto/examples/weak_hash_sha1_new.go b/tests/unit/rules/go/stdlib/crypto/examples/weak_hash_sha1_new.go index b7a1b6e0..c802a6a1 100644 --- a/tests/unit/rules/go/stdlib/crypto/examples/weak_hash_sha1_new.go +++ b/tests/unit/rules/go/stdlib/crypto/examples/weak_hash_sha1_new.go @@ -1,3 +1,8 @@ +// level: ERROR +// start_line: 9 +// end_line: 9 +// start_column: 38 +// end_column: 41 package main import ( diff --git a/tests/unit/rules/go/stdlib/crypto/examples/weak_key_dsa_1024.go b/tests/unit/rules/go/stdlib/crypto/examples/weak_key_dsa_1024.go new file mode 100644 index 00000000..f80e8c7a --- /dev/null +++ b/tests/unit/rules/go/stdlib/crypto/examples/weak_key_dsa_1024.go @@ -0,0 +1,41 @@ +// level: ERROR +// start_line: 21 +// end_line: 21 +// start_column: 38 +// end_column: 41 +package main + +import ( + "crypto/dsa" + "crypto/rand" + "fmt" + "log" + "math/big" +) + +func main() { + // Define DSA parameters + var params dsa.Parameters + + // Generate DSA parameters; here we choose a 1024-bit key size + if err := dsa.GenerateParameters(¶ms, rand.Reader, dsa.L1024N160); err != nil { + log.Fatalf("Failed to generate DSA parameters: %v", err) + } + + // Generate DSA keys + privateKey := new(dsa.PrivateKey) + privateKey.PublicKey.Parameters = params + if err := dsa.GenerateKey(privateKey, rand.Reader); err != nil { + log.Fatalf("Failed to generate DSA key: %v", err) + } + + // Extract the public key + publicKey := privateKey.PublicKey + + // Print the public key + fmt.Printf("Public Key:\n P:%s\n Q:%s\n G:%s\n Y:%s\n", + publicKey.P.String(), publicKey.Q.String(), publicKey.G.String(), publicKey.Y.String()) + + // Print the private key + fmt.Printf("Private Key:\n X:%s\n", privateKey.X.String()) +} diff --git a/tests/unit/rules/go/stdlib/crypto/examples/weak_key_rsa_1024.go b/tests/unit/rules/go/stdlib/crypto/examples/weak_key_rsa_1024.go new file mode 100644 index 00000000..0a6947c4 --- /dev/null +++ b/tests/unit/rules/go/stdlib/crypto/examples/weak_key_rsa_1024.go @@ -0,0 +1,39 @@ +// level: ERROR +// start_line: 18 +// end_line: 18 +// start_column: 38 +// end_column: 41 +package main + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "log" +) + +func main() { + // Generate the RSA key + privateKey, err := rsa.GenerateKey(rand.Reader, 1024) + if err != nil { + log.Fatalf("Failed to generate key: %v", err) + } + + // Extract the public key from the private key + publicKey := &privateKey.PublicKey + + // Encode the public key to PEM format + publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey) + if err != nil { + log.Fatalf("Failed to marshal public key: %v", err) + } + + publicKeyPEM := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PUBLIC KEY", + Bytes: publicKeyBytes, + }) + + // Print the public key + log.Println(string(publicKeyPEM)) +}