From a25dfabba514e333f17d7e671594e44e21acca83 Mon Sep 17 00:00:00 2001 From: Jay Gowdy Date: Thu, 21 May 2026 12:01:12 -0700 Subject: [PATCH] feat(core): add _warning field and save_meta doc comment Add a _warning field to KeyMeta that serializes at the top of .meta JSON files, warning users and agents not to modify these files directly since they are HMAC-verified. Add a doc comment to save_meta documenting the meta-tag invariant: every call must be followed by a meta-tag re-stamp, or ensure_meta_integrity will reject the key on next load. --- crates/enclaveapp-core/src/metadata.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/crates/enclaveapp-core/src/metadata.rs b/crates/enclaveapp-core/src/metadata.rs index b07e427..74962b7 100644 --- a/crates/enclaveapp-core/src/metadata.rs +++ b/crates/enclaveapp-core/src/metadata.rs @@ -10,9 +10,17 @@ use std::collections::BTreeSet; use std::io::Write; use std::path::{Path, PathBuf}; +pub fn meta_warning_default() -> String { + "HMAC-verified — do not modify this file directly. Use CLI tools (e.g. sshenc identity)." + .to_string() +} + /// Metadata stored alongside a hardware-bound key. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct KeyMeta { + /// Tamper warning rendered at the top of the JSON. + #[serde(default = "meta_warning_default", rename = "_warning")] + pub warning: String, /// Key label (unique identifier within the app). pub label: String, /// Type of key (signing or encryption). Defaults to Signing for backward @@ -40,6 +48,7 @@ impl KeyMeta { .as_secs() .to_string(); KeyMeta { + warning: meta_warning_default(), label: label.to_string(), key_type, access_policy, @@ -233,6 +242,15 @@ pub fn restrict_file_permissions(path: &Path) -> Result<()> { } /// Save key metadata to a JSON file. +/// +/// # Meta-tag invariant +/// +/// On platforms with meta-integrity tags (macOS, Windows, Linux), +/// every call to `save_meta` MUST be followed by a meta-tag re-stamp. +/// Callers that skip the re-stamp will break `ensure_meta_integrity` +/// verification on the next load. The canonical way to guarantee this +/// is to route meta mutations through the agent process, which stamps +/// the tag atomically. See sshenc-agent's `SetIdentity` handler. pub fn save_meta(dir: &Path, label: &str, meta: &KeyMeta) -> Result<()> { crate::types::validate_label(label)?; let meta_path = dir.join(format!("{label}.meta")); @@ -304,6 +322,7 @@ pub fn load_meta(dir: &Path, label: &str) -> Result { let meta_path = dir.join(format!("{label}.meta")); if !meta_path.exists() { return Ok(KeyMeta { + warning: meta_warning_default(), label: label.to_string(), key_type: crate::KeyType::Signing, access_policy: crate::AccessPolicy::None,