Skip to content

Commit

Permalink
Add Go rule for weak rsa and dsa key sizes (#190)
Browse files Browse the repository at this point in the history
Signed-off-by: Eric Brown <eric.brown@securesauce.dev>
  • Loading branch information
ericwb committed Jan 1, 2024
1 parent 8995c71 commit 57341cb
Show file tree
Hide file tree
Showing 9 changed files with 263 additions and 8 deletions.
4 changes: 2 additions & 2 deletions precli/core/argument.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 3 additions & 6 deletions precli/parsers/go.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
160 changes: 160 additions & 0 deletions precli/rules/go/stdlib/crypto/weak_key.py
Original file line number Diff line number Diff line change
@@ -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 <https://docs.securesauce.dev/rules/GO003>`_
- `dsa package - crypto_dsa - Go Packages <https://pkg.go.dev/crypto/dsa#ParameterSizes>`_
- `rsa package - crypto_rsa - Go Packages <https://pkg.go.dev/crypto/rsa#GenerateKey>`_
- `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 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,
)
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 5 additions & 0 deletions tests/unit/rules/go/stdlib/crypto/examples/weak_cipher_des.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
// level: ERROR
// start_line: 19
// end_line: 19
// start_column: 38
// end_column: 41
package main

import (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
// level: ERROR
// start_line: 14
// end_line: 14
// start_column: 38
// end_column: 41
package main

import (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
// level: ERROR
// start_line: 9
// end_line: 9
// start_column: 38
// end_column: 41
package main

import (
Expand Down
41 changes: 41 additions & 0 deletions tests/unit/rules/go/stdlib/crypto/examples/weak_key_dsa_1024.go
Original file line number Diff line number Diff line change
@@ -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(&params, 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())
}
39 changes: 39 additions & 0 deletions tests/unit/rules/go/stdlib/crypto/examples/weak_key_rsa_1024.go
Original file line number Diff line number Diff line change
@@ -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))
}

0 comments on commit 57341cb

Please sign in to comment.