diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 1539ba6..e5445ac 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -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 diff --git a/Cargo.toml b/Cargo.toml index c163961..ff12cdd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index 5b31af5..4f46d44 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/apdu.rs b/src/apdu.rs index bdf3601..5a3c2a9 100644 --- a/src/apdu.rs +++ b/src/apdu.rs @@ -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>; - /// 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 { + 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); diff --git a/src/cccid.rs b/src/cccid.rs new file mode 100644 index 0000000..4de4cca --- /dev/null +++ b/src/cccid.rs @@ -0,0 +1,85 @@ +//! Cardholder Capability Container (CCC) ID Support + +// Adapted from 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 { + 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 { + 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[CHUID_GUID_OFFS..(CHUID_GUID_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) + } +} diff --git a/src/certificate.rs b/src/certificate.rs new file mode 100644 index 0000000..a4e0ab8 --- /dev/null +++ b/src/certificate.rs @@ -0,0 +1,186 @@ +//! YubiKey Certificates + +// Adapted from 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 { + 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) -> Result { + 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 { + 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]) +} diff --git a/src/chuid.rs b/src/chuid.rs new file mode 100644 index 0000000..0bfbdce --- /dev/null +++ b/src/chuid.rs @@ -0,0 +1,92 @@ +//! Cardholder Unique Identifier (CHUID) Support + +// Adapted from 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 { + 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 { + 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) + } +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..1524d50 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,126 @@ +//! YubiKey Configuration Values + +// Adapted from 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 { + 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" + ); + } + + config.mgm_type = MgmType::Protected; + } + } + + Ok(config) + } +} diff --git a/src/consts.rs b/src/consts.rs index 5917f11..4321488 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -42,14 +42,23 @@ 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_ATR_MAX: usize = 33; + +pub const CB_BUF_MAX_NEO: usize = 2048; + +pub const CB_ECC_POINTP256: usize = 65; +pub const CB_ECC_POINTP384: usize = 97; + pub const CB_OBJ_MAX: usize = 3063; +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 +66,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 +144,7 @@ pub const YKPIV_INS_SELECT_APPLICATION: u8 = 0xa4; pub const YKPIV_INS_GET_RESPONSE_APDU: u8 = 0xc0; // Yubico vendor specific instructions +// pub const YKPIV_INS_SET_MGMKEY: u8 = 0xff; pub const YKPIV_INS_IMPORT_KEY: u8 = 0xfe; pub const YKPIV_INS_GET_VERSION: u8 = 0xfd; diff --git a/src/container.rs b/src/container.rs new file mode 100644 index 0000000..8a8659a --- /dev/null +++ b/src/container.rs @@ -0,0 +1,227 @@ +//! PIV container records. +//! +//! These appear(?) to be defined in Microsoft's Smart Card Minidriver Specification: +//! + +// Adapted from 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, 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 { + 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; 40]; + 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::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::new(bytes) + } +} diff --git a/src/error.rs b/src/error.rs index 30207ce..c8da1a9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -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, + }, /// 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 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, + } + } } diff --git a/src/internal.rs b/src/internal.rs deleted file mode 100644 index dcd3800..0000000 --- a/src/internal.rs +++ /dev/null @@ -1,235 +0,0 @@ -//! Internal functions (mostly 3DES) - -// Adapted from 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 -} diff --git a/src/key.rs b/src/key.rs new file mode 100644 index 0000000..64ece9a --- /dev/null +++ b/src/key.rs @@ -0,0 +1,373 @@ +//! 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: +// +// +// 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. +/// +// 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 { + 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, 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, + + /// Exponent + exp: Vec, + }, + /// ECC keys + Ecc { + /// ECC algorithm + algorithm: AlgorithmId, + + /// Public curve point (i.e. public key) + point: Vec, + }, +} + +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 { + 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 \ + 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 != 0 { + error!("{} (pin policy not supported?)", err_msg); + } else if touch_policy != 0 { + 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(); + + 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); + } + + // 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) + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 117d781..0ec876b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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: // @@ -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>; diff --git a/src/metadata.rs b/src/metadata.rs new file mode 100644 index 0000000..a1cc249 --- /dev/null +++ b/src/metadata.rs @@ -0,0 +1,261 @@ +//! YubiKey Device Metadata + +// Adapted from 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 data_len = data.len(); + 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 { + return Ok(slice::from_raw_parts(p_temp, cb_temp)); + } + + p_temp = p_temp.add(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(); + 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 { + 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 { + 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() { + 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 + } +} diff --git a/src/mgm.rs b/src/mgm.rs new file mode 100644 index 0000000..ce4f561 --- /dev/null +++ b/src/mgm.rs @@ -0,0 +1,363 @@ +//! Management Key (MGM) for authenticating to the YubiKey management applet + +// Adapted from 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 { + 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 { + 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 { + 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 { + 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::>(pin, &salt, ITER_MGM_PBKDF2, &mut mgm); + + MgmKey::from_bytes(mgm) + } + + /// Get protected management key (MGM) + pub fn get_protected(yubikey: &mut YubiKey) -> Result { + 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) -> 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; YKPIV_OBJ_MAX_SIZE]); + + 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 = YKPIV_OBJ_MAX_SIZE; + + 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::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 +} diff --git a/src/msroots.rs b/src/msroots.rs new file mode 100644 index 0000000..afdedcd --- /dev/null +++ b/src/msroots.rs @@ -0,0 +1,176 @@ +//! `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: +//! + +// Adapted from 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); + +impl MsRoots { + /// Initialize a local certificate struct from the given bytebuffer + pub fn new(msroots: impl AsRef<[u8]>) -> Result { + Ok(MsRoots(msroots.as_ref().into())) + } + + /// Read `msroots` file from YubiKey + pub fn read(yubikey: &mut YubiKey) -> Result, 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 != TAG_MSROOTS_MID && (tag != TAG_MSROOTS_END || object_id == YKPIV_OBJ_MSROOTS5) + { + // 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, &[]); + } + + n_objs = (data_len / (cb_obj_max - 4)) + 1; + + if n_objs > 5 { + return Err(Error::SizeError); + } + + for i in 0..n_objs { + offset = 0; + + data_chunk = if cb_obj_max - 4 < data_len - data_offset { + cb_obj_max - 4 + } 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() + } +} diff --git a/src/response.rs b/src/response.rs new file mode 100644 index 0000000..2b80953 --- /dev/null +++ b/src/response.rs @@ -0,0 +1,183 @@ +//! Responses to issued commands + +// Adapted from 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: +/// +#[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 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 for u32 { + fn from(sw: StatusWords) -> u32 { + sw.code() + } +} diff --git a/src/serialization.rs b/src/serialization.rs new file mode 100644 index 0000000..614f97d --- /dev/null +++ b/src/serialization.rs @@ -0,0 +1,99 @@ +//! Serialization functions + +// Adapted from 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 +} diff --git a/src/settings.rs b/src/settings.rs new file mode 100644 index 0000000..f1d7563 --- /dev/null +++ b/src/settings.rs @@ -0,0 +1,138 @@ +//! Configuration setting values parsed from the environment and config file: +//! `/etc/yubico/yubikeypiv.conf` + +// Adapted from 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 +} diff --git a/src/transaction.rs b/src/transaction.rs new file mode 100644 index 0000000..c3bb244 --- /dev/null +++ b/src/transaction.rs @@ -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 { + 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 { + 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 { + // 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 { + 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> { + 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() > 8 || new_pin.len() > 8 { + 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() < 8 { + ptr::write_bytes( + indata.as_mut_ptr().add(current_pin.len()), + 0xff, + 8 - current_pin.len(), + ); + } + + ptr::copy( + new_pin.as_ptr(), + indata.as_mut_ptr().offset(8), + new_pin.len(), + ); + + if new_pin.len() < 8 { + ptr::write_bytes( + indata.as_mut_ptr().offset(8).add(new_pin.len()), + 0xff, + 8 - new_pin.len(), + ); + } + } + + let status_words = self + .transfer_data(&templ, indata.as_ref(), 255)? + .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) -> 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 { + 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 to 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) { + // TODO(tarcieri): is this really OK? + 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 { + 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], YKPIV_OBJ_MAX_SIZE)?; + + if !response.is_success() { + return Err(Error::GenericError); + } + + let mut 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); + } + + // Remove the length tag + data.remove(0); + Ok(data) + } + + /// 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; YKPIV_OBJ_MAX_SIZE]; + + 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), + } + } +} diff --git a/src/util.rs b/src/util.rs deleted file mode 100644 index 3613bb7..0000000 --- a/src/util.rs +++ /dev/null @@ -1,2127 +0,0 @@ -//! Utility functions - -// Adapted from 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. - -#![allow(non_camel_case_types, non_snake_case)] -#![allow(clippy::missing_safety_doc, clippy::too_many_arguments)] - -use crate::{consts::*, error::Error, internal::*, yubikey::*}; -use getrandom::getrandom; -use hmac::Hmac; -use libc::{calloc, free, memcpy, memmove, realloc, time}; -use log::{error, warn}; -use pbkdf2::pbkdf2; -use sha1::Sha1; -use std::ops::DerefMut; -use std::{mem, os::raw::c_void, ptr}; -use zeroize::{Zeroize, Zeroizing}; - -/// 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) -pub 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 Capability Container (CCC) Template -/// -/// f0: Card Identifier -/// -/// - 0xa000000116 == GSC-IS RID -/// - 0xff == Manufacturer ID (dummy) -/// - 0x02 == Card type (javaCard) -/// - next 14 bytes: card ID -pub static mut 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, -]; - -/// Card ID -#[derive(Copy, Clone, Debug)] -pub struct CardId([u8; 16]); - -/// Get Card ID -pub unsafe fn ykpiv_util_get_cardid( - yubikey: &mut YubiKey, - cardid: *mut CardId, -) -> Result<(), Error> { - let mut buf = [0u8; CB_OBJ_MAX]; - let mut len = buf.len(); - let mut res = Ok(()); - - if cardid.is_null() { - return Err(Error::GenericError); - } - - yubikey._ykpiv_begin_transaction()?; - - if yubikey._ykpiv_ensure_application_selected().is_ok() { - res = yubikey._ykpiv_fetch_object(YKPIV_OBJ_CHUID as i32, buf.as_mut_ptr(), &mut len); - - if res.is_ok() { - if len != CHUID_TMPL.len() { - res = Err(Error::GenericError); - } else { - memcpy( - (*cardid).0.as_mut_ptr() as (*mut c_void), - buf.as_mut_ptr().add(CHUID_GUID_OFFS) as (*const c_void), - YKPIV_CARDID_SIZE, - ); - } - } - } - - let _ = yubikey._ykpiv_end_transaction(); - res -} - -/// Set Card ID -pub unsafe fn ykpiv_util_set_cardid( - yubikey: &mut YubiKey, - cardid: *const CardId, -) -> Result<(), Error> { - let mut id = [0u8; YKPIV_CARDID_SIZE]; - let mut buf = [0u8; CHUID_TMPL.len()]; - let mut res = Ok(()); - - if cardid.is_null() { - getrandom(&mut id).map_err(|_| Error::RandomnessError)?; - } else { - memcpy( - id.as_mut_ptr() as (*mut c_void), - (*cardid).0.as_ptr() as (*const c_void), - id.len(), - ); - } - - yubikey._ykpiv_begin_transaction()?; - - if yubikey._ykpiv_ensure_application_selected().is_ok() { - memcpy( - buf.as_mut_ptr() as *mut c_void, - CHUID_TMPL.as_ptr() as *const c_void, - buf.len(), - ); - - memcpy( - buf.as_mut_ptr().add(CHUID_GUID_OFFS) as *mut c_void, - id.as_mut_ptr() as *const c_void, - id.len(), - ); - - res = - yubikey._ykpiv_save_object(YKPIV_OBJ_CHUID as i32, buf.as_mut_ptr(), CHUID_TMPL.len()); - } - - let _ = yubikey._ykpiv_end_transaction(); - res -} - -/// Cardholder Capability Container (CCC) Identifier -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct CCCID([u8; 14]); - -/// Get Cardholder Capability Container (CCC) ID -pub unsafe fn ykpiv_util_get_cccid(yubikey: &mut YubiKey, ccc: *mut CCCID) -> Result<(), Error> { - let mut res = Ok(()); - let mut buf = [0u8; CB_OBJ_MAX]; - let mut len = buf.len(); - - if ccc.is_null() { - return Err(Error::GenericError); - } - - yubikey._ykpiv_begin_transaction()?; - - if yubikey._ykpiv_ensure_application_selected().is_ok() { - res = yubikey._ykpiv_fetch_object(YKPIV_OBJ_CAPABILITY as i32, buf.as_mut_ptr(), &mut len); - - if res.is_ok() { - if len != CCC_TMPL.len() { - let _ = yubikey._ykpiv_end_transaction(); - return Err(Error::GenericError); - } - - memcpy( - (*ccc).0.as_mut_ptr() as (*mut c_void), - buf.as_mut_ptr().offset(9) as (*const c_void), - YKPIV_CCCID_SIZE, - ); - } - } - - res -} - -/// Get Cardholder Capability Container (CCC) ID -pub unsafe fn ykpiv_util_set_cccid(yubikey: &mut YubiKey, ccc: *const CCCID) -> Result<(), Error> { - let mut res = Ok(()); - let mut id = [0u8; 14]; - let mut buf = [0u8; 51]; - let len: usize; - - if ccc.is_null() { - getrandom(&mut id).map_err(|_| Error::RandomnessError)?; - } else { - memcpy( - id.as_mut_ptr() as (*mut c_void), - (*ccc).0.as_ptr() as (*const c_void), - id.len(), - ); - } - - yubikey._ykpiv_begin_transaction()?; - - if yubikey._ykpiv_ensure_application_selected().is_ok() { - len = 51; - - memcpy( - buf.as_mut_ptr() as *mut c_void, - CCC_TMPL.as_ptr() as *const c_void, - len, - ); - - memcpy( - buf.as_mut_ptr().offset(9) as (*mut c_void), - id.as_mut_ptr() as (*const c_void), - 14, - ); - - res = yubikey._ykpiv_save_object(YKPIV_OBJ_CAPABILITY as i32, buf.as_mut_ptr(), len); - } - - let _ = yubikey._ykpiv_end_transaction(); - res -} - -/// Get YubiKey device model -pub unsafe fn ykpiv_util_devicemodel(yubikey: &mut YubiKey) -> u32 { - if yubikey.context == 0 || yubikey.context == -1 { - DEVTYPE_UNKNOWN - } else if yubikey.is_neo { - DEVTYPE_NEOr3 - } else { - DEVTYPE_YK4 - } -} - -/// Personal Identity Verification (PIV) keys -#[derive(Copy, Clone, Debug)] -pub struct YkPivKey { - /// Card slot - slot: u8, - - /// Length of cert - cert_len: u16, - - /// Cert - cert: [u8; 1], -} - -/// 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, -]; - -/// List Personal Identity Verification (PIV) keys -// TODO(tarcieri): fix clippy alignment warnings -#[allow(clippy::cast_ptr_alignment)] -pub unsafe fn ykpiv_util_list_keys( - yubikey: &mut YubiKey, - key_count: *mut u8, - data: *mut *mut YkPivKey, - data_len: *mut usize, -) -> Result<(), Error> { - let mut _currentBlock; - let mut res = Ok(()); - let mut p_key: *mut YkPivKey; - let mut p_data: *mut u8 = ptr::null_mut(); - let mut p_temp: *mut u8; - let mut cb_data: usize; - let mut offset: usize = 0; - let mut buf = [0u8; YKPIV_OBJ_MAX_SIZE]; - let mut cb_buf: usize; - let mut i: usize; - let mut cb_realloc: usize; - let CB_PAGE: usize = 4096; - - if data.is_null() || data_len.is_null() || key_count.is_null() { - return Err(Error::GenericError); - } - - yubikey._ykpiv_begin_transaction()?; - - if yubikey._ykpiv_ensure_application_selected().is_ok() { - *key_count = 0; - *data = ptr::null_mut(); - *data_len = 0; - - p_data = calloc(CB_PAGE, 1) as (*mut u8); - - if p_data.is_null() { - let _ = yubikey._ykpiv_end_transaction(); - return Err(Error::MemoryError); - } - - cb_data = CB_PAGE; - i = 0; - - loop { - if i >= mem::size_of::<*const u8>() { - _currentBlock = 6; - break; - } - - cb_buf = buf.len(); - res = _read_certificate(yubikey, SLOTS[i], buf.as_mut_ptr(), &mut cb_buf); - - if res.is_ok() && (cb_buf > 0) { - cb_realloc = if mem::size_of::() - .wrapping_add(cb_buf) - .wrapping_sub(1) - > cb_data.wrapping_sub(offset) - { - (if mem::size_of::() - .wrapping_add(cb_buf) - .wrapping_sub(1) - .wrapping_sub(cb_data.wrapping_sub(offset)) - > CB_PAGE - { - mem::size_of::() - .wrapping_add(cb_buf) - .wrapping_sub(1) - .wrapping_sub(cb_data.wrapping_sub(offset)) - } else { - CB_PAGE - }) - } else { - 0 - }; - - if cb_realloc != 0 { - if { - p_temp = realloc(p_data as (*mut c_void), cb_data.wrapping_add(cb_realloc)) - as (*mut u8); - p_temp - } - .is_null() - { - _currentBlock = 15; - break; - } - p_data = p_temp; - } - - cb_data = cb_data.wrapping_add(cb_realloc); - p_key = p_data.add(offset) as *mut YkPivKey; - (*p_key).slot = SLOTS[i]; - (*p_key).cert_len = cb_buf as (u16); - - memcpy( - (*p_key).cert.as_mut_ptr() as *mut c_void, - buf.as_mut_ptr() as *const c_void, - cb_buf, - ); - - offset = offset.wrapping_add( - mem::size_of::() - .wrapping_add(cb_buf) - .wrapping_sub(1), - ); - - *key_count = (*key_count as i32 + 1) as u8; - } - - i += 1; - } - - if _currentBlock == 6 { - *data = p_data as *mut YkPivKey; - p_data = ptr::null_mut(); - if !data_len.is_null() { - *data_len = offset; - } - res = Ok(()); - } else { - res = Err(Error::MemoryError); - } - } - - if !p_data.is_null() { - free(p_data as (*mut c_void)); - } - - let _ = yubikey._ykpiv_end_transaction(); - res -} - -/// Read certificate -pub unsafe fn ykpiv_util_read_cert( - yubikey: &mut YubiKey, - slot: u8, - data: *mut *mut u8, - data_len: *mut usize, -) -> Result<(), Error> { - let mut res = Ok(()); - let mut buf = [0u8; YKPIV_OBJ_MAX_SIZE]; - let mut cb_buf: usize = buf.len(); - - if data.is_null() || data_len.is_null() { - return Err(Error::GenericError); - } - - yubikey._ykpiv_begin_transaction()?; - - if yubikey._ykpiv_ensure_application_selected().is_ok() { - *data = ptr::null_mut(); - *data_len = 0; - res = _read_certificate(yubikey, slot, buf.as_mut_ptr(), &mut cb_buf); - if res.is_ok() { - if cb_buf == 0 { - *data = ptr::null_mut(); - *data_len = 0; - } else if { - *data = calloc(cb_buf, 1) as *mut u8; - *data - } - .is_null() - { - res = Err(Error::MemoryError); - } else { - memcpy( - *data as (*mut c_void), - buf.as_mut_ptr() as (*const c_void), - cb_buf, - ); - *data_len = cb_buf; - } - } - } - - let _ = yubikey._ykpiv_end_transaction(); - res -} - -/// Write certificate -pub unsafe fn ykpiv_util_write_cert( - yubikey: &mut YubiKey, - slot: u8, - data: *mut u8, - data_len: usize, - certinfo: u8, -) -> Result<(), Error> { - let mut res = Ok(()); - - yubikey._ykpiv_begin_transaction()?; - - if yubikey._ykpiv_ensure_application_selected().is_ok() { - res = _write_certificate(yubikey, slot, data, data_len, certinfo); - } - - let _ = yubikey._ykpiv_end_transaction(); - res -} - -/// Delete certificate -pub unsafe fn ykpiv_util_delete_cert(yubikey: &mut YubiKey, slot: u8) -> Result<(), Error> { - ykpiv_util_write_cert(yubikey, slot, ptr::null_mut(), 0, 0) -} - -/// Block PUK -pub unsafe fn ykpiv_util_block_puk(yubikey: &mut YubiKey) -> Result<(), Error> { - let mut res = Ok(()); - let mut puk = [0x30, 0x42, 0x41, 0x44, 0x46, 0x30, 0x30, 0x44]; - let mut tries_remaining: i32 = -1; - let mut data = [0u8; YKPIV_OBJ_MAX_SIZE]; - let mut cb_data: usize = data.len(); - let mut p_item: *mut u8 = ptr::null_mut(); - let mut cb_item: usize = 0; - let mut flags: u8 = 0; - - yubikey._ykpiv_begin_transaction()?; - - if yubikey._ykpiv_ensure_application_selected().is_err() { - let _ = yubikey._ykpiv_end_transaction(); - return Ok(()); - } - - while tries_remaining != 0 { - res = yubikey.ykpiv_change_puk(puk.as_ptr(), puk.len(), puk.as_ptr(), puk.len()); - - match res { - Ok(()) => puk[0] += 1, - Err(Error::WrongPin { tries }) => { - tries_remaining = tries; - continue; - } - Err(e) => { - if e != Error::PinLocked { - continue; - } - tries_remaining = 0; - res = Ok(()); - } - } - } - - if _read_metadata(yubikey, TAG_ADMIN, data.as_mut_ptr(), &mut cb_data).is_ok() - && _get_metadata_item( - data.as_mut_ptr(), - cb_data, - TAG_ADMIN_FLAGS_1, - &mut p_item, - &mut cb_item, - ) - .is_ok() - { - if cb_item == mem::size_of_val(&flags) { - // TODO(tarcieri): get rid of memcpy and pointers, replace with slices! - #[allow(trivial_casts)] - memcpy( - &mut flags as *mut u8 as *mut c_void, - p_item as (*const c_void), - cb_item, - ); - } else { - error!("admin flags exist, but are incorrect size = {}", cb_item); - } - } - - flags |= ADMIN_FLAGS_1_PUK_BLOCKED; - - if _set_metadata_item( - data.as_mut_ptr(), - &mut cb_data, - CB_OBJ_MAX, - TAG_ADMIN_FLAGS_1, - &mut flags, - 1, - ) - .is_ok() - { - if _write_metadata(yubikey, TAG_ADMIN, data.as_mut_ptr(), cb_data).is_err() { - error!("could not write admin metadata"); - } - } else { - error!("could not set admin flags"); - } - - let _ = yubikey._ykpiv_end_transaction(); - res -} - -/// PIV container -// TODO(tarcieri): dead code? -#[allow(dead_code)] -#[derive(Copy, Clone)] -pub struct YkPivContainer { - /// Name - name: [i32; 40], - - /// Card slot - slot: u8, - - /// Key spec - key_spec: u8, - - /// Key size in bits - key_size_bits: u16, - - /// Flags - flags: u8, - - /// PIN ID - pin_id: u8, - - /// Associated ECHD container - associated_echd_container: u8, - - /// Cert fingerprint - cert_fingerprint: [u8; 20], -} - -/// Read mscmap -pub unsafe fn ykpiv_util_read_mscmap( - yubikey: &mut YubiKey, - containers: *mut *mut YkPivContainer, - n_containers: *mut usize, -) -> Result<(), Error> { - let mut res = Ok(()); - let mut buf = [0u8; YKPIV_OBJ_MAX_SIZE]; - let mut cb_buf: usize = buf.len(); - let mut len: usize = 0; - let mut ptr: *mut u8; - - if containers.is_null() || n_containers.is_null() { - // TODO(str4d): Should this really continue on here? - res = Err(Error::GenericError); - } - - yubikey._ykpiv_begin_transaction()?; - - if yubikey._ykpiv_ensure_application_selected().is_ok() { - *containers = ptr::null_mut(); - *n_containers = 0; - - res = yubikey._ykpiv_fetch_object(YKPIV_OBJ_MSCMAP as i32, buf.as_mut_ptr(), &mut cb_buf); - - if res.is_err() { - let _ = yubikey._ykpiv_end_transaction(); - return res; - } - - ptr = buf.as_mut_ptr(); - - if cb_buf < CB_OBJ_TAG_MIN { - let _ = yubikey._ykpiv_end_transaction(); - return Ok(()); - } - - if *ptr == TAG_MSCMAP { - ptr = ptr.add(1); - ptr = ptr.add(_ykpiv_get_length(ptr, &mut len)); - - if len > cb_buf - (ptr as isize - buf.as_mut_ptr() as isize) as usize { - let _ = yubikey._ykpiv_end_transaction(); - return Ok(()); - } - - *containers = calloc(len, 1) as (*mut YkPivContainer); - - if (*containers).is_null() { - res = Err(Error::MemoryError); - } else { - memcpy(*containers as (*mut c_void), ptr as (*const c_void), len); - *n_containers = len.wrapping_div(mem::size_of::()); - } - } - } - - res -} - -/// Get max object size -unsafe fn _obj_size_max(yubikey: &mut YubiKey) -> usize { - if yubikey.is_neo { - 2048 - 9 - } else { - CB_OBJ_MAX - } -} - -/// Write mscmap -pub unsafe fn ykpiv_util_write_mscmap( - yubikey: &mut YubiKey, - containers: *mut YkPivContainer, - n_containers: usize, -) -> Result<(), Error> { - let mut res = Ok(()); - let mut buf = [0u8; CB_OBJ_MAX]; - let mut offset: usize = 0; - let data_len: usize = n_containers.wrapping_mul(mem::size_of::()); - - yubikey._ykpiv_begin_transaction()?; - - if yubikey._ykpiv_ensure_application_selected().is_ok() { - if containers.is_null() || n_containers == 0 { - if !containers.is_null() || n_containers != 0 { - res = Err(Error::GenericError); - } else { - res = yubikey._ykpiv_save_object(YKPIV_OBJ_MSCMAP as i32, ptr::null_mut(), 0); - } - - let _ = yubikey._ykpiv_end_transaction(); - return res; - } - - let req_len = 1 + _ykpiv_set_length(buf.as_mut_ptr(), data_len) + data_len; - - if req_len > _obj_size_max(yubikey) { - let _ = yubikey._ykpiv_end_transaction(); - return Err(Error::SizeError); - } - - buf[offset] = TAG_MSCMAP; - offset += 1; - offset += _ykpiv_set_length(buf.as_mut_ptr().add(offset), data_len); - memcpy( - buf.as_mut_ptr().add(offset) as (*mut c_void), - containers as (*mut u8) as (*const c_void), - data_len, - ); - offset = offset.wrapping_add(data_len); - res = yubikey._ykpiv_save_object(YKPIV_OBJ_MSCMAP as i32, buf.as_mut_ptr(), offset); - } - - let _ = yubikey._ykpiv_end_transaction(); - res -} - -/// Read msroots -pub unsafe fn ykpiv_util_read_msroots( - yubikey: &mut YubiKey, - data: *mut *mut u8, - data_len: *mut usize, -) -> Result<(), Error> { - let mut _currentBlock = 0; - let mut res; - let mut buf = [0u8; YKPIV_OBJ_MAX_SIZE]; - let mut cb_buf: usize; - let mut len: usize = 0; - let mut ptr: *mut u8; - let mut tag: u8; - let mut p_data: *mut u8; - let mut p_temp: *mut u8; - let mut cb_data: usize; - let mut cb_realloc: usize; - let mut offset: usize = 0; - - if data.is_null() || data_len.is_null() { - return Err(Error::GenericError); - } - - yubikey._ykpiv_begin_transaction()?; - - res = yubikey._ykpiv_ensure_application_selected(); - if res.is_err() { - let _ = yubikey._ykpiv_end_transaction(); - return res; - } - - *data = ptr::null_mut(); - *data_len = 0; - - // allocate first page - cb_data = _obj_size_max(yubikey); - p_data = calloc(cb_data, 1) as (*mut u8); - - if p_data.is_null() { - let _ = yubikey._ykpiv_end_transaction(); - return Err(Error::MemoryError); - } - - for object_id in YKPIV_OBJ_MSROOTS1..YKPIV_OBJ_MSROOTS5 { - cb_buf = buf.len(); - - res = yubikey._ykpiv_fetch_object(object_id as i32, buf.as_mut_ptr(), &mut cb_buf); - - if res.is_err() { - let _ = yubikey._ykpiv_end_transaction(); - return res; - } - - ptr = buf.as_mut_ptr(); - if cb_buf < CB_OBJ_TAG_MIN { - let _ = yubikey._ykpiv_end_transaction(); - return Ok(()); - } - - tag = *ptr; - ptr = ptr.add(1); - - if tag != TAG_MSROOTS_MID && (tag != TAG_MSROOTS_END || object_id == YKPIV_OBJ_MSROOTS5) { - // the current object doesn't contain a valid part of a msroots file - let _ = yubikey._ykpiv_end_transaction(); - - // treat condition as object isn't found - return Ok(()); - } - - ptr = ptr.add(_ykpiv_get_length(ptr, &mut len)); - - // check that decoded length represents object contents - if len > cb_buf - (ptr as isize - buf.as_mut_ptr() as isize) as usize { - let _ = yubikey._ykpiv_end_transaction(); - return Ok(()); - } - - cb_realloc = if len > cb_data.wrapping_sub(offset) { - len.wrapping_sub(cb_data.wrapping_sub(offset)) - } else { - 0 - }; - - if cb_realloc != 0 { - if { - p_temp = - realloc(p_data as (*mut c_void), cb_data.wrapping_add(cb_realloc)) as (*mut u8); - p_temp - } - .is_null() - { - // realloc failed, pData will be freed in cleanup - _currentBlock = 16; - break; - } - p_data = p_temp; - } - - cb_data = cb_data.wrapping_add(cb_realloc); - - memcpy( - p_data.add(offset) as (*mut c_void), - ptr as (*const c_void), - len, - ); - - offset = offset.wrapping_add(len); - - if tag == TAG_MSROOTS_END { - _currentBlock = 15; - break; - } - } - - if _currentBlock == 15 { - *data = p_data; - p_data = ptr::null_mut(); - *data_len = offset; - res = Ok(()); - } else if _currentBlock == 16 { - res = Err(Error::MemoryError); - } else if _currentBlock != 21 { - res = Ok(()); - } - - if !p_data.is_null() { - free(p_data as (*mut c_void)); - } - - let _ = yubikey._ykpiv_end_transaction(); - res -} - -/// Write msroots -pub unsafe fn ykpiv_util_write_msroots( - yubikey: &mut YubiKey, - data: *mut u8, - data_len: usize, -) -> Result<(), Error> { - let mut res = Ok(()); - let mut buf = [0u8; CB_OBJ_MAX]; - let mut offset: usize; - let mut data_offset: usize = 0; - let mut data_chunk: usize; - let n_objs: usize; - let cb_obj_max = _obj_size_max(yubikey); - - yubikey._ykpiv_begin_transaction()?; - - if yubikey._ykpiv_ensure_application_selected().is_ok() { - if data.is_null() || data_len == 0 { - if !data.is_null() || data_len != 0 { - res = Err(Error::GenericError); - } else { - res = yubikey._ykpiv_save_object(YKPIV_OBJ_MSROOTS1 as i32, ptr::null_mut(), 0); - } - - let _ = yubikey._ykpiv_end_transaction(); - return res; - } - - n_objs = (data_len / (cb_obj_max - 4)) + 1; - - if n_objs > 5 { - let _ = yubikey._ykpiv_end_transaction(); - return Err(Error::SizeError); - } - - for i in 0..n_objs { - offset = 0; - - data_chunk = if cb_obj_max - 4 < data_len - data_offset { - cb_obj_max - 4 - } else { - data_len - data_offset - }; - - buf[offset] = if i == n_objs - 1 { - TAG_MSROOTS_END - } else { - TAG_MSROOTS_MID - }; - - offset += 1; - offset += _ykpiv_set_length(buf.as_mut_ptr().add(offset), data_chunk); - - memcpy( - buf.as_mut_ptr().add(offset) as *mut c_void, - data.add(data_offset) as *const c_void, - data_chunk, - ); - - offset = offset.wrapping_add(data_chunk); - - res = yubikey._ykpiv_save_object( - (YKPIV_OBJ_MSROOTS1 + i as u32) as i32, - buf.as_mut_ptr(), - offset, - ); - - if res.is_err() { - break; - } - - data_offset = data_offset.wrapping_add(data_chunk); - } - } - - let _ = yubikey._ykpiv_end_transaction(); - res -} - -// 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."; - -/// Generate key -#[allow(clippy::cognitive_complexity)] -pub unsafe fn ykpiv_util_generate_key( - yubikey: &mut YubiKey, - slot: u8, - algorithm: u8, - pin_policy: u8, - touch_policy: u8, - modulus: *mut *mut u8, - modulus_len: *mut usize, - exp: *mut *mut u8, - exp_len: *mut usize, - point: *mut *mut u8, - point_len: *mut usize, -) -> Result<(), Error> { - let mut res = Ok(()); - let mut in_data = [0u8; 11]; - let mut in_ptr = in_data.as_mut_ptr(); - let mut data = [0u8; 1024]; - let mut templ = [0, YKPIV_INS_GENERATE_ASYMMETRIC, 0, 0]; - let mut recv_len = data.len(); - let mut sw: i32 = 0; - let mut ptr_modulus: *mut u8 = ptr::null_mut(); - let cb_modulus: usize; - let mut ptr_exp: *mut u8 = ptr::null_mut(); - let cb_exp: usize; - let mut ptr_point: *mut u8 = ptr::null_mut(); - let cb_point: usize; - let setting_roca: SettingBool; - - if ykpiv_util_devicemodel(yubikey) == DEVTYPE_YK4 - && (algorithm == YKPIV_ALGO_RSA1024 || algorithm == YKPIV_ALGO_RSA2048) - && yubikey.ver.major == 4 - && (yubikey.ver.minor < 3 || yubikey.ver.minor == 3 && (yubikey.ver.patch < 5)) - { - setting_roca = setting_get_bool(SZ_SETTING_ROCA, true); - - let psz_msg = match setting_roca.source { - SettingSource::User => { - if setting_roca.value { - SZ_ROCA_ALLOW_USER - } else { - SZ_ROCA_BLOCK_USER - } - } - SettingSource::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 \ - 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 => { - if point.is_null() || point_len.is_null() { - error!("invalid output parameter for ECC algorithm"); - return Err(Error::GenericError); - } - - *point = ptr::null_mut(); - *point_len = 0; - } - YKPIV_ALGO_ECCP256 | YKPIV_ALGO_ECCP384 => { - if modulus.is_null() || modulus_len.is_null() || exp.is_null() || exp_len.is_null() { - error!("invalid output parameter for RSA algorithm"); - return Err(Error::GenericError); - } - - *modulus = ptr::null_mut(); - *modulus_len = 0; - *exp = ptr::null_mut(); - *exp_len = 0; - } - _ => { - error!("invalid algorithm specified"); - return Err(Error::GenericError); - } - } - - yubikey._ykpiv_begin_transaction()?; - - if yubikey._ykpiv_ensure_application_selected().is_ok() { - templ[3] = slot; - - *in_ptr = 0xac; - *in_ptr.add(1) = 3; - *in_ptr.add(2) = YKPIV_ALGO_TAG; - *in_ptr.add(3) = 1; - *in_ptr.add(4) = algorithm; - in_ptr = in_ptr.add(5); - - if in_data[4] == 0 { - res = Err(Error::AlgorithmError); - error!("unexpected algorithm"); - } else { - if pin_policy != YKPIV_PINPOLICY_DEFAULT { - in_data[1] += 3; - *in_ptr = YKPIV_PINPOLICY_TAG; - *in_ptr.add(1) = 1; - *in_ptr.add(2) = pin_policy; - in_ptr = in_ptr.add(3); - } - - if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT { - in_data[1] += 3; - *in_ptr = YKPIV_TOUCHPOLICY_TAG; - *in_ptr.add(1) = 1; - *in_ptr.add(2) = touch_policy; - in_ptr = in_ptr.add(3); - } - - res = yubikey._ykpiv_transfer_data( - templ.as_ptr(), - in_data.as_mut_ptr(), - in_ptr as isize - in_data.as_mut_ptr() as isize, - data.as_mut_ptr(), - &mut recv_len, - &mut sw, - ); - - if res.is_err() { - error!("failed to communicate"); - } else if sw != SW_SUCCESS { - let err_msg = "failed to generate new key"; - - match sw { - SW_ERR_INCORRECT_SLOT => { - res = Err(Error::KeyError); - error!("{} (incorrect slot)", err_msg); - } - SW_ERR_INCORRECT_PARAM => { - res = Err(Error::AlgorithmError); - - if pin_policy != 0 { - error!("{} (pin policy not supported?)", err_msg); - } else if touch_policy != 0 { - error!("{} (touch policy not supported?)", err_msg); - } else { - error!("{} (algorithm not supported?)", err_msg); - } - } - SW_ERR_SECURITY_STATUS => { - res = Err(Error::AuthenticationError); - error!("{} (not authenticated)", err_msg); - } - _ => { - res = Err(Error::GenericError); - error!("{} (error {:x})", err_msg, sw); - } - } - } else if algorithm == YKPIV_ALGO_RSA1024 || algorithm == YKPIV_ALGO_RSA2048 { - let mut data_ptr: *mut u8 = data.as_mut_ptr().offset(5); - let mut len: usize = 0; - - if *data_ptr != TAG_RSA_MODULUS { - error!("Failed to parse public key structure (modulus)"); - res = Err(Error::ParseError); - } else { - data_ptr = data_ptr.add(1); - data_ptr = data_ptr.add(_ykpiv_get_length(data_ptr, &mut len)); - cb_modulus = len; - ptr_modulus = calloc(cb_modulus, 1) as *mut u8; - - if ptr_modulus.is_null() { - error!("failed to allocate memory for modulus"); - res = Err(Error::MemoryError); - } else { - memcpy( - ptr_modulus as *mut c_void, - data_ptr as *const c_void, - cb_modulus, - ); - - data_ptr = data_ptr.add(len); - if *data_ptr != TAG_RSA_EXP { - error!("failed to parse public key structure (public exponent)"); - res = Err(Error::ParseError); - } else { - data_ptr = data_ptr.add(1); - data_ptr = data_ptr.add(_ykpiv_get_length(data_ptr, &mut len)); - cb_exp = len; - ptr_exp = calloc(cb_exp, 1) as *mut u8; - if ptr_exp.is_null() { - error!("failed to allocate memory for public exponent"); - res = Err(Error::MemoryError); - } else { - memcpy( - ptr_exp as (*mut c_void), - data_ptr as (*const c_void), - cb_exp, - ); - - // set output parameters - *modulus = ptr_modulus; - ptr_modulus = ptr::null_mut(); - *modulus_len = cb_modulus; - *exp = ptr_exp; - ptr_exp = ptr::null_mut(); - *exp_len = cb_exp; - } - } - } - } - } else if algorithm == YKPIV_ALGO_ECCP256 || algorithm == YKPIV_ALGO_ECCP384 { - let mut data_ptr: *mut u8 = data.as_mut_ptr().offset(3); - - let len = if algorithm == YKPIV_ALGO_ECCP256 { - CB_ECC_POINTP256 - } else { - CB_ECC_POINTP384 - }; - - let tag = *data_ptr; - data_ptr = data_ptr.add(1); - - if tag != TAG_ECC_POINT { - error!("failed to parse public key structure"); - res = Err(Error::ParseError); - } else { - // the curve point should always be determined by the curve - let len_byte = *data_ptr; - data_ptr = data_ptr.add(1); - - if len_byte as usize != len { - error!("unexpected length"); - res = Err(Error::AlgorithmError); - } else { - cb_point = len; - ptr_point = calloc(cb_point, 1) as (*mut u8); - - if ptr_point.is_null() { - error!("failed to allocate memory for public point"); - res = Err(Error::MemoryError); - } else { - memcpy( - ptr_point as (*mut c_void), - data_ptr as (*const c_void), - cb_point, - ); - *point = ptr_point; - ptr_point = ptr::null_mut(); - *point_len = cb_point; - } - } - } - } else { - error!("wrong algorithm"); - res = Err(Error::AlgorithmError); - } - } - } - - if !ptr_modulus.is_null() { - free(modulus as (*mut c_void)); - } - - if !ptr_exp.is_null() { - free(ptr_exp as (*mut c_void)); - } - - if !ptr_point.is_null() { - free(ptr_exp as (*mut c_void)); - } - - let _ = yubikey._ykpiv_end_transaction(); - res -} - -/// Config mgm type -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -#[repr(i32)] -#[allow(non_camel_case_types)] -pub enum YkPivConfigMgmType { - /// Manual - YKPIV_CONFIG_MGM_MANUAL = 0i32, - - /// Derived - YKPIV_CONFIG_MGM_DERIVED = 1i32, - - /// Protected - YKPIV_CONFIG_MGM_PROTECTED = 2i32, -} - -/// Config -#[derive(Copy, Clone)] -pub struct YkPivConfig { - /// 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: YkPivConfigMgmType, -} - -/// Get config -pub unsafe fn ykpiv_util_get_config( - yubikey: &mut YubiKey, - config: *mut YkPivConfig, -) -> Result<(), Error> { - let mut data = [0u8; YKPIV_OBJ_MAX_SIZE]; - let mut cb_data: usize = mem::size_of::<[u8; YKPIV_OBJ_MAX_SIZE]>(); - let mut p_item: *mut u8 = ptr::null_mut(); - let mut cb_item: usize = 0; - let mut res = Ok(()); - - if config.is_null() { - return Err(Error::GenericError); - } - - (*config).protected_data_available = false; - (*config).puk_blocked = false; - (*config).puk_noblock_on_upgrade = false; - (*config).pin_last_changed = 0; - (*config).mgm_type = YkPivConfigMgmType::YKPIV_CONFIG_MGM_MANUAL; - - yubikey._ykpiv_begin_transaction()?; - - if yubikey._ykpiv_ensure_application_selected().is_ok() { - if _read_metadata(yubikey, TAG_ADMIN, data.as_mut_ptr(), &mut cb_data).is_ok() { - if _get_metadata_item( - data.as_mut_ptr(), - cb_data, - TAG_ADMIN_FLAGS_1, - &mut p_item, - &mut cb_item, - ) - .is_ok() - { - if *p_item & ADMIN_FLAGS_1_PUK_BLOCKED != 0 { - (*config).puk_blocked = true; - } - - if *p_item & ADMIN_FLAGS_1_PROTECTED_MGM != 0 { - (*config).mgm_type = YkPivConfigMgmType::YKPIV_CONFIG_MGM_PROTECTED; - } - } - if _get_metadata_item( - data.as_mut_ptr(), - cb_data, - TAG_ADMIN_SALT, - &mut p_item, - &mut cb_item, - ) - .is_ok() - { - if (*config).mgm_type != YkPivConfigMgmType::YKPIV_CONFIG_MGM_MANUAL { - error!("conflicting types of mgm key administration configured"); - } else { - (*config).mgm_type = YkPivConfigMgmType::YKPIV_CONFIG_MGM_DERIVED; - } - } - - if _get_metadata_item( - data.as_mut_ptr(), - cb_data, - TAG_ADMIN_TIMESTAMP, - &mut p_item, - &mut cb_item, - ) - .is_ok() - { - if cb_item != CB_ADMIN_TIMESTAMP { - error!("pin timestamp in admin metadata is an invalid size"); - } else { - // TODO(tarcieri): get rid of memcpy and pointers, replace with slices! - #[allow(trivial_casts)] - memcpy( - &mut (*config).pin_last_changed as (*mut u32) as (*mut c_void), - p_item as (*const c_void), - cb_item, - ); - } - } - } - - cb_data = YKPIV_OBJ_MAX_SIZE; - if _read_metadata(yubikey, TAG_PROTECTED, data.as_mut_ptr(), &mut cb_data).is_ok() { - (*config).protected_data_available = true; - - res = _get_metadata_item( - data.as_mut_ptr(), - cb_data, - TAG_PROTECTED_FLAGS_1, - &mut p_item, - &mut cb_item, - ); - - if res.is_ok() && *p_item & PROTECTED_FLAGS_1_PUK_NOBLOCK != 0 { - (*config).puk_noblock_on_upgrade = true; - } - - res = _get_metadata_item( - data.as_mut_ptr(), - cb_data, - TAG_PROTECTED_MGM, - &mut p_item, - &mut cb_item, - ); - - if res.is_ok() { - if (*config).mgm_type != YkPivConfigMgmType::YKPIV_CONFIG_MGM_PROTECTED { - error!("conflicting types of mgm key administration configured - protected mgm exists"); - } - - (*config).mgm_type = YkPivConfigMgmType::YKPIV_CONFIG_MGM_PROTECTED; - } - } - } - - let _ = yubikey._ykpiv_end_transaction(); - res -} - -/// Set PIN last changed -pub unsafe fn ykpiv_util_set_pin_last_changed(yubikey: &mut YubiKey) -> Result<(), Error> { - let mut data = [0u8; YKPIV_OBJ_MAX_SIZE]; - let mut cb_data = data.len(); - let mut res = Ok(()); - - yubikey._ykpiv_begin_transaction()?; - - if yubikey._ykpiv_ensure_application_selected().is_ok() { - if _read_metadata(yubikey, TAG_ADMIN, data.as_mut_ptr(), &mut cb_data).is_err() { - cb_data = 0; - } - - let mut tnow = time(ptr::null_mut()); - - res = { - // TODO(tarcieri): get rid of memcpy and pointers, replace with slices! - #[allow(trivial_casts)] - _set_metadata_item( - data.as_mut_ptr(), - &mut cb_data, - CB_OBJ_MAX, - TAG_ADMIN_TIMESTAMP, - &mut tnow as *mut i64 as *mut u8, - CB_ADMIN_TIMESTAMP, - ) - }; - - if let Err(e) = &res { - error!("could not set pin timestamp, err = {}", e); - } else { - res = _write_metadata(yubikey, TAG_ADMIN, data.as_mut_ptr(), cb_data); - if let Err(e) = &res { - error!("could not write admin data, err = {}", e); - } - } - } - let _ = yubikey._ykpiv_end_transaction(); - res -} - -/// Management key (MGM) -#[derive(Clone)] -pub struct YkPivMgm([u8; 24]); - -impl Zeroize for YkPivMgm { - fn zeroize(&mut self) { - self.0.zeroize(); - } -} - -impl Drop for YkPivMgm { - fn drop(&mut self) { - self.zeroize(); - } -} - -/// Get derived management key (MGM) -pub unsafe fn ykpiv_util_get_derived_mgm( - yubikey: &mut YubiKey, - pin: &[u8], - mgm: &mut YkPivMgm, -) -> Result<(), Error> { - let mut data = [0u8; YKPIV_OBJ_MAX_SIZE]; - let mut cb_data: usize = data.len(); - let mut p_item: *mut u8 = ptr::null_mut(); - let mut cb_item: usize = 0; - - yubikey._ykpiv_begin_transaction()?; - - let mut res = yubikey._ykpiv_ensure_application_selected(); - - if res.is_err() { - let _ = yubikey._ykpiv_end_transaction(); - return res; - } - - // recover management key - res = _read_metadata(yubikey, TAG_ADMIN, data.as_mut_ptr(), &mut cb_data); - - if res.is_ok() { - res = _get_metadata_item( - data.as_mut_ptr(), - cb_data, - TAG_ADMIN_SALT, - &mut p_item, - &mut cb_item, - ); - - if res.is_ok() { - if cb_item != CB_ADMIN_SALT { - error!( - "derived mgm salt exists, but is incorrect size = {}", - cb_item, - ); - - let _ = yubikey._ykpiv_end_transaction(); - return Err(Error::GenericError); - } - - let salt = std::slice::from_raw_parts_mut(p_item, cb_item); - pbkdf2::>(pin, &salt, ITER_MGM_PBKDF2, &mut (*mgm).0); - } - } - - let _ = yubikey._ykpiv_end_transaction(); - res -} - -/// Get protected management key (MGM) -pub unsafe fn ykpiv_util_get_protected_mgm( - yubikey: &mut YubiKey, - mgm: *mut YkPivMgm, -) -> Result<(), Error> { - // TODO(tarcieri): replace vec with wrapper type that impls `Zeroize` - let mut data = Zeroizing::new([0u8; YKPIV_OBJ_MAX_SIZE].to_vec()); - let mut cb_data: usize = data.len(); - let mut p_item: *mut u8 = ptr::null_mut(); - let mut cb_item: usize = 0; - let mut res = Ok(()); - - if mgm.is_null() { - return Err(Error::GenericError); - } - - yubikey._ykpiv_begin_transaction()?; - - if yubikey._ykpiv_ensure_application_selected().is_ok() { - res = _read_metadata(yubikey, TAG_PROTECTED, data.as_mut_ptr(), &mut cb_data); - - if res.is_err() { - error!("could not read protected data, err = {:?}", res); - } else { - res = _get_metadata_item( - data.as_mut_ptr(), - cb_data, - TAG_PROTECTED_MGM, - &mut p_item, - &mut cb_item, - ); - - if let Err(e) = &res { - error!("could not read protected mgm from metadata, err = {}", e,); - } else if cb_item != (*mgm).0.len() { - error!( - "protected data contains mgm, but is the wrong size = {}", - cb_item, - ); - res = Err(Error::AuthenticationError); - } else { - memcpy( - (*mgm).0.as_mut_ptr() as (*mut c_void), - p_item as (*const c_void), - cb_item, - ); - } - } - } - - let _ = yubikey._ykpiv_end_transaction(); - res -} - -/// Set protected management key (MGM) -/// -/// To set a generated mgm, pass NULL for mgm, or set mgm.data to all zeroes -#[allow(clippy::cognitive_complexity)] -pub unsafe fn ykpiv_util_set_protected_mgm( - yubikey: &mut YubiKey, - mgm: *mut YkPivMgm, -) -> Result<(), Error> { - let mut f_generate: bool; - let mut mgm_key = Zeroizing::new([0u8; 24]); - // TODO(tarcieri): replace vec with wrapper type that impls `Zeroize` - let mut data = Zeroizing::new([0u8; YKPIV_OBJ_MAX_SIZE].to_vec()); - let mut cb_data = data.len(); - let mut p_item: *mut u8 = ptr::null_mut(); - let mut cb_item: usize = 0; - let mut flags_1: u8 = 0; - - if mgm.is_null() { - f_generate = true; - } else { - f_generate = true; - memcpy( - (*mgm_key).as_mut_ptr() as (*mut c_void), - (*mgm).0.as_mut_ptr() as (*const c_void), - (*mgm).0.len(), - ); - - for i in 0..mgm_key.len() { - if mgm_key[i] != 0 { - f_generate = false; - break; - } - } - } - - yubikey._ykpiv_begin_transaction()?; - - if yubikey._ykpiv_ensure_application_selected().is_err() { - let _ = yubikey._ykpiv_end_transaction(); - return Ok(()); - } - - // try to set the mgm key as long as we don't encounter a fatal error - loop { - if f_generate { - // generate a new mgm key - if let Err(e) = getrandom(mgm_key.deref_mut()) { - error!("could not generate new mgm, err = {}", e); - let _ = yubikey._ykpiv_end_transaction(); - return Err(Error::RandomnessError); - } - } - - let ykrc = yubikey.ykpiv_set_mgmkey(&mgm_key); - - if ykrc.is_err() { - // if set_mgmkey fails with KeyError, it means the generated key is weak - // otherwise, log a warning, since the device mgm key is corrupt or we're in - // a state where we can't set the mgm key - if Err(Error::KeyError) != ykrc { - error!( - "could not set new derived mgm key, err = {}", - ykrc.as_ref().unwrap_err() - ); - - let _ = yubikey._ykpiv_end_transaction(); - return ykrc; - } - } else { - f_generate = false; - } - - if !f_generate { - break; - } - } - - if !mgm.is_null() { - memcpy( - (*mgm).0.as_mut_ptr() as (*mut c_void), - mgm_key.as_mut_ptr() as (*const c_void), - (*mgm).0.len(), - ); - } - - // 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 mut ykrc = _read_metadata(yubikey, TAG_PROTECTED, data.as_mut_ptr(), &mut cb_data); - - if ykrc.is_err() { - // set current metadata blob size to zero, we'll add to the blank blob - cb_data = 0; - } - - ykrc = _set_metadata_item( - data.as_mut_ptr(), - &mut cb_data, - CB_OBJ_MAX, - TAG_PROTECTED_MGM, - mgm_key.as_mut_ptr(), - mgm_key.len(), - ); - - if ykrc.is_err() { - error!("could not set protected mgm item, err = {:?}", ykrc); - } else { - ykrc = _write_metadata(yubikey, TAG_PROTECTED, data.as_mut_ptr(), cb_data); - - if ykrc.is_err() { - error!("could not write protected data, err = {:?}", ykrc); - let _ = yubikey._ykpiv_end_transaction(); - return ykrc; - } - } - - // set the protected mgm flag in admin data - cb_data = YKPIV_OBJ_MAX_SIZE; - ykrc = _read_metadata(yubikey, TAG_ADMIN, data.as_mut_ptr(), &mut cb_data); - - if ykrc.is_err() { - cb_data = 0; - } else { - ykrc = _get_metadata_item( - data.as_mut_ptr(), - cb_data, - TAG_ADMIN_FLAGS_1, - &mut p_item, - &mut cb_item, - ); - - if ykrc.is_err() { - // flags are not set - error!("admin data exists, but flags are not present",); - } - - if cb_item == 1 { - // TODO(tarcieri): get rid of memcpy and pointers, replace with slices! - #[allow(trivial_casts)] - memcpy( - &mut flags_1 as (*mut u8) as (*mut c_void), - p_item as (*const c_void), - cb_item, - ); - } else { - error!("admin data flags are an incorrect size = {}", cb_item,); - } - - // remove any existing salt - ykrc = _set_metadata_item( - data.as_mut_ptr(), - &mut cb_data, - CB_OBJ_MAX, - TAG_ADMIN_SALT, - ptr::null_mut(), - 0, - ); - - if let Err(e) = &ykrc { - error!("could not unset derived mgm salt, err = {}", e) - } - } - - flags_1 |= ADMIN_FLAGS_1_PROTECTED_MGM; - - ykrc = _set_metadata_item( - data.as_mut_ptr(), - &mut cb_data, - CB_OBJ_MAX, - TAG_ADMIN_FLAGS_1, - &mut flags_1, - mem::size_of_val(&flags_1), - ); - - if let Err(e) = &ykrc { - error!("could not set admin flags item, err = {}", e); - } else { - ykrc = _write_metadata(yubikey, TAG_ADMIN, data.as_mut_ptr(), cb_data); - if let Err(e) = ykrc.as_ref() { - error!("could not write admin data, err = {}", e); - } - } - - let _ = yubikey._ykpiv_end_transaction(); - Ok(()) -} - -/// Reset -pub unsafe fn ykpiv_util_reset(yubikey: &mut YubiKey) -> Result<(), Error> { - let templ = [0, YKPIV_INS_RESET, 0, 0]; - let mut data = [0u8; 255]; - let mut recv_len = data.len(); - let mut sw: i32 = 0; - - let res = yubikey.ykpiv_transfer_data( - templ.as_ptr(), - ptr::null(), - 0, - data.as_mut_ptr(), - &mut recv_len, - &mut sw, - ); - - match (res.is_ok(), sw) { - (true, SW_SUCCESS) => Ok(()), - _ => Err(Error::GenericError), - } -} - -/// Get object for slot -pub fn ykpiv_util_slot_object(slot: u8) -> u32 { - 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, - _ => { - if slot >= YKPIV_KEY_RETIRED1 && (slot <= YKPIV_KEY_RETIRED20) { - YKPIV_OBJ_RETIRED1 + (slot - YKPIV_KEY_RETIRED1) as u32 - } else { - 0 - } - } - } -} - -/// Read certificate -unsafe fn _read_certificate( - yubikey: &mut YubiKey, - slot: u8, - buf: *mut u8, - buf_len: *mut usize, -) -> Result<(), Error> { - let mut ptr: *mut u8; - let object_id = ykpiv_util_slot_object(slot) as i32; - let mut len: usize = 0; - - if object_id == -1 { - return Err(Error::InvalidObject); - } - - if yubikey._ykpiv_fetch_object(object_id, buf, buf_len).is_ok() { - ptr = buf; - - if *buf_len < CB_OBJ_TAG_MIN { - *buf_len = 0; - return Ok(()); - } else if *{ - let _old = ptr; - ptr = ptr.offset(1); - _old - } == TAG_CERT - { - ptr = ptr.add(_ykpiv_get_length(ptr, &mut len)); - - if len > *buf_len - (ptr as isize - buf as isize) as usize { - *buf_len = 0; - return Ok(()); - } else { - memmove(buf as (*mut c_void), ptr as (*const c_void), len); - *buf_len = len; - } - } - } else { - *buf_len = 0; - } - - Ok(()) -} - -/// Write certificate -unsafe fn _write_certificate( - yubikey: &mut YubiKey, - slot: u8, - data: *mut u8, - data_len: usize, - certinfo: u8, -) -> Result<(), Error> { - let mut buf = [0u8; CB_OBJ_MAX]; - let object_id = ykpiv_util_slot_object(slot) as i32; - let mut offset: usize = 0; - let mut req_len: usize; - - if object_id == -1 { - return Err(Error::InvalidObject); - } - - if data.is_null() || data_len == 0 { - if !data.is_null() || data_len != 0 { - return Err(Error::GenericError); - } - - return yubikey._ykpiv_save_object(object_id, ptr::null_mut(), 0); - } - - req_len = 1 /* cert tag */ + 3 /* compression tag + data*/ + 2 /* lrc */; - req_len += _ykpiv_set_length(buf.as_mut_ptr(), data_len); - req_len += data_len; - - if req_len < data_len || req_len > _obj_size_max(yubikey) { - return Err(Error::SizeError); - } - - buf[offset] = TAG_CERT; - offset += 1; - offset += _ykpiv_set_length(buf.as_mut_ptr().add(offset), data_len); - - memcpy( - buf.as_mut_ptr().add(offset) as (*mut c_void), - data as (*const c_void), - data_len, - ); - - 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; - - yubikey._ykpiv_save_object(object_id, buf.as_mut_ptr(), offset) -} - -/// Get metadata item -unsafe fn _get_metadata_item( - data: *mut u8, - cb_data: usize, - tag: u8, - pp_item: *mut *mut u8, - pcb_item: *mut usize, -) -> Result<(), Error> { - let mut p_temp: *mut u8 = data; - let mut cb_temp: usize = 0; - let mut tag_temp: u8; - - if data.is_null() || pp_item.is_null() || pcb_item.is_null() { - return Err(Error::GenericError); - } - - *pp_item = ptr::null_mut(); - *pcb_item = 0; - - while p_temp < data.add(cb_data) { - tag_temp = *p_temp; - p_temp = p_temp.add(1); - - if !_ykpiv_has_valid_length(p_temp, data.add(cb_data) as usize - p_temp as usize) { - return Err(Error::SizeError); - } - - p_temp = p_temp.add(_ykpiv_get_length(p_temp, &mut cb_temp)); - - if tag_temp == tag { - break; - } - - p_temp = p_temp.add(cb_temp); - } - - if p_temp < data.add(cb_data) { - *pp_item = p_temp; - *pcb_item = cb_temp; - - Ok(()) - } else { - Err(Error::GenericError) - } -} - -/// Get length size -fn _get_length_size(length: usize) -> i32 { - if length < 0x80 { - 1 - } else if length < 0xff { - 2 - } else { - 3 - } -} - -/// Set metadata item -unsafe fn _set_metadata_item( - data: *mut u8, - pcb_data: *mut usize, - cb_data_max: usize, - tag: u8, - p_item: *mut u8, - cb_item: usize, -) -> Result<(), Error> { - let mut p_temp: *mut u8 = data; - let mut cb_temp: usize = 0; - let mut tag_temp: u8 = 0; - let mut cb_len: usize = 0; - let p_next: *mut u8; - let cb_moved: isize; - - if data.is_null() || pcb_data.is_null() { - return Err(Error::GenericError); - } - - while p_temp < data.add(*pcb_data) { - tag_temp = *p_temp; - p_temp = p_temp.add(1); - - cb_len = _ykpiv_get_length(p_temp, &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 { - return Ok(()); - } - - p_temp = data.add(*pcb_data); - cb_len = _get_length_size(cb_item) as (usize); - - if (*pcb_data).wrapping_add(cb_len).wrapping_add(cb_item) > cb_data_max { - return Err(Error::GenericError); - } - - *p_temp = tag; - p_temp = p_temp.add(1); - p_temp = p_temp.add(_ykpiv_set_length(p_temp, cb_item)); - - memcpy(p_temp as (*mut c_void), p_item as (*const c_void), cb_item); - *pcb_data += 1 + cb_len + cb_item; - - return Ok(()); - } - - if cb_temp == cb_item { - memcpy(p_temp as (*mut c_void), p_item as (*const c_void), cb_item); - return Ok(()); - } - - p_next = p_temp.add(cb_temp); - cb_moved = cb_item as (isize) - cb_temp as (isize) - + (if cb_item != 0 { - _get_length_size(cb_item) - } else { - -1 - } as (isize) - - cb_len as (isize)); - - if (*pcb_data + cb_moved as usize) > cb_data_max { - return Err(Error::GenericError); - } - - memmove( - p_next.offset(cb_moved) as (*mut c_void), - p_next as (*const c_void), - (*pcb_data).wrapping_sub( - ((p_next as (isize)).wrapping_sub(data as (isize)) / mem::size_of::() as (isize)) - as (usize), - ), - ); - - *pcb_data = (*pcb_data).wrapping_add(cb_moved as (usize)); - - if cb_item != 0 { - p_temp = p_temp.offset(-(cb_len as (isize))); - p_temp = p_temp.add(_ykpiv_set_length(p_temp, cb_item)); - memcpy(p_temp as (*mut c_void), p_item as (*const c_void), cb_item); - } - - Ok(()) -} - -/// Read metadata -unsafe fn _read_metadata( - yubikey: &mut YubiKey, - tag: u8, - data: *mut u8, - pcb_data: *mut usize, -) -> Result<(), Error> { - let mut p_temp: *mut u8; - let mut cb_temp: usize; - - if data.is_null() || pcb_data.is_null() || YKPIV_OBJ_MAX_SIZE > *pcb_data { - return Err(Error::GenericError); - } - - let obj_id = match tag { - TAG_ADMIN => YKPIV_OBJ_ADMIN_DATA, - TAG_PROTECTED => YKPIV_OBJ_PRINTED, - _ => return Err(Error::InvalidObject), - } as i32; - - cb_temp = *pcb_data; - *pcb_data = 0; - - yubikey._ykpiv_fetch_object(obj_id, data, &mut cb_temp)?; - - if cb_temp < CB_OBJ_TAG_MIN { - return Err(Error::GenericError); - } - p_temp = data; - - if tag as (i32) - != *{ - let _old = p_temp; - p_temp = p_temp.offset(1); - _old - } as (i32) - { - return Err(Error::GenericError); - } - - p_temp = p_temp.add(_ykpiv_get_length(p_temp, pcb_data)); - - if *pcb_data > cb_temp - (p_temp as isize - data as isize) as usize { - *pcb_data = 0; - return Err(Error::GenericError); - } - - memmove(data as (*mut c_void), p_temp as (*const c_void), *pcb_data); - Ok(()) -} - -/// Write metadata -unsafe fn _write_metadata( - yubikey: &mut YubiKey, - tag: u8, - data: *mut u8, - cb_data: usize, -) -> Result<(), Error> { - let mut buf = [0u8; CB_OBJ_MAX]; // XXX REMEMBER TO ZERO - let mut p_temp: *mut u8 = buf.as_mut_ptr(); - - if cb_data > _obj_size_max(yubikey) - 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), - } as i32; - - if data.is_null() || cb_data == 0 { - return yubikey._ykpiv_save_object(obj_id, ptr::null_mut(), 0); - } - - *{ - let _old = p_temp; - p_temp = p_temp.offset(1); - _old - } = tag; - - p_temp = p_temp.add(_ykpiv_set_length(p_temp, cb_data)); - memcpy(p_temp as (*mut c_void), data as (*const c_void), cb_data); - p_temp = p_temp.add(cb_data); - - yubikey._ykpiv_save_object( - obj_id, - buf.as_mut_ptr(), - ((p_temp as (isize)).wrapping_sub(buf.as_mut_ptr() as (isize)) - / mem::size_of::() as (isize)) as (usize), - ) -} diff --git a/src/yubikey.rs b/src/yubikey.rs index b1cd089..525c8a7 100644 --- a/src/yubikey.rs +++ b/src/yubikey.rs @@ -34,88 +34,50 @@ #![allow(clippy::too_many_arguments, clippy::missing_safety_doc)] use crate::{ - apdu::APDU, - consts::*, - error::Error, - internal::{des_decrypt, des_encrypt, yk_des_is_weak_key, DesKey}, + apdu::APDU, consts::*, error::Error, key::SlotId, metadata, mgm::MgmKey, response::StatusWords, + serialization::*, transaction::Transaction, Buffer, ObjectId, }; use getrandom::getrandom; -use libc::{c_char, free, malloc, memcmp, memcpy, memmove, memset, strlen, strncasecmp}; -use log::{error, info, trace, warn}; -use std::{ffi::CStr, os::raw::c_void, ptr, slice}; -use zeroize::Zeroize; - -extern "C" { - fn SCardBeginTransaction(hCard: i32) -> i32; - fn SCardConnect( - hContext: i32, - szReader: *const c_char, - dwShareMode: u32, - dwPreferredProtocols: u32, - phCard: *mut i32, - pdwActiveProtocol: *mut u32, - ) -> i32; - fn SCardDisconnect(hCard: i32, dwDisposition: u32) -> i32; - fn SCardEndTransaction(hCard: i32, dwDisposition: u32) -> i32; - fn SCardEstablishContext( - dwScope: u32, - pvReserved1: *const c_void, - pvReserved2: *const c_void, - phContext: *mut i32, - ) -> i32; - fn SCardIsValidContext(hContext: i32) -> i32; - fn SCardListReaders( - hContext: i32, - mszGroups: *const c_char, - mszReaders: *mut c_char, - pcchReaders: *mut u32, - ) -> i32; - fn SCardReconnect( - hCard: i32, - dwShareMode: u32, - dwPreferredProtocols: u32, - dwInitialization: u32, - pdwActiveProtocol: *mut u32, - ) -> i32; - fn SCardReleaseContext(hContext: i32) -> i32; - fn SCardStatus( - hCard: i32, - mszReaderNames: *mut u8, - pcchReaderLen: *mut u32, - pdwState: *mut u32, - pdwProtocol: *mut u32, - pbAtr: *mut u8, - pcbAtrLen: *mut u32, - ) -> i32; - fn SCardTransmit( - hCard: i32, - pioSendPci: *const c_void, - pbSendBuffer: *const c_char, - cbSendLength: u32, - pioRecvPci: *const c_void, - pbRecvBuffer: *mut u8, - pcbRecvLength: *mut u32, - ) -> i32; -} - -/// TEMPORARY PLACEHOLDER for SCARD PCI_T1 -// TODO(tarcieri): PLACEHOLDER! -pub const SCARD_PCI_T1: *const c_void = ptr::null(); - -/// TEMPORARY PLACEHOLDER for SCARD success value -// TODO(tarcieri): PLACEHOLDER! -pub const SCARD_S_SUCCESS: i32 = 0; +use log::{error, info, warn}; +use pcsc::{Card, Context}; +use std::{ + convert::TryInto, + fmt::{self, Display}, + ptr, slice, + time::{SystemTime, UNIX_EPOCH}, +}; +use zeroize::Zeroizing; /// PIV Application ID pub const AID: [u8; 5] = [0xa0, 0x00, 0x00, 0x03, 0x08]; -/// MGMT Application ID +/// MGMT Application ID. +/// pub const MGMT_AID: [u8; 8] = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17]; -/// Default authentication key -pub const DEFAULT_AUTH_KEY: &[u8; DES_LEN_3DES] = b"\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08"; +/// YubiKey Serial Number +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub struct Serial(pub u32); -/// YubiKey PIV version +impl From for Serial { + fn from(num: u32) -> Serial { + Serial(num) + } +} + +impl From for u32 { + fn from(serial: Serial) -> u32 { + serial.0 + } +} + +impl Display for Serial { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +/// YubiKey Version #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Version { /// Major version component @@ -128,1223 +90,261 @@ pub struct Version { pub patch: u8, } -/// YubiKey PIV state +/// YubiKey Device: this is the primary API for opening a session and +/// performing various operations. +/// +/// Almost all functionality in this library will require an open session +/// with a YubiKey which is represented by this type. // TODO(tarcieri): reduce coupling to internal fields via `pub(crate)` -#[derive(Copy, Clone, Debug)] pub struct YubiKey { - pub(crate) context: i32, - pub(crate) card: i32, - pub(crate) pin: *mut u8, + pub(crate) card: Card, + pub(crate) pin: Option, pub(crate) is_neo: bool, - pub(crate) ver: Version, - pub(crate) serial: u32, + pub(crate) version: Version, + pub(crate) serial: Serial, } impl YubiKey { - /// Initialize YubiKey client instance - pub fn ykpiv_init() -> Self { - YubiKey { - context: -1, - card: 0, - pin: ptr::null_mut(), - is_neo: false, - ver: Version { - major: 0, - minor: 0, - patch: 0, - }, - serial: 0, - } - } + /// Open a connection to a YubiKey, optionally giving the name + /// (needed if e.g. there are multiple YubiKeys connected). + pub fn open(name: Option<&[u8]>) -> Result { + let context = Context::establish(pcsc::Scope::System)?; + let mut card = Self::connect(&context, name)?; - /// Cleanup YubiKey session - pub(crate) unsafe fn _ykpiv_done(&mut self, disconnect: bool) -> Result<(), Error> { - if disconnect { - self.ykpiv_disconnect(); - } + let mut is_neo = false; + let version: Version; + let serial: Serial; - let _ = self._cache_pin(ptr::null(), 0); - Ok(()) - } - - /// Cleanup YubiKey session with external card upon completion - // TODO(tarcieri): make this a `Drop` handler - pub unsafe fn ykpiv_done_with_external_card(&mut self) -> Result<(), Error> { - self._ykpiv_done(false) - } - - /// Cleanup YubiKey session upon completion - pub unsafe fn ykpiv_done(&mut self) -> Result<(), Error> { - self._ykpiv_done(true) - } - - /// Disconnect a YubiKey session - pub unsafe fn ykpiv_disconnect(&mut self) { - if self.card != 0 { - SCardDisconnect(self.card, 0x1); - self.card = 0i32; - } - - if SCardIsValidContext(self.context) == 0x0 { - SCardReleaseContext(self.context); - self.context = -1i32; - } - } - - /// Select application - pub(crate) unsafe fn _ykpiv_select_application(&mut self) -> Result<(), Error> { - let mut data = [0u8; 255]; - let mut recv_len = data.len() as u32; - let mut sw = 0i32; - - let apdu = APDU::new(YKPIV_INS_SELECT_APPLICATION) - .p1(0x04) - .data(&AID) - .to_bytes(); - - if let Err(e) = self._send_data(apdu.as_slice(), data.as_mut_ptr(), &mut recv_len, &mut sw) { - error!("failed communicating with card: \'{}\'", e); - return Err(e); - } - - if sw != SW_SUCCESS { - error!("failed selecting application: {:04x}", sw); - return Err(Error::GenericError); - } - - // now that the PIV application is selected, retrieve the version - // and serial number. Previously the NEO/YK4 required switching - // to the yk applet to retrieve the serial, YK5 implements this - // as a PIV applet command. Unfortunately, this change requires - // that we retrieve the version number first, so that get_serial - // can determine how to get the serial number, which for the NEO/Yk4 - // will result in another selection of the PIV applet. - - if let Err(e) = self._ykpiv_get_version() { - warn!("failed to retrieve version: \'{}\'", e); - } - - if let Err(e) = self._ykpiv_get_serial(false) { - warn!("failed to retrieve serial number: \'{}\'", e); - } - - Ok(()) - } - - /// Ensure an application is selected (presently noop) - pub(crate) unsafe fn _ykpiv_ensure_application_selected(&mut self) -> Result<(), Error> { - // TODO(tarcieri): ENABLE_APPLICATION_RESELECTION support? - // - // Original C code below: - // - // #if ENABLE_APPLICATION_RESELECTION - // if (NULL == self) { - // return YKPIV_GENERIC_ERROR; - // } - // - // res = ykpiv_verify(self, NULL, 0); - // - // if ((YKPIV_OK != res) && (YKPIV_WRONG_PIN != res)) { - // res = _ykpiv_select_application(self); - // } - // else { - // res = YKPIV_OK; - // } - // - // return res; - // #else - // (void)self; - // return res; - // #endif - - Ok(()) - } - - /// Connect to the YubiKey - pub(crate) unsafe fn _ykpiv_connect( - &mut self, - context: usize, - card: usize, - ) -> Result<(), Error> { - // if the context has changed, and the new context is not valid, return an error - if context != self.context as (usize) && (0x0i32 != SCardIsValidContext(context as (i32))) { - return Err(Error::PcscError); - } - - // if card handle has changed, determine if handle is valid (less efficient, but complete) - if card != self.card as usize { - let mut reader = [0u8; YKPIV_OBJ_MAX_SIZE]; - let mut reader_len = reader.len() as u32; - let mut atr = [0u8; 33]; - let mut atr_len = atr.len() as u32; - - // Cannot set the reader len to NULL. Confirmed in OSX 10.10, - // so we have to retrieve it even though we don't need it. - if SCardStatus( - card as i32, - reader.as_mut_ptr(), - &mut reader_len, - ptr::null_mut(), - ptr::null_mut(), - atr.as_mut_ptr(), - &mut atr_len, - ) != 0 - { - return Err(Error::PcscError); + let txn = Transaction::new(&mut card)?; + let mut atr_buf = [0; CB_ATR_MAX]; + let atr = txn.get_attribute(pcsc::Attribute::AtrString, &mut atr_buf)?; + if atr == YKPIV_ATR_NEO_R3 { + is_neo = true; } - self.is_neo = (atr_len as usize == YKPIV_ATR_NEO_R3.len() - 1) - && (memcmp( - YKPIV_ATR_NEO_R3.as_ptr() as *const c_void, - atr.as_mut_ptr() as *const c_void, - atr_len as usize, - ) == 0); + txn.select_application()?; + + // now that the PIV application is selected, retrieve the version + // and serial number. Previously the NEO/YK4 required switching + // to the yk applet to retrieve the serial, YK5 implements this + // as a PIV applet command. Unfortunately, this change requires + // that we retrieve the version number first, so that get_serial + // can determine how to get the serial number, which for the NEO/Yk4 + // will result in another selection of the PIV applet. + + version = txn.get_version().map_err(|e| { + warn!("failed to retrieve version: '{}'", e); + e + })?; + + serial = txn.get_serial(version).map_err(|e| { + warn!("failed to retrieve serial number: '{}'", e); + e + })?; } - self.context = context as i32; - self.card = card as i32; + let yubikey = YubiKey { + card, + pin: None, + is_neo, + version, + serial, + }; - // Do not select the applet here, as we need to accommodate commands that are - // sensitive to re-select (custom apdu/auth). All commands that can handle explicit - // selection already check the applet state and select accordingly anyway. - // ykpiv_verify_select is supplied for those who want to select explicitly. - // - // The applet _is_ selected by ykpiv_connect(), but is not selected when bypassing - // it with ykpiv_connect_with_external_card(). - - Ok(()) + Ok(yubikey) } - /// Connect to an external card - pub unsafe fn ykpiv_connect_with_external_card( - &mut self, - context: usize, - card: usize, - ) -> Result<(), Error> { - self._ykpiv_connect(context, card) - } + /// Connect to a YubiKey. + fn connect(context: &Context, name: Option<&[u8]>) -> Result { + // ensure PC/SC context is valid + context.is_valid()?; - /// Connect to a YubiKey - pub unsafe fn ykpiv_connect(&mut self, wanted: *const c_char) -> Result<(), Error> { - let mut active_protocol: u32 = 0; - let mut reader_buf: [c_char; 2048] = [0; 2048]; - let mut num_readers = reader_buf.len(); - let mut reader_ptr: *mut c_char; - let mut card: i32 = -1i32; + let buffer_len = context.list_readers_len()?; + let mut buffer = vec![0u8; buffer_len]; - self.ykpiv_list_readers(reader_buf.as_mut_ptr(), &mut num_readers)?; - - reader_ptr = reader_buf.as_mut_ptr(); - - while *reader_ptr != 0 { - if !wanted.is_null() { - let mut ptr = reader_ptr; - let mut found = false; - - while *ptr != 0 { - if strlen(ptr) < strlen(wanted) { - break; - } - - if strncasecmp(ptr, wanted, strlen(wanted)) == 0 { - found = true; - break; - } - - ptr = ptr.add(1); - } - - if !found { + for reader in context.list_readers(&mut buffer)? { + if let Some(wanted) = name { + if reader.to_bytes() != wanted { warn!( - "skipping reader \'{}\' since it doesn\'t match \'{}\'", - CStr::from_ptr(reader_ptr).to_string_lossy(), - CStr::from_ptr(wanted).to_string_lossy() + "skipping reader '{}' since it doesn't match '{}'", + reader.to_string_lossy(), + String::from_utf8_lossy(wanted) ); continue; } } - info!( - "trying to connect to reader \'{}\'", - CStr::from_ptr(reader_ptr).to_string_lossy() - ); + info!("trying to connect to reader '{}'", reader.to_string_lossy()); - let rc = SCardConnect( - self.context, - reader_ptr, - 0x2u32, - 0x2u32, - &mut card, - &mut active_protocol, - ); - - if rc != 0x0 { - error!("SCardConnect failed, rc={}", rc); - } else { - // at this point, card should not equal self->card, - // to allow _ykpiv_connect() to determine device type - if self - ._ykpiv_connect(self.context as (usize), card as (usize)) - .is_err() - { - break; - } - } - - reader_ptr = reader_ptr.add(strlen(reader_ptr) + 1); + return Ok(context.connect(reader, pcsc::ShareMode::Shared, pcsc::Protocols::T1)?); } - if *reader_ptr == b'\0' as c_char { - error!("error: no usable reader found"); - SCardReleaseContext(self.context); - self.context = -1; - return Err(Error::PcscError); - } - - // Select applet. This is done here instead of in _ykpiv_connect() because - // you may not want to select the applet when connecting to a card handle that - // was supplied by an external library. - self._ykpiv_begin_transaction()?; - - let res = self._ykpiv_select_application(); - let _ = self._ykpiv_end_transaction(); - res - } - - /// List readers - pub unsafe fn ykpiv_list_readers( - &mut self, - readers: *mut c_char, - len: *mut usize, - ) -> Result<(), Error> { - let mut num_readers: u32 = 0u32; - let mut rc: i32; - - if SCardIsValidContext(self.context) != 0 { - rc = SCardEstablishContext(0x2, ptr::null(), ptr::null(), &mut self.context); - - if rc != 0 { - error!("error: SCardEstablishContext failed, rc={}", rc); - return Err(Error::PcscError); - } - } - - rc = SCardListReaders(self.context, ptr::null(), ptr::null_mut(), &mut num_readers); - - if rc != 0 { - error!("error: SCardListReaders failed, rc={}", rc); - SCardReleaseContext(self.context); - self.context = -1i32; - return Err(Error::PcscError); - } - - if num_readers as (usize) > *len { - num_readers = *len as (u32); - } else if num_readers as (usize) < *len { - *len = num_readers as (usize); - } - - rc = SCardListReaders(self.context, ptr::null(), readers, &mut num_readers); - - if rc != 0 { - error!("error: SCardListReaders failed, rc={}", rc); - SCardReleaseContext(self.context); - self.context = -1i32; - return Err(Error::PcscError); - } - - *len = num_readers as usize; - Ok(()) + error!("error: no usable reader found"); + Err(Error::PcscError { inner: None }) } /// Reconnect to a YubiKey - pub(crate) unsafe fn reconnect(&mut self) -> Result<(), Error> { + pub fn reconnect(&mut self) -> Result<(), Error> { info!("trying to reconnect to current reader"); - let mut active_protocol: u32 = 0; - let rc = SCardReconnect(self.card, 0x2u32, 0x2u32, 0x1u32, &mut active_protocol); + self.card.reconnect( + pcsc::ShareMode::Shared, + pcsc::Protocols::T1, + pcsc::Disposition::ResetCard, + )?; - if rc != 0x0 { - error!("SCardReconnect failed, rc={}", rc); - return Err(Error::PcscError); - } + // TODO(tarcieri): zeroize pin! + let pin = self.pin.clone(); - self._ykpiv_select_application()?; + let txn = Transaction::new(&mut self.card)?; + txn.select_application()?; - if !self.pin.is_null() { - self.ykpiv_verify(self.pin as *const c_char).map(|_| ()) - } else { - Ok(()) - } - } - - /// Begin a transaction - pub(crate) unsafe fn _ykpiv_begin_transaction(&mut self) -> Result<(), Error> { - let mut rc = SCardBeginTransaction(self.card); - - if rc as usize & 0xffff_ffff == 0x8010_0068 { - self.reconnect()?; - rc = SCardBeginTransaction(self.card); - } - - if rc != 0 { - error!("failed to begin pcsc transaction, rc={}", rc); - return Err(Error::PcscError); + if let Some(p) = &pin { + txn.verify_pin(p)?; } Ok(()) } - /// End a transaction - pub(crate) unsafe fn _ykpiv_end_transaction(&mut self) -> Result<(), Error> { - let rc = SCardEndTransaction(self.card, 0x0); - - if rc != 0x0 { - error!("failed to end pcsc transaction, rc={}", rc); - return Err(Error::PcscError); - } - - Ok(()) + /// Begin a transaction. + pub(crate) fn begin_transaction(&mut self) -> Result, Error> { + // TODO(tarcieri): reconnect support + Ok(Transaction::new(&mut self.card)?) } - /// Transfer data - pub(crate) unsafe fn _ykpiv_transfer_data( - &mut self, - templ: *const u8, - in_data: *const u8, - in_len: isize, - mut out_data: *mut u8, - out_len: *mut usize, - sw: *mut i32, - ) -> Result<(), Error> { - let mut _currentBlock; - let mut in_ptr: *const u8 = in_data; - let max_out = *out_len; - let mut res: Result<(), Error>; - let mut recv_len: u32; + /// Authenticate to the card using the provided management key (MGM). + pub fn authenticate(&mut self, mgm_key: MgmKey) -> Result<(), Error> { + let txn = self.begin_transaction()?; - *out_len = 0; + // get a challenge from the card + let challenge = APDU::new(YKPIV_INS_AUTHENTICATE) + .params(YKPIV_ALGO_3DES, YKPIV_KEY_CARDMGM) + .data(&[0x7c, 0x02, 0x80, 0x00]) + .transmit(&txn, 261)?; - loop { - let mut this_size: usize = 0xff; - let mut data = [0u8; 261]; - recv_len = data.len() as u32; - - let cla = if in_ptr.offset(0xff) < in_data.offset(in_len) { - 0x10 - } else { - this_size = in_data.offset(in_len) as usize - in_ptr as usize; - *templ - }; - - trace!("going to send {} bytes in this go", this_size); - - let apdu = APDU::new(*templ.offset(1)) - .cla(cla) - .params(*templ.offset(2), *templ.offset(3)) - .data(slice::from_raw_parts(in_ptr, this_size)) - .to_bytes(); - - res = self._send_data(apdu.as_slice(), data.as_mut_ptr(), &mut recv_len, sw); - - if res.is_err() { - _currentBlock = 24; - break; - } - - if *sw != SW_SUCCESS && (*sw >> 8 != 0x61) { - _currentBlock = 24; - break; - } - - if (*out_len) - .wrapping_add(recv_len as (usize)) - .wrapping_sub(2usize) - > max_out - { - _currentBlock = 21; - break; - } - - if !out_data.is_null() { - memcpy( - out_data as (*mut c_void), - data.as_mut_ptr() as (*const c_void), - recv_len.wrapping_sub(2u32) as (usize), - ); - out_data = out_data.offset(recv_len.wrapping_sub(2u32) as (isize)); - *out_len = (*out_len).wrapping_add(recv_len.wrapping_sub(2u32) as (usize)); - } - - in_ptr = in_ptr.add(this_size); - - if in_ptr >= in_data.offset(in_len) { - _currentBlock = 10; - break; - } + if !challenge.is_success() || challenge.buffer().len() < 12 { + return Err(Error::AuthenticationError); } - if _currentBlock == 10 { - loop { - if *sw >> 8 != 0x61 { - _currentBlock = 24; - break; - } + // send a response to the cards challenge and a challenge of our own. + let response = mgm_key.decrypt(challenge.buffer()[4..12].try_into().unwrap()); - let mut data = [0u8; 261]; - recv_len = data.len() as u32; + let mut data = [0u8; 22]; + data[0] = 0x7c; + data[1] = 20; // 2 + 8 + 2 +8 + data[2] = 0x80; + data[3] = 8; + data[4..12].copy_from_slice(&response); + data[12] = 0x81; + data[13] = 8; - trace!( - "The card indicates there is {} bytes more data for us.", - *sw & 0xff - ); - - let apdu = APDU::new(YKPIV_INS_GET_RESPONSE_APDU).to_bytes(); - - res = self._send_data(apdu.as_slice(), data.as_mut_ptr(), &mut recv_len, sw); - - if res.is_err() { - _currentBlock = 24; - break; - } - if *sw != SW_SUCCESS && (*sw >> 8 != 0x61) { - _currentBlock = 24; - break; - } - if (*out_len).wrapping_add(recv_len as (usize)).wrapping_sub(2) > max_out { - _currentBlock = 18; - break; - } - - if out_data.is_null() { - continue; - } - - memcpy( - out_data as (*mut c_void), - data.as_mut_ptr() as (*const c_void), - recv_len.wrapping_sub(2) as (usize), - ); - - out_data = out_data.offset(recv_len.wrapping_sub(2) as (isize)); - *out_len = (*out_len).wrapping_add(recv_len.wrapping_sub(2) as (usize)); - } - - if _currentBlock != 24 { - error!( - "Output buffer to small, wanted to write {}, max was {}.", - (*out_len).wrapping_add(recv_len as usize).wrapping_sub(2), - max_out - ); - - return Err(Error::SizeError); - } - } else if _currentBlock == 21 { - error!( - "Output buffer to small, wanted to write {}, max was {}.", - (*out_len).wrapping_add(recv_len as usize).wrapping_sub(2), - max_out - ); - - return Err(Error::SizeError); + if getrandom(&mut data[14..22]).is_err() { + error!("failed getting randomness for authentication"); + return Err(Error::RandomnessError); } - res - } + let mut challenge = [0u8; 8]; + challenge.copy_from_slice(&data[14..22]); - /// Transfer data - pub unsafe fn ykpiv_transfer_data( - &mut self, - templ: *const u8, - in_data: *const u8, - in_len: isize, - out_data: *mut u8, - out_len: *mut usize, - sw: *mut i32, - ) -> Result<(), Error> { - if let Err(e) = self._ykpiv_begin_transaction() { - *out_len = 0; - return Err(e); + let authentication = APDU::new(YKPIV_INS_AUTHENTICATE) + .params(YKPIV_ALGO_3DES, YKPIV_KEY_CARDMGM) + .data(&data) + .transmit(&txn, 261)?; + + if !authentication.is_success() { + return Err(Error::AuthenticationError); } - let res = self._ykpiv_transfer_data(templ, in_data, in_len, out_data, out_len, sw); - let _ = self._ykpiv_end_transaction(); - res - } + // compare the response from the card with our challenge + let response = mgm_key.encrypt(&challenge); - /// Send data - pub(crate) unsafe fn _send_data( - &mut self, - apdu: impl AsRef<[u8]>, - data: *mut u8, - recv_len: *mut u32, - sw: *mut i32, - ) -> Result<(), Error> { - let send_len = apdu.as_ref().len() as u32; - let apdu_ptr = apdu.as_ref().as_ptr(); - let mut tmp_len = *recv_len; - - trace!("> {:?}", apdu.as_ref()); - - let rc = SCardTransmit( - self.card, - SCARD_PCI_T1, - apdu_ptr as *const i8, - send_len, - ptr::null(), - data, - &mut tmp_len, - ); - - if rc != SCARD_S_SUCCESS { - error!("error: SCardTransmit failed, rc={:08x}", rc); - return Err(Error::PcscError); + use subtle::ConstantTimeEq; + if response.ct_eq(&authentication.buffer()[4..12]).unwrap_u8() != 1 { + return Err(Error::AuthenticationError); } - *recv_len = tmp_len; - trace!("< {:?}", slice::from_raw_parts(data, *recv_len as usize)); - - if *recv_len >= 2 { - *sw = *data.offset((*recv_len).wrapping_sub(2) as (isize)) as (i32) << 8 - | *data.offset((*recv_len).wrapping_sub(1) as (isize)) as (i32); - } else { - *sw = 0; - } - - Ok(()) - } - - /// Authenticate to the card - pub unsafe fn ykpiv_authenticate( - &mut self, - key: Option<&[u8; DES_LEN_3DES]>, - ) -> Result<(), Error> { - let mut res = Ok(()); - - self._ykpiv_begin_transaction()?; - - if self._ykpiv_ensure_application_selected().is_ok() { - // use the provided mgm key to authenticate; if it hasn't been provided, use default - let mgm_key = DesKey::from_bytes(*key.unwrap_or(DEFAULT_AUTH_KEY)); - - // get a challenge from the card - let apdu = APDU::new(YKPIV_INS_AUTHENTICATE) - .params(YKPIV_ALGO_3DES, YKPIV_KEY_CARDMGM) - .data(&[0x7c, 0x02, 0x80, 0x00]) - .to_bytes(); - - let mut data = [0u8; 261]; - let mut recv_len = data.len() as u32; - let mut sw: i32 = 0; - - res = self._send_data(apdu.as_slice(), data.as_mut_ptr(), &mut recv_len, &mut sw); - - if res.is_err() { - let _ = self._ykpiv_end_transaction(); - return res; - } else if sw != SW_SUCCESS { - let _ = self._ykpiv_end_transaction(); - return Err(Error::AuthenticationError); - } - - let mut challenge = [0u8; 8]; - challenge.copy_from_slice(&data[4..12]); - - // send a response to the cards challenge and a challenge of our own. - let mut response = [0u8; 8]; - des_decrypt(&mgm_key, &challenge, &mut response); - - recv_len = data.len() as u32; - - let mut data = [0u8; 22]; - data[0] = 0x7c; - data[1] = 20; // 2 + 8 + 2 +8 - data[2] = 0x80; - data[3] = 8; - data[4..12].copy_from_slice(&response); - data[12] = 0x81; - data[13] = 8; - - if getrandom(&mut data[14..22]).is_err() { - error!("failed getting randomness for authentication."); - let _ = self._ykpiv_end_transaction(); - return Err(Error::RandomnessError); - } - - challenge.copy_from_slice(&data[14..22]); - - let apdu = APDU::new(YKPIV_INS_AUTHENTICATE) - .params(YKPIV_ALGO_3DES, YKPIV_KEY_CARDMGM) - .data(&data) - .to_bytes(); - - res = self._send_data(apdu.as_slice(), data.as_mut_ptr(), &mut recv_len, &mut sw); - - if res.is_err() { - let _ = self._ykpiv_end_transaction(); - return res; - } else if sw != SW_SUCCESS { - let _ = self._ykpiv_end_transaction(); - return Err(Error::AuthenticationError); - } - - // compare the response from the card with our challenge - des_encrypt(&mgm_key, &challenge, &mut response); - - // TODO(tarcieri): constant time comparison! - if response == data[4..12] { - res = Ok(()); - } else { - res = Err(Error::AuthenticationError); - } - } - - let _ = self._ykpiv_end_transaction(); - res - } - - /// Set the management key (MGM) - pub unsafe fn ykpiv_set_mgmkey(&mut self, new_key: &[u8; DES_LEN_3DES]) -> Result<(), Error> { - self.ykpiv_set_mgmkey2(new_key, 0) - } - - /// Set the management key (MGM) - pub(crate) unsafe fn ykpiv_set_mgmkey2( - &mut self, - new_key: &[u8; DES_LEN_3DES], - touch: u8, - ) -> Result<(), Error> { - let mut res = Ok(()); - - self._ykpiv_begin_transaction()?; - - if self._ykpiv_ensure_application_selected().is_ok() { - if yk_des_is_weak_key(new_key) { - error!( - "won't set new key '{:?}' since it's weak (with odd parity)", - new_key - ); - - let _ = self._ykpiv_end_transaction(); - return Err(Error::KeyError); - } - - let p2 = match touch { - 0 => 0xff, - 1 => 0xfe, - _ => { - let _ = self._ykpiv_end_transaction(); - 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); - - let apdu = APDU::new(YKPIV_INS_SET_MGMKEY) - .params(0xff, p2) - .data(&data) - .to_bytes(); - - let mut data = [0u8; 261]; - let mut recv_len = data.len() as u32; - let mut sw: i32 = 0; - res = self._send_data(apdu.as_slice(), data.as_mut_ptr(), &mut recv_len, &mut sw); - - if res.is_ok() && sw != SW_SUCCESS { - res = Err(Error::GenericError); - } - } - - res - } - - /// Authenticate to the YubiKey - pub(crate) unsafe fn _general_authenticate( - &mut self, - sign_in: *const u8, - in_len: usize, - out: *mut u8, - out_len: *mut usize, - algorithm: u8, - key: u8, - decipher: bool, - ) -> Result<(), Error> { - let mut _currentBlock; - let mut indata = [0u8; 1024]; - let mut dataptr: *mut u8 = indata.as_mut_ptr(); - let mut data = [0u8; 1024]; - let templ = [0, YKPIV_INS_AUTHENTICATE, algorithm, key]; - let mut recv_len = data.len(); - let mut sw: i32 = 0; - let bytes: usize; - 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); - } else { - _currentBlock = 16; - } - } - 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), - } - - if in_len < 0x80 { - bytes = 1; - } else if in_len < 0xff { - bytes = 2; - } else { - bytes = 3; - } - - *dataptr = 0x7c; - dataptr = dataptr.add(_ykpiv_set_length(dataptr, in_len + bytes + 3)); - *dataptr = 0x82; - *dataptr.add(1) = 0x00; - *dataptr.add(2) = - if (algorithm == YKPIV_ALGO_ECCP256 || algorithm == YKPIV_ALGO_ECCP384) && decipher { - 0x85 - } else { - 0x81 - }; - dataptr = dataptr.add(3 + _ykpiv_set_length(dataptr, in_len)); - memcpy(dataptr as *mut c_void, sign_in as *const c_void, in_len); - dataptr = dataptr.add(in_len); - - if let Err(e) = self.ykpiv_transfer_data( - templ.as_ptr(), - indata.as_mut_ptr(), - dataptr as isize - indata.as_mut_ptr() as isize, - data.as_mut_ptr(), - &mut recv_len, - &mut sw, - ) { - error!("sign command failed to communicate"); - return Err(e); - } - - if sw != SW_SUCCESS { - error!("Failed sign command with code {:x}", sw); - - if sw == SW_ERR_SECURITY_STATUS { - return Err(Error::AuthenticationError); - } else { - return Err(Error::GenericError); - } - } - - // skip the first 7c tag - if data[0] != 0x7c { - error!("failed parsing signature reply (0x7c byte)"); - return Err(Error::ParseError); - } - - dataptr = data.as_mut_ptr().add(1); - dataptr = dataptr.add(_ykpiv_get_length(dataptr, &mut len)); - - // skip the 82 tag - if *dataptr != 0x82 { - error!("failed parsing signature reply (0x82 byte)"); - return Err(Error::ParseError); - } - - dataptr = dataptr.add(1); - dataptr = dataptr.add(_ykpiv_get_length(dataptr, &mut len)); - - if len > *out_len { - error!("wrong size on output buffer"); - return Err(Error::SizeError); - } - - *out_len = len; - memcpy(out as (*mut c_void), dataptr as (*const c_void), len); Ok(()) } /// Sign data using a PIV key - pub unsafe fn ykpiv_sign_data( + pub fn sign_data( &mut self, - raw_in: *const u8, - in_len: usize, - sign_out: *mut u8, - out_len: *mut usize, + raw_in: &[u8], + sign_out: &mut [u8], + out_len: &mut usize, algorithm: u8, - key: u8, + key: SlotId, ) -> Result<(), Error> { - self._ykpiv_begin_transaction()?; + let txn = self.begin_transaction()?; // don't attempt to reselect in crypt operations to avoid problems with PIN_ALWAYS - - let res = - self._general_authenticate(raw_in, in_len, sign_out, out_len, algorithm, key, false); - - let _ = self._ykpiv_end_transaction(); - res + txn.authenticated_command(raw_in, sign_out, out_len, algorithm, key, false) } /// Decrypt data using a PIV key - pub unsafe fn ykpiv_decrypt_data( + pub fn decrypt_data( &mut self, - input: *const u8, - input_len: usize, - out: *mut u8, - out_len: *mut usize, + input: &[u8], + out: &mut [u8], + out_len: &mut usize, algorithm: u8, - key: u8, + key: SlotId, ) -> Result<(), Error> { - self._ykpiv_begin_transaction()?; + let txn = self.begin_transaction()?; // don't attempt to reselect in crypt operations to avoid problems with PIN_ALWAYS - - let res = self._general_authenticate(input, input_len, out, out_len, algorithm, key, true); - let _ = self._ykpiv_end_transaction(); - res + txn.authenticated_command(input, out, out_len, algorithm, key, true) } - /// Get the version of the PIV application installed on the YubiKey - pub(crate) unsafe fn _ykpiv_get_version(&mut self) -> Result { - let mut data = [0u8; 261]; - let mut recv_len = data.len() as u32; - let mut sw: i32 = 0; - - // get version from self if already from device - if self.ver.major != 0 || self.ver.minor != 0 || self.ver.patch != 0 { - return Ok(self.ver); - } - - // get version from device - let apdu = APDU::new(YKPIV_INS_GET_VERSION).to_bytes(); - - self._send_data(apdu.as_slice(), data.as_mut_ptr(), &mut recv_len, &mut sw)?; - - if sw != SW_SUCCESS { - return Err(Error::GenericError); - } - - if recv_len < 3 { - return Err(Error::SizeError); - } - - self.ver.major = data[0]; - self.ver.minor = data[1]; - self.ver.patch = data[2]; - - Ok(self.ver) - } - - /// Get the YubiKey's PIV application version as a string - pub unsafe fn ykpiv_get_version(&mut self) -> Result { - let mut res = Err(Error::GenericError); - - self._ykpiv_begin_transaction()?; - - if self._ykpiv_ensure_application_selected().is_ok() { - res = self._ykpiv_get_version(); - } - - let _ = self._ykpiv_end_transaction(); - res.map(|ver| format!("{}.{}.{}", ver.major, ver.minor, ver.patch)) - } - - /// Get YubiKey device serial number + /// Get the YubiKey's PIV application version. /// - /// NOTE: caller must make sure that this is wrapped in a transaction for synchronized operation - pub(crate) unsafe fn _ykpiv_get_serial(&mut self, f_force: bool) -> Result { - let yk_applet = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01, 0x01]; - let mut data = [0u8; 255]; - let mut recv_len = data.len() as u32; - let mut sw: i32 = 0; - let p_temp: *mut u8; + /// This always uses the cached version queried when the key is initialized. + pub fn version(&mut self) -> Version { + self.version + } - if !f_force && self.serial != 0 { - return Ok(self.serial); - } + /// Get YubiKey device serial number. + /// + /// This always uses the cached version queried when the key is initialized. + pub fn get_serial(&mut self) -> Serial { + self.serial + } - if self.ver.major < 5 { - // get serial from neo/yk4 devices using the otp applet - let mut temp = [0u8; 255]; - recv_len = temp.len() as u32; - - let apdu = APDU::new(YKPIV_INS_SELECT_APPLICATION) - .p1(0x04) - .data(&yk_applet) - .to_bytes(); - - if let Err(e) = - self._send_data(apdu.as_slice(), temp.as_mut_ptr(), &mut recv_len, &mut sw) - { - error!("failed communicating with card: '{}'", e); - return Err(e); - } - - if sw != SW_SUCCESS { - error!("failed selecting yk application: {:04x}", sw); - return Err(Error::GenericError); - } - - recv_len = temp.len() as u32; - let apdu = APDU::new(0x01).p1(0x10).to_bytes(); - - if let Err(e) = - self._send_data(apdu.as_slice(), data.as_mut_ptr(), &mut recv_len, &mut sw) - { - error!("failed communicating with card: '{}'", e); - return Err(e); - } - - if sw != SW_SUCCESS { - error!("failed retrieving serial number: {:04x}", sw); - return Err(Error::GenericError); - } - - recv_len = temp.len() as u32; - let apdu = APDU::new(YKPIV_INS_SELECT_APPLICATION) - .p1(0x04) - .data(&AID) - .to_bytes(); - - if let Err(e) = - self._send_data(apdu.as_slice(), temp.as_mut_ptr(), &mut recv_len, &mut sw) - { - error!("failed communicating with card: '{}'", e); - return Err(e); - } - - if sw != SW_SUCCESS { - error!("failed selecting application: {:04x}", sw); - return Err(Error::GenericError); - } - } else { - // get serial from yk5 and later devices using the f8 command - let apdu = APDU::new(YKPIV_INS_GET_SERIAL).to_bytes(); - - if let Err(e) = - self._send_data(apdu.as_slice(), data.as_mut_ptr(), &mut recv_len, &mut sw) - { - error!("failed communicating with card: '{}'", e); - return Err(e); - } - - if sw != SW_SUCCESS { - error!("failed retrieving serial number: {:04x}", sw); - return Err(Error::GenericError); - } - } - - // check that we received enough data for the serial number - if recv_len < 4 { - return Err(Error::SizeError); - } - - // TODO(tarcieri): replace pointers and casts with proper references! - #[allow(trivial_casts)] + /// Verify device PIN. + pub fn verify_pin(&mut self, pin: &[u8]) -> Result<(), Error> { { - p_temp = &mut self.serial as (*mut u32) as (*mut u8); + let txn = self.begin_transaction()?; + txn.verify_pin(pin)?; } - *p_temp = data[3]; - *p_temp.add(1) = data[2]; - *p_temp.add(2) = data[1]; - *p_temp.add(3) = data[0]; - - Ok(self.serial) - } - - /// Get YubiKey device serial number - pub unsafe fn ykpiv_get_serial(&mut self) -> Result { - let mut res = Err(Error::GenericError); - - self._ykpiv_begin_transaction()?; - - if self._ykpiv_ensure_application_selected().is_ok() { - res = self._ykpiv_get_serial(false); - } - - let _ = self._ykpiv_end_transaction(); - res - } - - /// Cache PIN in memory - // TODO(tarcieri): better security around the cached PIN - pub(crate) unsafe fn _cache_pin( - &mut self, - pin: *const c_char, - len: usize, - ) -> Result<(), Error> { - if !pin.is_null() && (self.pin as *const c_char == pin) { - return Ok(()); - } - - if !self.pin.is_null() { - // Zeroize the old cached PIN - let old_len = strlen(self.pin as *const c_char); - let pin_slice = slice::from_raw_parts_mut(self.pin, old_len); - pin_slice.zeroize(); - - free(self.pin as (*mut c_void)); - self.pin = ptr::null_mut(); - } - - if !pin.is_null() && len > 0 { - self.pin = malloc(len + 1) as (*mut u8); - - if self.pin.is_null() { - return Err(Error::MemoryError); - } - - memcpy(self.pin as (*mut c_void), pin as (*const c_void), len); - *self.pin.add(len) = 0u8; + if !pin.is_empty() { + self.pin = Some(Buffer::new(pin.into())) } Ok(()) } - /// Verify device PIN - /// - /// Returns the number of tries remaining both on success and on a wrong PIN. - pub unsafe fn ykpiv_verify(&mut self, pin: *const c_char) -> Result { - self.ykpiv_verify_select(pin, if !pin.is_null() { strlen(pin) } else { 0 }, false) - } - - /// Verify device PIN - /// - /// Returns the number of tries remaining both on success and on a wrong PIN. - pub(crate) unsafe fn _verify( - &mut self, - pin: *const c_char, - pin_len: usize, - ) -> Result { - let mut data = [0u8; 261]; - let mut recv_len = data.len() as u32; - let mut sw: i32 = 0; - - if pin_len > CB_PIN_MAX { - return Err(Error::SizeError); - } - - let mut apdu = APDU::new(YKPIV_INS_VERIFY); - apdu.params(0x00, 0x80); - - if !pin.is_null() { - let mut data = [0xFF; CB_PIN_MAX]; - - memcpy( - data.as_mut_ptr() as *mut c_void, - pin as *const c_void, - pin_len, - ); - - apdu.data(data); - } - - let res = self._send_data( - apdu.to_bytes().as_slice(), - data.as_mut_ptr(), - &mut recv_len, - &mut sw, - ); - - if let Err(e) = res { - return Err(e); - } - - if sw == SW_SUCCESS { - if !pin.is_null() && (pin_len != 0) { - // Intentionally ignore errors. If the PIN fails to save, it will only - // be a problem if a reconnect is attempted. Failure deferred until then. - let _ = self._cache_pin(pin, pin_len); - } - - Ok(sw & 0xf) - } else if sw >> 8 == 0x63 { - Err(Error::WrongPin { tries: sw & 0xf }) - } else if sw == SW_ERR_AUTH_BLOCKED { - Err(Error::WrongPin { tries: 0 }) - } else { - Err(Error::GenericError) - } - } - - /// Verify and select application - /// - /// Returns the number of tries remaining both on success and on a wrong PIN. - pub unsafe fn ykpiv_verify_select( - &mut self, - pin: *const c_char, - pin_len: usize, - force_select: bool, - ) -> Result { - let mut res = Ok(-1); - - self._ykpiv_begin_transaction()?; - - if force_select { - if let Err(e) = self._ykpiv_ensure_application_selected() { - res = Err(e); - } - } - - if res.is_ok() { - res = self._verify(pin, pin_len); - } - - let _ = self._ykpiv_end_transaction(); - res - } - /// Get the number of PIN retries - pub unsafe fn ykpiv_get_pin_retries(&mut self) -> Result { + pub fn get_pin_retries(&mut self) -> Result { + let txn = self.begin_transaction()?; + // Force a re-select to unverify, because once verified the spec dictates that // subsequent verify calls will return a "verification not needed" instead of // the number of tries left... - self._ykpiv_select_application()?; - - let ykrc = self.ykpiv_verify(ptr::null()); + txn.select_application()?; // WRONG_PIN is expected on successful query. - match ykrc { - Ok(tries) | Err(Error::WrongPin { tries }) => Ok(tries), + match txn.verify_pin(&[]) { + Ok(()) => Ok(0), // TODO(tarcieri): verify this matches `yubico-piv-tool` + Err(Error::WrongPin { tries }) => Ok(tries), Err(e) => Err(e), } } /// Set the number of PIN retries - pub unsafe fn ykpiv_set_pin_retries( - &mut self, - pin_tries: i32, - puk_tries: i32, - ) -> Result<(), Error> { - let mut res = Ok(()); - let mut templ = [0, YKPIV_INS_SET_PIN_RETRIES, 0, 0]; - let mut data = [0u8; 255]; - let mut recv_len: usize = data.len(); - let mut sw: i32 = 0i32; - + pub fn set_pin_retries(&mut self, pin_tries: usize, puk_tries: usize) -> Result<(), Error> { // Special case: if either retry count is 0, it's a successful no-op if pin_tries == 0 || puk_tries == 0 { return Ok(()); @@ -1354,145 +354,76 @@ impl YubiKey { return Err(Error::RangeError); } - templ[2] = pin_tries as (u8); - templ[3] = puk_tries as (u8); + let txn = self.begin_transaction()?; - self._ykpiv_begin_transaction()?; + let templ = [ + 0, + YKPIV_INS_SET_PIN_RETRIES, + pin_tries as u8, + puk_tries as u8, + ]; - if self._ykpiv_ensure_application_selected().is_ok() { - res = self.ykpiv_transfer_data( - templ.as_ptr(), - ptr::null(), - 0, - data.as_mut_ptr(), - &mut recv_len, - &mut sw, - ); + let status_words = txn.transfer_data(&templ, &[], 255)?.status_words(); - if res.is_ok() { - res = match sw { - SW_SUCCESS => Ok(()), - SW_ERR_AUTH_BLOCKED => Err(Error::AuthenticationError), - SW_ERR_SECURITY_STATUS => Err(Error::AuthenticationError), - _ => Err(Error::GenericError), - }; - } + match status_words { + StatusWords::Success => Ok(()), + StatusWords::AuthBlockedError => Err(Error::AuthenticationError), + StatusWords::SecurityStatusError => Err(Error::AuthenticationError), + _ => Err(Error::GenericError), } - - let _ = self._ykpiv_end_transaction(); - res - } - - /// Change the PIN - pub(crate) unsafe fn _ykpiv_change_pin( - &mut self, - action: i32, - current_pin: *const c_char, - current_pin_len: usize, - new_pin: *const c_char, - new_pin_len: usize, - ) -> Result<(), Error> { - let mut sw: i32 = 0; - let mut templ = [0, YKPIV_INS_CHANGE_REFERENCE, 0, 0x80]; - let mut indata = [0u8; 16]; - let mut data = [0u8; 255]; - let mut recv_len: usize = data.len(); - - if current_pin_len > 8 || new_pin_len > 8 { - 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; - } - - memcpy( - indata.as_mut_ptr() as (*mut c_void), - current_pin as (*const c_void), - current_pin_len, - ); - - if current_pin_len < 8 { - memset( - indata.as_mut_ptr().add(current_pin_len) as *mut c_void, - 0xff, - 8 - current_pin_len, - ); - } - - memcpy( - indata.as_mut_ptr().offset(8) as *mut c_void, - new_pin as *const c_void, - new_pin_len, - ); - - if new_pin_len < 8 { - memset( - indata.as_mut_ptr().offset(8).add(new_pin_len) as *mut c_void, - 0xff, - 8 - new_pin_len, - ); - } - - let res = self.ykpiv_transfer_data( - templ.as_ptr(), - indata.as_mut_ptr(), - indata.len() as isize, - data.as_mut_ptr(), - &mut recv_len, - &mut sw, - ); - - indata.zeroize(); - - if res.is_err() { - return res; - } - - if sw != SW_SUCCESS { - if sw >> 8 == 0x63 { - return Err(Error::WrongPin { tries: sw & 0xf }); - } - - if sw == SW_ERR_AUTH_BLOCKED { - return Err(Error::PinLocked); - } - - error!("failed changing pin, token response code: {:x}.", sw); - return Err(Error::GenericError); - } - - Ok(()) } /// Change the Personal Identification Number (PIN). /// /// The default PIN code is 123456 - pub unsafe fn ykpiv_change_pin( - &mut self, - current_pin: *const c_char, - current_pin_len: usize, - new_pin: *const c_char, - new_pin_len: usize, - ) -> Result<(), Error> { - let mut res = Err(Error::GenericError); - - self._ykpiv_begin_transaction()?; - - if self._ykpiv_ensure_application_selected().is_ok() { - res = self._ykpiv_change_pin(0, current_pin, current_pin_len, new_pin, new_pin_len); - - if res.is_ok() && !new_pin.is_null() { - // Intentionally ignore errors. If the PIN fails to save, it will only - // be a problem if a reconnect is attempted. Failure deferred until then. - let _ = self._cache_pin(new_pin, new_pin_len); - } + pub unsafe fn change_pin(&mut self, current_pin: &[u8], new_pin: &[u8]) -> Result<(), Error> { + { + let txn = self.begin_transaction()?; + txn.change_pin(0, current_pin, new_pin)?; } - let _ = self._ykpiv_end_transaction(); - res + if !new_pin.is_empty() { + self.pin = Some(Buffer::new(new_pin.into())); + } + + Ok(()) + } + + /// Set PIN last changed + pub unsafe fn set_pin_last_changed(yubikey: &mut YubiKey) -> Result<(), Error> { + let mut data = [0u8; YKPIV_OBJ_MAX_SIZE]; + let max_size = yubikey.obj_size_max(); + let txn = yubikey.begin_transaction()?; + + let buffer = metadata::read(&txn, TAG_ADMIN)?; + let mut cb_data = buffer.len(); + data[..cb_data].copy_from_slice(&buffer); + + // TODO(tarcieri): double check this is little endian + let tnow = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() + .to_le_bytes(); + + metadata::set_item( + &mut data, + &mut cb_data, + CB_OBJ_MAX, + TAG_ADMIN_TIMESTAMP, + &tnow, + ) + .map_err(|e| { + error!("could not set pin timestamp, err = {}", e); + e + })?; + + metadata::write(&txn, TAG_ADMIN, &data, max_size).map_err(|e| { + error!("could not write admin data, err = {}", e); + e + })?; + + Ok(()) } /// Change the PIN Unblocking Key (PUK). PUKs are codes for resetting @@ -1502,221 +433,145 @@ impl YubiKey { /// The PUK is part of the PIV standard that the YubiKey follows. /// /// The default PUK code is 12345678. - pub unsafe fn ykpiv_change_puk( - &mut self, - current_puk: *const c_char, - current_puk_len: usize, - new_puk: *const c_char, - new_puk_len: usize, - ) -> Result<(), Error> { - let mut res = Err(Error::GenericError); - - self._ykpiv_begin_transaction()?; - - if self._ykpiv_ensure_application_selected().is_ok() { - res = self._ykpiv_change_pin(2, current_puk, current_puk_len, new_puk, new_puk_len); - } - - let _ = self._ykpiv_end_transaction(); - res + pub unsafe fn change_puk(&mut self, current_puk: &[u8], new_puk: &[u8]) -> Result<(), Error> { + let txn = self.begin_transaction()?; + txn.change_pin(2, current_puk, new_puk) } - /// Unblock a Personal Identification Number (PIN) using a previously - /// configured PIN Unblocking Key (PUK). - pub unsafe fn ykpiv_unblock_pin( - &mut self, - puk: *const c_char, - puk_len: usize, - new_pin: *const c_char, - new_pin_len: usize, - ) -> Result<(), Error> { - let mut res = Err(Error::GenericError); + /// Block PUK: permanently prevent the PIN from becoming unblocked + pub fn block_puk(yubikey: &mut YubiKey) -> Result<(), Error> { + let mut puk = [0x30, 0x42, 0x41, 0x44, 0x46, 0x30, 0x30, 0x44]; + let mut tries_remaining: i32 = -1; + let mut flags = [0]; - self._ykpiv_begin_transaction()?; + let max_size = yubikey.obj_size_max(); + let txn = yubikey.begin_transaction()?; - if self._ykpiv_ensure_application_selected().is_ok() { - res = self._ykpiv_change_pin(1, puk, puk_len, new_pin, new_pin_len); + while tries_remaining != 0 { + // 2 -> change puk + let res = txn.change_pin(2, &puk, &puk); + + match res { + Ok(()) => puk[0] += 1, + Err(Error::WrongPin { tries }) => { + tries_remaining = tries as i32; + continue; + } + Err(e) => { + if e != Error::PinLocked { + continue; + } + tries_remaining = 0; + } + } } - let _ = self._ykpiv_end_transaction(); - res - } - - /// Fetch an object from the YubiKey - pub unsafe fn ykpiv_fetch_object( - &mut self, - object_id: i32, - data: *mut u8, - len: *mut usize, - ) -> Result<(), Error> { - let mut res = Ok(()); - - self._ykpiv_begin_transaction()?; - - if self._ykpiv_ensure_application_selected().is_ok() { - res = self._ykpiv_fetch_object(object_id, data, len); + if let Ok(data) = metadata::read(&txn, TAG_ADMIN) { + if let Ok(item) = metadata::get_item(&data, TAG_ADMIN_FLAGS_1) { + if item.len() == flags.len() { + flags.copy_from_slice(item) + } else { + error!( + "admin flags exist, but are incorrect size: {} (expected {})", + item.len(), + flags.len() + ); + } + } } - let _ = self._ykpiv_end_transaction(); - res - } + flags[0] |= ADMIN_FLAGS_1_PUK_BLOCKED; + let mut data = [0u8; YKPIV_OBJ_MAX_SIZE]; + let mut cb_data: usize = data.len(); - /// Fetch an object - pub(crate) unsafe fn _ykpiv_fetch_object( - &mut self, - object_id: i32, - data: *mut u8, - len: *mut usize, - ) -> Result<(), Error> { - let mut sw: i32 = 0; - let mut indata = [0u8; 5]; - let mut inptr: *mut u8 = indata.as_mut_ptr(); - let templ = [0, YKPIV_INS_GET_DATA, 0x3f, 0xff]; - - inptr = set_object(object_id, inptr); - - if inptr.is_null() { - return Err(Error::InvalidObject); + if metadata::set_item( + &mut data, + &mut cb_data, + CB_OBJ_MAX, + TAG_ADMIN_FLAGS_1, + &flags, + ) + .is_ok() + { + if metadata::write(&txn, TAG_ADMIN, &data[..cb_data], max_size).is_err() { + error!("could not write admin metadata"); + } + } else { + error!("could not set admin flags"); } - self.ykpiv_transfer_data( - templ.as_ptr(), - indata.as_mut_ptr(), - inptr as isize - indata.as_mut_ptr() as isize, - data, - len, - &mut sw, - )?; - - if sw != SW_SUCCESS { - return Err(Error::GenericError); - } - - let mut outlen: usize = 0; - - if *len < 2 || !_ykpiv_has_valid_length(data.offset(1), (*len).wrapping_sub(1)) { - return Err(Error::SizeError); - } - - let offs = _ykpiv_get_length(data.offset(1), &mut outlen); - - if offs == 0 { - return Err(Error::SizeError); - } - - if outlen.wrapping_add(offs).wrapping_add(1) != *len { - error!( - "invalid length indicated in object: total len is {} but indicated length is {}", - *len, outlen - ); - - return Err(Error::SizeError); - } - - memmove( - data as *mut c_void, - data.add(1).add(offs) as *const c_void, - outlen, - ); - *len = outlen; - Ok(()) } - /// Save an object - pub unsafe fn ykpiv_save_object( - &mut self, - object_id: i32, - indata: *mut u8, - len: usize, - ) -> Result<(), Error> { - let mut res = Ok(()); + /// Unblock a Personal Identification Number (PIN) using a previously + /// configured PIN Unblocking Key (PUK). + pub unsafe fn unblock_pin(&mut self, puk: &[u8], new_pin: &[u8]) -> Result<(), Error> { + let txn = self.begin_transaction()?; + txn.change_pin(1, puk, new_pin) + } - self._ykpiv_begin_transaction()?; - - if self._ykpiv_ensure_application_selected().is_ok() { - res = self._ykpiv_save_object(object_id, indata, len); - } - - let _ = self._ykpiv_end_transaction(); - res + /// Fetch an object from the YubiKey + pub fn fetch_object(&mut self, object_id: ObjectId) -> Result { + let txn = self.begin_transaction()?; + txn.fetch_object(object_id) } /// Save an object - pub unsafe fn _ykpiv_save_object( - &mut self, - object_id: i32, - indata: *mut u8, - len: usize, - ) -> Result<(), Error> { - let mut data = [0u8; YKPIV_OBJ_MAX_SIZE]; - let mut dataptr: *mut u8 = data.as_mut_ptr(); - let templ = [0, YKPIV_INS_PUT_DATA, 0x3f, 0xff]; - let mut sw: i32 = 0; - let mut outlen: usize = 0usize; - - if len > CB_OBJ_MAX { - return Err(Error::SizeError); - } - - dataptr = set_object(object_id, dataptr); - - if dataptr.is_null() { - return Err(Error::InvalidObject); - } - *{ - let _old = dataptr; - dataptr = dataptr.offset(1); - _old - } = 0x53; - - dataptr = dataptr.add(_ykpiv_set_length(dataptr, len)); - memcpy(dataptr as (*mut c_void), indata as (*const c_void), len); - dataptr = dataptr.add(len); - - self._ykpiv_transfer_data( - templ.as_ptr(), - data.as_mut_ptr(), - dataptr as isize - data.as_mut_ptr() as isize, - ptr::null_mut(), - &mut outlen, - &mut sw, - )?; - - match sw { - SW_SUCCESS => Ok(()), - SW_ERR_SECURITY_STATUS => Err(Error::AuthenticationError), - _ => Err(Error::GenericError), - } + pub fn save_object(&mut self, object_id: ObjectId, indata: &mut [u8]) -> Result<(), Error> { + let txn = self.begin_transaction()?; + txn.save_object(object_id, indata) } /// Import a private encryption or signing key into the YubiKey - pub unsafe fn ykpiv_import_private_key( + // TODO(tarcieri): refactor this into separate methods per key type + pub fn import_private_key( &mut self, - key: u8, + key: SlotId, algorithm: u8, - p: *const u8, - p_len: usize, - q: *const u8, - q_len: usize, - dp: *const u8, - dp_len: usize, - dq: *const u8, - dq_len: usize, - qinv: *const u8, - qinv_len: usize, - ec_data: *const u8, - ec_data_len: u8, + p: Option<&[u8]>, + q: Option<&[u8]>, + dp: Option<&[u8]>, + dq: Option<&[u8]>, + qinv: Option<&[u8]>, + ec_data: Option<&[u8]>, pin_policy: u8, touch_policy: u8, ) -> Result<(), Error> { - let mut key_data = [0u8; 1024]; + // TODO(tarcieri): get rid of legacy pointers + let (p, p_len) = match p { + Some(slice) => (slice.as_ptr(), slice.len()), + None => (ptr::null(), 0), + }; + + let (q, q_len) = match q { + Some(slice) => (slice.as_ptr(), slice.len()), + None => (ptr::null(), 0), + }; + + let (dp, dp_len) = match dp { + Some(slice) => (slice.as_ptr(), slice.len()), + None => (ptr::null(), 0), + }; + + let (dq, dq_len) = match dq { + Some(slice) => (slice.as_ptr(), slice.len()), + None => (ptr::null(), 0), + }; + + let (qinv, qinv_len) = match qinv { + Some(slice) => (slice.as_ptr(), slice.len()), + None => (ptr::null(), 0), + }; + + let (ec_data, ec_data_len) = match ec_data { + Some(slice) => (slice.as_ptr(), slice.len()), + None => (ptr::null(), 0), + }; + + let mut key_data = Zeroizing::new(vec![0u8; 1024]); let mut in_ptr: *mut u8 = key_data.as_mut_ptr(); let templ = [0, YKPIV_INS_IMPORT_KEY, algorithm, key]; - let mut data = [0u8; 256]; - let mut recv_len = data.len(); let mut elem_len: u32 = 0; - let mut sw: i32 = 0; let mut params: [*const u8; 5] = [ptr::null(); 5]; let mut lens = [0usize; 5]; let n_params: u8; @@ -1779,7 +634,7 @@ impl YubiKey { } } YKPIV_ALGO_ECCP256 | YKPIV_ALGO_ECCP384 => { - if ec_data_len as (usize) >= key_data.len() { + if ec_data_len >= key_data.len() { return Err(Error::SizeError); } @@ -1794,7 +649,7 @@ impl YubiKey { } params[0] = ec_data; - lens[0] = ec_data_len as usize; + lens[0] = ec_data_len; param_tag = 0x6; n_params = 1; } @@ -1802,262 +657,182 @@ impl YubiKey { } for i in 0..n_params { - *in_ptr = (param_tag + i as i32) as u8; - in_ptr = in_ptr.offset(1); + unsafe { + *in_ptr = (param_tag + i as i32) as u8; + in_ptr = in_ptr.offset(1); - in_ptr = in_ptr.add(_ykpiv_set_length(in_ptr, elem_len as usize)); - let padding = (elem_len as (usize)).wrapping_sub(lens[i as usize]); + in_ptr = in_ptr.add(set_length( + slice::from_raw_parts_mut( + in_ptr, + key_data.as_mut_ptr() as usize - in_ptr as usize, + ), + elem_len as usize, + )); + } + + let padding = elem_len as usize - lens[i as usize]; let remaining = (key_data.as_mut_ptr() as usize) + 1024 - in_ptr as usize; if padding > remaining { return Err(Error::AlgorithmError); } - memset(in_ptr as *mut c_void, 0, padding); - in_ptr = in_ptr.add(padding); - memcpy( - in_ptr as *mut c_void, - params[i as usize] as *const c_void, - lens[i as usize], - ); - in_ptr = in_ptr.add(lens[i as usize]); + unsafe { + ptr::write_bytes(in_ptr, 0, padding); + in_ptr = in_ptr.add(padding); + ptr::copy(params[i as usize], in_ptr, lens[i as usize]); + in_ptr = in_ptr.add(lens[i as usize]); + } } if pin_policy != YKPIV_PINPOLICY_DEFAULT { - *in_ptr = YKPIV_PINPOLICY_TAG; - *in_ptr.add(1) = 0x01; - *in_ptr.add(2) = pin_policy; - in_ptr = in_ptr.add(3); + unsafe { + *in_ptr = YKPIV_PINPOLICY_TAG; + *in_ptr.add(1) = 0x01; + *in_ptr.add(2) = pin_policy; + in_ptr = in_ptr.add(3); + } } if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT { - *in_ptr = YKPIV_TOUCHPOLICY_TAG; - *in_ptr.add(1) = 0x01; - *in_ptr.add(2) = touch_policy; - in_ptr = in_ptr.add(3); - } - - self._ykpiv_begin_transaction()?; - - let mut res = Ok(()); - if self._ykpiv_ensure_application_selected().is_ok() { - res = self.ykpiv_transfer_data( - templ.as_ptr(), - key_data.as_mut_ptr(), - in_ptr as isize - key_data.as_mut_ptr() as isize, - data.as_mut_ptr(), - &mut recv_len, - &mut sw, - ); - - if res.is_ok() && sw != SW_SUCCESS { - res = Err(Error::GenericError); - if sw == SW_ERR_SECURITY_STATUS { - res = Err(Error::AuthenticationError); - } + unsafe { + *in_ptr = YKPIV_TOUCHPOLICY_TAG; + *in_ptr.add(1) = 0x01; + *in_ptr.add(2) = touch_policy; + in_ptr = in_ptr.add(3); } } - key_data.zeroize(); - let _ = self._ykpiv_end_transaction(); - res + let txn = self.begin_transaction()?; + let len = in_ptr as usize - key_data.as_mut_ptr() as usize; + + let status_words = txn + .transfer_data(&templ, &key_data[..len], 256)? + .status_words(); + + match status_words { + StatusWords::Success => Ok(()), + StatusWords::SecurityStatusError => Err(Error::AuthenticationError), + _ => Err(Error::GenericError), + } } - /// Generate an attestation certificate for a stored key - pub unsafe fn ykpiv_attest( - &mut self, - key: u8, - data: *mut u8, - data_len: *mut usize, - ) -> Result<(), Error> { - let mut res = Err(Error::GenericError); + /// Generate an attestation certificate for a stored key. + /// + pub fn attest(&mut self, key: SlotId) -> Result { let templ = [0, YKPIV_INS_ATTEST, key, 0]; - let mut sw: i32 = 0; - let mut ul_data_len: usize; + let txn = self.begin_transaction()?; + let response = txn.transfer_data(&templ, &[], CB_OBJ_MAX)?; - if data.is_null() || data_len.is_null() { - return Err(Error::ArgumentError); - } - - ul_data_len = *data_len; - - self._ykpiv_begin_transaction()?; - - if self._ykpiv_ensure_application_selected().is_ok() { - res = self.ykpiv_transfer_data( - templ.as_ptr(), - ptr::null(), - 0, - data, - &mut ul_data_len, - &mut sw, - ); - - if res.is_ok() { - if sw != SW_SUCCESS { - res = Err(Error::GenericError); - if sw == SW_ERR_NOT_SUPPORTED { - res = Err(Error::NotSupported); - } - } else if *data as i32 != 0x30 { - res = Err(Error::GenericError); - } else { - *data_len = ul_data_len; - } + if !response.is_success() { + if response.status_words() == StatusWords::NotSupportedError { + return Err(Error::NotSupported); + } else { + return Err(Error::GenericError); } } - let _ = self._ykpiv_end_transaction(); - res + if response.buffer()[0] != 0x30 { + return Err(Error::GenericError); + } + + Ok(response.into_buffer()) } /// Get an auth challenge - pub unsafe fn ykpiv_auth_getchallenge(&mut self) -> Result<[u8; 8], Error> { - let mut data = [0u8; 261]; - let mut recv_len = data.len() as u32; - let mut sw: i32 = 0; - // TODO(str4d): What should the default value be if the application is not selected? - let mut res = Ok([0; 8]); + pub fn get_auth_challenge(&mut self) -> Result<[u8; 8], Error> { + let txn = self.begin_transaction()?; - self._ykpiv_begin_transaction()?; + let response = APDU::new(YKPIV_INS_AUTHENTICATE) + .params(YKPIV_ALGO_3DES, YKPIV_KEY_CARDMGM) + .data(&[0x7c, 0x02, 0x81, 0x00]) + .transmit(&txn, 261)?; - if self._ykpiv_ensure_application_selected().is_ok() { - let apdu = APDU::new(YKPIV_INS_AUTHENTICATE) - .params(YKPIV_ALGO_3DES, YKPIV_KEY_CARDMGM) - .data(&[0x7c, 0x02, 0x81, 0x00]) - .to_bytes(); - - if let Err(e) = - self._send_data(apdu.as_slice(), data.as_mut_ptr(), &mut recv_len, &mut sw) - { - res = Err(e) - } else if sw != SW_SUCCESS { - res = Err(Error::AuthenticationError); - } else { - let mut challenge = [0; 8]; - challenge.copy_from_slice(&data[4..12]); - res = Ok(challenge); - } + if !response.is_success() { + return Err(Error::AuthenticationError); } - let _ = self._ykpiv_end_transaction(); - res + Ok(response.buffer()[4..12].try_into().unwrap()) } /// Verify an auth response - pub unsafe fn ykpiv_auth_verifyresponse(&mut self, response: [u8; 8]) -> Result<(), Error> { - self._ykpiv_begin_transaction()?; - - let mut data = [ - 0x7c, 0x0a, 0x82, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ]; - + pub fn verify_auth_response(&mut self, response: [u8; 8]) -> Result<(), Error> { + let mut data = [0u8; 12]; + data[0] = 0x7c; + data[1] = 0x0a; + data[2] = 0x82; + data[3] = 0x08; data[4..12].copy_from_slice(&response); + let txn = self.begin_transaction()?; + // send the response to the card and a challenge of our own. - let apdu = APDU::new(YKPIV_INS_AUTHENTICATE) + let status_words = APDU::new(YKPIV_INS_AUTHENTICATE) .params(YKPIV_ALGO_3DES, YKPIV_KEY_CARDMGM) .data(&data) - .to_bytes(); + .transmit(&txn, 261)? + .status_words(); - let mut data = [0u8; 261]; - let mut recv_len = data.len() as u32; - let mut sw: i32 = 0; - let mut res = self._send_data(apdu.as_slice(), data.as_mut_ptr(), &mut recv_len, &mut sw); - - if res.is_ok() && sw != SW_SUCCESS { - res = Err(Error::AuthenticationError); + if !status_words.is_success() { + return Err(Error::AuthenticationError); } - let _ = self._ykpiv_end_transaction(); - res + Ok(()) } /// Deauthenticate - pub unsafe fn ykpiv_auth_deauthenticate(&mut self) -> Result<(), Error> { - let mut data = [0u8; 255]; - let mut recv_len = data.len() as u32; - let mut sw: i32 = 0; + pub fn deauthenticate(&mut self) -> Result<(), Error> { + let txn = self.begin_transaction()?; - self._ykpiv_begin_transaction()?; - - let apdu = APDU::new(YKPIV_INS_SELECT_APPLICATION) + let status_words = APDU::new(YKPIV_INS_SELECT_APPLICATION) .p1(0x04) .data(MGMT_AID) - .to_bytes(); + .transmit(&txn, 255)? + .status_words(); - let mut res = self._send_data(apdu.as_slice(), data.as_mut_ptr(), &mut recv_len, &mut sw); - - if let Err(e) = &res { - error!("failed communicating with card: \'{}\'", e); + if !status_words.is_success() { + error!( + "Failed selecting mgmt application: {:04x}", + status_words.code() + ); + return Err(Error::GenericError); } - if sw != SW_SUCCESS { - error!("Failed selecting mgmt application: {:04x}", sw); - res = Err(Error::GenericError); + Ok(()) + } + + /// Get YubiKey device model + // TODO(tarcieri): use an emum for this + pub fn device_model(&self) -> u32 { + if self.is_neo { + DEVTYPE_NEOr3 + } else { + // TODO(tarcieri): YK5? + DEVTYPE_YK4 + } + } + + /// Reset YubiKey. + /// + /// WARNING: this is a destructive operation which will destroy all keys! + pub fn reset_device(&mut self) -> Result<(), Error> { + let templ = [0, YKPIV_INS_RESET, 0, 0]; + let txn = self.begin_transaction()?; + let status_words = txn.transfer_data(&templ, &[], 255)?.status_words(); + + if !status_words.is_success() { + return Err(Error::GenericError); } - let _ = self._ykpiv_end_transaction(); - res - } -} - -/// Set length -pub(crate) unsafe fn _ykpiv_set_length(buffer: *mut u8, length: usize) -> usize { - if length < 0x80 { - *buffer = length as u8; - 1 - } else if length < 0x100 { - *buffer = 0x81; - *buffer.add(1) = length as u8; - 2 - } else { - *buffer = 0x82; - *buffer.add(1) = ((length >> 8) & 0xff) as u8; - *buffer.add(2) = (length & 0xff) as u8; - 3 - } -} - -/// Get length -pub(crate) unsafe fn _ykpiv_get_length(buffer: *const u8, len: *mut usize) -> usize { - if *buffer < 0x81 { - *len = *buffer as usize; - 1 - } else if (*buffer & 0x7f) == 1 { - *len = *buffer.add(1) as usize; - 2 - } else if (*buffer & 0x7f) == 2 { - let tmp = *buffer.add(1) as usize; - *len = (tmp << 8) + *buffer.add(2) as usize; - 3 - } else { - 0 - } -} - -/// Is length valid? -pub(crate) unsafe fn _ykpiv_has_valid_length(buffer: *const u8, len: usize) -> bool { - ((*buffer as i32) < 0x81 && (len > 0)) - || ((*buffer as i32 & 0x7f == 1) && (len > 1)) - || ((*buffer as i32 & 0x7f == 2) && (len > 2)) -} - -/// Set an object -pub(crate) unsafe fn set_object(object_id: i32, mut buffer: *mut u8) -> *mut u8 { - *buffer = 0x5c; - - if object_id == YKPIV_OBJ_DISCOVERY as i32 { - *buffer.add(1) = 1; - *buffer.add(2) = YKPIV_OBJ_DISCOVERY as u8; - buffer = buffer.add(3); - } else if object_id > 0xffff && object_id <= 0x00ff_ffff { - *buffer.add(1) = 3; - *buffer.add(2) = ((object_id >> 16) & 0xff) as u8; - *buffer.add(3) = ((object_id >> 8) & 0xff) as u8; - *buffer.add(4) = (object_id & 0xff) as u8; - buffer = buffer.add(5); + Ok(()) } - buffer + /// Get max object size supported by this device + pub(crate) fn obj_size_max(&self) -> usize { + if self.is_neo { + CB_OBJ_MAX_NEO + } else { + CB_OBJ_MAX + } + } }