use libpep::contexts::{EncryptionContext, PseudonymizationDomain};
use libpep::elgamal::arithmetic::group_elements::GroupElement;
use libpep::elgamal::arithmetic::scalars::ScalarNonZero;
use libpep::elgamal::ElGamal;
use libpep::factors::{EncryptionSecret, PseudonymizationSecret};
use libpep::keys::{PublicKey, SecretKey};
use std::fmt;
use std::io::Read;
pub const EXIT_INPUT: i32 = 1;
#[cfg_attr(not(feature = "elgamal3"), allow(dead_code))]
pub const EXIT_KEY_MISMATCH: i32 = 2;
#[derive(Debug)]
pub struct Error {
pub message: String,
pub code: i32,
}
impl Error {
pub fn input(message: impl Into<String>) -> Self {
Self {
message: message.into(),
code: EXIT_INPUT,
}
}
#[cfg_attr(not(feature = "elgamal3"), allow(dead_code))]
pub fn key_mismatch() -> Self {
Self {
message: "decryption failed: the key does not match the ciphertext".into(),
code: EXIT_KEY_MISMATCH,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.message)
}
}
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Self {
Error::input(e.to_string())
}
}
pub type Result<T> = std::result::Result<T, Error>;
pub fn read_arg(raw: &str) -> Result<String> {
if raw == "-" {
let mut buf = String::new();
std::io::stdin().read_to_string(&mut buf)?;
Ok(buf.trim().to_string())
} else if let Some(path) = raw.strip_prefix('@') {
let text = std::fs::read_to_string(path)
.map_err(|e| Error::input(format!("cannot read {path}: {e}")))?;
Ok(text.trim().to_string())
} else {
Ok(raw.to_string())
}
}
pub fn read_values(raw: &[String]) -> Result<Vec<String>> {
let mut out = Vec::new();
for r in raw {
let v = read_arg(r)?;
out.extend(v.split_whitespace().map(str::to_string));
}
if out.is_empty() {
return Err(Error::input("no value given"));
}
Ok(out)
}
pub fn scalar(raw: &str, what: &str) -> Result<ScalarNonZero> {
let s = read_arg(raw)?;
ScalarNonZero::from_hex(&s).ok_or_else(|| {
Error::input(format!(
"{what}: expected a non-zero scalar as 64 hex digits"
))
})
}
pub fn point(raw: &str, what: &str) -> Result<GroupElement> {
let s = read_arg(raw)?;
GroupElement::from_hex(&s)
.ok_or_else(|| Error::input(format!("{what}: expected a group element as 64 hex digits")))
}
pub fn ciphertext(raw: &str) -> Result<ElGamal> {
let s = read_arg(raw)?;
ElGamal::from_base64(&s)
.ok_or_else(|| Error::input("ciphertext: expected a base64-encoded ElGamal ciphertext"))
}
pub fn public_key<K: PublicKey>(raw: &str, what: &str) -> Result<K> {
let s = read_arg(raw)?;
K::from_hex(&s)
.ok_or_else(|| Error::input(format!("{what}: expected a public key as 64 hex digits")))
}
pub fn secret_key<K: SecretKey>(raw: &str, what: &str) -> Result<K> {
Ok(K::from_scalar(scalar(raw, what)?))
}
pub fn pseudonymization_secret(raw: &str) -> Result<PseudonymizationSecret> {
Ok(PseudonymizationSecret::from(read_arg(raw)?.into_bytes()))
}
pub fn encryption_secret(raw: &str) -> Result<EncryptionSecret> {
Ok(EncryptionSecret::from(read_arg(raw)?.into_bytes()))
}
pub fn domain(raw: &str) -> Result<PseudonymizationDomain> {
Ok(PseudonymizationDomain::from(read_arg(raw)?.as_str()))
}
pub fn context(raw: Option<&str>) -> Result<EncryptionContext> {
match raw {
Some(r) => Ok(EncryptionContext::from(read_arg(r)?.as_str())),
None => Ok(EncryptionContext::global()),
}
}
pub struct Output {
json: bool,
fields: Vec<(String, serde_json::Value)>,
}
impl Output {
pub fn new(json: bool) -> Self {
Self {
json,
fields: Vec::new(),
}
}
#[cfg_attr(not(feature = "json"), allow(dead_code))]
pub fn json(&self) -> bool {
self.json
}
pub fn value(&mut self, key: &str, value: impl Into<serde_json::Value>) {
let value = value.into();
if !self.json {
eprint!("{}: ", key.replace('_', " "));
match &value {
serde_json::Value::String(s) => println!("{s}"),
serde_json::Value::Array(items) => println!(
"{}",
items
.iter()
.map(|v| v
.as_str()
.map(str::to_string)
.unwrap_or_else(|| v.to_string()))
.collect::<Vec<_>>()
.join(" ")
),
other => println!("{other}"),
}
}
self.fields.push((key.to_string(), value));
}
pub fn note(&self, message: &str) {
eprintln!("{message}");
}
pub fn finish(self) {
if self.json {
let object: serde_json::Map<String, serde_json::Value> =
self.fields.into_iter().collect();
println!(
"{}",
serde_json::to_string_pretty(&serde_json::Value::Object(object))
.expect("output values are serializable")
);
}
}
}