use crate::error::{Error, Result};
pub fn backend_version() -> String {
rnp::version_string()
}
pub fn new_context() -> Result<rnp::Context> {
rnp::Context::new().map_err(rnp_err)
}
pub fn sign_inline(ctx: &rnp::Context, msg: &[u8], signing_key: &rnp::Key<'_>) -> Result<Vec<u8>> {
rnp::sign(ctx, msg, signing_key).map_err(rnp_err)
}
pub fn verify_inline(ctx: &rnp::Context, signed_msg: &[u8]) -> Result<()> {
let result = rnp::verify(ctx, signed_msg).map_err(rnp_err)?;
if result.any_valid().map_err(rnp_err)? {
Ok(())
} else {
Err(Error::SignatureVerify {
key_id: "OpenPGP inline signature".to_string(),
})
}
}
fn rnp_err(e: rnp::Error) -> Error {
Error::Botan(format!("OpenPGP (rnp): {e}"))
}
pub fn wrap_cek_to_pubkeys(pub_armors: &[String], cek: &[u8]) -> Result<Vec<(String, String)>> {
use rnp::key::LoadSaveFlags;
use rnp::{Encryptor, Output};
let mut out = Vec::new();
for armor in pub_armors {
let ctx = new_context()?;
ctx.load_keys(
rnp::KeyringFormat::Gpg,
armor.as_bytes(),
LoadSaveFlags::PUBLIC,
)
.map_err(rnp_err)?;
for id in ctx
.identifiers(rnp::IdentifierKind::Keyid)
.map_err(rnp_err)?
{
let key = match ctx.find_key(rnp::KeyIdentifier::Keyid(&id)) {
Ok(Some(k)) => k,
_ => continue,
};
let fp = key.fingerprint().map_err(rnp_err)?;
let mut ct = Output::to_memory().map_err(rnp_err)?;
Encryptor::new(&ctx, cek)
.map_err(rnp_err)?
.add_recipient(&key)
.build(&mut ct)
.map_err(rnp_err)?;
let bytes = ct.into_bytes().map_err(rnp_err)?;
out.push((
fp[fp.len().saturating_sub(16)..].to_string(),
crate::utils::base64_encode(&bytes)?,
));
}
}
if out.is_empty() {
return Err(Error::Botan(
"OpenPGP (rnp): no keys found in --pgp-pubkey armor".into(),
));
}
Ok(out)
}
pub fn unwrap_cek(msg_b64: &str, secret_armor: &str) -> Result<Vec<u8>> {
use rnp::Decryptor;
use rnp::key::LoadSaveFlags;
let msg = crate::utils::base64_decode(msg_b64)?;
let ctx = new_context()?;
ctx.load_keys(
rnp::KeyringFormat::Gpg,
secret_armor.as_bytes(),
LoadSaveFlags::SECRET,
)
.map_err(rnp_err)?;
let result = Decryptor::new(&ctx, &msg).build().map_err(rnp_err)?;
Ok(result.into_plaintext())
}
pub fn is_armored(text: &str) -> bool {
text.contains("-----BEGIN PGP")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sign_verify_round_trip() {
let ctx = match new_context() {
Ok(c) => c,
Err(e) => {
eprintln!("skipping: rnp init failed: {e}");
return;
}
};
let key = rnp::KeyBuilder::new(rnp::Algorithm::Eddsa)
.userid("enprot-test <test@example.com>")
.add_usage(rnp::KeyUsage::Sign)
.add_usage(rnp::KeyUsage::Certify)
.build(&ctx)
.expect("keygen");
let msg = b"hello, openpgp";
let signed = sign_inline(&ctx, msg, &key).expect("sign");
assert!(!signed.is_empty());
verify_inline(&ctx, &signed).expect("verify");
}
#[test]
fn backend_version_returns_nonempty() {
let v = backend_version();
assert!(!v.is_empty(), "version: {v}");
}
}