如何在 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(())
}
有这么多箱子可用,我怀疑用于编写加密 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()
.
这里是来自 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(())
}