Merge pull request #17 from tarcieri/pcsc

Rewrite translated code to use the `pcsc` crate
This commit is contained in:
Tony Arcieri
2019-11-25 09:17:51 -08:00
committed by GitHub
23 changed files with 3510 additions and 4126 deletions
+37 -3
View File
@@ -21,20 +21,51 @@ jobs:
toolchain: stable
override: true
- name: Install libpcsclite-dev
run: sudo apt-get install libpcsclite-dev
- name: Run cargo check
uses: actions-rs/cargo@v1
with:
command: check
# Need to install `libpscslite-dev` on Linux
linux:
name: Test Suite
strategy:
matrix:
toolchain:
- 1.39.0
- stable
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.toolchain }}
override: true
- name: Install libpcsclite-dev
run: sudo apt-get install libpcsclite-dev
- name: Run cargo test
uses: actions-rs/cargo@v1
env:
RUSTFLAGS: -D warnings
with:
command: test
args: --release
test:
name: Test Suite
strategy:
matrix:
platform:
- ubuntu-latest
- macos-latest
# TODO: support Windows after eliminating C legacy
# - windows-latest
- windows-latest
toolchain:
- 1.39.0
- stable
@@ -92,6 +123,9 @@ jobs:
toolchain: stable
override: true
- name: Install libpcsclite-dev
run: sudo apt-get install libpcsclite-dev
- name: Install clippy
run: rustup component add clippy
+2 -1
View File
@@ -20,8 +20,9 @@ keywords = ["ccid", "ecdsa", "rsa", "piv", "yubikey"]
des = "0.3"
getrandom = "0.1"
hmac = "0.7"
libc = "0.2"
log = "0.4"
pbkdf2 = "0.3"
pcsc = "2"
sha-1 = "0.8"
subtle = "2"
zeroize = "1"
+18 -14
View File
@@ -22,6 +22,9 @@ to store a number of RSA (2048/1024) and ECC (NIST P-256/P-384) private keys
with configurable access control policies. Both the signing (RSASSA/ECDSA) and
encryption (PKCS#1v1.5/ECIES) use cases are supported for either key type.
See [Yubico's guide to PIV-enabled YubiKeys][2] for more information
on which devices support PIV and the available functionality.
If you've been wanting to use Rust to sign and/or encrypt stuff using a
private key generated and stored on a Yubikey (with option PIN-based access),
this is the crate you've been after!
@@ -32,12 +35,12 @@ But it might be close?
## History
This library is a Rust translation of the [yubico-piv-tool][2] utility by
This library is a Rust translation of the [yubico-piv-tool][3] utility by
Yubico, which was originally written in C. It was mechanically translated
from C into Rust using [Corrode][3], and then subsequently heavily
from C into Rust using [Corrode][4], and then subsequently heavily
refactored into safer, more idiomatic Rust§.
Note that while this project started as a fork of a [Yubico][4] project,
Note that while this project started as a fork of a [Yubico][5] project,
this fork is **NOT** an official Yubico project and is in no way supported or
endorsed by Yubico.
@@ -59,15 +62,15 @@ USE AT YOUR OWN RISK!
## Code of Conduct
We abide by the [Contributor Covenant][5] and ask that you do as well.
We abide by the [Contributor Covenant][6] and ask that you do as well.
For more information, please see [CODE_OF_CONDUCT.md][6].
For more information, please see [CODE_OF_CONDUCT.md][7].
## License
**yubikey-piv.rs** is a fork of and originally a mechanical translation from
Yubico's [`yubico-piv-tool`][2], a C library/CLI program. The original library
was licensed under a [2-Clause BSD License][5], which this library inherits
Yubico's [`yubico-piv-tool`][3], a C library/CLI program. The original library
was licensed under a [2-Clause BSD License][8], which this library inherits
as a derived work.
Copyright (c) 2014-2019 Yubico AB, Tony Arcieri
@@ -101,7 +104,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Unless you explicitly state otherwise, any contribution intentionally
submitted for inclusion in the work by you shall be licensed under the
[2-Clause BSD License][5] as shown above, without any additional terms
[2-Clause BSD License][8] as shown above, without any additional terms
or conditions.
[//]: # (badges)
@@ -121,9 +124,10 @@ or conditions.
[//]: # (general links)
[1]: https://piv.idmanagement.gov/
[2]: https://github.com/Yubico/yubico-piv-tool/
[3]: https://github.com/jameysharp/corrode
[4]: https://www.yubico.com/
[5]: https://contributor-covenant.org/
[6]: https://github.com/tarcieri/yubikey-piv.rs/blob/develop/CODE_OF_CONDUCT.md
[7]: https://opensource.org/licenses/BSD-2-Clause
[2]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
[3]: https://github.com/Yubico/yubico-piv-tool/
[4]: https://github.com/jameysharp/corrode
[5]: https://www.yubico.com/
[6]: https://contributor-covenant.org/
[7]: https://github.com/tarcieri/yubikey-piv.rs/blob/develop/CODE_OF_CONDUCT.md
[8]: https://opensource.org/licenses/BSD-2-Clause
+8 -4
View File
@@ -1,20 +1,18 @@
//! Application Protocol Data Unit (APDU)
use crate::{error::Error, response::Response, transaction::Transaction, Buffer};
use std::fmt::{self, Debug};
use zeroize::{Zeroize, Zeroizing};
/// Size of a serialized APDU (5 byte header + 255 bytes data)
pub const APDU_SIZE: usize = 260;
/// Buffer type (self-zeroizing byte vector)
pub(crate) type Buffer = Zeroizing<Vec<u8>>;
/// Application Protocol Data Unit (APDU).
///
/// These messages are packets used to communicate with the YubiKey using the
/// Chip Card Interface Device (CCID) protocol.
#[derive(Clone)]
pub struct APDU {
pub(crate) struct APDU {
/// Instruction class - indicates the type of command, e.g. interindustry or proprietary
cla: u8,
@@ -81,6 +79,12 @@ impl APDU {
self
}
/// Transmit this APDU using the given card transaction
pub fn transmit(&self, txn: &Transaction<'_>, recv_len: usize) -> Result<Response, Error> {
let response_bytes = txn.transmit(&self.to_bytes(), recv_len)?;
Ok(Response::from_bytes(response_bytes))
}
/// Consume this APDU and return a self-zeroizing buffer
pub fn to_bytes(&self) -> Buffer {
let mut bytes = Vec::with_capacity(APDU_SIZE);
+85
View File
@@ -0,0 +1,85 @@
//! Cardholder Capability Container (CCC) ID Support
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
//
// Copyright (c) 2014-2016 Yubico AB
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, error::Error, yubikey::YubiKey};
use getrandom::getrandom;
/// Cardholder Capability Container (CCC) Template
///
/// f0: Card Identifier
///
/// - 0xa000000116 == GSC-IS RID
/// - 0xff == Manufacturer ID (dummy)
/// - 0x02 == Card type (javaCard)
/// - next 14 bytes: card ID
const CCC_TMPL: &[u8] = &[
0xf0, 0x15, 0xa0, 0x00, 0x00, 0x01, 0x16, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1, 0x01, 0x21, 0xf2, 0x01, 0x21, 0xf3, 0x00, 0xf4,
0x01, 0x00, 0xf5, 0x01, 0x10, 0xf6, 0x00, 0xf7, 0x00, 0xfa, 0x00, 0xfb, 0x00, 0xfc, 0x00, 0xfd,
0x00, 0xfe, 0x00,
];
/// Cardholder Capability Container (CCC) Identifier
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct CCCID([u8; YKPIV_CCCID_SIZE]);
impl CCCID {
/// Generate a random CCCID
pub fn generate() -> Result<Self, Error> {
let mut id = [0u8; YKPIV_CCCID_SIZE];
getrandom(&mut id).map_err(|_| Error::RandomnessError)?;
Ok(CCCID(id))
}
/// Get Cardholder Capability Container (CCC) ID
pub fn get(yubikey: &mut YubiKey) -> Result<Self, Error> {
let txn = yubikey.begin_transaction()?;
let response = txn.fetch_object(YKPIV_OBJ_CAPABILITY)?;
if response.len() != CCC_TMPL.len() {
return Err(Error::GenericError);
}
let mut cccid = [0u8; YKPIV_CCCID_SIZE];
cccid.copy_from_slice(&response[CCC_ID_OFFS..(CCC_ID_OFFS + YKPIV_CCCID_SIZE)]);
Ok(CCCID(cccid))
}
/// Get Cardholder Capability Container (CCC) ID
pub fn set(&self, yubikey: &mut YubiKey) -> Result<(), Error> {
let mut buf = CCC_TMPL.to_vec();
buf[CCC_ID_OFFS..(CCC_ID_OFFS + self.0.len())].copy_from_slice(&self.0);
let txn = yubikey.begin_transaction()?;
txn.save_object(YKPIV_OBJ_CAPABILITY, &buf)
}
}
+186
View File
@@ -0,0 +1,186 @@
//! YubiKey Certificates
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
//
// Copyright (c) 2014-2016 Yubico AB
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{
consts::*,
error::Error,
key::{self, SlotId},
serialization::*,
transaction::Transaction,
yubikey::YubiKey,
Buffer,
};
use log::error;
use std::ptr;
use zeroize::Zeroizing;
/// Certificates
#[derive(Clone, Debug)]
pub struct Certificate(Buffer);
impl Certificate {
/// Read a certificate from the given slot in the YubiKey
pub fn read(yubikey: &mut YubiKey, slot: SlotId) -> Result<Self, Error> {
let txn = yubikey.begin_transaction()?;
let buf = read_certificate(&txn, slot)?;
if buf.is_empty() {
return Err(Error::InvalidObject);
}
Ok(Certificate(buf))
}
/// Write this certificate into the YubiKey in the given slot
pub fn write(&self, yubikey: &mut YubiKey, slot: SlotId, certinfo: u8) -> Result<(), Error> {
let max_size = yubikey.obj_size_max();
let txn = yubikey.begin_transaction()?;
write_certificate(&txn, slot, Some(&self.0), certinfo, max_size)
}
/// Delete a certificate located at the given slot of the given YubiKey
pub fn delete(yubikey: &mut YubiKey, slot: SlotId) -> Result<(), Error> {
let max_size = yubikey.obj_size_max();
let txn = yubikey.begin_transaction()?;
write_certificate(&txn, slot, None, 0, max_size)
}
/// Initialize a local certificate struct from the given bytebuffer
pub fn new(cert: impl Into<Buffer>) -> Result<Self, Error> {
let cert = cert.into();
if cert.is_empty() {
error!("certificate cannot be empty");
return Err(Error::SizeError);
}
Ok(Certificate(cert))
}
/// Extract the inner buffer
pub fn into_buffer(self) -> Buffer {
self.0
}
}
impl AsRef<[u8]> for Certificate {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
/// Read certificate
pub(crate) fn read_certificate(txn: &Transaction<'_>, slot: SlotId) -> Result<Buffer, Error> {
let mut len: usize = 0;
let object_id = key::slot_object(slot)?;
let mut buf = match txn.fetch_object(object_id) {
Ok(b) => b,
Err(_) => {
// TODO(tarcieri): is this really ok?
return Ok(Zeroizing::new(vec![]));
}
};
if buf.len() < CB_OBJ_TAG_MIN {
// TODO(tarcieri): is this really ok?
return Ok(Zeroizing::new(vec![]));
}
if buf[0] == TAG_CERT {
let offset = 1 + get_length(&buf[1..], &mut len);
if len > buf.len() - offset {
// TODO(tarcieri): is this really ok?
return Ok(Zeroizing::new(vec![]));
}
unsafe {
ptr::copy(buf.as_ptr().add(offset), buf.as_mut_ptr(), len);
}
buf.truncate(len);
}
Ok(buf)
}
/// Write certificate
pub(crate) fn write_certificate(
txn: &Transaction<'_>,
slot: SlotId,
data: Option<&[u8]>,
certinfo: u8,
max_size: usize,
) -> Result<(), Error> {
let mut buf = [0u8; CB_OBJ_MAX];
let mut offset = 0;
let object_id = key::slot_object(slot)?;
if data.is_none() {
return txn.save_object(object_id, &[]);
}
let data = data.unwrap();
let mut req_len = 1 /* cert tag */ + 3 /* compression tag + data*/ + 2 /* lrc */;
req_len += set_length(&mut buf, data.len());
req_len += data.len();
if req_len < data.len() || req_len > max_size {
return Err(Error::SizeError);
}
buf[offset] = TAG_CERT;
offset += 1;
offset += set_length(&mut buf[offset..], data.len());
buf[offset..(offset + data.len())].copy_from_slice(&data);
offset += data.len();
// write compression info and LRC trailer
buf[offset] = TAG_CERT_COMPRESS;
buf[offset + 1] = 0x01;
buf[offset + 2] = if certinfo == YKPIV_CERTINFO_GZIP {
0x01
} else {
0x00
};
buf[offset + 3] = TAG_CERT_LRC;
buf[offset + 4] = 00;
offset += 5;
txn.save_object(object_id, &buf[..offset])
}
+92
View File
@@ -0,0 +1,92 @@
//! Cardholder Unique Identifier (CHUID) Support
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
//
// Copyright (c) 2014-2016 Yubico AB
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, error::Error, yubikey::YubiKey};
use getrandom::getrandom;
/// Cardholder Unique Identifier (CHUID) Template
///
/// Format defined in SP-800-73-4, Appendix A, Table 9
///
/// FASC-N containing S9999F9999F999999F0F1F0000000000300001E encoded in
/// 4-bit BCD with 1 bit parity. run through the tools/fasc.pl script to get
/// bytes. This CHUID has an expiry of 2030-01-01.
///
/// Defined fields:
///
/// - 0x30: FASC-N (hard-coded)
/// - 0x34: Card UUID / GUID (settable)
/// - 0x35: Exp. Date (hard-coded)
/// - 0x3e: Signature (hard-coded, empty)
/// - 0xfe: Error Detection Code (hard-coded)
const CHUID_TMPL: &[u8] = &[
0x30, 0x19, 0xd4, 0xe7, 0x39, 0xda, 0x73, 0x9c, 0xed, 0x39, 0xce, 0x73, 0x9d, 0x83, 0x68, 0x58,
0x21, 0x08, 0x42, 0x10, 0x84, 0x21, 0xc8, 0x42, 0x10, 0xc3, 0xeb, 0x34, 0x10, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x08, 0x32,
0x30, 0x33, 0x30, 0x30, 0x31, 0x30, 0x31, 0x3e, 0x00, 0xfe, 0x00,
];
/// Cardholder Unique Identifier (CHUID)
#[derive(Copy, Clone, Debug)]
pub struct CHUID([u8; YKPIV_CARDID_SIZE]);
impl CHUID {
/// Generate a random Cardholder Unique Identifier (CHUID)
pub fn generate() -> Result<Self, Error> {
let mut id = [0u8; YKPIV_CARDID_SIZE];
getrandom(&mut id).map_err(|_| Error::RandomnessError)?;
Ok(CHUID(id))
}
/// Get Cardholder Unique Identifier (CHUID)
pub fn get(yubikey: &mut YubiKey) -> Result<Self, Error> {
let txn = yubikey.begin_transaction()?;
let response = txn.fetch_object(YKPIV_OBJ_CHUID)?;
if response.len() != CHUID_TMPL.len() {
return Err(Error::GenericError);
}
let mut cardid = [0u8; YKPIV_CARDID_SIZE];
cardid.copy_from_slice(&response[CHUID_GUID_OFFS..(CHUID_GUID_OFFS + YKPIV_CARDID_SIZE)]);
Ok(CHUID(cardid))
}
/// Set Cardholder Unique Identifier (CHUID)
pub fn set(&self, yubikey: &mut YubiKey) -> Result<(), Error> {
let mut buf = CHUID_TMPL.to_vec();
buf[CHUID_GUID_OFFS..(CHUID_GUID_OFFS + self.0.len())].copy_from_slice(&self.0);
let txn = yubikey.begin_transaction()?;
txn.save_object(YKPIV_OBJ_CHUID, &buf)
}
}
+127
View File
@@ -0,0 +1,127 @@
//! YubiKey Configuration Values
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
//
// Copyright (c) 2014-2016 Yubico AB
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, error::Error, metadata, mgm::MgmType, yubikey::YubiKey};
use log::error;
use std::convert::TryInto;
/// Config
#[derive(Copy, Clone)]
pub struct Config {
/// Protected data available
protected_data_available: bool,
/// PUK blocked
puk_blocked: bool,
/// No block on upgrade
puk_noblock_on_upgrade: bool,
/// PIN last changed
pin_last_changed: u32,
/// MGM type
mgm_type: MgmType,
}
impl Config {
/// Get YubiKey config
pub fn get(yubikey: &mut YubiKey) -> Result<Config, Error> {
let mut config = Config {
protected_data_available: false,
puk_blocked: false,
puk_noblock_on_upgrade: false,
pin_last_changed: 0,
mgm_type: MgmType::Manual,
};
let txn = yubikey.begin_transaction()?;
if let Ok(data) = metadata::read(&txn, TAG_ADMIN) {
if let Ok(item) = metadata::get_item(&data, TAG_ADMIN_FLAGS_1) {
if item.is_empty() {
error!("empty response for admin flags metadata item! ignoring");
} else {
if item[0] & ADMIN_FLAGS_1_PUK_BLOCKED != 0 {
config.puk_blocked = true;
}
if item[0] & ADMIN_FLAGS_1_PROTECTED_MGM != 0 {
config.mgm_type = MgmType::Protected;
}
}
}
if metadata::get_item(&data, TAG_ADMIN_SALT).is_ok() {
if config.mgm_type != MgmType::Manual {
error!("conflicting types of MGM key administration configured");
} else {
config.mgm_type = MgmType::Derived;
}
}
if let Ok(item) = metadata::get_item(&data, TAG_ADMIN_TIMESTAMP) {
if item.len() != CB_ADMIN_TIMESTAMP {
error!("pin timestamp in admin metadata is an invalid size");
} else {
// TODO(tarcieri): double check this is little endian
config.pin_last_changed = u32::from_le_bytes(item.try_into().unwrap());
}
}
}
if let Ok(data) = metadata::read(&txn, TAG_PROTECTED) {
config.protected_data_available = true;
if let Ok(item) = metadata::get_item(&data, TAG_PROTECTED_FLAGS_1) {
if item.is_empty() {
error!("empty response for protected flags metadata item! ignoring");
} else if item[0] & PROTECTED_FLAGS_1_PUK_NOBLOCK != 0 {
config.puk_noblock_on_upgrade = true;
}
}
if metadata::get_item(&data, TAG_PROTECTED_MGM).is_ok() {
if config.mgm_type != MgmType::Protected {
error!(
"conflicting types of mgm key administration configured: protected MGM exists"
);
}
// Always favor protected MGM
config.mgm_type = MgmType::Protected;
}
}
Ok(config)
}
}
+19 -5
View File
@@ -42,14 +42,26 @@ pub const ADMIN_FLAGS_1_PROTECTED_MGM: u8 = 0x02;
pub const CB_ADMIN_TIMESTAMP: usize = 0x04;
pub const CB_ADMIN_SALT: usize = 16;
pub const CB_OBJ_MAX: usize = 3063;
pub const CB_ATR_MAX: usize = 33;
pub const CB_BUF_MAX_NEO: usize = 2048;
pub const CB_BUF_MAX_YK4: usize = 3072;
pub const CB_BUF_MAX: usize = CB_BUF_MAX_YK4;
pub const CB_ECC_POINTP256: usize = 65;
pub const CB_ECC_POINTP384: usize = 97;
pub const CB_OBJ_MAX_YK4: usize = CB_BUF_MAX_YK4 - 9;
pub const CB_OBJ_MAX: usize = CB_OBJ_MAX_YK4;
pub const CB_OBJ_MAX_NEO: usize = CB_BUF_MAX_NEO - 9;
pub const CB_OBJ_TAG_MIN: usize = 2; // 1 byte tag + 1 byte len
pub const CB_OBJ_TAG_MAX: usize = (CB_OBJ_TAG_MIN + 2); // 1 byte tag + 3 bytes len
pub const CB_PAGE: usize = 4096;
pub const CB_PIN_MAX: usize = 8;
pub const CB_ECC_POINTP256: usize = 65;
pub const CB_ECC_POINTP384: usize = 97;
pub const CCC_ID_OFFS: usize = 9;
pub const CHUID_GUID_OFFS: usize = 29;
@@ -57,6 +69,9 @@ pub const CHREF_ACT_CHANGE_PIN: i32 = 0;
pub const CHREF_ACT_UNBLOCK_PIN: i32 = 1;
pub const CHREF_ACT_CHANGE_PUK: i32 = 2;
pub const CONTAINER_NAME_LEN: usize = 40;
pub const CONTAINER_REC_LEN: usize = (2 * CONTAINER_NAME_LEN) + 27; // 80 + 1 + 1 + 2 + 1 + 1 + 1 + 20
pub const DES_TYPE_3DES: u8 = 1;
pub const DES_LEN_DES: usize = 8;
@@ -132,6 +147,7 @@ pub const YKPIV_INS_SELECT_APPLICATION: u8 = 0xa4;
pub const YKPIV_INS_GET_RESPONSE_APDU: u8 = 0xc0;
// Yubico vendor specific instructions
// <https://developers.yubico.com/PIV/Introduction/Yubico_extensions.html>
pub const YKPIV_INS_SET_MGMKEY: u8 = 0xff;
pub const YKPIV_INS_IMPORT_KEY: u8 = 0xfe;
pub const YKPIV_INS_GET_VERSION: u8 = 0xfd;
@@ -213,8 +229,6 @@ pub const YKPIV_OBJ_MSROOTS3: u32 = 0x005f_ff13;
pub const YKPIV_OBJ_MSROOTS4: u32 = 0x005f_ff14;
pub const YKPIV_OBJ_MSROOTS5: u32 = 0x005f_ff15;
pub const YKPIV_OBJ_MAX_SIZE: usize = 3072;
pub const YKPIV_PINPOLICY_TAG: u8 = 0xaa;
pub const YKPIV_PINPOLICY_DEFAULT: u8 = 0;
pub const YKPIV_PINPOLICY_NEVER: u8 = 1;
+227
View File
@@ -0,0 +1,227 @@
//! PIV container 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)>
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
//
// Copyright (c) 2014-2016 Yubico AB
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, error::Error, key::SlotId, serialization::*, yubikey::YubiKey};
use log::error;
use std::{
convert::{TryFrom, TryInto},
fmt::{self, Debug},
};
/// MS Container Map(?) records
#[derive(Copy, Clone)]
pub struct Container {
/// Container name
pub name: [u16; CONTAINER_NAME_LEN],
/// Card slot
pub slot: SlotId,
/// Key spec
pub key_spec: u8,
/// Key size in bits
pub key_size_bits: u16,
/// Flags
pub flags: u8,
/// PIN ID
pub pin_id: u8,
/// Associated ECHD(?) container (typo of "ecdh" perhaps?)
pub associated_echd_container: u8,
/// Cert fingerprint
pub cert_fingerprint: [u8; 20],
}
impl Container {
/// Read MS Container Map records
pub fn read_mscmap(yubikey: &mut YubiKey) -> Result<Vec<Self>, Error> {
let txn = yubikey.begin_transaction()?;
let response = txn.fetch_object(YKPIV_OBJ_MSCMAP)?;
let mut containers = vec![];
if response.len() < CB_OBJ_TAG_MIN {
// TODO(tarcieri): is this really OK?
return Ok(containers);
}
if response[0] != TAG_MSCMAP {
// TODO(tarcieri): yubico-piv-tool returned success here? should we?
return Err(Error::InvalidObject);
}
let mut len = 0;
let offset = 1 + get_length(&response[1..], &mut len);
if len > response.len() - offset {
// TODO(tarcieri): is this really OK?
return Ok(containers);
}
for chunk in response[offset..(offset + len)].chunks_exact(CONTAINER_REC_LEN) {
containers.push(Container::new(chunk)?);
}
Ok(containers)
}
/// Write MS Container Map records.
pub fn write_mscmap(yubikey: &mut YubiKey, containers: &[Self]) -> Result<(), Error> {
let mut buf = [0u8; CB_OBJ_MAX];
let mut offset = 0;
let n_containers = containers.len();
let data_len = n_containers * CONTAINER_REC_LEN;
let max_size = yubikey.obj_size_max();
let txn = yubikey.begin_transaction()?;
if n_containers == 0 {
return txn.save_object(YKPIV_OBJ_MSCMAP, &[]);
}
let req_len = 1 + set_length(&mut buf, data_len) + data_len;
if req_len > max_size {
return Err(Error::SizeError);
}
buf[offset] = TAG_MSCMAP;
offset += 1;
offset += set_length(&mut buf[offset..], data_len);
for (i, chunk) in buf[..data_len]
.chunks_exact_mut(CONTAINER_REC_LEN)
.enumerate()
{
chunk.copy_from_slice(&containers[i].to_bytes());
}
offset += data_len;
txn.save_object(YKPIV_OBJ_MSCMAP, &buf[..offset])
}
/// Parse a container record from a byte slice
pub fn new(bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() != CONTAINER_REC_LEN {
error!(
"couldn't parse PIV container: expected {}-bytes, got {}-bytes",
CONTAINER_REC_LEN,
bytes.len()
);
return Err(Error::ParseError);
}
let mut name = [0u16; CONTAINER_NAME_LEN];
let name_bytes_len = CONTAINER_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());
}
let mut cert_fingerprint = [0u8; 20];
cert_fingerprint.copy_from_slice(&bytes[(bytes.len() - 20)..]);
Ok(Container {
name,
slot: bytes[name_bytes_len],
key_spec: bytes[name_bytes_len + 1],
key_size_bits: u16::from_le_bytes(
bytes[(name_bytes_len + 2)..(name_bytes_len + 4)]
.try_into()
.unwrap(),
),
flags: bytes[name_bytes_len + 4],
pin_id: bytes[name_bytes_len + 5],
associated_echd_container: bytes[name_bytes_len + 6],
cert_fingerprint,
})
}
/// Parse the container name as a UTF-16 string
pub fn parse_name(&self) -> Result<String, Error> {
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] {
let mut bytes = Vec::with_capacity(CONTAINER_REC_LEN);
for i in 0..CONTAINER_NAME_LEN {
bytes.extend_from_slice(&self.name[i].to_le_bytes());
}
bytes.push(self.slot);
bytes.push(self.key_spec);
bytes.extend_from_slice(&self.key_size_bits.to_le_bytes());
bytes.push(self.flags);
bytes.push(self.pin_id);
bytes.push(self.associated_echd_container);
bytes.extend_from_slice(&self.cert_fingerprint);
// TODO(tarcieri): use TryInto here when const generics are available
let mut result = [0u8; CONTAINER_REC_LEN];
result.copy_from_slice(&bytes);
result
}
}
impl Debug for Container {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"PivContainer {{ name: {:?}, slot: {}, key_spec: {}, key_size_bits: {}, \
flags: {}, pin_id: {}, associated_echd_container: {}, cert_fingerprint: {:?} }}",
&self.name[..],
self.slot,
self.key_spec,
self.key_size_bits,
self.flags,
self.pin_id,
self.associated_echd_container,
&self.cert_fingerprint[..]
)
}
}
impl<'a> TryFrom<&'a [u8]> for Container {
type Error = Error;
fn try_from(bytes: &'a [u8]) -> Result<Self, Error> {
Self::new(bytes)
}
}
+23 -16
View File
@@ -30,7 +30,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.
use std::fmt;
use std::fmt::{self, Display};
/// Kinds of errors
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -39,7 +39,10 @@ pub enum Error {
MemoryError,
/// PCSC error
PcscError,
PcscError {
/// Original PC/SC error
inner: Option<pcsc::Error>,
},
/// Size error
SizeError,
@@ -65,7 +68,7 @@ pub enum Error {
/// Wrong PIN
WrongPin {
/// Number of tries remaining
tries: i32,
tries: u32,
},
/// Invalid object
@@ -95,7 +98,7 @@ impl Error {
pub fn name(self) -> &'static str {
match self {
Error::MemoryError => "YKPIV_MEMORY_ERROR",
Error::PcscError => "YKPIV_PCSC_ERROR",
Error::PcscError { .. } => "YKPIV_PCSC_ERROR",
Error::SizeError => "YKPIV_SIZE_ERROR",
Error::AppletError => "YKPIV_APPLET_ERROR",
Error::AuthenticationError => "YKPIV_AUTHENTICATION_ERROR",
@@ -117,7 +120,7 @@ impl Error {
pub fn msg(self) -> &'static str {
match self {
Error::MemoryError => "memory error",
Error::PcscError => "PCSC error",
Error::PcscError { .. } => "PCSC error",
Error::SizeError => "size error",
Error::AppletError => "applet error",
Error::AuthenticationError => "authentication error",
@@ -136,22 +139,26 @@ impl Error {
}
}
impl fmt::Display for Error {
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.msg())
}
}
impl std::error::Error for Error {}
/// Get a string representation of this error
// TODO(tarcieri): completely replace this with `Display`
pub fn ykpiv_strerror(err: Error) -> &'static str {
err.msg()
impl From<pcsc::Error> for Error {
fn from(err: pcsc::Error) -> Error {
Error::PcscError { inner: Some(err) }
}
}
/// Get the name of this error
// TODO(tarcieri): completely replace this with debug
pub fn ykpiv_strerror_name(err: Error) -> &'static str {
err.name()
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
#[allow(trivial_casts)] // why doesn't this work without the cast???
Error::PcscError { inner } => inner
.as_ref()
.map(|err| err as &(dyn std::error::Error + 'static)),
_ => None,
}
}
}
-235
View File
@@ -1,235 +0,0 @@
//! Internal functions (mostly 3DES)
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
//
// Copyright (c) 2014-2016 Yubico AB
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::consts::*;
use des::{
block_cipher_trait::{generic_array::GenericArray, BlockCipher},
TdesEde3,
};
use std::env;
use std::fs::File;
use std::io::{BufRead, BufReader};
use zeroize::Zeroize;
/// 3DES keys. The three subkeys are concatenated.
pub struct DesKey([u8; DES_LEN_3DES]);
impl DesKey {
pub fn from_bytes(bytes: [u8; DES_LEN_3DES]) -> Self {
DesKey(bytes)
}
}
impl AsRef<[u8; 24]> for DesKey {
fn as_ref(&self) -> &[u8; 24] {
&self.0
}
}
impl Zeroize for DesKey {
fn zeroize(&mut self) {
self.0.zeroize();
}
}
impl Drop for DesKey {
fn drop(&mut self) {
self.zeroize();
}
}
/// Encrypt with DES key
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn des_encrypt(key: &DesKey, input: &[u8; DES_LEN_DES], output: &mut [u8; DES_LEN_DES]) {
output.copy_from_slice(input);
TdesEde3::new(GenericArray::from_slice(&key.0))
.encrypt_block(GenericArray::from_mut_slice(output));
}
/// Decrypt with DES key
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn des_decrypt(key: &DesKey, input: &[u8; DES_LEN_DES], output: &mut [u8; DES_LEN_DES]) {
output.copy_from_slice(input);
TdesEde3::new(GenericArray::from_slice(&key.0))
.encrypt_block(GenericArray::from_mut_slice(output));
}
/// Is the given DES key weak?
pub fn yk_des_is_weak_key(key: &[u8; DES_LEN_3DES]) -> bool {
/// Weak and semi weak keys as taken from
/// %A D.W. Davies
/// %A W.L. Price
/// %T Security for Computer Networks
/// %I John Wiley & Sons
/// %D 1984
const WEAK_KEYS: [[u8; DES_LEN_DES]; 16] = [
// weak keys
[0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01],
[0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE],
[0x1F, 0x1F, 0x1F, 0x1F, 0x0E, 0x0E, 0x0E, 0x0E],
[0xE0, 0xE0, 0xE0, 0xE0, 0xF1, 0xF1, 0xF1, 0xF1],
// semi-weak keys
[0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE],
[0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01],
[0x1F, 0xE0, 0x1F, 0xE0, 0x0E, 0xF1, 0x0E, 0xF1],
[0xE0, 0x1F, 0xE0, 0x1F, 0xF1, 0x0E, 0xF1, 0x0E],
[0x01, 0xE0, 0x01, 0xE0, 0x01, 0xF1, 0x01, 0xF1],
[0xE0, 0x01, 0xE0, 0x01, 0xF1, 0x01, 0xF1, 0x01],
[0x1F, 0xFE, 0x1F, 0xFE, 0x0E, 0xFE, 0x0E, 0xFE],
[0xFE, 0x1F, 0xFE, 0x1F, 0xFE, 0x0E, 0xFE, 0x0E],
[0x01, 0x1F, 0x01, 0x1F, 0x01, 0x0E, 0x01, 0x0E],
[0x1F, 0x01, 0x1F, 0x01, 0x0E, 0x01, 0x0E, 0x01],
[0xE0, 0xFE, 0xE0, 0xFE, 0xF1, 0xFE, 0xF1, 0xFE],
[0xFE, 0xE0, 0xFE, 0xE0, 0xFE, 0xF1, 0xFE, 0xF1],
];
// set odd parity of key
let mut tmp = [0u8; DES_LEN_3DES];
for i in 0..DES_LEN_3DES {
// count number of set bits in byte, excluding the low-order bit - SWAR method
let mut c = key[i] & 0xFE;
c = (c & 0x55) + ((c >> 1) & 0x55);
c = (c & 0x33) + ((c >> 2) & 0x33);
c = (c & 0x0F) + ((c >> 4) & 0x0F);
// if count is even, set low key bit to 1, otherwise 0
tmp[i] = (key[i] & 0xFE) | (if c & 0x01 == 0x01 { 0x00 } else { 0x01 });
}
// check odd parity key against table by DES key block
let mut rv = false;
for weak_key in WEAK_KEYS.iter() {
if weak_key == &tmp[0..DES_LEN_DES]
|| weak_key == &tmp[DES_LEN_DES..2 * DES_LEN_DES]
|| weak_key == &tmp[2 * DES_LEN_DES..3 * DES_LEN_DES]
{
rv = true;
break;
}
}
tmp.zeroize();
rv
}
/// Source of how a setting was configured
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SettingSource {
/// User-specified setting
User,
/// Admin-specified setting
Admin,
/// Default setting
Default,
}
/// Setting booleans
#[derive(Copy, Clone, Debug)]
pub struct SettingBool {
/// Boolean value
pub value: bool,
/// Source of the configuration setting (user/admin/default)
pub source: SettingSource,
}
/// Get a boolean config value
pub fn _get_bool_config(key: &str) -> SettingBool {
let mut setting: SettingBool = SettingBool {
value: false,
source: SettingSource::Default,
};
if let Ok(f) = File::open("/etc/yubico/yubikeypiv.conf") {
for line in BufReader::new(f).lines() {
let line = match line {
Ok(line) => line,
_ => continue,
};
if line.starts_with('#') || line.starts_with('\r') || line.starts_with('\n') {
continue;
}
let (name, value) = {
let mut parts = line.splitn(1, '=');
let name = parts.next();
let value = parts.next();
match (name, value, parts.next()) {
(Some(name), Some(value), None) => (name.trim(), value.trim()),
_ => continue,
}
};
if name == key {
setting.source = SettingSource::Admin;
setting.value = value == "1" || value == "true";
break;
}
}
}
setting
}
/// Get a setting boolean from an environment variable
pub fn _get_bool_env(key: &str) -> SettingBool {
let mut setting: SettingBool = SettingBool {
value: false,
source: SettingSource::Default,
};
if let Ok(value) = env::var(format!("YUBIKEY_PIV_{}", key)) {
setting.source = SettingSource::User;
setting.value = value == "1" || value == "true";
}
setting
}
/// Get a setting boolean
pub fn setting_get_bool(key: &str, def: bool) -> SettingBool {
let mut setting = _get_bool_config(key);
if setting.source == SettingSource::Default {
setting = _get_bool_env(key);
}
if setting.source == SettingSource::Default {
setting.value = def;
}
setting
}
+375
View File
@@ -0,0 +1,375 @@
//! PIV cryptographic keys stored in a YubiKey.
//!
//! Supported algorithms:
//!
//! - **Encryption**: `RSA1024`, `RSA2048`, `ECCP256`, `ECCP384`
//! - **Signatures**:
//! - RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048`
//! - ECDSA: `ECCP256`, `ECCP384`
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
//
// Copyright (c) 2014-2016 Yubico AB
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{
certificate::{self, Certificate},
consts::*,
error::Error,
response::StatusWords,
serialization::*,
settings,
yubikey::YubiKey,
AlgorithmId, ObjectId,
};
use log::{debug, error, warn};
/// Slot identifiers.
/// <https://developers.yubico.com/PIV/Introduction/Certificate_slots.html>
// TODO(tarcieri): replace these with enums
pub type SlotId = u8;
/// Get the [`ObjectId`] that corresponds to a given [`SlotId`]
// TODO(tarcieri): factor this into a slot ID enum
pub(crate) fn slot_object(slot: SlotId) -> Result<ObjectId, Error> {
let id = match slot {
YKPIV_KEY_AUTHENTICATION => YKPIV_OBJ_AUTHENTICATION,
YKPIV_KEY_SIGNATURE => YKPIV_OBJ_SIGNATURE,
YKPIV_KEY_KEYMGM => YKPIV_OBJ_KEY_MANAGEMENT,
YKPIV_KEY_CARDAUTH => YKPIV_OBJ_CARD_AUTH,
YKPIV_KEY_ATTESTATION => YKPIV_OBJ_ATTESTATION,
slot if slot >= YKPIV_KEY_RETIRED1 && (slot <= YKPIV_KEY_RETIRED20) => {
YKPIV_OBJ_RETIRED1 + (slot - YKPIV_KEY_RETIRED1) as u32
}
_ => return Err(Error::InvalidObject),
};
Ok(id)
}
/// Personal Identity Verification (PIV) key slots
pub const SLOTS: [u8; 24] = [
YKPIV_KEY_AUTHENTICATION,
YKPIV_KEY_SIGNATURE,
YKPIV_KEY_KEYMGM,
YKPIV_KEY_RETIRED1,
YKPIV_KEY_RETIRED2,
YKPIV_KEY_RETIRED3,
YKPIV_KEY_RETIRED4,
YKPIV_KEY_RETIRED5,
YKPIV_KEY_RETIRED6,
YKPIV_KEY_RETIRED7,
YKPIV_KEY_RETIRED8,
YKPIV_KEY_RETIRED9,
YKPIV_KEY_RETIRED10,
YKPIV_KEY_RETIRED11,
YKPIV_KEY_RETIRED12,
YKPIV_KEY_RETIRED13,
YKPIV_KEY_RETIRED14,
YKPIV_KEY_RETIRED15,
YKPIV_KEY_RETIRED16,
YKPIV_KEY_RETIRED17,
YKPIV_KEY_RETIRED18,
YKPIV_KEY_RETIRED19,
YKPIV_KEY_RETIRED20,
YKPIV_KEY_CARDAUTH,
];
/// PIV cryptographic keys stored in a YubiKey
#[derive(Clone, Debug)]
pub struct Key {
/// Card slot
slot: SlotId,
/// Cert
cert: Certificate,
}
impl Key {
/// List Personal Identity Verification (PIV) keys stored in a YubiKey
pub fn list(yubikey: &mut YubiKey) -> Result<Vec<Self>, Error> {
let mut keys = vec![];
let txn = yubikey.begin_transaction()?;
for slot in SLOTS.iter().cloned() {
let buf = match certificate::read_certificate(&txn, slot) {
Ok(b) => b,
Err(e) => {
debug!("error reading certificate in slot {}: {}", slot, e);
continue;
}
};
let cert = Certificate::new(buf)?;
keys.push(Key { slot, cert });
}
Ok(keys)
}
/// Get the slot ID for this key
pub fn slot(&self) -> SlotId {
self.slot
}
/// Get the certificate for this key
pub fn certificate(&self) -> &Certificate {
&self.cert
}
}
// Keygen messages
// TODO(tarcieri): extract these into an I18N-handling type?
const SZ_SETTING_ROCA: &str = "Enable_Unsafe_Keygen_ROCA";
const SZ_ROCA_ALLOW_USER: &str =
"was permitted by an end-user configuration setting, but is not recommended.";
const SZ_ROCA_ALLOW_ADMIN: &str =
"was permitted by an administrator configuration setting, but is not recommended.";
const SZ_ROCA_BLOCK_USER: &str = "was blocked due to an end-user configuration setting.";
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.";
/// Information about a generated key
// TODO(tarcieri): this could use some more work
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum GeneratedKey {
/// RSA keys
Rsa {
/// RSA algorithm
algorithm: AlgorithmId,
/// Modulus
modulus: Vec<u8>,
/// Exponent
exp: Vec<u8>,
},
/// ECC keys
Ecc {
/// ECC algorithm
algorithm: AlgorithmId,
/// Public curve point (i.e. public key)
point: Vec<u8>,
},
}
impl GeneratedKey {
/// Get the algorithm
pub fn algorithm(&self) -> AlgorithmId {
*match self {
GeneratedKey::Rsa { algorithm, .. } => algorithm,
GeneratedKey::Ecc { algorithm, .. } => algorithm,
}
}
}
/// Generate key
#[allow(clippy::cognitive_complexity)]
pub fn generate(
yubikey: &mut YubiKey,
slot: SlotId,
algorithm: AlgorithmId,
pin_policy: u8,
touch_policy: u8,
) -> Result<GeneratedKey, Error> {
let mut in_data = [0u8; 11];
let mut templ = [0, YKPIV_INS_GENERATE_ASYMMETRIC, 0, 0];
let setting_roca: settings::BoolValue;
if yubikey.device_model() == DEVTYPE_YK4
&& (algorithm == YKPIV_ALGO_RSA1024 || algorithm == YKPIV_ALGO_RSA2048)
&& yubikey.version.major == 4
&& (yubikey.version.minor < 3 || yubikey.version.minor == 3 && (yubikey.version.patch < 5))
{
setting_roca = settings::BoolValue::get(SZ_SETTING_ROCA, true);
let psz_msg = match setting_roca.source {
settings::Source::User => {
if setting_roca.value {
SZ_ROCA_ALLOW_USER
} else {
SZ_ROCA_BLOCK_USER
}
}
settings::Source::Admin => {
if setting_roca.value {
SZ_ROCA_ALLOW_ADMIN
} else {
SZ_ROCA_BLOCK_ADMIN
}
}
_ => SZ_ROCA_DEFAULT,
};
warn!(
"YubiKey serial number {} is affected by vulnerability CVE-2017-15361 \
(ROCA) and should be replaced. On-chip key generation {} See \
YSA-2017-01 <https://www.yubico.com/support/security-advisories/ysa-2017-01/> \
for additional information on device replacement and mitigation assistance",
yubikey.serial, psz_msg
);
if !setting_roca.value {
return Err(Error::NotSupported);
}
}
match algorithm {
YKPIV_ALGO_RSA1024 | YKPIV_ALGO_RSA2048 | YKPIV_ALGO_ECCP256 | YKPIV_ALGO_ECCP384 => (),
_ => {
error!("invalid algorithm specified");
return Err(Error::GenericError);
}
}
let txn = yubikey.begin_transaction()?;
templ[3] = slot;
let mut offset = 5;
in_data[..offset].copy_from_slice(&[
0xac,
3, // length sans this 2-byte header
YKPIV_ALGO_TAG,
1,
algorithm,
]);
if in_data[4] == 0 {
error!("unexpected algorithm");
return Err(Error::AlgorithmError);
}
if pin_policy != YKPIV_PINPOLICY_DEFAULT {
in_data[1] += 3;
in_data[offset..(offset + 3)].copy_from_slice(&[YKPIV_PINPOLICY_TAG, 1, pin_policy]);
offset += 3;
}
if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT {
in_data[1] += 3;
in_data[offset..(offset + 3)].copy_from_slice(&[YKPIV_TOUCHPOLICY_TAG, 1, touch_policy]);
}
let response = txn.transfer_data(&templ, &in_data[..offset], 1024)?;
if !response.is_success() {
let err_msg = "failed to generate new key";
match response.status_words() {
StatusWords::IncorrectSlotError => {
error!("{} (incorrect slot)", err_msg);
return Err(Error::KeyError);
}
StatusWords::IncorrectParamError => {
if pin_policy != YKPIV_PINPOLICY_DEFAULT {
error!("{} (pin policy not supported?)", err_msg);
} else if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT {
error!("{} (touch policy not supported?)", err_msg);
} else {
error!("{} (algorithm not supported?)", err_msg);
}
return Err(Error::AlgorithmError);
}
StatusWords::SecurityStatusError => {
error!("{} (not authenticated)", err_msg);
return Err(Error::AuthenticationError);
}
other => {
error!("{} (error {:x})", err_msg, other.code());
return Err(Error::GenericError);
}
}
}
let data = response.into_buffer();
match algorithm {
YKPIV_ALGO_RSA1024 | YKPIV_ALGO_RSA2048 => {
let mut offset = 5;
let mut len = 0;
if data[offset] != TAG_RSA_MODULUS {
error!("Failed to parse public key structure (modulus)");
return Err(Error::ParseError);
}
offset += 1;
offset += get_length(&data[offset..], &mut len);
let modulus = data[offset..(offset + len)].to_vec();
offset += len;
if data[offset] != TAG_RSA_EXP {
error!("failed to parse public key structure (public exponent)");
return Err(Error::ParseError);
}
offset += 1;
offset += get_length(&data[offset..], &mut len);
let exp = data[offset..(offset + len)].to_vec();
Ok(GeneratedKey::Rsa {
algorithm,
modulus,
exp,
})
}
YKPIV_ALGO_ECCP256 | YKPIV_ALGO_ECCP384 => {
let mut offset = 3;
let len = if algorithm == YKPIV_ALGO_ECCP256 {
CB_ECC_POINTP256
} else {
CB_ECC_POINTP384
};
if data[offset] != TAG_ECC_POINT {
error!("failed to parse public key structure");
return Err(Error::ParseError);
}
offset += 1;
// the curve point should always be determined by the curve
let len_byte = data[offset];
offset += 1;
if len_byte as usize != len {
error!("unexpected length");
return Err(Error::AlgorithmError);
}
let point = data[offset..(offset + len)].to_vec();
Ok(GeneratedKey::Ecc { algorithm, point })
}
_ => {
error!("wrong algorithm");
Err(Error::AlgorithmError)
}
}
}
+34 -9
View File
@@ -9,6 +9,9 @@
//! utilize PIV encryption and signing keys which can be generated, imported,
//! and stored on YubiKey devices.
//!
//! See [Yubico's guide to PIV-enabled YubiKeys][6] for more information
//! on which devices support PIV and the available functionality.
//!
//! Supported algorithms:
//!
//! - **Authentication**: `3DES`
@@ -24,23 +27,24 @@
//!
//! ## History
//!
//! This library is a Rust translation of the [yubico-piv-tool][6] utility by
//! This library is a Rust translation of the [yubico-piv-tool][7] utility by
//! Yubico, which was originally written in C. It was mechanically translated
//! from C into Rust using [Corrode][7], and then subsequently heavily
//! from C into Rust using [Corrode][8], and then subsequently heavily
//! refactored into safer, more idiomatic Rust.
//!
//! For more information on `yubico-piv-tool` and background information on how
//! the YubiKey implementation of PIV works in general, see the
//! [Yubico PIV Tool Command Line Guide][8].
//! [Yubico PIV Tool Command Line Guide][9].
//!
//! [1]: https://www.yubico.com/products/yubikey-hardware/
//! [2]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf
//! [3]: https://www.yubico.com/
//! [4]: https://en.wikipedia.org/wiki/CCID_(protocol)
//! [5]: https://www.nist.gov/
//! [6]: https://github.com/Yubico/yubico-piv-tool/
//! [7]: https://github.com/jameysharp/corrode
//! [8]: https://www.yubico.com/wp-content/uploads/2016/05/Yubico_PIV_Tool_Command_Line_Guide_en.pdf
//! [6]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
//! [7]: https://github.com/Yubico/yubico-piv-tool/
//! [8]: https://github.com/jameysharp/corrode
//! [9]: https://www.yubico.com/wp-content/uploads/2016/05/Yubico_PIV_Tool_Command_Line_Guide_en.pdf
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
@@ -86,10 +90,31 @@
)]
mod apdu;
pub mod cccid;
pub mod certificate;
pub mod chuid;
pub mod config;
pub mod consts;
pub mod container;
pub mod error;
mod internal;
pub mod util;
pub mod key;
mod metadata;
pub mod mgm;
pub mod msroots;
mod response;
mod serialization;
pub mod settings;
mod transaction;
pub mod yubikey;
pub use self::yubikey::YubiKey;
pub use self::{key::Key, mgm::MgmKey, yubikey::YubiKey};
/// Algorithm identifiers
// TODO(tarcieri): make this an enum
pub type AlgorithmId = u8;
/// Object identifiers
pub type ObjectId = u32;
/// Buffer type (self-zeroizing byte vector)
pub(crate) type Buffer = zeroize::Zeroizing<Vec<u8>>;
+271
View File
@@ -0,0 +1,271 @@
//! YubiKey Device Metadata
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
//
// Copyright (c) 2014-2016 Yubico AB
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, error::Error, serialization::*, transaction::Transaction, Buffer};
use std::{ptr, slice};
use zeroize::Zeroizing;
/// Get metadata item
pub(crate) fn get_item(data: &[u8], tag: u8) -> Result<&[u8], Error> {
let mut p_temp: *const u8 = data.as_ptr();
let mut cb_temp: usize = 0;
let mut tag_temp: u8;
unsafe {
while p_temp < data.as_ptr().add(data.len()) {
tag_temp = *p_temp;
p_temp = p_temp.add(1);
let p_slice = slice::from_raw_parts(
p_temp,
data.as_ptr() as usize + data.len() - p_temp as usize,
);
if !has_valid_length(
p_slice,
data.as_ptr().add(data.len()) as usize - p_temp as usize,
) {
return Err(Error::SizeError);
}
p_temp = p_temp.add(get_length(p_slice, &mut cb_temp));
if tag_temp == tag {
// found tag
break;
}
p_temp = p_temp.add(cb_temp);
}
if p_temp < data.as_ptr().add(data.len()) {
return Ok(slice::from_raw_parts(p_temp, cb_temp));
}
}
Err(Error::GenericError)
}
/// Set metadata item
pub(crate) fn set_item(
data: &mut [u8],
pcb_data: &mut usize,
cb_data_max: usize,
tag: u8,
p_item: &[u8],
) -> Result<(), Error> {
let mut p_temp: *mut u8 = data.as_mut_ptr();
let mut cb_temp: usize = 0;
let mut tag_temp: u8 = 0;
let mut cb_len: usize = 0;
let cb_item = p_item.len();
// Must be signed to have negative offsets
let cb_moved: isize;
let p_next: *mut u8;
while p_temp < data[*pcb_data..].as_mut_ptr() {
unsafe {
tag_temp = *p_temp;
p_temp = p_temp.add(1);
cb_len = get_length(
slice::from_raw_parts(
p_temp,
data.as_mut_ptr() as usize + data.len() - p_temp as usize,
),
&mut cb_temp,
);
p_temp = p_temp.add(cb_len);
if tag_temp == tag {
break;
}
p_temp = p_temp.add(cb_temp);
}
}
if tag_temp != tag {
if cb_item == 0 {
// We've been asked to delete an existing item that isn't in the blob
return Ok(());
}
unsafe {
p_temp = data.as_mut_ptr().add(*pcb_data);
cb_len = get_length_size(cb_item);
if (*pcb_data + cb_len + cb_item) > cb_data_max {
return Err(Error::GenericError);
}
*p_temp = tag;
p_temp = p_temp.add(1);
p_temp = p_temp.add(set_length(
slice::from_raw_parts_mut(
p_temp,
data.as_ptr() as usize + data.len() - p_temp as usize,
),
cb_item,
));
ptr::copy(p_item.as_ptr(), p_temp, cb_item);
}
*pcb_data += 1 + cb_len + cb_item;
return Ok(());
}
if cb_temp == cb_item {
unsafe {
ptr::copy(p_item.as_ptr(), p_temp, cb_item);
}
return Ok(());
}
p_next = unsafe { p_temp.add(cb_temp) };
cb_moved = (cb_item as isize - cb_temp as isize)
+ if cb_item != 0 {
get_length_size(cb_item) as isize
} else {
-1
}
- cb_len as isize;
if (*pcb_data + cb_moved as usize) > cb_data_max {
return Err(Error::GenericError);
}
unsafe {
ptr::copy(
p_next,
p_next.offset(cb_moved),
*pcb_data - p_next as usize - data.as_ptr() as usize,
);
}
*pcb_data += cb_moved as usize;
if cb_item != 0 {
unsafe {
p_temp = p_temp.offset(-(cb_len as isize));
p_temp = p_temp.add(set_length(
slice::from_raw_parts_mut(
p_temp,
data.as_ptr() as usize + data.len() - p_temp as usize,
),
cb_item,
));
ptr::copy(p_item.as_ptr(), p_temp, cb_item);
}
}
Ok(())
}
/// Read metadata
pub(crate) fn read(txn: &Transaction<'_>, tag: u8) -> Result<Buffer, Error> {
let obj_id = match tag {
TAG_ADMIN => YKPIV_OBJ_ADMIN_DATA,
TAG_PROTECTED => YKPIV_OBJ_PRINTED,
_ => return Err(Error::InvalidObject),
};
let mut data = txn.fetch_object(obj_id)?;
if data.len() < CB_OBJ_TAG_MIN {
return Err(Error::GenericError);
}
if tag != data[0] {
return Err(Error::GenericError);
}
let mut pcb_data = 0;
let offset = 1 + get_length(&data[1..], &mut pcb_data);
if pcb_data > data.len() - offset {
return Err(Error::GenericError);
}
unsafe {
ptr::copy(data.as_ptr().add(offset), data.as_mut_ptr(), pcb_data);
}
data.truncate(pcb_data);
Ok(data)
}
/// Write metadata
pub(crate) fn write(
txn: &Transaction<'_>,
tag: u8,
data: &[u8],
max_size: usize,
) -> Result<(), Error> {
let mut buf = Zeroizing::new(vec![0u8; CB_OBJ_MAX]);
if data.len() > max_size - CB_OBJ_TAG_MAX {
return Err(Error::GenericError);
}
let obj_id = match tag {
TAG_ADMIN => YKPIV_OBJ_ADMIN_DATA,
TAG_PROTECTED => YKPIV_OBJ_PRINTED,
_ => return Err(Error::InvalidObject),
};
if data.is_empty() {
// Deleting metadata
return txn.save_object(obj_id, &[]);
}
buf[0] = tag;
let mut offset = set_length(&mut buf[1..], data.len());
buf[offset..(offset + data.len())].copy_from_slice(data);
offset += data.len();
txn.save_object(obj_id, &buf[..offset])
}
/// Get the size of a length tag for the given length
fn get_length_size(length: usize) -> usize {
if length < 0x80 {
1
} else if length < 0xff {
2
} else {
3
}
}
+363
View File
@@ -0,0 +1,363 @@
//! Management Key (MGM) for authenticating to the YubiKey management applet
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
//
// Copyright (c) 2014-2016 Yubico AB
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, error::Error, metadata, yubikey::YubiKey};
use des::{
block_cipher_trait::{generic_array::GenericArray, BlockCipher},
TdesEde3,
};
use getrandom::getrandom;
use hmac::Hmac;
use log::error;
use pbkdf2::pbkdf2;
use sha1::Sha1;
use std::convert::{TryFrom, TryInto};
use zeroize::{Zeroize, Zeroizing};
/// Default MGM key configured on all YubiKeys
const DEFAULT_MGM_KEY: [u8; DES_LEN_3DES] = [
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8,
];
/// Management Key (MGM) key types (manual/derived/protected)
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[allow(non_camel_case_types)]
pub enum MgmType {
/// Manual
Manual = 0,
/// Derived
Derived = 1,
/// Protected
Protected = 2,
}
/// Management Key (MGM).
///
/// This key is used to authenticate to the management applet running on
/// a YubiKey in order to perform administrative functions.
///
/// The only supported algorithm for MGM keys is 3DES.
#[derive(Clone)]
pub struct MgmKey([u8; DES_LEN_3DES]);
impl MgmKey {
/// Generate a random MGM key
pub fn generate() -> Result<Self, Error> {
let mut key_bytes = [0u8; DES_LEN_3DES];
if getrandom(&mut key_bytes).is_err() {
return Err(Error::RandomnessError);
}
MgmKey::new(key_bytes)
}
/// Create an MGM key from byte slice.
///
/// Returns an error if the slice is the wrong size or the key is weak.
pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, Error> {
bytes.as_ref().try_into()
}
/// Create an MGM key from the given byte array.
///
/// Returns an error if the key is weak.
pub fn new(key_bytes: [u8; DES_LEN_3DES]) -> Result<Self, Error> {
if is_weak_key(&key_bytes) {
error!(
"blacklisting key '{:?}' since it's weak (with odd parity)",
&key_bytes
);
return Err(Error::KeyError);
}
Ok(MgmKey(key_bytes))
}
/// Get derived management key (MGM)
pub fn get_derived(yubikey: &mut YubiKey, pin: &[u8]) -> Result<Self, Error> {
let txn = yubikey.begin_transaction()?;
// recover management key
let data = metadata::read(&txn, TAG_ADMIN)?;
let salt = metadata::get_item(&data, TAG_ADMIN_SALT)?;
if salt.len() != CB_ADMIN_SALT {
error!(
"derived MGM salt exists, but is incorrect size: {} (expected {})",
salt.len(),
CB_ADMIN_SALT
);
return Err(Error::GenericError);
}
let mut mgm = [0u8; DES_LEN_3DES];
pbkdf2::<Hmac<Sha1>>(pin, &salt, ITER_MGM_PBKDF2, &mut mgm);
MgmKey::from_bytes(mgm)
}
/// Get protected management key (MGM)
pub fn get_protected(yubikey: &mut YubiKey) -> Result<Self, Error> {
let txn = yubikey.begin_transaction()?;
let data = metadata::read(&txn, TAG_PROTECTED).map_err(|e| {
error!("could not read protected data (err: {:?})", e);
e
})?;
let item = metadata::get_item(&data, TAG_PROTECTED_MGM).map_err(|e| {
error!("could not read protected MGM from metadata (err: {:?})", e);
e
})?;
if item.len() != DES_LEN_3DES {
error!(
"protected data contains MGM, but is the wrong size: {} (expected {})",
item.len(),
DES_LEN_3DES
);
return Err(Error::AuthenticationError);
}
MgmKey::from_bytes(item)
}
/// Set the management key (MGM)
pub fn set(&self, yubikey: &mut YubiKey, touch: Option<u8>) -> Result<(), Error> {
let txn = yubikey.begin_transaction()?;
txn.set_mgm_key(&self, touch)
}
/// Set protected management key (MGM)
pub fn set_protected(&self, yubikey: &mut YubiKey) -> Result<(), Error> {
let mut data = Zeroizing::new(vec![0u8; CB_BUF_MAX]);
let max_size = yubikey.obj_size_max();
let txn = yubikey.begin_transaction()?;
txn.set_mgm_key(self, None).map_err(|e| {
// log a warning, since the device mgm key is corrupt or we're in
// a state where we can't set the mgm key
error!("could not set new derived mgm key, err = {}", e);
e
})?;
// after this point, we've set the mgm key, so the function should
// succeed, regardless of being able to set the metadata
// set the new mgm key in protected data
let buffer = match metadata::read(&txn, TAG_PROTECTED) {
Ok(b) => b,
Err(_) => {
// set current metadata blob size to zero, we'll add to the blank blob
Zeroizing::new(vec![])
}
};
let mut cb_data = buffer.len();
data[..cb_data].copy_from_slice(&buffer);
if let Err(e) = metadata::set_item(
data.as_mut_slice(),
&mut cb_data,
CB_OBJ_MAX,
TAG_PROTECTED_MGM,
self.as_ref(),
) {
error!("could not set protected mgm item, err = {:?}", e);
} else {
metadata::write(&txn, TAG_PROTECTED, &data, max_size).map_err(|e| {
error!("could not write protected data, err = {:?}", e);
e
})?;
}
// set the protected mgm flag in admin data
cb_data = data.len();
let mut flags_1 = [0u8; 1];
if let Ok(buffer) = metadata::read(&txn, TAG_ADMIN) {
if let Ok(item) = metadata::get_item(&buffer, TAG_ADMIN_FLAGS_1) {
if item.len() == flags_1.len() {
flags_1.copy_from_slice(item);
} else {
error!(
"admin data flags are an incorrect size: {} (expected {})",
item.len(),
flags_1.len()
);
}
} else {
// flags are not set
error!("admin data exists, but flags are not present");
}
// remove any existing salt
if let Err(e) =
metadata::set_item(&mut data, &mut cb_data, CB_OBJ_MAX, TAG_ADMIN_SALT, &[])
{
error!("could not unset derived mgm salt (err = {})", e)
}
} else {
cb_data = 0;
}
flags_1[0] |= ADMIN_FLAGS_1_PROTECTED_MGM;
if let Err(e) = metadata::set_item(
data.as_mut_slice(),
&mut cb_data,
CB_OBJ_MAX,
TAG_ADMIN_FLAGS_1,
&flags_1,
) {
error!("could not set admin flags item, err = {}", e);
} else if let Err(e) = metadata::write(&txn, TAG_ADMIN, &data[..cb_data], max_size) {
error!("could not write admin data, err = {}", e);
}
Ok(())
}
/// Encrypt with 3DES key
#[allow(clippy::trivially_copy_pass_by_ref)]
pub(crate) fn encrypt(&self, input: &[u8; DES_LEN_DES]) -> [u8; DES_LEN_DES] {
let mut output = input.to_owned();
TdesEde3::new(GenericArray::from_slice(&self.0))
.encrypt_block(GenericArray::from_mut_slice(&mut output));
output
}
/// Decrypt with 3DES key
#[allow(clippy::trivially_copy_pass_by_ref)]
pub(crate) fn decrypt(&self, input: &[u8; DES_LEN_DES]) -> [u8; DES_LEN_DES] {
let mut output = input.to_owned();
TdesEde3::new(GenericArray::from_slice(&self.0))
.encrypt_block(GenericArray::from_mut_slice(&mut output));
output
}
}
impl AsRef<[u8; DES_LEN_3DES]> for MgmKey {
fn as_ref(&self) -> &[u8; DES_LEN_3DES] {
&self.0
}
}
impl Default for MgmKey {
fn default() -> Self {
MgmKey(DEFAULT_MGM_KEY)
}
}
impl Drop for MgmKey {
fn drop(&mut self) {
self.0.zeroize();
}
}
impl<'a> TryFrom<&'a [u8]> for MgmKey {
type Error = Error;
fn try_from(key_bytes: &'a [u8]) -> Result<Self, Error> {
Self::new(key_bytes.try_into().map_err(|_| Error::SizeError)?)
}
}
/// Weak and semi weak DES keys as taken from:
/// %A D.W. Davies
/// %A W.L. Price
/// %T Security for Computer Networks
/// %I John Wiley & Sons
/// %D 1984
const WEAK_DES_KEYS: &[[u8; DES_LEN_DES]] = &[
// weak keys
[0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01],
[0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE],
[0x1F, 0x1F, 0x1F, 0x1F, 0x0E, 0x0E, 0x0E, 0x0E],
[0xE0, 0xE0, 0xE0, 0xE0, 0xF1, 0xF1, 0xF1, 0xF1],
// semi-weak keys
[0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE],
[0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01],
[0x1F, 0xE0, 0x1F, 0xE0, 0x0E, 0xF1, 0x0E, 0xF1],
[0xE0, 0x1F, 0xE0, 0x1F, 0xF1, 0x0E, 0xF1, 0x0E],
[0x01, 0xE0, 0x01, 0xE0, 0x01, 0xF1, 0x01, 0xF1],
[0xE0, 0x01, 0xE0, 0x01, 0xF1, 0x01, 0xF1, 0x01],
[0x1F, 0xFE, 0x1F, 0xFE, 0x0E, 0xFE, 0x0E, 0xFE],
[0xFE, 0x1F, 0xFE, 0x1F, 0xFE, 0x0E, 0xFE, 0x0E],
[0x01, 0x1F, 0x01, 0x1F, 0x01, 0x0E, 0x01, 0x0E],
[0x1F, 0x01, 0x1F, 0x01, 0x0E, 0x01, 0x0E, 0x01],
[0xE0, 0xFE, 0xE0, 0xFE, 0xF1, 0xFE, 0xF1, 0xFE],
[0xFE, 0xE0, 0xFE, 0xE0, 0xFE, 0xF1, 0xFE, 0xF1],
];
/// Is this 3DES key weak?
///
/// This check is performed automatically when the key is instantiated to
/// ensure no such keys are used.
fn is_weak_key(key: &[u8; DES_LEN_3DES]) -> bool {
// set odd parity of key
let mut tmp = Zeroizing::new([0u8; DES_LEN_3DES]);
for i in 0..DES_LEN_3DES {
// count number of set bits in byte, excluding the low-order bit - SWAR method
let mut c = key[i] & 0xFE;
c = (c & 0x55) + ((c >> 1) & 0x55);
c = (c & 0x33) + ((c >> 2) & 0x33);
c = (c & 0x0F) + ((c >> 4) & 0x0F);
// if count is even, set low key bit to 1, otherwise 0
tmp[i] = (key[i] & 0xFE) | (if c & 0x01 == 0x01 { 0x00 } else { 0x01 });
}
// check odd parity key against table by DES key block
let mut is_weak = false;
for weak_key in WEAK_DES_KEYS.iter() {
if weak_key == &tmp[0..DES_LEN_DES]
|| weak_key == &tmp[DES_LEN_DES..2 * DES_LEN_DES]
|| weak_key == &tmp[2 * DES_LEN_DES..3 * DES_LEN_DES]
{
is_weak = true;
break;
}
}
is_weak
}
+178
View File
@@ -0,0 +1,178 @@
//! `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>
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
//
// Copyright (c) 2014-2016 Yubico AB
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, error::Error, serialization::*, yubikey::YubiKey};
use log::error;
use std::{ptr, slice};
/// `msroots` file: PKCS#7-formatted certificate store for enterprise trust roots
pub struct MsRoots(Vec<u8>);
impl MsRoots {
/// Initialize a local certificate struct from the given bytebuffer
pub fn new(msroots: impl AsRef<[u8]>) -> Result<Self, Error> {
Ok(MsRoots(msroots.as_ref().into()))
}
/// Read `msroots` file from YubiKey
pub fn read(yubikey: &mut YubiKey) -> Result<Vec<Self>, Error> {
let mut len: usize = 0;
let mut ptr: *mut u8;
let mut tag: u8;
let mut offset: usize = 0;
let mut results = vec![];
let cb_data = yubikey.obj_size_max();
let txn = yubikey.begin_transaction()?;
// allocate first page
let mut p_data = vec![0u8; cb_data];
for object_id in YKPIV_OBJ_MSROOTS1..YKPIV_OBJ_MSROOTS5 {
let mut buf = txn.fetch_object(object_id)?;
let cb_buf = buf.len();
ptr = buf.as_mut_ptr();
if cb_buf < CB_OBJ_TAG_MIN {
return Ok(results);
}
unsafe {
tag = *ptr;
ptr = ptr.add(1);
}
if (TAG_MSROOTS_MID != tag || YKPIV_OBJ_MSROOTS5 == object_id)
&& (TAG_MSROOTS_END != tag)
{
// the current object doesn't contain a valid part of a msroots file
// treat condition as object isn't found
return Ok(results);
}
unsafe {
ptr = ptr.add(get_length(
slice::from_raw_parts(ptr, buf.as_ptr() as usize + buf.len() - ptr as usize),
&mut len,
));
}
// check that decoded length represents object contents
if len > cb_buf - (ptr as isize - buf.as_mut_ptr() as isize) as usize {
return Ok(results);
}
unsafe {
ptr::copy(ptr, p_data.as_mut_ptr().add(offset), len);
}
offset += len;
match MsRoots::new(&p_data[..offset]) {
Ok(msroots) => results.push(msroots),
Err(res) => error!("error parsing msroots: {:?}", res),
}
if tag == TAG_MSROOTS_END {
break;
}
}
Ok(results)
}
/// Write `msroots` file to YubiKey
pub fn write(&self, yubikey: &mut YubiKey) -> Result<(), Error> {
let mut buf = [0u8; CB_OBJ_MAX];
let mut offset: usize;
let mut data_offset: usize = 0;
let mut data_chunk: usize;
let data = &self.0;
let data_len = data.len();
let n_objs: usize;
let cb_obj_max = yubikey.obj_size_max();
let txn = yubikey.begin_transaction()?;
if data_len == 0 {
return txn.save_object(YKPIV_OBJ_MSROOTS1, &[]);
}
// Calculate number of objects required to store blob
n_objs = (data_len / (cb_obj_max - CB_OBJ_TAG_MAX)) + 1;
if n_objs > 5 {
return Err(Error::SizeError);
}
for i in 0..n_objs {
offset = 0;
data_chunk = if cb_obj_max - CB_OBJ_TAG_MAX < data_len - data_offset {
cb_obj_max - CB_OBJ_TAG_MAX
} else {
data_len - data_offset
};
buf[offset] = if i == n_objs - 1 {
TAG_MSROOTS_END
} else {
TAG_MSROOTS_MID
};
offset += 1;
offset += set_length(&mut buf[offset..], data_chunk);
buf[offset..].copy_from_slice(&data[data_offset..(data_offset + data_chunk)]);
offset += data_chunk;
txn.save_object(YKPIV_OBJ_MSROOTS1 + i as u32, &buf[..offset])?;
data_offset += data_chunk;
}
Ok(())
}
}
impl AsRef<[u8]> for MsRoots {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
+183
View File
@@ -0,0 +1,183 @@
//! Responses to issued commands
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
//
// Copyright (c) 2014-2016 Yubico AB
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::Buffer;
/// Parsed response to a command
pub(crate) struct Response {
/// Status words
status_words: StatusWords,
/// Buffer
buffer: Buffer,
}
impl Response {
/// Parse a response from the given buffer
pub fn from_bytes(mut buffer: Buffer) -> Self {
if buffer.len() >= 2 {
let sw = StatusWords::from(
(buffer[buffer.len() - 2] as u32) << 8 | (buffer[buffer.len() - 1] as u32),
);
let len = buffer.len() - 2;
buffer.truncate(len);
Response {
status_words: sw,
buffer,
}
} else {
Response {
status_words: StatusWords::None,
buffer,
}
}
}
/// Create a new response from the given status words and buffer
pub fn new(status_words: StatusWords, buffer: Buffer) -> Response {
Response {
status_words,
buffer,
}
}
/// Get the [`StatusWords`] for this response.
pub fn status_words(&self) -> StatusWords {
self.status_words
}
/// Get the raw [`StatusWords`] code for this response.
pub fn code(&self) -> u32 {
self.status_words.code()
}
/// Do the status words for this response indicate success?
pub fn is_success(&self) -> bool {
self.status_words.is_success()
}
/// Borrow the response buffer
pub fn buffer(&self) -> &[u8] {
self.buffer.as_ref()
}
/// Consume this response, returning its buffer
pub fn into_buffer(self) -> Buffer {
self.buffer
}
}
impl AsRef<[u8]> for Response {
fn as_ref(&self) -> &[u8] {
self.buffer()
}
}
/// Status Words (SW) are 2-byte values returned by a card command.
///
/// The first byte of a status word is referred to as SW1 and the second byte
/// of a status word is referred to as SW2.
///
/// See NIST special publication 800-73-4, section 5.6:
/// <https://csrc.nist.gov/publications/detail/sp/800-73/4/final>
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum StatusWords {
/// No status words present in response
None,
/// Successful execution
Success,
/// Security status not satisfied
SecurityStatusError,
/// Authentication method blocked
AuthBlockedError,
/// Incorrect parameter in command data field
IncorrectParamError,
//
// Custom Yubico Status Word extensions
//
/// Incorrect card slot error
IncorrectSlotError,
/// Not supported error
NotSupportedError,
/// Other/unrecognized status words
Other(u32),
}
impl StatusWords {
/// Get the numerical response code for these status words
pub fn code(self) -> u32 {
match self {
StatusWords::None => 0,
StatusWords::SecurityStatusError => 0x6982,
StatusWords::AuthBlockedError => 0x6983,
StatusWords::IncorrectParamError => 0x6a80,
StatusWords::IncorrectSlotError => 0x6b00,
StatusWords::NotSupportedError => 0x6d00,
StatusWords::Success => 0x9000,
StatusWords::Other(n) => n,
}
}
/// Do these status words indicate success?
pub fn is_success(self) -> bool {
self == StatusWords::Success
}
}
impl From<u32> for StatusWords {
fn from(sw: u32) -> Self {
match sw {
0x0000 => StatusWords::None,
0x6982 => StatusWords::SecurityStatusError,
0x6983 => StatusWords::AuthBlockedError,
0x6a80 => StatusWords::IncorrectParamError,
0x6b00 => StatusWords::IncorrectSlotError,
0x6d00 => StatusWords::NotSupportedError,
0x9000 => StatusWords::Success,
_ => StatusWords::Other(sw),
}
}
}
impl From<StatusWords> for u32 {
fn from(sw: StatusWords) -> u32 {
sw.code()
}
}
+99
View File
@@ -0,0 +1,99 @@
//! Serialization functions
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
//
// Copyright (c) 2014-2016 Yubico AB
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, ObjectId};
// TODO(tarcieri): refactor these into better serializers/message builders
/// Set length
pub(crate) fn set_length(buffer: &mut [u8], length: usize) -> usize {
if length < 0x80 {
buffer[0] = length as u8;
1
} else if length < 0x100 {
buffer[0] = 0x81;
buffer[1] = length as u8;
2
} else {
buffer[0] = 0x82;
buffer[1] = ((length >> 8) & 0xff) as u8;
buffer[2] = (length & 0xff) as u8;
3
}
}
/// Parse length tag, returning the size of the length tag itself as the
/// returned value, and setting the len parameter to the parsed length.
pub(crate) fn get_length(buffer: &[u8], len: &mut usize) -> usize {
if buffer[0] < 0x81 {
*len = buffer[0] as usize;
1
} else if (buffer[0] & 0x7f) == 1 {
*len = buffer[1] as usize;
2
} else if (buffer[0] & 0x7f) == 2 {
let tmp = buffer[1] as usize;
*len = (tmp << 8) + buffer[2] as usize;
3
} else {
0
}
}
/// Is length valid?
pub(crate) fn has_valid_length(buffer: &[u8], len: usize) -> bool {
(buffer[0] < 0x81 && len > 0)
|| ((buffer[0] & 0x7f) == 1 && len > 1)
|| ((buffer[0] & 0x7f == 2) && (len > 2))
}
/// Set an object ID header value in the given buffer, returning a mutable
/// slice immediately after the header.
///
/// Panics if the buffer is too small to contain the header.
pub(crate) fn set_object(object_id: ObjectId, mut buffer: &mut [u8]) -> &mut [u8] {
buffer[0] = 0x5c;
if object_id == YKPIV_OBJ_DISCOVERY {
buffer[1] = 1;
buffer[2] = YKPIV_OBJ_DISCOVERY as u8;
buffer = &mut buffer[3..];
} else if object_id > 0xffff && object_id <= 0x00ff_ffff {
buffer[1] = 3;
buffer[2] = ((object_id >> 16) & 0xff) as u8;
buffer[3] = ((object_id >> 8) & 0xff) as u8;
buffer[4] = (object_id & 0xff) as u8;
buffer = &mut buffer[5..];
}
buffer
}
+138
View File
@@ -0,0 +1,138 @@
//! Configuration setting values parsed from the environment and config file:
//! `/etc/yubico/yubikeypiv.conf`
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
//
// Copyright (c) 2014-2016 Yubico AB
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/// Default location of the YubiKey PIV configuration file
pub const DEFAULT_CONFIG_FILE: &str = "/etc/yubico/yubikeypiv.conf";
use std::{
env,
fs::File,
io::{BufRead, BufReader},
};
/// Source of how a setting was configured
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Source {
/// User-specified setting
User,
/// Admin-specified setting
Admin,
/// Default setting
Default,
}
/// Setting booleans
#[derive(Copy, Clone, Debug)]
pub struct BoolValue {
/// Boolean value
pub value: bool,
/// Source of the configuration setting (user/admin/default)
pub source: Source,
}
impl BoolValue {
/// Get a [`BoolValue`] value
pub fn get(key: &str, def: bool) -> Self {
let mut setting = get_setting_from_file(key);
if setting.source == Source::Default {
setting = get_setting_from_env(key);
}
if setting.source == Source::Default {
setting.value = def;
}
setting
}
}
/// Get a boolean config value
fn get_setting_from_file(key: &str) -> BoolValue {
let mut setting: BoolValue = BoolValue {
value: false,
source: Source::Default,
};
let file = match File::open(DEFAULT_CONFIG_FILE) {
Ok(f) => f,
Err(_) => return setting,
};
for line in BufReader::new(file).lines() {
let line = match line {
Ok(line) => line,
_ => continue,
};
if line.starts_with('#') || line.starts_with('\r') || line.starts_with('\n') {
continue;
}
let (name, value) = {
let mut parts = line.splitn(1, '=');
let name = parts.next();
let value = parts.next();
match (name, value, parts.next()) {
(Some(name), Some(value), None) => (name.trim(), value.trim()),
_ => continue,
}
};
if name == key {
setting.source = Source::Admin;
setting.value = value == "1" || value == "true";
break;
}
}
setting
}
/// Get a setting boolean from an environment variable
fn get_setting_from_env(key: &str) -> BoolValue {
let mut setting: BoolValue = BoolValue {
value: false,
source: Source::Default,
};
if let Ok(value) = env::var(format!("YUBIKEY_PIV_{}", key)) {
setting.source = Source::User;
setting.value = value == "1" || value == "true";
}
setting
}
+554
View File
@@ -0,0 +1,554 @@
//! YubiKey PC/SC transactions
use crate::{
apdu::APDU,
consts::*,
error::Error,
mgm::MgmKey,
response::{Response, StatusWords},
serialization::*,
yubikey::*,
Buffer, ObjectId,
};
use log::{error, trace};
use std::{convert::TryInto, ptr};
use zeroize::Zeroizing;
/// Exclusive transaction with the YubiKey's PC/SC card.
pub(crate) struct Transaction<'tx> {
inner: pcsc::Transaction<'tx>,
}
impl<'tx> Transaction<'tx> {
/// Create a new transaction with the given card
pub fn new(card: &'tx mut pcsc::Card) -> Result<Self, Error> {
Ok(Transaction {
inner: card.transaction()?,
})
}
/// Get an attribute of the card or card reader.
pub fn get_attribute<'buf>(
&self,
attribute: pcsc::Attribute,
buffer: &'buf mut [u8],
) -> Result<&'buf [u8], Error> {
Ok(self.inner.get_attribute(attribute, buffer)?)
}
/// Transmit a single serialized APDU to the card this transaction is open
/// with and receive a response.
///
/// This is a wrapper for the raw `SCardTransmit` function and operates on
/// single APDU messages at a time. For larger messages that need to be
/// split into multiple APDUs, use the [`Transaction::transfer_data`]
/// method instead.
pub fn transmit(&self, send_buffer: &[u8], recv_len: usize) -> Result<Buffer, Error> {
trace!(">>> {:?}", send_buffer);
let mut recv_buffer = Zeroizing::new(vec![0u8; recv_len]);
let len = self
.inner
.transmit(send_buffer, recv_buffer.as_mut())?
.len();
recv_buffer.truncate(len);
trace!("<<< {:?}", recv_buffer.as_slice());
Ok(recv_buffer)
}
/// Select application.
pub fn select_application(&self) -> Result<(), Error> {
let response = APDU::new(YKPIV_INS_SELECT_APPLICATION)
.p1(0x04)
.data(&AID)
.transmit(self, 0xFF)
.map_err(|e| {
error!("failed communicating with card: '{}'", e);
e
})?;
if !response.is_success() {
error!(
"failed selecting application: {:04x}",
response.status_words().code()
);
return Err(Error::GenericError);
}
Ok(())
}
/// Get the version of the PIV application installed on the YubiKey
pub fn get_version(&self) -> Result<Version, Error> {
// get version from device
let response = APDU::new(YKPIV_INS_GET_VERSION).transmit(self, 261)?;
if !response.is_success() {
return Err(Error::GenericError);
}
if response.buffer().len() < 3 {
return Err(Error::SizeError);
}
Ok(Version {
major: response.buffer()[0],
minor: response.buffer()[1],
patch: response.buffer()[2],
})
}
/// Get YubiKey device serial number
pub fn get_serial(&self, version: Version) -> Result<Serial, Error> {
let yk_applet = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01, 0x01];
let response = if version.major < 5 {
// get serial from neo/yk4 devices using the otp applet
let sw = APDU::new(YKPIV_INS_SELECT_APPLICATION)
.p1(0x04)
.data(&yk_applet)
.transmit(self, 0xFF)?
.status_words();
if !sw.is_success() {
error!("failed selecting yk application: {:04x}", sw.code());
return Err(Error::GenericError);
}
let resp = APDU::new(0x01).p1(0x10).transmit(self, 0xFF)?;
if !resp.is_success() {
error!(
"failed retrieving serial number: {:04x}",
resp.status_words().code()
);
return Err(Error::GenericError);
}
// reselect the PIV applet
let sw = APDU::new(YKPIV_INS_SELECT_APPLICATION)
.p1(0x04)
.data(&AID)
.transmit(self, 0xFF)?
.status_words();
if !sw.is_success() {
error!("failed selecting application: {:04x}", sw.code());
return Err(Error::GenericError);
}
resp
} else {
// get serial from yk5 and later devices using the f8 command
let resp = APDU::new(YKPIV_INS_GET_SERIAL).transmit(self, 0xFF)?;
if !resp.is_success() {
error!(
"failed retrieving serial number: {:04x}",
resp.status_words().code()
);
return Err(Error::GenericError);
}
resp
};
response.buffer()[..4]
.try_into()
.map(|serial| Serial::from(u32::from_be_bytes(serial)))
.map_err(|_| Error::SizeError)
}
/// Verify device PIN.
pub fn verify_pin(&self, pin: &[u8]) -> Result<(), Error> {
// TODO(tarcieri): allow unpadded (with `0xFF`) PIN shorter than CB_PIN_MAX?
if pin.len() != CB_PIN_MAX {
return Err(Error::SizeError);
}
let response = APDU::new(YKPIV_INS_VERIFY)
.params(0x00, 0x80)
.data(pin)
.transmit(self, 261)?;
match response.status_words() {
StatusWords::Success => Ok(()),
StatusWords::AuthBlockedError => Err(Error::WrongPin { tries: 0 }),
StatusWords::Other(sw) if sw >> 8 == 0x63 => Err(Error::WrongPin { tries: sw & 0xf }),
_ => Err(Error::GenericError),
}
}
/// Change the PIN
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 indata = Zeroizing::new([0u8; 16]);
if current_pin.len() > CB_PIN_MAX || new_pin.len() > CB_PIN_MAX {
return Err(Error::SizeError);
}
if action == CHREF_ACT_UNBLOCK_PIN {
templ[1] = YKPIV_INS_RESET_RETRY;
} else if action == CHREF_ACT_CHANGE_PUK {
templ[3] = 0x81;
}
unsafe {
ptr::copy(current_pin.as_ptr(), indata.as_mut_ptr(), current_pin.len());
if current_pin.len() < CB_PIN_MAX {
ptr::write_bytes(
indata.as_mut_ptr().add(current_pin.len()),
0xff,
CB_PIN_MAX - current_pin.len(),
);
}
ptr::copy(
new_pin.as_ptr(),
indata.as_mut_ptr().offset(8),
new_pin.len(),
);
if new_pin.len() < CB_PIN_MAX {
ptr::write_bytes(
indata.as_mut_ptr().offset(8).add(new_pin.len()),
0xff,
CB_PIN_MAX - new_pin.len(),
);
}
}
let status_words = self
.transfer_data(&templ, indata.as_ref(), 0xFF)?
.status_words();
match status_words {
StatusWords::Success => Ok(()),
StatusWords::AuthBlockedError => Err(Error::PinLocked),
StatusWords::Other(sw) if sw >> 8 == 0x63 => Err(Error::WrongPin { tries: sw & 0xf }),
_ => {
error!(
"failed changing pin, token response code: {:x}.",
status_words.code()
);
Err(Error::GenericError)
}
}
}
/// Set the management key (MGM).
pub fn set_mgm_key(&self, new_key: &MgmKey, touch: Option<u8>) -> Result<(), Error> {
let p2 = match touch.unwrap_or_default() {
0 => 0xff,
1 => 0xfe,
_ => {
return Err(Error::GenericError);
}
};
let mut data = [0u8; DES_LEN_3DES + 3];
data[0] = YKPIV_ALGO_3DES;
data[1] = YKPIV_KEY_CARDMGM;
data[2] = DES_LEN_3DES as u8;
data[3..3 + DES_LEN_3DES].copy_from_slice(new_key.as_ref());
let status_words = APDU::new(YKPIV_INS_SET_MGMKEY)
.params(0xff, p2)
.data(&data)
.transmit(self, 261)?
.status_words();
if !status_words.is_success() {
return Err(Error::GenericError);
}
Ok(())
}
/// Perform a YubiKey operation which requires authentication.
///
/// This is the common backend for all public key encryption and signing
/// operations.
// TODO(tarcieri): refactor this to be less gross/coupled.
#[allow(clippy::too_many_arguments)]
pub(crate) fn authenticated_command(
&self,
sign_in: &[u8],
out: &mut [u8],
out_len: &mut usize,
algorithm: u8,
key: u8,
decipher: bool,
) -> Result<(), Error> {
let in_len = sign_in.len();
let mut indata = [0u8; 1024];
let templ = [0, YKPIV_INS_AUTHENTICATE, algorithm, key];
let mut len: usize = 0;
match algorithm {
YKPIV_ALGO_RSA1024 | YKPIV_ALGO_RSA2048 => {
let key_len = if algorithm == YKPIV_ALGO_RSA1024 {
128
} else {
256
};
if in_len != key_len {
return Err(Error::SizeError);
}
}
YKPIV_ALGO_ECCP256 | YKPIV_ALGO_ECCP384 => {
let key_len = if algorithm == YKPIV_ALGO_ECCP256 {
32
} else {
48
};
if (!decipher && (in_len > key_len)) || (decipher && (in_len != (key_len * 2) + 1))
{
return Err(Error::SizeError);
}
}
_ => return Err(Error::AlgorithmError),
}
let bytes = if in_len < 0x80 {
1
} else if in_len < 0xff {
2
} else {
3
};
indata[0] = 0x7c;
let mut offset = 1 + set_length(&mut indata[1..], in_len + bytes + 3);
indata[offset] = 0x82;
indata[offset + 1] = 0x00;
indata[offset + 2] =
if (algorithm == YKPIV_ALGO_ECCP256 || algorithm == YKPIV_ALGO_ECCP384) && decipher {
0x85
} else {
0x81
};
offset += 3;
offset += set_length(&mut indata[offset..], in_len);
indata[offset..(offset + in_len)].copy_from_slice(sign_in);
offset += in_len;
let response = self
.transfer_data(&templ, &indata[..offset], 1024)
.map_err(|e| {
error!("sign command failed to communicate: {}", e);
e
})?;
if !response.is_success() {
error!("failed sign command with code {:x}", response.code());
if response.status_words() == StatusWords::SecurityStatusError {
return Err(Error::AuthenticationError);
} else {
return Err(Error::GenericError);
}
}
let data = response.buffer();
// skip the first 7c tag
if data[0] != 0x7c {
error!("failed parsing signature reply (0x7c byte)");
return Err(Error::ParseError);
}
let mut offset = 1 + get_length(&data[1..], &mut len);
// skip the 82 tag
if data[offset] != 0x82 {
error!("failed parsing signature reply (0x82 byte)");
return Err(Error::ParseError);
}
offset += 1;
offset += get_length(&data[offset..], &mut len);
if len > *out_len {
error!("wrong size on output buffer");
return Err(Error::SizeError);
}
*out_len = len;
out[..len].copy_from_slice(&data[offset..(offset + len)]);
Ok(())
}
/// Send/receive large amounts of data to/from the YubiKey, splitting long
/// messages into smaller APDU-sized messages (using the provided APDU
/// template to construct them), and then sending those via
/// [`Transaction::transmit`].
pub fn transfer_data(
&self,
templ: &[u8],
in_data: &[u8],
max_out: usize,
) -> Result<Response, Error> {
let mut in_offset = 0;
let mut out_data = Zeroizing::new(vec![]);
let mut sw = 0;
loop {
let mut this_size = 0xff;
let cla = if in_offset + 0xff < in_data.len() {
0x10
} else {
this_size = in_data.len() - in_offset;
templ[0]
};
trace!("going to send {} bytes in this go", this_size);
let response = APDU::new(templ[1])
.cla(cla)
.params(templ[2], templ[3])
.data(&in_data[in_offset..(in_offset + this_size)])
.transmit(self, 261)?;
if !response.is_success() && (response.status_words().code() >> 8 != 0x61) {
// TODO(tarcieri): is this really OK?
return Ok(Response::new(sw.into(), out_data));
}
sw = response.status_words().code();
if out_data.len() - response.buffer().len() - 2 > max_out {
error!(
"output buffer too small: wanted to write {}, max was {}",
out_data.len() - response.buffer().len() - 2,
max_out
);
return Err(Error::SizeError);
}
out_data.extend_from_slice(&response.buffer()[..response.buffer().len() - 2]);
in_offset += this_size;
if in_offset >= in_data.len() {
break;
}
}
while sw >> 8 == 0x61 {
trace!(
"The card indicates there is {} bytes more data for us",
sw & 0xff
);
let response = APDU::new(YKPIV_INS_GET_RESPONSE_APDU).transmit(self, 261)?;
sw = response.status_words().code();
if sw != StatusWords::Success.code() && (sw >> 8 != 0x61) {
return Ok(Response::new(sw.into(), Zeroizing::new(vec![])));
}
if out_data.len() + response.buffer().len() - 2 > max_out {
error!(
"output buffer too small: wanted to write {}, max was {}",
out_data.len() + response.buffer().len() - 2,
max_out
);
return Err(Error::SizeError);
}
out_data.extend_from_slice(&response.buffer()[..response.buffer().len() - 2]);
}
Ok(Response::new(sw.into(), out_data))
}
/// Fetch an object
pub fn fetch_object(&self, object_id: ObjectId) -> Result<Buffer, Error> {
let mut indata = [0u8; 5];
let templ = [0, YKPIV_INS_GET_DATA, 0x3f, 0xff];
let mut inlen = indata.len();
let indata_remaining = set_object(object_id, &mut indata);
inlen -= indata_remaining.len();
let response = self.transfer_data(&templ, &indata[..inlen], CB_BUF_MAX)?;
if !response.is_success() {
return Err(Error::GenericError);
}
let data = response.into_buffer();
let mut outlen = 0;
if data.len() < 2 || !has_valid_length(&data[1..], data.len() - 1) {
return Err(Error::SizeError);
}
let offs = get_length(&data[1..], &mut outlen);
if offs == 0 {
return Err(Error::SizeError);
}
if outlen + offs + 1 != data.len() {
error!(
"invalid length indicated in object: total len is {} but indicated length is {}",
data.len(),
outlen
);
return Err(Error::SizeError);
}
Ok(Zeroizing::new(
data[(1 + offs)..(1 + offs + outlen)].to_vec(),
))
}
/// Save an object
pub fn save_object(&self, object_id: ObjectId, indata: &[u8]) -> Result<(), Error> {
let templ = [0, YKPIV_INS_PUT_DATA, 0x3f, 0xff];
// TODO(tarcieri): replace with vector
let mut data = [0u8; CB_BUF_MAX];
if indata.len() > CB_OBJ_MAX {
return Err(Error::SizeError);
}
let mut len = data.len();
let mut data_remaining = set_object(object_id, &mut data);
data_remaining[0] = 0x53;
data_remaining = &mut data_remaining[1..];
let offset = set_length(data_remaining, indata.len());
data_remaining = &mut data_remaining[offset..];
data_remaining[..indata.len()].copy_from_slice(indata);
data_remaining = &mut data_remaining[indata.len()..];
len -= data_remaining.len();
let status_words = self
.transfer_data(&templ, &data[..len], 255)?
.status_words();
match status_words {
StatusWords::Success => Ok(()),
StatusWords::SecurityStatusError => Err(Error::AuthenticationError),
_ => Err(Error::GenericError),
}
}
}
-2127
View File
File diff suppressed because it is too large Load Diff
+491 -1712
View File
File diff suppressed because it is too large Load Diff