This commit is contained in:
Grimmauld 2025-02-06 12:03:02 +01:00
parent ec1d1bba0e
commit 5c01623394
No known key found for this signature in database
2 changed files with 72 additions and 81 deletions

View file

@ -1,13 +1,11 @@
/// Utilities for interacting with YubiKey OATH/TOTP functionality
extern crate pcsc;
extern crate byteorder; extern crate byteorder;
/// Utilities for interacting with YubiKey OATH/TOTP functionality
extern crate pcsc;
use std::ffi::{CString};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::ffi::CString;
use std::io::{Cursor, Read, Write}; use std::io::{Cursor, Read, Write};
use std::time::{SystemTime}; use std::time::SystemTime;
pub type DetectResult<'a> = Result<Vec<YubiKey<'a>>, pcsc::Error>; pub type DetectResult<'a> = Result<Vec<YubiKey<'a>>, pcsc::Error>;
@ -39,55 +37,43 @@ pub fn format_code(code: u32, digits: OathDigits) -> String {
} else { } else {
code_string.split_off(code_string.len() - 6) code_string.split_off(code_string.len() - 6)
} }
}, }
OathDigits::Eight => { OathDigits::Eight => {
if code_string.len() <= 8 { if code_string.len() <= 8 {
format!("{:0>8}", code_string) format!("{:0>8}", code_string)
} else { } else {
code_string.split_off(code_string.len() - 8) code_string.split_off(code_string.len() - 8)
} }
}, }
} }
} }
fn to_error_response(sw1: u8, sw2: u8) -> Option<String> { fn to_error_response(sw1: u8, sw2: u8) -> Option<String> {
let code: usize = (sw1 as usize | sw2 as usize) << 8; let code: usize = (sw1 as usize | sw2 as usize) << 8;
match code { match code {
code if code == ErrorResponse::GenericError as usize => { code if code == ErrorResponse::GenericError as usize => Some(String::from("Generic error")),
Some(String::from("Generic error")) code if code == ErrorResponse::NoSpace as usize => Some(String::from("No space on device")),
},
code if code == ErrorResponse::NoSpace as usize => {
Some(String::from("No space on device"))
},
code if code == ErrorResponse::CommandAborted as usize => { code if code == ErrorResponse::CommandAborted as usize => {
Some(String::from("Command was aborted")) Some(String::from("Command was aborted"))
}, }
code if code == ErrorResponse::AuthRequired as usize => { code if code == ErrorResponse::AuthRequired as usize => {
Some(String::from("Authentication required")) Some(String::from("Authentication required"))
}, }
code if code == ErrorResponse::WrongSyntax as usize => { code if code == ErrorResponse::WrongSyntax as usize => Some(String::from("Wrong syntax")),
Some(String::from("Wrong syntax"))
},
code if code == ErrorResponse::InvalidInstruction as usize => { code if code == ErrorResponse::InvalidInstruction as usize => {
Some(String::from("Invalid instruction")) Some(String::from("Invalid instruction"))
}, }
code if code == SuccessResponse::Okay as usize => { code if code == SuccessResponse::Okay as usize => None,
None sw1 if sw1 == SuccessResponse::MoreData as usize => None,
}, _ => Some(String::from("Unknown error")),
sw1 if sw1 == SuccessResponse::MoreData as usize => {
None
},
_ => {
Some(String::from("Unknown error"))
},
} }
} }
fn to_tlv(tag: Tag, value: &[u8]) -> Vec<u8> { fn to_tlv(tag: Tag, value: &[u8]) -> Vec<u8> {
let mut buf = vec![tag as u8]; let mut buf = vec![tag as u8];
let len = value.len(); let len = value.len();
if len < 0x80 { if len < 0x80 {
buf.push(len as u8); buf.push(len as u8);
} else if len < 0xff { } else if len < 0xff {
@ -97,7 +83,7 @@ fn to_tlv(tag: Tag, value: &[u8]) -> Vec<u8> {
buf.push(0x82); buf.push(0x82);
buf.write_u16::<BigEndian>(len as u16).unwrap(); buf.write_u16::<BigEndian>(len as u16).unwrap();
} }
buf.write(value).unwrap(); buf.write(value).unwrap();
buf buf
} }
@ -176,12 +162,12 @@ pub enum OathType {
pub struct OathCredential { pub struct OathCredential {
pub name: String, pub name: String,
pub code: OathCode, pub code: OathCode,
// TODO: Support this stuff // TODO: Support this stuff
// pub oath_type: OathType, // pub oath_type: OathType,
// pub touch: bool, // pub touch: bool,
// pub algo: OathAlgo, // pub algo: OathAlgo,
// pub hidden: bool, // pub hidden: bool,
// pub steam: bool, // pub steam: bool,
} }
impl OathCredential { impl OathCredential {
@ -189,11 +175,11 @@ impl OathCredential {
OathCredential { OathCredential {
name: name.to_string(), name: name.to_string(),
code: code, code: code,
// oath_type: oath_type, // oath_type: oath_type,
// touch: touch, // touch: touch,
// algo: algo, // algo: algo,
// hidden: name.starts_with("_hidden:"), // hidden: name.starts_with("_hidden:"),
// steam: name.starts_with("Steam:"), // steam: name.starts_with("Steam:"),
} }
} }
} }
@ -208,8 +194,8 @@ pub enum OathDigits {
pub struct OathCode { pub struct OathCode {
pub digits: OathDigits, pub digits: OathDigits,
pub value: u32, pub value: u32,
// pub expiration: u32, // pub expiration: u32,
// pub steam: bool, // pub steam: bool,
} }
pub struct ApduResponse { pub struct ApduResponse {
@ -222,9 +208,9 @@ pub struct YubiKey<'a> {
pub name: &'a str, pub name: &'a str,
} }
impl<'a> YubiKey<'a> { impl<'a> YubiKey<'a> {
/// Read the OATH codes from the device /// Read the OATH codes from the device
pub fn get_oath_codes(&self) -> Result<Vec<OathCredential>, String>{ pub fn get_oath_codes(&self) -> Result<Vec<OathCredential>, String> {
// Establish a PC/SC context // Establish a PC/SC context
let ctx = match pcsc::Context::establish(pcsc::Scope::User) { let ctx = match pcsc::Context::establish(pcsc::Scope::User) {
Ok(ctx) => ctx, Ok(ctx) => ctx,
@ -233,9 +219,9 @@ impl<'a> YubiKey<'a> {
// Connect to the card // Connect to the card
let mut card = match ctx.connect( let mut card = match ctx.connect(
&CString::new(self.name).unwrap(), &CString::new(self.name).unwrap(),
pcsc::ShareMode::Shared, pcsc::ShareMode::Shared,
pcsc::Protocols::ANY pcsc::Protocols::ANY,
) { ) {
Ok(card) => card, Ok(card) => card,
Err(err) => return Err(format!("{}", err)), Err(err) => return Err(format!("{}", err)),
@ -256,9 +242,17 @@ impl<'a> YubiKey<'a> {
let mut response_buf = Vec::new(); let mut response_buf = Vec::new();
// Request OATH codes from device // Request OATH codes from device
let response = self.apdu(&tx, 0, Instruction::CalculateAll as u8, 0, let response = self.apdu(
0x01, Some(&to_tlv(Tag::Challenge, &tx,
&time_challenge(Some(SystemTime::now()))))); 0,
Instruction::CalculateAll as u8,
0,
0x01,
Some(&to_tlv(
Tag::Challenge,
&time_challenge(Some(SystemTime::now())),
)),
);
// Handle errors from command // Handle errors from command
match response { match response {
@ -275,19 +269,19 @@ impl<'a> YubiKey<'a> {
sw1 = more_resp.sw1; sw1 = more_resp.sw1;
sw2 = more_resp.sw2; sw2 = more_resp.sw2;
response_buf.extend(more_resp.buf); response_buf.extend(more_resp.buf);
}, }
Err(e) => { Err(e) => {
return Err(format!("{}", e)); return Err(format!("{}", e));
}, }
} }
} }
if let Some(msg) = to_error_response(sw1, sw2) { if let Some(msg) = to_error_response(sw1, sw2) {
return Err(format!("{}", msg)); return Err(format!("{}", msg));
} }
return Ok(self.parse_list(&response_buf).unwrap()); return Ok(self.parse_list(&response_buf).unwrap());
}, }
Err(e) => { Err(e) => {
return Err(format!("{}", e)); return Err(format!("{}", e));
} }
@ -298,7 +292,7 @@ impl<'a> YubiKey<'a> {
pub fn parse_list(&self, b: &[u8]) -> Result<Vec<OathCredential>, String> { pub fn parse_list(&self, b: &[u8]) -> Result<Vec<OathCredential>, String> {
let mut rdr = Cursor::new(b); let mut rdr = Cursor::new(b);
let mut results = Vec::new(); let mut results = Vec::new();
loop { loop {
if let Err(_) = rdr.read_u8() { if let Err(_) = rdr.read_u8() {
break; break;
@ -334,10 +328,10 @@ impl<'a> YubiKey<'a> {
if let Err(_) = rdr.read_exact(&mut name) { if let Err(_) = rdr.read_exact(&mut name) {
break; break;
}; };
rdr.read_u8().unwrap(); // TODO: Don't discard the response tag rdr.read_u8().unwrap(); // TODO: Don't discard the response tag
rdr.read_u8().unwrap(); // TODO: Don't discard the response lenght + 1 rdr.read_u8().unwrap(); // TODO: Don't discard the response lenght + 1
let digits = match rdr.read_u8() { let digits = match rdr.read_u8() {
Ok(6) => OathDigits::Six, Ok(6) => OathDigits::Six,
Ok(8) => OathDigits::Eight, Ok(8) => OathDigits::Eight,
@ -352,10 +346,10 @@ impl<'a> YubiKey<'a> {
results.push(OathCredential::new( results.push(OathCredential::new(
&String::from_utf8(name).unwrap(), &String::from_utf8(name).unwrap(),
OathCode { digits, value } OathCode { digits, value },
)); ));
} }
Ok(results) Ok(results)
} }
@ -363,11 +357,11 @@ impl<'a> YubiKey<'a> {
pub fn apdu( pub fn apdu(
&self, &self,
tx: &pcsc::Transaction, tx: &pcsc::Transaction,
class: u8, class: u8,
instruction: u8, instruction: u8,
parameter1: u8, parameter1: u8,
parameter2: u8, parameter2: u8,
data: Option<&[u8]> data: Option<&[u8]>,
) -> Result<ApduResponse, pcsc::Error> { ) -> Result<ApduResponse, pcsc::Error> {
// Create a container for the transaction payload // Create a container for the transaction payload
let mut tx_buf = Vec::new(); let mut tx_buf = Vec::new();
@ -386,7 +380,7 @@ impl<'a> YubiKey<'a> {
tx_buf.push(instruction); tx_buf.push(instruction);
tx_buf.push(parameter1); tx_buf.push(parameter1);
tx_buf.push(parameter2); tx_buf.push(parameter2);
// Construct and attach the data's byte count // Construct and attach the data's byte count
if nc > 255 { if nc > 255 {
tx_buf.push(0); tx_buf.push(0);
@ -394,7 +388,7 @@ impl<'a> YubiKey<'a> {
} else { } else {
tx_buf.push(nc as u8); tx_buf.push(nc as u8);
} }
// Attach the data itself if included // Attach the data itself if included
if let Some(data) = data { if let Some(data) = data {
tx_buf.write(data).unwrap(); tx_buf.write(data).unwrap();
@ -405,7 +399,7 @@ impl<'a> YubiKey<'a> {
let mut s = String::new(); let mut s = String::new();
for byte in &tx_buf { for byte in &tx_buf {
s += &format!("{:02X} ", byte); s += &format!("{:02X} ", byte);
} }
println!("DEBUG (SEND) >> {}", s); println!("DEBUG (SEND) >> {}", s);
} }
@ -438,9 +432,8 @@ impl<'a> YubiKey<'a> {
Ok(ApduResponse { Ok(ApduResponse {
buf, buf,
sw1: *sw1, sw1: *sw1,
sw2: *sw2, sw2: *sw2,
}) })
} }
} }

View file

@ -15,17 +15,17 @@ fn main() {
let context = pcsc::Context::establish(pcsc::Scope::User).unwrap(); let context = pcsc::Context::establish(pcsc::Scope::User).unwrap();
let mut readers_buf = [0; 2048]; let mut readers_buf = [0; 2048];
let readers = context.list_readers(&mut readers_buf).unwrap(); let readers = context.list_readers(&mut readers_buf).unwrap();
// Initialize a vector to track all our detected devices // Initialize a vector to track all our detected devices
let mut yubikeys: Vec<lib_ykoath::YubiKey> = Vec::new(); let mut yubikeys: Vec<lib_ykoath::YubiKey> = Vec::new();
// Iterate over the connected USB devices // Iterate over the connected USB devices
for reader in readers { for reader in readers {
yubikeys.push(lib_ykoath::YubiKey{ yubikeys.push(lib_ykoath::YubiKey {
name: reader.to_str().unwrap(), name: reader.to_str().unwrap(),
}); });
} }
// Show message if no YubiKey(s) // Show message if no YubiKey(s)
if yubikeys.len() == 0 { if yubikeys.len() == 0 {
println!("No yubikeys detected"); println!("No yubikeys detected");
@ -41,9 +41,9 @@ fn main() {
Err(e) => { Err(e) => {
println!("ERROR {}", e); println!("ERROR {}", e);
continue; continue;
}, }
}; };
// Show message is node codes found // Show message is node codes found
if codes.len() == 0 { if codes.len() == 0 {
println!("No credentials on device {}", device_label); println!("No credentials on device {}", device_label);
@ -54,10 +54,8 @@ fn main() {
let code = lib_ykoath::format_code(oath.code.value, oath.code.digits); let code = lib_ykoath::format_code(oath.code.value, oath.code.digits);
let name_clone = oath.name.clone(); let name_clone = oath.name.clone();
let mut label_vec: Vec<&str> = name_clone.split(":").collect(); let mut label_vec: Vec<&str> = name_clone.split(":").collect();
let mut code_entry_label: String = String::from( let mut code_entry_label: String = String::from(label_vec.remove(0));
label_vec.remove(0)
);
if label_vec.len() > 0 { if label_vec.len() > 0 {
code_entry_label.push_str(" ("); code_entry_label.push_str(" (");
code_entry_label.push_str(&label_vec.join("")); code_entry_label.push_str(&label_vec.join(""));