From 8103ad3d77c13ed97a0826a4968e9349f040afc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20G=C3=B3mez?= Date: Mon, 19 Oct 2026 19:06:31 +0200 Subject: [PATCH] =?UTF-8?q?feat(identity):=20el=20Almac=C3=A9n=20de=20macO?= =?UTF-8?q?S=20solo=20lista=20certificados=20con=20clave=20de=20software?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements #2008: El Almacén de macOS no lista las identidades con clave en un token - el filtro está en el listado, no en el adaptador, para probarlo con un doble del puerto Token --- .../src/identity/application/certificates.rs | 13 ++++-- .../certificates/tests/macos_store.rs | 44 +++++++++++++++++++ .../src/identity/domain/certificate.rs | 6 +++ 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/rfirma-app/src-tauri/src/identity/application/certificates.rs b/rfirma-app/src-tauri/src/identity/application/certificates.rs index f86c8300..c4127676 100644 --- a/rfirma-app/src-tauri/src/identity/application/certificates.rs +++ b/rfirma-app/src-tauri/src/identity/application/certificates.rs @@ -103,7 +103,9 @@ pub fn on_the_desktop(found: Vec) -> Vec { fn holders_only(found: Vec) -> Vec { found .into_iter() - .filter(|certificate| !certificate.is_an_authority()) + .filter(|certificate| { + !certificate.is_an_authority() && !certificate.is_a_macos_token_identity() + }) .collect() } @@ -115,9 +117,12 @@ pub fn certificates_by_class( ) -> Vec<(StoreClass, usize)> { let mut counted: Vec<(StoreClass, usize)> = Vec::new(); for store in stores { - let found = token - .list(store) - .map_or(0, |certificates| certificates.len()); + let found = token.list(store).map_or(0, |certificates| { + certificates + .iter() + .filter(|certificate| !certificate.is_a_macos_token_identity()) + .count() + }); if found == 0 { continue; } diff --git a/rfirma-app/src-tauri/src/identity/application/certificates/tests/macos_store.rs b/rfirma-app/src-tauri/src/identity/application/certificates/tests/macos_store.rs index 6a0bd712..55dfa153 100644 --- a/rfirma-app/src-tauri/src/identity/application/certificates/tests/macos_store.rs +++ b/rfirma-app/src-tauri/src/identity/application/certificates/tests/macos_store.rs @@ -121,3 +121,47 @@ fn without_a_card_the_macos_copy_is_the_one_behind_the_row() { assert_eq!(rows.len(), 1); assert_eq!(rows[0].store, StoreClass::Macos); } + +fn in_the_keychain_on_a_token(label: &str, der: &[u8]) -> TokenCertificate { + let certificate = in_the_keychain(label, der); + TokenCertificate::new( + certificate + .reference() + .clone() + .with_key_provider("com.apple.setoken:Card"), + der.to_vec(), + ) +} + +#[test] +fn a_keychain_certificate_with_its_key_in_a_token_is_not_listed() { + let home = tempfile::tempdir().expect("deberia haber directorio temporal"); + let found = [ + in_the_keychain("EN-SOFTWARE", &TestAuthority::root("EN SOFTWARE").der()), + in_the_keychain_on_a_token("EN-TOKEN", &TestAuthority::root("EN TOKEN").der()), + ]; + + let rows = listed_on_the_mac(&found, &[the_macos_store()], home.path()); + + assert_eq!(rows.len(), 1); + assert_eq!(rows[0].store, StoreClass::Macos); +} + +#[test] +fn a_card_certificate_reaching_the_keychain_on_a_token_is_listed_once_by_pkcs11() { + let home = tempfile::tempdir().expect("deberia haber directorio temporal"); + let der = TestAuthority::root("EIDAS CERTIFICADO PRUEBAS - 99999999R").der(); + let found = [ + a_copy_in(&Store::module(CARD), "COPIA-TARJETA", &der), + in_the_keychain_on_a_token("COPIA-LLAVERO", &der), + ]; + + let rows = listed_on_the_mac( + &found, + &[Store::module(CARD), the_macos_store()], + home.path(), + ); + + assert_eq!(rows.len(), 1); + assert_eq!(rows[0].store, StoreClass::Card); +} diff --git a/rfirma-app/src-tauri/src/identity/domain/certificate.rs b/rfirma-app/src-tauri/src/identity/domain/certificate.rs index 028d3a7b..6bca9036 100644 --- a/rfirma-app/src-tauri/src/identity/domain/certificate.rs +++ b/rfirma-app/src-tauri/src/identity/domain/certificate.rs @@ -335,6 +335,12 @@ impl TokenCertificate { }) } + /// Si es del Almacén de macOS con la clave en un token del sistema, que se queda para las tarjetas. + pub fn is_a_macos_token_identity(&self) -> bool { + self.reference().store().class() == StoreClass::Macos + && self.reference().key_provider().is_some() + } + /// Si es de una autoridad, con `CA:TRUE`. pub fn is_an_authority(&self) -> bool { Certificate::from_der(&self.der).is_ok_and(|certificate| {