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