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