Merge pull request #30 from tarcieri/untested-feature
Add `untested` Cargo feature for untested functionality
This commit is contained in:
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user