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

172 lines
5.6 KiB
Rust

// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
// SPDX-License-Identifier: MIT OR Apache-2.0
use std::convert::TryInto;
use anyhow::anyhow;
use openpgp::crypto;
use openpgp::crypto::mpi;
use openpgp::policy::Policy;
use openpgp::types::{Curve, PublicKeyAlgorithm};
use sequoia_openpgp as openpgp;
use openpgp_card::crypto_data::Hash;
use openpgp_card::{CardApp, Error};
use crate::PublicKey;
pub struct CardSigner<'a> {
/// The OpenPGP card (authenticated to allow signing operations)
ca: &'a mut CardApp,
/// The matching public key for the card's signing key
public: PublicKey,
}
impl<'a> CardSigner<'a> {
/// Try to create a CardSigner.
///
/// An Error is returned if no match between the card's signing
/// key and a (sub)key of `cert` can be made.
pub fn new(
ca: &'a mut CardApp,
cert: &openpgp::Cert,
policy: &dyn Policy,
) -> Result<CardSigner<'a>, Error> {
// Get the fingerprint for the signing key from the card.
let ard = ca.get_application_related_data()?;
let fps = ard.get_fingerprints()?;
let fp = fps.signature();
if let Some(fp) = fp {
// Transform into Sequoia Fingerprint
let fp = openpgp::Fingerprint::from_bytes(fp.as_bytes());
// Find the matching signing-capable (sub)key in `cert`
let keys: Vec<_> = cert
.keys()
.with_policy(policy, None)
.alive()
.revoked(false)
.for_signing()
.filter(|ka| ka.fingerprint() == fp)
.map(|ka| ka.key())
.collect();
// Exactly one matching (sub)key should be found. If not, fail!
if keys.len() == 1 {
let public = keys[0].clone();
Ok(Self::with_pubkey(ca, public))
} else {
Err(Error::InternalError(anyhow!(
"Failed to find a matching (sub)key in cert"
)))
}
} else {
Err(Error::InternalError(anyhow!(
"Failed to get the signing key's Fingerprint from the card"
)))
}
}
pub(crate) fn with_pubkey(
ca: &'a mut CardApp,
public: PublicKey,
) -> CardSigner<'a> {
CardSigner { ca, public }
}
}
impl<'a> crypto::Signer for CardSigner<'a> {
fn public(&self) -> &PublicKey {
&self.public
}
fn sign(
&mut self,
hash_algo: openpgp::types::HashAlgorithm,
digest: &[u8],
) -> openpgp::Result<mpi::Signature> {
// Delegate a signing 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)
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 {
openpgp::types::HashAlgorithm::SHA256 => Hash::SHA256(
digest
.try_into()
.map_err(|_| anyhow!("invalid slice length"))?,
),
openpgp::types::HashAlgorithm::SHA384 => Hash::SHA384(
digest
.try_into()
.map_err(|_| anyhow!("invalid slice length"))?,
),
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
));
}
};
let sig = self.ca.signature_for_hash(hash)?;
let mpi = mpi::MPI::new(&sig[..]);
Ok(mpi::Signature::RSA { s: mpi })
}
(PublicKeyAlgorithm::EdDSA, mpi::PublicKey::EdDSA { .. }) => {
let hash = Hash::EdDSA(digest);
let sig = self.ca.signature_for_hash(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),
};
let sig = self.ca.signature_for_hash(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
)),
}
}
}