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