use std::ffi::c_void;
use confium_store::backend::{Compartment, Options, StoreBackend, StoreInstance};
use confium_store::error::{Error, NotImplementedSnafu, Result};
use confium_store::register_backend;
use crate::config::TpmConfig;
const SKELETON_NOT_IMPLEMENTED: &str = "tpm 2.0 backend (skeleton; enable the `tpm` feature)";
#[derive(Debug, Default, Clone, Copy)]
pub struct TpmBackend;
impl StoreBackend for TpmBackend {
fn name(&self) -> &'static str {
"tpm"
}
fn open(&self, opts: &Options) -> Result<Box<dyn StoreInstance>> {
let config = TpmConfig::from_options(opts)?;
Ok(Box::new(TpmInstance::from_config(config)))
}
}
register_backend!(TpmBackend);
pub struct TpmInstance {
pub config: TpmConfig,
#[cfg(feature = "tpm")]
session: Option<()>,
}
impl TpmInstance {
pub fn from_config(config: TpmConfig) -> Self {
Self {
config,
#[cfg(feature = "tpm")]
session: None,
}
}
fn not_implemented() -> Error {
NotImplementedSnafu {
what: SKELETON_NOT_IMPLEMENTED,
}
.build()
}
}
impl StoreInstance for TpmInstance {
fn put_secret(
&mut self,
_module: &str,
_app: &str,
_key_id: &str,
_key: *mut c_void,
) -> Result<()> {
Err(Self::not_implemented())
}
fn get_secret(&self, _module: &str, _app: &str, _key_id: &str) -> Result<*mut c_void> {
Err(Self::not_implemented())
}
fn put_public(
&mut self,
_module: &str,
_app: &str,
_identity: &str,
_key: *mut c_void,
_sig: &[u8],
) -> Result<()> {
Err(Self::not_implemented())
}
fn get_public(
&self,
_module: &str,
_app: &str,
_identity: &str,
) -> Result<(*mut c_void, Vec<u8>)> {
Err(Self::not_implemented())
}
fn enumerate(
&self,
_module: &str,
_app: &str,
_compartment: Compartment,
) -> Result<Vec<(*mut c_void, String)>> {
Err(Self::not_implemented())
}
}
unsafe impl Send for TpmInstance {}
unsafe impl Sync for TpmInstance {}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::Hierarchy;
use std::collections::HashMap;
fn open() -> TpmInstance {
let mut opts: Options = HashMap::new();
opts.insert("tpm_device".into(), "/dev/tpmrmis0".into());
opts.insert("hierarchy".into(), "owner".into());
opts.insert("parent_handle".into(), "0x81000001".into());
let boxed = TpmBackend.open(&opts).expect("tpm backend opens");
let raw = Box::into_raw(boxed) as *mut TpmInstance;
unsafe { *Box::from_raw(raw) }
}
fn sentinel(n: usize) -> *mut c_void {
n as *mut c_void
}
#[test]
fn backend_advertises_tpm_wire_name() {
assert_eq!(TpmBackend.name(), "tpm");
}
#[test]
fn backend_is_registered() {
let backend = confium_store::backend::find("tpm").expect("tpm backend registered");
assert_eq!(backend.name(), "tpm");
}
#[test]
fn open_parses_config() {
let inst = open();
assert_eq!(inst.config.hierarchy, Hierarchy::Owner);
assert_eq!(
inst.config.device.as_deref(),
Some(std::path::Path::new("/dev/tpmrmis0"))
);
assert_eq!(inst.config.parent_handle.unwrap().raw(), 0x8100_0001);
}
#[test]
fn put_secret_returns_not_implemented() {
let mut ks = open();
let err = ks
.put_secret("mod", "app", "k1", sentinel(0x1000))
.unwrap_err();
assert!(matches!(err, Error::NotImplemented { .. }));
}
#[test]
fn get_secret_returns_not_implemented() {
let ks = open();
let err = ks.get_secret("mod", "app", "k1").unwrap_err();
assert!(matches!(err, Error::NotImplemented { .. }));
}
#[test]
fn put_public_returns_not_implemented() {
let mut ks = open();
let err = ks
.put_public("mod", "app", "id", sentinel(0x2000), &[0u8])
.unwrap_err();
assert!(matches!(err, Error::NotImplemented { .. }));
}
#[test]
fn get_public_returns_not_implemented() {
let ks = open();
let err = ks.get_public("mod", "app", "id").unwrap_err();
assert!(matches!(err, Error::NotImplemented { .. }));
}
#[test]
fn enumerate_returns_not_implemented() {
let ks = open();
let err = ks
.enumerate("mod", "app", Compartment::Private)
.unwrap_err();
assert!(matches!(err, Error::NotImplemented { .. }));
}
#[test]
fn from_config_preserves_config() {
let cfg = TpmConfig {
device: Some(std::path::PathBuf::from("/dev/tpm0")),
hierarchy: Hierarchy::Endorsement,
parent_handle: Some(crate::config::ParentHandle(0x8100_0042)),
parent_password: b"pw".to_vec(),
};
let inst = TpmInstance::from_config(cfg.clone());
assert_eq!(inst.config, cfg);
}
#[cfg(feature = "tpm")]
fn hw_available() -> bool {
std::env::var_os("CFM_TPM_TEST").is_some()
}
#[cfg(feature = "tpm")]
#[test]
fn put_get_secret_round_trip_on_simulator() {
if !hw_available() {
eprintln!("skipping TPM simulator test: set CFM_TPM_TEST=1 and start swtpm to enable");
return;
}
}
}