1package org.bouncycastle.crypto.modes;
2
3import org.bouncycastle.crypto.BlockCipher;
4import org.bouncycastle.crypto.BufferedBlockCipher;
5import org.bouncycastle.crypto.DataLengthException;
6import org.bouncycastle.crypto.InvalidCipherTextException;
7
8/**
9 * A Cipher Text Stealing (CTS) mode cipher. CTS allows block ciphers to
10 * be used to produce cipher text which is the same length as the plain text.
11 */
12public class CTSBlockCipher
13    extends BufferedBlockCipher
14{
15    private int     blockSize;
16
17    /**
18     * Create a buffered block cipher that uses Cipher Text Stealing
19     *
20     * @param cipher the underlying block cipher this buffering object wraps.
21     */
22    public CTSBlockCipher(
23        BlockCipher     cipher)
24    {
25        if ((cipher instanceof OFBBlockCipher) || (cipher instanceof CFBBlockCipher) || (cipher instanceof SICBlockCipher))
26        {
27            // TODO: This is broken - need to introduce marker interface to differentiate block cipher primitive from mode?
28            throw new IllegalArgumentException("CTSBlockCipher can only accept ECB, or CBC ciphers");
29        }
30
31        this.cipher = cipher;
32
33        blockSize = cipher.getBlockSize();
34
35        buf = new byte[blockSize * 2];
36        bufOff = 0;
37    }
38
39    /**
40     * return the size of the output buffer required for an update
41     * an input of len bytes.
42     *
43     * @param len the length of the input.
44     * @return the space required to accommodate a call to update
45     * with len bytes of input.
46     */
47    public int getUpdateOutputSize(
48        int len)
49    {
50        int total       = len + bufOff;
51        int leftOver    = total % buf.length;
52
53        if (leftOver == 0)
54        {
55            return total - buf.length;
56        }
57
58        return total - leftOver;
59    }
60
61    /**
62     * return the size of the output buffer required for an update plus a
63     * doFinal with an input of len bytes.
64     *
65     * @param len the length of the input.
66     * @return the space required to accommodate a call to update and doFinal
67     * with len bytes of input.
68     */
69    public int getOutputSize(
70        int len)
71    {
72        return len + bufOff;
73    }
74
75    /**
76     * process a single byte, producing an output block if necessary.
77     *
78     * @param in the input byte.
79     * @param out the space for any output that might be produced.
80     * @param outOff the offset from which the output will be copied.
81     * @return the number of output bytes copied to out.
82     * @exception DataLengthException if there isn't enough space in out.
83     * @exception IllegalStateException if the cipher isn't initialised.
84     */
85    public int processByte(
86        byte        in,
87        byte[]      out,
88        int         outOff)
89        throws DataLengthException, IllegalStateException
90    {
91        int         resultLen = 0;
92
93        if (bufOff == buf.length)
94        {
95            resultLen = cipher.processBlock(buf, 0, out, outOff);
96            System.arraycopy(buf, blockSize, buf, 0, blockSize);
97
98            bufOff = blockSize;
99        }
100
101        buf[bufOff++] = in;
102
103        return resultLen;
104    }
105
106    /**
107     * process an array of bytes, producing output if necessary.
108     *
109     * @param in the input byte array.
110     * @param inOff the offset at which the input data starts.
111     * @param len the number of bytes to be copied out of the input array.
112     * @param out the space for any output that might be produced.
113     * @param outOff the offset from which the output will be copied.
114     * @return the number of output bytes copied to out.
115     * @exception DataLengthException if there isn't enough space in out.
116     * @exception IllegalStateException if the cipher isn't initialised.
117     */
118    public int processBytes(
119        byte[]      in,
120        int         inOff,
121        int         len,
122        byte[]      out,
123        int         outOff)
124        throws DataLengthException, IllegalStateException
125    {
126        if (len < 0)
127        {
128            throw new IllegalArgumentException("Can't have a negative input length!");
129        }
130
131        int blockSize   = getBlockSize();
132        int length      = getUpdateOutputSize(len);
133
134        if (length > 0)
135        {
136            if ((outOff + length) > out.length)
137            {
138                throw new DataLengthException("output buffer too short");
139            }
140        }
141
142        int resultLen = 0;
143        int gapLen = buf.length - bufOff;
144
145        if (len > gapLen)
146        {
147            System.arraycopy(in, inOff, buf, bufOff, gapLen);
148
149            resultLen += cipher.processBlock(buf, 0, out, outOff);
150            System.arraycopy(buf, blockSize, buf, 0, blockSize);
151
152            bufOff = blockSize;
153
154            len -= gapLen;
155            inOff += gapLen;
156
157            while (len > blockSize)
158            {
159                System.arraycopy(in, inOff, buf, bufOff, blockSize);
160                resultLen += cipher.processBlock(buf, 0, out, outOff + resultLen);
161                System.arraycopy(buf, blockSize, buf, 0, blockSize);
162
163                len -= blockSize;
164                inOff += blockSize;
165            }
166        }
167
168        System.arraycopy(in, inOff, buf, bufOff, len);
169
170        bufOff += len;
171
172        return resultLen;
173    }
174
175    /**
176     * Process the last block in the buffer.
177     *
178     * @param out the array the block currently being held is copied into.
179     * @param outOff the offset at which the copying starts.
180     * @return the number of output bytes copied to out.
181     * @exception DataLengthException if there is insufficient space in out for
182     * the output.
183     * @exception IllegalStateException if the underlying cipher is not
184     * initialised.
185     * @exception InvalidCipherTextException if cipher text decrypts wrongly (in
186     * case the exception will never get thrown).
187     */
188    public int doFinal(
189        byte[]  out,
190        int     outOff)
191        throws DataLengthException, IllegalStateException, InvalidCipherTextException
192    {
193        if (bufOff + outOff > out.length)
194        {
195            throw new DataLengthException("output buffer to small in doFinal");
196        }
197
198        int     blockSize = cipher.getBlockSize();
199        int     len = bufOff - blockSize;
200        byte[]  block = new byte[blockSize];
201
202        if (forEncryption)
203        {
204            if (bufOff < blockSize)
205            {
206                throw new DataLengthException("need at least one block of input for CTS");
207            }
208
209            cipher.processBlock(buf, 0, block, 0);
210
211            if (bufOff > blockSize)
212            {
213                for (int i = bufOff; i != buf.length; i++)
214                {
215                    buf[i] = block[i - blockSize];
216                }
217
218                for (int i = blockSize; i != bufOff; i++)
219                {
220                    buf[i] ^= block[i - blockSize];
221                }
222
223                if (cipher instanceof CBCBlockCipher)
224                {
225                    BlockCipher c = ((CBCBlockCipher)cipher).getUnderlyingCipher();
226
227                    c.processBlock(buf, blockSize, out, outOff);
228                }
229                else
230                {
231                    cipher.processBlock(buf, blockSize, out, outOff);
232                }
233
234                System.arraycopy(block, 0, out, outOff + blockSize, len);
235            }
236            else
237            {
238                System.arraycopy(block, 0, out, outOff, blockSize);
239            }
240        }
241        else
242        {
243            if (bufOff < blockSize)
244            {
245                throw new DataLengthException("need at least one block of input for CTS");
246            }
247
248            byte[]  lastBlock = new byte[blockSize];
249
250            if (bufOff > blockSize)
251            {
252                if (cipher instanceof CBCBlockCipher)
253                {
254                    BlockCipher c = ((CBCBlockCipher)cipher).getUnderlyingCipher();
255
256                    c.processBlock(buf, 0, block, 0);
257                }
258                else
259                {
260                    cipher.processBlock(buf, 0, block, 0);
261                }
262
263                for (int i = blockSize; i != bufOff; i++)
264                {
265                    lastBlock[i - blockSize] = (byte)(block[i - blockSize] ^ buf[i]);
266                }
267
268                System.arraycopy(buf, blockSize, block, 0, len);
269
270                cipher.processBlock(block, 0, out, outOff);
271                System.arraycopy(lastBlock, 0, out, outOff + blockSize, len);
272            }
273            else
274            {
275                cipher.processBlock(buf, 0, block, 0);
276
277                System.arraycopy(block, 0, out, outOff, blockSize);
278            }
279        }
280
281        int offset = bufOff;
282
283        reset();
284
285        return offset;
286    }
287}
288