1use core::convert::{TryFrom, TryInto};
2
3use iso7816::{Data, Status};
4
5use crate::oath;
6
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9pub enum Command<'l> {
10 Select(Select<'l>),
12 Calculate(Calculate<'l>),
14 CalculateAll(CalculateAll<'l>),
16 ClearPassword,
18 Delete(Delete<'l>),
20 ListCredentials,
22 Register(Register<'l>),
24 Reset,
26 SetPassword(SetPassword<'l>),
28 Validate(Validate<'l>),
30}
31
32#[derive(Clone, Copy, Eq, PartialEq)]
34pub struct Select<'l> {
35 pub aid: &'l [u8],
36}
37
38impl core::fmt::Debug for Select<'_> {
39 fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::result::Result<(), core::fmt::Error> {
40 fmt.debug_struct("Select")
41 .field("aid", &hex_str!(&self.aid, 5))
42 .finish()
43 }
44}
45
46#[derive(Clone, Copy, Debug, Eq, PartialEq)]
47pub struct SetPassword<'l> {
48 pub kind: oath::Kind,
49 pub algorithm: oath::Algorithm,
50 pub key: &'l [u8],
51 pub challenge: &'l [u8],
52 pub response: &'l [u8],
53}
54
55impl<'l, const C: usize> TryFrom<&'l Data<C>> for SetPassword<'l> {
56 type Error = Status;
57 fn try_from(data: &'l Data<C>) -> Result<Self, Self::Error> {
58 use flexiber::TaggedSlice;
70 let mut decoder = flexiber::Decoder::new(data);
71 let slice: TaggedSlice = decoder.decode().unwrap();
72 assert!(slice.tag() == (oath::Tag::Key as u8).try_into().unwrap());
73 let (key_header, key) = slice.as_bytes().split_at(1);
74
75 let kind: oath::Kind = key_header[0].try_into()?;
76 let algorithm: oath::Algorithm = key_header[0].try_into()?;
78 let slice: TaggedSlice = decoder.decode().unwrap();
81 assert!(slice.tag() == (oath::Tag::Challenge as u8).try_into().unwrap());
82 let challenge = slice.as_bytes();
83 let slice: TaggedSlice = decoder.decode().unwrap();
86 assert!(slice.tag() == (oath::Tag::Response as u8).try_into().unwrap());
87 let response = slice.as_bytes();
88 Ok(SetPassword {
91 kind,
92 algorithm,
93 key,
94 challenge,
95 response,
96 })
97 }
98}
99
100#[derive(Clone, Copy, Debug, Eq, PartialEq)]
101pub struct Validate<'l> {
102 pub response: &'l [u8],
103 pub challenge: &'l [u8],
104}
105
106impl<'l, const C: usize> TryFrom<&'l Data<C>> for Validate<'l> {
107 type Error = Status;
108 fn try_from(data: &'l Data<C>) -> Result<Self, Self::Error> {
109 use flexiber::TaggedSlice;
110 let mut decoder = flexiber::Decoder::new(data);
111
112 let slice: TaggedSlice = decoder.decode().unwrap();
113 assert!(slice.tag() == (oath::Tag::Response as u8).try_into().unwrap());
114 let response = slice.as_bytes();
115
116 let slice: TaggedSlice = decoder.decode().unwrap();
117 assert!(slice.tag() == (oath::Tag::Challenge as u8).try_into().unwrap());
118 let challenge = slice.as_bytes();
119
120 Ok(Validate { challenge, response })
121 }
122}
123
124#[derive(Clone, Copy, Debug, Eq, PartialEq)]
125pub struct Calculate<'l> {
126 pub label: &'l [u8],
127 pub challenge: &'l [u8],
128}
129
130impl<'l, const C: usize> TryFrom<&'l Data<C>> for Calculate<'l> {
131 type Error = Status;
132 fn try_from(data: &'l Data<C>) -> Result<Self, Self::Error> {
133 use flexiber::TaggedSlice;
134 let mut decoder = flexiber::Decoder::new(data);
135
136 let first: TaggedSlice = decoder.decode().unwrap();
137 assert!(first.tag() == (oath::Tag::Name as u8).try_into().unwrap());
138 let label = first.as_bytes();
139
140 let second: TaggedSlice = decoder.decode().unwrap();
141 assert!(second.tag() == (oath::Tag::Challenge as u8).try_into().unwrap());
142 let challenge = second.as_bytes();
143
144 Ok(Calculate { label, challenge })
145 }
146}
147
148#[derive(Clone, Copy, Debug, Eq, PartialEq)]
149pub struct CalculateAll<'l> {
150 pub challenge: &'l [u8],
151}
152
153impl<'l, const C: usize> TryFrom<&'l Data<C>> for CalculateAll<'l> {
154 type Error = Status;
155 fn try_from(data: &'l Data<C>) -> Result<Self, Self::Error> {
156 use flexiber::TaggedSlice;
157 let mut decoder = flexiber::Decoder::new(data);
158
159 let first: TaggedSlice = decoder.decode().unwrap();
160 assert!(first.tag() == (oath::Tag::Challenge as u8).try_into().unwrap());
161 let challenge = first.as_bytes();
162
163 Ok(CalculateAll { challenge })
164 }
165}
166
167
168#[derive(Clone, Copy, Eq, PartialEq)]
169pub struct Delete<'l> {
170 pub label: &'l [u8],
171}
172
173impl core::fmt::Debug for Delete<'_> {
174 fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::result::Result<(), core::fmt::Error> {
175 fmt.debug_struct("Credential")
176 .field("label", &core::str::from_utf8(self.label).unwrap_or(&"invalid UTF8 label"))
177 .finish()
178 }
179}
180
181
182impl<'l, const C: usize> TryFrom<&'l Data<C>> for Delete<'l> {
183 type Error = iso7816::Status;
184 fn try_from(data: &'l Data<C>) -> Result<Self, Self::Error> {
185 use flexiber::TaggedSlice;
186 let mut decoder = flexiber::Decoder::new(data);
187
188 let first: TaggedSlice = decoder.decode().unwrap();
189 assert!(first.tag() == (oath::Tag::Name as u8).try_into().unwrap());
190 let label = first.as_bytes();
191
192 Ok(Delete { label })
193 }
194}
195
196#[derive(Clone, Copy, Debug, Eq, PartialEq)]
197pub struct Register<'l> {
198 pub credential: Credential<'l>,
199}
200
201#[derive(Clone, Copy, Eq, PartialEq)]
202pub struct Credential<'l> {
203 pub label: &'l [u8],
204 pub kind: oath::Kind,
205 pub algorithm: oath::Algorithm,
206 pub digits: u8,
207 pub secret: &'l [u8],
220 pub touch_required: bool,
221 pub counter: Option<u32>,
222}
223
224impl core::fmt::Debug for Credential<'_> {
225 fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::result::Result<(), core::fmt::Error> {
226 fmt.debug_struct("Credential")
227 .field("label", &core::str::from_utf8(self.label).unwrap_or(&"invalid UTF8 label")) .field("kind", &self.kind)
229 .field("alg", &self.algorithm)
230 .field("digits", &self.digits)
231 .field("secret", &hex_str!(&self.secret, 4))
232 .field("touch", &self.touch_required)
233 .field("counter", &self.counter)
234 .finish()
235 }
236}
237
238#[derive(Clone, Copy, Debug, Eq, PartialEq)]
248struct Properties(u8);
249
250impl Properties {
251 fn touch_required(&self) -> bool {
252 self.0 & (oath::Properties::RequireTouch as u8) != 0
253 }
254}
255impl<'a> flexiber::Decodable<'a> for Properties {
256 fn decode(decoder: &mut flexiber::Decoder<'a>) -> flexiber::Result<Properties> {
257 let two_bytes: [u8; 2] = decoder.decode()?;
258 let [tag, properties] = two_bytes;
259 use flexiber::Tagged;
260 assert_eq!(flexiber::Tag::try_from(tag).unwrap(), Self::tag());
261 Ok(Properties(properties))
262 }
263}
264impl flexiber::Tagged for Properties {
265 fn tag() -> flexiber::Tag {
266 let ret = flexiber::Tag::try_from(oath::Tag::Property as u8).unwrap();
267 ret
268
269 }
270}
271
272impl<'l, const C: usize> TryFrom<&'l Data<C>> for Register<'l> {
273 type Error = iso7816::Status;
274 fn try_from(data: &'l Data<C>) -> Result<Self, Self::Error> {
275 use flexiber::{Decodable, TagLike};
276 type TaggedSlice<'a> = flexiber::TaggedSlice<'a, flexiber::SimpleTag>;
277 let mut decoder = flexiber::Decoder::new(data);
278
279 let first: TaggedSlice = decoder.decode().unwrap();
281 assert!(first.tag() == (oath::Tag::Name as u8).try_into().unwrap());
282 let label = first.as_bytes();
283
284 let second: TaggedSlice = decoder.decode().unwrap();
286 second.tag().assert_eq((oath::Tag::Key as u8).try_into().unwrap()).unwrap();
287 let (secret_header, secret) = second.as_bytes().split_at(2);
288
289 let kind: oath::Kind = secret_header[0].try_into()?;
290 let algorithm: oath::Algorithm = secret_header[0].try_into()?;
291 let digits = secret_header[1];
292
293 let maybe_properties: Option<Properties> = decoder.decode().unwrap();
294 let touch_required = maybe_properties
297 .map(|properties| {
298 info_now!("unraveling {:?}", &properties);
299 properties.touch_required()
300 })
301 .unwrap_or(false);
302
303 let mut counter = None;
304 if kind == oath::Kind::Hotp {
307 counter = Some(0);
309 if let Ok(last) = TaggedSlice::decode(&mut decoder) {
310 if last.tag() == (oath::Tag::InitialMovingFactor as u8).try_into().unwrap() {
311 let bytes = last.as_bytes();
312 if bytes.len() == 4 {
313 counter = Some(u32::from_be_bytes(bytes.try_into().unwrap()));
314 }
315 }
316 }
317 debug_now!("counter set to {:?}", &counter);
318 }
319
320 let credential = Credential {
321 label,
322 kind,
323 algorithm,
324 digits,
325 secret,
326 touch_required,
327 counter,
328 };
329
330 Ok(Register { credential })
331 }
332}
333
334impl<'l, const C: usize> TryFrom<&'l iso7816::Command<C>> for Command<'l> {
335 type Error = Status;
336 fn try_from(command: &'l iso7816::Command<C>) -> Result<Self, Self::Error> {
343 let (class, instruction, p1, p2) = (command.class(), command.instruction(), command.p1, command.p2);
344 let data = command.data();
345
346 if !class.secure_messaging().none() {
347 return Err(Status::SecureMessagingNotSupported);
348 }
349
350 if class.channel() != Some(0) {
351 return Err(Status::LogicalChannelNotSupported);
352 }
353
354 if (0x00, iso7816::Instruction::Select, 0x04, 0x00) == (class.into_inner(), instruction, p1, p2) {
357 Ok(Self::Select(Select::try_from(data)?))
358 } else {
359 let instruction_byte: u8 = instruction.into();
360 let instruction: oath::Instruction = instruction_byte.try_into()?;
361 Ok(match (class.into_inner(), instruction, p1, p2) {
362 (0x00, oath::Instruction::Calculate, 0x00, 0x01) => Self::Calculate(Calculate::try_from(data)?),
364 (0x00, oath::Instruction::CalculateAll, 0x00, 0x01) => Self::CalculateAll(CalculateAll::try_from(data)?),
365 (0x00, oath::Instruction::Delete, 0x00, 0x00) => Self::Delete(Delete::try_from(data)?),
366 (0x00, oath::Instruction::List, 0x00, 0x00) => Self::ListCredentials,
367 (0x00, oath::Instruction::Put, 0x00, 0x00) => Self::Register(Register::try_from(data)?),
368 (0x00, oath::Instruction::Reset, 0xde, 0xad) => Self::Reset,
369 (0x00, oath::Instruction::SetCode, 0x00, 0x00) => {
370 if data.len() == 2 {
372 Self::ClearPassword
373 } else {
374 Self::SetPassword(SetPassword::try_from(data)?)
375 }
376 }
377 (0x00, oath::Instruction::Validate, 0x00, 0x00) => Self::Validate(Validate::try_from(data)?),
378 _ => return Err(Status::InstructionNotSupportedOrInvalid),
379 })
380 }
381 }
382}
383
384impl<'l, const C: usize> TryFrom<&'l Data<C>> for Select<'l> {
385 type Error = Status;
386 fn try_from(data: &'l Data<C>) -> Result<Self, Self::Error> {
387 Ok(match data.as_slice() {
389 crate::YUBICO_OATH_AID => Self { aid: data },
390 _ => return Err(Status::NotFound),
391 })
392 }
393}
394