diff --git a/Cargo.lock b/Cargo.lock index b9200a6..3f85311 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1963,6 +1963,18 @@ dependencies = [ "libc", ] +[[package]] +name = "fsv" +version = "0.1.0" +dependencies = [ + "anyhow", + "fsv-sys", + "libc", + "num_enum", + "thiserror", + "utils", +] + [[package]] name = "fsv-sys" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 5c19dfc..f7ad9d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "crates/backend", "crates/desktop", "crates/sesam-vitale", + "crates/fsv", "crates/fsv-sys", "crates/utils", "migration", diff --git a/crates/fsv/Cargo.toml b/crates/fsv/Cargo.toml new file mode 100644 index 0000000..e7bcb4b --- /dev/null +++ b/crates/fsv/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "fsv" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.89" +libc = "0.2.159" +num_enum = { version = "0.7.3", features = ["complex-expressions"] } +thiserror = "1.0.64" + +fsv-sys = { path = "../fsv-sys" } +utils = { path = "../utils" } diff --git a/crates/fsv/src/lib.rs b/crates/fsv/src/lib.rs new file mode 100644 index 0000000..77a1012 --- /dev/null +++ b/crates/fsv/src/lib.rs @@ -0,0 +1 @@ +mod ssv; diff --git a/crates/fsv/src/ssv/errors_ssv.rs b/crates/fsv/src/ssv/errors_ssv.rs new file mode 100644 index 0000000..5646311 --- /dev/null +++ b/crates/fsv/src/ssv/errors_ssv.rs @@ -0,0 +1,183 @@ +use num_enum::FromPrimitive; +use thiserror::Error; + +#[derive(Error, Debug, Eq, PartialEq, FromPrimitive)] +#[repr(u16)] +/// Liste des codes d'erreur retournés par la librairie C SSV +/// Documentation: Manuel de programmation SSV - Annexe A (p. 215) +pub enum SSVErrorCodes { + #[error("La Carte du Professionnel de Santé est absente du lecteur.")] + CPSMissing = 0xF001, + #[error("La Carte du Professionnel de Santé bloquée après trois codes porteur erronés.")] + CPSBlocked = 0xF002, + #[error("Le code porteur présenté est erroné.")] + CPSPinWrong = 0xF003, + #[error("Carte du Professionnel de Santé non valide ou inexploitable par le Logiciel Lecteur. Vérifier la présence d'un Domaine d'Assurance Maladie (DAM).")] + CPSInvalid = 0xF004, + #[error("La Carte du Professionnel de Santé est retirée du lecteur.")] + CPSRemoved = 0xF005, + /// - Sécurisation d'une série de lots en cours. + /// - Pour les fonctions TLA (sauf Identifier TLA) : Cette erreur survient lorsque le simulateur TLA est en mode 1.50. + /// - Lire Date Lecteur, Mettre à jour Date Lecteur, Lire Droits Vitale : Cette erreur peut survenir lorsque le Logiciel Lecteur ne connaît pas la fonction sollicitée, c'est-à-dire si la version du Logiciel Lecteur est antérieure à 2.00. + /// - Décharger Données Bénéficiaires : cette erreur peut survenir pour signaler que le format des données issues du lecteur est incompatible avec cette version de SSV. + #[error("F022: Erreur commune à plusieurs fonctions.")] + F022 = 0xF022, + #[error("Message du lecteur incohérent. Débrancher et rebrancher le lecteur.")] + PCSCInconsistentMessage = 0xF0FF, + #[error("Le nom de lecteur fourni ne correspond à aucun lecteur reconnu.")] + PCSCReaderNotFound = 0xF101, + #[error("La fonction InitLIB2 n'est pas encore appelée ou la fonction TermLIB a déjà été appelée.")] + FunctionInitLib2NotCalled = 0xF600, + #[error("La bibliothèque SSV n’est pas chargée en mémoire. Vérifier que la fonction InitLIB2 a bien été appelée.")] + LibraryNotLoaded = 0xF690, // Warning + #[error("Carte vitale en opposition.")] + VitaleOpposition = 0xF6A1, + #[error("Zone de mémoire non allouée en sortie.")] + MemoryNotAllocated = 0xF800, + #[error("Erreur d'allocation de la zone de mémoire en sortie.")] + MemoryAllocationError = 0xF801, + #[error("Un des paramètres obligatoires d'entrée est non alloué ou invalide.")] + InputParameterNotAllocatedOrInvalid = 0xF802, + #[error("Zone de mémoire spécifiée en entrée non valide. Vérifier que la zone allouée ne dépasse pas la taille maximale autorisée (MAXBLOC).")] + InputMemoryInvalid = 0xF803, + #[error("Le format de la zone de mémoire d'entrée ou le nombre de zones mémoire est incorrect.")] + InputMemoryFormatIncorrect = 0xF810, + #[error("Problème lors de l’initialisation du protocole. Erreur du Ressource Manager PC/SC. Vérifiez le lecteur.")] + PCSCProtocolInitError = 0xFF01, + #[error("Time-out au niveau protocolaire ou transmission déjà en cours avec le lecteur. Vérifiez le lecteur et l'insertion de la carte.")] + PCSCProtocolTimeout = 0xFF02, + #[error("Taille insuffisante allouée en entrée d’une fonction du Resource Manager.")] + PCSCProtocolInputMemoryTooSmall = 0xFF03, + #[error("Erreur de transmission du protocole. Vérifiez le lecteur et l'insertion de la carte.")] + PCSCProtocolTransmissionError = 0xFF04, + #[error("Lecteur absent ou indisponible.")] + PCSCReaderMissingOrUnavailable = 0xFF05, + #[error("Le nom du lecteur transmis est inconnu du Resource Manager PC/SC.")] + PCSCReaderUnknown = 0xFF06, + #[error("Erreur inconnue remontée par le Resource Manager PC/SC.")] + PCSCUnknownError = 0xFF07, + #[error("Erreur interne Resource Manager PC/SC.")] + PCSCInternalError = 0xFF08, + #[error("Ressource PC/SC déjà prise en exclusivité. Vérifiez qu'une autre application n'utilise pas le lecteur.")] + PCSCResourceAlreadyExclusive = 0xFF09, + #[error("Protocole incompatible avec la carte à puce. Vérifiez l'insertion de la carte et son état.")] + PCSCProtocolIncompatible = 0xFF0A, + #[error("Paramètre incorrect. Erreur interne à la librairie SSV.")] + PCSCIncorrectParameter = 0xFF0B, + #[error("Carte absente. Insérez une carte dans le lecteur.")] + PCSCCardMissing = 0xFF0C, + #[error("L'état de la carte a été modifié (RAZ ou mise hors tension). Vérifiez si la carte n'a pas été retirée ou si une autre application n'utilise pas la carte.")] + PCSCCardStateChanged = 0xFF0D, + #[error("Carte muette ou non supportée. Vérifiez l'insertion de la carte.")] + PCSCCardUnsupported = 0xFF0E, + #[error("Code porteur CPS non renseigné.")] + CPSPinMissing = 0xFF21, + #[error("Ressource PC/SC déjà prise en exclusivité. Vérifiez que le processus en cours n'utilise pas déjà le lecteur.")] + PCSCReaderAlreadyExclusiveForCurrentProcess = 0xFF24, + #[error("Plusieurs lecteurs ou cartes de même type identifiés lors de la détection automatique.")] + PCSCDuplicatedReadersOrCardsDetected = 0xFF29, + #[error("Problème de chargement de la librairie cryptographique ou erreur retournée par la librairie cryptographique.")] + CryptoLibraryError = 0xFF30, + #[error("Erreurs internes aux Services SESAM-Vitale. Vérifiez les traces.")] + #[num_enum(alternatives = [0xFFF1..=0xFFFF])] + SSVInternalError = 0xFFF0, + #[error("Le fichier `tablebin.smc` est inaccessible en lecture (inexistant ou pas de droits d'accès).")] + FileMissingTablebinMsc = 0xF610, // tablebin.smc + #[error("Le fichier `scripts.sms` est inaccessible en lecture (inexistant ou pas de droits d'accès).")] + FileMissingScriptsSms = 0xF611, // scripts.sms + #[error("Le fichier `tablebin.ssv` est inaccessible en lecture (inexistant ou pas de droits d'accès).")] + FileMissingTablebinSsv = 0xF612, // tablebin.ssv + #[error("Le fichier `script.ssv` est inaccessible en lecture (inexistant ou pas de droits d'accès).")] + FileMissingScriptSsv = 0xF613, // script.ssv + #[error("La version du fichier `tablebin.smc` est incompatible avec la bibliothèque des SSV.")] + FileVersionIncompatibleTablebinMsc = 0xF620, // tablebin.smc + #[error("La version du fichier `scripts.sms` est incompatible avec la bibliothèque des SSV.")] + FileVersionIncompatibleScriptsSms = 0xF621, // scripts.sms + #[error("La version du fichier `tablebin.ssv` est incompatible avec la bibliothèque des SSV.")] + FileVersionIncompatibleTablebinSsv = 0xF622, // tablebin.ssv + #[error("La version du fichier `script.ssv` est incompatible avec la bibliothèque des SSV.")] + FileVersionIncompatibleScriptSsv = 0xF623, // script.ssv + #[error("L'intégrité du fichier `tablebin.smc` est incorrecte.")] + FileIntegrityIncorrectTablebinMsc = 0xF630, // tablebin.smc + #[error("L'intégrité du fichier `scripts.sms` est incorrecte.")] + FileIntegrityIncorrectScriptsSms = 0xF631, // scripts.sms + #[error("L'intégrité du fichier `tablebin.ssv` est incorrecte.")] + FileIntegrityIncorrectTablebinSsv = 0xF632, // tablebin.ssv + #[error("L'intégrité du fichier `script.ssv` est incorrecte.")] + FileIntegrityIncorrectScriptSsv = 0xF633, // script.ssv + #[error("La structure interne du fichier `tablebin.smc` est invalide.")] + FileStructureInvalidTablebinMsc = 0xF640, // tablebin.smc + #[error("La structure interne du fichier `scripts.sms` est invalide.")] + FileStructureInvalidScriptsSms = 0xF641, // scripts.sms + #[error("La structure interne du fichier `tablebin.ssv` est invalide.")] + FileStructureInvalidTablebinSsv = 0xF642, // tablebin.ssv + #[error("La structure interne du fichier `script.ssv` est invalide.")] + FileStructureInvalidScriptSsv = 0xF643, // script.ssv + #[error("Le fichier `tablebin.smc` n'a pas pu être chargé en mémoire. Essayez de libérer de la mémoire.")] + FileLoadFailedTablebinMsc = 0xF650, // tablebin.smc + #[error("Le fichier `scripts.sms` n'a pas pu être chargé en mémoire. Essayez de libérer de la mémoire.")] + FileLoadFailedScriptsSms = 0xF651, // scripts.sms + #[error("Le fichier `tablebin.ssv` n'a pas pu être chargé en mémoire. Essayez de libérer de la mémoire.")] + FileLoadFailedTablebinSsv = 0xF652, // tablebin.ssv + #[error("Le fichier `script.ssv` n'a pas pu être chargé en mémoire. Essayez de libérer de la mémoire.")] + FileLoadFailedScriptSsv = 0xF653, // script.ssv + #[error("Le nom du fichier `tablebin.smc` est invalide.")] + FileNameInvalidTablebinMsc = 0xF660, // tablebin.smc + #[error("Le nom du fichier `scripts.sms` est invalide.")] + FileNameInvalidScriptsSms = 0xF661, // scripts.sms + #[error("Le nom du fichier `tablebin.ssv` est invalide.")] + FileNameInvalidTablebinSsv = 0xF662, // tablebin.ssv + #[error("Le nom du fichier `script.ssv` est invalide.")] + FileNameInvalidScriptSsv = 0xF663, // script.ssv + #[error("La fonction Initialiser Librairie est déjà appelée.")] + FunctionInitLib2AlreadyCalled = 0xF670, // Warning + #[error("Le fichier SESAM.INI est inaccessible en lecture (fichier ou droit d’accès manquant) ou ne contient pas le chemin des tables binaires des SSV.")] + SesamIniMissingFileOrTablebinPath = 0xF680, + #[error("Le chemin du répertoire de travail est absent du fichier SESAM.INI.")] + SesamIniMissingWorkDir = 0xF6F1, + #[error("Les fichiers d’extension adm ne sont pas accessibles en écriture.")] + AdmFilesNotWritable = 0xF6F2, // Warning + #[error("Aucune version de FSV du socle technique trouvé. Vérifier que la version du fichier script.sms est bonne.")] + NoFsvVersionFound = 0xF6F4, + #[error("Librairie SGD absente ou incomplète.")] + LibraryMissingOrIncompleteSGD = 0xF6F5, + #[error("Librairie SMC absente ou incomplète.")] + LibraryMissingOrIncompleteSMC = 0xF6F6, + #[error("Librairie SJS absente ou incomplète.")] + LibraryMissingOrIncompleteSJS = 0xF6F7, + #[error("Librairie SMS absente ou incomplète.")] + LibraryMissingOrIncompleteSMS = 0xF6F8, + #[error("Section MGC absente / clé RepertoireConfigTrace absente / fichier log4crc.xml non trouvé à l’emplacement indiqué par la clé RepertoireConfigTrace du fichier SESAM.INI.")] + SesamIniTracingConfigMissing = 0xFF22, // Warning + #[error("Interface Full PC/SC : problème de chargement de la librairie cryptographique ou erreur retournée par la librairie cryptographique.")] + PCSCInterfaceCryptoLibraryError = 0xFF25, + #[error("Valorisation incorrecte des paramètres de gestion de l'accès aux ressources dans le SESAM.INI. Vérifier les valeurs des clés tempoexclusivite, repetitionexclusivite, tempoexclusivitePCSC, repetitionexclusivitePCSC")] + SesamIniResourceAccessParamsIncorrect = 0xFF2A, + #[num_enum(catch_all)] + #[error("Erreur inattendue de la librairie SSV (code d'erreur: {0}).")] + Unexpected(u16), +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_code_ranges() { + let error_code = 0xFFF1; + let error = SSVErrorCodes::from(error_code); + assert_eq!(error, SSVErrorCodes::SSVInternalError); + + let error_code = 0xFFF8; + let error = SSVErrorCodes::from(error_code); + assert_eq!(error, SSVErrorCodes::SSVInternalError); + } + + #[test] + fn test_catch_all() { + let error_code = 0xFBFF; // Not a valid error code + let error = SSVErrorCodes::from(error_code); + assert_eq!(error, SSVErrorCodes::Unexpected(0xFBFF)); + } +} + \ No newline at end of file diff --git a/crates/fsv/src/ssv/mod.rs b/crates/fsv/src/ssv/mod.rs new file mode 100644 index 0000000..d611c4c --- /dev/null +++ b/crates/fsv/src/ssv/mod.rs @@ -0,0 +1,208 @@ +use std::{ffi::CString, ptr}; + +use thiserror::Error; + +use fsv_sys::{ + get_library_path, + Error as FsvError, + SSVLibrary, + SSVLibraryCommon, + SupportedFsvVersion, + V1_40_13, + V1_40_14 +}; + +mod errors_ssv; +use errors_ssv::SSVErrorCodes; + +#[derive(Error, Debug)] +pub enum Error { + #[error(transparent)] + FSVSysLibrary(#[from] FsvError), + #[error(transparent)] + SSVError(#[from] SSVErrorCodes), +} + +/// Enum to hold the different versions of the SSV library +enum SsvLibraryVersion { + V1_40_13(SSVLibrary), + V1_40_14(SSVLibrary), +} + +/// Struct to hold the SSV library and access its functions +pub struct SSV { + library: SsvLibraryVersion, +} + +impl SSV { + fn new(version: SupportedFsvVersion) -> Result { + let library = match version { + SupportedFsvVersion::V1_40_13 => { + let lib_path = get_library_path(&version); + let library = SSVLibrary::::new(&lib_path)?; + SsvLibraryVersion::V1_40_13(library) + }, + SupportedFsvVersion::V1_40_14 => { + let lib_path = get_library_path(&version); + let library = SSVLibrary::::new(&lib_path)?; + SsvLibraryVersion::V1_40_14(library) + }, + }; + Ok(Self { + library, + }) + } + + /// # Initialize the SSV library + /// Implement: SSV_InitLIB2 + pub fn init_library(&self, sesam_ini_path: &str) -> Result<(), Error> { + let sesam_ini_path = CString::new(sesam_ini_path).expect("CString::new failed"); + let result = match &self.library { + SsvLibraryVersion::V1_40_13(library) => { + unsafe { library.ssv_init_lib2(sesam_ini_path.as_ptr()) }? + }, + SsvLibraryVersion::V1_40_14(library) => { + unsafe { library.ssv_init_lib2(sesam_ini_path.as_ptr()) }? + }, + }; + if result != 0 { + let error = SSVErrorCodes::from(result); + return Err(Error::SSVError(error)); + } + Ok(()) + } + + /// # Read the CPS card + /// Implement: SSV_LireCartePS + pub fn read_professional_card(&self, pin_code: &str) -> Result<(), Error> { + let pcsc_reader_name = "Gemalto PC Twin Reader (645D94C3) 00 00"; + + let pin_code = CString::new(pin_code).expect("CString::new failed"); + let pcsc_reader_name = CString::new(pcsc_reader_name).expect("CString::new failed"); + let mut out_buffer_ptr: *mut libc::c_void = ptr::null_mut(); + let mut out_buffer_size: libc::size_t = 0; + + let result = match &self.library { + SsvLibraryVersion::V1_40_13(library) => { + unsafe { library.ssv_lire_carte_ps( + pcsc_reader_name.as_ptr(), + pcsc_reader_name.as_ptr(), + pin_code.as_ptr(), + &mut out_buffer_ptr, + &mut out_buffer_size) + }? + }, + SsvLibraryVersion::V1_40_14(library) => { + unsafe { library.ssv_lire_carte_ps( + pcsc_reader_name.as_ptr(), + pcsc_reader_name.as_ptr(), + pin_code.as_ptr(), + &mut out_buffer_ptr, + &mut out_buffer_size) + }? + }, + }; + + if result != 0 { + // Free memory + unsafe { libc::free(out_buffer_ptr) }; + let error = SSVErrorCodes::from(result); + return Err(Error::SSVError(error)); + } + // Print 10 bytes of the buffer + let buffer = unsafe { std::slice::from_raw_parts(out_buffer_ptr as *const u8, 10) }; + println!("{:?}", buffer); + // Free memory + unsafe { libc::free(out_buffer_ptr) }; + Ok(()) + } + + /// # Get the configuration of the SSV library + /// Implement: SSV_LireConfig + pub fn get_config(&self) -> Result<(), Error> { + let mut buffer_ptr: *mut libc::c_void = ptr::null_mut(); + let mut size: libc::size_t = 0; + + let result = match &self.library { + SsvLibraryVersion::V1_40_13(library) => { + unsafe { library.ssv_lire_config(&mut buffer_ptr, &mut size) }? + }, + SsvLibraryVersion::V1_40_14(library) => { + unsafe { library.ssv_lire_config(&mut buffer_ptr, &mut size) }? + }, + }; + + if result != 0 { + // Free memory + unsafe { libc::free(buffer_ptr) }; + let error = SSVErrorCodes::from(result); + return Err(Error::SSVError(error)); + } + // Print 10 bytes of the buffer + let buffer = unsafe { std::slice::from_raw_parts(buffer_ptr as *const u8, 10) }; + println!("{:?}", buffer); + // Free memory + unsafe { libc::free(buffer_ptr) }; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use std::env; + + use utils::config::load_config; + use anyhow::Result; + + use super::*; + + mod setup { + use super::*; + + pub fn init() -> Result { + load_config().unwrap(); + let sesam_ini_path = env::var("SESAM_INI_PATH").expect("SESAM_INI_PATH must be set"); + let lib = SSV::new(SupportedFsvVersion::V1_40_13)?; + lib.init_library(&sesam_ini_path)?; + Ok(lib) + } + } + + #[test] + fn test_init_library() -> Result<()> { + setup::init()?; + Ok(()) + } + + #[test] + fn test_read_professional_card_good_pin() -> Result<()> { + let lib = setup::init()?; + let pin_code = "1234"; + lib.read_professional_card(pin_code)?; + Ok(()) + } + + #[ignore] + #[test] + fn test_read_professional_card_bad_pin() -> Result<()> { + let lib = setup::init()?; + let pin_code = "0000"; + // Should return an error + let err = lib.read_professional_card(pin_code).unwrap_err(); + assert_eq!(err.to_string(), "Le code porteur présenté est erroné."); + match err { + Error::SSVError(err) => { + assert_eq!(err as SSVErrorCodes, SSVErrorCodes::CPSPinWrong); + }, + _ => panic!("Error type is not SSVError"), + } + Ok(()) + } + + #[test] + fn test_get_config() -> Result<()> { + let lib = setup::init()?; + lib.get_config()?; + Ok(()) + } +}