oath_authenticator/state.rs
1use core::convert::TryInto;
2
3use iso7816::Status;
4// use iso7816::response::Result;
5
6use trussed::{
7 postcard_deserialize, postcard_serialize_bytes,
8 syscall, try_syscall,
9 types::{KeyId, Location, PathBuf},
10};
11
12#[derive(Clone, Debug, Default, Eq, PartialEq)]
13pub struct State {
14 // at startup, trussed is not callable yet.
15 // moreover, when worst comes to worst, filesystems are not available
16 // persistent: Option<Persistent>,
17 pub runtime: Runtime,
18 // temporary "state", to be removed again
19 // pub hack: Hack,
20 // trussed: RefCell<Trussed<S>>,
21}
22
23#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
24pub struct Persistent {
25 pub salt: [u8; 8],
26 /// This is the user's password, passed through PBKDF-HMAC-SHA1.
27 /// It is used for authorization using challenge HMAC-SHA1'ing.
28 pub authorization_key: Option<KeyId>,
29}
30
31#[derive(Clone, Debug, Default, Eq, PartialEq)]
32pub struct Runtime {
33 /// Not actually used - need to figure out "many credentials" case
34 pub previously: Option<CommandState>,
35 /// This gets rotated regularly, so someone sniffing on the bus can't replay.
36 /// There is a small window between a legitimate client authenticating,
37 /// and its next command that needs such authentication.
38 pub challenge: [u8; 8],
39 /// Gets set after a successful VALIDATE call,
40 /// good for use right after (e.g. to set/change/remove password),
41 /// and cleared thereafter.
42 pub client_authorized: bool,
43 /// For book-keeping purposes, set client_authorized / prevents it from being cleared before
44 /// returning control to caller of the app
45 pub client_newly_authorized: bool,
46}
47
48impl Runtime {
49 pub fn reset(&mut self) {
50 *self = Self::default();
51 }
52}
53
54impl Persistent {
55 pub fn password_set(&self) -> bool {
56 self.authorization_key.is_some()
57 }
58}
59
60impl State {
61 const FILENAME: &'static str = "state.bin";
62
63 // pub fn persistent<E, T>(
64 // &mut self,
65 // trussed: &mut T,
66 // f: impl FnOnce(&mut T, &mut Persistent) -> core::result::Result<(), E>
67 // )
68 // -> Result<(), E>
69
70 pub fn try_persistent<T>(
71 &mut self,
72 trussed: &mut T,
73 f: impl FnOnce(&mut T, &mut Persistent) -> Result<(), Status>
74 )
75 -> Result<(), Status>
76
77 where
78 T: trussed::Client,
79 {
80 // 1. If there is serialized, persistent state (i.e., the try_syscall! to `read_file` does
81 // not fail), then assume it is valid and deserialize it. If the reading fails, assume
82 // that this is the first run, and set defaults.
83 //
84 // NB: This is an attack vector. If the state can be corrupted, this clears the password.
85 // Consider resetting the device in this situation
86 let mut state: Persistent = try_syscall!(trussed.read_file(Location::Internal, PathBuf::from(Self::FILENAME)))
87 .map(|response| postcard_deserialize(&response.data).unwrap())
88 .unwrap_or_else(|_| {
89 let salt: [u8; 8] = syscall!(trussed.random_bytes(8)).bytes.as_ref().try_into().unwrap();
90 Persistent { salt, authorization_key: None }
91 });
92
93 // 2. Let the app read or modify the state
94 let result = f(trussed, &mut state);
95
96 // 3. Always write it back
97 syscall!(trussed.write_file(
98 Location::Internal,
99 PathBuf::from(Self::FILENAME),
100 postcard_serialize_bytes(&state).unwrap(),
101 None,
102 ));
103
104 // 4. Return whatever
105 result
106 }
107 pub fn persistent<T, X>(
108 &mut self,
109 trussed: &mut T,
110 f: impl FnOnce(&mut T, &mut Persistent) -> X
111 )
112 -> X
113
114 where
115 T: trussed::Client,
116 {
117 // 1. If there is serialized, persistent state (i.e., the try_syscall! to `read_file` does
118 // not fail), then assume it is valid and deserialize it. If the reading fails, assume
119 // that this is the first run, and set defaults.
120 //
121 // NB: This is an attack vector. If the state can be corrupted, this clears the password.
122 // Consider resetting the device in this situation
123 let mut state: Persistent = try_syscall!(trussed.read_file(Location::Internal, PathBuf::from(Self::FILENAME)))
124 .map(|response| postcard_deserialize(&response.data).unwrap())
125 .unwrap_or_else(|_| {
126 let salt: [u8; 8] = syscall!(trussed.random_bytes(8)).bytes.as_ref().try_into().unwrap();
127 Persistent { salt, authorization_key: None }
128 });
129
130 // 2. Let the app read or modify the state
131 let x = f(trussed, &mut state);
132
133 // 3. Always write it back
134 syscall!(trussed.write_file(
135 Location::Internal,
136 PathBuf::from(Self::FILENAME),
137 postcard_serialize_bytes(&state).unwrap(),
138 None,
139 ));
140 x
141 }
142}
143
144#[derive(Clone, Debug, Eq, PartialEq)]
145pub enum CommandState {
146 ListCredentials(usize),
147}
148