Skip to content

Commit

Permalink
Rule to check for insufficient key size to HMAC
Browse files Browse the repository at this point in the history
HMAC algorithms require a minimum key size that corresponds to
their digest size. Using a size less than the digest size is
considered weak.

Signed-off-by: Eric Brown <eric.brown@securesauce.dev>
  • Loading branch information
ericwb committed Mar 26, 2024
1 parent 3d06551 commit 901f0ed
Show file tree
Hide file tree
Showing 61 changed files with 482 additions and 45 deletions.
10 changes: 10 additions & 0 deletions precli/parsers/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,17 @@ def visit_assignment(self, nodes: list[Node]):
):
left_hand = self.resolve(nodes[0], default=nodes[0])
right_hand = self.resolve(nodes[2], default=nodes[2])

# This is in case a variable is reassigned
self.current_symtab.put(
nodes[0].text.decode(), tokens.IDENTIFIER, right_hand
)

# This is to help full resolution of an attribute/call
# TODO: reconsider how this is done. There are two entries for this
# assignment.
self.current_symtab.put(left_hand, tokens.IDENTIFIER, right_hand)

if nodes[2].type == tokens.CALL:
(call_args, call_kwargs) = self.get_func_args(
nodes[2].children[1]
Expand Down
199 changes: 199 additions & 0 deletions precli/rules/python/stdlib/hmac_weak_key.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
# Copyright 2024 Secure Saurce LLC
r"""
# Inadequate Encryption Strength Using Weak Keys in SSLContext
Using weak key sizes for cryptographic algorithms like Elliptic Curve can
compromise the security of your encryption and digital signatures. Here's
a brief overview of the risks associated with weak key sizes for this
algorithm:
Elliptic Curve cryptography provides strong security with relatively small
key sizes compared to RSA and DSA. However, even in the case of EC, using
weak curve parameters or small key sizes can expose you to vulnerabilities.
The strength of an EC key depends on the curve's properties and the size of
the prime used.
Recommended EC key sizes depend on the curve you select, but for modern
applications, curves like NIST P-256 (secp256r1) with a 256-bit key size
are considered secure. Larger curves, like NIST P-384 or P-521, can provide
even higher security margins.
## Example
```python
import ssl
context = ssl.SSLContext()
context.set_ecdh_curve("prime192v1")
```
## Remediation
Its recommended to increase the key size to at least 224 EC algorithms.
```python
import ssl
context = ssl.SSLContext()
context.set_ecdh_curve("prime256v1")
```
## See also
- [hmac — Keyed-Hashing for Message Authentication](https://docs.python.org/3/library/hmac.html)
- [CWE-326: Inadequate Encryption Strength](https://cwe.mitre.org/data/definitions/326.html)
_New in version 0.4.3_
""" # noqa: E501
from precli.core.call import Call
from precli.core.location import Location
from precli.core.result import Result
from precli.rules import Rule


HASH_NAME_SIZES = {
"blake2s": 32,
"blake2b": 64,
"sha224": 28,
"sha256": 32,
"sha384": 48,
"sha512": 64,
"sha3_224": 28,
"sha3_256": 32,
"sha3_384": 48,
"sha3_512": 64,
"sha512_224": 28,
"sha512_256": 32,
"shake_128": 0, # TODO
"shake_256": 0, # TODO
"sm3": 32,
}

HASHLIB_SIZES = {
"hashlib.blake2s": 32,
"hashlib.blake2b": 64,
"hashlib.sha224": 28,
"hashlib.sha256": 32,
"hashlib.sha384": 48,
"hashlib.sha512": 64,
"hashlib.sha3_224": 28,
"hashlib.sha3_256": 32,
"hashlib.sha3_384": 48,
"hashlib.sha3_512": 64,
"hashlib.sha512_224": 28,
"hashlib.sha512_256": 32,
"hashlib.shake_128": 0, # TODO
"hashlib.shake_256": 0, # TODO
"hashlib.sm3": 32,
}


class HmacWeakKey(Rule):
def __init__(self, id: str) -> None:
super().__init__(
id=id,
name="insufficient_key_size",
description=__doc__,
cwe_id=326,
message="The given key is only '{0}' bytes which is insufficient "
"for the '{2}' algorithm.",
wildcards={
"hashlib.*": [
"blake2s",
"blake2b",
"sha224",
"sha256",
"sha384",
"sha512",
"sha3_224",
"sha3_256",
"sha3_384",
"sha3_512",
"sha512_224",
"sha512_256",
"shake_128",
"shake_256",
"sm3",
],
"hmac.*": [
"new",
"digest",
],
},
)

def analyze_call(self, context: dict, call: Call) -> Result:
if call.name_qualified not in ("hmac.new", "hmac.digest"):
return

# new(key, msg=None, digestmod='')
# Create a new hashing object and return it.
#
# key: bytes or buffer, The starting key for the hash.
# msg: bytes or buffer, Initial input for the hash, or None.
# digestmod: A hash name suitable for hashlib.new(). *OR*
# A hashlib constructor returning a new hash object.
# *OR*
# A module supporting PEP 247.
#
# Required as of 3.8, despite its position after the
# optional
# msg argument. Passing it as a keyword argument is
# recommended, though not required for legacy API
# reasons.
#
# You can now feed arbitrary bytes into the object using its
# update() method, and can ask for the hash value at any time
# by calling its digest() or hexdigest() methods.
#
# digest(key, msg, digest)
# Fast inline implementation of HMAC.
#
# key: bytes or buffer, The key for the keyed hash object.
# msg: bytes or buffer, Input message.
# digest: A hash name suitable for hashlib.new() for best
# performance. *OR*
# A hashlib constructor returning a new hash object.
# *OR*
# A module supporting PEP 247.
if call.name_qualified == "hmac.new":
arg_name = "digestmod"
else:
arg_name = "digest"

arg0 = call.get_argument(position=0, name="key")
if arg0.value in (
"secrets.token_bytes",
"secrets.token_hex",
"secrets.token_urlsafe",
):
symbol = context["symtab"].get(arg0.node.text.decode())
lastcall = symbol.call_history[-1]
nbytes = lastcall.get_argument(position=0, name="nbytes").value
key_size = nbytes if nbytes is not None else 32
elif arg0.is_str:
key_size = len(arg0.value_str)
else:
return

arg2 = call.get_argument(position=2, name=arg_name)
if arg2.value in HASHLIB_SIZES:
digest = arg2.value
min_digest_size = HASHLIB_SIZES.get(digest)
elif arg2.is_str and arg2.value_str in HASH_NAME_SIZES:
digest = arg2.value_str
min_digest_size = HASH_NAME_SIZES.get(digest)
else:
return

if key_size >= min_digest_size:
return

return Result(
rule_id=self.id,
location=Location(node=arg0.node),
message=self.message.format(key_size, min_digest_size, digest),
)
2 changes: 1 addition & 1 deletion precli/rules/python/stdlib/secrets_weak_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def analyze_call(self, context: dict, call: Call) -> Result:
return

arg = call.get_argument(position=0, name="nbytes")
nbytes = int(arg.value) if arg.node else 32
nbytes = int(arg.value) if arg.value else 32

if nbytes < 32:
fixes = Rule.get_fixes(
Expand Down
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -145,5 +145,8 @@ precli.rules.python =
# precli/rules/python/stdlib/re_denial_of_service.py
PY033 = precli.rules.python.stdlib.re_denial_of_service:ReDenialOfService

# precli/rules/python/stdlib/hmac_weak_key.py
PY034 = precli.rules.python.stdlib.hmac_weak_key:HmacWeakKey

[build_sphinx]
all_files = 1
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# level: NONE
import hmac
import secrets


key = b"my-secret-key"
key = secrets.token_bytes(64)
message = b"Hello, world!"
hmac.digest(key, message, digest="blake2b")
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
import hmac


key = b"my-secret-key"
key = b"my-super-duper-secret-key-string"
message = b"Hello, world!"
hmac.digest(key, message, digest="blake2s")
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# level: NONE
import hashlib
import hmac
import secrets


key = b"my-secret-key"
key = secrets.token_bytes(64)
message = b"Hello, world!"
hmac.digest(key, message, digest=hashlib.blake2b)
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
import hmac


key = b"my-secret-key"
key = b"my-super-duper-secret-key-string"
message = b"Hello, world!"
hmac.digest(key, message, digest=hashlib.blake2s)
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
import hmac


key = b"my-secret-key"
key = b"my-super-duper-secret-key-string"
message = b"Hello, world!"
hmac.digest(key, message, digest=hashlib.sha224)
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
import hmac


key = b"my-secret-key"
key = b"my-super-duper-secret-key-string"
message = b"Hello, world!"
hmac.digest(key, message, digest=hashlib.sha256)
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# level: NONE
import hashlib
import hmac
from secrets import token_bytes


key = b"my-secret-key"
key = token_bytes(nbytes=48)
message = b"Hello, world!"
hmac.digest(key, message, digest=hashlib.sha384)
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
import hmac


key = b"my-secret-key"
key = b"my-super-duper-secret-key-string"
message = b"Hello, world!"
hmac.digest(key, message, digest=hashlib.sha3_224)
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
import hmac


key = b"my-secret-key"
key = b"my-super-duper-secret-key-string"
message = b"Hello, world!"
hmac.digest(key, message, digest=hashlib.sha3_256)
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# level: NONE
import hashlib
import hmac
import secrets


key = b"my-secret-key"
key = secrets.token_bytes(nbytes=48)
message = b"Hello, world!"
hmac.digest(key, message, digest=hashlib.sha3_384)
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# level: NONE
import hashlib
import hmac
import secrets


key = b"my-secret-key"
key = secrets.token_bytes(64)
message = b"Hello, world!"
hmac.digest(key, message, digest=hashlib.sha3_512)
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# level: NONE
import hashlib
import hmac
import secrets


key = b"my-secret-key"
key = secrets.token_bytes(64)
message = b"Hello, world!"
hmac.digest(key, message, digest=hashlib.sha512)
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
import hmac


key = b"my-secret-key"
key = b"my-super-duper-secret-key-string"
message = b"Hello, world!"
hmac.digest(key, message, digest="sha224")
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
import hmac


key = b"my-secret-key"
key = b"my-super-duper-secret-key-string"
message = b"Hello, world!"
hmac.digest(key, message, digest="sha256")
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# level: NONE
import hmac
from secrets import token_bytes


key = b"my-secret-key"
key = token_bytes(nbytes=48)
message = b"Hello, world!"
hmac.digest(key, message, digest="sha384")
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
import hmac


key = b"my-secret-key"
key = b"my-super-duper-secret-key-string"
message = b"Hello, world!"
hmac.digest(key, message, digest="sha3_224")
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# level: NONE
import hmac
import secrets


key = b"my-secret-key"
key = secrets.token_bytes()
message = b"Hello, world!"
hmac.digest(key, message, digest="sha3_256")
Loading

0 comments on commit 901f0ed

Please sign in to comment.