Add untested Cargo feature for untested functionality

This adds an `untested` feature to any functions which have not yet been
tested live against a YubiKey device (which is presently pretty much
everything).

This sets a clear expectation of what is presently supported, and
additionally documents the status in the README (and a series of GitHub
issues).

Adds a `cargo build --all-features` to GitHub Actions' `test` step in
order to make sure that `untested` functionality still compiles.
This commit is contained in:
Tony Arcieri
2019-11-25 15:02:22 -08:00
parent 9083194c3b
commit a23af7dc31
8 changed files with 146 additions and 64 deletions
+16
View File
@@ -59,6 +59,14 @@ jobs:
command: test command: test
args: --release args: --release
- name: Run cargo build --all-features
uses: actions-rs/cargo@v1
env:
RUSTFLAGS: -D warnings
with:
command: build
args: --all-features
test: test:
name: Test Suite name: Test Suite
strategy: strategy:
@@ -88,6 +96,14 @@ jobs:
command: test command: test
args: --release args: --release
- name: Run cargo build --all-features
uses: actions-rs/cargo@v1
env:
RUSTFLAGS: -D warnings
with:
command: build
args: --all-features
fmt: fmt:
name: Rustfmt name: Rustfmt
runs-on: ubuntu-latest runs-on: ubuntu-latest
+13 -5
View File
@@ -7,7 +7,6 @@ application providing general-purpose public-key signing and encryption
with hardware-backed private keys for RSA (2048/1024) and ECC (P-256/P-384) with hardware-backed private keys for RSA (2048/1024) and ECC (P-256/P-384)
algorithms (e.g, PKCS#1v1.5, ECDSA) algorithms (e.g, PKCS#1v1.5, ECDSA)
""" """
authors = ["Tony Arcieri <bascule@gmail.com>", "Yubico AB"] authors = ["Tony Arcieri <bascule@gmail.com>", "Yubico AB"]
edition = "2018" edition = "2018"
license = "BSD-2-Clause" license = "BSD-2-Clause"
@@ -16,16 +15,25 @@ readme = "README.md"
categories = ["api-bindings", "cryptography", "hardware-support"] categories = ["api-bindings", "cryptography", "hardware-support"]
keywords = ["ccid", "ecdsa", "rsa", "piv", "yubikey"] keywords = ["ccid", "ecdsa", "rsa", "piv", "yubikey"]
[badges]
maintenance = { status = "experimental" }
[dependencies] [dependencies]
des = "0.3" des = "0.3"
getrandom = "0.1" getrandom = "0.1"
hmac = "0.7" hmac = { version = "0.7", optional = true }
log = "0.4" log = "0.4"
pbkdf2 = "0.3" pbkdf2 = { version = "0.3", optional = true }
pcsc = "2" pcsc = "2"
sha-1 = "0.8" sha-1 = { version = "0.8", optional = true }
subtle = "2" subtle = { version = "2", optional = true }
zeroize = "1" zeroize = "1"
[dev-dependencies] [dev-dependencies]
env_logger = "0.7" env_logger = "0.7"
[features]
untested = ["hmac", "pbkdf2", "sha-1", "subtle"]
[package.metadata.docs.rs]
all-features = true
+5 -2
View File
@@ -80,8 +80,11 @@ Legend:
| 🚧 | Testing and validation in progress | | 🚧 | Testing and validation in progress |
| ⚠️ | Untested support | | ⚠️ | Untested support |
NOTE: Commands marked ⚠️ have not been properly tested and may contain bugs or NOTE: Commands marked ⚠️ are disabled by default as they have have not been properly tested and may contain bugs or
not work at all. not work at all. USE AT YOUR OWN RISK!
Enable the `untested` feature in your `Cargo.toml` to enable features marked ⚠️
above.
## Testing ## Testing
+2
View File
@@ -71,6 +71,7 @@ impl APDU {
} }
/// Set this APDU's class /// Set this APDU's class
#[cfg(feature = "untested")]
pub fn cla(&mut self, value: u8) -> &mut Self { pub fn cla(&mut self, value: u8) -> &mut Self {
self.cla = value; self.cla = value;
self self
@@ -83,6 +84,7 @@ impl APDU {
} }
/// Set both parameters for this APDU /// Set both parameters for this APDU
#[cfg(feature = "untested")]
pub fn params(&mut self, p1: u8, p2: u8) -> &mut Self { pub fn params(&mut self, p1: u8, p2: u8) -> &mut Self {
self.p1 = p1; self.p1 = p1;
self.p2 = p2; self.p2 = p2;
+16 -2
View File
@@ -40,7 +40,7 @@
//! code from upstream [yubico-piv-tool] has been translated into Rust //! code from upstream [yubico-piv-tool] has been translated into Rust
//! presenting a safe interface, much of it is still untested. //! presenting a safe interface, much of it is still untested.
//! //!
//! Please see the project's README.md for a complete status. //! Please see the [project's README.md for a complete status][status].
//! //!
//! ## History //! ## History
//! //!
@@ -83,6 +83,7 @@
//! [YubiKey NEO]: https://support.yubico.com/support/solutions/articles/15000006494-yubikey-neo //! [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 4]: https://support.yubico.com/support/solutions/articles/15000006486-yubikey-4
//! [YubiKey 5]: https://www.yubico.com/products/yubikey-5-overview/ //! [YubiKey 5]: https://www.yubico.com/products/yubikey-5-overview/
//! [status]: https://github.com/tarcieri/yubikey-piv.rs#status
//! [yubico-piv-tool]: https://github.com/Yubico/yubico-piv-tool/ //! [yubico-piv-tool]: https://github.com/Yubico/yubico-piv-tool/
//! [Corrode]: https://github.com/jameysharp/corrode //! [Corrode]: https://github.com/jameysharp/corrode
//! [piv-tool-guide]: https://www.yubico.com/wp-content/uploads/2016/05/Yubico_PIV_Tool_Command_Line_Guide_en.pdf //! [piv-tool-guide]: https://www.yubico.com/wp-content/uploads/2016/05/Yubico_PIV_Tool_Command_Line_Guide_en.pdf
@@ -134,24 +135,37 @@
)] )]
mod apdu; mod apdu;
#[cfg(feature = "untested")]
pub mod cccid; pub mod cccid;
#[cfg(feature = "untested")]
pub mod certificate; pub mod certificate;
#[cfg(feature = "untested")]
pub mod chuid; pub mod chuid;
#[cfg(feature = "untested")]
pub mod config; pub mod config;
pub mod consts; pub mod consts;
#[cfg(feature = "untested")]
pub mod container; pub mod container;
pub mod error; pub mod error;
#[cfg(feature = "untested")]
pub mod key; pub mod key;
#[cfg(feature = "untested")]
mod metadata; mod metadata;
#[cfg(feature = "untested")]
pub mod mgm; pub mod mgm;
#[cfg(feature = "untested")]
pub mod msroots; pub mod msroots;
mod response; mod response;
#[cfg(feature = "untested")]
mod serialization; mod serialization;
#[cfg(feature = "untested")]
pub mod settings; pub mod settings;
mod transaction; mod transaction;
pub mod yubikey; pub mod yubikey;
pub use self::{key::Key, mgm::MgmKey, yubikey::YubiKey}; #[cfg(feature = "untested")]
pub use self::{key::Key, mgm::MgmKey};
pub use yubikey::YubiKey;
/// Algorithm identifiers /// Algorithm identifiers
// TODO(tarcieri): make this an enum // TODO(tarcieri): make this an enum
+3
View File
@@ -64,6 +64,7 @@ impl Response {
} }
/// Create a new response from the given status words and buffer /// Create a new response from the given status words and buffer
#[cfg(feature = "untested")]
pub fn new(status_words: StatusWords, buffer: Buffer) -> Response { pub fn new(status_words: StatusWords, buffer: Buffer) -> Response {
Response { Response {
status_words, status_words,
@@ -77,6 +78,7 @@ impl Response {
} }
/// Get the raw [`StatusWords`] code for this response. /// Get the raw [`StatusWords`] code for this response.
#[cfg(feature = "untested")]
pub fn code(&self) -> u32 { pub fn code(&self) -> u32 {
self.status_words.code() self.status_words.code()
} }
@@ -92,6 +94,7 @@ impl Response {
} }
/// Consume this response, returning its buffer /// Consume this response, returning its buffer
#[cfg(feature = "untested")]
pub fn into_buffer(self) -> Buffer { pub fn into_buffer(self) -> Buffer {
self.buffer self.buffer
} }
+13 -6
View File
@@ -1,17 +1,17 @@
//! YubiKey PC/SC transactions //! YubiKey PC/SC transactions
use crate::{apdu::APDU, consts::*, error::Error, yubikey::*, Buffer};
#[cfg(feature = "untested")]
use crate::{ use crate::{
apdu::APDU,
consts::*,
error::Error,
mgm::MgmKey, mgm::MgmKey,
response::{Response, StatusWords}, response::{Response, StatusWords},
serialization::*, serialization::*,
yubikey::*, ObjectId,
Buffer, ObjectId,
}; };
use log::{error, trace}; use log::{error, trace};
use std::{convert::TryInto, ptr}; use std::convert::TryInto;
#[cfg(feature = "untested")]
use std::ptr;
use zeroize::Zeroizing; use zeroize::Zeroizing;
/// Exclusive transaction with the YubiKey's PC/SC card. /// Exclusive transaction with the YubiKey's PC/SC card.
@@ -164,6 +164,7 @@ impl<'tx> Transaction<'tx> {
} }
/// Verify device PIN. /// Verify device PIN.
#[cfg(feature = "untested")]
pub fn verify_pin(&self, pin: &[u8]) -> Result<(), Error> { pub fn verify_pin(&self, pin: &[u8]) -> Result<(), Error> {
// TODO(tarcieri): allow unpadded (with `0xFF`) PIN shorter than CB_PIN_MAX? // TODO(tarcieri): allow unpadded (with `0xFF`) PIN shorter than CB_PIN_MAX?
if pin.len() != CB_PIN_MAX { if pin.len() != CB_PIN_MAX {
@@ -184,6 +185,7 @@ impl<'tx> Transaction<'tx> {
} }
/// Change the PIN /// Change the PIN
#[cfg(feature = "untested")]
pub fn change_pin(&self, action: i32, current_pin: &[u8], new_pin: &[u8]) -> Result<(), Error> { pub fn change_pin(&self, action: i32, current_pin: &[u8], new_pin: &[u8]) -> Result<(), Error> {
let mut templ = [0, YKPIV_INS_CHANGE_REFERENCE, 0, 0x80]; let mut templ = [0, YKPIV_INS_CHANGE_REFERENCE, 0, 0x80];
let mut indata = Zeroizing::new([0u8; 16]); let mut indata = Zeroizing::new([0u8; 16]);
@@ -243,6 +245,7 @@ impl<'tx> Transaction<'tx> {
} }
/// Set the management key (MGM). /// Set the management key (MGM).
#[cfg(feature = "untested")]
pub fn set_mgm_key(&self, new_key: &MgmKey, touch: Option<u8>) -> Result<(), Error> { pub fn set_mgm_key(&self, new_key: &MgmKey, touch: Option<u8>) -> Result<(), Error> {
let p2 = match touch.unwrap_or_default() { let p2 = match touch.unwrap_or_default() {
0 => 0xff, 0 => 0xff,
@@ -276,6 +279,7 @@ impl<'tx> Transaction<'tx> {
/// This is the common backend for all public key encryption and signing /// This is the common backend for all public key encryption and signing
/// operations. /// operations.
// TODO(tarcieri): refactor this to be less gross/coupled. // TODO(tarcieri): refactor this to be less gross/coupled.
#[cfg(feature = "untested")]
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub(crate) fn authenticated_command( pub(crate) fn authenticated_command(
&self, &self,
@@ -392,6 +396,7 @@ impl<'tx> Transaction<'tx> {
/// messages into smaller APDU-sized messages (using the provided APDU /// messages into smaller APDU-sized messages (using the provided APDU
/// template to construct them), and then sending those via /// template to construct them), and then sending those via
/// [`Transaction::transmit`]. /// [`Transaction::transmit`].
#[cfg(feature = "untested")]
pub fn transfer_data( pub fn transfer_data(
&self, &self,
templ: &[u8], templ: &[u8],
@@ -475,6 +480,7 @@ impl<'tx> Transaction<'tx> {
} }
/// Fetch an object /// Fetch an object
#[cfg(feature = "untested")]
pub fn fetch_object(&self, object_id: ObjectId) -> Result<Buffer, Error> { pub fn fetch_object(&self, object_id: ObjectId) -> Result<Buffer, Error> {
let mut indata = [0u8; 5]; let mut indata = [0u8; 5];
let templ = [0, YKPIV_INS_GET_DATA, 0x3f, 0xff]; let templ = [0, YKPIV_INS_GET_DATA, 0x3f, 0xff];
@@ -518,6 +524,7 @@ impl<'tx> Transaction<'tx> {
} }
/// Save an object /// Save an object
#[cfg(feature = "untested")]
pub fn save_object(&self, object_id: ObjectId, indata: &[u8]) -> Result<(), Error> { pub fn save_object(&self, object_id: ObjectId, indata: &[u8]) -> Result<(), Error> {
let templ = [0, YKPIV_INS_PUT_DATA, 0x3f, 0xff]; let templ = [0, YKPIV_INS_PUT_DATA, 0x3f, 0xff];
+78 -49
View File
@@ -33,19 +33,24 @@
#![allow(non_snake_case, non_upper_case_globals)] #![allow(non_snake_case, non_upper_case_globals)]
#![allow(clippy::too_many_arguments, clippy::missing_safety_doc)] #![allow(clippy::too_many_arguments, clippy::missing_safety_doc)]
#[cfg(feature = "untested")]
use crate::{ use crate::{
apdu::APDU, consts::*, error::Error, key::SlotId, metadata, mgm::MgmKey, response::StatusWords, apdu::APDU, key::SlotId, metadata, mgm::MgmKey, response::StatusWords, serialization::*,
serialization::*, transaction::Transaction, Buffer, ObjectId, ObjectId,
}; };
use crate::{consts::*, error::Error, transaction::Transaction, Buffer};
#[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, Context};
use std::fmt::{self, Display};
#[cfg(feature = "untested")]
use std::{ use std::{
convert::TryInto, convert::TryInto,
fmt::{self, Display},
ptr, slice, ptr, slice,
time::{SystemTime, UNIX_EPOCH}, time::{SystemTime, UNIX_EPOCH},
}; };
#[cfg(feature = "untested")]
use zeroize::Zeroizing; use zeroize::Zeroizing;
/// PIV Application ID /// PIV Application ID
@@ -96,6 +101,7 @@ pub struct Version {
/// Almost all functionality in this library will require an open session /// Almost all functionality in this library will require an open session
/// with a YubiKey which is represented by this type. /// with a YubiKey which is represented by this type.
// TODO(tarcieri): reduce coupling to internal fields via `pub(crate)` // TODO(tarcieri): reduce coupling to internal fields via `pub(crate)`
#[cfg_attr(not(feature = "untested"), allow(dead_code))]
pub struct YubiKey { pub struct YubiKey {
pub(crate) card: Card, pub(crate) card: Card,
pub(crate) pin: Option<Buffer>, pub(crate) pin: Option<Buffer>,
@@ -198,6 +204,7 @@ impl YubiKey {
} }
/// Reconnect to a YubiKey /// Reconnect to a YubiKey
#[cfg(feature = "untested")]
pub fn reconnect(&mut self) -> Result<(), Error> { pub fn reconnect(&mut self) -> Result<(), Error> {
info!("trying to reconnect to current reader"); info!("trying to reconnect to current reader");
@@ -221,12 +228,40 @@ impl YubiKey {
} }
/// Begin a transaction. /// Begin a transaction.
#[cfg(feature = "untested")]
pub(crate) fn begin_transaction(&mut self) -> Result<Transaction<'_>, Error> { pub(crate) fn begin_transaction(&mut self) -> Result<Transaction<'_>, Error> {
// TODO(tarcieri): reconnect support // TODO(tarcieri): reconnect support
Ok(Transaction::new(&mut self.card)?) Ok(Transaction::new(&mut self.card)?)
} }
/// Get the YubiKey's PIV application version.
///
/// This always uses the cached version queried when the key is initialized.
pub fn version(&mut self) -> Version {
self.version
}
/// Get YubiKey device serial number.
///
/// This always uses the cached version queried when the key is initialized.
pub fn serial(&mut self) -> Serial {
self.serial
}
/// Get YubiKey device model
// TODO(tarcieri): use an emum for this
#[cfg(feature = "untested")]
pub fn device_model(&self) -> u32 {
if self.is_neo {
DEVTYPE_NEOr3
} else {
// TODO(tarcieri): YK5?
DEVTYPE_YK4
}
}
/// Authenticate to the card using the provided management key (MGM). /// Authenticate to the card using the provided management key (MGM).
#[cfg(feature = "untested")]
pub fn authenticate(&mut self, mgm_key: MgmKey) -> Result<(), Error> { pub fn authenticate(&mut self, mgm_key: MgmKey) -> Result<(), Error> {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
@@ -280,7 +315,30 @@ impl YubiKey {
Ok(()) Ok(())
} }
/// Deauthenticate
#[cfg(feature = "untested")]
pub fn deauthenticate(&mut self) -> Result<(), Error> {
let txn = self.begin_transaction()?;
let status_words = APDU::new(YKPIV_INS_SELECT_APPLICATION)
.p1(0x04)
.data(MGMT_AID)
.transmit(&txn, 255)?
.status_words();
if !status_words.is_success() {
error!(
"Failed selecting mgmt application: {:04x}",
status_words.code()
);
return Err(Error::GenericError);
}
Ok(())
}
/// Sign data using a PIV key /// Sign data using a PIV key
#[cfg(feature = "untested")]
pub fn sign_data( pub fn sign_data(
&mut self, &mut self,
raw_in: &[u8], raw_in: &[u8],
@@ -296,6 +354,7 @@ impl YubiKey {
} }
/// Decrypt data using a PIV key /// Decrypt data using a PIV key
#[cfg(feature = "untested")]
pub fn decrypt_data( pub fn decrypt_data(
&mut self, &mut self,
input: &[u8], input: &[u8],
@@ -310,21 +369,8 @@ impl YubiKey {
txn.authenticated_command(input, out, out_len, algorithm, key, true) txn.authenticated_command(input, out, out_len, algorithm, key, true)
} }
/// Get the YubiKey's PIV application version.
///
/// This always uses the cached version queried when the key is initialized.
pub fn version(&mut self) -> Version {
self.version
}
/// Get YubiKey device serial number.
///
/// This always uses the cached version queried when the key is initialized.
pub fn serial(&mut self) -> Serial {
self.serial
}
/// Verify device PIN. /// Verify device PIN.
#[cfg(feature = "untested")]
pub fn verify_pin(&mut self, pin: &[u8]) -> Result<(), Error> { pub fn verify_pin(&mut self, pin: &[u8]) -> Result<(), Error> {
{ {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
@@ -339,6 +385,7 @@ impl YubiKey {
} }
/// Get the number of PIN retries /// Get the number of PIN retries
#[cfg(feature = "untested")]
pub fn get_pin_retries(&mut self) -> Result<u32, Error> { pub fn get_pin_retries(&mut self) -> Result<u32, Error> {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
@@ -356,6 +403,7 @@ impl YubiKey {
} }
/// Set the number of PIN retries /// Set the number of PIN retries
#[cfg(feature = "untested")]
pub fn set_pin_retries(&mut self, pin_tries: usize, puk_tries: usize) -> Result<(), Error> { pub fn set_pin_retries(&mut self, pin_tries: usize, puk_tries: usize) -> Result<(), Error> {
// Special case: if either retry count is 0, it's a successful no-op // Special case: if either retry count is 0, it's a successful no-op
if pin_tries == 0 || puk_tries == 0 { if pin_tries == 0 || puk_tries == 0 {
@@ -388,6 +436,7 @@ impl YubiKey {
/// Change the Personal Identification Number (PIN). /// Change the Personal Identification Number (PIN).
/// ///
/// The default PIN code is 123456 /// The default PIN code is 123456
#[cfg(feature = "untested")]
pub 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()?; let txn = self.begin_transaction()?;
@@ -402,6 +451,7 @@ impl YubiKey {
} }
/// Set PIN last changed /// Set PIN last changed
#[cfg(feature = "untested")]
pub 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 mut data = [0u8; CB_BUF_MAX];
let max_size = yubikey.obj_size_max(); let max_size = yubikey.obj_size_max();
@@ -445,12 +495,14 @@ impl YubiKey {
/// The PUK is part of the PIV standard that the YubiKey follows. /// The PUK is part of the PIV standard that the YubiKey follows.
/// ///
/// The default PUK code is 12345678. /// The default PUK code is 12345678.
#[cfg(feature = "untested")]
pub 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()?; let txn = self.begin_transaction()?;
txn.change_pin(CHREF_ACT_CHANGE_PUK, current_puk, new_puk) txn.change_pin(CHREF_ACT_CHANGE_PUK, current_puk, new_puk)
} }
/// Block PUK: permanently prevent the PIN from becoming unblocked /// Block PUK: permanently prevent the PIN from becoming unblocked
#[cfg(feature = "untested")]
pub fn block_puk(yubikey: &mut YubiKey) -> Result<(), Error> { pub fn block_puk(yubikey: &mut YubiKey) -> Result<(), Error> {
let mut puk = [0x30, 0x42, 0x41, 0x44, 0x46, 0x30, 0x30, 0x44]; let mut puk = [0x30, 0x42, 0x41, 0x44, 0x46, 0x30, 0x30, 0x44];
let mut tries_remaining: i32 = -1; let mut tries_remaining: i32 = -1;
@@ -519,18 +571,21 @@ impl YubiKey {
/// Unblock a Personal Identification Number (PIN) using a previously /// Unblock a Personal Identification Number (PIN) using a previously
/// configured PIN Unblocking Key (PUK). /// configured PIN Unblocking Key (PUK).
#[cfg(feature = "untested")]
pub 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()?; let txn = self.begin_transaction()?;
txn.change_pin(CHREF_ACT_UNBLOCK_PIN, puk, new_pin) txn.change_pin(CHREF_ACT_UNBLOCK_PIN, puk, new_pin)
} }
/// Fetch an object from the YubiKey /// Fetch an object from the YubiKey
#[cfg(feature = "untested")]
pub fn fetch_object(&mut self, object_id: ObjectId) -> Result<Buffer, Error> { pub fn fetch_object(&mut self, object_id: ObjectId) -> Result<Buffer, Error> {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
txn.fetch_object(object_id) txn.fetch_object(object_id)
} }
/// Save an object /// Save an object
#[cfg(feature = "untested")]
pub fn save_object(&mut self, object_id: ObjectId, indata: &mut [u8]) -> Result<(), Error> { pub fn save_object(&mut self, object_id: ObjectId, indata: &mut [u8]) -> Result<(), Error> {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
txn.save_object(object_id, indata) txn.save_object(object_id, indata)
@@ -538,6 +593,7 @@ impl YubiKey {
/// Import a private encryption or signing key into the YubiKey /// Import a private encryption or signing key into the YubiKey
// TODO(tarcieri): refactor this into separate methods per key type // TODO(tarcieri): refactor this into separate methods per key type
#[cfg(feature = "untested")]
pub fn import_private_key( pub fn import_private_key(
&mut self, &mut self,
key: SlotId, key: SlotId,
@@ -733,6 +789,7 @@ impl YubiKey {
/// Generate an attestation certificate for a stored key. /// Generate an attestation certificate for a stored key.
/// <https://developers.yubico.com/PIV/Introduction/PIV_attestation.html> /// <https://developers.yubico.com/PIV/Introduction/PIV_attestation.html>
#[cfg(feature = "untested")]
pub fn attest(&mut self, key: SlotId) -> Result<Buffer, Error> { pub fn attest(&mut self, key: SlotId) -> Result<Buffer, Error> {
let templ = [0, YKPIV_INS_ATTEST, key, 0]; let templ = [0, YKPIV_INS_ATTEST, key, 0];
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
@@ -754,6 +811,7 @@ impl YubiKey {
} }
/// Get an auth challenge /// Get an auth challenge
#[cfg(feature = "untested")]
pub fn get_auth_challenge(&mut self) -> Result<[u8; 8], Error> { pub fn get_auth_challenge(&mut self) -> Result<[u8; 8], Error> {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
@@ -770,6 +828,7 @@ impl YubiKey {
} }
/// Verify an auth response /// Verify an auth response
#[cfg(feature = "untested")]
pub fn verify_auth_response(&mut self, response: [u8; 8]) -> Result<(), Error> { pub fn verify_auth_response(&mut self, response: [u8; 8]) -> Result<(), Error> {
let mut data = [0u8; 12]; let mut data = [0u8; 12];
data[0] = 0x7c; data[0] = 0x7c;
@@ -794,43 +853,12 @@ impl YubiKey {
Ok(()) Ok(())
} }
/// Deauthenticate
pub fn deauthenticate(&mut self) -> Result<(), Error> {
let txn = self.begin_transaction()?;
let status_words = APDU::new(YKPIV_INS_SELECT_APPLICATION)
.p1(0x04)
.data(MGMT_AID)
.transmit(&txn, 255)?
.status_words();
if !status_words.is_success() {
error!(
"Failed selecting mgmt application: {:04x}",
status_words.code()
);
return Err(Error::GenericError);
}
Ok(())
}
/// Get YubiKey device model
// TODO(tarcieri): use an emum for this
pub fn device_model(&self) -> u32 {
if self.is_neo {
DEVTYPE_NEOr3
} else {
// TODO(tarcieri): YK5?
DEVTYPE_YK4
}
}
/// Reset YubiKey. /// Reset YubiKey.
/// ///
/// WARNING: this is a destructive operation which will destroy all keys! /// WARNING: this is a destructive operation which will destroy all keys!
/// ///
/// The reset function is only available when both pins are blocked. /// The reset function is only available when both pins are blocked.
#[cfg(feature = "untested")]
pub fn reset_device(&mut self) -> Result<(), Error> { pub fn reset_device(&mut self) -> Result<(), Error> {
let templ = [0, YKPIV_INS_RESET, 0, 0]; let templ = [0, YKPIV_INS_RESET, 0, 0];
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
@@ -844,6 +872,7 @@ impl YubiKey {
} }
/// Get max object size supported by this device /// Get max object size supported by this device
#[cfg(feature = "untested")]
pub(crate) fn obj_size_max(&self) -> usize { pub(crate) fn obj_size_max(&self) -> usize {
if self.is_neo { if self.is_neo {
CB_OBJ_MAX_NEO CB_OBJ_MAX_NEO