Skip to content

Subtract the tag length, not 16, in the CCMBlockCipher decryption payload limit check - #2499

Open
Arpan0995 wants to merge 1 commit into
bcgit:mainfrom
Arpan0995:ccm-decrypt-length-limit-tag-size
Open

Arpan0995 wants to merge 1 commit into
bcgit:mainfrom
Arpan0995:ccm-decrypt-length-limit-tag-size

Conversation

@Arpan0995

Copy link
Copy Markdown
Contributor

CCMBlockCipher.processPacket (core/src/main/java/org/bouncycastle/crypto/modes/CCMBlockCipher.java:278) refuses a payload of 2^(8q) bytes or more, q being 15 minus the nonce length (NIST SP 800-38C sec. A.1). On decryption it first subtracts inputAdjustment from inLen (:303), and on main that is the constant 1 /* flags */ + 15 /* n + q */ (:300), under the comment "input includes 16 additional bytes: CCM flags and n+q values." The decryption input is the payload followed by the tag, not B0, so the bytes to subtract are macSize. The two agree only for a 16 byte tag, which is all the boundary loop added with the fix for #1570 (5c19683) covers (CCMTest.java:165).

With a shorter tag t, decryption admits payloads of up to 2^(8q) + 15 - t bytes, where encryption stops at 2^(8q) - 1, and calculateMac (:411) writes that length into B0 with a loop (:435) that runs until the length is zero, not for q bytes, so the extra length byte overwrites the last nonce byte.

Reproduced on released 1.86 and the 1.87-SNAPSHOT beta (1.87.0.20737); main has the same source. With a 13 byte nonce (q = 2) and an 8 byte tag, a 65536 or 65543 byte payload tagged over that B0 decrypts to the plaintext through processPacket and processBytes/doFinal, the 65543 byte one also through the provider's AES/CCM/NoPadding. 65544 bytes is refused with IllegalStateException "CCM packet too large for choice of q", as is 65536 bytes on encryption for every tag length. Such a ciphertext needs a tag made under the key over that malformed B0, which BC's own encryption never produces, so this is a consistency fix, not a security one.

This change:

  • sets inputAdjustment = macSize on decryption (:300), so both directions apply the same limit and an over-limit ciphertext is refused before its tag is checked, with the IllegalStateException encryption already throws; and
  • adds payloadLengthLimitTest to CCMTest: the largest payload round-trips with a 64 bit tag, one byte more is refused on encryption, and a ciphertext carrying 2^16 payload bytes is refused on decryption before the tag check for tags of 32 to 128 bits.

A ciphertext in that window that fails its tag now gets IllegalStateException instead of InvalidCipherTextException "mac check in CCM failed" (AEADBadTagException through the provider), as longer input already does. Payloads within the limit, and all 16 byte tags, are unaffected.

The new test fails without the change (over-limit ciphertext reached the tag check, tag length 32) and passes with it; the existing CCMTest checks still pass. Across the core and prov RegressionTest.tests arrays only CCMTest changes status (without a bc-test-data checkout, the 22 core and 5 prov entries that need it could not run). :core:checkstyleMain is clean.

Base tree only: one CCMBlockCipher.java, no META-INF/versions copy, no module-info or OSGi change. A release-note entry is included, happy to move it to another block.

hubot pushed a commit that referenced this pull request Oct 23, 2026
… payload rather than the input less 16 bytes, and the B0 length field is written as exactly q bytes, relates to github #2499.
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.

1 participant