From 480524276eb6ddadd149e1fff68a89e077cdfe90 Mon Sep 17 00:00:00 2001 From: Timotej Lazar Date: Sat, 14 Mar 2026 00:53:11 +0100 Subject: Implement detached signatures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- fauxsign.py | 69 ++++++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 14 deletions(-) (limited to 'fauxsign.py') 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 ('' + - lxml.etree.tostring(signed, encoding='unicode')) +# XML templates for detached signatures +detached_digest = '''\ +\ +\ +\ +\ +\ +{digest}\ +\ +''' + +detached_response = '''\ +\ +\ +\ +\ +\ +\ +{digest}\ +\ +\ +{signature}\ +\ +{certificate}\ +\ +''' + +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': '' + 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() -- cgit v1.3