1pub mod local;
6pub mod pass;
7#[cfg(feature = "tpm")]
8pub mod tpm;
9
10pub use local::LocalPinStorage;
11pub use pass::PassPinStorage;
12#[cfg(feature = "tpm")]
13pub use tpm::TpmPinStorage;
14
15use serde::{Deserialize, Serialize};
16use soft_fido2::{PinState, StatusCode};
17
18#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
23pub struct SerializablePinConfig {
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub pin_hash: Option<Vec<u8>>,
27 #[serde(default = "default_min_pin_length")]
29 pub min_pin_length: u8,
30 #[serde(default)]
32 pub version: u64,
33 #[serde(default)]
35 pub force_pin_change: bool,
36 #[serde(default)]
38 pub credential_wrapping_generation: u64,
39 #[serde(skip_serializing_if = "Option::is_none")]
42 pub modified_at: Option<u64>,
43}
44
45fn default_min_pin_length() -> u8 {
46 4
47}
48
49impl SerializablePinConfig {
50 pub fn to_json_bytes(&self) -> Result<Vec<u8>, StatusCode> {
52 serde_json::to_vec(self).map_err(|_| StatusCode::Other)
53 }
54
55 pub fn from_json_bytes(bytes: &[u8]) -> Result<Self, StatusCode> {
57 serde_json::from_slice(bytes).map_err(|_| StatusCode::InvalidParameter)
58 }
59
60 pub fn is_pin_set(&self) -> bool {
62 self.pin_hash.is_some()
63 }
64}
65
66impl From<&PinState> for SerializablePinConfig {
67 fn from(state: &PinState) -> Self {
68 let modified_at = std::time::SystemTime::now()
69 .duration_since(std::time::UNIX_EPOCH)
70 .map(|d| d.as_millis() as u64)
71 .ok();
72 Self {
73 pin_hash: state.pin_hash.as_ref().map(|h| h.as_array().to_vec()),
74 min_pin_length: state.min_pin_length,
75 version: state.version,
76 force_pin_change: state.force_pin_change,
77 credential_wrapping_generation: state.credential_wrapping_generation,
78 modified_at,
79 }
80 }
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct SerializablePinRetries {
89 #[serde(default = "default_retries")]
91 pub retries: u8,
92 #[serde(default = "default_uv_retries")]
98 pub uv_retries: u8,
99 #[serde(skip_serializing_if = "Option::is_none")]
101 pub locked_until: Option<u64>,
102}
103
104fn default_retries() -> u8 {
105 8
106}
107
108fn default_uv_retries() -> u8 {
111 3
112}
113
114impl Default for SerializablePinRetries {
115 fn default() -> Self {
116 Self {
117 retries: 8,
118 uv_retries: 8,
119 locked_until: None,
120 }
121 }
122}
123
124impl SerializablePinRetries {
125 pub fn new(max_retries: u8, max_uv_retries: u8) -> Self {
127 Self {
128 retries: max_retries,
129 uv_retries: max_uv_retries,
130 locked_until: None,
131 }
132 }
133
134 pub fn to_json_bytes(&self) -> Result<Vec<u8>, StatusCode> {
136 serde_json::to_vec(self).map_err(|_| StatusCode::Other)
137 }
138
139 pub fn from_json_bytes(bytes: &[u8]) -> Result<Self, StatusCode> {
141 serde_json::from_slice(bytes).map_err(|_| StatusCode::InvalidParameter)
142 }
143}
144
145impl From<&PinState> for SerializablePinRetries {
146 fn from(state: &PinState) -> Self {
147 Self {
148 retries: state.retries,
149 uv_retries: state.uv_retries,
150 locked_until: state.locked_until,
151 }
152 }
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct SerializablePinState {
162 #[serde(skip_serializing_if = "Option::is_none")]
164 pub pin_hash: Option<Vec<u8>>,
165 pub retries: u8,
167 #[serde(default = "default_uv_retries")]
172 pub uv_retries: u8,
173 pub min_pin_length: u8,
175 pub version: u64,
177 #[serde(default)]
179 pub force_pin_change: bool,
180 #[serde(default)]
182 pub credential_wrapping_generation: u64,
183 #[serde(skip_serializing_if = "Option::is_none")]
185 pub locked_until: Option<u64>,
186}
187
188impl Default for SerializablePinState {
189 fn default() -> Self {
190 Self {
191 pin_hash: None,
192 retries: 8,
193 uv_retries: 8,
194 min_pin_length: 4,
195 version: 0,
196 force_pin_change: false,
197 credential_wrapping_generation: 0,
198 locked_until: None,
199 }
200 }
201}
202
203impl From<&PinState> for SerializablePinState {
204 fn from(state: &PinState) -> Self {
205 Self {
206 pin_hash: state.pin_hash.as_ref().map(|h| h.as_array().to_vec()),
207 retries: state.retries,
208 uv_retries: state.uv_retries,
209 min_pin_length: state.min_pin_length,
210 version: state.version,
211 force_pin_change: state.force_pin_change,
212 locked_until: state.locked_until,
213 credential_wrapping_generation: state.credential_wrapping_generation,
214 }
215 }
216}
217
218impl From<SerializablePinState> for PinState {
219 fn from(state: SerializablePinState) -> Self {
220 use soft_fido2_ctap::SecPinHash;
221 Self {
222 pin_hash: state.pin_hash.map(|h| {
223 let arr: [u8; 32] = h.as_slice().try_into().unwrap_or([0u8; 32]);
224 SecPinHash::new(arr)
225 }),
226 retries: state.retries,
227 uv_retries: state.uv_retries,
228 min_pin_length: state.min_pin_length,
229 version: state.version,
230 force_pin_change: state.force_pin_change,
231 locked_until: state.locked_until,
232 credential_wrapping_generation: state.credential_wrapping_generation,
233 }
234 }
235}
236
237impl SerializablePinState {
238 pub fn to_json_bytes(&self) -> Result<Vec<u8>, StatusCode> {
240 serde_json::to_vec(self).map_err(|_| StatusCode::Other)
241 }
242
243 pub fn from_json_bytes(bytes: &[u8]) -> Result<Self, StatusCode> {
245 serde_json::from_slice(bytes).map_err(|_| StatusCode::InvalidParameter)
246 }
247
248 pub fn from_parts(config: &SerializablePinConfig, retries: &SerializablePinRetries) -> Self {
250 Self {
251 pin_hash: config.pin_hash.clone(),
252 retries: retries.retries,
253 uv_retries: retries.uv_retries,
254 min_pin_length: config.min_pin_length,
255 version: config.version,
256 force_pin_change: config.force_pin_change,
257 locked_until: retries.locked_until,
258 credential_wrapping_generation: config.credential_wrapping_generation,
259 }
260 }
261}
262
263pub trait PinStorage: Send + Sync {
268 fn load_pin_state(&self) -> Result<PinState, StatusCode>;
272
273 fn save_pin_state(&self, state: &PinState) -> Result<(), StatusCode>;
275}
276
277impl PinStorage for () {
279 fn load_pin_state(&self) -> Result<PinState, StatusCode> {
280 Ok(PinState::new())
281 }
282
283 fn save_pin_state(&self, _state: &PinState) -> Result<(), StatusCode> {
284 Ok(())
285 }
286}
287
288impl PinStorage for Box<dyn PinStorage> {
289 fn load_pin_state(&self) -> Result<PinState, StatusCode> {
290 (**self).load_pin_state()
291 }
292
293 fn save_pin_state(&self, state: &PinState) -> Result<(), StatusCode> {
294 (**self).save_pin_state(state)
295 }
296}