diff --git a/justfile b/justfile index 9608230df..070730cf0 100644 --- a/justfile +++ b/justfile @@ -231,6 +231,12 @@ knip: po-import certs action: ./testdata/softhsm/certs.sh {{ action }} +# Instala o quita los certificados de pruebas en un llavero aparte de macOS, el que ve la app instalada: `just keychain-certs install|uninstall`. +[macos] +[group('dev')] +keychain-certs action: + ./testdata/keychain/certs.sh {{ action }} + # Descarga (a etiqueta y sha fijados) el autoscript.js del banco de conformidad. [group('ci')] [script('bash')] diff --git a/rfirma-app/src-tauri/tests/command_line/support.rs b/rfirma-app/src-tauri/tests/command_line/support.rs index eec06295d..f5ec0a39b 100644 --- a/rfirma-app/src-tauri/tests/command_line/support.rs +++ b/rfirma-app/src-tauri/tests/command_line/support.rs @@ -35,7 +35,9 @@ pub use rfirma_lib::Roots; pub use base64::Engine; pub use std::sync::Arc; -pub const CARD_MODULE: &str = "/usr/lib/softhsm/libsofthsm2.so"; +#[path = "../support/softhsm.rs"] +mod softhsm; + pub const CARD_ACTIVE: &str = "FNMT-ACTIVO-99999999R"; pub const KIT_PASSWORD: &str = "1234"; @@ -128,16 +130,7 @@ pub fn the_token_alone() -> std::sync::MutexGuard<'static, ()> { .unwrap_or_else(std::sync::PoisonError::into_inner) } -pub fn the_card_module() -> PathBuf { - let module = PathBuf::from(CARD_MODULE); - assert!( - module.is_file(), - "falta el modulo PKCS#11 en {}. Estas pruebas necesitan SoftHSM:\n \ - sudo apt install -y softhsm2 opensc\n just certs install", - module.display() - ); - module -} +pub use softhsm::the_softhsm_module as the_card_module; /// La configuración de SoftHSM de quien corre la prueba, que el binario no encontraría con otra casa. pub fn the_softhsm_configuration() -> Option { diff --git a/rfirma-app/src-tauri/tests/installed_signing.rs b/rfirma-app/src-tauri/tests/installed_signing.rs index a6c2e0bfb..ed3eba520 100644 --- a/rfirma-app/src-tauri/tests/installed_signing.rs +++ b/rfirma-app/src-tauri/tests/installed_signing.rs @@ -22,10 +22,12 @@ use rfirma_lib::identity::IdentityRoot; use rfirma_lib::memory_error::MemoryError; use rfirma_lib::signing::ports::Signer; +#[path = "support/softhsm.rs"] +mod softhsm; + const KIT_PASSWORD: &str = "1234"; const KEYRING_PIN: &str = "pin-de-pruebas-del-almacen-de-rfirma"; -const CARD_MODULE: &str = "/usr/lib/softhsm/libsofthsm2.so"; const CARD_ACTIVE: &str = "FNMT-ACTIVO-99999999R"; fn repository_root() -> PathBuf { @@ -152,14 +154,7 @@ fn installed_certificate(installed: &Path) -> CertificateRef { } fn card_certificate() -> CertificateRef { - let module = PathBuf::from(CARD_MODULE); - assert!( - module.is_file(), - "falta el modulo PKCS#11 en {}. Las pruebas de grada B necesitan SoftHSM:\n \ - sudo apt install -y softhsm2 opensc\n just certs install", - module.display() - ); - pkcs11::list_certificates(Store::module(module)) + pkcs11::list_certificates(Store::module(softhsm::the_softhsm_module())) .expect("no se ha podido listar el token") .into_iter() .find(|certificate| certificate.reference().label() == CARD_ACTIVE) diff --git a/rfirma-app/src-tauri/tests/native_cycle/support.rs b/rfirma-app/src-tauri/tests/native_cycle/support.rs index 5d8ba5008..e39aea5ca 100644 --- a/rfirma-app/src-tauri/tests/native_cycle/support.rs +++ b/rfirma-app/src-tauri/tests/native_cycle/support.rs @@ -25,6 +25,9 @@ use rfirma_lib::signing::domain::{ use rfirma_lib::site::domain::protocol::AskedAlgorithm; use rfirma_lib::site::ports::composed_for; +#[path = "../support/softhsm.rs"] +mod softhsm; + pub(crate) const TOKEN: &str = "rfirma-test"; pub(crate) const PIN: &str = "1234"; /// Contraseña del `.p12` del kit de pruebas. @@ -60,19 +63,7 @@ pub(crate) fn bridge() -> NativeBridge { NativeBridge::open_at(&library()).expect("la librería debería cargarse") } -pub(crate) fn module() -> PathBuf { - let module = PathBuf::from( - std::env::var("RFIRMA_PKCS11_MODULE") - .unwrap_or_else(|_| "/usr/lib/softhsm/libsofthsm2.so".to_owned()), - ); - assert!( - module.is_file(), - "falta el modulo PKCS#11 en {}. La grada C necesita SoftHSM:\n \ - sudo apt install -y softhsm2 opensc\n just certs install", - module.display() - ); - module -} +pub(crate) use softhsm::the_softhsm_module as module; pub(crate) fn signing_certificate() -> TokenCertificate { certificate_labelled(ACTIVE) diff --git a/rfirma-app/src-tauri/tests/native_first_xades.rs b/rfirma-app/src-tauri/tests/native_first_xades.rs index e8b3fc5cd..58e01e82f 100644 --- a/rfirma-app/src-tauri/tests/native_first_xades.rs +++ b/rfirma-app/src-tauri/tests/native_first_xades.rs @@ -11,6 +11,9 @@ use rfirma_lib::signing::application::cycle::{self, SigningRequest}; use rfirma_lib::signing::domain::bridge::{Format, SignatureOperation, XadesVariant}; use rfirma_lib::signing::domain::{AdmissibleDocument, SignatureConfig, Waivers}; +#[path = "support/softhsm.rs"] +mod softhsm; + const PIN: &str = "1234"; /// Certificado activo del kit de pruebas. const ACTIVE: &str = "FNMT-ACTIVO-99999999R"; @@ -28,11 +31,7 @@ fn bridge() -> NativeBridge { } fn signing_certificate() -> TokenCertificate { - let module = PathBuf::from( - std::env::var("RFIRMA_PKCS11_MODULE") - .unwrap_or_else(|_| "/usr/lib/softhsm/libsofthsm2.so".to_owned()), - ); - pkcs11::list_certificates(module) + pkcs11::list_certificates(softhsm::the_softhsm_module()) .expect("no se ha podido listar el token") .into_iter() .find(|certificate| certificate.reference().label() == ACTIVE) diff --git a/rfirma-app/src-tauri/tests/pkcs11_token/support.rs b/rfirma-app/src-tauri/tests/pkcs11_token/support.rs index a29166a68..8e1f922f3 100644 --- a/rfirma-app/src-tauri/tests/pkcs11_token/support.rs +++ b/rfirma-app/src-tauri/tests/pkcs11_token/support.rs @@ -1,6 +1,5 @@ //! Fixtures del token de pruebas y ayudantes de firma/verificación para `pkcs11_token.rs`. -use std::path::PathBuf; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use openssl::hash::MessageDigest; @@ -17,6 +16,9 @@ use rsa::RsaPublicKey; use sha2::Sha256; use x509_cert::der::{Decode, Encode}; +#[path = "../support/softhsm.rs"] +mod softhsm; + pub(crate) const TOKEN: &str = "rfirma-test"; pub(crate) const PIN: &str = "1234"; pub(crate) const ACTIVE: &str = "FNMT-ACTIVO-99999999R"; @@ -34,19 +36,7 @@ pub(crate) const REPRESENTATIVE_LEGAL_ENTITY: &str = "FNMT-REPRESENTANTE-PJ"; pub(crate) const PRESIGN: &[u8] = b"31 5f 30 18 06 09 2a 86 SignedAttributes de mentira, sin hashear"; -pub(crate) fn module() -> PathBuf { - let module = PathBuf::from( - std::env::var("RFIRMA_PKCS11_MODULE") - .unwrap_or_else(|_| "/usr/lib/softhsm/libsofthsm2.so".to_owned()), - ); - assert!( - module.is_file(), - "falta el modulo PKCS#11 en {}. Las pruebas de grada B necesitan SoftHSM:\n \ - sudo apt install -y softhsm2 opensc\n just certs install", - module.display() - ); - module -} +pub(crate) use softhsm::the_softhsm_module as module; pub(crate) fn certificates() -> Vec { let found = pkcs11::list_certificates(module()).expect("no se ha podido listar el token"); diff --git a/rfirma-app/src-tauri/tests/remembered_certificate.rs b/rfirma-app/src-tauri/tests/remembered_certificate.rs index 1fe6ed67c..e951c787b 100644 --- a/rfirma-app/src-tauri/tests/remembered_certificate.rs +++ b/rfirma-app/src-tauri/tests/remembered_certificate.rs @@ -1,7 +1,5 @@ //! Ciclo de persistencia del certificado recordado entre sesiones contra token SoftHSM (ADR-0014). -use std::path::PathBuf; - use rfirma_lib::desktop::adapters::paths::Paths; use rfirma_lib::identity::adapters::pkcs11; use rfirma_lib::identity::application::certificates::{rows_of, ListedCertificates}; @@ -9,24 +7,15 @@ use rfirma_lib::identity::domain::certificate::{CertificateRef, TokenCertificate use rfirma_lib::signing::adapters::memory::Memory; use rfirma_lib::signing::application::configuration_memory::Configuration; +#[path = "support/softhsm.rs"] +mod softhsm; + const TOKEN: &str = "rfirma-test"; const ACTIVE: &str = "FNMT-ACTIVO-99999999R"; /// Certificados gemelos que comparten `CKA_LABEL` pero difieren en `CKA_ID`. const TWIN: &str = "FNMT-GEMELO-99999999R"; -fn module() -> PathBuf { - let module = PathBuf::from( - std::env::var("RFIRMA_PKCS11_MODULE") - .unwrap_or_else(|_| "/usr/lib/softhsm/libsofthsm2.so".to_owned()), - ); - assert!( - module.is_file(), - "falta el modulo PKCS#11 en {}. Las pruebas de grada B necesitan SoftHSM:\n \ - sudo apt install -y softhsm2 opensc\n just certs install", - module.display() - ); - module -} +use softhsm::the_softhsm_module as module; fn certificates() -> Vec { let found = pkcs11::list_certificates(module()).expect("no se ha podido listar el token"); diff --git a/rfirma-app/src-tauri/tests/single_cfg_os_site.rs b/rfirma-app/src-tauri/tests/single_cfg_os_site.rs index d0dcb3790..83d361dcf 100644 --- a/rfirma-app/src-tauri/tests/single_cfg_os_site.rs +++ b/rfirma-app/src-tauri/tests/single_cfg_os_site.rs @@ -5,7 +5,7 @@ use std::path::{Path, PathBuf}; use std::process::Command; /// Los ficheros autorizados, relativos a la raíz del repositorio. -const AUTHORISED_SITES: [&str; 34] = [ +const AUTHORISED_SITES: [&str; 35] = [ "rfirma-app/src-tauri/build.rs", "rfirma-app/src-tauri/src/desktop/adapters/channel.rs", "rfirma-app/src-tauri/src/desktop/adapters/choice/tests.rs", @@ -40,6 +40,7 @@ const AUTHORISED_SITES: [&str; 34] = [ "rfirma-app/src-tauri/tests/fake_card.rs", "rfirma-app/src-tauri/tests/native_fake_card.rs", "rfirma-app/src-tauri/tests/native_leak.rs", + "rfirma-app/src-tauri/tests/support/softhsm.rs", ]; /// Fichero de esta prueba para no acusarse a sí misma. diff --git a/rfirma-app/src-tauri/tests/site_token_signing.rs b/rfirma-app/src-tauri/tests/site_token_signing.rs index 4de9f01e9..e50b3016a 100644 --- a/rfirma-app/src-tauri/tests/site_token_signing.rs +++ b/rfirma-app/src-tauri/tests/site_token_signing.rs @@ -1,7 +1,5 @@ //! Pruebas de integración del puerto de firma del lote contra el módulo PKCS#11 SoftHSM (ADR-0014). -use std::path::PathBuf; - use openssl::hash::MessageDigest; use openssl::pkey::PKey; use openssl::sign::Verifier; @@ -20,6 +18,9 @@ use rsa::RsaPublicKey; use sha2::{Sha256, Sha512}; use x509_cert::der::{Decode, Encode}; +#[path = "support/softhsm.rs"] +mod softhsm; + const TOKEN: &str = "rfirma-test"; const PIN: &str = "1234"; const ACTIVE: &str = "FNMT-ACTIVO-99999999R"; @@ -28,19 +29,7 @@ const ACTIVE: &str = "FNMT-ACTIVO-99999999R"; const FIRST: &[u8] = b"31 5f 30 18 SignedAttributes del primer documento del lote"; const SECOND: &[u8] = b"31 5f 30 18 SignedAttributes del segundo documento del lote"; -fn module() -> PathBuf { - let module = PathBuf::from( - std::env::var("RFIRMA_PKCS11_MODULE") - .unwrap_or_else(|_| "/usr/lib/softhsm/libsofthsm2.so".to_owned()), - ); - assert!( - module.is_file(), - "falta el modulo PKCS#11 en {}. Las pruebas de grada B necesitan SoftHSM:\n \ - sudo apt install -y softhsm2 opensc\n just certs install", - module.display() - ); - module -} +use softhsm::the_softhsm_module as module; fn certificate() -> TokenCertificate { let found = pkcs11::list_certificates(module()).expect("no se ha podido listar el token"); diff --git a/rfirma-app/src-tauri/tests/support/desk.rs b/rfirma-app/src-tauri/tests/support/desk.rs index 7772b2a05..eef21c7b7 100644 --- a/rfirma-app/src-tauri/tests/support/desk.rs +++ b/rfirma-app/src-tauri/tests/support/desk.rs @@ -2,20 +2,7 @@ use super::*; -/// El módulo PKCS#11 del token de pruebas. -pub fn the_test_module() -> PathBuf { - let module = PathBuf::from( - std::env::var("RFIRMA_PKCS11_MODULE") - .unwrap_or_else(|_| "/usr/lib/softhsm/libsofthsm2.so".to_owned()), - ); - assert!( - module.is_file(), - "falta el modulo PKCS#11 en {}. La grada C necesita SoftHSM:\n \ - sudo apt install -y softhsm2 opensc\n just certs install", - module.display() - ); - module -} +pub use super::softhsm::the_softhsm_module as the_test_module; /// Las cinco raíces de un rFirma en marcha que solo ve el token de pruebas y recuerda bajo esa /// carpeta, para que el caso arranque siempre sin certificado recordado. diff --git a/rfirma-app/src-tauri/tests/support/mod.rs b/rfirma-app/src-tauri/tests/support/mod.rs index fc25ef582..de7de5b96 100644 --- a/rfirma-app/src-tauri/tests/support/mod.rs +++ b/rfirma-app/src-tauri/tests/support/mod.rs @@ -1,6 +1,7 @@ //! Andamiaje compartido del banco de conformidad (ADR-0014): el cliente publicado bajo Node y el canal que abre rFirma para atenderlo. mod desk; +mod softhsm; pub use desk::*; pub use std::io::{BufRead, BufReader}; diff --git a/rfirma-app/src-tauri/tests/support/softhsm.rs b/rfirma-app/src-tauri/tests/support/softhsm.rs new file mode 100644 index 000000000..645c5f1d1 --- /dev/null +++ b/rfirma-app/src-tauri/tests/support/softhsm.rs @@ -0,0 +1,31 @@ +//! El módulo PKCS#11 de SoftHSM en el que `just certs install` monta los tokens de pruebas. + +use std::path::PathBuf; + +#[cfg(target_os = "macos")] +fn system_module() -> PathBuf { + let prefix = std::env::var("HOMEBREW_PREFIX").unwrap_or_else(|_| "/opt/homebrew".to_owned()); + PathBuf::from(prefix).join("lib/softhsm/libsofthsm2.so") +} + +#[cfg(not(target_os = "macos"))] +fn system_module() -> PathBuf { + PathBuf::from("/usr/lib/softhsm/libsofthsm2.so") +} + +#[cfg(target_os = "macos")] +const INSTALL: &str = "brew install softhsm opensc"; +#[cfg(not(target_os = "macos"))] +const INSTALL: &str = "sudo apt install -y softhsm2 opensc"; + +/// El módulo de `RFIRMA_PKCS11_MODULE` o, sin ella, el SoftHSM del sistema. +pub fn the_softhsm_module() -> PathBuf { + let module = std::env::var_os("RFIRMA_PKCS11_MODULE").map_or_else(system_module, PathBuf::from); + assert!( + module.is_file(), + "falta el modulo PKCS#11 en {}. Estas pruebas necesitan SoftHSM:\n \ + {INSTALL}\n just certs install", + module.display() + ); + module +} diff --git a/scripts/isolated-store.sh b/scripts/isolated-store.sh index d8e36148c..4adb22e53 100755 --- a/scripts/isolated-store.sh +++ b/scripts/isolated-store.sh @@ -39,7 +39,7 @@ set -euo pipefail here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" fnmt="$here/../testdata/fnmt" site_driver="$here/../testdata/site-driver" -module="${RFIRMA_PKCS11_MODULE:-/usr/lib/softhsm/libsofthsm2.so}" +source "$here/../testdata/softhsm/platform.sh" subject="${1:-}" store="${3:-rsa}" @@ -109,7 +109,7 @@ if $with_token; then for tool in softhsm2-util pkcs11-tool openssl; do command -v "$tool" >/dev/null || { echo "falta: $tool" >&2 - echo " sudo apt install -y softhsm2 opensc openssl" >&2 + echo " $install_tools" >&2 exit 1 } done @@ -135,7 +135,7 @@ export SOFTHSM2_CONF="$softhsm_conf" if $with_token; then [ -f "$module" ] || { echo "falta el modulo PKCS#11: $module" >&2 - echo " sudo apt install -y softhsm2" >&2 + echo " $install_softhsm" >&2 exit 1 } if [ "$store" = token ]; then diff --git a/testdata/keychain/certs.sh b/testdata/keychain/certs.sh new file mode 100755 index 000000000..914afb7b9 --- /dev/null +++ b/testdata/keychain/certs.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# +# Instala (`install`) o quita (`uninstall`) los .p12 de testdata/fnmt en un llavero +# de pruebas de macOS que se añade a la lista de búsqueda del usuario, donde los +# ve rFirma; quitarlo borra el llavero entero y no toca el de inicio de sesión. + +set -euo pipefail + +keychain="$HOME/Library/Keychains/rfirma-test.keychain-db" +password="1234" +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +fnmt="$here/../fnmt" +scratch="" + +the_password_of() { + awk -F'|' -v name="\`$1\`" '$2 ~ name { gsub(/[ `]/, "", $3); print $3; exit }' \ + "$fnmt/README.md" +} + +# El Llavero rechaza los .p12 con AES y MAC SHA-256 como si la contraseña fuera mala. +in_legacy_cipher() { + openssl pkcs12 -in "$1" -passin "pass:$2" -nodes -legacy -out "$scratch/unwrapped.pem" + openssl pkcs12 -export -legacy -in "$scratch/unwrapped.pem" -passout "pass:$password" -out "$3" +} + +search_list() { + security list-keychains -d user | sed 's/^[[:space:]]*"//; s/"[[:space:]]*$//' +} + +install() { + if [ -f "$keychain" ]; then + echo "el llavero $keychain ya existe; para rehacerlo: uninstall e install" + return + fi + security create-keychain -p "$password" "$keychain" + security set-keychain-settings "$keychain" + security unlock-keychain -p "$password" "$keychain" + + local keychains=() + while IFS= read -r line; do keychains+=("$line"); done < <(search_list) + security list-keychains -d user -s "${keychains[@]}" "$keychain" + + local p12 + scratch="$(mktemp -d)" + trap '[ -z "$scratch" ] || rm -rf "$scratch"' EXIT + for p12 in "$fnmt"/*.p12; do + in_legacy_cipher "$p12" "$(the_password_of "$(basename "$p12")")" "$scratch/legacy.p12" + security import "$scratch/legacy.p12" -k "$keychain" -P "$password" -A >/dev/null + echo "importado $(basename "$p12")" + done + echo "llavero de pruebas listo en $keychain (contraseña $password)" +} + +uninstall() { + if [ ! -f "$keychain" ]; then + echo "no hay llavero de pruebas en $keychain" + return + fi + security delete-keychain "$keychain" + echo "llavero de pruebas borrado" +} + +case "${1:-}" in + install) install ;; + uninstall) uninstall ;; + *) + echo "uso: $0 install|uninstall" >&2 + exit 1 + ;; +esac diff --git a/testdata/softhsm/certs.sh b/testdata/softhsm/certs.sh index b7a7cf422..8222dc309 100755 --- a/testdata/softhsm/certs.sh +++ b/testdata/softhsm/certs.sh @@ -15,7 +15,6 @@ set -euo pipefail -module="${RFIRMA_PKCS11_MODULE:-/usr/lib/softhsm/libsofthsm2.so}" kit="${RFIRMA_TEST_CERTS:-$HOME/.local/share/rfirma-test-certs}" token_label="rfirma-kit" pin="1234" @@ -31,6 +30,7 @@ selection=( ) here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$here/platform.sh" delete_token() { if softhsm2-util --show-slots | grep -q "Label:[[:space:]]*$1[[:space:]]*$"; then diff --git a/testdata/softhsm/platform.sh b/testdata/softhsm/platform.sh new file mode 100644 index 000000000..5196b8412 --- /dev/null +++ b/testdata/softhsm/platform.sh @@ -0,0 +1,12 @@ +# Módulo de SoftHSM y órdenes de instalación de cada sistema; se carga con `source`. + +if [ "$(uname -s)" = Darwin ]; then + softhsm_module="${HOMEBREW_PREFIX:-/opt/homebrew}/lib/softhsm/libsofthsm2.so" + install_tools="brew install softhsm opensc openssl" + install_softhsm="brew install softhsm" +else + softhsm_module="/usr/lib/softhsm/libsofthsm2.so" + install_tools="sudo apt install -y softhsm2 opensc openssl" + install_softhsm="sudo apt install -y softhsm2" +fi +module="${RFIRMA_PKCS11_MODULE:-$softhsm_module}" diff --git a/testdata/softhsm/provision-token.sh b/testdata/softhsm/provision-token.sh index d92da618a..1b6c0f13e 100755 --- a/testdata/softhsm/provision-token.sh +++ b/testdata/softhsm/provision-token.sh @@ -63,24 +63,24 @@ set -euo pipefail -module="${RFIRMA_PKCS11_MODULE:-/usr/lib/softhsm/libsofthsm2.so}" pin="1234" so_pin="3737" here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$here/platform.sh" kit="$here/../fnmt" for tool in softhsm2-util pkcs11-tool openssl; do command -v "$tool" >/dev/null || { echo "falta: $tool" >&2 - echo " sudo apt install -y softhsm2 opensc openssl" >&2 + echo " $install_tools" >&2 exit 1 } done [ -f "$module" ] || { echo "falta el modulo PKCS#11: $module" >&2 - echo " sudo apt install -y softhsm2" >&2 + echo " $install_softhsm" >&2 exit 1 }