Chromium Code Reviews| Index: nss/mozilla/security/nss/lib/freebl/cts.c |
| =================================================================== |
| --- nss/mozilla/security/nss/lib/freebl/cts.c (revision 0) |
| +++ nss/mozilla/security/nss/lib/freebl/cts.c (revision 0) |
| @@ -0,0 +1,292 @@ |
| +/* This Source Code Form is subject to the terms of the Mozilla Public |
| + * License, v. 2.0. If a copy of the MPL was not distributed with this |
| + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| + |
| +#ifdef FREEBL_NO_DEPEND |
| +#include "stubs.h" |
| +#endif |
| +#include "blapit.h" |
| +#include "blapii.h" |
| +#include "cts.h" |
| +#include "secerr.h" |
| + |
| +struct CTSContextStr { |
| + freeblCipherFunc cipher; |
| + void *context; |
| + unsigned char iv[MAX_BLOCK_SIZE]; |
| +}; |
| + |
| +CTSContext * |
| +CTS_CreateContext(void *context, freeblCipherFunc cipher, |
| + const unsigned char *iv, unsigned int blocksize) |
| +{ |
| + CTSContext *cts; |
| + |
| + cts = PORT_ZNew(CTSContext); |
| + if (cts == NULL) { |
| + return NULL; |
| + } |
| + PORT_Memcpy(cts->iv, iv, blocksize); |
| + cts->cipher = cipher; |
| + cts->context = context; |
| + return cts; |
| +} |
| + |
| +void |
| +CTS_DestroyContext(CTSContext *cts, PRBool freeit) |
| +{ |
| + if (freeit) { |
| + PORT_Free(cts); |
| + } |
| +} |
| + |
| +/* |
| + * See addemdum to NIST SP 800-38A |
| + * Generically handle cipher text stealing. Basically this is doing CBC |
| + * operations except someone can pass us a partial block. |
| + * |
| + * Output Order: |
| + * CS-1: C1||C2||C3..Cn-1(could be partial)||Cn (NIST) |
| + * CS-2: pad == 0 C1||C2||C3...Cn-1(is full)||Cn (Schneier) |
| + * CS-2: pad != 0 C1||C2||C3...Cn||Cn-1(is partial)(Schneier) |
| + * CS-3: C1||C2||C3...Cn||Cn-1(could be partial) (Kerberos) |
| + * |
| + * The characteristics of these three options: |
| + * - NIST & Schneier (CS-1 & CS-2) are identical to CBC if there are no |
| + * partial blocks on input. |
| + * - Scheier and Kerberos (CS-2 and CS-3) have no embedded partial blocks, |
| + * which make decoding easier. |
| + * - NIST & Kerberos (CS-1 and CS-3) have consistant block order independent |
| + * of padding. |
| + * |
| + * PKCS #11 did not specify which version to implement, but points to the NIST |
| + * spec, so this code implements CTS-CS-1 from NIST. |
| + * |
| + * To convert the returned buffer to: |
| + * CS-2 (Schneier): do |
| + * unsigned char tmp[MAX_BLOCK_SIZE]; |
| + * pad = *outlen % blocksize; |
| + * if (pad) { |
| + * memcpy(tmp, outbuf+*outlen-blocksize, blocksize); |
| + * memcpy(outbuf+*outlen-pad,outbuf+*outlen-blocksize-pad, pad); |
| + * memcpy(outbuf+*outlen-blocksize-pad, tmp, blocksize); |
| + * } |
| + * CS-3 (Kerberos): do |
| + * unsigned char tmp[MAX_BLOCK_SIZE]; |
| + * pad = *outlen % blocksize; |
| + * if (pad == 0) { |
| + * pad = blocksize; |
| + * } |
| + * memcpy(tmp, outbuf+*outlen-blocksize, blocksize); |
| + * memcpy(outbuf+*outlen-pad,outbuf+*outlen-blocksize-pad, pad); |
| + * memcpy(outbuf+*outlen-blocksize-pad, tmp, blocksize); |
| + */ |
| +SECStatus |
| +CTS_EncryptUpdate(CTSContext *cts, unsigned char *outbuf, |
| + unsigned int *outlen, unsigned int maxout, |
| + const unsigned char *inbuf, unsigned int inlen, |
| + unsigned int blocksize) |
| +{ |
| + unsigned char lastBlock[MAX_BLOCK_SIZE]; |
| + unsigned int tmp; |
| + int fullblocks; |
| + int written; |
| + SECStatus rv; |
| + |
| + if (inlen < blocksize) { |
| + PORT_SetError(SEC_ERROR_INPUT_LEN); |
| + return SECFailure; |
| + } |
| + |
| + if (maxout < inlen) { |
| + *outlen = inlen; |
| + PORT_SetError(SEC_ERROR_OUTPUT_LEN); |
| + return SECFailure; |
| + } |
| + fullblocks = (inlen/blocksize)*blocksize; |
| + rv = (*cts->cipher)(cts->context, outbuf, outlen, maxout, inbuf, |
| + fullblocks, blocksize); |
| + if (rv != SECSuccess) { |
| + return SECFailure; |
| + } |
| + inbuf += fullblocks; |
| + inlen -= fullblocks; |
| + if (inlen == 0) { |
| + return SECSuccess; |
| + } |
| + written = *outlen - (blocksize - inlen); |
| + outbuf += written; |
| + maxout -= written; |
| + |
| + /* |
| + * here's the CTS magic, we pad our final block with zeros, |
| + * then do a CBC encrypt. CBC will xor our plain text with |
| + * the previous block (Cn-1), capturing part of that block (Cn-1**) as it |
| + * xors with the zero pad. We then write this full block, overwritting |
| + * (Cn-1**) in our buffer. This allows us to have input data == output |
| + * data since Cn contains enough information to reconver Cn-1** when |
| + * we decrypt (at the cost of some complexity as you can see in decrypt |
| + * below */ |
| + PORT_Memcpy(lastBlock, inbuf, inlen); |
| + if (blocksize - inlen != 0) { |
|
Ryan Sleevi
2012/09/17 22:57:26
Is it worth adding the explicit parenthesis?
if ((
wtc
2012/09/18 01:03:40
I usually don't add parentheses in this kind of ex
|
| + PORT_Memset(lastBlock + inlen, 0, blocksize - inlen); |
| + } |
| + rv = (*cts->cipher)(cts->context, outbuf, &tmp, maxout, lastBlock, |
| + blocksize, blocksize); |
| + PORT_Memset(lastBlock, 0, blocksize); |
| + return rv; |
| +} |
| + |
| + |
| +#define XOR_BLOCK(x,y,count) for(i=0; i < count; i++) x[i] = x[i] ^ y[i] |
| + |
| +/* |
| + * See addemdum to NIST SP 800-38A |
| + * Decrypt, Expect CS-1: input. See the comment on the encrypt side |
| + * to understand what CS-2 and CS-3 mean. |
| + * |
| + * To convert the input buffer to CS-1 from ... |
| + * CS-2 (Schneier): do |
| + * unsigned char tmp[MAX_BLOCK_SIZE]; |
| + * pad = inlen % blocksize; |
| + * if (pad) { |
| + * memcpy(tmp, inbuf+inlen-blocksize-pad, blocksize); |
| + * memcpy(inbuf+inlen-blocksize-pad,inbuf+inlen-pad, pad); |
| + * memcpy(inbuf+inlen-blocksize, tmp, blocksize); |
| + * } |
| + * CS-3 (Kerberos): do |
| + * unsigned char tmp[MAX_BLOCK_SIZE]; |
| + * pad = inlen % blocksize; |
| + * if (pad == 0) { |
| + * pad = blocksize; |
| + * } |
| + * memcpy(tmp, inbuf+inlen-blocksize-pad, blocksize); |
| + * memcpy(inbuf+inlen-blocksize-pad,inbuf+inlen-pad, pad); |
| + * memcpy(inbuf+inlen-blocksize, tmp, blocksize); |
| + */ |
| +SECStatus |
| +CTS_DecryptUpdate(CTSContext *cts, unsigned char *outbuf, |
| + unsigned int *outlen, unsigned int maxout, |
| + const unsigned char *inbuf, unsigned int inlen, |
| + unsigned int blocksize) |
| +{ |
| + unsigned char *Pn; |
| + unsigned char Cn_2[MAX_BLOCK_SIZE]; /* block Cn-2 */ |
| + unsigned char Cn_1[MAX_BLOCK_SIZE]; /* block Cn-1 */ |
| + unsigned char Cn[MAX_BLOCK_SIZE]; /* block Cn */ |
| + unsigned char lastBlock[MAX_BLOCK_SIZE]; |
| + const unsigned char *tmp; |
| + unsigned int tmpLen; |
| + int fullblocks, pad, i; |
| + SECStatus rv; |
| + |
| + if (inlen < blocksize) { |
| + PORT_SetError(SEC_ERROR_INPUT_LEN); |
| + return SECFailure; |
| + } |
| + |
| + if (maxout < inlen) { |
| + *outlen = inlen; |
| + PORT_SetError(SEC_ERROR_OUTPUT_LEN); |
| + return SECFailure; |
| + } |
| + |
| + fullblocks = (inlen/blocksize)*blocksize; |
| + |
| + /* even though we expect the input to be CS-1, CS-2 is easier to parse, |
| + * so convert to CS-2 immediately. NOTE: this is the same code as in |
| + * the comment for encrypt. NOTE2: since we can't modify inbuf unless |
| + * inbuf and outbuf overlap, just copy inbuf to outbuf and modify it there |
| + */ |
| + pad = blocksize - (fullblocks - inlen); |
|
wtc
2012/09/18 01:03:40
pad = blocksize + (inlen - fullblocks); // avoid
|
| + if (pad != blocksize) { |
| + if (inbuf != outbuf) { |
| + memcpy(outbuf, inbuf, inlen); |
| + /* keep the names so we logically know how we are using the |
| + * buffers */ |
| + inbuf = outbuf; |
| + } |
| + memcpy(lastBlock, inbuf+inlen-blocksize-pad, blocksize); |
| + /* we know inbuf == outbuf now, inbuf is declared const and can't |
| + * be the target, so use outbuf for the target here */ |
| + memcpy(outbuf+inlen-blocksize-pad, inbuf+inlen-pad, pad); |
| + memcpy(outbuf+inlen-blocksize, lastBlock, blocksize); |
|
wtc
2012/09/18 01:03:40
BUG: you are doing the opposite conversion (from C
|
| + } |
| + /* save the previous to last block so we can undo the misordered chaining*/ |
| + tmp = (fullblocks < blocksize*2) ? cts->iv : |
| + inbuf+fullblocks-blocksize*2; |
| + PORT_Memcpy(Cn_2, tmp, blocksize); |
| + PORT_Memcpy(Cn, inbuf+fullblocks-blocksize, blocksize); |
| + rv = (*cts->cipher)(cts->context, outbuf, outlen, maxout, inbuf, |
| + fullblocks, blocksize); |
| + if (rv != SECSuccess) { |
| + return SECFailure; |
| + } |
| + inbuf += fullblocks; |
| + inlen -= fullblocks; |
| + if (inlen == 0) { |
| + return SECSuccess; |
| + } |
| + outbuf += fullblocks; |
| + maxout -= fullblocks; |
| + |
| + /* recover the stolen text */ |
| + PORT_Memset(lastBlock, 0, blocksize); |
| + PORT_Memcpy(lastBlock, inbuf, inlen); |
| + PORT_Memcpy(Cn_1, inbuf, inlen); |
| + Pn = outbuf-blocksize; |
| + /* inbuf points to Cn-1* in the input buffer */ |
| + /* NOTE: below there are 2 sections marked "make up for the out of order |
| + * cbc decryption". You may ask, what is going on here. |
| + * Short answer: CBC automatically xors the plain text with the previous |
| + * encrypted block. We are decrypting the last 2 blocks out of order, so |
| + * we have to 'back out' the decrypt xor and 'add back' the encrypt xor. |
| + * Long answer: When we encrypted, we encrypted as follows: |
| + * Pn-2, Pn-1, (Pn || 0), but on decryption we can't |
| + * decrypt Cn-1 until we decrypt Cn because part of Cn-1 is stored in |
| + * Cn (see below). So above we decrypted all the full blocks: |
| + * Cn-2, Cn, |
| + * to get: |
| + * Pn-2, Pn, Except that Pn is not yet corect. On encrypt, we |
| + * xor'd Pn || 0 with Cn-1, but on decrypt we xor'd it with Cn-2 |
| + * To recover Pn, we xor the block with Cn-1* || 0 (in last block) and |
| + * Cn-2 to get Pn || Cn-1**. Pn can then be written to the output buffer |
| + * and we can now reunite Cn-1. With the full Cn-1 we can decrypt it, |
| + * but now decrypt is going to xor the decrypted data with Cn instead of |
| + * Cn-2. xoring Cn and Cn-2 restores the original Pn-1 and we can now |
| + * write that oout to the buffer */ |
| + |
| + /* make up for the out of order CBC decryption */ |
| + XOR_BLOCK(lastBlock, Cn_2, blocksize); |
| + XOR_BLOCK(lastBlock, Pn, blocksize); |
| + /* last buf now has Pn || Cn-1**, copy out Pn */ |
| + PORT_Memcpy(outbuf, lastBlock, inlen); |
| + *outlen += inlen; |
| + /* copy Cn-1* into last buf to recover Cn-1 */ |
| + PORT_Memcpy(lastBlock, Cn-1, inlen); |
| + /* note: because Cn and Cn-1 were out of order, our pointer to Pn also |
| + * points to where Pn-1 needs to reside. From here on out read Pn in |
| + * the code as really Pn-1. */ |
| + rv = (*cts->cipher)(cts->context, Pn, &tmpLen, blocksize, lastBlock, |
| + blocksize, blocksize); |
| + if (rv != SECSuccess) { |
| + return SECFailure; |
| + } |
| + /* make up for the out of order CBC decryption */ |
| + XOR_BLOCK(Pn, Cn_2, blocksize); |
| + XOR_BLOCK(Pn, Cn, blocksize); |
| + /* reset iv to Cn */ |
| + PORT_Memcpy(cts->iv, Cn, blocksize); |
| + /* This makes Cn the last block for the next decrypt operation, which |
| + * matches the encrypt. We don't care about the contexts of last block, |
| + * only the side effect of setting the internal IV */ |
| + (void) (*cts->cipher)(cts->context, lastBlock, &tmpLen, blocksize, Cn, |
|
wtc
2012/09/18 01:03:40
Should we check the return value?
|
| + blocksize, blocksize); |
| + /* clear last block. At this point last block contains Pn xor Cn_1 xor |
| + * Cn_2, both of with an attacker would know, so we need to clear this |
| + * buffer out */ |
| + PORT_Memset(lastBlock, 0, blocksize); |
| + /* Cn, Cn_1, and Cn_2 have encrypted data, so no need to clear them */ |
| + return SECSuccess; |
| +} |
| + |
| Property changes on: nss/mozilla/security/nss/lib/freebl/cts.c |
| ___________________________________________________________________ |
| Added: svn:eol-style |
| + LF |