Skip to content

fix: re-enable PKCS#1 v1.5 padding for signatures#103

Merged
atoomic merged 2 commits intocpan-authors:mainfrom
toddr-bot:koan.toddr.bot/fix-pkcs1-signatures
Mar 17, 2026
Merged

fix: re-enable PKCS#1 v1.5 padding for signatures#103
atoomic merged 2 commits intocpan-authors:mainfrom
toddr-bot:koan.toddr.bot/fix-pkcs1-signatures

Conversation

@toddr-bot
Copy link
Copy Markdown
Contributor

@toddr-bot toddr-bot commented Mar 14, 2026

What

Re-enables use_pkcs1_padding() for sign()/verify() while keeping it blocked for encryption. Closes #61.

Why

v0.35 disabled PKCS#1 v1.5 entirely to mitigate the Marvin attack, but Marvin only affects decryption padding oracles — not signatures. This broke every CPAN module using RSASSA-PKCS1-v1.5 (RS256): Net::ACME2, Google::SAML::Response, Crypt::LE, JSON::WebToken, WWW::LetsEncrypt.

How

  • use_pkcs1_padding() sets RSA_PKCS1_PADDING again instead of croaking
  • sign()/verify() on OpenSSL 3.x respect the padding mode (RSA_PKCS1_PADDING passes through instead of being forced to PSS)
  • encrypt()/decrypt() croak with a clear Marvin attack message on all OpenSSL versions (moved before the #if block)
  • PSS-specific parameters (MGF1, salt length) only apply when RSA_PKCS1_PSS_PADDING is set

Testing

  • Full suite passes: 385 tests across 10 files (OpenSSL 1.1.1k)
  • PKCS#1 v1.5 sign/verify tested across all hash algorithms (md5, sha1, sha224, sha256, sha384, sha512, ripemd160)
  • Marvin croak verified for encrypt() with PKCS#1 v1.5 padding
  • Existing PSS and OAEP tests unchanged

🤖 Generated with Claude Code


Quality Report

Changes: 4 files changed, 54 insertions(+), 46 deletions(-)

Code scan: clean

Tests: passed (OK)

Branch hygiene: clean

Generated by Kōan post-mission quality pipeline

@atoomic
Copy link
Copy Markdown
Collaborator

atoomic commented Mar 16, 2026

@toddr-bot rebase

@Koan-Bot
Copy link
Copy Markdown
Contributor

PR Review — fix: re-enable PKCS#1 v1.5 padding for signatures

The PR correctly re-enables PKCS#1 v1.5 for signatures while keeping it blocked for encryption. The core logic is sound: the Marvin attack only affects decryption padding oracles, not signatures. The implementation places the encryption croak before memory allocation (no leak), and the sign/verify paths on OpenSSL 3.x properly pass through PKCS1 padding instead of forcing PSS. Documentation updates are accurate and thorough. The suggestions are non-blocking improvements. Merge-ready.


🟡 Important

1. Marvin croak blocks private_encrypt/public_decrypt with PKCS1 (RSA.xs, L330-333)
The PKCS1 croak in rsa_crypt() blocks all four operations that pass through this function: encrypt(), decrypt(), private_encrypt(), and public_decrypt(). While blocking encrypt/decrypt is correct, blocking private_encrypt()/public_decrypt() with PKCS1 may break users who relied on these for raw PKCS#1 v1.5 signature operations (the pre-sign() API pattern).

If this is intentional (steering users to sign()/verify()), the croak message should mention that private_encrypt()/public_decrypt() are also blocked. If unintentional, you'd need to check the is_encrypt context or the specific function pointer to distinguish encryption from raw signing.

The current croak message says "Use use_pkcs1_oaep_padding() for encryption, or use_pkcs1_padding() with sign()/verify()" which is good guidance but doesn't acknowledge the private_encrypt case.

if(p_rsa->padding == RSA_PKCS1_PADDING) {
    croak("PKCS#1 v1.5 padding for encryption is vulnerable to the Marvin attack. "
          "Use use_pkcs1_oaep_padding() for encryption, or use_pkcs1_padding() with sign()/verify().");
}

🟢 Suggestions

1. rsa_crypt still forces OAEP for non-NO_PADDING on 3.x (RSA.xs, L348-350)
After adding the PKCS1 croak guard, the condition at line 348 if (p_rsa->padding != RSA_NO_PADDING) { crypt_pad = RSA_PKCS1_OAEP_PADDING; } now only triggers for OAEP (since PKCS1 croaks and PSS croaks earlier at line 333). This is correct but somewhat implicit — the only reachable padding values here are RSA_NO_PADDING and RSA_PKCS1_OAEP_PADDING. A comment noting this would aid readability.

crypt_pad = p_rsa->padding;
if (p_rsa->padding != RSA_NO_PADDING) {
    crypt_pad = RSA_PKCS1_OAEP_PADDING;
}

2. pkcs1 pad value 11 is meaningless for sign-only padding (t/padding.t, L112)
The pad value in the %padding_methods hash is used as $rsa->size() - $pad to determine plaintext length for both encrypt and sign tests. For pkcs1 with sign => 1, encrypt => 0, the pad => 11 value is only used to compute the plaintext length passed to _Test_Sign_And_Verify. Since sign() hashes the plaintext anyway, the length doesn't matter — this works but the value 11 (PKCS1 encryption overhead) is misleading for a sign-only padding mode. Consider using pad => 1 or adding a comment explaining this is just a non-zero dummy.

'pkcs1'       => {'sign' => 1, 'encrypt' => 0, 'pad' => 11},

3. Missing test: encrypt() with PKCS1 should croak (t/padding.t, L99-100)
The test verifies that use_pkcs1_padding() no longer croaks (line 99), but there's no explicit test verifying that encrypt() croaks when PKCS1 padding is active. The %padding_methods hash has encrypt => 0 for pkcs1, which skips _Test_Encrypt_And_Decrypt, but the "Invalid signing methods" test block (line 144) only triggers when !$sign && $pad — which doesn't apply to pkcs1 anymore since sign is now 1.

Consider adding an explicit test like:

eval { $rsa->use_pkcs1_padding; $rsa->encrypt($plaintext); };
like($@, qr/Marvin/, 'encrypt with PKCS1 croaks with Marvin warning');

This would directly validate the security invariant.


Checklist

  • No hardcoded secrets
  • Input validation at boundaries
  • Resource leak on error paths
  • Security: Marvin attack mitigation preserved for encryption
  • Test coverage for new behavior
  • Test coverage for blocked behavior (encrypt+PKCS1 croak) — suggestion Format code-like text elements in POD as code #4

Summary

The PR correctly re-enables PKCS#1 v1.5 for signatures while keeping it blocked for encryption. The core logic is sound: the Marvin attack only affects decryption padding oracles, not signatures. The implementation places the encryption croak before memory allocation (no leak), and the sign/verify paths on OpenSSL 3.x properly pass through PKCS1 padding instead of forcing PSS. Documentation updates are accurate and thorough. The suggestions are non-blocking improvements. Merge-ready.


Automated review by Kōan

toddr-bot and others added 2 commits March 16, 2026 15:05
…s#61)

The Marvin attack (CVE-2023-6129) targets PKCS#1 v1.5 *decryption*
padding oracles, not signatures. Version 0.35 disabled use_pkcs1_padding()
entirely, breaking every CPAN module that signs with RSASSA-PKCS1-v1.5
(RS256): Net::ACME2, Google::SAML::Response, Crypt::LE, JSON::WebToken,
and others.

This commit:
- Re-enables use_pkcs1_padding() to set RSA_PKCS1_PADDING
- Respects PKCS#1 v1.5 in sign()/verify() on OpenSSL 3.x instead of
  forcing PSS for all non-NO_PADDING modes
- Blocks PKCS#1 v1.5 for encrypt()/decrypt() with a clear error
  message about the Marvin attack (all OpenSSL versions)
- Updates documentation and tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@toddr-bot
Copy link
Copy Markdown
Contributor Author

Rebase: fix: re-enable PKCS#1 v1.5 padding for signatures

Branch koan.toddr.bot/fix-pkcs1-signatures rebased onto main and force-pushed.

Diff: 4 files changed, 68 insertions(+), 56 deletions(-)

Review feedback was analyzed and applied.

Actions

  • Resolved merge conflicts (3 round(s))
  • Rebased koan.toddr.bot/fix-pkcs1-signatures onto upstream/main
  • Applied review feedback
  • Force-pushed koan.toddr.bot/fix-pkcs1-signatures to origin

Automated by Kōan

@toddr-bot toddr-bot force-pushed the koan.toddr.bot/fix-pkcs1-signatures branch from baddf44 to 790297b Compare March 16, 2026 15:07
@atoomic atoomic marked this pull request as ready for review March 16, 2026 23:02
@atoomic atoomic merged commit 83dcac2 into cpan-authors:main Mar 17, 2026
54 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Version 0.35 and 0.37 have broken several CPAN dependents

3 participants