1 package org.bouncycastle.crypto.modes; 2 3 import java.io.ByteArrayOutputStream; 4 5 import org.bouncycastle.crypto.BlockCipher; 6 import org.bouncycastle.crypto.CipherParameters; 7 import org.bouncycastle.crypto.DataLengthException; 8 import org.bouncycastle.crypto.InvalidCipherTextException; 9 import org.bouncycastle.crypto.Mac; 10 import org.bouncycastle.crypto.OutputLengthException; 11 import org.bouncycastle.crypto.macs.CBCBlockCipherMac; 12 import org.bouncycastle.crypto.params.AEADParameters; 13 import org.bouncycastle.crypto.params.ParametersWithIV; 14 import org.bouncycastle.util.Arrays; 15 16 /** 17 * Implements the Counter with Cipher Block Chaining mode (CCM) detailed in 18 * NIST Special Publication 800-38C. 19 * <p> 20 * <b>Note</b>: this mode is a packet mode - it needs all the data up front. 21 */ 22 public class CCMBlockCipher 23 implements AEADBlockCipher 24 { 25 private BlockCipher cipher; 26 private int blockSize; 27 private boolean forEncryption; 28 private byte[] nonce; 29 private byte[] initialAssociatedText; 30 private int macSize; 31 private CipherParameters keyParam; 32 private byte[] macBlock; 33 private ExposedByteArrayOutputStream associatedText = new ExposedByteArrayOutputStream(); 34 private ExposedByteArrayOutputStream data = new ExposedByteArrayOutputStream(); 35 36 /** 37 * Basic constructor. 38 * 39 * @param c the block cipher to be used. 40 */ CCMBlockCipher(BlockCipher c)41 public CCMBlockCipher(BlockCipher c) 42 { 43 this.cipher = c; 44 this.blockSize = c.getBlockSize(); 45 this.macBlock = new byte[blockSize]; 46 47 if (blockSize != 16) 48 { 49 throw new IllegalArgumentException("cipher required with a block size of 16."); 50 } 51 } 52 53 /** 54 * return the underlying block cipher that we are wrapping. 55 * 56 * @return the underlying block cipher that we are wrapping. 57 */ getUnderlyingCipher()58 public BlockCipher getUnderlyingCipher() 59 { 60 return cipher; 61 } 62 63 init(boolean forEncryption, CipherParameters params)64 public void init(boolean forEncryption, CipherParameters params) 65 throws IllegalArgumentException 66 { 67 this.forEncryption = forEncryption; 68 69 CipherParameters cipherParameters; 70 if (params instanceof AEADParameters) 71 { 72 AEADParameters param = (AEADParameters)params; 73 74 nonce = param.getNonce(); 75 initialAssociatedText = param.getAssociatedText(); 76 macSize = param.getMacSize() / 8; 77 cipherParameters = param.getKey(); 78 } 79 else if (params instanceof ParametersWithIV) 80 { 81 ParametersWithIV param = (ParametersWithIV)params; 82 83 nonce = param.getIV(); 84 initialAssociatedText = null; 85 macSize = macBlock.length / 2; 86 cipherParameters = param.getParameters(); 87 } 88 else 89 { 90 throw new IllegalArgumentException("invalid parameters passed to CCM: " + params.getClass().getName()); 91 } 92 93 // NOTE: Very basic support for key re-use, but no performance gain from it 94 if (cipherParameters != null) 95 { 96 keyParam = cipherParameters; 97 } 98 99 if (nonce == null || nonce.length < 7 || nonce.length > 13) 100 { 101 throw new IllegalArgumentException("nonce must have length from 7 to 13 octets"); 102 } 103 104 reset(); 105 } 106 getAlgorithmName()107 public String getAlgorithmName() 108 { 109 return cipher.getAlgorithmName() + "/CCM"; 110 } 111 processAADByte(byte in)112 public void processAADByte(byte in) 113 { 114 associatedText.write(in); 115 } 116 processAADBytes(byte[] in, int inOff, int len)117 public void processAADBytes(byte[] in, int inOff, int len) 118 { 119 // TODO: Process AAD online 120 associatedText.write(in, inOff, len); 121 } 122 processByte(byte in, byte[] out, int outOff)123 public int processByte(byte in, byte[] out, int outOff) 124 throws DataLengthException, IllegalStateException 125 { 126 data.write(in); 127 128 return 0; 129 } 130 processBytes(byte[] in, int inOff, int inLen, byte[] out, int outOff)131 public int processBytes(byte[] in, int inOff, int inLen, byte[] out, int outOff) 132 throws DataLengthException, IllegalStateException 133 { 134 if (in.length < (inOff + inLen)) 135 { 136 throw new DataLengthException("Input buffer too short"); 137 } 138 data.write(in, inOff, inLen); 139 140 return 0; 141 } 142 doFinal(byte[] out, int outOff)143 public int doFinal(byte[] out, int outOff) 144 throws IllegalStateException, InvalidCipherTextException 145 { 146 int len = processPacket(data.getBuffer(), 0, data.size(), out, outOff); 147 148 reset(); 149 150 return len; 151 } 152 reset()153 public void reset() 154 { 155 cipher.reset(); 156 associatedText.reset(); 157 data.reset(); 158 } 159 160 /** 161 * Returns a byte array containing the mac calculated as part of the 162 * last encrypt or decrypt operation. 163 * 164 * @return the last mac calculated. 165 */ getMac()166 public byte[] getMac() 167 { 168 byte[] mac = new byte[macSize]; 169 170 System.arraycopy(macBlock, 0, mac, 0, mac.length); 171 172 return mac; 173 } 174 getUpdateOutputSize(int len)175 public int getUpdateOutputSize(int len) 176 { 177 return 0; 178 } 179 getOutputSize(int len)180 public int getOutputSize(int len) 181 { 182 int totalData = len + data.size(); 183 184 if (forEncryption) 185 { 186 return totalData + macSize; 187 } 188 189 return totalData < macSize ? 0 : totalData - macSize; 190 } 191 192 /** 193 * Process a packet of data for either CCM decryption or encryption. 194 * 195 * @param in data for processing. 196 * @param inOff offset at which data starts in the input array. 197 * @param inLen length of the data in the input array. 198 * @return a byte array containing the processed input.. 199 * @throws IllegalStateException if the cipher is not appropriately set up. 200 * @throws InvalidCipherTextException if the input data is truncated or the mac check fails. 201 */ processPacket(byte[] in, int inOff, int inLen)202 public byte[] processPacket(byte[] in, int inOff, int inLen) 203 throws IllegalStateException, InvalidCipherTextException 204 { 205 byte[] output; 206 207 if (forEncryption) 208 { 209 output = new byte[inLen + macSize]; 210 } 211 else 212 { 213 if (inLen < macSize) 214 { 215 throw new InvalidCipherTextException("data too short"); 216 } 217 output = new byte[inLen - macSize]; 218 } 219 220 processPacket(in, inOff, inLen, output, 0); 221 222 return output; 223 } 224 225 /** 226 * Process a packet of data for either CCM decryption or encryption. 227 * 228 * @param in data for processing. 229 * @param inOff offset at which data starts in the input array. 230 * @param inLen length of the data in the input array. 231 * @param output output array. 232 * @param outOff offset into output array to start putting processed bytes. 233 * @return the number of bytes added to output. 234 * @throws IllegalStateException if the cipher is not appropriately set up. 235 * @throws InvalidCipherTextException if the input data is truncated or the mac check fails. 236 * @throws DataLengthException if output buffer too short. 237 */ processPacket(byte[] in, int inOff, int inLen, byte[] output, int outOff)238 public int processPacket(byte[] in, int inOff, int inLen, byte[] output, int outOff) 239 throws IllegalStateException, InvalidCipherTextException, DataLengthException 240 { 241 // TODO: handle null keyParam (e.g. via RepeatedKeySpec) 242 // Need to keep the CTR and CBC Mac parts around and reset 243 if (keyParam == null) 244 { 245 throw new IllegalStateException("CCM cipher unitialized."); 246 } 247 248 int n = nonce.length; 249 int q = 15 - n; 250 if (q < 4) 251 { 252 int limitLen = 1 << (8 * q); 253 if (inLen >= limitLen) 254 { 255 throw new IllegalStateException("CCM packet too large for choice of q."); 256 } 257 } 258 259 byte[] iv = new byte[blockSize]; 260 iv[0] = (byte)((q - 1) & 0x7); 261 System.arraycopy(nonce, 0, iv, 1, nonce.length); 262 263 BlockCipher ctrCipher = new SICBlockCipher(cipher); 264 ctrCipher.init(forEncryption, new ParametersWithIV(keyParam, iv)); 265 266 int outputLen; 267 int inIndex = inOff; 268 int outIndex = outOff; 269 270 if (forEncryption) 271 { 272 outputLen = inLen + macSize; 273 if (output.length < (outputLen + outOff)) 274 { 275 throw new OutputLengthException("Output buffer too short."); 276 } 277 278 calculateMac(in, inOff, inLen, macBlock); 279 280 byte[] encMac = new byte[blockSize]; 281 282 ctrCipher.processBlock(macBlock, 0, encMac, 0); // S0 283 284 while (inIndex < (inOff + inLen - blockSize)) // S1... 285 { 286 ctrCipher.processBlock(in, inIndex, output, outIndex); 287 outIndex += blockSize; 288 inIndex += blockSize; 289 } 290 291 byte[] block = new byte[blockSize]; 292 293 System.arraycopy(in, inIndex, block, 0, inLen + inOff - inIndex); 294 295 ctrCipher.processBlock(block, 0, block, 0); 296 297 System.arraycopy(block, 0, output, outIndex, inLen + inOff - inIndex); 298 299 System.arraycopy(encMac, 0, output, outOff + inLen, macSize); 300 } 301 else 302 { 303 if (inLen < macSize) 304 { 305 throw new InvalidCipherTextException("data too short"); 306 } 307 outputLen = inLen - macSize; 308 if (output.length < (outputLen + outOff)) 309 { 310 throw new OutputLengthException("Output buffer too short."); 311 } 312 313 System.arraycopy(in, inOff + outputLen, macBlock, 0, macSize); 314 315 ctrCipher.processBlock(macBlock, 0, macBlock, 0); 316 317 for (int i = macSize; i != macBlock.length; i++) 318 { 319 macBlock[i] = 0; 320 } 321 322 while (inIndex < (inOff + outputLen - blockSize)) 323 { 324 ctrCipher.processBlock(in, inIndex, output, outIndex); 325 outIndex += blockSize; 326 inIndex += blockSize; 327 } 328 329 byte[] block = new byte[blockSize]; 330 331 System.arraycopy(in, inIndex, block, 0, outputLen - (inIndex - inOff)); 332 333 ctrCipher.processBlock(block, 0, block, 0); 334 335 System.arraycopy(block, 0, output, outIndex, outputLen - (inIndex - inOff)); 336 337 byte[] calculatedMacBlock = new byte[blockSize]; 338 339 calculateMac(output, outOff, outputLen, calculatedMacBlock); 340 341 if (!Arrays.constantTimeAreEqual(macBlock, calculatedMacBlock)) 342 { 343 throw new InvalidCipherTextException("mac check in CCM failed"); 344 } 345 } 346 347 return outputLen; 348 } 349 calculateMac(byte[] data, int dataOff, int dataLen, byte[] macBlock)350 private int calculateMac(byte[] data, int dataOff, int dataLen, byte[] macBlock) 351 { 352 Mac cMac = new CBCBlockCipherMac(cipher, macSize * 8); 353 354 cMac.init(keyParam); 355 356 // 357 // build b0 358 // 359 byte[] b0 = new byte[16]; 360 361 if (hasAssociatedText()) 362 { 363 b0[0] |= 0x40; 364 } 365 366 b0[0] |= (((cMac.getMacSize() - 2) / 2) & 0x7) << 3; 367 368 b0[0] |= ((15 - nonce.length) - 1) & 0x7; 369 370 System.arraycopy(nonce, 0, b0, 1, nonce.length); 371 372 int q = dataLen; 373 int count = 1; 374 while (q > 0) 375 { 376 b0[b0.length - count] = (byte)(q & 0xff); 377 q >>>= 8; 378 count++; 379 } 380 381 cMac.update(b0, 0, b0.length); 382 383 // 384 // process associated text 385 // 386 if (hasAssociatedText()) 387 { 388 int extra; 389 390 int textLength = getAssociatedTextLength(); 391 if (textLength < ((1 << 16) - (1 << 8))) 392 { 393 cMac.update((byte)(textLength >> 8)); 394 cMac.update((byte)textLength); 395 396 extra = 2; 397 } 398 else // can't go any higher than 2^32 399 { 400 cMac.update((byte)0xff); 401 cMac.update((byte)0xfe); 402 cMac.update((byte)(textLength >> 24)); 403 cMac.update((byte)(textLength >> 16)); 404 cMac.update((byte)(textLength >> 8)); 405 cMac.update((byte)textLength); 406 407 extra = 6; 408 } 409 410 if (initialAssociatedText != null) 411 { 412 cMac.update(initialAssociatedText, 0, initialAssociatedText.length); 413 } 414 if (associatedText.size() > 0) 415 { 416 cMac.update(associatedText.getBuffer(), 0, associatedText.size()); 417 } 418 419 extra = (extra + textLength) % 16; 420 if (extra != 0) 421 { 422 for (int i = extra; i != 16; i++) 423 { 424 cMac.update((byte)0x00); 425 } 426 } 427 } 428 429 // 430 // add the text 431 // 432 cMac.update(data, dataOff, dataLen); 433 434 return cMac.doFinal(macBlock, 0); 435 } 436 getAssociatedTextLength()437 private int getAssociatedTextLength() 438 { 439 return associatedText.size() + ((initialAssociatedText == null) ? 0 : initialAssociatedText.length); 440 } 441 hasAssociatedText()442 private boolean hasAssociatedText() 443 { 444 return getAssociatedTextLength() > 0; 445 } 446 447 private class ExposedByteArrayOutputStream 448 extends ByteArrayOutputStream 449 { ExposedByteArrayOutputStream()450 public ExposedByteArrayOutputStream() 451 { 452 } 453 getBuffer()454 public byte[] getBuffer() 455 { 456 return this.buf; 457 } 458 } 459 } 460