Subject: [lamps] Protected Headers update: proposed guidance for composing MUAs

From: Daniel Kahn Gillmor <[email-masked]>
Date: Wed, 11 Nov 2020 02:14:28 -0500

Hi folks!

More followup on the header protection work!  After settling on the
guidance for receiving MUAs back on November 2-4, Bernie and Alexey and I met
recently to discuss the rest of the ongoing work.

This message contains text that is intended as guidance for MUAs that
compose a message with protected headers (the "sending side").

This section describes how to create cryptographic e-mail with header
protection using both identified schemes: "Wrapped Message" and
"Injected Headers", in the same way that the earlier discussion about
the receiving side documents them both.

The difference here is of course that composing MUA has a decision to
make about what to generate -- the receiving MUA has to deal with
whatever it sees.

The text as currently written doesn't have an opinion about what that
choice should be -- but obviously we will need to make that decision
before we finalize the document.  It's in as a TODO item.

My goal with this intermediate version is just to spell out very clearly
what steps a composing MUA should take should they decide to use one
scheme or another.

The overall outline of this section looks like:

* Sending Side
*.1 Composing a Cryptographically-Protected Message Without Header Protection
*.2 Header Confidentiality Policy
*.3 Composing with "Wrapped Message" Header Protection
*.4 Composing with "Injected Headers" Header Protection
*.5 Choosing Between Wrapped Message and Injected Headers

One novel thing in this writeup is the declaration of a "Header
Confidentiality Policy" (section *.2) or "HCP", which is a special
shorthand for describing how protected headers in *encrypted* messages
should be granted confidentiality.

The proposed text below contains one example (very conservative) HCP
that only provides confidentiality for the "Subject" header value.

In my next protected header message to this list, i'll document another
(more aggressive) HCP.  The final document will need to recommend one as
a default (or perhaps as a baseline), but again, that's a simple
outstanding TODO item.

Feedback welcome, as always!

            --dkg

### Sending Side

This section describes the process an MUA should use to apply cryptographic protection to an e-mail message with header protection.
We start by describing the legacy message composition process as a baseline.

#### Composing a Cryptographically-Protected Message Without Header Protection {#compose-legacy}

[I-D.dkg-lamps-e2e-mail-guidance] describes the typical process for a legacy crypto MUA to apply cryptographic protections to an e-mail message.
That guidance and terminology is replicated here for reference:

- `origbody`: the traditional unprotected message body as a well-formed MIME tree (possibly just a single MIME leaf part).
  As a well-formed MIME tree, `origbody` already has structural headers (`Content-*`) present.
- `origheaders`: the intended non-structural headers for the message, represented here as a list of `(h,v)` pairs, where `h` is a header field name and `v` is the associated value.
  Note that these are header fields that the MUA intends to be visible to the recipient of the message.
  In particular, if the MUA uses the `Bcc` header during composition, but plans to omit it from the message (see section 3.6.3 of [RFC5322]), it will not be in `origheaders`.
- `crypto`: The series of cryptographic protections to apply (for example, "sign with the secret key corresponding to X.509 certificate X, then encrypt to X.509 certificates X and Y").
  This is a routine that accepts a MIME tree as input (the Cryptographic Payload), wraps the input in the appropriate Cryptographic Envelope, and returns the resultant MIME tree as output.

The algorithm returns a MIME object that is ready to be injected into the mail system:

- Apply `crypto` to `origbody`, yielding MIME tree `output`
- For each header name and value `(h,v)` in `origheaders`:
  - Add header `h` of `output` with value `v`
- Return `output`

#### Header Confidentiality Policy {#header-confidentiality-policy}

When composing an encrypted message with protected headers, the composing MUA needs a Header Confidentialiy Policy.
In this document, we represent that Header Confidentiality Policy as a function `hcp`:

- `hcp(name, val_in) → val_out`: this function takes a header field name `name` and initial value `val_in` as arguments, and returns a replacement header value `val_out`.
  If `val_out` is the special value `null`, it mean that the header in question should be omitted from the set of headers visible outside the Cryptographic Envelope.

For example, an MUA that only obscures the `Subject` header field by replacing it with the literal string `...` and does not offer confidentiality to any other header fields would be represented as (in pseudocode):

```
hcp(name, val_in) → val_out:
    if name is 'Subject':
        return '...'
    else:
        return val_in
```

Note that such a policy is only needed when the end-to-end protections include encryption (confidentiality).
No comparable policy is needed for other end-to-end cryptographic protections (integrity and authenticity), as they are simply uniformly applied so that all header fields known by the sender have these protections.

This asymmetry is an unfortunate consequence of complexities in message delivery systems, some of which may reject, drop, or delay messages where all headers are removed from the top-level MIME object.

This document does not mandate any particular Header Confidentiality Policy, though it offers guidance for MUA implementers in selecting one in {{default-hcp}}.
Future documents may recommend or mandate such a policy for an MUA with specific needs.
Such a recommendation might be motivated by descriptions of metadata-derived attacks, or stem from research about message deliverability, or describe new signalling mechanisms, but these topics are out of scope for this document.

#### Composing with "Wrapped Message" Header Protection {#compose-wrapped-message}

To compose a message using "Wrapped Message" header protection, we use those inputs described in {{compose-legacy}} plus the Header Confidentiality Policy `hcp` defined in {{header-confidentiality-policy}}.
The new algorithm is:

- For header name and value `(h,v)` in `origheaders`:
  - Add header `h` of `origbody` with value `v`
- If any of the header fields in `origbody`, including headers in the nested internal MIME structure, contain any 8-bit UTF-8 characters (see section section 3.7 of [RFC6532]):
  - Let `payload` be a new MIME part with one header: `Content-Type: message/global; forwarded=no`, and whose body is `origbody`.
- Else:
  - Let `payload` be a new MIME part with one header: `Content-Type: message/rfc822; forwarded=no`, and whose body is `origbody`.
- Apply `crypto` to `payload`, yielding MIME tree `output`
- If `crypto` contains encryption:
  - Create new empty list of header field names and values `newh`
  - For header name and value `(h,v)` in `origheaders`:
    - Let `newval` be `hcp(h, v)`
    - If `newval` is not `null`:
      - Append `(h,newval)` to `newh`
  - Set `origheaders` to `newh`
- For header name and value `(h,v)` in `origheaders`:
  - Add header `h` of `output`  with value `v`
- Return `output`

Note that the Header Confidentiality Policy `hcp` is ignored if `crypto` does not contain encryption.
This is by design.

#### Composing with "Injected Headers" Header Protection {#compose-injected-headers}

To compose a message using "Injected Headers" header protection, the composing MUA needs one additional input in addition to the Header Confidentiality Policy `hcp` defined in {{header-confidentiality-policy}}.

- `legacy`: a boolean value, indicating whether any recipient of the message is believed to have a legacy client.
  If all recipients are known to implement this draft, `legacy` should be set to `false`.
  (How a MUA determines the value of `legacy` is out of scope for this document; an initial implementation can simply set it to `true`)

The revised algorithm for applying cryptographic protection to a message is as follows:

- Create a new MIME leaf part `legacydisplay` with header `Content-Type: text/plain; protected-headers="v1"` and an empty body.
- if `crypto` contains encryption, and `legacy` is `true`:
  - For each header name and value `(h,v)` in `origheaders`:
    - If `h` is user-facing (see {{I-D.autocrypt-lamps-protected-headers}}):
      - If `hcp(h,v)` is not `v`:
        - Add `h: v` to the body of `legacydisplay`.  For example, if `h` is `Subject`, and `v` is `lunch plans?`, then add the line `Subject: lunch plans?` to the body of `legacydisplay`
- If the body of `legacydisplay` is empty:
  - Let `payload` be MIME part `origbody`, discarding `legacydisplay`
- Else: (body of `legacydisplay` is not empty)
  - Construct a new MIME part `wrapper` with `Content-Type: multipart/mixed`
  - Give `wrapper` exactly two subparts: `legacydisplay` and `origbody`, in that order.
  - Let `payload` be MIME part `wrapper`
- For each header name and value `(h,v)` in `origheaders`:
  - Add header `h` of MIME part `payload` with value `v`
- Set the `protected-headers` parameter on the `Content-Type` of `payload` to `v1`
- Apply `crypto` to `payload`, producing MIME tree `output`
- If `crypto` contains encryption:
  - Create new empty list of header field names and values `newh`
  - For header name and value `(h,v)` in `origheaders`:
    - Let `newval` be `hcp(h, v)`
    - If `newval` is not `null`:
      - Add `newh[h]` to `newval`
  - Set `origheaders` to `newh`
- For each header name and value `(h,v)` in `origheaders`:
  - Add header `h` of `output` with value `v`
- Return `output`

Note that both new parameters (`hcp` and `legacy`) are effectively ignored if `crypto` does not contain encryption.
This is by design, because they are irrelevant for signed-only cryptographic protections.

#### Choosing Between Wrapped Message and Injected Headers

When composing a message with end-to-end cryptographic protections, an MUA SHOULD protect the headers of that message as well as the body.

An MUA MAY protect the headers of any outbound message using either the "Wrapped Message" or the "Injected Headers" style of protection.
See {{backward-compatibility-use-cases}} for more discussion about reasons to choose one mechanism or another.

\[\[ TODO: this document should recommend generation of one particular scheme by default for new implementers \]\]