readers: Use Reader to connect to YubiKey
Removes the legacy API inherited from `yubico-piv-tool` and uses the `reader` module exclusively for selecting and opening the PC/SC reader.
This commit is contained in:
+11
-5
@@ -1,8 +1,9 @@
|
|||||||
//! Support for enumerating available readers
|
//! Support for enumerating available readers
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::{error::Error, yubikey::YubiKey};
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
|
convert::TryInto,
|
||||||
ffi::CStr,
|
ffi::CStr,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
@@ -20,7 +21,8 @@ pub struct Readers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Readers {
|
impl Readers {
|
||||||
/// Open a PC/SC context
|
/// Open a PC/SC context, which can be used to enumerate available PC/SC
|
||||||
|
/// readers (which can be used to connect to YubiKeys).
|
||||||
pub fn open() -> Result<Self, Error> {
|
pub fn open() -> Result<Self, Error> {
|
||||||
let ctx = pcsc::Context::establish(pcsc::Scope::System)?;
|
let ctx = pcsc::Context::establish(pcsc::Scope::System)?;
|
||||||
let reader_names = vec![0u8; ctx.list_readers_len()?];
|
let reader_names = vec![0u8; ctx.list_readers_len()?];
|
||||||
@@ -74,9 +76,13 @@ impl<'ctx> Reader<'ctx> {
|
|||||||
self.name.to_string_lossy()
|
self.name.to_string_lossy()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open the given reader
|
/// Open a connection to this reader, returning a `YubiKey` if successful
|
||||||
// TODO(tarcieri): return a `YubiKey` here rather than a `pcsc::Card`
|
pub fn open(&self) -> Result<YubiKey, Error> {
|
||||||
pub fn open(&self) -> Result<pcsc::Card, Error> {
|
self.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connect to this reader, returning its `pcsc::Card`
|
||||||
|
pub(crate) fn connect(&self) -> Result<pcsc::Card, Error> {
|
||||||
let ctx = self.ctx.lock().unwrap();
|
let ctx = self.ctx.lock().unwrap();
|
||||||
Ok(ctx.connect(self.name, pcsc::ShareMode::Shared, pcsc::Protocols::T1)?)
|
Ok(ctx.connect(self.name, pcsc::ShareMode::Shared, pcsc::Protocols::T1)?)
|
||||||
}
|
}
|
||||||
|
|||||||
+84
-88
@@ -42,14 +42,22 @@ use crate::{
|
|||||||
serialization::*,
|
serialization::*,
|
||||||
Buffer, ObjectId,
|
Buffer, ObjectId,
|
||||||
};
|
};
|
||||||
use crate::{consts::*, error::Error, transaction::Transaction};
|
use crate::{
|
||||||
|
consts::*,
|
||||||
|
error::Error,
|
||||||
|
readers::{Reader, Readers},
|
||||||
|
transaction::Transaction,
|
||||||
|
};
|
||||||
#[cfg(feature = "untested")]
|
#[cfg(feature = "untested")]
|
||||||
use getrandom::getrandom;
|
use getrandom::getrandom;
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use pcsc::{Card, Context};
|
use pcsc::Card;
|
||||||
#[cfg(feature = "untested")]
|
#[cfg(feature = "untested")]
|
||||||
use secrecy::ExposeSecret;
|
use secrecy::ExposeSecret;
|
||||||
use std::fmt::{self, Display};
|
use std::{
|
||||||
|
convert::TryFrom,
|
||||||
|
fmt::{self, Display},
|
||||||
|
};
|
||||||
#[cfg(feature = "untested")]
|
#[cfg(feature = "untested")]
|
||||||
use std::{
|
use std::{
|
||||||
convert::TryInto,
|
convert::TryInto,
|
||||||
@@ -130,96 +138,28 @@ pub struct YubiKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl YubiKey {
|
impl YubiKey {
|
||||||
/// Open a connection to a YubiKey, optionally giving the name
|
/// Open a connection to a YubiKey.
|
||||||
/// (needed if e.g. there are multiple YubiKeys connected).
|
///
|
||||||
pub fn open(name: Option<&[u8]>) -> Result<YubiKey, Error> {
|
/// Returns an error if there is more than one YubiKey detected.
|
||||||
let context = Context::establish(pcsc::Scope::System)?;
|
///
|
||||||
let mut card = Self::connect(&context, name)?;
|
/// If you need to operate in environments with more than one YubiKey
|
||||||
|
/// attached to the same system, use [`yubikey_piv::Readers`] to select
|
||||||
|
/// from the available PC/SC readers connected.
|
||||||
|
pub fn open() -> Result<Self, Error> {
|
||||||
|
let mut readers = Readers::open()?;
|
||||||
|
let mut reader_iter = readers.iter()?;
|
||||||
|
|
||||||
let mut is_neo = false;
|
if let Some(reader) = reader_iter.next() {
|
||||||
let version: Version;
|
if reader_iter.next().is_some() {
|
||||||
let serial: Serial;
|
error!("multiple YubiKeys detected!");
|
||||||
|
return Err(Error::PcscError { inner: None });
|
||||||
{
|
|
||||||
let txn = Transaction::new(&mut card)?;
|
|
||||||
let mut atr_buf = [0; CB_ATR_MAX];
|
|
||||||
let atr = txn.get_attribute(pcsc::Attribute::AtrString, &mut atr_buf)?;
|
|
||||||
if atr == YKPIV_ATR_NEO_R3 {
|
|
||||||
is_neo = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
txn.select_application()?;
|
return reader.open();
|
||||||
|
|
||||||
// now that the PIV application is selected, retrieve the version
|
|
||||||
// and serial number. Previously the NEO/YK4 required switching
|
|
||||||
// to the yk applet to retrieve the serial, YK5 implements this
|
|
||||||
// as a PIV applet command. Unfortunately, this change requires
|
|
||||||
// that we retrieve the version number first, so that get_serial
|
|
||||||
// can determine how to get the serial number, which for the NEO/Yk4
|
|
||||||
// will result in another selection of the PIV applet.
|
|
||||||
|
|
||||||
version = txn.get_version().map_err(|e| {
|
|
||||||
warn!("failed to retrieve version: '{}'", e);
|
|
||||||
e
|
|
||||||
})?;
|
|
||||||
|
|
||||||
serial = txn.get_serial(version).map_err(|e| {
|
|
||||||
warn!("failed to retrieve serial number: '{}'", e);
|
|
||||||
e
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let yubikey = YubiKey {
|
error!("no YubiKey detected!");
|
||||||
card,
|
Err(Error::GenericError)
|
||||||
pin: None,
|
|
||||||
is_neo,
|
|
||||||
version,
|
|
||||||
serial,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(yubikey)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Connect to a YubiKey PC/SC card.
|
|
||||||
fn connect(context: &Context, name: Option<&[u8]>) -> Result<Card, Error> {
|
|
||||||
// ensure PC/SC context is valid
|
|
||||||
context.is_valid()?;
|
|
||||||
|
|
||||||
let buffer_len = context.list_readers_len()?;
|
|
||||||
let mut buffer = vec![0u8; buffer_len];
|
|
||||||
|
|
||||||
for reader in context.list_readers(&mut buffer)? {
|
|
||||||
if let Some(wanted) = name {
|
|
||||||
if reader.to_bytes() != wanted {
|
|
||||||
warn!(
|
|
||||||
"skipping reader '{}' since it doesn't match '{}'",
|
|
||||||
reader.to_string_lossy(),
|
|
||||||
String::from_utf8_lossy(wanted)
|
|
||||||
);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("trying to connect to reader '{}'", reader.to_string_lossy());
|
|
||||||
|
|
||||||
match context.connect(reader, pcsc::ShareMode::Shared, pcsc::Protocols::T1) {
|
|
||||||
Ok(card) => {
|
|
||||||
info!("connected to '{}' successfully", reader.to_string_lossy());
|
|
||||||
return Ok(card);
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
error!(
|
|
||||||
"skipping '{}' due to connection error: {}",
|
|
||||||
reader.to_string_lossy(),
|
|
||||||
err
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
error!("error: no usable reader found");
|
|
||||||
Err(Error::PcscError { inner: None })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reconnect to a YubiKey
|
/// Reconnect to a YubiKey
|
||||||
@@ -818,3 +758,59 @@ impl YubiKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a Reader<'_>> for YubiKey {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(reader: &'a Reader<'_>) -> Result<Self, Error> {
|
||||||
|
let mut card = reader.connect().map_err(|e| {
|
||||||
|
error!("error connecting to reader '{}': {}", reader.name(), e);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
|
info!("connected to reader: {}", reader.name());
|
||||||
|
|
||||||
|
let mut is_neo = false;
|
||||||
|
let version: Version;
|
||||||
|
let serial: Serial;
|
||||||
|
|
||||||
|
{
|
||||||
|
let txn = Transaction::new(&mut card)?;
|
||||||
|
let mut atr_buf = [0; CB_ATR_MAX];
|
||||||
|
let atr = txn.get_attribute(pcsc::Attribute::AtrString, &mut atr_buf)?;
|
||||||
|
if atr == YKPIV_ATR_NEO_R3 {
|
||||||
|
is_neo = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
txn.select_application()?;
|
||||||
|
|
||||||
|
// now that the PIV application is selected, retrieve the version
|
||||||
|
// and serial number. Previously the NEO/YK4 required switching
|
||||||
|
// to the yk applet to retrieve the serial, YK5 implements this
|
||||||
|
// as a PIV applet command. Unfortunately, this change requires
|
||||||
|
// that we retrieve the version number first, so that get_serial
|
||||||
|
// can determine how to get the serial number, which for the NEO/Yk4
|
||||||
|
// will result in another selection of the PIV applet.
|
||||||
|
|
||||||
|
version = txn.get_version().map_err(|e| {
|
||||||
|
warn!("failed to retrieve version: '{}'", e);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
|
serial = txn.get_serial(version).map_err(|e| {
|
||||||
|
warn!("failed to retrieve serial number: '{}'", e);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let yubikey = YubiKey {
|
||||||
|
card,
|
||||||
|
pin: None,
|
||||||
|
is_neo,
|
||||||
|
version,
|
||||||
|
serial,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(yubikey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ fn connect() {
|
|||||||
env_logger::builder().format_timestamp(None).init();
|
env_logger::builder().format_timestamp(None).init();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut yubikey = YubiKey::open(None).unwrap();
|
let mut yubikey = YubiKey::open().unwrap();
|
||||||
dbg!(&yubikey.version());
|
dbg!(&yubikey.version());
|
||||||
dbg!(&yubikey.serial());
|
dbg!(&yubikey.serial());
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user