Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion rfirma-app/src-tauri/src/crossing/guards/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ fn the_list_of_commands_is_closed_and_this_is_how_long_it_is() {
.map(|(_, source)| production_half(source).matches("#[tauri::command").count())
.sum();

assert_eq!(orders, 66, "la lista de ordenes es cerrada a proposito");
assert_eq!(orders, 67, "la lista de ordenes es cerrada a proposito");
}

#[test]
Expand Down
76 changes: 54 additions & 22 deletions rfirma-app/src-tauri/src/identity/adapters/macos_store/dnie.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ struct Login {
shown_attempts: Option<u8>,
depth: usize,
cut: Option<TokenError>,
last_read: Option<(Store, Vec<TokenCertificate>)>,
}

impl<O: CardOpener> DnieToken<O> {
Expand Down Expand Up @@ -133,30 +134,61 @@ impl<O: CardOpener> DnieToken<O> {
.map_err(|error| token_error(&error))
}

fn read(
&self,
store: &Store,
with_the_intermediate: bool,
) -> Result<Vec<TokenCertificate>, TokenError> {
let mut session = match self.opener.open() {
Ok(session) => session,
Err(error) if is_no_readable_dnie(&error) => return Ok(Vec::new()),
Err(error) => return Err(token_error(&error)),
/// Titulares y CA intermedia en una sola sesión; todo lo leído se queda para la cadena del mismo listado.
fn read_holders(&self, store: &Store) -> Result<Vec<TokenCertificate>, TokenError> {
let Some(mut session) = self.open_if_readable()? else {
return Ok(Vec::new());
};
let mut found: Vec<TokenCertificate> = session
.certificates()
.map_err(|error| token_error(&error))?
.into_iter()
.map(|certificate| in_the_store(store, &certificate.label, certificate.der))
.collect();
if with_the_intermediate {
let intermediate = session
.certificate(INTERMEDIATE_CA_LABEL)
.map_err(|error| token_error(&error))?;
found.extend(intermediate.map(|der| in_the_store(store, INTERMEDIATE_CA_LABEL, der)));
let holders = holders_of(store, &mut session)?;
let intermediate = session
.certificate(INTERMEDIATE_CA_LABEL)
.ok()
.flatten()
.map(|der| in_the_store(store, INTERMEDIATE_CA_LABEL, der));
self.login().last_read = intermediate.map(|intermediate| {
let mut every = holders.clone();
every.push(intermediate);
(store.clone(), every)
});
Ok(holders)
}

fn read_every_certificate(&self, store: &Store) -> Result<Vec<TokenCertificate>, TokenError> {
if let Some((kept, every)) = self.login().last_read.take() {
if &kept == store {
return Ok(every);
}
}
let Some(mut session) = self.open_if_readable()? else {
return Ok(Vec::new());
};
let mut found = holders_of(store, &mut session)?;
let intermediate = session
.certificate(INTERMEDIATE_CA_LABEL)
.map_err(|error| token_error(&error))?;
found.extend(intermediate.map(|der| in_the_store(store, INTERMEDIATE_CA_LABEL, der)));
Ok(found)
}

fn open_if_readable(&self) -> Result<Option<Session<O::Card>>, TokenError> {
match self.opener.open() {
Ok(session) => Ok(Some(session)),
Err(error) if is_no_readable_dnie(&error) => Ok(None),
Err(error) => Err(token_error(&error)),
}
}
}

fn holders_of<T: Transport>(
store: &Store,
session: &mut Session<T>,
) -> Result<Vec<TokenCertificate>, TokenError> {
Ok(session
.certificates()
.map_err(|error| token_error(&error))?
.into_iter()
.map(|certificate| in_the_store(store, &certificate.label, certificate.der))
.collect())
}

/// La versión de rdnie que fija `Cargo.lock`.
Expand Down Expand Up @@ -264,11 +296,11 @@ fn has_a_length_the_dnie_admits(secret: &ProtectedSecret) -> bool {

impl<O: CardOpener> Token for DnieToken<O> {
fn list(&self, store: &Store) -> Result<Vec<TokenCertificate>, TokenError> {
self.read(store, false)
self.read_holders(store)
}

fn every_certificate(&self, store: &Store) -> Result<Vec<TokenCertificate>, TokenError> {
self.read(store, true)
self.read_every_certificate(store)
}

fn list_authenticated(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;

use openssl::asn1::Asn1Time;
use openssl::bn::BigNum;
use openssl::hash::MessageDigest;
Expand Down Expand Up @@ -250,6 +253,41 @@ fn the_intermediate_ca_completes_the_chain_of_both_certificates_for_a_site() {
}
}

#[test]
fn a_full_listing_with_holders_and_chain_opens_the_card_once() {
for (version, card) in the_two_versions() {
let police = OfThePolice::new();
let opened = Arc::new(AtomicUsize::new(0));
let counter = Arc::clone(&opened);
let dnie = the_dnie(move || {
counter.fetch_add(1, Ordering::SeqCst);
police.on(card())
});

let offered = certificates_with_their_chains(&dnie, &[dnie_store()])
.expect("el DNIe deberia listarse");

assert_eq!(offered.len(), 2, "{version}");
assert_eq!(opened.load(Ordering::SeqCst), 1, "{version}");
}
}

#[test]
fn the_chain_of_a_later_listing_is_read_from_the_card_again() {
let police = OfThePolice::new();
let opened = Arc::new(AtomicUsize::new(0));
let counter = Arc::clone(&opened);
let dnie = the_dnie(move || {
counter.fetch_add(1, Ordering::SeqCst);
police.on(EmulatedDnie::dnie_3())
});

certificates_with_their_chains(&dnie, &[dnie_store()]).expect("el DNIe deberia listarse");
certificates_with_their_chains(&dnie, &[dnie_store()]).expect("el DNIe deberia listarse");

assert_eq!(opened.load(Ordering::SeqCst), 2);
}

#[test]
fn the_signing_panel_offers_only_the_signing_certificate_of_the_dnie() {
for (version, card) in the_two_versions() {
Expand Down
20 changes: 18 additions & 2 deletions rfirma-app/src-tauri/src/identity/adapters/tauri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,19 @@ pub fn list_certificates(
.collect())
}

/// Al recuperar el foco la ventana principal: lista solo los almacenes que no son de tarjeta (ADR-0048).
#[tauri::command(async)]
pub fn list_certificates_other_than_cards(
identity: State<'_, IdentityRoot>,
) -> Result<Vec<CertificateView>, Failure> {
Ok(identity
.card_listing()
.relisted_other_than_cards()?
.into_iter()
.map(CertificateView::from)
.collect())
}

/// El último estado anunciado de los lectores, para la ventana que se monta después del anuncio (ADR-0048).
#[tauri::command]
pub fn read_card_readers(identity: State<'_, IdentityRoot>) -> ReaderStatusView {
Expand Down Expand Up @@ -116,8 +129,11 @@ pub fn remove_certificate(id: String, identity: State<'_, IdentityRoot>) -> Resu
identity.memory.as_ref(),
identity.installed_certificates(),
&id,
&identity.listed,
&identity.installed_copies,
crate::identity::application::certificates::RemovalListing {
listed: &identity.listed,
installed_copies: &identity.installed_copies,
last: &identity.last_listing,
},
)?,
)
}
Expand Down
54 changes: 33 additions & 21 deletions rfirma-app/src-tauri/src/identity/application/certificates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::collections::HashMap;
use std::path::Path;

use crate::documents::domain::handles::Handles;
use crate::identity::application::readers::LastListing;
use crate::identity::application::readers::{rows_marking_the_installed, LastListing};
use crate::identity::domain::certificate::{CertificateRef, ListedCertificate, TokenCertificate};
use crate::identity::domain::chain::issuers_of;
use crate::identity::domain::copies::{copies_of_each_certificate, ChosenCopy};
Expand Down Expand Up @@ -60,24 +60,16 @@ pub fn listed_rows(
memory: &dyn CertificateMemory,
last: &LastListing,
) -> Result<Vec<ListedCertificate>, TokenError> {
let found = token.list_across(stores)?;
let found = with_their_chains(token, token.list_across(stores)?);
last.keep(&found);
Ok(rows_keeping_handles(
on_the_desktop(found),
Ok(rows_marking_the_installed(
token,
found,
installed_dir,
listed,
installed_copies,
memory,
)
.into_iter()
.map(|mut row| {
row.installed = row.installed
|| listed
.get(&row.id)
.is_some_and(|reference| token.installed_in_the_system(installed_dir, &reference));
row
})
.collect())
))
}

/// Si rFirma instaló `reference` desde un `.p12`: en su Almacén o, donde instala el sistema, apuntado por el token.
Expand All @@ -95,10 +87,17 @@ pub fn certificates_with_their_chains(
token: &dyn Token,
stores: &[Store],
) -> Result<Vec<TokenCertificate>, TokenError> {
let found = holders_only(token.list_across(stores)?);
Ok(with_their_chains(
token,
holders_only(token.list_across(stores)?),
))
}

/// Cada certificado con los emisores que su propio almacén aporta.
pub fn with_their_chains(token: &dyn Token, found: Vec<TokenCertificate>) -> Vec<TokenCertificate> {
let mut neighbours: HashMap<Store, Vec<TokenCertificate>> = HashMap::new();

Ok(found
found
.into_iter()
.map(|certificate| {
let store = certificate.reference().store();
Expand All @@ -108,7 +107,7 @@ pub fn certificates_with_their_chains(
let issuers = issuers_of(&certificate, in_the_store);
certificate.with_its_issuers(issuers)
})
.collect())
.collect()
}

/// Lo que se enseña en el escritorio: sin autoridades ni el de autenticación del DNIe, como AutoFirma.
Expand Down Expand Up @@ -413,19 +412,31 @@ fn only_supported_keys(
Ok(())
}

/// El último listado donde se busca el asa que se quita y del que se borra.
pub struct RemovalListing<'a> {
/// Las filas del último listado.
pub listed: &'a ListedCertificates,
/// La copia instalada de cada fila que tenga una.
pub installed_copies: &'a ListedCertificates,
/// El último listado completo.
pub last: &'a LastListing,
}

/// Quita un certificado del Almacén de rFirma: lo borra, con su clave, de la base única (ADR-0034).
pub fn remove_installed(
token: &dyn Token,
keyring: &dyn Keyring,
memory: &dyn CertificateMemory,
installed_dir: &Path,
handle: &str,
listed: &ListedCertificates,
installed_copies: &ListedCertificates,
listing: RemovalListing<'_>,
) -> Result<(), InstallError> {
let reference = match installed_copies.get(handle) {
let reference = match listing.installed_copies.get(handle) {
Some(installed) => installed,
None => listed.get(handle).ok_or_else(not_from_the_last_listing)?,
None => listing
.listed
.get(handle)
.ok_or_else(not_from_the_last_listing)?,
};
if !installed_by_rfirma(token, installed_dir, &reference) {
return Err(TokenError::new(
Expand All @@ -440,6 +451,7 @@ pub fn remove_installed(
keyring.pin()?
};
token.remove_certificate(installed_dir, &reference, &pin)?;
listing.last.forget(&reference);
if memory.remembered_certificate().as_ref() == Some(&reference) {
let _ = memory.forget_the_certificate();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::path::Path;

use super::super::{
certificate_behind, remember_the_certificate, remove_installed, rows_of, ListedCertificates,
RemovalListing,
};
use super::removal::{FixedPinKeyring, RemovalOutcome};
use super::{CARD, INSTALLED, SOFTOKEN};
Expand Down Expand Up @@ -336,8 +337,11 @@ fn a_row_signed_with_a_remembered_browser_copy_still_removes_its_installed_copy(
&memory,
&installed_dir,
&rows[0].id,
&listed,
&installed_copies,
RemovalListing {
listed: &listed,
installed_copies: &installed_copies,
last: &crate::identity::application::readers::LastListing::default(),
},
)
.expect("la copia instalada deberia poder quitarse aunque la elegida sea de firefox");
}
Expand Down
Loading
Loading