Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support PGP #963

Open
ilka-schulz opened this issue May 16, 2024 · 2 comments
Open

support PGP #963

ilka-schulz opened this issue May 16, 2024 · 2 comments

Comments

@ilka-schulz
Copy link
Contributor

Is your feature request related to a problem? Please describe.
I want to send PGP-signed / PGP-encrypted emails. GnuPG is already set up and I use it with my main mail client. Now, I want to use it with lettre.

Describe the solution you'd like

  • add support for signing messages with PGP
  • add support for encrypting messages with PGP
  • add example using the new PGP feature

Describe alternatives you've considered
read into PGP specification, call gnupg myself, create the message body myself and hand the custom message body to lettre

Additional context

@ilka-schulz
Copy link
Contributor Author

ilka-schulz commented May 16, 2024

I am trying to implement sth. by calling gnupg myself:

// define content

    let content: Vec<u8> = Vec::from("Hello World!".as_bytes());

    let single_part = lettre::message::SinglePartBuilder::new()
        .content_type(lettre::message::header::ContentType::TEXT_PLAIN)
        .header(lettre::message::header::ContentTransferEncoding::Base64)
        .body(content.clone());

// create signature

    let mut ctx = gpgme::Context::from_protocol(gpgme::Protocol::OpenPgp).unwrap();
    ctx.set_armor(true);
    let key = ctx
        .get_secret_key("2F41D7EE4EC511199A5468D645ED94248268D858")
        .unwrap();
    ctx.add_signer(&key).unwrap();
    let mut sign_output: Vec<u8> = Vec::new();
    let mut sign_input: Vec<u8> =
    // Vec::from(single_part.formatted());  // base64-encoded content plus headers
    // Vec::from(single_part.raw_body());  // base-64-encoded content
    content.clone();  // raw content
    println!(
        "sign_input: >{}<",
        String::from_utf8(sign_input.clone()).unwrap()
    );
    let sign_result = ctx
        .sign(gpgme::SignMode::Detached, &mut sign_input, &mut sign_output)
        .unwrap();
    let hash_alg = sign_result
        .new_signatures()
        .next()
        .unwrap()
        .hash_algorithm();

// create message

    let message = lettre::message::MessageBuilder::new()
        .from(lettre::message::Mailbox::new(
            Some("Dummy".to_string()),
            lettre::Address::new("dummy", "example.com").unwrap(),
        ))
        .to(lettre::message::Mailbox::new(
            Some("Ilka".to_string()),
            lettre::Address::new("ilka", "example.com").unwrap(),
        ))
        .multipart(
            lettre::message::MultiPart::signed(
                "application/pgp-signature".to_string(),
                format!("pgp-{}", hash_alg.to_string().to_lowercase()),
            )
            .singlepart(single_part)
            .singlepart(
                lettre::message::Attachment::new("signature.asc".to_string()).body(
                    sign_output,
                    lettre::message::header::ContentType::from_str("application/pgp-signature")
                        .unwrap(),
                ),
            ),
        )
        .unwrap();

The resulting message looks promising (this is the "source code" of the message received with Thunderbird from the target mail service, I removed the signature content):

From: Dummy <[email protected]>
To: Ilka <[email protected]>
MIME-Version: 1.0
Date: Thu, 16 May 2024 14:11:21 +0000
Content-Type: multipart/signed;
 boundary="F4npC45WD9WFiNnA2oYmqSLfBF7A25bIzlMzdX4G";
 protocol="application/pgp-signature"; micalg="pgp-sha512"

--F4npC45WD9WFiNnA2oYmqSLfBF7A25bIzlMzdX4G
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: base64

SGVsbG8gV29ybGQh
--F4npC45WD9WFiNnA2oYmqSLfBF7A25bIzlMzdX4G
Content-Disposition: attachment; filename="signature.asc"
Content-Type: application/pgp-signature
Content-Transfer-Encoding: 7bit

-----BEGIN PGP SIGNATURE-----

<removed: some base64-encoded content>
-----END PGP SIGNATURE-----

--F4npC45WD9WFiNnA2oYmqSLfBF7A25bIzlMzdX4G--

Unfortunately, the resulting signature does not seem to be valid. Regardless which sign_input I choose above, Thunderbird always fails to verify the signature.

I also tried GPGME_SIG_MODE_CLEAR which works fine. However, this is not recommended and I could not get it to work with attachments.

@ilka-schulz
Copy link
Contributor Author

ilka-schulz commented May 16, 2024

I got it working after I read through RFC 3156:

  1. prepare the entire single_part: SinglePart
  2. use this as the input for gpgme: Vec::from(single_part.formatted().trim_ascii_end())
  3. create the signature with gpgme and GPGME_SIG_MODE_DETACH
  4. create a multipart message with type lettre::message::MultiPart::signed

minimum working example: https://gitlab.com/islabtech/examples/rust-pgp-signed-mail

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

1 participant