summaryrefslogtreecommitdiffstats
path: root/security/nss/lib/freebl/cts.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/nss/lib/freebl/cts.c')
-rw-r--r--security/nss/lib/freebl/cts.c307
1 files changed, 307 insertions, 0 deletions
diff --git a/security/nss/lib/freebl/cts.c b/security/nss/lib/freebl/cts.c
new file mode 100644
index 000000000..99ccebb60
--- /dev/null
+++ b/security/nss/lib/freebl/cts.c
@@ -0,0 +1,307 @@
+/* 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;
+ /* iv stores the last ciphertext block of the previous message.
+ * Only used by decrypt. */
+ unsigned char iv[MAX_BLOCK_SIZE];
+};
+
+CTSContext *
+CTS_CreateContext(void *context, freeblCipherFunc cipher,
+ const unsigned char *iv, unsigned int blocksize)
+{
+ CTSContext *cts;
+
+ if (blocksize > MAX_BLOCK_SIZE) {
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return NULL;
+ }
+ 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 consistent 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;
+ unsigned char *saveout = outbuf;
+ 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;
+ }
+ *outlen = fullblocks; /* AES low level doesn't set outlen */
+ 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);
+ PORT_Memset(lastBlock + inlen, 0, blocksize - inlen);
+ rv = (*cts->cipher)(cts->context, outbuf, &tmp, maxout, lastBlock,
+ blocksize, blocksize);
+ PORT_Memset(lastBlock, 0, blocksize);
+ if (rv == SECSuccess) {
+ *outlen = written + blocksize;
+ } else {
+ PORT_Memset(saveout, 0, written + 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 char *saveout = outbuf;
+ unsigned int tmpLen;
+ unsigned int fullblocks, pad;
+ unsigned int 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 = inlen - fullblocks;
+ if (pad != 0) {
+ 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, 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 - pad, inbuf + inlen - blocksize - pad, pad);
+ memcpy(outbuf + inlen - blocksize - pad, lastBlock, blocksize);
+ }
+ /* 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;
+ }
+ *outlen = fullblocks; /* AES low level doesn't set outlen */
+ inbuf += fullblocks;
+ inlen -= fullblocks;
+ if (inlen == 0) {
+ return SECSuccess;
+ }
+ outbuf += 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) {
+ PORT_Memset(lastBlock, 0, blocksize);
+ PORT_Memset(saveout, 0, *outlen);
+ 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,
+ 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;
+}