Closed Bug 298538 Opened 19 years ago Closed 19 years ago

S/MIME message verification fails if cert is signing-only

Categories

(NSS :: Libraries, defect, P2)

3.10
defect

Tracking

(Not tracked)

RESOLVED FIXED
3.10.2

People

(Reporter: julien.pierre, Assigned: julien.pierre)

References

Details

Attachments

(3 files, 3 obsolete files)

Using the attached message and signature files, and a secmod.db containing the
root cert module, the following command fails :

cmsutil -D -d . -h 1 -c message -i signature

                signer0.id="Iain MacDonnell (50766)";
signer0.status=SigningCertNotTrusted;

The problem is actually that CERT_FindCertIssuer returned NULL . This is despite
the fact that the certs were previously imported as temporary.

If you rerun this command with the additional -k argument, which causes the
certs to be imported to the permanent DB, then the verification passes.

There is no reason why our chain building and verification algorithm should
behave any different whether the certs are temp certs or a permanent certs. I
believe the certs are fine, and the verification should succeed in both cases.
A clarification here - I don't expect the message (as attached) to pass the
signature verification . There will be a DigestMismatch error when -k is used.
But that's farther along than without -k - which dies because of
CERT_VerifyCertificate .

The problem was diagnosed with a real cert chain, which is 4 certs long - 1 EE,
2 intermediates and one root. The message signature includes 3 - all but the
root, which is valid. The root is GTE, and is available in the built-ins. I will
attach the DER for the individual certs in a moment.
Looks like the certs are all being imported with CERT_ImportCerts, and
immediately destroyed in NSS_CMSSignedData_ImportCerts . This causes cmsutil -D
to always fail without the -k option, as best as I can tell, even with my own
cert chain .
Depends on: 54014
It turns out the problem is related to the fact that the cert is only a signing
cert. Mozilla actually tries to import the certs permanently, but only with the
certUsageEmailRecipient usage. With Iain's certs, this fails. NSS_CMSSignedData
should do a temporary import of the certs, but instead, it just discards them. I
believe I can add a CERTCertList to the NSS_CMSSignedData structure for the
purpose of holding the temp certs.

Assignee: wtchang → julien.pierre.bugs
Attached patch Fix (obsolete) — Splinter Review
The fix is to save the temp certs within the NSSCMSSignedData structure, so
they are always available when the signature on this data needs to be verified
later .
Attachment #187256 - Flags: superreview?(rrelyea)
Attachment #187256 - Flags: review?(nelson)
I verified that this fix works both for my cmsutil test case and with mozilla
mail . In mozilla mail, with the updated libsmime3.so, I can now verify the
original message signed with a signing-only SunPKI cert. This operation
previously failed because the issuer was not found.

IMO, this fix should be urgently go into Mozilla Mail and Thunderbird as well as
in NSS 3.10.1 .
Priority: -- → P2
Target Milestone: --- → 3.10.1
Summary: S/MIME message verification fails if NSS_CMSSignedData_ImportCerts is called with keepCerts = PR_FALSE → S/MIME message verification fails if cert is signing-only
I applied the patch to thunderbird 1.0.2. My signed messages are still not
verifying - it says that the certificate used to sign the message was issued by
a CA that I do not trust. When I view the certificate, I do see the complete
chain now, though, which I did not before (only say the signing cert and CA cert
that signed it). Is there something more that needs to be done to integrate the
fix into thunderbird?
Comment on attachment 187256 [details] [diff] [review]
Fix

I'm not sure that the new list of certs, named tempCerts, is needed.  I think
that perhaps the existing list of certs, named simply certs, could be used for
this purpose.  
 Even if it's not strictly needed, it may be that having two lists will be
cleaner, avoiding any interaction between two sets of certs combined into a
single list.  But the code in this patch appears to be correct, so r=nelson. 
I agree that this should go into the email clients ASAP.
Attachment #187256 - Flags: review?(nelson) → review+
Sorry, scratch my previous message - I had a bad (expired) copy of one of the
higher-up CA certs stored in my software security device. I cleaned that out,
and now my messages verify OK. I went back to my previous (unpatched)
thunderbird build (same profile) and confirmed that they do NOT verify OK there,
then back to the patched version, and all looks good.
Comment on attachment 187256 [details] [diff] [review]
Fix

You can avoid adding the NSS_CMSSignedData_AddTempCertificate function
by passing &sigd->tempCerts instead of &certArray to
CERT_ImportCerts in NSS_CMSSignedData_ImportCerts.

The purpose of "tempCerts" should be documented in cmst.h.

It seems that the existing "certs" field is only used for *encoding*
a signed message, so it should be possible to use "certs" for a
different purpose when we *decode* a signed message.  But I agree that
it may not be worthwhile to do it.
Another reason to not add NSS_CMSSignedData_AddTempCertificate
is that unlike NSS_CMSSignedData_AddCertificateit, it is not a
generally useful function.  At least, it should be made a static
function or a module private function (declared in cmslocal.h).
Wan-Teh,

certArray is a local variable . I can't just use CERT_ImportCerts on it. I need
to keep track of it, so I can destroy it with the NSSCMSSignedData . That's why
I added an "AddTempCertificate" function . I could add an "AddTempCertArray"
function instead, but I think the later is more complicated as it involves
merging arrays. It is also less flexible, in case the future source of temp
certs isn't an array . I don't agree the new function may not have other uses -
I think it could - but I agree it should be made static and moved to cmslocal.h
. I will provide a new patch to that effect .

Do you agree the fix should go into both 3.10.1 and 3.11 ?

Nelson,

I added a separate temp cert list because I didn't want to interfere with the
use of the existing cert list.
Attached patch update CMS header files (obsolete) — Splinter Review
I didn't make the new function NSS_CMSSignedData_AddTempCertificate static,
because nearly all of the functions in cmslocal.h are extern . I think it's OK
for other source files within libsmime to call that function. It just shouldn't
be exported in the shared library.
Attachment #187434 - Flags: review?(wtchang)
This patch is the best way to describe the changes
I suggested.
Comment on attachment 187434 [details] [diff] [review]
update CMS header files

I don't think the NSS_CMSSignedData_AddTempCertificate function
is useful in any other circumstances, but it's okay to add it.
Attachment #187434 - Flags: review?(wtchang) → review+
Comment on attachment 187435 [details] [diff] [review]
Patch that describes wtc's suggested changes

Wan-Teh,

I understand how your patch works and I believe it is correct. However, I think
the original author of the S/MIME library tried to maintain a relatively high
degree of data abstraction, and thus created setter and/or getter functions for
many of the structure fields, and my patch continues that.
Thanks for the review, Wan-Teh.
I checked in the patch to NSS_3_10_BRANCH :

Checking in cmslocal.h;
/cvsroot/mozilla/security/nss/lib/smime/cmslocal.h,v  <--  cmslocal.h
new revision: 1.4.8.1; previous revision: 1.4
done
Checking in cmssigdata.c;
/cvsroot/mozilla/security/nss/lib/smime/cmssigdata.c,v	<--  cmssigdata.c
new revision: 1.28.8.1; previous revision: 1.28
done
Checking in cmst.h;
/cvsroot/mozilla/security/nss/lib/smime/cmst.h,v  <--  cmst.h
new revision: 1.9.8.1; previous revision: 1.9
done

and to the tip

Checking in cmslocal.h;
/cvsroot/mozilla/security/nss/lib/smime/cmslocal.h,v  <--  cmslocal.h
new revision: 1.5; previous revision: 1.4
done
Checking in cmssigdata.c;
/cvsroot/mozilla/security/nss/lib/smime/cmssigdata.c,v	<--  cmssigdata.c
new revision: 1.29; previous revision: 1.28
done
Checking in cmst.h;
/cvsroot/mozilla/security/nss/lib/smime/cmst.h,v  <--  cmst.h
new revision: 1.10; previous revision: 1.9
done
Attachment #187256 - Attachment is obsolete: true
Attachment #187434 - Attachment is obsolete: true
Attachment #187435 - Attachment is obsolete: true
Attachment #187256 - Flags: superreview?(rrelyea)
Status: NEW → RESOLVED
Closed: 19 years ago
Resolution: --- → FIXED
There is one thing about this bug that I don't understand.

It is common for a user to have two certs: one signing-only
cert, and one encryption-only cert.

If a signed email message has both of these certs, do we
import both of them or just the encryption-only cert into
the permanent database?

If we only import the encryption-only cert into the permanent
database, how were we able to verify the signature before?
(In reply to comment #19)
> There is one thing about this bug that I don't understand.
> 
> It is common for a user to have two certs: one signing-only
> cert, and one encryption-only cert.

The reason we do this is so we can escrow the private key that's used for
encryption (so we can recover encrypted data if it gets lost) whereas we
don't want to escrow the signing key.


> If a signed email message has both of these certs, do we
> import both of them or just the encryption-only cert into
> the permanent database?

I'd say just the encryption one, as that's the one you'll need if you
want to send an encrypted response.


> If we only import the encryption-only cert into the permanent
> database, how were we able to verify the signature before?

We weren't - hence the bug :) It only worked for certs that were for both
signing and encryption... Right?



Wan-Teh,

In the early releases of Mozilla - 1.0 through 1.3, the PSM S/MIME code was
importing all the e-mail certs permanently, setting the keepCerts argument to 1.
This was happening regardless of whether the certs were for signing, encryption,
or both .

In Mozilla 1.4, PSM got changed to no longer import the signing certs
permanently. Only the encryption certs were imported permanently. The other
certs were supposed to be imported temporarily - except they weren't, due to
this NSS bug.

Thus, Mozilla hasn't been able to verify the signing-only certs since version 1.4.
Does the signed message come with both the signing-only
and encryption-only certs, or just the signing-only cert?

If it is the former, this is a major regression and I'm
surprised that I haven't run into it.
(In reply to comment #22)
> Does the signed message come with both the signing-only
> and encryption-only certs, or just the signing-only cert?

Depends how the sending client is configured, I guess. In my tests, I was
sending the signing cert only - hadn't bothered with the encryption cert yet.

Yes, this is a major regression that happened long ago.  
It shows that our SMIME QA coverage is woefully inadequate.  
In particular, it seems we don't cover separate signing/encryption certs.
Additional QA is greatly needed.  Seems like this is just a matter of 
scripting.  I doubt that the cmsutil program needs to be changed.  Although 
I wish it were otherwise, SMIME QA is not going to be a priority at Sun.
I think what happens in the case where you attach the encryption cert too is
that the CA cert gets imported permanently as part of the encryption cert
permanent import. Thus, when the signature check happens, it finds the CA cert,
because it is the same for both the encryption and signing certs. I think it
wouldn't work if the two certs (signing and encryption) had different issuers,
because in that case the signing cert's issuer would not be imported permanently.
So it's the CA cert that needs to be imported.  I thought
it's the leaf signing-only cert that needs to be imported
as temporary.

This explains why I haven't run into this problem.  Our
test CA issues signing-only and encryption-only certs to
each person, but our email application always sends the
encryption-only cert with signed messages, and our
encryption-only cert is issued by the same CA as the
signing-only cert.

I fully understand the bug now.  Thanks for explaining.
Wan-Teh,

Technically, both the signing cert and the CA cert need to be imported for the
e-mail signature to verify. But the failure that was seen here was resulting in
the issuer cert not being found in CERT_VerifyCert - and this is what the patch
fixed. In order for there to even be a CERT_VerifyCert call, the signing cert
itself had to have been decoded, outside of NSS_CMSSignedData_ImportCerts. I
didn't check where exactly where that was happening .

On a related note, for a long time, Mozilla mail required an encryption cert,
even if one only wanted to sign a message. The requirement was dropped recently
in PSM, so that signed emails could be sent without encryption certs in them. 
Iain apparently took advantage of that feature, and discovered it was broken.
Julien,

I believe PSM verified a signed message here:
http://lxr.mozilla.org/seamonkey/source/security/manager/ssl/src/nsCMS.cpp#289

289   // We verify the first signer info,  only //
290   if (NSS_CMSSignedData_VerifySignerInfo(sigd, 0, CERT_GetDefaultCertDB(),
certUsageEmailSigner) != SECSuccess) {

If you trace the code, you will see that sigd->signerInfos[i]->cert
is a reference to the signing cert.  That cert reference ensures
that the signing cert is in at least the temp cert database.

Iain, could you explain how to configure Mozilla mail to not require
an encryption cert?
Wan-Teh,

I'm not sure how to do the configuration, but I can tell you that the test case
I have received from Iain was signed with Thunderbird 1.0.2 . Maybe Mozilla Mail
didn't get the fix to allow signing only .
(In reply to comment #28)

> Iain, could you explain how to configure Mozilla mail to not require
> an encryption cert?

I've been using thunderbird, but I believe it's the same in mozilla 1.7. When
you select a signing certificate for an email account, the preferences dialog
asks you if you want to use the same certificate as your encryption one. I
always hit cancel at that point, which results in the encryption cert being
left blank.
Comment on attachment 187446 [details] [diff] [review]
Patch as checked in

Requesting approval to have this fix for a serious S/MIME regression integrated
.
Attachment #187446 - Flags: approval1.7.9?
Attachment #187446 - Flags: approval-aviary1.0.5?
Elsewhere, it has been suggested that, ever since libSMIME was changed to 
only import encryption certs, that it was not permanently importing 
signing-only CA certs that are part of the encryption cert chain, 
leading to inability to verify encryption certs, when attempting to send
encrypted email at a later time.

If true, that is also a part of this same regression, and IMO should be
fixed at the same time.  Are we sure that problem is actually fixed here?
(In reply to comment #32)
> Elsewhere, it has been suggested that, ever since libSMIME was changed to 
> only import encryption certs, that it was not permanently importing 
> signing-only CA certs that are part of the encryption cert chain, 
> leading to inability to verify encryption certs, when attempting to send
> encrypted email at a later time.

Is there any such thing as a "signing-only CA cert"? Trust attributes are
usually assigned to CA certs by the user on (manual) import, but I don't
think they are properties of the CA cert itself?

Julien and I did test messages signed with my signing cert AND also
carrying my encryption cert. In that case, the encryption cert and the CA
chain do get stored (and consequently the signature verifies successfully).

So now we are relying on a new (undocumented) side effect
of NSS_CMSSignedData_ImportCerts, that the function will
decode sigd->rawCerts and save references to the temp certs.
This fixes the bug for apps such as Mozilla and Thunderbird
that call NSS_CMSSignedData_ImportCerts.

However, it seems reasonable for someone to just verify a
signed message without calling NSS_CMSSignedData_ImportCerts.
So it seems that it is best for the decoding of sigd->rawCerts
to occur in NSS_CMSSignedData_VerifySignerInfo, if
sigd->rawCerts haven't been decoded yet.
Wan-Teh,

I think NSS_CMSSignedData_ImportCerts is the right function for the applications
to use . Even our examples (cmsutil) rely on it to import the certs for
signature verification. It takes a keepCerts and a usage argument, so that the
application can specify if it wants to import the certs permanently or
temporarily. What would be the purpose of having a keepCerts argument at all if
the function was always a no-op when the value is PR_FALSE ? This is the
behavior my patch fixed .
You are right that it would be cleaner to just be able to call
NSS_CMSSignedData_VerifySignerInfo without calling
NSS_CMSSignedData_ImportCerts. However, I believe that would constitute a change
of API, because the requirement to call NSS_CMSSignedData_ImportCerts always
existed. No signature could ever have been verified without that call in the
past regardless of the type of cert; nor could any encrypted e-mails have been
sent to the recipient because Mozilla verifies the recipient's cert chain before
allowing an e-mail to be sent.

Nelson,

If you look at the body of code in NSS_CMSSignedData_ImportCerts, it filters the
certs and tries to permanently import (if keepCerts is set) only the ones that
validate with the usage passed . But then, it also reconstructs the cert chain
from those certs, and imports all the certs in the chain permanently. So,
effectively, in most cases, all the certs get imported permanently, unless there
were some extraneous certs unrelated to the encryption or signing cert, or some
expired certs in the signature where another newer one exists in the user's DB.
So, I don't think the problem you mention actually ever existed. If the
encryption cert was imported, then its whole chain was imported as well.

The decision to import signing vs encryption certs isn't made by libsmime. It is
made by PSM, in the arguments it chooses to pass to
NSS_CMSSignedData_ImportCerts . It used to import certs for both encryption and
signing permanently, by making multiple calls to NSS_CMSSignedData_ImportCerts,
with the different usages, but both with keepCerts = PR_TRUE . Now, it still
makes two ImportCerts calls, but keepCerts is set only for the encryption usage,
not the signing usage. My patch fixes the problem becauses it causes all the
certs to always be imported temporarily, regardless of their usage, which I
believe was the intent of setting keepCerts to PR_FALSE. The logic to decide
which certs to make permanent is unchanged.
*** Bug 54014 has been marked as a duplicate of this bug. ***
No longer depends on: 54014
Before we settle on this patch, we should discuss one issue:
   poison cert corruption.

The problem is if we just take the list of certs as is and import them into
either the temp or the perm db long term, we can open ourselves up to a
difficult to detect denial of service attack. An attacker creates a certificate
with the same subject and key id of an existing CA we trust or a popular
intermediate CA. He includes that CA in his email chain. If that cert has
'appropriate' expiration times and cert extensions, NSS could choose this cert
over the trusted CA cert, and any validation against that chain could fail.

Since the patch involves temp certs, the effect is much less than perm certs
(older versions of NSS blindly imported all certs from the email message, rather
than validating each imported cert in turn because of this potential attack).
Also this is a Denial of Service attack, not a spoofing attack, and it's effect
would only be while the certs are held in the temp DB (that is while the email
message was open). I don't see it as an issue for thunderbird (more of an NSS
correctness issue).

Anyway the solutions could be as simple as leaving the code 'as is' to using the
equivalent logic we have for permanently importing the encryption cert and its
chain for tempararily importing the signing cert and it's chain.

bob.
Bob,

I thought of this poison cert problem when I wrote the patch. However, my take
on is that the application should have the whole cert chain available when
verifying the message signature - in order to be able to display it, for example
. Once the message goes away, the certs can no longer poison anything.

Also, you need to import the certs anyway, even for a brief period of time, to
find out they don't validate - and during that period of time, they are still
poisonous . This isn't much of a problem in a client app, but it is in a server.

I believe the same problem exists with SSL as well. We can receive a chain from
a server, import the certs temporarily, and this may cause long-term problems. I
have filed bugs on that issue before. The only true fix would be to have real
compartmentalized contexts in which we could import the certs safely, without
affecting anything else going in the NSS application, in particular the other
threads. I think Stan was supposed to solve this with the cryto contexts ... :-(
The thing I worry about is how long we keep the certs.

The question is do we need to keep the certs if the verification fails. The
answer is 'it depends'. If the verify certificate fails on the signing cert in
the import, it won't matter to the next stage the the cert chain is imported or
not (the signature will fail in either case). However, if you have code that
displays the chain to the user so the user can see why the signature failed, you
would want to keep the chain around even if verification fails.

I believe, in the SSL case, the chain is kept around as long as the page is
displayed (in Firefox anyway. PSM extracts the chain before SSL frees the cert).
Note that spurious poison certs won't live longer than the SSL verification in
SSL, though poison certs in the actual chain my live through the life of the page.

The question is how long does the NSSCMSSignedData structure that we are now
storing the temp certs in stay around. If it's only over the decode, the current
code is absolutely no problem. If it's over the life of email, then it would be
better to extract only the signing cert chain. That way we don't have a problem
with spurious poison certs, only those poison certs that are part of the signing
chain.

Anyway I suspect that we have the former case (NSSCMSSignedData data only lives
as long as the life of the decode), I just want to verify that.

bob
Bob,

NSS_CMSSignedData_Destroy is called as part of NSS_CMSMessage_Destroy, which is
called by PSM immediately after the cert verification happens.

I found that PSM makes a copy of the cert chain with CERT_DupCertificate before
it calls NSS_CMSMessage_Destroy. This is what allows you to click on the
signature in the UI in Mozilla after the verification has been done and get the
whole cert chain, after the NSS_CMSMessage object has been destroyed.

I couldn't see exactly what PSM was doing since I had an optimized build of
mozilla, I didn't build Mozilla myself. Only my NSS bits were debug.
OK, I don't think this patch is a risk for any NSS client then. (including mozilla).

bob
BTW, I believe with libpkix, the whole concept of "poison certs" will no longer
exist, because the algorithm backtracks if it finds a cert chain it can't
validate, and finds another possible issuer cert. So, adding bogus untrusted
certs should not have a negative effect functionally - it will only slightly
increase validation time.
Comment on attachment 187446 [details] [diff] [review]
Patch as checked in

1.0.5 and 1.7.9 have already shipped; unsetting approval requests.
Attachment #187446 - Flags: approval1.7.9?
Attachment #187446 - Flags: approval-aviary1.0.5?
Target Milestone: 3.10.1 → 3.10.2
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: