tests: Initial connect test and docs
Adds an extremely basic initial test to ensure that we are able to connect to a YubiKey. The test is marked `#[ignore]` in the hope that we can eventually start adding tests which run in CI, e.g. against a mock card. This also includes a fix for calculating the APDU size, since the ones we were sending originally were overly long.
This commit is contained in:
+54
-39
@@ -1,11 +1,41 @@
|
||||
//! Application Protocol Data Unit (APDU)
|
||||
|
||||
// Adapted from yubico-piv-tool:
|
||||
// <https://github.com/Yubico/yubico-piv-tool/>
|
||||
//
|
||||
// Copyright (c) 2014-2016 Yubico AB
|
||||
// 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.
|
||||
|
||||
use crate::{error::Error, response::Response, transaction::Transaction, Buffer};
|
||||
use std::fmt::{self, Debug};
|
||||
use zeroize::{Zeroize, Zeroizing};
|
||||
|
||||
/// Size of a serialized APDU (5 byte header + 255 bytes data)
|
||||
pub const APDU_SIZE: usize = 260;
|
||||
/// Maximum amount of command data that can be included in an APDU
|
||||
const APDU_DATA_MAX: usize = 0xFF;
|
||||
|
||||
/// Application Protocol Data Unit (APDU).
|
||||
///
|
||||
@@ -13,31 +43,32 @@ pub const APDU_SIZE: usize = 260;
|
||||
/// Chip Card Interface Device (CCID) protocol.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct APDU {
|
||||
/// Instruction class - indicates the type of command, e.g. interindustry or proprietary
|
||||
/// Instruction class: indicates the type of command (e.g. inter-industry or proprietary)
|
||||
cla: u8,
|
||||
|
||||
/// Instruction code - indicates the specific command, e.g. "write data"
|
||||
/// Instruction code: indicates the specific command (e.g. "write data")
|
||||
ins: u8,
|
||||
|
||||
/// Instruction parameter 1 for the command, e.g. offset into file at which to write the data
|
||||
/// Instruction parameter 1 for the command (e.g. offset into file at which to write the data)
|
||||
p1: u8,
|
||||
|
||||
/// Instruction parameter 2 for the command
|
||||
p2: u8,
|
||||
|
||||
/// Length of command - encodes the number of bytes of command data to follow
|
||||
lc: u8,
|
||||
|
||||
/// Command data
|
||||
data: [u8; 255],
|
||||
/// Command data to be sent (`lc` is calculated as `data.len()`)
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl APDU {
|
||||
/// Create a new APDU with the given instruction code
|
||||
pub fn new(ins: u8) -> Self {
|
||||
let mut apdu = Self::default();
|
||||
apdu.ins = ins;
|
||||
apdu
|
||||
Self {
|
||||
cla: 0,
|
||||
ins,
|
||||
p1: 0,
|
||||
p2: 0,
|
||||
data: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Set this APDU's class
|
||||
@@ -63,19 +94,18 @@ impl APDU {
|
||||
///
|
||||
/// Panics if the byte slice is more than 255 bytes!
|
||||
pub fn data(&mut self, bytes: impl AsRef<[u8]>) -> &mut Self {
|
||||
assert_eq!(self.lc, 0, "APDU command already set!");
|
||||
assert!(self.data.is_empty(), "APDU command already set!");
|
||||
|
||||
let bytes = bytes.as_ref();
|
||||
|
||||
assert!(
|
||||
bytes.len() <= self.data.len(),
|
||||
"APDU command data too large: {}-bytes",
|
||||
bytes.len()
|
||||
bytes.len() <= APDU_DATA_MAX,
|
||||
"APDU command data too long: {} (max: {})",
|
||||
bytes.len(),
|
||||
APDU_DATA_MAX
|
||||
);
|
||||
|
||||
self.lc = bytes.len() as u8;
|
||||
self.data[..bytes.len()].copy_from_slice(bytes);
|
||||
|
||||
self.data.extend_from_slice(bytes);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -87,14 +117,13 @@ impl APDU {
|
||||
|
||||
/// Consume this APDU and return a self-zeroizing buffer
|
||||
pub fn to_bytes(&self) -> Buffer {
|
||||
let mut bytes = Vec::with_capacity(APDU_SIZE);
|
||||
let mut bytes = Vec::with_capacity(5 + self.data.len());
|
||||
bytes.push(self.cla);
|
||||
bytes.push(self.ins);
|
||||
bytes.push(self.p1);
|
||||
bytes.push(self.p2);
|
||||
bytes.push(self.lc);
|
||||
bytes.push(self.data.len() as u8);
|
||||
bytes.extend_from_slice(self.data.as_ref());
|
||||
|
||||
Zeroizing::new(bytes)
|
||||
}
|
||||
}
|
||||
@@ -108,25 +137,12 @@ impl Debug for APDU {
|
||||
self.ins,
|
||||
self.p1,
|
||||
self.p2,
|
||||
self.lc,
|
||||
&self.data[..]
|
||||
self.data.len(),
|
||||
self.data.as_slice()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for APDU {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
cla: 0,
|
||||
ins: 0,
|
||||
p1: 0,
|
||||
p2: 0,
|
||||
lc: 0,
|
||||
data: [0u8; 255],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for APDU {
|
||||
fn drop(&mut self) {
|
||||
self.zeroize();
|
||||
@@ -139,7 +155,6 @@ impl Zeroize for APDU {
|
||||
self.ins.zeroize();
|
||||
self.p1.zeroize();
|
||||
self.p2.zeroize();
|
||||
self.lc.zeroize();
|
||||
self.data.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
+18
-13
@@ -1,15 +1,15 @@
|
||||
//! [YubiKey][1] PIV: [Personal Identity Verification][2] support for
|
||||
//! [Yubico][3] devices using the Chip Card Interface Device ([CCID][4])
|
||||
//! protocol.
|
||||
//! [Yubico][3] devices using the Personal Computer/Smart Card ([PC/SC][4])
|
||||
//! interface as provided by the [`pcsc` crate][5].
|
||||
//!
|
||||
//! **PIV** is a [NIST][5] standard for both *signing* and *encryption*
|
||||
//! **PIV** is a [NIST][6] standard for both *signing* and *encryption*
|
||||
//! using SmartCards and SmartCard-based hardware tokens like YubiKeys.
|
||||
//!
|
||||
//! This library natively implements the CCID protocol used to manage and
|
||||
//! utilize PIV encryption and signing keys which can be generated, imported,
|
||||
//! and stored on YubiKey devices.
|
||||
//!
|
||||
//! See [Yubico's guide to PIV-enabled YubiKeys][6] for more information
|
||||
//! See [Yubico's guide to PIV-enabled YubiKeys][7] for more information
|
||||
//! on which devices support PIV and the available functionality.
|
||||
//!
|
||||
//! Supported algorithms:
|
||||
@@ -25,26 +25,31 @@
|
||||
//! This library is a work-in-progress translation and is not yet usable.
|
||||
//! Check back later for updates.
|
||||
//!
|
||||
//! ## Minimum Supported Rust Version
|
||||
//!
|
||||
//! Rust 1.39+
|
||||
//!
|
||||
//! ## History
|
||||
//!
|
||||
//! This library is a Rust translation of the [yubico-piv-tool][7] utility by
|
||||
//! This library is a Rust translation of the [yubico-piv-tool][8] utility by
|
||||
//! Yubico, which was originally written in C. It was mechanically translated
|
||||
//! from C into Rust using [Corrode][8], and then subsequently heavily
|
||||
//! from C into Rust using [Corrode][9], and then subsequently heavily
|
||||
//! refactored into safer, more idiomatic Rust.
|
||||
//!
|
||||
//! For more information on `yubico-piv-tool` and background information on how
|
||||
//! the YubiKey implementation of PIV works in general, see the
|
||||
//! [Yubico PIV Tool Command Line Guide][9].
|
||||
//! [Yubico PIV Tool Command Line Guide][10].
|
||||
//!
|
||||
//! [1]: https://www.yubico.com/products/yubikey-hardware/
|
||||
//! [2]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf
|
||||
//! [3]: https://www.yubico.com/
|
||||
//! [4]: https://en.wikipedia.org/wiki/CCID_(protocol)
|
||||
//! [5]: https://www.nist.gov/
|
||||
//! [6]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
|
||||
//! [7]: https://github.com/Yubico/yubico-piv-tool/
|
||||
//! [8]: https://github.com/jameysharp/corrode
|
||||
//! [9]: https://www.yubico.com/wp-content/uploads/2016/05/Yubico_PIV_Tool_Command_Line_Guide_en.pdf
|
||||
//! [4]: https://en.wikipedia.org/wiki/PC/SC
|
||||
//! [5]: https://github.com/bluetech/pcsc-rust
|
||||
//! [6]: https://www.nist.gov/
|
||||
//! [7]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
|
||||
//! [8]: https://github.com/Yubico/yubico-piv-tool/
|
||||
//! [9]: https://github.com/jameysharp/corrode
|
||||
//! [10]: https://www.yubico.com/wp-content/uploads/2016/05/Yubico_PIV_Tool_Command_Line_Guide_en.pdf
|
||||
|
||||
// Adapted from yubico-piv-tool:
|
||||
// <https://github.com/Yubico/yubico-piv-tool/>
|
||||
|
||||
+19
-7
@@ -155,7 +155,7 @@ impl YubiKey {
|
||||
Ok(yubikey)
|
||||
}
|
||||
|
||||
/// Connect to a 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()?;
|
||||
@@ -178,7 +178,19 @@ impl YubiKey {
|
||||
|
||||
info!("trying to connect to reader '{}'", reader.to_string_lossy());
|
||||
|
||||
return Ok(context.connect(reader, pcsc::ShareMode::Shared, pcsc::Protocols::T1)?);
|
||||
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");
|
||||
@@ -308,7 +320,7 @@ impl YubiKey {
|
||||
/// Get YubiKey device serial number.
|
||||
///
|
||||
/// This always uses the cached version queried when the key is initialized.
|
||||
pub fn get_serial(&mut self) -> Serial {
|
||||
pub fn serial(&mut self) -> Serial {
|
||||
self.serial
|
||||
}
|
||||
|
||||
@@ -376,7 +388,7 @@ impl YubiKey {
|
||||
/// Change the Personal Identification Number (PIN).
|
||||
///
|
||||
/// The default PIN code is 123456
|
||||
pub unsafe fn change_pin(&mut self, current_pin: &[u8], new_pin: &[u8]) -> Result<(), Error> {
|
||||
pub fn change_pin(&mut self, current_pin: &[u8], new_pin: &[u8]) -> Result<(), Error> {
|
||||
{
|
||||
let txn = self.begin_transaction()?;
|
||||
txn.change_pin(CHREF_ACT_CHANGE_PIN, current_pin, new_pin)?;
|
||||
@@ -390,7 +402,7 @@ impl YubiKey {
|
||||
}
|
||||
|
||||
/// Set PIN last changed
|
||||
pub unsafe fn set_pin_last_changed(yubikey: &mut YubiKey) -> Result<(), Error> {
|
||||
pub fn set_pin_last_changed(yubikey: &mut YubiKey) -> Result<(), Error> {
|
||||
let mut data = [0u8; CB_BUF_MAX];
|
||||
let max_size = yubikey.obj_size_max();
|
||||
let txn = yubikey.begin_transaction()?;
|
||||
@@ -433,7 +445,7 @@ impl YubiKey {
|
||||
/// The PUK is part of the PIV standard that the YubiKey follows.
|
||||
///
|
||||
/// The default PUK code is 12345678.
|
||||
pub unsafe fn change_puk(&mut self, current_puk: &[u8], new_puk: &[u8]) -> Result<(), Error> {
|
||||
pub fn change_puk(&mut self, current_puk: &[u8], new_puk: &[u8]) -> Result<(), Error> {
|
||||
let txn = self.begin_transaction()?;
|
||||
txn.change_pin(CHREF_ACT_CHANGE_PUK, current_puk, new_puk)
|
||||
}
|
||||
@@ -507,7 +519,7 @@ impl YubiKey {
|
||||
|
||||
/// Unblock a Personal Identification Number (PIN) using a previously
|
||||
/// configured PIN Unblocking Key (PUK).
|
||||
pub unsafe fn unblock_pin(&mut self, puk: &[u8], new_pin: &[u8]) -> Result<(), Error> {
|
||||
pub fn unblock_pin(&mut self, puk: &[u8], new_pin: &[u8]) -> Result<(), Error> {
|
||||
let txn = self.begin_transaction()?;
|
||||
txn.change_pin(CHREF_ACT_UNBLOCK_PIN, puk, new_pin)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user