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