Open Bug 2014073 Opened 8 months ago Updated 3 months ago

Incorrect/obsolete CMS structure definition for S/MIME

Categories

(NSS :: Libraries, defect, P3)

Tracking

(Not tracked)

ASSIGNED

People

(Reporter: taavi.eomae+bugzilla, Assigned: taavi.eomae+bugzilla)

Details

Attachments

(3 files, 4 obsolete files)

Steps to reproduce:

  1. Generate an S/MIME email that follows RFC
  2. Add CRL to the CMS structure based on RFC 3852 or RFC 5652

Looking at NSS code (https://github.com/nss-dev/nss/blob/master/lib/smime/cmsasn1.c#L90) the definition seems to be based on some ancient CMS version.

const SEC_ASN1Template NSSCMSSignedDataTemplate[] = {
/***/
          SEC_ASN1_XTRN | 1,
      offsetof(NSSCMSSignedData, crls),
      SEC_ASN1_SUB(CERT_SetOfSignedCrlTemplate) },

RFC 5652 (from 2009) and RFC 3852 (from 2004) define SignedData as follows:

      SignedData ::= SEQUENCE {
        version CMSVersion,
        digestAlgorithms DigestAlgorithmIdentifiers,
        encapContentInfo EncapsulatedContentInfo,
        certificates [0] IMPLICIT CertificateSet OPTIONAL,
        crls [1] IMPLICIT RevocationInfoChoices OPTIONAL,
        signerInfos SignerInfos }

Unfortunately NSS does not seem to check the version field appropriately and does not read crls as RevocationInfoChoices.

Actual results:

Thunderbird refuses to verify the CMS structure when there's more than one CRL (RevocationInfoChoice) in crls.

Expected results:

Even if Thunderbird/NSS does not make use of the crls* in SignedData, it should at least follow S/MIME 3.1 and have up-to-date CMS parsing data structures available to tolerate this field being used. (This also probably avoids unnecessary incompatibility with CAdES-L.)

    • In this case crls can be one or more CertificateList or OtherRevocationInfoFormat, which can contain OCSPResponse (RFC 5940).

Vaguely related is https://bugzilla.mozilla.org/show_bug.cgi?id=36454, but that's a feature request that asks for the revocation data to be used. However, it existing not being tolerated is a bug.

Component: Security → Security: S/MIME
Product: Thunderbird → MailNews Core

Re-reading it I might've worded it a bit odd. Just to clarify, NSS seems to tolerate CertificateList in crls but it definitely does not tolerate RevocationInfoChoice (->OtherRevocationInfoFormat) in crls. Which it should.

Structure for reference:

      RevocationInfoChoices ::= SET OF RevocationInfoChoice

      RevocationInfoChoice ::= CHOICE {
        crl CertificateList,
        other [1] IMPLICIT OtherRevocationInfoFormat }

      OtherRevocationInfoFormat ::= SEQUENCE {
        otherRevInfoFormat OBJECT IDENTIFIER,
        otherRevInfo ANY DEFINED BY otherRevInfoFormat }
Attached file galileo-modified.eml —

CMS with a CRL and an OCSP response

I've added an example S/MIME email that contains an RevocationInfoChoice that contains an OCSP response. It is correctly handled by OpenSSL, but not by Thunderbird.

The CMS SignedData crls field is defined as RevocationInfoChoices per
RFC 5652, which is a SET OF RevocationInfoChoice (a CHOICE between a
CRL and other revocation info formats like OCSP responses). The previous
ASN.1 template used CERT_SetOfSignedCrlTemplate, which required every
entry to be a valid CertificateList, causing parse failures when
OtherRevocationInfoFormat entries were present.

Change the template to SEC_SetOfAnyTemplate (matching the rawCerts
pattern) and rename the field to rawCrls. The CRL data was never
processed (marked with XXX TODO), so this change only prevents
rejection of valid messages.

Assignee: nobody → taavi.eomae+bugzilla
Status: UNCONFIRMED → ASSIGNED
Ever confirmed: true

When an opaque-signed message (smime-type=signed-data) fails CMS
decoding, report a signature error instead of "cannot decrypt".

When an unsupported CMS content type (e.g. authEnveloped-data) is
encountered, show the decryption error page instead of a blank message.

Add support for decoding and decrypting AuthEnvelopedData CMS content
type with AES-GCM (AES-128/192/256-GCM). This enables Thunderbird to
read S/MIME messages encrypted with authenticated encryption.

AEAD ciphers do not support streaming, so AuthEnvelopedData bypasses
the CMS streaming filter. The ASN.1 decoder accumulates ciphertext in
rawContent, and one-shot PK11_Decrypt runs after the full structure
(including the mac field) is parsed.

Also adds GCMParameters decoding to PK11_ParamFromAlgid (RFC 5084).

Comment on attachment 9554370 [details]
Bug 2014073 - Implement CMS AuthEnvelopedData decryption (RFC 5083/5084). r?kaie

Revision D288495 was moved to bug 1835697. Setting attachment 9554370 [details] to obsolete.

Attachment #9554370 - Attachment is obsolete: true

Comment on attachment 9554242 [details]
Bug 2014073 - Improve S/MIME error reporting for opaque-signed and unsupported CMS types. r?kaie

Revision D288451 was moved to bug 594565. Setting attachment 9554242 [details] to obsolete.

Attachment #9554242 - Attachment is obsolete: true
Attachment #9554370 - Attachment is obsolete: false

Comment on attachment 9554370 [details]
Bug 2014073 - Implement CMS AuthEnvelopedData decryption (RFC 5083/5084). r?kaie

Revision D288495 was moved to bug 1835697. Setting attachment 9554370 [details] to obsolete.

Attachment #9554370 - Attachment is obsolete: true
Attachment #9554005 - Attachment is obsolete: true

The CMS SignedData crls field is defined as RevocationInfoChoices per
RFC 5652, which is a SET OF RevocationInfoChoice (a CHOICE between a
CRL and other revocation info formats like OCSP responses). The previous
ASN.1 template used CERT_SetOfSignedCrlTemplate, which required every
entry to be a valid CertificateList, causing parse failures when
OtherRevocationInfoFormat entries were present.

Change the template to SEC_SetOfAnyTemplate (matching the rawCerts
pattern) and rename the field to rawRevInfos, reflecting that the
contents are RevocationInfoChoices rather than CRLs specifically. The
CRL data was never processed (marked with XXX TODO), so this change
only prevents rejection of valid messages.

Adds a gtest with an RFC 5940 example (from pyasn1-modules) whose crls
field carries both a CRL and an OCSP response; it fails to parse without
the template change.

gtests/smime_gtest/testvectors/signeddata-crl-ocsp.der is already expired. That's good. Because your test code works, it confirms our low level code accepts that it has a signature. I'm mentioning that, because for S/MIME emails, we treat email signatures from an expired certificate as invalid, but it seems that doesn't affect for your low level test.

You're changing the template to be able to parse the data that you've provided. Can we be certain that users don't have stored S/MIME messages in their archive that they must be able to access, but which only matched the old template, and could no longer be opened after this change?

(In reply to Kai Engert [:KaiE:] from comment #12)

You're changing the template to be able to parse the data that you've provided. Can we be certain that users don't have stored S/MIME messages in their archive that they must be able to access, but which only matched the old template, and could no longer be opened after this change?

Yes, because the template now accepts both structures (CRL and/or OCSP) instead of just the (old) one (CRL).

Your initial patch changes the nested attributes of the NSSCMSMessage data that is used in existing public APIs. That is forbidden because of the ABI compatibility promises that NSS gives.

NSS supports using an application that was built against an older version of the NSS headers, in combination with binary libraries that were built from a newer version of the NSS code.

An application might call NSS_CMSMessage_CreateFromDER(), receive an NSSCMSMessage object, and access the NSSCMSMessage.contentInfo.content.signedData->crls attribute (or the NSSCMSMessage.contentInfo.content.NSSCMSEnvelopedData.originatorInfo->crls attribute).

If it did, the compiled code expects those variables to be of type CERTSignedCrl**.
If the application attempts to access that, but the variable is actually of type SECItem** instead, this will fail or crash.

This scenario might happen on a long-term-support version of an operating system that offers NSS using a system-wide package, that upgrades the system to a newer version of NSS. An organization might be deploying an individual application that uses that API. Because the API exposes that attribute, the app might use that field.

I see two possible ways to fix that.

(1)
Keep the existing output type, unmodified, for using them with the API.
Continue to use the existing template, unmodified, when encoding the types.

Add a separate, new template definition, with the structure you have suggested, only to be used internally by the APIs.
Define a new structure type, strictly internally, that uses the modified variable.

When parsing from DER, parse using the new internal structure and type.

Don't return that data to the caller. Rather, transform it to the type that was originally expected.

The transformation would iterate the new attributes that are found in rawRevInfos and use them to populate the original crls variable, and ignoring all the other parsed attributes.

The intention is that old applications that receive old messages, that used a format that worked, must continue to work with the new NSS version, and must still be able to obtain all the attributes that worked on the past.

(2)
Introduce a new generation of the API, replacements for the CMS decoder functions.

When doing so, we should use a different approach for the output values from the API. I think we should move away from the old approach where a complex nested C structure is directly returned.

The reason is, our current implementation might differ from the spec in many additional aspects, and those might require additional changes to the data that we return from decoding etc.

I would like to avoid that we'd introduce generation 2 of the API, and notice soon after that the new API is still wrong. We don't want to go to a version 3 API soon afterwards.

So, instead of returning the data directly from the API, I suggest to return an opaque pointer, and have the consumers use accessor functions to obtain the attributes.

That's the idea, but it might not be trivial and more work.

This patch contains untested exploration of option (1)

I will apply the patch to avoid changing the ABI.

However, having access to the field properly would be necessary to utilize the "stapled" OCSP response in Thunderbird later on.

(In reply to Taavi from comment #17)

However, having access to the field properly would be necessary to utilize the "stapled" OCSP response in Thunderbird later on.

That will require a new API.

I really couldn't find a better way to implement this than something inspired by your patch. I updated the patchset.

Is it possible that at some point the current APIs could be switched to the new structures as an intentional ABI break? Or is it perpetual? Requires a parallel API?

Attachment #9584523 - Attachment description: Bug 2014073 - Use SEC_SetOfAnyTemplate for CMS SignedData crls field. r?kaie → Bug 2014073 - Parse CMS SignedData crls field as RFC 5652 RevocationInfoChoices. r?kaie

(In reply to Taavi from comment #19)

Is it possible that at some point the current APIs could be switched to the new structures as an intentional ABI break?

No. The only way is to add another API.

Attachment #9593620 - Attachment description: 2014073-abi-diff.txt → 2014073-abi-diff.txt (for abandoned revision)
Attachment #9593620 - Attachment is obsolete: true
Component: Security: S/MIME → Libraries
Product: MailNews Core → NSS
Version: Thunderbird 147 → unspecified

The severity field is not set for this bug.
:jschanck, could you have a look please?

For more information, please visit BugBot documentation.

Flags: needinfo?(jschanck)
Severity: -- → N/A
Flags: needinfo?(jschanck)
Priority: -- → P3
Severity: N/A → S3
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: