openpgp-card/openpgp-card-sequoia/src/signer.rs

180 lines
5.6 KiB
Rust

// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer <heiko@schaefer.name>
// SPDX-License-Identifier: MIT OR Apache-2.0
use std::convert::TryInto;
use anyhow::anyhow;
use openpgp_card::crypto_data::Hash;
use openpgp_card::Transaction;
use sequoia_openpgp::crypto;
use sequoia_openpgp::crypto::mpi;
use sequoia_openpgp::types::{Curve, PublicKeyAlgorithm};
use crate::PublicKey;
pub struct CardSigner<'a, 'app> {
/// The OpenPGP card (authenticated to allow signing operations)
ca: &'a mut Transaction<'app>,
/// The matching public key for the card's signing key
public: PublicKey,
/// Callback function to signal if touch confirmation is needed
touch_prompt: &'a (dyn Fn() + Send + Sync),
/// sign or auth? (true = auth)
auth: bool,
}
impl<'a, 'app> CardSigner<'a, 'app> {
pub(crate) fn with_pubkey(
ca: &'a mut Transaction<'app>,
public: PublicKey,
touch_prompt: &'a (dyn Fn() + Send + Sync),
) -> CardSigner<'a, 'app> {
CardSigner {
ca,
public,
touch_prompt,
auth: false,
}
}
pub(crate) fn with_pubkey_for_auth(
ca: &'a mut Transaction<'app>,
public: PublicKey,
touch_prompt: &'a (dyn Fn() + Send + Sync),
) -> CardSigner<'a, 'app> {
CardSigner {
ca,
public,
touch_prompt,
auth: true,
}
}
}
impl<'a, 'app> crypto::Signer for CardSigner<'a, 'app> {
fn public(&self) -> &PublicKey {
&self.public
}
fn sign(
&mut self,
hash_algo: sequoia_openpgp::types::HashAlgorithm,
digest: &[u8],
) -> sequoia_openpgp::Result<mpi::Signature> {
// FIXME: use cached ARD value from caller?
let ard = self.ca.application_related_data()?;
// Get UIF setting for the sign or auth slot
let uif = if !self.auth {
ard.uif_pso_cds()?
} else {
ard.uif_pso_aut()?
};
// Touch is required if:
// - the card supports the feature
// - and the policy is set to a value other than 'Off'
let touch_required = if let Some(uif) = uif {
uif.touch_policy().touch_required()
} else {
false
};
let sig_fn = if !self.auth {
Transaction::signature_for_hash
} else {
Transaction::authenticate_for_hash
};
// Delegate a signing (or auth) operation to the OpenPGP card.
//
// This fn prepares the data structures that openpgp-card needs to
// perform the signing operation.
//
// (7.2.10 PSO: COMPUTE DIGITAL SIGNATURE)
// or (7.2.13 INTERNAL AUTHENTICATE)
match (self.public.pk_algo(), self.public.mpis()) {
#[allow(deprecated)]
(PublicKeyAlgorithm::RSASign, mpi::PublicKey::RSA { .. })
| (PublicKeyAlgorithm::RSAEncryptSign, mpi::PublicKey::RSA { .. }) => {
let hash = match hash_algo {
sequoia_openpgp::types::HashAlgorithm::SHA256 => Hash::SHA256(
digest
.try_into()
.map_err(|_| anyhow!("invalid slice length"))?,
),
sequoia_openpgp::types::HashAlgorithm::SHA384 => Hash::SHA384(
digest
.try_into()
.map_err(|_| anyhow!("invalid slice length"))?,
),
sequoia_openpgp::types::HashAlgorithm::SHA512 => Hash::SHA512(
digest
.try_into()
.map_err(|_| anyhow!("invalid slice length"))?,
),
_ => {
return Err(anyhow!(
"Unsupported hash algorithm for RSA {:?}",
hash_algo
));
}
};
if touch_required {
(self.touch_prompt)();
}
let sig = sig_fn(self.ca, hash)?;
let mpi = mpi::MPI::new(&sig[..]);
Ok(mpi::Signature::RSA { s: mpi })
}
(PublicKeyAlgorithm::EdDSA, mpi::PublicKey::EdDSA { .. }) => {
let hash = Hash::EdDSA(digest);
if touch_required {
(self.touch_prompt)();
}
let sig = sig_fn(self.ca, hash)?;
let r = mpi::MPI::new(&sig[..32]);
let s = mpi::MPI::new(&sig[32..]);
Ok(mpi::Signature::EdDSA { r, s })
}
(PublicKeyAlgorithm::ECDSA, mpi::PublicKey::ECDSA { curve, .. }) => {
let hash = match curve {
Curve::NistP256 => Hash::ECDSA(&digest[..32]),
Curve::NistP384 => Hash::ECDSA(&digest[..48]),
Curve::NistP521 => Hash::ECDSA(&digest[..64]),
_ => Hash::ECDSA(digest),
};
if touch_required {
(self.touch_prompt)();
}
let sig = sig_fn(self.ca, hash)?;
let len_2 = sig.len() / 2;
let r = mpi::MPI::new(&sig[..len_2]);
let s = mpi::MPI::new(&sig[len_2..]);
Ok(mpi::Signature::ECDSA { r, s })
}
// FIXME: implement NIST etc
(pk_algo, _) => Err(anyhow!(
"Unsupported combination of algorithm {:?} and pubkey {:?}",
pk_algo,
self.public
)),
}
}
}