ceal 0.1.0

Opportunistic E-Mail Encryption - Stand-Alone Library and Extensions for Lettre
Documentation
//! Opportunistic Encryption for E-Mail
//!
//! This library can query the S/MIME and OpenPGP keys from the recipient's DNS record
//! and use it to encrypt messages. The keys are stored in the [SMIMEA] or [OPENPGPKEY]
//! records respectively. All recipients must have at least one of the same record.
//! Unfortunately, E-Mail programs are not very good when presented with a choice between
//! two different encryption methods within the same message. If encryption is not
//! possible due to lack of keys (of the same type), the message is sent unencrypted.
//!
//! You can use this as a stand-alone library or as an extension to [`lettre`].
//!
//! ## Example Usage: Stand-Alone library
//!
//! You can use this library by providing raw messages to it, like this:
//!
//! ```
//! use email_address::EmailAddress;
#![cfg_attr(feature = "tokio", doc = "# #[tokio::main]")]
//! # async fn main() {
//! let message = b"From: John Doe <john.doe@example.org>\r
//! To: Max Mustermann <max.mustermann@example.org>\r
//! Subject: Hello\r
//! Content-Type: text/plain; charset=\"utf-8\"\r
//! Content-Transfer-Encoding: 7bit\r
//! \r
//! Hello, how are you?\r
//! ";
//!
//! let recipients = ["max.mustermann@example.org".parse::<EmailAddress>().unwrap()];
//! let message = ceal::encrypt(message, &recipients).await;
//! # }
//! ```
//! 
//! If an encryption key was found, the result will look something like this:
//! ```eml
//! From: John Doe <john.doe@example.org>
//! To: Max Mustermann <max.mustermann@example.org>
//! Subject: Hello
//! Content-Type: multipart/encrypted; protocol="application/pgp-encrypted";
//!  boundary="=-fRbE3wtPT1u0rEVI"
//!
//! --=-fRbE3wtPT1u0rEVI
//! Content-Type: application/pgp-encrypted
//! Content-Transfer-Encoding: 7bit
//!
//! Version: 1
//!
//! --=-fRbE3wtPT1u0rEVI
//! Content-Type: application/octet-stream; name="encrypted.asc"
//! Content-Description: OpenPGP Encrypted Message
//! Content-Transfer-Encoding: 7bit
//!
//! -----BEGIN PGP MESSAGE-----
//!
//! wWwGFQQAaKICkqGFdPWZFLNGsneBbdOVVBIBB0DS2mtKK0qe7UPwhrxFKUFzywC6
//! yaXOKdNnIb1fHPM7eDBttgL9Injkz237nD98dwvdF4RD3dLbLGoQtw9nufbgyihf
//! mRr7zUVHdZf8hjpkRVfSrwIJAgaO3vLYxhj3lMiOCgDda12W0bJJl9clRwXEOnlh
//! m1duZv2Wxlx+ig1WRJuSYbKIUKg9MrX1mqsVdlF5DdNkEH2ywCmI1UTXBYzSKQEv
//! 1h/+77iozcYqiJfV74TuWM9AT3YrNPA0SZnFFkttXV0vhsuvi72DbTTq9sieqH13
//! IH9FiCin5kefw5y4/MeIFqJXbfKzgP+8z4tXYwAQmrqskT662PndfvzbJSJ+4LQ=
//! -----END PGP MESSAGE-----
//!
//! --=-fRbE3wtPT1u0rEVI--
//! ```
//! 
//! ## Example Usage: [`lettre`] transport
//!
//! First, you need to enable the `lettre` feature of this crate. Then you may use it
//! to create a [`lettre`] transport that applies opportunistic encryption just before
//! sending the message:
//! ```
//! use lettre::{
//! 	AsyncTransport, Message, SmtpTransport, Tokio1Executor,
//! 	message::SinglePart,
//! 	transport::smtp::{AsyncSmtpTransport, client::{Tls, TlsParameters}}
//! };
//! use ceal::lettre::AsyncTransportExt as _;
//!
//! # async fn async_main() {
//! let email = Message::builder()
//! 	.from("John Doe <john.doe@example.org>".parse().unwrap())
//! 	.to("Max Mustermann <max.mustermann@example.org>".parse().unwrap())
//! 	.subject("Hello")
//! 	.singlepart(SinglePart::plain(String::from("Hello, how are you?")))
//! 	.unwrap();
//!
//! let mailer = AsyncSmtpTransport::<Tokio1Executor>::relay("mail.example.org")
//! 	.unwrap()
//! 	.tls(Tls::Required(TlsParameters::new("mail.example.org".into()).unwrap()))
//! 	.build()
//! 	.with_opportunistic_encryption();
//!
//! mailer.send(email).await.expect("Could not send E-Mail");
//! # }
//! ```
//! 
//!
//!  [SMIMEA]: https://datatracker.ietf.org/doc/html/rfc8162#section-2
//!  [OPENPGPKEY]: https://datatracker.ietf.org/doc/html/rfc7929#section-2

pub mod address;
pub mod dns;
#[cfg(feature = "lettre")]
pub mod lettre;
mod message;
pub mod openpgp;
pub mod smime;

use self::address::Address;
use openssl::x509::X509;
use std::borrow::Cow;

#[cfg(not(any(feature = "tokio")))]
compile_error!(
	"No runtime provider enabled. You must enable one of the following features: tokio"
);

#[cfg(not(any(feature = "aws-lc-rs", feature = "ring")))]
compile_error!(
	"No crypto provider enabled. You must enable one of the following features: aws-lc-rs, ring"
);

/// Apply opportunistic encryption to this message.
///
/// This will query the DANE DNS records (SMIMEA and OPENPGPKEY) for each recipient
/// of this message. If all recipients have at least one record of the same type,
/// the message is encrypted for all recipients.
///
/// The chance of finding suitable keys is thus increased the less recipients there
/// are. It is therefore advisable to create one message per recipient.
///
/// If encryption fails, the message will be returned unencrypted.
pub async fn encrypt<'m, A: Address>(msg: &'m [u8], recipients: &[A]) -> Cow<'m, [u8]> {
	if let Ok(smime_certs) = smime::find_certs_for_all_recipients(recipients).await {
		return encrypt_smime(msg, smime_certs);
	}

	if let Ok(openpgp_certs) = openpgp::find_certs_for_all_recipients(recipients).await {
		return encrypt_openpgp(msg, openpgp_certs);
	}

	msg.into()
}

/// Encrypt the message using the provided S/MIME certificates.
pub fn encrypt_smime(msg: &[u8], recipient_certs: Vec<X509>) -> Cow<'_, [u8]> {
	smime::encrypt(msg, recipient_certs)
}

/// Encrypt the message using the provided OpenPGP certificates.
pub fn encrypt_openpgp(
	msg: &[u8],
	recipient_certs: Vec<sequoia_openpgp::Cert>
) -> Cow<'_, [u8]> {
	openpgp::encrypt(msg, recipient_certs)
}