如何在 Rust 中编写 S/MIME 加密消息?

How to compose a S/MIME encrypted message in Rust?

有这么多箱子可用,我怀疑用于编写加密 S/MIME 消息的箱子会丢失。我知道 pgp which should handle PGP/MIME. I'm also aware of lettre_email emailmessage and mailparse mail-core 可用于编写 MIME 电子邮件...

如果没有,我想问一下是否有人已经这样做了,这样我就可以复制 cat 并可能发布。否则我会自己与它作斗争,并且会很感激一个良好的开端。

目标是对存储在邮件服务器上的静态消息进行加密 Samotop。知道收件人的 public 密钥,我应该能够为该收件人包装加密任何传入的消息,以便只有拥有该密钥的用户才能解密该消息。很可能 S/MIME 不适合这个,但我希望在 S/MIME 支持的情况下将其用于现有的电子邮件客户端。

首先,我想会有一个加密消息的对称密钥,这个密钥将使用非对称密钥为收件人(可能为多个收件人)加密并包含在有效负载中。这里有一个sketch of my ideas.

生成随机对称密钥:

    let mut key = [0u8; 32];
    SystemRandom::new().fill(&mut key).unwrap();

内容被加密:

    // Sealing key used to encrypt data
    let mut sealing_key = SealingKey::new(
        UnboundKey::new(&CHACHA20_POLY1305, key.as_ref()).unwrap(),
        Nonces::new(5),
    );

    // Encrypt data into in_out variable
    sealing_key
        .seal_in_place_append_tag(Aad::empty(), &mut encrypted)
        .unwrap();

对称密钥为收件人进行非对称加密:

    let enc_key = pub_key.encrypt(&mut rng, PaddingScheme::new_pkcs1v15_encrypt(), &key[..]).expect("failed to encrypt");
    assert_ne!(&key[..], &enc_key[..]);

现在是编写加密的 MIME 部分的时候了……想法?板条箱?参考实现? rfc8551

目前我找到的唯一现成的解决方案是 openssl binding。它有一个 Pkcs7 结构,应该能够 encrypt()sign() 并使用 to_smime().

生成 mime 部分

这里是来自 openssl 存储库的 test 稍作修改:

        let cert = X509::from_pem(CERT)?;
        let mut certs = Stack::new()?;
        certs.push(cert.clone())?;

        let flags = Pkcs7Flags::STREAM;
        let message = b"secret stuff";

        let pkcs7 = Pkcs7::encrypt(&certs.as_ref(), message, Cipher::aes_256_cbc(), flags)?;

        let encrypted = pkcs7.to_smime(message, flags).expect("should succeed");

figured this much. If I have to depend on openssl, I might just as well depend on it externally, running it as a child process. This will enable async io streaming as a bonus which doesn't seem to be supported by rust bindings (yet?).

fn main() -> Result<(), Box<dyn std::error::Error>> {
    async_std::task::block_on(main_fut())
}

async fn main_fut() -> Result<(), Box<dyn std::error::Error>> {
    let mut sign = async_process::Command::new("openssl")
        .arg("smime")
        .arg("-stream")
        .arg("-in")
        .arg("test/msg")
        .arg("-sign")
        .arg("-inkey")
        .arg("test/my.key")
        .arg("-signer")
        .arg("test/my.crt")
        .kill_on_drop(true)
        .reap_on_drop(true)
        .stdout(async_process::Stdio::piped())
        .spawn()?;

    let mut encrypt = async_process::Command::new("openssl")
        .arg("smime")
        .arg("-stream")
        .arg("-out")
        .arg("test/enc")
        .arg("-encrypt")
        .arg("test/her.crt")
        .kill_on_drop(true)
        .reap_on_drop(true)
        .stdin(async_process::Stdio::piped())
        .spawn()?;

    let pipe = async_std::io::copy(
        sign.stdout.as_mut().expect("sign output"),
        encrypt.stdin.as_mut().expect("encrypt input"),
    );

    pipe.await?;

    println!("sign: {:?}", sign.status().await);
    println!("encrypt: {:?}", encrypt.status().await);

    Ok(())
}