mirror of
https://github.com/rn10950/RetroZilla.git
synced 2024-11-11 02:10:17 +01:00
303 lines
10 KiB
C
303 lines
10 KiB
C
|
/* 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;
|
||
|
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;
|
||
|
}
|
||
|
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;
|
||
|
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;
|
||
|
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,
|
||
|
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;
|
||
|
}
|