Skip to content

Commit 8c9b5c5

Browse files
Koan-Botclaude
andcommitted
test: add error-path and edge-case test coverage
Add t/error.t with 34 tests covering previously untested error paths: - Malformed PEM key loading (garbage, empty, undef, corrupted body) - Unrecognized public key format detection - Wrong/missing passphrase on encrypted keys - Public key operation restrictions (sign, decrypt, private_encrypt, check_key) - Corrupted/truncated/wrong-length ciphertext handling - Plaintext size boundary for OAEP padding (max and overflow) - Cross-key signature verification (sign with key1, verify with key2) - Empty message signing and verification - Truncated and extended signature rejection - Custom exponent key generation (3, 17, even exponent rejection) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7b04faa commit 8c9b5c5

1 file changed

Lines changed: 155 additions & 0 deletions

File tree

t/error.t

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
use strict;
2+
use Test::More;
3+
4+
use Crypt::OpenSSL::Random;
5+
use Crypt::OpenSSL::RSA;
6+
7+
Crypt::OpenSSL::Random::random_seed("OpenSSL needs at least 32 bytes.");
8+
Crypt::OpenSSL::RSA->import_random_seed();
9+
10+
my $rsa = Crypt::OpenSSL::RSA->generate_key(2048);
11+
my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($rsa->get_public_key_string());
12+
13+
# --- Malformed key loading ---
14+
15+
eval { Crypt::OpenSSL::RSA->new_private_key("not a key at all") };
16+
ok($@, "new_private_key croaks on garbage input");
17+
18+
eval { Crypt::OpenSSL::RSA->new_private_key("") };
19+
ok($@, "new_private_key croaks on empty string");
20+
21+
eval { Crypt::OpenSSL::RSA->new_private_key(undef) };
22+
ok($@, "new_private_key croaks on undef");
23+
24+
eval {
25+
Crypt::OpenSSL::RSA->new_private_key(
26+
"-----BEGIN RSA PRIVATE KEY-----\ngarbage\n-----END RSA PRIVATE KEY-----\n"
27+
);
28+
};
29+
ok($@, "new_private_key croaks on corrupted PEM body");
30+
31+
eval { Crypt::OpenSSL::RSA->_new_public_key_pkcs1("not a key") };
32+
ok($@, "_new_public_key_pkcs1 croaks on garbage input");
33+
34+
eval { Crypt::OpenSSL::RSA->_new_public_key_x509("not a key") };
35+
ok($@, "_new_public_key_x509 croaks on garbage input");
36+
37+
# --- Unrecognized public key format (Perl-level croak) ---
38+
39+
eval { Crypt::OpenSSL::RSA->new_public_key("-----BEGIN CERTIFICATE-----\nfoo\n-----END CERTIFICATE-----\n") };
40+
like($@, qr/unrecognized key format/, "new_public_key croaks on unrecognized PEM header");
41+
42+
eval { Crypt::OpenSSL::RSA->new_public_key("just plain text") };
43+
like($@, qr/unrecognized key format/, "new_public_key croaks on plain text");
44+
45+
# --- Wrong passphrase on encrypted key ---
46+
47+
my $encrypted_pem = $rsa->get_private_key_string("correct_passphrase", "aes-128-cbc");
48+
eval { Crypt::OpenSSL::RSA->new_private_key($encrypted_pem, "wrong_passphrase") };
49+
ok($@, "new_private_key croaks on wrong passphrase");
50+
51+
eval { Crypt::OpenSSL::RSA->new_private_key($encrypted_pem) };
52+
ok($@, "new_private_key croaks on encrypted key without passphrase");
53+
54+
# --- Public key cannot perform private operations ---
55+
56+
eval { $rsa_pub->sign("hello") };
57+
like($@, qr/Public keys cannot sign/i, "public key cannot sign");
58+
59+
eval { $rsa_pub->decrypt("hello") };
60+
like($@, qr/Public keys cannot decrypt/i, "public key cannot decrypt");
61+
62+
eval { $rsa_pub->private_encrypt("hello") };
63+
like($@, qr/Public keys cannot private_encrypt/i, "public key cannot private_encrypt");
64+
65+
eval { $rsa_pub->check_key() };
66+
like($@, qr/Public keys cannot be checked/i, "public key cannot check_key");
67+
68+
# --- Corrupted ciphertext ---
69+
70+
$rsa->use_pkcs1_oaep_padding();
71+
my $ciphertext = $rsa->encrypt("test message");
72+
73+
# Flip bits in ciphertext
74+
my $corrupted = $ciphertext;
75+
substr($corrupted, 10, 1) ^= "\xff";
76+
eval { $rsa->decrypt($corrupted) };
77+
ok($@, "decrypt croaks on corrupted ciphertext");
78+
79+
# Wrong-length ciphertext
80+
eval { $rsa->decrypt("too short") };
81+
ok($@, "decrypt croaks on wrong-length ciphertext");
82+
83+
eval { $rsa->decrypt("") };
84+
ok($@, "decrypt croaks on empty ciphertext");
85+
86+
# --- Plaintext too large for padding mode ---
87+
88+
$rsa->use_pkcs1_oaep_padding();
89+
my $max_oaep = $rsa->size() - 42;
90+
my $too_large = "x" x ($max_oaep + 1);
91+
eval { $rsa->encrypt($too_large) };
92+
ok($@, "encrypt croaks when plaintext exceeds OAEP max size");
93+
94+
# Exact max should work
95+
my $exact_max = "x" x $max_oaep;
96+
my $ct = eval { $rsa->encrypt($exact_max) };
97+
ok(!$@, "encrypt succeeds at exact OAEP max size");
98+
is(eval { $rsa->decrypt($ct) }, $exact_max, "round-trip at OAEP max size");
99+
100+
# --- Cross-key signature verification ---
101+
102+
my $rsa2 = Crypt::OpenSSL::RSA->generate_key(2048);
103+
$rsa->use_pkcs1_pss_padding();
104+
$rsa2->use_pkcs1_pss_padding();
105+
106+
my $sig = $rsa->sign("message to sign");
107+
ok(!$rsa2->verify("message to sign", $sig), "signature from key1 does not verify with key2");
108+
109+
my $rsa2_pub = Crypt::OpenSSL::RSA->new_public_key($rsa2->get_public_key_string());
110+
$rsa2_pub->use_pkcs1_pss_padding();
111+
ok(!$rsa2_pub->verify("message to sign", $sig), "signature from key1 does not verify with key2 public");
112+
113+
# --- Empty message signing ---
114+
115+
my $empty_sig = eval { $rsa->sign("") };
116+
ok(!$@, "sign succeeds on empty message");
117+
ok($rsa->verify("", $empty_sig), "verify succeeds on empty message signature");
118+
ok(!$rsa->verify("not empty", $empty_sig), "empty message signature does not verify different message");
119+
120+
# --- Truncated signature ---
121+
122+
my $full_sig = $rsa->sign("test data");
123+
my $truncated_sig = substr($full_sig, 0, length($full_sig) - 1);
124+
ok(!eval { $rsa->verify("test data", $truncated_sig) }, "truncated signature does not verify");
125+
126+
my $extended_sig = $full_sig . "\x00";
127+
ok(!eval { $rsa->verify("test data", $extended_sig) }, "extended signature does not verify");
128+
129+
# --- Key size boundary ---
130+
131+
my $small_rsa = eval { Crypt::OpenSSL::RSA->generate_key(512) };
132+
ok(!$@, "512-bit key generation succeeds");
133+
is($small_rsa->size() * 8, 512, "512-bit key has correct size");
134+
135+
# --- generate_key with custom exponent ---
136+
137+
my $rsa_e3 = eval { Crypt::OpenSSL::RSA->generate_key(2048, 3) };
138+
SKIP: {
139+
skip "OpenSSL rejected exponent 3", 2 if $@;
140+
ok($rsa_e3, "generate_key with exponent 3 succeeds");
141+
ok($rsa_e3->check_key(), "key with exponent 3 passes check_key");
142+
}
143+
144+
my $rsa_e17 = eval { Crypt::OpenSSL::RSA->generate_key(2048, 17) };
145+
SKIP: {
146+
skip "OpenSSL rejected exponent 17", 2 if $@;
147+
ok($rsa_e17, "generate_key with exponent 17 succeeds");
148+
ok($rsa_e17->check_key(), "key with exponent 17 passes check_key");
149+
}
150+
151+
# Even exponent should fail
152+
eval { Crypt::OpenSSL::RSA->generate_key(2048, 2) };
153+
ok($@, "generate_key croaks on even exponent");
154+
155+
done_testing;

0 commit comments

Comments
 (0)