mirror of
https://github.com/LordGrimmauld/yubi-oath-rs.git
synced 2025-03-04 05:44:40 +01:00
Merge branch 'main' of github.com:LordGrimmauld/yubi-oath-rs
This commit is contained in:
commit
bb83774861
9 changed files with 123 additions and 189 deletions
3
.rustfmt.toml
Normal file
3
.rustfmt.toml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
imports_granularity = "Crate"
|
||||||
|
format_code_in_doc_comments = true
|
||||||
|
group_imports = "StdExternalCrate"
|
26
Cargo.lock
generated
26
Cargo.lock
generated
|
@ -121,19 +121,7 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "lib_ykoath2"
|
||||||
version = "0.2.169"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "memchr"
|
|
||||||
version = "2.7.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "oath-rs-experiments"
|
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"apdu-core",
|
"apdu-core",
|
||||||
|
@ -150,6 +138,18 @@ dependencies = [
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.169"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ouroboros"
|
name = "ouroboros"
|
||||||
version = "0.18.5"
|
version = "0.18.5"
|
||||||
|
|
|
@ -13,9 +13,13 @@ strum = { version = "0.27.0", features = [ ] }
|
||||||
strum_macros = "0.27.0"
|
strum_macros = "0.27.0"
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "oath-rs-experiments"
|
name = "lib_ykoath2"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["Grimmauld <grimmauld@grimmauld.de>"]
|
authors = ["Grimmauld <grimmauld@grimmauld.de>"]
|
||||||
description = "experiments with smartcards"
|
description = "experiments with smartcards"
|
||||||
license-file = "LICENSE"
|
license-file = "LICENSE"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "example"
|
||||||
|
path = "./src/example.rs"
|
|
@ -1,8 +1,7 @@
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use iso7816_tlv::simple::Tlv;
|
use iso7816_tlv::simple::Tlv;
|
||||||
use sha1::{Digest, Sha1};
|
use sha1::Digest;
|
||||||
use sha2::{Sha256, Sha512};
|
|
||||||
use strum::IntoEnumIterator; // 0.17.1
|
use strum::IntoEnumIterator; // 0.17.1
|
||||||
use strum_macros::EnumIter; // 0.17.1
|
use strum_macros::EnumIter; // 0.17.1
|
||||||
pub const INS_SELECT: u8 = 0xa4;
|
pub const INS_SELECT: u8 = 0xa4;
|
||||||
|
@ -25,19 +24,6 @@ pub enum ErrorResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ErrorResponse {
|
impl ErrorResponse {
|
||||||
pub fn as_string(self) -> String {
|
|
||||||
match self {
|
|
||||||
ErrorResponse::NoSpace => "No Space left on device",
|
|
||||||
ErrorResponse::CommandAborted => "Command aborted",
|
|
||||||
ErrorResponse::InvalidInstruction => "Invalid instruction",
|
|
||||||
ErrorResponse::AuthRequired => "Authentication required",
|
|
||||||
ErrorResponse::WrongSyntax => "Wrong syntax",
|
|
||||||
ErrorResponse::GenericError => "Generic Error",
|
|
||||||
ErrorResponse::NoSuchObject => "No such Object",
|
|
||||||
}
|
|
||||||
.to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn any_match(code: u16) -> Option<ErrorResponse> {
|
pub fn any_match(code: u16) -> Option<ErrorResponse> {
|
||||||
for resp in ErrorResponse::iter() {
|
for resp in ErrorResponse::iter() {
|
||||||
if code == resp as u16 {
|
if code == resp as u16 {
|
||||||
|
@ -48,6 +34,22 @@ impl ErrorResponse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ErrorResponse {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::NoSpace => f.write_str("No Space left on device"),
|
||||||
|
Self::CommandAborted => f.write_str("Command aborted"),
|
||||||
|
Self::InvalidInstruction => f.write_str("Invalid instruction"),
|
||||||
|
Self::AuthRequired => f.write_str("Authentication required"),
|
||||||
|
Self::WrongSyntax => f.write_str("Wrong syntax"),
|
||||||
|
Self::GenericError => f.write_str("Generic Error"),
|
||||||
|
Self::NoSuchObject => f.write_str("No such Object"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for ErrorResponse {}
|
||||||
|
|
||||||
#[derive(Debug, EnumIter, Clone, Copy)]
|
#[derive(Debug, EnumIter, Clone, Copy)]
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
pub enum SuccessResponse {
|
pub enum SuccessResponse {
|
||||||
|
@ -114,18 +116,18 @@ impl HashAlgo {
|
||||||
// returns a function capable of hashing a byte array
|
// returns a function capable of hashing a byte array
|
||||||
pub fn get_hash_fun(&self) -> impl Fn(&[u8]) -> Vec<u8> {
|
pub fn get_hash_fun(&self) -> impl Fn(&[u8]) -> Vec<u8> {
|
||||||
match self {
|
match self {
|
||||||
HashAlgo::Sha1 => |m: &[u8]| {
|
Self::Sha1 => |m: &[u8]| {
|
||||||
let mut hasher = Sha1::new();
|
let mut hasher = sha1::Sha1::new();
|
||||||
hasher.update(m);
|
hasher.update(m);
|
||||||
hasher.finalize().to_vec()
|
hasher.finalize().to_vec()
|
||||||
},
|
},
|
||||||
HashAlgo::Sha256 => |m: &[u8]| {
|
Self::Sha256 => |m: &[u8]| {
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = sha2::Sha256::new();
|
||||||
hasher.update(m);
|
hasher.update(m);
|
||||||
hasher.finalize().to_vec()
|
hasher.finalize().to_vec()
|
||||||
},
|
},
|
||||||
HashAlgo::Sha512 => |m: &[u8]| {
|
Self::Sha512 => |m: &[u8]| {
|
||||||
let mut hasher = Sha512::new();
|
let mut hasher = sha2::Sha512::new();
|
||||||
hasher.update(m);
|
hasher.update(m);
|
||||||
hasher.finalize().to_vec()
|
hasher.finalize().to_vec()
|
||||||
},
|
},
|
||||||
|
@ -135,9 +137,9 @@ impl HashAlgo {
|
||||||
// returns digest output size in number of bytes
|
// returns digest output size in number of bytes
|
||||||
pub fn digest_size(&self) -> usize {
|
pub fn digest_size(&self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
HashAlgo::Sha1 => 20,
|
Self::Sha1 => 20,
|
||||||
HashAlgo::Sha256 => 32,
|
Self::Sha256 => 32,
|
||||||
HashAlgo::Sha512 => 64,
|
Self::Sha512 => 64,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,12 +1,6 @@
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
mod lib_ykoath2;
|
|
||||||
|
|
||||||
use core::time;
|
|
||||||
use lib_ykoath2::OathSession;
|
use lib_ykoath2::OathSession;
|
||||||
use pcsc;
|
|
||||||
use std::process;
|
|
||||||
use std::thread;
|
|
||||||
// use crate::args::Cli;
|
// use crate::args::Cli;
|
||||||
|
|
||||||
// use clap::Parser;
|
// use clap::Parser;
|
||||||
|
@ -29,14 +23,14 @@ fn main() {
|
||||||
// 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");
|
||||||
process::exit(0);
|
std::process::exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print device info for all the YubiKeys we detected
|
// Print device info for all the YubiKeys we detected
|
||||||
for yubikey in yubikeys {
|
for yubikey in yubikeys {
|
||||||
let device_label: &str = yubikey;
|
let device_label: &str = yubikey;
|
||||||
println!("Found device with label {}", device_label);
|
println!("Found device with label {}", device_label);
|
||||||
let session = OathSession::new(yubikey);
|
let session = OathSession::new(yubikey).unwrap();
|
||||||
println!("YubiKey version is {:?}", session.get_version());
|
println!("YubiKey version is {:?}", session.get_version());
|
||||||
for c in session.list_oath_codes().unwrap() {
|
for c in session.list_oath_codes().unwrap() {
|
||||||
println!("{}", c);
|
println!("{}", c);
|
||||||
|
@ -55,7 +49,7 @@ fn main() {
|
||||||
println!("No credentials on device {}", device_label);
|
println!("No credentials on device {}", device_label);
|
||||||
}
|
}
|
||||||
|
|
||||||
thread::sleep(time::Duration::from_secs(5)); // show refresh is working
|
std::thread::sleep(std::time::Duration::from_secs(5)); // show refresh is working
|
||||||
|
|
||||||
// Enumerate the OATH codes
|
// Enumerate the OATH codes
|
||||||
for oath in codes {
|
for oath in codes {
|
|
@ -4,23 +4,13 @@ mod transaction;
|
||||||
use transaction::*;
|
use transaction::*;
|
||||||
mod oath_credential;
|
mod oath_credential;
|
||||||
mod oath_credentialid;
|
mod oath_credentialid;
|
||||||
use oath_credential::*;
|
|
||||||
use oath_credentialid::*;
|
|
||||||
/// Utilities for interacting with YubiKey OATH/TOTP functionality
|
/// Utilities for interacting with YubiKey OATH/TOTP functionality
|
||||||
extern crate pcsc;
|
use std::{fmt::Display, time::Duration, time::SystemTime};
|
||||||
use pbkdf2::pbkdf2_hmac_array;
|
|
||||||
use sha1::Sha1;
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
fmt::Display,
|
|
||||||
str::{self},
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
|
|
||||||
use base64::{engine::general_purpose, Engine as _};
|
use base64::{engine::general_purpose, Engine as _};
|
||||||
use hmac::{Hmac, Mac};
|
use hmac::{Hmac, Mac};
|
||||||
|
use oath_credential::*;
|
||||||
use std::time::SystemTime;
|
use oath_credentialid::*;
|
||||||
|
|
||||||
fn _get_device_id(salt: Vec<u8>) -> String {
|
fn _get_device_id(salt: Vec<u8>) -> String {
|
||||||
let result = HashAlgo::Sha256.get_hash_fun()(salt.leak());
|
let result = HashAlgo::Sha256.get_hash_fun()(salt.leak());
|
||||||
|
@ -32,13 +22,13 @@ fn _get_device_id(salt: Vec<u8>) -> String {
|
||||||
return general_purpose::URL_SAFE_NO_PAD.encode(hash_16_bytes);
|
return general_purpose::URL_SAFE_NO_PAD.encode(hash_16_bytes);
|
||||||
}
|
}
|
||||||
fn _hmac_sha1(key: &[u8], message: &[u8]) -> Vec<u8> {
|
fn _hmac_sha1(key: &[u8], message: &[u8]) -> Vec<u8> {
|
||||||
let mut mac = Hmac::<Sha1>::new_from_slice(key).expect("Invalid key length");
|
let mut mac = Hmac::<sha1::Sha1>::new_from_slice(key).expect("Invalid key length");
|
||||||
mac.update(message);
|
mac.update(message);
|
||||||
mac.finalize().into_bytes().to_vec()
|
mac.finalize().into_bytes().to_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _derive_key(salt: &[u8], passphrase: &str) -> Vec<u8> {
|
fn _derive_key(salt: &[u8], passphrase: &str) -> Vec<u8> {
|
||||||
pbkdf2_hmac_array::<Sha1, 16>(passphrase.as_bytes(), salt, 1000).to_vec()
|
pbkdf2::pbkdf2_hmac_array::<sha1::Sha1, 16>(passphrase.as_bytes(), salt, 1000).to_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _hmac_shorten_key(key: &[u8], algo: HashAlgo) -> Vec<u8> {
|
fn _hmac_shorten_key(key: &[u8], algo: HashAlgo) -> Vec<u8> {
|
||||||
|
@ -143,11 +133,10 @@ impl<'a> RefreshableOathCredential<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> OathSession<'a> {
|
impl<'a> OathSession<'a> {
|
||||||
pub fn new(name: &str) -> Self {
|
pub fn new(name: &str) -> Result<Self, Error> {
|
||||||
let transaction_context = TransactionContext::from_name(name);
|
let transaction_context = TransactionContext::from_name(name)?;
|
||||||
let info_buffer = transaction_context
|
let info_buffer =
|
||||||
.apdu_read_all(0, INS_SELECT, 0x04, 0, Some(&OATH_AID))
|
transaction_context.apdu_read_all(0, INS_SELECT, 0x04, 0, Some(&OATH_AID))?;
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let info_map = tlv_to_map(info_buffer);
|
let info_map = tlv_to_map(info_buffer);
|
||||||
for (tag, data) in &info_map {
|
for (tag, data) in &info_map {
|
||||||
|
@ -155,7 +144,7 @@ impl<'a> OathSession<'a> {
|
||||||
println!("{:?}: {:?}", tag, data);
|
println!("{:?}: {:?}", tag, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
OathSession {
|
Ok(Self {
|
||||||
version: clone_with_lifetime(
|
version: clone_with_lifetime(
|
||||||
info_map.get(&(Tag::Version as u8)).unwrap_or(&vec![0u8; 0]),
|
info_map.get(&(Tag::Version as u8)).unwrap_or(&vec![0u8; 0]),
|
||||||
)
|
)
|
||||||
|
@ -170,7 +159,7 @@ impl<'a> OathSession<'a> {
|
||||||
.leak(),
|
.leak(),
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
transaction_context,
|
transaction_context,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_version(&self) -> &[u8] {
|
pub fn get_version(&self) -> &[u8] {
|
||||||
|
@ -181,7 +170,7 @@ impl<'a> OathSession<'a> {
|
||||||
&self,
|
&self,
|
||||||
old: CredentialIDData,
|
old: CredentialIDData,
|
||||||
new: CredentialIDData,
|
new: CredentialIDData,
|
||||||
) -> Result<CredentialIDData, FormattableErrorResponse> {
|
) -> Result<CredentialIDData, Error> {
|
||||||
// require_version(self.version, (5, 3, 1)) TODO: version checking
|
// require_version(self.version, (5, 3, 1)) TODO: version checking
|
||||||
self.transaction_context.apdu(
|
self.transaction_context.apdu(
|
||||||
0,
|
0,
|
||||||
|
@ -193,10 +182,7 @@ impl<'a> OathSession<'a> {
|
||||||
Ok(new)
|
Ok(new)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_code(
|
pub fn delete_code(&self, cred: OathCredential) -> Result<ApduResponse, Error> {
|
||||||
&self,
|
|
||||||
cred: OathCredential,
|
|
||||||
) -> Result<ApduResponse, FormattableErrorResponse> {
|
|
||||||
self.transaction_context.apdu(
|
self.transaction_context.apdu(
|
||||||
0,
|
0,
|
||||||
Instruction::Delete as u8,
|
Instruction::Delete as u8,
|
||||||
|
@ -210,9 +196,9 @@ impl<'a> OathSession<'a> {
|
||||||
&self,
|
&self,
|
||||||
cred: OathCredential,
|
cred: OathCredential,
|
||||||
timestamp_sys: Option<SystemTime>,
|
timestamp_sys: Option<SystemTime>,
|
||||||
) -> Result<OathCodeDisplay, FormattableErrorResponse> {
|
) -> Result<OathCodeDisplay, Error> {
|
||||||
if self.name != cred.device_id {
|
if self.name != cred.device_id {
|
||||||
return Err(FormattableErrorResponse::DeviceMismatchError);
|
return Err(Error::DeviceMismatchError);
|
||||||
}
|
}
|
||||||
|
|
||||||
let timestamp = time_to_u64(timestamp_sys.unwrap_or_else(SystemTime::now));
|
let timestamp = time_to_u64(timestamp_sys.unwrap_or_else(SystemTime::now));
|
||||||
|
@ -233,22 +219,18 @@ impl<'a> OathSession<'a> {
|
||||||
Some(&data),
|
Some(&data),
|
||||||
);
|
);
|
||||||
|
|
||||||
let meta =
|
let meta = TlvIter::from_vec(resp?).next().ok_or(Error::ParsingError(
|
||||||
TlvIter::from_vec(resp?)
|
|
||||||
.next()
|
|
||||||
.ok_or(FormattableErrorResponse::ParsingError(
|
|
||||||
"No credentials to unpack found in response".to_string(),
|
"No credentials to unpack found in response".to_string(),
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
OathCodeDisplay::from_tlv(meta).ok_or(FormattableErrorResponse::ParsingError(
|
OathCodeDisplay::from_tlv(meta).ok_or(Error::ParsingError(
|
||||||
"error parsing calculation response".to_string(),
|
"error parsing calculation response".to_string(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the OATH codes from the device, calculate TOTP codes that don't need touch
|
/// Read the OATH codes from the device, calculate TOTP codes that don't
|
||||||
pub fn calculate_oath_codes(
|
/// need touch
|
||||||
&self,
|
pub fn calculate_oath_codes(&self) -> Result<Vec<RefreshableOathCredential>, Error> {
|
||||||
) -> Result<Vec<RefreshableOathCredential>, FormattableErrorResponse> {
|
|
||||||
let timestamp = SystemTime::now();
|
let timestamp = SystemTime::now();
|
||||||
// Request OATH codes from device
|
// Request OATH codes from device
|
||||||
let response = self.transaction_context.apdu_read_all(
|
let response = self.transaction_context.apdu_read_all(
|
||||||
|
@ -266,8 +248,8 @@ impl<'a> OathSession<'a> {
|
||||||
let id_data = CredentialIDData::from_tlv(cred_id.value(), meta.tag());
|
let id_data = CredentialIDData::from_tlv(cred_id.value(), meta.tag());
|
||||||
let code = OathCodeDisplay::from_tlv(meta);
|
let code = OathCodeDisplay::from_tlv(meta);
|
||||||
|
|
||||||
/* println!("id bytes: {:?}", cred_id.value());
|
// println!("id bytes: {:?}", cred_id.value());
|
||||||
println!("id recon: {:?}", id_data.format_cred_id()); */
|
// println!("id recon: {:?}", id_data.format_cred_id());
|
||||||
|
|
||||||
let cred = OathCredential {
|
let cred = OathCredential {
|
||||||
device_id: self.name.clone(),
|
device_id: self.name.clone(),
|
||||||
|
@ -283,7 +265,7 @@ impl<'a> OathSession<'a> {
|
||||||
|
|
||||||
return Ok(key_buffer);
|
return Ok(key_buffer);
|
||||||
}
|
}
|
||||||
pub fn list_oath_codes(&self) -> Result<Vec<CredentialIDData>, FormattableErrorResponse> {
|
pub fn list_oath_codes(&self) -> Result<Vec<CredentialIDData>, Error> {
|
||||||
// Request OATH codes from device
|
// Request OATH codes from device
|
||||||
let response =
|
let response =
|
||||||
self.transaction_context
|
self.transaction_context
|
|
@ -1,20 +1,9 @@
|
||||||
#[crate_type = "lib"]
|
use std::{
|
||||||
use crate::lib_ykoath2::*;
|
cmp::Ordering,
|
||||||
/// Utilities for interacting with YubiKey OATH/TOTP functionality
|
hash::{Hash, Hasher},
|
||||||
extern crate pcsc;
|
};
|
||||||
use pbkdf2::pbkdf2_hmac_array;
|
|
||||||
use regex::Regex;
|
|
||||||
use sha1::Sha1;
|
|
||||||
|
|
||||||
use std::str::{self};
|
use crate::CredentialIDData;
|
||||||
|
|
||||||
use base64::{engine::general_purpose, Engine as _};
|
|
||||||
use hmac::{Hmac, Mac};
|
|
||||||
|
|
||||||
use std::cmp::Ordering;
|
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
|
|
||||||
use std::time::SystemTime;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct OathCredential {
|
pub struct OathCredential {
|
|
@ -1,13 +1,8 @@
|
||||||
#![crate_type = "lib"]
|
use std::fmt::Display;
|
||||||
use crate::lib_ykoath2::*;
|
|
||||||
/// Utilities for interacting with YubiKey OATH/TOTP functionality
|
|
||||||
extern crate pcsc;
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use std::{
|
use crate::{to_tlv, OathType, Tag, DEFAULT_PERIOD};
|
||||||
fmt::Write,
|
|
||||||
str::{self},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||||
pub struct CredentialIDData {
|
pub struct CredentialIDData {
|
||||||
|
@ -52,7 +47,7 @@ impl CredentialIDData {
|
||||||
|
|
||||||
// Function to parse the credential ID
|
// Function to parse the credential ID
|
||||||
fn parse_cred_id(cred_id: &[u8], oath_type: OathType) -> (Option<String>, String, u32) {
|
fn parse_cred_id(cred_id: &[u8], oath_type: OathType) -> (Option<String>, String, u32) {
|
||||||
let data = match str::from_utf8(cred_id) {
|
let data = match std::str::from_utf8(cred_id) {
|
||||||
Ok(d) => d,
|
Ok(d) => d,
|
||||||
Err(_) => return (None, String::new(), 0), // Handle invalid UTF-8
|
Err(_) => return (None, String::new(), 0), // Handle invalid UTF-8
|
||||||
};
|
};
|
|
@ -1,22 +1,14 @@
|
||||||
#[crate_type = "lib"]
|
use std::{collections::HashMap, ffi::CString, fmt::Display};
|
||||||
use crate::lib_ykoath2::*;
|
|
||||||
/// Utilities for interacting with YubiKey OATH/TOTP functionality
|
|
||||||
extern crate pcsc;
|
|
||||||
use iso7816_tlv::simple::{Tag as TlvTag, Tlv};
|
|
||||||
use ouroboros::self_referencing;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt::Display;
|
|
||||||
use std::str::{self};
|
|
||||||
|
|
||||||
use apdu_core::{Command, Response};
|
use apdu_core::{Command, Response};
|
||||||
|
use iso7816_tlv::simple::{Tag as TlvTag, Tlv};
|
||||||
|
use ouroboros::self_referencing;
|
||||||
use pcsc::{Card, Transaction};
|
use pcsc::{Card, Transaction};
|
||||||
|
|
||||||
use std::ffi::CString;
|
use crate::{ErrorResponse, Instruction, SuccessResponse, Tag};
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug)]
|
#[derive(PartialEq, Eq, Debug)]
|
||||||
pub enum FormattableErrorResponse {
|
pub enum Error {
|
||||||
NoError,
|
|
||||||
Unknown(String),
|
Unknown(String),
|
||||||
Protocol(ErrorResponse),
|
Protocol(ErrorResponse),
|
||||||
PcscError(pcsc::Error),
|
PcscError(pcsc::Error),
|
||||||
|
@ -24,50 +16,37 @@ pub enum FormattableErrorResponse {
|
||||||
DeviceMismatchError,
|
DeviceMismatchError,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FormattableErrorResponse {
|
impl Error {
|
||||||
pub fn from_apdu_response(sw1: u8, sw2: u8) -> FormattableErrorResponse {
|
fn from_apdu_response(sw1: u8, sw2: u8) -> Result<(), Self> {
|
||||||
let code: u16 = (sw1 as u16 | sw2 as u16) << 8;
|
let code: u16 = (sw1 as u16 | sw2 as u16) << 8;
|
||||||
if let Some(e) = ErrorResponse::any_match(code) {
|
if let Some(e) = ErrorResponse::any_match(code) {
|
||||||
return FormattableErrorResponse::Protocol(e);
|
return Err(Self::Protocol(e));
|
||||||
}
|
}
|
||||||
if SuccessResponse::any_match(code)
|
if SuccessResponse::any_match(code)
|
||||||
.or(SuccessResponse::any_match(sw1.into()))
|
.or(SuccessResponse::any_match(sw1.into()))
|
||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
return FormattableErrorResponse::NoError;
|
return Ok(());
|
||||||
}
|
|
||||||
FormattableErrorResponse::Unknown(String::from("Unknown error"))
|
|
||||||
}
|
|
||||||
pub fn is_ok(&self) -> bool {
|
|
||||||
*self == FormattableErrorResponse::NoError
|
|
||||||
}
|
|
||||||
pub fn as_opt(self) -> Option<FormattableErrorResponse> {
|
|
||||||
if self.is_ok() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_transmit(err: pcsc::Error) -> FormattableErrorResponse {
|
|
||||||
FormattableErrorResponse::PcscError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_string(&self) -> String {
|
|
||||||
match self {
|
|
||||||
FormattableErrorResponse::NoError => "ok".to_string(),
|
|
||||||
FormattableErrorResponse::Unknown(msg) => msg.to_owned(),
|
|
||||||
FormattableErrorResponse::Protocol(error_response) => error_response.as_string(),
|
|
||||||
FormattableErrorResponse::PcscError(error) => format!("{}", error),
|
|
||||||
FormattableErrorResponse::ParsingError(msg) => msg.to_owned(),
|
|
||||||
FormattableErrorResponse::DeviceMismatchError => "Devices do not match".to_string(),
|
|
||||||
}
|
}
|
||||||
|
Err(Self::Unknown(String::from("Unknown error")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for FormattableErrorResponse {
|
impl From<pcsc::Error> for Error {
|
||||||
|
fn from(value: pcsc::Error) -> Self {
|
||||||
|
Self::PcscError(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_str(&self.as_string())
|
match self {
|
||||||
|
Self::Unknown(msg) => f.write_str(msg),
|
||||||
|
Self::Protocol(error_response) => f.write_fmt(format_args!("{}", error_response)),
|
||||||
|
Self::PcscError(error) => f.write_fmt(format_args!("{}", error)),
|
||||||
|
Self::ParsingError(msg) => f.write_str(msg),
|
||||||
|
Self::DeviceMismatchError => f.write_str("Devices do not match"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +64,7 @@ fn apdu(
|
||||||
parameter1: u8,
|
parameter1: u8,
|
||||||
parameter2: u8,
|
parameter2: u8,
|
||||||
data: Option<&[u8]>,
|
data: Option<&[u8]>,
|
||||||
) -> Result<ApduResponse, FormattableErrorResponse> {
|
) -> Result<ApduResponse, Error> {
|
||||||
let command = if let Some(data) = data {
|
let command = if let Some(data) = data {
|
||||||
Command::new_with_payload(class, instruction, parameter1, parameter2, data)
|
Command::new_with_payload(class, instruction, parameter1, parameter2, data)
|
||||||
} else {
|
} else {
|
||||||
|
@ -98,19 +77,9 @@ fn apdu(
|
||||||
let mut rx_buf = [0; pcsc::MAX_BUFFER_SIZE];
|
let mut rx_buf = [0; pcsc::MAX_BUFFER_SIZE];
|
||||||
|
|
||||||
// Write the payload to the device and error if there is a problem
|
// Write the payload to the device and error if there is a problem
|
||||||
let rx_buf = match tx.transmit(&tx_buf, &mut rx_buf) {
|
let rx_buf = tx.transmit(&tx_buf, &mut rx_buf)?;
|
||||||
Ok(slice) => slice,
|
|
||||||
// Err(err) => return Err(format!("{}", err)),
|
|
||||||
Err(err) => return Err(FormattableErrorResponse::from_transmit(err)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let resp = Response::from(rx_buf);
|
let resp = Response::from(rx_buf);
|
||||||
let error_context =
|
Error::from_apdu_response(resp.trailer.0, resp.trailer.1)?;
|
||||||
FormattableErrorResponse::from_apdu_response(resp.trailer.0, resp.trailer.1);
|
|
||||||
|
|
||||||
if !error_context.is_ok() {
|
|
||||||
return Err(error_context);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ApduResponse {
|
Ok(ApduResponse {
|
||||||
buf: resp.payload.to_vec(),
|
buf: resp.payload.to_vec(),
|
||||||
|
@ -126,7 +95,7 @@ fn apdu_read_all(
|
||||||
parameter1: u8,
|
parameter1: u8,
|
||||||
parameter2: u8,
|
parameter2: u8,
|
||||||
data: Option<&[u8]>,
|
data: Option<&[u8]>,
|
||||||
) -> Result<Vec<u8>, FormattableErrorResponse> {
|
) -> Result<Vec<u8>, Error> {
|
||||||
let mut response_buf = Vec::new();
|
let mut response_buf = Vec::new();
|
||||||
let mut resp = apdu(tx, class, instruction, parameter1, parameter2, data)?;
|
let mut resp = apdu(tx, class, instruction, parameter1, parameter2, data)?;
|
||||||
response_buf.extend(resp.buf);
|
response_buf.extend(resp.buf);
|
||||||
|
@ -146,26 +115,22 @@ pub struct TransactionContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TransactionContext {
|
impl TransactionContext {
|
||||||
pub fn from_name(name: &str) -> Self {
|
pub fn from_name(name: &str) -> Result<Self, Error> {
|
||||||
// FIXME: error handling here
|
|
||||||
|
|
||||||
// Establish a PC/SC context
|
// Establish a PC/SC context
|
||||||
let ctx = pcsc::Context::establish(pcsc::Scope::User).unwrap();
|
let ctx = pcsc::Context::establish(pcsc::Scope::User)?;
|
||||||
|
|
||||||
// Connect to the card
|
// Connect to the card
|
||||||
let card = ctx
|
let card = ctx.connect(
|
||||||
.connect(
|
|
||||||
&CString::new(name).unwrap(),
|
&CString::new(name).unwrap(),
|
||||||
pcsc::ShareMode::Shared,
|
pcsc::ShareMode::Shared,
|
||||||
pcsc::Protocols::ANY,
|
pcsc::Protocols::ANY,
|
||||||
)
|
)?;
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
TransactionContextBuilder {
|
Ok(TransactionContextBuilder {
|
||||||
card,
|
card,
|
||||||
transaction_builder: |c| c.transaction().unwrap(),
|
transaction_builder: |c| c.transaction().unwrap(),
|
||||||
}
|
}
|
||||||
.build()
|
.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apdu(
|
pub fn apdu(
|
||||||
|
@ -175,7 +140,7 @@ impl TransactionContext {
|
||||||
parameter1: u8,
|
parameter1: u8,
|
||||||
parameter2: u8,
|
parameter2: u8,
|
||||||
data: Option<&[u8]>,
|
data: Option<&[u8]>,
|
||||||
) -> Result<ApduResponse, FormattableErrorResponse> {
|
) -> Result<ApduResponse, Error> {
|
||||||
apdu(
|
apdu(
|
||||||
self.borrow_transaction(),
|
self.borrow_transaction(),
|
||||||
class,
|
class,
|
||||||
|
@ -193,7 +158,7 @@ impl TransactionContext {
|
||||||
parameter1: u8,
|
parameter1: u8,
|
||||||
parameter2: u8,
|
parameter2: u8,
|
||||||
data: Option<&[u8]>,
|
data: Option<&[u8]>,
|
||||||
) -> Result<Vec<u8>, FormattableErrorResponse> {
|
) -> Result<Vec<u8>, Error> {
|
||||||
apdu_read_all(
|
apdu_read_all(
|
||||||
self.borrow_transaction(),
|
self.borrow_transaction(),
|
||||||
class,
|
class,
|
Loading…
Add table
Reference in a new issue