diff --git a/rfirma-app/src-tauri/Cargo.toml b/rfirma-app/src-tauri/Cargo.toml index a8384d7e..f68c53b2 100644 --- a/rfirma-app/src-tauri/Cargo.toml +++ b/rfirma-app/src-tauri/Cargo.toml @@ -193,9 +193,9 @@ glib = "0.18" # secretos dentro del flatpak, Secret Service por D-Bus fuera de él (ADR-0034). oo7 = "0.5" -# El vigilante de lectores y tarjetas: `libpcsclite` en Linux y `winscard.dll` -# en Windows (ADR-0048). -[target.'cfg(any(target_os = "linux", windows))'.dependencies] +# El vigilante de lectores y tarjetas: `libpcsclite` en Linux, `winscard.dll` +# en Windows y `PCSC.framework` en macOS (ADR-0048). +[target.'cfg(any(target_os = "linux", windows, target_os = "macos"))'.dependencies] pcsc = "2.9" # Windows no trae OpenSSL: se compila desde las fuentes, y solo alli (ADR-0035). diff --git a/rfirma-app/src-tauri/src/identity/adapters/mod.rs b/rfirma-app/src-tauri/src/identity/adapters/mod.rs index 5bd62158..4563a11e 100644 --- a/rfirma-app/src-tauri/src/identity/adapters/mod.rs +++ b/rfirma-app/src-tauri/src/identity/adapters/mod.rs @@ -6,7 +6,7 @@ pub mod folder; pub mod keyring; #[cfg(target_os = "macos")] pub mod macos_store; -#[cfg(any(target_os = "linux", windows))] +#[cfg(any(target_os = "linux", windows, target_os = "macos"))] pub mod pcsc; #[cfg(target_os = "macos")] pub mod pending_macos_keychain; @@ -30,21 +30,21 @@ pub use pending_macos_keychain::PendingMacosKeychain as DesktopKeyring; pub use windows_credential_manager::WindowsCredentialManager as DesktopKeyring; /// Una consulta puntual a los lectores; `None`, si PC/SC no responde. -#[cfg(any(target_os = "linux", windows))] +#[cfg(any(target_os = "linux", windows, target_os = "macos"))] pub use pcsc::survey_readers; /// El vigilante de lectores de esta plataforma. -#[cfg(any(target_os = "linux", windows))] +#[cfg(any(target_os = "linux", windows, target_os = "macos"))] pub use pcsc::PcscReaderWatch as DesktopReaderWatch; /// Una consulta puntual a los lectores; sin PC/SC en esta plataforma, nunca hay respuesta. -#[cfg(not(any(target_os = "linux", windows)))] +#[cfg(not(any(target_os = "linux", windows, target_os = "macos")))] pub fn survey_readers() -> Option> { None } /// Si esta plataforma habla con PC/SC. -pub const SPEAKS_PCSC: bool = cfg!(any(target_os = "linux", windows)); +pub const SPEAKS_PCSC: bool = cfg!(any(target_os = "linux", windows, target_os = "macos")); /// El vigilante de lectores de esta plataforma. -#[cfg(not(any(target_os = "linux", windows)))] +#[cfg(not(any(target_os = "linux", windows, target_os = "macos")))] pub use readers::UnavailableReaderWatch as DesktopReaderWatch; /// El token de esta plataforma. diff --git a/rfirma-app/src-tauri/src/identity/adapters/pcsc.rs b/rfirma-app/src-tauri/src/identity/adapters/pcsc.rs index 89564b8e..e0f9288f 100644 --- a/rfirma-app/src-tauri/src/identity/adapters/pcsc.rs +++ b/rfirma-app/src-tauri/src/identity/adapters/pcsc.rs @@ -1,4 +1,4 @@ -//! El vigilante PC/SC de Linux y Windows: espera a `SCardGetStatusChange` sobre todos los lectores y las notificaciones PnP (ADR-0048). +//! El vigilante PC/SC de Linux, Windows y macOS: espera a `SCardGetStatusChange` sobre todos los lectores y las notificaciones PnP (ADR-0048). use std::ffi::CString; use std::time::Duration; @@ -70,7 +70,7 @@ impl ReaderWatch for StatusWatch { } } -/// La fuente real: `libpcsclite` en Linux, `winscard.dll` en Windows. +/// La fuente real: `libpcsclite` en Linux, `winscard.dll` en Windows, `PCSC.framework` en macOS. #[derive(Default)] pub struct PcscSource { context: Option, diff --git a/rfirma-app/src-tauri/src/identity/application/readers.rs b/rfirma-app/src-tauri/src/identity/application/readers.rs index 62cd3fce..0f572350 100644 --- a/rfirma-app/src-tauri/src/identity/application/readers.rs +++ b/rfirma-app/src-tauri/src/identity/application/readers.rs @@ -45,7 +45,7 @@ pub fn watches_the_readers(platform: Platform, channel: Channel) -> bool { match platform { Platform::Linux => matches!(channel, Channel::Native | Channel::Flatpak), Platform::Windows => channel == Channel::Windows, - Platform::MacOs => false, + Platform::MacOs => channel == Channel::Native, } } @@ -190,11 +190,11 @@ fn listed_in(token: &dyn Token, stores: &[Store]) -> Vec { token.list_across(stores).unwrap_or_default() } -/// Si el almacén se vuelve a listar al cambiar los lectores: uno de tarjeta, o el de Windows, que copia las suyas. +/// Si el almacén se vuelve a listar al cambiar los lectores: uno de tarjeta, o el de Windows o macOS, que copian las suyas. fn shows_cards(store: &Store, installed_dir: &Path) -> bool { matches!( store.class_under(installed_dir), - StoreClass::Card | StoreClass::Windows + StoreClass::Card | StoreClass::Windows | StoreClass::Macos ) } diff --git a/rfirma-app/src-tauri/src/identity/application/readers/tests.rs b/rfirma-app/src-tauri/src/identity/application/readers/tests.rs index c177b668..fff98724 100644 --- a/rfirma-app/src-tauri/src/identity/application/readers/tests.rs +++ b/rfirma-app/src-tauri/src/identity/application/readers/tests.rs @@ -229,14 +229,14 @@ fn with_no_full_listing_yet_every_store_is_opened() { } #[test] -fn the_readers_are_watched_on_linux_and_windows_but_not_on_macos() { +fn the_readers_are_watched_on_linux_windows_and_macos() { use crate::desktop::domain::channel::Channel; use crate::desktop::domain::platform::Platform; assert!(watches_the_readers(Platform::Linux, Channel::Native)); assert!(watches_the_readers(Platform::Linux, Channel::Flatpak)); assert!(watches_the_readers(Platform::Windows, Channel::Windows)); - assert!(!watches_the_readers(Platform::MacOs, Channel::Native)); + assert!(watches_the_readers(Platform::MacOs, Channel::Native)); } const CARD_KSP: &str = "Microsoft Smart Card Key Storage Provider"; @@ -421,3 +421,59 @@ fn without_the_smart_card_service_there_is_no_reader_and_the_listing_goes_on() { assert_eq!(listed.len(), 1); assert_eq!(listed[0].label, IN_SOFTWARE); } + +fn the_macos_store() -> Store { + Store::module(format!( + "{}search-list", + crate::identity::domain::store::MACOS_STORE_PREFIX + )) +} + +fn in_macos(label: &str, der: Vec) -> TokenCertificate { + TokenCertificate::new( + CertificateRef::new( + the_macos_store(), + "login.keychain-db", + label, + label.as_bytes().to_vec(), + ), + der, + ) +} + +#[test] +fn a_card_in_the_reader_with_no_card_class_certificate_in_macos_is_unreadable() { + let token = Counting::showing(|_store| { + vec![in_macos( + IN_SOFTWARE, + TestAuthority::root("Firmante en el disco").der(), + )] + }); + let arranged = Arranged::over(token, vec![the_macos_store()]); + let steps = [true]; + let (card, _) = WindowsWithACard::of(Vec::new()); + + let heard = followed(&arranged, &card, &steps); + + assert_eq!( + heard.iter().map(|news| news.reader).collect::>(), + vec![ReaderStatus::Reading, ReaderStatus::Unreadable] + ); + assert_eq!(labels_in(&heard[1]), vec![IN_SOFTWARE]); +} + +#[test] +fn a_change_in_the_readers_lists_the_macos_store_and_the_card_modules_again() { + let token = Counting::new(); + let arranged = Arranged::over(token, vec![the_macos_store(), Store::module(CARD)]); + arranged.listed_in_full(); + arranged.token.asked(); + let (card, _) = WindowsWithACard::of(Vec::new()); + + followed(&arranged, &card, &[false, true]); + + let mut asked = arranged.token.asked(); + asked.dedup(); + assert!(asked.contains(&the_macos_store())); + assert!(asked.contains(&Store::module(CARD))); +}