diff options
Diffstat (limited to 'security/nss/lib/smime/cmscipher.c')
-rw-r--r-- | security/nss/lib/smime/cmscipher.c | 722 |
1 files changed, 722 insertions, 0 deletions
diff --git a/security/nss/lib/smime/cmscipher.c b/security/nss/lib/smime/cmscipher.c new file mode 100644 index 0000000000..9c8330b235 --- /dev/null +++ b/security/nss/lib/smime/cmscipher.c @@ -0,0 +1,722 @@ +/* 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/. */ + +/* + * Encryption/decryption routines for CMS implementation, none of which are exported. + */ + +#include "cmslocal.h" + +#include "secoid.h" +#include "secitem.h" +#include "pk11func.h" +#include "secerr.h" +#include "secpkcs5.h" + +/* + * ------------------------------------------------------------------- + * Cipher stuff. + */ + +typedef SECStatus (*nss_cms_cipher_function)(void *, unsigned char *, unsigned int *, + unsigned int, const unsigned char *, unsigned int); +typedef SECStatus (*nss_cms_cipher_destroy)(void *, PRBool); + +#define BLOCK_SIZE 4096 + +struct NSSCMSCipherContextStr { + void *cx; /* PK11 cipher context */ + nss_cms_cipher_function doit; + nss_cms_cipher_destroy destroy; + PRBool encrypt; /* encrypt / decrypt switch */ + int block_size; /* block & pad sizes for cipher */ + int pad_size; + int pending_count; /* pending data (not yet en/decrypted */ + unsigned char pending_buf[BLOCK_SIZE]; /* because of blocking */ +}; + +/* + * NSS_CMSCipherContext_StartDecrypt - create a cipher context to do decryption + * based on the given bulk encryption key and algorithm identifier (which + * may include an iv). + * + * XXX Once both are working, it might be nice to combine this and the + * function below (for starting up encryption) into one routine, and just + * have two simple cover functions which call it. + */ +NSSCMSCipherContext * +NSS_CMSCipherContext_StartDecrypt(PK11SymKey *key, SECAlgorithmID *algid) +{ + NSSCMSCipherContext *cc; + void *ciphercx; + CK_MECHANISM_TYPE cryptoMechType; + PK11SlotInfo *slot; + SECOidTag algtag; + SECItem *param = NULL; + + algtag = SECOID_GetAlgorithmTag(algid); + + /* set param and mechanism */ + if (SEC_PKCS5IsAlgorithmPBEAlg(algid)) { + SECItem *pwitem; + + pwitem = PK11_GetSymKeyUserData(key); + if (!pwitem) + return NULL; + + cryptoMechType = PK11_GetPBECryptoMechanism(algid, ¶m, pwitem); + if (cryptoMechType == CKM_INVALID_MECHANISM) { + SECITEM_FreeItem(param, PR_TRUE); + return NULL; + } + + } else { + cryptoMechType = PK11_AlgtagToMechanism(algtag); + if ((param = PK11_ParamFromAlgid(algid)) == NULL) + return NULL; + } + + cc = (NSSCMSCipherContext *)PORT_ZAlloc(sizeof(NSSCMSCipherContext)); + if (cc == NULL) { + SECITEM_FreeItem(param, PR_TRUE); + return NULL; + } + + /* figure out pad and block sizes */ + cc->pad_size = PK11_GetBlockSize(cryptoMechType, 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(cryptoMechType, 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; +} + +/* + * NSS_CMSCipherContext_StartEncrypt - create a cipher object to do encryption, + * based on the given bulk encryption key and algorithm tag. Fill in the + * algorithm identifier (which may include an iv) appropriately. + * + * XXX Once both are working, it might be nice to combine this and the + * function above (for starting up decryption) into one routine, and just + * have two simple cover functions which call it. + */ +NSSCMSCipherContext * +NSS_CMSCipherContext_StartEncrypt(PLArenaPool *poolp, PK11SymKey *key, SECAlgorithmID *algid) +{ + NSSCMSCipherContext *cc; + void *ciphercx = NULL; + SECStatus rv; + CK_MECHANISM_TYPE cryptoMechType; + PK11SlotInfo *slot; + SECItem *param = NULL; + PRBool needToEncodeAlgid = PR_FALSE; + SECOidTag algtag = SECOID_GetAlgorithmTag(algid); + + /* set param and mechanism */ + if (SEC_PKCS5IsAlgorithmPBEAlg(algid)) { + SECItem *pwitem; + + pwitem = PK11_GetSymKeyUserData(key); + if (!pwitem) + return NULL; + + cryptoMechType = PK11_GetPBECryptoMechanism(algid, ¶m, pwitem); + if (cryptoMechType == CKM_INVALID_MECHANISM) { + SECITEM_FreeItem(param, PR_TRUE); + return NULL; + } + } else { + cryptoMechType = PK11_AlgtagToMechanism(algtag); + if ((param = PK11_GenerateNewParam(cryptoMechType, key)) == NULL) + return NULL; + needToEncodeAlgid = PR_TRUE; + } + + cc = (NSSCMSCipherContext *)PORT_ZAlloc(sizeof(NSSCMSCipherContext)); + if (cc == NULL) { + goto loser; + } + + /* now find pad and block sizes for our mechanism */ + cc->pad_size = PK11_GetBlockSize(cryptoMechType, 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(cryptoMechType, 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; + ciphercx = NULL; + 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); + if (ciphercx) { + PK11_DestroyContext(ciphercx, PR_TRUE); + } + + return cc; +} + +void +NSS_CMSCipherContext_Destroy(NSSCMSCipherContext *cc) +{ + PORT_Assert(cc != NULL); + if (cc == NULL) + return; + (*cc->destroy)(cc->cx, PR_TRUE); + PORT_Free(cc); +} + +/* + * NSS_CMSCipherContext_DecryptLength - find the output length of the next call to decrypt. + * + * cc - the cipher context + * input_len - number of bytes used as input + * final - true if this is the final chunk of data + * + * Result can be used to perform memory allocations. Note that the amount + * is exactly accurate only when not doing a block cipher or when final + * is false, otherwise it is an upper bound on the amount because until + * we see the data we do not know how many padding bytes there are + * (always between 1 and bsize). + * + * Note that this can return zero, which does not mean that the decrypt + * operation can be skipped! (It simply means that there are not enough + * bytes to make up an entire block; the bytes will be reserved until + * there are enough to encrypt/decrypt at least one block.) However, + * if zero is returned it *does* mean that no output buffer need be + * passed in to the subsequent decrypt operation, as no output bytes + * will be stored. + */ +unsigned int +NSS_CMSCipherContext_DecryptLength(NSSCMSCipherContext *cc, unsigned int input_len, PRBool final) +{ + 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; +} + +/* + * NSS_CMSCipherContext_EncryptLength - find the output length of the next call to encrypt. + * + * cc - the cipher context + * input_len - number of bytes used as input + * final - true if this is the final chunk of data + * + * Result can be used to perform memory allocations. + * + * Note that this can return zero, which does not mean that the encrypt + * operation can be skipped! (It simply means that there are not enough + * bytes to make up an entire block; the bytes will be reserved until + * there are enough to encrypt/decrypt at least one block.) However, + * if zero is returned it *does* mean that no output buffer need be + * passed in to the subsequent encrypt operation, as no output bytes + * will be stored. + */ +unsigned int +NSS_CMSCipherContext_EncryptLength(NSSCMSCipherContext *cc, unsigned int input_len, PRBool final) +{ + 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; +} + +/* + * NSS_CMSCipherContext_Decrypt - do the decryption + * + * cc - the cipher context + * output - buffer for decrypted result bytes + * output_len_p - number of bytes in output + * max_output_len - upper bound on bytes to put into output + * input - pointer to input bytes + * input_len - number of input bytes + * final - true if this is the final chunk of data + * + * Decrypts a given length of input buffer (starting at "input" and + * containing "input_len" bytes), placing the decrypted bytes in + * "output" and storing the output length in "*output_len_p". + * "cc" is the return value from NSS_CMSCipher_StartDecrypt. + * When "final" is true, this is the last of the data to be decrypted. + * + * This is much more complicated than it sounds when the cipher is + * a block-type, meaning that the decryption function will only + * operate on whole blocks. But our caller is operating stream-wise, + * and can pass in any number of bytes. So we need to keep track + * of block boundaries. We save excess bytes between calls in "cc". + * We also need to determine which bytes are padding, and remove + * them from the output. We can only do this step when we know we + * have the final block of data. PKCS #7 specifies that the padding + * used for a block cipher is a string of bytes, each of whose value is + * the same as the length of the padding, and that all data is padded. + * (Even data that starts out with an exact multiple of blocks gets + * added to it another block, all of which is padding.) + */ +SECStatus +NSS_CMSCipherContext_Decrypt(NSSCMSCipherContext *cc, unsigned char *output, + unsigned int *output_len_p, unsigned int max_output_len, + const unsigned char *input, unsigned int input_len, + PRBool final) +{ + unsigned int blocks, bsize, pcount, padsize; + unsigned int max_needed, ifraglen, ofraglen, output_len; + unsigned char *pbuf; + SECStatus 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 = NSS_CMSCipherContext_DecryptLength(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 NSS_CMSCipherContext_DecryptLength 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; +} + +/* + * NSS_CMSCipherContext_Encrypt - do the encryption + * + * cc - the cipher context + * output - buffer for decrypted result bytes + * output_len_p - number of bytes in output + * max_output_len - upper bound on bytes to put into output + * input - pointer to input bytes + * input_len - number of input bytes + * final - true if this is the final chunk of data + * + * Encrypts a given length of input buffer (starting at "input" and + * containing "input_len" bytes), placing the encrypted bytes in + * "output" and storing the output length in "*output_len_p". + * "cc" is the return value from NSS_CMSCipher_StartEncrypt. + * When "final" is true, this is the last of the data to be encrypted. + * + * This is much more complicated than it sounds when the cipher is + * a block-type, meaning that the encryption function will only + * operate on whole blocks. But our caller is operating stream-wise, + * and can pass in any number of bytes. So we need to keep track + * of block boundaries. We save excess bytes between calls in "cc". + * We also need to add padding bytes at the end. PKCS #7 specifies + * that the padding used for a block cipher is a string of bytes, + * each of whose value is the same as the length of the padding, + * and that all data is padded. (Even data that starts out with + * an exact multiple of blocks gets added to it another block, + * all of which is padding.) + * + * XXX I would kind of like to combine this with the function above + * which does decryption, since they have a lot in common. But the + * tricky parts about padding and filling blocks would be much + * harder to read that way, so I left them separate. At least for + * now until it is clear that they are right. + */ +SECStatus +NSS_CMSCipherContext_Encrypt(NSSCMSCipherContext *cc, unsigned char *output, + unsigned int *output_len_p, unsigned int max_output_len, + const unsigned char *input, unsigned int input_len, + PRBool final) +{ + int blocks, bsize, padlen, pcount, padsize; + unsigned int max_needed, ifraglen, ofraglen, output_len; + unsigned char *pbuf; + SECStatus 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 = NSS_CMSCipherContext_EncryptLength(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) { + if (padsize <= 0) { + padlen = 0; + } else { + 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; +} |