rsop-oct 0.1.9

SOP CLI tool for OpenPGP card devices based on rPGP
Documentation
// SPDX-FileCopyrightText: Heiko Schaefer <heiko@schaefer.name>
// SPDX-License-Identifier: MIT OR Apache-2.0

use std::io;

use pgp::types::{KeyDetails, Password, Timestamp};
use rpgpie::certificate::{Certificate, Checked};
use sop::errors::Error;

use crate::{Keys, RPGSOPOCT, Sigs, card, card::get_card};

pub(crate) struct Sign {
    pub(crate) mode: sop::ops::SignAs,
    pub(crate) with_key_password: Vec<sop::Password>,
    pub(crate) signers: Vec<Certificate>,
}

impl Sign {
    pub(crate) fn new() -> Self {
        Self {
            mode: Default::default(),
            with_key_password: Default::default(),
            signers: Default::default(),
        }
    }
}

impl Sign {
    pub(crate) fn add_signing_keys(&mut self, keys: &Keys) -> sop::Result<()> {
        for key in &keys.keys {
            self.add_signing_key(key)?;
        }
        Ok(())
    }

    fn add_signing_key(&mut self, cert: &Certificate) -> sop::Result<()> {
        self.signers.push(cert.clone());
        Ok(())
    }
}

impl<'a> sop::ops::Sign<'a, RPGSOPOCT, Keys, Sigs> for Sign {
    fn mode(
        mut self: Box<Self>,
        mode: sop::ops::SignAs,
    ) -> Box<dyn sop::ops::Sign<'a, RPGSOPOCT, Keys, Sigs> + 'a> {
        self.mode = mode;
        self
    }

    fn keys(
        mut self: Box<Self>,
        keys: &Keys,
    ) -> sop::Result<Box<dyn sop::ops::Sign<'a, RPGSOPOCT, Keys, Sigs> + 'a>> {
        self.add_signing_keys(keys)?;
        Ok(self)
    }

    fn with_key_password(
        mut self: Box<Self>,
        password: sop::Password,
    ) -> sop::Result<Box<dyn sop::ops::Sign<'a, RPGSOPOCT, Keys, Sigs> + 'a>> {
        self.with_key_password.push(password);
        Ok(self)
    }

    fn data(
        self: Box<Self>,
        input: &mut (dyn io::Read + Send + Sync),
    ) -> sop::Result<(sop::ops::Micalg, Sigs)> {
        if self.signers.is_empty() {
            return Err(sop::errors::Error::MissingArg);
        }

        let mut data = vec![];
        input.read_to_end(&mut data)?;

        let mut sigs = vec![];

        for cert in self.signers {
            let ccert: Checked = cert.into();

            let keys: Vec<_> = ccert
                .valid_signing_capable_component_keys_at(Timestamp::now())
                .iter()
                .map(|x| x.as_componentkey().clone())
                .collect();

            for signer in keys {
                log::info!(
                    "Trying to sign data with signer: {:02x?}",
                    signer.fingerprint()
                );

                let mut card = get_card(&signer, openpgp_card::ocard::KeyType::Signing)
                    .map_err(|_| Error::UnspecifiedFailure)?;

                let sig = card::do_on_card(
                    &mut card,
                    openpgp_card::ocard::KeyType::Signing,
                    &self.with_key_password,
                    &|| {},
                    |cs| {
                        let result = cs.sign_data(
                            &data,
                            self.mode == sop::ops::SignAs::Text,
                            &Password::empty(),
                            signer.public_params().hash_alg(),
                        );

                        if result.is_err() {
                            log::warn!("Signing failed: {result:?}");
                        }

                        result.map_err(|e| e.into())
                    },
                )
                .ok();

                match sig {
                    Some(s) => sigs.push(s),
                    None => {
                        log::warn!(
                            "Couldn't sign with signer key {:02x?}",
                            signer.fingerprint()
                        );

                        // signing with this signing key failed but let's continue
                    }
                }
            }
        }

        if sigs.is_empty() {
            // FIXME: probably the password(s) were wrong, but this is a bit of a guess
            return Err(sop::errors::Error::KeyIsProtected);
        }

        Ok((
            sop::ops::Micalg::Unknown("".to_string()),
            Sigs {
                sigs,
                source_name: None,
            },
        ))
    }
}