use std::io;
use pgp::composed::{ArmorOptions, CleartextSignedMessage, MessageBuilder};
use pgp::packet::Signature;
use pgp::types::{KeyDetails, Password, SigningKey, Timestamp};
use rand::thread_rng;
use rpgpie::certificate::Checked;
use rpgpie::key::ComponentKeyPub;
use crate::card::get_card;
use crate::cmd::sign::Sign;
use crate::{card, Keys, RPGSOPOCT};
pub(crate) struct InlineSign {
armor: bool,
sign: Sign,
mode: sop::ops::InlineSignAs,
}
impl InlineSign {
pub(crate) fn new() -> Self {
Self {
armor: true,
sign: Sign::new(),
mode: Default::default(),
}
}
}
impl<'a> sop::ops::InlineSign<'a, RPGSOPOCT, Keys> for InlineSign {
fn no_armor(mut self: Box<Self>) -> Box<dyn sop::ops::InlineSign<'a, RPGSOPOCT, Keys> + 'a> {
self.armor = false;
self
}
fn mode(
mut self: Box<Self>,
mode: sop::ops::InlineSignAs,
) -> Box<dyn sop::ops::InlineSign<'a, RPGSOPOCT, Keys> + 'a> {
self.mode = mode;
self
}
fn keys(
mut self: Box<Self>,
keys: &Keys,
) -> sop::Result<Box<dyn sop::ops::InlineSign<'a, RPGSOPOCT, Keys> + 'a>> {
self.sign.add_signing_keys(keys)?;
Ok(self)
}
fn with_key_password(
mut self: Box<Self>,
password: sop::Password,
) -> sop::Result<Box<dyn sop::ops::InlineSign<'a, RPGSOPOCT, Keys> + 'a>> {
self.sign.with_key_password.push(password);
Ok(self)
}
fn data<'d>(
self: Box<Self>,
data: &'d mut (dyn io::Read + Send + Sync),
) -> sop::Result<Box<dyn sop::ops::Ready + 'd>>
where
'a: 'd,
{
if self.sign.signers.is_empty() {
return Err(sop::errors::Error::MissingArg);
}
if !self.armor && matches!(self.mode, sop::ops::InlineSignAs::ClearSigned) {
return Err(sop::errors::Error::IncompatibleOptions);
}
Ok(Box::new(InlineSignReady {
inline_sign: *self,
data,
}))
}
}
struct InlineSignReady<'a> {
inline_sign: InlineSign,
data: &'a mut (dyn io::Read + Send + Sync),
}
impl sop::ops::Ready for InlineSignReady<'_> {
fn to_writer(
mut self: Box<Self>,
mut sink: &mut (dyn io::Write + Send + Sync),
) -> sop::Result<()> {
let hash_algo = self
.inline_sign
.sign
.hash_algos
.first()
.cloned()
.unwrap_or_default();
assert!(!self.inline_sign.sign.signers.is_empty());
let mut signers: Vec<ComponentKeyPub> = vec![];
for cert in self.inline_sign.sign.signers {
let ccert: Checked = cert.clone().into();
let mut keys: Vec<ComponentKeyPub> = ccert
.valid_signing_capable_component_keys_at(Timestamp::now())
.iter()
.map(|x| x.as_componentkey().clone())
.collect();
if keys.is_empty() {
panic!(
"no signing capable component key found for signer {:02x?}",
cert.fingerprint()
);
}
signers.append(&mut keys);
}
if self.inline_sign.mode == sop::ops::InlineSignAs::ClearSigned {
let mut data = vec![];
self.data.read_to_end(&mut data)?;
let signers = |text: &str| -> Result<Vec<Signature>, pgp::errors::Error> {
let mut sigs: Vec<Signature> = vec![];
for ds in signers {
let mut card =
get_card(ds.public_params(), openpgp_card::ocard::KeyType::Signing)
.expect("FIXME");
let res = card::do_on_card(
&mut card,
openpgp_card::ocard::KeyType::Signing,
&self.inline_sign.sign.with_key_password,
&|| {},
|cs| {
let res =
cs.sign_data(text.as_bytes(), true, &Password::empty(), hash_algo);
if let Ok(sig) = res {
Ok(sig)
} else {
Err(crate::Error::Message(
"Didn't get a Message from signer".to_string(),
))
}
},
);
if let Ok(signature) = res {
sigs.push(signature);
} else {
return Err(pgp::errors::Error::Message {
message: "Couldn't sign".to_string(),
backtrace: None,
});
}
}
Ok(sigs)
};
let text = core::str::from_utf8(&data).map_err(|_| sop::errors::Error::BadData)?;
let Ok(csf) = CleartextSignedMessage::new_many(text, signers) else {
return Err(sop::errors::Error::KeyIsProtected);
};
csf.to_armored_writer(&mut sink, ArmorOptions::default())
.expect("writing failed");
Ok(())
} else {
if signers.len() != 1 {
eprintln!(
"Found {} potential signing component keys, must be 1!",
signers.len()
);
return Err(sop::errors::Error::BadData);
}
let sig = &signers[0];
let mut card = get_card(sig.public_params(), openpgp_card::ocard::KeyType::Signing)
.expect("FIXME");
let res = card::do_on_card(
&mut card,
openpgp_card::ocard::KeyType::Signing,
&self.inline_sign.sign.with_key_password,
&|| {},
|cs| {
let mut builder = MessageBuilder::from_reader("", &mut self.data);
if self.inline_sign.mode == sop::ops::InlineSignAs::Text {
builder.sign_text();
}
builder.sign(cs, Password::empty(), cs.hash_alg());
let rng = thread_rng();
if self.inline_sign.armor {
builder
.to_armored_writer(rng, ArmorOptions::default(), sink)
.expect("write failure");
} else {
builder.to_writer(rng, sink).expect("write failure");
}
Ok(())
},
);
if res.is_ok() {
Ok(())
} else {
unimplemented!("error")
}
}
}
}