1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 #ifdef FREEBL_NO_DEPEND
6 #include "stubs.h"
7 #endif
8 #include "blapit.h"
9 #include "blapii.h"
10 #include "cts.h"
11 #include "secerr.h"
12 
13 struct CTSContextStr {
14     freeblCipherFunc cipher;
15     void *context;
16     /* iv stores the last ciphertext block of the previous message.
17      * Only used by decrypt. */
18     unsigned char iv[MAX_BLOCK_SIZE];
19 };
20 
21 CTSContext *
22 CTS_CreateContext(void *context, freeblCipherFunc cipher,
23                   const unsigned char *iv)
24 {
25     CTSContext *cts;
26 
27     cts = PORT_ZNew(CTSContext);
28     if (cts == NULL) {
29         return NULL;
30     }
31     PORT_Memcpy(cts->iv, iv, MAX_BLOCK_SIZE);
32     cts->cipher = cipher;
33     cts->context = context;
34     return cts;
35 }
36 
37 void
38 CTS_DestroyContext(CTSContext *cts, PRBool freeit)
39 {
40     if (freeit) {
41         PORT_Free(cts);
42     }
43 }
44 
45 /*
46  * See addemdum to NIST SP 800-38A
47  * Generically handle cipher text stealing. Basically this is doing CBC
48  * operations except someone can pass us a partial block.
49  *
50  *  Output Order:
51  *  CS-1:  C1||C2||C3..Cn-1(could be partial)||Cn   (NIST)
52  *  CS-2: pad == 0 C1||C2||C3...Cn-1(is full)||Cn   (Schneier)
53  *  CS-2: pad != 0 C1||C2||C3...Cn||Cn-1(is partial)(Schneier)
54  *  CS-3: C1||C2||C3...Cn||Cn-1(could be partial)   (Kerberos)
55  *
56  * The characteristics of these three options:
57  *  - NIST & Schneier (CS-1 & CS-2) are identical to CBC if there are no
58  * partial blocks on input.
59  *  - Scheier and Kerberos (CS-2 and CS-3) have no embedded partial blocks,
60  * which make decoding easier.
61  *  - NIST & Kerberos (CS-1 and CS-3) have consistent block order independent
62  * of padding.
63  *
64  * PKCS #11 did not specify which version to implement, but points to the NIST
65  * spec, so this code implements CTS-CS-1 from NIST.
66  *
67  * To convert the returned buffer to:
68  *   CS-2 (Schneier): do
69  *       unsigned char tmp[MAX_BLOCK_SIZE];
70  *       pad = *outlen % blocksize;
71  *       if (pad) {
72  *          memcpy(tmp, outbuf+*outlen-blocksize, blocksize);
73  *          memcpy(outbuf+*outlen-pad,outbuf+*outlen-blocksize-pad, pad);
74  *      memcpy(outbuf+*outlen-blocksize-pad, tmp, blocksize);
75  *       }
76  *   CS-3 (Kerberos): do
77  *       unsigned char tmp[MAX_BLOCK_SIZE];
78  *       pad = *outlen % blocksize;
79  *       if (pad == 0) {
80  *           pad = blocksize;
81  *       }
82  *       memcpy(tmp, outbuf+*outlen-blocksize, blocksize);
83  *       memcpy(outbuf+*outlen-pad,outbuf+*outlen-blocksize-pad, pad);
84  *   memcpy(outbuf+*outlen-blocksize-pad, tmp, blocksize);
85  */
86 SECStatus
87 CTS_EncryptUpdate(CTSContext *cts, unsigned char *outbuf,
88                   unsigned int *outlen, unsigned int maxout,
89                   const unsigned char *inbuf, unsigned int inlen,
90                   unsigned int blocksize)
91 {
92     unsigned char lastBlock[MAX_BLOCK_SIZE];
93     unsigned int tmp;
94     int fullblocks;
95     int written;
96     unsigned char *saveout = outbuf;
97     SECStatus rv;
98 
99     if (inlen < blocksize) {
100         PORT_SetError(SEC_ERROR_INPUT_LEN);
101         return SECFailure;
102     }
103 
104     if (maxout < inlen) {
105         *outlen = inlen;
106         PORT_SetError(SEC_ERROR_OUTPUT_LEN);
107         return SECFailure;
108     }
109     fullblocks = (inlen / blocksize) * blocksize;
110     rv = (*cts->cipher)(cts->context, outbuf, outlen, maxout, inbuf,
111                         fullblocks, blocksize);
112     if (rv != SECSuccess) {
113         return SECFailure;
114     }
115     *outlen = fullblocks; /* AES low level doesn't set outlen */
116     inbuf += fullblocks;
117     inlen -= fullblocks;
118     if (inlen == 0) {
119         return SECSuccess;
120     }
121     written = *outlen - (blocksize - inlen);
122     outbuf += written;
123     maxout -= written;
124 
125     /*
126      * here's the CTS magic, we pad our final block with zeros,
127      * then do a CBC encrypt. CBC will xor our plain text with
128      * the previous block (Cn-1), capturing part of that block (Cn-1**) as it
129      * xors with the zero pad. We then write this full block, overwritting
130      * (Cn-1**) in our buffer. This allows us to have input data == output
131      * data since Cn contains enough information to reconver Cn-1** when
132      * we decrypt (at the cost of some complexity as you can see in decrypt
133      * below */
134     PORT_Memcpy(lastBlock, inbuf, inlen);
135     PORT_Memset(lastBlock + inlen, 0, blocksize - inlen);
136     rv = (*cts->cipher)(cts->context, outbuf, &tmp, maxout, lastBlock,
137                         blocksize, blocksize);
138     PORT_Memset(lastBlock, 0, blocksize);
139     if (rv == SECSuccess) {
140         *outlen = written + blocksize;
141     } else {
142         PORT_Memset(saveout, 0, written + blocksize);
143     }
144     return rv;
145 }
146 
147 #define XOR_BLOCK(x, y, count)  \
148     for (i = 0; i < count; i++) \
149     x[i] = x[i] ^ y[i]
150 
151 /*
152  * See addemdum to NIST SP 800-38A
153  * Decrypt, Expect CS-1: input. See the comment on the encrypt side
154  * to understand what CS-2 and CS-3 mean.
155  *
156  * To convert the input buffer to CS-1 from ...
157  *   CS-2 (Schneier): do
158  *       unsigned char tmp[MAX_BLOCK_SIZE];
159  *       pad = inlen % blocksize;
160  *       if (pad) {
161  *          memcpy(tmp, inbuf+inlen-blocksize-pad, blocksize);
162  *          memcpy(inbuf+inlen-blocksize-pad,inbuf+inlen-pad, pad);
163  *      memcpy(inbuf+inlen-blocksize, tmp, blocksize);
164  *       }
165  *   CS-3 (Kerberos): do
166  *       unsigned char tmp[MAX_BLOCK_SIZE];
167  *       pad = inlen % blocksize;
168  *       if (pad == 0) {
169  *           pad = blocksize;
170  *       }
171  *       memcpy(tmp, inbuf+inlen-blocksize-pad, blocksize);
172  *       memcpy(inbuf+inlen-blocksize-pad,inbuf+inlen-pad, pad);
173  *   memcpy(inbuf+inlen-blocksize, tmp, blocksize);
174  */
175 SECStatus
176 CTS_DecryptUpdate(CTSContext *cts, unsigned char *outbuf,
177                   unsigned int *outlen, unsigned int maxout,
178                   const unsigned char *inbuf, unsigned int inlen,
179                   unsigned int blocksize)
180 {
181     unsigned char *Pn;
182     unsigned char Cn_2[MAX_BLOCK_SIZE]; /* block Cn-2 */
183     unsigned char Cn_1[MAX_BLOCK_SIZE]; /* block Cn-1 */
184     unsigned char Cn[MAX_BLOCK_SIZE];   /* block Cn   */
185     unsigned char lastBlock[MAX_BLOCK_SIZE];
186     const unsigned char *tmp;
187     unsigned char *saveout = outbuf;
188     unsigned int tmpLen;
189     unsigned int fullblocks, pad;
190     unsigned int i;
191     SECStatus rv;
192 
193     if (inlen < blocksize) {
194         PORT_SetError(SEC_ERROR_INPUT_LEN);
195         return SECFailure;
196     }
197 
198     if (maxout < inlen) {
199         *outlen = inlen;
200         PORT_SetError(SEC_ERROR_OUTPUT_LEN);
201         return SECFailure;
202     }
203 
204     fullblocks = (inlen / blocksize) * blocksize;
205 
206     /* even though we expect the input to be CS-1, CS-2 is easier to parse,
207      * so convert to CS-2 immediately. NOTE: this is the same code as in
208      * the comment for encrypt. NOTE2: since we can't modify inbuf unless
209      * inbuf and outbuf overlap, just copy inbuf to outbuf and modify it there
210      */
211     pad = inlen - fullblocks;
212     if (pad != 0) {
213         if (inbuf != outbuf) {
214             memcpy(outbuf, inbuf, inlen);
215             /* keep the names so we logically know how we are using the
216          * buffers */
217             inbuf = outbuf;
218         }
219         memcpy(lastBlock, inbuf + inlen - blocksize, blocksize);
220         /* we know inbuf == outbuf now, inbuf is declared const and can't
221      * be the target, so use outbuf for the target here */
222         memcpy(outbuf + inlen - pad, inbuf + inlen - blocksize - pad, pad);
223         memcpy(outbuf + inlen - blocksize - pad, lastBlock, blocksize);
224     }
225     /* save the previous to last block so we can undo the misordered
226      * chaining */
227     tmp = (fullblocks < blocksize * 2) ? cts->iv : inbuf + fullblocks - blocksize * 2;
228     PORT_Memcpy(Cn_2, tmp, blocksize);
229     PORT_Memcpy(Cn, inbuf + fullblocks - blocksize, blocksize);
230     rv = (*cts->cipher)(cts->context, outbuf, outlen, maxout, inbuf,
231                         fullblocks, blocksize);
232     if (rv != SECSuccess) {
233         return SECFailure;
234     }
235     *outlen = fullblocks; /* AES low level doesn't set outlen */
236     inbuf += fullblocks;
237     inlen -= fullblocks;
238     if (inlen == 0) {
239         return SECSuccess;
240     }
241     outbuf += fullblocks;
242 
243     /* recover the stolen text */
244     PORT_Memset(lastBlock, 0, blocksize);
245     PORT_Memcpy(lastBlock, inbuf, inlen);
246     PORT_Memcpy(Cn_1, inbuf, inlen);
247     Pn = outbuf - blocksize;
248     /* inbuf points to Cn-1* in the input buffer */
249     /* NOTE: below there are 2 sections marked "make up for the out of order
250      * cbc decryption". You may ask, what is going on here.
251      *   Short answer: CBC automatically xors the plain text with the previous
252      * encrypted block. We are decrypting the last 2 blocks out of order, so
253      * we have to 'back out' the decrypt xor and 'add back' the encrypt xor.
254      *   Long answer: When we encrypted, we encrypted as follows:
255      *       Pn-2, Pn-1, (Pn || 0), but on decryption we can't
256      *  decrypt Cn-1 until we decrypt Cn because part of Cn-1 is stored in
257      *  Cn (see below).  So above we decrypted all the full blocks:
258      *       Cn-2, Cn,
259      *  to get:
260      *       Pn-2, Pn, Except that Pn is not yet corect. On encrypt, we
261      *  xor'd Pn || 0  with Cn-1, but on decrypt we xor'd it with Cn-2
262      *  To recover Pn, we xor the block with Cn-1* || 0 (in last block) and
263      *  Cn-2 to get Pn || Cn-1**. Pn can then be written to the output buffer
264      *  and we can now reunite Cn-1. With the full Cn-1 we can decrypt it,
265      *  but now decrypt is going to xor the decrypted data with Cn instead of
266      *  Cn-2. xoring Cn and Cn-2 restores the original Pn-1 and we can now
267      *  write that oout to the buffer */
268 
269     /* make up for the out of order CBC decryption */
270     XOR_BLOCK(lastBlock, Cn_2, blocksize);
271     XOR_BLOCK(lastBlock, Pn, blocksize);
272     /* last buf now has Pn || Cn-1**, copy out Pn */
273     PORT_Memcpy(outbuf, lastBlock, inlen);
274     *outlen += inlen;
275     /* copy Cn-1* into last buf to recover Cn-1 */
276     PORT_Memcpy(lastBlock, Cn_1, inlen);
277     /* note: because Cn and Cn-1 were out of order, our pointer to Pn also
278      * points to where Pn-1 needs to reside. From here on out read Pn in
279      * the code as really Pn-1. */
280     rv = (*cts->cipher)(cts->context, Pn, &tmpLen, blocksize, lastBlock,
281                         blocksize, blocksize);
282     if (rv != SECSuccess) {
283         PORT_Memset(lastBlock, 0, blocksize);
284         PORT_Memset(saveout, 0, *outlen);
285         return SECFailure;
286     }
287     /* make up for the out of order CBC decryption */
288     XOR_BLOCK(Pn, Cn_2, blocksize);
289     XOR_BLOCK(Pn, Cn, blocksize);
290     /* reset iv to Cn  */
291     PORT_Memcpy(cts->iv, Cn, blocksize);
292     /* This makes Cn the last block for the next decrypt operation, which
293      * matches the encrypt. We don't care about the contexts of last block,
294      * only the side effect of setting the internal IV */
295     (void)(*cts->cipher)(cts->context, lastBlock, &tmpLen, blocksize, Cn,
296                          blocksize, blocksize);
297     /* clear last block. At this point last block contains Pn xor Cn_1 xor
298      * Cn_2, both of with an attacker would know, so we need to clear this
299      * buffer out */
300     PORT_Memset(lastBlock, 0, blocksize);
301     /* Cn, Cn_1, and Cn_2 have encrypted data, so no need to clear them */
302     return SECSuccess;
303 }
304