From fd77e9f84422a8b96620c98d2d71130a2467ce64 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 25 Nov 2019 09:45:02 -0800 Subject: [PATCH] tests: Initial connect test and docs Adds an extremely basic initial test to ensure that we are able to connect to a YubiKey. The test is marked `#[ignore]` in the hope that we can eventually start adding tests which run in CI, e.g. against a mock card. This also includes a fix for calculating the APDU size, since the ones we were sending originally were overly long. --- Cargo.toml | 5 +- README.md | 68 ++- src/apdu.rs | 93 ++-- src/lib.rs | 31 +- src/yubikey.rs | 26 +- tests/Makefile.am | 46 -- tests/api.c | 993 ------------------------------------------- tests/basic.c | 98 ----- tests/integration.rs | 20 + tests/parse_key.c | 101 ----- 10 files changed, 167 insertions(+), 1314 deletions(-) delete mode 100644 tests/Makefile.am delete mode 100644 tests/api.c delete mode 100644 tests/basic.c create mode 100644 tests/integration.rs delete mode 100644 tests/parse_key.c diff --git a/Cargo.toml b/Cargo.toml index ff12cdd6..a31db29b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "yubikey-piv" version = "0.0.1" # Also update html_root_url in lib.rs when bumping this description = """ Pure Rust host-side driver for the YubiKey Personal Identity Verification (PIV) -CCID application providing general-purpose public-key signing and encryption +application providing general-purpose public-key signing and encryption with hardware-backed private keys for RSA (2048/1024) and ECC (P-256/P-384) algorithms (e.g, PKCS#1v1.5, ECDSA) """ @@ -26,3 +26,6 @@ pcsc = "2" sha-1 = "0.8" subtle = "2" zeroize = "1" + +[dev-dependencies] +env_logger = "0.7" diff --git a/README.md b/README.md index 4f46d44b..173aff5e 100644 --- a/README.md +++ b/README.md @@ -33,32 +33,68 @@ One small problem, it's not done yet... 😫 But it might be close? +## Minimum Supported Rust Version + +- Rust **1.39+** + +## Security Warning + +No security audits of this crate have ever been performed. Presently it is in +an experimental stage and may still contain high-severity issues. + +USE AT YOUR OWN RISK! + ## History 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][4], and then subsequently heavily -refactored into safer, more idiomatic Rust§. +refactored into safer, more idiomatic Rust. 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. -§ *NOTE*: This section is actually full of lies and notes aspirations/goals, - not history. That said, there's been a decent amount of work cleaning up the - mechanically translated code, and at ~5klocs it's not that much. - -## Security Warning - -No security audits of this crate have ever been performed, and it has not been -thoroughly assessed to ensure its operation is constant-time on common CPU -architectures. - -USE AT YOUR OWN RISK! - -## Requirements - -- Rust 1.39+ +## Testing + +To run the full test suite, you'll need a connected YubiKey NEO/4/5 device in +the default state (i.e. default PIN/PUK). + +Tests which run live against a YubiKey device are marked as `#[ignore]` by +default in order to pass when running in a CI environment. To run these +tests locally, invoke the following command: + +``` +cargo test -- --ignored +``` + +This crate makes extensive use of the `log` facade to provide detailed +information about what is happening. If you'd like to print this logging +information while running the tests, set the `RUST_LOG` environment variable +to a relevant loglevel (e.g. `error`, `warn`, `info`, `debug`, `trace`): + +``` +RUST_LOG=info cargo test -- --ignored +``` + +To trace every message sent to/from the card i.e. the raw +Application Protocol Data Unit (APDU) messages, use the `trace` log level: + +``` +running 1 test +[INFO yubikey_piv::yubikey] trying to connect to reader 'Yubico YubiKey OTP+FIDO+CCID' +[INFO yubikey_piv::yubikey] connected to 'Yubico YubiKey OTP+FIDO+CCID' successfully +[TRACE yubikey_piv::transaction] >>> [0, 164, 4, 0, 5, 160, 0, 0, 3, 8] +[TRACE yubikey_piv::transaction] <<< [97, 17, 79, 6, 0, 0, 16, 0, 1, 0, 121, 7, 79, 5, 160, 0, 0, 3, 8, 144, 0] +[TRACE yubikey_piv::transaction] >>> [0, 253, 0, 0, 0] +[TRACE yubikey_piv::transaction] <<< [5, 1, 2, 144, 0] +[TRACE yubikey_piv::transaction] >>> [0, 248, 0, 0, 0] +[TRACE yubikey_piv::transaction] <<< [0, 115, 0, 178, 144, 0] +test connect ... ok +``` + +APDU messages labeled `>>>` are being sent to the YubiKey's internal SmartCard, +and ones labeled `<<<` are the responses. ## Code of Conduct diff --git a/src/apdu.rs b/src/apdu.rs index 5a3c2a98..3b741cf2 100644 --- a/src/apdu.rs +++ b/src/apdu.rs @@ -1,11 +1,41 @@ //! Application Protocol Data Unit (APDU) +// 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::{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; +/// Maximum amount of command data that can be included in an APDU +const APDU_DATA_MAX: usize = 0xFF; /// Application Protocol Data Unit (APDU). /// @@ -13,31 +43,32 @@ pub const APDU_SIZE: usize = 260; /// Chip Card Interface Device (CCID) protocol. #[derive(Clone)] pub(crate) struct APDU { - /// Instruction class - indicates the type of command, e.g. interindustry or proprietary + /// Instruction class: indicates the type of command (e.g. inter-industry or proprietary) cla: u8, - /// Instruction code - indicates the specific command, e.g. "write data" + /// Instruction code: indicates the specific command (e.g. "write data") ins: u8, - /// Instruction parameter 1 for the command, e.g. offset into file at which to write the data + /// Instruction parameter 1 for the command (e.g. offset into file at which to write the data) p1: u8, /// Instruction parameter 2 for the command p2: u8, - /// Length of command - encodes the number of bytes of command data to follow - lc: u8, - - /// Command data - data: [u8; 255], + /// Command data to be sent (`lc` is calculated as `data.len()`) + data: Vec, } impl APDU { /// Create a new APDU with the given instruction code pub fn new(ins: u8) -> Self { - let mut apdu = Self::default(); - apdu.ins = ins; - apdu + Self { + cla: 0, + ins, + p1: 0, + p2: 0, + data: vec![], + } } /// Set this APDU's class @@ -63,19 +94,18 @@ impl APDU { /// /// Panics if the byte slice is more than 255 bytes! pub fn data(&mut self, bytes: impl AsRef<[u8]>) -> &mut Self { - assert_eq!(self.lc, 0, "APDU command already set!"); + assert!(self.data.is_empty(), "APDU command already set!"); let bytes = bytes.as_ref(); assert!( - bytes.len() <= self.data.len(), - "APDU command data too large: {}-bytes", - bytes.len() + bytes.len() <= APDU_DATA_MAX, + "APDU command data too long: {} (max: {})", + bytes.len(), + APDU_DATA_MAX ); - self.lc = bytes.len() as u8; - self.data[..bytes.len()].copy_from_slice(bytes); - + self.data.extend_from_slice(bytes); self } @@ -87,14 +117,13 @@ impl APDU { /// Consume this APDU and return a self-zeroizing buffer pub fn to_bytes(&self) -> Buffer { - let mut bytes = Vec::with_capacity(APDU_SIZE); + let mut bytes = Vec::with_capacity(5 + self.data.len()); bytes.push(self.cla); bytes.push(self.ins); bytes.push(self.p1); bytes.push(self.p2); - bytes.push(self.lc); + bytes.push(self.data.len() as u8); bytes.extend_from_slice(self.data.as_ref()); - Zeroizing::new(bytes) } } @@ -108,25 +137,12 @@ impl Debug for APDU { self.ins, self.p1, self.p2, - self.lc, - &self.data[..] + self.data.len(), + self.data.as_slice() ) } } -impl Default for APDU { - fn default() -> Self { - Self { - cla: 0, - ins: 0, - p1: 0, - p2: 0, - lc: 0, - data: [0u8; 255], - } - } -} - impl Drop for APDU { fn drop(&mut self) { self.zeroize(); @@ -139,7 +155,6 @@ impl Zeroize for APDU { self.ins.zeroize(); self.p1.zeroize(); self.p2.zeroize(); - self.lc.zeroize(); self.data.zeroize(); } } diff --git a/src/lib.rs b/src/lib.rs index 0ec876ba..ae3027ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,15 +1,15 @@ //! [YubiKey][1] PIV: [Personal Identity Verification][2] support for -//! [Yubico][3] devices using the Chip Card Interface Device ([CCID][4]) -//! protocol. +//! [Yubico][3] devices using the Personal Computer/Smart Card ([PC/SC][4]) +//! interface as provided by the [`pcsc` crate][5]. //! -//! **PIV** is a [NIST][5] standard for both *signing* and *encryption* +//! **PIV** is a [NIST][6] standard for both *signing* and *encryption* //! using SmartCards and SmartCard-based hardware tokens like YubiKeys. //! //! This library natively implements the CCID protocol used to manage and //! 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 +//! See [Yubico's guide to PIV-enabled YubiKeys][7] for more information //! on which devices support PIV and the available functionality. //! //! Supported algorithms: @@ -25,26 +25,31 @@ //! This library is a work-in-progress translation and is not yet usable. //! Check back later for updates. //! +//! ## Minimum Supported Rust Version +//! +//! Rust 1.39+ +//! //! ## History //! -//! This library is a Rust translation of the [yubico-piv-tool][7] utility by +//! This library is a Rust translation of the [yubico-piv-tool][8] utility by //! Yubico, which was originally written in C. It was mechanically translated -//! from C into Rust using [Corrode][8], and then subsequently heavily +//! from C into Rust using [Corrode][9], 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][9]. +//! [Yubico PIV Tool Command Line Guide][10]. //! //! [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://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 +//! [4]: https://en.wikipedia.org/wiki/PC/SC +//! [5]: https://github.com/bluetech/pcsc-rust +//! [6]: https://www.nist.gov/ +//! [7]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html +//! [8]: https://github.com/Yubico/yubico-piv-tool/ +//! [9]: https://github.com/jameysharp/corrode +//! [10]: https://www.yubico.com/wp-content/uploads/2016/05/Yubico_PIV_Tool_Command_Line_Guide_en.pdf // Adapted from yubico-piv-tool: // diff --git a/src/yubikey.rs b/src/yubikey.rs index 80114266..968e4446 100644 --- a/src/yubikey.rs +++ b/src/yubikey.rs @@ -155,7 +155,7 @@ impl YubiKey { Ok(yubikey) } - /// Connect to a YubiKey. + /// Connect to a YubiKey PC/SC card. fn connect(context: &Context, name: Option<&[u8]>) -> Result { // ensure PC/SC context is valid context.is_valid()?; @@ -178,7 +178,19 @@ impl YubiKey { info!("trying to connect to reader '{}'", reader.to_string_lossy()); - return Ok(context.connect(reader, pcsc::ShareMode::Shared, pcsc::Protocols::T1)?); + match context.connect(reader, pcsc::ShareMode::Shared, pcsc::Protocols::T1) { + Ok(card) => { + info!("connected to '{}' successfully", reader.to_string_lossy()); + return Ok(card); + } + Err(err) => { + error!( + "skipping '{}' due to connection error: {}", + reader.to_string_lossy(), + err + ); + } + } } error!("error: no usable reader found"); @@ -308,7 +320,7 @@ impl YubiKey { /// Get YubiKey device serial number. /// /// This always uses the cached version queried when the key is initialized. - pub fn get_serial(&mut self) -> Serial { + pub fn serial(&mut self) -> Serial { self.serial } @@ -376,7 +388,7 @@ impl YubiKey { /// Change the Personal Identification Number (PIN). /// /// The default PIN code is 123456 - pub unsafe fn change_pin(&mut self, current_pin: &[u8], new_pin: &[u8]) -> Result<(), Error> { + pub fn change_pin(&mut self, current_pin: &[u8], new_pin: &[u8]) -> Result<(), Error> { { let txn = self.begin_transaction()?; txn.change_pin(CHREF_ACT_CHANGE_PIN, current_pin, new_pin)?; @@ -390,7 +402,7 @@ impl YubiKey { } /// Set PIN last changed - pub unsafe fn set_pin_last_changed(yubikey: &mut YubiKey) -> Result<(), Error> { + pub fn set_pin_last_changed(yubikey: &mut YubiKey) -> Result<(), Error> { let mut data = [0u8; CB_BUF_MAX]; let max_size = yubikey.obj_size_max(); let txn = yubikey.begin_transaction()?; @@ -433,7 +445,7 @@ impl YubiKey { /// The PUK is part of the PIV standard that the YubiKey follows. /// /// The default PUK code is 12345678. - pub unsafe fn change_puk(&mut self, current_puk: &[u8], new_puk: &[u8]) -> Result<(), Error> { + pub fn change_puk(&mut self, current_puk: &[u8], new_puk: &[u8]) -> Result<(), Error> { let txn = self.begin_transaction()?; txn.change_pin(CHREF_ACT_CHANGE_PUK, current_puk, new_puk) } @@ -507,7 +519,7 @@ impl YubiKey { /// 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> { + pub fn unblock_pin(&mut self, puk: &[u8], new_pin: &[u8]) -> Result<(), Error> { let txn = self.begin_transaction()?; txn.change_pin(CHREF_ACT_UNBLOCK_PIN, puk, new_pin) } diff --git a/tests/Makefile.am b/tests/Makefile.am deleted file mode 100644 index 716c69c9..00000000 --- a/tests/Makefile.am +++ /dev/null @@ -1,46 +0,0 @@ -# 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. - -AM_CFLAGS = $(WARN_CFLAGS) @CHECK_CFLAGS@ $(OPENSSL_CFLAGS) -AM_CPPFLAGS = -I$(top_srcdir)/lib -I$(top_builddir)/lib $(OPENSSL_CFLAGS) $(PCSC_CFLAGS) - -AM_LDFLAGS = @CHECK_LIBS@ - -if COMPILER_CLANG -AM_LDFLAGS += -no-fast-install -else -AM_LDFLAGS += -no-install -endif - -LDADD = ../libykpiv.la $(OPENSSL_LIBS) - -api_LDADD = ../../tool/libpiv_util.la -api_SOURCES = api.c -check_PROGRAMS = basic parse_key api -TESTS = $(check_PROGRAMS) - -LOG_COMPILER = $(VALGRIND) diff --git a/tests/api.c b/tests/api.c deleted file mode 100644 index a41e08c2..00000000 --- a/tests/api.c +++ /dev/null @@ -1,993 +0,0 @@ -/* - * 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. - * - */ - -#include "ykpiv.h" -#include "internal.h" -#include "../../tool/openssl-compat.h" - -#include -#include -#include -#include - -#include - -#ifdef __MINGW32__ -#define dprintf(fd, ...) fprintf(stdout, __VA_ARGS__) -#endif - -int destruction_confirmed(void); - -// only defined in libcheck 0.11+ (linux distros still shipping 0.10) -#ifndef ck_assert_ptr_nonnull -#define ck_assert_ptr_nonnull(a) ck_assert((a) != NULL) -#endif -#ifndef ck_assert_mem_eq -#define ck_assert_mem_eq(a,b,n) ck_assert(memcmp((a), (b), (n)) == 0) -#endif -// only defined in libcheck 0.10+ (RHEL7 is still shipping 0.9) -#ifndef ck_assert_ptr_eq -#define ck_assert_ptr_eq(a,b) ck_assert((void *)(a) == (void *)(b)) -#endif - -ykpiv_state *g_state; -const uint8_t g_cert[] = { - "0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK" - "0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK" - "0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK" - "0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK" - "0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK" -}; - -void setup(void) { - ykpiv_rc res; - - // Require user confirmation to continue, since this test suite will clear - // any data stored on connected keys. - if (!destruction_confirmed()) - exit(77); // exit code 77 == skipped tests - - res = ykpiv_init(&g_state, true); - ck_assert_int_eq(res, YKPIV_OK); - ck_assert_ptr_nonnull(g_state); - - res = ykpiv_connect(g_state, NULL); - ck_assert_int_eq(res, YKPIV_OK); -} - -void teardown(void) { - ykpiv_rc res; - - // This is the expected case, if the allocator test ran, since it de-inits. - if (NULL == g_state) - return; - - res = ykpiv_disconnect(g_state); - ck_assert_int_eq(res, YKPIV_OK); - - res = ykpiv_done(g_state); - ck_assert_int_eq(res, YKPIV_OK); -} - -#ifdef HW_TESTS -START_TEST(test_devicemodel) { - ykpiv_rc res; - ykpiv_devmodel model; - char version[256]; - char reader_buf[2048]; - size_t num_readers = sizeof(reader_buf); - - res = ykpiv_get_version(g_state, version, sizeof(version)); - ck_assert_int_eq(res, YKPIV_OK); - fprintf(stderr, "Version: %s\n", version); - model = ykpiv_util_devicemodel(g_state); - fprintf(stdout, "Model: %u\n", model); - ck_assert(model == DEVTYPE_YK4 || model == DEVTYPE_NEOr3); - - res = ykpiv_list_readers(g_state, reader_buf, &num_readers); - ck_assert_int_eq(res, YKPIV_OK); - ck_assert_int_gt(num_readers, 0); - if (model == DEVTYPE_YK4) { - ck_assert_ptr_nonnull(strstr(reader_buf, "Yubikey 4")); - ck_assert(version[0] == '4'); // Verify app version 4.x - ck_assert(version[1] == '.'); - } - else { - ck_assert_ptr_nonnull(strstr(reader_buf, "Yubikey NEO")); - ck_assert(version[0] == '1'); // Verify app version 1.x - ck_assert(version[1] == '.'); - } -} -END_TEST - -START_TEST(test_get_set_cardid) { - ykpiv_rc res; - ykpiv_cardid set_id; - ykpiv_cardid get_id; - - memset(&set_id.data, 'i', sizeof(set_id.data)); - memset(&get_id.data, 0, sizeof(get_id.data)); - - res = ykpiv_util_set_cardid(g_state, &set_id); - ck_assert_int_eq(res, YKPIV_OK); - - res = ykpiv_util_get_cardid(g_state, &get_id); - ck_assert_int_eq(res, YKPIV_OK); - ck_assert_mem_eq(&set_id.data, &get_id.data, sizeof(set_id.data)); -} -END_TEST - -START_TEST(test_list_readers) { - ykpiv_rc res; - char reader_buf[2048]; - size_t num_readers = sizeof(reader_buf); - char *reader_ptr; - res = ykpiv_list_readers(g_state, reader_buf, &num_readers); - ck_assert_int_eq(res, YKPIV_OK); - ck_assert_int_gt(num_readers, 0); - for(reader_ptr = reader_buf; *reader_ptr != '\0'; reader_ptr += strlen(reader_ptr) + 1) { - fprintf(stdout, "Found device: %s\n", reader_ptr); - } -} -END_TEST - -START_TEST(test_read_write_list_delete_cert) { - ykpiv_rc res; - uint8_t *read_cert = NULL; - size_t read_cert_len = 0; - - { - res = ykpiv_util_write_cert(g_state, YKPIV_KEY_AUTHENTICATION, (uint8_t*)g_cert, sizeof(g_cert), YKPIV_CERTINFO_UNCOMPRESSED); - ck_assert_int_eq(res, YKPIV_OK); - - res = ykpiv_util_read_cert(g_state, YKPIV_KEY_AUTHENTICATION, &read_cert, &read_cert_len); - ck_assert_int_eq(res, YKPIV_OK); - ck_assert_ptr_nonnull(read_cert); - ck_assert_int_eq(read_cert_len, sizeof(g_cert)); - ck_assert_mem_eq(g_cert, read_cert, sizeof(g_cert)); - - res = ykpiv_util_free(g_state, read_cert); - ck_assert_int_eq(res, YKPIV_OK); - } - - { - ykpiv_key *keys = NULL; - size_t data_len; - uint8_t key_count; - res = ykpiv_util_list_keys(g_state, &key_count, &keys, &data_len); - ck_assert_int_eq(res, YKPIV_OK); - ck_assert_ptr_nonnull(keys); - ck_assert_int_gt(key_count, 0); - - res = ykpiv_util_free(g_state, keys); - ck_assert_int_eq(res, YKPIV_OK); - } - - { - res = ykpiv_util_delete_cert(g_state, YKPIV_KEY_AUTHENTICATION); - ck_assert_int_eq(res, YKPIV_OK); - - res = ykpiv_util_read_cert(g_state, YKPIV_KEY_AUTHENTICATION, &read_cert, &read_cert_len); - ck_assert_int_eq(res, YKPIV_GENERIC_ERROR); - - res = ykpiv_util_free(g_state, read_cert); - ck_assert_int_eq(res, YKPIV_OK); - } -} -END_TEST - -#include -#include -#include -#include - -// RSA2048 private key, generated with: `openssl genrsa 2048 -out private.pem` -static const char *private_key_pem = - "-----BEGIN RSA PRIVATE KEY-----\n" - "MIIEpAIBAAKCAQEAwVUwmVbc+ffOy2+RivxBpgleTVN6bUa0q7jNYB+AseFQYaYq\n" - "EGfa+VGdxSGo+8DV1KT9+fNEd5243gXn/tcjtMItKeB+oAQc64s9lIFlYuR8bpq1\n" - "ibr33iW2elnnv9mpecqohdCVwM2McWveoPyb7MwlwVuhqexOzJO29bqJcazLbtkf\n" - "ZETK0oBx53/ylA4Y6nE9Pa46jW2qhj+KShf1iBg+gAyt3eI+wI2Wmub1WxLLH8D2\n" - "w+kow8QhQOa8dHCkRRw771JxVO5+d+Y/Y+x9B1HgF4q0q9xUlhWLK2TR4ChBFzXe\n" - "47sAHsSqi/pl5JbwYrHPOE/VEBLukmjL8NFCSQIDAQABAoIBADmEyOK2DyRnb6Ti\n" - "2qBJEJb/boj+7wuX36S/ZIrWlIlXiXyj3RvoaiOG/rNpokbURknvlIhKsfIMgLW9\n" - "eBo/k6Xxp1IwMjwVPS1uzbFjFfDoHYUijiQd9iSnf7TDDsnrThqoCp9VQViNTt1n\n" - "xGKNBS7cRddTFbPiVEdVIzfUeZPR2oRrc4maBCRCrQgg8WNknawmc8zhkf2NiPj3\n" - "tWLQHMy1/MgW2W1LM9sgzllEtS5CZUnyGy2HbbhS2tbZ6j9kPzOp0pPxxTTzJmmV\n" - "fi1vkJcVW4+MdXjWmhALcPA4dO7Y2Ljiu6VxIxQORRO1DyiCjAs1AVMQxgPAAY41\n" - "YR4Q2EkCgYEA4zE0oytg97aVaBY9CKi7/PqR+NI/uEvfoQCnT+ddaJgp/qsspuXo\n" - "tJt94p13ANd8O7suqQTVNvbZq1rX10xQjJZ9nvlqQa6iHkN6Epq31XBK3Z+acjIV\n" - "A2rAgKBByjz9/CpKHqnOsrTWU1Y7x416IG4BZt42hHdrxRH98/wiDH8CgYEA2djj\n" - "AjwgK+MwDnshwT1NNgCSP/2ZHatBAykZ5BCs9BJ6MNYqqXVGYoqs5Z5kSkow+Db3\n" - "pipkEieo5w2Rd5zkolTThaVCvRkSe5wRiBpZhaeY+b0UFwavGCb6zU/MmJIMDPiI\n" - "2iRGeCXgQDvIS/icIqzbTtp6dZaoMgG7LdSR7TcCgYBtxGhaLas8A8tL7vKuLFgn\n" - "cij0vyBqOr5hW596y54l2t7vXGTGfm5gVIAN7WaB0ZsEgPuaTet2Eu44DDwcmZKR\n" - "WmR3Wqor8eQCGzfvpTEMvqRtT5+fbPMaI4m+m68ttyo/m28UQZbMYPLscM2RLJnE\n" - "8WFcAiD0/33iST8ZksggoQKBgQDE/7Yhsj+hkHxHzB+1QPtOp2uaBHnvc4uCESwB\n" - "qvbMbN0kxrejsJLqz98UcozdBYSNIiAHmvQN2uGJuCJhGXdEORNjGxRkLoUhVPwh\n" - "qTplfC8BQHQncnrqi21oNw6ctg3BuQsAwaccRZwqWiWCVhrT3J8iCr6NEaWeOySK\n" - "iF1CNwKBgQCRpkkZArlccwS0kMvkK+tQ1rG2xWm7c05G34gP/g6dHFRy0gPNMyvi\n" - "SkiLTJmQIEZSAEiq0FFgcVwM6o556ftvQZuwDp5rHUbwqnHCpMJKpD9aJpStvfPi\n" - "4p9JbYdaGqnq4eoNKemmGnbUof0dR9Zr0lGmcMTwwzBib+4E1d7soA==\n" - "-----END RSA PRIVATE KEY-----\n"; - -// Certificate signed with key above: -// `openssl req -x509 -key private.pem -out cert.pem -subj "/CN=bar/OU=test/O=example.com/" -new` -static const char *certificate_pem = - "-----BEGIN CERTIFICATE-----\n" - "MIIC5zCCAc+gAwIBAgIJAOq8A/cmpxF5MA0GCSqGSIb3DQEBCwUAMDMxDDAKBgNV\n" - "BAMMA2JhcjENMAsGA1UECwwEdGVzdDEUMBIGA1UECgwLZXhhbXBsZS5jb20wHhcN\n" - "MTcwODAzMTE1MDI2WhcNMTgwODAzMTE1MDI2WjAzMQwwCgYDVQQDDANiYXIxDTAL\n" - "BgNVBAsMBHRlc3QxFDASBgNVBAoMC2V4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0B\n" - "AQEFAAOCAQ8AMIIBCgKCAQEAwVUwmVbc+ffOy2+RivxBpgleTVN6bUa0q7jNYB+A\n" - "seFQYaYqEGfa+VGdxSGo+8DV1KT9+fNEd5243gXn/tcjtMItKeB+oAQc64s9lIFl\n" - "YuR8bpq1ibr33iW2elnnv9mpecqohdCVwM2McWveoPyb7MwlwVuhqexOzJO29bqJ\n" - "cazLbtkfZETK0oBx53/ylA4Y6nE9Pa46jW2qhj+KShf1iBg+gAyt3eI+wI2Wmub1\n" - "WxLLH8D2w+kow8QhQOa8dHCkRRw771JxVO5+d+Y/Y+x9B1HgF4q0q9xUlhWLK2TR\n" - "4ChBFzXe47sAHsSqi/pl5JbwYrHPOE/VEBLukmjL8NFCSQIDAQABMA0GCSqGSIb3\n" - "DQEBCwUAA4IBAQCamrwdEhNmY2GCQWq6U90Q3XQT6w0HHW/JmtuGeF+BTpVr12gN\n" - "/UvEXTo9geWbGcCTjaMMURTa7mUjVUIttIWEVHZMKqBuvsUM1RcuOEX/vitaJJ8K\n" - "Sw4upjCNa3ZxUXmSA1FBixZgDzFqjEeSiaJjMU0yX5W2p1T4iNYtF3YqzMF5AWSI\n" - "qCO7gP5ezPyg5kDnrO3V7DBgnDiqawq7Pyn9DynKNULX/hc1yls/R+ebb2u8Z+h5\n" - "W4YXbzGZb8qdT27qIZaHD638tL6liLkI6UE4KCXH8X8e3fqdbmqvwrq403nOGmsP\n" - "cbJb2PEXibNEQG234riKxm7x7vNDLL79Jwtc\n" - "-----END CERTIFICATE-----\n"; - -static bool set_component(unsigned char *in_ptr, const BIGNUM *bn, int element_len) { - int real_len = BN_num_bytes(bn); - - if(real_len > element_len) { - return false; - } - memset(in_ptr, 0, (size_t)(element_len - real_len)); - in_ptr += element_len - real_len; - BN_bn2bin(bn, in_ptr); - - return true; -} - -static bool prepare_rsa_signature(const unsigned char *in, unsigned int in_len, unsigned char *out, unsigned int *out_len, int nid) { - X509_SIG *digestInfo; - X509_ALGOR *algor; - ASN1_OCTET_STRING *digest; - unsigned char data[1024]; - - memcpy(data, in, in_len); - - digestInfo = X509_SIG_new(); - X509_SIG_getm(digestInfo, &algor, &digest); - algor->algorithm = OBJ_nid2obj(nid); - X509_ALGOR_set0(algor, OBJ_nid2obj(nid), V_ASN1_NULL, NULL); - ASN1_STRING_set(digest, data, in_len); - *out_len = (unsigned int)i2d_X509_SIG(digestInfo, &out); - X509_SIG_free(digestInfo); - return true; -} - -static void import_key(unsigned char slot, unsigned char pin_policy) { - ykpiv_rc res; - { - unsigned char pp = pin_policy; - unsigned char tp = YKPIV_TOUCHPOLICY_DEFAULT; - EVP_PKEY *private_key = NULL; - BIO *bio = NULL; - RSA *rsa_private_key = NULL; - unsigned char e[4]; - unsigned char p[128]; - unsigned char q[128]; - unsigned char dmp1[128]; - unsigned char dmq1[128]; - unsigned char iqmp[128]; - int element_len = 128; - const BIGNUM *bn_e, *bn_p, *bn_q, *bn_dmp1, *bn_dmq1, *bn_iqmp; - - bio = BIO_new_mem_buf(private_key_pem, strlen(private_key_pem)); - private_key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL); - ck_assert_ptr_nonnull(private_key); - BIO_free(bio); - rsa_private_key = EVP_PKEY_get1_RSA(private_key); - ck_assert_ptr_nonnull(rsa_private_key); - RSA_get0_key(rsa_private_key, NULL, &bn_e, NULL); - RSA_get0_factors(rsa_private_key, &bn_p, &bn_q); - RSA_get0_crt_params(rsa_private_key, &bn_dmp1, &bn_dmq1, &bn_iqmp); - ck_assert(set_component(e, bn_e, 3)); - ck_assert(set_component(p, bn_p, element_len)); - ck_assert(set_component(q, bn_q, element_len)); - ck_assert(set_component(dmp1, bn_dmp1, element_len)); - ck_assert(set_component(dmq1, bn_dmq1, element_len)); - ck_assert(set_component(iqmp, bn_iqmp, element_len)); - - // Try wrong algorithm, fail. - res = ykpiv_import_private_key(g_state, - slot, - YKPIV_ALGO_RSA1024, - p, element_len, - q, element_len, - dmp1, element_len, - dmq1, element_len, - iqmp, element_len, - NULL, 0, - pp, tp); - ck_assert_int_eq(res, YKPIV_ALGORITHM_ERROR); - - // Try right algorithm - res = ykpiv_import_private_key(g_state, - slot, - YKPIV_ALGO_RSA2048, - p, element_len, - q, element_len, - dmp1, element_len, - dmq1, element_len, - iqmp, element_len, - NULL, 0, - pp, tp); - ck_assert_int_eq(res, YKPIV_OK); - RSA_free(rsa_private_key); - EVP_PKEY_free(private_key); - } - - // Use imported key to decrypt a thing. See that it works. - { - BIO *bio = NULL; - X509 *cert = NULL; - EVP_PKEY *pub_key = NULL; - unsigned char secret[32]; - unsigned char secret2[32]; - unsigned char data[256]; - int len; - size_t len2 = sizeof(data); - RSA *rsa = NULL; - bio = BIO_new_mem_buf(certificate_pem, strlen(certificate_pem)); - cert = PEM_read_bio_X509(bio, NULL, NULL, NULL); - ck_assert_ptr_nonnull(cert); - BIO_free(bio); - pub_key = X509_get_pubkey(cert); - ck_assert_ptr_nonnull(pub_key); - rsa = EVP_PKEY_get1_RSA(pub_key); - ck_assert_ptr_nonnull(rsa); - EVP_PKEY_free(pub_key); - - ck_assert_int_gt(RAND_bytes(secret, sizeof(secret)), 0); - len = RSA_public_encrypt(sizeof(secret), secret, data, rsa, RSA_PKCS1_PADDING); - ck_assert_int_ge(len, 0); - res = ykpiv_verify(g_state, "123456", NULL); - ck_assert_int_eq(res, YKPIV_OK); - res = ykpiv_decipher_data(g_state, data, (size_t)len, data, &len2, YKPIV_ALGO_RSA2048, slot); - ck_assert_int_eq(res, YKPIV_OK); - len = RSA_padding_check_PKCS1_type_2(secret2, sizeof(secret2), data + 1, len2 - 1, RSA_size(rsa)); - ck_assert_int_eq(len, sizeof(secret)); - ck_assert_int_eq(memcmp(secret, secret2, sizeof(secret)), 0); - RSA_free(rsa); - X509_free(cert); - } -} - -START_TEST(test_import_key) { - ykpiv_rc res; - - import_key(0x9a, YKPIV_PINPOLICY_DEFAULT); - - // Verify certificate - { - BIO *bio = NULL; - X509 *cert = NULL; - RSA *rsa = NULL; - EVP_PKEY *pub_key = NULL; - const EVP_MD *md = EVP_sha256(); - EVP_MD_CTX *mdctx; - - unsigned char signature[1024]; - unsigned char encoded[1024]; - unsigned char data[1024]; - unsigned char signinput[1024]; - unsigned char rand[128]; - - size_t sig_len = sizeof(signature); - size_t padlen = 256; - unsigned int enc_len; - unsigned int data_len; - - bio = BIO_new_mem_buf(certificate_pem, strlen(certificate_pem)); - cert = PEM_read_bio_X509(bio, NULL, NULL, NULL); - ck_assert_ptr_nonnull(cert); - BIO_free(bio); - pub_key = X509_get_pubkey(cert); - ck_assert_ptr_nonnull(pub_key); - rsa = EVP_PKEY_get1_RSA(pub_key); - ck_assert_ptr_nonnull(rsa); - EVP_PKEY_free(pub_key); - - ck_assert_int_gt(RAND_bytes(rand, 128), 0); - mdctx = EVP_MD_CTX_create(); - EVP_DigestInit_ex(mdctx, md, NULL); - EVP_DigestUpdate(mdctx, rand, 128); - EVP_DigestFinal_ex(mdctx, data, &data_len); - - prepare_rsa_signature(data, data_len, encoded, &enc_len, EVP_MD_type(md)); - ck_assert_int_ne(RSA_padding_add_PKCS1_type_1(signinput, padlen, encoded, enc_len), 0); - res = ykpiv_sign_data(g_state, signinput, padlen, signature, &sig_len, YKPIV_ALGO_RSA2048, 0x9a); - ck_assert_int_eq(res, YKPIV_OK); - - ck_assert_int_eq(RSA_verify(EVP_MD_type(md), data, data_len, signature, sig_len, rsa), 1); - - RSA_free(rsa); - X509_free(cert); - EVP_MD_CTX_destroy(mdctx); - } - - // Verify that imported key can not be attested - { - unsigned char attest[2048]; - size_t attest_len = sizeof(attest); - ykpiv_devmodel model; - model = ykpiv_util_devicemodel(g_state); - res = ykpiv_attest(g_state, 0x9a, attest, &attest_len); - if (model == DEVTYPE_YK4) { - ck_assert_int_eq(res, YKPIV_GENERIC_ERROR); - } - else { - ck_assert_int_eq(res, YKPIV_NOT_SUPPORTED); - } - } -} -END_TEST - -START_TEST(test_pin_policy_always) { - ykpiv_rc res; - - { - ykpiv_devmodel model; - model = ykpiv_util_devicemodel(g_state); - // Only works with YK4. NEO should skip. - if (model != DEVTYPE_YK4) { - fprintf(stderr, "WARNING: Not supported with Yubikey NEO. Test skipped.\n"); - return; - } - } - - import_key(0x9e, YKPIV_PINPOLICY_ALWAYS); - - // Verify certificate - { - BIO *bio = NULL; - X509 *cert = NULL; - RSA *rsa = NULL; - EVP_PKEY *pub_key = NULL; - const EVP_MD *md = EVP_sha256(); - EVP_MD_CTX *mdctx; - - unsigned char signature[1024]; - unsigned char encoded[1024]; - unsigned char data[1024]; - unsigned char signinput[1024]; - unsigned char rand[128]; - - size_t sig_len = sizeof(signature); - size_t padlen = 256; - unsigned int enc_len; - unsigned int data_len; - - bio = BIO_new_mem_buf(certificate_pem, strlen(certificate_pem)); - cert = PEM_read_bio_X509(bio, NULL, NULL, NULL); - ck_assert_ptr_nonnull(cert); - BIO_free(bio); - pub_key = X509_get_pubkey(cert); - ck_assert_ptr_nonnull(pub_key); - rsa = EVP_PKEY_get1_RSA(pub_key); - ck_assert_ptr_nonnull(rsa); - EVP_PKEY_free(pub_key); - - ck_assert_int_gt(RAND_bytes(rand, 128), 0); - mdctx = EVP_MD_CTX_create(); - EVP_DigestInit_ex(mdctx, md, NULL); - EVP_DigestUpdate(mdctx, rand, 128); - EVP_DigestFinal_ex(mdctx, data, &data_len); - - prepare_rsa_signature(data, data_len, encoded, &enc_len, EVP_MD_type(md)); - ck_assert_int_ne(RSA_padding_add_PKCS1_type_1(signinput, padlen, encoded, enc_len), 0); - - // Sign without verify: fail - res = ykpiv_sign_data(g_state, signinput, padlen, signature, &sig_len, YKPIV_ALGO_RSA2048, 0x9e); - ck_assert_int_eq(res, YKPIV_AUTHENTICATION_ERROR); - - // Sign with verify: pass - res = ykpiv_verify(g_state, "123456", NULL); - ck_assert_int_eq(res, YKPIV_OK); - res = ykpiv_sign_data(g_state, signinput, padlen, signature, &sig_len, YKPIV_ALGO_RSA2048, 0x9e); - ck_assert_int_eq(res, YKPIV_OK); - - // Sign again without verify: fail - res = ykpiv_sign_data(g_state, signinput, padlen, signature, &sig_len, YKPIV_ALGO_RSA2048, 0x9e); - ck_assert_int_eq(res, YKPIV_AUTHENTICATION_ERROR); - - // Sign again with verify: pass - res = ykpiv_verify(g_state, "123456", NULL); - ck_assert_int_eq(res, YKPIV_OK); - res = ykpiv_sign_data(g_state, signinput, padlen, signature, &sig_len, YKPIV_ALGO_RSA2048, 0x9e); - ck_assert_int_eq(res, YKPIV_OK); - - ck_assert_int_eq(RSA_verify(EVP_MD_type(md), data, data_len, signature, sig_len, rsa), 1); - - RSA_free(rsa); - X509_free(cert); - EVP_MD_CTX_destroy(mdctx); - } -} -END_TEST - -START_TEST(test_generate_key) { - ykpiv_rc res; - uint8_t *mod, *exp; - size_t mod_len, exp_len; - res = ykpiv_util_write_cert(g_state, YKPIV_KEY_AUTHENTICATION, (uint8_t*)g_cert, sizeof(g_cert), YKPIV_CERTINFO_UNCOMPRESSED); - ck_assert_int_eq(res, YKPIV_OK); - res = ykpiv_util_generate_key(g_state, - YKPIV_KEY_AUTHENTICATION, - YKPIV_ALGO_RSA2048, - YKPIV_PINPOLICY_DEFAULT, - YKPIV_TOUCHPOLICY_DEFAULT, - &mod, - &mod_len, - &exp, - &exp_len, - NULL, - NULL); - ck_assert_int_eq(res, YKPIV_OK); - res = ykpiv_util_free(g_state, mod); - ck_assert_int_eq(res, YKPIV_OK); - res = ykpiv_util_free(g_state, exp); - ck_assert_int_eq(res, YKPIV_OK); - - // Verify that imported key can be attested - { - ykpiv_devmodel model; - unsigned char attest[2048]; - size_t attest_len = sizeof(attest); - model = ykpiv_util_devicemodel(g_state); - res = ykpiv_attest(g_state, YKPIV_KEY_AUTHENTICATION, attest, &attest_len); - // Only works with YK4. NEO should error. - if (model == DEVTYPE_YK4) { - ck_assert_int_eq(res, YKPIV_OK); - ck_assert_int_gt(attest_len, 0); - } - else { - ck_assert_int_eq(res, YKPIV_NOT_SUPPORTED); - } - } -} -END_TEST - -START_TEST(test_authenticate) { - ykpiv_rc res; - const char *default_mgm_key = "010203040506070801020304050607080102030405060708"; - const char *mgm_key = "112233445566778811223344556677881122334455667788"; - const char *weak_mgm_key = "FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFE"; - unsigned char key[24]; - size_t key_len = sizeof(key); - - // Try new key, fail. - res = ykpiv_hex_decode(mgm_key, strlen(mgm_key), key, &key_len); - ck_assert_int_eq(res, YKPIV_OK); - res = ykpiv_authenticate(g_state, key); - ck_assert_int_eq(res, YKPIV_AUTHENTICATION_ERROR); - - // Try default key, succeed - res = ykpiv_hex_decode(default_mgm_key, strlen(default_mgm_key), key, &key_len); - ck_assert_int_eq(res, YKPIV_OK); - res = ykpiv_authenticate(g_state, key); - ck_assert_int_eq(res, YKPIV_OK); - - // Verify same key works twice - res = ykpiv_hex_decode(default_mgm_key, strlen(default_mgm_key), key, &key_len); - ck_assert_int_eq(res, YKPIV_OK); - res = ykpiv_authenticate(g_state, key); - ck_assert_int_eq(res, YKPIV_OK); - - // Change to new key - res = ykpiv_hex_decode(mgm_key, strlen(mgm_key), key, &key_len); - ck_assert_int_eq(res, YKPIV_OK); - res = ykpiv_set_mgmkey(g_state, key); - ck_assert_int_eq(res, YKPIV_OK); - - // Try new key, succeed. - res = ykpiv_hex_decode(mgm_key, strlen(mgm_key), key, &key_len); - ck_assert_int_eq(res, YKPIV_OK); - res = ykpiv_authenticate(g_state, key); - ck_assert_int_eq(res, YKPIV_OK); - - // Change back to default key - res = ykpiv_hex_decode(default_mgm_key, strlen(default_mgm_key), key, &key_len); - ck_assert_int_eq(res, YKPIV_OK); - res = ykpiv_set_mgmkey(g_state, key); - ck_assert_int_eq(res, YKPIV_OK); - - // Try default key, succeed - res = ykpiv_hex_decode(default_mgm_key, strlen(default_mgm_key), key, &key_len); - ck_assert_int_eq(res, YKPIV_OK); - res = ykpiv_authenticate(g_state, key); - ck_assert_int_eq(res, YKPIV_OK); - - // Try to set a weak key, fail - res = ykpiv_hex_decode(weak_mgm_key, strlen(weak_mgm_key), key, &key_len); - ck_assert_int_eq(res, YKPIV_OK); - res = ykpiv_set_mgmkey(g_state, key); - ck_assert_int_eq(res, YKPIV_KEY_ERROR); - - // Try default key, succeed - res = ykpiv_hex_decode(default_mgm_key, strlen(default_mgm_key), key, &key_len); - ck_assert_int_eq(res, YKPIV_OK); - res = ykpiv_authenticate(g_state, key); - ck_assert_int_eq(res, YKPIV_OK); -} -END_TEST - -START_TEST(test_change_pin) { - ykpiv_rc res; - - res = ykpiv_verify(g_state, "123456", NULL); - ck_assert_int_eq(res, YKPIV_OK); - - res = ykpiv_change_pin(g_state, "123456", 6, "ABCDEF", 6, NULL); - ck_assert_int_eq(res, YKPIV_OK); - - res = ykpiv_verify(g_state, "123456", NULL); - ck_assert_int_eq(res, YKPIV_WRONG_PIN); - - res = ykpiv_verify(g_state, "ABCDEF", NULL); - ck_assert_int_eq(res, YKPIV_OK); - - res = ykpiv_change_pin(g_state, "ABCDEF", 6, "123456", 6, NULL); - ck_assert_int_eq(res, YKPIV_OK); - - res = ykpiv_verify(g_state, "ABCDEF", NULL); - ck_assert_int_eq(res, YKPIV_WRONG_PIN); - - res = ykpiv_verify(g_state, "123456", NULL); - ck_assert_int_eq(res, YKPIV_OK); -} -END_TEST - -START_TEST(test_change_puk) { - ykpiv_rc res; - - res = ykpiv_unblock_pin(g_state, "12345678", 8, "123456", 6, NULL); - ck_assert_int_eq(res, YKPIV_OK); - - res = ykpiv_change_puk(g_state, "12345678", 8, "ABCDEFGH", 8, NULL); - ck_assert_int_eq(res, YKPIV_OK); - - res = ykpiv_unblock_pin(g_state, "12345678", 8, "123456", 6, NULL); - ck_assert_int_eq(res, YKPIV_WRONG_PIN); - - res = ykpiv_unblock_pin(g_state, "ABCDEFGH", 8, "123456", 6, NULL); - ck_assert_int_eq(res, YKPIV_OK); - - res = ykpiv_change_puk(g_state, "ABCDEFGH", 8, "12345678", 8, NULL); - ck_assert_int_eq(res, YKPIV_OK); - - res = ykpiv_unblock_pin(g_state, "ABCDEFGH", 8, "123456", 6, NULL); - ck_assert_int_eq(res, YKPIV_WRONG_PIN); - - res = ykpiv_unblock_pin(g_state, "12345678", 8, "123456", 6, NULL); - ck_assert_int_eq(res, YKPIV_OK); -} -END_TEST - -static int block_and_reset() { - ykpiv_rc res; - int tries = 100; - int tries_until_blocked; - - tries_until_blocked = 0; - while (tries) { - res = ykpiv_verify(g_state, "AAAAAA", &tries); - if (res == YKPIV_PIN_LOCKED) - break; - ck_assert_int_eq(res, YKPIV_WRONG_PIN); - tries_until_blocked++; - } - - // Verify no PIN retries remaining - tries = 100; - res = ykpiv_get_pin_retries(g_state, &tries); - ck_assert_int_eq(res, YKPIV_OK); - ck_assert_int_eq(tries, 0); - - tries = 100; - while (tries) { - res = ykpiv_change_puk(g_state, "AAAAAAAA", 8, "AAAAAAAA", 8, &tries); - if (res == YKPIV_PIN_LOCKED) - break; - ck_assert_int_eq(res, YKPIV_WRONG_PIN); - } - res = ykpiv_util_reset(g_state); - ck_assert_int_eq(res, YKPIV_OK); - return tries_until_blocked; -} - -START_TEST(test_reset) { - ykpiv_rc res; - int tries = 100; - int tries_until_blocked; - - // Block and reset, with default PIN retries - tries_until_blocked = block_and_reset(); - ck_assert_int_eq(tries_until_blocked, 3); - - // Authenticate and increase PIN retries - test_authenticate(0); - res = ykpiv_verify(g_state, "123456", NULL); - ck_assert_int_eq(res, YKPIV_OK); - res = ykpiv_set_pin_retries(g_state, 8, 3); - ck_assert_int_eq(res, YKPIV_OK); - - // Block and reset again, verifying increased PIN retries - tries_until_blocked = block_and_reset(); - ck_assert_int_eq(tries_until_blocked, 8); - // Note: defaults back to 3 retries after reset - - // Verify default (3) PIN retries remaining - tries = 0; - res = ykpiv_get_pin_retries(g_state, &tries); - ck_assert_int_eq(res, YKPIV_OK); - ck_assert_int_eq(tries, 3); - - // Verify still (3) PIN retries remaining - tries = 0; - res = ykpiv_get_pin_retries(g_state, &tries); - ck_assert_int_eq(res, YKPIV_OK); - ck_assert_int_eq(tries, 3); - - // Try wrong PIN - res = ykpiv_verify(g_state, "AAAAAA", &tries); - ck_assert_int_eq(res, YKPIV_WRONG_PIN); - - // Verify 2 PIN retries remaining - tries = 0; - res = ykpiv_get_pin_retries(g_state, &tries); - ck_assert_int_eq(res, YKPIV_OK); - ck_assert_int_eq(tries, 2); - - // Verify correct PIN - tries = 100; - res = ykpiv_verify(g_state, "123456", &tries); - ck_assert_int_eq(res, YKPIV_OK); - - // Verify back to 3 PIN retries remaining - tries = 0; - res = ykpiv_get_pin_retries(g_state, &tries); - ck_assert_int_eq(res, YKPIV_OK); - ck_assert_int_eq(tries, 3); -} -END_TEST - - -struct t_alloc_data{ - uint32_t count; -} g_alloc_data; - -static void* _test_alloc(void *data, size_t cb) { - ck_assert_ptr_eq(data, &g_alloc_data); - ((struct t_alloc_data*)data)->count++; - return calloc(cb, 1); -} - -static void * _test_realloc(void *data, void *p, size_t cb) { - ck_assert_ptr_eq(data, &g_alloc_data); - return realloc(p, cb); -} - -static void _test_free(void *data, void *p) { - fflush(stderr); - ck_assert_ptr_eq(data, &g_alloc_data); - ((struct t_alloc_data*)data)->count--; - free(p); -} - -ykpiv_allocator test_allocator_cbs = { - .pfn_alloc = _test_alloc, - .pfn_realloc = _test_realloc, - .pfn_free = _test_free, - .alloc_data = &g_alloc_data -}; - -uint8_t *alloc_auth_cert() { - ykpiv_rc res; - uint8_t *read_cert = NULL; - size_t read_cert_len = 0; - - res = ykpiv_util_write_cert(g_state, YKPIV_KEY_AUTHENTICATION, (uint8_t*)g_cert, sizeof(g_cert), YKPIV_CERTINFO_UNCOMPRESSED); - ck_assert_int_eq(res, YKPIV_OK); - - res = ykpiv_util_read_cert(g_state, YKPIV_KEY_AUTHENTICATION, &read_cert, &read_cert_len); - ck_assert_int_eq(res, YKPIV_OK); - ck_assert_ptr_nonnull(read_cert); - ck_assert_int_eq(read_cert_len, sizeof(g_cert)); - ck_assert_mem_eq(g_cert, read_cert, sizeof(g_cert)); - return read_cert; -} - -START_TEST(test_allocator) { - ykpiv_rc res; - const ykpiv_allocator allocator; - uint8_t *cert1, *cert2; - - res = ykpiv_done(g_state); - ck_assert_int_eq(res, YKPIV_OK); - g_state = NULL; - - res = ykpiv_init_with_allocator(&g_state, false, &test_allocator_cbs); - ck_assert_int_eq(res, YKPIV_OK); - ck_assert_ptr_nonnull(g_state); - - // Verify we can communicate with device and make some allocations - res = ykpiv_connect(g_state, NULL); - ck_assert_int_eq(res, YKPIV_OK); - test_authenticate(0); - cert1 = alloc_auth_cert(); - cert2 = alloc_auth_cert(); - - // Verify allocations went through custom allocator, and still live - ck_assert_int_gt(g_alloc_data.count, 1); - - // Free and shutdown everything - ykpiv_util_free(g_state, cert2); - ykpiv_util_free(g_state, cert1); - res = ykpiv_disconnect(g_state); - ck_assert_int_eq(res, YKPIV_OK); - res = ykpiv_done(g_state); - ck_assert_int_eq(res, YKPIV_OK); - - // Verify equal number of frees as allocations - ck_assert_int_eq(g_alloc_data.count, 0); - - // Clear g_state so teardown() is skipped - g_state = NULL; -} -END_TEST - -START_TEST(test_pin_cache) { - ykpiv_rc res; - ykpiv_state *local_state; - unsigned char data[256]; - unsigned char data_in[256] = {0}; - int len = sizeof(data); - size_t len2 = sizeof(data); - - import_key(0x9a, YKPIV_PINPOLICY_DEFAULT); - - // Disconnect and reconnect to device to guarantee it is not authed - res = ykpiv_disconnect(g_state); - ck_assert_int_eq(res, YKPIV_OK); - res = ykpiv_done(g_state); - ck_assert_int_eq(res, YKPIV_OK); - res = ykpiv_init(&g_state, true); - ck_assert_int_eq(res, YKPIV_OK); - ck_assert_ptr_nonnull(g_state); - res = ykpiv_connect(g_state, NULL); - ck_assert_int_eq(res, YKPIV_OK); - - // Verify decryption does not work without auth - res = ykpiv_decipher_data(g_state, data_in, (size_t)len, data, &len2, YKPIV_ALGO_RSA2048, 0x9a); - ck_assert_int_eq(res, YKPIV_AUTHENTICATION_ERROR); - - // Verify decryption does work when authed - res = ykpiv_verify_select(g_state, "123456", 6, NULL, true); - ck_assert_int_eq(res, YKPIV_OK); - res = ykpiv_decipher_data(g_state, data_in, (size_t)len, data, &len2, YKPIV_ALGO_RSA2048, 0x9a); - ck_assert_int_eq(res, YKPIV_OK); - - // Verify PIN policy allows continuing to decrypt without re-verifying - res = ykpiv_decipher_data(g_state, data_in, (size_t)len, data, &len2, YKPIV_ALGO_RSA2048, 0x9a); - ck_assert_int_eq(res, YKPIV_OK); - - // Create a new ykpiv state, connect, and close it. - // This forces a card reset from another context, so the original global - // context will require a reconnect for its next transaction. - res = ykpiv_init(&local_state, true); - ck_assert_int_eq(res, YKPIV_OK); - ck_assert_ptr_nonnull(local_state); - res = ykpiv_connect(local_state, NULL); - ck_assert_int_eq(res, YKPIV_OK); - res = ykpiv_disconnect(local_state); - ck_assert_int_eq(res, YKPIV_OK); - res = ykpiv_done(local_state); - ck_assert_int_eq(res, YKPIV_OK); - - // Verify we are still authenticated on the global context. This will - // require an automatic reconnect and re-verify with the cached PIN. - // - // Note that you can verify that this fails by rebuilding with - // DISABLE_PIN_CACHE set to 1. - res = ykpiv_decipher_data(g_state, data_in, (size_t)len, data, &len2, YKPIV_ALGO_RSA2048, 0x9a); - ck_assert_int_eq(res, YKPIV_OK); -} -END_TEST -#endif - -int destruction_confirmed(void) { - char *confirmed = getenv("YKPIV_ENV_HWTESTS_CONFIRMED"); - if (confirmed && confirmed[0] == '1') - return 1; - // Use dprintf() to write directly to stdout, since automake eats the standard stdout/stderr pointers. - dprintf(0, "\n***\n*** Hardware tests skipped. Run \"make hwcheck\".\n***\n\n"); - return 0; -} - -Suite *test_suite(void) { - Suite *s; - TCase *tc; - - s = suite_create("libykpiv api"); - tc = tcase_create("api"); -#ifdef HW_TESTS - tcase_add_unchecked_fixture(tc, setup, teardown); - - // Must be first: Reset device. Tests run serially, and depend on a clean slate. - tcase_add_test(tc, test_reset); - - // Authenticate after reset. - tcase_add_test(tc, test_authenticate); - - // Test API functionality - tcase_add_test(tc, test_change_pin); - tcase_add_test(tc, test_change_puk); - tcase_add_test(tc, test_devicemodel); - tcase_add_test(tc, test_get_set_cardid); - tcase_add_test(tc, test_list_readers); - tcase_add_test(tc, test_read_write_list_delete_cert); - tcase_add_test(tc, test_import_key); - tcase_add_test(tc, test_pin_policy_always); - tcase_add_test(tc, test_generate_key); - tcase_add_test(tc, test_pin_cache); - - // Must be last: tear down and re-test with custom memory allocator - tcase_add_test(tc, test_allocator); -#endif - suite_add_tcase(s, tc); - - return s; -} - -int main(void) -{ - int number_failed; - Suite *s; - SRunner *sr; - - s = test_suite(); - sr = srunner_create(s); - srunner_set_fork_status(sr, CK_NOFORK); - srunner_run_all(sr, CK_VERBOSE); - number_failed = srunner_ntests_failed(sr); - srunner_free(sr); - - return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/tests/basic.c b/tests/basic.c deleted file mode 100644 index 49b6f6b8..00000000 --- a/tests/basic.c +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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. - * - */ - -#include "ykpiv.h" - -#include -#include -#include -#include - -#include - -START_TEST(test_version_string) { - if (strcmp(YKPIV_VERSION_STRING, ykpiv_check_version(NULL)) != 0) { - ck_abort_msg("version mismatch %s != %s\n", YKPIV_VERSION_STRING, - ykpiv_check_version(NULL)); - } - - if (ykpiv_check_version(YKPIV_VERSION_STRING) == NULL) { - ck_abort_msg("version NULL?\n"); - } - - if (ykpiv_check_version("99.99.99") != NULL) { - ck_abort_msg("version not NULL?\n"); - } - - fprintf(stderr, "ykpiv version: header %s library %s\n", - YKPIV_VERSION_STRING, ykpiv_check_version (NULL)); -} -END_TEST - -START_TEST(test_strerror) { - const char *s; - - if (ykpiv_strerror(YKPIV_OK) == NULL) { - ck_abort_msg("ykpiv_strerror NULL\n"); - } - - s = ykpiv_strerror_name(YKPIV_OK); - if (s == NULL || strcmp(s, "YKPIV_OK") != 0) { - ck_abort_msg("ykpiv_strerror_name %s\n", s); - } -} -END_TEST - -Suite *basic_suite(void) { - Suite *s; - TCase *tc; - - s = suite_create("libykpiv basic"); - tc = tcase_create("basic"); - tcase_add_test(tc, test_version_string); - tcase_add_test(tc, test_strerror); - suite_add_tcase(s, tc); - - return s; -} - -int main(void) -{ - int number_failed; - Suite *s; - SRunner *sr; - - s = basic_suite(); - sr = srunner_create(s); - srunner_run_all(sr, CK_NORMAL); - number_failed = srunner_ntests_failed(sr); - srunner_free(sr); - return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/tests/integration.rs b/tests/integration.rs new file mode 100644 index 00000000..b3ef4c5c --- /dev/null +++ b/tests/integration.rs @@ -0,0 +1,20 @@ +//! Integration tests + +#![forbid(unsafe_code)] +#![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)] + +use std::env; +use yubikey_piv::YubiKey; + +#[test] +#[ignore] +fn connect() { + // Only show logs if `RUST_LOG` is set + if env::var("RUST_LOG").is_ok() { + env_logger::builder().format_timestamp(None).init(); + } + + let mut yubikey = YubiKey::open(None).unwrap(); + dbg!(&yubikey.version()); + dbg!(&yubikey.serial()); +} diff --git a/tests/parse_key.c b/tests/parse_key.c deleted file mode 100644 index 0a339c99..00000000 --- a/tests/parse_key.c +++ /dev/null @@ -1,101 +0,0 @@ - /* - * 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. - * - */ - -#include -#include -#include - -#include - -#include "ykpiv.h" - -struct key { - const char text[49]; - const unsigned char formatted[24]; - int valid; -} keys[] = { - {"010203040506070801020304050607080102030405060708", - {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, - 1}, - {"a1a2a3a4a5a6a7a8a1a2a3a4a5a6a7a8a1a2a3a4a5a6a7a8", - {0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8}, - 1}, - {"A1A2A3A4A5A6A7A8A1A2A3A4A5A6A7A8A1A2A3A4A5A6A7A8", - {0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8}, - 1}, - {"This is not something considered valid hex......", - {}, - 0}, -}; - -static int parse_key(const char *text, const unsigned char *expected, int valid) { - unsigned char key[24]; - size_t len = sizeof(key); - ykpiv_rc res = ykpiv_hex_decode(text, strlen(text), key, &len); - if (valid) { - ck_assert(res == YKPIV_OK); - ck_assert(memcmp(expected, key, 24) == 0); - } else { - ck_assert(res != YKPIV_OK); - } - return EXIT_SUCCESS; -} - -START_TEST(test_parse_key) { - int res = parse_key(keys[_i].text, keys[_i].formatted, keys[_i].valid); - ck_assert(res == EXIT_SUCCESS); -} -END_TEST - -Suite *parsekey_suite(void) { - Suite *s; - TCase *tc; - - s = suite_create("libykpiv parsekey"); - tc = tcase_create("parsekey"); - tcase_add_loop_test(tc, test_parse_key, 0, sizeof(keys) / sizeof(struct key)); - suite_add_tcase(s, tc); - - return s; -} - -int main(void) -{ - int number_failed; - Suite *s; - SRunner *sr; - - s = parsekey_suite(); - sr = srunner_create(s); - srunner_run_all(sr, CK_NORMAL); - number_failed = srunner_ntests_failed(sr); - srunner_free(sr); - return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; -}