#[derive(Debug, thiserror::Error)]
pub enum TpmError {
#[error("platform credential store not available: {0}")]
NotAvailable(String),
#[error("platform credential binding failed: {0}")]
Failure(String),
#[error("no sealed material found for label '{0}'")]
NotFound(String),
}
#[cfg(feature = "keyring")]
const SERVICE_NAME: &str = "koi-certmesh";
#[cfg(feature = "keyring")]
pub fn is_available() -> bool {
if std::env::var("KOI_NO_CREDENTIAL_STORE").is_ok() {
return false;
}
let probe_user = "koi-probe-test";
let entry = match keyring::Entry::new(SERVICE_NAME, probe_user) {
Ok(e) => e,
Err(_) => return false,
};
if entry.set_password("probe").is_err() {
return false;
}
let _ = entry.delete_credential();
true
}
#[cfg(feature = "keyring")]
pub fn seal_key_material(label: &str, data: &[u8]) -> Result<(), TpmError> {
let entry = keyring::Entry::new(SERVICE_NAME, label)
.map_err(|e| TpmError::NotAvailable(e.to_string()))?;
entry
.set_secret(data)
.map_err(|e| TpmError::Failure(format!("seal failed for '{label}': {e}")))?;
tracing::debug!(label, "Key material sealed in platform credential store");
Ok(())
}
#[cfg(feature = "keyring")]
pub fn unseal_key_material(label: &str) -> Result<Vec<u8>, TpmError> {
let entry = keyring::Entry::new(SERVICE_NAME, label)
.map_err(|e| TpmError::NotAvailable(e.to_string()))?;
entry
.get_secret()
.map_err(|e| TpmError::NotFound(format!("unseal failed for '{label}': {e}")))
}
#[cfg(feature = "keyring")]
pub fn delete_key_material(label: &str) -> Result<(), TpmError> {
let entry = keyring::Entry::new(SERVICE_NAME, label)
.map_err(|e| TpmError::NotAvailable(e.to_string()))?;
entry
.delete_credential()
.map_err(|e| TpmError::Failure(format!("delete failed for '{label}': {e}")))?;
tracing::debug!(label, "Sealed key material deleted from credential store");
Ok(())
}
#[cfg(not(feature = "keyring"))]
pub fn is_available() -> bool {
false
}
#[cfg(not(feature = "keyring"))]
pub fn seal_key_material(label: &str, _data: &[u8]) -> Result<(), TpmError> {
Err(TpmError::NotAvailable(format!(
"koi-crypto built without the `keyring` feature; cannot seal '{label}'"
)))
}
#[cfg(not(feature = "keyring"))]
pub fn unseal_key_material(label: &str) -> Result<Vec<u8>, TpmError> {
Err(TpmError::NotFound(label.to_string()))
}
#[cfg(not(feature = "keyring"))]
pub fn delete_key_material(_label: &str) -> Result<(), TpmError> {
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_available_returns_bool() {
std::env::set_var("KOI_NO_CREDENTIAL_STORE", "1");
assert!(!is_available());
std::env::remove_var("KOI_NO_CREDENTIAL_STORE");
}
#[test]
fn seal_unseal_round_trip() {
if !is_available() {
eprintln!("platform credential store not available, skipping");
return;
}
let label = "koi-test-round-trip";
let data = b"test-secret-material-1234";
seal_key_material(label, data).expect("seal should succeed");
let recovered = unseal_key_material(label).expect("unseal should succeed");
assert_eq!(&recovered, data);
delete_key_material(label).expect("delete should succeed");
assert!(unseal_key_material(label).is_err());
}
#[test]
fn unseal_nonexistent_returns_error() {
if !is_available() {
return;
}
assert!(unseal_key_material("koi-test-nonexistent-key-99999").is_err());
}
}