copia-forense 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- copia_forense-1.0.0/PKG-INFO +58 -0
- copia_forense-1.0.0/README.md +47 -0
- copia_forense-1.0.0/copia_forense.egg-info/PKG-INFO +58 -0
- copia_forense-1.0.0/copia_forense.egg-info/SOURCES.txt +12 -0
- copia_forense-1.0.0/copia_forense.egg-info/dependency_links.txt +1 -0
- copia_forense-1.0.0/copia_forense.egg-info/entry_points.txt +2 -0
- copia_forense-1.0.0/copia_forense.egg-info/requires.txt +1 -0
- copia_forense-1.0.0/copia_forense.egg-info/top_level.txt +4 -0
- copia_forense-1.0.0/forensic_copier.py +25 -0
- copia_forense-1.0.0/forensic_hasher.py +27 -0
- copia_forense-1.0.0/logger_pdf.py +61 -0
- copia_forense-1.0.0/main.py +224 -0
- copia_forense-1.0.0/pyproject.toml +26 -0
- copia_forense-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: copia-forense
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Forensic Logical Acquisition Tool with SHA-256 validation.
|
|
5
|
+
Author: Mathias Nesci
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: Operating System :: OS Independent
|
|
8
|
+
Requires-Python: >=3.7
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: fpdf2
|
|
11
|
+
|
|
12
|
+
# Forensic Logical Acquisition Tool
|
|
13
|
+
|
|
14
|
+
This project is a portable application developed in Python for the logical acquisition of data and files for forensic purposes. It is designed to ensure the chain of custody, data immutability, and the repeatability of the operation, solving common issues that occur during mass copying across heterogeneous file systems.
|
|
15
|
+
|
|
16
|
+
## 🚀 Key Features
|
|
17
|
+
|
|
18
|
+
- **Cryptographic Integrity:** Automatic pre- and post-copy **SHA-256** hash calculation. The 1:1 validation ensures absolute correspondence between the original evidence and the copy.
|
|
19
|
+
- **MAC Times Extraction:** Preservation and reporting of the original temporal metadata (Modification, Access, and Creation Dates) via `os.stat`, bypassing the limitations of destination file systems (e.g., FAT32).
|
|
20
|
+
- **Sandboxing & Exception Handling:** The copy engine intercepts Operating System locks (files in use) and `PermissionError`s, recording them in the report without interrupting the operational flow (No Crash).
|
|
21
|
+
- **MAX_PATH Bypass (Windows):** Dynamic implementation of the `\\?\` prefix to bypass the historical 260-character path limit on Windows.
|
|
22
|
+
- **Anti-Inception Security:** Algorithmic check based on `os.path.commonpath` to prevent selecting a destination nested within the source (prevents recursion loops).
|
|
23
|
+
- **Pre-flight Check:** Calculation of required disk space (with a 10% buffer) before interacting with the original media.
|
|
24
|
+
- **Dual Reporting and Signature:** Automatic generation of a *Human-Readable* report (PDF) and a *Machine-Readable* one (JSON). The tool concludes the operation by generating a SHA-256 signature of the reports themselves to certify their immutability.
|
|
25
|
+
|
|
26
|
+
## 📦 Standalone Executables
|
|
27
|
+
The binary files attached to the GitHub Releases (automatically generated via GitHub Actions) do not require Python or other dependencies to be installed. Download the appropriate version for your operating system and CPU architecture:
|
|
28
|
+
- **Windows (Intel/AMD/ARM):** `CopiaForense-windows-x64.zip`
|
|
29
|
+
- **macOS (Intel):** `CopiaForense-macos-intel.zip`
|
|
30
|
+
- **macOS (Apple Silicon):** `CopiaForense-macos-silicon.zip`
|
|
31
|
+
- **Linux (Intel/AMD):** `CopiaForense-linux-x64.zip`
|
|
32
|
+
- **Linux (ARM64):** `CopiaForense-linux-arm64.zip`
|
|
33
|
+
|
|
34
|
+
## ⚠️ Running the Executables (macOS & Windows)
|
|
35
|
+
|
|
36
|
+
Because this tool is an independent open-source project and not digitally signed by a paid commercial certificate, your Operating System might initially block its execution. This is normal and expected behavior.
|
|
37
|
+
|
|
38
|
+
**For macOS Users (Gatekeeper):**
|
|
39
|
+
If you see an "Apple could not verify..." warning:
|
|
40
|
+
1. Go to **System Settings** -> **Privacy & Security**.
|
|
41
|
+
2. Scroll down to the security section and click **Open Anyway** next to the blocked app notification.
|
|
42
|
+
3. Alternatively, in Finder, **Right-Click** the executable and select **Open**.
|
|
43
|
+
|
|
44
|
+
**For Windows Users (SmartScreen):**
|
|
45
|
+
If you see a "Windows protected your PC" blue screen warning:
|
|
46
|
+
1. Click on **More info**.
|
|
47
|
+
2. Click the **Run anyway** button that appears at the bottom.
|
|
48
|
+
|
|
49
|
+
## 🛠️ Development Requirements
|
|
50
|
+
|
|
51
|
+
The project mainly uses modules from the Python Standard Library (`os`, `shutil`, `hashlib`, `tkinter`).
|
|
52
|
+
The only external dependency required for generating the PDF report is:
|
|
53
|
+
- `fpdf2`
|
|
54
|
+
|
|
55
|
+
To install the dependencies in a development environment:
|
|
56
|
+
```bash
|
|
57
|
+
pip install -r requirements.txt
|
|
58
|
+
```
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Forensic Logical Acquisition Tool
|
|
2
|
+
|
|
3
|
+
This project is a portable application developed in Python for the logical acquisition of data and files for forensic purposes. It is designed to ensure the chain of custody, data immutability, and the repeatability of the operation, solving common issues that occur during mass copying across heterogeneous file systems.
|
|
4
|
+
|
|
5
|
+
## 🚀 Key Features
|
|
6
|
+
|
|
7
|
+
- **Cryptographic Integrity:** Automatic pre- and post-copy **SHA-256** hash calculation. The 1:1 validation ensures absolute correspondence between the original evidence and the copy.
|
|
8
|
+
- **MAC Times Extraction:** Preservation and reporting of the original temporal metadata (Modification, Access, and Creation Dates) via `os.stat`, bypassing the limitations of destination file systems (e.g., FAT32).
|
|
9
|
+
- **Sandboxing & Exception Handling:** The copy engine intercepts Operating System locks (files in use) and `PermissionError`s, recording them in the report without interrupting the operational flow (No Crash).
|
|
10
|
+
- **MAX_PATH Bypass (Windows):** Dynamic implementation of the `\\?\` prefix to bypass the historical 260-character path limit on Windows.
|
|
11
|
+
- **Anti-Inception Security:** Algorithmic check based on `os.path.commonpath` to prevent selecting a destination nested within the source (prevents recursion loops).
|
|
12
|
+
- **Pre-flight Check:** Calculation of required disk space (with a 10% buffer) before interacting with the original media.
|
|
13
|
+
- **Dual Reporting and Signature:** Automatic generation of a *Human-Readable* report (PDF) and a *Machine-Readable* one (JSON). The tool concludes the operation by generating a SHA-256 signature of the reports themselves to certify their immutability.
|
|
14
|
+
|
|
15
|
+
## 📦 Standalone Executables
|
|
16
|
+
The binary files attached to the GitHub Releases (automatically generated via GitHub Actions) do not require Python or other dependencies to be installed. Download the appropriate version for your operating system and CPU architecture:
|
|
17
|
+
- **Windows (Intel/AMD/ARM):** `CopiaForense-windows-x64.zip`
|
|
18
|
+
- **macOS (Intel):** `CopiaForense-macos-intel.zip`
|
|
19
|
+
- **macOS (Apple Silicon):** `CopiaForense-macos-silicon.zip`
|
|
20
|
+
- **Linux (Intel/AMD):** `CopiaForense-linux-x64.zip`
|
|
21
|
+
- **Linux (ARM64):** `CopiaForense-linux-arm64.zip`
|
|
22
|
+
|
|
23
|
+
## ⚠️ Running the Executables (macOS & Windows)
|
|
24
|
+
|
|
25
|
+
Because this tool is an independent open-source project and not digitally signed by a paid commercial certificate, your Operating System might initially block its execution. This is normal and expected behavior.
|
|
26
|
+
|
|
27
|
+
**For macOS Users (Gatekeeper):**
|
|
28
|
+
If you see an "Apple could not verify..." warning:
|
|
29
|
+
1. Go to **System Settings** -> **Privacy & Security**.
|
|
30
|
+
2. Scroll down to the security section and click **Open Anyway** next to the blocked app notification.
|
|
31
|
+
3. Alternatively, in Finder, **Right-Click** the executable and select **Open**.
|
|
32
|
+
|
|
33
|
+
**For Windows Users (SmartScreen):**
|
|
34
|
+
If you see a "Windows protected your PC" blue screen warning:
|
|
35
|
+
1. Click on **More info**.
|
|
36
|
+
2. Click the **Run anyway** button that appears at the bottom.
|
|
37
|
+
|
|
38
|
+
## 🛠️ Development Requirements
|
|
39
|
+
|
|
40
|
+
The project mainly uses modules from the Python Standard Library (`os`, `shutil`, `hashlib`, `tkinter`).
|
|
41
|
+
The only external dependency required for generating the PDF report is:
|
|
42
|
+
- `fpdf2`
|
|
43
|
+
|
|
44
|
+
To install the dependencies in a development environment:
|
|
45
|
+
```bash
|
|
46
|
+
pip install -r requirements.txt
|
|
47
|
+
```
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: copia-forense
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Forensic Logical Acquisition Tool with SHA-256 validation.
|
|
5
|
+
Author: Mathias Nesci
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: Operating System :: OS Independent
|
|
8
|
+
Requires-Python: >=3.7
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: fpdf2
|
|
11
|
+
|
|
12
|
+
# Forensic Logical Acquisition Tool
|
|
13
|
+
|
|
14
|
+
This project is a portable application developed in Python for the logical acquisition of data and files for forensic purposes. It is designed to ensure the chain of custody, data immutability, and the repeatability of the operation, solving common issues that occur during mass copying across heterogeneous file systems.
|
|
15
|
+
|
|
16
|
+
## 🚀 Key Features
|
|
17
|
+
|
|
18
|
+
- **Cryptographic Integrity:** Automatic pre- and post-copy **SHA-256** hash calculation. The 1:1 validation ensures absolute correspondence between the original evidence and the copy.
|
|
19
|
+
- **MAC Times Extraction:** Preservation and reporting of the original temporal metadata (Modification, Access, and Creation Dates) via `os.stat`, bypassing the limitations of destination file systems (e.g., FAT32).
|
|
20
|
+
- **Sandboxing & Exception Handling:** The copy engine intercepts Operating System locks (files in use) and `PermissionError`s, recording them in the report without interrupting the operational flow (No Crash).
|
|
21
|
+
- **MAX_PATH Bypass (Windows):** Dynamic implementation of the `\\?\` prefix to bypass the historical 260-character path limit on Windows.
|
|
22
|
+
- **Anti-Inception Security:** Algorithmic check based on `os.path.commonpath` to prevent selecting a destination nested within the source (prevents recursion loops).
|
|
23
|
+
- **Pre-flight Check:** Calculation of required disk space (with a 10% buffer) before interacting with the original media.
|
|
24
|
+
- **Dual Reporting and Signature:** Automatic generation of a *Human-Readable* report (PDF) and a *Machine-Readable* one (JSON). The tool concludes the operation by generating a SHA-256 signature of the reports themselves to certify their immutability.
|
|
25
|
+
|
|
26
|
+
## 📦 Standalone Executables
|
|
27
|
+
The binary files attached to the GitHub Releases (automatically generated via GitHub Actions) do not require Python or other dependencies to be installed. Download the appropriate version for your operating system and CPU architecture:
|
|
28
|
+
- **Windows (Intel/AMD/ARM):** `CopiaForense-windows-x64.zip`
|
|
29
|
+
- **macOS (Intel):** `CopiaForense-macos-intel.zip`
|
|
30
|
+
- **macOS (Apple Silicon):** `CopiaForense-macos-silicon.zip`
|
|
31
|
+
- **Linux (Intel/AMD):** `CopiaForense-linux-x64.zip`
|
|
32
|
+
- **Linux (ARM64):** `CopiaForense-linux-arm64.zip`
|
|
33
|
+
|
|
34
|
+
## ⚠️ Running the Executables (macOS & Windows)
|
|
35
|
+
|
|
36
|
+
Because this tool is an independent open-source project and not digitally signed by a paid commercial certificate, your Operating System might initially block its execution. This is normal and expected behavior.
|
|
37
|
+
|
|
38
|
+
**For macOS Users (Gatekeeper):**
|
|
39
|
+
If you see an "Apple could not verify..." warning:
|
|
40
|
+
1. Go to **System Settings** -> **Privacy & Security**.
|
|
41
|
+
2. Scroll down to the security section and click **Open Anyway** next to the blocked app notification.
|
|
42
|
+
3. Alternatively, in Finder, **Right-Click** the executable and select **Open**.
|
|
43
|
+
|
|
44
|
+
**For Windows Users (SmartScreen):**
|
|
45
|
+
If you see a "Windows protected your PC" blue screen warning:
|
|
46
|
+
1. Click on **More info**.
|
|
47
|
+
2. Click the **Run anyway** button that appears at the bottom.
|
|
48
|
+
|
|
49
|
+
## 🛠️ Development Requirements
|
|
50
|
+
|
|
51
|
+
The project mainly uses modules from the Python Standard Library (`os`, `shutil`, `hashlib`, `tkinter`).
|
|
52
|
+
The only external dependency required for generating the PDF report is:
|
|
53
|
+
- `fpdf2`
|
|
54
|
+
|
|
55
|
+
To install the dependencies in a development environment:
|
|
56
|
+
```bash
|
|
57
|
+
pip install -r requirements.txt
|
|
58
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
forensic_copier.py
|
|
3
|
+
forensic_hasher.py
|
|
4
|
+
logger_pdf.py
|
|
5
|
+
main.py
|
|
6
|
+
pyproject.toml
|
|
7
|
+
copia_forense.egg-info/PKG-INFO
|
|
8
|
+
copia_forense.egg-info/SOURCES.txt
|
|
9
|
+
copia_forense.egg-info/dependency_links.txt
|
|
10
|
+
copia_forense.egg-info/entry_points.txt
|
|
11
|
+
copia_forense.egg-info/requires.txt
|
|
12
|
+
copia_forense.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fpdf2
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
|
|
3
|
+
class ForensicCopier:
|
|
4
|
+
"""Gestisce la clonazione fisica dei dati e la preservazione dei metadati."""
|
|
5
|
+
|
|
6
|
+
def copy_file_with_metadata(self, source_path: str, dest_path: str) -> tuple[bool, str]:
|
|
7
|
+
"""
|
|
8
|
+
Esegue la copia bit-a-bit e tenta di trasferire i metadati (MAC times).
|
|
9
|
+
Restituisce una tupla contenente l'esito (bool) e un messaggio diagnostico.
|
|
10
|
+
"""
|
|
11
|
+
try:
|
|
12
|
+
shutil.copyfile(source_path, dest_path)
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
shutil.copystat(source_path, dest_path)
|
|
16
|
+
return True, ""
|
|
17
|
+
except OSError:
|
|
18
|
+
return True, "Copiato (Metadati originali non supportati dal File System di destinazione)"
|
|
19
|
+
|
|
20
|
+
except PermissionError:
|
|
21
|
+
return False, "Accesso negato in lettura (PermissionError)"
|
|
22
|
+
except OSError:
|
|
23
|
+
return False, "File bloccato o in uso dall'OS"
|
|
24
|
+
except Exception as e:
|
|
25
|
+
return False, f"Errore generico di copia: {e}"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
|
|
3
|
+
class ForensicHasher:
|
|
4
|
+
"""Gestisce il calcolo degli hash crittografici per l'integrità dei reperti."""
|
|
5
|
+
|
|
6
|
+
def __init__(self, chunk_size: int = 4194304):
|
|
7
|
+
self.chunk_size = chunk_size
|
|
8
|
+
|
|
9
|
+
def calculate_sha256(self, file_path: str) -> str:
|
|
10
|
+
"""
|
|
11
|
+
Calcola l'hash SHA-256 di un file leggendolo a blocchi per ottimizzare la RAM.
|
|
12
|
+
Restituisce la stringa esadecimale o un codice di errore in caso di fallimento I/O.
|
|
13
|
+
"""
|
|
14
|
+
try:
|
|
15
|
+
sha256_hash = hashlib.sha256()
|
|
16
|
+
|
|
17
|
+
with open(file_path, "rb") as f:
|
|
18
|
+
# Itera sul file stream via chunk per contenere l'impronta in RAM
|
|
19
|
+
for chunk in iter(lambda: f.read(self.chunk_size), b""):
|
|
20
|
+
sha256_hash.update(chunk)
|
|
21
|
+
|
|
22
|
+
return sha256_hash.hexdigest()
|
|
23
|
+
|
|
24
|
+
except PermissionError:
|
|
25
|
+
return "ERRORE_PERMESSI"
|
|
26
|
+
except Exception as e:
|
|
27
|
+
return f"ERRORE_GENERICO: {e}"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from fpdf import FPDF
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
|
|
4
|
+
class ForensicReport:
|
|
5
|
+
"""Genera il verbale PDF riassuntivo (Executive Summary) dell'acquisizione."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, operatore: str = "Agente Operante"):
|
|
8
|
+
self.operatore = operatore
|
|
9
|
+
self.data_acquisizione = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
|
|
10
|
+
|
|
11
|
+
def genera_pdf_riassuntivo(self, registro_dati: list[dict], percorso_salvataggio: str) -> None:
|
|
12
|
+
"""Crea un PDF di singola pagina con le statistiche globali, omettendo la lista file."""
|
|
13
|
+
pdf = FPDF(orientation="P", unit="mm", format="A4")
|
|
14
|
+
pdf.add_page()
|
|
15
|
+
|
|
16
|
+
# INTESTAZIONE
|
|
17
|
+
pdf.set_font("helvetica", style="B", size=16)
|
|
18
|
+
pdf.cell(0, 10, "VERBALE DI ACQUISIZIONE LOGICA FORENSE", align="C", new_x="LMARGIN", new_y="NEXT")
|
|
19
|
+
pdf.ln(5)
|
|
20
|
+
|
|
21
|
+
# METADATI OPERAZIONE
|
|
22
|
+
pdf.set_font("helvetica", style="B", size=12)
|
|
23
|
+
pdf.cell(0, 8, "DETTAGLI OPERAZIONE", new_x="LMARGIN", new_y="NEXT")
|
|
24
|
+
pdf.set_font("helvetica", size=11)
|
|
25
|
+
pdf.cell(0, 8, f"Data Acquisizione: {self.data_acquisizione}", new_x="LMARGIN", new_y="NEXT")
|
|
26
|
+
pdf.cell(0, 8, f"Operatore: {self.operatore}", new_x="LMARGIN", new_y="NEXT")
|
|
27
|
+
pdf.ln(5)
|
|
28
|
+
|
|
29
|
+
# STATISTICHE
|
|
30
|
+
totale = len(registro_dati)
|
|
31
|
+
successi = sum(1 for f in registro_dati if f.get("esito") == "SUCCESSO")
|
|
32
|
+
anomalie = totale - successi
|
|
33
|
+
|
|
34
|
+
pdf.set_font("helvetica", style="B", size=12)
|
|
35
|
+
pdf.cell(0, 8, "STATISTICHE DI ACQUISIZIONE", new_x="LMARGIN", new_y="NEXT")
|
|
36
|
+
pdf.set_font("helvetica", size=11)
|
|
37
|
+
pdf.cell(0, 8, f"Totale File Rilevati : {totale}", new_x="LMARGIN", new_y="NEXT")
|
|
38
|
+
pdf.set_text_color(0, 128, 0)
|
|
39
|
+
pdf.cell(0, 8, f"Copie con Successo : {successi}", new_x="LMARGIN", new_y="NEXT")
|
|
40
|
+
|
|
41
|
+
if anomalie > 0:
|
|
42
|
+
pdf.set_text_color(200, 0, 0)
|
|
43
|
+
else:
|
|
44
|
+
pdf.set_text_color(0, 0, 0)
|
|
45
|
+
|
|
46
|
+
pdf.cell(0, 8, f"Anomalie / Saltati : {anomalie}", new_x="LMARGIN", new_y="NEXT")
|
|
47
|
+
pdf.set_text_color(0, 0, 0)
|
|
48
|
+
pdf.ln(10)
|
|
49
|
+
|
|
50
|
+
# DICHIARAZIONE ALLEGATI
|
|
51
|
+
pdf.set_font("helvetica", style="I", size=10)
|
|
52
|
+
nota = (
|
|
53
|
+
"NOTA: Per garantire la leggibilita' del verbale e gestire moli di dati massive, "
|
|
54
|
+
"l'elenco dettagliato dei file acquisiti, inclusivi di percorsi originari, "
|
|
55
|
+
"dimensioni, timestamp (MAC times) e firme crittografiche SHA-256 pre/post copia, "
|
|
56
|
+
"e' demandato esclusivamente agli allegati strutturati (File JSON/Testuali) "
|
|
57
|
+
"inclusi nel supporto di destinazione."
|
|
58
|
+
)
|
|
59
|
+
pdf.multi_cell(0, 6, nota)
|
|
60
|
+
|
|
61
|
+
pdf.output(percorso_salvataggio)
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import shutil
|
|
4
|
+
import platform
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
import tkinter as tk
|
|
7
|
+
from tkinter import filedialog, messagebox
|
|
8
|
+
|
|
9
|
+
from forensic_hasher import ForensicHasher
|
|
10
|
+
from forensic_copier import ForensicCopier
|
|
11
|
+
from logger_pdf import ForensicReport
|
|
12
|
+
|
|
13
|
+
# --- UTILITIES E CONTROLLI PRE-VOLO ---
|
|
14
|
+
|
|
15
|
+
def prompt_directory_selection() -> tuple[str, str]:
|
|
16
|
+
"""Inizializza l'interfaccia grafica e acquisisce i percorsi operativi."""
|
|
17
|
+
root = tk.Tk()
|
|
18
|
+
root.withdraw()
|
|
19
|
+
messagebox.showinfo(
|
|
20
|
+
"Acquisizione Forense",
|
|
21
|
+
"Premi OK per selezionare:\n\n1. La cartella SORGENTE (Reperto)\n2. La cartella di DESTINAZIONE (Pendrive)"
|
|
22
|
+
)
|
|
23
|
+
sorgente = filedialog.askdirectory(title="1. Seleziona la cartella SORGENTE (Reperto)")
|
|
24
|
+
destinazione = filedialog.askdirectory(title="2. Seleziona la cartella di DESTINAZIONE (Pendrive)")
|
|
25
|
+
|
|
26
|
+
# Distrugge la root di Tkinter per prevenire l'hanging del thread UI su macOS (WindowServer beach balling)
|
|
27
|
+
root.destroy()
|
|
28
|
+
|
|
29
|
+
return sorgente, destinazione
|
|
30
|
+
|
|
31
|
+
def is_safe_path(source: str, dest: str) -> bool:
|
|
32
|
+
"""Valida che il path di destinazione non sia annidato all'interno della sorgente."""
|
|
33
|
+
source_abs = os.path.abspath(source)
|
|
34
|
+
dest_abs = os.path.abspath(dest)
|
|
35
|
+
return os.path.commonpath([source_abs, dest_abs]) != source_abs
|
|
36
|
+
|
|
37
|
+
def check_disk_space(source: str, dest: str) -> bool:
|
|
38
|
+
"""Verifica che la destinazione abbia spazio sufficiente (+10% di buffer) per ospitare il reperto."""
|
|
39
|
+
total_size = 0
|
|
40
|
+
for dirpath, _, filenames in os.walk(source):
|
|
41
|
+
for f in filenames:
|
|
42
|
+
fp = os.path.join(dirpath, f)
|
|
43
|
+
if not os.path.islink(fp):
|
|
44
|
+
total_size += os.path.getsize(fp)
|
|
45
|
+
|
|
46
|
+
required_space = total_size * 1.10 # 10% di margine per i log forensi
|
|
47
|
+
free_space = shutil.disk_usage(dest).free
|
|
48
|
+
return free_space >= required_space
|
|
49
|
+
|
|
50
|
+
def format_mac_time(timestamp: float) -> str:
|
|
51
|
+
"""Converte un timestamp Unix in formato UTC leggibile."""
|
|
52
|
+
return datetime.fromtimestamp(timestamp, timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# --- CORE LOGIC ---
|
|
56
|
+
CHUNK_SIZE = 4194304 # 4MB
|
|
57
|
+
|
|
58
|
+
def process_directories(source_dir: str, dest_dir: str) -> list[dict]:
|
|
59
|
+
"""
|
|
60
|
+
Orchestra l'estrazione metadati, l'esplorazione del file system, l'hashing e la copia logica.
|
|
61
|
+
Restituisce la struttura dati completa contenente il registro delle operazioni.
|
|
62
|
+
"""
|
|
63
|
+
hasher = ForensicHasher(chunk_size=CHUNK_SIZE)
|
|
64
|
+
copier = ForensicCopier()
|
|
65
|
+
registro = []
|
|
66
|
+
dir_metadata_to_copy = []
|
|
67
|
+
|
|
68
|
+
if not os.path.exists(dest_dir):
|
|
69
|
+
os.makedirs(dest_dir)
|
|
70
|
+
|
|
71
|
+
for root, _, files in os.walk(source_dir):
|
|
72
|
+
percorso_relativo = os.path.relpath(root, source_dir)
|
|
73
|
+
cartella_dest_corrente = os.path.join(dest_dir, percorso_relativo)
|
|
74
|
+
|
|
75
|
+
if not os.path.exists(cartella_dest_corrente):
|
|
76
|
+
os.makedirs(cartella_dest_corrente)
|
|
77
|
+
|
|
78
|
+
# Memorizza i path delle directory per ripristinare i MAC times originali post-copia
|
|
79
|
+
dir_metadata_to_copy.append((root, cartella_dest_corrente))
|
|
80
|
+
|
|
81
|
+
for nome_file in files:
|
|
82
|
+
percorso_sorgente = os.path.join(root, nome_file)
|
|
83
|
+
percorso_destinazione = os.path.join(cartella_dest_corrente, nome_file)
|
|
84
|
+
percorso_visivo = os.path.normpath(os.path.join(percorso_relativo, nome_file))
|
|
85
|
+
|
|
86
|
+
# Dizionario Dati Base
|
|
87
|
+
dati_file = {
|
|
88
|
+
"percorso": percorso_visivo,
|
|
89
|
+
"esito": "SALTATO",
|
|
90
|
+
"hash_sha256": "N/D",
|
|
91
|
+
"note_errore": "",
|
|
92
|
+
"dimensione_byte": 0,
|
|
93
|
+
"data_modifica": "N/D",
|
|
94
|
+
"data_accesso": "N/D",
|
|
95
|
+
"data_creazione": "N/D"
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# ESTRAZIONE METADATI MAC TIMES E DIMENSIONE
|
|
99
|
+
try:
|
|
100
|
+
stat_info = os.stat(percorso_sorgente)
|
|
101
|
+
dati_file["dimensione_byte"] = stat_info.st_size
|
|
102
|
+
dati_file["data_modifica"] = format_mac_time(stat_info.st_mtime)
|
|
103
|
+
dati_file["data_accesso"] = format_mac_time(stat_info.st_atime)
|
|
104
|
+
dati_file["data_creazione"] = format_mac_time(stat_info.st_ctime)
|
|
105
|
+
except OSError as e:
|
|
106
|
+
dati_file["note_errore"] = f"Impossibile estrarre metadati originali: {e}"
|
|
107
|
+
|
|
108
|
+
# HASH PRE-COPIA
|
|
109
|
+
hash_originale = hasher.calculate_sha256(percorso_sorgente)
|
|
110
|
+
if hash_originale.startswith("ERRORE"):
|
|
111
|
+
dati_file["note_errore"] += f" | Errore I/O in lettura sorgente ({hash_originale})"
|
|
112
|
+
print(f"[⚠️ SALTATO] {percorso_visivo} (Accesso Negato)")
|
|
113
|
+
registro.append(dati_file)
|
|
114
|
+
continue
|
|
115
|
+
|
|
116
|
+
# COPIA FORENSE E VALIDAZIONE POST-COPIA
|
|
117
|
+
successo, msg_errore = copier.copy_file_with_metadata(percorso_sorgente, percorso_destinazione)
|
|
118
|
+
|
|
119
|
+
if successo:
|
|
120
|
+
hash_copia = hasher.calculate_sha256(percorso_destinazione)
|
|
121
|
+
if hash_originale == hash_copia:
|
|
122
|
+
dati_file["esito"] = "SUCCESSO"
|
|
123
|
+
dati_file["hash_sha256"] = hash_originale
|
|
124
|
+
print(f"[✅ OK] {percorso_visivo}")
|
|
125
|
+
else:
|
|
126
|
+
dati_file["esito"] = "FALLITO"
|
|
127
|
+
dati_file["note_errore"] = "Integrità compromessa: mismatch dell'hash post-copia"
|
|
128
|
+
print(f"[❌ ALTERATO] {percorso_visivo}")
|
|
129
|
+
else:
|
|
130
|
+
dati_file["note_errore"] = msg_errore
|
|
131
|
+
print(f"[⚠️ ERRORE COPIA] {percorso_visivo} - {msg_errore}")
|
|
132
|
+
|
|
133
|
+
registro.append(dati_file)
|
|
134
|
+
|
|
135
|
+
# Ripristina i metadati delle directory alla fine per evitare alterazioni dovute all'I/O
|
|
136
|
+
for src_dir, dst_dir in dir_metadata_to_copy:
|
|
137
|
+
try:
|
|
138
|
+
shutil.copystat(src_dir, dst_dir)
|
|
139
|
+
except OSError:
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
return registro
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# --- GENERAZIONE REPORT E FIRME ---
|
|
146
|
+
|
|
147
|
+
def genera_report_strutturato(registro: list[dict], path_destinazione: str, nome_sorgente: str = "") -> str:
|
|
148
|
+
"""Serializza il registro in formato JSON per l'ingestione automatizzata (es. ElasticSearch/Splunk)."""
|
|
149
|
+
nome_file = f"Verbale_Strutturato_{nome_sorgente}.json" if nome_sorgente else "Verbale_Strutturato.json"
|
|
150
|
+
json_path = os.path.join(path_destinazione, nome_file)
|
|
151
|
+
with open(json_path, 'w', encoding='utf-8') as f:
|
|
152
|
+
json.dump(registro, f, indent=4, ensure_ascii=False)
|
|
153
|
+
return json_path
|
|
154
|
+
|
|
155
|
+
def firma_catena_custodia(paths_verbali: list[str], path_destinazione: str, nome_sorgente: str = ""):
|
|
156
|
+
"""Calcola l'hash dei verbali generati per garantirne l'immodificabilità."""
|
|
157
|
+
hasher = ForensicHasher()
|
|
158
|
+
nome_file = f"Certificato_Firma_Verbali_{nome_sorgente}.txt" if nome_sorgente else "Certificato_Firma_Verbali.txt"
|
|
159
|
+
firma_path = os.path.join(path_destinazione, nome_file)
|
|
160
|
+
|
|
161
|
+
with open(firma_path, 'w', encoding='utf-8') as f:
|
|
162
|
+
f.write("=== CERTIFICATO DI IMMODIFICABILITA' DEI VERBALI ===\n")
|
|
163
|
+
f.write(f"Data: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')}\n\n")
|
|
164
|
+
for path in paths_verbali:
|
|
165
|
+
hash_val = hasher.calculate_sha256(path)
|
|
166
|
+
nome_file_verbale = os.path.basename(path)
|
|
167
|
+
f.write(f"{nome_file_verbale}\nSHA-256: {hash_val}\n\n")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def main() -> None:
|
|
171
|
+
"""Entry point dell'applicativo."""
|
|
172
|
+
print("Inizializzazione ambiente forense...")
|
|
173
|
+
cartella_sorgente, cartella_destinazione = prompt_directory_selection()
|
|
174
|
+
|
|
175
|
+
# 1. Controlli di Sicurezza
|
|
176
|
+
if not cartella_sorgente or not cartella_destinazione:
|
|
177
|
+
print("❌ Acquisizione annullata dall'operatore.")
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
if not is_safe_path(cartella_sorgente, cartella_destinazione):
|
|
181
|
+
msg = "ERRORE CRITICO: Destinazione interna alla sorgente. Rischio loop infinito."
|
|
182
|
+
messagebox.showerror("Violazione di Sicurezza", msg)
|
|
183
|
+
return
|
|
184
|
+
|
|
185
|
+
print("Verifica spazio su disco in corso...")
|
|
186
|
+
if not check_disk_space(cartella_sorgente, cartella_destinazione):
|
|
187
|
+
msg = "ERRORE CAPACITA': Spazio insufficiente sul disco di destinazione."
|
|
188
|
+
messagebox.showerror("Spazio Insufficiente", msg)
|
|
189
|
+
return
|
|
190
|
+
|
|
191
|
+
# 2. Acquisizione
|
|
192
|
+
print(f"📁 Target Acquisizione: {cartella_sorgente}")
|
|
193
|
+
print(f"💾 Storage Destinazione: {cartella_destinazione}\n")
|
|
194
|
+
print("Avvio procedura di estrazione logica...")
|
|
195
|
+
|
|
196
|
+
# Inizializza la root dir di destinazione mantenendo il nome del volume/cartella sorgente
|
|
197
|
+
nome_sorgente = os.path.basename(os.path.normpath(cartella_sorgente))
|
|
198
|
+
if not nome_sorgente:
|
|
199
|
+
nome_sorgente = "Acquisizione_Reperto"
|
|
200
|
+
|
|
201
|
+
cartella_acquisizione = os.path.join(cartella_destinazione, nome_sorgente)
|
|
202
|
+
|
|
203
|
+
registro_operazioni = process_directories(cartella_sorgente, cartella_acquisizione)
|
|
204
|
+
|
|
205
|
+
# 3. Reportistica
|
|
206
|
+
print(f"\nOperazioni concluse. File processati: {len(registro_operazioni)}")
|
|
207
|
+
print("Generazione catena di custodia in corso...")
|
|
208
|
+
|
|
209
|
+
# PDF
|
|
210
|
+
report_pdf = ForensicReport(operatore="Operatore PG")
|
|
211
|
+
path_pdf = os.path.join(cartella_destinazione, f"Verbale_Acquisizione_{nome_sorgente}.pdf")
|
|
212
|
+
report_pdf.genera_pdf_riassuntivo(registro_operazioni, path_pdf)
|
|
213
|
+
|
|
214
|
+
# JSON Strutturato
|
|
215
|
+
path_json = genera_report_strutturato(registro_operazioni, cartella_destinazione, nome_sorgente)
|
|
216
|
+
|
|
217
|
+
# Firma Verbali
|
|
218
|
+
firma_catena_custodia([path_pdf, path_json], cartella_destinazione, nome_sorgente)
|
|
219
|
+
|
|
220
|
+
print(f"✅ Acquisizione completata e sigillata in: {cartella_destinazione}")
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
if __name__ == "__main__":
|
|
224
|
+
main()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "copia-forense"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Mathias Nesci" }
|
|
10
|
+
]
|
|
11
|
+
description = "Forensic Logical Acquisition Tool with SHA-256 validation."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.7"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
]
|
|
18
|
+
dependencies = [
|
|
19
|
+
"fpdf2",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.scripts]
|
|
23
|
+
copia-forense = "main:main"
|
|
24
|
+
|
|
25
|
+
[tool.setuptools]
|
|
26
|
+
py-modules = ["main", "forensic_copier", "forensic_hasher", "logger_pdf"]
|