From 1380cc2271bc1e3c7e7cc56694fef755570326fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Tue, 4 Aug 2026 16:08:16 +0200 Subject: [PATCH 01/13] Update dependencies --- Cargo.toml | 16 ++++----- src/constants.rs | 38 ++++++++++---------- src/container.rs | 76 +++++++++++++++++++-------------------- src/lib.rs | 12 +++---- tests/command_response.rs | 74 +++++++++++++++++--------------------- tests/opensc.rs | 3 +- tests/pivy.rs | 5 +-- 7 files changed, 108 insertions(+), 116 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3e461fb..d7b9a3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ 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 } @@ -40,24 +40,24 @@ littlefs2-core = "0.1.0" cfg-if = "1.0.0" [dev-dependencies] -rand_core = { version = "0.6", features = ["getrandom"] } +rand_core = { version = "0.10" } trussed = { version = "0.1.0", features = ["virt"] } -env_logger = "0.9" +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 = [] 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..a7bdc2d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -165,7 +165,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 +199,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 +485,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/command_response.rs b/tests/command_response.rs index f576a37..615c7f5 100644 --- a/tests/command_response.rs +++ b/tests/command_response.rs @@ -4,10 +4,10 @@ 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 +579,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 +601,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 +623,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 +646,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 +682,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 +714,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..357acdf 100644 --- a/tests/opensc.rs +++ b/tests/opensc.rs @@ -7,7 +7,8 @@ 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"); diff --git a/tests/pivy.rs b/tests/pivy.rs index 22b0e5a..d480a7a 100644 --- a/tests/pivy.rs +++ b/tests/pivy.rs @@ -1,12 +1,13 @@ #![cfg(all(feature = "vpicc", feature = "pivy-tests"))] -#![allow(clippy::zombie_processes)] +#![allow(clippy::zombie_processes, clippy::result_large_err)] 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}; From 282033644f697cf3527f309518768d8395535d63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Tue, 4 Aug 2026 17:06:15 +0200 Subject: [PATCH 02/13] Fix test with updated OpenSC --- tests/opensc.rs | 57 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/tests/opensc.rs b/tests/opensc.rs index 357acdf..c7f3b43 100644 --- a/tests/opensc.rs +++ b/tests/opensc.rs @@ -24,9 +24,14 @@ fn list() { .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! { @@ -52,9 +57,14 @@ fn admin_mutual() { .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! { @@ -79,9 +89,14 @@ fn admin_card() { 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! { @@ -162,10 +177,14 @@ fn generate_key() { 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! { @@ -187,10 +206,14 @@ fn generate_key() { 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! { From 11cd7e2cd3e9f5be485e302b4507bf3cbccf5e0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Wed, 5 Aug 2026 11:41:15 +0200 Subject: [PATCH 03/13] Move virt and vpicc to separate crate --- Cargo.toml | 20 +++++++------------- Makefile | 6 +++--- dev-vpicc/Cargo.toml | 19 +++++++++++++++++++ dev-vpicc/src/lib.rs | 2 ++ {src => dev-vpicc/src}/virt.rs | 7 +------ {src => dev-vpicc/src}/vpicc.rs | 6 ++++-- examples/vpicc.rs | 5 +++-- src/lib.rs | 7 +------ tests/card/mod.rs | 3 ++- tests/command_response.rs | 2 -- tests/opensc.rs | 2 -- tests/pivy.rs | 1 - tests/setup/mod.rs | 6 ++---- 13 files changed, 44 insertions(+), 42 deletions(-) create mode 100644 dev-vpicc/Cargo.toml create mode 100644 dev-vpicc/src/lib.rs rename {src => dev-vpicc/src}/virt.rs (97%) rename {src => dev-vpicc/src}/vpicc.rs (97%) diff --git a/Cargo.toml b/Cargo.toml index d7b9a3a..362c41a 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 } @@ -20,28 +24,24 @@ heapless = "0.9.1" 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] +dev-vpicc = { path = "dev-vpicc" } +vpicc = "0.1.0" rand_core = { version = "0.10" } -trussed = { version = "0.1.0", features = ["virt"] } env_logger = "0.10" serde = { version = "1", features = ["derive"] } serde_cbor = { version = "0.11", features = ["std"] } @@ -62,12 +62,6 @@ 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"] diff --git a/Makefile b/Makefile index 2ee55fd..425e300 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: @@ -36,7 +36,7 @@ tarpaulin: .PHONY: vpicc-example vpicc-example: - cargo run --example vpicc --features vpicc,rsa + cargo run --example vpicc .PHONY: ci ci: lint tarpaulin 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/lib.rs b/src/lib.rs index a7bdc2d..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; diff --git a/tests/card/mod.rs b/tests/card/mod.rs index db6853c..3e71285 100644 --- a/tests/card/mod.rs +++ b/tests/card/mod.rs @@ -1,4 +1,5 @@ -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; diff --git a/tests/command_response.rs b/tests/command_response.rs index 615c7f5..6b423f0 100644 --- a/tests/command_response.rs +++ b/tests/command_response.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "virt")] - mod setup; use std::borrow::Cow; diff --git a/tests/opensc.rs b/tests/opensc.rs index c7f3b43..36e9a3a 100644 --- a/tests/opensc.rs +++ b/tests/opensc.rs @@ -1,5 +1,3 @@ -#![cfg(all(feature = "vpicc", feature = "opensc-tests"))] - mod card; use std::process::Command; diff --git a/tests/pivy.rs b/tests/pivy.rs index d480a7a..ea4b023 100644 --- a/tests/pivy.rs +++ b/tests/pivy.rs @@ -1,4 +1,3 @@ -#![cfg(all(feature = "vpicc", feature = "pivy-tests"))] #![allow(clippy::zombie_processes, clippy::result_large_err)] mod card; diff --git a/tests/setup/mod.rs b/tests/setup/mod.rs index 9cf26aa..e2ee314 100644 --- a/tests/setup/mod.rs +++ b/tests/setup/mod.rs @@ -8,10 +8,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>; From b47292f8f159b1d60f97d24df36b7b4e1c1fbe4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Wed, 5 Aug 2026 11:50:12 +0200 Subject: [PATCH 04/13] Remove dangerous-test-real-card feature --- Cargo.toml | 3 -- Makefile | 2 +- tests/aa_serial.rs | 8 +++- tests/card/mod.rs | 10 +++-- tests/opensc.rs | 85 +++++++++++++++++------------------------ tests/pivy.rs | 95 +++++++++++++++++++--------------------------- 6 files changed, 87 insertions(+), 116 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 362c41a..61e6d7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,6 @@ trussed-chunked = "0.3.0" trussed-hpke = "0.3.0" trussed-wrap-key-to-file = "0.3.0" littlefs2-core = "0.1.0" -cfg-if = "1.0.0" [dev-dependencies] dev-vpicc = { path = "dev-vpicc" } @@ -74,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 425e300..44bb879 100644 --- a/Makefile +++ b/Makefile @@ -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: diff --git a/tests/aa_serial.rs b/tests/aa_serial.rs index 4815668..f43af76 100644 --- a/tests/aa_serial.rs +++ b/tests/aa_serial.rs @@ -1,13 +1,17 @@ -#![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"); +mod card; + #[test] fn test_serial_number() { + if !card::dangerous_real_card_enabled() { + return; + } let mut p = spawn("piv-tool --serial").unwrap(); p.expect(format!("Using reader with a card: {CARD}")) .unwrap(); diff --git a/tests/card/mod.rs b/tests/card/mod.rs index 3e71285..e80a943 100644 --- a/tests/card/mod.rs +++ b/tests/card/mod.rs @@ -1,3 +1,5 @@ +#![allow(unused)] + use dev_vpicc::{virt::with_ram_client, vpicc::VpiccCard}; use piv_authenticator::{Authenticator, Options}; @@ -8,14 +10,15 @@ 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") @@ -53,7 +56,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); diff --git a/tests/opensc.rs b/tests/opensc.rs index 36e9a3a..da167aa 100644 --- a/tests/opensc.rs +++ b/tests/opensc.rs @@ -4,7 +4,6 @@ use std::process::Command; use card::*; -use cfg_if::cfg_if; use expectrl::process::unix::WaitStatus; use expectrl::{spawn, Eof, Expect}; @@ -32,13 +31,11 @@ fn list() { "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); } } @@ -65,13 +62,11 @@ fn admin_mutual() { "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); } } @@ -97,13 +92,11 @@ fn admin_card() { "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); } } @@ -125,13 +118,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); } } @@ -153,13 +144,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); } } @@ -185,13 +174,11 @@ fn generate_key() { "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 = || { @@ -214,12 +201,10 @@ fn generate_key() { "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 ea4b023..e821e70 100644 --- a/tests/pivy.rs +++ b/tests/pivy.rs @@ -4,7 +4,6 @@ mod card; use card::*; -use cfg_if::cfg_if; use expectrl::process::unix::WaitStatus; use expectrl::{spawn, Eof, Expect, Regex}; @@ -82,13 +81,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 +116,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 +137,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 +183,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 +224,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 +336,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 +427,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 +445,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); } } From 8956236997ba838cf1c5e1510f93896e7f37bd48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Wed, 5 Aug 2026 11:57:33 +0200 Subject: [PATCH 05/13] Fix CI --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index fd4bed2..618b87f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -21,4 +21,4 @@ jobs: - name: Run checks run: make lint - name: Run tests - run: make test TEST_FEATURES="rsa,virt" + run: make test TEST_FEATURES="rsa" From 8ea2a3f0829f51e4574775cc2c9d1b2bdfb939d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Wed, 5 Aug 2026 15:57:49 +0200 Subject: [PATCH 06/13] Update docker image and use it in CI --- .github/workflows/ci.yaml | 1 + ci/Dockerfile | 17 ++++++++--------- ci/Makefile | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 618b87f..84a2d60 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -13,6 +13,7 @@ on: jobs: build: runs-on: ubuntu-latest + container: ghcr.io/trussed-dev/piv-authenticator-build steps: - uses: actions/checkout@master diff --git a/ci/Dockerfile b/ci/Dockerfile index cf08d2e..c21c166 100644 --- a/ci/Dockerfile +++ b/ci/Dockerfile @@ -1,16 +1,15 @@ 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-vpcd 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 @@ -19,7 +18,7 @@ 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 diff --git a/ci/Makefile b/ci/Makefile index 62d42ff..ef52f3a 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-build DOCKER ?= docker FUZZ_JOBS?=$(shell nproc) FUZZ_DURATION?="0" From a243467d383146b1dff3037ff0d935d0bf80ce0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Wed, 5 Aug 2026 16:00:35 +0200 Subject: [PATCH 07/13] Remove allow(unused) and use pub mod instead This allows us to still have warnings for internal code --- tests/aa_serial.rs | 2 +- tests/card/mod.rs | 2 -- tests/command_response.rs | 2 +- tests/opensc.rs | 2 +- tests/pivy.rs | 2 +- tests/setup/mod.rs | 1 - 6 files changed, 4 insertions(+), 7 deletions(-) diff --git a/tests/aa_serial.rs b/tests/aa_serial.rs index f43af76..dfeafe4 100644 --- a/tests/aa_serial.rs +++ b/tests/aa_serial.rs @@ -5,7 +5,7 @@ use expectrl::{spawn, Eof, Expect}; const CARD: &str = env!("PIV_DANGEROUS_TEST_CARD_READER"); const SERIAL: &str = env!("PIV_DANGEROUS_TEST_CARD_PIV_SERIAL"); -mod card; +pub mod card; #[test] fn test_serial_number() { diff --git a/tests/card/mod.rs b/tests/card/mod.rs index e80a943..57fc48c 100644 --- a/tests/card/mod.rs +++ b/tests/card/mod.rs @@ -1,5 +1,3 @@ -#![allow(unused)] - use dev_vpicc::{virt::with_ram_client, vpicc::VpiccCard}; use piv_authenticator::{Authenticator, Options}; diff --git a/tests/command_response.rs b/tests/command_response.rs index 6b423f0..b0e2287 100644 --- a/tests/command_response.rs +++ b/tests/command_response.rs @@ -1,4 +1,4 @@ -mod setup; +pub mod setup; use std::borrow::Cow; diff --git a/tests/opensc.rs b/tests/opensc.rs index da167aa..4c91db6 100644 --- a/tests/opensc.rs +++ b/tests/opensc.rs @@ -1,4 +1,4 @@ -mod card; +pub mod card; use std::process::Command; diff --git a/tests/pivy.rs b/tests/pivy.rs index e821e70..cc77a79 100644 --- a/tests/pivy.rs +++ b/tests/pivy.rs @@ -1,6 +1,6 @@ #![allow(clippy::zombie_processes, clippy::result_large_err)] -mod card; +pub mod card; use card::*; diff --git a/tests/setup/mod.rs b/tests/setup/mod.rs index e2ee314..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] From bde2aabb962cb5c55b66277d535a4c637ecba74f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Wed, 5 Aug 2026 16:10:42 +0200 Subject: [PATCH 08/13] Attempt to fix CI --- .github/workflows/ci.yaml | 8 ++++++-- ci/Dockerfile | 2 ++ ci/Makefile | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 84a2d60..5bf7ca6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -13,10 +13,14 @@ on: jobs: build: runs-on: ubuntu-latest - container: ghcr.io/trussed-dev/piv-authenticator-build - + container: ghcr.io/trussed-dev/piv-authenticator + permissions: + contents: read + packages: read steps: - uses: actions/checkout@master + - name: start pcscd + run: pcscd - name: Install rust run: rustup show active-toolchain || rustup toolchain install - name: Run checks diff --git a/ci/Dockerfile b/ci/Dockerfile index c21c166..79a0039 100644 --- a/ci/Dockerfile +++ b/ci/Dockerfile @@ -22,5 +22,7 @@ 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 ef52f3a..b9eaf10 100644 --- a/ci/Makefile +++ b/ci/Makefile @@ -1,6 +1,6 @@ -include config.mk -TAG := ghcr.io/trussed-dev/piv-authenticator-build +TAG := ghcr.io/trussed-dev/piv-authenticator:latest DOCKER ?= docker FUZZ_JOBS?=$(shell nproc) FUZZ_DURATION?="0" From bb378d0fd596b3a2d0ce458759196304a3e5dd61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Thu, 6 Aug 2026 15:30:47 +0200 Subject: [PATCH 09/13] Add logging of command tests --- tests/card/mod.rs | 20 ++++++++++++++++++++ tests/opensc.rs | 28 +++++++++++++++++++++------- tests/pivy.rs | 19 ------------------- 3 files changed, 41 insertions(+), 26 deletions(-) diff --git a/tests/card/mod.rs b/tests/card/mod.rs index 57fc48c..49cbb3b 100644 --- a/tests/card/mod.rs +++ b/tests/card/mod.rs @@ -4,6 +4,7 @@ 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; @@ -81,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/opensc.rs b/tests/opensc.rs index 4c91db6..e356db7 100644 --- a/tests/opensc.rs +++ b/tests/opensc.rs @@ -15,7 +15,9 @@ 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(); @@ -46,7 +48,9 @@ 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(); @@ -77,7 +81,9 @@ 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(); @@ -107,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(); @@ -133,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(); @@ -159,7 +169,9 @@ 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(); @@ -186,7 +198,9 @@ 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(); diff --git a/tests/pivy.rs b/tests/pivy.rs index cc77a79..4483a83 100644 --- a/tests/pivy.rs +++ b/tests/pivy.rs @@ -43,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 = || { From 01519cfa5c842e3ffaae17020a51397ba7ea90ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Thu, 6 Aug 2026 15:35:16 +0200 Subject: [PATCH 10/13] Run serial test even outside of real card --- tests/aa_serial.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/tests/aa_serial.rs b/tests/aa_serial.rs index dfeafe4..625b140 100644 --- a/tests/aa_serial.rs +++ b/tests/aa_serial.rs @@ -6,15 +6,22 @@ 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() { - if !card::dangerous_real_card_enabled() { - return; + 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); } - 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(); } From 6d1d7e68d784b3edae1fc4889b1190c371c337dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Thu, 6 Aug 2026 15:48:33 +0200 Subject: [PATCH 11/13] Run pcscd in the background --- .github/workflows/ci.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5bf7ca6..288807a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -20,10 +20,14 @@ jobs: steps: - uses: actions/checkout@master - name: start pcscd - run: 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" + - name: Stop pcscd + cancel: pcscd From 7315278751f836700e3f1d627134eea843b70c89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Thu, 6 Aug 2026 16:50:16 +0200 Subject: [PATCH 12/13] Fix virtualsmartcard implementation in image --- ci/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Dockerfile b/ci/Dockerfile index 79a0039..6100678 100644 --- a/ci/Dockerfile +++ b/ci/Dockerfile @@ -9,7 +9,7 @@ RUN cd pivy-0.12.1 && make pivy-tool FROM docker.io/rust:latest -RUN apt update && apt install --yes scdaemon libclang-dev llvm python3-pip vsmartcard-vpcd pkg-config nettle-dev libpcsclite-dev opensc libbsd-dev +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 From 4e52705dfbbd0524613b0db11a9413851826ed13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Thu, 6 Aug 2026 16:50:42 +0200 Subject: [PATCH 13/13] Use tarpaulin and upload reports --- .github/workflows/ci.yaml | 14 +++++++++++--- Makefile | 4 ++-- ci/Dockerfile | 2 -- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 288807a..2863c9b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -25,9 +25,17 @@ jobs: 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" + 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/Makefile b/Makefile index 44bb879..1c33606 100644 --- a/Makefile +++ b/Makefile @@ -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 .PHONY: ci -ci: lint tarpaulin +ci: lint test diff --git a/ci/Dockerfile b/ci/Dockerfile index 6100678..b049d50 100644 --- a/ci/Dockerfile +++ b/ci/Dockerfile @@ -13,8 +13,6 @@ RUN apt update && apt install --yes scdaemon libclang-dev llvm python3-pip vsmar 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