From 65c70c12b24f12a948e3862a072ceaa55777029b Mon Sep 17 00:00:00 2001 From: Agnibho Mondal Date: Thu, 14 Sep 2023 02:48:41 +0530 Subject: [PATCH] Signature verification against a root bundle --- config.py | 2 ++ config/config.json | 4 +++- data/prescriber/prescriber.json | 2 +- setting.py | 17 ++++++++++++++ signature.py | 39 +++++++++++++++++++++++++++++++-- window.py | 9 ++++---- 6 files changed, 65 insertions(+), 8 deletions(-) diff --git a/config.py b/config.py index 0f7feb8..1396588 100644 --- a/config.py +++ b/config.py @@ -33,6 +33,8 @@ default = { "preset_directory": "preset", "preset_newline": "True", "preset_delimiter": ",", + "smime": "False", + "root_bundle": "", "private_key": "", "certificate": "" } diff --git a/config/config.json b/config/config.json index de0481a..65d9130 100644 --- a/config/config.json +++ b/config/config.json @@ -7,8 +7,10 @@ "template_directory": "template", "template": "default", "preset_directory": "preset", - "preset_newline": "True", + "preset_newline": true, "preset_delimiter": ",", + "smime": false, + "root_bundle": "", "certificate": "", "private_key": "" } diff --git a/data/prescriber/prescriber.json b/data/prescriber/prescriber.json index feb17d9..097dca5 100644 --- a/data/prescriber/prescriber.json +++ b/data/prescriber/prescriber.json @@ -5,4 +5,4 @@ "address": "", "contact": "", "extra": "" -} \ No newline at end of file +} diff --git a/setting.py b/setting.py index 11bbd51..152a850 100644 --- a/setting.py +++ b/setting.py @@ -29,6 +29,10 @@ class EditConfiguration(QMainWindow): f=QFileDialog.getOpenFileName(self, "Select Certificate", os.path.expanduser("~"), "PEM (*.pem);; All Files (*)")[0] if(f): self.input_certificate.setText(f) + def select_root(self): + f=QFileDialog.getOpenFileName(self, "Select Root Bundle", os.path.expanduser("~"), "PEM (*.pem);; All Files (*)")[0] + if(f): + self.input_root.setText(f) def load(self): try: @@ -37,8 +41,10 @@ class EditConfiguration(QMainWindow): self.input_prescriber.setText(self.config["prescriber"]) self.input_newline.setChecked(bool(self.config["preset_newline"])) self.input_delimiter.setCurrentText(self.config["preset_delimiter"]) + self.input_smime.setChecked(bool(self.config["smime"])) self.input_key.setText(self.config["private_key"]) self.input_certificate.setText(self.config["certificate"]) + self.input_root.setText(self.config["root_bundle"]) except Exception as e: QMessageBox.critical(self,"Failed to load", "Failed to load the data into the application.") raise(e) @@ -50,8 +56,10 @@ class EditConfiguration(QMainWindow): self.config["prescriber"]=self.input_prescriber.text() self.config["preset_newline"]=self.input_newline.isChecked() self.config["preset_delimiter"]=self.input_delimiter.currentText() + self.config["smime"]=self.input_smime.isChecked() self.config["private_key"]=self.input_key.text() self.config["certificate"]=self.input_certificate.text() + self.config["root_bundle"]=self.input_root.text() with open(config_file, "w") as f: f.write(json.dumps(self.config, indent=4)) QMessageBox.information(self,"Saved", "Configuration saved. Please restart MedScript.") @@ -90,6 +98,8 @@ class EditConfiguration(QMainWindow): self.input_delimiter=QComboBox(self) self.input_delimiter.addItems([",", ";"]) layout.addRow("Preset Delimiter", self.input_delimiter) + self.input_smime=QCheckBox("Enable digital signature (experimental)", self) + layout.addRow("S/MIME", self.input_smime) self.input_key=QLineEdit(self) btn_key=QPushButton("...", self) btn_key.clicked.connect(self.select_key) @@ -104,6 +114,13 @@ class EditConfiguration(QMainWindow): layout_certificate.addWidget(self.input_certificate) layout_certificate.addWidget(btn_certificate) layout.addRow("X509 Certificate", layout_certificate) + self.input_root=QLineEdit(self) + btn_root=QPushButton("...", self) + btn_root.clicked.connect(self.select_root) + layout_root=QHBoxLayout() + layout_root.addWidget(self.input_root) + layout_root.addWidget(btn_root) + layout.addRow("Root Bundle", layout_root) button_save=QPushButton("Save") button_save.clicked.connect(self.save) button_reset=QPushButton("Reset") diff --git a/signature.py b/signature.py index 604a2a3..0e35e28 100644 --- a/signature.py +++ b/signature.py @@ -8,12 +8,12 @@ from M2Crypto import BIO, Rand, SMIME, X509 from config import config from hashlib import sha256 +from datetime import datetime class Signature(): def sign(data, certificate, privkey, password=""): def get_password(*args): - print(password) return bytes(password, "ascii") hash=sha256(data.encode()).hexdigest() smime=SMIME.SMIME() @@ -24,6 +24,13 @@ class Signature(): return(out.read().decode()) def verify(data, certificate, signature): + try: + if(not Signature.verify_chain(certificate)): + return False + except Exception as e: + print(e) + return False + hash=sha256(data.encode()).hexdigest() smime=SMIME.SMIME() @@ -41,8 +48,36 @@ class Signature(): p7=SMIME.smime_load_pkcs7_bio(buf)[0] try: - v=smime.verify(p7, BIO.MemoryBuffer(hash.encode())) + smime.verify(p7, BIO.MemoryBuffer(hash.encode())) return(x509.get_subject().as_text()) except SMIME.PKCS7_Error as e: print(e) return False + + def verify_chain(cert_chain_path): + cert_chain=[] + with open(cert_chain_path) as chain_file: + cert_data="" + for line in chain_file: + cert_data=cert_data+line.strip()+"\n" + if "----END CERTIFICATE----" in line: + cert_chain.append(X509.load_cert_string(cert_data)) + cert_data="" + + for i in range(len(cert_chain)): + cert=cert_chain[i] + if(datetime.utcnow().timestamp()>cert.get_not_after().get_datetime().timestamp()): + print("Certificate expired") + return False + if(i>0): + prev_cert=cert_chain[i-1] + prev_public_key=prev_cert.get_pubkey().get_rsa() + if(not prev_cert.verify(cert.get_pubkey())): + print("Certificate chain signature verification failed") + return False + with open(config["root_bundle"]) as root: + root_bundle=root.read() + if(cert_chain[-1].as_pem().decode() not in root_bundle): + print("Certificate not in root bundle") + return False + return True diff --git a/window.py b/window.py index 97a1727..d26f0f1 100644 --- a/window.py +++ b/window.py @@ -253,7 +253,7 @@ class MainWindow(QMainWindow): def load_interface(self, file="", date=None, id="", name="", age="", sex="", address="", contact="", extra="", mode="", daw="", diagnosis="", note="", report="", advice="", investigation="", medication="", additional=""): try: file_msg=self.current_file.file if self.current_file.file else "New file" - sign_msg="(signed)" if self.current_file.is_signed() else "" + sign_msg="(signed)" if config["smime"] and self.current_file.is_signed() else "" self.statusbar.showMessage(file_msg+" "+sign_msg) if date is None: d=QDateTime.currentDateTime() @@ -447,9 +447,10 @@ class MainWindow(QMainWindow): menu_prepare=menubar.addMenu("Prepare") menu_prepare.addAction(action_render) menu_prepare.addAction(action_refresh) - menu_prepare.addAction(action_sign) - menu_prepare.addAction(action_unsign) - menu_prepare.addAction(action_verify) + if(config["smime"]): + menu_prepare.addAction(action_sign) + menu_prepare.addAction(action_unsign) + menu_prepare.addAction(action_verify) menu_settings=menubar.addMenu("Settings") menu_settings.addAction(action_configuration) menu_settings.addAction(action_prescriber) -- 2.39.2