Coverage Report

Created: 2024-11-21 07:03

/src/nss-nspr/nss/lib/freebl/cts.c
Line
Count
Source (jump to first uncovered line)
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
0
{
25
0
    CTSContext *cts;
26
27
0
    cts = PORT_ZNew(CTSContext);
28
0
    if (cts == NULL) {
29
0
        return NULL;
30
0
    }
31
0
    PORT_Memcpy(cts->iv, iv, MAX_BLOCK_SIZE);
32
0
    cts->cipher = cipher;
33
0
    cts->context = context;
34
0
    return cts;
35
0
}
36
37
void
38
CTS_DestroyContext(CTSContext *cts, PRBool freeit)
39
0
{
40
0
    if (freeit) {
41
0
        PORT_Free(cts);
42
0
    }
43
0
}
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
0
{
92
0
    unsigned char lastBlock[MAX_BLOCK_SIZE];
93
0
    unsigned int tmp;
94
0
    int fullblocks;
95
0
    int written;
96
0
    unsigned char *saveout = outbuf;
97
0
    SECStatus rv;
98
99
0
    if (inlen < blocksize) {
100
0
        PORT_SetError(SEC_ERROR_INPUT_LEN);
101
0
        return SECFailure;
102
0
    }
103
104
0
    if (maxout < inlen) {
105
0
        *outlen = inlen;
106
0
        PORT_SetError(SEC_ERROR_OUTPUT_LEN);
107
0
        return SECFailure;
108
0
    }
109
0
    fullblocks = (inlen / blocksize) * blocksize;
110
0
    rv = (*cts->cipher)(cts->context, outbuf, outlen, maxout, inbuf,
111
0
                        fullblocks, blocksize);
112
0
    if (rv != SECSuccess) {
113
0
        return SECFailure;
114
0
    }
115
0
    *outlen = fullblocks; /* AES low level doesn't set outlen */
116
0
    inbuf += fullblocks;
117
0
    inlen -= fullblocks;
118
0
    if (inlen == 0) {
119
0
        return SECSuccess;
120
0
    }
121
0
    written = *outlen - (blocksize - inlen);
122
0
    outbuf += written;
123
0
    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
0
    PORT_Memcpy(lastBlock, inbuf, inlen);
135
0
    PORT_Memset(lastBlock + inlen, 0, blocksize - inlen);
136
0
    rv = (*cts->cipher)(cts->context, outbuf, &tmp, maxout, lastBlock,
137
0
                        blocksize, blocksize);
138
0
    PORT_Memset(lastBlock, 0, blocksize);
139
0
    if (rv == SECSuccess) {
140
0
        *outlen = written + blocksize;
141
0
    } else {
142
0
        PORT_Memset(saveout, 0, written + blocksize);
143
0
    }
144
0
    return rv;
145
0
}
146
147
#define XOR_BLOCK(x, y, count)  \
148
0
    for (i = 0; i < count; i++) \
149
0
    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
0
{
181
0
    unsigned char *Pn;
182
0
    unsigned char Cn_2[MAX_BLOCK_SIZE]; /* block Cn-2 */
183
0
    unsigned char Cn_1[MAX_BLOCK_SIZE]; /* block Cn-1 */
184
0
    unsigned char Cn[MAX_BLOCK_SIZE];   /* block Cn   */
185
0
    unsigned char lastBlock[MAX_BLOCK_SIZE];
186
0
    const unsigned char *tmp;
187
0
    unsigned char *saveout = outbuf;
188
0
    unsigned int tmpLen;
189
0
    unsigned int fullblocks, pad;
190
0
    unsigned int i;
191
0
    SECStatus rv;
192
193
0
    if (inlen < blocksize) {
194
0
        PORT_SetError(SEC_ERROR_INPUT_LEN);
195
0
        return SECFailure;
196
0
    }
197
198
0
    if (maxout < inlen) {
199
0
        *outlen = inlen;
200
0
        PORT_SetError(SEC_ERROR_OUTPUT_LEN);
201
0
        return SECFailure;
202
0
    }
203
204
0
    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
0
    pad = inlen - fullblocks;
212
0
    if (pad != 0) {
213
0
        if (inbuf != outbuf) {
214
0
            memcpy(outbuf, inbuf, inlen);
215
            /* keep the names so we logically know how we are using the
216
             * buffers */
217
0
            inbuf = outbuf;
218
0
        }
219
0
        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
0
        memcpy(outbuf + inlen - pad, inbuf + inlen - blocksize - pad, pad);
223
0
        memcpy(outbuf + inlen - blocksize - pad, lastBlock, blocksize);
224
0
    }
225
    /* save the previous to last block so we can undo the misordered
226
     * chaining */
227
0
    tmp = (fullblocks < blocksize * 2) ? cts->iv : inbuf + fullblocks - blocksize * 2;
228
0
    PORT_Memcpy(Cn_2, tmp, blocksize);
229
0
    PORT_Memcpy(Cn, inbuf + fullblocks - blocksize, blocksize);
230
0
    rv = (*cts->cipher)(cts->context, outbuf, outlen, maxout, inbuf,
231
0
                        fullblocks, blocksize);
232
0
    if (rv != SECSuccess) {
233
0
        return SECFailure;
234
0
    }
235
0
    *outlen = fullblocks; /* AES low level doesn't set outlen */
236
0
    inbuf += fullblocks;
237
0
    inlen -= fullblocks;
238
0
    if (inlen == 0) {
239
0
        return SECSuccess;
240
0
    }
241
0
    outbuf += fullblocks;
242
243
    /* recover the stolen text */
244
0
    PORT_Memset(lastBlock, 0, blocksize);
245
0
    PORT_Memcpy(lastBlock, inbuf, inlen);
246
0
    PORT_Memcpy(Cn_1, inbuf, inlen);
247
0
    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
0
    XOR_BLOCK(lastBlock, Cn_2, blocksize);
271
0
    XOR_BLOCK(lastBlock, Pn, blocksize);
272
    /* last buf now has Pn || Cn-1**, copy out Pn */
273
0
    PORT_Memcpy(outbuf, lastBlock, inlen);
274
0
    *outlen += inlen;
275
    /* copy Cn-1* into last buf to recover Cn-1 */
276
0
    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
0
    rv = (*cts->cipher)(cts->context, Pn, &tmpLen, blocksize, lastBlock,
281
0
                        blocksize, blocksize);
282
0
    if (rv != SECSuccess) {
283
0
        PORT_Memset(lastBlock, 0, blocksize);
284
0
        PORT_Memset(saveout, 0, *outlen);
285
0
        return SECFailure;
286
0
    }
287
    /* make up for the out of order CBC decryption */
288
0
    XOR_BLOCK(Pn, Cn_2, blocksize);
289
0
    XOR_BLOCK(Pn, Cn, blocksize);
290
    /* reset iv to Cn  */
291
0
    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
0
    (void)(*cts->cipher)(cts->context, lastBlock, &tmpLen, blocksize, Cn,
296
0
                         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
0
    PORT_Memset(lastBlock, 0, blocksize);
301
    /* Cn, Cn_1, and Cn_2 have encrypted data, so no need to clear them */
302
0
    return SECSuccess;
303
0
}