diff --git a/htdocs/blockedlog/admin/blockedlog_archives.php b/htdocs/blockedlog/admin/blockedlog_archives.php index 50796433751..9f56e881ce5 100644 --- a/htdocs/blockedlog/admin/blockedlog_archives.php +++ b/htdocs/blockedlog/admin/blockedlog_archives.php @@ -383,7 +383,12 @@ if ($action == 'export' && $user->hasRight('blockedlog', 'read')) { // read is */ if ($checksignature) { - $statusofrecord = $langs->transnoentitiesnoconv('StatusValid'); + if (!empty($block_static->note)) { // signature ok but end of chain deletion detected + $statusofrecord = 'KO'; + $statusofrecordnote = $block_static->note; + } else { + $statusofrecord = $langs->transnoentitiesnoconv('StatusValid'); + } if ($loweridinerror > 0) { $statusofrecordnote = 'ValidButFoundAPreviousKO'; } else { diff --git a/htdocs/blockedlog/class/blockedlog.class.php b/htdocs/blockedlog/class/blockedlog.class.php index fb5658185b9..bda70cdb87d 100644 --- a/htdocs/blockedlog/class/blockedlog.class.php +++ b/htdocs/blockedlog/class/blockedlog.class.php @@ -1323,11 +1323,15 @@ class BlockedLog $this->object_version = DOL_VERSION; - // The object_format define the formatting rules into buildKeyForSignature and buildFirstPartOfKeyForSignature and buildFinalSignatureHash - $this->object_format = 'V2'; // TODO Switch to V2 for every version + // The object_format defines the formatting rules and syntax into + // buildKeyForSignature and buildFirstPartOfKeyForSignature and buildFinalSignatureHash + // This may vary when the Immutable Log module version is modified, but only if algorithm has changed. + $this->object_format = 'V2'; + /* if (defined('CERTIF_LNE') && in_array((int) constant('CERTIF_LNE'), array(1, 2))) { $this->object_format = 'V2'; } + */ $tz = 'gmt'; // if (empty($this->object_format) || $this->object_format == 'V1') { @@ -1461,7 +1465,7 @@ class BlockedLog throw new Exception("Cannot write into the blockedlog .end file ".$lockfile.' to update it. Is the file writable by the running user and not open by another process? Transaction aborted.'); } - // Check and update the .end flag file. + // Check the .end flag file. $headstring = ''; $remoteobfuscationkey = ''; if (preg_match('/^dolcrypt/', $line)) { // Old method (does not happen after migration) @@ -1498,7 +1502,7 @@ class BlockedLog throw new Exception("Failed to decode the content of the head file ".$lockfile." with the obfuscation key, so we can't record the head file so we abort the transaction."); } - // If a different signature has been forced to generate an error. + // If a note has been added to track an anomaly (signature is also different in this case). if ($finalsignature != $this->signature) { // For debug info (we can clean this field later) if (getDolGlobalString('BLOCKEDLOG_ADD_DEBUG_INFO')) { @@ -1519,12 +1523,16 @@ class BlockedLog // Lock acquired, we can now write the new .end file $stringtowrite = 'BLOCKEDLOGHEAD '.$this->id." ".dol_print_date($this->date_creation, 'dayhourrfc', 'gmt')." ".(string) $finalsignature; - $remoteobfuscationkey = $this->getObfuscationKey(); - if (empty($remoteobfuscationkey)) { - throw new Exception("Failed to get the remote obfuscation key. We can't record the end of chain flag file so we abort the transaction."); - } - $stringtowriteencoded = dolEncrypt($stringtowrite, $remoteobfuscationkey, '', '', 'dolobfuscationv1-'.$mysoc->idprof1); + if (isALNERunningVersion(1) && $mysoc->country_code == 'FR') { + $remoteobfuscationkey = $this->getObfuscationKey(); + if (empty($remoteobfuscationkey)) { + throw new Exception("Failed to get the remote obfuscation key. We can't record the end of chain flag file so we abort the transaction."); + } + $stringtowriteencoded = dolEncrypt($stringtowrite, $remoteobfuscationkey, '', '', 'dolobfuscationv1-'.$mysoc->idprof1); + } else { + $stringtowriteencoded = dolEncrypt($stringtowrite, '', '', '', 'dolcrypt'); + } // Update or create the .end file. $lockhandle = fopen($lockfile, 'w+'); diff --git a/htdocs/blockedlog/lib/blockedlog.lib.php b/htdocs/blockedlog/lib/blockedlog.lib.php index e6a413b6ec4..7477a8e7c3e 100644 --- a/htdocs/blockedlog/lib/blockedlog.lib.php +++ b/htdocs/blockedlog/lib/blockedlog.lib.php @@ -646,3 +646,100 @@ function pdfWriteBlockedLogSignature(&$pdf, $outputlangs, $page_height, $object, } } } + + +/** + * Migrate an old database to add the .end file. + * + * @return int Return -1 if KO, 1 if OK + */ +function migrate_blockedlog_add_end_file() +{ + global $conf, $db, $langs,$mysoc; + + include_once DOL_DOCUMENT_ROOT.'/blockedlog/class/blockedlog.class.php'; + + $blocklog_static = new BlockedLog($db); + + // Loop on all entities found in llx_blockedlog + $listofentities = array(); + $sql = "SELECT DISTINCT entity FROM ".MAIN_DB_PREFIX."blockedlog"; + $resql = $db->query($sql); + if ($resql) { + while ($obj = $db->fetch_object($resql)) { + $listofentities[] = $obj->entity; + } + } else { + print ''; + print ''.$langs->trans('InitEndFlagFile').":\n"; + print 'Error: Failed to get list of entities in blockedlog table'; + print ''; + return -1; + } + + foreach ($listofentities as $entity) { + print ''; + print ''.$langs->trans('InitEndFlagFile')." (entity = ".$entity."):\n"; + + // Load the data of company (country, ...). Erase current one so this function can be used by migration script only. + $conf->setEntityValues($db, $entity); + $mysoc = new Societe($db); + $mysoc->setMysoc($conf); + + $maxid = 0; + $sql = "SELECT MAX(rowid) as maxid FROM ".MAIN_DB_PREFIX."blockedlog WHERE entity = ".((int) $entity); + $resql = $db->query($sql); + if ($resql) { + $obj = $db->fetch_object($resql); + if ($obj) { + $maxid = $obj->maxid; + } + } else { + print 'Error: Failed to get max id of blockedlog table'; + print ''; + return -1; + } + + $blocklog_static->fetch($maxid); + + $lockfile = $blocklog_static->getEndOfChainFlagFile(); + + // Lock acquired, we can now write the new .end file + $stringtowrite = 'BLOCKEDLOGHEAD '.$blocklog_static->id." ".dol_print_date($blocklog_static->date_creation, 'dayhourrfc', 'gmt')." ".(string) $blocklog_static->signature; + + if (isALNERunningVersion(1) && $mysoc->country_code == 'FR') { + $remoteobfuscationkey = $blocklog_static->getObfuscationKey(); + if (empty($remoteobfuscationkey)) { + print "Error: Failed to get the remote obfuscation key. We can't record the end of chain flag file so we abort the transaction."; + print ''; + return -1; + } + + $stringtowriteencoded = dolEncrypt($stringtowrite, $remoteobfuscationkey, '', '', 'dolobfuscationv1-'.$mysoc->idprof1); + } else { + $stringtowriteencoded = dolEncrypt($stringtowrite, '', '', '', 'dolcrypt'); + } + + // Update or create the .end file. + $lockhandle = fopen($lockfile, 'w+'); + if ($lockhandle) { + if (fwrite($lockhandle, $stringtowriteencoded."\n") === false) { + print "Cannot write to the blockedlog .end file ".$lockfile; + print ''; + return -1; + } + + fclose($lockhandle); // Remove the lock + dolChmod($lockfile); + } else { + print "Cannot open for writing the blockedlog .end file ".$lockfile; + print ''; + return -1; + } + + print $langs->trans("Done"); + print ''; + } + + return 1; +} diff --git a/htdocs/core/lib/securitycore.lib.php b/htdocs/core/lib/securitycore.lib.php index 9779dead2a0..cedd07bbff2 100644 --- a/htdocs/core/lib/securitycore.lib.php +++ b/htdocs/core/lib/securitycore.lib.php @@ -103,7 +103,7 @@ function dolEncrypt($chain, $key = '', $ciphering = '', $forceseed = '', $obfusc } if (empty($forceseed)) { $ivseed = dolGetRandomBytes($ivlen); - } else { + } else { // This case has been abandoned $ivseed = dol_substr(md5($forceseed), 0, $ivlen, 'ascii', 1); } diff --git a/htdocs/install/upgrade2.php b/htdocs/install/upgrade2.php index 800d9922d41..07105c51909 100644 --- a/htdocs/install/upgrade2.php +++ b/htdocs/install/upgrade2.php @@ -81,6 +81,7 @@ require_once $dolibarr_main_document_root.'/core/class/menubase.class.php'; require_once $dolibarr_main_document_root.'/core/lib/admin.lib.php'; require_once $dolibarr_main_document_root.'/core/lib/files.lib.php'; require_once $dolibarr_main_document_root.'/user/class/user.class.php'; +require_once $dolibarr_main_document_root.'/blockedlog/lib/blockedlog.lib.php'; global $langs; @@ -681,6 +682,8 @@ if (!GETPOST('action', 'aZ09') || preg_match('/upgrade/i', GETPOST('action', 'aZ dol_syslog("Run migrate_... versionto is between ".json_encode($afterversionarray)." and ".json_encode($beforeversionarray)); migrate_rename_directories($db, $langs, $conf, '/banque', '/bank'); + + migrate_blockedlog_add_end_file(); } } diff --git a/htdocs/langs/en_US/admin.lang b/htdocs/langs/en_US/admin.lang index c5d75a4c939..9417ad2b253 100644 --- a/htdocs/langs/en_US/admin.lang +++ b/htdocs/langs/en_US/admin.lang @@ -2911,3 +2911,4 @@ McpServer=MCP Server NoMcpToolsDiscovered=No MCP tools discovered. Please ensure the AI module is correctly configured. ViewOnly=View only ToolActionType=Action Type +InitEndFlagFile=Init the .end file to protect end of the blockedlog chain diff --git a/htdocs/langs/en_US/blockedlog.lang b/htdocs/langs/en_US/blockedlog.lang index c1059618194..df4f47d1333 100644 --- a/htdocs/langs/en_US/blockedlog.lang +++ b/htdocs/langs/en_US/blockedlog.lang @@ -20,12 +20,12 @@ FingerprintsDesc2=If you really need to purge this table because you used your a CompanyInitialKey=Company initial key (hash of genesis block) BrowseBlockedLog=Immutable logs ArchiveLogs=Archives of immutable logs -ShowAllFingerPrintsMightBeTooLong=Show all archived unlaterable logs (might be long) +ShowAllFingerPrintsMightBeTooLong=Show all immutable logs (might be long) ShowAllFingerPrintsErrorsMightBeTooLong=Show all non-valid immutable logs (might be long) DownloadBlockChain=Download blockchain -KoCheckFingerprintValidity=Archived log entry is not valid. It means someone (a hacker?) has modified some data of this record after it was recorded, OR has erased the previous record (check that the line with previous # exists) OR has modified the checksum of the previous record. -OkCheckFingerprintValidity=Archived log record is valid. The data on this line was not modified and the entry follows the previous one. -OkCheckFingerprintValidityButChainIsKo=Archived log seems valid compared to previous one but the chain was corrupted previously. +KoCheckFingerprintValidity=Immutable log entry is not valid. It means someone (a hacker?) has modified some data of this record after it was recorded, OR has erased the previous record (check that the line with previous # exists) OR has modified the checksum of the previous record. +OkCheckFingerprintValidity=Immutable log record is valid. The data on this line was not modified and the entry follows the previous one. +OkCheckFingerprintValidityButChainIsKo=Immutable log seems valid compared to previous one but the chain was corrupted previously. AddedByAuthority=Stored into remote authority NotAddedByAuthorityYet=Not yet stored into remote authority BlockedLogBillDownload=Customer invoice download