X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/libsecurity_comcryption/lib/comcryption.c diff --git a/Security/libsecurity_comcryption/lib/comcryption.c b/Security/libsecurity_comcryption/lib/comcryption.c new file mode 100644 index 00000000..a7d23e95 --- /dev/null +++ b/Security/libsecurity_comcryption/lib/comcryption.c @@ -0,0 +1,1438 @@ +/* + * Copyright (c) 1997,2011-2012,2014 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +#include +#include +#include +#include "comcryption.h" +#include "comDebug.h" +#include "comcryptPriv.h" + +#if COM_PROFILE + +unsigned comProfEnable; +comprof_t cmcTotal; +comprof_t cmcQueSearch; +comprof_t cmcQueMatchMove; +comprof_t cmcQueMissMove; +comprof_t cmcLevel2; +comprof_t cmcPerWordOhead; + +#endif /*COM_PROFILE*/ + +void comMallocRegister(comMallocExternFcn *mallocExtern, + comFreeExternFcn *freeExtern) +{ + comMallocExt = mallocExtern; + comFreeExt = freeExtern; +} + +/* + * Call once at startup. The resulting comcryptObj can be reused multiple + * times. + */ +comcryptObj comcryptAlloc(void) +{ + comcryptPriv *cpriv = (comcryptPriv *) ascMalloc(sizeof(comcryptPriv)); + + if(cpriv == NULL) { + return NULL; + } + memset(cpriv, 0, sizeof(comcryptPriv)); + +#if COMCRYPT_EXPORT_ONLY + cpriv->key = (unsigned char *)ascMalloc(EXPORT_KEY_SIZE); +#else /*COMCRYPT_EXPORT_ONLY*/ + cpriv->key = (unsigned char *)ascMalloc(COMCRYPT_MAX_KEYLENGTH); +#endif /*COMCRYPT_EXPORT_ONLY*/ + + if(cpriv->key == NULL) { + return NULL; + } + cpriv->map = (unsigned char *)ascMalloc(256); + cpriv->invmap = (unsigned char *)ascMalloc(256); + if((cpriv->map == NULL) || (cpriv->invmap == NULL)) { + return NULL; + } + mallocCodeBufs(&cpriv->cbuf); + if((cpriv->cbuf.codeBuf == NULL) || + (cpriv->cbuf.level2Buf == NULL)) { + return NULL; + } + #if QUEUE_LOOKAHEAD + if(cpriv->cbuf.lookAhead == NULL) { + return NULL; + } + #endif + + /* + * Hard coded limit of two levels of comcryption + */ + cpriv->cbuf.nextBuf = (comcryptBuf *)ascMalloc(sizeof(comcryptBuf)); + if(cpriv->cbuf.nextBuf == NULL) { + return NULL; + } + mallocCodeBufs(cpriv->cbuf.nextBuf); + if((cpriv->cbuf.nextBuf->codeBuf == NULL) || + (cpriv->cbuf.nextBuf->level2Buf == NULL)) { + return NULL; + } + #if QUEUE_LOOKAHEAD + if(cpriv->cbuf.nextBuf->lookAhead == NULL) { + return NULL; + } + #endif + + cpriv->cbuf.nextBuf->nextBuf = NULL; + return cpriv; +} + +/* + * Call this before starting every stream process + */ +comcryptReturn comcryptInit( + comcryptObj cobj, + const unsigned char *key, + unsigned keyLen, + comcryptOptimize optimize) // CCO_SIZE, etc. +{ + comcryptPriv *cpriv = (comcryptPriv *)cobj; + unsigned maxKeySize; + +#if COMCRYPT_EXPORT_ONLY + /* + * FIXME - NSA might not be satisfied with this, may have to enforce + * elsewhere + */ + maxKeySize = EXPORT_KEY_SIZE; +#else /*COMCRYPT_EXPORT_ONLY*/ + maxKeySize = COMCRYPT_MAX_KEYLENGTH; +#endif /*COMCRYPT_EXPORT_ONLY*/ + + if(keyLen > maxKeySize) { + keyLen = maxKeySize; + } + memmove(cpriv->key, key, keyLen); + cpriv->keybytes = keyLen; + cpriv->cbuf.codeBufLength = 0; + cpriv->cbuf.nextBuf->codeBufLength = 0; + cpriv->version = 0; + cpriv->versionBytes = 0; + cpriv->spareBytes = 0; + cpriv->optimize = optimize; + + /* + * Derive feature enable bits from optimize arg. This is highly likely + * to change.... + */ + cpriv->level2enable = 1; + cpriv->sigSeqEnable = 1; + switch(optimize) { + case CCO_TIME: + cpriv->level2enable = 0; + break; + case CCO_TIME_SIZE: + cpriv->sigSeqEnable = 0; + break; + default: + break; + } +#if QUEUE_LOOKAHEAD + cpriv->laEnable = 1; +#else /* QUEUE_LOOKAHEAD */ + cpriv->laEnable = 0; +#endif /* QUEUE_LOOKAHEAD */ + + /* + * init queue and maps + */ + initCodeBufs(&cpriv->cbuf, key, keyLen, cpriv->laEnable, + cpriv->sigSeqEnable); + initCodeBufs(cpriv->cbuf.nextBuf, key, keyLen, cpriv->laEnable, + cpriv->sigSeqEnable); + key_perm(key, keyLen, cpriv->map, cpriv->invmap); + return CCR_SUCCESS; +} + +/* + * Free a comcryptObj object obtained via comcryptAlloc() + */ +void comcryptObjFree(comcryptObj cobj) +{ + comcryptPriv *cpriv = (comcryptPriv *)cobj; + + if(cpriv->key != NULL) { + ascFree(cpriv->key); + } + if(cpriv->map != NULL) { + ascFree(cpriv->map); + } + if(cpriv->invmap != NULL) { + ascFree(cpriv->invmap); + } + freeCodeBufs(&cpriv->cbuf); + ascFree(cpriv); +} + +/* + * Return the maximum input buffer size allowed for for specified + * output buffer size. Note that for both comcrypt and decomcrypt, + * to cover the worst case, the output buffer always has to be + * larger than the input buffer. + */ +unsigned comcryptMaxInBufSize(comcryptObj cobj, + unsigned outBufSize, + comcryptOp op) +{ + unsigned fullBlocks; + unsigned minCblockSize; + unsigned resid; + unsigned rtn; + unsigned tokenBytes; + comcryptPriv *cpriv = (comcryptPriv *)cobj; + unsigned ptextFromCodeBuf; + + switch(op) { + case CCOP_COMCRYPT: + /* + * Worst case: no compression. Also, establish a minimum + * ciphertext size to accomodate header and one block. + */ + minCblockSize = MIN_CBLOCK_SIZE; + if(cpriv->versionBytes == 0) { + minCblockSize += CTEXT_HDR_SIZE; + } + if(outBufSize < (minCblockSize)) { + return 0; + } + if(cpriv->versionBytes == 0) { + outBufSize -= CTEXT_HDR_SIZE; + } + fullBlocks = outBufSize / MAX_CBLOCK_SIZE; + rtn = (fullBlocks * CC_BLOCK_SIZE); // bytes of ptext + + /* + * code must be even aligned, then chop off one for odd ptext + */ + rtn &= 0xfffffffe; + rtn--; + if(rtn <= 0) { + return 0; + } + resid = outBufSize % MAX_CBLOCK_SIZE; + if(resid) { + rtn += resid; + + /* + * Account for resid block overhead + */ + if(rtn < MIN_CBLOCK_SIZE) { + return 0; + } + rtn -= MIN_CBLOCK_SIZE; + + tokenBytes = TOKEN_BYTES_FROM_PTEXT(resid); + if(rtn <= tokenBytes) { + return 0; + } + rtn -= tokenBytes; + } + if(rtn > INBUF_TRUNC_THRESH) { + /* + * Truncate to even block size to minimize partial cipherblocks + */ + rtn &= ~(CC_BLOCK_SIZE - 1); + } + return rtn; + + case CCOP_DECOMCRYPT: + /* + * Worst case - 4:1 compression and an almost full block in + * codeBuf. Note 4:1 is a super-conservative, easy arithmetic + * version of (9/16) squared... + */ + ptextFromCodeBuf = cpriv->cbuf.codeBufLength * 4; + if(outBufSize < ptextFromCodeBuf) { + /* decrypting codeBuf might overflow output (plaintext) + * buffer - won't be able to move anything */ + rtn = 0; + } + else { + /* can decrypt (this much plainText - ptextFromCodeBuf) / 4 */ + rtn = (outBufSize - ptextFromCodeBuf) / 4; + } + + /* may be able to handle a bit extra for initial decrypt... */ + if(cpriv->versionBytes < VERSION_BYTES) { + rtn += (VERSION_BYTES - cpriv->versionBytes); + } + if(cpriv->spareBytes < SPARE_BYTES) { + rtn += (SPARE_BYTES - cpriv->spareBytes); + } + return rtn; + + default: + ddprintf(("bogus op (%d) in comcryptMaxInBufSize()\n", op)); + return 0; + } +} + +/* + * Return the maximum output buffer size for specified input buffer size. + * Output buffer size will always be larger than input buffer size. + */ +unsigned comcryptMaxOutBufSize(comcryptObj cobj, + unsigned inBufSize, + comcryptOp op, + char final) +{ + unsigned fullBlocks; + unsigned resid; + unsigned rtn; + comcryptPriv *cpriv = (comcryptPriv *)cobj; + + switch(op) { + case CCOP_COMCRYPT: + fullBlocks = inBufSize / CC_BLOCK_SIZE; + rtn = fullBlocks * MAX_CBLOCK_SIZE; + resid = inBufSize % CC_BLOCK_SIZE; + if(resid != 0) { + /* + * partial block + */ + unsigned tokenBytes = TOKEN_BYTES_FROM_PTEXT(resid); + + rtn += MIN_CBLOCK_SIZE; + rtn += tokenBytes; + rtn += resid; // no compression + if(resid & 1) { + rtn++; // oddByte uses extra + } + } + if((cpriv == NULL) || // i.e., we're being called from mallocCodeBufs + (cpriv->versionBytes == 0)) { + rtn += CTEXT_HDR_SIZE; // first of a stream + } + return rtn; + + case CCOP_DECOMCRYPT: + /* + * Here assume max compression, including resid block in codeBuf + */ + inBufSize += cpriv->cbuf.codeBufLength; + if(inBufSize) { + /* may be able to handle a bit extra for initial decrypt... */ + unsigned delta; + if(cpriv->versionBytes < VERSION_BYTES) { + delta = VERSION_BYTES - cpriv->versionBytes; + if(inBufSize > delta) { + inBufSize -= delta; + } + else { + inBufSize = 0; + } + } + if(cpriv->spareBytes < SPARE_BYTES) { + delta = SPARE_BYTES - cpriv->spareBytes; + if(inBufSize > delta) { + inBufSize -= delta; + } + else { + inBufSize = 0; + } + } + } + rtn = 4 * inBufSize; + return rtn; + + default: + ddprintf(("bogus op (%d) in comcryptMaxOutBufSize()\n", op)); + return 0; + } +} + +/* + * Threshold for using memmove() rather than hard-coded loop for + * moving queue segment. This was derived empirically on a Pentium; + * we should do similar measurements on PPC. + */ +#define QUEUE_MEMMOVE_THRESH 3 + +/* + * peek at queue[0] before search. This appears to only be a win for + * constant plaintext, i.e., the codeword is almost always at queue[0]. + */ +#define QUEUE_PEEK 0 + +/* + * Comcrypt one block. + */ +static comcryptReturn comcryptBlock( + comcryptPriv *cpriv, + comcryptBuf *cbuf, // not necessarily cpriv->cbuf + const unsigned char *plainText, + unsigned plainTextLen, + unsigned char *cipherText, + unsigned *cipherTextLen, // IN/OUT + unsigned recursLevel) +{ + unsigned char *byteCodePtr; + unsigned char *destByteCodePtr; + unsigned char *longCodePtr; + unsigned char *startLongCodePtr; + unsigned char *tokenPtr; + unsigned char *startTokenPtr; + unsigned char *startCtextPtr = cipherText; + unsigned numTokenBytes; // in bytes, constant + unsigned short codeWord; + unsigned oddByte = 0; + unsigned match; + unsigned jmatch=0; + unsigned tokenDex = 0; // index into array of token bits + unsigned j; + unsigned numLongCodes = 0; + unsigned numByteCodes = 0; + unsigned totalCipherTextLen; + unsigned above; + unsigned jmatchTotal = 0; + unsigned jmatchAvg; + comcryptReturn crtn; + unsigned char blockDesc = CBD_MAGIC; + unsigned fullBlock = 0; + int len; + queueElt *src; + queueElt *dst; + queueElt *cbufq = &cbuf->queue[0]; + + /* + * 'nibble' is added to 'above' in the call to nextSigWord() for + * additional security. + * + * Normal case : nibble = keynybble() + * last word on odd byte : nibble = nibbleDex + * hit on queue q : nibble = nibbleDex (optimize to avoid keynybble() + * call) + */ + unsigned char nibble; + + COMPROF_LOCALS; + + #if COM_LA_DEBUG + if(testLookAhead(cbuf, 0, 0)) { + return CCR_INTERNAL; + } + #endif + + laprintf(("comcryptBlock recurs level %d\n", recursLevel)); + + /* + * Set up ptrs for the three arrays we'll be writing + */ + tokenPtr = cipherText + CTBO_NUM_TOKENS + 1; + if(plainTextLen >= (CC_BLOCK_SIZE - 1)) { + /* + * Optimized for full block - no token count in block. Note + * that plainTextLen == (CC_BLOCK_SIZE - 1) is also a full block + * in that it uses up a full block's worth of tokens! + */ + numTokenBytes = CC_BLOCK_SIZE >> 4; + tokenPtr--; + blockDesc |= CBD_FULL_BLOCK; + fullBlock = 1; + } + else { + numTokenBytes = (plainTextLen + 15) >> 4; + } + longCodePtr = tokenPtr + numTokenBytes; + startLongCodePtr = longCodePtr; + byteCodePtr = cbuf->codeBuf; + startTokenPtr = tokenPtr; + + if((unsigned)(longCodePtr - cipherText) > *cipherTextLen) { + ddprintf(("comcryptBlock: short block (1)\n")); + return CCR_OUTBUFFER_TOO_SMALL; + } + memset(tokenPtr, 0, numTokenBytes); + + /* + * Entering time-critical region. This loop executes once for every + * 2 bytes of plaintext. Make every attempt to streamline the code + * here; avoid function calls in favor of macros; etc. + */ + while(plainTextLen != 0) { + + /* + * assemble a 16-bit word from two bytes if possible + */ + if(plainTextLen == 1) { + /* + * Odd byte case + */ + codeWord = ((unsigned short)(cpriv->map[*plainText]) << 8) | + cpriv->map[0]; // a bit of obfuscation - mapped zero + oddByte = 1; + blockDesc |= CBD_ODD; + plainTextLen--; + } + else { + codeWord = ((unsigned short)(cpriv->map[*plainText]) << 8) | + (unsigned short)(cpriv->map[plainText[1]]); + plainText += 2; + plainTextLen -= 2; + } + + /* + * Calibrate how much profiling is costing us. + */ + COMPROF_START; + COMPROF_END(cmcPerWordOhead); + + /* + * See if this word is in queue[]. Skip if oddByte; we'll force + * a 16-bit word in that case. Also skip the search if we know + * via lookahead that a search would be fruitless. + */ + COMPROF_START; /* cmcQueSearch */ + match = 0; + do { /* while 0 - for easy breaks w/o goto */ + + /* + * First handle some optimizations and special cases + */ + if(oddByte) { + break; // force longcode + } + +#if QUEUE_PEEK + if(cbufq[0] == codeWord) { + match = 1; + jmatch = 0; + break; + + } +#endif /*QUEUE_PEEK*/ + + if(cpriv->laEnable && !inQueue(cbuf, codeWord)) { + break; + } + + /* + * OK, do the gruntwork search + */ + for(j=0; j < QLEN; j++) { + if(cbufq[j] == codeWord) { + match = 1; + jmatch = j; + break; + } + } + +#if COM_LA_DEBUG + if(cpriv->laEnable && !match) { + printf("inQueue, not found in queue!\n"); + return CCR_INTERNAL; + } + + /* + * Search for duplicates. + */ + if(match) { + for(j=jmatch+1; jkey, cpriv->keybytes, + (cbuf->nybbleDex)++); +#endif /*SKIP_NIBBLE_ON_QUEUE_0*/ + + COMPROF_START; + if(match) { + /* + * 16-bit symbol is in queue. 8 bits of ciphertext, token bit is 0. + */ + if(jmatch == 0) { + /* + * Optimization: jmatch = 0. Keep state machine in sync, + * but skip queue update. + */ + above = 0; + laprintf(("...queue hit at queue[0]\n")); +#if SKIP_NIBBLE_ON_QUEUE_0 + nibble = (cbuf->nybbleDex)++; +#endif /*SKIP_NIBBLE_ON_QUEUE_0*/ + } + else { +#if SKIP_NIBBLE_ON_QUEUE_0 + nibble = keynybble(cpriv->key, cpriv->keybytes, + (cbuf->nybbleDex)++); +#endif /*SKIP_NIBBLE_ON_QUEUE_0*/ + + above = (cbuf->f1 * jmatch * (16 + nibble)) >> 9; + + /* + * queue[above..(jmatch-1)] move one element towards end + * queue[above] = this codeWord + */ + laprintf(("...queue hit, moving 0x%x from 0x%x to 0x%x\n", + codeWord, jmatch, above)); + + len = (int)jmatch - (int)above; + if(len > QUEUE_MEMMOVE_THRESH) { + src = &cbufq[above]; + dst = src + 1; + len *= sizeof(queueElt); + memmove(dst, src, len); + } + else { + for(j = jmatch; j>above; j--) { + cbufq[j] = cbufq[j-1]; + } + } + + cbufq[above] = codeWord; +#if COM_LA_DEBUG + if(testLookAhead(cbuf, above, jmatch)) { + return CCR_INTERNAL; + } +#endif /*COM_LA_DEBUG*/ + } + COMPROF_END(cmcQueMatchMove); + + codeWord = jmatch; + incr1byteFrags(recursLevel); + jmatchTotal += jmatch; + } + else if(oddByte == 0) { + /* + * 16-bit symbol is not in queue. 16 bits of ciphertext. + * Token bit is 1. + * + * queue[above...QLEN-1] move one element toward end + * queue[QLEN-1] discarded + * queue[above] = new codeword + * + * Note we skip this queue manipulation in the oddbyte case, since + * we don't really know (or care) if the current code word is in + * the queue or not. + */ +#if SKIP_NIBBLE_ON_QUEUE_0 + nibble = keynybble(cpriv->key, cpriv->keybytes, + (cbuf->nybbleDex)++); +#endif /*SKIP_NIBBLE_ON_QUEUE_0*/ + + above = ABOVE(cbuf->f2) + nibble; + +#if COM_DEBUG + if(above > QLEN) { + printf("Hey Doug! above %d QLEN %d\n", above, QLEN); + return CCR_INTERNAL; + } +#endif + + laprintf(("...queue miss, adding 0x%x at 0x%x, deleting 0x%x\n", + codeWord, above, cbufq[QLEN-1])); + + if(cpriv->laEnable) { + markInQueue(cbuf, codeWord, 1); // new entry + markInQueue(cbuf, cbufq[QLEN-1], 0); // bumped out + } + + len = QLEN - 1 - (int)above; + if(len > QUEUE_MEMMOVE_THRESH) { + src = &cbufq[above]; + dst = src + 1; + len *= sizeof(queueElt); + memmove(dst, src, len); + } + else { + for(j=QLEN-1; j > above; j--) { + cbufq[j] = cbufq[j-1]; + } + } + + cbufq[above] = codeWord; + +#if COM_LA_DEBUG + if(testLookAhead(cbuf, above, 0)) { + return CCR_INTERNAL; + } +#endif /*COM_LA_DEBUG*/ + + COMPROF_END(cmcQueMissMove); + incr2byteFrags(recursLevel); + } + else { + /* + * Odd byte case, at least gather stats. + */ + incr2byteFrags(recursLevel); + + /* + * ...and keep this in sync for signature sequence + */ + above = 0; +#if SKIP_NIBBLE_ON_QUEUE_0 + nibble = (cbuf->nybbleDex)++; +#endif /*SKIP_NIBBLE_ON_QUEUE_0*/ + } + + updateToken(tokenPtr, tokenDex, !match); + tokenDex++; + + if(match) { + *byteCodePtr++ = codeWord & 0xff; + numByteCodes++; + } + else { + serializeShort(codeWord, longCodePtr); + longCodePtr += 2; + numLongCodes++; + } + if(cpriv->sigSeqEnable) { + nextSigWord(cbuf, tokenDex, match, (above + nibble)); + } + } + +#if COM_DEBUG + if(numTokenBytes != ((tokenDex + 7) >> 3)) { + ddprintf(("comcryptBlock: numTokenBytes (%d), tokenDex (%d)\n", + numTokenBytes, tokenDex)); + } +#endif /*COM_DEBUG*/ + + /* + * We already wrote tokens and longcode to cipherText; verify we + * didn't overrun + */ + totalCipherTextLen = (unsigned)(longCodePtr - startCtextPtr); + if(*cipherTextLen < totalCipherTextLen) { + ddprintf(("comcryptBlock: short block (2)\n")); + return CCR_OUTBUFFER_TOO_SMALL; + } + if(!fullBlock) { + cipherText[CTBO_NUM_TOKENS] = tokenDex; + } + cipherText[CTBO_NUM_LONG_CODES] = numLongCodes; + +#if COM_DEBUG + if(tokenDex > MAX_TOKENS) { + ddprintf(("comcryptBlock: counter overflow!\n")); + return CCR_INTERNAL; + } + if((numByteCodes + numLongCodes) != tokenDex) { + ddprintf(("comcryptBlock: counter mismatch!\n")); + return CCR_INTERNAL; + } +#endif /*COM_DEBUG*/ + + /* + * See if doing a second level comcryption makes sense. + */ + destByteCodePtr = startLongCodePtr + (numLongCodes * 2); + if(numByteCodes > 0) { + jmatchAvg = jmatchTotal / numByteCodes; + } + else { + jmatchAvg = cbuf->jmatchThresh + 1; + } + if((recursLevel == 0) && // hard coded recursion limit + (cpriv->level2enable) && // enabled by caller + (numByteCodes >= cbuf->minByteCode) && // meaningful # of bytecodes + (jmatchAvg <= cbuf->jmatchThresh)) { // reasonable compression + // already achieved + + unsigned thisCtext = cbuf->level2BufSize; + + COMPROF_START; + crtn = comcryptBlock(cpriv, + cbuf->nextBuf, + cbuf->codeBuf, + numByteCodes, + cbuf->level2Buf, + &thisCtext, + recursLevel + 1); + if(crtn) { + return crtn; + } + + /* + * Write level2Buf to cipherText (as byteCodeArray). + * Size of 2nd level comcrypted byte code follows longcode array, + * then the bytecode itself. + * First bump totalCipherTextLen by the size of the comcrypted array + * plus one (for the size byte itself), and verify no overflow + */ + totalCipherTextLen += (thisCtext + 1); + if(*cipherTextLen < totalCipherTextLen) { + ddprintf(("comcryptBlock: short block (3)\n")); + return CCR_OUTBUFFER_TOO_SMALL; + } + *destByteCodePtr++ = thisCtext; + COMPROF_END(cmcLevel2); + memmove(destByteCodePtr, cbuf->level2Buf, thisCtext); + blockDesc |= CBD_DOUBLE; + + l2printf(("***2nd-level comcrypt: numByteCodes %d encrypted " + "size %d\n", numByteCodes, thisCtext)); + incrComStat(level2byteCode, numByteCodes); + incrComStat(level2cipherText, thisCtext); + incrComStat(level2jmatch, jmatchTotal); + incrComStat(level2blocks, 1); + } + else { + /* + * Normal one-level comcryption. Write byteCodes to ciphertext. + * numByteCodes is inferred. + */ + totalCipherTextLen += numByteCodes; + if(*cipherTextLen < totalCipherTextLen) { + ddprintf(("comcryptBlock: short block (3)\n")); + return CCR_OUTBUFFER_TOO_SMALL; + } + memmove(destByteCodePtr, cbuf->codeBuf, numByteCodes); + blockDesc |= CBD_SINGLE; + if(recursLevel == 0) { + incrComStat(level1blocks, 1); + } + /* else this is a 2nd-level, our caller will count */ + + /* + * obfuscate via sigArray (only when we're NOT doing 2nd level + * comcrypt) + */ + if(cpriv->sigSeqEnable) { + sigMunge(cbuf, startTokenPtr, tokenDex, + destByteCodePtr, startLongCodePtr); + + /* + * Prime sigArray state machine for next block. Note in the case + * of 2nd level, we skip this step, so the next block starts from + * the same state as this one did. + */ + cbuf->sigArray[0] = cbuf->sigArray[tokenDex]; + } + } + cipherText[CTBO_BLOCK_DESC] = blockDesc; + *cipherTextLen = totalCipherTextLen; + return CCR_SUCCESS; +} + +/* + * Main public encrypt function. + */ +comcryptReturn comcryptData( + comcryptObj cobj, + unsigned char *plainText, + unsigned plainTextLen, + unsigned char *cipherText, // malloc'd by caller + unsigned *cipherTextLen, // IN/OUT + comcryptEos endOfStream) // CCE_END_OF_STREAM, etc. +{ + comcryptPriv *cpriv = (comcryptPriv *)cobj; + unsigned ctextLen = *cipherTextLen; + comcryptReturn crtn; + unsigned thisPtext; + unsigned thisCtext; + COMPROF_LOCALS; + + COMPROF_START; + incrComStat(plaintextBytes, plainTextLen); + if(cpriv->versionBytes == 0) { + /* + * First, put header (version, spare) into head of ciphertext. + */ + if(ctextLen < CTEXT_HDR_SIZE) { + ddprintf(("comcryptData: overflow (0)\n")); + return CCR_OUTBUFFER_TOO_SMALL; + } + serializeInt(VERSION_3_Dec_97, cipherText); + cipherText += VERSION_BYTES; + cpriv->versionBytes = VERSION_BYTES; + serializeInt(0, cipherText); // spares + cipherText += SPARE_BYTES; + ctextLen -= CTEXT_HDR_SIZE; + } + + /* + * OK, grind it out, one block at a time. + */ + while (plainTextLen != 0) { + thisPtext = CC_BLOCK_SIZE; + if(thisPtext > plainTextLen) { + thisPtext = plainTextLen; + } + thisCtext = ctextLen; + crtn = comcryptBlock(cpriv, + &cpriv->cbuf, + plainText, + thisPtext, + cipherText, + &thisCtext, + 0); // recurs level + if(crtn) { + return crtn; + } + plainText += thisPtext; + plainTextLen -= thisPtext; + if(thisCtext > ctextLen) { + ddprintf(("comcryptData: undetected ciphertext overlow\n")); + return CCR_OUTBUFFER_TOO_SMALL; + } + cipherText += thisCtext; + ctextLen -= thisCtext; + } + *cipherTextLen = *cipherTextLen - ctextLen; + incrComStat(ciphertextBytes, *cipherTextLen); + COMPROF_END(cmcTotal); + return CCR_SUCCESS; +} + +/* + * Return values from deComcryptBlock(). + */ +typedef enum { + DCB_SUCCESS, // OK + DCB_SHORT, // incomplete block, try again with more ciphertext + DCB_PARSE_ERROR, // bad block + DCB_OUTBUFFER_TOO_SMALL +} dcbReturn; + +/* + * Assumes exactly one block of ciphertext, error otherwise. + */ +static dcbReturn deComcryptBlock( + comcryptPriv *cpriv, + comcryptBuf *cbuf, // not necessarily cpriv->cbuf + unsigned char *cipherText, + unsigned cipherTextLen, + unsigned char *plainText, + unsigned *plainTextLen, // IN/OUT + comcryptEos endOfStream, // CCE_END_OF_STREAM, etc. + unsigned *blockSize) // RETURNED on DCB_SUCCESS +{ + unsigned char *tokenPtr; + unsigned numTokenBits; // constant, from ciphertext + unsigned numTokenBytes; + unsigned char *longCodePtr; + unsigned numLongCodes; + unsigned char *byteCodePtr; + unsigned numByteCodes; + unsigned tokenDex; + unsigned oddByte = 0; + unsigned short codeWord; + unsigned char codeByte; + unsigned ptextLen = *plainTextLen; // bytes REMAINING + unsigned above; + unsigned j; + unsigned char blockDesc; + dcbReturn drtn; + int len; + queueElt *src; + queueElt *dst; + int lastWord = 0; + queueElt *cbufq = &cbuf->queue[0]; + int level2 = 0; // 2nd level comcrypted block + unsigned match; + unsigned char sigSeq; // signature sequence enable + unsigned char nibble; + + blockDesc = cipherText[CTBO_BLOCK_DESC]; + if((blockDesc & CBD_MAGIC_MASK) != CBD_MAGIC) { + ddprintf(("deComcryptBlock: bad CBD_MAGIC\n")); + return DCB_PARSE_ERROR; + } + + /* + * Min block size - blockDesc, numLongCodes, numTokens, one token byte, + * one bytecode + */ + if(cipherTextLen < 5) { + return DCB_SHORT; + } + if((blockDesc & CBD_FULL_BLOCK_MASK) == CBD_FULL_BLOCK) { + /* + * # of token bits implied for full block + */ + numTokenBits = TOKEN_BITS_FROM_PTEXT(CC_BLOCK_SIZE); + numTokenBytes = TOKEN_BYTES_FROM_PTEXT(CC_BLOCK_SIZE); + tokenPtr = cipherText + CTBO_NUM_TOKENS; + } + else { + numTokenBits = cipherText[CTBO_NUM_TOKENS]; + numTokenBytes = TOKEN_BYTES_FROM_TOKEN_BITS(numTokenBits); + tokenPtr = cipherText + CTBO_NUM_TOKENS + 1; + } + longCodePtr = tokenPtr + numTokenBytes; + numLongCodes = cipherText[CTBO_NUM_LONG_CODES]; + + byteCodePtr = longCodePtr + (numLongCodes * 2); // may increment... + if((blockDesc & CBD_BLOCK_TYPE_MASK) == CBD_SINGLE) { + /* + * # of bytecodes implied from numTokenBits and numLongCodes + */ + numByteCodes = numTokenBits - numLongCodes; + } + else { + /* + * size of 2nd level comcrypted bytecode specified after longCode + * array (and before the bytecode itself). + * Careful, verify that we can read numByteCodes first... + */ + if((unsigned)(byteCodePtr - cipherText) > cipherTextLen) { + return DCB_SHORT; + } + numByteCodes = *byteCodePtr++; + level2 = 1; + } + *blockSize = (unsigned)(byteCodePtr - cipherText) + numByteCodes; + if(*blockSize > cipherTextLen) { + return DCB_SHORT; + } + + /* + * We now know that we have a complete cipherblock. Go for it. + */ + if(level2) { + /* + * this block's bytecode array contains 2nd level comcrypted bytecodes. + */ + unsigned thisPtext = cbuf->level2BufSize; + unsigned level1CodeSize; + + if(cbuf->nextBuf == NULL) { + ddprintf(("2-level comcypt, no nextBuf available!\n")); + return DCB_PARSE_ERROR; + } + drtn = deComcryptBlock(cpriv, + cbuf->nextBuf, + byteCodePtr, + numByteCodes, + cbuf->level2Buf, + &thisPtext, + CCE_END_OF_STREAM, + &level1CodeSize); + switch(drtn) { + case DCB_SHORT: + ddprintf(("CBT_DOUBLE block, incomplete cipherblock in " + "2nd level code\n")); + return DCB_PARSE_ERROR; + + case DCB_OUTBUFFER_TOO_SMALL: // not our fault! + case DCB_PARSE_ERROR: + default: + ddprintf(("2nd-level decomcrypt error (%d)\n", drtn)); + return drtn; + + case DCB_SUCCESS: + /* + * Supposedly we passed in exactly one cipherblock... + */ + if(numByteCodes != level1CodeSize) { + ddprintf(("2nd-level decomcrypt: " + "numByteCodes != level1CodeSize\n")); + return DCB_PARSE_ERROR; + } + l2printf(("2nd-level decomcrypt: ciphertext %d " + "numByteCodes %d\n", numByteCodes, thisPtext)); + break; + } + byteCodePtr = cbuf->level2Buf; + numByteCodes = thisPtext; + } + + if((blockDesc & CBD_ODD_MASK) == CBD_ODD) { + oddByte = 1; + } + + /* + * Skip signature sequence if this was a 2nd level comcrypted block + */ + sigSeq = cpriv->sigSeqEnable && !level2; + + for(tokenDex=0; tokenDexkey, cpriv->keybytes, + (cbuf->nybbleDex)++); + + if(match) { + codeByte = *byteCodePtr++; + + if(sigSeq) { + codeByte ^= (unsigned char)(cbuf->sigArray[tokenDex]); + } + + /* + * dynamically process the queue for match - 8 bits + * of ciphercode, 16 bits of plaintext + */ + codeWord = cbufq[codeByte]; + above = (cbuf->f1 * codeByte * (16 + nibble)) >> 9; + +#if SKIP_NIBBLE_ON_QUEUE_0 + if(codeByte == 0) { + /* + * Special case for top of queue optimization during + * comcrypt + */ + nibble = cbuf->nybbleDex - 1; + } +#endif /*SKIP_NIBBLE_ON_QUEUE_0*/ + + /* + * queue[above..codeByte] move one element towards end + * queue[above] = this codeWord + */ + len = (int)codeByte - (int)above; + if(len > QUEUE_MEMMOVE_THRESH) { + src = &cbufq[above]; + dst = src + 1; + len *= sizeof(queueElt); + memmove(dst, src, len); + } + else { + for(j = codeByte; j > above; j--) { + cbufq[j] = cbufq[j-1]; + } + } + cbufq[above] = codeWord; + } + else { + /* + * !match, 16 bits of code + */ + deserializeShort(codeWord, longCodePtr); + if(sigSeq) { + codeWord ^= cbuf->sigArray[tokenDex]; + } + + if(oddByte && (tokenDex == (numTokenBits - 1))) { + lastWord = 1; + above = 0; +#if SKIP_NIBBLE_ON_QUEUE_0 + nibble = cbuf->nybbleDex - 1; +#endif /*SKIP_NIBBLE_ON_QUEUE_0*/ + } + else { + longCodePtr += 2; + + /* + * dynamically process the queue for unmatch; skip if this + * is an oddByte codeword. + * queue[above...QLEN-1] move one element toward end + * queue[above] = new codeWord + */ + above = ABOVE(cbuf->f2) + nibble; + len = QLEN - 1 - (int)above; + if(len > QUEUE_MEMMOVE_THRESH) { + src = &cbufq[above]; + dst = src + 1; + len *= sizeof(queueElt); + memmove(dst, src, len); + } + else { + for(j=QLEN-1; j > above; j--) { + cbufq[j] = cbufq[j-1]; + } + } + cbufq[above] = codeWord; + } + } + + if(sigSeq) { + /* + * Advance signature sequence state machine. + */ + nextSigWord(cbuf, tokenDex+1, match, (above + nibble)); + } + + /* + * cook up a byte or two of plainText from code word and invmap[] + */ + if(ptextLen < 1) { + ddprintf(("decryptBlock: ptext overflow (1)\n")); + return DCB_OUTBUFFER_TOO_SMALL; + } + *plainText++ = cpriv->invmap[(codeWord >> 8) & 0xff]; + ptextLen--; + if(lastWord) { + /* + * end of oddByte block. + */ + tokenDex++; // for sigArray maintenance + break; // out of main loop + } + else { + if(ptextLen < 1) { + ddprintf(("decryptBlock: ptext overflow (2)\n")); + return DCB_OUTBUFFER_TOO_SMALL; + } + *plainText++ = cpriv->invmap[(codeWord) & 0xff]; + ptextLen--; + } + } + + /* + * Prime sigArray state machine for next block. + */ + if(sigSeq) { + cbuf->sigArray[0] = cbuf->sigArray[tokenDex]; + } + *plainTextLen = *plainTextLen - ptextLen; + return DCB_SUCCESS; +} + +comcryptReturn deComcryptData( + comcryptObj cobj, + unsigned char *cipherText, + unsigned cipherTextLen, + unsigned char *plainText, + unsigned *plainTextLen, // IN/OUT + comcryptEos endOfStream) // CCE_END_OF_STREAM, etc. + +{ + comcryptPriv *cpriv = (comcryptPriv *)cobj; + unsigned char *outorigin = plainText; + unsigned ptextLen = *plainTextLen; + unsigned thisPtext; // per block + unsigned blockSize; + dcbReturn drtn; + unsigned ctextUsed; + + /* + * Snag version from ciphertext, or as much as we can get + */ + while((cpriv->versionBytes < VERSION_BYTES) && cipherTextLen) { + cpriv->version <<= 8; + cpriv->version |= *cipherText; + cpriv->versionBytes++; + cipherText++; + cipherTextLen--; + } + + /* + * Then skip over the remainder of the header (currently spares) + */ + if((cpriv->spareBytes < SPARE_BYTES) && cipherTextLen) { + unsigned toSkip = SPARE_BYTES - cpriv->spareBytes; + + if(toSkip > cipherTextLen) { + toSkip = cipherTextLen; + } + cpriv->spareBytes += toSkip; + cipherText += toSkip; + cipherTextLen -= toSkip; + } + + if(cipherTextLen == 0) { + *plainTextLen = 0; + return CCR_SUCCESS; + } + + if(cpriv->version != VERSION_3_Dec_97) { + ddprintf(("Incompatible version.\n")); + return CCR_BAD_CIPHERTEXT; + } + + while(cipherTextLen != 0) { + + /* + * Main loop. First deal with possible existing partial block. + */ + if(cpriv->cbuf.codeBufLength != 0) { + unsigned toCopy = + cpriv->cbuf.codeBufSize - cpriv->cbuf.codeBufLength; + unsigned origBufSize = cpriv->cbuf.codeBufLength; + + if(toCopy > cipherTextLen) { + toCopy = cipherTextLen; + } + memmove(cpriv->cbuf.codeBuf + cpriv->cbuf.codeBufLength, + cipherText, toCopy); + cpriv->cbuf.codeBufLength += toCopy; + + thisPtext = ptextLen; + drtn = deComcryptBlock(cpriv, + &cpriv->cbuf, + cpriv->cbuf.codeBuf, + cpriv->cbuf.codeBufLength, + plainText, + &thisPtext, + endOfStream, + &blockSize); + switch(drtn) { + case DCB_SHORT: + /* + * Incomplete block in codeBuf + */ + if(endOfStream == CCE_END_OF_STREAM) { + /* + * Caller thinks this is the end, but we need more + */ + ddprintf(("deComcryptData(): CCE_END_OF_STREAM, " + "not end of block\n")); + return CCR_BAD_CIPHERTEXT; + } + cipherTextLen -= toCopy; + if(cipherTextLen != 0) { + /* + * i.e., codeBuf overflow - could be s/w error? Do + * we need a bigger buffer? + */ + ddprintf(("deComcryptData: full codeBuf, incomplete " + "block\n")); + return CCR_BAD_CIPHERTEXT; + } + else { + /* + * OK, stash it and try again + */ + scprintf(("====incomplete codeBuf, codeBufLength %d, " + "cipherTextLen %d\n", + cpriv->cbuf.codeBufLength, toCopy)); + break; // out of main loop (after this switch) + } + + case DCB_OUTBUFFER_TOO_SMALL: + ddprintf(("codeBuf decomcrypt error short buf\n")); + return CCR_OUTBUFFER_TOO_SMALL; + + case DCB_PARSE_ERROR: + default: + ddprintf(("codeBuf decomcrypt error (%d)\n", drtn)); + return CCR_BAD_CIPHERTEXT; + + case DCB_SUCCESS: + /* + * ctextUsed is how much of caller's ciphertext we used + * in this buffered block + */ + ctextUsed = blockSize - origBufSize; + scprintf(("====decrypted block in codeBuf, blockSize %d, " + "ctextUsed %d, thisPtext %d\n", + blockSize, ctextUsed, thisPtext)); + cipherText += ctextUsed; + cipherTextLen -= ctextUsed; + plainText += thisPtext; + ptextLen -= thisPtext; + cpriv->cbuf.codeBufLength = 0; + break; + } + + /* + * We might have used up all of caller's cipherText processing + * codeBuf... + */ + if(cipherTextLen == 0) { + break; // out of main loop + } + + } /* buffered ciphertext in codeBuf */ + + /* + * Snarf ciphertext, one block at a time. + */ + + thisPtext = ptextLen; + drtn = deComcryptBlock(cpriv, + &cpriv->cbuf, + cipherText, + cipherTextLen, + plainText, + &thisPtext, + endOfStream, + &blockSize); + switch(drtn) { + case DCB_SHORT: + /* + * Incomplete block + */ + if(endOfStream == CCE_END_OF_STREAM) { + ddprintf(("deComcryptData(): CCE_END_OF_STREAM, not end of " + "block (2)\n")); + return CCR_BAD_CIPHERTEXT; + } + if(cipherTextLen > + (cpriv->cbuf.codeBufSize - cpriv->cbuf.codeBufLength)) { + ddprintf(("deComcryptData(): codeBuf overflow!\n")); + return CCR_BAD_CIPHERTEXT; + } + memmove(cpriv->cbuf.codeBuf + cpriv->cbuf.codeBufLength, + cipherText, cipherTextLen); + cpriv->cbuf.codeBufLength += cipherTextLen; + cipherTextLen = 0; + scprintf(("====Incomplete block, cipherTextLen %d " + "codeBufLength %d\n", cipherTextLen, + cpriv->cbuf.codeBufLength)); + break; // actually out of main loop + + case DCB_PARSE_ERROR: + case DCB_OUTBUFFER_TOO_SMALL: + default: + return CCR_BAD_CIPHERTEXT; + + case DCB_SUCCESS: + if(ptextLen < thisPtext) { + /* + * Software error + */ + ddprintf(("deComcryptData: undetected ptext " + "overflow (2)\n")); + return CCR_BAD_CIPHERTEXT; + } + plainText += thisPtext; + ptextLen -= thisPtext; + cipherText += blockSize; + cipherTextLen -= blockSize; + scprintf(("====decrypted one block, blockSize %d " + "thisPtext %d\n", blockSize, thisPtext)); + break; + } + } /* main loop */ + + *plainTextLen = (unsigned)(plainText - outorigin); + return CCR_SUCCESS; +}