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 decoding, verification.
*/
#include "p7local.h"
#include "cert.h"
/* XXX do not want to have to include */
#include "certdb.h" /* certdb.h -- the trust stuff needed by */
/* the add certificate code needs to get */
/* rewritten/abstracted and then this */
/* include should be removed! */
/*#include "cdbhdl.h" */
#include "cryptohi.h"
#include "keyhi.h"
#include "secasn1.h"
#include "secitem.h"
#include "secoid.h"
#include "pk11func.h"
#include "prtime.h"
#include "secerr.h"
#include "sechash.h" /* for HASH_GetHashObject() */
#include "secder.h"
#include "secpkcs5.h"
struct sec_pkcs7_decoder_worker {
int depth;
int digcnt;
void **digcxs;
const 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;
PLArenaPool *tmp_poolp;
int error;
SEC_PKCS7GetDecryptKeyCallback dkcb;
void *dkcb_arg;
SEC_PKCS7DecryptionAllowedCallback decrypt_allowed_cb;
};
/*
* 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.
*
* XXX 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; /* XXX 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 = (unsigned char *)PORT_ArenaAlloc(p7dcx->cinfo->poolp,
buflen);
} else {
buf = (unsigned char *)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; /* XXX indicate error? */
}
rv = sec_PKCS7Decrypt(worker->decryptobj, buf, &outlen, buflen,
data, inlen, final);
if (rv != SECSuccess) {
p7dcx->error = PORT_GetError();
return; /* XXX 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 = (SEC_PKCS7DecoderContext *)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)
{
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.
* Just act as if there were no algorithms specified.
*/
if (digcnt == 0)
return SECSuccess;
p7dcx->worker.digcxs = (void **)PORT_ArenaAlloc(p7dcx->tmp_poolp,
digcnt * sizeof(void *));
p7dcx->worker.digobjs = (const SECHashObject **)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++) {
SECAlgorithmID *algid = digestalgs[i];
SECOidTag oidTag = SECOID_FindOIDTag(&(algid->algorithm));
const SECHashObject *digobj = HASH_GetHashObjectByOidTag(oidTag);
void *digcx;
/*
* 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,
(PRBool)(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,
PLArenaPool *poolp,
SECItem ***digestsp)
{
struct sec_pkcs7_decoder_worker *worker;
const 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 =
(SECItem **)PORT_ArenaAlloc(poolp, (worker->digcnt + 1) * sizeof(SECItem *));
digest = (SECItem *)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 = (unsigned char *)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;
PORT_ArenaUnmark(poolp, mark);
return SECSuccess;
}
/*
* XXX Need comment explaining following helper function (which is used
* by sec_pkcs7_decoder_start_decrypt).
*/
static PK11SymKey *
sec_pkcs7_decoder_get_recipient_key(SEC_PKCS7DecoderContext *p7dcx,
SEC_PKCS7RecipientInfo **recipientinfos,
SEC_PKCS7EncryptedContentInfo *enccinfo)
{
SEC_PKCS7RecipientInfo *ri;
CERTCertificate *cert = NULL;
SECKEYPrivateKey *privkey = NULL;
PK11SymKey *bulkkey = NULL;
SECOidTag keyalgtag, bulkalgtag, encalgtag;
PK11SlotInfo *slot = NULL;
if (recipientinfos == NULL || recipientinfos[0] == NULL) {
p7dcx->error = SEC_ERROR_NOT_A_RECIPIENT;
goto no_key_found;
}
cert = PK11_FindCertAndKeyByRecipientList(&slot, recipientinfos, &ri,
&privkey, p7dcx->pwfn_arg);
if (cert == NULL) {
p7dcx->error = SEC_ERROR_NOT_A_RECIPIENT;
goto no_key_found;
}
ri->cert = cert; /* so we can find it later */
PORT_Assert(privkey != NULL);
keyalgtag = SECOID_GetAlgorithmTag(&(cert->subjectPublicKeyInfo.algorithm));
encalgtag = SECOID_GetAlgorithmTag(&(ri->keyEncAlg));
if (keyalgtag != encalgtag) {
p7dcx->error = SEC_ERROR_PKCS7_KEYALG_MISMATCH;
goto no_key_found;
}
bulkalgtag = SECOID_GetAlgorithmTag(&(enccinfo->contentEncAlg));
switch (encalgtag) {
case SEC_OID_PKCS1_RSA_ENCRYPTION:
bulkkey = PK11_PubUnwrapSymKey(privkey, &ri->encKey,
PK11_AlgtagToMechanism(bulkalgtag),
CKA_DECRYPT, 0);
if (bulkkey == NULL) {
p7dcx->error = PORT_GetError();
PORT_SetError(0);
goto no_key_found;
}
break;
default:
p7dcx->error = SEC_ERROR_UNSUPPORTED_KEYALG;
break;
}
no_key_found:
if (privkey != NULL)
SECKEY_DestroyPrivateKey(privkey);
if (slot != NULL)
PK11_FreeSlot(slot);
return bulkkey;
}
/*
* XXX The following comment is old -- the function used to only handle
* EnvelopedData or SignedAndEnvelopedData but now handles EncryptedData
* as well (and it had all of the code of the helper function above
* built into it), though the comment was left as is. Fix it...
*
* 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)
{
PK11SymKey *bulkkey = NULL;
sec_PKCS7CipherObject *decryptobj;
/*
* If a callback is supplied to retrieve the encryption key,
* for instance, for Encrypted Content infos, then retrieve
* the bulkkey from the callback. Otherwise, assume that
* we are processing Enveloped or SignedAndEnveloped data
* content infos.
*
* XXX Put an assert here?
*/
if (SEC_PKCS7ContentType(p7dcx->cinfo) == SEC_OID_PKCS7_ENCRYPTED_DATA) {
if (p7dcx->dkcb != NULL) {
bulkkey = (*p7dcx->dkcb)(p7dcx->dkcb_arg,
&(enccinfo->contentEncAlg));
}
enccinfo->keysize = 0;
} else {
bulkkey = sec_pkcs7_decoder_get_recipient_key(p7dcx, recipientinfos,
enccinfo);
if (bulkkey == NULL)
goto no_decryption;
enccinfo->keysize = PK11_GetKeyStrength(bulkkey,
&(enccinfo->contentEncAlg));
}
/*
* XXX I think following should set error in p7dcx and clear set error
* (as used to be done here, or as is done in get_receipient_key above.
*/
if (bulkkey == NULL) {
goto no_decryption;
}
/*
* We want to make sure decryption is allowed. This is done via
* a callback specified in SEC_PKCS7DecoderStart().
*/
if (p7dcx->decrypt_allowed_cb) {
if ((*p7dcx->decrypt_allowed_cb)(&(enccinfo->contentEncAlg),
bulkkey) == PR_FALSE) {
p7dcx->error = SEC_ERROR_DECRYPTION_DISALLOWED;
goto no_decryption;
}
} else {
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,
(PRBool)(p7dcx->cb != NULL));
p7dcx->worker.depth = depth;
p7dcx->worker.decryptobj = decryptobj;
return SECSuccess;
no_decryption:
PK11_FreeSymKey(bulkkey);
/*
* 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,
PLArenaPool *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);
worker->decryptobj = NULL;
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;
SEC_PKCS7EncryptedData *encd;
SEC_PKCS7DigestedData *digd;
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 = (SEC_PKCS7DecoderContext *)arg;
if (!p7dcx) {
return;
}
cinfo = p7dcx->cinfo;
if (!cinfo) {
return;
}
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. (This is a requirement
* of PKCS7 that is unfortunate, 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:
digd = cinfo->content.digestedData;
/*
* XXX Want to do the digest or not? Maybe future enhancement...
*/
if (before && dest == &(digd->contentInfo.content.data)) {
SEC_ASN1DecoderSetFilterProc(p7dcx->dcx, sec_pkcs7_decoder_filter,
p7dcx,
(PRBool)(p7dcx->cb != NULL));
break;
}
/*
* Are we done?
*/
if (after && dest == &(digd->contentInfo.content.data)) {
SEC_ASN1DecoderClearFilterProc(p7dcx->dcx);
}
break;
case SEC_OID_PKCS7_ENCRYPTED_DATA:
encd = cinfo->content.encryptedData;
if (!encd) {
break;
}
/*
* XXX If the decryption key callback is set, we want to start
* the decryption. If the callback is not set, we will treat the
* content as plain data, since we do not have the key.
*
* Is this the proper thing to do?
*/
if (before && dest == &(encd->encContentInfo.encContent)) {
/*
* Start the encryption process if the decryption key callback
* is present. Otherwise, treat the content like plain data.
*/
rv = SECSuccess;
if (p7dcx->dkcb != NULL) {
rv = sec_pkcs7_decoder_start_decrypt(p7dcx, depth, NULL,
&(encd->encContentInfo),
NULL);
}
if (rv != SECSuccess)
SEC_ASN1DecoderClearNotifyProc(p7dcx->dcx);
break;
}
/*
* Are we done?
*/
if (after && dest == &(encd->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,
&(encd->encContentInfo));
/*
* Stop notify.
*/
SEC_ASN1DecoderClearNotifyProc(p7dcx->dcx);
}
break;
case SEC_OID_PKCS7_DATA:
/*
* If a output callback has been specified, we want to set the filter
* to call the callback. This is taken care of in
* sec_pkcs7_decoder_start_decrypt() or
* sec_pkcs7_decoder_start_digests() for the other content types.
*/
if (before && dest == &(cinfo->content.data)) {
/*
* Set the filter proc up.
*/
SEC_ASN1DecoderSetFilterProc(p7dcx->dcx,
sec_pkcs7_decoder_filter,
p7dcx,
(PRBool)(p7dcx->cb != NULL));
break;
}
if (after && dest == &(cinfo->content.data)) {
/*
* Time to clean up after ourself, stop the Notify and Filter
* procedures.
*/
SEC_ASN1DecoderClearNotifyProc(p7dcx->dcx);
SEC_ASN1DecoderClearFilterProc(p7dcx->dcx);
}
break;
default:
SEC_ASN1DecoderClearNotifyProc(p7dcx->dcx);
break;
}
}
SEC_PKCS7DecoderContext *
SEC_PKCS7DecoderStart(SEC_PKCS7DecoderContentCallback cb, void *cb_arg,
SECKEYGetPasswordKey pwfn, void *pwfn_arg,
SEC_PKCS7GetDecryptKeyCallback decrypt_key_cb,
void *decrypt_key_cb_arg,
SEC_PKCS7DecryptionAllowedCallback decrypt_allowed_cb)
{
SEC_PKCS7DecoderContext *p7dcx;
SEC_ASN1DecoderContext *dcx;
SEC_PKCS7ContentInfo *cinfo;
PLArenaPool *poolp;
poolp = PORT_NewArena(1024); /* XXX what is right value? */
if (poolp == NULL)
return NULL;
cinfo = (SEC_PKCS7ContentInfo *)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 =
(SEC_PKCS7DecoderContext *)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;
p7dcx->dkcb = decrypt_key_cb;
p7dcx->dkcb_arg = decrypt_key_cb_arg;
p7dcx->decrypt_allowed_cb = decrypt_allowed_cb;
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) {
PORT_SetError(SEC_ERROR_INVALID_ARGS);
return SECFailure;
}
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;
}
}
/* free any NSS data structures */
if (p7dcx->worker.decryptobj) {
sec_PKCS7DestroyDecryptObject(p7dcx->worker.decryptobj);
}
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_PKCS7GetDecryptKeyCallback decrypt_key_cb,
void *decrypt_key_cb_arg,
SEC_PKCS7DecryptionAllowedCallback decrypt_allowed_cb)
{
SEC_PKCS7DecoderContext *p7dcx;
p7dcx = SEC_PKCS7DecoderStart(cb, cb_arg, pwfn, pwfn_arg, decrypt_key_cb,
decrypt_key_cb_arg, decrypt_allowed_cb);
if (!p7dcx) {
/* error code is set */
return NULL;
}
(void)SEC_PKCS7DecoderUpdate(p7dcx, (char *)p7item->data, p7item->len);
return SEC_PKCS7DecoderFinish(p7dcx);
}
/*
* Abort the ASN.1 stream. Used by pkcs 12
*/
void
SEC_PKCS7DecoderAbort(SEC_PKCS7DecoderContext *p7dcx, int error)
{
PORT_Assert(p7dcx);
SEC_ASN1DecoderAbort(p7dcx->dcx, error);
}
/*
* 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_pkcs7_verify_signature
*
* 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" at
* - "*atTime" if "atTime" is not null, or
* - the signing time if the signing time is available in "cinfo", or
* - the current time (as returned by PR_Now).
*
* 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 less helpful error string associated with it right now;
* if/when that changes, review and change these as needed.
*
* 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,
const SECItem *detached_digest,
HASH_HashType digest_type,
PRBool keepcerts,
const PRTime *atTime)
{
SECAlgorithmID **digestalgs, *bulkid;
const SECItem *digest;
SECItem **digests;
SECItem **rawcerts;
SEC_PKCS7SignerInfo **signerinfos, *signerinfo;
CERTCertificate *cert, **certs;
PRBool goodsig;
CERTCertDBHandle *certdb, *defaultdb;
SECOidTag encTag, digestTag;
HASH_HashType found_type;
int i, certcount;
SECKEYPublicKey *publickey;
SECItem *content_type;
PK11SymKey *sigkey;
SECItem *encoded_stime;
PRTime stime;
PRTime verificationTime;
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;
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;
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 should be 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) {
goto done;
}
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.
*/
encoded_stime = SEC_PKCS7GetSigningTime(cinfo);
if (encoded_stime != NULL) {
if (DER_DecodeTimeChoice(&stime, encoded_stime) != SECSuccess)
encoded_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 (atTime) {
verificationTime = *atTime;
} else if (encoded_stime != NULL) {
verificationTime = stime;
} else {
verificationTime = PR_Now();
}
if (CERT_VerifyCert(certdb, cert, PR_TRUE, certusage, verificationTime,
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);
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 and confirm digest algorithm.
*/
digestTag = SECOID_FindOIDTag(&(signerinfo->digestAlg.algorithm));
/* make sure we understand the digest type first */
found_type = HASH_GetHashTypeByOidTag(digestTag);
if ((digestTag == SEC_OID_UNKNOWN) || (found_type == HASH_AlgNULL)) {
PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE);
goto done;
}
if (detached_digest != NULL) {
unsigned int hashLen = HASH_ResultLen(found_type);
if (digest_type != found_type ||
detached_digest->len != hashLen) {
PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE);
goto done;
}
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
*/
for (i = 0; digestalgs[i] != NULL; i++) {
if (SECOID_FindOIDTag(&(digestalgs[i]->algorithm)) == digestTag)
break;
}
if (digestalgs[i] == NULL) {
PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE);
goto done;
}
digest = digests[i];
}
encTag = SECOID_FindOIDTag(&(signerinfo->digestEncAlg.algorithm));
if (encTag == SEC_OID_UNKNOWN) {
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 = (PRBool)(VFY_VerifyDataDirect(encoded_attrs.data,
encoded_attrs.len,
publickey, &(signerinfo->encDigest),
encTag, digestTag, NULL,
cinfo->pwfn_arg) == SECSuccess);
PORT_Free(encoded_attrs.data);
} else {
SECItem *sig;
SECItem holder;
/*
* 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 = (unsigned char *)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);
sec_PKCS7DestroyDecryptObject(decryptobj);
if (rv != SECSuccess) {
goto done;
}
sig = &holder;
}
goodsig = (PRBool)(VFY_VerifyDigestDirect(digest, publickey, sig,
encTag, digestTag, 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 && cert->emailAddr[0] &&
((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, encoded_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 (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, NULL);
}
/*
* 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,
const SECItem *detached_digest,
HASH_HashType digest_type,
PRBool keepcerts)
{
return sec_pkcs7_verify_signature(cinfo, certusage,
detached_digest, digest_type,
keepcerts, NULL);
}
/*
* SEC_PKCS7VerifyDetachedSignatureAtTime
* 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" at time "atTime".
*
* In addition, if "keepcerts" is true, add any new certificates found
* into our local database.
*/
PRBool
SEC_PKCS7VerifyDetachedSignatureAtTime(SEC_PKCS7ContentInfo *cinfo,
SECCertUsage certusage,
const SECItem *detached_digest,
HASH_HashType digest_type,
PRBool keepcerts,
PRTime atTime)
{
return sec_pkcs7_verify_signature(cinfo, certusage,
detached_digest, digest_type,
keepcerts, &atTime);
}
/*
* 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, NULL);
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:
if (signercert->emailAddr && signercert->emailAddr[0]) {
container = PORT_Strdup(signercert->emailAddr);
} else {
container = NULL;
}
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 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);
}