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