use super::signature::{
check_blob_signature, signed_digest_v2, SigVerdict, SIG_TRAILER_LEN, SIG_V2,
};
use crate::crypto;
use crate::piv::PivBackend;
use crate::store::{Object, Store};
use anyhow::{bail, Context, Result};
use p256::ecdsa::VerifyingKey;
use p256::PublicKey;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BlobMeta {
pub name: String,
pub key_byte: u8,
pub compressed: bool,
pub plain_size: u32,
pub stored_size: u32,
pub mtime: u32,
}
impl BlobMeta {
pub fn from_head(head: &Object) -> Self {
Self {
name: head.blob_name.clone(),
key_byte: head.blob_key_slot,
compressed: head.is_compressed,
plain_size: head.blob_plain_size,
stored_size: head.blob_size,
mtime: head.blob_mtime,
}
}
pub fn is_encrypted(&self) -> bool {
self.key_byte != 0
}
}
pub struct Protected {
pub stored: Vec<u8>,
pub key_byte: u8,
pub trailer: Vec<u8>,
pub management_key: Option<zeroize::Zeroizing<String>>,
}
pub trait Protection {
fn overhead(&self, encrypt: bool) -> (usize, usize);
fn protect(
&self,
name: &str,
payload: &[u8],
encrypt: bool,
compressed: bool,
plain_size: u32,
mtime: u32,
) -> Result<Protected>;
fn open(&self, meta: &BlobMeta, stored: &[u8], trailer: &[u8]) -> Result<Vec<u8>>;
fn quick_check(&self, head: &Object, store: &Store) -> SigVerdict;
}
pub struct LegacyProtection<'a> {
pub piv: &'a dyn PivBackend,
pub reader: &'a str,
pub slot: u8,
pub encryption_key: Option<&'a PublicKey>,
pub verifying_key: Option<&'a VerifyingKey>,
pub pin: Option<&'a str>,
pub debug: bool,
}
impl Protection for LegacyProtection<'_> {
fn overhead(&self, encrypt: bool) -> (usize, usize) {
let encryption = if encrypt { crypto::GCM_OVERHEAD } else { 0 };
(encryption, SIG_TRAILER_LEN)
}
fn protect(
&self,
name: &str,
payload: &[u8],
encrypt: bool,
compressed: bool,
plain_size: u32,
mtime: u32,
) -> Result<Protected> {
let (stored, key_byte) = if encrypt {
let Some(pk) = self.encryption_key else {
bail!("blob '{name}': no key to encrypt to");
};
let enc = crypto::hybrid_encrypt(payload, pk).context("encrypting blob")?;
(enc, self.slot)
} else {
(payload.to_vec(), 0u8)
};
let meta = BlobMeta {
name: name.to_owned(),
key_byte,
compressed,
plain_size,
stored_size: stored.len() as u32,
mtime,
};
let digest = signed_digest_v2(&meta, &stored);
let raw_sig = self
.piv
.ecdsa_sign(self.reader, self.slot, &digest, self.pin)
.map_err(|e| anyhow::anyhow!("signing the blob failed; nothing was stored: {e:#}"))?;
let mut trailer = Vec::with_capacity(SIG_TRAILER_LEN);
trailer.push(SIG_V2);
trailer.extend_from_slice(&raw_sig[0..64]); Ok(Protected {
stored,
key_byte,
trailer,
management_key: None,
})
}
fn open(&self, meta: &BlobMeta, stored: &[u8], _trailer: &[u8]) -> Result<Vec<u8>> {
if !meta.is_encrypted() {
return Ok(stored.to_vec());
}
let name = &meta.name;
if self.pin.is_none() {
bail!("blob '{name}' is encrypted but no PIN was provided");
}
crypto::hybrid_decrypt(
self.piv,
self.reader,
meta.key_byte,
stored,
self.pin,
self.debug,
)
.with_context(|| format!("decrypting blob '{name}'"))
}
fn quick_check(&self, head: &Object, store: &Store) -> SigVerdict {
check_blob_signature(head, store, self.verifying_key)
}
}