From 69c51f0971a3ba380f175bb0cda56a29b7f7279c Mon Sep 17 00:00:00 2001 From: Agnibho Mondal Date: Tue, 12 Sep 2023 02:11:17 +0530 Subject: [PATCH] Implemented SMIME Signature --- config.py | 4 +++- config/config.json | 4 +++- filehandler.py | 39 ++++++++++++++++++++++++++++++++++++ prescription.py | 1 + signature.py | 49 ++++++++++++++++++++++++++++++++++++++++++++++ window.py | 43 +++++++++++++++++++++++++++++++++++++++- 6 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 signature.py diff --git a/config.py b/config.py index c2f70c1..0f7feb8 100644 --- a/config.py +++ b/config.py @@ -32,7 +32,9 @@ default = { "template": "default_prescription", "preset_directory": "preset", "preset_newline": "True", - "preset_delimiter": "," + "preset_delimiter": ",", + "private_key": "", + "certificate": "" } with open(config_file) as conf: diff --git a/config/config.json b/config/config.json index 73edfd2..de0481a 100644 --- a/config/config.json +++ b/config/config.json @@ -8,5 +8,7 @@ "template": "default", "preset_directory": "preset", "preset_newline": "True", - "preset_delimiter": "," + "preset_delimiter": ",", + "certificate": "", + "private_key": "" } diff --git a/filehandler.py b/filehandler.py index b80da90..edec0bf 100644 --- a/filehandler.py +++ b/filehandler.py @@ -8,6 +8,7 @@ import os, shutil, glob, tempfile, json from zipfile import ZipFile from config import config +from signature import Signature class FileHandler(): @@ -56,3 +57,41 @@ class FileHandler(): self.file=file with ZipFile(self.file, "r", strict_timestamps=False) as source: source.extractall(self.directory.name) + + #def hash(self): + # allfiles=[] + # allhash="" + # #with open(os.path.join(self.directory.name, "prescription.json"), "rb") as f: + # # print(sha256(f.read()).hexdigest()) + # for root, dirs, files in os.walk(self.directory.name): + # for file in files: + # allfiles.append(os.path.join(root, file)) + # try: + # allfiles.remove(os.path.join(self.directory.name, "certificate.pem")) + # allfiles.remove(os.path.join(self.directory.name, "signature.p7m")) + # except ValueError as e: + # pass + # for file in allfiles: + # with open(file, "rb") as f: + # allhash=allhash+sha256(f.read()).hexdigest() + # return(allhash) + + def sign(self): + with open(os.path.join(self.directory.name, "prescription.json"), "r") as file: + data=file.read() + signature=Signature.sign(data, certificate=config["certificate"], privkey=config["private_key"]) + with open(os.path.join(self.directory.name, "signature.p7m"), "w") as file: + file.write(signature) + shutil.copyfile(config["certificate"], os.path.join(self.directory.name, "certificate.pem")) + + def verify(self): + with open(os.path.join(self.directory.name, "prescription.json"), "r") as file: + data=file.read() + try: + with open(os.path.join(self.directory.name, "certificate.pem")) as file: + certificate=file.read() + with open(os.path.join(self.directory.name, "signature.p7m")) as file: + signature=file.read() + return Signature.verify(data, certificate=os.path.join(self.directory.name, "certificate.pem"), signature=os.path.join(self.directory.name, "signature.p7m")) + except FileNotFoundError as e: + print(e) diff --git a/prescription.py b/prescription.py index 678a822..ab89569 100644 --- a/prescription.py +++ b/prescription.py @@ -90,6 +90,7 @@ class Prescription: def write_to(self, file): with open(file, "w") as f: + del self.file f.write(self.get_json()) self.file=file diff --git a/signature.py b/signature.py new file mode 100644 index 0000000..7587411 --- /dev/null +++ b/signature.py @@ -0,0 +1,49 @@ +# MedScript +# Copyright (C) 2023 Dr. Agnibho Mondal +# This file is part of MedScript. +# MedScript is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +# MedScript is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# You should have received a copy of the GNU General Public License along with MedScript. If not, see . + +from M2Crypto import BIO, Rand, SMIME, X509 +from config import config +from hashlib import sha256 + +class Signature(): + + def sign(data, certificate, privkey): + try: + hash=sha256(data.encode()).hexdigest() + smime=SMIME.SMIME() + smime.load_key(privkey, certificate) + p7=smime.sign(BIO.MemoryBuffer(hash.encode()), SMIME.PKCS7_DETACHED) + out=BIO.MemoryBuffer() + smime.write(out, p7) + return(out.read().decode()) + except Exception as e: + print(e) + return None + + def verify(data, certificate, signature): + hash=sha256(data.encode()).hexdigest() + smime=SMIME.SMIME() + + x509=X509.load_cert(certificate) + sk=X509.X509_Stack() + sk.push(x509) + smime.set_x509_stack(sk) + + st=X509.X509_Store() + st.load_info(certificate) + smime.set_x509_store(st) + + with open(signature) as file: + buf=BIO.MemoryBuffer(file.read().encode()) + p7=SMIME.smime_load_pkcs7_bio(buf)[0] + + try: + v=smime.verify(p7, BIO.MemoryBuffer(hash.encode())) + return(x509.get_subject().as_text()) + except SMIME.PKCS7_Error as e: + print(e) + return False diff --git a/window.py b/window.py index 5845481..68a4242 100644 --- a/window.py +++ b/window.py @@ -93,7 +93,42 @@ class MainWindow(QMainWindow): self.signal_view.emit(target) self.renderbox.showMaximized() else: - QMessageBox.information(self,"Save first", "Please save the file before rendering.") + QMessageBox.information(self, "Save first", "Please save the file before rendering.") + + def cmd_sign(self): + self.update_instance() + if(self.save_state==md5(self.prescription.get_json().encode()).hexdigest()): + try: + self.current_file.sign() + self.cmd_save() + except FileNotFoundError as e: + print(e) + QMessageBox.information(self, "Save first", "Please save the file before signing.") + except TypeError as e: + print(e) + QMessageBox.information(self, "Configure", "Please add valid key and certificate to the config file.") + except Exception as e: + print(e) + QMessageBox.information(self, "Failed", "Failed to sign.") + else: + QMessageBox.information(self, "Save first", "Please save the file before signing.") + + def cmd_verify(self): + try: + result=self.current_file.verify() + if result is False: + QMessageBox.critical(self, "Verification failed", "Signature is invalid.") + elif result is None: + QMessageBox.warning(self, "No Siganture", "No signature was found.") + else: + print(result) + QMessageBox.information(self, "Valid signature", "Valid signature found with the following information:\n"+result) + except FileNotFoundError as e: + print(e) + QMessageBox.warning(self, "No Siganture", "No signature was found.") + except Exception as e: + print(e) + QMessageBox.warning(self, "Failed", "Failed to verify.") def cmd_prescriber(self): self.edit_prescriber.show() @@ -352,6 +387,10 @@ class MainWindow(QMainWindow): action_render2=QAction(icon_render, "Render", self) action_render.triggered.connect(self.cmd_render) action_render2.triggered.connect(self.cmd_render) + action_sign=QAction("Sign", self) + action_sign.triggered.connect(self.cmd_sign) + action_verify=QAction("Verify", self) + action_verify.triggered.connect(self.cmd_verify) action_prescriber=QAction("Prescriber", self) action_prescriber.triggered.connect(self.cmd_prescriber) action_switch=QAction("Switch", self) @@ -370,6 +409,8 @@ class MainWindow(QMainWindow): menu_file.addAction(action_quit) menu_prepare=menubar.addMenu("Prepare") menu_prepare.addAction(action_render) + menu_prepare.addAction(action_sign) + menu_prepare.addAction(action_verify) menu_prepare.addAction(action_refresh) menu_prepare.addAction(action_prescriber) menu_prepare.addAction(action_switch) -- 2.39.2