use crate::io::{self, Output, Result};
use clap::Subcommand;
use libpep::data::json::{EncryptedPEPJSONValue, PEPJSONBuilder};
use libpep::data::traits::{Encryptable, Encrypted, Transcryptable};
use libpep::factors::TranscryptionInfo;
use libpep::keys::{GlobalPublicKeys, SessionKeys};
use rand_core::{CryptoRng, Rng};
#[derive(Subcommand)]
pub enum Json {
Encrypt {
#[arg(long)]
keys: String,
#[arg(long)]
global: bool,
#[arg(long = "pseudonym-field")]
pseudonym_fields: Vec<String>,
value: String,
},
Decrypt {
#[arg(long)]
keys: String,
value: String,
},
Transcrypt {
#[arg(long)]
pseudonymization_secret: String,
#[arg(long)]
encryption_secret: String,
#[arg(long)]
from_domain: String,
#[arg(long)]
to_domain: String,
#[arg(long)]
from_context: Option<String>,
#[arg(long)]
to_context: Option<String>,
value: String,
},
}
fn keys_json<T: serde::de::DeserializeOwned>(raw: &str, key: &str, what: &str) -> Result<T> {
let text = io::read_arg(raw)?;
let mut value: serde_json::Value = serde_json::from_str(&text)
.map_err(|e| io::Error::input(format!("{what}: not valid JSON: {e}")))?;
if let Some(inner) = value.get_mut(key) {
value = inner.take();
}
serde_json::from_value(value).map_err(|e| io::Error::input(format!("{what}: {e}")))
}
fn document(raw: &str) -> Result<serde_json::Value> {
let text = io::read_arg(raw)?;
serde_json::from_str(&text).map_err(|e| io::Error::input(format!("value: not valid JSON: {e}")))
}
pub fn run<R: Rng + CryptoRng>(cmd: Json, rng: &mut R, out: &mut Output) -> Result<()> {
match cmd {
Json::Encrypt {
keys,
global,
pseudonym_fields,
value,
} => {
let fields: Vec<&str> = pseudonym_fields.iter().map(String::as_str).collect();
let document = PEPJSONBuilder::from_json(&document(&value)?, &fields)
.ok_or_else(|| io::Error::input("value: a pseudonym field must hold a string"))?
.build();
let encrypted = if global {
let keys: GlobalPublicKeys = keys_json(&keys, "global_public_keys", "keys")?;
document.encrypt_global(&keys, rng)
} else {
let keys: SessionKeys = keys_json(&keys, "session_keys", "keys")?;
document.encrypt(&keys, rng)
};
out.value(
"encrypted",
serde_json::to_value(&encrypted).expect("encrypted JSON is serializable"),
);
}
Json::Decrypt { keys, value } => {
let keys: SessionKeys = keys_json(&keys, "session_keys", "keys")?;
let encrypted: EncryptedPEPJSONValue = serde_json::from_value(document(&value)?)
.map_err(|e| io::Error::input(format!("value: not an encrypted document: {e}")))?;
#[cfg(feature = "elgamal3")]
let decrypted = encrypted
.decrypt(&keys)
.ok_or_else(io::Error::key_mismatch)?;
#[cfg(not(feature = "elgamal3"))]
let decrypted = encrypted.decrypt(&keys);
let json = decrypted.to_value().map_err(|e| {
io::Error::input(format!("the document does not decode to JSON: {e}"))
})?;
out.value("document", json);
}
Json::Transcrypt {
pseudonymization_secret,
encryption_secret,
from_domain,
to_domain,
from_context,
to_context,
value,
} => {
let info = TranscryptionInfo::new(
&io::domain(&from_domain)?,
&io::domain(&to_domain)?,
&io::context(from_context.as_deref())?,
&io::context(to_context.as_deref())?,
&io::pseudonymization_secret(&pseudonymization_secret)?,
&io::encryption_secret(&encryption_secret)?,
);
let encrypted: EncryptedPEPJSONValue = serde_json::from_value(document(&value)?)
.map_err(|e| io::Error::input(format!("value: not an encrypted document: {e}")))?;
out.value(
"encrypted",
serde_json::to_value(encrypted.transcrypt(&info))
.expect("encrypted JSON is serializable"),
);
}
}
Ok(())
}