Skip to content

Commit e73dbc5

Browse files
committed
Fix CCM input length check
As reported by @CipherGato, CCM fails to decrypt a full-length message for n=13, q=2 (maximum length 65,535). This is because the input message already includes the flags and nonce components (B0 from SP800 38C). Correctly handle full-length CCM decryption. Resolves: #1570 Signed-off-by: Alexander Scheel <alexander.scheel@keyfactor.com>
1 parent 4520a1f commit e73dbc5

2 files changed

Lines changed: 58 additions & 2 deletions

File tree

core/src/main/java/org/bouncycastle/crypto/modes/CCMBlockCipher.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,9 +261,19 @@ public int processPacket(byte[] in, int inOff, int inLen, byte[] output, int out
261261
if (q < 4)
262262
{
263263
int limitLen = 1 << (8 * q);
264-
if (inLen >= limitLen)
264+
265+
// no input length adjustment for encryption
266+
int inputAdjustment = 0;
267+
268+
if (!forEncryption)
269+
{
270+
// input includes 16 additional bytes: CCM flags and n+q values.
271+
inputAdjustment = 1 /* flags */ + 15 /* n + q */;
272+
}
273+
274+
if ((inLen-inputAdjustment) >= limitLen)
265275
{
266-
throw new IllegalStateException("CCM packet too large for choice of q.");
276+
throw new IllegalStateException("CCM packet too large for choice of q");
267277
}
268278
}
269279

core/src/test/java/org/bouncycastle/crypto/test/CCMTest.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.bouncycastle.crypto.params.AEADParameters;
88
import org.bouncycastle.crypto.params.KeyParameter;
99
import org.bouncycastle.crypto.params.ParametersWithIV;
10+
import org.bouncycastle.util.Arrays;
1011
import org.bouncycastle.util.Strings;
1112
import org.bouncycastle.util.encoders.Hex;
1213
import org.bouncycastle.util.test.SimpleTest;
@@ -150,6 +151,51 @@ public void performTest()
150151
// expected
151152
}
152153

154+
// For small number of allowed blocks, validate boundary
155+
// conditions are properly handled. Zero and greater will
156+
// fail as size bound is a strict inequality.
157+
int[] offsets = new int[]{-10, -2, -1, 0, 1, 10};
158+
int[] ns = new int[]{13, 12};
159+
for (int n_len : ns)
160+
{
161+
for (int offset : offsets)
162+
{
163+
try
164+
{
165+
ccm.init(true, new AEADParameters(new KeyParameter(K1), 128, new byte[n_len]));
166+
167+
// Encrypt up to 2^(8q) + offset. Note that message length
168+
// must be strictly less than 2^(8q) so offset=0 will not
169+
// work (per SP 800-38C Section A.1 Length Requirements).
170+
int q = 15 - n_len;
171+
int size = 1 << (8*q);
172+
inBuf = new byte[size + offset];
173+
174+
outBuf = new byte[ccm.getOutputSize(inBuf.length)];
175+
len = ccm.processPacket(inBuf, 0, inBuf.length, outBuf, 0);
176+
177+
if (offset >= 0) {
178+
fail("expected to fail to encrypt boundary bytes n=" + n_len + "size=" + size + " offset=" + offset);
179+
} else {
180+
// Decrypt should also succeed if encryption succeeded.
181+
ccm.init(false, new AEADParameters(new KeyParameter(K1), 128, new byte[n_len]));
182+
out = ccm.processPacket(outBuf, 0, outBuf.length);
183+
184+
if (out.length != inBuf.length || !Arrays.areEqual(inBuf, out))
185+
{
186+
fail("encryption output incorrect");
187+
}
188+
}
189+
}
190+
catch (Exception e)
191+
{
192+
if (offset < 0) {
193+
fail("unexpected failure to encrypt boundary bytes n=" + n_len + " offset=" + offset + " msg=" + e.getMessage());
194+
}
195+
}
196+
}
197+
}
198+
153199
AEADTestUtil.testReset(this, new CCMBlockCipher(AESEngine.newInstance()), new CCMBlockCipher(AESEngine.newInstance()), new AEADParameters(new KeyParameter(K1), 32, N2));
154200
AEADTestUtil.testTampering(this, ccm, new AEADParameters(new KeyParameter(K1), 32, N2));
155201
AEADTestUtil.testOutputSizes(this, new CCMBlockCipher(AESEngine.newInstance()), new AEADParameters(

0 commit comments

Comments
 (0)