Skip to content

Rufuse large RSA keys #531

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions libp2p/crypto/rsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,49 @@
pkcs1_15,
)

from libp2p.crypto.exceptions import (
CryptographyError,
)
from libp2p.crypto.keys import (
KeyPair,
KeyType,
PrivateKey,
PublicKey,
)

MAX_RSA_KEY_SIZE = 4096


def validate_rsa_key_length(key_length: int) -> None:
"""
Validate that the RSA key length is positive and within the allowed maximum.

:param key_length: RSA key size in bits.
:raises CryptographyError:
If the key size is not positive or exceeds MAX_RSA_KEY_SIZE.
"""
if key_length <= 0:
raise CryptographyError("RSA key size must be positive")
if key_length > MAX_RSA_KEY_SIZE:
raise CryptographyError(
f"RSA key size {key_length} exceeds maximum allowed size {MAX_RSA_KEY_SIZE}"
)


def validate_rsa_key_size(key: RsaKey) -> None:
"""
Validate that an RSA key's size is within acceptable bounds.

:param key: The RSA key to validate.
:raises CryptographyError: If the key size is invalid.
"""
key_size = key.size_in_bits()
validate_rsa_key_length(key_size)


class RSAPublicKey(PublicKey):
def __init__(self, impl: RsaKey) -> None:
validate_rsa_key_size(impl)
self.impl = impl

def to_bytes(self) -> bytes:
Expand All @@ -27,6 +60,7 @@ def to_bytes(self) -> bytes:
@classmethod
def from_bytes(cls, key_bytes: bytes) -> "RSAPublicKey":
rsakey = RSA.import_key(key_bytes)
validate_rsa_key_size(rsakey)
return cls(rsakey)

def get_type(self) -> KeyType:
Expand All @@ -43,10 +77,12 @@ def verify(self, data: bytes, signature: bytes) -> bool:

class RSAPrivateKey(PrivateKey):
def __init__(self, impl: RsaKey) -> None:
validate_rsa_key_size(impl)
self.impl = impl

@classmethod
def new(cls, bits: int = 2048, e: int = 65537) -> "RSAPrivateKey":
validate_rsa_key_length(bits)
private_key_impl = RSA.generate(bits, e=e)
return cls(private_key_impl)

Expand Down
2 changes: 2 additions & 0 deletions newsfragments/523.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Added a maximum RSA key size limit of 4096 bits to prevent resource exhaustion attacks.Consolidated validation logic to use a single error message source and
added tests to catch invalid key sizes (including negative values).
30 changes: 30 additions & 0 deletions tests/core/crypto/test_rsa.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import pytest

from libp2p.crypto.exceptions import (
CryptographyError,
)
from libp2p.crypto.rsa import (
MAX_RSA_KEY_SIZE,
RSAPrivateKey,
validate_rsa_key_size,
)


def test_validate_rsa_key_size():
# Test valid key size
key = RSAPrivateKey.new(2048)
validate_rsa_key_size(key.impl)

# Test key size too large
with pytest.raises(
CryptographyError, match=f".*exceeds maximum allowed size {MAX_RSA_KEY_SIZE}"
):
RSAPrivateKey.new(MAX_RSA_KEY_SIZE + 1)

# Test negative key size (this would be caught when creating the key)
with pytest.raises(CryptographyError, match="RSA key size must be positive"):
RSAPrivateKey.new(-1)

# Test zero key size
with pytest.raises(CryptographyError, match="RSA key size must be positive"):
RSAPrivateKey.new(0)