diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index fd4bed2..2863c9b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -13,12 +13,29 @@ on: jobs: build: runs-on: ubuntu-latest - + container: ghcr.io/trussed-dev/piv-authenticator + permissions: + contents: read + packages: read steps: - uses: actions/checkout@master + - name: start pcscd + run: pcscd -d -f --disable-polkit + id: pcscd + background: true - name: Install rust run: rustup show active-toolchain || rustup toolchain install - - name: Run checks - run: make lint - name: Run tests - run: make test TEST_FEATURES="rsa,virt" + run: make ci + - name: Upload tarpaulin html report + uses: actions/upload-artifact@v7 + with: + name: tarpaulin-html-report + path: tarpaulin-report.html + - name: Upload tarpaulin xml report + uses: actions/upload-artifact@v7 + with: + name: tarpaulin-xml-report + path: tarpaulin-report.xml + - name: Stop pcscd + cancel: pcscd diff --git a/Cargo.toml b/Cargo.toml index 3e461fb..61e6d7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,7 @@ +[workspace] +resolver = "3" +members = ["dev-vpicc"] + [package] name = "piv-authenticator" version = "0.6.0" @@ -9,7 +13,7 @@ documentation = "https://docs.rs/piv-authenticator" [[example]] name = "vpicc" -required-features = ["vpicc"] +required-features = [] [dependencies] apdu-app = { version = "0.2", optional = true } @@ -17,57 +21,46 @@ cbor-smol = { version = "0.5", features = ["heapless-bytes-v0-5"] } delog = { version = "0.1.5", optional = true } flexiber = { version = "0.2", features = ["derive", "heapless"] } heapless = "0.9.1" -hex-literal = "0.3" +hex-literal = "1" iso7816 = "0.2.0" serde = { version = "1", default-features = false, features = ["derive"] } -trussed = { version = "0.1", default-features = false, features = ["aes256-cbc", "chacha8-poly1305", "crypto-client", "ed255", "filesystem-client", "p256", "p384", "shared-secret", "serde-extensions", "tdes", "x255"], optional = true } trussed-auth = "0.5" -trussed-auth-backend = { version = "0.1.0", optional = true } untrusted = "0.9" -vpicc = { version = "0.1.0", optional = true } log = "0.4" heapless-bytes = "0.5.0" subtle = { version = "2", default-features = false } # TODO: only enable rsa features when needed trussed-core = { version = "0.2", features = ["aes256-cbc", "chacha8-poly1305", "crypto-client", "ed255", "filesystem-client", "p256", "p384", "rsa2048", "rsa3072", "rsa4096", "shared-secret", "tdes", "x255"] } trussed-rsa-types = { version = "0.2", optional = true } -trussed-rsa-alloc = { version = "0.4", features = ["raw"], optional = true } trussed-chunked = "0.3.0" trussed-hpke = "0.3.0" trussed-wrap-key-to-file = "0.3.0" -trussed-staging = { version = "0.4", features = ["chunked", "hpke", "wrap-key-to-file"], default-features = false, optional = true } littlefs2-core = "0.1.0" -cfg-if = "1.0.0" [dev-dependencies] -rand_core = { version = "0.6", features = ["getrandom"] } -trussed = { version = "0.1.0", features = ["virt"] } -env_logger = "0.9" +dev-vpicc = { path = "dev-vpicc" } +vpicc = "0.1.0" +rand_core = { version = "0.10" } +env_logger = "0.10" serde = { version = "1", features = ["derive"] } serde_cbor = { version = "0.11", features = ["std"] } hex = "0.4" test-log = "0.2.11" ron = "0.8" -des = "0.8" -aes = "0.8.2" +des = "0.9" +aes = "0.9.2" stoppable_thread = "0.2.1" -expectrl = "0.7.0" +expectrl = "0.9.0" iso7816 = { version = "0.2", features = ["std"] } # Examples # usbip -rand = "0.8.5" -asn1 = "0.15.2" +rand = "0.10" +asn1 = "0.24.1" [features] default = [] apdu-dispatch = ["dep:apdu-app"] -strict-pin = [] -std = [] -vpicc = ["std", "dep:vpicc", "virt", "dep:trussed-auth-backend"] -virt = ["std", "trussed/virt", "dep:trussed-staging", "dep:trussed-auth-backend", "dep:trussed-rsa-alloc"] -pivy-tests = [] -opensc-tests = [] alloc = [] rsa = ["dep:trussed-rsa-types", "alloc"] delog = ["dep:delog"] @@ -80,8 +73,6 @@ log-info = [] log-warn = [] log-error = [] -dangerous-test-real-card = [] - [patch.crates-io] trussed = { git = "https://github.com/trussed-dev/trussed", rev = "0f8df68be879acdde1f8cf428c11e5d29692a47b" } trussed-auth-backend = { git = "https://github.com/trussed-dev/trussed-auth", tag = "backend-v0.1.0" } diff --git a/Makefile b/Makefile index 2ee55fd..1c33606 100644 --- a/Makefile +++ b/Makefile @@ -3,9 +3,9 @@ -include variables.mk export RUST_LOG ?= info,cargo_tarpaulin=off -TEST_FEATURES ?=vpicc,pivy-tests,opensc-tests,rsa +TEST_FEATURES ?= rsa export PIV_DANGEROUS_TEST_CARD_READER ?= Virtual PCD 00 00 -export PIV_DANGEROUS_TEST_CARD_PIV_SERIAL ?= 04 B2 BB FB 54 40 4A E3 9B B8 6A E3 CA 82 9C 24 +export PIV_DANGEROUS_TEST_CARD_PIV_SERIAL ?= 99 37 9A 79 9E DC 48 1E A5 3E 11 CF EF 74 47 62 .PHONY: build-cortex-m4 build-cortex-m4: @@ -17,7 +17,7 @@ test: .PHONY: dangerous-test-real-card dangerous-test-real-card: - cargo test --features $(TEST_FEATURES),dangerous-test-real-card + DANGEROUS_TEST_RUN_REAL_CARD=true cargo test --features $(TEST_FEATURES) .PHONY: check check: @@ -32,12 +32,12 @@ lint: .PHONY: tarpaulin tarpaulin: - cargo tarpaulin --features $(TEST_FEATURES) -o Html -o Xml + cargo tarpaulin --engine Llvm --features $(TEST_FEATURES) -o Html -o Xml .PHONY: vpicc-example vpicc-example: - cargo run --example vpicc --features vpicc,rsa + cargo run --example vpicc .PHONY: ci -ci: lint tarpaulin +ci: lint test diff --git a/ci/Dockerfile b/ci/Dockerfile index cf08d2e..b049d50 100644 --- a/ci/Dockerfile +++ b/ci/Dockerfile @@ -1,27 +1,26 @@ FROM docker.io/rust:latest -RUN apt update && apt install --yes libpcsclite-dev \ - && wget https://github.com/arekinath/pivy/releases/download/v0.10.0/pivy-0.10.0-src.tar.gz \ - && tar xvf pivy-0.10.0-src.tar.gz \ - && cd pivy-0.10.0 \ - && make pivy-tool +RUN apt update && apt install --yes ragel libpcsclite-dev libbsd-dev \ + && wget https://github.com/arekinath/pivy/archive/refs/tags/v0.12.1.tar.gz \ + && tar xvf v0.12.1.tar.gz -FROM docker.io/rust:latest +RUN cd pivy-0.12.1 && sed -i '1s/^/\#include \\n/' bunyan.c +RUN cd pivy-0.12.1 && make pivy-tool -RUN apt update && apt install --yes scdaemon libclang-dev llvm python3-pip vsmartcard-vpcd pkg-config nettle-dev libpcsclite-dev opensc +FROM docker.io/rust:latest -RUN python3 -m pip install reuse +RUN apt update && apt install --yes scdaemon libclang-dev llvm python3-pip vsmartcard-vpicc pkg-config nettle-dev libpcsclite-dev opensc libbsd-dev RUN rustup component add clippy rustfmt && rustup target add thumbv7em-none-eabi RUN cargo install cargo-tarpaulin --profile release && rm -rf "$CARGO_HOME"/registry -# initialize cargo cache -RUN cargo search ENV CARGO_HOME=/app/.cache/cargo -COPY --from=0 pivy-0.10.0/pivy-tool /bin/pivy-tool +COPY --from=0 pivy-0.12.1/pivy-tool /bin/pivy-tool WORKDIR /app +LABEL org.opencontainers.image.source https://github.com/trussed-dev/piv-authenticator + COPY entrypoint.sh /entrypoint.sh ENTRYPOINT ["/bin/bash", "/entrypoint.sh"] diff --git a/ci/Makefile b/ci/Makefile index 62d42ff..b9eaf10 100644 --- a/ci/Makefile +++ b/ci/Makefile @@ -1,6 +1,6 @@ -include config.mk -TAG := registry.git.nitrokey.com/nitrokey/piv-authenticator/piv-authenticator-build +TAG := ghcr.io/trussed-dev/piv-authenticator:latest DOCKER ?= docker FUZZ_JOBS?=$(shell nproc) FUZZ_DURATION?="0" diff --git a/dev-vpicc/Cargo.toml b/dev-vpicc/Cargo.toml new file mode 100644 index 0000000..24c0d25 --- /dev/null +++ b/dev-vpicc/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "dev-vpicc" +version = "0.1.0" +edition = "2024" + +[dependencies] +iso7816 = "0.2.0" +heapless = "0.9" +vpicc = "0.1.0" +piv-authenticator = { path = ".." } +log = "0.4" +trussed-rsa-alloc = { version = "0.4", features = ["raw"] } +trussed = { version = "0.1", default-features = false, features = ["aes256-cbc", "chacha8-poly1305", "crypto-client", "ed255", "filesystem-client", "p256", "p384", "shared-secret", "serde-extensions", "tdes", "x255", "virt"] } +trussed-auth-backend = "0.1.0" +trussed-staging = { version = "0.4", features = ["chunked", "hpke", "wrap-key-to-file"], default-features = false } +trussed-wrap-key-to-file = "0.3.0" +trussed-hpke = "0.3.0" +trussed-chunked = "0.3.0" +trussed-auth = "0.5" diff --git a/dev-vpicc/src/lib.rs b/dev-vpicc/src/lib.rs new file mode 100644 index 0000000..57b4e24 --- /dev/null +++ b/dev-vpicc/src/lib.rs @@ -0,0 +1,2 @@ +pub mod virt; +pub mod vpicc; diff --git a/src/virt.rs b/dev-vpicc/src/virt.rs similarity index 97% rename from src/virt.rs rename to dev-vpicc/src/virt.rs index 6d1de19..8f74db0 100644 --- a/src/virt.rs +++ b/dev-vpicc/src/virt.rs @@ -3,7 +3,7 @@ pub mod dispatch { use trussed::{ - api::{reply, request, Reply, Request}, + api::{Reply, Request, reply, request}, backend::{Backend as _, BackendId}, error::Error, platform::Platform, @@ -15,7 +15,6 @@ pub mod dispatch { use trussed_auth_backend::{AuthBackend, AuthContext, FilesystemLayout, MAX_HW_KEY_LEN}; use trussed_chunked::ChunkedExtension; use trussed_hpke::HpkeExtension; - #[cfg(feature = "rsa")] use trussed_rsa_alloc::SoftwareRsa; use trussed_staging::{StagingBackend, StagingContext}; use trussed_wrap_key_to_file::WrapKeyToFileExtension; @@ -24,7 +23,6 @@ pub mod dispatch { pub const BACKENDS: &[BackendId] = &[ BackendId::Custom(Backend::Staging), BackendId::Custom(Backend::Auth), - #[cfg(feature = "rsa")] BackendId::Custom(Backend::Rsa), BackendId::Core, ]; @@ -32,7 +30,6 @@ pub mod dispatch { #[derive(Debug, Clone, Copy)] pub enum Backend { Auth, - #[cfg(feature = "rsa")] Rsa, Staging, } @@ -129,7 +126,6 @@ pub mod dispatch { request, resources, ), - #[cfg(feature = "rsa")] Backend::Rsa => SoftwareRsa.request(&mut ctx.core, &mut (), request, resources), } } @@ -182,7 +178,6 @@ pub mod dispatch { } Extension::Auth => Err(Error::RequestNotAvailable), } - #[cfg(feature = "rsa")] Backend::Rsa => Err(Error::RequestNotAvailable), } } diff --git a/src/vpicc.rs b/dev-vpicc/src/vpicc.rs similarity index 97% rename from src/vpicc.rs rename to dev-vpicc/src/vpicc.rs index 781174a..f6800e3 100644 --- a/src/vpicc.rs +++ b/dev-vpicc/src/vpicc.rs @@ -1,10 +1,12 @@ -use iso7816::{command::FromSliceError, Command, Status}; +use iso7816::{Command, Status, command::FromSliceError}; use crate::virt::VirtClient; use std::convert::{TryFrom, TryInto}; -use crate::Authenticator; +use piv_authenticator::Authenticator; + +use log::{trace, warn}; const REQUEST_LEN: usize = 7609; const RESPONSE_LEN: usize = 7609; diff --git a/examples/vpicc.rs b/examples/vpicc.rs index 674f34f..b2d118e 100644 --- a/examples/vpicc.rs +++ b/examples/vpicc.rs @@ -8,14 +8,15 @@ // TODO: add CLI -use piv_authenticator::{virt::with_ram_client, Authenticator, Options}; +use dev_vpicc::{virt::with_ram_client, vpicc::VpiccCard}; +use piv_authenticator::{Authenticator, Options}; fn main() { env_logger::init(); with_ram_client("piv-authenticator", |client| { let card = Authenticator::new(client, Options::default()); - let mut vpicc_card = piv_authenticator::vpicc::VpiccCard::new(card); + let mut vpicc_card = VpiccCard::new(card); let vpicc = vpicc::connect().expect("failed to connect to vpicc"); vpicc .run(&mut vpicc_card) diff --git a/src/constants.rs b/src/constants.rs index 9a2b8da..29c94c8 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -43,29 +43,27 @@ pub const YUBICO_DEFAULT_MANAGEMENT_KEY_ALG: AdministrationAlgorithm = AdministrationAlgorithm::Tdes; pub const DISCOVERY_OBJECT: [u8; 18] = hex!( - " - 4f 0b // PIV AID - a000000308000010000100 - 5f2f 02 // PIN usage Policy - 4010" + "4f 0b" // PIV AID + "a000000308000010000100" + "5f2f 02" // PIN usage Policy + "4010" ); pub const CARD_CAP: [u8; 27] = hex!( - " - F0 00 // card identifier - F1 00 // capability container version - F2 00 // capability container grammar - F3 00 // application card url - F4 00 // pkcs15 - F5 01 10 // registereddata model number - F6 00 // access control rule table - F7 00 // card apdus - FA 00 // redirection tag - FB 00 // capability tuples - FC 00 // status tuples - FD 00 // next ccc - FE 00 // Error detection code -" + "F0 00" // card identifier + "F1 00" // capability container version + "F2 00" // capability container grammar + "F3 00" // application card url + "F4 00" // pkcs15 + "F5 01 10" // registereddata model number + "F6 00" // access control rule table + "F7 00" // card apdus + "FA 00" // redirection tag + "FB 00" // capability tuples + "FC 00" // status tuples + "FD 00" // next ccc + "FE 00" // Error detection code + ); pub const PRINTED_INFORMATION: [u8; 63] = hex!( diff --git a/src/container.rs b/src/container.rs index 2df0781..e5a19e7 100644 --- a/src/container.rs +++ b/src/container.rs @@ -452,44 +452,44 @@ impl TryFrom<&[u8]> for Container { fn try_from(tag: &[u8]) -> Result { use Container::*; Ok(match tag { - hex!("5FC107") => CardCapabilityContainer, - hex!("5FC102") => CardHolderUniqueIdentifier, - hex!("5FC105") => X509CertificateFor9A, - hex!("5FC103") => CardholderFingerprints, - hex!("5FC106") => SecurityObject, - hex!("5FC108") => CardholderFacialImage, - hex!("5FC101") => X509CertificateFor9E, - hex!("5FC109") => PrintedInformation, - hex!("5FC10A") => X509CertificateFor9C, - hex!("5FC10B") => X509CertificateFor9D, - hex!("5FC10C") => KeyHistoryObject, - hex!("5FC10D") => RetiredCert01, - hex!("5FC10E") => RetiredCert02, - hex!("5FC10F") => RetiredCert03, - hex!("5FC110") => RetiredCert04, - hex!("5FC111") => RetiredCert05, - hex!("5FC112") => RetiredCert06, - hex!("5FC113") => RetiredCert07, - hex!("5FC114") => RetiredCert08, - hex!("5FC115") => RetiredCert09, - hex!("5FC116") => RetiredCert10, - hex!("5FC117") => RetiredCert11, - hex!("5FC118") => RetiredCert12, - hex!("5FC119") => RetiredCert13, - hex!("5FC11A") => RetiredCert14, - hex!("5FC11B") => RetiredCert15, - hex!("5FC11C") => RetiredCert16, - hex!("5FC11D") => RetiredCert17, - hex!("5FC11E") => RetiredCert18, - hex!("5FC11F") => RetiredCert19, - hex!("5FC120") => RetiredCert20, - - hex!("5FC121") => CardholderIrisImages, - hex!("5FC122") => SecureMessagingCertificateSigner, - hex!("5FC123") => PairingCodeReferenceDataContainer, - - hex!("7E") => DiscoveryObject, - hex!("7F61") => BiometricInformationTemplatesGroupTemplate, + _ if tag == hex!("5FC107") => CardCapabilityContainer, + _ if tag == hex!("5FC102") => CardHolderUniqueIdentifier, + _ if tag == hex!("5FC105") => X509CertificateFor9A, + _ if tag == hex!("5FC103") => CardholderFingerprints, + _ if tag == hex!("5FC106") => SecurityObject, + _ if tag == hex!("5FC108") => CardholderFacialImage, + _ if tag == hex!("5FC101") => X509CertificateFor9E, + _ if tag == hex!("5FC109") => PrintedInformation, + _ if tag == hex!("5FC10A") => X509CertificateFor9C, + _ if tag == hex!("5FC10B") => X509CertificateFor9D, + _ if tag == hex!("5FC10C") => KeyHistoryObject, + _ if tag == hex!("5FC10D") => RetiredCert01, + _ if tag == hex!("5FC10E") => RetiredCert02, + _ if tag == hex!("5FC10F") => RetiredCert03, + _ if tag == hex!("5FC110") => RetiredCert04, + _ if tag == hex!("5FC111") => RetiredCert05, + _ if tag == hex!("5FC112") => RetiredCert06, + _ if tag == hex!("5FC113") => RetiredCert07, + _ if tag == hex!("5FC114") => RetiredCert08, + _ if tag == hex!("5FC115") => RetiredCert09, + _ if tag == hex!("5FC116") => RetiredCert10, + _ if tag == hex!("5FC117") => RetiredCert11, + _ if tag == hex!("5FC118") => RetiredCert12, + _ if tag == hex!("5FC119") => RetiredCert13, + _ if tag == hex!("5FC11A") => RetiredCert14, + _ if tag == hex!("5FC11B") => RetiredCert15, + _ if tag == hex!("5FC11C") => RetiredCert16, + _ if tag == hex!("5FC11D") => RetiredCert17, + _ if tag == hex!("5FC11E") => RetiredCert18, + _ if tag == hex!("5FC11F") => RetiredCert19, + _ if tag == hex!("5FC120") => RetiredCert20, + + _ if tag == hex!("5FC121") => CardholderIrisImages, + _ if tag == hex!("5FC122") => SecureMessagingCertificateSigner, + _ if tag == hex!("5FC123") => PairingCodeReferenceDataContainer, + + _ if tag == hex!("7E") => DiscoveryObject, + _ if tag == hex!("7F61") => BiometricInformationTemplatesGroupTemplate, _ => return Err(()), }) } diff --git a/src/lib.rs b/src/lib.rs index 7e7b65f..52aefeb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg_attr(not(any(test, feature = "std")), no_std)] +#![cfg_attr(not(test), no_std)] #[cfg(not(feature = "delog"))] #[macro_use] @@ -27,11 +27,6 @@ use trussed_chunked::ChunkedClient; use trussed_hpke::HpkeClient; use trussed_wrap_key_to_file::WrapKeyToFileClient; -#[cfg(feature = "virt")] -pub mod virt; -#[cfg(feature = "vpicc")] -pub mod vpicc; - use core::convert::TryInto; use flexiber::EncodableHeapless; @@ -165,7 +160,7 @@ where // info!("PIV responding to {:02x?}", command); let parsed_command: Command = command.try_into()?; - info!("parsed: {:02x?}", &parsed_command); + info!("parsed: {:02x?}", parsed_command); let reply = Reply(reply); match parsed_command { @@ -199,7 +194,7 @@ where instruction: YubicoPivExtension, mut reply: Reply<'_>, ) -> Result { - info!("yubico extension: {:?}", &instruction); + info!("yubico extension: {:?}", instruction); match instruction { YubicoPivExtension::GetSerial => { // make up a 4-byte serial @@ -485,10 +480,10 @@ impl LoadedAuthenticator<'_, T> { debug!( "witness: {}, challenge: {}, response: {}, exponentiation: {}", - &parsed.witness.is_some(), - &parsed.challenge.is_some(), - &parsed.response.is_some(), - &parsed.exponentiation.is_some(), + parsed.witness.is_some(), + parsed.challenge.is_some(), + parsed.response.is_some(), + parsed.exponentiation.is_some(), ); match parsed { diff --git a/tests/aa_serial.rs b/tests/aa_serial.rs index 4815668..625b140 100644 --- a/tests/aa_serial.rs +++ b/tests/aa_serial.rs @@ -1,16 +1,27 @@ -#![cfg(feature = "dangerous-test-real-card")] //! Test the serial number to ensure that tests run on the correct card -use expectrl::{spawn, Eof}; +use expectrl::{spawn, Eof, Expect}; const CARD: &str = env!("PIV_DANGEROUS_TEST_CARD_READER"); const SERIAL: &str = env!("PIV_DANGEROUS_TEST_CARD_PIV_SERIAL"); +pub mod card; +use card::*; + #[test] fn test_serial_number() { - let mut p = spawn("piv-tool --serial").unwrap(); - p.expect(format!("Using reader with a card: {CARD}")) - .unwrap(); - p.expect(SERIAL).unwrap(); - p.expect(Eof).unwrap(); + let test = || { + let p = spawn("piv-tool --serial").unwrap(); + let mut logger = LogWriter(Vec::new()); + let mut p = expectrl::session::log(p, &mut logger).unwrap(); + p.expect(format!("Using reader with a card: {CARD}")) + .unwrap(); + p.expect(SERIAL).unwrap(); + p.expect(Eof).unwrap(); + }; + if card::dangerous_real_card_enabled() { + with_lock_and_reset(test) + } else { + with_vsc(WITHOUT_UUID, test); + } } diff --git a/tests/card/mod.rs b/tests/card/mod.rs index db6853c..49cbb3b 100644 --- a/tests/card/mod.rs +++ b/tests/card/mod.rs @@ -1,20 +1,23 @@ -use piv_authenticator::{virt::with_ram_client, vpicc::VpiccCard, Authenticator, Options}; +use dev_vpicc::{virt::with_ram_client, vpicc::VpiccCard}; +use piv_authenticator::{Authenticator, Options}; use std::{sync::mpsc, thread::sleep, time::Duration}; use stoppable_thread::spawn; +use std::io::{self, Write}; use std::panic::{catch_unwind, resume_unwind, UnwindSafe}; use std::process::Command; use std::sync::Mutex; +pub fn dangerous_real_card_enabled() -> bool { + option_env!("DANGEROUS_TEST_RUN_REAL_CARD") == Some("true") +} + static VSC_MUTEX: Mutex<()> = Mutex::new(()); -#[cfg_attr(feature = "dangerous-test-real-card", expect(unused))] pub const WITH_UUID: Options = Options::new().uuid(Some([0; 16])); -#[cfg_attr(feature = "dangerous-test-real-card", expect(unused))] pub const WITHOUT_UUID: Options = Options::new(); -#[cfg_attr(feature = "dangerous-test-real-card", expect(unused))] pub fn with_vsc R, R>(options: Options, f: F) -> R { let Ok(_lock) = VSC_MUTEX.lock() else { panic!("Some other test failed, this test is therefore ignored") @@ -52,7 +55,6 @@ pub fn with_vsc R, R>(options: Options, f: F) -> R { result } -#[cfg_attr(not(feature = "dangerous-test-real-card"), expect(unused))] pub fn with_lock_and_reset R, R: UnwindSafe>(f: F) { let lock = VSC_MUTEX.lock(); let res = catch_unwind(f); @@ -80,3 +82,22 @@ pub fn with_lock_and_reset R, R: UnwindSafe>(f: F) { drop(lock); } + +#[derive(Default)] +pub struct LogWriter(pub Vec); + +impl Write for LogWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.0.flush() + } +} + +impl Drop for LogWriter { + fn drop(&mut self) { + io::stdout().write_all(&self.0).unwrap(); + } +} diff --git a/tests/command_response.rs b/tests/command_response.rs index f576a37..b0e2287 100644 --- a/tests/command_response.rs +++ b/tests/command_response.rs @@ -1,13 +1,11 @@ -#![cfg(feature = "virt")] - -mod setup; +pub mod setup; use std::borrow::Cow; +use aes::cipher::{Array, BlockCipherDecrypt, BlockCipherEncrypt}; use hex_literal::hex; -use rand::thread_rng; +use rand::rng; use serde::Deserialize; -use trussed::types::GenericArray; macro_rules! assert_eq_hex { ($left:expr, $right:expr $(,)?) => { @@ -579,10 +577,7 @@ impl IoCmd { card: &mut setup::Piv, ) { use aes::Aes256Enc; - use des::{ - cipher::{BlockEncrypt, KeyInit}, - TdesEde3, - }; + use des::{cipher::KeyInit, TdesEde3}; let command = build_command(0x00, 0x87, alg as u8, 0x9B, &hex!("7C 02 81 00"), 0); let mut res = Self::run_bytes(&command, &MATCH_ANY, expected_status_challenge, card); let key = parse_hex(key); @@ -604,12 +599,12 @@ impl IoCmd { assert_eq_hex!(challenge.len(), alg.challenge_len()); match alg { Algorithm::Tdes => { - let cipher = TdesEde3::new(GenericArray::from_slice(&key)); - cipher.encrypt_block(GenericArray::from_mut_slice(challenge)); + let cipher = TdesEde3::new(Array::slice_as_array(&key).unwrap()); + cipher.encrypt_block(Array::slice_as_mut_array(challenge).unwrap()); } Algorithm::Aes256 => { - let cipher = Aes256Enc::new(GenericArray::from_slice(&key)); - cipher.encrypt_block(GenericArray::from_mut_slice(challenge)); + let cipher = Aes256Enc::new(Array::slice_as_array(&key).unwrap()); + cipher.encrypt_block(Array::slice_as_mut_array(challenge).unwrap()); } _ => panic!(), } @@ -626,11 +621,8 @@ impl IoCmd { card: &mut setup::Piv, ) { use aes::Aes256Dec; - use des::{ - cipher::{BlockDecrypt, KeyInit}, - TdesEde3, - }; - use rand::RngCore; + use des::{cipher::KeyInit, TdesEde3}; + use rand::Rng; let command = build_command(0x00, 0x87, alg as u8, 0x9B, &hex!("7C 02 80 00"), 0); let mut res = Self::run_bytes(&command, &MATCH_ANY, expected_status_challenge, card); let key = parse_hex(key); @@ -652,17 +644,17 @@ impl IoCmd { assert_eq_hex!(challenge.len(), alg.challenge_len()); match alg { Algorithm::Tdes => { - let cipher = TdesEde3::new(GenericArray::from_slice(&key)); - cipher.decrypt_block(GenericArray::from_mut_slice(challenge)); + let cipher = TdesEde3::new(Array::slice_as_array(&key).unwrap()); + cipher.decrypt_block(Array::slice_as_mut_array(challenge).unwrap()); } Algorithm::Aes256 => { - let cipher = Aes256Dec::new(GenericArray::from_slice(&key)); - cipher.decrypt_block(GenericArray::from_mut_slice(challenge)); + let cipher = Aes256Dec::new(Array::slice_as_array(&key).unwrap()); + cipher.decrypt_block(Array::slice_as_mut_array(challenge).unwrap()); } _ => panic!(), } let mut random_challenge = vec![0; alg.challenge_len()]; - thread_rng().fill_bytes(&mut random_challenge); + rng().fill_bytes(&mut random_challenge); let challenge_and_random: Vec = [tlv(&[0x80], challenge), tlv(&[0x81], &random_challenge)] .into_iter() @@ -688,12 +680,12 @@ impl IoCmd { assert_eq_hex!(response_challenge.len(), alg.challenge_len()); match alg { Algorithm::Tdes => { - let cipher = TdesEde3::new(GenericArray::from_slice(&key)); - cipher.decrypt_block(GenericArray::from_mut_slice(response_challenge)); + let cipher = TdesEde3::new(Array::slice_as_array(&key).unwrap()); + cipher.decrypt_block(Array::slice_as_mut_array(response_challenge).unwrap()); } Algorithm::Aes256 => { - let cipher = Aes256Dec::new(GenericArray::from_slice(&key)); - cipher.decrypt_block(GenericArray::from_mut_slice(response_challenge)); + let cipher = Aes256Dec::new(Array::slice_as_array(&key).unwrap()); + cipher.decrypt_block(Array::slice_as_mut_array(response_challenge).unwrap()); } _ => panic!(), } @@ -720,27 +712,25 @@ impl IoCmd { fn run_select(card: &mut setup::Piv) { let matcher = OutputMatcher::Bytes(Cow::Borrowed(&hex!( - " - 61 69 // Card application property template - 4f 06 000010000100 // Application identifier - 50 0c 4e6974726f6b657920504956 // Application label = b\"Nitrokey PIV\" + "61 69" // Card application property template + "4f 06 000010000100" // Application identifier + "50 0c 4e6974726f6b657920504956" // Application label = b\"Nitrokey PIV\" // URL = b\"https://github.com/Nitrokey/piv-authenticator\" - 5f50 2d 68747470733a2f2f6769746875622e636f6d2f4e6974726f6b65792f7069762d61757468656e74696361746f72 + "5f50 2d 68747470733a2f2f6769746875622e636f6d2f4e6974726f6b65792f7069762d61757468656e74696361746f72" // Cryptographic Algorithm Identifier Template - ac 18 - 80 01 03 // TDES - ECB - 80 01 0c // AES256 - ECB - 80 01 11 // P-256 - 80 01 07 // RSA 2048 - 80 01 05 // RSA 3072 - 80 01 16 // RSA 4096 - 80 01 14 // P384 - 06 01 00 + "ac 18" + "80 01 03" // TDES - ECB + "80 01 0c" // AES256 - ECB + "80 01 11" // P-256 + "80 01 07" // RSA 2048 + "80 01 05" // RSA 3072 + "80 01 16" // RSA 4096 + "80 01 14" // P384 + "06 01 00" // Coexistent Tag Allocation Authority Template - 79 07 - 4f 05 a000000308 - " + "79 07" + "4f 05 a000000308" ))); Self::run_bytes( &hex!("00 A4 04 00 0C A000000308000010000100 00"), diff --git a/tests/opensc.rs b/tests/opensc.rs index a6c7177..e356db7 100644 --- a/tests/opensc.rs +++ b/tests/opensc.rs @@ -1,13 +1,11 @@ -#![cfg(all(feature = "vpicc", feature = "opensc-tests"))] - -mod card; +pub mod card; use std::process::Command; use card::*; -use cfg_if::cfg_if; -use expectrl::{spawn, Eof, WaitStatus}; +use expectrl::process::unix::WaitStatus; +use expectrl::{spawn, Eof, Expect}; const CARD: &str = env!("PIV_DANGEROUS_TEST_CARD_READER"); @@ -17,24 +15,29 @@ const EXPECT_TIMEOUT: Option = Some(Duration::from_secs(30)); #[test_log::test] fn list() { let test = || { - let mut p = spawn("piv-tool -n").unwrap(); + let p = spawn("piv-tool -n").unwrap(); + let mut logger = LogWriter(Vec::new()); + let mut p = expectrl::session::log(p, &mut logger).unwrap(); p.set_expect_timeout(EXPECT_TIMEOUT); p.expect(format!("Using reader with a card: {CARD}")) .unwrap(); p.expect("Personal Identity Verification Card").unwrap(); p.expect(Eof).unwrap(); - assert_eq!( - p.get_process().wait().unwrap(), - WaitStatus::Exited(p.get_process().pid(), 0) + let WaitStatus::Exited(pid, exit_code) = p.get_process().wait().unwrap() else { + panic!("Got wrong wait status"); + }; + assert_eq!(pid, p.get_process().pid()); + // Some old versions on opensc could return 1 even on success. + assert!( + [0, 1].contains(&exit_code), + "Unexpectedexitcode: {exit_code}" ); }; - cfg_if! { - if #[cfg(not(feature = "dangerous-test-real-card"))] { - with_vsc(WITHOUT_UUID, test); - with_vsc(WITH_UUID, test); - } else { - with_lock_and_reset(test) - } + if card::dangerous_real_card_enabled() { + with_lock_and_reset(test) + } else { + with_vsc(WITHOUT_UUID, test); + with_vsc(WITH_UUID, test); } } @@ -45,24 +48,29 @@ fn admin_mutual() { command .env("PIV_EXT_AUTH_KEY", "tests/default_admin_key") .args(["-A", "M:9B:03"]); - let mut p = expectrl::session::Session::spawn(command).unwrap(); + let p = expectrl::session::Session::spawn(command).unwrap(); + let mut logger = LogWriter(Vec::new()); + let mut p = expectrl::session::log(p, &mut logger).unwrap(); p.set_expect_timeout(EXPECT_TIMEOUT); p.expect(format!("Using reader with a card: {CARD}")) .unwrap(); // p.expect("Personal Identity Verification Card").unwrap(); p.expect(Eof).unwrap(); - assert_eq!( - p.get_process().wait().unwrap(), - WaitStatus::Exited(p.get_process().pid(), 0) + let WaitStatus::Exited(pid, exit_code) = p.get_process().wait().unwrap() else { + panic!("Got wrong wait status"); + }; + assert_eq!(pid, p.get_process().pid()); + // Some old versions on opensc could return 1 even on success. + assert!( + [0, 1].contains(&exit_code), + "Unexpectedexitcode: {exit_code}" ); }; - cfg_if! { - if #[cfg(not(feature = "dangerous-test-real-card"))]{ - with_vsc(WITH_UUID, test); - with_vsc(WITHOUT_UUID, test); - } else { - with_lock_and_reset(test) - } + if card::dangerous_real_card_enabled() { + with_lock_and_reset(test) + } else { + with_vsc(WITHOUT_UUID, test); + with_vsc(WITH_UUID, test); } } @@ -73,23 +81,28 @@ fn admin_card() { command .env("PIV_EXT_AUTH_KEY", "tests/default_admin_key") .args(["-A", "A:9B:03"]); - let mut p = expectrl::session::Session::spawn(command).unwrap(); + let p = expectrl::session::Session::spawn(command).unwrap(); + let mut logger = LogWriter(Vec::new()); + let mut p = expectrl::session::log(p, &mut logger).unwrap(); p.set_expect_timeout(EXPECT_TIMEOUT); p.expect(format!("Using reader with a card: {CARD}")) .unwrap(); p.expect(Eof).unwrap(); - assert_eq!( - p.get_process().wait().unwrap(), - WaitStatus::Exited(p.get_process().pid(), 0) + let WaitStatus::Exited(pid, exit_code) = p.get_process().wait().unwrap() else { + panic!("Got wrong wait status"); + }; + assert_eq!(pid, p.get_process().pid()); + // Some old versions on opensc could return 1 even on success. + assert!( + [0, 1].contains(&exit_code), + "Unexpectedexitcode: {exit_code}" ); }; - cfg_if! { - if #[cfg(not(feature = "dangerous-test-real-card"))]{ - with_vsc(WITH_UUID, test); - with_vsc(WITHOUT_UUID, test); - } else { - with_lock_and_reset(test) - } + if card::dangerous_real_card_enabled() { + with_lock_and_reset(test) + } else { + with_vsc(WITHOUT_UUID, test); + with_vsc(WITH_UUID, test); } } @@ -100,7 +113,9 @@ fn admin_mutual_bad_key() { command .env("PIV_EXT_AUTH_KEY", "tests/bad_admin_key") .args(["-A", "M:9B:03"]); - let mut p = expectrl::session::Session::spawn(command).unwrap(); + let p = expectrl::session::Session::spawn(command).unwrap(); + let mut logger = LogWriter(Vec::new()); + let mut p = expectrl::session::log(p, &mut logger).unwrap(); p.set_expect_timeout(EXPECT_TIMEOUT); p.expect(format!("Using reader with a card: {CARD}")) .unwrap(); @@ -111,13 +126,11 @@ fn admin_mutual_bad_key() { WaitStatus::Exited(p.get_process().pid(), 75) ); }; - cfg_if! { - if #[cfg(not(feature = "dangerous-test-real-card"))]{ - with_vsc(WITH_UUID, test); - with_vsc(WITHOUT_UUID, test); - } else { - with_lock_and_reset(test) - } + if card::dangerous_real_card_enabled() { + with_lock_and_reset(test) + } else { + with_vsc(WITHOUT_UUID, test); + with_vsc(WITH_UUID, test); } } @@ -128,7 +141,9 @@ fn admin_card_bad_key() { command .env("PIV_EXT_AUTH_KEY", "tests/bad_admin_key") .args(["-A", "A:9B:03"]); - let mut p = expectrl::session::Session::spawn(command).unwrap(); + let p = expectrl::session::Session::spawn(command).unwrap(); + let mut logger = LogWriter(Vec::new()); + let mut p = expectrl::session::log(p, &mut logger).unwrap(); p.set_expect_timeout(EXPECT_TIMEOUT); p.expect(format!("Using reader with a card: {CARD}")) .unwrap(); @@ -139,13 +154,11 @@ fn admin_card_bad_key() { WaitStatus::Exited(p.get_process().pid(), 75) ); }; - cfg_if! { - if #[cfg(not(feature = "dangerous-test-real-card"))]{ - with_vsc(WITH_UUID, test); - with_vsc(WITHOUT_UUID, test); - } else { - with_lock_and_reset(test) - } + if card::dangerous_real_card_enabled() { + with_lock_and_reset(test) + } else { + with_vsc(WITHOUT_UUID, test); + with_vsc(WITH_UUID, test); } } @@ -156,24 +169,28 @@ fn generate_key() { command .env("PIV_EXT_AUTH_KEY", "tests/default_admin_key") .args(["-A", "M:9B:03", "-G", "9A:11"]); - let mut p = expectrl::session::Session::spawn(command).unwrap(); + let p = expectrl::session::Session::spawn(command).unwrap(); + let mut logger = LogWriter(Vec::new()); + let mut p = expectrl::session::log(p, &mut logger).unwrap(); p.set_expect_timeout(EXPECT_TIMEOUT); p.expect(format!("Using reader with a card: {CARD}")) .unwrap(); p.expect(Eof).unwrap(); - // Non zero exit code? - assert_eq!( - p.get_process().wait().unwrap(), - WaitStatus::Exited(p.get_process().pid(), 1) + let WaitStatus::Exited(pid, exit_code) = p.get_process().wait().unwrap() else { + panic!("Got wrong wait status"); + }; + assert_eq!(pid, p.get_process().pid()); + // Some old versions on opensc could return 1 even on success. + assert!( + [0, 1].contains(&exit_code), + "Unexpectedexitcode: {exit_code}" ); }; - cfg_if! { - if #[cfg(not(feature = "dangerous-test-real-card"))]{ - with_vsc(WITH_UUID, test); - with_vsc(WITHOUT_UUID, test); - } else { - with_lock_and_reset(test) - } + if card::dangerous_real_card_enabled() { + with_lock_and_reset(test) + } else { + with_vsc(WITHOUT_UUID, test); + with_vsc(WITH_UUID, test); } let test = || { @@ -181,23 +198,27 @@ fn generate_key() { command .env("PIV_EXT_AUTH_KEY", "tests/default_admin_key") .args(["-A", "M:9B:03", "-G", "9A:07"]); - let mut p = expectrl::session::Session::spawn(command).unwrap(); + let p = expectrl::session::Session::spawn(command).unwrap(); + let mut logger = LogWriter(Vec::new()); + let mut p = expectrl::session::log(p, &mut logger).unwrap(); p.set_expect_timeout(EXPECT_TIMEOUT); p.expect(format!("Using reader with a card: {CARD}")) .unwrap(); p.expect(Eof).unwrap(); - // Non zero exit code? - assert_eq!( - p.get_process().wait().unwrap(), - WaitStatus::Exited(p.get_process().pid(), 1) + let WaitStatus::Exited(pid, exit_code) = p.get_process().wait().unwrap() else { + panic!("Got wrong wait status"); + }; + assert_eq!(pid, p.get_process().pid()); + // Some old versions on opensc could return 1 even on success. + assert!( + [0, 1].contains(&exit_code), + "Unexpectedexitcode: {exit_code}" ); }; - cfg_if! { - if #[cfg(not(feature = "dangerous-test-real-card"))]{ - with_vsc(WITH_UUID, test); - with_vsc(WITHOUT_UUID, test); - } else { - with_lock_and_reset(test) - } + if card::dangerous_real_card_enabled() { + with_lock_and_reset(test) + } else { + with_vsc(WITHOUT_UUID, test); + with_vsc(WITH_UUID, test); } } diff --git a/tests/pivy.rs b/tests/pivy.rs index 22b0e5a..4483a83 100644 --- a/tests/pivy.rs +++ b/tests/pivy.rs @@ -1,12 +1,11 @@ -#![cfg(all(feature = "vpicc", feature = "pivy-tests"))] -#![allow(clippy::zombie_processes)] +#![allow(clippy::zombie_processes, clippy::result_large_err)] -mod card; +pub mod card; use card::*; -use cfg_if::cfg_if; -use expectrl::{spawn, Eof, Regex, WaitStatus}; +use expectrl::process::unix::WaitStatus; +use expectrl::{spawn, Eof, Expect, Regex}; use std::io::{self, Read, Write}; use std::ops::{Deref, DerefMut}; @@ -44,25 +43,6 @@ const CARD: &str = env!("PIV_DANGEROUS_TEST_CARD_READER"); const EXPECT_TIMEOUT: Option = Some(Duration::from_secs(30)); -#[derive(Default)] -struct LogWriter(Vec); - -impl Write for LogWriter { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.0.flush() - } -} - -impl Drop for LogWriter { - fn drop(&mut self) { - io::stdout().write_all(&self.0).unwrap(); - } -} - #[test_log::test] fn list() { let test = || { @@ -82,13 +62,11 @@ fn list() { WaitStatus::Exited(p.get_process().pid(), 0) ); }; - cfg_if! { - if #[cfg(not(feature = "dangerous-test-real-card"))]{ - with_vsc(WITH_UUID, test); - with_vsc(WITHOUT_UUID, test); - } else { - with_lock_and_reset(test) - } + if card::dangerous_real_card_enabled() { + with_lock_and_reset(test) + } else { + with_vsc(WITHOUT_UUID, test); + with_vsc(WITH_UUID, test); } } @@ -119,13 +97,11 @@ fn generate() { WaitStatus::Exited(p.get_process().pid(), 0) ); }; - cfg_if! { - if #[cfg(not(feature = "dangerous-test-real-card"))]{ - with_vsc(WITH_UUID, test); - with_vsc(WITHOUT_UUID, test); - } else { - with_lock_and_reset(test) - } + if card::dangerous_real_card_enabled() { + with_lock_and_reset(test) + } else { + with_vsc(WITHOUT_UUID, test); + with_vsc(WITH_UUID, test); } #[cfg(feature = "rsa")] { @@ -142,13 +118,11 @@ fn generate() { WaitStatus::Exited(p.get_process().pid(), 0) ); }; - cfg_if! { - if #[cfg(not(feature = "dangerous-test-real-card"))]{ - with_vsc(WITH_UUID, test); - with_vsc(WITHOUT_UUID, test); - } else { + if card::dangerous_real_card_enabled() { with_lock_and_reset(test) - } + } else { + with_vsc(WITHOUT_UUID, test); + with_vsc(WITH_UUID, test); } } } @@ -190,13 +164,11 @@ fn ecdh_inner(key: &str, requires_pin: bool) { assert_eq!(p.wait().unwrap().code(), Some(0)); }; - cfg_if! { - if #[cfg(not(feature = "dangerous-test-real-card"))]{ - with_vsc(WITH_UUID, test); - with_vsc(WITHOUT_UUID, test); - } else { - with_lock_and_reset(test) - } + if card::dangerous_real_card_enabled() { + with_lock_and_reset(test) + } else { + with_vsc(WITHOUT_UUID, test); + with_vsc(WITH_UUID, test); } let test = || { @@ -233,13 +205,11 @@ fn ecdh_inner(key: &str, requires_pin: bool) { assert_eq!(p.wait().unwrap().code(), Some(0)); }; - cfg_if! { - if #[cfg(not(feature = "dangerous-test-real-card"))]{ - with_vsc(WITH_UUID, test); - with_vsc(WITHOUT_UUID, test); - } else { - with_lock_and_reset(test) - } + if card::dangerous_real_card_enabled() { + with_lock_and_reset(test) + } else { + with_vsc(WITHOUT_UUID, test); + with_vsc(WITH_UUID, test); } } @@ -347,13 +317,11 @@ fn sign_inner(key: &str, requires_pin: bool) { test_rsa(); }; - cfg_if! { - if #[cfg(not(feature = "dangerous-test-real-card"))]{ - with_vsc(WITH_UUID, test); - with_vsc(WITHOUT_UUID, test); - } else { - with_lock_and_reset(test) - } + if card::dangerous_real_card_enabled() { + with_lock_and_reset(test) + } else { + with_vsc(WITHOUT_UUID, test); + with_vsc(WITH_UUID, test); } } @@ -440,13 +408,11 @@ fn large_cert() { assert_eq!(buf.strip_suffix('\n').unwrap(), LARGE_CERT); assert_eq!(p.wait().unwrap().code(), Some(0)); }; - cfg_if! { - if #[cfg(not(feature = "dangerous-test-real-card"))]{ - with_vsc(WITH_UUID, test); - with_vsc(WITHOUT_UUID, test); - } else { - with_lock_and_reset(test) - } + if card::dangerous_real_card_enabled() { + with_lock_and_reset(test) + } else { + with_vsc(WITHOUT_UUID, test); + with_vsc(WITH_UUID, test); } } @@ -460,12 +426,10 @@ fn bad_admin_key() { assert_eq!(output.status.code(), Some(2)); }; - cfg_if! { - if #[cfg(not(feature = "dangerous-test-real-card"))]{ - with_vsc(WITH_UUID, test); - with_vsc(WITHOUT_UUID, test); - } else { - with_lock_and_reset(test) - } + if card::dangerous_real_card_enabled() { + with_lock_and_reset(test) + } else { + with_vsc(WITHOUT_UUID, test); + with_vsc(WITH_UUID, test); } } diff --git a/tests/setup/mod.rs b/tests/setup/mod.rs index 9cf26aa..19ca854 100644 --- a/tests/setup/mod.rs +++ b/tests/setup/mod.rs @@ -1,4 +1,3 @@ -#[allow(unused)] pub const COMMAND_SIZE: usize = 3072; #[macro_export] @@ -8,10 +7,8 @@ macro_rules! cmd { }; } -use piv_authenticator::{ - virt::{with_ram_client, VirtClient}, - Authenticator, Options, -}; +use dev_vpicc::virt::{with_ram_client, VirtClient}; +use piv_authenticator::{Authenticator, Options}; pub type Piv<'a> = piv_authenticator::Authenticator>;