confium_store_openpgp_card/
backend.rs1use crate::slot::OpenpgpSlot;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct CardId(pub String);
9
10#[derive(Debug, thiserror::Error)]
12pub enum CardError {
13 #[error("card not present: {0}")]
15 NotPresent(String),
16 #[error("PIN blocked")]
18 PinBlocked,
19 #[error("wrong PIN ({attempts_remaining} attempts remaining)")]
21 WrongPin {
22 attempts_remaining: u32,
24 },
25 #[error("slot {0:?} not configured")]
27 SlotNotConfigured(OpenpgpSlot),
28 #[error("verification required for {0}")]
30 VerificationRequired(String),
31 #[error("card I/O error: {0}")]
33 Io(String),
34}
35
36pub trait OpenpgpCardBackend {
41 fn card_id(&self) -> Result<CardId, CardError>;
43
44 fn generate_keypair(
46 &mut self,
47 slot: OpenpgpSlot,
48 algorithm: &str,
49 ) -> Result<Vec<u8>, CardError>;
50
51 fn import_keypair(&mut self, slot: OpenpgpSlot, private_key: &[u8]) -> Result<(), CardError>;
53
54 fn public_key(&self, slot: OpenpgpSlot) -> Result<Vec<u8>, CardError>;
56
57 fn sign(&self, digest: &[u8]) -> Result<Vec<u8>, CardError>;
59
60 fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, CardError>;
62
63 fn verify_pin(&self, pin: &str) -> Result<(), CardError>;
65
66 fn verify_admin_pin(&self, admin_pin: &str) -> Result<(), CardError>;
68
69 fn factory_reset(&mut self) -> Result<(), CardError>;
71}
72
73pub struct MockOpenpgpCardBackend {
75 card_id: CardId,
76 keys: std::collections::HashMap<OpenpgpSlot, Vec<u8>>,
77 pin_verified: bool,
78 admin_verified: bool,
79}
80
81impl MockOpenpgpCardBackend {
82 pub fn new(card_id: impl Into<String>) -> Self {
84 Self {
85 card_id: CardId(card_id.into()),
86 keys: std::collections::HashMap::new(),
87 pin_verified: false,
88 admin_verified: false,
89 }
90 }
91}
92
93impl OpenpgpCardBackend for MockOpenpgpCardBackend {
94 fn card_id(&self) -> Result<CardId, CardError> {
95 Ok(self.card_id.clone())
96 }
97
98 fn generate_keypair(
99 &mut self,
100 slot: OpenpgpSlot,
101 algorithm: &str,
102 ) -> Result<Vec<u8>, CardError> {
103 if !self.admin_verified {
104 return Err(CardError::VerificationRequired("admin PIN".into()));
105 }
106 let _ = algorithm;
107 Ok(vec![slot as u8; 32])
109 }
110
111 fn import_keypair(&mut self, _slot: OpenpgpSlot, _private_key: &[u8]) -> Result<(), CardError> {
112 if !self.admin_verified {
113 return Err(CardError::VerificationRequired("admin PIN".into()));
114 }
115 Ok(())
116 }
117
118 fn public_key(&self, slot: OpenpgpSlot) -> Result<Vec<u8>, CardError> {
119 self.keys
120 .get(&slot)
121 .cloned()
122 .or_else(|| Some(vec![slot as u8; 32]))
123 .ok_or(CardError::SlotNotConfigured(slot))
124 }
125
126 fn sign(&self, digest: &[u8]) -> Result<Vec<u8>, CardError> {
127 if !self.pin_verified {
128 return Err(CardError::VerificationRequired("user PIN".into()));
129 }
130 Ok(digest.to_vec())
131 }
132
133 fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, CardError> {
134 if !self.pin_verified {
135 return Err(CardError::VerificationRequired("user PIN".into()));
136 }
137 Ok(ciphertext.to_vec())
138 }
139
140 fn verify_pin(&self, _pin: &str) -> Result<(), CardError> {
141 Ok(())
143 }
144
145 fn verify_admin_pin(&self, _admin_pin: &str) -> Result<(), CardError> {
146 Ok(())
147 }
148
149 fn factory_reset(&mut self) -> Result<(), CardError> {
150 Ok(())
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn mock_backend_card_id() {
160 let backend = MockOpenpgpCardBackend::new("YubiKey-001234");
161 let id = backend.card_id().unwrap();
162 assert_eq!(id.0, "YubiKey-001234");
163 }
164
165 #[test]
166 fn mock_backend_sign_without_pin_fails() {
167 let backend = MockOpenpgpCardBackend::new("test");
168 let result = backend.sign(b"hello");
169 assert!(matches!(result, Err(CardError::VerificationRequired(_))));
170 }
171}