Merge remote-tracking branch 'upstream/develop' into develop

This commit is contained in:
Carl Wallace
2019-12-03 15:12:22 -05:00
18 changed files with 1693 additions and 100 deletions
+5 -5
View File
@@ -57,7 +57,7 @@ jobs:
RUSTFLAGS: -D warnings
with:
command: test
args: --release
args: --all --release
- name: Run cargo build --all-features
uses: actions-rs/cargo@v1
@@ -65,7 +65,7 @@ jobs:
RUSTFLAGS: -D warnings
with:
command: build
args: --all-features
args: --all --all-features
test:
name: Test Suite
@@ -94,7 +94,7 @@ jobs:
RUSTFLAGS: -D warnings
with:
command: test
args: --release
args: --all --release
- name: Run cargo build --all-features
uses: actions-rs/cargo@v1
@@ -102,7 +102,7 @@ jobs:
RUSTFLAGS: -D warnings
with:
command: build
args: --all-features
args: --all --all-features
fmt:
name: Rustfmt
@@ -149,7 +149,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: clippy
args: --all-features -- -D warnings
args: --all --all-features -- -D warnings
# TODO: use actions-rs/audit-check
security_audit:
-1
View File
@@ -1,3 +1,2 @@
/target
**/*.rs.bk
Cargo.lock
+29
View File
@@ -4,6 +4,35 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.0.3] (2019-12-02)
### Added
- Initial `Readers` enumerator for detecting YubiKeys ([#51])
- Certificate parsing ([#45])
### Changed
- Use `Reader` to connect to `YubiKey` ([#51])
- Convert `SlotId` and `AlgorithmId` into enums ([#44])
- Use `secrecy` crate for storing `CachedPin` ([#43])
- Change `CHUID` struct to hold complete CHUID value ([#42])
- Eliminate all usages of `unsafe` ([#37], [#39])
- Make anonymous CHUID struct public ([#36])
- Have `sign_data` and `decrypt_data` return a `Buffer` ([#34])
- `Ins` (APDU instruction codes) enum ([#33])
- Factor `Response` into `apdu` module; improved debugging ([#32])
[0.0.3]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/53
[#51]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/51
[#45]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/45
[#44]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/44
[#43]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/43
[#42]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/42
[#39]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/39
[#37]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/37
[#36]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/36
[#34]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/34
[#33]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/33
[#32]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/32
## [0.0.2] (2019-11-25)
### Added
- `untested` Cargo feature to mark untested functionality ([#30])
Generated
+1000
View File
File diff suppressed because it is too large Load Diff
+5 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "yubikey-piv"
version = "0.0.2" # Also update html_root_url in lib.rs when bumping this
version = "0.0.3" # Also update html_root_url in lib.rs when bumping this
description = """
Pure Rust host-side driver for the YubiKey Personal Identity Verification (PIV)
application providing general-purpose public-key signing and encryption
@@ -13,7 +13,10 @@ license = "BSD-2-Clause"
repository = "https://github.com/iqlusioninc/yubikey-piv.rs"
readme = "README.md"
categories = ["api-bindings", "cryptography", "hardware-support"]
keywords = ["ccid", "ecdsa", "rsa", "piv", "yubikey"]
keywords = ["ecdsa", "rsa", "piv", "pcsc", "yubikey"]
[workspace]
members = [".", "cli"]
[badges]
maintenance = { status = "experimental" }
+2 -1
View File
@@ -4,7 +4,7 @@
[![crate][crate-image]][crate-link]
[![Docs][docs-image]][docs-link]
![Apache2/MIT licensed][license-image]
[![2-Clause BSD Licensed][license-image]][license-link]
![Rust Version][rustc-image]
![Maintenance Status: Experimental][maintenance-image]
[![Safety Dance][safety-image]][safety-link]
@@ -192,6 +192,7 @@ or conditions.
[docs-image]: https://docs.rs/yubikey-piv/badge.svg
[docs-link]: https://docs.rs/yubikey-piv/
[license-image]: https://img.shields.io/badge/license-BSD-blue.svg
[license-link]: https://github.com/iqlusioninc/yubikey-piv.rs/blob/develop/COPYING
[rustc-image]: https://img.shields.io/badge/rustc-1.39+-blue.svg
[maintenance-image]: https://img.shields.io/badge/maintenance-experimental-blue.svg
[safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg
+8
View File
@@ -0,0 +1,8 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 0.0.1 (2019-12-02)
- Initial release
+21
View File
@@ -0,0 +1,21 @@
[package]
name = "yubikey-cli"
version = "0.0.1"
description = """
Command-line interface for performing encryption and signing using RSA and/or
ECC keys stored on YubiKey devices.
"""
authors = ["Tony Arcieri <bascule@gmail.com>"]
edition = "2018"
license = "BSD-2-Clause"
repository = "https://github.com/iqlusioninc/yubikey-piv.rs"
readme = "README.md"
categories = ["command-line-utilities", "cryptography", "hardware-support"]
keywords = ["ecdsa", "rsa", "piv", "pcsc", "yubikey"]
[dependencies]
gumdrop = "0.7"
env_logger = "0.7"
lazy_static = "1"
termcolor = "1"
yubikey-piv = { version = "0.0.3", path = ".." }
+112
View File
@@ -0,0 +1,112 @@
<img src="https://raw.githubusercontent.com/tendermint/yubihsm-rs/develop/img/logo.png" width="150" height="110">
# yubikey-cli.rs
[![crate][crate-image]][crate-link]
[![Docs][docs-image]][docs-link]
![Apache2/MIT licensed][license-image]
![Rust Version][rustc-image]
![Maintenance Status: Experimental][maintenance-image]
[![Safety Dance][safety-image]][safety-link]
[![Build Status][build-image]][build-link]
[![Gitter Chat][gitter-image]][gitter-link]
Pure Rust host-side YubiKey [Personal Identity Verification (PIV)][PIV] CLI
utility with general-purpose public-key encryption and signing support.
[Documentation][docs-link]
## Minimum Supported Rust Version
- Rust **1.39+**
## Supported YubiKeys
- [YubiKey NEO] series (may be dropped in the future, see [#18])
- [YubiKey 4] series
- [YubiKey 5] series
NOTE: Nano and USB-C variants of the above are also supported
## Security Warning
No security audits of this crate have ever been performed. Presently it is in
an experimental stage and may still contain high-severity issues.
USE AT YOUR OWN RISK!
## Status
WIP. Check back later.
## Code of Conduct
We abide by the [Contributor Covenant][cc-md] and ask that you do as well.
For more information, please see [CODE_OF_CONDUCT.md][cc-md].
## License
Copyright (c) 2014-2019 Yubico AB, Tony Arcieri
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally
submitted for inclusion in the work by you shall be licensed under the
[2-Clause BSD License][BSDL] as shown above, without any additional terms
or conditions.
[//]: # (badges)
[crate-image]: https://img.shields.io/crates/v/yubikey-cli.svg
[crate-link]: https://crates.io/crates/yubikey-cli
[docs-image]: https://docs.rs/yubikey-cli/badge.svg
[docs-link]: https://docs.rs/yubikey-cli/
[license-image]: https://img.shields.io/badge/license-BSD-blue.svg
[rustc-image]: https://img.shields.io/badge/rustc-1.39+-blue.svg
[maintenance-image]: https://img.shields.io/badge/maintenance-experimental-blue.svg
[safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg
[safety-link]: https://github.com/rust-secure-code/safety-dance/
[build-image]: https://github.com/iqlusioninc/yubikey-piv.rs/workflows/Rust/badge.svg?branch=develop&event=push
[build-link]: https://github.com/iqlusioninc/yubikey-piv.rs/actions
[gitter-image]: https://badges.gitter.im/badge.svg
[gitter-link]: https://gitter.im/iqlusioninc/community
[//]: # (general links)
[PIV]: https://piv.idmanagement.gov/
[yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
[Yubico]: https://www.yubico.com/
[YubiKey NEO]: https://support.yubico.com/support/solutions/articles/15000006494-yubikey-neo
[YubiKey 4]: https://support.yubico.com/support/solutions/articles/15000006486-yubikey-4
[YubiKey 5]: https://www.yubico.com/products/yubikey-5-overview/
[yubico-piv-tool]: https://github.com/Yubico/yubico-piv-tool/
[Corrode]: https://github.com/jameysharp/corrode
[cc-web]: https://contributor-covenant.org/
[cc-md]: https://github.com/iqlusioninc/yubikey-cli.rs/blob/develop/CODE_OF_CONDUCT.md
[BSDL]: https://opensource.org/licenses/BSD-2-Clause
+16
View File
@@ -0,0 +1,16 @@
//! `yubikey` command-line utility
#![forbid(unsafe_code)]
#![warn(
missing_docs,
rust_2018_idioms,
unused_lifetimes,
unused_qualifications
)]
use gumdrop::Options;
use yubikey_cli::commands::YubikeyCli;
fn main() {
YubikeyCli::parse_args_default_or_exit().run();
}
+98
View File
@@ -0,0 +1,98 @@
//! Commands of the CLI application
pub mod list;
use self::list::ListCmd;
use crate::status;
use gumdrop::Options;
use std::env;
use std::process::exit;
use termcolor::ColorChoice;
/// The `yubikey` CLI utility
#[derive(Debug, Options)]
pub struct YubikeyCli {
/// Obtain help about the current command
#[options(short = "h", help = "print help message")]
pub help: bool,
/// Subcommand to execute.
#[options(command)]
pub command: Option<Commands>,
}
impl YubikeyCli {
/// Run the underlying command type or print usage info and exit
pub fn run(&self) {
// TODO(tarcieri): make this more configurable
status::set_color_choice(ColorChoice::Auto);
// Only show logs if `RUST_LOG` is set
if env::var("RUST_LOG").is_ok() {
env_logger::builder().format_timestamp(None).init();
}
match &self.command {
Some(cmd) => cmd.run(),
None => println!("{}", Commands::usage()),
}
}
}
/// Subcommands of this application
#[derive(Debug, Options)]
pub enum Commands {
/// `help` subcommand
#[options(help = "show help for a command")]
Help(HelpOpts),
/// `version` subcommand
#[options(help = "display version information")]
Version(VersionOpts),
/// `list` subcommand
#[options(help = "list detected readers")]
List(ListCmd),
}
impl Commands {
/// Run the given command
pub fn run(&self) {
match self {
Commands::Help(help) => help.run(),
Commands::Version(version) => version.run(),
Commands::List(list) => list.run(),
}
}
}
/// Help options
#[derive(Debug, Options)]
pub struct HelpOpts {
#[options(free, help = "subcommand to get help for")]
free: Vec<String>,
}
impl HelpOpts {
fn run(&self) {
if let Some(command) = self.free.first() {
if let Some(usage) = Commands::command_usage(command) {
println!("{}", usage);
exit(1);
}
}
println!("{}", Commands::usage());
}
}
/// Version options
#[derive(Debug, Options)]
pub struct VersionOpts {}
impl VersionOpts {
/// Display version information
pub fn run(&self) {
println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
}
}
+40
View File
@@ -0,0 +1,40 @@
//! List detected readers
use gumdrop::Options;
use std::process::exit;
use yubikey_piv::readers::Readers;
/// The `list` subcommand
#[derive(Debug, Options)]
pub struct ListCmd {}
impl ListCmd {
/// Run the `list` subcommand
pub fn run(&self) {
let mut readers = Readers::open().unwrap_or_else(|e| {
status_err!("couldn't open PC/SC context: {}", e);
exit(1);
});
let readers_iter = readers.iter().unwrap_or_else(|e| {
status_err!("couldn't enumerate PC/SC readers: {}", e);
exit(1);
});
if readers_iter.len() == 0 {
status_err!("no YubiKeys detected!");
exit(1);
}
for (i, reader) in readers_iter.enumerate() {
let name = reader.name();
let mut yubikey = match reader.open() {
Ok(yk) => yk,
Err(_) => continue,
};
let serial = yubikey.serial();
println!("{}: {} (serial: {})", i + 1, name, serial);
}
}
}
+14
View File
@@ -0,0 +1,14 @@
//! `yubikey` command-line utility
#![forbid(unsafe_code)]
#![warn(
missing_docs,
rust_2018_idioms,
unused_lifetimes,
unused_qualifications
)]
#[macro_use]
pub mod status;
pub mod commands;
+165
View File
@@ -0,0 +1,165 @@
//! Status messages
use lazy_static::lazy_static;
use std::io::{self, Write};
use std::sync::Mutex;
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
/// Print a success status message (in green if colors are enabled)
#[macro_export]
macro_rules! status_ok {
($status:expr, $msg:expr) => {
$crate::status::Status::new()
.justified()
.bold()
.color(termcolor::Color::Green)
.status($status)
.print_stdout($msg);
};
($status:expr, $fmt:expr, $($arg:tt)+) => {
$crate::status_ok!($status, format!($fmt, $($arg)+));
};
}
/// Print a warning status message (in yellow if colors are enabled)
#[macro_export]
macro_rules! status_warn {
($msg:expr) => {
$crate::status::Status::new()
.bold()
.color(termcolor::Color::Yellow)
.status("warning:")
.print_stdout($msg);
};
($fmt:expr, $($arg:tt)+) => {
$crate::status_warn!(format!($fmt, $($arg)+));
};
}
/// Print an error message (in red if colors are enabled)
#[macro_export]
macro_rules! status_err {
($msg:expr) => {
$crate::status::Status::new()
.bold()
.color(termcolor::Color::Red)
.status("error:")
.print_stderr($msg);
};
($fmt:expr, $($arg:tt)+) => {
$crate::status_err!(format!($fmt, $($arg)+));
};
}
lazy_static! {
/// Color configuration
static ref COLOR_CHOICE: Mutex<Option<ColorChoice>> = Mutex::new(None);
/// Standard output
pub static ref STDOUT: StandardStream = StandardStream::stdout(get_color_choice());
/// Standard error
pub static ref STDERR: StandardStream = StandardStream::stderr(get_color_choice());
}
/// Obtain the color configuration.
///
/// Panics if no configuration has been provided.
fn get_color_choice() -> ColorChoice {
let choice = COLOR_CHOICE.lock().unwrap();
*choice
.as_ref()
.expect("terminal stream accessed before initialized!")
}
/// Set the color configuration.
///
/// Panics if the terminal has already been configured.
pub(super) fn set_color_choice(color_choice: ColorChoice) {
let mut choice = COLOR_CHOICE.lock().unwrap();
assert!(choice.is_none(), "terminal colors already configured!");
*choice = Some(color_choice);
}
/// Status message builder
#[derive(Clone, Debug, Default)]
pub struct Status {
/// Should the status be justified?
justified: bool,
/// Should colors be bold?
bold: bool,
/// Color in which status should be displayed
color: Option<Color>,
/// Prefix of the status message (e.g. `Success`)
status: Option<String>,
}
impl Status {
/// Create a new status message with default settings
pub fn new() -> Self {
Self::default()
}
/// Justify status on display
pub fn justified(mut self) -> Self {
self.justified = true;
self
}
/// Make colors bold
pub fn bold(mut self) -> Self {
self.bold = true;
self
}
/// Set the colors used to display this message
pub fn color(mut self, c: Color) -> Self {
self.color = Some(c);
self
}
/// Set a status message to display
pub fn status<S>(mut self, msg: S) -> Self
where
S: ToString,
{
self.status = Some(msg.to_string());
self
}
/// Print the given message to stdout
pub fn print_stdout(self, msg: impl AsRef<str>) {
self.print(&*STDOUT, msg)
.expect("error printing to stdout!")
}
/// Print the given message to stderr
pub fn print_stderr(self, msg: impl AsRef<str>) {
self.print(&*STDERR, msg)
.expect("error printing to stderr!")
}
/// Print the given message
fn print(self, stream: &StandardStream, msg: impl AsRef<str>) -> Result<(), io::Error> {
let mut s = stream.lock();
s.reset()?;
s.set_color(ColorSpec::new().set_fg(self.color).set_bold(self.bold))?;
if let Some(status) = self.status {
if self.justified {
write!(s, "{:>12}", status)?;
} else {
write!(s, "{}", status)?;
}
}
s.reset()?;
writeln!(s, " {}", msg.as_ref())?;
s.flush()?;
Ok(())
}
}
+4 -2
View File
@@ -123,7 +123,7 @@
#![doc(
html_logo_url = "https://raw.githubusercontent.com/tarcieri/yubikey-piv.rs/develop/img/logo.png",
html_root_url = "https://docs.rs/yubikey-piv/0.0.2"
html_root_url = "https://docs.rs/yubikey-piv/0.0.3"
)]
#![forbid(unsafe_code)]
#![warn(
@@ -156,6 +156,7 @@ mod metadata;
pub mod mgm;
#[cfg(feature = "untested")]
pub mod msroots;
pub mod readers;
#[cfg(feature = "untested")]
mod serialization;
#[cfg(feature = "untested")]
@@ -163,9 +164,10 @@ pub mod settings;
mod transaction;
pub mod yubikey;
pub use self::{readers::Readers, yubikey::YubiKey};
#[cfg(feature = "untested")]
pub use self::{key::Key, mgm::MgmKey};
pub use yubikey::YubiKey;
/// Object identifiers
pub type ObjectId = u32;
+89
View File
@@ -0,0 +1,89 @@
//! Support for enumerating available readers
use crate::{error::Error, yubikey::YubiKey};
use std::{
borrow::Cow,
convert::TryInto,
ffi::CStr,
sync::{Arc, Mutex},
};
/// Iterator over connected readers
pub type Iter<'ctx> = std::vec::IntoIter<Reader<'ctx>>;
/// Enumeration support for available readers
pub struct Readers {
/// PC/SC context
ctx: Arc<Mutex<pcsc::Context>>,
/// Buffer for storing reader names
reader_names: Vec<u8>,
}
impl Readers {
/// 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> {
let ctx = pcsc::Context::establish(pcsc::Scope::System)?;
let reader_names = vec![0u8; ctx.list_readers_len()?];
Ok(Self {
ctx: Arc::new(Mutex::new(ctx)),
reader_names,
})
}
/// Iterate over the available readers
pub fn iter(&mut self) -> Result<Iter<'_>, Error> {
let Self { ctx, reader_names } = self;
let reader_cstrs: Vec<_> = {
let c = ctx.lock().unwrap();
// ensure PC/SC context is valid
c.is_valid()?;
c.list_readers(reader_names)?.collect()
};
let readers: Vec<_> = reader_cstrs
.iter()
.map(|name| Reader::new(name, Arc::clone(ctx)))
.collect();
Ok(readers.into_iter())
}
}
/// An individual connected reader
pub struct Reader<'ctx> {
/// Name of this reader
name: &'ctx CStr,
/// PC/SC context
ctx: Arc<Mutex<pcsc::Context>>,
}
impl<'ctx> Reader<'ctx> {
/// Create a new reader from its name and context
fn new(name: &'ctx CStr, ctx: Arc<Mutex<pcsc::Context>>) -> Self {
// TODO(tarcieri): open devices, determine they're YubiKeys, get serial?
Self { name, ctx }
}
/// Get this reader's name
pub fn name(&self) -> Cow<'_, str> {
// TODO(tarcieri): is lossy ok here? try to avoid lossiness?
self.name.to_string_lossy()
}
/// Open a connection to this reader, returning a `YubiKey` if successful
pub fn open(&self) -> Result<YubiKey, 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();
Ok(ctx.connect(self.name, pcsc::ShareMode::Shared, pcsc::Protocols::T1)?)
}
}
+84 -88
View File
@@ -42,14 +42,22 @@ use crate::{
serialization::*,
Buffer, ObjectId,
};
use crate::{consts::*, error::Error, transaction::Transaction};
use crate::{
consts::*,
error::Error,
readers::{Reader, Readers},
transaction::Transaction,
};
#[cfg(feature = "untested")]
use getrandom::getrandom;
use log::{error, info, warn};
use pcsc::{Card, Context};
use pcsc::Card;
#[cfg(feature = "untested")]
use secrecy::ExposeSecret;
use std::fmt::{self, Display};
use std::{
convert::TryFrom,
fmt::{self, Display},
};
#[cfg(feature = "untested")]
use std::{
convert::TryInto,
@@ -130,96 +138,28 @@ pub struct YubiKey {
}
impl YubiKey {
/// Open a connection to a YubiKey, optionally giving the name
/// (needed if e.g. there are multiple YubiKeys connected).
pub fn open(name: Option<&[u8]>) -> Result<YubiKey, Error> {
let context = Context::establish(pcsc::Scope::System)?;
let mut card = Self::connect(&context, name)?;
/// Open a connection to a YubiKey.
///
/// Returns an error if there is more than one YubiKey detected.
///
/// 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;
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;
if let Some(reader) = reader_iter.next() {
if reader_iter.next().is_some() {
error!("multiple YubiKeys detected!");
return Err(Error::PcscError { inner: None });
}
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
})?;
return reader.open();
}
let yubikey = YubiKey {
card,
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 })
error!("no YubiKey detected!");
Err(Error::GenericError)
}
/// 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)
}
}
+1 -1
View File
@@ -14,7 +14,7 @@ fn connect() {
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.serial());
}