python / python/cpython

email.generator.Generator ignores policy when using `multipart/signed` → ruins signing

Open
#99,533 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

stdlib topic-email type-bug
Dominant language
Python
Stars
77.2k
Forks
36k
PR merge metrics
PR metrics pending

Description

Bug report

EmailMessage behaves differently when being set to multipart/signed mimetype.

from email.message import EmailMessage

inner = EmailMessage()
inner.add_attachment("some data", "text/plain", filename="*"*35)

outer1 = EmailMessage()
outer1.set_type("multipart/signed")  # affected by generator.py/_handle_multipart_signed
outer1.attach(inner)

outer2 = EmailMessage()
outer2.set_type("multipart/signeX")  # not affected by generator.py/_handle_multipart_signed
outer2.attach(inner)

# When accessing given submessage, nothing weird happens
outer1.get_payload()[0].as_string() == outer2.get_payload()[0].as_string()  # True

# However, when accessing whole message at once, headers folding change for the outer1 `multipart/signed` message
inner.as_string() in outer1.as_string()  # !False!
inner.as_string() in outer2.as_string()  # True

This is due to a 13 years old generator.py code that for an unknown reason rewrites the policy so that no header was folded:

    def _handle_multipart_signed(self, msg):
        # The contents of signed parts has to stay unmodified in order to keep
        # the signature intact per RFC1847 2.1, so we disable header wrapping.
        # RDM: This isn't enough to completely preserve the part, but it helps.
        p = self.policy
        self.policy = p.clone(max_line_length=0)
        try:
            self._handle_multipart(msg)
        finally:
            self.policy = p

As a result, when I GPG-sign the inner message and attach it to a wrapping-outer message along with the signature (which is the right thing), the signature is void because policy being ignored, the headers folding got disabled on the output. I understand the method _handle_multipart_signed should help the message signing but it ruins it instead. One dirty solution would be to set the policy to max_line_length=0 which fails for whatever reason:

from email import policy
pol = policy.default.clone(max_line_length=0)
inner = EmailMessage(policy=pol)
inner.add_attachment("some data", "text/plain", filename="*"*35)  # ValueError: maxlinelen must be at least 4

Therefore, I am not able to sign the inner message with the headers fold (as it is output unfold), not I am able to sign the inner message with the headers unfold (as ValueError prevents me to set the policy to not stop folding headers).

So my proposal is to remove _handle_multipart_signed altogether (which would be sufficient) or to find a use-case where it does make sense (I could not find any).

Your environment

  • CPython versions tested on: Python 3.10.6
  • Operating system and architecture: Ubuntu 22.04.1 LTS x86_64
Linked PRs
  • gh-100204

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with Lib/email/generator.py at _handle_multipart_signed and reproduce the issue using the EmailMessage example with multipart/signed and a long filename. Check the linked work in gh-100204 and relevant email package tests; done means the signed part preserves the intended policy and header serialization without breaking existing multipart behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.