From dcba92ca4e1443c9b4ed5a0de33995302f244b62 Mon Sep 17 00:00:00 2001 From: Toddr Bot Date: Mon, 30 Mar 2026 04:38:20 +0000 Subject: [PATCH 1/2] test: add PKCS#1 v1.5 signing regression tests (#146) Covers the RSASSA-PKCS1-v1_5 signing workflow used by ACME/Let's Encrypt clients (RS256). Verifies that use_pkcs1_padding() works for sign/verify while remaining blocked for encrypt (Marvin protection). Tests: sign/verify with SHA-256 and SHA-1, cross-padding rejection, Marvin guard for encryption, key PEM round-trip. Co-Authored-By: Claude Opus 4.6 --- MANIFEST | 1 + t/pkcs1_sign.t | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 t/pkcs1_sign.t diff --git a/MANIFEST b/MANIFEST index e7351c6..c43d26a 100644 --- a/MANIFEST +++ b/MANIFEST @@ -16,6 +16,7 @@ t/fakelib/Crypt/OpenSSL/Bignum.pm t/format.t t/key_lifecycle.t t/padding.t +t/pkcs1_sign.t t/private_crypt.t t/pss_auto_promote.t t/rsa.t diff --git a/t/pkcs1_sign.t b/t/pkcs1_sign.t new file mode 100644 index 0000000..76c77d9 --- /dev/null +++ b/t/pkcs1_sign.t @@ -0,0 +1,98 @@ +use strict; +use Test::More; + +use Crypt::OpenSSL::Random; +use Crypt::OpenSSL::RSA; + +# Regression tests for PKCS#1 v1.5 signing (RSASSA-PKCS1-v1_5). +# Issue #146: PKCS#1 v1.5 was disabled entirely in v0.35 to mitigate +# the Marvin attack, but the Marvin attack only affects decryption. +# PKCS#1 v1.5 signatures are secure and required by protocols like +# ACME/Let's Encrypt (RS256) and many JOSE/JWS implementations. + +plan tests => 10; + +Crypt::OpenSSL::Random::random_seed("OpenSSL needs at least 32 bytes."); +Crypt::OpenSSL::RSA->import_random_seed(); + +my $rsa = Crypt::OpenSSL::RSA->generate_key(2048); +my $pub_pem = $rsa->get_public_key_x509_string(); +my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($pub_pem); + +# --- use_pkcs1_padding() must not croak --- +eval { $rsa->use_pkcs1_padding() }; +ok(!$@, "use_pkcs1_padding() does not croak"); + +# --- SHA-256 sign/verify (RS256 — used by ACME/Let's Encrypt) --- +{ + $rsa->use_pkcs1_padding(); + $rsa->use_sha256_hash(); + $rsa_pub->use_pkcs1_padding(); + $rsa_pub->use_sha256_hash(); + + my $msg = '{"protected":"...","payload":"..."}'; + my $sig = $rsa->sign($msg); + ok(defined $sig && length($sig) > 0, + "PKCS#1 v1.5 + SHA-256 sign produces signature"); + + ok($rsa_pub->verify($msg, $sig), + "PKCS#1 v1.5 + SHA-256 signature verifies"); + + ok(!$rsa_pub->verify("tampered", $sig), + "PKCS#1 v1.5 + SHA-256 rejects tampered message"); +} + +# --- SHA-1 sign/verify (RS1 — legacy but still used) --- +SKIP: { + $rsa->use_pkcs1_padding(); + $rsa->use_sha1_hash(); + my $sig = eval { $rsa->sign("sha1 test") }; + skip "SHA-1 signing not available on this system", 2 if $@; + + ok(defined $sig, "PKCS#1 v1.5 + SHA-1 sign produces signature"); + + $rsa_pub->use_pkcs1_padding(); + $rsa_pub->use_sha1_hash(); + ok($rsa_pub->verify("sha1 test", $sig), + "PKCS#1 v1.5 + SHA-1 signature verifies"); +} + +# --- Cross-padding: sign with PKCS1, verify with PSS must fail --- +{ + $rsa->use_pkcs1_padding(); + $rsa->use_sha256_hash(); + my $sig = $rsa->sign("cross-padding test"); + + $rsa_pub->use_pkcs1_pss_padding(); + $rsa_pub->use_sha256_hash(); + my $result = eval { $rsa_pub->verify("cross-padding test", $sig) }; + ok(!$result, "PKCS1 signature does not verify with PSS padding"); +} + +# --- Encryption with PKCS1 must still croak (Marvin protection) --- +{ + $rsa->use_pkcs1_padding(); + eval { $rsa->encrypt("test") }; + like($@, qr/Marvin|vulnerable/i, + "PKCS#1 v1.5 encryption still blocked (Marvin)"); +} + +# --- Reload key from PEM and verify signature --- +{ + $rsa->use_pkcs1_padding(); + $rsa->use_sha256_hash(); + my $sig = $rsa->sign("persistence test"); + + my $priv_pem = $rsa->get_private_key_string(); + my $rsa2 = Crypt::OpenSSL::RSA->new_private_key($priv_pem); + $rsa2->use_pkcs1_padding(); + $rsa2->use_sha256_hash(); + ok($rsa2->verify("persistence test", $sig), + "signature verifies after key round-trip through PEM"); + + my $rsa_pub2 = Crypt::OpenSSL::RSA->new_public_key($pub_pem); + $rsa_pub2->use_pkcs1_padding(); + $rsa_pub2->use_sha256_hash(); + ok($rsa_pub2->verify("persistence test", $sig), + "signature verifies with fresh public key object"); +} From c9a018d594a92057c5e422fb47a06db667280d0c Mon Sep 17 00:00:00 2001 From: Timothy Legge Date: Mon, 6 Apr 2026 22:34:46 -0300 Subject: [PATCH 2/2] Implement OpenSSL version check in pkcs1_sign tests Add version check for OpenSSL to adjust PKCS1 padding usage in tests. In OpenSSL < 3.0 the padding defaults to PKCS1 for RSA_sign --- t/pkcs1_sign.t | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/t/pkcs1_sign.t b/t/pkcs1_sign.t index 76c77d9..4032a62 100644 --- a/t/pkcs1_sign.t +++ b/t/pkcs1_sign.t @@ -4,6 +4,10 @@ use Test::More; use Crypt::OpenSSL::Random; use Crypt::OpenSSL::RSA; +use Crypt::OpenSSL::Guess qw(openssl_version); + +my ($major, $minor, $patch) = openssl_version(); + # Regression tests for PKCS#1 v1.5 signing (RSASSA-PKCS1-v1_5). # Issue #146: PKCS#1 v1.5 was disabled entirely in v0.35 to mitigate # the Marvin attack, but the Marvin attack only affects decryption. @@ -58,7 +62,9 @@ SKIP: { } # --- Cross-padding: sign with PKCS1, verify with PSS must fail --- -{ +SKIP: { + skip "sign uses pkcs1_padding only on OpenSSL < 3.x", 1 + if $major < 3; $rsa->use_pkcs1_padding(); $rsa->use_sha256_hash(); my $sig = $rsa->sign("cross-padding test");