diff --git a/delivery1/client/bin/rep_add_doc b/delivery1/client/bin/rep_add_doc index 209e72d..8576080 100755 --- a/delivery1/client/bin/rep_add_doc +++ b/delivery1/client/bin/rep_add_doc @@ -51,13 +51,14 @@ def addDoc(args): args.session = json.load(f) #Encrypt content - key, nonce = encrypt_file(BASE_DIR + args.file, BASE_DIR + 'encryptedText') + key, content, nonce = encrypt_file(BASE_DIR + args.file, BASE_DIR + 'encryptedText') #Upload document metadata - doc = {'document_name' : args.name, 'key' : key, 'alg' : 'AES-CFB', 'nonce' : nonce } + doc = {'document_name' : args.name, 'key' : key.hex(), 'alg' : 'AES-CFB', 'nonce' : nonce.hex() } try: - req = requests.post(f'http://{state['REP_ADDRESS']}/file/upload', json=json.dumps(doc), headers={'Authorization': args.session['token']}) + req = requests.post(f'http://{state['REP_ADDRESS']}/file/upload/metadata', json=json.dumps(doc), + headers={'Authorization': args.session['token']}) req.raise_for_status() except requests.exceptions.RequestException as errex: @@ -65,16 +66,12 @@ def addDoc(args): sys.exit(-1) #Upload Document content - - with open(BASE_DIR + 'encryptedText', 'rb') as f: - content = f.read() - - file = {'file' : open(BASE_DIR + args.file, 'rb')} + file = {'file' : (BASE_DIR + args.file, content)} try: - req = requests.post(f'http://{state['REP_ADDRESS']}/file/upload', + req = requests.post(f'http://{state['REP_ADDRESS']}/file/upload/content', files=file, - headers={'Authorization': args.session['token'], 'File-Checksum' : content.hex()}) + headers={'Authorization': args.session['token'], 'File-Checksum' : digest.get_hash(content)}) req.raise_for_status() except requests.exceptions.RequestException as errex: diff --git a/delivery1/client/bin/rep_add_subject b/delivery1/client/bin/rep_add_subject index 52b4d43..0ff3c2c 100755 --- a/delivery1/client/bin/rep_add_subject +++ b/delivery1/client/bin/rep_add_subject @@ -8,7 +8,8 @@ import argparse from subject import main -from lib import encryption_functs +sys.path.append(os.path.abspath('../../')) +from lib import asymmetric_functs logging.basicConfig(format='%(levelname)s\t- %(message)s') logger = logging.getLogger() @@ -50,13 +51,15 @@ def addSubject(args): logger.error("File '" + args.file + "' not found") sys.exit(1) - subject = {'username' : args.username, 'name' : args.name, 'email' : args.email, 'credentials_file' : args.credentials} + pubKey = asymmetric_functs.load_public_key(BASE_DIR + args.credentials) + + subject = {'username' : args.username, 'full_name' : args.name, 'email' : args.email, 'public_key' : pubKey} try: req = requests.post(f'http://{state['REP_ADDRESS']}/user/create', json=json.dumps(subject), headers={'Authorization': args.session['token']}) req.raise_for_status() - except requests.exceptions.RequestException as errex: + except requests.exceptions.RequestException as errex: logger.error("Failed to obtain response from server.") sys.exit(-1) diff --git a/delivery1/client/bin/rep_create_session b/delivery1/client/bin/rep_create_session index 126a947..ea3d781 100755 --- a/delivery1/client/bin/rep_create_session +++ b/delivery1/client/bin/rep_create_session @@ -56,7 +56,7 @@ def createSession(args): sys.exit(-1) with open(BASE_DIR + args.session, 'w') as f: - f.write(req.json()) + json.dump(req.json(), f) sys.exit(0) diff --git a/delivery1/client/bin/rep_decrypt_file b/delivery1/client/bin/rep_decrypt_file index 9771c32..373ca3a 100755 --- a/delivery1/client/bin/rep_decrypt_file +++ b/delivery1/client/bin/rep_decrypt_file @@ -5,6 +5,7 @@ import logging import argparse import json +sys.path.append(os.path.abspath("../../")) from lib import symmetric_encryption logging.basicConfig(format='%(levelname)s\t- %(message)s') @@ -33,14 +34,15 @@ def decryptFile(args): logger.error("File '" + args.encrypted + "' not found.") sys.exit(1) - if (not os.path.isfile(BASE_DIR + args.metadata)): - logger.error("File '" + args.metadata + "' not found.") - sys.exit(1) + # if (not os.path.isfile(BASE_DIR + args.metadata)): + # logger.error("File '" + args.metadata + "' not found.") + # sys.exit(1) #Decrypt file - metadata = json.loads(BASE_DIR + args.metadata) + print(args.metadata) + metadata = json.loads(args.metadata) - content = symmetric_encryption.decrypt_file(args.encrypted) + content = symmetric_encryption.decrypt_file(metadata['nonce'].encode(), metadata['key'].encode(), BASE_DIR + args.encrypted) # Send decrypted content to stdout sys.stdout.write(content) diff --git a/delivery1/client/bin/rep_delete_doc b/delivery1/client/bin/rep_delete_doc index 4f78ec4..dc1ccf6 100755 --- a/delivery1/client/bin/rep_delete_doc +++ b/delivery1/client/bin/rep_delete_doc @@ -5,6 +5,8 @@ import logging import requests import json import argparse + +sys.path.append(os.path.abspath("../../")) from lib import digest from subject import main @@ -43,7 +45,6 @@ def delDoc(args): args.session = json.load(f) doc_name = digest.get_hash(bytes(args.name, encoding='utf-8')) - doc = {'document_name' : doc_name} try: req = requests.post(f'http://{state['REP_ADDRESS']}/file/delete/' + doc_name, headers={'Authorization': args.session['token']}) diff --git a/delivery1/client/bin/rep_get_doc_file b/delivery1/client/bin/rep_get_doc_file index c10059e..d8f1037 100755 --- a/delivery1/client/bin/rep_get_doc_file +++ b/delivery1/client/bin/rep_get_doc_file @@ -8,6 +8,7 @@ import argparse from subject import main +sys.path.append(os.path.abspath("../../")) from lib import digest from lib import symmetric_encryption @@ -47,10 +48,10 @@ def getDoc(args): # Get Document metadata - doc_name = digest.get_hash(args.name) + doc_name = digest.get_hash(bytes(args.name, encoding='utf-8')) try: - metadata = requests.post(f'http://{state['REP_ADDRESS']}/file/' + doc_name, headers={'Authorization': args.session['token']}) + metadata = requests.get(f'http://{state['REP_ADDRESS']}/file/get/' + doc_name + '/metadata', headers={'Authorization': args.session['token']}) metadata.raise_for_status() except requests.exceptions.RequestException as errex: @@ -76,7 +77,6 @@ def getDoc(args): content = symmetric_encryption.decrypt_file(file) - if args.output: with open(BASE_DIR + args.output, 'w') as f: f.write(content) diff --git a/delivery1/client/bin/rep_get_doc_metadata b/delivery1/client/bin/rep_get_doc_metadata index 257c07b..564cf1d 100755 --- a/delivery1/client/bin/rep_get_doc_metadata +++ b/delivery1/client/bin/rep_get_doc_metadata @@ -5,6 +5,8 @@ import logging import requests import json import argparse + +sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..')) from lib import digest from subject import main @@ -46,16 +48,15 @@ def getDocMetadata(args): doc_name = digest.get_hash(bytes(args.name, encoding='utf-8')) try: - metadata = requests.post(f'http://{state['REP_ADDRESS']}/file/' + doc_name + '/metadata', headers={'Authorization': args.session['token']}) + metadata = requests.get(f'http://{state['REP_ADDRESS']}/file/get/' + doc_name + '/metadata', headers={'Authorization': args.session['token']}) metadata.raise_for_status() - except requests.exceptions.RequestException as errex: logger.error("Failed to obtain response from server.") sys.exit(-1) metadata = metadata.json() - sys.stdout.write(metadata) + sys.stdout.write(json.dumps(metadata)) sys.exit(0) diff --git a/delivery1/client/bin/rep_get_file b/delivery1/client/bin/rep_get_file index a8f7d50..fe8a95d 100755 --- a/delivery1/client/bin/rep_get_file +++ b/delivery1/client/bin/rep_get_file @@ -37,26 +37,26 @@ def getFile(args): if not args.filehandle: logger.error("Need a file handle.") sys.exit(1) - else: - if not os.path.isfile(BASE_DIR + args.filehandle): - logger.error("File '" + args.filehandle + "' not found" ) - sys.exit(1) + #else: + # if not os.path.isfile(BASE_DIR + args.filehandle): + # logger.error("File '" + args.filehandle + "' not found" ) + # sys.exit(1) #Get file try: - file = requests.get(f'http://{state['REP_ADDRESS']}/get/' + args.file_handle + '/content') + file = requests.get(f'http://{state['REP_ADDRESS']}/file/get/' + args.filehandle + '/content') file.raise_for_status() except requests.exceptions.RequestException as errex: logger.error("Failed to obtain response from server.") sys.exit(-1) - file = file.json() + print(file) if not args.file: - sys.stdout.write(file) + sys.stdout.write(file.text) else: with open(BASE_DIR + args.file, "wb") as f: - f.write(file) + f.write(file.content) sys.exit(0) diff --git a/delivery1/client/bin/rep_list_subjects b/delivery1/client/bin/rep_list_subjects index ccf4d49..75c2d07 100755 --- a/delivery1/client/bin/rep_list_subjects +++ b/delivery1/client/bin/rep_list_subjects @@ -65,9 +65,8 @@ def list_subjects(args): logger.error("Failed to obtain response from server.") sys.exit(-1) - for s in subjects.json(): - sys.stdout.write(s['id'] + " - " + s['username']) - + for s,d in subjects.json().items(): + sys.stdout.write(s + " - " + d['username'] + "\n") sys.exit(0) diff --git a/delivery1/client/tests/test_client.py b/delivery1/client/tests/test_client.py new file mode 100644 index 0000000..1624c4a --- /dev/null +++ b/delivery1/client/tests/test_client.py @@ -0,0 +1,128 @@ +import json +import os, subprocess, sys + +import requests + +DELIVERY_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) +FILES_PATH = os.path.join(os.path.expanduser('~'), '.sio/') + +# !!! database.db must be deleted/reset before running the tests !!! +requests.post("http://localhost:5000/reset", json={"password": "123"}) +os.system(f"rm {FILES_PATH}*") + +def test_address_set(): + # Initialize the server path on state.json + process = subprocess.Popen(f"python3 {DELIVERY_PATH}/client/bin/subject.py -r localhost:5000 ", shell=True) + process.wait() + assert os.path.exists(os.path.join(FILES_PATH, 'state.json')) + + +def test_rep_subject_credentials(): + # Test the rep_subject_create command + process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_subject_credentials password pub.pem priv.pem ", shell=True) + process.wait() + assert os.path.exists(os.path.join(FILES_PATH, 'pub.pem')) and os.path.exists(os.path.join(FILES_PATH, 'priv.pem')) + + +def test_rep_create_org(): + # Test the rep_create_org command + process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_create_org org1 username name email@org.com pub.pem", shell=True) + process.wait() + assert process.returncode == 0 + + +def test_rep_list_orgs(): + # Test the list_orgs command + process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_list_orgs", shell=True) + process.wait() + assert process.returncode == 0 + + +def test_rep_create_session(): + # Test the rep_create_session command + process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_create_session org1 username password pub.pem session.json", shell=True) + process.wait() + assert process.returncode == 0 + + +def test_rep_list_subjects(): + #Test the rep_list_subjects command + process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_list_subjects session.json", shell=True) + process.wait() + assert process.returncode == 0 + +def test_rep_add_subject(): + # Test the rep_subject_create command + process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_subject_credentials password pub_extra.pem priv_extra.pem ", shell=True) + process.wait() + assert os.path.exists(os.path.join(FILES_PATH, 'pub_extra.pem')) and os.path.exists(os.path.join(FILES_PATH, 'priv_extra.pem')) + + process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_add_subject session.json username2 name2 name2@any.com pub_extra.pem", shell=True) + process.wait() + assert process.returncode == 0 + + +def test_rep_suspend_subject(): + # Test the rep_suspend_subject command + process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_suspend_subject session.json username2", shell=True) + process.wait() + assert process.returncode == 0 + + +def test_rep_activate_subject(): + # Test the rep_activate_subject command + process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_activate_subject session.json username2", shell=True) + process.wait() + assert process.returncode == 0 + + +def test_rep_add_doc(): + # Test the rep_add_doc command + process = subprocess.Popen(f"dd if=/dev/urandom of={FILES_PATH}test.txt bs=1024 count=1000 ", shell=True) + process.wait() + assert process.returncode == 0 + + process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_add_doc session.json doc test.txt ", shell=True) + process.wait() + assert process.returncode == 0 + +metadata = {} + +def test_rep_get_doc_metadata(): + # Test the rep_get_doc_metadata command + global metadata + process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_get_doc_metadata session.json doc", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + process.wait() + stdout, stderr = process.communicate() + metadata = json.loads(stdout) + assert process.returncode == 0 + +def test_rep_get_file(): + # Test the rep_get_file command + process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_get_file {metadata['file_handle']} file.txt ", shell=True) + process.wait() + assert process.returncode == 0 + +def test_decrypt_file(): + # Test the rep_decrypt_file command + process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_decrypt_file file.txt '{json.dumps(metadata)}'", shell=True) + process.wait() + assert process.returncode == 0 + +def test_rep_get_doc_file(): + # Test the rep_get_doc_file + process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_get_doc_file session.json doc ", shell=True) + process.wait() + assert process.returncode == 0 + +def test_rep_delete_doc(): + # Test the rep_get_doc_file + process = subprocess.Popen(f"{DELIVERY_PATH}/client/bin/rep_delete_doc session.json doc ", shell=True) + process.wait() + assert process.returncode == 0 + + + + + + diff --git a/delivery1/lib/symmetric_encryption.py b/delivery1/lib/symmetric_encryption.py index dfccb66..094e6a3 100644 --- a/delivery1/lib/symmetric_encryption.py +++ b/delivery1/lib/symmetric_encryption.py @@ -4,57 +4,43 @@ from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend -# Function to derive a 256-bit key from a password and salt -def derive_key(salt): - kdf = PBKDF2HMAC( - algorithm=hashes.SHA256(), - length=32, - salt=salt, - iterations=10000, - backend=default_backend() - ) - return kdf.derive(b'') - - # Function to encrypt a file using a salt def encrypt_file(input_file, output_file=None): - salt = os.urandom(16) - key = derive_key(salt) + key = os.urandom(16) iv = os.urandom(16) - nonce = os.urandom(16) - cipher = Cipher(algorithms.AES(key), modes.CFB(iv), backend=default_backend()) + cipher = Cipher(algorithms.AES(key), modes.CFB(iv)) encryptor = cipher.encryptor() with open(input_file, 'rb') as f: plaintext = f.read() ciphertext = encryptor.update(plaintext) + encryptor.finalize() + ciphertext = iv + ciphertext if output_file is not None: with open(output_file, 'wb') as f: - f.write(salt + iv + ciphertext) + f.write(ciphertext) - return salt + iv + ciphertext, nonce + print(iv.hex()) + + return key, ciphertext, iv # Function to decrypt a file -def decrypt_file(input_file, output_file=None): +def decrypt_file(nonce, key, input_file, output_file=None): with open(input_file, 'rb') as f: encrypted_data = f.read() - salt = encrypted_data[:16] - iv = encrypted_data[16:32] - ciphertext = encrypted_data[32:] + ciphertext = encrypted_data - key = derive_key(salt) - cipher = Cipher(algorithms.AES(key), modes.CFB(iv), backend=default_backend()) + cipher = Cipher(algorithms.AES(key), modes.CFB(nonce)) decryptor = cipher.decryptor() plaintext = decryptor.update(ciphertext) + decryptor.finalize() - if output_file is None: - return plaintext - else: + if output_file is not None: with open(output_file, 'wb') as f: f.write(plaintext) + + return plaintext.hex() diff --git a/delivery1/server/routes/file.py b/delivery1/server/routes/file.py index da64c67..5eb08a6 100644 --- a/delivery1/server/routes/file.py +++ b/delivery1/server/routes/file.py @@ -83,14 +83,16 @@ def file_upload_content(): if not file: return jsonify({"error": "Invalid file data"}), 400 + file_data = utils.get_hex_from_temp_file(file.stream) + file_sum = request.headers.get("File-Checksum") if not file_sum: return jsonify({"error": "No file checksum provided"}), 400 - if file_sum != utils.get_hash(file.stream): + if file_sum != str(utils.get_hash(file_data)): return jsonify({"error": "File checksum mismatch"}), 400 - file = upload_service.write_file(session_token, file.stream) + file = upload_service.write_file(session_token, file_sum, file_data) if isinstance(file, tuple): return file diff --git a/delivery1/server/services/files.py b/delivery1/server/services/files.py index a39ee11..9bf55f7 100644 --- a/delivery1/server/services/files.py +++ b/delivery1/server/services/files.py @@ -28,24 +28,24 @@ class FileService: creator = user ) - self.current_requests[session_token] = file - db.add(file) db.commit() db.refresh(file) + self.current_requests[session_token] = file.id + return file - def write_file(self, session_token: str, file_data: bytes) -> File | tuple: + def write_file(self, session_token: str, file_handle: str, file_data: bytes) -> File | tuple: if session_token not in self.current_requests: return jsonify({"error": "No file upload request found"}), 400 - file = self.current_requests[session_token] + file = db.query(File).filter(File.id == self.current_requests[session_token]).first() file_path = os.path.join(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "repository"), file.org.name, file.document_handle) with open(file_path, "wb") as f: f.write(file_data) - file.file_handle = get_hash(file_data) + file.file_handle = file_handle db.commit() db.refresh(file) diff --git a/delivery1/server/utils/__init__.py b/delivery1/server/utils/__init__.py index cfb2387..8eb7741 100644 --- a/delivery1/server/utils/__init__.py +++ b/delivery1/server/utils/__init__.py @@ -1,2 +1,2 @@ from .checks import check_valid_time -from .hashing import get_hash \ No newline at end of file +from .hashing import get_hash, get_hex_from_temp_file \ No newline at end of file diff --git a/delivery1/server/utils/hashing.py b/delivery1/server/utils/hashing.py index f03531c..34e26ea 100644 --- a/delivery1/server/utils/hashing.py +++ b/delivery1/server/utils/hashing.py @@ -1,8 +1,16 @@ +from tempfile import SpooledTemporaryFile import cryptography.hazmat.primitives.hashes + def get_hash(data): if isinstance(data, str): data = data.encode('utf-8') digest = cryptography.hazmat.primitives.hashes.Hash(cryptography.hazmat.primitives.hashes.SHA256()) digest.update(data) - return digest.finalize().hex() \ No newline at end of file + return digest.finalize().hex() + + +def get_hex_from_temp_file(temp_file: SpooledTemporaryFile) -> bytes: + temp_file.seek(0) + file_data = temp_file.read() + return file_data