mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
2056 lines
59 KiB
C
2056 lines
59 KiB
C
|
/* ***** BEGIN LICENSE BLOCK *****
|
||
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||
|
*
|
||
|
* The contents of this file are subject to the Mozilla Public License Version
|
||
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
||
|
* the License. You may obtain a copy of the License at
|
||
|
* http://www.mozilla.org/MPL/
|
||
|
*
|
||
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
||
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||
|
* for the specific language governing rights and limitations under the
|
||
|
* License.
|
||
|
*
|
||
|
* The Original Code is the Netscape security libraries.
|
||
|
*
|
||
|
* The Initial Developer of the Original Code is
|
||
|
* Netscape Communications Corporation.
|
||
|
* Portions created by the Initial Developer are Copyright (C) 1994-2000
|
||
|
* the Initial Developer. All Rights Reserved.
|
||
|
*
|
||
|
* Contributor(s):
|
||
|
* Dr Vipul Gupta <vipul.gupta@sun.com>, Sun Microsystems Laboratories
|
||
|
*
|
||
|
* Alternatively, the contents of this file may be used under the terms of
|
||
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||
|
* of those above. If you wish to allow use of your version of this file only
|
||
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||
|
* use your version of this file under the terms of the MPL, indicate your
|
||
|
* decision by deleting the provisions above and replace them with the notice
|
||
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||
|
* the provisions above, a recipient may use your version of this file under
|
||
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
||
|
*
|
||
|
* ***** END LICENSE BLOCK ***** */
|
||
|
|
||
|
/*
|
||
|
* PKCS7 decoding, verification.
|
||
|
*
|
||
|
* $Id: p7decode.c,v 1.25 2008/03/10 00:01:26 wtc%google.com Exp $
|
||
|
*/
|
||
|
|
||
|
#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 "key.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;
|
||
|
PRArenaPool *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,
|
||
|
PRArenaPool *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).
|
||
|
*/
|
||
|
extern const SEC_ASN1Template SEC_SMIMEKEAParamTemplateAllParams[];
|
||
|
|
||
|
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;
|
||
|
int bulkLength = 0;
|
||
|
|
||
|
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 ((encalgtag != SEC_OID_NETSCAPE_SMIME_KEA) && (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;
|
||
|
/* ### mwelch -- KEA */
|
||
|
case SEC_OID_NETSCAPE_SMIME_KEA:
|
||
|
{
|
||
|
SECStatus err;
|
||
|
CK_MECHANISM_TYPE bulkType;
|
||
|
PK11SymKey *tek;
|
||
|
SECKEYPublicKey *senderPubKey;
|
||
|
SEC_PKCS7SMIMEKEAParameters keaParams;
|
||
|
|
||
|
(void) memset(&keaParams, 0, sizeof(keaParams));
|
||
|
|
||
|
/* Decode the KEA algorithm parameters. */
|
||
|
err = SEC_ASN1DecodeItem(NULL,
|
||
|
&keaParams,
|
||
|
SEC_SMIMEKEAParamTemplateAllParams,
|
||
|
&(ri->keyEncAlg.parameters));
|
||
|
if (err != SECSuccess)
|
||
|
{
|
||
|
p7dcx->error = err;
|
||
|
PORT_SetError(0);
|
||
|
goto no_key_found;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* We just got key data, no key structure. So, we
|
||
|
create one. */
|
||
|
senderPubKey =
|
||
|
PK11_MakeKEAPubKey(keaParams.originatorKEAKey.data,
|
||
|
keaParams.originatorKEAKey.len);
|
||
|
if (senderPubKey == NULL)
|
||
|
{
|
||
|
p7dcx->error = PORT_GetError();
|
||
|
PORT_SetError(0);
|
||
|
goto no_key_found;
|
||
|
}
|
||
|
|
||
|
/* Generate the TEK (token exchange key) which we use
|
||
|
to unwrap the bulk encryption key. */
|
||
|
tek = PK11_PubDerive(privkey, senderPubKey,
|
||
|
PR_FALSE,
|
||
|
&keaParams.originatorRA,
|
||
|
NULL,
|
||
|
CKM_KEA_KEY_DERIVE, CKM_SKIPJACK_WRAP,
|
||
|
CKA_WRAP, 0, p7dcx->pwfn_arg);
|
||
|
SECKEY_DestroyPublicKey(senderPubKey);
|
||
|
|
||
|
if (tek == NULL)
|
||
|
{
|
||
|
p7dcx->error = PORT_GetError();
|
||
|
PORT_SetError(0);
|
||
|
goto no_key_found;
|
||
|
}
|
||
|
|
||
|
/* Now that we have the TEK, unwrap the bulk key
|
||
|
with which to decrypt the message. We have to
|
||
|
do one of two different things depending on
|
||
|
whether Skipjack was used for bulk encryption
|
||
|
of the message. */
|
||
|
bulkType = PK11_AlgtagToMechanism (bulkalgtag);
|
||
|
switch(bulkType)
|
||
|
{
|
||
|
case CKM_SKIPJACK_CBC64:
|
||
|
case CKM_SKIPJACK_ECB64:
|
||
|
case CKM_SKIPJACK_OFB64:
|
||
|
case CKM_SKIPJACK_CFB64:
|
||
|
case CKM_SKIPJACK_CFB32:
|
||
|
case CKM_SKIPJACK_CFB16:
|
||
|
case CKM_SKIPJACK_CFB8:
|
||
|
/* Skipjack is being used as the bulk encryption algorithm.*/
|
||
|
/* Unwrap the bulk key. */
|
||
|
bulkkey = PK11_UnwrapSymKey(tek, CKM_SKIPJACK_WRAP,
|
||
|
NULL, &ri->encKey,
|
||
|
CKM_SKIPJACK_CBC64,
|
||
|
CKA_DECRYPT, 0);
|
||
|
break;
|
||
|
default:
|
||
|
/* Skipjack was not used for bulk encryption of this
|
||
|
message. Use Skipjack CBC64, with the nonSkipjackIV
|
||
|
part of the KEA key parameters, to decrypt
|
||
|
the bulk key. If we got a parameter indicating that the
|
||
|
bulk key size is different than the encrypted key size,
|
||
|
pass in the real key size. */
|
||
|
|
||
|
/* Check for specified bulk key length (unspecified implies
|
||
|
that the bulk key length is the same as encrypted length) */
|
||
|
if (keaParams.bulkKeySize.len > 0)
|
||
|
{
|
||
|
p7dcx->error = SEC_ASN1DecodeItem(NULL, &bulkLength,
|
||
|
SEC_ASN1_GET(SEC_IntegerTemplate),
|
||
|
&keaParams.bulkKeySize);
|
||
|
}
|
||
|
|
||
|
if (p7dcx->error != SECSuccess)
|
||
|
goto no_key_found;
|
||
|
|
||
|
bulkkey = PK11_UnwrapSymKey(tek, CKM_SKIPJACK_CBC64,
|
||
|
&keaParams.nonSkipjackIV,
|
||
|
&ri->encKey,
|
||
|
bulkType,
|
||
|
CKA_DECRYPT, bulkLength);
|
||
|
}
|
||
|
|
||
|
|
||
|
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:
|
||
|
/*
|
||
|
* 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);
|
||
|
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;
|
||
|
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. (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;
|
||
|
|
||
|
/*
|
||
|
* 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;
|
||
|
PRArenaPool *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->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_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 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,
|
||
|
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 *certdb, *defaultdb;
|
||
|
SECOidTag encTag,digestTag;
|
||
|
HASH_HashType found_type;
|
||
|
int i, certcount;
|
||
|
SECKEYPublicKey *publickey;
|
||
|
SECItem *content_type;
|
||
|
PK11SymKey *sigkey;
|
||
|
SECItem *encoded_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 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 (CERT_VerifyCert (certdb, cert, PR_TRUE, certusage,
|
||
|
encoded_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);
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
#ifndef NSS_ECC_MORE_THAN_SUITE_B
|
||
|
if (encTag == SEC_OID_ANSIX962_EC_PUBLIC_KEY) {
|
||
|
PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE);
|
||
|
goto done;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
|
||
|
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;
|
||
|
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 = (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);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* 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:
|
||
|
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);
|
||
|
}
|