]> Softwares of Agnibho - medscript.git/commitdiff
Signature verification against a root bundle
authorAgnibho Mondal <mondal@agnibho.com>
Wed, 13 Sep 2023 21:18:41 +0000 (02:48 +0530)
committerAgnibho Mondal <mondal@agnibho.com>
Wed, 13 Sep 2023 21:18:41 +0000 (02:48 +0530)
config.py
config/config.json
data/prescriber/prescriber.json
setting.py
signature.py
window.py

index 0f7feb828805edc33b6d39ef1dd2c5ee3828a14d..139658802006dbdefbd64c73da4f64e63115f38c 100644 (file)
--- 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": ""
         }
index de0481a74e0653a51cdecc24c7fe36949f9fbbfb..65d9130e6ad52637c14800cc60538f403c836241 100644 (file)
@@ -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": ""
 }
index feb17d975ff6f81d0e024988b89ad09c9c09d702..097dca5530e2101d8dfce77d20f09075981f0a15 100644 (file)
@@ -5,4 +5,4 @@
     "address": "",
     "contact": "",
     "extra": ""
-}
\ No newline at end of file
+}
index 11bbd516c4e30d614ae2c670bddbe408f4296ba0..152a85065c114c253c67a33eeae5e539a7a3c97d 100644 (file)
@@ -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")
index 604a2a35c76d9f50c1b59c427b36c99fbd2e3574..0e35e2804d87e8f567beb7b150b120b7afbaedb6 100644 (file)
@@ -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
index 97a17275437a488054a437a56646c8ecc0d90b48..d26f0f103f6070f81e7bddf74625a4f55b2db2a5 100644 (file)
--- 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)