use crate::generate_error::{ProofModeError, Result};
#[cfg(feature = "sequoia-openpgp")]
mod sequoia_impl {
use super::*;
use openpgp::crypto::Password;
use openpgp::parse::{stream::*, Parse};
use openpgp::policy::StandardPolicy;
use openpgp::serialize::stream::{Armorer, Message, Signer};
use openpgp::serialize::Serialize;
use openpgp::types::HashAlgorithm;
use openpgp::Cert;
use sequoia_openpgp as openpgp;
use std::io::Write;
pub struct PgpUtils {
cert: Option<Cert>,
}
impl Default for PgpUtils {
fn default() -> Self {
Self::new()
}
}
impl PgpUtils {
pub fn new() -> Self {
Self { cert: None }
}
pub fn generate_keys(&mut self, email: &str, passphrase: &str) -> Result<()> {
let (cert, _) = openpgp::cert::CertBuilder::new()
.add_userid(email)
.set_creation_time(std::time::SystemTime::now())
.add_signing_subkey()
.set_password(Some(passphrase.into()))
.generate()
.map_err(|e| ProofModeError::Pgp(e.to_string()))?;
self.cert = Some(cert);
Ok(())
}
pub fn get_public_key_string(&self) -> Result<String> {
match &self.cert {
Some(cert) => {
let mut buf = Vec::new();
cert.as_tsk()
.armored()
.serialize(&mut buf)
.map_err(|e| ProofModeError::Pgp(e.to_string()))?;
Ok(String::from_utf8_lossy(&buf).to_string())
}
None => Err(ProofModeError::Crypto(
"No certificate available".to_string(),
)),
}
}
pub fn sign_data(&self, data: &[u8], passphrase: &str) -> Result<Vec<u8>> {
match &self.cert {
Some(cert) => {
let mut output = Vec::new();
let policy = StandardPolicy::new();
let signing_keypair = cert
.keys()
.secret()
.with_policy(&policy, None)
.supported()
.for_signing()
.alive()
.revoked(false)
.next()
.ok_or_else(|| {
ProofModeError::Crypto("No signing key available".to_string())
})?;
let key = signing_keypair.key();
let keypair = key
.clone()
.decrypt_secret(&Password::from(passphrase))
.map_err(|e| ProofModeError::Pgp(e.to_string()))?
.into_keypair()
.map_err(|e| ProofModeError::Pgp(e.to_string()))?;
{
let message = Message::new(&mut output);
let message = Armorer::new(message)
.kind(openpgp::armor::Kind::Signature)
.build()
.map_err(|e| ProofModeError::Pgp(e.to_string()))?;
let mut signer = Signer::new(message, keypair)
.map_err(|e: anyhow::Error| ProofModeError::Pgp(e.to_string()))?
.detached()
.hash_algo(HashAlgorithm::SHA256)
.map_err(|e: anyhow::Error| ProofModeError::Pgp(e.to_string()))?
.build()
.map_err(|e: anyhow::Error| ProofModeError::Pgp(e.to_string()))?;
signer
.write_all(data)
.map_err(|e: std::io::Error| ProofModeError::Io(e.to_string()))?;
signer
.finalize()
.map_err(|e: anyhow::Error| ProofModeError::Pgp(e.to_string()))?;
}
Ok(output)
}
None => Err(ProofModeError::Crypto(
"No certificate available".to_string(),
)),
}
}
pub fn verify_signature(&self, data: &[u8], signature: &[u8]) -> Result<bool> {
match &self.cert {
Some(cert) => {
use std::io::Cursor;
struct Helper<'a> {
cert: &'a Cert,
}
impl VerificationHelper for Helper<'_> {
fn get_certs(
&mut self,
_ids: &[openpgp::KeyHandle],
) -> openpgp::Result<Vec<Cert>> {
Ok(vec![self.cert.clone()])
}
fn check(&mut self, structure: MessageStructure) -> openpgp::Result<()> {
use openpgp::parse::stream::MessageLayer;
for layer in structure.iter() {
if let MessageLayer::SignatureGroup { results } = layer {
for result in results {
if result.is_ok() {
return Ok(());
}
}
}
}
Err(anyhow::anyhow!("No valid signatures found"))
}
}
let policy = StandardPolicy::new();
let helper = Helper { cert };
let signature_reader = Cursor::new(signature);
let data_reader = Cursor::new(data);
openpgp::parse::stream::DetachedVerifierBuilder::from_reader(signature_reader)
.map_err(|e| ProofModeError::Pgp(e.to_string()))?
.with_policy(&policy, None, helper)
.map_err(|e| ProofModeError::Pgp(e.to_string()))?
.verify_reader(data_reader)
.map_err(|e| ProofModeError::Pgp(e.to_string()))?;
Ok(true)
}
None => Err(ProofModeError::Crypto(
"No certificate available".to_string(),
)),
}
}
pub fn load_keys(&mut self, secret_key_armor: &str, _passphrase: &str) -> Result<()> {
let cert = Cert::from_reader(secret_key_armor.as_bytes())
.map_err(|e| ProofModeError::Pgp(e.to_string()))?;
self.cert = Some(cert);
Ok(())
}
}
}
#[cfg(feature = "sequoia-openpgp")]
pub use sequoia_impl::PgpUtils;
#[cfg(not(feature = "sequoia-openpgp"))]
pub struct PgpUtils;
#[cfg(not(feature = "sequoia-openpgp"))]
impl PgpUtils {
pub fn new() -> Self {
Self
}
pub fn generate_keys(&mut self, _email: &str, _passphrase: &str) -> Result<()> {
Err(ProofModeError::Crypto(
"PGP functionality not available in WASM builds".to_string(),
))
}
pub fn get_public_key_string(&self) -> Result<String> {
Err(ProofModeError::Crypto(
"PGP functionality not available in WASM builds".to_string(),
))
}
pub fn sign_data(&self, _data: &[u8], _passphrase: &str) -> Result<Vec<u8>> {
Err(ProofModeError::Crypto(
"PGP functionality not available in WASM builds".to_string(),
))
}
pub fn verify_signature(&self, _data: &[u8], _signature: &[u8]) -> Result<bool> {
Err(ProofModeError::Crypto(
"PGP functionality not available in WASM builds".to_string(),
))
}
pub fn load_keys(&mut self, _secret_key_armor: &str, _passphrase: &str) -> Result<()> {
Err(ProofModeError::Crypto(
"PGP functionality not available in WASM builds".to_string(),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pgp_utils_new() {
let pgp = PgpUtils::new();
assert!(true);
}
#[cfg(feature = "sequoia-openpgp")]
#[test]
fn test_generate_keys() {
let mut pgp = PgpUtils::new();
let result = pgp.generate_keys("test@example.com", "testpassword");
assert!(result.is_ok());
}
#[cfg(feature = "sequoia-openpgp")]
#[test]
fn test_get_public_key_string_without_keys() {
let pgp = PgpUtils::new();
let result = pgp.get_public_key_string();
assert!(result.is_err());
}
#[cfg(feature = "sequoia-openpgp")]
#[test]
fn test_sign_data_without_keys() {
let pgp = PgpUtils::new();
let data = b"test data";
let result = pgp.sign_data(data, "password");
assert!(result.is_err());
}
#[cfg(feature = "sequoia-openpgp")]
#[test]
fn test_full_pgp_workflow() {
let mut pgp = PgpUtils::new();
pgp.generate_keys("test@example.com", "testpassword")
.unwrap();
let public_key = pgp.get_public_key_string();
assert!(public_key.is_ok());
let public_key = public_key.unwrap();
assert!(public_key.contains("-----BEGIN PGP"));
let data = b"test message to sign";
let signature = pgp.sign_data(data, "testpassword");
assert!(signature.is_ok());
let signature = signature.unwrap();
assert!(!signature.is_empty());
let verification = pgp.verify_signature(data, &signature);
assert!(verification.is_ok());
assert!(verification.unwrap());
}
#[cfg(not(feature = "sequoia-openpgp"))]
#[test]
fn test_wasm_pgp_fallback() {
let mut pgp = PgpUtils::new();
assert!(pgp.generate_keys("test@example.com", "password").is_err());
assert!(pgp.get_public_key_string().is_err());
assert!(pgp.sign_data(b"data", "password").is_err());
assert!(pgp.verify_signature(b"data", b"signature").is_err());
assert!(pgp.load_keys("key", "password").is_err());
}
}