summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTimotej Lazar <timotej.lazar@araneo.si>2026-03-14 00:53:11 +0100
committerTimotej Lazar <timotej.lazar@araneo.si>2026-03-14 11:41:41 +0100
commit480524276eb6ddadd149e1fff68a89e077cdfe90 (patch)
tree2ee588ea9d084991fe4fe8120fac690ada1b9763
parent232fc1d97f0eab42ef6f9a00a43d30aa65417dd8 (diff)
Implement detached signatures
Could not get signxml to do detached signatures like proXSign®, so do it manually. The protocol doesn’t seem to support using other canonicalization and signature methods.
-rwxr-xr-xfauxsign.py69
1 files changed, 55 insertions, 14 deletions
diff --git a/fauxsign.py b/fauxsign.py
index c2c2349..7fa346b 100755
--- a/fauxsign.py
+++ b/fauxsign.py
@@ -1,23 +1,65 @@
#!/usr/bin/env python3
import argparse
+import base64
import http.server
import json
import pathlib
import ssl
+import subprocess
import urllib.parse
import lxml
import signxml
-version = '2.2.9.276'
+version = '2.2.13.38'
identifier = 'f8e5f470-bcff-4c50-8fd6-ccfa2fea12d6'
-def sign(xml, key, cert):
- original = lxml.etree.fromstring(xml)
- signed = signxml.XMLSigner().sign(original, key=key, cert=cert)
- return ('<?xml version="1.0" encoding="UTF-8" standalone="no" ?>' +
- lxml.etree.tostring(signed, encoding='unicode'))
+# XML templates for detached signatures
+detached_digest = '''\
+<ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">\
+<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:CanonicalizationMethod>\
+<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></ds:SignatureMethod>\
+<ds:Reference URI="{reference}">\
+<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></ds:DigestMethod>\
+<ds:DigestValue>{digest}</ds:DigestValue>\
+</ds:Reference>\
+</ds:SignedInfo>'''
+
+detached_response = '''\
+<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">\
+<ds:SignedInfo>\
+<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>\
+<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>\
+<ds:Reference URI="{reference}">\
+<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>\
+<ds:DigestValue>{digest}</ds:DigestValue>\
+</ds:Reference>\
+</ds:SignedInfo>\
+<ds:SignatureValue>{signature}</ds:SignatureValue>\
+<ds:KeyInfo>\
+<ds:X509Data><ds:X509Certificate>{certificate}</ds:X509Certificate></ds:X509Data>\
+</ds:KeyInfo>\
+</ds:Signature>'''
+
+def sign(data, key, cert):
+ if 'SIG_TYPE_DETACHED' in data['options']:
+ # might be possible to use signxml for detached signatures also
+ reference, digest = data['URIId'][0].split(',')
+ signature = subprocess.run(
+ ['openssl', 'dgst', '-sha1', '-sign', key], capture_output=True,
+ input=detached_digest.format(reference=reference, digest=digest).encode()
+ ).stdout
+ return detached_response.format(
+ reference=reference, digest=digest,
+ signature=base64.b64encode(signature).decode(),
+ certificate=open(cert).read() # strip header and footer
+ .split('-----BEGIN CERTIFICATE-----', 1)[1]
+ .split('-----END CERTIFICATE-----', 1)[0])
+ else:
+ original = lxml.etree.fromstring(data['bytes'][0].removeprefix('XML:').encode())
+ signed = signxml.XMLSigner().sign(original, key=open(key).read(), cert=open(cert).read())
+ return lxml.etree.tostring(signed, encoding='unicode')
class Handler(http.server.BaseHTTPRequestHandler):
def reply(self, data):
@@ -43,15 +85,14 @@ class Handler(http.server.BaseHTTPRequestHandler):
case '/signXML':
length = int(self.headers['content-length'])
data = json.loads(self.rfile.read(length).decode())
- xml = data['bytes'][0].removeprefix('XML:').encode()
- print(f'{self.headers.get("origin", "unknown")} wants to sign:\n{xml}\nConfirm?', end=' ')
+ print(f'{self.headers.get("origin", "unknown")} wants to sign:\n{data}\nConfirm?', end=' ')
if input() in ('y', 'yes'):
- signed = sign(xml, key=self.server.user_key, cert=self.server.user_cert)
+ signed = sign(data, key=self.server.user_key, cert=self.server.user_cert)
self.reply({
'error': 1,
'errorMessage': '',
'filename': '',
- 'result': signed,
+ 'result': '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>' + signed,
'signatures': [],
'timestamps': []
})
@@ -60,8 +101,8 @@ class Handler(http.server.BaseHTTPRequestHandler):
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Fake the proXSign® application.')
- parser.add_argument('-k', '--user-key', type=open, required=True, help='key file')
- parser.add_argument('-c', '--user-cert', type=open, required=True, help='certificate file')
+ parser.add_argument('-k', '--user-key', type=pathlib.Path, required=True, help='key file')
+ parser.add_argument('-c', '--user-cert', type=pathlib.Path, required=True, help='certificate file')
parser.add_argument('-K', '--app-key', type=pathlib.Path, required=True, help='app key file')
parser.add_argument('-C', '--app-cert', type=pathlib.Path, required=True, help='app certificate file')
parser.add_argument('-p', '--port', type=int, default=14972, help='port to listen on')
@@ -72,7 +113,7 @@ if __name__ == '__main__':
tls_context.load_cert_chain(keyfile=args.app_key, certfile=args.app_cert)
httpd = http.server.HTTPServer(('localhost', args.port), Handler)
- httpd.user_key = args.user_key.read()
- httpd.user_cert = args.user_cert.read()
+ httpd.user_key = args.user_key
+ httpd.user_cert = args.user_cert
httpd.socket = tls_context.wrap_socket(httpd.socket, server_side=True)
httpd.serve_forever()