Source code

Revision control

Copy as Markdown

Other Tools

/* 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/. */
/*
* PKCS7 implementation -- the exported parts that are used whether
* creating or decoding.
*/
#include "p7local.h"
#include "cert.h"
#include "secitem.h"
#include "secoid.h"
#include "pk11func.h"
/*
* Find out (saving pointer to lookup result for future reference)
* and return the inner content type.
*/
SECOidTag
SEC_PKCS7ContentType(SEC_PKCS7ContentInfo *cinfo)
{
if (cinfo->contentTypeTag == NULL)
cinfo->contentTypeTag = SECOID_FindOID(&(cinfo->contentType));
if (cinfo->contentTypeTag == NULL)
return SEC_OID_UNKNOWN;
return cinfo->contentTypeTag->offset;
}
/*
* Destroy a PKCS7 contentInfo and all of its sub-pieces.
*/
void
SEC_PKCS7DestroyContentInfo(SEC_PKCS7ContentInfo *cinfo)
{
SECOidTag kind;
CERTCertificate **certs;
CERTCertificateList **certlists;
SEC_PKCS7SignerInfo **signerinfos;
SEC_PKCS7RecipientInfo **recipientinfos;
PORT_Assert(cinfo->refCount > 0);
if (cinfo->refCount <= 0)
return;
cinfo->refCount--;
if (cinfo->refCount > 0)
return;
certs = NULL;
certlists = NULL;
recipientinfos = NULL;
signerinfos = NULL;
kind = SEC_PKCS7ContentType(cinfo);
switch (kind) {
case SEC_OID_PKCS7_ENVELOPED_DATA: {
SEC_PKCS7EnvelopedData *edp;
edp = cinfo->content.envelopedData;
if (edp != NULL) {
recipientinfos = edp->recipientInfos;
}
} break;
case SEC_OID_PKCS7_SIGNED_DATA: {
SEC_PKCS7SignedData *sdp;
sdp = cinfo->content.signedData;
if (sdp != NULL) {
certs = sdp->certs;
certlists = sdp->certLists;
signerinfos = sdp->signerInfos;
}
} break;
case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: {
SEC_PKCS7SignedAndEnvelopedData *saedp;
saedp = cinfo->content.signedAndEnvelopedData;
if (saedp != NULL) {
certs = saedp->certs;
certlists = saedp->certLists;
recipientinfos = saedp->recipientInfos;
signerinfos = saedp->signerInfos;
if (saedp->sigKey != NULL)
PK11_FreeSymKey(saedp->sigKey);
}
} break;
default:
/* XXX Anything else that needs to be "manually" freed/destroyed? */
break;
}
if (certs != NULL) {
CERTCertificate *cert;
while ((cert = *certs++) != NULL) {
CERT_DestroyCertificate(cert);
}
}
if (certlists != NULL) {
CERTCertificateList *certlist;
while ((certlist = *certlists++) != NULL) {
CERT_DestroyCertificateList(certlist);
}
}
if (recipientinfos != NULL) {
SEC_PKCS7RecipientInfo *ri;
while ((ri = *recipientinfos++) != NULL) {
if (ri->cert != NULL)
CERT_DestroyCertificate(ri->cert);
}
}
if (signerinfos != NULL) {
SEC_PKCS7SignerInfo *si;
while ((si = *signerinfos++) != NULL) {
if (si->cert != NULL)
CERT_DestroyCertificate(si->cert);
if (si->certList != NULL)
CERT_DestroyCertificateList(si->certList);
}
}
if (cinfo->poolp != NULL) {
PORT_FreeArena(cinfo->poolp, PR_FALSE); /* XXX clear it? */
}
}
/*
* Return a copy of the given contentInfo. The copy may be virtual
* or may be real -- either way, the result needs to be passed to
* SEC_PKCS7DestroyContentInfo later (as does the original).
*/
SEC_PKCS7ContentInfo *
SEC_PKCS7CopyContentInfo(SEC_PKCS7ContentInfo *cinfo)
{
if (cinfo == NULL)
return NULL;
PORT_Assert(cinfo->refCount > 0);
if (cinfo->created) {
/*
* Want to do a real copy of these; otherwise subsequent
* changes made to either copy are likely to be a surprise.
* XXX I suspect that this will not actually be called for yet,
* which is why the assert, so to notice if it is...
*/
PORT_Assert(0);
/*
* XXX Create a new pool here, and copy everything from
* within. For cert stuff, need to call the appropriate
* copy functions, etc.
*/
}
cinfo->refCount++;
return cinfo;
}
/*
* Return a pointer to the actual content. In the case of those types
* which are encrypted, this returns the *plain* content.
* XXX Needs revisiting if/when we handle nested encrypted types.
*/
SECItem *
SEC_PKCS7GetContent(SEC_PKCS7ContentInfo *cinfo)
{
SECOidTag kind;
kind = SEC_PKCS7ContentType(cinfo);
switch (kind) {
case SEC_OID_PKCS7_DATA:
return cinfo->content.data;
case SEC_OID_PKCS7_DIGESTED_DATA: {
SEC_PKCS7DigestedData *digd;
digd = cinfo->content.digestedData;
if (digd == NULL)
break;
return SEC_PKCS7GetContent(&(digd->contentInfo));
}
case SEC_OID_PKCS7_ENCRYPTED_DATA: {
SEC_PKCS7EncryptedData *encd;
encd = cinfo->content.encryptedData;
if (encd == NULL)
break;
return &(encd->encContentInfo.plainContent);
}
case SEC_OID_PKCS7_ENVELOPED_DATA: {
SEC_PKCS7EnvelopedData *envd;
envd = cinfo->content.envelopedData;
if (envd == NULL)
break;
return &(envd->encContentInfo.plainContent);
}
case SEC_OID_PKCS7_SIGNED_DATA: {
SEC_PKCS7SignedData *sigd;
sigd = cinfo->content.signedData;
if (sigd == NULL)
break;
return SEC_PKCS7GetContent(&(sigd->contentInfo));
}
case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: {
SEC_PKCS7SignedAndEnvelopedData *saed;
saed = cinfo->content.signedAndEnvelopedData;
if (saed == NULL)
break;
return &(saed->encContentInfo.plainContent);
}
default:
PORT_Assert(0);
break;
}
return NULL;
}
/*
* XXX Fix the placement and formatting of the
* following routines (i.e. make them consistent with the rest of
* the pkcs7 code -- I think some/many belong in other files and
* they all need a formatting/style rehaul)
*/
/* retrieve the algorithm identifier for encrypted data.
* the identifier returned is a copy of the algorithm identifier
* in the content info and needs to be freed after being used.
*
* cinfo is the content info for which to retrieve the
* encryption algorithm.
*
* if the content info is not encrypted data or an error
* occurs NULL is returned.
*/
SECAlgorithmID *
SEC_PKCS7GetEncryptionAlgorithm(SEC_PKCS7ContentInfo *cinfo)
{
SECAlgorithmID *alg = 0;
switch (SEC_PKCS7ContentType(cinfo)) {
case SEC_OID_PKCS7_ENCRYPTED_DATA:
alg = &cinfo->content.encryptedData->encContentInfo.contentEncAlg;
break;
case SEC_OID_PKCS7_ENVELOPED_DATA:
alg = &cinfo->content.envelopedData->encContentInfo.contentEncAlg;
break;
case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
alg = &cinfo->content.signedAndEnvelopedData
->encContentInfo.contentEncAlg;
break;
default:
alg = 0;
break;
}
return alg;
}
/* set the content of the content info. For data content infos,
* the data is set. For encrytped content infos, the plainContent
* is set, and is expected to be encrypted later.
*
* cinfo is the content info where the data will be set
*
* buf is a buffer of the data to set
*
* len is the length of the data being set.
*
* in the event of an error, SECFailure is returned. SECSuccess
* indicates the content was successfully set.
*/
SECStatus
SEC_PKCS7SetContent(SEC_PKCS7ContentInfo *cinfo,
const char *buf,
unsigned long len)
{
SECOidTag cinfo_type;
SECStatus rv;
SECItem content;
SECOidData *contentTypeTag = NULL;
content.type = siBuffer;
content.data = (unsigned char *)buf;
content.len = len;
cinfo_type = SEC_PKCS7ContentType(cinfo);
/* set inner content */
switch (cinfo_type) {
case SEC_OID_PKCS7_SIGNED_DATA:
if (content.len > 0) {
/* we "leak" the old content here, but as it's all in the pool */
/* it does not really matter */
/* create content item if necessary */
if (cinfo->content.signedData->contentInfo.content.data == NULL)
cinfo->content.signedData->contentInfo.content.data = SECITEM_AllocItem(cinfo->poolp, NULL, 0);
rv = SECITEM_CopyItem(cinfo->poolp,
cinfo->content.signedData->contentInfo.content.data,
&content);
} else {
cinfo->content.signedData->contentInfo.content.data->data = NULL;
cinfo->content.signedData->contentInfo.content.data->len = 0;
rv = SECSuccess;
}
if (rv == SECFailure)
goto loser;
break;
case SEC_OID_PKCS7_ENCRYPTED_DATA:
/* XXX this forces the inner content type to be "data" */
/* do we really want to override without asking or reason? */
contentTypeTag = SECOID_FindOIDByTag(SEC_OID_PKCS7_DATA);
if (contentTypeTag == NULL)
goto loser;
rv = SECITEM_CopyItem(cinfo->poolp,
&(cinfo->content.encryptedData->encContentInfo.contentType),
&(contentTypeTag->oid));
if (rv == SECFailure)
goto loser;
if (content.len > 0) {
rv = SECITEM_CopyItem(cinfo->poolp,
&(cinfo->content.encryptedData->encContentInfo.plainContent),
&content);
} else {
cinfo->content.encryptedData->encContentInfo.plainContent.data = NULL;
cinfo->content.encryptedData->encContentInfo.encContent.data = NULL;
cinfo->content.encryptedData->encContentInfo.plainContent.len = 0;
cinfo->content.encryptedData->encContentInfo.encContent.len = 0;
rv = SECSuccess;
}
if (rv == SECFailure)
goto loser;
break;
case SEC_OID_PKCS7_DATA:
cinfo->content.data = (SECItem *)PORT_ArenaZAlloc(cinfo->poolp,
sizeof(SECItem));
if (cinfo->content.data == NULL)
goto loser;
if (content.len > 0) {
rv = SECITEM_CopyItem(cinfo->poolp,
cinfo->content.data, &content);
} else {
/* handle case with NULL content */
rv = SECSuccess;
}
if (rv == SECFailure)
goto loser;
break;
default:
goto loser;
}
return SECSuccess;
loser:
return SECFailure;
}
/* the content of an encrypted data content info is encrypted.
* it is assumed that for encrypted data, that the data has already
* been set and is in the "plainContent" field of the content info.
*
* cinfo is the content info to encrypt
*
* key is the key with which to perform the encryption. if the
* algorithm is a password based encryption algorithm, the
* key is actually a password which will be processed per
* PKCS #5.
*
* in the event of an error, SECFailure is returned. SECSuccess
* indicates a success.
*/
SECStatus
SEC_PKCS7EncryptContents(PLArenaPool *poolp,
SEC_PKCS7ContentInfo *cinfo,
SECItem *key,
void *wincx)
{
SECAlgorithmID *algid = NULL;
SECItem *src;
SECItem *dest;
SECItem *blocked_data = NULL;
void *mark;
void *cx;
PK11SymKey *eKey = NULL;
PK11SlotInfo *slot = NULL;
CK_MECHANISM_TYPE cryptoMechType;
int bs;
SECStatus rv = SECFailure;
SECItem *c_param = NULL;
if ((cinfo == NULL) || (key == NULL))
return SECFailure;
if (SEC_PKCS7ContentType(cinfo) != SEC_OID_PKCS7_ENCRYPTED_DATA)
return SECFailure;
algid = SEC_PKCS7GetEncryptionAlgorithm(cinfo);
if (algid == NULL)
return SECFailure;
if (poolp == NULL)
poolp = cinfo->poolp;
mark = PORT_ArenaMark(poolp);
src = &cinfo->content.encryptedData->encContentInfo.plainContent;
dest = &cinfo->content.encryptedData->encContentInfo.encContent;
dest->data = (unsigned char *)PORT_ArenaZAlloc(poolp, (src->len + 64));
dest->len = (src->len + 64);
if (dest->data == NULL) {
rv = SECFailure;
goto loser;
}
slot = PK11_GetInternalKeySlot();
if (slot == NULL) {
rv = SECFailure;
goto loser;
}
eKey = PK11_PBEKeyGen(slot, algid, key, PR_FALSE, wincx);
if (eKey == NULL) {
rv = SECFailure;
goto loser;
}
cryptoMechType = PK11_GetPBECryptoMechanism(algid, &c_param, key);
if (cryptoMechType == CKM_INVALID_MECHANISM) {
rv = SECFailure;
goto loser;
}
/* block according to PKCS 8 */
bs = PK11_GetBlockSize(cryptoMechType, c_param);
rv = SECSuccess;
if (bs) {
char pad_char;
pad_char = (char)(bs - (src->len % bs));
if (src->len % bs) {
rv = SECSuccess;
blocked_data = PK11_BlockData(src, bs);
if (blocked_data) {
PORT_Memset((blocked_data->data + blocked_data->len - (int)pad_char),
pad_char, (int)pad_char);
} else {
rv = SECFailure;
goto loser;
}
} else {
blocked_data = SECITEM_DupItem(src);
if (blocked_data) {
blocked_data->data = (unsigned char *)PORT_Realloc(
blocked_data->data,
blocked_data->len + bs);
if (blocked_data->data) {
blocked_data->len += bs;
PORT_Memset((blocked_data->data + src->len), (char)bs, bs);
} else {
rv = SECFailure;
goto loser;
}
} else {
rv = SECFailure;
goto loser;
}
}
} else {
blocked_data = SECITEM_DupItem(src);
if (!blocked_data) {
rv = SECFailure;
goto loser;
}
}
cx = PK11_CreateContextBySymKey(cryptoMechType, CKA_ENCRYPT,
eKey, c_param);
if (cx == NULL) {
rv = SECFailure;
goto loser;
}
rv = PK11_CipherOp((PK11Context *)cx, dest->data, (int *)(&dest->len),
(int)(src->len + 64), blocked_data->data,
(int)blocked_data->len);
PK11_DestroyContext((PK11Context *)cx, PR_TRUE);
loser:
/* let success fall through */
if (blocked_data != NULL)
SECITEM_ZfreeItem(blocked_data, PR_TRUE);
if (rv == SECFailure)
PORT_ArenaRelease(poolp, mark);
else
PORT_ArenaUnmark(poolp, mark);
if (eKey != NULL)
PK11_FreeSymKey(eKey);
if (slot != NULL)
PK11_FreeSlot(slot);
if (c_param != NULL)
SECITEM_ZfreeItem(c_param, PR_TRUE);
return rv;
}
/* the content of an encrypted data content info is decrypted.
* it is assumed that for encrypted data, that the data has already
* been set and is in the "encContent" field of the content info.
*
* cinfo is the content info to decrypt
*
* key is the key with which to perform the decryption. if the
* algorithm is a password based encryption algorithm, the
* key is actually a password which will be processed per
* PKCS #5.
*
* in the event of an error, SECFailure is returned. SECSuccess
* indicates a success.
*/
SECStatus
SEC_PKCS7DecryptContents(PLArenaPool *poolp,
SEC_PKCS7ContentInfo *cinfo,
SECItem *key,
void *wincx)
{
SECAlgorithmID *algid = NULL;
SECStatus rv = SECFailure;
SECItem *dest, *src;
void *mark;
PK11SymKey *eKey = NULL;
PK11SlotInfo *slot = NULL;
CK_MECHANISM_TYPE cryptoMechType;
void *cx;
SECItem *c_param = NULL;
int bs;
if ((cinfo == NULL) || (key == NULL))
return SECFailure;
if (SEC_PKCS7ContentType(cinfo) != SEC_OID_PKCS7_ENCRYPTED_DATA)
return SECFailure;
algid = SEC_PKCS7GetEncryptionAlgorithm(cinfo);
if (algid == NULL)
return SECFailure;
if (poolp == NULL)
poolp = cinfo->poolp;
mark = PORT_ArenaMark(poolp);
src = &cinfo->content.encryptedData->encContentInfo.encContent;
dest = &cinfo->content.encryptedData->encContentInfo.plainContent;
dest->data = (unsigned char *)PORT_ArenaZAlloc(poolp, (src->len + 64));
dest->len = (src->len + 64);
if (dest->data == NULL) {
rv = SECFailure;
goto loser;
}
slot = PK11_GetInternalKeySlot();
if (slot == NULL) {
rv = SECFailure;
goto loser;
}
eKey = PK11_PBEKeyGen(slot, algid, key, PR_FALSE, wincx);
if (eKey == NULL) {
rv = SECFailure;
goto loser;
}
cryptoMechType = PK11_GetPBECryptoMechanism(algid, &c_param, key);
if (cryptoMechType == CKM_INVALID_MECHANISM) {
rv = SECFailure;
goto loser;
}
cx = PK11_CreateContextBySymKey(cryptoMechType, CKA_DECRYPT,
eKey, c_param);
if (cx == NULL) {
rv = SECFailure;
goto loser;
}
rv = PK11_CipherOp((PK11Context *)cx, dest->data, (int *)(&dest->len),
(int)(src->len + 64), src->data, (int)src->len);
PK11_DestroyContext((PK11Context *)cx, PR_TRUE);
bs = PK11_GetBlockSize(cryptoMechType, c_param);
if (bs) {
/* check for proper badding in block algorithms. this assumes
* RC2 cbc or a DES cbc variant. and the padding is thus defined
*/
if (((int)dest->data[dest->len - 1] <= bs) &&
((int)dest->data[dest->len - 1] > 0)) {
dest->len -= (int)dest->data[dest->len - 1];
} else {
rv = SECFailure;
/* set an error ? */
}
}
loser:
/* let success fall through */
if (rv == SECFailure)
PORT_ArenaRelease(poolp, mark);
else
PORT_ArenaUnmark(poolp, mark);
if (eKey != NULL)
PK11_FreeSymKey(eKey);
if (slot != NULL)
PK11_FreeSlot(slot);
if (c_param != NULL)
SECITEM_ZfreeItem(c_param, PR_TRUE);
return rv;
}
SECItem **
SEC_PKCS7GetCertificateList(SEC_PKCS7ContentInfo *cinfo)
{
switch (SEC_PKCS7ContentType(cinfo)) {
case SEC_OID_PKCS7_SIGNED_DATA:
return cinfo->content.signedData->rawCerts;
break;
default:
return NULL;
break;
}
}
int
SEC_PKCS7GetKeyLength(SEC_PKCS7ContentInfo *cinfo)
{
if (cinfo->contentTypeTag->offset == SEC_OID_PKCS7_ENVELOPED_DATA)
return cinfo->content.envelopedData->encContentInfo.keysize;
else
return 0;
}