netscape-revival
security/lib/pkcs7/p7decode.c
/*
* PKCS7 decoding, verification.
*
* Copyright © 1996 Netscape Communications Corporation, all rights reserved.
*
* $Id: p7decode.c,v 1.11.2.15 1997/05/12 02:31:41 repka Exp $
*/
#include "p7local.h"
#include "cert.h"
#ifdef XP_MAC /* XXX do not want to have to include */
#include "certdb.h" /* certdb.h -- the trust stuff needed by */
#else /* the add certificate code needs to get */
#include "../cert/certdb.h" /* rewritten/abstracted and then this */
#endif /* include should be removed! */
#include "crypto.h"
#include "key.h"
#include "secasn1.h"
#include "secitem.h"
#include "secoid.h"
#include "pk11func.h"
#include "prtime.h"
extern int SEC_ERROR_BAD_SIGNATURE;
extern int SEC_ERROR_DECRYPTION_DISALLOWED;
extern int SEC_ERROR_NO_MEMORY;
extern int SEC_ERROR_NOT_A_RECIPIENT;
extern int SEC_ERROR_PKCS7_KEYALG_MISMATCH;
extern int SEC_ERROR_PKCS7_BAD_SIGNATURE;
extern int SEC_ERROR_UNSUPPORTED_KEYALG;
/*
* XXX See comment where this function is called. It is temporary and
* should be removed when the explicit call itself is removed.
*/
extern PRBool
SECMIME_DecryptionAllowed (SECAlgorithmID *algid, PK11SymKey *key);
struct sec_pkcs7_decoder_worker {
int depth;
int digcnt;
void **digcxs;
SECHashObject **digobjs;
sec_PKCS7CipherObject *decryptobj;
PRBool saw_contents;
};
struct SEC_PKCS7DecoderContextStr {
SEC_ASN1DecoderContext *dcx;
SEC_PKCS7ContentInfo *cinfo;
SEC_PKCS7DecoderContentCallback cb;
void *cb_arg;
SECKEYGetPasswordKey pwfn;
void *pwfn_arg;
struct sec_pkcs7_decoder_worker worker;
PRArenaPool *tmp_poolp;
int error;
};
/*
* Handle one worker, decrypting and digesting the data as necessary.
*
* XXX If/when we support nested contents, this probably needs to be
* revised somewhat to get passed the content-info (which unfortunately
* can be two different types depending on whether it is encrypted or not)
* corresponding to the given worker.
*/
static void
sec_pkcs7_decoder_work_data (SEC_PKCS7DecoderContext *p7dcx,
struct sec_pkcs7_decoder_worker *worker,
const unsigned char *data, unsigned long len,
PRBool final)
{
unsigned char *buf = NULL;
SECStatus rv;
int i;
/*
* We should really have data to process, or we should be trying
* to finish/flush the last block. (This is an overly paranoid
* check since all callers are in this file and simple inspection
* proves they do it right. But it could find a bug in future
* modifications/development, that is why it is here.)
*/
PORT_Assert ((data != NULL && len) || final);
/*
* Decrypt this chunk.
*
* #### If we get an error, we do not want to do the digest or callback,
* but we want to keep decoding. Or maybe we want to stop decoding
* altogether if there is a callback, because obviously we are not
* sending the data back and they want to know that.
*/
if (worker->decryptobj != NULL) {
/* XXX the following lengths should all be longs? */
unsigned int inlen; /* length of data being decrypted */
unsigned int outlen; /* length of decrypted data */
unsigned int buflen; /* length available for decrypted data */
SECItem *plain;
inlen = len;
buflen = sec_PKCS7DecryptLength (worker->decryptobj, inlen, final);
if (buflen == 0) {
if (inlen == 0) /* no input and no output */
return;
/*
* No output is expected, but the input data may be buffered
* so we still have to call Decrypt.
*/
rv = sec_PKCS7Decrypt (worker->decryptobj, NULL, NULL, 0,
data, inlen, final);
if (rv != SECSuccess) {
p7dcx->error = PORT_GetError();
return; /* #### indicate error? */
}
return;
}
if (p7dcx->cb != NULL) {
buf = (unsigned char *) PORT_Alloc (buflen);
plain = NULL;
} else {
unsigned long oldlen;
/*
* XXX This assumes one level of content only.
* See comment above about nested content types.
* XXX Also, it should work for signedAndEnvelopedData, too!
*/
plain = &(p7dcx->cinfo->
content.envelopedData->encContentInfo.plainContent);
oldlen = plain->len;
if (oldlen == 0) {
buf = PORT_ArenaAlloc (p7dcx->cinfo->poolp, buflen);
} else {
buf = PORT_ArenaGrow (p7dcx->cinfo->poolp, plain->data,
oldlen, oldlen + buflen);
if (buf != NULL)
buf += oldlen;
}
plain->data = buf;
}
if (buf == NULL) {
p7dcx->error = SEC_ERROR_NO_MEMORY;
return; /* #### indicate error? */
}
rv = sec_PKCS7Decrypt (worker->decryptobj, buf, &outlen, buflen,
data, inlen, final);
if (rv != SECSuccess) {
p7dcx->error = PORT_GetError();
return; /* #### indicate error? */
}
if (plain != NULL) {
PORT_Assert (final || outlen == buflen);
plain->len += outlen;
}
data = buf;
len = outlen;
}
/*
* Update the running digests.
*/
if (len) {
for (i = 0; i < worker->digcnt; i++) {
(* worker->digobjs[i]->update) (worker->digcxs[i], data, len);
}
}
/*
* Pass back the contents bytes, and free the temporary buffer.
*/
if (p7dcx->cb != NULL) {
if (len)
(* p7dcx->cb) (p7dcx->cb_arg, (const char *)data, len);
if (worker->decryptobj != NULL) {
PORT_Assert (buf != NULL);
PORT_Free (buf);
}
}
}
static void
sec_pkcs7_decoder_filter (void *arg, const char *data, unsigned long len,
int depth, SEC_ASN1EncodingPart data_kind)
{
SEC_PKCS7DecoderContext *p7dcx;
struct sec_pkcs7_decoder_worker *worker;
/*
* Since we do not handle any nested contents, the only bytes we
* are really interested in are the actual contents bytes (not
* the identifier, length, or end-of-contents bytes). If we were
* handling nested types we would probably need to do something
* smarter based on depth and data_kind.
*/
if (data_kind != SEC_ASN1_Contents)
return;
/*
* The ASN.1 decoder should not even call us with a length of 0.
* Just being paranoid.
*/
PORT_Assert (len);
if (len == 0)
return;
p7dcx = arg;
/*
* Handling nested contents would mean that there is a chain
* of workers -- one per each level of content. The following
* would start with the first worker and loop over them.
*/
worker = &(p7dcx->worker);
worker->saw_contents = PR_TRUE;
sec_pkcs7_decoder_work_data (p7dcx, worker,
(const unsigned char *) data, len, PR_FALSE);
}
/*
* Create digest contexts for each algorithm in "digestalgs".
* No algorithms is not an error, we just do not do anything.
* An error (like trouble allocating memory), marks the error
* in "p7dcx" and returns SECFailure, which means that our caller
* should just give up altogether.
*/
static SECStatus
sec_pkcs7_decoder_start_digests (SEC_PKCS7DecoderContext *p7dcx, int depth,
SECAlgorithmID **digestalgs)
{
SECAlgorithmID *algid;
SECOidData *oiddata;
SECHashObject *digobj;
void *digcx;
int i, digcnt;
if (digestalgs == NULL)
return SECSuccess;
/*
* Count the algorithms.
*/
digcnt = 0;
while (digestalgs[digcnt] != NULL)
digcnt++;
/*
* No algorithms means no work to do.
* This is not expected, so cause an assert.
* But if it does happen, just act as if there were
* no algorithms specified.
*/
PORT_Assert (digcnt != 0);
if (digcnt == 0)
return SECSuccess;
p7dcx->worker.digcxs = PORT_ArenaAlloc (p7dcx->tmp_poolp,
digcnt * sizeof (void *));
p7dcx->worker.digobjs = PORT_ArenaAlloc (p7dcx->tmp_poolp,
digcnt * sizeof (SECHashObject *));
if (p7dcx->worker.digcxs == NULL || p7dcx->worker.digobjs == NULL) {
p7dcx->error = SEC_ERROR_NO_MEMORY;
return SECFailure;
}
p7dcx->worker.depth = depth;
p7dcx->worker.digcnt = 0;
/*
* Create a digest context for each algorithm.
*/
for (i = 0; i < digcnt; i++) {
algid = digestalgs[i];
oiddata = SECOID_FindOID(&(algid->algorithm));
if (oiddata == NULL) {
digobj = NULL;
} else {
switch (oiddata->offset) {
case SEC_OID_MD2:
digobj = &SECHashObjects[HASH_AlgMD2];
break;
case SEC_OID_MD5:
digobj = &SECHashObjects[HASH_AlgMD5];
break;
case SEC_OID_SHA1:
digobj = &SECHashObjects[HASH_AlgSHA1];
break;
default:
digobj = NULL;
break;
}
}
/*
* Skip any algorithm we do not even recognize; obviously,
* this could be a problem, but if it is critical then the
* result will just be that the signature does not verify.
* We do not necessarily want to error out here, because
* the particular algorithm may not actually be important,
* but we cannot know that until later.
*/
if (digobj == NULL) {
p7dcx->worker.digcnt--;
continue;
}
digcx = (* digobj->create)();
if (digcx != NULL) {
(* digobj->begin) (digcx);
p7dcx->worker.digobjs[p7dcx->worker.digcnt] = digobj;
p7dcx->worker.digcxs[p7dcx->worker.digcnt] = digcx;
p7dcx->worker.digcnt++;
}
}
if (p7dcx->worker.digcnt != 0)
SEC_ASN1DecoderSetFilterProc (p7dcx->dcx,
sec_pkcs7_decoder_filter,
p7dcx,
p7dcx->cb != NULL);
return SECSuccess;
}
/*
* Close out all of the digest contexts, storing the results in "digestsp".
*/
static SECStatus
sec_pkcs7_decoder_finish_digests (SEC_PKCS7DecoderContext *p7dcx,
PRArenaPool *poolp,
SECItem ***digestsp)
{
struct sec_pkcs7_decoder_worker *worker;
SECHashObject *digobj;
void *digcx;
SECItem **digests, *digest;
int i;
void *mark;
/*
* XXX Handling nested contents would mean that there is a chain
* of workers -- one per each level of content. The following
* would want to find the last worker in the chain.
*/
worker = &(p7dcx->worker);
/*
* If no digests, then we have nothing to do.
*/
if (worker->digcnt == 0)
return SECSuccess;
/*
* No matter what happens after this, we want to stop filtering.
* XXX If we handle nested contents, we only want to stop filtering
* if we are finishing off the *last* worker.
*/
SEC_ASN1DecoderClearFilterProc (p7dcx->dcx);
/*
* If we ended up with no contents, just destroy each
* digest context -- they are meaningless and potentially
* confusing, because their presence would imply some content
* was digested.
*/
if (! worker->saw_contents) {
for (i = 0; i < worker->digcnt; i++) {
digcx = worker->digcxs[i];
digobj = worker->digobjs[i];
(* digobj->destroy) (digcx, PR_TRUE);
}
return SECSuccess;
}
mark = PORT_ArenaMark (poolp);
/*
* Close out each digest context, saving digest away.
*/
digests = PORT_ArenaAlloc (poolp, (worker->digcnt + 1) * sizeof(SECItem *));
digest = PORT_ArenaAlloc (poolp, worker->digcnt * sizeof(SECItem));
if (digests == NULL || digest == NULL) {
p7dcx->error = PORT_GetError();
PORT_ArenaRelease (poolp, mark);
return SECFailure;
}
for (i = 0; i < worker->digcnt; i++, digest++) {
digcx = worker->digcxs[i];
digobj = worker->digobjs[i];
digest->data = PORT_ArenaAlloc (poolp, digobj->length);
if (digest->data == NULL) {
p7dcx->error = PORT_GetError();
PORT_ArenaRelease (poolp, mark);
return SECFailure;
}
digest->len = digobj->length;
(* digobj->end) (digcx, digest->data, &(digest->len), digest->len);
(* digobj->destroy) (digcx, PR_TRUE);
digests[i] = digest;
}
digests[i] = NULL;
*digestsp = digests;
return SECSuccess;
}
/*
* We are just about to decode the content of an EnvelopedData.
* Set up a decryption context so we can decrypt as we go.
* Presumably we are one of the recipients listed in "recipientinfos".
* (XXX And if we are not, or if we have trouble, what should we do?
* It would be nice to let the decoding still work. Maybe it should
* be an error if there is a content callback, but not an error otherwise?)
* The encryption key and related information can be found in "enccinfo".
*/
static SECStatus
sec_pkcs7_decoder_start_decrypt (SEC_PKCS7DecoderContext *p7dcx, int depth,
SEC_PKCS7RecipientInfo **recipientinfos,
SEC_PKCS7EncryptedContentInfo *enccinfo,
PK11SymKey **copy_key_for_signature)
{
SEC_PKCS7RecipientInfo *ri;
CERTCertificate *cert;
SECKEYPrivateKey *privkey = NULL;
PK11SymKey *bulkkey;
SECOidTag keyalgtag, bulkalgtag;
sec_PKCS7CipherObject *decryptobj;
PK11SlotInfo *slot;
int i;
if (recipientinfos == NULL || recipientinfos[0] == NULL) {
p7dcx->error = SEC_ERROR_NOT_A_RECIPIENT;
goto no_decryption;
}
cert = NULL; /* quiet gcc warning about used uninitialized */
for (i = 0; (ri = recipientinfos[i]) != NULL; i++) {
cert = PK11_FindCertByIssuerAndSN (&slot, ri->issuerAndSN,
p7dcx->pwfn_arg);
if (cert != NULL) {
privkey = PK11_FindPrivateKeyFromCert (slot, cert, p7dcx->pwfn_arg);
if (privkey != NULL) {
PK11_FreeSlot (slot);
break;
}
PK11_FreeSlot (slot);
CERT_DestroyCertificate (cert);
}
}
if (ri == NULL) {
p7dcx->error = SEC_ERROR_NOT_A_RECIPIENT;
goto no_decryption;
}
ri->cert = cert; /* so we can find it later */
PORT_Assert(privkey != NULL);
keyalgtag = SECOID_GetAlgorithmTag(&(cert->subjectPublicKeyInfo.algorithm));
if (keyalgtag != SECOID_GetAlgorithmTag (&(ri->keyEncAlg))) {
p7dcx->error = SEC_ERROR_PKCS7_KEYALG_MISMATCH;
goto no_decryption;
}
bulkalgtag = SECOID_GetAlgorithmTag (&(enccinfo->contentEncAlg));
switch (keyalgtag) {
case SEC_OID_PKCS1_RSA_ENCRYPTION:
bulkkey = PK11_PubUnwrapSymKey (privkey, &ri->encKey,
PK11_AlgtagToMechanism (bulkalgtag),
0);
if (bulkkey == NULL) {
p7dcx->error = PORT_GetError();
PORT_SetError(0);
goto no_decryption;
}
break;
default:
p7dcx->error = SEC_ERROR_UNSUPPORTED_KEYALG;
goto no_decryption;
}
enccinfo->keysize = PK11_GetKeyStrength(bulkkey);
SECKEY_DestroyPrivateKey (privkey);
privkey = NULL;
/*
* XXX This is BAD, because it assumes that the only user of inline
* PKCS7 decryption is SMIME. Which is true right now, but *not*
* how this code should be structured. The fix for this is to have
* the caller of SEC_PKCS7DecoderStart() pass in the following
* function (with the API possibly modified, as appropriate) as a
* callback. The callback should be stored in p7dcx for us to get
* at it here, and would then work basically the same way (we call
* it to ask the question of whether this decryption is allowed).
* Callers of the Decoder who do not expect to do any decryption
* (e.g. those who "know" what they have is a signature, etc.)
* should pass in NULL for the callback, and if we find here that
* the callback is NULL we just assume the decryption is not allowed.
*/
if (SECMIME_DecryptionAllowed (&(enccinfo->contentEncAlg),
bulkkey) == PR_FALSE) {
p7dcx->error = SEC_ERROR_DECRYPTION_DISALLOWED;
goto no_decryption;
}
/*
* When decrypting a signedAndEnvelopedData, the signature also has
* to be decrypted with the bulk encryption key; to avoid having to
* get it all over again later (and do another potentially expensive
* RSA operation), copy it for later signature verification to use.
*/
if (copy_key_for_signature != NULL)
*copy_key_for_signature = PK11_ReferenceSymKey (bulkkey);
/*
* Now we have the bulk encryption key (in bulkkey) and the
* the algorithm (in enccinfo->contentEncAlg). Using those,
* create a decryption context.
*/
decryptobj = sec_PKCS7CreateDecryptObject (bulkkey,
&(enccinfo->contentEncAlg));
/*
* We are done with (this) bulkkey now.
*/
PK11_FreeSymKey (bulkkey);
if (decryptobj == NULL) {
p7dcx->error = PORT_GetError();
PORT_SetError(0);
goto no_decryption;
}
SEC_ASN1DecoderSetFilterProc (p7dcx->dcx,
sec_pkcs7_decoder_filter,
p7dcx,
p7dcx->cb != NULL);
p7dcx->worker.depth = depth;
p7dcx->worker.decryptobj = decryptobj;
return SECSuccess;
no_decryption:
if (privkey != NULL)
SECKEY_DestroyPrivateKey (privkey);
/*
* For some reason (error set already, if appropriate), we cannot
* decrypt the content. I am not sure what exactly is the right
* thing to do here; in some cases we want to just stop, and in
* others we want to let the decoding finish even though we cannot
* decrypt the content. My current thinking is that if the caller
* set up a content callback, then they are really interested in
* getting (decrypted) content, and if they cannot they will want
* to know about it. However, if no callback was specified, then
* maybe it is not important that the decryption failed.
*/
if (p7dcx->cb != NULL)
return SECFailure;
else
return SECSuccess; /* Let the decoding continue. */
}
static SECStatus
sec_pkcs7_decoder_finish_decrypt (SEC_PKCS7DecoderContext *p7dcx,
PRArenaPool *poolp,
SEC_PKCS7EncryptedContentInfo *enccinfo)
{
struct sec_pkcs7_decoder_worker *worker;
/*
* XXX Handling nested contents would mean that there is a chain
* of workers -- one per each level of content. The following
* would want to find the last worker in the chain.
*/
worker = &(p7dcx->worker);
/*
* If no decryption context, then we have nothing to do.
*/
if (worker->decryptobj == NULL)
return SECSuccess;
/*
* No matter what happens after this, we want to stop filtering.
* XXX If we handle nested contents, we only want to stop filtering
* if we are finishing off the *last* worker.
*/
SEC_ASN1DecoderClearFilterProc (p7dcx->dcx);
/*
* Handle the last block.
*/
sec_pkcs7_decoder_work_data (p7dcx, worker, NULL, 0, PR_TRUE);
/*
* All done, destroy it.
*/
sec_PKCS7DestroyDecryptObject (worker->decryptobj);
return SECSuccess;
}
static void
sec_pkcs7_decoder_notify (void *arg, PRBool before, void *dest, int depth)
{
SEC_PKCS7DecoderContext *p7dcx;
SEC_PKCS7ContentInfo *cinfo;
SEC_PKCS7SignedData *sigd;
SEC_PKCS7EnvelopedData *envd;
SEC_PKCS7SignedAndEnvelopedData *saed;
PRBool after;
SECStatus rv;
/*
* Just to make the code easier to read, create an "after" variable
* that is equivalent to "not before".
* (This used to be just the statement "after = !before", but that
* causes a warning on the mac; to avoid that, we do it the long way.)
*/
if (before)
after = PR_FALSE;
else
after = PR_TRUE;
p7dcx = arg;
cinfo = p7dcx->cinfo;
if (cinfo->contentTypeTag == NULL) {
if (after && dest == &(cinfo->contentType))
cinfo->contentTypeTag = SECOID_FindOID(&(cinfo->contentType));
return;
}
switch (cinfo->contentTypeTag->offset) {
case SEC_OID_PKCS7_SIGNED_DATA:
sigd = cinfo->content.signedData;
if (sigd == NULL)
break;
if (sigd->contentInfo.contentTypeTag == NULL) {
if (after && dest == &(sigd->contentInfo.contentType))
sigd->contentInfo.contentTypeTag =
SECOID_FindOID(&(sigd->contentInfo.contentType));
break;
}
/*
* We only set up a filtering digest if the content is
* plain DATA; anything else needs more work because a
* second pass is required to produce a DER encoding from
* an input that can be BER encoded. Whoever thought that
* was an acceptable constraint for PKCS7 was batty, but
* there you have it.
*
* XXX Also, since we stop here if this is not DATA, the
* inner content is not getting processed at all. Someday
* we may want to fix that.
*/
if (sigd->contentInfo.contentTypeTag->offset != SEC_OID_PKCS7_DATA) {
/* XXX Set an error in p7dcx->error */
SEC_ASN1DecoderClearNotifyProc (p7dcx->dcx);
break;
}
/*
* Just before the content, we want to set up a digest context
* for each digest algorithm listed, and start a filter which
* will run all of the contents bytes through that digest.
*/
if (before && dest == &(sigd->contentInfo.content)) {
rv = sec_pkcs7_decoder_start_digests (p7dcx, depth,
sigd->digestAlgorithms);
if (rv != SECSuccess)
SEC_ASN1DecoderClearNotifyProc (p7dcx->dcx);
break;
}
/*
* XXX To handle nested types, here is where we would want
* to check for inner boundaries that need handling.
*/
/*
* Are we done?
*/
if (after && dest == &(sigd->contentInfo.content)) {
/*
* Close out the digest contexts. We ignore any error
* because we are stopping anyway; the error status left
* behind in p7dcx will be seen by outer functions.
*/
(void) sec_pkcs7_decoder_finish_digests (p7dcx, cinfo->poolp,
&(sigd->digests));
/*
* XXX To handle nested contents, we would need to remove
* the worker from the chain (and free it).
*/
/*
* Stop notify.
*/
SEC_ASN1DecoderClearNotifyProc (p7dcx->dcx);
}
break;
case SEC_OID_PKCS7_ENVELOPED_DATA:
envd = cinfo->content.envelopedData;
if (envd == NULL)
break;
if (envd->encContentInfo.contentTypeTag == NULL) {
if (after && dest == &(envd->encContentInfo.contentType))
envd->encContentInfo.contentTypeTag =
SECOID_FindOID(&(envd->encContentInfo.contentType));
break;
}
/*
* Just before the content, we want to set up a decryption
* context, and start a filter which will run all of the
* contents bytes through it to determine the plain content.
*/
if (before && dest == &(envd->encContentInfo.encContent)) {
rv = sec_pkcs7_decoder_start_decrypt (p7dcx, depth,
envd->recipientInfos,
&(envd->encContentInfo),
NULL);
if (rv != SECSuccess)
SEC_ASN1DecoderClearNotifyProc (p7dcx->dcx);
break;
}
/*
* Are we done?
*/
if (after && dest == &(envd->encContentInfo.encContent)) {
/*
* Close out the decryption context. We ignore any error
* because we are stopping anyway; the error status left
* behind in p7dcx will be seen by outer functions.
*/
(void) sec_pkcs7_decoder_finish_decrypt (p7dcx, cinfo->poolp,
&(envd->encContentInfo));
/*
* XXX To handle nested contents, we would need to remove
* the worker from the chain (and free it).
*/
/*
* Stop notify.
*/
SEC_ASN1DecoderClearNotifyProc (p7dcx->dcx);
}
break;
case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
saed = cinfo->content.signedAndEnvelopedData;
if (saed == NULL)
break;
if (saed->encContentInfo.contentTypeTag == NULL) {
if (after && dest == &(saed->encContentInfo.contentType))
saed->encContentInfo.contentTypeTag =
SECOID_FindOID(&(saed->encContentInfo.contentType));
break;
}
/*
* Just before the content, we want to set up a decryption
* context *and* digest contexts, and start a filter which
* will run all of the contents bytes through both.
*/
if (before && dest == &(saed->encContentInfo.encContent)) {
rv = sec_pkcs7_decoder_start_decrypt (p7dcx, depth,
saed->recipientInfos,
&(saed->encContentInfo),
&(saed->sigKey));
if (rv == SECSuccess)
rv = sec_pkcs7_decoder_start_digests (p7dcx, depth,
saed->digestAlgorithms);
if (rv != SECSuccess)
SEC_ASN1DecoderClearNotifyProc (p7dcx->dcx);
break;
}
/*
* Are we done?
*/
if (after && dest == &(saed->encContentInfo.encContent)) {
/*
* Close out the decryption and digests contexts.
* We ignore any errors because we are stopping anyway;
* the error status left behind in p7dcx will be seen by
* outer functions.
*
* Note that the decrypt stuff must be called first;
* it may have a last buffer to do which in turn has
* to be added to the digest.
*/
(void) sec_pkcs7_decoder_finish_decrypt (p7dcx, cinfo->poolp,
&(saed->encContentInfo));
(void) sec_pkcs7_decoder_finish_digests (p7dcx, cinfo->poolp,
&(saed->digests));
/*
* XXX To handle nested contents, we would need to remove
* the worker from the chain (and free it).
*/
/*
* Stop notify.
*/
SEC_ASN1DecoderClearNotifyProc (p7dcx->dcx);
}
break;
case SEC_OID_PKCS7_DIGESTED_DATA:
/*
* XXX Want to do the digest or not? Maybe future enhancement...
*/
/* fallthru */
case SEC_OID_PKCS7_ENCRYPTED_DATA:
/*
* We can only treat this like plain data because we do not have
* the key (it is supposed to be supplied by other means).
*/
/* fallthru */
case SEC_OID_PKCS7_DATA:
default:
SEC_ASN1DecoderClearNotifyProc (p7dcx->dcx);
break;
}
}
SEC_PKCS7DecoderContext *
SEC_PKCS7DecoderStart(SEC_PKCS7DecoderContentCallback cb, void *cb_arg,
SECKEYGetPasswordKey pwfn, void *pwfn_arg)
{
SEC_PKCS7DecoderContext *p7dcx;
SEC_ASN1DecoderContext *dcx;
SEC_PKCS7ContentInfo *cinfo;
PRArenaPool *poolp;
poolp = PORT_NewArena (1024); /* XXX what is right value? */
if (poolp == NULL)
return NULL;
cinfo = PORT_ArenaZAlloc (poolp, sizeof(*cinfo));
if (cinfo == NULL) {
PORT_FreeArena (poolp, PR_FALSE);
return NULL;
}
cinfo->poolp = poolp;
cinfo->pwfn = pwfn;
cinfo->pwfn_arg = pwfn_arg;
cinfo->created = PR_FALSE;
cinfo->refCount = 1;
p7dcx = PORT_ZAlloc (sizeof(SEC_PKCS7DecoderContext));
if (p7dcx == NULL) {
PORT_FreeArena (poolp, PR_FALSE);
return NULL;
}
p7dcx->tmp_poolp = PORT_NewArena (1024); /* XXX what is right value? */
if (p7dcx->tmp_poolp == NULL) {
PORT_Free (p7dcx);
PORT_FreeArena (poolp, PR_FALSE);
return NULL;
}
dcx = SEC_ASN1DecoderStart (poolp, cinfo, sec_PKCS7ContentInfoTemplate);
if (dcx == NULL) {
PORT_FreeArena (p7dcx->tmp_poolp, PR_FALSE);
PORT_Free (p7dcx);
PORT_FreeArena (poolp, PR_FALSE);
return NULL;
}
SEC_ASN1DecoderSetNotifyProc (dcx, sec_pkcs7_decoder_notify, p7dcx);
p7dcx->dcx = dcx;
p7dcx->cinfo = cinfo;
p7dcx->cb = cb;
p7dcx->cb_arg = cb_arg;
p7dcx->pwfn = pwfn;
p7dcx->pwfn_arg = pwfn_arg;
return p7dcx;
}
/*
* Do the next chunk of PKCS7 decoding. If there is a problem, set
* an error and return a failure status. Note that in the case of
* an error, this routine is still prepared to be called again and
* again in case that is the easiest route for our caller to take.
* We simply detect it and do not do anything except keep setting
* that error in case our caller has not noticed it yet...
*/
SECStatus
SEC_PKCS7DecoderUpdate(SEC_PKCS7DecoderContext *p7dcx,
const char *buf, unsigned long len)
{
if (p7dcx->cinfo != NULL && p7dcx->dcx != NULL) {
PORT_Assert (p7dcx->error == 0);
if (p7dcx->error == 0) {
if (SEC_ASN1DecoderUpdate (p7dcx->dcx, buf, len) != SECSuccess) {
p7dcx->error = PORT_GetError();
PORT_Assert (p7dcx->error);
if (p7dcx->error == 0)
p7dcx->error = -1;
}
}
}
if (p7dcx->error) {
if (p7dcx->dcx != NULL) {
(void) SEC_ASN1DecoderFinish (p7dcx->dcx);
p7dcx->dcx = NULL;
}
if (p7dcx->cinfo != NULL) {
SEC_PKCS7DestroyContentInfo (p7dcx->cinfo);
p7dcx->cinfo = NULL;
}
PORT_SetError (p7dcx->error);
return SECFailure;
}
return SECSuccess;
}
SEC_PKCS7ContentInfo *
SEC_PKCS7DecoderFinish(SEC_PKCS7DecoderContext *p7dcx)
{
SEC_PKCS7ContentInfo *cinfo;
cinfo = p7dcx->cinfo;
if (p7dcx->dcx != NULL) {
if (SEC_ASN1DecoderFinish (p7dcx->dcx) != SECSuccess) {
SEC_PKCS7DestroyContentInfo (cinfo);
cinfo = NULL;
}
}
PORT_FreeArena (p7dcx->tmp_poolp, PR_FALSE);
PORT_Free (p7dcx);
return cinfo;
}
SEC_PKCS7ContentInfo *
SEC_PKCS7DecodeItem(SECItem *p7item,
SEC_PKCS7DecoderContentCallback cb, void *cb_arg,
SECKEYGetPasswordKey pwfn, void *pwfn_arg)
{
SEC_PKCS7DecoderContext *p7dcx;
p7dcx = SEC_PKCS7DecoderStart(cb, cb_arg, pwfn, pwfn_arg);
(void) SEC_PKCS7DecoderUpdate(p7dcx, (char *) p7item->data, p7item->len);
return SEC_PKCS7DecoderFinish(p7dcx);
}
/*
* If the thing contains any certs or crls return true; false otherwise.
*/
PRBool
SEC_PKCS7ContainsCertsOrCrls(SEC_PKCS7ContentInfo *cinfo)
{
SECOidTag kind;
SECItem **certs;
CERTSignedCrl **crls;
kind = SEC_PKCS7ContentType (cinfo);
switch (kind) {
default:
case SEC_OID_PKCS7_DATA:
case SEC_OID_PKCS7_DIGESTED_DATA:
case SEC_OID_PKCS7_ENVELOPED_DATA:
case SEC_OID_PKCS7_ENCRYPTED_DATA:
return PR_FALSE;
case SEC_OID_PKCS7_SIGNED_DATA:
certs = cinfo->content.signedData->rawCerts;
crls = cinfo->content.signedData->crls;
break;
case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
certs = cinfo->content.signedAndEnvelopedData->rawCerts;
crls = cinfo->content.signedAndEnvelopedData->crls;
break;
}
/*
* I know this could be collapsed, but I was in a mood to be explicit.
*/
if (certs != NULL && certs[0] != NULL)
return PR_TRUE;
else if (crls != NULL && crls[0] != NULL)
return PR_TRUE;
else
return PR_FALSE;
}
/* return the content length...could use GetContent, however we
* need the encrypted content length
*/
PRBool
SEC_PKCS7IsContentEmpty(SEC_PKCS7ContentInfo *cinfo, unsigned int minLen)
{
SECItem *item = NULL;
if(cinfo == NULL) {
return PR_TRUE;
}
switch(SEC_PKCS7ContentType(cinfo))
{
case SEC_OID_PKCS7_DATA:
item = cinfo->content.data;
break;
case SEC_OID_PKCS7_ENCRYPTED_DATA:
item = &cinfo->content.encryptedData->encContentInfo.encContent;
break;
default:
/* add other types */
return PR_FALSE;
}
if(!item) {
return PR_TRUE;
} else if(item->len <= minLen) {
return PR_TRUE;
}
return PR_FALSE;
}
PRBool
SEC_PKCS7ContentIsEncrypted(SEC_PKCS7ContentInfo *cinfo)
{
SECOidTag kind;
kind = SEC_PKCS7ContentType (cinfo);
switch (kind) {
default:
case SEC_OID_PKCS7_DATA:
case SEC_OID_PKCS7_DIGESTED_DATA:
case SEC_OID_PKCS7_SIGNED_DATA:
return PR_FALSE;
case SEC_OID_PKCS7_ENCRYPTED_DATA:
case SEC_OID_PKCS7_ENVELOPED_DATA:
case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
return PR_TRUE;
}
}
/*
* If the PKCS7 content has a signature (not just *could* have a signature)
* return true; false otherwise. This can/should be called before calling
* VerifySignature, which will always indicate failure if no signature is
* present, but that does not mean there even was a signature!
* Note that the content itself can be empty (detached content was sent
* another way); it is the presence of the signature that matters.
*/
PRBool
SEC_PKCS7ContentIsSigned(SEC_PKCS7ContentInfo *cinfo)
{
SECOidTag kind;
SEC_PKCS7SignerInfo **signerinfos;
kind = SEC_PKCS7ContentType (cinfo);
switch (kind) {
default:
case SEC_OID_PKCS7_DATA:
case SEC_OID_PKCS7_DIGESTED_DATA:
case SEC_OID_PKCS7_ENVELOPED_DATA:
case SEC_OID_PKCS7_ENCRYPTED_DATA:
return PR_FALSE;
case SEC_OID_PKCS7_SIGNED_DATA:
signerinfos = cinfo->content.signedData->signerInfos;
break;
case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
signerinfos = cinfo->content.signedAndEnvelopedData->signerInfos;
break;
}
/*
* I know this could be collapsed; but I kind of think it will get
* more complicated before I am finished, so...
*/
if (signerinfos != NULL && signerinfos[0] != NULL)
return PR_TRUE;
else
return PR_FALSE;
}
/*
* SEC_PKCS7ContentVerifySignature
* Look at a PKCS7 contentInfo and check if the signature is good.
* The digest was either calculated earlier (and is stored in the
* contentInfo itself) or is passed in via "detached_digest".
*
* The verification checks that the signing cert is valid and trusted
* for the purpose specified by "certusage".
*
* In addition, if "keepcerts" is true, add any new certificates found
* into our local database.
*
* XXX Each place which returns PR_FALSE should be sure to have a good
* error set for inspection by the caller. Alternatively, we could create
* an enumeration of success and each type of failure and return that
* instead of a boolean. For now, the default in a bad situation is to
* set the error to SEC_ERROR_PKCS7_BAD_SIGNATURE. But this should be
* reviewed; better (more specific) errors should be possible (to distinguish
* a signature failure from a badly-formed pkcs7 signedData, for example).
* Some of the errors should probably just be SEC_ERROR_BAD_SIGNATURE,
* but that has a lousy error string associated with it right now, which
* is too hard to change at this date, but it should be fixed eventually.
*
* XXX This is broken wrt signedAndEnvelopedData. In that case, the
* message digest is doubly encrypted -- first encrypted with the signer
* private key but then again encrypted with the bulk encryption key used
* to encrypt the content. So before we can pass the digest to VerifyDigest,
* we need to decrypt it with the bulk encryption key. Also, in this case,
* there should be NO authenticatedAttributes (signerinfo->authAttr should
* be NULL).
*/
static PRBool
sec_pkcs7_verify_signature(SEC_PKCS7ContentInfo *cinfo,
SECCertUsage certusage,
SECItem *detached_digest,
HASH_HashType digest_type,
PRBool keepcerts)
{
SECAlgorithmID **digestalgs, *bulkid;
SECItem *digest;
SECItem **digests;
SECItem **rawcerts;
CERTSignedCrl **crls;
SEC_PKCS7SignerInfo **signerinfos, *signerinfo;
CERTCertificate *cert, **certs;
PRBool goodsig;
CERTCertDBHandle local_certdb, *certdb, *defaultdb;
SECOidData *algiddata;
int i, certcount;
SECKEYPublicKey *publickey;
SECItem *content_type;
PK11SymKey *sigkey;
SECItem *utc_stime;
int64 stime;
SECStatus rv;
/*
* Everything needed in order to "goto done" safely.
*/
goodsig = PR_FALSE;
certcount = 0;
cert = NULL;
certs = NULL;
certdb = NULL;
defaultdb = CERT_GetDefaultCertDB();
publickey = NULL;
if (! SEC_PKCS7ContentIsSigned(cinfo)) {
PORT_SetError (SEC_ERROR_PKCS7_BAD_SIGNATURE);
goto done;
}
PORT_Assert (cinfo->contentTypeTag != NULL);
switch (cinfo->contentTypeTag->offset) {
default:
case SEC_OID_PKCS7_DATA:
case SEC_OID_PKCS7_DIGESTED_DATA:
case SEC_OID_PKCS7_ENVELOPED_DATA:
case SEC_OID_PKCS7_ENCRYPTED_DATA:
/* Could only get here if SEC_PKCS7ContentIsSigned is broken. */
PORT_Assert (0);
case SEC_OID_PKCS7_SIGNED_DATA:
{
SEC_PKCS7SignedData *sdp;
sdp = cinfo->content.signedData;
digestalgs = sdp->digestAlgorithms;
digests = sdp->digests;
rawcerts = sdp->rawCerts;
crls = sdp->crls;
signerinfos = sdp->signerInfos;
content_type = &(sdp->contentInfo.contentType);
sigkey = NULL;
bulkid = NULL;
}
break;
case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
{
SEC_PKCS7SignedAndEnvelopedData *saedp;
saedp = cinfo->content.signedAndEnvelopedData;
digestalgs = saedp->digestAlgorithms;
digests = saedp->digests;
rawcerts = saedp->rawCerts;
crls = saedp->crls;
signerinfos = saedp->signerInfos;
content_type = &(saedp->encContentInfo.contentType);
sigkey = saedp->sigKey;
bulkid = &(saedp->encContentInfo.contentEncAlg);
}
break;
}
if ((signerinfos == NULL) || (signerinfos[0] == NULL)) {
PORT_SetError (SEC_ERROR_PKCS7_BAD_SIGNATURE);
goto done;
}
/*
* XXX Need to handle multiple signatures; checking them is easy,
* but what the hell are the semantics here (like, return value)?
*/
if (signerinfos[1] != NULL) {
PORT_SetError (SEC_ERROR_PKCS7_BAD_SIGNATURE);
goto done;
}
signerinfo = signerinfos[0];
/*
* XXX I would like to just pass the issuerAndSN, along with the rawcerts
* and crls, to some function that did all of this certificate stuff
* (open/close the database if necessary, verifying the certs, etc.)
* and gave me back a cert pointer if all was good.
*/
certdb = defaultdb;
if (certdb == NULL) {
if (CERT_OpenCertDBFilename (&local_certdb, NULL,
!keepcerts) != SECSuccess)
goto done;
certdb = &local_certdb;
}
certcount = 0;
if (rawcerts != NULL) {
for (; rawcerts[certcount] != NULL; certcount++) {
/* just counting */
}
}
/*
* Note that the result of this is that each cert in "certs"
* needs to be destroyed.
*/
rv = CERT_ImportCerts(certdb, certusage, certcount, rawcerts, &certs,
keepcerts, PR_FALSE, NULL);
if ( rv != SECSuccess ) {
goto done;
}
/*
* This cert will also need to be freed, but since we save it
* in signerinfo for later, we do not want to destroy it when
* we leave this function -- we let the clean-up of the entire
* cinfo structure later do the destroy of this cert.
*/
cert = CERT_FindCertByIssuerAndSN(certdb, signerinfo->issuerAndSN);
if (cert == NULL) {
goto done;
}
signerinfo->cert = cert;
/*
* Get and convert the signing time; if available, it will be used
* both on the cert verification and for importing the sender
* email profile.
*/
utc_stime = SEC_PKCS7GetSigningTime (cinfo);
if (utc_stime != NULL) {
if (DER_UTCTimeToTime (&stime, utc_stime) != SECSuccess)
utc_stime = NULL; /* conversion failed, so pretend none */
}
/*
* XXX This uses the signing time, if available. Additionally, we
* might want to, if there is no signing time, get the message time
* from the mail header itself, and use that. That would require
* a change to our interface though, and for S/MIME callers to pass
* in a time (and for non-S/MIME callers to pass in nothing, or
* maybe make them pass in the current time, always?).
*/
if (CERT_VerifyCert (certdb, cert, PR_TRUE, certusage,
utc_stime != NULL ? stime : PR_Now(),
cinfo->pwfn_arg, NULL) != SECSuccess) {
/*
* XXX Give the user an option to check the signature anyway?
* If we want to do this, need to give a way to leave and display
* some dialog and get the answer and come back through (or do
* the rest of what we do below elsewhere, maybe by putting it
* in a function that we call below and could call from a dialog
* finish handler).
*/
goto savecert;
}
publickey = CERT_ExtractPublicKey (&cert->subjectPublicKeyInfo);
if (publickey == NULL)
goto done;
/*
* XXX No! If digests is empty, see if we can create it now by
* digesting the contents. This is necessary if we want to allow
* somebody to do a simple decode (without filtering, etc.) and
* then later call us here to do the verification.
* OR, we can just specify that the interface to this routine
* *requires* that the digest(s) be done before calling and either
* stashed in the struct itself or passed in explicitly (as would
* be done for detached contents).
*/
if ((digests == NULL || digests[0] == NULL)
&& (detached_digest == NULL || detached_digest->data == NULL))
goto done;
/*
* Find or confirm digest algorithm.
*/
algiddata = SECOID_FindOID (&(signerinfo->digestAlg.algorithm));
if (detached_digest != NULL) {
switch (digest_type) {
default:
case HASH_AlgNULL:
PORT_SetError (SEC_ERROR_PKCS7_BAD_SIGNATURE);
goto done;
case HASH_AlgMD2:
PORT_Assert (detached_digest->len == MD2_LENGTH);
if (algiddata->offset != SEC_OID_MD2) {
PORT_SetError (SEC_ERROR_PKCS7_BAD_SIGNATURE);
goto done;
}
break;
case HASH_AlgMD5:
PORT_Assert (detached_digest->len == MD5_LENGTH);
if (algiddata->offset != SEC_OID_MD5) {
PORT_SetError (SEC_ERROR_PKCS7_BAD_SIGNATURE);
goto done;
}
break;
case HASH_AlgSHA1:
PORT_Assert (detached_digest->len == SHA1_LENGTH);
if (algiddata->offset != SEC_OID_SHA1) {
PORT_SetError (SEC_ERROR_PKCS7_BAD_SIGNATURE);
goto done;
}
break;
}
digest = detached_digest;
} else {
PORT_Assert (digestalgs != NULL && digestalgs[0] != NULL);
if (digestalgs == NULL || digestalgs[0] == NULL) {
PORT_SetError (SEC_ERROR_PKCS7_BAD_SIGNATURE);
goto done;
}
/*
* pick digest matching signerinfo->digestAlg from digests
*/
if (algiddata == NULL) {
PORT_SetError (SEC_ERROR_PKCS7_BAD_SIGNATURE);
goto done;
}
for (i = 0; digestalgs[i] != NULL; i++) {
if (SECOID_FindOID (&(digestalgs[i]->algorithm)) == algiddata)
break;
}
if (digestalgs[i] == NULL) {
PORT_SetError (SEC_ERROR_PKCS7_BAD_SIGNATURE);
goto done;
}
digest = digests[i];
}
/*
* XXX The only signature algorithm we (currently) support is RSA,
* and this algorithm is what is assumed by VFY_Verify{Data,Digest}.
*/
algiddata = SECOID_FindOID (&(signerinfo->digestEncAlg.algorithm));
if (algiddata == NULL ||
((algiddata->offset != SEC_OID_PKCS1_RSA_ENCRYPTION) &&
(algiddata->offset != SEC_OID_ANSIX9_DSA_SIGNATURE))) {
PORT_SetError (SEC_ERROR_PKCS7_BAD_SIGNATURE);
goto done;
}
if (signerinfo->authAttr != NULL) {
SEC_PKCS7Attribute *attr;
SECItem *value;
SECItem encoded_attrs;
/*
* We have a sigkey only for signedAndEnvelopedData, which is
* not supposed to have any authenticated attributes.
*/
if (sigkey != NULL) {
PORT_SetError (SEC_ERROR_PKCS7_BAD_SIGNATURE);
goto done;
}
/*
* PKCS #7 says that if there are any authenticated attributes,
* then there must be one for content type which matches the
* content type of the content being signed, and there must
* be one for message digest which matches our message digest.
* So check these things first.
* XXX Might be nice to have a compare-attribute-value function
* which could collapse the following nicely.
*/
attr = sec_PKCS7FindAttribute (signerinfo->authAttr,
SEC_OID_PKCS9_CONTENT_TYPE, PR_TRUE);
value = sec_PKCS7AttributeValue (attr);
if (value == NULL || value->len != content_type->len) {
PORT_SetError (SEC_ERROR_PKCS7_BAD_SIGNATURE);
goto done;
}
if (PORT_Memcmp (value->data, content_type->data, value->len) != 0) {
PORT_SetError (SEC_ERROR_PKCS7_BAD_SIGNATURE);
goto done;
}
attr = sec_PKCS7FindAttribute (signerinfo->authAttr,
SEC_OID_PKCS9_MESSAGE_DIGEST, PR_TRUE);
value = sec_PKCS7AttributeValue (attr);
if (value == NULL || value->len != digest->len) {
PORT_SetError (SEC_ERROR_PKCS7_BAD_SIGNATURE);
goto done;
}
if (PORT_Memcmp (value->data, digest->data, value->len) != 0) {
PORT_SetError (SEC_ERROR_PKCS7_BAD_SIGNATURE);
goto done;
}
/*
* Okay, we met the constraints of the basic attributes.
* Now check the signature, which is based on a digest of
* the DER-encoded authenticated attributes. So, first we
* encode and then we digest/verify.
*/
encoded_attrs.data = NULL;
encoded_attrs.len = 0;
if (sec_PKCS7EncodeAttributes (NULL, &encoded_attrs,
&(signerinfo->authAttr)) == NULL)
goto done;
if (encoded_attrs.data == NULL || encoded_attrs.len == 0) {
PORT_SetError (SEC_ERROR_PKCS7_BAD_SIGNATURE);
goto done;
}
goodsig = (VFY_VerifyData (encoded_attrs.data, encoded_attrs.len,
publickey, &(signerinfo->encDigest),
cinfo->pwfn_arg) == SECSuccess);
PORT_Free (encoded_attrs.data);
} else {
SECItem *sig;
SECItem holder;
SECStatus rv;
/*
* No authenticated attributes.
* The signature is based on the plain message digest.
*/
sig = &(signerinfo->encDigest);
if (sig->len == 0) { /* bad signature */
PORT_SetError (SEC_ERROR_PKCS7_BAD_SIGNATURE);
goto done;
}
if (sigkey != NULL) {
sec_PKCS7CipherObject *decryptobj;
unsigned int buflen;
/*
* For signedAndEnvelopedData, we first must decrypt the encrypted
* digest with the bulk encryption key. The result is the normal
* encrypted digest (aka the signature).
*/
decryptobj = sec_PKCS7CreateDecryptObject (sigkey, bulkid);
if (decryptobj == NULL)
goto done;
buflen = sec_PKCS7DecryptLength (decryptobj, sig->len, PR_TRUE);
PORT_Assert (buflen);
if (buflen == 0) { /* something is wrong */
sec_PKCS7DestroyDecryptObject (decryptobj);
goto done;
}
holder.data = PORT_Alloc (buflen);
if (holder.data == NULL) {
sec_PKCS7DestroyDecryptObject (decryptobj);
goto done;
}
rv = sec_PKCS7Decrypt (decryptobj, holder.data, &holder.len, buflen,
sig->data, sig->len, PR_TRUE);
if (rv != SECSuccess) {
sec_PKCS7DestroyDecryptObject (decryptobj);
goto done;
}
sig = &holder;
}
goodsig = (VFY_VerifyDigest (digest, publickey, sig, cinfo->pwfn_arg)
== SECSuccess);
if (sigkey != NULL) {
PORT_Assert (sig == &holder);
PORT_ZFree (holder.data, holder.len);
}
}
if (! goodsig) {
/*
* XXX Change the generic error into our specific one, because
* in that case we get a better explanation out of the Security
* Advisor. This is really a bug in our error strings (the
* "generic" error has a lousy/wrong message associated with it
* which assumes the signature verification was done for the
* purposes of checking the issuer signature on a certificate)
* but this is at least an easy workaround and/or in the
* Security Advisor, which specifically checks for the error
* SEC_ERROR_PKCS7_BAD_SIGNATURE and gives more explanation
* in that case but does not similarly check for
* SEC_ERROR_BAD_SIGNATURE. It probably should, but then would
* probably say the wrong thing in the case that it *was* the
* certificate signature check that failed during the cert
* verification done above. Our error handling is really a mess.
*/
if (PORT_GetError() == SEC_ERROR_BAD_SIGNATURE)
PORT_SetError (SEC_ERROR_PKCS7_BAD_SIGNATURE);
}
savecert:
/*
* Only save the smime profile if we are checking an email message and
* the cert has an email address in it.
*/
if ( ( cert->emailAddr != NULL ) &&
( ( certusage == certUsageEmailSigner ) ||
( certusage == certUsageEmailRecipient ) ) ) {
SECItem *profile = NULL;
int save_error;
/*
* Remember the current error set because we do not care about
* anything set by the functions we are about to call.
*/
save_error = PORT_GetError();
if (goodsig && (signerinfo->authAttr != NULL)) {
/*
* If the signature is good, then we can save the S/MIME profile,
* if we have one.
*/
SEC_PKCS7Attribute *attr;
attr = sec_PKCS7FindAttribute (signerinfo->authAttr,
SEC_OID_PKCS9_SMIME_CAPABILITIES,
PR_TRUE);
profile = sec_PKCS7AttributeValue (attr);
}
rv = CERT_SaveSMimeProfile (cert, profile, utc_stime);
/*
* Restore the saved error in case the calls above set a new
* one that we do not actually care about.
*/
PORT_SetError (save_error);
/*
* XXX Failure is not indicated anywhere -- the signature
* verification itself is unaffected by whether or not the
* profile was successfully saved.
*/
}
done:
/*
* See comment above about why we do not want to destroy cert
* itself here.
*/
if (certs != NULL)
CERT_DestroyCertArray (certs, certcount);
if (defaultdb == NULL && certdb != NULL)
CERT_ClosePermCertDB (certdb);
if (publickey != NULL)
SECKEY_DestroyPublicKey (publickey);
return goodsig;
}
/*
* SEC_PKCS7VerifySignature
* Look at a PKCS7 contentInfo and check if the signature is good.
* The verification checks that the signing cert is valid and trusted
* for the purpose specified by "certusage".
*
* In addition, if "keepcerts" is true, add any new certificates found
* into our local database.
*/
PRBool
SEC_PKCS7VerifySignature(SEC_PKCS7ContentInfo *cinfo,
SECCertUsage certusage,
PRBool keepcerts)
{
return sec_pkcs7_verify_signature (cinfo, certusage,
NULL, HASH_AlgNULL, keepcerts);
}
/*
* SEC_PKCS7VerifyDetachedSignature
* Look at a PKCS7 contentInfo and check if the signature matches
* a passed-in digest (calculated, supposedly, from detached contents).
* The verification checks that the signing cert is valid and trusted
* for the purpose specified by "certusage".
*
* In addition, if "keepcerts" is true, add any new certificates found
* into our local database.
*/
PRBool
SEC_PKCS7VerifyDetachedSignature(SEC_PKCS7ContentInfo *cinfo,
SECCertUsage certusage,
SECItem *detached_digest,
HASH_HashType digest_type,
PRBool keepcerts)
{
return sec_pkcs7_verify_signature (cinfo, certusage,
detached_digest, digest_type,
keepcerts);
}
/*
* Return the asked-for portion of the name of the signer of a PKCS7
* signed object.
*
* Returns a pointer to allocated memory, which must be freed.
* A NULL return value is an error.
*/
#define sec_common_name 1
#define sec_email_address 2
static char *
sec_pkcs7_get_signer_cert_info(SEC_PKCS7ContentInfo *cinfo, int selector)
{
SECOidTag kind;
SEC_PKCS7SignerInfo **signerinfos;
CERTCertificate *signercert;
char *container;
kind = SEC_PKCS7ContentType (cinfo);
switch (kind) {
default:
case SEC_OID_PKCS7_DATA:
case SEC_OID_PKCS7_DIGESTED_DATA:
case SEC_OID_PKCS7_ENVELOPED_DATA:
case SEC_OID_PKCS7_ENCRYPTED_DATA:
PORT_Assert (0);
return NULL;
case SEC_OID_PKCS7_SIGNED_DATA:
{
SEC_PKCS7SignedData *sdp;
sdp = cinfo->content.signedData;
signerinfos = sdp->signerInfos;
}
break;
case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
{
SEC_PKCS7SignedAndEnvelopedData *saedp;
saedp = cinfo->content.signedAndEnvelopedData;
signerinfos = saedp->signerInfos;
}
break;
}
if (signerinfos == NULL || signerinfos[0] == NULL)
return NULL;
signercert = signerinfos[0]->cert;
/*
* No cert there; see if we can find one by calling verify ourselves.
*/
if (signercert == NULL) {
/*
* The cert usage does not matter in this case, because we do not
* actually care about the verification itself, but we have to pick
* some valid usage to pass in.
*/
(void) sec_pkcs7_verify_signature (cinfo, certUsageEmailSigner,
NULL, HASH_AlgNULL, PR_FALSE);
signercert = signerinfos[0]->cert;
if (signercert == NULL)
return NULL;
}
switch (selector) {
case sec_common_name:
container = CERT_GetCommonName (&signercert->subject);
break;
case sec_email_address:
container = CERT_GetCertEmailAddress (&signercert->subject);
break;
default:
PORT_Assert (0);
container = NULL;
break;
}
return container;
}
char *
SEC_PKCS7GetSignerCommonName(SEC_PKCS7ContentInfo *cinfo)
{
return sec_pkcs7_get_signer_cert_info(cinfo, sec_common_name);
}
char *
SEC_PKCS7GetSignerEmailAddress(SEC_PKCS7ContentInfo *cinfo)
{
return sec_pkcs7_get_signer_cert_info(cinfo, sec_email_address);
}
/*
* Return the the signing time, in UTCTime format, of a PKCS7 contentInfo.
*/
SECItem *
SEC_PKCS7GetSigningTime(SEC_PKCS7ContentInfo *cinfo)
{
SEC_PKCS7SignerInfo **signerinfos;
SEC_PKCS7Attribute *attr;
if (SEC_PKCS7ContentType (cinfo) != SEC_OID_PKCS7_SIGNED_DATA)
return NULL;
signerinfos = cinfo->content.signedData->signerInfos;
/*
* No signature, or more than one, means no deal.
*/
if (signerinfos == NULL || signerinfos[0] == NULL || signerinfos[1] != NULL)
return NULL;
attr = sec_PKCS7FindAttribute (signerinfos[0]->authAttr,
SEC_OID_PKCS9_SIGNING_TIME, PR_TRUE);
return sec_PKCS7AttributeValue (attr);
}