1pub mod calculate;
4pub mod calculate_all;
5mod error;
6mod escape_ascii;
7pub mod list;
8pub mod select;
9
10pub use error::Error;
11use pcsc::{Card, Context, Protocols, Scope, ShareMode, MAX_BUFFER_SIZE};
12use std::ffi::CString;
13use std::fmt;
14
15pub struct YubiKey {
16 name: CString,
17 card: Card,
18}
19
20impl fmt::Debug for YubiKey {
21 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22 f.debug_tuple("YubiKey").field(&self.name).finish()
23 }
24}
25
26impl YubiKey {
27 #[tracing::instrument(err, ret, skip_all)]
28 pub fn connect(buf: &mut Vec<u8>) -> Result<Self, Error> {
29 let context = Context::establish(Scope::User)?;
30 Self::connect_with(&context, buf)
31 }
32
33 #[tracing::instrument(err, ret, skip_all)]
34 pub fn connect_with(context: &Context, buf: &mut Vec<u8>) -> Result<Self, Error> {
35 const NAME: &[u8] = b"yubico yubikey";
37
38 buf.resize(context.list_readers_len()?, 0);
39 let name = context
40 .list_readers(buf)?
41 .find(|name| {
42 name.to_bytes()
44 .to_ascii_lowercase()
45 .windows(NAME.len())
46 .any(|window| window == NAME)
47 })
48 .ok_or(Error::NoDevice)?;
49 tracing::debug!(?name);
50 Ok(Self {
51 name: name.to_owned(),
52 card: context.connect(name, ShareMode::Shared, Protocols::ANY)?,
53 })
54 }
55
56 #[tracing::instrument(err, level = "debug", ret)]
57 fn transmit<'a>(&self, buf: &'a mut Vec<u8>) -> Result<&'a [u8], Error> {
58 #[allow(clippy::redundant_locals)]
60 let buf = buf;
61 if buf.len() >= 5 {
62 buf[4] = (buf.len() - 5).try_into()?;
64 }
65 let mid = buf.len();
66 loop {
67 let len = buf.len();
68 buf.resize(len + MAX_BUFFER_SIZE, 0);
69 let (occupied, vacant) = buf.split_at_mut(len);
70 let send = if mid == len {
71 &occupied[..mid]
72 } else {
73 &[0x00, 0xa5, 0x00, 0x00]
75 };
76 tracing::debug!(?send);
77 let receive = self.card.transmit(send, vacant)?;
78 tracing::debug!(?receive);
79 let len = len + receive.len();
80 buf.truncate(len);
81 let code = u16::from_le_bytes([
82 buf.pop().ok_or(Error::InsufficientData)?,
83 buf.pop().ok_or(Error::InsufficientData)?,
84 ]);
85 match code {
86 0x9000 => {
87 break Ok(&buf[mid..]);
88 }
89 0x6100..=0x61ff => Ok(()),
90 0x6a84 => Err(Error::NoSpace),
91 0x6984 => Err(Error::NoSuchObject),
92 0x6982 => Err(Error::AuthRequired),
93 0x6a80 => Err(Error::WrongSyntax),
94 0x6581 => Err(Error::GenericError),
95 _ => Err(Error::UnknownCode(code)),
96 }?;
97 }
98 }
99
100 fn push(buf: &mut Vec<u8>, tag: u8, data: &[u8]) -> Result<(), Error> {
101 buf.push(tag);
102 buf.push(data.len().try_into()?);
103 buf.extend_from_slice(data);
104 Ok(())
105 }
106
107 fn pop<'a>(buf: &mut &'a [u8], tags: &[u8]) -> Result<(u8, &'a [u8]), Error> {
108 let tag = *buf.first().ok_or(Error::InsufficientData)?;
109 if tags.contains(&tag) {
110 let len = *buf.get(1).ok_or(Error::InsufficientData)? as usize;
111 let data = buf.get(2..2 + len).ok_or(Error::InsufficientData)?;
112 *buf = &buf[2 + len..];
113 Ok((tag, data))
114 } else {
115 Err(Error::UnexpectedValue(tag))
116 }
117 }
118}
119
120#[derive(Clone, Copy, Debug)]
121pub enum Algorithm {
122 HmacSha1,
123 HmacSha256,
124 HmacSha512,
125}
126
127impl TryFrom<u8> for Algorithm {
128 type Error = Error;
129
130 fn try_from(value: u8) -> Result<Self, Self::Error> {
131 match value {
132 0x01 => Ok(Self::HmacSha1),
133 0x02 => Ok(Self::HmacSha256),
134 0x03 => Ok(Self::HmacSha512),
135 _ => Err(Error::UnexpectedValue(value)),
136 }
137 }
138}
139
140#[derive(Clone, Copy, Debug)]
141pub enum Type {
142 Hotp,
143 Totp,
144}
145
146impl TryFrom<u8> for Type {
147 type Error = Error;
148
149 fn try_from(value: u8) -> Result<Self, Self::Error> {
150 match value {
151 0x10 => Ok(Self::Hotp),
152 0x20 => Ok(Self::Totp),
153 _ => Err(Error::UnexpectedValue(value)),
154 }
155 }
156}