Skip to content

Fix RSA-OAEP to allow zero-length plaintext per RFC 8017#10012

Open
MarkAtwood wants to merge 2 commits into
wolfSSL:masterfrom
MarkAtwood:fix/rsa-oaep-allow-empty-plaintext
Open

Fix RSA-OAEP to allow zero-length plaintext per RFC 8017#10012
MarkAtwood wants to merge 2 commits into
wolfSSL:masterfrom
MarkAtwood:fix/rsa-oaep-allow-empty-plaintext

Conversation

@MarkAtwood
Copy link
Copy Markdown
Contributor

@MarkAtwood MarkAtwood commented Mar 19, 2026

Summary

Encrypt side: RsaPublicEncryptEx() rejected inLen == 0 unconditionally with BAD_FUNC_ARG. RFC 8017 Section 7.1.1 (RSAES-OAEP-ENCRYPT) permits zero-length messages: the message-length constraint mLen <= k - 2*hLen - 2 is satisfied by mLen = 0. Gate the inLen == 0 rejection on pad_type != WC_RSA_OAEP_PAD.

Decrypt side: RsaUnPad_OAEP() deliberately returned 0 on padding errors (as a chosen-ciphertext defense — see the ctMaskSelWord32 on the error path). This meant that padding errors and valid empty messages both produced ret == 0, making them indistinguishable. Previously this was fine because RsaPrivateDecryptEx() rejected all ret == 0 results. But naively allowing ret == 0 for OAEP (to support empty messages) would silently accept invalid OAEP ciphertext as a successful empty-message decryption.

Fix: Change the error sentinel in RsaUnPad_OAEP() from pkcsBlockLen to pkcsBlockLen + 1, so the return value on padding error becomes -1 (via unsigned wraparound to (int)(pkcsBlockLen - (pkcsBlockLen + 1))) instead of 0. Valid empty messages still return 0. In RsaPrivateDecryptEx(), use constant-time masking (ctMaskEq(pad_type, WC_RSA_OAEP_PAD)) to allow ret == 0 specifically for OAEP, while preserving the existing rejection of zero-length results for other padding types.

Constant-time properties preserved: The only change to RsaUnPad_OAEP is the value of the constant passed to ctMaskSelWord32 on the error path (pkcsBlockLen + 1 instead of pkcsBlockLen). The control flow, number of operations, and timing characteristics are identical. The *output pointer on the error path points one byte past the buffer end (pkcsBlock + pkcsBlockLen + 1), but this is safe because ret < 0 prevents any caller from dereferencing or copying from it (the copy path is guarded by ret >= 0).

Both OpenSSL and BoringSSL accept empty OAEP plaintexts. Found via Wycheproof test vectors.

Spec references

Test plan

  • RSA-OAEP encrypt/decrypt round-trip with zero-length plaintext succeeds
  • RSA-OAEP decrypt of invalid ciphertext returns error (not empty success)
  • Existing RSA-OAEP tests pass
  • Non-OAEP padding types still reject zero-length input
  • Run existing wolfSSL test suite

Copilot AI review requested due to automatic review settings March 19, 2026 02:11
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates RSA-OAEP handling to permit zero-length plaintexts on both encryption and decryption, aligning behavior with RFC 8017 and other TLS/crypto libraries.

Changes:

  • Relaxed RsaPublicEncryptEx() argument validation to allow inLen == 0 for OAEP padding.
  • Adjusted RsaPrivateDecryptEx() constant-time error handling to permit ret == 0 results for OAEP padding.
  • Added RFC 8017 references in inline comments to document the behavioral change.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread wolfcrypt/src/rsa.c Outdated
Comment thread wolfcrypt/src/rsa.c Outdated
Comment thread wolfcrypt/src/rsa.c Outdated
@MarkAtwood MarkAtwood force-pushed the fix/rsa-oaep-allow-empty-plaintext branch from eefa39a to a0752c3 Compare March 23, 2026 14:43
Copilot AI review requested due to automatic review settings April 12, 2026 05:20
@MarkAtwood MarkAtwood force-pushed the fix/rsa-oaep-allow-empty-plaintext branch from a0752c3 to e8e7cd1 Compare April 12, 2026 05:20
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates wolfCrypt RSA-OAEP handling to align with RFC 8017 by permitting zero-length plaintexts on encrypt and decrypt paths.

Changes:

  • Adjusts RsaPublicEncryptEx() argument validation to allow inLen == 0 for OAEP padding.
  • Adjusts RsaPrivateDecryptEx() handling of ret == 0 to allow zero-length OAEP results (under #ifndef WOLFSSL_RSA_DECRYPT_TO_0_LEN) using constant-time masking.
  • Adds inline RFC 8017 references in both code paths.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread wolfcrypt/src/rsa.c Outdated
Comment thread wolfcrypt/src/rsa.c Outdated
Comment thread wolfcrypt/src/rsa.c Outdated
Comment thread wolfcrypt/src/rsa.c Outdated
Comment thread wolfcrypt/src/rsa.c Outdated
@MarkAtwood MarkAtwood force-pushed the fix/rsa-oaep-allow-empty-plaintext branch from e8e7cd1 to 5cbeab1 Compare April 17, 2026 21:27
@MarkAtwood
Copy link
Copy Markdown
Contributor Author

/cc @wolfSSL-Fenrir-bot please review — particularly the constant-time masking in the decrypt path

Copy link
Copy Markdown
Member

@LinuxJedi LinuxJedi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MarkAtwood please remove the Claude co-authored-by in the commit message.

@MarkAtwood MarkAtwood force-pushed the fix/rsa-oaep-allow-empty-plaintext branch from 5cbeab1 to a8c6769 Compare June 3, 2026 21:18
Encrypt side: RsaPublicEncryptEx() rejected inLen==0 unconditionally.
RFC 8017 Section 7.1.1 permits zero-length messages (mLen=0 satisfies
the message-length bound mLen <= k - 2*hLen - 2). Gate the inLen==0
rejection on pad_type != WC_RSA_OAEP_PAD.

Decrypt side: RsaUnPad_OAEP() returned 0 on both padding errors and
valid empty messages, making them indistinguishable. Change the error
sentinel from pkcsBlockLen to pkcsBlockLen+1 so padding errors produce
ret==-1 (via unsigned wraparound) while valid empty messages produce
ret==0. In RsaPrivateDecryptEx(), use constant-time masking to allow
ret==0 specifically for WC_RSA_OAEP_PAD, preserving the existing
rejection of zero-length results for other padding types.

The constant-time properties of the OAEP unpadding are preserved: the
only change is the value of the error-path constant, not the control
flow or number of operations.

Both OpenSSL and BoringSSL accept empty OAEP plaintexts.
Found via Wycheproof test vectors.
@MarkAtwood MarkAtwood force-pushed the fix/rsa-oaep-allow-empty-plaintext branch from a8c6769 to 85f796a Compare June 4, 2026 00:21
@ColtonWilley
Copy link
Copy Markdown
Contributor

Jenkins retest this please

@MarkAtwood
Copy link
Copy Markdown
Contributor Author

Co-Authored-By line was already removed in the current commit. The CHANGES_REQUESTED is stale — ready for re-review.

- Tighten RFC 8017 comments to specify step 1b/3g scope
- Rename zeroOk to zeroOkMask (byte, not int) to match ctMask* API
- Add zero-length OAEP encrypt/decrypt round-trip test
- Verify corrupted OAEP ciphertext returns negative error
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.

5 participants