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