| /* 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); |
| } |