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
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ como fallaría Linux sin el servicio y dice en su nombre qué falta.
| --- | --- |
| OpenSSL del sistema | `openssl` con `vendored`, como en Windows: el binario no depende del de Homebrew y compila en cruzado. |
| Canal TLS local con `native-tls` | rustls, como en Windows (ADR-0036). |
| Token PKCS#11 y sus almacenes | El mismo `RealToken` de Linux; solo encuentra módulo si `RFIRMA_PKCS11_MODULE` lo nombra. |
| Token PKCS#11 y sus almacenes | `MacosToken`: el llavero por Security.framework para el Almacén de macOS, que todavía no firma, y el `RealToken` de Linux para los módulos, que solo encuentra si `RFIRMA_PKCS11_MODULE` lo nombra. |
| `mlock` del secreto | `mlock`; `MADV_DONTDUMP` es solo de Linux. |
| Cerrojos (Firefox, carpeta de paso) | Los de Unix, sin cambios. |
| Llavero `oo7` (ADR-0034) | `PendingMacosKeychain`: responde que no hay llavero. |
Expand All @@ -23,8 +23,8 @@ como fallaría Linux sin el servicio y dice en su nombre qué falta.
La firma trifásica (ADR-0001) funciona de extremo a extremo con una clave en memoria: PAdES,
CAdES y XAdES pasan por el puente y el validador de AutoFirma da por válidas las PAdES. El PIN
de una tarjeta por PKCS#11 lo pide una alerta de AppKit con campo seguro, modal sobre la ventana
que lo pidió (ADR-0047); para el almacén del sistema falta un adaptador del llavero de macOS como
el de CNG en Windows.
que lo pidió (ADR-0047); el Almacén de macOS ya lista los certificados del llavero, pero aún no
firma con ellos como firma el de CNG en Windows.

## En macOS, la API de macOS siempre que se pueda, salvo el DNIe

Expand Down
2 changes: 1 addition & 1 deletion packaging/flatpak/sources.lock
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0c70a1048823f3a8f468a30da28e9695b6d316b64c4f03e900e3af9a463d8138 rfirma-app/src-tauri/Cargo.lock
9520c54cf8146264ea9d8112d3a7cd6cef9d43e9255e24bee8cedd7993e83e11 rfirma-app/src-tauri/Cargo.lock
3 changes: 3 additions & 0 deletions rfirma-app/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions rfirma-app/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,10 @@ block2 = "0.6"
objc2 = "0.6"
objc2-app-kit = { version = "0.3", default-features = false, features = ["std", "block2", "NSAlert", "NSApplication", "NSButton", "NSControl", "NSResponder", "NSSecureTextField", "NSTextField", "NSView", "NSWindow"] }
objc2-foundation = { version = "0.3", default-features = false, features = ["std", "NSGeometry", "NSString"] }
# El Almacén de macOS: las identidades del llavero por Security.framework (ADR-0040).
security-framework = "3.7"
security-framework-sys = "2.17"
core-foundation = "0.10"

[target.'cfg(not(windows))'.dev-dependencies]
# La tarjeta falsa de las pruebas: no se compila en Windows y nunca llega a un paquete (ADR-0004).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
"type": "string"
},
"store": {
"description": "El almacén, con el nombre que acepta --store: pkcs11:<ruta del módulo>, mozilla para la familia NSS, o windows.",
"description": "El almacén, con el nombre que acepta --store: pkcs11:<ruta del módulo>, mozilla para la familia NSS, windows o apple.",
"type": "string",
"pattern": "^(pkcs11:.+|mozilla|windows)$"
"pattern": "^(pkcs11:.+|mozilla|windows|apple)$"
}
},
"required": ["alias", "store"],
Expand Down
1 change: 1 addition & 0 deletions rfirma-app/src-tauri/src/desktop/adapters/views.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ crossing! {
Card,
Installed,
Windows,
Macos,
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,5 +173,6 @@ pub(super) fn store_of(class: StoreClass, reference: &CertificateRef) -> String
StoreClass::Browser(owner) => owner.label(),
StoreClass::Nssdb => "NSS del sistema".to_owned(),
StoreClass::Windows => "Windows".to_owned(),
StoreClass::Macos => "Almacén de macOS".to_owned(),
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ fn store_name_of(certificate: &TokenCertificate) -> String {
match reference.store().class() {
StoreClass::Card => format!("pkcs11:{}", reference.module().display()),
StoreClass::Windows => "windows".to_owned(),
StoreClass::Macos => "apple".to_owned(),
StoreClass::Browser(_) | StoreClass::Nssdb | StoreClass::Installed => "mozilla".to_owned(),
}
}
Expand Down
1 change: 1 addition & 0 deletions rfirma-app/src-tauri/src/desktop/application/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ pub fn brand_of_class(class: StoreClass) -> StoreBrand {
StoreClass::Card => StoreBrand::Card,
StoreClass::Installed => StoreBrand::Installed,
StoreClass::Windows => StoreBrand::Windows,
StoreClass::Macos => StoreBrand::Macos,
StoreClass::Nssdb => StoreBrand::Nssdb,
StoreClass::Browser(owner) => match owner.browser_name() {
Some(name) => StoreBrand::Browser {
Expand Down
2 changes: 2 additions & 0 deletions rfirma-app/src-tauri/src/desktop/domain/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ pub enum StoreBrand {
Installed,
/// Almacén raíz del usuario de Windows.
Windows,
/// Los certificados con clave del llavero de macOS.
Macos,
}

/// Un almacén, con su marca y si la señal es de confianza en él.
Expand Down
2 changes: 1 addition & 1 deletion rfirma-app/src-tauri/src/identity/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
El contexto de la **identidad**: qué certificados hay, cuál se usa y cómo entra o
sale un `.p12`. Aquí no se firma ningún documento, solo se dice quién puede.
Su adaptador de verdad es `adapters/pkcs11/`, la única parte del backend que
habla con el token; en Windows, también `adapters/windows_store/`. Rutas relativas a `src/identity/`.
habla con el token; en Windows, también `adapters/windows_store/`, y en macOS, `adapters/macos_store/`. Rutas relativas a `src/identity/`.

## Dónde vive qué

Expand Down
162 changes: 162 additions & 0 deletions rfirma-app/src-tauri/src/identity/adapters/macos_store.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
//! `MacosToken`, el Almacén de macOS detrás del puerto `Token`, por Security.framework; los módulos PKCS#11 siguen en `RealToken` (ADR-0040).

mod keychain;

use std::path::Path;

use super::pkcs11::RealToken;
use crate::identity::domain::algorithm::SignatureAlgorithm;
use crate::identity::domain::certificate::{CertificateRef, TokenCertificate};
use crate::identity::domain::error::{Situation, TokenError};
use crate::identity::domain::protected_secret::ProtectedSecret;
use crate::identity::domain::secret::{PinWarning, StoreSecret};
use crate::identity::domain::store::{Store, StoreClass, MACOS_STORE_PREFIX};
use crate::identity::ports::Token;

const SEARCH_LIST: &str = "search-list";

/// El llavero de la persona usuaria: su lista de búsqueda entera.
pub fn user_store() -> Store {
Store::module(format!("{MACOS_STORE_PREFIX}{SEARCH_LIST}"))
}

/// Un llavero suelto, por su fichero.
pub fn keychain_store(path: &Path) -> Store {
Store::module(format!("{MACOS_STORE_PREFIX}{}", path.display()))
}

fn is_a_keychain(store: &Store) -> bool {
store.class() == StoreClass::Macos
}

/// Los almacenes de esta máquina: el llavero y los PKCS#11 descubiertos, o solo el módulo nombrado.
pub fn from_environment() -> Vec<Store> {
stores_besides(
super::pkcs11::stores::from_environment(),
std::env::var_os(crate::PKCS11_MODULE_VARIABLE).is_some(),
)
}

fn stores_besides(pkcs11: Vec<Store>, module_named: bool) -> Vec<Store> {
if module_named {
return pkcs11;
}
std::iter::once(user_store()).chain(pkcs11).collect()
}

fn not_signing_yet() -> TokenError {
TokenError::new(
Situation::MechanismNotOffered,
"el Almacén de macOS todavía no firma",
)
}

/// El adaptador de [`Token`] en macOS: el llavero para el Almacén de macOS y [`RealToken`] para los demás.
#[derive(Clone, Copy, Debug, Default)]
pub struct MacosToken;

impl Token for MacosToken {
fn list(&self, store: &Store) -> Result<Vec<TokenCertificate>, TokenError> {
if is_a_keychain(store) {
return keychain::signable_certificates(store);
}
RealToken.list(store)
}

fn every_certificate(&self, store: &Store) -> Result<Vec<TokenCertificate>, TokenError> {
if is_a_keychain(store) {
return keychain::every_certificate(store);
}
RealToken.every_certificate(store)
}

fn list_authenticated(
&self,
store: &Store,
pin: &ProtectedSecret,
) -> Result<Vec<TokenCertificate>, TokenError> {
if is_a_keychain(store) {
return keychain::signable_certificates(store);
}
RealToken.list_authenticated(store, pin)
}

fn secret_of(&self, reference: &CertificateRef) -> Result<StoreSecret, TokenError> {
if is_a_keychain(&reference.store()) {
return Ok(StoreSecret::NotNeeded);
}
RealToken.secret_of(reference)
}

fn pin_warning(&self, reference: &CertificateRef) -> Result<PinWarning, TokenError> {
if is_a_keychain(&reference.store()) {
return Ok(PinWarning::Quiet);
}
RealToken.pin_warning(reference)
}

fn offers(
&self,
reference: &CertificateRef,
algorithm: SignatureAlgorithm,
) -> Result<(), TokenError> {
if is_a_keychain(&reference.store()) {
return Err(not_signing_yet());
}
RealToken.offers(reference, algorithm)
}

fn accepts_the_secret(
&self,
reference: &CertificateRef,
secret: &ProtectedSecret,
) -> Result<(), TokenError> {
if is_a_keychain(&reference.store()) {
return Ok(());
}
RealToken.accepts_the_secret(reference, secret)
}

fn sign_with_secret(
&self,
reference: &CertificateRef,
secret: &ProtectedSecret,
algorithm: SignatureAlgorithm,
data: &[u8],
) -> Result<Vec<u8>, TokenError> {
if is_a_keychain(&reference.store()) {
return Err(not_signing_yet());
}
RealToken.sign_with_secret(reference, secret, algorithm, data)
}

fn hold_one_login(&self, reference: &CertificateRef) {
RealToken.hold_one_login(reference);
}

fn release_the_login(&self, reference: &CertificateRef) {
RealToken.release_the_login(reference);
}

fn import_pkcs12(
&self,
directory: &Path,
pkcs12: &[u8],
password: &str,
pin: &ProtectedSecret,
) -> Result<Store, TokenError> {
RealToken.import_pkcs12(directory, pkcs12, password, pin)
}

fn remove_certificate(
&self,
directory: &Path,
reference: &CertificateRef,
pin: &ProtectedSecret,
) -> Result<(), TokenError> {
RealToken.remove_certificate(directory, reference, pin)
}
}

#[cfg(test)]
mod tests;
Loading
Loading