Rebrand SooSeF to FieldWitness
Complete project rebrand for better positioning in the press freedom and digital security space. FieldWitness communicates both field deployment and evidence testimony — appropriate for the target audience of journalists, NGOs, and human rights organizations. Rename mapping: - soosef → fieldwitness (package, CLI, all imports) - soosef.stegasoo → fieldwitness.stego - soosef.verisoo → fieldwitness.attest - ~/.soosef/ → ~/.fwmetadata/ (innocuous data dir name) - SOOSEF_DATA_DIR → FIELDWITNESS_DATA_DIR - SoosefConfig → FieldWitnessConfig - SoosefError → FieldWitnessError Also includes: - License switch from MIT to GPL-3.0 - C2PA bridge module (Phase 0-2 MVP): cert.py, export.py, vendor_assertions.py - README repositioned to lead with provenance/federation, stego backgrounded - Threat model skeleton at docs/security/threat-model.md - Planning docs: docs/planning/c2pa-integration.md, docs/planning/gtm-feasibility.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,27 +1,27 @@
|
||||
"""
|
||||
SooSeF Web Frontend
|
||||
FieldWitness Web Frontend
|
||||
|
||||
Flask application factory that unifies Stegasoo (steganography) and Verisoo
|
||||
Flask application factory that unifies Stego (steganography) and Attest
|
||||
(provenance attestation) into a single web UI with fieldkit security features.
|
||||
|
||||
ARCHITECTURE
|
||||
============
|
||||
|
||||
The stegasoo web UI (3,600+ lines, 60 routes) is mounted wholesale via
|
||||
_register_stegasoo_routes() rather than being rewritten into a blueprint.
|
||||
The stego web UI (3,600+ lines, 60 routes) is mounted wholesale via
|
||||
_register_stego_routes() rather than being rewritten into a blueprint.
|
||||
This preserves the battle-tested subprocess isolation, async job management,
|
||||
and all existing route logic without modification.
|
||||
|
||||
SooSeF-native features (attest, fieldkit, keys) are clean blueprints.
|
||||
FieldWitness-native features (attest, fieldkit, keys) are clean blueprints.
|
||||
|
||||
Stegasoo routes (mounted at root):
|
||||
Stego routes (mounted at root):
|
||||
/encode, /decode, /generate, /tools, /api/*
|
||||
|
||||
SooSeF blueprints:
|
||||
FieldWitness blueprints:
|
||||
/attest, /verify → attest blueprint
|
||||
/fieldkit/* → fieldkit blueprint
|
||||
/keys/* → keys blueprint
|
||||
/admin/* → admin blueprint (extends stegasoo's)
|
||||
/admin/* → admin blueprint (extends stego's)
|
||||
"""
|
||||
|
||||
import io
|
||||
@@ -42,18 +42,18 @@ from flask import (
|
||||
url_for,
|
||||
)
|
||||
|
||||
import soosef
|
||||
from soosef.config import SoosefConfig
|
||||
from soosef.paths import INSTANCE_DIR, SECRET_KEY_FILE, TEMP_DIR, ensure_dirs
|
||||
import fieldwitness
|
||||
from fieldwitness.config import FieldWitnessConfig
|
||||
from fieldwitness.paths import INSTANCE_DIR, SECRET_KEY_FILE, TEMP_DIR, ensure_dirs
|
||||
|
||||
# Suppress numpy/scipy warnings in subprocesses
|
||||
os.environ["NUMPY_MADVISE_HUGEPAGE"] = "0"
|
||||
os.environ["OMP_NUM_THREADS"] = "1"
|
||||
|
||||
|
||||
def create_app(config: SoosefConfig | None = None) -> Flask:
|
||||
def create_app(config: FieldWitnessConfig | None = None) -> Flask:
|
||||
"""Application factory."""
|
||||
config = config or SoosefConfig.load()
|
||||
config = config or FieldWitnessConfig.load()
|
||||
ensure_dirs()
|
||||
|
||||
web_dir = Path(__file__).parent
|
||||
@@ -68,7 +68,7 @@ def create_app(config: SoosefConfig | None = None) -> Flask:
|
||||
app.config["MAX_CONTENT_LENGTH"] = config.max_upload_mb * 1024 * 1024
|
||||
app.config["AUTH_ENABLED"] = config.auth_enabled
|
||||
app.config["HTTPS_ENABLED"] = config.https_enabled
|
||||
app.config["SOOSEF_CONFIG"] = config
|
||||
app.config["FIELDWITNESS_CONFIG"] = config
|
||||
|
||||
# Session security: timeout + secure cookie flags
|
||||
from datetime import timedelta
|
||||
@@ -84,7 +84,7 @@ def create_app(config: SoosefConfig | None = None) -> Flask:
|
||||
|
||||
csrf = CSRFProtect(app)
|
||||
|
||||
# Point temp_storage at ~/.soosef/temp/ before any routes run, so all
|
||||
# Point temp_storage at ~/.fieldwitness/temp/ before any routes run, so all
|
||||
# uploaded files land where the killswitch's destroy_temp_files step
|
||||
# expects them. Must happen after ensure_dirs() so the directory exists.
|
||||
import temp_storage as _ts
|
||||
@@ -103,10 +103,10 @@ def create_app(config: SoosefConfig | None = None) -> Flask:
|
||||
|
||||
init_auth(app)
|
||||
|
||||
# ── Register stegasoo routes ──────────────────────────────────
|
||||
_register_stegasoo_routes(app)
|
||||
# ── Register stego routes ──────────────────────────────────
|
||||
_register_stego_routes(app)
|
||||
|
||||
# ── Register SooSeF-native blueprints ─────────────────────────
|
||||
# ── Register FieldWitness-native blueprints ─────────────────────────
|
||||
from frontends.web.blueprints.attest import bp as attest_bp
|
||||
from frontends.web.blueprints.fieldkit import bp as fieldkit_bp
|
||||
from frontends.web.blueprints.keys import bp as keys_bp
|
||||
@@ -131,7 +131,7 @@ def create_app(config: SoosefConfig | None = None) -> Flask:
|
||||
|
||||
@app.context_processor
|
||||
def inject_globals():
|
||||
from soosef.keystore import KeystoreManager
|
||||
from fieldwitness.keystore import KeystoreManager
|
||||
|
||||
ks = KeystoreManager()
|
||||
ks_status = ks.status()
|
||||
@@ -139,7 +139,7 @@ def create_app(config: SoosefConfig | None = None) -> Flask:
|
||||
# Fieldkit alert level
|
||||
fieldkit_status = "ok"
|
||||
if config.deadman_enabled:
|
||||
from soosef.fieldkit.deadman import DeadmanSwitch
|
||||
from fieldwitness.fieldkit.deadman import DeadmanSwitch
|
||||
|
||||
dm = DeadmanSwitch()
|
||||
if dm.should_fire():
|
||||
@@ -147,10 +147,10 @@ def create_app(config: SoosefConfig | None = None) -> Flask:
|
||||
elif dm.is_overdue():
|
||||
fieldkit_status = "warn"
|
||||
|
||||
# Stegasoo capabilities
|
||||
# Stego capabilities
|
||||
try:
|
||||
from soosef.stegasoo import HAS_AUDIO_SUPPORT, get_channel_status, has_dct_support
|
||||
from soosef.stegasoo.constants import (
|
||||
from fieldwitness.stego import HAS_AUDIO_SUPPORT, get_channel_status, has_dct_support
|
||||
from fieldwitness.stego.constants import (
|
||||
DEFAULT_PASSPHRASE_WORDS,
|
||||
MAX_FILE_PAYLOAD_SIZE,
|
||||
MAX_MESSAGE_CHARS,
|
||||
@@ -166,7 +166,7 @@ def create_app(config: SoosefConfig | None = None) -> Flask:
|
||||
has_audio = HAS_AUDIO_SUPPORT
|
||||
channel_status = get_channel_status()
|
||||
|
||||
# Stegasoo-specific template vars (needed by stego templates)
|
||||
# Stego-specific template vars (needed by stego templates)
|
||||
stego_vars = {
|
||||
"has_dct": has_dct,
|
||||
"has_audio": has_audio,
|
||||
@@ -188,13 +188,13 @@ def create_app(config: SoosefConfig | None = None) -> Flask:
|
||||
has_audio = False
|
||||
stego_vars = {}
|
||||
|
||||
# Verisoo availability
|
||||
# Attest availability
|
||||
try:
|
||||
import soosef.verisoo # noqa: F401
|
||||
import fieldwitness.attest # noqa: F401
|
||||
|
||||
has_verisoo = True
|
||||
has_attest = True
|
||||
except ImportError:
|
||||
has_verisoo = False
|
||||
has_attest = False
|
||||
|
||||
# Saved channel keys for authenticated users
|
||||
saved_channel_keys = []
|
||||
@@ -209,8 +209,8 @@ def create_app(config: SoosefConfig | None = None) -> Flask:
|
||||
pass
|
||||
|
||||
base_vars = {
|
||||
"version": soosef.__version__,
|
||||
"has_verisoo": has_verisoo,
|
||||
"version": fieldwitness.__version__,
|
||||
"has_attest": has_attest,
|
||||
"has_fieldkit": config.killswitch_enabled or config.deadman_enabled,
|
||||
"fieldkit_status": fieldkit_status,
|
||||
"channel_configured": ks_status.has_channel_key,
|
||||
@@ -247,22 +247,22 @@ def create_app(config: SoosefConfig | None = None) -> Flask:
|
||||
# (deadman status, key presence, memory, etc. are operational intel)
|
||||
if not auth_is_authenticated():
|
||||
from flask import jsonify
|
||||
return jsonify({"status": "ok", "version": __import__("soosef").__version__})
|
||||
return jsonify({"status": "ok", "version": __import__("fieldwitness").__version__})
|
||||
|
||||
import platform
|
||||
import sys
|
||||
|
||||
from flask import jsonify
|
||||
|
||||
from soosef.keystore.manager import KeystoreManager
|
||||
from fieldwitness.keystore.manager import KeystoreManager
|
||||
|
||||
ks = KeystoreManager()
|
||||
|
||||
# Core modules
|
||||
modules = {}
|
||||
for name, import_path in [
|
||||
("stegasoo", "soosef.stegasoo"),
|
||||
("verisoo", "soosef.verisoo"),
|
||||
("stego", "fieldwitness.stego"),
|
||||
("attest", "fieldwitness.attest"),
|
||||
]:
|
||||
try:
|
||||
mod = __import__(import_path, fromlist=["__version__"])
|
||||
@@ -275,27 +275,27 @@ def create_app(config: SoosefConfig | None = None) -> Flask:
|
||||
|
||||
# DCT steganography
|
||||
try:
|
||||
from soosef.stegasoo import has_dct_support
|
||||
from fieldwitness.stego import has_dct_support
|
||||
capabilities["stego_dct"] = {
|
||||
"status": "ok" if has_dct_support() else "unavailable",
|
||||
"hint": None if has_dct_support() else "Install soosef[stego-dct] (scipy, jpeglib, reedsolo)",
|
||||
"hint": None if has_dct_support() else "Install fieldwitness[stego-dct] (scipy, jpeglib, reedsolo)",
|
||||
}
|
||||
except ImportError:
|
||||
capabilities["stego_dct"] = {"status": "missing", "hint": "Install soosef[stego-dct]"}
|
||||
capabilities["stego_dct"] = {"status": "missing", "hint": "Install fieldwitness[stego-dct]"}
|
||||
|
||||
# Audio steganography
|
||||
try:
|
||||
from soosef.stegasoo import HAS_AUDIO_SUPPORT
|
||||
from fieldwitness.stego import HAS_AUDIO_SUPPORT
|
||||
capabilities["stego_audio"] = {
|
||||
"status": "ok" if HAS_AUDIO_SUPPORT else "unavailable",
|
||||
"hint": None if HAS_AUDIO_SUPPORT else "Install soosef[stego-audio] (soundfile, numpy)",
|
||||
"hint": None if HAS_AUDIO_SUPPORT else "Install fieldwitness[stego-audio] (soundfile, numpy)",
|
||||
}
|
||||
except ImportError:
|
||||
capabilities["stego_audio"] = {"status": "missing", "hint": "Install soosef[stego-audio]"}
|
||||
capabilities["stego_audio"] = {"status": "missing", "hint": "Install fieldwitness[stego-audio]"}
|
||||
|
||||
# Video steganography
|
||||
try:
|
||||
from soosef.stegasoo.constants import VIDEO_ENABLED
|
||||
from fieldwitness.stego.constants import VIDEO_ENABLED
|
||||
capabilities["stego_video"] = {
|
||||
"status": "ok" if VIDEO_ENABLED else "unavailable",
|
||||
"hint": None if VIDEO_ENABLED else "Requires ffmpeg in PATH",
|
||||
@@ -303,33 +303,33 @@ def create_app(config: SoosefConfig | None = None) -> Flask:
|
||||
except (ImportError, AttributeError):
|
||||
capabilities["stego_video"] = {"status": "missing", "hint": "Requires ffmpeg"}
|
||||
|
||||
# LMDB (verisoo storage)
|
||||
# LMDB (attest storage)
|
||||
try:
|
||||
import lmdb # noqa: F401
|
||||
capabilities["lmdb"] = {"status": "ok"}
|
||||
except ImportError:
|
||||
capabilities["lmdb"] = {"status": "missing", "hint": "Install soosef[attest]"}
|
||||
capabilities["lmdb"] = {"status": "missing", "hint": "Install fieldwitness[attest]"}
|
||||
|
||||
# Perceptual hashing
|
||||
try:
|
||||
import imagehash # noqa: F401
|
||||
capabilities["imagehash"] = {"status": "ok"}
|
||||
except ImportError:
|
||||
capabilities["imagehash"] = {"status": "missing", "hint": "Install soosef[attest]"}
|
||||
capabilities["imagehash"] = {"status": "missing", "hint": "Install fieldwitness[attest]"}
|
||||
|
||||
# USB monitoring
|
||||
try:
|
||||
import pyudev # noqa: F401
|
||||
capabilities["usb_monitor"] = {"status": "ok"}
|
||||
except ImportError:
|
||||
capabilities["usb_monitor"] = {"status": "unavailable", "hint": "Install soosef[fieldkit] (Linux only)"}
|
||||
capabilities["usb_monitor"] = {"status": "unavailable", "hint": "Install fieldwitness[fieldkit] (Linux only)"}
|
||||
|
||||
# GPIO (RPi killswitch)
|
||||
try:
|
||||
import gpiozero # noqa: F401
|
||||
capabilities["gpio"] = {"status": "ok"}
|
||||
except ImportError:
|
||||
capabilities["gpio"] = {"status": "unavailable", "hint": "Install soosef[rpi] (Raspberry Pi only)"}
|
||||
capabilities["gpio"] = {"status": "unavailable", "hint": "Install fieldwitness[rpi] (Raspberry Pi only)"}
|
||||
|
||||
# Key status (existence only, no material)
|
||||
keys = {
|
||||
@@ -350,7 +350,7 @@ def create_app(config: SoosefConfig | None = None) -> Flask:
|
||||
"chain_enabled": config.chain_enabled,
|
||||
}
|
||||
if config.deadman_enabled:
|
||||
from soosef.fieldkit.deadman import DeadmanSwitch
|
||||
from fieldwitness.fieldkit.deadman import DeadmanSwitch
|
||||
dm = DeadmanSwitch()
|
||||
dm_status = dm.status()
|
||||
fieldkit["deadman_armed"] = dm_status["armed"]
|
||||
@@ -380,7 +380,7 @@ def create_app(config: SoosefConfig | None = None) -> Flask:
|
||||
|
||||
return jsonify({
|
||||
"status": "ok" if all_ok else "degraded",
|
||||
"version": __import__("soosef").__version__,
|
||||
"version": __import__("fieldwitness").__version__,
|
||||
"modules": modules,
|
||||
"capabilities": capabilities,
|
||||
"keys": keys,
|
||||
@@ -408,26 +408,26 @@ except ImportError:
|
||||
_HAS_QRCODE_READ = False
|
||||
|
||||
|
||||
# ── Stegasoo route mounting ──────────────────────────────────────────
|
||||
# ── Stego route mounting ──────────────────────────────────────────
|
||||
|
||||
|
||||
def _register_stegasoo_routes(app: Flask) -> None:
|
||||
def _register_stego_routes(app: Flask) -> None:
|
||||
"""
|
||||
Mount all stegasoo web routes into the Flask app.
|
||||
Mount all stego web routes into the Flask app.
|
||||
|
||||
Rather than rewriting 3,600 lines of battle-tested route logic,
|
||||
we import stegasoo's app.py and re-register its routes.
|
||||
The stegasoo templates are in templates/stego/ and extend our base.html.
|
||||
we import fieldwitness.stego's app.py and re-register its routes.
|
||||
The stego templates are in templates/stego/ and extend our base.html.
|
||||
"""
|
||||
import temp_storage
|
||||
from auth import admin_required, login_required
|
||||
from soosef.stegasoo import (
|
||||
from fieldwitness.stego import (
|
||||
export_rsa_key_pem,
|
||||
generate_credentials,
|
||||
get_channel_status,
|
||||
load_rsa_key,
|
||||
)
|
||||
from soosef.stegasoo.constants import (
|
||||
from fieldwitness.stego.constants import (
|
||||
DEFAULT_PASSPHRASE_WORDS,
|
||||
MAX_PIN_LENGTH,
|
||||
MIN_PASSPHRASE_WORDS,
|
||||
@@ -435,7 +435,7 @@ def _register_stegasoo_routes(app: Flask) -> None:
|
||||
TEMP_FILE_EXPIRY,
|
||||
VALID_RSA_SIZES,
|
||||
)
|
||||
from soosef.stegasoo.qr_utils import (
|
||||
from fieldwitness.stego.qr_utils import (
|
||||
can_fit_in_qr,
|
||||
generate_qr_code,
|
||||
)
|
||||
@@ -443,7 +443,7 @@ def _register_stegasoo_routes(app: Flask) -> None:
|
||||
SubprocessStego,
|
||||
)
|
||||
|
||||
from soosef.audit import log_action
|
||||
from fieldwitness.audit import log_action
|
||||
|
||||
# Initialize subprocess wrapper
|
||||
subprocess_stego = SubprocessStego(timeout=180)
|
||||
@@ -511,7 +511,7 @@ def _register_stegasoo_routes(app: Flask) -> None:
|
||||
_login_attempts[username] = attempts
|
||||
|
||||
if len(attempts) >= max_attempts:
|
||||
from soosef.audit import log_action
|
||||
from fieldwitness.audit import log_action
|
||||
|
||||
log_action(
|
||||
actor=username,
|
||||
@@ -818,7 +818,7 @@ def _register_stegasoo_routes(app: Flask) -> None:
|
||||
io.BytesIO(qr_png),
|
||||
mimetype="image/png",
|
||||
as_attachment=True,
|
||||
download_name="soosef_rsa_key_qr.png",
|
||||
download_name="fieldwitness_rsa_key_qr.png",
|
||||
)
|
||||
except Exception as e:
|
||||
return f"Error generating QR code: {e}", 500
|
||||
@@ -838,7 +838,7 @@ def _register_stegasoo_routes(app: Flask) -> None:
|
||||
private_key = load_rsa_key(key_pem.encode("utf-8"))
|
||||
encrypted_pem = export_rsa_key_pem(private_key, password=password)
|
||||
key_id = secrets.token_hex(4)
|
||||
filename = f"soosef_key_{private_key.key_size}_{key_id}.pem"
|
||||
filename = f"fieldwitness_key_{private_key.key_size}_{key_id}.pem"
|
||||
return send_file(
|
||||
io.BytesIO(encrypted_pem),
|
||||
mimetype="application/x-pem-file",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Stegasoo Authentication Module (v4.1.0)
|
||||
Stego Authentication Module (v4.1.0)
|
||||
|
||||
Multi-user authentication with role-based access control.
|
||||
- Admin user created at first-run setup
|
||||
@@ -20,7 +20,7 @@ from argon2 import PasswordHasher
|
||||
from argon2.exceptions import VerifyMismatchError
|
||||
from flask import current_app, flash, g, redirect, session, url_for
|
||||
|
||||
# Argon2 password hasher (lighter than stegasoo's 256MB for faster login)
|
||||
# Argon2 password hasher (lighter than stego's 256MB for faster login)
|
||||
ph = PasswordHasher(
|
||||
time_cost=3,
|
||||
memory_cost=65536, # 64MB
|
||||
@@ -51,8 +51,8 @@ class User:
|
||||
|
||||
|
||||
def get_db_path() -> Path:
|
||||
"""Get database path — uses soosef auth directory."""
|
||||
from soosef.paths import AUTH_DB
|
||||
"""Get database path — uses fieldwitness auth directory."""
|
||||
from fieldwitness.paths import AUTH_DB
|
||||
|
||||
AUTH_DB.parent.mkdir(parents=True, exist_ok=True)
|
||||
return AUTH_DB
|
||||
@@ -273,7 +273,7 @@ def verify_and_reset_admin_password(recovery_key: str, new_password: str) -> tup
|
||||
Returns:
|
||||
(success, message) tuple
|
||||
"""
|
||||
from soosef.stegasoo.recovery import verify_recovery_key
|
||||
from fieldwitness.stego.recovery import verify_recovery_key
|
||||
|
||||
stored_hash = get_recovery_key_hash()
|
||||
if not stored_hash:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""
|
||||
Admin routes are registered directly in app.py via _register_stegasoo_routes()
|
||||
Admin routes are registered directly in app.py via _register_stego_routes()
|
||||
alongside the auth routes (setup, login, logout, account, admin/users).
|
||||
"""
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Attestation blueprint — attest and verify images via Verisoo.
|
||||
Attestation blueprint — attest and verify images via Attest.
|
||||
|
||||
Wraps verisoo's attestation and verification libraries to provide:
|
||||
Wraps attest's attestation and verification libraries to provide:
|
||||
- Image attestation: upload → hash → sign → store in append-only log
|
||||
- Image verification: upload → hash → search log → display matches
|
||||
- Verification receipt: same as verify but returns a downloadable JSON file
|
||||
@@ -21,27 +21,27 @@ bp = Blueprint("attest", __name__)
|
||||
|
||||
|
||||
def _get_storage():
|
||||
"""Get verisoo LocalStorage pointed at soosef's attestation directory."""
|
||||
from soosef.verisoo.storage import LocalStorage
|
||||
"""Get attest LocalStorage pointed at fieldwitness's attestation directory."""
|
||||
from fieldwitness.attest.storage import LocalStorage
|
||||
|
||||
from soosef.paths import ATTESTATIONS_DIR
|
||||
from fieldwitness.paths import ATTESTATIONS_DIR
|
||||
|
||||
return LocalStorage(base_path=ATTESTATIONS_DIR)
|
||||
|
||||
|
||||
def _get_private_key():
|
||||
"""Load the Ed25519 private key from soosef identity directory."""
|
||||
from soosef.verisoo.crypto import load_private_key
|
||||
"""Load the Ed25519 private key from fieldwitness identity directory."""
|
||||
from fieldwitness.attest.crypto import load_private_key
|
||||
|
||||
from soosef.paths import IDENTITY_PRIVATE_KEY
|
||||
from fieldwitness.paths import IDENTITY_PRIVATE_KEY
|
||||
|
||||
if not IDENTITY_PRIVATE_KEY.exists():
|
||||
return None
|
||||
return load_private_key(IDENTITY_PRIVATE_KEY)
|
||||
|
||||
|
||||
def _wrap_in_chain(verisoo_record, private_key, metadata: dict | None = None):
|
||||
"""Wrap a Verisoo attestation record in the hash chain.
|
||||
def _wrap_in_chain(attest_record, private_key, metadata: dict | None = None):
|
||||
"""Wrap a Attest attestation record in the hash chain.
|
||||
|
||||
Returns the chain record, or None if chain is disabled.
|
||||
"""
|
||||
@@ -49,23 +49,23 @@ def _wrap_in_chain(verisoo_record, private_key, metadata: dict | None = None):
|
||||
|
||||
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
||||
|
||||
from soosef.config import SoosefConfig
|
||||
from soosef.federation.chain import ChainStore
|
||||
from soosef.paths import CHAIN_DIR, IDENTITY_PRIVATE_KEY
|
||||
from fieldwitness.config import FieldWitnessConfig
|
||||
from fieldwitness.federation.chain import ChainStore
|
||||
from fieldwitness.paths import CHAIN_DIR, IDENTITY_PRIVATE_KEY
|
||||
|
||||
config = SoosefConfig.load()
|
||||
config = FieldWitnessConfig.load()
|
||||
if not config.chain_enabled or not config.chain_auto_wrap:
|
||||
return None
|
||||
|
||||
# Hash the verisoo record bytes as chain content
|
||||
# Hash the attest record bytes as chain content
|
||||
record_bytes = (
|
||||
verisoo_record.to_bytes()
|
||||
if hasattr(verisoo_record, "to_bytes")
|
||||
else str(verisoo_record).encode()
|
||||
attest_record.to_bytes()
|
||||
if hasattr(attest_record, "to_bytes")
|
||||
else str(attest_record).encode()
|
||||
)
|
||||
content_hash = hashlib.sha256(record_bytes).digest()
|
||||
|
||||
# Load Ed25519 key for chain signing (need the cryptography key, not verisoo's)
|
||||
# Load Ed25519 key for chain signing (need the cryptography key, not attest's)
|
||||
priv_pem = IDENTITY_PRIVATE_KEY.read_bytes()
|
||||
chain_private_key = load_pem_private_key(priv_pem, password=None)
|
||||
|
||||
@@ -79,7 +79,7 @@ def _wrap_in_chain(verisoo_record, private_key, metadata: dict | None = None):
|
||||
store = ChainStore(CHAIN_DIR)
|
||||
return store.append(
|
||||
content_hash=content_hash,
|
||||
content_type="verisoo/attestation-v1",
|
||||
content_type="attest/attestation-v1",
|
||||
private_key=chain_private_key,
|
||||
metadata=chain_metadata,
|
||||
)
|
||||
@@ -111,7 +111,7 @@ def attest():
|
||||
if request.method == "POST":
|
||||
if not has_identity:
|
||||
flash(
|
||||
"No identity configured. Run 'soosef init' or generate one from the Keys page.",
|
||||
"No identity configured. Run 'fieldwitness init' or generate one from the Keys page.",
|
||||
"error",
|
||||
)
|
||||
return redirect(url_for("attest.attest"))
|
||||
@@ -151,7 +151,7 @@ def attest():
|
||||
# Extract-then-classify: get evidentiary metadata before attestation
|
||||
# so user can control what's included
|
||||
if auto_exif and strip_device:
|
||||
from soosef.metadata import extract_and_classify
|
||||
from fieldwitness.metadata import extract_and_classify
|
||||
|
||||
extraction = extract_and_classify(image_data)
|
||||
# Merge evidentiary fields (GPS, timestamp) but exclude
|
||||
@@ -166,7 +166,7 @@ def attest():
|
||||
metadata[f"exif_{key}"] = str(value)
|
||||
|
||||
# Create the attestation
|
||||
from soosef.verisoo.attestation import create_attestation
|
||||
from fieldwitness.attest.attestation import create_attestation
|
||||
|
||||
attestation = create_attestation(
|
||||
image_data=image_data,
|
||||
@@ -194,14 +194,14 @@ def attest():
|
||||
|
||||
# Save our own identity so we can look it up during verification
|
||||
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
|
||||
from soosef.verisoo.models import Identity
|
||||
from fieldwitness.attest.models import Identity
|
||||
|
||||
pub_key = private_key.public_key()
|
||||
pub_bytes = pub_key.public_bytes(Encoding.Raw, PublicFormat.Raw)
|
||||
identity = Identity(
|
||||
public_key=pub_bytes,
|
||||
fingerprint=attestation.record.attestor_fingerprint,
|
||||
metadata={"name": "SooSeF Local Identity"},
|
||||
metadata={"name": "FieldWitness Local Identity"},
|
||||
)
|
||||
try:
|
||||
storage.save_identity(identity)
|
||||
@@ -246,11 +246,11 @@ def attest_batch():
|
||||
"""
|
||||
import hashlib
|
||||
|
||||
from soosef.verisoo.hashing import hash_image
|
||||
from fieldwitness.attest.hashing import hash_image
|
||||
|
||||
private_key = _get_private_key()
|
||||
if private_key is None:
|
||||
return {"error": "No identity key. Run soosef init first."}, 400
|
||||
return {"error": "No identity key. Run fieldwitness init first."}, 400
|
||||
|
||||
files = request.files.getlist("images")
|
||||
if not files:
|
||||
@@ -271,14 +271,14 @@ def attest_batch():
|
||||
results.append({"file": filename, "status": "skipped", "reason": "already attested"})
|
||||
continue
|
||||
|
||||
from soosef.verisoo.attestation import create_attestation
|
||||
from fieldwitness.attest.attestation import create_attestation
|
||||
|
||||
attestation = create_attestation(image_data, private_key)
|
||||
index = storage.append_record(attestation.record)
|
||||
|
||||
# Wrap in chain if enabled
|
||||
chain_index = None
|
||||
config = request.app.config.get("SOOSEF_CONFIG") if hasattr(request, "app") else None
|
||||
config = request.app.config.get("FIELDWITNESS_CONFIG") if hasattr(request, "app") else None
|
||||
if config and getattr(config, "chain_enabled", False) and getattr(config, "chain_auto_wrap", False):
|
||||
try:
|
||||
chain_record = _wrap_in_chain(attestation.record, private_key, {})
|
||||
@@ -365,11 +365,11 @@ def _verify_image(image_data: bytes) -> dict:
|
||||
"""Run the full verification pipeline against the attestation log.
|
||||
|
||||
Returns a dict with keys:
|
||||
query_hashes — ImageHashes object from verisoo
|
||||
query_hashes — ImageHashes object from fieldwitness.attest
|
||||
matches — list of match dicts (record, match_type, distances, attestor_name)
|
||||
record_count — total records searched
|
||||
"""
|
||||
from soosef.verisoo.hashing import compute_all_distances, hash_image, is_same_image
|
||||
from fieldwitness.attest.hashing import compute_all_distances, hash_image, is_same_image
|
||||
|
||||
query_hashes = hash_image(image_data)
|
||||
storage = _get_storage()
|
||||
@@ -541,12 +541,12 @@ def verify_receipt():
|
||||
|
||||
# Chain position proof — look up this attestation in the hash chain
|
||||
try:
|
||||
from soosef.config import SoosefConfig
|
||||
from soosef.federation.chain import ChainStore
|
||||
from soosef.federation.serialization import compute_record_hash
|
||||
from soosef.paths import CHAIN_DIR
|
||||
from fieldwitness.config import FieldWitnessConfig
|
||||
from fieldwitness.federation.chain import ChainStore
|
||||
from fieldwitness.federation.serialization import compute_record_hash
|
||||
from fieldwitness.paths import CHAIN_DIR
|
||||
|
||||
chain_config = SoosefConfig.load()
|
||||
chain_config = FieldWitnessConfig.load()
|
||||
if chain_config.chain_enabled:
|
||||
chain_store = ChainStore(CHAIN_DIR)
|
||||
# Search chain for a record whose content_hash matches this attestation
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Source drop box blueprint — anonymous, token-gated file submission.
|
||||
|
||||
Provides a SecureDrop-like intake that lives inside SooSeF:
|
||||
Provides a SecureDrop-like intake that lives inside FieldWitness:
|
||||
- Admin creates a time-limited upload token
|
||||
- Source opens the token URL in a browser (no account needed)
|
||||
- Files are uploaded, EXIF-stripped, and auto-attested on receipt
|
||||
@@ -21,8 +21,8 @@ from pathlib import Path
|
||||
from auth import admin_required, login_required
|
||||
from flask import Blueprint, Response, flash, redirect, render_template, request, url_for
|
||||
|
||||
from soosef.audit import log_action
|
||||
from soosef.paths import AUTH_DIR, TEMP_DIR
|
||||
from fieldwitness.audit import log_action
|
||||
from fieldwitness.paths import AUTH_DIR, TEMP_DIR
|
||||
|
||||
bp = Blueprint("dropbox", __name__, url_prefix="/dropbox")
|
||||
|
||||
@@ -188,7 +188,7 @@ def upload(token):
|
||||
# 1. Extract EXIF into attestation metadata (evidentiary fields)
|
||||
# 2. Attest the ORIGINAL bytes (hash matches what source submitted)
|
||||
# 3. Strip metadata from the stored copy (protect source device info)
|
||||
from soosef.metadata import extract_strip_pipeline
|
||||
from fieldwitness.metadata import extract_strip_pipeline
|
||||
|
||||
extraction, stripped_data = extract_strip_pipeline(raw_data)
|
||||
|
||||
@@ -204,7 +204,7 @@ def upload(token):
|
||||
# is preserved in the attestation metadata; dangerous fields
|
||||
# (device serial) are excluded.
|
||||
try:
|
||||
from soosef.verisoo.attestation import create_attestation
|
||||
from fieldwitness.attest.attestation import create_attestation
|
||||
|
||||
from blueprints.attest import _get_private_key, _get_storage
|
||||
|
||||
@@ -240,7 +240,7 @@ def upload(token):
|
||||
# insufficient), making valid receipts unforgeable.
|
||||
import hmac
|
||||
|
||||
from soosef.paths import SECRET_KEY_FILE
|
||||
from fieldwitness.paths import SECRET_KEY_FILE
|
||||
|
||||
server_secret = SECRET_KEY_FILE.read_bytes() if SECRET_KEY_FILE.exists() else token.encode()
|
||||
receipt_code = hmac.new(
|
||||
@@ -279,7 +279,7 @@ def upload(token):
|
||||
return Response(receipt_text, content_type="text/plain")
|
||||
|
||||
# GET — show upload form with client-side SHA-256 hashing
|
||||
# Minimal page, no SooSeF branding (source safety)
|
||||
# Minimal page, no FieldWitness branding (source safety)
|
||||
remaining = token_data["max_files"] - token_data["used"]
|
||||
return f"""<!DOCTYPE html>
|
||||
<html><head><title>Secure Upload</title>
|
||||
|
||||
@@ -12,7 +12,7 @@ bp = Blueprint("federation", __name__, url_prefix="/federation")
|
||||
@login_required
|
||||
def status():
|
||||
"""Federation status dashboard."""
|
||||
from soosef.verisoo.peer_store import PeerStore
|
||||
from fieldwitness.attest.peer_store import PeerStore
|
||||
|
||||
store = PeerStore()
|
||||
peers = store.list_peers()
|
||||
@@ -21,9 +21,9 @@ def status():
|
||||
# Get local node info
|
||||
node_info = {"root": None, "size": 0}
|
||||
try:
|
||||
from soosef.verisoo.storage import LocalStorage
|
||||
from fieldwitness.attest.storage import LocalStorage
|
||||
|
||||
import soosef.paths as _paths
|
||||
import fieldwitness.paths as _paths
|
||||
|
||||
storage = LocalStorage(_paths.ATTESTATIONS_DIR)
|
||||
stats = storage.get_stats()
|
||||
@@ -48,7 +48,7 @@ def status():
|
||||
@admin_required
|
||||
def peer_add():
|
||||
"""Add a federation peer."""
|
||||
from soosef.verisoo.peer_store import PeerStore
|
||||
from fieldwitness.attest.peer_store import PeerStore
|
||||
|
||||
url = request.form.get("url", "").strip()
|
||||
fingerprint = request.form.get("fingerprint", "").strip()
|
||||
@@ -67,7 +67,7 @@ def peer_add():
|
||||
@admin_required
|
||||
def peer_remove():
|
||||
"""Remove a federation peer."""
|
||||
from soosef.verisoo.peer_store import PeerStore
|
||||
from fieldwitness.attest.peer_store import PeerStore
|
||||
|
||||
url = request.form.get("url", "").strip()
|
||||
store = PeerStore()
|
||||
|
||||
@@ -5,7 +5,7 @@ Fieldkit blueprint — killswitch, dead man's switch, status dashboard.
|
||||
from auth import admin_required, get_username, login_required
|
||||
from flask import Blueprint, flash, redirect, render_template, request, url_for
|
||||
|
||||
from soosef.audit import log_action
|
||||
from fieldwitness.audit import log_action
|
||||
|
||||
bp = Blueprint("fieldkit", __name__, url_prefix="/fieldkit")
|
||||
|
||||
@@ -14,7 +14,7 @@ bp = Blueprint("fieldkit", __name__, url_prefix="/fieldkit")
|
||||
@login_required
|
||||
def status():
|
||||
"""Fieldkit status dashboard — all monitors and system health."""
|
||||
from soosef.fieldkit.deadman import DeadmanSwitch
|
||||
from fieldwitness.fieldkit.deadman import DeadmanSwitch
|
||||
|
||||
deadman = DeadmanSwitch()
|
||||
return render_template(
|
||||
@@ -39,7 +39,7 @@ def killswitch():
|
||||
flash("Killswitch requires password confirmation.", "danger")
|
||||
return render_template("fieldkit/killswitch.html")
|
||||
|
||||
from soosef.fieldkit.killswitch import PurgeScope, execute_purge
|
||||
from fieldwitness.fieldkit.killswitch import PurgeScope, execute_purge
|
||||
|
||||
actor = username
|
||||
result = execute_purge(PurgeScope.ALL, reason="web_ui")
|
||||
@@ -71,7 +71,7 @@ def killswitch():
|
||||
@login_required
|
||||
def deadman_checkin():
|
||||
"""Record a dead man's switch check-in."""
|
||||
from soosef.fieldkit.deadman import DeadmanSwitch
|
||||
from fieldwitness.fieldkit.deadman import DeadmanSwitch
|
||||
|
||||
deadman = DeadmanSwitch()
|
||||
deadman.checkin()
|
||||
|
||||
@@ -5,7 +5,7 @@ Key management blueprint — unified view of all key material.
|
||||
from auth import get_username, login_required
|
||||
from flask import Blueprint, flash, redirect, render_template, url_for
|
||||
|
||||
from soosef.audit import log_action
|
||||
from fieldwitness.audit import log_action
|
||||
|
||||
bp = Blueprint("keys", __name__, url_prefix="/keys")
|
||||
|
||||
@@ -14,7 +14,7 @@ bp = Blueprint("keys", __name__, url_prefix="/keys")
|
||||
@login_required
|
||||
def index():
|
||||
"""Key management dashboard."""
|
||||
from soosef.keystore import KeystoreManager
|
||||
from fieldwitness.keystore import KeystoreManager
|
||||
|
||||
ks = KeystoreManager()
|
||||
return render_template("fieldkit/keys.html", keystore=ks.status())
|
||||
@@ -24,7 +24,7 @@ def index():
|
||||
@login_required
|
||||
def generate_channel():
|
||||
"""Generate a new channel key."""
|
||||
from soosef.keystore import KeystoreManager
|
||||
from fieldwitness.keystore import KeystoreManager
|
||||
|
||||
ks = KeystoreManager()
|
||||
try:
|
||||
@@ -54,7 +54,7 @@ def generate_channel():
|
||||
@login_required
|
||||
def generate_identity():
|
||||
"""Generate a new Ed25519 identity."""
|
||||
from soosef.keystore import KeystoreManager
|
||||
from fieldwitness.keystore import KeystoreManager
|
||||
|
||||
ks = KeystoreManager()
|
||||
try:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""
|
||||
Steganography routes are registered directly in app.py via _register_stegasoo_routes()
|
||||
rather than as a blueprint, because the stegasoo route logic (3,600+ lines) uses
|
||||
Steganography routes are registered directly in app.py via _register_stego_routes()
|
||||
rather than as a blueprint, because the stego route logic (3,600+ lines) uses
|
||||
module-level state (ThreadPoolExecutor, jobs dict, subprocess_stego instance)
|
||||
that doesn't translate cleanly to a blueprint.
|
||||
|
||||
The stego templates are in templates/stego/ and extend the soosef base.html.
|
||||
The stego templates are in templates/stego/ and extend the fieldwitness base.html.
|
||||
"""
|
||||
|
||||
@@ -83,7 +83,7 @@ def generate_self_signed_cert(
|
||||
# Create certificate
|
||||
subject = issuer = x509.Name(
|
||||
[
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Stegasoo"),
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "FieldWitness"),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, hostname),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/**
|
||||
* Stegasoo Authentication Pages JavaScript
|
||||
* FieldWitness Authentication Pages JavaScript
|
||||
* Handles login, setup, account, and admin user management pages
|
||||
*/
|
||||
|
||||
const StegasooAuth = {
|
||||
const StegoAuth = {
|
||||
|
||||
// ========================================================================
|
||||
// PASSWORD VISIBILITY TOGGLE
|
||||
@@ -128,15 +128,15 @@ const StegasooAuth = {
|
||||
|
||||
// Make togglePassword available globally for onclick handlers
|
||||
function togglePassword(inputId, btn) {
|
||||
StegasooAuth.togglePassword(inputId, btn);
|
||||
StegoAuth.togglePassword(inputId, btn);
|
||||
}
|
||||
|
||||
// Make copyField available globally for onclick handlers
|
||||
function copyField(fieldId) {
|
||||
StegasooAuth.copyField(fieldId);
|
||||
StegoAuth.copyField(fieldId);
|
||||
}
|
||||
|
||||
// Make regeneratePassword available globally for onclick handlers
|
||||
function regeneratePassword() {
|
||||
StegasooAuth.regeneratePassword();
|
||||
StegoAuth.regeneratePassword();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/**
|
||||
* Stegasoo Frontend JavaScript
|
||||
* FieldWitness Frontend JavaScript
|
||||
* Shared functionality across encode, decode, and generate pages
|
||||
*/
|
||||
|
||||
const Stegasoo = {
|
||||
const Stego = {
|
||||
|
||||
// ========================================================================
|
||||
// PASSWORD/PIN VISIBILITY TOGGLES
|
||||
@@ -97,10 +97,10 @@ const Stegasoo = {
|
||||
if (this.files && this.files[0]) {
|
||||
const file = this.files[0];
|
||||
if (file.type.startsWith('image/') && preview) {
|
||||
Stegasoo.showImagePreview(file, preview, label, zone);
|
||||
Stego.showImagePreview(file, preview, label, zone);
|
||||
} else if (file.type.startsWith('audio/') || !file.type.startsWith('image/')) {
|
||||
// Audio or non-image files: show file info instead of image preview
|
||||
Stegasoo.showAudioFileInfo(file, zone);
|
||||
Stego.showAudioFileInfo(file, zone);
|
||||
if (label) {
|
||||
label.classList.add('d-none');
|
||||
}
|
||||
@@ -155,9 +155,9 @@ const Stegasoo = {
|
||||
|
||||
// Trigger appropriate animation
|
||||
if (isScanContainer) {
|
||||
Stegasoo.triggerScanAnimation(zone, file);
|
||||
Stego.triggerScanAnimation(zone, file);
|
||||
} else if (isPixelContainer) {
|
||||
Stegasoo.triggerPixelReveal(zone, file);
|
||||
Stego.triggerPixelReveal(zone, file);
|
||||
}
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
@@ -264,7 +264,7 @@ const Stegasoo = {
|
||||
|
||||
if (hashEl) {
|
||||
// Generate a deterministic fake hash preview from filename + size
|
||||
const fakeHash = Stegasoo.generateFakeHash(file.name + file.size);
|
||||
const fakeHash = Stego.generateFakeHash(file.name + file.size);
|
||||
hashEl.textContent = `SHA256: ${fakeHash.substring(0, 8)}····${fakeHash.substring(56)}`;
|
||||
}
|
||||
}
|
||||
@@ -328,7 +328,7 @@ const Stegasoo = {
|
||||
tracesContainer.style.left = imgLeft + 'px';
|
||||
|
||||
// Generate Tron-style circuit traces covering the image
|
||||
Stegasoo.generateEmbedTraces(tracesContainer, imgWidth, imgHeight);
|
||||
Stego.generateEmbedTraces(tracesContainer, imgWidth, imgHeight);
|
||||
};
|
||||
|
||||
// Wait for image to be ready
|
||||
@@ -349,7 +349,7 @@ const Stegasoo = {
|
||||
if (grid) grid.remove();
|
||||
|
||||
// Populate data panel
|
||||
Stegasoo.populatePixelDataPanel(container, file, preview);
|
||||
Stego.populatePixelDataPanel(container, file, preview);
|
||||
}, duration);
|
||||
},
|
||||
|
||||
@@ -453,7 +453,7 @@ const Stegasoo = {
|
||||
|
||||
input.addEventListener('change', function() {
|
||||
if (this.files && this.files[0]) {
|
||||
Stegasoo.showImagePreview(this.files[0], preview, label, container);
|
||||
Stego.showImagePreview(this.files[0], preview, label, container);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1721,10 +1721,10 @@ const Stegasoo = {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Detect page and initialize
|
||||
if (document.getElementById('encodeForm')) {
|
||||
Stegasoo.initEncodePage();
|
||||
Stego.initEncodePage();
|
||||
} else if (document.getElementById('decodeForm')) {
|
||||
Stegasoo.initDecodePage();
|
||||
Stego.initDecodePage();
|
||||
} else if (document.querySelector('[data-page="generate"]')) {
|
||||
Stegasoo.initGeneratePage();
|
||||
Stego.initGeneratePage();
|
||||
}
|
||||
});
|
||||
@@ -1,9 +1,9 @@
|
||||
/**
|
||||
* Stegasoo Generate Page JavaScript
|
||||
* FieldWitness Stego Generate Page JavaScript
|
||||
* Handles credential generation form and display
|
||||
*/
|
||||
|
||||
const StegasooGenerate = {
|
||||
const StegoGenerate = {
|
||||
|
||||
// ========================================================================
|
||||
// FORM CONTROLS
|
||||
@@ -260,20 +260,20 @@ const StegasooGenerate = {
|
||||
|
||||
// Global function wrappers for onclick handlers
|
||||
function togglePinVisibility() {
|
||||
StegasooGenerate.togglePinVisibility();
|
||||
StegoGenerate.togglePinVisibility();
|
||||
}
|
||||
|
||||
function togglePassphraseVisibility() {
|
||||
StegasooGenerate.togglePassphraseVisibility();
|
||||
StegoGenerate.togglePassphraseVisibility();
|
||||
}
|
||||
|
||||
function printQrCode() {
|
||||
StegasooGenerate.printQrCode();
|
||||
StegoGenerate.printQrCode();
|
||||
}
|
||||
|
||||
// Auto-init form controls
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (document.querySelector('[data-page="generate"]')) {
|
||||
StegasooGenerate.initForm();
|
||||
StegoGenerate.initForm();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* ============================================================================
|
||||
SooSeF - Main Stylesheet
|
||||
Adapted from Stegasoo's style.css — same dark theme, same patterns.
|
||||
FieldWitness - Main Stylesheet
|
||||
Dark theme stylesheet for the FieldWitness web UI.
|
||||
============================================================================ */
|
||||
|
||||
:root {
|
||||
@@ -26,7 +26,7 @@
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
/* Nav icon + label pattern from stegasoo */
|
||||
/* Nav icon + label pattern */
|
||||
.nav-icons .nav-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Stegasoo encode/decode/tools routes.
|
||||
Stego encode/decode/tools routes.
|
||||
|
||||
Ported from stegasoo's frontends/web/app.py. These routes handle:
|
||||
Ported from fieldwitness.stego's frontends/web/app.py. These routes handle:
|
||||
- Image encode with async progress tracking
|
||||
- Audio encode (v4.3.0)
|
||||
- Image/audio decode
|
||||
@@ -33,7 +33,7 @@ from PIL import Image
|
||||
|
||||
|
||||
def register_stego_routes(app, **deps):
|
||||
"""Register all stegasoo encode/decode routes on the Flask app."""
|
||||
"""Register all stego encode/decode routes on the Flask app."""
|
||||
|
||||
# Unpack dependencies passed from app.py
|
||||
login_required = deps["login_required"]
|
||||
@@ -41,7 +41,7 @@ def register_stego_routes(app, **deps):
|
||||
temp_storage = deps["temp_storage"]
|
||||
_has_qrcode_read = deps.get("has_qrcode_read", False)
|
||||
|
||||
from soosef.stegasoo import (
|
||||
from fieldwitness.stego import (
|
||||
HAS_AUDIO_SUPPORT,
|
||||
CapacityError,
|
||||
DecryptionError,
|
||||
@@ -49,7 +49,7 @@ def register_stego_routes(app, **deps):
|
||||
InvalidHeaderError,
|
||||
InvalidMagicBytesError,
|
||||
ReedSolomonError,
|
||||
StegasooError,
|
||||
StegoError,
|
||||
generate_filename,
|
||||
has_dct_support,
|
||||
validate_file_payload,
|
||||
@@ -60,13 +60,13 @@ def register_stego_routes(app, **deps):
|
||||
validate_rsa_key,
|
||||
validate_security_factors,
|
||||
)
|
||||
from soosef.stegasoo.channel import resolve_channel_key
|
||||
from soosef.stegasoo.constants import (
|
||||
from fieldwitness.stego.channel import resolve_channel_key
|
||||
from fieldwitness.stego.constants import (
|
||||
TEMP_FILE_EXPIRY,
|
||||
THUMBNAIL_QUALITY,
|
||||
THUMBNAIL_SIZE,
|
||||
)
|
||||
from soosef.stegasoo.qr_utils import (
|
||||
from fieldwitness.stego.qr_utils import (
|
||||
decompress_data,
|
||||
extract_key_from_qr,
|
||||
is_compressed,
|
||||
@@ -152,7 +152,7 @@ def register_stego_routes(app, **deps):
|
||||
else:
|
||||
return f"{n/(1024*1024):.1f} MB"
|
||||
|
||||
# ── Routes below are extracted from stegasoo app.py ──
|
||||
# ── Routes below are extracted from fieldwitness.stego app.py ──
|
||||
|
||||
def _run_encode_job(job_id: str, encode_params: dict) -> None:
|
||||
"""Background thread function for async encode."""
|
||||
@@ -686,7 +686,7 @@ def register_stego_routes(app, **deps):
|
||||
return _error_response(result.error_message)
|
||||
|
||||
# Pre-check payload capacity BEFORE encode (fail fast)
|
||||
from soosef.stegasoo.steganography import will_fit_by_mode
|
||||
from fieldwitness.stego.steganography import will_fit_by_mode
|
||||
|
||||
payload_size = (
|
||||
len(payload.data) if hasattr(payload, "data") else len(payload.encode("utf-8"))
|
||||
@@ -770,7 +770,7 @@ def register_stego_routes(app, **deps):
|
||||
error_msg = encode_result.error or "Encoding failed"
|
||||
if "capacity" in error_msg.lower():
|
||||
raise CapacityError(error_msg)
|
||||
raise StegasooError(error_msg)
|
||||
raise StegoError(error_msg)
|
||||
|
||||
# Determine actual output format for filename and storage
|
||||
if embed_mode == "dct" and dct_output_format == "jpeg":
|
||||
@@ -813,7 +813,7 @@ def register_stego_routes(app, **deps):
|
||||
|
||||
except CapacityError as e:
|
||||
return _error_response(str(e))
|
||||
except StegasooError as e:
|
||||
except StegoError as e:
|
||||
return _error_response(str(e))
|
||||
except Exception as e:
|
||||
return _error_response(f"Error: {e}")
|
||||
@@ -1443,7 +1443,7 @@ def register_stego_routes(app, **deps):
|
||||
or decode_result.error_type == "DecryptionError"
|
||||
):
|
||||
raise DecryptionError(error_msg)
|
||||
raise StegasooError(error_msg)
|
||||
raise StegoError(error_msg)
|
||||
|
||||
if decode_result.is_file:
|
||||
# File content - store temporarily for download
|
||||
@@ -1479,7 +1479,7 @@ def register_stego_routes(app, **deps):
|
||||
|
||||
except InvalidMagicBytesError:
|
||||
flash(
|
||||
"This doesn't appear to be a Stegasoo image. Try a different mode (LSB/DCT).",
|
||||
"This doesn't appear to be a Stego image. Try a different mode (LSB/DCT).",
|
||||
"warning",
|
||||
)
|
||||
return render_template("stego/decode.html", has_qrcode_read=_has_qrcode_read)
|
||||
@@ -1501,7 +1501,7 @@ def register_stego_routes(app, **deps):
|
||||
"warning",
|
||||
)
|
||||
return render_template("stego/decode.html", has_qrcode_read=_has_qrcode_read)
|
||||
except StegasooError as e:
|
||||
except StegoError as e:
|
||||
flash(str(e), "error")
|
||||
return render_template("stego/decode.html", has_qrcode_read=_has_qrcode_read)
|
||||
except Exception as e:
|
||||
@@ -1613,8 +1613,8 @@ def register_stego_routes(app, **deps):
|
||||
@app.route("/about")
|
||||
def about():
|
||||
from auth import get_current_user
|
||||
from soosef.stegasoo import has_argon2
|
||||
from soosef.stegasoo.channel import get_channel_status
|
||||
from fieldwitness.stego import has_argon2
|
||||
from fieldwitness.stego.channel import get_channel_status
|
||||
|
||||
channel_status = get_channel_status()
|
||||
current_user = get_current_user()
|
||||
@@ -1644,7 +1644,7 @@ def register_stego_routes(app, **deps):
|
||||
@login_required
|
||||
def api_tools_capacity():
|
||||
"""Calculate image capacity for steganography."""
|
||||
from soosef.stegasoo.dct_steganography import estimate_capacity_comparison
|
||||
from fieldwitness.stego.dct_steganography import estimate_capacity_comparison
|
||||
|
||||
carrier = request.files.get("image")
|
||||
if not carrier:
|
||||
@@ -1666,7 +1666,7 @@ def register_stego_routes(app, **deps):
|
||||
"""Strip EXIF/metadata from image."""
|
||||
import io
|
||||
|
||||
from soosef.stegasoo.utils import strip_image_metadata
|
||||
from fieldwitness.stego.utils import strip_image_metadata
|
||||
|
||||
image_file = request.files.get("image")
|
||||
if not image_file:
|
||||
@@ -1689,7 +1689,7 @@ def register_stego_routes(app, **deps):
|
||||
@login_required
|
||||
def api_tools_exif():
|
||||
"""Read EXIF metadata from image."""
|
||||
from soosef.stegasoo.utils import read_image_exif
|
||||
from fieldwitness.stego.utils import read_image_exif
|
||||
|
||||
image_file = request.files.get("image")
|
||||
if not image_file:
|
||||
@@ -1718,7 +1718,7 @@ def register_stego_routes(app, **deps):
|
||||
@login_required
|
||||
def api_tools_exif_update():
|
||||
"""Update EXIF fields in image."""
|
||||
from soosef.stegasoo.utils import write_image_exif
|
||||
from fieldwitness.stego.utils import write_image_exif
|
||||
|
||||
image_file = request.files.get("image")
|
||||
if not image_file:
|
||||
@@ -1757,7 +1757,7 @@ def register_stego_routes(app, **deps):
|
||||
@login_required
|
||||
def api_tools_exif_clear():
|
||||
"""Remove all EXIF metadata from image."""
|
||||
from soosef.stegasoo.utils import strip_image_metadata
|
||||
from fieldwitness.stego.utils import strip_image_metadata
|
||||
|
||||
image_file = request.files.get("image")
|
||||
if not image_file:
|
||||
@@ -2062,7 +2062,7 @@ def register_stego_routes(app, **deps):
|
||||
|
||||
@app.route("/test-capacity", methods=["POST"])
|
||||
def test_capacity():
|
||||
"""Minimal capacity test - no stegasoo code, just PIL."""
|
||||
"""Minimal capacity test - no stego code, just PIL."""
|
||||
carrier = request.files.get("carrier")
|
||||
if not carrier:
|
||||
return jsonify({"error": "No carrier image provided"}), 400
|
||||
@@ -2095,7 +2095,7 @@ def register_stego_routes(app, **deps):
|
||||
|
||||
@app.route("/test-capacity-nopil", methods=["POST"])
|
||||
def test_capacity_nopil():
|
||||
"""Ultra-minimal test - no PIL, no stegasoo."""
|
||||
"""Ultra-minimal test - no PIL, no stego."""
|
||||
carrier = request.files.get("carrier")
|
||||
if not carrier:
|
||||
return jsonify({"error": "No carrier image provided"}), 400
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Stegasoo Subprocess Worker (v4.0.0)
|
||||
Stego Subprocess Worker (v4.0.0)
|
||||
|
||||
This script runs in a subprocess and handles encode/decode operations.
|
||||
If it crashes due to jpeglib/scipy issues, the parent Flask process survives.
|
||||
@@ -25,12 +25,12 @@ import sys
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
|
||||
# Ensure stegasoo is importable
|
||||
# Ensure stego is importable
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
# Configure logging for worker subprocess
|
||||
_log_level = os.environ.get("STEGASOO_LOG_LEVEL", "").strip().upper()
|
||||
_log_level = os.environ.get("FIELDWITNESS_LOG_LEVEL", "").strip().upper()
|
||||
if _log_level and hasattr(logging, _log_level):
|
||||
logging.basicConfig(
|
||||
level=getattr(logging, _log_level),
|
||||
@@ -38,19 +38,19 @@ if _log_level and hasattr(logging, _log_level):
|
||||
datefmt="%H:%M:%S",
|
||||
stream=sys.stderr,
|
||||
)
|
||||
elif os.environ.get("STEGASOO_DEBUG", "").strip() in ("1", "true", "yes"):
|
||||
elif os.environ.get("FIELDWITNESS_DEBUG", "").strip() in ("1", "true", "yes"):
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format="[%(asctime)s.%(msecs)03d] [%(levelname)s] [%(name)s] %(message)s",
|
||||
datefmt="%H:%M:%S",
|
||||
stream=sys.stderr,
|
||||
)
|
||||
logger = logging.getLogger("stegasoo.worker")
|
||||
logger = logging.getLogger("stego.worker")
|
||||
|
||||
|
||||
def _resolve_channel_key(channel_key_param):
|
||||
"""
|
||||
Resolve channel_key parameter to value for stegasoo.
|
||||
Resolve channel_key parameter to value for stego.
|
||||
|
||||
Args:
|
||||
channel_key_param: 'auto', 'none', explicit key, or None
|
||||
@@ -73,7 +73,7 @@ def _get_channel_info(resolved_key):
|
||||
Returns:
|
||||
(mode, fingerprint) tuple
|
||||
"""
|
||||
from soosef.stegasoo import get_channel_status, has_channel_key
|
||||
from fieldwitness.stego import get_channel_status, has_channel_key
|
||||
|
||||
if resolved_key == "":
|
||||
return "public", None
|
||||
@@ -94,7 +94,7 @@ def _get_channel_info(resolved_key):
|
||||
def encode_operation(params: dict) -> dict:
|
||||
"""Handle encode operation."""
|
||||
logger.debug("encode_operation: mode=%s", params.get("embed_mode", "lsb"))
|
||||
from soosef.stegasoo import FilePayload, encode
|
||||
from fieldwitness.stego import FilePayload, encode
|
||||
|
||||
# Decode base64 inputs
|
||||
carrier_data = base64.b64decode(params["carrier_b64"])
|
||||
@@ -173,7 +173,7 @@ def _write_decode_progress(progress_file: str | None, percent: int, phase: str)
|
||||
def decode_operation(params: dict) -> dict:
|
||||
"""Handle decode operation."""
|
||||
logger.debug("decode_operation: mode=%s", params.get("embed_mode", "auto"))
|
||||
from soosef.stegasoo import decode
|
||||
from fieldwitness.stego import decode
|
||||
|
||||
progress_file = params.get("progress_file")
|
||||
|
||||
@@ -227,7 +227,7 @@ def decode_operation(params: dict) -> dict:
|
||||
|
||||
def compare_operation(params: dict) -> dict:
|
||||
"""Handle compare_modes operation."""
|
||||
from soosef.stegasoo import compare_modes
|
||||
from fieldwitness.stego import compare_modes
|
||||
|
||||
carrier_data = base64.b64decode(params["carrier_b64"])
|
||||
result = compare_modes(carrier_data)
|
||||
@@ -240,7 +240,7 @@ def compare_operation(params: dict) -> dict:
|
||||
|
||||
def capacity_check_operation(params: dict) -> dict:
|
||||
"""Handle will_fit_by_mode operation."""
|
||||
from soosef.stegasoo import will_fit_by_mode
|
||||
from fieldwitness.stego import will_fit_by_mode
|
||||
|
||||
carrier_data = base64.b64decode(params["carrier_b64"])
|
||||
|
||||
@@ -259,7 +259,7 @@ def capacity_check_operation(params: dict) -> dict:
|
||||
def encode_audio_operation(params: dict) -> dict:
|
||||
"""Handle audio encode operation (v4.3.0)."""
|
||||
logger.debug("encode_audio_operation: mode=%s", params.get("embed_mode", "audio_lsb"))
|
||||
from soosef.stegasoo import FilePayload, encode_audio
|
||||
from fieldwitness.stego import FilePayload, encode_audio
|
||||
|
||||
carrier_data = base64.b64decode(params["carrier_b64"])
|
||||
reference_data = base64.b64decode(params["reference_b64"])
|
||||
@@ -324,7 +324,7 @@ def encode_audio_operation(params: dict) -> dict:
|
||||
def decode_audio_operation(params: dict) -> dict:
|
||||
"""Handle audio decode operation (v4.3.0)."""
|
||||
logger.debug("decode_audio_operation: mode=%s", params.get("embed_mode", "audio_auto"))
|
||||
from soosef.stegasoo import decode_audio
|
||||
from fieldwitness.stego import decode_audio
|
||||
|
||||
progress_file = params.get("progress_file")
|
||||
_write_decode_progress(progress_file, 5, "reading")
|
||||
@@ -370,9 +370,9 @@ def decode_audio_operation(params: dict) -> dict:
|
||||
|
||||
def audio_info_operation(params: dict) -> dict:
|
||||
"""Handle audio info operation (v4.3.0)."""
|
||||
from soosef.stegasoo import get_audio_info
|
||||
from soosef.stegasoo.audio_steganography import calculate_audio_lsb_capacity
|
||||
from soosef.stegasoo.spread_steganography import calculate_audio_spread_capacity
|
||||
from fieldwitness.stego import get_audio_info
|
||||
from fieldwitness.stego.audio_steganography import calculate_audio_lsb_capacity
|
||||
from fieldwitness.stego.spread_steganography import calculate_audio_spread_capacity
|
||||
|
||||
audio_data = base64.b64decode(params["audio_b64"])
|
||||
|
||||
@@ -397,7 +397,7 @@ def audio_info_operation(params: dict) -> dict:
|
||||
|
||||
def channel_status_operation(params: dict) -> dict:
|
||||
"""Handle channel status check (v4.0.0)."""
|
||||
from soosef.stegasoo import get_channel_status
|
||||
from fieldwitness.stego import get_channel_status
|
||||
|
||||
status = get_channel_status()
|
||||
reveal = params.get("reveal", False)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Subprocess Steganography Wrapper (v4.0.0)
|
||||
|
||||
Runs stegasoo operations in isolated subprocesses to prevent crashes
|
||||
Runs stego operations in isolated subprocesses to prevent crashes
|
||||
from taking down the Flask server.
|
||||
|
||||
CHANGES in v4.0.0:
|
||||
@@ -743,7 +743,7 @@ def generate_job_id() -> str:
|
||||
|
||||
def get_progress_file_path(job_id: str) -> str:
|
||||
"""Get the progress file path for a job ID."""
|
||||
return str(Path(tempfile.gettempdir()) / f"stegasoo_progress_{job_id}.json")
|
||||
return str(Path(tempfile.gettempdir()) / f"stego_progress_{job_id}.json")
|
||||
|
||||
|
||||
def read_progress(job_id: str) -> dict | None:
|
||||
|
||||
@@ -12,7 +12,7 @@ Files are stored in a temp directory with:
|
||||
IMPORTANT: This module ONLY manages files in the temp directory.
|
||||
It does NOT touch instance/ (auth database) or any other directories.
|
||||
|
||||
All temp files are written to ~/.soosef/temp/ (soosef.paths.TEMP_DIR) so
|
||||
All temp files are written to ~/.fieldwitness/temp/ (fieldwitness.paths.TEMP_DIR) so
|
||||
that the killswitch's destroy_temp_files step covers them.
|
||||
"""
|
||||
|
||||
@@ -24,9 +24,9 @@ import time
|
||||
from pathlib import Path
|
||||
from threading import Lock
|
||||
|
||||
import soosef.paths as paths
|
||||
import fieldwitness.paths as paths
|
||||
|
||||
# Default temp directory — always under ~/.soosef/temp/ so the killswitch
|
||||
# Default temp directory — always under ~/.fieldwitness/temp/ so the killswitch
|
||||
# (which purges paths.TEMP_DIR) can reach every file written here.
|
||||
DEFAULT_TEMP_DIR: Path = paths.TEMP_DIR
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Account - Stegasoo{% endblock %}
|
||||
{% block title %}Account - Stego{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
@@ -269,16 +269,16 @@
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/auth.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/soosef.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/fieldwitness.js') }}"></script>
|
||||
{% if is_admin %}
|
||||
<script src="{{ url_for('static', filename='js/qrcode.min.js') }}"></script>
|
||||
{% endif %}
|
||||
<script>
|
||||
StegasooAuth.initPasswordConfirmation('accountForm', 'newPasswordInput', 'newPasswordConfirmInput');
|
||||
StegoAuth.initPasswordConfirmation('accountForm', 'newPasswordInput', 'newPasswordConfirmInput');
|
||||
|
||||
// Webcam QR scanning for channel key input (v4.1.5)
|
||||
document.getElementById('scanChannelKeyBtn')?.addEventListener('click', function() {
|
||||
Stegasoo.showQrScanner((text) => {
|
||||
Stego.showQrScanner((text) => {
|
||||
const input = document.getElementById('channelKeyInput');
|
||||
if (input) {
|
||||
// Clean and format the key
|
||||
@@ -294,7 +294,7 @@ document.getElementById('scanChannelKeyBtn')?.addEventListener('click', function
|
||||
|
||||
// Format channel key input as user types
|
||||
document.getElementById('channelKeyInput')?.addEventListener('input', function() {
|
||||
Stegasoo.formatChannelKeyInput(this);
|
||||
Stego.formatChannelKeyInput(this);
|
||||
});
|
||||
|
||||
function renameKey(keyId, currentName) {
|
||||
@@ -336,7 +336,7 @@ document.getElementById('qrDownload')?.addEventListener('click', function() {
|
||||
const keyName = document.getElementById('qrKeyName').textContent;
|
||||
if (canvas) {
|
||||
const link = document.createElement('a');
|
||||
link.download = 'stegasoo-channel-key-' + keyName.toLowerCase().replace(/\s+/g, '-') + '.png';
|
||||
link.download = 'stego-channel-key-' + keyName.toLowerCase().replace(/\s+/g, '-') + '.png';
|
||||
link.href = canvas.toDataURL('image/png');
|
||||
link.click();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Password Reset - Stegasoo{% endblock %}
|
||||
{% block title %}Password Reset - Stego{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Settings — SooSeF Admin{% endblock %}
|
||||
{% block title %}Settings — FieldWitness Admin{% endblock %}
|
||||
{% block content %}
|
||||
<h2><i class="bi bi-sliders me-2"></i>System Settings</h2>
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
System settings will be migrated from stegasoo's admin panel.
|
||||
System settings will be migrated from fieldwitness.stego's admin panel.
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}User Created - Stegasoo{% endblock %}
|
||||
{% block title %}User Created - Stego{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Add User - Stegasoo{% endblock %}
|
||||
{% block title %}Add User - Stego{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Manage Users - Stegasoo{% endblock %}
|
||||
{% block title %}Manage Users - Stego{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Attest Image — SooSeF{% endblock %}
|
||||
{% block title %}Attest Image — FieldWitness{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
@@ -18,7 +18,7 @@
|
||||
<div class="alert alert-warning">
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>
|
||||
<strong>No identity configured.</strong> Generate one from the
|
||||
<a href="/keys" class="alert-link">Keys page</a> or run <code>soosef init</code>.
|
||||
<a href="/keys" class="alert-link">Keys page</a> or run <code>fieldwitness init</code>.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Attestation Log — SooSeF{% endblock %}
|
||||
{% block title %}Attestation Log — FieldWitness{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Attestation Record — SooSeF{% endblock %}
|
||||
{% block title %}Attestation Record — FieldWitness{% endblock %}
|
||||
{% block content %}
|
||||
<h2><i class="bi bi-file-earmark-check me-2"></i>Attestation Record</h2>
|
||||
<p class="text-muted">Record ID: <code>{{ record_id }}</code></p>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Attestation Created — SooSeF{% endblock %}
|
||||
{% block title %}Attestation Created — FieldWitness{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Verify Image — SooSeF{% endblock %}
|
||||
{% block title %}Verify Image — FieldWitness{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Verification Result — SooSeF{% endblock %}
|
||||
{% block title %}Verification Result — FieldWitness{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}SooSeF{% endblock %}</title>
|
||||
<title>{% block title %}FieldWitness{% endblock %}</title>
|
||||
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='favicon.svg') }}">
|
||||
<link href="{{ url_for('static', filename='vendor/css/bootstrap.min.css') }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static', filename='vendor/css/bootstrap-icons.min.css') }}" rel="stylesheet">
|
||||
@@ -13,7 +13,7 @@
|
||||
<nav class="navbar navbar-expand-lg navbar-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/" style="padding-left: 6px; margin-right: 8px;">
|
||||
<strong>SooSeF</strong>
|
||||
<strong>FieldWitness</strong>
|
||||
</a>
|
||||
|
||||
{# Channel + Identity indicators #}
|
||||
@@ -40,7 +40,7 @@
|
||||
</li>
|
||||
|
||||
{% if not auth_enabled or is_authenticated %}
|
||||
{# ── Stegasoo ── #}
|
||||
{# ── Stego ── #}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link nav-expand" href="/encode"><i class="bi bi-lock"></i><span>Encode</span></a>
|
||||
</li>
|
||||
@@ -51,8 +51,8 @@
|
||||
<a class="nav-link nav-expand" href="/generate"><i class="bi bi-key"></i><span>Generate</span></a>
|
||||
</li>
|
||||
|
||||
{# ── Verisoo ── #}
|
||||
{% if has_verisoo %}
|
||||
{# ── Attest ── #}
|
||||
{% if has_attest %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link nav-expand" href="/attest"><i class="bi bi-patch-check"></i><span>Attest</span></a>
|
||||
</li>
|
||||
@@ -140,9 +140,9 @@
|
||||
<footer class="py-4 mt-5">
|
||||
<div class="container text-center text-muted">
|
||||
<small>
|
||||
SooSeF v{{ version }} — Soo Security Fieldkit
|
||||
FieldWitness v{{ version }} — FieldWitness
|
||||
<span class="mx-2">|</span>
|
||||
<span class="text-muted">Stegasoo + Verisoo</span>
|
||||
<span class="text-muted">Stego + Attest</span>
|
||||
</small>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Source Drop Box — SooSeF{% endblock %}
|
||||
{% block title %}Source Drop Box — FieldWitness{% endblock %}
|
||||
{% block content %}
|
||||
<h2><i class="bi bi-inbox me-2"></i>Source Drop Box</h2>
|
||||
<p class="text-muted">Create time-limited upload links for sources who cannot install SooSeF.</p>
|
||||
<p class="text-muted">Create time-limited upload links for sources who cannot install FieldWitness.</p>
|
||||
|
||||
<div class="card bg-dark mb-4">
|
||||
<div class="card-body">
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Federation — SooSeF{% endblock %}
|
||||
{% block title %}Federation — FieldWitness{% endblock %}
|
||||
{% block content %}
|
||||
<h2><i class="bi bi-diagram-3 me-2"></i>Federation</h2>
|
||||
<p class="text-muted">Gossip-based attestation sync between SooSeF instances.</p>
|
||||
<p class="text-muted">Gossip-based attestation sync between FieldWitness instances.</p>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-4">
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Keys — SooSeF{% endblock %}
|
||||
{% block title %}Keys — FieldWitness{% endblock %}
|
||||
{% block content %}
|
||||
<h2><i class="bi bi-key me-2"></i>Key Management</h2>
|
||||
<p class="text-muted">Manage Stegasoo channel keys and Verisoo Ed25519 identity.</p>
|
||||
<p class="text-muted">Manage Stego channel keys and Attest Ed25519 identity.</p>
|
||||
|
||||
<div class="row g-4">
|
||||
{# Channel Key #}
|
||||
@@ -13,7 +13,7 @@
|
||||
{% if keystore.has_channel_key %}
|
||||
<p class="text-muted small">
|
||||
Fingerprint: <code>{{ keystore.channel_fingerprint }}</code><br>
|
||||
Used for Stegasoo deployment isolation.
|
||||
Used for Stego deployment isolation.
|
||||
</p>
|
||||
{% else %}
|
||||
<p class="text-muted small">No channel key configured.</p>
|
||||
@@ -36,7 +36,7 @@
|
||||
{% if keystore.has_identity %}
|
||||
<p class="text-muted small">
|
||||
Fingerprint: <code>{{ keystore.identity_fingerprint }}</code><br>
|
||||
Used for Verisoo attestation signing.
|
||||
Used for Attest attestation signing.
|
||||
</p>
|
||||
{% else %}
|
||||
<p class="text-muted small">No identity configured.</p>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Killswitch — SooSeF{% endblock %}
|
||||
{% block title %}Killswitch — FieldWitness{% endblock %}
|
||||
{% block content %}
|
||||
<h2 class="text-danger"><i class="bi bi-exclamation-octagon me-2"></i>Emergency Killswitch</h2>
|
||||
<p class="text-muted">Destroy all key material and sensitive data. This action is irreversible.</p>
|
||||
@@ -9,7 +9,7 @@
|
||||
<h5 class="card-title text-danger">Destruction Order</h5>
|
||||
<ol class="text-muted small">
|
||||
<li>Ed25519 identity keys (signing identity)</li>
|
||||
<li>Stegasoo channel key (deployment binding)</li>
|
||||
<li>Stego channel key (deployment binding)</li>
|
||||
<li>Flask session secret (invalidates all sessions)</li>
|
||||
<li>Auth database (user accounts)</li>
|
||||
<li>Attestation log + index (provenance records)</li>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Fieldkit Status — SooSeF{% endblock %}
|
||||
{% block title %}Fieldkit Status — FieldWitness{% endblock %}
|
||||
{% block content %}
|
||||
<h2><i class="bi bi-speedometer2 me-2"></i>Fieldkit Status</h2>
|
||||
<p class="text-muted">Security monitors and system health.</p>
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}SooSeF — Soo Security Fieldkit{% endblock %}
|
||||
{% block title %}FieldWitness — FieldWitness{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="text-center mb-5">
|
||||
<h1 class="display-5 fw-bold">Soo Security Fieldkit</h1>
|
||||
<h1 class="display-5 fw-bold">FieldWitness</h1>
|
||||
<p class="lead text-muted">Offline-first security toolkit for field operations</p>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
{# ── Stegasoo Card ── #}
|
||||
{# ── Stego Card ── #}
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="card h-100 bg-dark border-secondary">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="bi bi-lock me-2 text-primary"></i>Encode</h5>
|
||||
<p class="card-text text-muted">Hide encrypted messages in images or audio using Stegasoo's hybrid authentication.</p>
|
||||
<p class="card-text text-muted">Hide encrypted messages in images or audio using Stego's hybrid authentication.</p>
|
||||
<a href="/encode" class="btn btn-outline-primary btn-sm">Encode Message</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -37,8 +37,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# ── Verisoo Cards ── #}
|
||||
{% if has_verisoo %}
|
||||
{# ── Attest Cards ── #}
|
||||
{% if has_attest %}
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="card h-100 bg-dark border-secondary">
|
||||
<div class="card-body">
|
||||
@@ -102,10 +102,10 @@
|
||||
<i class="bi bi-image me-1"></i>DCT: {{ 'Available' if has_dct else 'Unavailable' }}
|
||||
</span>
|
||||
</div>
|
||||
{% if has_verisoo %}
|
||||
{% if has_attest %}
|
||||
<div class="col-auto">
|
||||
<span class="badge bg-success">
|
||||
<i class="bi bi-patch-check me-1"></i>Verisoo: Active
|
||||
<i class="bi bi-patch-check me-1"></i>Attest: Active
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Login - Stegasoo{% endblock %}
|
||||
{% block title %}Login - Stego{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Password Recovery - Stegasoo{% endblock %}
|
||||
{% block title %}Password Recovery - Stego{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
@@ -116,7 +116,7 @@
|
||||
<div class="alert alert-warning mt-4 small">
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>
|
||||
<strong>Note:</strong> This will reset the admin password. If you don't have a valid recovery key,
|
||||
you'll need to delete the database and reconfigure Stegasoo.
|
||||
you'll need to delete the database and reconfigure Stego.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -125,6 +125,6 @@
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/auth.js') }}"></script>
|
||||
<script>
|
||||
StegasooAuth.initPasswordConfirmation('recoverForm', 'passwordInput', 'passwordConfirmInput');
|
||||
StegoAuth.initPasswordConfirmation('recoverForm', 'passwordInput', 'passwordConfirmInput');
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Regenerate Recovery Key - Stegasoo{% endblock %}
|
||||
{% block title %}Regenerate Recovery Key - Stego{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
@@ -142,7 +142,7 @@ function copyToClipboard() {
|
||||
// Download as text file
|
||||
function downloadTextFile() {
|
||||
const key = document.getElementById('recoveryKey').value;
|
||||
const content = `Stegasoo Recovery Key
|
||||
const content = `Stego Recovery Key
|
||||
=====================
|
||||
|
||||
${key}
|
||||
@@ -158,7 +158,7 @@ Generated: ${new Date().toISOString()}
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'stegasoo-recovery-key.txt';
|
||||
a.download = 'stego-recovery-key.txt';
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
@@ -170,7 +170,7 @@ function downloadQRImage() {
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.href = img.src;
|
||||
a.download = 'stegasoo-recovery-qr.png';
|
||||
a.download = 'stego-recovery-qr.png';
|
||||
a.click();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Setup - Stegasoo{% endblock %}
|
||||
{% block title %}Setup - Stego{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
@@ -12,7 +12,7 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted text-center mb-4">
|
||||
Welcome to Stegasoo! Create your admin account to get started.
|
||||
Welcome to Stego! Create your admin account to get started.
|
||||
</p>
|
||||
|
||||
<form method="POST" action="{{ url_for('setup') }}" id="setupForm">
|
||||
@@ -72,6 +72,6 @@
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/auth.js') }}"></script>
|
||||
<script>
|
||||
StegasooAuth.initPasswordConfirmation('setupForm', 'passwordInput', 'passwordConfirmInput');
|
||||
StegoAuth.initPasswordConfirmation('setupForm', 'passwordInput', 'passwordConfirmInput');
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Recovery Key Setup - Stegasoo{% endblock %}
|
||||
{% block title %}Recovery Key Setup - Stego{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
@@ -135,7 +135,7 @@ function copyToClipboard() {
|
||||
// Download as text file
|
||||
function downloadTextFile() {
|
||||
const key = document.getElementById('recoveryKey').value;
|
||||
const content = `Stegasoo Recovery Key
|
||||
const content = `Stego Recovery Key
|
||||
=====================
|
||||
|
||||
${key}
|
||||
@@ -151,7 +151,7 @@ Generated: ${new Date().toISOString()}
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'stegasoo-recovery-key.txt';
|
||||
a.download = 'stego-recovery-key.txt';
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
@@ -163,7 +163,7 @@ function downloadQRImage() {
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.href = img.src;
|
||||
a.download = 'stegasoo-recovery-qr.png';
|
||||
a.download = 'stego-recovery-qr.png';
|
||||
a.click();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}About - Stegasoo{% endblock %}
|
||||
{% block title %}About - Stego{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-10">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-info-circle me-2"></i>About Stegasoo</h5>
|
||||
<h5 class="mb-0"><i class="bi bi-info-circle me-2"></i>About Stego</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="lead">
|
||||
Stegasoo hides encrypted messages and files inside images using multi-factor authentication.
|
||||
Stego hides encrypted messages and files inside images using multi-factor authentication.
|
||||
</p>
|
||||
|
||||
<h6 class="text-primary mt-4 mb-3">Features</h6>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Decode Message - Stegasoo{% endblock %}
|
||||
{% block title %}Decode Message - Stego{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<style>
|
||||
@@ -487,7 +487,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/soosef.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/fieldwitness.js') }}"></script>
|
||||
<script>
|
||||
// ============================================================================
|
||||
// MODE HINT - Dynamic text based on selected extraction mode
|
||||
@@ -677,6 +677,6 @@ if (document.getElementById('modeDct')?.disabled) {
|
||||
// LOADING STATE
|
||||
// ============================================================================
|
||||
|
||||
Stegasoo.initFormLoading('decodeForm', 'decodeBtn', 'Decoding...');
|
||||
Stego.initFormLoading('decodeForm', 'decodeBtn', 'Decoding...');
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Encode Message - Stegasoo{% endblock %}
|
||||
{% block title %}Encode Message - Stego{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<style>
|
||||
@@ -507,7 +507,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/soosef.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/fieldwitness.js') }}"></script>
|
||||
<script>
|
||||
// ============================================================================
|
||||
// MODE HINT - Dynamic text based on selected embedding mode
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Encode Success - Stegasoo{% endblock %}
|
||||
{% block title %}Encode Success - Stego{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
@@ -218,7 +218,7 @@ if (navigator.share && navigator.canShare) {
|
||||
try {
|
||||
await navigator.share({
|
||||
files: [file],
|
||||
title: 'Stegasoo Image',
|
||||
title: 'Stego Image',
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.name !== 'AbortError') {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Generate Credentials - Stegasoo{% endblock %}
|
||||
{% block title %}Generate Credentials - Stego{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center" data-page="generate">
|
||||
@@ -500,7 +500,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/soosef.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/fieldwitness.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/generate.js') }}"></script>
|
||||
{% if generated %}
|
||||
<script>
|
||||
@@ -508,7 +508,7 @@
|
||||
const passphraseWords = '{{ passphrase|default("", true) }}'.split(' ').filter(w => w.length > 0);
|
||||
|
||||
function copyPin() {
|
||||
Stegasoo.copyToClipboard(
|
||||
Stego.copyToClipboard(
|
||||
'{{ pin|default("", true) }}',
|
||||
document.getElementById('pinCopyIcon'),
|
||||
document.getElementById('pinCopyText')
|
||||
@@ -516,7 +516,7 @@ function copyPin() {
|
||||
}
|
||||
|
||||
function copyPassphrase() {
|
||||
Stegasoo.copyToClipboard(
|
||||
Stego.copyToClipboard(
|
||||
'{{ passphrase|default("", true) }}',
|
||||
document.getElementById('passphraseCopyIcon'),
|
||||
document.getElementById('passphraseCopyText')
|
||||
@@ -524,11 +524,11 @@ function copyPassphrase() {
|
||||
}
|
||||
|
||||
function toggleMemoryAid() {
|
||||
StegasooGenerate.toggleMemoryAid(passphraseWords);
|
||||
StegoGenerate.toggleMemoryAid(passphraseWords);
|
||||
}
|
||||
|
||||
function regenerateStory() {
|
||||
StegasooGenerate.regenerateStory(passphraseWords);
|
||||
StegoGenerate.regenerateStory(passphraseWords);
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Tools - Stegasoo{% endblock %}
|
||||
{% block title %}Tools - Stego{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
|
||||
Reference in New Issue
Block a user