mirror of
https://git.sr.ht/~rjarry/aerc
synced 2026-03-02 18:23:33 +01:00
gpg: fix mime-version header position
Some MTAs try to normalize the case of all headers (including signed text parts headers). Unfortunately, Mime-Version can be normalized to different casing depending on the implementation (MIME- vs Mime-). Since the signature is computed on the whole part, including its header, changing the case can cause the signature to become invalid. Due to how multipart/signed messages are constructed, we need to hack around go-message writers to intercept the writing of a text part, compute its signature and write the actual message with the proper headers. Unfortunately, go-message does not allow creating a message writer that does not insert a Mime-Version header. This causes the Mime-Version header to be inserted in the wrong place: it is put inside the signed text part header instead on the top level header. Thus, included in the signed content. Make sure to remove any Mime-Version header from the signed part header. Finally, ensure that Mime-Version is set on the top-level header so that messages are compliant with RFC 2045. Fixes: https://todo.sr.ht/~rjarry/aerc/143 Link: https://github.com/emersion/go-message/issues/165 Link: https://github.com/emersion/go-pgpmail/pull/15 Link: https://lists.sr.ht/~rjarry/aerc-devel/%3CCQRPF5EA0TF8.PEJ4AKCEGMFM%40fembook%3E Changelog-fixed: `Mime-Version` is no longer inserted in signed text parts headers. MTAs normalizing header case will not corrupt signatures anymore. Reported-by: Coco Liliace <chloe@liliace.dev> Reported-by: Kirill Chibisov <contact@kchibisov.com> Signed-off-by: Robin Jarry <robin@jarry.cc> Tested-by: Tim Culverhouse <tim@timculverhouse.com>
This commit is contained in:
@@ -942,14 +942,6 @@ func (c *Composer) WriteMessage(header *mail.Header, writer io.Writer) error {
|
||||
}
|
||||
}
|
||||
|
||||
if header != nil && !header.Has("MIME-Version") {
|
||||
// sign and encrypt will create multipart/* messages
|
||||
// without setting the MIME-Version header. Set it
|
||||
// manually at the top level to be compliant with RFC
|
||||
// 2045.
|
||||
header.Set("MIME-Version", "1.0")
|
||||
}
|
||||
|
||||
if c.encrypt {
|
||||
rcpts, err := getRecipientsEmail(c)
|
||||
if err != nil {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"mime"
|
||||
|
||||
"git.sr.ht/~rjarry/aerc/lib/crypto/gpg/gpgbin"
|
||||
"github.com/emersion/go-message"
|
||||
"github.com/emersion/go-message/textproto"
|
||||
)
|
||||
|
||||
@@ -50,9 +51,26 @@ func (s *Signer) Write(p []byte) (int, error) {
|
||||
}
|
||||
|
||||
func (s *Signer) Close() (err error) {
|
||||
// TODO should write the whole message up here so we can get the proper micalg from the signature packet
|
||||
msg, err := message.Read(&s.signedMsg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Make sure that MIME-Version is *not* set on the signed part header.
|
||||
// It must be set *only* on the top level header.
|
||||
//
|
||||
// Some MTAs actually normalize the case of all headers (including
|
||||
// signed text parts). MIME-Version can be normalized to different
|
||||
// casing depending on the implementation (MIME- vs Mime-).
|
||||
//
|
||||
// Since the signature is computed on the whole part, including its
|
||||
// header, changing the case can cause the signature to become invalid.
|
||||
msg.Header.Del("Mime-Version")
|
||||
|
||||
sig, micalg, err := gpgbin.Sign(bytes.NewReader(s.signedMsg.Bytes()), s.from)
|
||||
var buf bytes.Buffer
|
||||
_ = textproto.WriteHeader(&buf, msg.Header.Header)
|
||||
_, _ = io.Copy(&buf, msg.Body)
|
||||
|
||||
sig, micalg, err := gpgbin.Sign(bytes.NewReader(buf.Bytes()), s.from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -62,13 +80,16 @@ func (s *Signer) Close() (err error) {
|
||||
"micalg": micalg,
|
||||
}
|
||||
s.header.Set("Content-Type", mime.FormatMediaType("multipart/signed", params))
|
||||
// Ensure Mime-Version header is set on the top level to be compliant
|
||||
// with RFC 2045
|
||||
s.header.Set("Mime-Version", "1.0")
|
||||
|
||||
if err = textproto.WriteHeader(s.w, s.header); err != nil {
|
||||
return err
|
||||
}
|
||||
boundary := s.mw.Boundary()
|
||||
fmt.Fprintf(s.w, "--%s\r\n", boundary)
|
||||
_, _ = s.w.Write(s.signedMsg.Bytes())
|
||||
_, _ = s.w.Write(buf.Bytes())
|
||||
_, _ = s.w.Write([]byte("\r\n"))
|
||||
|
||||
var signedHeader textproto.Header
|
||||
@@ -114,6 +135,9 @@ func Encrypt(w io.Writer, h textproto.Header, rcpts []string, from string) (io.W
|
||||
"protocol": "application/pgp-encrypted",
|
||||
}
|
||||
h.Set("Content-Type", mime.FormatMediaType("multipart/encrypted", params))
|
||||
// Ensure Mime-Version header is set on the top level to be compliant
|
||||
// with RFC 2045
|
||||
h.Set("Mime-Version", "1.0")
|
||||
|
||||
if err := textproto.WriteHeader(w, h); err != nil {
|
||||
return nil, err
|
||||
|
||||
Reference in New Issue
Block a user