diff --git a/EncryptDecrypt/__init__.py b/EncryptDecrypt/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/EncryptDecrypt/app.py b/EncryptDecrypt/app.py new file mode 100644 index 0000000..208c1cc --- /dev/null +++ b/EncryptDecrypt/app.py @@ -0,0 +1,346 @@ +import sys +import os +import threading +import json +from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, send_file +import subprocess +# extend PYTHONPATH to project root +dir_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +sys.path.insert(0, dir_root) + +# Import container for dependency injection +from framework.Container import Container +# Import key management and cryptors from NessKeys +from NessKeys.KeyManager import KeyManager +from services.node import node as NodesUpdater +from NessKeys.cryptors.Aes import Aes as AESCipher +from NessKeys.cryptors.Salsa20 import Salsa20 as Salsa20Cipher +from NessKeys.JsonChecker.Checker import JsonChecker + +# Initialize Flask app +def create_app(): + app = Flask(__name__) + # Configuration + app.config.from_mapping( + SECRET_KEY=os.environ.get('FLASK_SECRET', 'supersecret'), + UPLOAD_FOLDER=os.environ.get('UPLOAD_FOLDER', os.path.join(dir_root, 'uploads')), + NODES_JSON=os.environ.get('NODES_JSON', os.path.join(dir_root, 'nodes.json')), + MAX_CONTENT_LENGTH=16 * 1024 * 1024 # 16MB limit + ) + + # Ensure upload folder exists + os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) + + # Thread-safe storage for background tasks + tasks_lock = threading.Lock() + running_tasks = {} + + # Helpers + def get_km(): + return Container.KeyManager() + filemgr = Container.FileManager() + + # ----- Routes ----- + + @app.route('/') + def index(): + return render_template('index.html') + + @app.route('/keygen', methods=['GET', 'POST']) + def keygen(): + if request.method == 'GET': + return render_template('keygen.html') + username = request.form['username'] + entropy = int(request.form.get('entropy', 256)) + try: + km = get_km() + km.createUsersKey(username, entropy) + flash(f'User key generated: {username}', 'success') + except Exception as e: + flash(f'Error generating key: {e}', 'danger') + return redirect(url_for('keygen')) + + @app.route('/nodes', methods=['GET', 'POST']) + def nodes(): + km = get_km() + if request.method == 'POST': + mode = request.form['mode'] + try: + if mode == 'public': + NodesUpdater.fetch_public_list() + flash('Public list fetched', 'success') + else: + rpc = { + 'host': request.form['rpc_host'], + 'port': request.form['rpc_port'], + 'user': request.form['rpc_user'], + 'password': request.form['rpc_password'] + } + NodesUpdater.update_blockchain(rpc, app.config['NODES_JSON']) + flash('Updated from RPC', 'success') + except Exception as e: + flash(f'Error managing nodes: {e}', 'danger') + return redirect(url_for('nodes')) + nodes = km.listNodes() + return render_template('nodes.html', nodes=nodes) + + @app.route('/node', methods=['POST']) + def select_node(): + """Mark a node as selected in the nodes.json file, creating it if missing.""" + node_url = request.form['node_url'] + nodes_file = app.config['NODES_JSON'] + try: + # Load or initialize nodes JSON + if not os.path.exists(nodes_file): + data = {'nodes': [], 'selected_node': node_url} + else: + with open(nodes_file, 'r') as f: + try: + data = json.load(f) + except json.JSONDecodeError: + data = {} + # Ensure dict + if not isinstance(data, dict): + data = {'nodes': data} + data['selected_node'] = node_url + # Write back + with open(nodes_file, 'w') as f: + json.dump(data, f, indent=2) + flash(f'Node selected: {node_url}', 'success') + except Exception as e: + flash(f'Error selecting node: {e}', 'danger') + return redirect(url_for('nodes')) + + @app.route('/user', methods=['GET', 'POST']) + def user(): + # POST: perform sel | nvs | worm via user.py + if request.method == 'POST': + username = request.form.get('username', '').strip() + action = request.form.get('action', '').strip() + + if not username: + flash('Please select a user first.', 'warning') + return redirect(url_for('user')) + + if action not in ('sel', 'nvs', 'worm'): + flash('Invalid action.', 'danger') + return redirect(url_for('user')) + + cli = os.path.join(dir_root, 'user.py') + cmd = [sys.executable, cli, action, username] + + try: + result = subprocess.run( + cmd, + cwd=dir_root, + capture_output=True, + text=True, + check=True + ) + # Use stdout if available, else fallback to a canned message + out = result.stdout.strip() or f'User {action} completed for {username}.' + flash(out, 'success') + except subprocess.CalledProcessError as e: + err = e.stderr.strip() or e.stdout.strip() or str(e) + flash(f'Error ({action}): {err}', 'danger') + + return redirect(url_for('user')) + + # GET: fetch users via `user.py ls` + users = [] + cli = os.path.join(dir_root, 'user.py') + try: + result = subprocess.run( + [sys.executable, cli, 'ls'], + cwd=dir_root, + capture_output=True, + text=True, + check=True + ) + for line in result.stdout.splitlines(): + line = line.strip() + # Lines that start with '|' and contain real content + if line.startswith('|') and not set(line.strip()).issubset({'|','-'}): + name = line.strip('|').strip() + # strip CLI arrows: ==> user <== + name = name.replace('==>', '').replace('<==', '').strip() + if name.lower() != 'username': + users.append(name) + except subprocess.CalledProcessError as e: + flash('Failed to fetch users. Please ensure user.py is working.', 'danger') + + return render_template('user.html', users=users) + + @app.route('/publish', methods=['GET', 'POST']) + def publish(): + if request.method == 'GET': + return render_template('publish.html') + action = request.form['action'] + try: + km = get_km() + if action == 'user': + km.prepareUserNVS() + elif action == 'worm': + km.prepareWormNVS() + elif action == 'nodes': + km.prepareNodesNVS() + flash('Prepared NVS records', 'success') + except Exception as e: + flash(f'Error preparing NVS: {e}', 'danger') + return redirect(url_for('publish')) + + # File system operations + @app.route('/upload', methods=['POST']) + def upload(): + file = request.files['file'] + dest = request.form.get('destination', '') + try: + filemgr.upload(file, dest) + flash('Upload successful', 'success') + except Exception as e: + flash(f'Error uploading: {e}', 'danger') + return redirect(url_for('index')) + + @app.route('/download') + def download(): + path = request.args.get('path') + try: + return filemgr.download(path) + except Exception as e: + flash(f'Error downloading: {e}', 'danger') + return redirect(url_for('index')) + + @app.route('/mkdir', methods=['POST']) + def mkdir(): + path = request.form['path'] + try: + filemgr.mkdir(path) + flash('Directory created', 'success') + except Exception as e: + flash(f'Error: {e}', 'danger') + return redirect(url_for('index')) + + @app.route('/rmdir', methods=['POST']) + def rmdir(): + path = request.form['path'] + try: + filemgr.rmdir(path) + flash('Directory removed', 'success') + except Exception as e: + flash(f'Error: {e}', 'danger') + return redirect(url_for('index')) + + @app.route('/ls') + def ls(): + path = request.args.get('path', '') + try: + return jsonify(filemgr.ls(path)) + except Exception as e: + return jsonify({'error': str(e)}) + + @app.route('/move', methods=['POST']) + def move(): + src = request.form['src'] + dst = request.form['dst'] + try: + filemgr.move(src, dst) + flash('Move successful', 'success') + except Exception as e: + flash(f'Error: {e}', 'danger') + return redirect(url_for('index')) + + @app.route('/remove', methods=['POST']) + def remove(): + path = request.form['path'] + try: + filemgr.remove(path) + flash('Removed', 'success') + except Exception as e: + flash(f'Error: {e}', 'danger') + return redirect(url_for('index')) + + @app.route('/tree') + def tree(): + root = request.args.get('root', '') + try: + return jsonify(filemgr.tree(root)) + except Exception as e: + return jsonify({'error': str(e)}) + + @app.route('/quota') + def quota(): + try: + return jsonify(filemgr.quota()) + except Exception as e: + return jsonify({'error': str(e)}) + + @app.route('/fileinfo') + def fileinfo(): + path = request.args.get('path') + try: + return jsonify(filemgr.fileinfo(path)) + except Exception as e: + return jsonify({'error': str(e)}) + + # Encryption/Decryption + @app.route('/encrypt', methods=['GET', 'POST'], endpoint='encrypt_route') + def encrypt(): + if request.method == 'GET': + return render_template('encrypt.html') + algorithm = request.form['algorithm'] + file = request.files['file'] + km = get_km() + key = km.getCurrentKey() # uses selected user context + cipher = AESCipher(key) if algorithm == 'aes' else Salsa20Cipher(key) + infile = os.path.join(app.config['UPLOAD_FOLDER'], file.filename) + file.save(infile) + outfile = cipher.encrypt_file(infile) + metadata = cipher.get_metadata() + return render_template('encrypt_result.html', filename=os.path.basename(outfile), metadata=metadata) + + @app.route('/decrypt', methods=['GET', 'POST'], endpoint='decrypt_route') + def decrypt(): + if request.method == 'GET': + return render_template('decrypt.html') + algorithm = request.form['algorithm'] + key_hex = request.form['key'] + nonce = request.form.get('nonce') + iv = request.form.get('iv') + file = request.files['file'] + key = bytes.fromhex(key_hex) + cipher = AESCipher(key, iv=bytes.fromhex(iv)) if algorithm == 'aes' else Salsa20Cipher(key, nonce=bytes.fromhex(nonce)) + infile = os.path.join(app.config['UPLOAD_FOLDER'], file.filename) + file.save(infile) + outfile = cipher.decrypt_file(infile) + return render_template('decrypt_result.html', filename=os.path.basename(outfile)) + + # Background tasks + @app.route('/task//start', methods=['POST']) + def task_start(task_id): + with tasks_lock: + running_tasks[task_id] = {'status': 'running', 'progress': 0} + return jsonify(running_tasks[task_id]) + + @app.route('/task//pause', methods=['POST']) + def task_pause(task_id): + with tasks_lock: + if task_id in running_tasks: + running_tasks[task_id]['status'] = 'paused' + return jsonify(running_tasks.get(task_id, {})) + + @app.route('/task//resume', methods=['POST']) + def task_resume(task_id): + with tasks_lock: + if task_id in running_tasks: + running_tasks[task_id]['status'] = 'running' + return jsonify(running_tasks.get(task_id, {})) + + @app.route('/task//progress', methods=['GET']) + def task_progress(task_id): + return jsonify(running_tasks.get(task_id, {'status': 'unknown', 'progress': 0})) + + return app + +if __name__ == '__main__': + app = create_app() + app.run(host='0.0.0.0', port=5000, debug=True) diff --git a/EncryptDecrypt/static/css/styles.css b/EncryptDecrypt/static/css/styles.css new file mode 100644 index 0000000..ec8659d --- /dev/null +++ b/EncryptDecrypt/static/css/styles.css @@ -0,0 +1,82 @@ +body { + background: #101c2a; color: #e0e0e0; + font-family: 'Fira Sans', Arial, sans-serif; + margin: 0; + max-width: 700px; + margin: 40px auto; + background: #171f2e; + border-radius: 12px; + box-shadow: 0 0 12px #0008; + padding: 2em 2.5em; + + } +/*.container { + max-width: 700px; + margin: 40px auto; + background: #171f2e; + border-radius: 12px; + box-shadow: 0 0 12px #0008; + padding: 2em 2.5em; +}*/ +h1 { + color: #6ec6ff; + text-align: center; + } + + nav a { + color: #2196f3; + } + nav a:hover { + color: #e0e0e0; + } + button .select { + padding: 0.5em 1em; + } + button:hover { + background-color: #e0e0e0; + } +label { + display: block; + margin-top: 1.3em; + font-weight: bold; +} +input, +textarea, +select { + width: 100%; + margin-top: 0.3em; + padding: 0.5em; + font-size: 1em; + border-radius: 6px; + border: 1px solid #28334a; + background: #101c2a; + color: #e0e0e0; +} +button { + margin-top: 1.5em; + padding: 0.7em 2em; + border: none; + border-radius: 6px; + background: #6ec6ff; + color: #101c2a; + font-weight: bold; + cursor: pointer; + font-size: 1.1em; +} +button:active { + background: #2196f3; +} +.flashes { + list-style: none; + padding: 0; +} +.flashes li { + margin-bottom: 0.5em; +} +.flashes .success { + color: #6efc97; +} +.flashes .danger { + color: #fc6e6e; +} +@media (max-width: 800px) { .container { padding: 1em 0.5em; } } \ No newline at end of file diff --git a/EncryptDecrypt/static/script.js b/EncryptDecrypt/static/script.js new file mode 100644 index 0000000..a779dc5 --- /dev/null +++ b/EncryptDecrypt/static/script.js @@ -0,0 +1,38 @@ +$(document).ready(function() { + // Toggle RPC fields in nodes form + $('input[name="mode"]').change(function() { + if ($(this).val() === 'rpc') { + $('#rpc-fields').show(); + } else { + $('#rpc-fields').hide(); + } + }); + + // Handle task controls (example) + $('.task-start').click(function() { + const taskId = $(this).data('task-id'); + $.post(`/task/${taskId}/start`, function(res) { + console.log('Started:', res); + }); + }); + $('.task-pause').click(function() { + const taskId = $(this).data('task-id'); + $.post(`/task/${taskId}/pause`, function(res) { + console.log('Paused:', res); + }); + }); + $('.task-resume').click(function() { + const taskId = $(this).data('task-id'); + $.post(`/task/${taskId}/resume`, function(res) { + console.log('Resumed:', res); + }); + }); + + // Example: poll progress + function pollProgress(taskId) { + $.get(`/task/${taskId}/progress`, function(res) { + $(`#progress-${taskId}`).text(res.progress + '% - ' + res.status); + if (res.status === 'running') setTimeout(() => pollProgress(taskId), 1000); + }); + } + }); \ No newline at end of file diff --git a/EncryptDecrypt/templates/decrypt.html b/EncryptDecrypt/templates/decrypt.html new file mode 100644 index 0000000..f044fec --- /dev/null +++ b/EncryptDecrypt/templates/decrypt.html @@ -0,0 +1,17 @@ +{% extends 'layout.html' %} +{% block content %} +

Decrypt File

+
+
+
+
+
+
+ +
+{% endblock %} \ No newline at end of file diff --git a/EncryptDecrypt/templates/encrypt.html b/EncryptDecrypt/templates/encrypt.html new file mode 100644 index 0000000..d336edf --- /dev/null +++ b/EncryptDecrypt/templates/encrypt.html @@ -0,0 +1,14 @@ +{% extends 'layout.html' %} +{% block content %} +

Encrypt File

+
+
+
+ +
+{% endblock %} \ No newline at end of file diff --git a/EncryptDecrypt/templates/encrypt_result.html b/EncryptDecrypt/templates/encrypt_result.html new file mode 100644 index 0000000..7a435af --- /dev/null +++ b/EncryptDecrypt/templates/encrypt_result.html @@ -0,0 +1,11 @@ +{% extends 'layout.html' %} +{% block content %} +

Encryption Result

+

Encrypted file: {{ filename }}

+

Metadata

+
    + {% for key, val in metadata.items() %} +
  • {{ key }}: {{ val }}
  • + {% endfor %} +
+{% endblock %} \ No newline at end of file diff --git a/EncryptDecrypt/templates/index.html b/EncryptDecrypt/templates/index.html new file mode 100644 index 0000000..6aefce7 --- /dev/null +++ b/EncryptDecrypt/templates/index.html @@ -0,0 +1,6 @@ +{% extends 'layout.html' %} +{% block content %} +

Welcome to PrivateNess Network

+

Use the navigation menu to encrypt, decrypt, manage keys, nodes, and users.

+

Or start encrypting now.

+{% endblock %} \ No newline at end of file diff --git a/EncryptDecrypt/templates/keygen.html b/EncryptDecrypt/templates/keygen.html new file mode 100644 index 0000000..0726c84 --- /dev/null +++ b/EncryptDecrypt/templates/keygen.html @@ -0,0 +1,9 @@ +{% extends 'layout.html' %} +{% block content %} +

Generate User Key

+
+
+
+ +
+{% endblock %} \ No newline at end of file diff --git a/EncryptDecrypt/templates/layout.html b/EncryptDecrypt/templates/layout.html new file mode 100644 index 0000000..a435d70 --- /dev/null +++ b/EncryptDecrypt/templates/layout.html @@ -0,0 +1,31 @@ + + + + + EncryptDecrypt + + + +

PrivateNess Data Security

+ +
+ {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} +
    + {% for category, msg in messages %} +
  • {{ msg }}
  • + {% endfor %} +
+ {% endif %} + {% endwith %} + {% block content %}{% endblock %} +
+ + \ No newline at end of file diff --git a/EncryptDecrypt/templates/nodes.html b/EncryptDecrypt/templates/nodes.html new file mode 100644 index 0000000..5300e04 --- /dev/null +++ b/EncryptDecrypt/templates/nodes.html @@ -0,0 +1,33 @@ +{% extends 'layout.html' %} +{% block content %} +

Manage Nodes

+
+ +
+ + +
+ +

Available Nodes:

+
    + {% for node in nodes %} +
  • + {{ node.url }} ({{ node.network }}, Services: {{ node.services|join(', ') }}) +
    + + +
    +
  • + {% endfor %} +
+ +{% endblock %} \ No newline at end of file diff --git a/EncryptDecrypt/templates/publish.html b/EncryptDecrypt/templates/publish.html new file mode 100644 index 0000000..83ae581 --- /dev/null +++ b/EncryptDecrypt/templates/publish.html @@ -0,0 +1,9 @@ +{% extends 'layout.html' %} +{% block content %} +

Prepare NVS Records

+
+ + + +
+{% endblock %} \ No newline at end of file diff --git a/EncryptDecrypt/templates/user.html b/EncryptDecrypt/templates/user.html new file mode 100644 index 0000000..c1bcd9c --- /dev/null +++ b/EncryptDecrypt/templates/user.html @@ -0,0 +1,18 @@ +{% extends 'layout.html' %} +{% block content %} +

User Management

+
+ + + +
+ + + +
+
+{% endblock %} diff --git a/EncryptDecrypt/utils/db.py b/EncryptDecrypt/utils/db.py new file mode 100644 index 0000000..6e13800 --- /dev/null +++ b/EncryptDecrypt/utils/db.py @@ -0,0 +1,24 @@ +import sqlite3 +from flask import g + +DATABASE = 'filemeta.db' + +def get_db(): + db = getattr(g, '_database', None) + if db is None: + db = g._database = sqlite3.connect(DATABASE) + db.row_factory = sqlite3.Row + return db + +def init_db(): + with sqlite3.connect(DATABASE) as db: + db.execute(''' + CREATE TABLE IF NOT EXISTS files ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + shadowname TEXT UNIQUE, + filename TEXT, + algorithm TEXT, + uploaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + db.commit() diff --git a/NessKeys/Prng.py b/NessKeys/Prng.py index e1b9a64..ee6bd3b 100644 --- a/NessKeys/Prng.py +++ b/NessKeys/Prng.py @@ -162,7 +162,11 @@ def add_entropy(self, *arguments): for i in range(0, len(arguments) - 1): args.append(arguments[i]) - self.hash(str(time.time()) + ' ' + ''.join(args) + str(random.random())) + self.hash( + str(time.time()) + ' ' + + ''.join(a.hex() if isinstance(a, bytes) else str(a) for a in args) + + str(random.random())) + def generate(self, rng: int, count: int): result = [] diff --git a/NessKeys/__init__.py b/NessKeys/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/NessKeys/cryptors/__init__.py b/NessKeys/cryptors/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/framework/__init__.py b/framework/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/services/__init__.py b/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/services/web/index.html b/services/web/index.html new file mode 100644 index 0000000..d35dbb9 --- /dev/null +++ b/services/web/index.html @@ -0,0 +1,70 @@ + + + + + + Node List + + + + +

Node List:

+
+ + + + + + + + +
Node NameNode URL
+
+ + + + \ No newline at end of file diff --git a/services/web/web_client.py b/services/web/web_client.py new file mode 100644 index 0000000..18166ac --- /dev/null +++ b/services/web/web_client.py @@ -0,0 +1,65 @@ +import sys +import os +print(sys.path) +# Add the parent directory of NessKeys to the module search path +parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) +sys.path.insert(0, parent_dir) + +from flask import Flask, render_template +from NessKeys.KeyManager import KeyManager +from NessKeys.StorageJson import StorageJson +from NessKeys.KeyMakerNess import KeyMakerNess +from NessKeys.FileManager import FileManager +from NessKeys.NodeManager import NodeManager +from NessKeys.ConsoleOutput import ConsoleOutput +from NessKeys.interfaces.output import output as ioutput +from services.node import node +from services.files import files + +app = Flask(__name__, template_folder='../services/web') + +class Container: + def KeyManager(self) -> KeyManager: + storage = StorageJson() + maker = KeyMakerNess() + return KeyManager(storage, maker) + + def NodeService(self) -> node: + km = self.KeyManager() + return node(km.getUsersKey(), km.getNodesKey(), km.getMyNodesKey(), self.output()) + + def FilesService(self) -> files: + km = self.KeyManager() + return files(km.getUsersKey(), km.getNodesKey(), km.getMyNodesKey(), km.getFilesKey(), km.getDirectoriesKey(), km.getBackupKey(), self.output()) + + def NodeManager(self) -> NodeManager: + return NodeManager(self.KeyManager(), self.NodeService()) + + def FileManager(self) -> FileManager: + def ns(): + return self.NodeService() + + def fs(): + return self.FilesService() + + return FileManager(self.KeyManager(), ns, fs) + + def output(self) -> ioutput: + return ConsoleOutput() + + def get_node_list(self): + km = self.KeyManager() + node_service = self.NodeService() + node_list = node_service.get_nodes() + return node_list + + +@app.route('/') +def index(): + container = Container() + node_list = container.get_node_list() + return render_template('index.html', node_list=node_list) + + +if __name__ == '__main__': + app.run(debug=True) \ No newline at end of file