/* 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, SECAlgorithmI
D *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;
}
Messung V0.5 in Prozent C=89 H=93 G=90
¤ Dauer der Verarbeitung: 0.18 Sekunden
¤
*© Formatika GbR, Deutschland