openpgp-card/card-functionality/src/util.rs

169 lines
4.6 KiB
Rust

// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
// SPDX-License-Identifier: MIT OR Apache-2.0
use anyhow::{anyhow, Result};
use std::io::Write;
use std::time::SystemTime;
use sequoia_openpgp::cert::amalgamation::key::ValidKeyAmalgamation;
use sequoia_openpgp::packet::key::{SecretParts, UnspecifiedRole};
use sequoia_openpgp::parse::stream::{
DetachedVerifierBuilder, MessageLayer, MessageStructure,
VerificationHelper,
};
use sequoia_openpgp::parse::Parse;
use sequoia_openpgp::policy::StandardPolicy;
use sequoia_openpgp::serialize::stream::{
Armorer, Encryptor, LiteralWriter, Message,
};
use sequoia_openpgp::Cert;
use openpgp_card::{CardApp, KeyType};
use openpgp_card_sequoia::vka_as_uploadable_key;
pub const SP: &StandardPolicy = &StandardPolicy::new();
pub(crate) fn upload_subkeys(
ca: &mut CardApp,
cert: &Cert,
) -> Result<Vec<(String, u32)>> {
let mut out = vec![];
for kt in [
KeyType::Signing,
KeyType::Decryption,
KeyType::Authentication,
] {
let vka = get_subkey(cert, kt)?;
// store fingerprint as return-value
let fp = vka.fingerprint().to_hex();
// store key creation time as return-value
let creation = vka
.creation_time()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs() as u32;
out.push((fp, creation));
// upload key
let cuk = vka_as_uploadable_key(vka, None);
let _ = ca.upload_key(cuk, kt)?;
}
Ok(out)
}
fn get_subkey(
cert: &Cert,
key_type: KeyType,
) -> Result<ValidKeyAmalgamation<'_, SecretParts, UnspecifiedRole, bool>> {
// Find all suitable (sub)keys for key_type.
let mut valid_ka = cert
.keys()
.with_policy(SP, None)
.secret()
.alive()
.revoked(false);
valid_ka = match key_type {
KeyType::Decryption => valid_ka.for_storage_encryption(),
KeyType::Signing => valid_ka.for_signing(),
KeyType::Authentication => valid_ka.for_authentication(),
_ => return Err(anyhow!("Unexpected KeyType")),
};
// FIXME: for now, we just pick the first (sub)key from the list
if let Some(vka) = valid_ka.next() {
Ok(vka)
} else {
Err(anyhow!("No suitable (sub)key found"))
}
}
/// Perform signature verification for one Cert and a simple signature
/// over a message.
struct VHelper<'a> {
cert: &'a Cert,
}
impl<'a> VHelper<'a> {
fn new(cert: &'a Cert) -> Self {
Self { cert }
}
}
impl<'a> VerificationHelper for VHelper<'a> {
fn get_certs(
&mut self,
_ids: &[sequoia_openpgp::KeyHandle],
) -> sequoia_openpgp::Result<Vec<Cert>> {
// Hand out our single Cert
Ok(vec![self.cert.clone()])
}
fn check(
&mut self,
structure: MessageStructure,
) -> sequoia_openpgp::Result<()> {
// We are interested in signatures over the data (level 0 signatures)
if let Some(MessageLayer::SignatureGroup { results }) =
structure.into_iter().next()
{
match results.into_iter().next() {
Some(Ok(_)) => Ok(()), // Good signature.
Some(Err(e)) => Err(sequoia_openpgp::Error::from(e).into()),
None => Err(anyhow::anyhow!("No signature")),
}
} else {
Err(anyhow::anyhow!("Unexpected message structure"))
}
}
}
pub fn verify_sig(cert: &Cert, msg: &[u8], sig: &[u8]) -> Result<bool> {
let vh = VHelper::new(cert);
let mut dv = DetachedVerifierBuilder::from_bytes(&sig)?
.with_policy(SP, None, vh)?;
Ok(dv.verify_bytes(msg).is_ok())
}
pub fn encrypt_to(cleartext: &str, cert: &Cert) -> Result<String> {
let p = &StandardPolicy::new();
let mut recipients = Vec::new();
// Make sure we add at least one subkey from every
// certificate.
let mut found_one = false;
for key in cert
.keys()
.with_policy(p, None)
.supported()
.alive()
.revoked(false)
.for_transport_encryption()
{
recipients.push(key);
found_one = true;
}
if !found_one {
return Err(anyhow::anyhow!(
"No suitable encryption subkey for {}",
cert
));
}
let mut sink = vec![];
let message = Message::new(&mut sink);
let message = Armorer::new(message).build()?;
let message = Encryptor::for_recipients(message, recipients).build()?;
let mut w = LiteralWriter::new(message).build()?;
w.write_all(cleartext.as_bytes())?;
w.finalize()?;
Ok(String::from_utf8(sink).unwrap())
}