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 92 public String getAlgorithmName() 93 { 94 return cipher.getAlgorithmName() + "/CCM"; 95 } 96 97 public void processAADByte(byte in) 98 { 99 associatedText.write(in); 100 } 101 102 public void processAADBytes(byte[] in, int inOff, int len) 103 { 104 // TODO: Process AAD online 105 associatedText.write(in, inOff, len); 106 } 107 108 public int processByte(byte in, byte[] out, int outOff) 109 throws DataLengthException, IllegalStateException 110 { 111 data.write(in); 112 113 return 0; 114 } 115 116 public int processBytes(byte[] in, int inOff, int inLen, byte[] out, int outOff) 117 throws DataLengthException, IllegalStateException 118 { 119 data.write(in, inOff, inLen); 120 121 return 0; 122 } 123 124 public int doFinal(byte[] out, int outOff) 125 throws IllegalStateException, InvalidCipherTextException 126 { 127 byte[] text = data.toByteArray(); 128 byte[] enc = processPacket(text, 0, text.length); 129 130 System.arraycopy(enc, 0, out, outOff, enc.length); 131 132 reset(); 133 134 return enc.length; 135 } 136 137 public void reset() 138 { 139 cipher.reset(); 140 associatedText.reset(); 141 data.reset(); 142 } 143 144 /** 145 * Returns a byte array containing the mac calculated as part of the 146 * last encrypt or decrypt operation. 147 * 148 * @return the last mac calculated. 149 */ 150 public byte[] getMac() 151 { 152 byte[] mac = new byte[macSize]; 153 154 System.arraycopy(macBlock, 0, mac, 0, mac.length); 155 156 return mac; 157 } 158 159 public int getUpdateOutputSize(int len) 160 { 161 return 0; 162 } 163 164 public int getOutputSize(int len) 165 { 166 int totalData = len + data.size(); 167 168 if (forEncryption) 169 { 170 return totalData + macSize; 171 } 172 173 return totalData < macSize ? 0 : totalData - macSize; 174 } 175 176 public byte[] processPacket(byte[] in, int inOff, int inLen) 177 throws IllegalStateException, InvalidCipherTextException 178 { 179 // TODO: handle null keyParam (e.g. via RepeatedKeySpec) 180 // Need to keep the CTR and CBC Mac parts around and reset 181 if (keyParam == null) 182 { 183 throw new IllegalStateException("CCM cipher unitialized."); 184 } 185 186 BlockCipher ctrCipher = new SICBlockCipher(cipher); 187 byte[] iv = new byte[blockSize]; 188 byte[] out; 189 190 iv[0] = (byte)(((15 - nonce.length) - 1) & 0x7); 191 192 System.arraycopy(nonce, 0, iv, 1, nonce.length); 193 194 ctrCipher.init(forEncryption, new ParametersWithIV(keyParam, iv)); 195 196 if (forEncryption) 197 { 198 int index = inOff; 199 int outOff = 0; 200 201 out = new byte[inLen + macSize]; 202 203 calculateMac(in, inOff, inLen, macBlock); 204 205 ctrCipher.processBlock(macBlock, 0, macBlock, 0); // S0 206 207 while (index < inLen - blockSize) // S1... 208 { 209 ctrCipher.processBlock(in, index, out, outOff); 210 outOff += blockSize; 211 index += blockSize; 212 } 213 214 byte[] block = new byte[blockSize]; 215 216 System.arraycopy(in, index, block, 0, inLen - index); 217 218 ctrCipher.processBlock(block, 0, block, 0); 219 220 System.arraycopy(block, 0, out, outOff, inLen - index); 221 222 outOff += inLen - index; 223 224 System.arraycopy(macBlock, 0, out, outOff, out.length - outOff); 225 } 226 else 227 { 228 int index = inOff; 229 int outOff = 0; 230 231 out = new byte[inLen - macSize]; 232 233 System.arraycopy(in, inOff + inLen - macSize, macBlock, 0, macSize); 234 235 ctrCipher.processBlock(macBlock, 0, macBlock, 0); 236 237 for (int i = macSize; i != macBlock.length; i++) 238 { 239 macBlock[i] = 0; 240 } 241 242 while (outOff < out.length - blockSize) 243 { 244 ctrCipher.processBlock(in, index, out, outOff); 245 outOff += blockSize; 246 index += blockSize; 247 } 248 249 byte[] block = new byte[blockSize]; 250 251 System.arraycopy(in, index, block, 0, out.length - outOff); 252 253 ctrCipher.processBlock(block, 0, block, 0); 254 255 System.arraycopy(block, 0, out, outOff, out.length - outOff); 256 257 byte[] calculatedMacBlock = new byte[blockSize]; 258 259 calculateMac(out, 0, out.length, calculatedMacBlock); 260 261 if (!Arrays.constantTimeAreEqual(macBlock, calculatedMacBlock)) 262 { 263 throw new InvalidCipherTextException("mac check in CCM failed"); 264 } 265 } 266 267 return out; 268 } 269 270 private int calculateMac(byte[] data, int dataOff, int dataLen, byte[] macBlock) 271 { 272 Mac cMac = new CBCBlockCipherMac(cipher, macSize * 8); 273 274 cMac.init(keyParam); 275 276 // 277 // build b0 278 // 279 byte[] b0 = new byte[16]; 280 281 if (hasAssociatedText()) 282 { 283 b0[0] |= 0x40; 284 } 285 286 b0[0] |= (((cMac.getMacSize() - 2) / 2) & 0x7) << 3; 287 288 b0[0] |= ((15 - nonce.length) - 1) & 0x7; 289 290 System.arraycopy(nonce, 0, b0, 1, nonce.length); 291 292 int q = dataLen; 293 int count = 1; 294 while (q > 0) 295 { 296 b0[b0.length - count] = (byte)(q & 0xff); 297 q >>>= 8; 298 count++; 299 } 300 301 cMac.update(b0, 0, b0.length); 302 303 // 304 // process associated text 305 // 306 if (hasAssociatedText()) 307 { 308 int extra; 309 310 int textLength = getAssociatedTextLength(); 311 if (textLength < ((1 << 16) - (1 << 8))) 312 { 313 cMac.update((byte)(textLength >> 8)); 314 cMac.update((byte)textLength); 315 316 extra = 2; 317 } 318 else // can't go any higher than 2^32 319 { 320 cMac.update((byte)0xff); 321 cMac.update((byte)0xfe); 322 cMac.update((byte)(textLength >> 24)); 323 cMac.update((byte)(textLength >> 16)); 324 cMac.update((byte)(textLength >> 8)); 325 cMac.update((byte)textLength); 326 327 extra = 6; 328 } 329 330 if (initialAssociatedText != null) 331 { 332 cMac.update(initialAssociatedText, 0, initialAssociatedText.length); 333 } 334 if (associatedText.size() > 0) 335 { 336 byte[] tmp = associatedText.toByteArray(); 337 cMac.update(tmp, 0, tmp.length); 338 } 339 340 extra = (extra + textLength) % 16; 341 if (extra != 0) 342 { 343 for (int i = 0; i != 16 - extra; i++) 344 { 345 cMac.update((byte)0x00); 346 } 347 } 348 } 349 350 // 351 // add the text 352 // 353 cMac.update(data, dataOff, dataLen); 354 355 return cMac.doFinal(macBlock, 0); 356 } 357 358 private int getAssociatedTextLength() 359 { 360 return associatedText.size() + ((initialAssociatedText == null) ? 0 : initialAssociatedText.length); 361 } 362 363 private boolean hasAssociatedText() 364 { 365 return getAssociatedTextLength() > 0; 366 } 367 } 368