use crate::io::{self, Output, Result};
use clap::{Args, Subcommand};
use libpep::data::long::{
LongAttribute, LongEncryptedAttribute, LongEncryptedPseudonym, LongPseudonym,
};
use libpep::data::simple::{
Attribute, ElGamalEncryptable, ElGamalEncrypted, EncryptedAttribute, EncryptedPseudonym,
Pseudonym,
};
use libpep::data::traits::{Encryptable, Encrypted, Pseudonymizable, Rekeyable, Transcryptable};
use libpep::elgamal::arithmetic::scalars::ScalarNonZero;
use libpep::factors::{
AttributeRekeyFactor, AttributeRekeyInfo, PseudonymRekeyFactor, PseudonymRekeyInfo,
PseudonymizationInfo, ReshuffleFactor, TranscryptionInfo,
};
use libpep::keys::{
AttributeGlobalPublicKey, AttributeSessionPublicKey, AttributeSessionSecretKey,
PseudonymGlobalPublicKey, PseudonymSessionPublicKey, PseudonymSessionSecretKey, PublicKey,
SecretKey,
};
#[cfg(feature = "insecure")]
use libpep::keys::{AttributeGlobalSecretKey, PseudonymGlobalSecretKey};
use rand_core::{CryptoRng, Rng};
const LIZARD_BYTES: usize = 16;
const HEX_BLOCK: usize = 64;
#[derive(Subcommand)]
pub enum PseudonymCommand {
Random,
Encode(EncodeArgs),
Decode(DecodeArgs),
Encrypt(EncryptArgs),
Decrypt(DecryptArgs),
Rerandomize(RerandomizeArgs),
Rekey(RekeyArgs),
Pseudonymize(PseudonymizeArgs),
Transcrypt(TranscryptArgs),
}
#[derive(Subcommand)]
pub enum AttributeCommand {
Random,
Encode(EncodeArgs),
Decode(DecodeArgs),
Encrypt(EncryptArgs),
Decrypt(DecryptArgs),
Rerandomize(RerandomizeArgs),
Rekey(RekeyArgs),
Transcrypt(TranscryptArgs),
}
#[derive(Args)]
pub struct EncodeArgs {
text: String,
}
#[derive(Args)]
pub struct DecodeArgs {
#[arg(required = true)]
value: Vec<String>,
}
#[derive(Args)]
pub struct EncryptArgs {
#[arg(long)]
key: String,
#[arg(long)]
global: bool,
#[arg(required = true)]
value: Vec<String>,
}
#[derive(Args)]
pub struct DecryptArgs {
#[arg(long)]
key: String,
#[cfg(feature = "insecure")]
#[arg(long)]
global: bool,
#[arg(allow_hyphen_values = true)]
ciphertext: String,
}
#[derive(Args)]
pub struct RerandomizeArgs {
#[cfg(not(feature = "elgamal3"))]
#[arg(long)]
key: String,
#[arg(allow_hyphen_values = true)]
ciphertext: String,
}
#[derive(Args)]
pub struct RekeyArgs {
#[arg(long)]
k: String,
#[arg(allow_hyphen_values = true)]
ciphertext: String,
}
#[derive(Args)]
pub struct PseudonymizeArgs {
#[arg(long)]
s: String,
#[arg(long)]
k: String,
#[arg(allow_hyphen_values = true)]
ciphertext: String,
}
#[derive(Args)]
pub struct TranscryptArgs {
#[arg(long)]
pseudonymization_secret: Option<String>,
#[arg(long)]
encryption_secret: String,
#[arg(long)]
from_domain: Option<String>,
#[arg(long)]
to_domain: Option<String>,
#[arg(long)]
from_context: Option<String>,
#[arg(long)]
to_context: Option<String>,
#[arg(allow_hyphen_values = true)]
ciphertext: String,
}
pub trait Kind {
type Plain: ElGamalEncryptable
+ Encryptable<
EncryptedType = Self::Enc,
PublicKeyType = Self::SessionPk,
GlobalPublicKeyType = Self::GlobalPk,
>;
type Enc: ElGamalEncrypted
+ Encrypted<UnencryptedType = Self::Plain, SecretKeyType = Self::SessionSk>
+ Rekeyable<RekeyInfo = Self::RekeyInfo>
+ Transcryptable;
type Long: Encryptable<
EncryptedType = Self::LongEnc,
PublicKeyType = Self::SessionPk,
GlobalPublicKeyType = Self::GlobalPk,
>;
type LongEnc: Encrypted<UnencryptedType = Self::Long, SecretKeyType = Self::SessionSk>
+ Rekeyable<RekeyInfo = Self::RekeyInfo>
+ Transcryptable;
type SessionPk: PublicKey;
type SessionSk: SecretKey;
type GlobalPk: PublicKey;
#[cfg(feature = "insecure")]
type GlobalSk: SecretKey;
type RekeyInfo: Copy;
fn rekey_info(k: ScalarNonZero) -> Self::RekeyInfo;
fn long_from_blocks(blocks: Vec<Self::Plain>) -> Self::Long;
fn long_from_text(text: &str) -> Self::Long;
fn long_to_text(long: &Self::Long) -> std::io::Result<String>;
fn long_to_hex(long: &Self::Long) -> String;
fn long_enc_serialize(long: &Self::LongEnc) -> String;
fn long_enc_deserialize(text: &str) -> std::io::Result<Self::LongEnc>;
#[cfg(feature = "insecure")]
fn decrypt_global(enc: &Self::Enc, key: &Self::GlobalSk) -> Result<Self::Plain>;
#[cfg(feature = "insecure")]
fn decrypt_global_long(enc: &Self::LongEnc, key: &Self::GlobalSk) -> Result<Self::Long>;
}
pub struct PseudonymKind;
pub struct AttributeKind;
macro_rules! impl_kind {
($kind:ty {
plain: $plain:ty, enc: $enc:ty, long: $long:ident, long_enc: $long_enc:ident,
session_pk: $spk:ty, session_sk: $ssk:ty, global_pk: $gpk:ty, global_sk: $gsk:ty,
rekey_info: $info:ty, rekey_factor: $factor:ty
}) => {
impl Kind for $kind {
type Plain = $plain;
type Enc = $enc;
type Long = $long;
type LongEnc = $long_enc;
type SessionPk = $spk;
type SessionSk = $ssk;
type GlobalPk = $gpk;
#[cfg(feature = "insecure")]
type GlobalSk = $gsk;
type RekeyInfo = $info;
fn rekey_info(k: ScalarNonZero) -> Self::RekeyInfo {
<$info>::from(<$factor>::from(k))
}
fn long_from_blocks(blocks: Vec<Self::Plain>) -> Self::Long {
$long(blocks)
}
fn long_from_text(text: &str) -> Self::Long {
$long::from_string_padded(text)
}
fn long_to_text(long: &Self::Long) -> std::io::Result<String> {
long.to_string_padded()
}
fn long_to_hex(long: &Self::Long) -> String {
long.0
.iter()
.map(|b| b.to_hex())
.collect::<Vec<_>>()
.join(" ")
}
fn long_enc_serialize(long: &Self::LongEnc) -> String {
long.serialize()
}
fn long_enc_deserialize(text: &str) -> std::io::Result<Self::LongEnc> {
$long_enc::deserialize(text)
}
#[cfg(feature = "insecure")]
fn decrypt_global(enc: &Self::Enc, key: &Self::GlobalSk) -> Result<Self::Plain> {
#[cfg(feature = "elgamal3")]
return enc.decrypt_global(key).ok_or_else(io::Error::key_mismatch);
#[cfg(not(feature = "elgamal3"))]
Ok(enc.decrypt_global(key))
}
#[cfg(feature = "insecure")]
fn decrypt_global_long(
enc: &Self::LongEnc,
key: &Self::GlobalSk,
) -> Result<Self::Long> {
#[cfg(feature = "elgamal3")]
return enc.decrypt_global(key).ok_or_else(io::Error::key_mismatch);
#[cfg(not(feature = "elgamal3"))]
Ok(enc.decrypt_global(key))
}
}
};
}
impl_kind!(PseudonymKind {
plain: Pseudonym,
enc: EncryptedPseudonym,
long: LongPseudonym,
long_enc: LongEncryptedPseudonym,
session_pk: PseudonymSessionPublicKey,
session_sk: PseudonymSessionSecretKey,
global_pk: PseudonymGlobalPublicKey,
global_sk: PseudonymGlobalSecretKey,
rekey_info: PseudonymRekeyInfo,
rekey_factor: PseudonymRekeyFactor
});
impl_kind!(AttributeKind {
plain: Attribute,
enc: EncryptedAttribute,
long: LongAttribute,
long_enc: LongEncryptedAttribute,
session_pk: AttributeSessionPublicKey,
session_sk: AttributeSessionSecretKey,
global_pk: AttributeGlobalPublicKey,
global_sk: AttributeGlobalSecretKey,
rekey_info: AttributeRekeyInfo,
rekey_factor: AttributeRekeyFactor
});
enum Value<K: Kind> {
Short(K::Plain),
Long(K::Long),
}
impl<K: Kind> Value<K> {
fn parse(raw: &[String]) -> Result<Self> {
let blocks = io::read_values(raw)?
.iter()
.flat_map(|token| {
if token.len() > HEX_BLOCK && token.len() % HEX_BLOCK == 0 {
token
.as_bytes()
.chunks(HEX_BLOCK)
.map(|c| String::from_utf8_lossy(c).into_owned())
.collect::<Vec<_>>()
} else {
vec![token.clone()]
}
})
.collect::<Vec<_>>()
.iter()
.map(|hex| {
K::Plain::from_hex(hex).ok_or_else(|| {
io::Error::input(format!(
"value: {hex} is not a valid group element (64 hex digits)"
))
})
})
.collect::<Result<Vec<_>>>()?;
Ok(if blocks.len() == 1 {
Value::Short(blocks.into_iter().next().expect("one block"))
} else {
Value::<K>::Long(K::long_from_blocks(blocks))
})
}
fn emit(&self, out: &mut Output) {
match self {
Value::<K>::Short(v) => out.value("value", v.to_hex()),
Value::<K>::Long(v) => out.value("value", K::long_to_hex(v)),
}
}
}
enum Cipher<K: Kind> {
Short(K::Enc),
Long(K::LongEnc),
}
impl<K: Kind> Cipher<K> {
fn parse(raw: &str) -> Result<Self> {
let text = io::read_arg(raw)?;
if text.contains('|') {
K::long_enc_deserialize(&text)
.map(Cipher::Long)
.map_err(|_| io::Error::input("ciphertext: expected `|`-separated base64 blocks"))
} else {
K::Enc::from_base64(&text)
.map(Cipher::Short)
.ok_or_else(|| io::Error::input("ciphertext: expected a base64-encoded ciphertext"))
}
}
fn emit(&self, out: &mut Output) {
match self {
Cipher::Short(c) => out.value("ciphertext", c.to_base64()),
Cipher::Long(c) => out.value("ciphertext", K::long_enc_serialize(c)),
}
}
}
fn encode<K: Kind>(args: EncodeArgs, out: &mut Output) -> Result<()> {
let text = io::read_arg(&args.text)?;
let bytes = text.as_bytes();
if bytes.len() <= LIZARD_BYTES {
let mut padded = [0u8; LIZARD_BYTES];
padded[..bytes.len()].copy_from_slice(bytes);
out.value("value", K::Plain::from_lizard(&padded).to_hex());
} else {
let long = K::long_from_text(&text);
out.note(&format!(
"The value is longer than {LIZARD_BYTES} bytes and is encoded as a long value with PKCS#7 padding. \
The number of blocks is visible, and blocks can reveal subgroups."
));
out.value("value", K::long_to_hex(&long));
}
Ok(())
}
fn decode<K: Kind>(args: DecodeArgs, out: &mut Output) -> Result<()> {
let text = match Value::<K>::parse(&args.value)? {
Value::<K>::Short(v) => {
let bytes = v
.to_lizard()
.ok_or_else(|| io::Error::input("the value does not encode an identifier"))?;
let end = bytes.iter().rposition(|&b| b != 0).map_or(0, |i| i + 1);
String::from_utf8_lossy(&bytes[..end]).into_owned()
}
Value::<K>::Long(v) => K::long_to_text(&v)
.map_err(|e| io::Error::input(format!("the value does not encode text: {e}")))?,
};
out.value("text", text);
Ok(())
}
fn encrypt<K: Kind, R: Rng + CryptoRng>(
args: EncryptArgs,
rng: &mut R,
out: &mut Output,
) -> Result<()> {
let value = Value::<K>::parse(&args.value)?;
let cipher = if args.global {
let key: K::GlobalPk = io::public_key(&args.key, "global public key")?;
match value {
Value::<K>::Short(v) => Cipher::<K>::Short(v.encrypt_global(&key, rng)),
Value::<K>::Long(v) => Cipher::<K>::Long(v.encrypt_global(&key, rng)),
}
} else {
let key: K::SessionPk = io::public_key(&args.key, "session public key")?;
match value {
Value::<K>::Short(v) => Cipher::<K>::Short(v.encrypt(&key, rng)),
Value::<K>::Long(v) => Cipher::<K>::Long(v.encrypt(&key, rng)),
}
};
cipher.emit(out);
Ok(())
}
fn decrypt<K: Kind>(args: DecryptArgs, out: &mut Output) -> Result<()> {
#[cfg(feature = "insecure")]
if args.global {
let key: K::GlobalSk = io::secret_key(&args.key, "global secret key")?;
let cipher = Cipher::<K>::parse(&args.ciphertext)?;
let value = match cipher {
Cipher::Short(c) => Value::<K>::Short(K::decrypt_global(&c, &key)?),
Cipher::Long(c) => Value::<K>::Long(K::decrypt_global_long(&c, &key)?),
};
value.emit(out);
return Ok(());
}
let key: K::SessionSk = io::secret_key(&args.key, "session secret key")?;
let cipher = Cipher::<K>::parse(&args.ciphertext)?;
#[cfg(feature = "elgamal3")]
let value = match cipher {
Cipher::Short(c) => Value::<K>::Short(c.decrypt(&key).ok_or_else(io::Error::key_mismatch)?),
Cipher::Long(c) => Value::<K>::Long(c.decrypt(&key).ok_or_else(io::Error::key_mismatch)?),
};
#[cfg(not(feature = "elgamal3"))]
let value = match cipher {
Cipher::Short(c) => Value::<K>::Short(c.decrypt(&key)),
Cipher::Long(c) => Value::<K>::Long(c.decrypt(&key)),
};
value.emit(out);
Ok(())
}
fn rerandomize<K: Kind, R: Rng + CryptoRng>(
args: RerandomizeArgs,
rng: &mut R,
out: &mut Output,
) -> Result<()> {
let cipher = Cipher::<K>::parse(&args.ciphertext)?;
#[cfg(feature = "elgamal3")]
let result = match cipher {
Cipher::Short(c) => Cipher::<K>::Short(c.rerandomize(rng)),
Cipher::Long(c) => Cipher::<K>::Long(c.rerandomize(rng)),
};
#[cfg(not(feature = "elgamal3"))]
let result = {
let key: K::SessionPk = io::public_key(&args.key, "public key")?;
match cipher {
Cipher::Short(c) => Cipher::<K>::Short(c.rerandomize(&key, rng)),
Cipher::Long(c) => Cipher::<K>::Long(c.rerandomize(&key, rng)),
}
};
result.emit(out);
Ok(())
}
fn rekey<K: Kind>(args: RekeyArgs, out: &mut Output) -> Result<()> {
let info = K::rekey_info(io::scalar(&args.k, "k")?);
let result = match Cipher::<K>::parse(&args.ciphertext)? {
Cipher::Short(c) => Cipher::<K>::Short(c.rekey(&info)),
Cipher::Long(c) => Cipher::<K>::Long(c.rekey(&info)),
};
result.emit(out);
Ok(())
}
fn pseudonymize(args: PseudonymizeArgs, out: &mut Output) -> Result<()> {
let info = PseudonymizationInfo {
s: ReshuffleFactor::from(io::scalar(&args.s, "s")?),
k: PseudonymRekeyFactor::from(io::scalar(&args.k, "k")?),
};
let result = match Cipher::<PseudonymKind>::parse(&args.ciphertext)? {
Cipher::Short(c) => Cipher::<PseudonymKind>::Short(c.pseudonymize(&info)),
Cipher::Long(c) => Cipher::<PseudonymKind>::Long(c.pseudonymize(&info)),
};
result.emit(out);
Ok(())
}
fn transcrypt_pseudonym(args: TranscryptArgs, out: &mut Output) -> Result<()> {
let missing =
|what: &str| io::Error::input(format!("--{what} is required to transcrypt a pseudonym"));
let info = TranscryptionInfo::new(
&io::domain(
args.from_domain
.as_deref()
.ok_or_else(|| missing("from-domain"))?,
)?,
&io::domain(
args.to_domain
.as_deref()
.ok_or_else(|| missing("to-domain"))?,
)?,
&io::context(args.from_context.as_deref())?,
&io::context(args.to_context.as_deref())?,
&io::pseudonymization_secret(
args.pseudonymization_secret
.as_deref()
.ok_or_else(|| missing("pseudonymization-secret"))?,
)?,
&io::encryption_secret(&args.encryption_secret)?,
);
let result = match Cipher::<PseudonymKind>::parse(&args.ciphertext)? {
Cipher::Short(c) => Cipher::<PseudonymKind>::Short(c.transcrypt(&info)),
Cipher::Long(c) => Cipher::<PseudonymKind>::Long(c.transcrypt(&info)),
};
result.emit(out);
Ok(())
}
fn transcrypt_attribute(args: TranscryptArgs, out: &mut Output) -> Result<()> {
if args.pseudonymization_secret.is_some()
|| args.from_domain.is_some()
|| args.to_domain.is_some()
{
out.note(
"Attributes are not reshuffled; the pseudonymization secret and domains are ignored.",
);
}
let info = AttributeRekeyInfo::new(
&io::context(args.from_context.as_deref())?,
&io::context(args.to_context.as_deref())?,
&io::encryption_secret(&args.encryption_secret)?,
);
let result = match Cipher::<AttributeKind>::parse(&args.ciphertext)? {
Cipher::Short(c) => Cipher::<AttributeKind>::Short(c.rekey(&info)),
Cipher::Long(c) => Cipher::<AttributeKind>::Long(c.rekey(&info)),
};
result.emit(out);
Ok(())
}
pub fn run_pseudonym<R: Rng + CryptoRng>(
cmd: PseudonymCommand,
rng: &mut R,
out: &mut Output,
) -> Result<()> {
match cmd {
PseudonymCommand::Random => {
out.value("value", Pseudonym::random(rng).to_hex());
Ok(())
}
PseudonymCommand::Encode(args) => encode::<PseudonymKind>(args, out),
PseudonymCommand::Decode(args) => decode::<PseudonymKind>(args, out),
PseudonymCommand::Encrypt(args) => encrypt::<PseudonymKind, R>(args, rng, out),
PseudonymCommand::Decrypt(args) => decrypt::<PseudonymKind>(args, out),
PseudonymCommand::Rerandomize(args) => rerandomize::<PseudonymKind, R>(args, rng, out),
PseudonymCommand::Rekey(args) => rekey::<PseudonymKind>(args, out),
PseudonymCommand::Pseudonymize(args) => pseudonymize(args, out),
PseudonymCommand::Transcrypt(args) => transcrypt_pseudonym(args, out),
}
}
pub fn run_attribute<R: Rng + CryptoRng>(
cmd: AttributeCommand,
rng: &mut R,
out: &mut Output,
) -> Result<()> {
match cmd {
AttributeCommand::Random => {
out.value("value", Attribute::random(rng).to_hex());
Ok(())
}
AttributeCommand::Encode(args) => encode::<AttributeKind>(args, out),
AttributeCommand::Decode(args) => decode::<AttributeKind>(args, out),
AttributeCommand::Encrypt(args) => encrypt::<AttributeKind, R>(args, rng, out),
AttributeCommand::Decrypt(args) => decrypt::<AttributeKind>(args, out),
AttributeCommand::Rerandomize(args) => rerandomize::<AttributeKind, R>(args, rng, out),
AttributeCommand::Rekey(args) => rekey::<AttributeKind>(args, out),
AttributeCommand::Transcrypt(args) => transcrypt_attribute(args, out),
}
}