- A-01/A-02/A-04: archive_pending_jobs je Vorgang (ArchivePendingQueue), Depublikationsstand am Auftrag festgehalten, Nachholer ohne Schlüssel FAILURE; archive_pending_at nur noch Sammelmarker - O07: Direktweg und Nachholer beanspruchen den Auftrag unter Zeilensperre - O08: fehlende Snapshot-Datei → Verweis auf den image_removed-Eintrag oder offener Auftrag, nie ein still unvollständiger Nachweis - A-03/A-06: Sweep verschiebt in die Quarantäne, Pause beim Restore, archive:reregister; Restore-Abgleich löscht Anwendungskopien erneut - O04/O05: Restore ordnet über den Payload-Pfad zu; unsignierte Exporte nur mit --accept-unsigned, reregister bricht bei ungültigem/fehlendem Export ab - A-05, A-07 bis A-15: Altbestand als möglicherweise veröffentlicht, Hold- Historie, Zweck per POST, Nachweise gelöschter Bilder nur mit compliance:manage, Anhänge auf ihrer Disk, Schlüssel-ID und ARCHIVE_PREVIOUS_KEYS, HMAC per HKDF, archive:verify-export - 5.1 d: Depublikation im Meldeverfahren trägt legal_request_id PressReleaseService.php enthält zusätzlich K-01 (Prüfung im Publish-Lock), N19 (archive() gibt Kopien frei) und N09 (markImagesPublished nur für auslieferbare Bilder), weil die Datei ungeteilt committet ist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
54 lines
1.7 KiB
PHP
54 lines
1.7 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Services\Archive\EvidenceArchive;
|
|
use Illuminate\Console\Command;
|
|
use ZipArchive;
|
|
|
|
/**
|
|
* Beweisarchiv (Umsetzungsaudit 29.09.2026, A-15): Signatur eines
|
|
* Anwaltsexports prüfen — neue Exporte (abgeleiteter Schlüssel) und
|
|
* Exporte vor der Umstellung (Archivschlüssel) gegen alle bekannten
|
|
* Schlüssel, dazu den Hash von payload.zip.
|
|
*/
|
|
class ArchiveVerifyExport extends Command
|
|
{
|
|
protected $signature = 'archive:verify-export {path : Pfad zur exportierten ZIP-Datei}';
|
|
|
|
protected $description = 'Prüft Hash und HMAC-Signatur eines Beweisarchiv-Exports';
|
|
|
|
public function handle(EvidenceArchive $archive): int
|
|
{
|
|
$zip = new ZipArchive;
|
|
|
|
if ($zip->open((string) $this->argument('path')) !== true) {
|
|
$this->error('Datei ist kein lesbares ZIP.');
|
|
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$payload = $zip->getFromName('payload.zip');
|
|
$signature = $zip->getFromName('signature.txt');
|
|
$metadata = json_decode((string) $zip->getFromName('metadata.json'), true);
|
|
$zip->close();
|
|
|
|
$hash = is_array($metadata) ? (string) ($metadata['payload_sha256'] ?? '') : '';
|
|
|
|
if (! is_string($payload) || ! is_string($signature) || $hash === '' || ! hash_equals($hash, hash('sha256', $payload))) {
|
|
$this->error('Export ungültig: payload.zip passt nicht zum Hash in metadata.json.');
|
|
|
|
return self::FAILURE;
|
|
}
|
|
|
|
if (! $archive->verifyExportSignature($hash, $signature)) {
|
|
$this->error('Signatur ungültig oder mit keinem bekannten Schlüssel prüfbar.');
|
|
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$this->info('Export gültig: Hash und Signatur stimmen.');
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
}
|