security/nss/lib/freebl/cts.c

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial