gecko/security/nss/lib/pkcs7/p7local.c

1411 lines
43 KiB
C
Raw Normal View History

2008-06-06 05:40:11 -07:00
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Netscape security libraries.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1994-2000
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/*
* Support routines for PKCS7 implementation, none of which are exported.
* This file should only contain things that are needed by both the
* encoding/creation side *and* the decoding/decryption side. Anything
* else should be static routines in the appropriate file.
*
* $Id: p7local.c,v 1.14 2010/03/15 07:25:14 nelson%bolyard.com Exp $
2008-06-06 05:40:11 -07:00
*/
#include "p7local.h"
#include "cryptohi.h"
#include "secasn1.h"
#include "secoid.h"
#include "secitem.h"
#include "pk11func.h"
#include "secpkcs5.h"
#include "secerr.h"
/*
* -------------------------------------------------------------------
* Cipher stuff.
*/
typedef SECStatus (*sec_pkcs7_cipher_function) (void *,
unsigned char *,
unsigned *,
unsigned int,
const unsigned char *,
unsigned int);
typedef SECStatus (*sec_pkcs7_cipher_destroy) (void *, PRBool);
#define BLOCK_SIZE 4096
struct sec_pkcs7_cipher_object {
void *cx;
sec_pkcs7_cipher_function doit;
sec_pkcs7_cipher_destroy destroy;
PRBool encrypt;
int block_size;
int pad_size;
int pending_count;
unsigned char pending_buf[BLOCK_SIZE];
};
SEC_ASN1_MKSUB(CERT_IssuerAndSNTemplate)
SEC_ASN1_MKSUB(CERT_SetOfSignedCrlTemplate)
SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate)
SEC_ASN1_MKSUB(SEC_OctetStringTemplate)
SEC_ASN1_MKSUB(SEC_SetOfAnyTemplate)
/*
* Create a cipher object to do decryption, based on the given bulk
* encryption key and algorithm identifier (which may include an iv).
*
* XXX This interface, or one similar, would be really nice available
* in general... I tried to keep the pkcs7-specific stuff (mostly
* having to do with padding) out of here.
*
* 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.
*/
sec_PKCS7CipherObject *
sec_PKCS7CreateDecryptObject (PK11SymKey *key, SECAlgorithmID *algid)
{
sec_PKCS7CipherObject *result;
SECOidTag algtag;
void *ciphercx;
CK_MECHANISM_TYPE cryptoMechType;
PK11SlotInfo *slot;
SECItem *param = NULL;
2008-06-06 05:40:11 -07:00
result = (struct sec_pkcs7_cipher_object*)
PORT_ZAlloc (sizeof(struct sec_pkcs7_cipher_object));
if (result == NULL)
return NULL;
ciphercx = NULL;
algtag = SECOID_GetAlgorithmTag (algid);
if (SEC_PKCS5IsAlgorithmPBEAlg(algid)) {
SECItem *pwitem;
pwitem = (SECItem *)PK11_GetSymKeyUserData(key);
if (!pwitem) {
PORT_Free(result);
return NULL;
}
cryptoMechType = PK11_GetPBECryptoMechanism(algid, &param, pwitem);
if (cryptoMechType == CKM_INVALID_MECHANISM) {
PORT_Free(result);
SECITEM_FreeItem(param,PR_TRUE);
2008-06-06 05:40:11 -07:00
return NULL;
}
} else {
cryptoMechType = PK11_AlgtagToMechanism(algtag);
param = PK11_ParamFromAlgid(algid);
if (param == NULL) {
PORT_Free(result);
return NULL;
}
}
result->pad_size = PK11_GetBlockSize(cryptoMechType, param);
slot = PK11_GetSlotFromKey(key);
result->block_size = PK11_IsHW(slot) ? BLOCK_SIZE : result->pad_size;
PK11_FreeSlot(slot);
ciphercx = PK11_CreateContextBySymKey(cryptoMechType, CKA_DECRYPT,
key, param);
SECITEM_FreeItem(param,PR_TRUE);
if (ciphercx == NULL) {
PORT_Free (result);
return NULL;
}
result->cx = ciphercx;
result->doit = (sec_pkcs7_cipher_function) PK11_CipherOp;
result->destroy = (sec_pkcs7_cipher_destroy) PK11_DestroyContext;
result->encrypt = PR_FALSE;
result->pending_count = 0;
return result;
}
/*
* 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 This interface, or one similar, would be really nice available
* in general... I tried to keep the pkcs7-specific stuff (mostly
* having to do with padding) out of here.
*
* 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.
*/
sec_PKCS7CipherObject *
sec_PKCS7CreateEncryptObject (PRArenaPool *poolp, PK11SymKey *key,
SECOidTag algtag, SECAlgorithmID *algid)
{
sec_PKCS7CipherObject *result;
void *ciphercx;
SECStatus rv;
CK_MECHANISM_TYPE cryptoMechType;
PK11SlotInfo *slot;
SECItem *param = NULL;
PRBool needToEncodeAlgid = PR_FALSE;
2008-06-06 05:40:11 -07:00
result = (struct sec_pkcs7_cipher_object*)
PORT_ZAlloc (sizeof(struct sec_pkcs7_cipher_object));
if (result == NULL)
return NULL;
ciphercx = NULL;
if (SEC_PKCS5IsAlgorithmPBEAlg(algid)) {
SECItem *pwitem;
pwitem = (SECItem *)PK11_GetSymKeyUserData(key);
if (!pwitem) {
PORT_Free(result);
return NULL;
}
cryptoMechType = PK11_GetPBECryptoMechanism(algid, &param, pwitem);
if (cryptoMechType == CKM_INVALID_MECHANISM) {
PORT_Free(result);
SECITEM_FreeItem(param,PR_TRUE);
2008-06-06 05:40:11 -07:00
return NULL;
}
} else {
cryptoMechType = PK11_AlgtagToMechanism(algtag);
param = PK11_GenerateNewParam(cryptoMechType, key);
if (param == NULL) {
PORT_Free(result);
return NULL;
}
needToEncodeAlgid = PR_TRUE;
}
result->pad_size = PK11_GetBlockSize(cryptoMechType,param);
slot = PK11_GetSlotFromKey(key);
result->block_size = PK11_IsHW(slot) ? BLOCK_SIZE : result->pad_size;
PK11_FreeSlot(slot);
ciphercx = PK11_CreateContextBySymKey(cryptoMechType, CKA_ENCRYPT,
key, param);
if (ciphercx == NULL) {
PORT_Free (result);
SECITEM_FreeItem(param,PR_TRUE);
return NULL;
}
/*
* 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.
*/
if (needToEncodeAlgid) {
rv = PK11_ParamToAlgid(algtag,param,poolp,algid);
if(rv != SECSuccess) {
PORT_Free (result);
SECITEM_FreeItem(param,PR_TRUE);
return NULL;
}
}
SECITEM_FreeItem(param,PR_TRUE);
result->cx = ciphercx;
result->doit = (sec_pkcs7_cipher_function) PK11_CipherOp;
result->destroy = (sec_pkcs7_cipher_destroy) PK11_DestroyContext;
result->encrypt = PR_TRUE;
result->pending_count = 0;
return result;
}
/*
* Destroy the cipher object.
*/
static void
sec_pkcs7_destroy_cipher (sec_PKCS7CipherObject *obj)
{
(* obj->destroy) (obj->cx, PR_TRUE);
PORT_Free (obj);
}
void
sec_PKCS7DestroyDecryptObject (sec_PKCS7CipherObject *obj)
{
PORT_Assert (obj != NULL);
if (obj == NULL)
return;
PORT_Assert (! obj->encrypt);
sec_pkcs7_destroy_cipher (obj);
}
void
sec_PKCS7DestroyEncryptObject (sec_PKCS7CipherObject *obj)
{
PORT_Assert (obj != NULL);
if (obj == NULL)
return;
PORT_Assert (obj->encrypt);
sec_pkcs7_destroy_cipher (obj);
}
/*
* XXX I think all of the following lengths should be longs instead
* of ints, but our current crypto interface uses ints, so I did too.
*/
/*
* What will be the output length of the next call to decrypt?
* 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
sec_PKCS7DecryptLength (sec_PKCS7CipherObject *obj, unsigned int input_len,
PRBool final)
{
int blocks, block_size;
PORT_Assert (! obj->encrypt);
block_size = obj->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 obj->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 = (obj->pending_count + input_len - 1) / block_size;
return blocks * block_size;
}
/*
* What will be the output length of the next call to encrypt?
* 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
sec_PKCS7EncryptLength (sec_PKCS7CipherObject *obj, unsigned int input_len,
PRBool final)
{
int blocks, block_size;
int pad_size;
PORT_Assert (obj->encrypt);
block_size = obj->block_size;
pad_size = obj->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 obj->pending_count + input_len;
} else {
blocks = (obj->pending_count + input_len) / pad_size;
blocks++;
return blocks*pad_size;
}
}
/*
* Now, count the number of complete blocks of data we have.
*/
blocks = (obj->pending_count + input_len) / block_size;
return blocks * block_size;
}
/*
* Decrypt 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".
* "obj" is the return value from sec_PKCS7CreateDecryptObject.
* 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 "obj".
* 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
sec_PKCS7Decrypt (sec_PKCS7CipherObject *obj, 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, pcount, padsize;
unsigned int max_needed, ifraglen, ofraglen, output_len;
unsigned char *pbuf;
SECStatus rv;
PORT_Assert (! obj->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 = sec_PKCS7DecryptLength (obj, 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 = obj->block_size;
padsize = obj->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 (* obj->doit) (obj->cx, output, output_len_p, max_output_len,
input, input_len);
}
pcount = obj->pending_count;
pbuf = obj->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) {
obj->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.
*/
PORT_Assert ((padsize == 0) || (pcount % padsize) == 0);
if ((padsize != 0) && (pcount % padsize) != 0) {
PORT_Assert (final);
PORT_SetError (SEC_ERROR_BAD_DATA);
return SECFailure;
}
/*
* Decrypt the block.
*/
rv = (* obj->doit) (obj->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_PKCS7DecryptLength 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);
obj->pending_count = pcount;
}
if (ifraglen) {
rv = (* obj->doit) (obj->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;
}
/*
* Encrypt 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".
* "obj" is the return value from sec_PKCS7CreateEncryptObject.
* 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 "obj".
* 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
sec_PKCS7Encrypt (sec_PKCS7CipherObject *obj, 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 (obj->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 = sec_PKCS7EncryptLength (obj, input_len, final);
PORT_Assert (max_output_len >= max_needed);
if (max_output_len < max_needed) {
/* PORT_SetError (XXX); */
return SECFailure;
}
bsize = obj->block_size;
padsize = obj->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 (* obj->doit) (obj->cx, output, output_len_p, max_output_len,
input, input_len);
}
pcount = obj->pending_count;
pbuf = obj->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) {
obj->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 = (* obj->doit) (obj->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 = (* obj->doit) (obj->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 = (* obj->doit) (obj->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 {
obj->pending_count = pcount;
}
PORT_Assert (output_len_p != NULL || output_len == 0);
if (output_len_p != NULL)
*output_len_p = output_len;
return SECSuccess;
}
/*
* End of cipher stuff.
* -------------------------------------------------------------------
*/
/*
* -------------------------------------------------------------------
* XXX The following Attribute stuff really belongs elsewhere.
* The Attribute type is *not* part of pkcs7 but rather X.501.
* But for now, since PKCS7 is the only customer of attributes,
* we define them here. Once there is a use outside of PKCS7,
* then change the attribute types and functions from internal
* to external naming convention, and move them elsewhere!
*/
/*
* Look through a set of attributes and find one that matches the
* specified object ID. If "only" is true, then make sure that
* there is not more than one attribute of the same type. Otherwise,
* just return the first one found. (XXX Does anybody really want
* that first-found behavior? It was like that when I found it...)
*/
SEC_PKCS7Attribute *
sec_PKCS7FindAttribute (SEC_PKCS7Attribute **attrs, SECOidTag oidtag,
PRBool only)
{
SECOidData *oid;
SEC_PKCS7Attribute *attr1, *attr2;
if (attrs == NULL)
return NULL;
oid = SECOID_FindOIDByTag(oidtag);
if (oid == NULL)
return NULL;
while ((attr1 = *attrs++) != NULL) {
if (attr1->type.len == oid->oid.len && PORT_Memcmp (attr1->type.data,
oid->oid.data,
oid->oid.len) == 0)
break;
}
if (attr1 == NULL)
return NULL;
if (!only)
return attr1;
while ((attr2 = *attrs++) != NULL) {
if (attr2->type.len == oid->oid.len && PORT_Memcmp (attr2->type.data,
oid->oid.data,
oid->oid.len) == 0)
break;
}
if (attr2 != NULL)
return NULL;
return attr1;
}
/*
* Return the single attribute value, doing some sanity checking first:
* - Multiple values are *not* expected.
* - Empty values are *not* expected.
*/
SECItem *
sec_PKCS7AttributeValue(SEC_PKCS7Attribute *attr)
{
SECItem *value;
if (attr == NULL)
return NULL;
value = attr->values[0];
if (value == NULL || value->data == NULL || value->len == 0)
return NULL;
if (attr->values[1] != NULL)
return NULL;
return value;
}
static const SEC_ASN1Template *
sec_attr_choose_attr_value_template(void *src_or_dest, PRBool encoding)
{
const SEC_ASN1Template *theTemplate;
SEC_PKCS7Attribute *attribute;
SECOidData *oiddata;
PRBool encoded;
PORT_Assert (src_or_dest != NULL);
if (src_or_dest == NULL)
return NULL;
attribute = (SEC_PKCS7Attribute*)src_or_dest;
if (encoding && attribute->encoded)
return SEC_ASN1_GET(SEC_AnyTemplate);
oiddata = attribute->typeTag;
if (oiddata == NULL) {
oiddata = SECOID_FindOID(&attribute->type);
attribute->typeTag = oiddata;
}
if (oiddata == NULL) {
encoded = PR_TRUE;
theTemplate = SEC_ASN1_GET(SEC_AnyTemplate);
} else {
switch (oiddata->offset) {
default:
encoded = PR_TRUE;
theTemplate = SEC_ASN1_GET(SEC_AnyTemplate);
break;
case SEC_OID_PKCS9_EMAIL_ADDRESS:
case SEC_OID_RFC1274_MAIL:
case SEC_OID_PKCS9_UNSTRUCTURED_NAME:
encoded = PR_FALSE;
theTemplate = SEC_ASN1_GET(SEC_IA5StringTemplate);
break;
case SEC_OID_PKCS9_CONTENT_TYPE:
encoded = PR_FALSE;
theTemplate = SEC_ASN1_GET(SEC_ObjectIDTemplate);
break;
case SEC_OID_PKCS9_MESSAGE_DIGEST:
encoded = PR_FALSE;
theTemplate = SEC_ASN1_GET(SEC_OctetStringTemplate);
break;
case SEC_OID_PKCS9_SIGNING_TIME:
encoded = PR_FALSE;
theTemplate = SEC_ASN1_GET(CERT_TimeChoiceTemplate);
break;
/* XXX Want other types here, too */
}
}
if (encoding) {
/*
* If we are encoding and we think we have an already-encoded value,
* then the code which initialized this attribute should have set
* the "encoded" property to true (and we would have returned early,
* up above). No devastating error, but that code should be fixed.
* (It could indicate that the resulting encoded bytes are wrong.)
*/
PORT_Assert (!encoded);
} else {
/*
* We are decoding; record whether the resulting value is
* still encoded or not.
*/
attribute->encoded = encoded;
}
return theTemplate;
}
static const SEC_ASN1TemplateChooserPtr sec_attr_chooser
= sec_attr_choose_attr_value_template;
static const SEC_ASN1Template sec_pkcs7_attribute_template[] = {
{ SEC_ASN1_SEQUENCE,
0, NULL, sizeof(SEC_PKCS7Attribute) },
{ SEC_ASN1_OBJECT_ID,
offsetof(SEC_PKCS7Attribute,type) },
{ SEC_ASN1_DYNAMIC | SEC_ASN1_SET_OF,
offsetof(SEC_PKCS7Attribute,values),
&sec_attr_chooser },
{ 0 }
};
static const SEC_ASN1Template sec_pkcs7_set_of_attribute_template[] = {
{ SEC_ASN1_SET_OF, 0, sec_pkcs7_attribute_template },
};
/*
* If you are wondering why this routine does not reorder the attributes
* first, and might be tempted to make it do so, see the comment by the
* call to ReorderAttributes in p7encode.c. (Or, see who else calls this
* and think long and hard about the implications of making it always
* do the reordering.)
*/
SECItem *
sec_PKCS7EncodeAttributes (PRArenaPool *poolp, SECItem *dest, void *src)
{
return SEC_ASN1EncodeItem (poolp, dest, src,
sec_pkcs7_set_of_attribute_template);
}
/*
* Make sure that the order of the attributes guarantees valid DER
* (which must be in lexigraphically ascending order for a SET OF);
* if reordering is necessary it will be done in place (in attrs).
*/
SECStatus
sec_PKCS7ReorderAttributes (SEC_PKCS7Attribute **attrs)
{
PRArenaPool *poolp;
int num_attrs, i, pass, besti;
unsigned int j;
SECItem **enc_attrs;
SEC_PKCS7Attribute **new_attrs;
/*
* I think we should not be called with NULL. But if we are,
* call it a success anyway, because the order *is* okay.
*/
PORT_Assert (attrs != NULL);
if (attrs == NULL)
return SECSuccess;
/*
* Count how many attributes we are dealing with here.
*/
num_attrs = 0;
while (attrs[num_attrs] != NULL)
num_attrs++;
/*
* Again, I think we should have some attributes here.
* But if we do not, or if there is only one, then call it
* a success because it also already has a fine order.
*/
PORT_Assert (num_attrs);
if (num_attrs == 0 || num_attrs == 1)
return SECSuccess;
/*
* Allocate an arena for us to work with, so it is easy to
* clean up all of the memory (fairly small pieces, really).
*/
poolp = PORT_NewArena (1024); /* XXX what is right value? */
if (poolp == NULL)
return SECFailure; /* no memory; nothing we can do... */
/*
* Allocate arrays to hold the individual encodings which we will use
* for comparisons and the reordered attributes as they are sorted.
*/
enc_attrs=(SECItem**)PORT_ArenaZAlloc(poolp, num_attrs*sizeof(SECItem *));
new_attrs = (SEC_PKCS7Attribute**)PORT_ArenaZAlloc (poolp,
num_attrs * sizeof(SEC_PKCS7Attribute *));
if (enc_attrs == NULL || new_attrs == NULL) {
PORT_FreeArena (poolp, PR_FALSE);
return SECFailure;
}
/*
* DER encode each individual attribute.
*/
for (i = 0; i < num_attrs; i++) {
enc_attrs[i] = SEC_ASN1EncodeItem (poolp, NULL, attrs[i],
sec_pkcs7_attribute_template);
if (enc_attrs[i] == NULL) {
PORT_FreeArena (poolp, PR_FALSE);
return SECFailure;
}
}
/*
* Now compare and sort them; this is not the most efficient sorting
* method, but it is just fine for the problem at hand, because the
* number of attributes is (always) going to be small.
*/
for (pass = 0; pass < num_attrs; pass++) {
/*
* Find the first not-yet-accepted attribute. (Once one is
* sorted into the other array, it is cleared from enc_attrs.)
*/
for (i = 0; i < num_attrs; i++) {
if (enc_attrs[i] != NULL)
break;
}
PORT_Assert (i < num_attrs);
besti = i;
/*
* Find the lowest (lexigraphically) encoding. One that is
* shorter than all the rest is known to be "less" because each
* attribute is of the same type (a SEQUENCE) and so thus the
* first octet of each is the same, and the second octet is
* the length (or the length of the length with the high bit
* set, followed by the length, which also works out to always
* order the shorter first). Two (or more) that have the
* same length need to be compared byte by byte until a mismatch
* is found.
*/
for (i = besti + 1; i < num_attrs; i++) {
if (enc_attrs[i] == NULL) /* slot already handled */
continue;
if (enc_attrs[i]->len != enc_attrs[besti]->len) {
if (enc_attrs[i]->len < enc_attrs[besti]->len)
besti = i;
continue;
}
for (j = 0; j < enc_attrs[i]->len; j++) {
if (enc_attrs[i]->data[j] < enc_attrs[besti]->data[j]) {
besti = i;
break;
}
}
/*
* For this not to be true, we would have to have encountered
* two *identical* attributes, which I think we should not see.
* So assert if it happens, but even if it does, let it go
* through; the ordering of the two does not matter.
*/
PORT_Assert (j < enc_attrs[i]->len);
}
/*
* Now we have found the next-lowest one; copy it over and
* remove it from enc_attrs.
*/
new_attrs[pass] = attrs[besti];
enc_attrs[besti] = NULL;
}
/*
* Now new_attrs has the attributes in the order we want;
* copy them back into the attrs array we started with.
*/
for (i = 0; i < num_attrs; i++)
attrs[i] = new_attrs[i];
PORT_FreeArena (poolp, PR_FALSE);
return SECSuccess;
}
/*
* End of attribute stuff.
* -------------------------------------------------------------------
*/
/*
* Templates and stuff. Keep these at the end of the file.
*/
/* forward declaration */
static const SEC_ASN1Template *
sec_pkcs7_choose_content_template(void *src_or_dest, PRBool encoding);
static const SEC_ASN1TemplateChooserPtr sec_pkcs7_chooser
= sec_pkcs7_choose_content_template;
const SEC_ASN1Template sec_PKCS7ContentInfoTemplate[] = {
{ SEC_ASN1_SEQUENCE | SEC_ASN1_MAY_STREAM,
0, NULL, sizeof(SEC_PKCS7ContentInfo) },
{ SEC_ASN1_OBJECT_ID,
offsetof(SEC_PKCS7ContentInfo,contentType) },
{ SEC_ASN1_OPTIONAL | SEC_ASN1_DYNAMIC | SEC_ASN1_MAY_STREAM
| SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0,
offsetof(SEC_PKCS7ContentInfo,content),
&sec_pkcs7_chooser },
{ 0 }
};
/* XXX These names should change from external to internal convention. */
static const SEC_ASN1Template SEC_PKCS7SignerInfoTemplate[] = {
{ SEC_ASN1_SEQUENCE,
0, NULL, sizeof(SEC_PKCS7SignerInfo) },
{ SEC_ASN1_INTEGER,
offsetof(SEC_PKCS7SignerInfo,version) },
{ SEC_ASN1_POINTER | SEC_ASN1_XTRN,
offsetof(SEC_PKCS7SignerInfo,issuerAndSN),
SEC_ASN1_SUB(CERT_IssuerAndSNTemplate) },
{ SEC_ASN1_INLINE | SEC_ASN1_XTRN,
offsetof(SEC_PKCS7SignerInfo,digestAlg),
SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
{ SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0,
offsetof(SEC_PKCS7SignerInfo,authAttr),
sec_pkcs7_set_of_attribute_template },
{ SEC_ASN1_INLINE | SEC_ASN1_XTRN,
offsetof(SEC_PKCS7SignerInfo,digestEncAlg),
SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
{ SEC_ASN1_OCTET_STRING,
offsetof(SEC_PKCS7SignerInfo,encDigest) },
{ SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1,
offsetof(SEC_PKCS7SignerInfo,unAuthAttr),
sec_pkcs7_set_of_attribute_template },
{ 0 }
};
static const SEC_ASN1Template SEC_PKCS7SignedDataTemplate[] = {
{ SEC_ASN1_SEQUENCE | SEC_ASN1_MAY_STREAM,
0, NULL, sizeof(SEC_PKCS7SignedData) },
{ SEC_ASN1_INTEGER,
offsetof(SEC_PKCS7SignedData,version) },
{ SEC_ASN1_SET_OF | SEC_ASN1_XTRN,
offsetof(SEC_PKCS7SignedData,digestAlgorithms),
SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
{ SEC_ASN1_INLINE,
offsetof(SEC_PKCS7SignedData,contentInfo),
sec_PKCS7ContentInfoTemplate },
{ SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC |
SEC_ASN1_XTRN | 0,
offsetof(SEC_PKCS7SignedData,rawCerts),
SEC_ASN1_SUB(SEC_SetOfAnyTemplate) },
{ SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC |
SEC_ASN1_XTRN | 1,
offsetof(SEC_PKCS7SignedData,crls),
SEC_ASN1_SUB(CERT_SetOfSignedCrlTemplate) },
{ SEC_ASN1_SET_OF,
offsetof(SEC_PKCS7SignedData,signerInfos),
SEC_PKCS7SignerInfoTemplate },
{ 0 }
};
static const SEC_ASN1Template SEC_PointerToPKCS7SignedDataTemplate[] = {
{ SEC_ASN1_POINTER, 0, SEC_PKCS7SignedDataTemplate }
};
static const SEC_ASN1Template SEC_PKCS7RecipientInfoTemplate[] = {
{ SEC_ASN1_SEQUENCE,
0, NULL, sizeof(SEC_PKCS7RecipientInfo) },
{ SEC_ASN1_INTEGER,
offsetof(SEC_PKCS7RecipientInfo,version) },
{ SEC_ASN1_POINTER | SEC_ASN1_XTRN,
offsetof(SEC_PKCS7RecipientInfo,issuerAndSN),
SEC_ASN1_SUB(CERT_IssuerAndSNTemplate) },
{ SEC_ASN1_INLINE | SEC_ASN1_XTRN,
offsetof(SEC_PKCS7RecipientInfo,keyEncAlg),
SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
{ SEC_ASN1_OCTET_STRING,
offsetof(SEC_PKCS7RecipientInfo,encKey) },
{ 0 }
};
static const SEC_ASN1Template SEC_PKCS7EncryptedContentInfoTemplate[] = {
{ SEC_ASN1_SEQUENCE | SEC_ASN1_MAY_STREAM,
0, NULL, sizeof(SEC_PKCS7EncryptedContentInfo) },
{ SEC_ASN1_OBJECT_ID,
offsetof(SEC_PKCS7EncryptedContentInfo,contentType) },
{ SEC_ASN1_INLINE | SEC_ASN1_XTRN,
offsetof(SEC_PKCS7EncryptedContentInfo,contentEncAlg),
SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
{ SEC_ASN1_OPTIONAL | SEC_ASN1_MAY_STREAM | SEC_ASN1_CONTEXT_SPECIFIC |
SEC_ASN1_XTRN | 0,
offsetof(SEC_PKCS7EncryptedContentInfo,encContent),
SEC_ASN1_SUB(SEC_OctetStringTemplate) },
{ 0 }
};
static const SEC_ASN1Template SEC_PKCS7EnvelopedDataTemplate[] = {
{ SEC_ASN1_SEQUENCE | SEC_ASN1_MAY_STREAM,
0, NULL, sizeof(SEC_PKCS7EnvelopedData) },
{ SEC_ASN1_INTEGER,
offsetof(SEC_PKCS7EnvelopedData,version) },
{ SEC_ASN1_SET_OF,
offsetof(SEC_PKCS7EnvelopedData,recipientInfos),
SEC_PKCS7RecipientInfoTemplate },
{ SEC_ASN1_INLINE,
offsetof(SEC_PKCS7EnvelopedData,encContentInfo),
SEC_PKCS7EncryptedContentInfoTemplate },
{ 0 }
};
static const SEC_ASN1Template SEC_PointerToPKCS7EnvelopedDataTemplate[] = {
{ SEC_ASN1_POINTER, 0, SEC_PKCS7EnvelopedDataTemplate }
};
static const SEC_ASN1Template SEC_PKCS7SignedAndEnvelopedDataTemplate[] = {
{ SEC_ASN1_SEQUENCE | SEC_ASN1_MAY_STREAM,
0, NULL, sizeof(SEC_PKCS7SignedAndEnvelopedData) },
{ SEC_ASN1_INTEGER,
offsetof(SEC_PKCS7SignedAndEnvelopedData,version) },
{ SEC_ASN1_SET_OF,
offsetof(SEC_PKCS7SignedAndEnvelopedData,recipientInfos),
SEC_PKCS7RecipientInfoTemplate },
{ SEC_ASN1_SET_OF | SEC_ASN1_XTRN,
offsetof(SEC_PKCS7SignedAndEnvelopedData,digestAlgorithms),
SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
{ SEC_ASN1_INLINE,
offsetof(SEC_PKCS7SignedAndEnvelopedData,encContentInfo),
SEC_PKCS7EncryptedContentInfoTemplate },
{ SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC |
SEC_ASN1_XTRN | 0,
offsetof(SEC_PKCS7SignedAndEnvelopedData,rawCerts),
SEC_ASN1_SUB(SEC_SetOfAnyTemplate) },
{ SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC |
SEC_ASN1_XTRN | 1,
offsetof(SEC_PKCS7SignedAndEnvelopedData,crls),
SEC_ASN1_SUB(CERT_SetOfSignedCrlTemplate) },
{ SEC_ASN1_SET_OF,
offsetof(SEC_PKCS7SignedAndEnvelopedData,signerInfos),
SEC_PKCS7SignerInfoTemplate },
{ 0 }
};
static const SEC_ASN1Template
SEC_PointerToPKCS7SignedAndEnvelopedDataTemplate[] = {
{ SEC_ASN1_POINTER, 0, SEC_PKCS7SignedAndEnvelopedDataTemplate }
};
static const SEC_ASN1Template SEC_PKCS7DigestedDataTemplate[] = {
{ SEC_ASN1_SEQUENCE | SEC_ASN1_MAY_STREAM,
0, NULL, sizeof(SEC_PKCS7DigestedData) },
{ SEC_ASN1_INTEGER,
offsetof(SEC_PKCS7DigestedData,version) },
{ SEC_ASN1_INLINE | SEC_ASN1_XTRN,
offsetof(SEC_PKCS7DigestedData,digestAlg),
SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
{ SEC_ASN1_INLINE,
offsetof(SEC_PKCS7DigestedData,contentInfo),
sec_PKCS7ContentInfoTemplate },
{ SEC_ASN1_OCTET_STRING,
offsetof(SEC_PKCS7DigestedData,digest) },
{ 0 }
};
static const SEC_ASN1Template SEC_PointerToPKCS7DigestedDataTemplate[] = {
{ SEC_ASN1_POINTER, 0, SEC_PKCS7DigestedDataTemplate }
};
static const SEC_ASN1Template SEC_PKCS7EncryptedDataTemplate[] = {
{ SEC_ASN1_SEQUENCE | SEC_ASN1_MAY_STREAM,
0, NULL, sizeof(SEC_PKCS7EncryptedData) },
{ SEC_ASN1_INTEGER,
offsetof(SEC_PKCS7EncryptedData,version) },
{ SEC_ASN1_INLINE,
offsetof(SEC_PKCS7EncryptedData,encContentInfo),
SEC_PKCS7EncryptedContentInfoTemplate },
{ 0 }
};
static const SEC_ASN1Template SEC_PointerToPKCS7EncryptedDataTemplate[] = {
{ SEC_ASN1_POINTER, 0, SEC_PKCS7EncryptedDataTemplate }
};
const SEC_ASN1Template SEC_SMIMEKEAParamTemplateSkipjack[] = {
{ SEC_ASN1_SEQUENCE,
0, NULL, sizeof(SEC_PKCS7SMIMEKEAParameters) },
{ SEC_ASN1_OCTET_STRING /* | SEC_ASN1_OPTIONAL */,
offsetof(SEC_PKCS7SMIMEKEAParameters,originatorKEAKey) },
{ SEC_ASN1_OCTET_STRING,
offsetof(SEC_PKCS7SMIMEKEAParameters,originatorRA) },
{ 0 }
};
const SEC_ASN1Template SEC_SMIMEKEAParamTemplateNoSkipjack[] = {
{ SEC_ASN1_SEQUENCE,
0, NULL, sizeof(SEC_PKCS7SMIMEKEAParameters) },
{ SEC_ASN1_OCTET_STRING /* | SEC_ASN1_OPTIONAL */,
offsetof(SEC_PKCS7SMIMEKEAParameters,originatorKEAKey) },
{ SEC_ASN1_OCTET_STRING,
offsetof(SEC_PKCS7SMIMEKEAParameters,originatorRA) },
{ SEC_ASN1_OCTET_STRING | SEC_ASN1_OPTIONAL ,
offsetof(SEC_PKCS7SMIMEKEAParameters,nonSkipjackIV) },
{ 0 }
};
const SEC_ASN1Template SEC_SMIMEKEAParamTemplateAllParams[] = {
{ SEC_ASN1_SEQUENCE,
0, NULL, sizeof(SEC_PKCS7SMIMEKEAParameters) },
{ SEC_ASN1_OCTET_STRING /* | SEC_ASN1_OPTIONAL */,
offsetof(SEC_PKCS7SMIMEKEAParameters,originatorKEAKey) },
{ SEC_ASN1_OCTET_STRING,
offsetof(SEC_PKCS7SMIMEKEAParameters,originatorRA) },
{ SEC_ASN1_OCTET_STRING | SEC_ASN1_OPTIONAL ,
offsetof(SEC_PKCS7SMIMEKEAParameters,nonSkipjackIV) },
{ SEC_ASN1_OCTET_STRING | SEC_ASN1_OPTIONAL ,
offsetof(SEC_PKCS7SMIMEKEAParameters,bulkKeySize) },
{ 0 }
};
const SEC_ASN1Template*
sec_pkcs7_get_kea_template(SECKEATemplateSelector whichTemplate)
{
const SEC_ASN1Template *returnVal = NULL;
switch(whichTemplate)
{
case SECKEAUsesNonSkipjack:
returnVal = SEC_SMIMEKEAParamTemplateNoSkipjack;
break;
case SECKEAUsesSkipjack:
returnVal = SEC_SMIMEKEAParamTemplateSkipjack;
break;
case SECKEAUsesNonSkipjackWithPaddedEncKey:
default:
returnVal = SEC_SMIMEKEAParamTemplateAllParams;
break;
}
return returnVal;
}
static const SEC_ASN1Template *
sec_pkcs7_choose_content_template(void *src_or_dest, PRBool encoding)
{
const SEC_ASN1Template *theTemplate;
SEC_PKCS7ContentInfo *cinfo;
SECOidTag kind;
PORT_Assert (src_or_dest != NULL);
if (src_or_dest == NULL)
return NULL;
cinfo = (SEC_PKCS7ContentInfo*)src_or_dest;
kind = SEC_PKCS7ContentType (cinfo);
switch (kind) {
default:
theTemplate = SEC_ASN1_GET(SEC_PointerToAnyTemplate);
break;
case SEC_OID_PKCS7_DATA:
theTemplate = SEC_ASN1_GET(SEC_PointerToOctetStringTemplate);
break;
case SEC_OID_PKCS7_SIGNED_DATA:
theTemplate = SEC_PointerToPKCS7SignedDataTemplate;
break;
case SEC_OID_PKCS7_ENVELOPED_DATA:
theTemplate = SEC_PointerToPKCS7EnvelopedDataTemplate;
break;
case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
theTemplate = SEC_PointerToPKCS7SignedAndEnvelopedDataTemplate;
break;
case SEC_OID_PKCS7_DIGESTED_DATA:
theTemplate = SEC_PointerToPKCS7DigestedDataTemplate;
break;
case SEC_OID_PKCS7_ENCRYPTED_DATA:
theTemplate = SEC_PointerToPKCS7EncryptedDataTemplate;
break;
}
return theTemplate;
}
/*
* End of templates. Do not add stuff after this; put new code
* up above the start of the template definitions.
*/