X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/914fc88e61be54aed6b18205ff2775b48793a3b6..866f8763175ff60e4fa455b92b5eb660a12fe6c7:/libsecurity_smime/lib/cmscipher.c diff --git a/libsecurity_smime/lib/cmscipher.c b/libsecurity_smime/lib/cmscipher.c index 5d869a6e..3c86da76 100644 --- a/libsecurity_smime/lib/cmscipher.c +++ b/libsecurity_smime/lib/cmscipher.c @@ -45,8 +45,14 @@ #include #include +#if USE_CDSA_CRYPTO +#include +#include +#include +#else #include #include +#endif /* * ------------------------------------------------------------------- @@ -92,6 +98,68 @@ __unused static const SecAsn1Template sec_rc2cbc_parameter_template[] = { { 0 } }; +// TODO: get rid of this? +#if USE_CDSA_CRYPTO +/* +** Convert a der encoded *signed* integer into a machine integral value. +** If an underflow/overflow occurs, sets error code and returns min/max. +*/ +static long +DER_GetInteger(SecAsn1Item *it) +{ + long ival = 0; + unsigned len = it->Length; + unsigned char *cp = it->Data; + unsigned long overflow = 0x1ffUL << (((sizeof(ival) - 1) * 8) - 1); + unsigned long ofloinit; + + if (*cp & 0x80) + ival = -1L; + ofloinit = ival & overflow; + + while (len) { + if ((ival & overflow) != ofloinit) { + PORT_SetError(SEC_ERROR_BAD_DER); + if (ival < 0) { + return LONG_MIN; + } + return LONG_MAX; + } + ival = ival << 8; + ival |= *cp++; + --len; + } + return ival; +} + +/* S/MIME picked id values to represent differnt keysizes */ +/* I do have a formula, but it ain't pretty, and it only works because you + * can always match three points to a parabola:) */ +static unsigned char rc2_map(SecAsn1Item *version) +{ + long x; + + x = DER_GetInteger(version); + + switch (x) { + case 58: return 128; + case 120: return 64; + case 160: return 40; + } + return 128; +} + +static unsigned long rc2_unmap(unsigned long x) +{ + switch (x) { + case 128: return 58; + case 64: return 120; + case 40: return 160; + } + return 58; +} +#endif /* USE_CDSA_CRYPTO */ + /* default IV size in bytes */ #define DEFAULT_IV_SIZE 8 /* IV/block size for AES */ @@ -99,12 +167,14 @@ __unused static const SecAsn1Template sec_rc2cbc_parameter_template[] = { /* max IV size in bytes */ #define MAX_IV_SIZE AES_BLOCK_SIZE +#if !USE_CDSA_CRYPTO #ifndef kCCKeySizeMaxRC2 #define kCCKeySizeMaxRC2 16 #endif #ifndef kCCBlockSizeRC2 #define kCCBlockSizeRC2 8 #endif +#endif static SecCmsCipherContextRef SecCmsCipherContextStart(PRArenaPool *poolp, SecSymmetricKeyRef key, SECAlgorithmID *algid, Boolean encrypt) @@ -115,16 +185,79 @@ SecCmsCipherContextStart(PRArenaPool *poolp, SecSymmetricKeyRef key, SECAlgorith OSStatus rv; uint8_t ivbuf[MAX_IV_SIZE]; SecAsn1Item initVector = { DEFAULT_IV_SIZE, ivbuf }; +#if USE_CDSA_CRYPTO + CSSM_CC_HANDLE ciphercc = 0; + CSSM_ALGORITHMS algorithm; + CSSM_PADDING padding = CSSM_PADDING_PKCS7; + CSSM_ENCRYPT_MODE mode; + CSSM_CSP_HANDLE cspHandle; + const CSSM_KEY *cssmKey; + //CSSM_CONTEXT_ATTRIBUTE contextAttribute = { CSSM_ATTRIBUTE_ALG_PARAMS, sizeof(SecAsn1Item *) }; +#else CCCryptorRef ciphercc = NULL; CCOptions cipheroptions = kCCOptionPKCS7Padding; int cipher_blocksize = 0; +#endif + +#if USE_CDSA_CRYPTO + rv = SecKeyGetCSPHandle(key, &cspHandle); + if (rv) + goto loser; + rv = SecKeyGetCSSMKey(key, &cssmKey); + if (rv) + goto loser; +#endif + // @@@ Add support for PBE based stuff oidData = SECOID_FindOID(&algid->algorithm); if (!oidData) goto loser; algtag = oidData->offset; +#if USE_CDSA_CRYPTO + algorithm = oidData->cssmAlgorithm; + if (!algorithm) + goto loser; + + switch (algtag) + { + case SEC_OID_RC2_CBC: + case SEC_OID_RC4: + case SEC_OID_DES_EDE3_CBC: + case SEC_OID_DES_EDE: + case SEC_OID_DES_CBC: + case SEC_OID_RC5_CBC_PAD: + case SEC_OID_FORTEZZA_SKIPJACK: + mode = CSSM_ALGMODE_CBCPadIV8; + break; + + /* RFC 3565 says that these sizes refer to key size, NOT block size */ + case SEC_OID_AES_128_CBC: + case SEC_OID_AES_192_CBC: + case SEC_OID_AES_256_CBC: + initVector.Length = AES_BLOCK_SIZE; + mode = CSSM_ALGMODE_CBCPadIV8; + break; + + case SEC_OID_DES_ECB: + case SEC_OID_AES_128_ECB: + case SEC_OID_AES_192_ECB: + case SEC_OID_AES_256_ECB: + mode = CSSM_ALGMODE_ECBPad; + break; + + case SEC_OID_DES_OFB: + mode = CSSM_ALGMODE_OFBPadIV8; + break; + case SEC_OID_DES_CFB: + mode = CSSM_ALGMODE_CFBPadIV8; + break; + + default: + goto loser; + } +#else CCAlgorithm alg = -1; switch (algtag) { case SEC_OID_DES_CBC: @@ -149,12 +282,30 @@ SecCmsCipherContextStart(PRArenaPool *poolp, SecSymmetricKeyRef key, SECAlgorith default: goto loser; } +#endif if (encrypt) { +#if USE_CDSA_CRYPTO + CSSM_CC_HANDLE randomcc; + //SecAsn1Item *parameters; + + // Generate random initVector + if (CSSM_CSP_CreateRandomGenContext(cspHandle, + CSSM_ALGID_APPLE_YARROW, + NULL, /* seed*/ + initVector.Length, + &randomcc)) + goto loser; + + if (CSSM_GenerateRandom(randomcc, &initVector)) + goto loser; + CSSM_DeleteContext(randomcc); +#else if (SecRandomCopyBytes(kSecRandomDefault, initVector.Length, initVector.Data)) goto loser; +#endif // Put IV into algid.parameters switch (algtag) @@ -179,6 +330,25 @@ SecCmsCipherContextStart(PRArenaPool *poolp, SecSymmetricKeyRef key, SECAlgorith goto loser; break; case SEC_OID_RC2_CBC: +#if USE_CDSA_CRYPTO + { + sec_rc2cbcParameter rc2 = {}; + unsigned long rc2version; + SecAsn1Item *newParams; + + rc2.iv = initVector; + rc2version = rc2_unmap(cssmKey->KeyHeader.LogicalKeySizeInBits); + if (!SEC_ASN1EncodeUnsignedInteger (NULL, &(rc2.rc2ParameterVersion), + rc2version)) + goto loser; + newParams = SEC_ASN1EncodeItem (poolp, &algid->parameters, &rc2, + sec_rc2cbc_parameter_template); + PORT_Free(rc2.rc2ParameterVersion.Data); + if (newParams == NULL) + goto loser; + break; + } +#endif case SEC_OID_RC5_CBC_PAD: default: // @@@ Implement rc5 params stuff. @@ -221,6 +391,31 @@ SecCmsCipherContextStart(PRArenaPool *poolp, SecSymmetricKeyRef key, SECAlgorith break; } case SEC_OID_RC2_CBC: +#if USE_CDSA_CRYPTO + { + sec_rc2cbcParameter rc2 = {}; + unsigned long ulEffectiveBits; + + rv = SEC_ASN1DecodeItem(NULL, &rc2 ,sec_rc2cbc_parameter_template, + &(algid->parameters)); + if (rv) + goto loser; + + if (initVector.Length != rc2.iv.Length) { + PORT_Free(rc2.iv.Data); + PORT_Free(rc2.rc2ParameterVersion.Data); + goto loser; + } + memcpy(initVector.Data, rc2.iv.Data, initVector.Length); + PORT_Free(rc2.iv.Data); + + ulEffectiveBits = rc2_map(&rc2.rc2ParameterVersion); + PORT_Free(rc2.rc2ParameterVersion.Data); + if (ulEffectiveBits != cssmKey->KeyHeader.LogicalKeySizeInBits) + goto loser; + break; + } +#endif case SEC_OID_RC5_CBC_PAD: default: // @@@ Implement rc5 params stuff. @@ -229,10 +424,30 @@ SecCmsCipherContextStart(PRArenaPool *poolp, SecSymmetricKeyRef key, SECAlgorith } } +#if USE_CDSA_CRYPTO + if (CSSM_CSP_CreateSymmetricContext(cspHandle, + algorithm, + mode, + NULL, /* accessCred */ + cssmKey, + &initVector, + padding, + NULL, /* reserved */ + &ciphercc)) + goto loser; + + if (encrypt) + rv = CSSM_EncryptDataInit(ciphercc); + else + rv = CSSM_DecryptDataInit(ciphercc); + if (rv) + goto loser; +#else if (CCCryptorCreate(encrypt ? kCCEncrypt : kCCDecrypt, alg, cipheroptions, CFDataGetBytePtr(key), CFDataGetLength(key), initVector.Data, &ciphercc)) goto loser; +#endif cc = (SecCmsCipherContextRef)PORT_ZAlloc(sizeof(SecCmsCipherContext)); if (cc == NULL) @@ -240,11 +455,17 @@ SecCmsCipherContextStart(PRArenaPool *poolp, SecSymmetricKeyRef key, SECAlgorith cc->cc = ciphercc; cc->encrypt = encrypt; +#if !USE_CDSA_CRYPTO cc->block_size =cipher_blocksize; +#endif return cc; loser: if (ciphercc) +#if USE_CDSA_CRYPTO + CSSM_DeleteContext(ciphercc); +#else CCCryptorRelease(ciphercc); +#endif return NULL; } @@ -261,6 +482,88 @@ SecCmsCipherContextRef SecCmsCipherContextStartDecrypt(SecSymmetricKeyRef key, SECAlgorithmID *algid) { return SecCmsCipherContextStart(NULL, key, algid, PR_FALSE); +#if 0 + SecCmsCipherContextRef cc; + void *ciphercx; + CK_MECHANISM_TYPE mechanism; + SecAsn1Item * param; + PK11SlotInfo *slot; + SECOidTag algtag; + + algtag = SECOID_GetAlgorithmTag(algid); + + /* set param and mechanism */ + if (SEC_PKCS5IsAlgorithmPBEAlg(algid)) { + CK_MECHANISM pbeMech, cryptoMech; + SecAsn1Item * pbeParams; + SEC_PKCS5KeyAndPassword *keyPwd; + + PORT_Memset(&pbeMech, 0, sizeof(CK_MECHANISM)); + PORT_Memset(&cryptoMech, 0, sizeof(CK_MECHANISM)); + + /* HACK ALERT! + * in this case, key is not actually a SecSymmetricKeyRef, but a SEC_PKCS5KeyAndPassword * + */ + keyPwd = (SEC_PKCS5KeyAndPassword *)key; + key = keyPwd->key; + + /* find correct PK11 mechanism and parameters to initialize pbeMech */ + pbeMech.mechanism = PK11_AlgtagToMechanism(algtag); + pbeParams = PK11_ParamFromAlgid(algid); + if (!pbeParams) + return NULL; + pbeMech.pParameter = pbeParams->Data; + pbeMech.ulParameterLen = pbeParams->Length; + + /* now map pbeMech to cryptoMech */ + if (PK11_MapPBEMechanismToCryptoMechanism(&pbeMech, &cryptoMech, keyPwd->pwitem, + PR_FALSE) != CKR_OK) { + SECITEM_ZfreeItem(pbeParams, PR_TRUE); + return NULL; + } + SECITEM_ZfreeItem(pbeParams, PR_TRUE); + + /* and use it to initialize param & mechanism */ + if ((param = (SecAsn1Item *)PORT_ZAlloc(sizeof(SecAsn1Item))) == NULL) + return NULL; + + param->Data = (unsigned char *)cryptoMech.pParameter; + param->Length = cryptoMech.ulParameterLen; + mechanism = cryptoMech.mechanism; + } else { + mechanism = PK11_AlgtagToMechanism(algtag); + if ((param = PK11_ParamFromAlgid(algid)) == NULL) + return NULL; + } + + cc = (SecCmsCipherContextRef)PORT_ZAlloc(sizeof(SecCmsCipherContext)); + if (cc == NULL) { + SECITEM_FreeItem(param,PR_TRUE); + return NULL; + } + + /* figure out pad and block sizes */ + cc->pad_size = PK11_GetBlockSize(mechanism, param); + slot = PK11_GetSlotFromKey(key); + cc->block_size = PK11_IsHW(slot) ? BLOCK_SIZE : cc->pad_size; + PK11_FreeSlot(slot); + + /* create PK11 cipher context */ + ciphercx = PK11_CreateContextBySymKey(mechanism, CKA_DECRYPT, key, param); + SECITEM_FreeItem(param, PR_TRUE); + if (ciphercx == NULL) { + PORT_Free (cc); + return NULL; + } + + cc->cx = ciphercx; + cc->doit = (nss_cms_cipher_function) PK11_CipherOp; + cc->destroy = (nss_cms_cipher_destroy) PK11_DestroyContext; + cc->encrypt = PR_FALSE; + cc->pending_count = 0; + + return cc; +#endif } /* @@ -276,6 +579,107 @@ SecCmsCipherContextRef SecCmsCipherContextStartEncrypt(PRArenaPool *poolp, SecSymmetricKeyRef key, SECAlgorithmID *algid) { return SecCmsCipherContextStart(poolp, key, algid, PR_TRUE); +#if 0 + SecCmsCipherContextRef cc; + void *ciphercx; + SecAsn1Item * param; + OSStatus rv; + CK_MECHANISM_TYPE mechanism; + PK11SlotInfo *slot; + Boolean needToEncodeAlgid = PR_FALSE; + SECOidTag algtag = SECOID_GetAlgorithmTag(algid); + + /* set param and mechanism */ + if (SEC_PKCS5IsAlgorithmPBEAlg(algid)) { + CK_MECHANISM pbeMech, cryptoMech; + SecAsn1Item * pbeParams; + SEC_PKCS5KeyAndPassword *keyPwd; + + PORT_Memset(&pbeMech, 0, sizeof(CK_MECHANISM)); + PORT_Memset(&cryptoMech, 0, sizeof(CK_MECHANISM)); + + /* HACK ALERT! + * in this case, key is not actually a SecSymmetricKeyRef, but a SEC_PKCS5KeyAndPassword * + */ + keyPwd = (SEC_PKCS5KeyAndPassword *)key; + key = keyPwd->key; + + /* find correct PK11 mechanism and parameters to initialize pbeMech */ + pbeMech.mechanism = PK11_AlgtagToMechanism(algtag); + pbeParams = PK11_ParamFromAlgid(algid); + if (!pbeParams) + return NULL; + pbeMech.pParameter = pbeParams->Data; + pbeMech.ulParameterLen = pbeParams->Length; + + /* now map pbeMech to cryptoMech */ + if (PK11_MapPBEMechanismToCryptoMechanism(&pbeMech, &cryptoMech, keyPwd->pwitem, + PR_FALSE) != CKR_OK) { + SECITEM_ZfreeItem(pbeParams, PR_TRUE); + return NULL; + } + SECITEM_ZfreeItem(pbeParams, PR_TRUE); + + /* and use it to initialize param & mechanism */ + if ((param = (SecAsn1Item *)PORT_ZAlloc(sizeof(SecAsn1Item))) == NULL) + return NULL; + + param->Data = (unsigned char *)cryptoMech.pParameter; + param->Length = cryptoMech.ulParameterLen; + mechanism = cryptoMech.mechanism; + } else { + mechanism = PK11_AlgtagToMechanism(algtag); + if ((param = PK11_GenerateNewParam(mechanism, key)) == NULL) + return NULL; + needToEncodeAlgid = PR_TRUE; + } + + cc = (SecCmsCipherContextRef)PORT_ZAlloc(sizeof(SecCmsCipherContext)); + if (cc == NULL) + return NULL; + + /* now find pad and block sizes for our mechanism */ + cc->pad_size = PK11_GetBlockSize(mechanism,param); + slot = PK11_GetSlotFromKey(key); + cc->block_size = PK11_IsHW(slot) ? BLOCK_SIZE : cc->pad_size; + PK11_FreeSlot(slot); + + /* and here we go, creating a PK11 cipher context */ + ciphercx = PK11_CreateContextBySymKey(mechanism, CKA_ENCRYPT, key, param); + if (ciphercx == NULL) { + PORT_Free(cc); + cc = NULL; + goto loser; + } + + /* + * These are placed after the CreateContextBySymKey() because some + * mechanisms have to generate their IVs from their card (i.e. FORTEZZA). + * Don't move it from here. + * XXX is that right? the purpose of this is to get the correct algid + * containing the IVs etc. for encoding. this means we need to set this up + * BEFORE encoding the algid in the contentInfo, right? + */ + if (needToEncodeAlgid) { + rv = PK11_ParamToAlgid(algtag, param, poolp, algid); + if(rv != SECSuccess) { + PORT_Free(cc); + cc = NULL; + goto loser; + } + } + + cc->cx = ciphercx; + cc->doit = (nss_cms_cipher_function)PK11_CipherOp; + cc->destroy = (nss_cms_cipher_destroy)PK11_DestroyContext; + cc->encrypt = PR_TRUE; + cc->pending_count = 0; + +loser: + SECITEM_FreeItem(param, PR_TRUE); + + return cc; +#endif } void @@ -284,16 +688,31 @@ SecCmsCipherContextDestroy(SecCmsCipherContextRef cc) PORT_Assert(cc != NULL); if (cc == NULL) return; - +#if USE_CDSA_CRYPTO + CSSM_DeleteContext(cc->cc); +#else CCCryptorRelease(cc->cc); - +#endif PORT_Free(cc); } static unsigned int SecCmsCipherContextLength(SecCmsCipherContextRef cc, unsigned int input_len, Boolean final, Boolean encrypt) { +#if USE_CDSA_CRYPTO + CSSM_QUERY_SIZE_DATA dataBlockSize[2] = { { input_len, 0 }, { input_len, 0 } }; + /* Hack CDSA treats the last block as the final one. So unless we are being asked to report the final size we ask for 2 block and ignore the second (final) one. */ + OSStatus rv = CSSM_QuerySize(cc->cc, cc->encrypt, final ? 1 : 2, dataBlockSize); + if (rv) + { + PORT_SetError(rv); + return 0; + } + + return dataBlockSize[0].SizeOutputBlock; +#else return ((input_len + cc->block_size - 1) / cc->block_size * cc->block_size) + (final ? cc->block_size : 0); +#endif } /* @@ -320,7 +739,46 @@ SecCmsCipherContextLength(SecCmsCipherContextRef cc, unsigned int input_len, Boo unsigned int SecCmsCipherContextDecryptLength(SecCmsCipherContextRef cc, unsigned int input_len, Boolean final) { +#if 1 return SecCmsCipherContextLength(cc, input_len, final, PR_FALSE); +#else + int blocks, block_size; + + PORT_Assert (! cc->encrypt); + + block_size = cc->block_size; + + /* + * If this is not a block cipher, then we always have the same + * number of output bytes as we had input bytes. + */ + if (block_size == 0) + return input_len; + + /* + * On the final call, we will always use up all of the pending + * bytes plus all of the input bytes, *but*, there will be padding + * at the end and we cannot predict how many bytes of padding we + * will end up removing. The amount given here is actually known + * to be at least 1 byte too long (because we know we will have + * at least 1 byte of padding), but seemed clearer/better to me. + */ + if (final) + return cc->pending_count + input_len; + + /* + * Okay, this amount is exactly what we will output on the + * next cipher operation. We will always hang onto the last + * 1 - block_size bytes for non-final operations. That is, + * we will do as many complete blocks as we can *except* the + * last block (complete or partial). (This is because until + * we know we are at the end, we cannot know when to interpret + * and removing the padding byte(s), which are guaranteed to + * be there.) + */ + blocks = (cc->pending_count + input_len - 1) / block_size; + return blocks * block_size; +#endif } /* @@ -343,7 +801,48 @@ SecCmsCipherContextDecryptLength(SecCmsCipherContextRef cc, unsigned int input_l unsigned int SecCmsCipherContextEncryptLength(SecCmsCipherContextRef cc, unsigned int input_len, Boolean final) { +#if 1 return SecCmsCipherContextLength(cc, input_len, final, PR_TRUE); +#else + int blocks, block_size; + int pad_size; + + PORT_Assert (cc->encrypt); + + block_size = cc->block_size; + pad_size = cc->pad_size; + + /* + * If this is not a block cipher, then we always have the same + * number of output bytes as we had input bytes. + */ + if (block_size == 0) + return input_len; + + /* + * On the final call, we only send out what we need for + * remaining bytes plus the padding. (There is always padding, + * so even if we have an exact number of blocks as input, we + * will add another full block that is just padding.) + */ + if (final) { + if (pad_size == 0) { + return cc->pending_count + input_len; + } else { + blocks = (cc->pending_count + input_len) / pad_size; + blocks++; + return blocks*pad_size; + } + } + + /* + * Now, count the number of complete blocks of data we have. + */ + blocks = (cc->pending_count + input_len) / block_size; + + + return blocks * block_size; +#endif } @@ -358,16 +857,34 @@ SecCmsCipherContextCrypt(SecCmsCipherContextRef cc, unsigned char *output, if (input_len) { + +#if USE_CDSA_CRYPTO + SecAsn1Item inputBuf = { input_len, (uint8_t *)input }; + SecAsn1Item outputBuf = { max_output_len, output }; + if (encrypt) + rv = CSSM_EncryptDataUpdate(cc->cc, &inputBuf, 1, &outputBuf, 1, &bytes_output); + else + rv = CSSM_DecryptDataUpdate(cc->cc, &inputBuf, 1, &outputBuf, 1, &bytes_output); +#else rv = CCCryptorUpdate(cc->cc, input, input_len, output, max_output_len, &bytes_output); +#endif } if (!rv && final) { +#if USE_CDSA_CRYPTO + SecAsn1Item remainderBuf = { max_output_len - bytes_output, output + bytes_output }; + if (encrypt) + rv = CSSM_EncryptDataFinal(cc->cc, &remainderBuf); + else + rv = CSSM_DecryptDataFinal(cc->cc, &remainderBuf); + bytes_output += remainderBuf.Length; +#else size_t bytes_output_final = 0; rv = CCCryptorFinal(cc->cc, output+bytes_output, max_output_len-bytes_output, &bytes_output_final); bytes_output += bytes_output_final; +#endif } - if (rv) PORT_SetError(SEC_ERROR_BAD_DATA); else if (output_len_p) @@ -412,10 +929,181 @@ SecCmsCipherContextDecrypt(SecCmsCipherContextRef cc, unsigned char *output, const unsigned char *input, unsigned int input_len, Boolean final) { +#if 1 return SecCmsCipherContextCrypt(cc, output, output_len_p, max_output_len, input, input_len, final, PR_FALSE); +#else + int blocks, bsize, pcount, padsize; + unsigned int max_needed, ifraglen, ofraglen, output_len; + unsigned char *pbuf; + OSStatus rv; + + PORT_Assert (! cc->encrypt); + + /* + * Check that we have enough room for the output. Our caller should + * already handle this; failure is really an internal error (i.e. bug). + */ + max_needed = SecCmsCipherContextDecryptLength(cc, input_len, final); + PORT_Assert (max_output_len >= max_needed); + if (max_output_len < max_needed) { + /* PORT_SetError (XXX); */ + return SECFailure; + } + + /* + * hardware encryption does not like small decryption sizes here, so we + * allow both blocking and padding. + */ + bsize = cc->block_size; + padsize = cc->pad_size; + + /* + * When no blocking or padding work to do, we can simply call the + * cipher function and we are done. + */ + if (bsize == 0) { + return (* cc->doit) (cc->cx, output, output_len_p, max_output_len, + input, input_len); + } + + pcount = cc->pending_count; + pbuf = cc->pending_buf; + + output_len = 0; + + if (pcount) { + /* + * Try to fill in an entire block, starting with the bytes + * we already have saved away. + */ + while (input_len && pcount < bsize) { + pbuf[pcount++] = *input++; + input_len--; + } + /* + * If we have at most a whole block and this is not our last call, + * then we are done for now. (We do not try to decrypt a lone + * single block because we cannot interpret the padding bytes + * until we know we are handling the very last block of all input.) + */ + if (input_len == 0 && !final) { + cc->pending_count = pcount; + if (output_len_p) + *output_len_p = 0; + return SECSuccess; + } + /* + * Given the logic above, we expect to have a full block by now. + * If we do not, there is something wrong, either with our own + * logic or with (length of) the data given to us. + */ + if ((padsize != 0) && (pcount % padsize) != 0) { + PORT_Assert (final); + PORT_SetError (SEC_ERROR_BAD_DATA); + return SECFailure; + } + /* + * Decrypt the block. + */ + rv = (*cc->doit)(cc->cx, output, &ofraglen, max_output_len, + pbuf, pcount); + if (rv != SECSuccess) + return rv; + + /* + * For now anyway, all of our ciphers have the same number of + * bytes of output as they do input. If this ever becomes untrue, + * then SecCmsCipherContextDecryptLength needs to be made smarter! + */ + PORT_Assert(ofraglen == pcount); + + /* + * Account for the bytes now in output. + */ + max_output_len -= ofraglen; + output_len += ofraglen; + output += ofraglen; + } + + /* + * If this is our last call, we expect to have an exact number of + * blocks left to be decrypted; we will decrypt them all. + * + * If not our last call, we always save between 1 and bsize bytes + * until next time. (We must do this because we cannot be sure + * that none of the decrypted bytes are padding bytes until we + * have at least another whole block of data. You cannot tell by + * looking -- the data could be anything -- you can only tell by + * context, knowing you are looking at the last block.) We could + * decrypt a whole block now but it is easier if we just treat it + * the same way we treat partial block bytes. + */ + if (final) { + if (padsize) { + blocks = input_len / padsize; + ifraglen = blocks * padsize; + } else ifraglen = input_len; + PORT_Assert (ifraglen == input_len); + + if (ifraglen != input_len) { + PORT_SetError(SEC_ERROR_BAD_DATA); + return SECFailure; + } + } else { + blocks = (input_len - 1) / bsize; + ifraglen = blocks * bsize; + PORT_Assert (ifraglen < input_len); + + pcount = input_len - ifraglen; + PORT_Memcpy (pbuf, input + ifraglen, pcount); + cc->pending_count = pcount; + } + + if (ifraglen) { + rv = (* cc->doit)(cc->cx, output, &ofraglen, max_output_len, + input, ifraglen); + if (rv != SECSuccess) + return rv; + + /* + * For now anyway, all of our ciphers have the same number of + * bytes of output as they do input. If this ever becomes untrue, + * then sec_PKCS7DecryptLength needs to be made smarter! + */ + PORT_Assert (ifraglen == ofraglen); + if (ifraglen != ofraglen) { + PORT_SetError(SEC_ERROR_BAD_DATA); + return SECFailure; + } + + output_len += ofraglen; + } else { + ofraglen = 0; + } + + /* + * If we just did our very last block, "remove" the padding by + * adjusting the output length. + */ + if (final && (padsize != 0)) { + unsigned int padlen = *(output + ofraglen - 1); + + if (padlen == 0 || padlen > padsize) { + PORT_SetError(SEC_ERROR_BAD_DATA); + return SECFailure; + } + output_len -= padlen; + } + + PORT_Assert (output_len_p != NULL || output_len == 0); + if (output_len_p != NULL) + *output_len_p = output_len; + + return SECSuccess; +#endif } /* @@ -459,8 +1147,146 @@ SecCmsCipherContextEncrypt(SecCmsCipherContextRef cc, unsigned char *output, const unsigned char *input, unsigned int input_len, Boolean final) { +#if 1 return SecCmsCipherContextCrypt(cc, output, output_len_p, max_output_len, input, input_len, final, PR_TRUE); +#else + int blocks, bsize, padlen, pcount, padsize; + unsigned int max_needed, ifraglen, ofraglen, output_len; + unsigned char *pbuf; + OSStatus rv; + + PORT_Assert (cc->encrypt); + + /* + * Check that we have enough room for the output. Our caller should + * already handle this; failure is really an internal error (i.e. bug). + */ + max_needed = SecCmsCipherContextEncryptLength (cc, input_len, final); + PORT_Assert (max_output_len >= max_needed); + if (max_output_len < max_needed) { + /* PORT_SetError (XXX); */ + return SECFailure; + } + + bsize = cc->block_size; + padsize = cc->pad_size; + + /* + * When no blocking and padding work to do, we can simply call the + * cipher function and we are done. + */ + if (bsize == 0) { + return (*cc->doit)(cc->cx, output, output_len_p, max_output_len, + input, input_len); + } + + pcount = cc->pending_count; + pbuf = cc->pending_buf; + + output_len = 0; + + if (pcount) { + /* + * Try to fill in an entire block, starting with the bytes + * we already have saved away. + */ + while (input_len && pcount < bsize) { + pbuf[pcount++] = *input++; + input_len--; + } + /* + * If we do not have a full block and we know we will be + * called again, then we are done for now. + */ + if (pcount < bsize && !final) { + cc->pending_count = pcount; + if (output_len_p != NULL) + *output_len_p = 0; + return SECSuccess; + } + /* + * If we have a whole block available, encrypt it. + */ + if ((padsize == 0) || (pcount % padsize) == 0) { + rv = (* cc->doit) (cc->cx, output, &ofraglen, max_output_len, + pbuf, pcount); + if (rv != SECSuccess) + return rv; + + /* + * For now anyway, all of our ciphers have the same number of + * bytes of output as they do input. If this ever becomes untrue, + * then sec_PKCS7EncryptLength needs to be made smarter! + */ + PORT_Assert (ofraglen == pcount); + + /* + * Account for the bytes now in output. + */ + max_output_len -= ofraglen; + output_len += ofraglen; + output += ofraglen; + + pcount = 0; + } + } + + if (input_len) { + PORT_Assert (pcount == 0); + + blocks = input_len / bsize; + ifraglen = blocks * bsize; + + if (ifraglen) { + rv = (* cc->doit) (cc->cx, output, &ofraglen, max_output_len, + input, ifraglen); + if (rv != SECSuccess) + return rv; + + /* + * For now anyway, all of our ciphers have the same number of + * bytes of output as they do input. If this ever becomes untrue, + * then sec_PKCS7EncryptLength needs to be made smarter! + */ + PORT_Assert (ifraglen == ofraglen); + + max_output_len -= ofraglen; + output_len += ofraglen; + output += ofraglen; + } + + pcount = input_len - ifraglen; + PORT_Assert (pcount < bsize); + if (pcount) + PORT_Memcpy (pbuf, input + ifraglen, pcount); + } + + if (final) { + padlen = padsize - (pcount % padsize); + PORT_Memset (pbuf + pcount, padlen, padlen); + rv = (* cc->doit) (cc->cx, output, &ofraglen, max_output_len, + pbuf, pcount+padlen); + if (rv != SECSuccess) + return rv; + + /* + * For now anyway, all of our ciphers have the same number of + * bytes of output as they do input. If this ever becomes untrue, + * then sec_PKCS7EncryptLength needs to be made smarter! + */ + PORT_Assert (ofraglen == (pcount+padlen)); + output_len += ofraglen; + } else { + cc->pending_count = pcount; + } + + PORT_Assert (output_len_p != NULL || output_len == 0); + if (output_len_p != NULL) + *output_len_p = output_len; + + return SECSuccess; +#endif }