stalwartlabs / mail-send

E-mail delivery library for Rust with DKIM support
https://docs.rs/mail-send/
Apache License 2.0
195 stars 19 forks source link

✨ Feature request: OpenPGP encryption support #1

Open spikecodes opened 2 years ago

spikecodes commented 2 years ago

Introduction

Hi! I love this project idea and really appreciate the dedication to modern security standards (such as DKIM support) as well as mail-send's RFC compliance. The current feature set would serve as a fantastic base for a security-oriented Rust-written mail client if that is something Stalwart Labs would be interested in pursuing.

The final component of mail-send that would make it a serious security contender (in terms of client features) to the likes of ProtonMail and Tutanota would be support for OpenPGP, the open standard for email encryption.

Proposal

Standards

mail-send could implement OpenPGP support by following these Internet Message Format standards:

There are two strong libraries that have implemented RFC 4880: rpgp and sequoia.

*Comparison of OpenPGP vs. PGP/MIME and a table of major email client support for them

Implementation

The loading of a public PGP key could be done in a similar syntax to the implementation of DKIM in this crate:

// Set up DKIM signer
let pgp_key = PGP::from_pkcs1_asc_file("./key.asc")
    .unwrap()

^ I'm not very familiar with PGP so not sure if any other inputs besides the file path should be provided

// Build a encrypted text message with a single attachment
let message = MessageBuilder::new()
    .from(("John Doe", "john@example.com"))
    .to("jane@example.com")
    .subject("Howdy!")
    .text_body("These pretzels are making me thirsty.")
    .binary_attachment("image/png", "pretzels.png", [1, 2, 3, 4].as_ref())
    .pgp_encrypt(pgp_key);

Interested to hear your thoughts! Let me know if this issue would be better suited at mail-builder.

mdecimus commented 2 years ago

Thank you for the suggestion. At the moment I am writing a mail server in Rust so I won't be able to work on adding PGP right away. However, OpenPGP is a feature that I was definitely planning to add to the library (and also the server) so I'll keep this issue open. Hopefully I'll be able to work on this in the next few weeks.

soywod commented 1 year ago

I think I can help!

I have a project that allows users to compile MML-based email templates into MIME Messages using your awesome mail-builder. Users can encrypt and sign parts using PGP shell commands (mostly used with gpg), so I already implemented the glue to add encrypted and signed parts to the builder. Recently I implemented the opposite: interpret raw MIME Messages into MML-base template, and users can decrypt and verify parts using PGP shell commands as well. So I already implemented the glue to decrypt parts (and parse new subparts) and verify parts from parsed emails (using your mail-parser).

The next task I am going to work on is to bring native PGP support using rPGP (instead of shell commands). I also plan to implement the autocrypt standard to simplify encryption/decryption process, which would allow us to use the syntax @spikecodes was proposing:

// Build a encrypted text message with a single attachment
let message = MessageBuilder::new()
    .from(("John Doe", "john@example.com"))
    .to("jane@example.com")
    .subject("Howdy!")
    .text_body("These pretzels are making me thirsty.")
    .binary_attachment("image/png", "pretzels.png", [1, 2, 3, 4].as_ref())
    // we could also add encryption/signing with PGP shell commands:
    // .pgp_encrypt_cmd(cmd)
    .pgp_encrypt(pgp_key);

Would you be interested in a PR?

(In fact it could be one PR for mail-builder (encrypt/sign) and another one for mail-parser (decrypt/verify))

mdecimus commented 1 year ago

Thanks for the help @soywod but to minimize duplicating efforts please let's discuss this again at the end of the year as I have plans to implement encryption at rest on the JMAP/IMAP servers and probably the JMAP for S/MIME draft (although S/MIME does not seem to be used by lots of people and Let's Encrypt does not even plan to support it).

soywod commented 1 year ago

To minimize duplicating efforts please let's discuss this again at the end of the year as I have plans to implement encryption at rest on the JMAP/IMAP servers and probably the JMAP for S/MIME draft

The problem is that encryption is my last task on the nlnet planning, so I have to implement sth now. Do you think we could discuss it now so we could split tasks and avoid duplicated code? This way I could develop my part now and keep your part for the end of the year.

mdecimus commented 1 year ago

Unfortunately this is a topic that I haven't researched much and I am a bit behind my nlnet deliverables to do that now. Probably it would be better to create a separate library for encrypting/decrypting emails rather than adding support to mail-builder/mail-parser. Would it make sense that you use rPGP for Himalaya and once I finish my nlnet part we see how we can integrate the code? None of my tasks involve encryption so there won't be any overlap. Adding encryption is something that I plan to do once I finish with all the nlnet deliverables.

soywod commented 1 year ago

Would it make sense that you use rPGP for Himalaya and once I finish my nlnet part we see how we can integrate the code?

Yes it makes sense, let's go for it. Let see at the end of the year how we could put code in common.

soywod commented 4 months ago

let's discuss this again at the end of the year

Is it still sth you are interested in? Since our last discussion, I developed PGP operations based on native rPGP and on GPG lib. I would be glad to integrate it to your libs.

mdecimus commented 4 months ago

Hi @soywod

Yes, OpenPGP support would be nice to have. We just need to make sure that the license of those libraries is compatible with the MIT/Apache license of mail-send. Also, regarding rpgp, I was using this library in Stalwart and had to switch to sequoia (AGPL license) because the emails signed with rpgp could not be opened in the latest versions of Thunderbird. I forgot what it was exactly but it was related to rpgp using a certificate of the chain that had a flag indicating that it could not be used to sign messages.

soywod commented 4 months ago

Also, regarding rpgp, I was using this library in Stalwart and had to switch to sequoia (AGPL license) because the emails signed with rpgp could not be opened in the latest versions of Thunderbird.

Let me try from my side, I will let you know.

Why this issue was opened on mail-send? I would say that mail-builder for encrypting/signing and mail-parser for decrypting/verifying are better candidates.

mdecimus commented 4 months ago

Let me try from my side, I will let you know.

I think there was an open issue in the rpgp repo about this and back then it seemed like it was not a priority to fix it.

Why this issue was opened on mail-send?

I agree that it should be in mail-builder.

soywod commented 4 months ago

Are you referencing this issue? https://github.com/rpgp/rpgp/issues/184#issuecomment-1709814491

mdecimus commented 4 months ago

Are you referencing this issue? https://github.com/rpgp/rpgp/issues/184#issuecomment-1709814491

Yes, that's the one. It looks like it's not a priority for them but it makes the crate unusable. At least not with Thunderbird.

soywod commented 4 months ago

Here how I set up my tests:

I generated 2 pair keys: one using GPG and one using pgp-lib (the rPGP wrapper I implemented for Himalaya). I added secret keys to Thunderbird, and published public keys to https://keys.openpgp.org/. I was able to send from Himalaya an encrypted mime part in a message and to decrypt it from Thunderbird:

image

I encountered few issues, but it was not related to PGP directly: the encrypted part was encoded to base64 by your builder (I was able to force it to 7bit), and the ASCII-armored message was missing carriage returns. But overall it works! The difference with your previous implementation is that I manually select the key for encryption:

https://git.sr.ht/~soywod/pimalaya/tree/master/item/pgp/src/encrypt.rs#L99

If I remember well, I took this from Delta Chat.

I will quickly test signing, but should work as expected as well.

Would you like me to propose PRs based on pgp-lib for mail-builder and mail-parser, and discuss there?

mdecimus commented 4 months ago

I generated 2 pair keys: one using GPG and one using pgp-lib (the rPGP wrapper I implemented for Himalaya). I added secret keys to Thunderbird, and published public keys to https://keys.openpgp.org/. I was able to send from Himalaya an encrypted mime part in a message and to decrypt it from Thunderbird:

Can you try generating the keys with the latest Thunderbird and then encrypting the message using those keys? I think the problem was that Thunderbird was including certificates marked as "not for encryption" and rPGP was using them anyway. I remember that it was possible to read that flag but what was not possible is to tell rpgp which certs to use when signing.

Would you like me to propose PRs based on pgp-lib for mail-builder and mail-parser, and discuss there?

Sure, but first we need to make sure that rpgp works. I had to drop it because of the issue mentioned before. Also, I took a quick look at pgp-lib and it is async, both mail-parser and mail-parser are sync.

soywod commented 4 months ago

I generated a new key pair using Thunderbird, downloaded the pub key, uploaded it to key.openpgp.org then sent an encrypted message using Himalaya:

image

Is there any scenario you have in mind to test before opening PRs?

Also, I took a quick look at pgp-lib and it is async, both mail-parser and mail-parser are sync.

I can separate sync and not sync APIs in different modules, or use sth like https://docs.rs/maybe-async/latest/maybe_async/.

soywod commented 4 months ago

I have issues with signing, not sure if it comes from rpgp or if it comes from the way I sign MIME parts.

soywod commented 4 months ago

I have issues with signing, not sure if it comes from rpgp or if it comes from the way I sign MIME parts.

Ok I made it work, it was an issue with micalg, looks like Thunderbird does not appreciate SHA1, I switched to SHA256 and signing works.

image

EDIT: I also needed to select the right secret key for signing, see cd2b2.

mdecimus commented 4 months ago

I am going to retest this then.