From cd6894f52bf1b3134e8dd8a34530f88235124ff8 Mon Sep 17 00:00:00 2001
From: Martin van Es <martin@mrvanes.com>
Date: Thu, 16 Dec 2021 13:56:04 +0100
Subject: [PATCH] Add PKCS11 (softHSM2) Signer

---
 README.md        | 18 +++++++++++++++++-
 mdserver.py      |  9 +++++----
 requirements.txt |  2 ++
 signers.py       |  8 +++++++-
 utils.py         | 43 +++++++++++++++++++++++++++----------------
 5 files changed, 58 insertions(+), 22 deletions(-)

diff --git a/README.md b/README.md
index 28db9e5..c3436ce 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ Alternate MDX research project
 ## ```mdsigner.py [mdfile] [mdfile] [mdfile] ...```
 Reads source metadata file(s) and outputs them signed to filesystem
 
-## ```mdserver.py [mdfile] [mdfile] [mdfile] ...```
+## ```mdserver.py```
 Starts a metadata signer server.
 Reads source metadata files(s) from mdsigner.yaml configuration, see example.
 Reloads metadata on inotify CLOSE_WRITE of metadata file.
@@ -28,3 +28,19 @@ MDQ Queries can then be pointed at
 
 - ```http://mdserver:5001/sign/<entityid>```
 - ```http://mdproxy:5002/cache/<entityid>```
+
+## Bootstrap softHSM2
+This is a very brief summary of the successive commands to initialize softHSM2 for testing. Tested on Ubuntu 21.10.
+```
+# softhsm2-util --show-slots
+# softhsm2-util --init-token --slot 0 --label "My token 1" --pin "secret" --so-pin "secret"
+# softhsm2-util --show-slots
+
+# pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so -l -k --key-type rsa:1024 --slot-index 0 --id a1b2 --label test --pin secret
+# pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so -l --pin secret -O
+
+# openssl req -new -x509 -subj "/CN=Test Signer" -engine pkcs11 -keyform engine -key label_test -passin 'pass:secret' -out hsm.crt
+# openssl x509 -inform PEM -outform DER -in hsm.crt -out hsm.der
+
+# pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so -l --slot-index 0 --id a1b2 --label test -y cert -w hsm.der --pin secret
+```
\ No newline at end of file
diff --git a/mdserver.py b/mdserver.py
index 1384575..a1263ae 100755
--- a/mdserver.py
+++ b/mdserver.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-from utils import read_config, server, event_notifier
+from utils import read_config, Resource, server
 from flask import Flask, Response
 
 config = read_config()
@@ -24,9 +24,10 @@ def serve(domain, entity_id):
 
 for domain, values in config.items():
     print(f"domain: {domain}")
-    conf = (values['metadir'], values['signer'])
-    server[domain] = conf
+    location = values['metadir']
+    signer = values['signer']
+    server[domain] = Resource(location, signer)
+    server.add_watch(domain, location)
 
 
-event_notifier.start()
 app.run(host='127.0.0.1', port=5001)
diff --git a/requirements.txt b/requirements.txt
index 30cbf8e..b43dcbc 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,3 +6,5 @@ python-dateutil
 isoduration
 pyyaml
 pyinotify
+pyXMLSecurity
+PyKCS11
diff --git a/signers.py b/signers.py
index b2a0b61..9168c7a 100644
--- a/signers.py
+++ b/signers.py
@@ -1,3 +1,4 @@
+import xmlsec
 from signxml import XMLSigner
 
 cert = open("meta.crt").read()
@@ -17,10 +18,15 @@ def Signers(signer):
         print("Foobar signer")
         return XMLSigner().sign(xml, key=key, cert=cert)
 
+    def _hsm_signer(xml):
+        print("HSM signer")
+        return xmlsec.sign(xml, key_spec="pkcs11:///usr/lib/softhsm/libsofthsm2.so/test?pin=secret")
+
     signers = {
         'normal_signer': _normal_signer,
         'test_signer': _test_signer,
-        'foobar_signer': _foobar_signer
+        'foobar_signer': _foobar_signer,
+        'hsm_signer': _hsm_signer
     }
 
     return signers[signer]
diff --git a/utils.py b/utils.py
index 23cb4f2..cd66514 100755
--- a/utils.py
+++ b/utils.py
@@ -9,8 +9,8 @@ import yaml
 import pyinotify
 from signers import Signers
 
-watch_list = {}
-watch_manager = pyinotify.WatchManager()
+# watch_list = {}
+# watch_manager = pyinotify.WatchManager()
 
 
 def read_config():
@@ -26,13 +26,6 @@ def hasher(entity_id):
     return sha1_digest
 
 
-class EventProcessor(pyinotify.ProcessEvent):
-    def process_IN_CLOSE_WRITE(self, event):
-        domain = watch_list[event.path]
-        print(f"Notify {domain} {event.path}")
-        server[domain].walk_location(event.path)
-
-
 class Entity:
     def __init__(self):
         self.md = None
@@ -60,7 +53,6 @@ class Resource:
         found = 0
         removed = 0
         old_idps = self.mdfiles[mdfile].copy()
-        print(f"old_idps: {old_idps}")
         tree = ET.ElementTree(file=mdfile)
         root = tree.getroot()
         ns = root.nsmap.copy()
@@ -129,16 +121,35 @@ class Resource:
         return data
 
 
+class EventProcessor(pyinotify.ProcessEvent):
+    def process_IN_CLOSE_WRITE(self, event):
+        server.process(event.path)
+
+
 class Server:
-    def __setitem__(self, domain, conf):
-        location, signer = conf
-        self.__dict__[domain] = Resource(location, signer)
-        watch_list[location] = domain
-        watch_manager.add_watch(location, pyinotify.IN_CLOSE_WRITE)
+    watch_list = {}
+
+    def __init__(self):
+        self.watch_manager = pyinotify.WatchManager()
+        self.event_notifier = pyinotify.ThreadedNotifier(self.watch_manager, EventProcessor())
+        self.event_notifier.start()
+
+    def add_watch(self, domain, location):
+        self.watch_list[location] = domain
+        self.watch_manager.add_watch(location, pyinotify.IN_CLOSE_WRITE)
+
+    def process(self, location):
+        domain = self.watch_list[location]
+        print(f"Notify {domain} {location}")
+        self.__dict__[domain].walk_location(location)
+
+    def __setitem__(self, domain, resource):
+        self.__dict__[domain] = resource
+        # watch_list[location] = domain
+        # watch_manager.add_watch(location, pyinotify.IN_CLOSE_WRITE)
 
     def __getitem__(self, domain):
         return self.__dict__[domain]
 
 
 server = Server()
-event_notifier = pyinotify.ThreadedNotifier(watch_manager, EventProcessor())
-- 
GitLab