1#![cfg_attr(not(test), no_std)]
5#![warn(
6 missing_debug_implementations,
7 missing_docs,
8 non_ascii_idents,
9 trivial_casts,
10 unused,
11 unused_qualifications,
12 clippy::expect_used,
13 clippy::unwrap_used
14)]
15#![deny(unsafe_code)]
16
17#[allow(missing_docs)]
62pub mod reply;
63#[allow(missing_docs)]
64pub mod request;
65
66use core::str::FromStr;
67
68use serde::{Deserialize, Serialize};
69use trussed_core::{
70 config::MAX_SHORT_DATA_LENGTH,
71 serde_extensions::{Extension, ExtensionClient, ExtensionResult},
72 types::{Bytes, KeyId, Message, PathBuf},
73};
74
75pub const MAX_PIN_LENGTH: usize = MAX_SHORT_DATA_LENGTH;
77
78pub type Pin = Bytes<MAX_PIN_LENGTH>;
80
81#[derive(
104 Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize,
105)]
106pub struct PinId(u8);
107
108#[derive(
110 Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize,
111)]
112pub struct PinIdFromStrError;
113
114impl PinId {
115 pub fn path(&self) -> PathBuf {
119 let mut path = [0; 6];
120 path[0..4].copy_from_slice(b"pin.");
121 path[4..].copy_from_slice(&self.hex());
122
123 #[allow(clippy::unwrap_used)]
125 PathBuf::try_from(&path).ok().unwrap()
126 }
127
128 pub fn hex(&self) -> [u8; 2] {
130 const CHARS: &[u8; 16] = b"0123456789abcdef";
131 [
132 CHARS[usize::from(self.0 >> 4)],
133 CHARS[usize::from(self.0 & 0xf)],
134 ]
135 }
136
137 pub fn from_path(path: &str) -> Result<Self, PinIdFromStrError> {
139 let path = path.strip_prefix("pin.").ok_or(PinIdFromStrError)?;
140 if path.len() != 2 {
141 return Err(PinIdFromStrError);
142 }
143
144 let id = u8::from_str_radix(path, 16).map_err(|_| PinIdFromStrError)?;
145 Ok(PinId(id))
146 }
147}
148
149impl From<u8> for PinId {
150 fn from(id: u8) -> Self {
151 Self(id)
152 }
153}
154
155impl From<PinId> for u8 {
156 fn from(id: PinId) -> Self {
157 id.0
158 }
159}
160
161impl FromStr for PinId {
162 type Err = PinIdFromStrError;
163 fn from_str(s: &str) -> Result<Self, Self::Err> {
164 Self::from_path(s)
165 }
166}
167
168pub type AuthResult<'a, R, C> = ExtensionResult<'a, AuthExtension, R, C>;
170
171#[derive(Debug, Default)]
175pub struct AuthExtension;
176
177impl Extension for AuthExtension {
178 type Request = AuthRequest;
179 type Reply = AuthReply;
180}
181
182#[allow(clippy::large_enum_variant)]
183#[derive(Debug, Deserialize, Serialize)]
184#[allow(missing_docs)]
185pub enum AuthRequest {
186 HasPin(request::HasPin),
187 CheckPin(request::CheckPin),
188 GetPinKey(request::GetPinKey),
189 GetApplicationKey(request::GetApplicationKey),
190 SetPin(request::SetPin),
191 SetPinWithKey(request::SetPinWithKey),
192 ChangePin(request::ChangePin),
193 DeletePin(request::DeletePin),
194 DeleteAllPins(request::DeleteAllPins),
195 PinRetries(request::PinRetries),
196 ResetAppKeys(request::ResetAppKeys),
197 ResetAuthData(request::ResetAuthData),
198}
199
200#[derive(Debug, Deserialize, Serialize)]
201#[allow(missing_docs)]
202pub enum AuthReply {
203 HasPin(reply::HasPin),
204 CheckPin(reply::CheckPin),
205 GetPinKey(reply::GetPinKey),
206 GetApplicationKey(reply::GetApplicationKey),
207 SetPin(reply::SetPin),
208 SetPinWithKey(reply::SetPinWithKey),
209 ChangePin(reply::ChangePin),
210 DeletePin(reply::DeletePin),
211 DeleteAllPins(reply::DeleteAllPins),
212 PinRetries(reply::PinRetries),
213 ResetAppKeys(reply::ResetAppKeys),
214 ResetAuthData(reply::ResetAuthData),
215}
216
217pub trait AuthClient: ExtensionClient<AuthExtension> {
231 fn has_pin<I: Into<PinId>>(&mut self, id: I) -> AuthResult<'_, reply::HasPin, Self> {
233 self.extension(request::HasPin { id: id.into() })
234 }
235
236 fn check_pin<I>(&mut self, id: I, pin: Pin) -> AuthResult<'_, reply::CheckPin, Self>
242 where
243 I: Into<PinId>,
244 {
245 self.extension(request::CheckPin { id: id.into(), pin })
246 }
247
248 fn get_pin_key<I>(&mut self, id: I, pin: Pin) -> AuthResult<'_, reply::GetPinKey, Self>
255 where
256 I: Into<PinId>,
257 {
258 self.extension(request::GetPinKey { id: id.into(), pin })
259 }
260
261 fn set_pin<I: Into<PinId>>(
266 &mut self,
267 id: I,
268 pin: Pin,
269 retries: Option<u8>,
270 derive_key: bool,
271 ) -> AuthResult<'_, reply::SetPin, Self> {
272 self.extension(request::SetPin {
273 id: id.into(),
274 pin,
275 retries,
276 derive_key,
277 })
278 }
279
280 fn set_pin_with_key<I: Into<PinId>>(
286 &mut self,
287 id: I,
288 pin: Pin,
289 retries: Option<u8>,
290 key: KeyId,
291 ) -> AuthResult<'_, reply::SetPinWithKey, Self> {
292 self.extension(request::SetPinWithKey {
293 id: id.into(),
294 pin,
295 retries,
296 key,
297 })
298 }
299
300 fn change_pin<I: Into<PinId>>(
304 &mut self,
305 id: I,
306 old_pin: Pin,
307 new_pin: Pin,
308 ) -> AuthResult<'_, reply::ChangePin, Self> {
309 self.extension(request::ChangePin {
310 id: id.into(),
311 old_pin,
312 new_pin,
313 })
314 }
315
316 fn delete_pin<I: Into<PinId>>(&mut self, id: I) -> AuthResult<'_, reply::DeletePin, Self> {
318 self.extension(request::DeletePin { id: id.into() })
319 }
320
321 fn delete_all_pins(&mut self) -> AuthResult<'_, reply::DeleteAllPins, Self> {
323 self.extension(request::DeleteAllPins)
324 }
325
326 fn pin_retries<I: Into<PinId>>(&mut self, id: I) -> AuthResult<'_, reply::PinRetries, Self> {
328 self.extension(request::PinRetries { id: id.into() })
329 }
330
331 fn get_application_key(
333 &mut self,
334 info: Message,
335 ) -> AuthResult<'_, reply::GetApplicationKey, Self> {
336 self.extension(request::GetApplicationKey { info })
337 }
338
339 fn reset_app_keys(&mut self) -> AuthResult<'_, reply::ResetAppKeys, Self> {
341 self.extension(request::ResetAppKeys {})
342 }
343
344 fn reset_auth_data(&mut self) -> AuthResult<'_, reply::ResetAuthData, Self> {
346 self.extension(request::ResetAuthData {})
347 }
348}
349
350impl<C: ExtensionClient<AuthExtension>> AuthClient for C {}
351
352#[cfg(test)]
353mod tests {
354 use super::PinId;
355 use trussed_core::types::PathBuf;
356
357 #[test]
358 fn pin_id_path() {
359 for i in 0..=u8::MAX {
360 assert_eq!(Ok(PinId(i)), PinId::from_path(PinId(i).path().as_ref()));
361 let actual = PinId(i).path();
362 #[allow(clippy::unwrap_used)]
363 let expected = PathBuf::try_from(format!("pin.{i:02x}").as_str()).unwrap();
364 println!("id: {i}, actual: {actual}, expected: {expected}");
365 assert_eq!(actual, expected);
366 }
367 }
368}