Skip to content
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
34 changes: 29 additions & 5 deletions RSA.xs
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ SV* rsa_crypt(rsaData* p_rsa, SV* p_from,
#else

SV* rsa_crypt(rsaData* p_rsa, SV* p_from,
int (*p_crypt)(int, const unsigned char*, unsigned char*, RSA*, int), int is_encrypt)
int (*p_crypt)(int, const unsigned char*, unsigned char*, RSA*, int), int public, int is_encrypt)
#endif
{
STRLEN from_length;
Expand All @@ -361,6 +361,30 @@ SV* rsa_crypt(rsaData* p_rsa, SV* p_from,
"Use use_pkcs1_oaep_padding() for encryption, or use_pkcs1_padding() with sign()/verify().");
}

/* Pre-validate plaintext length before calling OpenSSL.
Only applies to encryption direction (encrypt, private_encrypt),
not to decryption (decrypt, public_decrypt) where input is ciphertext. */
if (public == is_encrypt) {
int max_len = -1;
const char *pad_name = NULL;

if (p_rsa->padding == RSA_PKCS1_OAEP_PADDING) {
max_len = size - 42; /* 2 * SHA1_DIGEST_LENGTH + 2 */
pad_name = "OAEP";
} else if (p_rsa->padding == RSA_PKCS1_PADDING) {
max_len = size - 11; /* PKCS#1 v1.5 overhead */
pad_name = "PKCS#1 v1.5";
} else if (p_rsa->padding == RSA_NO_PADDING) {
max_len = size;
pad_name = "no";
}

if (max_len >= 0 && (int)from_length > max_len) {
croak("plaintext too long for key size with %s padding"
" (%d bytes max, got %d)", pad_name, max_len, (int)from_length);
}
}

#if OPENSSL_VERSION_NUMBER >= 0x30000000L

if(p_rsa->padding == RSA_PKCS1_PSS_PADDING) {
Expand Down Expand Up @@ -898,7 +922,7 @@ encrypt(p_rsa, p_plaintext)
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
RETVAL = rsa_crypt(p_rsa, p_plaintext, EVP_PKEY_encrypt, EVP_PKEY_encrypt_init, 1 /* public */, 1 /* is_encrypt */);
#else
RETVAL = rsa_crypt(p_rsa, p_plaintext, RSA_public_encrypt, 1 /* is_encrypt */);
RETVAL = rsa_crypt(p_rsa, p_plaintext, RSA_public_encrypt, 1 /* public */, 1 /* is_encrypt */);
#endif
OUTPUT:
RETVAL
Expand All @@ -915,7 +939,7 @@ decrypt(p_rsa, p_ciphertext)
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
RETVAL = rsa_crypt(p_rsa, p_ciphertext, EVP_PKEY_decrypt, EVP_PKEY_decrypt_init, 0 /* private */, 1 /* is_encrypt */);
#else
RETVAL = rsa_crypt(p_rsa, p_ciphertext, RSA_private_decrypt, 1 /* is_encrypt */);
RETVAL = rsa_crypt(p_rsa, p_ciphertext, RSA_private_decrypt, 0 /* private */, 1 /* is_encrypt */);
#endif
OUTPUT:
RETVAL
Expand All @@ -932,7 +956,7 @@ private_encrypt(p_rsa, p_plaintext)
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
RETVAL = rsa_crypt(p_rsa, p_plaintext, EVP_PKEY_sign, EVP_PKEY_sign_init, 0 /* private */, 0 /* is_encrypt */);
#else
RETVAL = rsa_crypt(p_rsa, p_plaintext, RSA_private_encrypt, 0 /* is_encrypt */);
RETVAL = rsa_crypt(p_rsa, p_plaintext, RSA_private_encrypt, 0 /* private */, 0 /* is_encrypt */);
#endif
OUTPUT:
RETVAL
Expand All @@ -945,7 +969,7 @@ public_decrypt(p_rsa, p_ciphertext)
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
RETVAL = rsa_crypt(p_rsa, p_ciphertext, EVP_PKEY_verify_recover, EVP_PKEY_verify_recover_init, 1 /*public */, 0 /* is_encrypt */);
#else
RETVAL = rsa_crypt(p_rsa, p_ciphertext, RSA_public_decrypt, 0 /* is_encrypt */);
RETVAL = rsa_crypt(p_rsa, p_ciphertext, RSA_public_decrypt, 1 /* public */, 0 /* is_encrypt */);
#endif
OUTPUT:
RETVAL
Expand Down
49 changes: 48 additions & 1 deletion t/crypto.t
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use Crypt::OpenSSL::RSA;
# Tests for encrypt/decrypt error paths, boundary conditions, and edge cases.
# These cover gaps not addressed by rsa.t or padding.t.

plan tests => 12;
plan tests => 20;

Crypt::OpenSSL::Random::random_seed("OpenSSL needs at least 32 bytes.");
Crypt::OpenSSL::RSA->import_random_seed();
Expand Down Expand Up @@ -119,3 +119,50 @@ $rsa->use_pkcs1_oaep_padding();
eval { $rsa->encrypt("test") };
ok($@, "PSS padding cannot be used for encryption");
}

# --- Plaintext length pre-validation error messages ---

# OAEP: clear message with byte counts
{
$rsa->use_pkcs1_oaep_padding();
my $too_long = "x" x ($oaep_max + 1);
eval { $rsa->encrypt($too_long) };
like($@, qr/plaintext too long for key size with OAEP padding/,
"OAEP oversized plaintext gives clear error message");
like($@, qr/\Q$oaep_max bytes max\E/,
"OAEP error includes max byte count");
my $got = $oaep_max + 1;
like($@, qr/got $got/,
"OAEP error includes actual byte count");
}

# PKCS#1 v1.5 via private_encrypt: clear message
{
$rsa->use_pkcs1_padding();
my $pkcs1_max = $key_size - 11;
my $too_long = "x" x ($pkcs1_max + 1);
eval { $rsa->private_encrypt($too_long) };
like($@, qr/plaintext too long for key size with PKCS#1 v1\.5 padding/,
"PKCS#1 v1.5 oversized plaintext gives clear error message");
like($@, qr/\Q$pkcs1_max bytes max\E/,
"PKCS#1 v1.5 error includes max byte count");
}

# no-padding: clear message
{
$rsa->use_no_padding();
eval { $rsa->encrypt("x" x ($key_size + 1)) };
like($@, qr/plaintext too long for key size with no padding/,
"no-padding oversized plaintext gives clear error message");
}

# Decrypt still works (no false positive from validation)
{
$rsa->use_pkcs1_oaep_padding();
my $ct = $rsa->encrypt("validation bypass test");
my $pt = eval { $rsa->decrypt($ct) };
ok(!$@, "decrypt not affected by plaintext length validation")
or diag $@;
is($pt, "validation bypass test",
"decrypt returns correct plaintext after validation change");
}
2 changes: 1 addition & 1 deletion t/error_queue.t
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@ like($second_error, qr/OpenSSL error: \S/, "second error has a meaningful OpenSS
# Trigger yet another failure after two eval-caught ones — error queue should be clean
eval { $rsa->encrypt("A" x 500) };
my $third_error = $@;
like($third_error, qr/too large|data greater|asym cipher failure/i,
like($third_error, qr/too large|data greater|asym cipher failure|plaintext too long/i,
"third error reports actual problem (data too large), not stale from earlier failures");
Loading