confium_tc_keys/
hsm_protection.rs1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct SealedShare {
9 pub key_handle: String,
11 pub encrypted_share: Vec<u8>,
13 pub attestation_pubkey_hex: String,
15}
16
17pub trait HsmBackend: Send + Sync {
19 fn seal(&self, share: &[u8], label: &str) -> Result<SealedShare, HsmError>;
21
22 fn unseal(&self, sealed: &SealedShare) -> Result<Vec<u8>, HsmError>;
24
25 fn generate_key(&self, label: &str) -> Result<String, HsmError>;
27
28 fn delete_key(&self, handle: &str) -> Result<(), HsmError>;
30
31 fn attest(&self, challenge: &[u8]) -> Result<Vec<u8>, HsmError>;
33
34 fn name(&self) -> &str;
36}
37
38#[derive(Debug)]
40pub enum HsmError {
41 KeyNotFound(String),
42 AttestationFailed,
43 OperationFailed(String),
44 Unsupported,
45}
46
47impl std::fmt::Display for HsmError {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 match self {
50 Self::KeyNotFound(h) => write!(f, "key not found: {h}"),
51 Self::AttestationFailed => write!(f, "attestation failed"),
52 Self::OperationFailed(m) => write!(f, "operation failed: {m}"),
53 Self::Unsupported => write!(f, "unsupported operation"),
54 }
55 }
56}
57
58impl std::error::Error for HsmError {}
59
60#[derive(Default)]
62pub struct MockHsmBackend {
63 keys: std::sync::Mutex<HashMap<String, Vec<u8>>>,
64 counter: std::sync::Mutex<u64>,
65}
66
67impl HsmBackend for MockHsmBackend {
68 fn seal(&self, share: &[u8], label: &str) -> Result<SealedShare, HsmError> {
69 let handle = format!("mock-key-{label}");
70 self.keys
71 .lock()
72 .unwrap()
73 .insert(handle.clone(), share.to_vec());
74 Ok(SealedShare {
75 key_handle: handle,
76 encrypted_share: share.iter().map(|b| b ^ 0x42).collect(),
77 attestation_pubkey_hex: "mock-attestation-pubkey".into(),
78 })
79 }
80
81 fn unseal(&self, sealed: &SealedShare) -> Result<Vec<u8>, HsmError> {
82 let keys = self.keys.lock().unwrap();
83 match keys.get(&sealed.key_handle) {
84 Some(share) => Ok(share.clone()),
85 None => Err(HsmError::KeyNotFound(sealed.key_handle.clone())),
86 }
87 }
88
89 fn generate_key(&self, label: &str) -> Result<String, HsmError> {
90 let mut counter = self.counter.lock().unwrap();
91 *counter += 1;
92 let handle = format!("mock-key-{label}-{}", *counter);
93 self.keys
94 .lock()
95 .unwrap()
96 .insert(handle.clone(), vec![0; 32]);
97 Ok(handle)
98 }
99
100 fn delete_key(&self, handle: &str) -> Result<(), HsmError> {
101 self.keys.lock().unwrap().remove(handle);
102 Ok(())
103 }
104
105 fn attest(&self, challenge: &[u8]) -> Result<Vec<u8>, HsmError> {
106 let mut result = vec![0u8; 32];
107 for (i, b) in challenge.iter().enumerate() {
108 result[i % 32] ^= b;
109 }
110 Ok(result)
111 }
112
113 fn name(&self) -> &str {
114 "mock-hsm"
115 }
116}
117
118pub struct ShareVault {
120 backend: Box<dyn HsmBackend>,
121}
122
123impl ShareVault {
124 pub fn new(backend: Box<dyn HsmBackend>) -> Self {
125 Self { backend }
126 }
127
128 pub fn store(&self, party_idx: u32, share: &[u8]) -> Result<SealedShare, HsmError> {
129 self.backend.seal(share, &format!("party-{party_idx}"))
130 }
131
132 pub fn retrieve(&self, sealed: &SealedShare) -> Result<Vec<u8>, HsmError> {
133 self.backend.unseal(sealed)
134 }
135
136 pub fn attest(&self, challenge: &[u8]) -> Result<Vec<u8>, HsmError> {
137 self.backend.attest(challenge)
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn mock_backend_seal_unseal() {
147 let hsm = MockHsmBackend::default();
148 let sealed = hsm.seal(b"secret share", "test").unwrap();
149 let recovered = hsm.unseal(&sealed).unwrap();
150 assert_eq!(recovered, b"secret share");
151 }
152
153 #[test]
154 fn mock_backend_key_not_found() {
155 let hsm = MockHsmBackend::default();
156 let sealed = SealedShare {
157 key_handle: "nonexistent".into(),
158 encrypted_share: vec![],
159 attestation_pubkey_hex: "".into(),
160 };
161 assert!(matches!(hsm.unseal(&sealed), Err(HsmError::KeyNotFound(_))));
162 }
163
164 #[test]
165 fn mock_backend_generate_and_delete() {
166 let hsm = MockHsmBackend::default();
167 let handle = hsm.generate_key("test").unwrap();
168 hsm.delete_key(&handle).unwrap();
169 }
170
171 #[test]
172 fn mock_backend_attest() {
173 let hsm = MockHsmBackend::default();
174 let attestation = hsm.attest(b"challenge").unwrap();
175 assert_eq!(attestation.len(), 32);
176 }
177
178 #[test]
179 fn vault_store_retrieve() {
180 let vault = ShareVault::new(Box::new(MockHsmBackend::default()));
181 let sealed = vault.store(1, b"party-1-share").unwrap();
182 let recovered = vault.retrieve(&sealed).unwrap();
183 assert_eq!(recovered, b"party-1-share");
184 }
185
186 #[test]
187 fn vault_attest() {
188 let vault = ShareVault::new(Box::new(MockHsmBackend::default()));
189 let attestation = vault.attest(b"nonce").unwrap();
190 assert_eq!(attestation.len(), 32);
191 }
192
193 #[test]
194 fn different_labels_different_handles() {
195 let hsm = MockHsmBackend::default();
196 let s1 = hsm.seal(b"a", "label1").unwrap();
197 let s2 = hsm.seal(b"b", "label2").unwrap();
198 assert_ne!(s1.key_handle, s2.key_handle);
199 }
200
201 #[test]
202 fn backend_name() {
203 let hsm = MockHsmBackend::default();
204 assert_eq!(hsm.name(), "mock-hsm");
205 }
206}