use std::io::Cursor;
use std::path::{Path, PathBuf};
use anyhow::{anyhow, Result};
use clap::Parser;
use openpgp_card::ocard::KeyType;
use openpgp_card_rpgp::CardSlot;
use pgp::composed::{ArmorOptions, CleartextSignedMessage, MessageBuilder};
use pgp::types::{Password, SecretKeyTrait};
use rand::thread_rng;
use crate::util;
#[derive(Parser, Debug)]
pub struct SignCommand {
#[arg(
name = "card ident",
short = 'c',
long = "card",
help = "Identifier of the card to use"
)]
pub ident: String,
#[arg(
name = "User PIN file",
short = 'p',
long = "user-pin",
help = "Optionally, get User PIN from a file"
)]
pub user_pin: Option<PathBuf>,
#[command(subcommand)]
pub form: SignSubCommand,
}
#[derive(Debug, Parser, PartialEq)]
pub enum SignSubCommand {
Detached {
#[arg(name = "input")]
input: Option<PathBuf>,
#[arg(name = "output", long = "output", short = 'o')]
output: Option<PathBuf>,
},
Cleartext {
#[arg(name = "input")]
input: Option<PathBuf>,
#[arg(name = "output", long = "output", short = 'o')]
output: Option<PathBuf>,
},
Inline {
#[arg(name = "input")]
input: Option<PathBuf>,
#[arg(name = "output", long = "output", short = 'o')]
output: Option<PathBuf>,
},
}
impl SignSubCommand {
pub fn get_input_output(&self) -> (Option<&Path>, Option<&Path>) {
match self {
SignSubCommand::Detached { input, output }
| SignSubCommand::Cleartext { input, output }
| SignSubCommand::Inline { input, output } => (input.as_deref(), output.as_deref()),
}
}
pub fn is_cleartext(&self) -> bool {
matches!(self, SignSubCommand::Cleartext { .. })
}
pub fn is_detached(&self) -> bool {
matches!(self, SignSubCommand::Detached { .. })
}
}
pub fn sign(command: SignCommand) -> Result<(), Box<dyn std::error::Error>> {
sign_message(&command.ident, command.user_pin, command.form)
}
pub fn sign_message(
ident: &str,
pin_file: Option<PathBuf>,
form: SignSubCommand,
) -> Result<(), Box<dyn std::error::Error>> {
let cleartext = form.is_cleartext();
let detached = form.is_detached();
let (input, output) = form.get_input_output();
let mut input = util::open_or_stdin(input)?;
let mut open = util::open_card(ident)?;
let mut tx = open.transaction()?;
if tx.fingerprints()?.signature().is_none() {
return Err(anyhow!("Can't sign: this card has no key in the signing slot.").into());
}
let user_pin = util::get_pin(&mut tx, pin_file, crate::ENTER_USER_PIN)?;
let _ = util::verify_to_sign(&mut tx, user_pin)?;
let cs = CardSlot::init_from_card(&mut tx, KeyType::Signing, &|| {
eprintln!("Touch confirmation needed for signing");
})?;
let mut data = vec![];
input.read_to_end(&mut data)?;
let mut sink = util::open_or_stdout(output)?;
if cleartext {
let text = String::from_utf8(data)?;
let csf = CleartextSignedMessage::sign(thread_rng(), &text, &cs, &Password::empty())?;
csf.to_armored_writer(&mut sink, ArmorOptions::default())?;
} else if detached {
let signature = cs.sign_data(
&data,
false, &Password::empty(),
cs.hash_alg(),
)?;
rpgpie::signature::save(&[signature], true, &mut sink)?
} else {
let mut builder = MessageBuilder::from_reader("", Cursor::new(data));
builder.sign(&cs, Password::empty(), cs.hash_alg());
builder.to_armored_writer(thread_rng(), ArmorOptions::default(), sink)?;
}
Ok(())
}