diff options
| author | Timotej Lazar <timotej.lazar@araneo.si> | 2026-03-14 00:53:11 +0100 |
|---|---|---|
| committer | Timotej Lazar <timotej.lazar@araneo.si> | 2026-03-14 11:41:41 +0100 |
| commit | 480524276eb6ddadd149e1fff68a89e077cdfe90 (patch) | |
| tree | 2ee588ea9d084991fe4fe8120fac690ada1b9763 | |
| parent | 232fc1d97f0eab42ef6f9a00a43d30aa65417dd8 (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-x | fauxsign.py | 69 |
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() |
