Flatten API (#274)
Re-exports types from the toplevel instead of placing them in individual modules (often which only contain one type). This makes the API easier for users to navigate, while still retaining the same module structure internally. Additionally, this commit uses the `uuid` crate for modeling UUIDs.
This commit is contained in:
committed by
GitHub
parent
1228d16439
commit
1765e11bc0
Generated
+10
@@ -887,6 +887,15 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||
dependencies = [
|
||||
"getrandom 0.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.3"
|
||||
@@ -998,6 +1007,7 @@ dependencies = [
|
||||
"sha2",
|
||||
"subtle",
|
||||
"subtle-encoding",
|
||||
"uuid",
|
||||
"x509",
|
||||
"x509-parser",
|
||||
"zeroize",
|
||||
|
||||
@@ -44,6 +44,7 @@ sha-1 = "0.9"
|
||||
sha2 = "0.9"
|
||||
subtle = "2"
|
||||
subtle-encoding = "0.5"
|
||||
uuid = { version = "0.8", features = ["v4"] }
|
||||
x509 = "0.2"
|
||||
x509-parser = "0.9"
|
||||
zeroize = "1"
|
||||
@@ -57,3 +58,4 @@ untested = []
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
@@ -53,41 +53,6 @@ an experimental stage and may still contain high-severity issues.
|
||||
|
||||
USE AT YOUR OWN RISK!
|
||||
|
||||
## Status
|
||||
|
||||
This project is a largely incomplete work-in-progress. So far the only
|
||||
functionality which has actually been tested is connecting to Yubikeys.
|
||||
|
||||
If you're interested helping test functionality, the table below documents
|
||||
the current status of the project and relevant GitHub issues for various
|
||||
functions of the YubiKey:
|
||||
|
||||
| | Module | Issue | Description |
|
||||
|----|---------------|-------|-------------|
|
||||
| 🚧 | `yubikey` | [#20] | Core functionality: auth, keys, PIN/PUK, encrypt, sign, attest |
|
||||
| 🚧 | `cccid` | [#21] | Cardholder Capability Container (CCC) IDs |
|
||||
| 🚧️ | `certificate` | [#22] | Certificates for stored keys |
|
||||
| 🚧 | `chuid` | [#23] | Cardholder Unique Identifier (CHUID) |
|
||||
| ✅️ | `config` | [#24] | Support for reading on-key configuration |
|
||||
| 🚧 | `key` | [#26] | Crypto key management: list, generate, import |
|
||||
| 🚧 | `mgm` | [#26] | Management Key (MGM) support: set, get, derive |
|
||||
| ⚠️ | `mscmap` | [#25] | MS Container Map Records |
|
||||
| ⚠️ | `msroots` | [#28] | `msroots` file: PKCS#7 formatted certificate store for enterprise trusted roots |
|
||||
|
||||
Legend:
|
||||
|
||||
| | Description |
|
||||
|----|------------------------------------|
|
||||
| ✅ | Working |
|
||||
| 🚧 | Testing and validation in progress |
|
||||
| ⚠️ | Untested support |
|
||||
|
||||
NOTE: Commands marked ⚠️ are disabled by default as they have have not been properly tested and may contain bugs or
|
||||
not work at all. USE AT YOUR OWN RISK!
|
||||
|
||||
Enable the `untested` feature in your `Cargo.toml` to enable features marked ⚠️
|
||||
above.
|
||||
|
||||
## Testing
|
||||
|
||||
To run the full test suite, you'll need a connected YubiKey NEO/4/5 device in
|
||||
|
||||
+22
-29
@@ -32,15 +32,12 @@
|
||||
|
||||
use crate::{Error, Result, YubiKey};
|
||||
use getrandom::getrandom;
|
||||
use std::fmt::{self, Debug, Display};
|
||||
use std::{
|
||||
fmt::{self, Debug, Display},
|
||||
str,
|
||||
};
|
||||
use subtle_encoding::hex;
|
||||
|
||||
/// CCCID size
|
||||
pub const CCCID_SIZE: usize = 14;
|
||||
|
||||
/// CCC size
|
||||
pub const CCC_SIZE: usize = 51;
|
||||
|
||||
/// CCCID offset
|
||||
const CCC_ID_OFFS: usize = 9;
|
||||
|
||||
@@ -62,28 +59,34 @@ const CCC_TMPL: &[u8] = &[
|
||||
0x00, 0xfe, 0x00,
|
||||
];
|
||||
|
||||
/// Cardholder Capability Container (CCC) Identifier Card ID
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub struct CardId(pub [u8; CCCID_SIZE]);
|
||||
/// Cardholder Capability Container (CCC) Identifier Card ID.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct CardId(pub [u8; Self::BYTE_SIZE]);
|
||||
|
||||
impl CardId {
|
||||
/// CCCID size in bytes
|
||||
pub const BYTE_SIZE: usize = 14;
|
||||
|
||||
/// Generate a random CCC Card ID
|
||||
pub fn generate() -> Result<Self> {
|
||||
let mut id = [0u8; CCCID_SIZE];
|
||||
let mut id = [0u8; Self::BYTE_SIZE];
|
||||
getrandom(&mut id).map_err(|_| Error::RandomnessError)?;
|
||||
Ok(Self(id))
|
||||
}
|
||||
}
|
||||
|
||||
/// Cardholder Capability Container (CCC) Identifier
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Ccc(pub [u8; CCC_SIZE]);
|
||||
/// Cardholder Capability Container (CCC) Identifier.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub struct Ccc(pub [u8; Self::BYTE_SIZE]);
|
||||
|
||||
impl Ccc {
|
||||
/// CCC size in bytes
|
||||
pub const BYTE_SIZE: usize = 51;
|
||||
|
||||
/// Return CardId component of CCC
|
||||
pub fn card_id(&self) -> Result<CardId> {
|
||||
let mut cccid = [0u8; CCCID_SIZE];
|
||||
cccid.copy_from_slice(&self.0[CCC_ID_OFFS..(CCC_ID_OFFS + CCCID_SIZE)]);
|
||||
let mut cccid = [0u8; CardId::BYTE_SIZE];
|
||||
cccid.copy_from_slice(&self.0[CCC_ID_OFFS..(CCC_ID_OFFS + CardId::BYTE_SIZE)]);
|
||||
Ok(CardId(cccid))
|
||||
}
|
||||
|
||||
@@ -96,8 +99,8 @@ impl Ccc {
|
||||
return Err(Error::GenericError);
|
||||
}
|
||||
|
||||
let mut ccc = [0u8; CCC_SIZE];
|
||||
ccc.copy_from_slice(&response[0..CCC_SIZE]);
|
||||
let mut ccc = [0u8; Self::BYTE_SIZE];
|
||||
ccc.copy_from_slice(&response[0..Self::BYTE_SIZE]);
|
||||
Ok(Self(ccc))
|
||||
}
|
||||
|
||||
@@ -112,18 +115,8 @@ impl Ccc {
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Ccc {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "CCC({:?})", &self.0[..])
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Ccc {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
String::from_utf8(hex::encode(&self.0[..])).unwrap()
|
||||
)
|
||||
write!(f, "{}", str::from_utf8(&hex::encode(&self.0[..])).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
//! YubiKey Certificates
|
||||
//! X.509 certificate support.
|
||||
|
||||
// Adapted from yubico-piv-tool:
|
||||
// <https://github.com/Yubico/yubico-piv-tool/>
|
||||
|
||||
+31
-59
@@ -31,21 +31,13 @@
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
use crate::{Error, Result, YubiKey};
|
||||
use getrandom::getrandom;
|
||||
use std::fmt::{self, Debug, Display};
|
||||
use std::{
|
||||
convert::TryInto,
|
||||
fmt::{self, Debug, Display},
|
||||
str,
|
||||
};
|
||||
use subtle_encoding::hex;
|
||||
|
||||
/// CHUID size
|
||||
pub const CHUID_SIZE: usize = 59;
|
||||
|
||||
/// CARDID size
|
||||
pub const CARDID_SIZE: usize = 16;
|
||||
|
||||
/// FASC-N component size
|
||||
pub const FASCN_SIZE: usize = 25;
|
||||
|
||||
/// Expiration size
|
||||
pub const EXPIRATION_SIZE: usize = 8;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// FASC-N offset
|
||||
const CHUID_FASCN_OFFS: usize = 2;
|
||||
@@ -81,46 +73,38 @@ const CHUID_TMPL: &[u8] = &[
|
||||
0x30, 0x33, 0x30, 0x30, 0x31, 0x30, 0x31, 0x3e, 0x00, 0xfe, 0x00,
|
||||
];
|
||||
|
||||
/// Cardholder Unique Identifier (CHUID) Card UUID/GUID value
|
||||
/// Cardholder Unique Identifier (CHUID).
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Uuid(pub [u8; CARDID_SIZE]);
|
||||
|
||||
impl Uuid {
|
||||
/// Generate a random Cardholder Unique Identifier (CHUID) UUID
|
||||
pub fn generate() -> Result<Self> {
|
||||
let mut id = [0u8; CARDID_SIZE];
|
||||
getrandom(&mut id).map_err(|_| Error::RandomnessError)?;
|
||||
Ok(Self(id))
|
||||
}
|
||||
}
|
||||
|
||||
/// Cardholder Unique Identifier (CHUID)
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ChuId(pub [u8; CHUID_SIZE]);
|
||||
pub struct ChuId(pub [u8; Self::BYTE_SIZE]);
|
||||
|
||||
impl ChuId {
|
||||
/// CHUID size in bytes
|
||||
pub const BYTE_SIZE: usize = 59;
|
||||
|
||||
/// FASC-N component size
|
||||
pub const FASCN_SIZE: usize = 25;
|
||||
|
||||
/// Expiration size
|
||||
pub const EXPIRATION_SIZE: usize = 8;
|
||||
|
||||
/// Return FASC-N component of CHUID
|
||||
pub fn fascn(&self) -> Result<[u8; FASCN_SIZE]> {
|
||||
let mut fascn = [0u8; FASCN_SIZE];
|
||||
fascn.copy_from_slice(&self.0[CHUID_FASCN_OFFS..(CHUID_FASCN_OFFS + FASCN_SIZE)]);
|
||||
Ok(fascn)
|
||||
pub fn fascn(&self) -> [u8; Self::FASCN_SIZE] {
|
||||
self.0[CHUID_FASCN_OFFS..(CHUID_FASCN_OFFS + Self::FASCN_SIZE)]
|
||||
.try_into()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Return Card UUID/GUID component of CHUID
|
||||
pub fn uuid(&self) -> Result<[u8; CARDID_SIZE]> {
|
||||
let mut uuid = [0u8; CARDID_SIZE];
|
||||
uuid.copy_from_slice(&self.0[CHUID_GUID_OFFS..(CHUID_GUID_OFFS + CARDID_SIZE)]);
|
||||
Ok(uuid)
|
||||
pub fn uuid(&self) -> Uuid {
|
||||
Uuid::from_slice(&self.0[CHUID_GUID_OFFS..(CHUID_GUID_OFFS + 16)]).unwrap()
|
||||
}
|
||||
|
||||
/// Return expiration date component of CHUID
|
||||
// TODO(tarcieri): parse expiration?
|
||||
pub fn expiration(&self) -> Result<[u8; EXPIRATION_SIZE]> {
|
||||
let mut expiration = [0u8; EXPIRATION_SIZE];
|
||||
expiration.copy_from_slice(
|
||||
&self.0[CHUID_EXPIRATION_OFFS..(CHUID_EXPIRATION_OFFS + EXPIRATION_SIZE)],
|
||||
);
|
||||
Ok(expiration)
|
||||
pub fn expiration(&self) -> [u8; Self::EXPIRATION_SIZE] {
|
||||
self.0[CHUID_EXPIRATION_OFFS..(CHUID_EXPIRATION_OFFS + Self::EXPIRATION_SIZE)]
|
||||
.try_into()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Get Cardholder Unique Identifier (CHUID)
|
||||
@@ -132,17 +116,15 @@ impl ChuId {
|
||||
return Err(Error::GenericError);
|
||||
}
|
||||
|
||||
let mut chuid = [0u8; CHUID_SIZE];
|
||||
chuid.copy_from_slice(&response[0..CHUID_SIZE]);
|
||||
let retval = ChuId { 0: chuid };
|
||||
Ok(retval)
|
||||
Ok(ChuId(response[..Self::BYTE_SIZE].try_into().unwrap()))
|
||||
}
|
||||
|
||||
/// Set Cardholder Unique Identifier (CHUID)
|
||||
#[cfg(feature = "untested")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
pub fn set(&self, yubikey: &mut YubiKey) -> Result<()> {
|
||||
let mut buf = CHUID_TMPL.to_vec();
|
||||
buf[0..self.0.len()].copy_from_slice(&self.0);
|
||||
buf[..Self::BYTE_SIZE].copy_from_slice(&self.0);
|
||||
|
||||
let txn = yubikey.begin_transaction()?;
|
||||
txn.save_object(OBJ_CHUID, &buf)
|
||||
@@ -151,16 +133,6 @@ impl ChuId {
|
||||
|
||||
impl Display for ChuId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
String::from_utf8(hex::encode(&self.0[..])).unwrap()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ChuId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "CHUID({:?})", &self.0[..])
|
||||
write!(f, "{}", str::from_utf8(&hex::encode(&self.0[..])).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
+9
-11
@@ -46,28 +46,28 @@ use std::{
|
||||
const CB_ADMIN_TIMESTAMP: usize = 0x04;
|
||||
const PROTECTED_FLAGS_1_PUK_NOBLOCK: u8 = 0x01;
|
||||
|
||||
/// Config
|
||||
/// YubiKey configuration.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Config {
|
||||
/// Protected data available
|
||||
protected_data_available: bool,
|
||||
pub protected_data_available: bool,
|
||||
|
||||
/// PUK blocked
|
||||
puk_blocked: bool,
|
||||
pub puk_blocked: bool,
|
||||
|
||||
/// No block on upgrade
|
||||
puk_noblock_on_upgrade: bool,
|
||||
pub puk_noblock_on_upgrade: bool,
|
||||
|
||||
/// PIN last changed
|
||||
pin_last_changed: Option<SystemTime>,
|
||||
pub pin_last_changed: Option<SystemTime>,
|
||||
|
||||
/// MGM type
|
||||
mgm_type: MgmType,
|
||||
pub mgm_type: MgmType,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Get YubiKey config
|
||||
pub fn get(yubikey: &mut YubiKey) -> Result<Config> {
|
||||
/// Get YubiKey config.
|
||||
pub(crate) fn get(yubikey: &mut YubiKey) -> Result<Config> {
|
||||
let mut config = Config {
|
||||
protected_data_available: false,
|
||||
puk_blocked: false,
|
||||
@@ -129,9 +129,7 @@ impl Config {
|
||||
|
||||
if protected_data.get_item(TAG_PROTECTED_MGM).is_ok() {
|
||||
if config.mgm_type != MgmType::Protected {
|
||||
error!(
|
||||
"conflicting types of mgm key administration configured: protected MGM exists"
|
||||
);
|
||||
error!("conflicting MGM key types: protected MGM exists");
|
||||
}
|
||||
|
||||
// Always favor protected MGM
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ use std::fmt::{self, Display};
|
||||
/// Result type with [`Error`].
|
||||
pub type Result<T> = core::result::Result<T, Error>;
|
||||
|
||||
/// Kinds of errors
|
||||
/// Kinds of errors.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum Error {
|
||||
|
||||
+19
-8
@@ -350,10 +350,13 @@ pub const SLOTS: [SlotId; 24] = [
|
||||
pub enum AlgorithmId {
|
||||
/// 1024-bit RSA.
|
||||
Rsa1024,
|
||||
|
||||
/// 2048-bit RSA.
|
||||
Rsa2048,
|
||||
|
||||
/// ECDSA with the NIST P256 curve.
|
||||
EccP256,
|
||||
|
||||
/// ECDSA with the NIST P384 curve.
|
||||
EccP384,
|
||||
}
|
||||
@@ -390,6 +393,7 @@ impl AlgorithmId {
|
||||
}
|
||||
|
||||
#[cfg(feature = "untested")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
fn get_elem_len(self) -> usize {
|
||||
match self {
|
||||
AlgorithmId::Rsa1024 => 64,
|
||||
@@ -400,6 +404,7 @@ impl AlgorithmId {
|
||||
}
|
||||
|
||||
#[cfg(feature = "untested")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
fn get_param_tag(self) -> u8 {
|
||||
match self {
|
||||
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => 0x01,
|
||||
@@ -453,8 +458,7 @@ impl Key {
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate key
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
/// Generate new key.
|
||||
pub fn generate(
|
||||
yubikey: &mut YubiKey,
|
||||
slot: SlotId,
|
||||
@@ -473,7 +477,7 @@ pub fn generate(
|
||||
const SZ_ROCA_BLOCK_ADMIN: &str = "was blocked due to an administrator configuration setting.";
|
||||
const SZ_ROCA_DEFAULT: &str = "was permitted by default, but is not recommended. The default behavior will change in a future Yubico release.";
|
||||
|
||||
let setting_roca: settings::ConfigValue;
|
||||
let setting_roca: settings::SettingValue;
|
||||
|
||||
match algorithm {
|
||||
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
|
||||
@@ -481,17 +485,17 @@ pub fn generate(
|
||||
&& (yubikey.version.minor < 3
|
||||
|| yubikey.version.minor == 3 && (yubikey.version.patch < 5))
|
||||
{
|
||||
setting_roca = settings::ConfigValue::get(SZ_SETTING_ROCA, true);
|
||||
setting_roca = settings::SettingValue::get(SZ_SETTING_ROCA, true);
|
||||
|
||||
let psz_msg = match setting_roca.source {
|
||||
settings::Source::User => {
|
||||
settings::SettingSource::User => {
|
||||
if setting_roca.value {
|
||||
SZ_ROCA_ALLOW_USER
|
||||
} else {
|
||||
SZ_ROCA_BLOCK_USER
|
||||
}
|
||||
}
|
||||
settings::Source::Admin => {
|
||||
settings::SettingSource::Admin => {
|
||||
if setting_roca.value {
|
||||
SZ_ROCA_ALLOW_ADMIN
|
||||
} else {
|
||||
@@ -660,6 +664,7 @@ pub fn generate(
|
||||
}
|
||||
|
||||
#[cfg(feature = "untested")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
fn write_key(
|
||||
yubikey: &mut YubiKey,
|
||||
slot: SlotId,
|
||||
@@ -708,6 +713,7 @@ fn write_key(
|
||||
|
||||
/// The key data that makes up an RSA key.
|
||||
#[cfg(feature = "untested")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
pub struct RsaKeyData {
|
||||
/// The secret prime `p`.
|
||||
p: Buffer,
|
||||
@@ -769,6 +775,7 @@ impl RsaKeyData {
|
||||
///
|
||||
/// Errors if `algorithm` isn't `AlgorithmId::Rsa1024` or `AlgorithmId::Rsa2048`.
|
||||
#[cfg(feature = "untested")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
pub fn import_rsa_key(
|
||||
yubikey: &mut YubiKey,
|
||||
slot: SlotId,
|
||||
@@ -803,6 +810,7 @@ pub fn import_rsa_key(
|
||||
///
|
||||
/// Errors if `algorithm` isn't `AlgorithmId::EccP256` or ` AlgorithmId::EccP384`.
|
||||
#[cfg(feature = "untested")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
pub fn import_ecc_key(
|
||||
yubikey: &mut YubiKey,
|
||||
slot: SlotId,
|
||||
@@ -828,8 +836,10 @@ pub fn import_ecc_key(
|
||||
}
|
||||
|
||||
/// Generate an attestation certificate for a stored key.
|
||||
///
|
||||
/// <https://developers.yubico.com/PIV/Introduction/PIV_attestation.html>
|
||||
#[cfg(feature = "untested")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
pub fn attest(yubikey: &mut YubiKey, key: SlotId) -> Result<Buffer> {
|
||||
let templ = [0, Ins::Attest.code(), key.into(), 0];
|
||||
let txn = yubikey.begin_transaction()?;
|
||||
@@ -850,7 +860,7 @@ pub fn attest(yubikey: &mut YubiKey, key: SlotId) -> Result<Buffer> {
|
||||
Ok(Buffer::new(response.data().into()))
|
||||
}
|
||||
|
||||
/// Sign data using a PIV key
|
||||
/// Sign data using a PIV key.
|
||||
pub fn sign_data(
|
||||
yubikey: &mut YubiKey,
|
||||
raw_in: &[u8],
|
||||
@@ -863,8 +873,9 @@ pub fn sign_data(
|
||||
txn.authenticated_command(raw_in, algorithm, key, false)
|
||||
}
|
||||
|
||||
/// Decrypt data using a PIV key
|
||||
/// Decrypt data using a PIV key.
|
||||
#[cfg(feature = "untested")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
pub fn decrypt_data(
|
||||
yubikey: &mut YubiKey,
|
||||
input: &[u8],
|
||||
|
||||
+25
-23
@@ -34,14 +34,6 @@
|
||||
//!
|
||||
//! NOTE: RSASSA-PSS signatures and RSA-OAEP encryption may be supportable (TBD)
|
||||
//!
|
||||
//! ## Status
|
||||
//!
|
||||
//! This is a work-in-progress effort, and while much of the library-level
|
||||
//! code from upstream [yubico-piv-tool] has been translated into Rust
|
||||
//! presenting a safe interface, much of it is still untested.
|
||||
//!
|
||||
//! Please see the [project's README.md for a complete status][status].
|
||||
//!
|
||||
//! ## History
|
||||
//!
|
||||
//! This library is a Rust translation of the [yubico-piv-tool] utility by
|
||||
@@ -83,7 +75,6 @@
|
||||
//! [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/
|
||||
//! [status]: https://github.com/iqlusioninc/yubikey.rs#status
|
||||
//! [yubico-piv-tool]: https://github.com/Yubico/yubico-piv-tool/
|
||||
//! [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
|
||||
@@ -121,6 +112,7 @@
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![doc(
|
||||
html_logo_url = "https://raw.githubusercontent.com/iqlusioninc/yubikey.rs/main/img/logo.png",
|
||||
html_root_url = "https://docs.rs/yubikey/0.4.0-pre"
|
||||
@@ -129,34 +121,44 @@
|
||||
#![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)]
|
||||
|
||||
mod apdu;
|
||||
pub mod cccid;
|
||||
mod cccid;
|
||||
pub mod certificate;
|
||||
pub mod chuid;
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
mod chuid;
|
||||
mod config;
|
||||
mod error;
|
||||
pub mod key;
|
||||
mod metadata;
|
||||
pub mod mgm;
|
||||
mod mgm;
|
||||
#[cfg(feature = "untested")]
|
||||
pub mod mscmap;
|
||||
mod mscmap;
|
||||
#[cfg(feature = "untested")]
|
||||
pub mod msroots;
|
||||
pub mod policy;
|
||||
mod msroots;
|
||||
mod policy;
|
||||
pub mod readers;
|
||||
mod serialization;
|
||||
pub mod settings;
|
||||
mod settings;
|
||||
mod transaction;
|
||||
pub mod yubikey;
|
||||
mod yubikey;
|
||||
|
||||
pub use self::{
|
||||
pub use crate::{
|
||||
cccid::{CardId, Ccc},
|
||||
chuid::ChuId,
|
||||
config::Config,
|
||||
error::{Error, Result},
|
||||
key::Key,
|
||||
mgm::MgmKey,
|
||||
mgm::{MgmKey, MgmType},
|
||||
policy::{PinPolicy, TouchPolicy},
|
||||
readers::Readers,
|
||||
yubikey::{Serial, YubiKey},
|
||||
settings::{SettingSource, SettingValue},
|
||||
yubikey::{CachedPin, Serial, Version, YubiKey},
|
||||
};
|
||||
|
||||
/// Object identifiers
|
||||
#[cfg(feature = "untested")]
|
||||
pub use crate::{mscmap::MsContainer, msroots::MsRoots};
|
||||
|
||||
pub use uuid::Uuid;
|
||||
|
||||
/// Object identifiers: handles to particular objects stored on a YubiKey.
|
||||
pub type ObjectId = u32;
|
||||
|
||||
/// Buffer type (self-zeroizing byte vector)
|
||||
|
||||
+6
-1
@@ -73,7 +73,7 @@ pub(crate) const DES_LEN_3DES: usize = DES_LEN_DES * 3;
|
||||
#[cfg(feature = "untested")]
|
||||
const ITER_MGM_PBKDF2: u32 = 10000;
|
||||
|
||||
/// Management Key (MGM) key types (manual/derived/protected)
|
||||
/// Management Key (MGM) key types (manual/derived/protected).
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum MgmType {
|
||||
/// Manual
|
||||
@@ -132,6 +132,7 @@ impl MgmKey {
|
||||
|
||||
/// Get derived management key (MGM)
|
||||
#[cfg(feature = "untested")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
pub fn get_derived(yubikey: &mut YubiKey, pin: &[u8]) -> Result<Self> {
|
||||
let txn = yubikey.begin_transaction()?;
|
||||
|
||||
@@ -157,6 +158,7 @@ impl MgmKey {
|
||||
|
||||
/// Get protected management key (MGM)
|
||||
#[cfg(feature = "untested")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
pub fn get_protected(yubikey: &mut YubiKey) -> Result<Self> {
|
||||
let txn = yubikey.begin_transaction()?;
|
||||
|
||||
@@ -187,6 +189,7 @@ impl MgmKey {
|
||||
///
|
||||
/// This will wipe any metadata related to derived and PIN-protected management keys.
|
||||
#[cfg(feature = "untested")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
pub fn set_default(yubikey: &mut YubiKey) -> Result<()> {
|
||||
MgmKey::default().set_manual(yubikey, false)
|
||||
}
|
||||
@@ -198,6 +201,7 @@ impl MgmKey {
|
||||
///
|
||||
/// This will wipe any metadata related to derived and PIN-protected management keys.
|
||||
#[cfg(feature = "untested")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
pub fn set_manual(&self, yubikey: &mut YubiKey, require_touch: bool) -> Result<()> {
|
||||
let txn = yubikey.begin_transaction()?;
|
||||
|
||||
@@ -257,6 +261,7 @@ impl MgmKey {
|
||||
///
|
||||
/// This enables key management operations to be performed with access to the PIN.
|
||||
#[cfg(feature = "untested")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
pub fn set_protected(&self, yubikey: &mut YubiKey) -> Result<()> {
|
||||
let txn = yubikey.begin_transaction()?;
|
||||
|
||||
|
||||
+44
-40
@@ -1,7 +1,4 @@
|
||||
//! MS Container Map Records
|
||||
//!
|
||||
//! These appear(?) to be defined in Microsoft's Smart Card Minidriver Specification:
|
||||
//! <https://docs.microsoft.com/en-us/previous-versions/windows/hardware/design/dn631754(v=vs.85)>
|
||||
//! MS Container Map Records.
|
||||
|
||||
// Adapted from yubico-piv-tool:
|
||||
// <https://github.com/Yubico/yubico-piv-tool/>
|
||||
@@ -37,46 +34,53 @@ use crate::{key::SlotId, serialization::*, Error, Result, YubiKey, CB_OBJ_MAX};
|
||||
use log::error;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
|
||||
/// Container name length
|
||||
const CONTAINER_NAME_LEN: usize = 40;
|
||||
|
||||
/// Container record length: 27 = 80 + 1 + 1 + 2 + 1 + 1 + 1 + 20
|
||||
const CONTAINER_REC_LEN: usize = (2 * CONTAINER_NAME_LEN) + 27;
|
||||
|
||||
const OBJ_MSCMAP: u32 = 0x005f_ff10;
|
||||
|
||||
const TAG_MSCMAP: u8 = 0x81;
|
||||
|
||||
/// MS Container Map(?) Records
|
||||
/// MS Container Map records.
|
||||
///
|
||||
/// Defined in Microsoft's Smart Card Minidriver Specification:
|
||||
/// <https://docs.microsoft.com/en-us/previous-versions/windows/hardware/design/dn631754(v=vs.85)>
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Container {
|
||||
/// Container name
|
||||
pub name: [u16; CONTAINER_NAME_LEN],
|
||||
pub struct MsContainer {
|
||||
/// Container name.
|
||||
pub name: [u16; Self::NAME_LEN],
|
||||
|
||||
/// Card slot
|
||||
/// Card slot.
|
||||
pub slot: SlotId,
|
||||
|
||||
/// Key spec
|
||||
/// Key spec.
|
||||
pub key_spec: u8,
|
||||
|
||||
/// Key size in bits
|
||||
/// Key size in bits.
|
||||
pub key_size_bits: u16,
|
||||
|
||||
/// Flags
|
||||
/// Flags.
|
||||
pub flags: u8,
|
||||
|
||||
/// PIN ID
|
||||
/// PIN ID.
|
||||
pub pin_id: u8,
|
||||
|
||||
/// Associated ECHD(?) container (typo of "ecdh" perhaps?)
|
||||
/// Associated ECHD container.
|
||||
pub associated_echd_container: u8,
|
||||
|
||||
/// Cert fingerprint
|
||||
pub cert_fingerprint: [u8; 20],
|
||||
/// Cert fingerprint.
|
||||
pub cert_fingerprint: [u8; Self::CERT_FINGERPRINT_LEN],
|
||||
}
|
||||
|
||||
impl Container {
|
||||
/// Read MS Container Map records
|
||||
impl MsContainer {
|
||||
/// Container name length in UTF-16 chars.
|
||||
const NAME_LEN: usize = 40;
|
||||
|
||||
/// Container record length: 27 = 80 + 1 + 1 + 2 + 1 + 1 + 1 + 20
|
||||
const REC_LEN: usize = (2 * Self::NAME_LEN) + 27;
|
||||
|
||||
/// Length of a certificate fingerprint.
|
||||
const CERT_FINGERPRINT_LEN: usize = 20;
|
||||
|
||||
/// Read MS Container Map records.
|
||||
pub fn read_mscmap(yubikey: &mut YubiKey) -> Result<Vec<Self>> {
|
||||
let txn = yubikey.begin_transaction()?;
|
||||
let response = txn.fetch_object(OBJ_MSCMAP)?;
|
||||
@@ -95,8 +99,8 @@ impl Container {
|
||||
return Err(Error::InvalidObject);
|
||||
}
|
||||
|
||||
for chunk in tlv.value.chunks_exact(CONTAINER_REC_LEN) {
|
||||
containers.push(Container::new(chunk)?);
|
||||
for chunk in tlv.value.chunks_exact(Self::REC_LEN) {
|
||||
containers.push(MsContainer::new(chunk)?);
|
||||
}
|
||||
|
||||
Ok(containers)
|
||||
@@ -105,7 +109,7 @@ impl Container {
|
||||
/// Write MS Container Map records.
|
||||
pub fn write_mscmap(yubikey: &mut YubiKey, containers: &[Self]) -> Result<()> {
|
||||
let n_containers = containers.len();
|
||||
let data_len = n_containers * CONTAINER_REC_LEN;
|
||||
let data_len = n_containers * Self::REC_LEN;
|
||||
|
||||
let txn = yubikey.begin_transaction()?;
|
||||
|
||||
@@ -115,7 +119,7 @@ impl Container {
|
||||
|
||||
let mut buf = [0u8; CB_OBJ_MAX];
|
||||
let offset = Tlv::write_as(&mut buf, TAG_MSCMAP, data_len, |buf| {
|
||||
for (i, chunk) in buf.chunks_exact_mut(CONTAINER_REC_LEN).enumerate() {
|
||||
for (i, chunk) in buf.chunks_exact_mut(Self::REC_LEN).enumerate() {
|
||||
chunk.copy_from_slice(&containers[i].to_bytes());
|
||||
}
|
||||
})?;
|
||||
@@ -123,19 +127,19 @@ impl Container {
|
||||
txn.save_object(OBJ_MSCMAP, &buf[..offset])
|
||||
}
|
||||
|
||||
/// Parse a container record from a byte slice
|
||||
/// Parse a container record from a byte slice.
|
||||
pub fn new(bytes: &[u8]) -> Result<Self> {
|
||||
if bytes.len() != CONTAINER_REC_LEN {
|
||||
if bytes.len() != Self::REC_LEN {
|
||||
error!(
|
||||
"couldn't parse PIV container: expected {}-bytes, got {}-bytes",
|
||||
CONTAINER_REC_LEN,
|
||||
Self::REC_LEN,
|
||||
bytes.len()
|
||||
);
|
||||
return Err(Error::ParseError);
|
||||
}
|
||||
|
||||
let mut name = [0u16; CONTAINER_NAME_LEN];
|
||||
let name_bytes_len = CONTAINER_NAME_LEN * 2;
|
||||
let mut name = [0u16; Self::NAME_LEN];
|
||||
let name_bytes_len = Self::NAME_LEN * 2;
|
||||
|
||||
for (i, chunk) in bytes[..name_bytes_len].chunks_exact(2).enumerate() {
|
||||
name[i] = u16::from_le_bytes(chunk.try_into().unwrap());
|
||||
@@ -144,7 +148,7 @@ impl Container {
|
||||
let mut cert_fingerprint = [0u8; 20];
|
||||
cert_fingerprint.copy_from_slice(&bytes[(bytes.len() - 20)..]);
|
||||
|
||||
Ok(Container {
|
||||
Ok(Self {
|
||||
name,
|
||||
slot: bytes[name_bytes_len].try_into()?,
|
||||
key_spec: bytes[name_bytes_len + 1],
|
||||
@@ -160,17 +164,17 @@ impl Container {
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse the container name as a UTF-16 string
|
||||
/// Parse the container name as a UTF-16 string.
|
||||
pub fn parse_name(&self) -> Result<String> {
|
||||
String::from_utf16(&self.name).map_err(|_| Error::ParseError)
|
||||
}
|
||||
|
||||
/// Serialize a container record as a byte size
|
||||
pub fn to_bytes(&self) -> [u8; CONTAINER_REC_LEN] {
|
||||
/// Serialize a container record as a byte size.
|
||||
pub fn to_bytes(&self) -> [u8; Self::REC_LEN] {
|
||||
// TODO(tarcieri): use array instead of `Vec`
|
||||
let mut bytes = Vec::with_capacity(CONTAINER_REC_LEN);
|
||||
let mut bytes = Vec::with_capacity(Self::REC_LEN);
|
||||
|
||||
for i in 0..CONTAINER_NAME_LEN {
|
||||
for i in 0..Self::NAME_LEN {
|
||||
bytes.extend_from_slice(&self.name[i].to_le_bytes());
|
||||
}
|
||||
|
||||
@@ -185,7 +189,7 @@ impl Container {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a [u8]> for Container {
|
||||
impl<'a> TryFrom<&'a [u8]> for MsContainer {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(bytes: &'a [u8]) -> Result<Self> {
|
||||
|
||||
+10
-9
@@ -1,11 +1,4 @@
|
||||
//! `msroots`: PKCS#7 formatted certificate store for enterprise trusted roots.
|
||||
//!
|
||||
//! This `msroots` file contains a bag of certificates with empty content and
|
||||
//! an empty signature, allowing an enterprise root certificate truststore to
|
||||
//! be written to and read from a YubiKey.
|
||||
//!
|
||||
//! For more information, see:
|
||||
//! <https://docs.microsoft.com/en-us/windows-hardware/drivers/smartcard/developer-guidelines#-interoperability-with-msroots>
|
||||
//! PKCS#7 formatted certificate store for enterprise trusted roots.
|
||||
|
||||
// Adapted from yubico-piv-tool:
|
||||
// <https://github.com/Yubico/yubico-piv-tool/>
|
||||
@@ -53,7 +46,15 @@ const OBJ_MSROOTS5: u32 = 0x005f_ff15;
|
||||
const TAG_MSROOTS_END: u8 = 0x82;
|
||||
const TAG_MSROOTS_MID: u8 = 0x83;
|
||||
|
||||
/// `msroots` file: PKCS#7-formatted certificate store for enterprise trust roots
|
||||
/// PKCS#7-formatted certificate store for enterprise trust roots.
|
||||
///
|
||||
/// The `msroots` file contains a bag of certificates with empty content and
|
||||
/// an empty signature, allowing an enterprise root certificate truststore to
|
||||
/// be written to and read from a YubiKey.
|
||||
///
|
||||
/// For more information, see:
|
||||
/// <https://docs.microsoft.com/en-us/windows-hardware/drivers/smartcard/developer-guidelines#-interoperability-with-msroots>
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
pub struct MsRoots(Vec<u8>);
|
||||
|
||||
impl MsRoots {
|
||||
|
||||
+6
-4
@@ -3,8 +3,9 @@
|
||||
use crate::{serialization::Tlv, Result};
|
||||
|
||||
/// Specifies how often the PIN needs to be entered for access to the credential in a
|
||||
/// given slot. This policy must be set upon key generation or importation, and cannot be
|
||||
/// changed later.
|
||||
/// given slot.
|
||||
///
|
||||
/// This policy must be set when keys are generated or imported, and cannot be changed later.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum PinPolicy {
|
||||
/// Use the default PIN policy for the slot. See the slot's documentation for details.
|
||||
@@ -46,8 +47,9 @@ impl PinPolicy {
|
||||
}
|
||||
|
||||
/// Specifies under what conditions a physical touch on the metal contact is required, in
|
||||
/// addition to the [`PinPolicy`]. This policy must be set upon key generation or
|
||||
/// importation, and cannot be changed later.
|
||||
/// addition to the [`PinPolicy`].
|
||||
///
|
||||
/// This policy must be set when keys are generated or imported, and cannot be changed later.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum TouchPolicy {
|
||||
/// Use the default touch policy for the slot.
|
||||
|
||||
+7
-7
@@ -1,4 +1,4 @@
|
||||
//! Support for enumerating available readers
|
||||
//! Support for enumerating available PC/SC card readers.
|
||||
|
||||
use crate::{Result, YubiKey};
|
||||
use std::{
|
||||
@@ -32,7 +32,7 @@ impl Readers {
|
||||
})
|
||||
}
|
||||
|
||||
/// Iterate over the available readers
|
||||
/// Iterate over the available readers.
|
||||
pub fn iter(&mut self) -> Result<Iter<'_>> {
|
||||
let Self { ctx, reader_names } = self;
|
||||
|
||||
@@ -54,7 +54,7 @@ impl Readers {
|
||||
}
|
||||
}
|
||||
|
||||
/// An individual connected reader
|
||||
/// An individual connected PC/SC card reader.
|
||||
pub struct Reader<'ctx> {
|
||||
/// Name of this reader
|
||||
name: &'ctx CStr,
|
||||
@@ -64,24 +64,24 @@ pub struct Reader<'ctx> {
|
||||
}
|
||||
|
||||
impl<'ctx> Reader<'ctx> {
|
||||
/// Create a new reader from its name and context
|
||||
/// 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
|
||||
/// 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
|
||||
/// Open a connection to this reader, returning a `YubiKey` if successful.
|
||||
pub fn open(&self) -> Result<YubiKey> {
|
||||
self.try_into()
|
||||
}
|
||||
|
||||
/// Connect to this reader, returning its `pcsc::Card`
|
||||
/// Connect to this reader, returning its `pcsc::Card`.
|
||||
pub(crate) fn connect(&self) -> Result<pcsc::Card> {
|
||||
let ctx = self.ctx.lock().unwrap();
|
||||
Ok(ctx.connect(self.name, pcsc::ShareMode::Shared, pcsc::Protocols::T1)?)
|
||||
|
||||
+23
-18
@@ -40,43 +40,48 @@ use std::{
|
||||
io::{BufRead, BufReader},
|
||||
};
|
||||
|
||||
/// Source of how a setting was configured
|
||||
/// Source of how a setting was configured.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Source {
|
||||
/// User-specified setting
|
||||
pub enum SettingSource {
|
||||
/// User-specified setting: sourced via `YUBIKEY_PIV_*` environment vars.
|
||||
User,
|
||||
|
||||
/// Admin-specified setting
|
||||
/// Admin-specified setting: sourced via the `/etc/yubico/yubikeypiv.conf`
|
||||
/// configuration file.
|
||||
Admin,
|
||||
|
||||
/// Default setting
|
||||
/// Default setting.
|
||||
Default,
|
||||
}
|
||||
|
||||
impl Default for Source {
|
||||
impl Default for SettingSource {
|
||||
fn default() -> Self {
|
||||
Self::Default
|
||||
}
|
||||
}
|
||||
|
||||
/// Setting booleans
|
||||
/// Setting booleans: configuration values sourced from a file or the environment.
|
||||
///
|
||||
/// These can be configured globally in `/etc/yubico/yubikeypiv.conf` by a
|
||||
/// system administrator, or by the local user via `YUBIKEY_PIV_*` environment
|
||||
/// variables.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct ConfigValue {
|
||||
pub struct SettingValue {
|
||||
/// Boolean value
|
||||
pub value: bool,
|
||||
|
||||
/// Source of the configuration setting (user, admin, or default)
|
||||
pub source: Source,
|
||||
pub source: SettingSource,
|
||||
}
|
||||
|
||||
impl ConfigValue {
|
||||
/// Get a [`BoolValue`] value by name.
|
||||
impl SettingValue {
|
||||
/// Get a [`SettingValue`] value by name.
|
||||
pub fn get(key: &str, default: bool) -> Self {
|
||||
Self::from_file(key)
|
||||
.or_else(|| Self::from_env(key))
|
||||
.unwrap_or(Self {
|
||||
value: default,
|
||||
source: Source::Default,
|
||||
source: SettingSource::Default,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -104,8 +109,8 @@ impl ConfigValue {
|
||||
};
|
||||
|
||||
if name == key {
|
||||
return Some(ConfigValue {
|
||||
source: Source::Admin,
|
||||
return Some(SettingValue {
|
||||
source: SettingSource::Admin,
|
||||
value: value == "1" || value == "true",
|
||||
});
|
||||
}
|
||||
@@ -119,18 +124,18 @@ impl ConfigValue {
|
||||
fn from_env(key: &str) -> Option<Self> {
|
||||
env::var(format!("YUBIKEY_PIV_{}", key))
|
||||
.ok()
|
||||
.map(|value| ConfigValue {
|
||||
source: Source::User,
|
||||
.map(|value| SettingValue {
|
||||
source: SettingSource::User,
|
||||
value: value == "1" || value == "true",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ConfigValue {
|
||||
impl Default for SettingValue {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
value: false,
|
||||
source: Source::default(),
|
||||
source: SettingSource::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+37
-26
@@ -70,10 +70,10 @@ pub(crate) const KEY_CARDMGM: u8 = 0x9b;
|
||||
|
||||
const TAG_DYN_AUTH: u8 = 0x7c;
|
||||
|
||||
/// Cached YubiKey PIN
|
||||
/// Cached YubiKey PIN.
|
||||
pub type CachedPin = secrecy::SecretVec<u8>;
|
||||
|
||||
/// YubiKey Serial Number
|
||||
/// YubiKey serial number.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct Serial(pub u32);
|
||||
|
||||
@@ -103,7 +103,7 @@ impl Display for Serial {
|
||||
}
|
||||
}
|
||||
|
||||
/// YubiKey Version
|
||||
/// YubiKey version.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub struct Version {
|
||||
/// Major version component
|
||||
@@ -133,8 +133,7 @@ impl Display for Version {
|
||||
}
|
||||
}
|
||||
|
||||
/// YubiKey Device: this is the primary API for opening a session and
|
||||
/// performing various operations.
|
||||
/// YubiKey device: primary API for opening a session and performing various operations.
|
||||
///
|
||||
/// Almost all functionality in this library will require an open session
|
||||
/// with a YubiKey which is represented by this type.
|
||||
@@ -203,8 +202,9 @@ impl YubiKey {
|
||||
Err(Error::NotFound)
|
||||
}
|
||||
|
||||
/// Reconnect to a YubiKey
|
||||
/// Reconnect to a YubiKey.
|
||||
#[cfg(feature = "untested")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
pub fn reconnect(&mut self) -> Result<()> {
|
||||
info!("trying to reconnect to current reader");
|
||||
|
||||
@@ -235,7 +235,7 @@ impl YubiKey {
|
||||
Transaction::new(&mut self.card)
|
||||
}
|
||||
|
||||
/// Get the name of the associated PC/SC card reader
|
||||
/// Get the name of the associated PC/SC card reader.
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
@@ -259,12 +259,12 @@ impl YubiKey {
|
||||
Config::get(self)
|
||||
}
|
||||
|
||||
/// Get CHUID
|
||||
/// Get Cardholder Unique Identifier (CHUID).
|
||||
pub fn chuid(&mut self) -> Result<ChuId> {
|
||||
ChuId::get(self)
|
||||
}
|
||||
|
||||
/// Get CCCID
|
||||
/// Get Cardholder Capability Container (CCC) Identifier.
|
||||
pub fn cccid(&mut self) -> Result<Ccc> {
|
||||
Ccc::get(self)
|
||||
}
|
||||
@@ -325,6 +325,7 @@ impl YubiKey {
|
||||
|
||||
/// Deauthenticate
|
||||
#[cfg(feature = "untested")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
pub fn deauthenticate(&mut self) -> Result<()> {
|
||||
let txn = self.begin_transaction()?;
|
||||
|
||||
@@ -359,7 +360,7 @@ impl YubiKey {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the number of PIN retries
|
||||
/// Get the number of PIN retries.
|
||||
pub fn get_pin_retries(&mut self) -> Result<u8> {
|
||||
let txn = self.begin_transaction()?;
|
||||
|
||||
@@ -376,8 +377,9 @@ impl YubiKey {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the number of PIN retries
|
||||
/// Set the number of PIN retries.
|
||||
#[cfg(feature = "untested")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
pub fn set_pin_retries(&mut self, pin_tries: u8, puk_tries: u8) -> Result<()> {
|
||||
// Special case: if either retry count is 0, it's a successful no-op
|
||||
if pin_tries == 0 || puk_tries == 0 {
|
||||
@@ -400,8 +402,9 @@ impl YubiKey {
|
||||
|
||||
/// Change the Personal Identification Number (PIN).
|
||||
///
|
||||
/// The default PIN code is 123456
|
||||
/// The default PIN code is `123456`.
|
||||
#[cfg(feature = "untested")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
pub fn change_pin(&mut self, current_pin: &[u8], new_pin: &[u8]) -> Result<()> {
|
||||
{
|
||||
let txn = self.begin_transaction()?;
|
||||
@@ -415,8 +418,9 @@ impl YubiKey {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set PIN last changed
|
||||
/// Set PIN last changed.
|
||||
#[cfg(feature = "untested")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
pub fn set_pin_last_changed(yubikey: &mut YubiKey) -> Result<()> {
|
||||
let txn = yubikey.begin_transaction()?;
|
||||
|
||||
@@ -450,15 +454,17 @@ impl YubiKey {
|
||||
///
|
||||
/// 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")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
pub fn change_puk(&mut self, current_puk: &[u8], new_puk: &[u8]) -> Result<()> {
|
||||
let txn = self.begin_transaction()?;
|
||||
txn.change_ref(ChangeRefAction::ChangePuk, 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")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
pub fn block_puk(yubikey: &mut YubiKey) -> Result<()> {
|
||||
let mut puk = [0x30, 0x42, 0x41, 0x44, 0x46, 0x30, 0x30, 0x44];
|
||||
let mut tries_remaining: i32 = -1;
|
||||
@@ -488,9 +494,9 @@ impl YubiKey {
|
||||
}
|
||||
|
||||
// Attempt to set the "PUK blocked" flag in admin data.
|
||||
|
||||
let mut admin_data = if let Ok(admin_data) = AdminData::read(&txn) {
|
||||
if let Ok(item) = admin_data.get_item(TAG_ADMIN_FLAGS_1) {
|
||||
let mut admin_data = AdminData::read(&txn)
|
||||
.map(|data| {
|
||||
if let Ok(item) = data.get_item(TAG_ADMIN_FLAGS_1) {
|
||||
if item.len() == flags.len() {
|
||||
flags.copy_from_slice(item)
|
||||
} else {
|
||||
@@ -502,10 +508,9 @@ impl YubiKey {
|
||||
}
|
||||
}
|
||||
|
||||
admin_data
|
||||
} else {
|
||||
AdminData::default()
|
||||
};
|
||||
data
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
flags[0] |= ADMIN_FLAGS_1_PUK_BLOCKED;
|
||||
|
||||
@@ -523,27 +528,31 @@ impl YubiKey {
|
||||
/// Unblock a Personal Identification Number (PIN) using a previously
|
||||
/// configured PIN Unblocking Key (PUK).
|
||||
#[cfg(feature = "untested")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
pub fn unblock_pin(&mut self, puk: &[u8], new_pin: &[u8]) -> Result<()> {
|
||||
let txn = self.begin_transaction()?;
|
||||
txn.change_ref(ChangeRefAction::UnblockPin, puk, new_pin)
|
||||
}
|
||||
|
||||
/// Fetch an object from the YubiKey
|
||||
/// Fetch an object from the YubiKey.
|
||||
#[cfg(feature = "untested")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
pub fn fetch_object(&mut self, object_id: ObjectId) -> Result<Buffer> {
|
||||
let txn = self.begin_transaction()?;
|
||||
txn.fetch_object(object_id)
|
||||
}
|
||||
|
||||
/// Save an object
|
||||
/// Save an object.
|
||||
#[cfg(feature = "untested")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
pub fn save_object(&mut self, object_id: ObjectId, indata: &mut [u8]) -> Result<()> {
|
||||
let txn = self.begin_transaction()?;
|
||||
txn.save_object(object_id, indata)
|
||||
}
|
||||
|
||||
/// Get an auth challenge
|
||||
/// Get an auth challenge.
|
||||
#[cfg(feature = "untested")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
pub fn get_auth_challenge(&mut self) -> Result<[u8; 8]> {
|
||||
let txn = self.begin_transaction()?;
|
||||
|
||||
@@ -559,8 +568,9 @@ impl YubiKey {
|
||||
Ok(response.data()[4..12].try_into().unwrap())
|
||||
}
|
||||
|
||||
/// Verify an auth response
|
||||
/// Verify an auth response.
|
||||
#[cfg(feature = "untested")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
pub fn verify_auth_response(&mut self, response: [u8; 8]) -> Result<()> {
|
||||
let mut data = [0u8; 12];
|
||||
data[0] = 0x7c;
|
||||
@@ -591,6 +601,7 @@ impl YubiKey {
|
||||
///
|
||||
/// The reset function is only available when both pins are blocked.
|
||||
#[cfg(feature = "untested")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
||||
pub fn reset_device(&mut self) -> Result<()> {
|
||||
let templ = [0, Ins::Reset.code(), 0, 0];
|
||||
let txn = self.begin_transaction()?;
|
||||
|
||||
@@ -17,8 +17,7 @@ use x509::RelativeDistinguishedName;
|
||||
use yubikey::{
|
||||
certificate::{Certificate, PublicKeyInfo},
|
||||
key::{self, AlgorithmId, Key, RetiredSlotId, SlotId},
|
||||
policy::{PinPolicy, TouchPolicy},
|
||||
Error, MgmKey, YubiKey,
|
||||
Error, MgmKey, PinPolicy, TouchPolicy, YubiKey,
|
||||
};
|
||||
|
||||
lazy_static! {
|
||||
|
||||
Reference in New Issue
Block a user