pub mod local;
pub mod pass;
#[cfg(feature = "tpm")]
pub mod tpm;
pub use local::LocalPinStorage;
pub use pass::PassPinStorage;
#[cfg(feature = "tpm")]
pub use tpm::TpmPinStorage;
use serde::{Deserialize, Serialize};
use soft_fido2::{PinState, StatusCode};
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
pub struct SerializablePinConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub pin_hash: Option<Vec<u8>>,
#[serde(default = "default_min_pin_length")]
pub min_pin_length: u8,
#[serde(default)]
pub version: u64,
#[serde(default)]
pub force_pin_change: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub modified_at: Option<u64>,
}
fn default_min_pin_length() -> u8 {
4
}
impl SerializablePinConfig {
pub fn to_json_bytes(&self) -> Result<Vec<u8>, StatusCode> {
serde_json::to_vec(self).map_err(|_| StatusCode::Other)
}
pub fn from_json_bytes(bytes: &[u8]) -> Result<Self, StatusCode> {
serde_json::from_slice(bytes).map_err(|_| StatusCode::InvalidParameter)
}
pub fn is_pin_set(&self) -> bool {
self.pin_hash.is_some()
}
}
impl From<&PinState> for SerializablePinConfig {
fn from(state: &PinState) -> Self {
let modified_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.ok();
Self {
pin_hash: state.pin_hash.as_ref().map(|h| h.as_array().to_vec()),
min_pin_length: state.min_pin_length,
version: state.version,
force_pin_change: state.force_pin_change,
modified_at,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializablePinRetries {
#[serde(default = "default_retries")]
pub retries: u8,
#[serde(default = "default_uv_retries")]
pub uv_retries: u8,
#[serde(skip_serializing_if = "Option::is_none")]
pub locked_until: Option<u64>,
}
fn default_retries() -> u8 {
8
}
fn default_uv_retries() -> u8 {
3
}
impl Default for SerializablePinRetries {
fn default() -> Self {
Self {
retries: 8,
uv_retries: 3,
locked_until: None,
}
}
}
impl SerializablePinRetries {
pub fn to_json_bytes(&self) -> Result<Vec<u8>, StatusCode> {
serde_json::to_vec(self).map_err(|_| StatusCode::Other)
}
pub fn from_json_bytes(bytes: &[u8]) -> Result<Self, StatusCode> {
serde_json::from_slice(bytes).map_err(|_| StatusCode::InvalidParameter)
}
}
impl From<&PinState> for SerializablePinRetries {
fn from(state: &PinState) -> Self {
Self {
retries: state.retries,
uv_retries: state.uv_retries,
locked_until: state.locked_until,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializablePinState {
#[serde(skip_serializing_if = "Option::is_none")]
pub pin_hash: Option<Vec<u8>>,
pub retries: u8,
#[serde(default = "default_uv_retries")]
pub uv_retries: u8,
pub min_pin_length: u8,
pub version: u64,
#[serde(default)]
pub force_pin_change: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub locked_until: Option<u64>,
}
impl Default for SerializablePinState {
fn default() -> Self {
Self {
pin_hash: None,
retries: 8,
uv_retries: 3,
min_pin_length: 4,
version: 0,
force_pin_change: false,
locked_until: None,
}
}
}
impl From<&PinState> for SerializablePinState {
fn from(state: &PinState) -> Self {
Self {
pin_hash: state.pin_hash.as_ref().map(|h| h.as_array().to_vec()),
retries: state.retries,
uv_retries: state.uv_retries,
min_pin_length: state.min_pin_length,
version: state.version,
force_pin_change: state.force_pin_change,
locked_until: state.locked_until,
}
}
}
impl From<SerializablePinState> for PinState {
fn from(state: SerializablePinState) -> Self {
use soft_fido2_ctap::SecPinHash;
Self {
pin_hash: state.pin_hash.map(|h| {
let arr: [u8; 32] = h.as_slice().try_into().unwrap_or([0u8; 32]);
SecPinHash::new(arr)
}),
retries: state.retries,
uv_retries: state.uv_retries,
min_pin_length: state.min_pin_length,
version: state.version,
force_pin_change: state.force_pin_change,
locked_until: state.locked_until,
}
}
}
impl SerializablePinState {
pub fn to_json_bytes(&self) -> Result<Vec<u8>, StatusCode> {
serde_json::to_vec(self).map_err(|_| StatusCode::Other)
}
pub fn from_json_bytes(bytes: &[u8]) -> Result<Self, StatusCode> {
serde_json::from_slice(bytes).map_err(|_| StatusCode::InvalidParameter)
}
pub fn from_parts(config: &SerializablePinConfig, retries: &SerializablePinRetries) -> Self {
Self {
pin_hash: config.pin_hash.clone(),
retries: retries.retries,
uv_retries: retries.uv_retries,
min_pin_length: config.min_pin_length,
version: config.version,
force_pin_change: config.force_pin_change,
locked_until: retries.locked_until,
}
}
}
pub trait PinStorage: Send + Sync {
fn load_pin_state(&self) -> Result<PinState, StatusCode>;
fn save_pin_state(&self, state: &PinState) -> Result<(), StatusCode>;
}
impl PinStorage for () {
fn load_pin_state(&self) -> Result<PinState, StatusCode> {
Ok(PinState::new())
}
fn save_pin_state(&self, _state: &PinState) -> Result<(), StatusCode> {
Ok(())
}
}