Merge pull request #24 from overcuriousity/claude/clarify-timestamp-format-cBGwJ

Claude/clarify timestamp format c b gw j
This commit is contained in:
overcuriousity
2025-12-14 21:57:30 +01:00
committed by GitHub
6 changed files with 97 additions and 11 deletions

View File

@@ -145,6 +145,14 @@ The codebase is organized into focused, single-responsibility modules to make it
**Integrity System**: Every note automatically gets: **Integrity System**: Every note automatically gets:
1. SHA256 hash of `timestamp:content` (via `Note.calculate_hash()`) 1. SHA256 hash of `timestamp:content` (via `Note.calculate_hash()`)
- **Timestamp Format**: Unix epoch timestamp as float (seconds since 1970-01-01 00:00:00 UTC)
- **Hash Input Format**: `"{timestamp}:{content}"` where timestamp is converted to string using Python's default str() conversion
- **Example**: For content "Suspicious process detected" with timestamp 1702345678.123456, the hash input is:
```
1702345678.123456:Suspicious process detected
```
- This ensures integrity of both WHAT was said (content) and WHEN it was said (timestamp)
- The exact float precision is preserved in the hash, making timestamps forensically tamper-evident
2. Optional GPG clearsign signature (if `pgp_enabled` in settings and GPG available) 2. Optional GPG clearsign signature (if `pgp_enabled` in settings and GPG available)
**Tag System**: Regex-based hashtag extraction (`#word`) **Tag System**: Regex-based hashtag extraction (`#word`)

View File

@@ -20,7 +20,7 @@ trace "IR team gained shell access. Initial persistence checks running."
trace "Observed outbound connection to 192.168.1.55 on port 80. #suspicious #network" trace "Observed outbound connection to 192.168.1.55 on port 80. #suspicious #network"
``` ```
**System Integrity Chain:** Each command-line note is immediately stamped, concatenated with its content, and hashed using SHA256 before storage. This ensures a non-repudiable log entry. **System Integrity Chain:** Each command-line note is immediately stamped with a Unix epoch timestamp (seconds since 1970-01-01 00:00:00 UTC as float, e.g., `1702345678.123456`), concatenated with its content in the format `"{timestamp}:{content}"`, and hashed using SHA256 before storage. This ensures a non-repudiable log entry with forensically tamper-evident timestamps.
## CLI Command Reference ## CLI Command Reference
@@ -195,7 +195,7 @@ After this, you can log with just: `t "Your note here"`
| Feature | Description | Operational Impact | | Feature | Description | Operational Impact |
| :--- | :--- | :--- | | :--- | :--- | :--- |
| **Integrity Hashing** | SHA256 applied to every log entry (content + timestamp). | **Guaranteed log integrity.** No modification possible post-entry. | | **Integrity Hashing** | SHA256 applied to every log entry using format `"{unix_timestamp}:{content}"`. Timestamp is Unix epoch as float (e.g., `1702345678.123456`). | **Guaranteed log integrity.** No modification possible post-entry. Timestamps are forensically tamper-evident with full float precision. |
| **GPG Signing** | Optional PGP/GPG signature applied to notes. | **Non-repudiation** for formal evidence handling. | | **GPG Signing** | Optional PGP/GPG signature applied to notes. | **Non-repudiation** for formal evidence handling. |
| **IOC Extraction** | Automatic parsing of IPv4, FQDNs, URLs, hashes, and email addresses. | **Immediate intelligence gathering** from raw text. | | **IOC Extraction** | Automatic parsing of IPv4, FQDNs, URLs, hashes, and email addresses. | **Immediate intelligence gathering** from raw text. |
| **Tag System** | Supports `#hashtags` for classification and filtering. | **Efficient triage** of large log sets. | | **Tag System** | Supports `#hashtags` for classification and filtering. | **Efficient triage** of large log sets. |
@@ -208,20 +208,33 @@ After this, you can log with just: `t "Your note here"`
### Layer 1: Note-Level Integrity (Always Active) ### Layer 1: Note-Level Integrity (Always Active)
**Process:** **Process:**
1. **Timestamp Generation** - Precise Unix timestamp captured at note creation 1. **Timestamp Generation** - Precise Unix epoch timestamp (float) captured at note creation
2. **Content Hashing** - SHA256 hash computed from `timestamp:content` - Format: Seconds since 1970-01-01 00:00:00 UTC (e.g., `1702345678.123456`)
- Full float precision preserved for forensic tamper-evidence
2. **Content Hashing** - SHA256 hash computed from `"{timestamp}:{content}"`
3. **Optional Signature** - Hash is signed with investigator's GPG private key 3. **Optional Signature** - Hash is signed with investigator's GPG private key
**Mathematical Representation:** **Mathematical Representation:**
``` ```
hash = SHA256(timestamp + ":" + content) timestamp = Unix epoch time as float (e.g., 1702345678.123456)
hash_input = "{timestamp}:{content}"
hash = SHA256(hash_input)
signature = GPG_Sign(hash, private_key) signature = GPG_Sign(hash, private_key)
``` ```
**Example:**
```
Content: "Suspicious process detected"
Timestamp: 1702345678.123456
Hash input: "1702345678.123456:Suspicious process detected"
Hash: SHA256 of above = a3f5b2c8d9e1f4a7b6c3d8e2f5a9b4c7d1e6f3a8b5c2d9e4f7a1b8c6d3e0f5a2
```
**Security Properties:** **Security Properties:**
- **Temporal Integrity**: Timestamp is cryptographically bound to content (cannot backdate notes) - **Temporal Integrity**: Timestamp is cryptographically bound to content (cannot backdate notes)
- **Tamper Detection**: Any modification to content or timestamp invalidates the hash - **Tamper Detection**: Any modification to content or timestamp invalidates the hash
- **Non-Repudiation**: GPG signature proves who created the note (if signing enabled) - **Non-Repudiation**: GPG signature proves who created the note (if signing enabled)
- **Hash Reproducibility**: Exported markdown includes Unix timestamp for independent verification
- **Efficient Storage**: Signing only the hash (64 hex chars) instead of full content - **Efficient Storage**: Signing only the hash (64 hex chars) instead of full content
### Layer 2: Export-Level Integrity (On Demand) ### Layer 2: Export-Level Integrity (On Demand)
@@ -328,6 +341,26 @@ Individual note signatures are embedded in the markdown export. To verify a spec
- The GPG signature proves who created that hash - The GPG signature proves who created that hash
- Together: Proves this specific content was created by this investigator at this time - Together: Proves this specific content was created by this investigator at this time
**Hash Verification (Manual):**
To independently verify a note's hash from the markdown export:
1. Locate the note in the export file and extract:
- Unix Timestamp (e.g., `1702345678.123456`)
- Content (e.g., `"Suspicious process detected"`)
- Claimed Hash (e.g., `a3f5b2c8...`)
2. Recompute the hash:
```bash
# Using Python
python3 -c "import hashlib; print(hashlib.sha256(b'1702345678.123456:Suspicious process detected').hexdigest())"
# Using command-line tools
echo -n "1702345678.123456:Suspicious process detected" | sha256sum
```
3. Compare the computed hash with the claimed hash - they must match exactly
### Cryptographic Trust Model ### Cryptographic Trust Model
``` ```

View File

@@ -364,9 +364,14 @@ def export_markdown(output_file: str = "export.md"):
sys.exit(1) sys.exit(1)
def format_note_for_export(note: Note) -> str: def format_note_for_export(note: Note) -> str:
"""Format a single note for export (returns string instead of writing to file)""" """Format a single note for export (returns string instead of writing to file)
Includes Unix timestamp for hash reproducibility - anyone can recompute the hash
using the formula: SHA256("{unix_timestamp}:{content}")
"""
lines = [] lines = []
lines.append(f"- **{time.ctime(note.timestamp)}**\n") lines.append(f"- **{time.ctime(note.timestamp)}**\n")
lines.append(f" - Unix Timestamp: `{note.timestamp}` (for hash verification)\n")
lines.append(f" - Content:\n") lines.append(f" - Content:\n")
# Properly indent multi-line content # Properly indent multi-line content
for line in note.content.splitlines(): for line in note.content.splitlines():

View File

@@ -184,5 +184,25 @@ class Crypto:
@staticmethod @staticmethod
def hash_content(content: str, timestamp: float) -> str: def hash_content(content: str, timestamp: float) -> str:
"""Calculate SHA256 hash of timestamp:content.
Hash input format: "{timestamp}:{content}"
- timestamp: Unix epoch timestamp as float (seconds since 1970-01-01 00:00:00 UTC)
Example: 1702345678.123456
- The float is converted to string using Python's default str() conversion
- Colon (':') separator between timestamp and content
- Ensures integrity of both WHAT was said and WHEN it was said
Args:
content: The note content to hash
timestamp: Unix epoch timestamp as float
Returns:
SHA256 hash as hexadecimal string (64 characters)
Example:
>>> hash_content("Suspicious process detected", 1702345678.123456)
Computes SHA256 of: "1702345678.123456:Suspicious process detected"
"""
data = f"{timestamp}:{content}".encode('utf-8') data = f"{timestamp}:{content}".encode('utf-8')
return hashlib.sha256(data).hexdigest() return hashlib.sha256(data).hexdigest()

View File

@@ -12,6 +12,9 @@ from .extractors import TagExtractor, IOCExtractor
@dataclass @dataclass
class Note: class Note:
content: str content: str
# Unix timestamp: seconds since 1970-01-01 00:00:00 UTC as float
# Example: 1702345678.123456
# This exact float value (with full precision) is used in hash calculation
timestamp: float = field(default_factory=time.time) timestamp: float = field(default_factory=time.time)
note_id: str = field(default_factory=lambda: str(uuid.uuid4())) note_id: str = field(default_factory=lambda: str(uuid.uuid4()))
content_hash: str = "" content_hash: str = ""
@@ -28,7 +31,16 @@ class Note:
self.iocs = IOCExtractor.extract_iocs(self.content) self.iocs = IOCExtractor.extract_iocs(self.content)
def calculate_hash(self): def calculate_hash(self):
# We hash the content + timestamp to ensure integrity of 'when' it was said """Calculate SHA256 hash of timestamp:content.
Hash input format: "{timestamp}:{content}"
- timestamp: Unix epoch timestamp as float (e.g., "1702345678.123456")
- The float is converted to string using Python's default str() conversion
- Colon separator between timestamp and content
- Ensures integrity of both WHAT was said and WHEN it was said
Example hash input: "1702345678.123456:Suspicious process detected"
"""
data = f"{self.timestamp}:{self.content}".encode('utf-8') data = f"{self.timestamp}:{self.content}".encode('utf-8')
self.content_hash = hashlib.sha256(data).hexdigest() self.content_hash = hashlib.sha256(data).hexdigest()

View File

@@ -222,15 +222,23 @@ class ExportHandler:
@staticmethod @staticmethod
def _write_note_markdown(f, note: Note): def _write_note_markdown(f, note: Note):
"""Helper to write a note in markdown format""" """Helper to write a note in markdown format
Includes Unix timestamp for hash reproducibility - anyone can recompute the hash
using the formula: SHA256("{unix_timestamp}:{content}")
"""
f.write(f"- **{time.ctime(note.timestamp)}**\n") f.write(f"- **{time.ctime(note.timestamp)}**\n")
f.write(f" - Content: {note.content}\n") f.write(f" - Unix Timestamp: `{note.timestamp}` (for hash verification)\n")
f.write(f" - Content:\n")
# Properly indent multi-line content
for line in note.content.splitlines():
f.write(f" {line}\n")
if note.tags: if note.tags:
tags_str = " ".join([f"#{tag}" for tag in note.tags]) tags_str = " ".join([f"#{tag}" for tag in note.tags])
f.write(f" - Tags: {tags_str}\n") f.write(f" - Tags: {tags_str}\n")
f.write(f" - Hash: `{note.content_hash}`\n") f.write(f" - SHA256 Hash (timestamp:content): `{note.content_hash}`\n")
if note.signature: if note.signature:
f.write(" - **Signature Verified:**\n") f.write(" - **GPG Signature of Hash:**\n")
f.write(" ```\n") f.write(" ```\n")
for line in note.signature.splitlines(): for line in note.signature.splitlines():
f.write(f" {line}\n") f.write(f" {line}\n")