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[]                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