Home | History | Annotate | Download | only in modes
      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