First commit

Heavily amended.
This commit is contained in:
Timotej Lazar 2023-07-04 22:47:41 +02:00
commit f1d9b1a291
No known key found for this signature in database
GPG key ID: 872369B696594350
5 changed files with 126 additions and 0 deletions

1
LICENSE Symbolic link
View file

@ -0,0 +1 @@
UNLICENSE

21
README.md Normal file
View file

@ -0,0 +1,21 @@
# fauxsign
Python script to replace [SETCCE proXSign®](https://proxsign.setcce.si/proXSignCustomerPages/). Currently only supports XML documents with SHA256 signatures, required to submit requests on certain gov.si sites.
## Setup
Websites that want to sign XML or PDF documents submit requests to the proXSign® component acting as a local HTTPS server listening on port 14972. To replicate this behavior, a self-signed TLS certificate is required. One can be generated with
openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \
-subj "/CN=fauxsign" -keyout fauxsign.key -out fauxsign.crt
## Usage
Assuming your personal certificate and key are stored in `user.crt` and `user.key` respectively, start the server with
./fauxsign --app-key fauxsign.key --app-cert fauxsign.crt \
--user-key user.key --user-cert user.crt
Before signing, add a browser exception for the app certificate generated above by navigating to [https://localhost:14972/version](https://localhost:14972/version). This only needs to be done once.
Visit the [XML signing test page](https://proxsign.setcce.si/proXSignCustomerPages/testXML.html) to verify the script works correctly. The script will prompt for each signature request; answer `y` or `yes` to confirm.

24
UNLICENSE Normal file
View file

@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>

78
fauxsign.py Executable file
View file

@ -0,0 +1,78 @@
#!/usr/bin/env python3
import argparse
import http.server
import json
import pathlib
import ssl
import urllib.parse
import lxml
import signxml
version = '2.2.9.276'
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'))
class Handler(http.server.BaseHTTPRequestHandler):
def reply(self, data):
self.send_response(200)
self.send_header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept')
self.send_header('Access-Control-Allow-Methods', 'POST,GET,OPTIONS')
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Private-Network', 'true')
self.end_headers()
self.wfile.write(json.dumps(data).encode())
def do_GET(self):
url = urllib.parse.urlparse(self.path)
match url.path:
case '/version':
self.reply({'identifier': identifier, 'version': version})
def do_POST(self):
url = urllib.parse.urlparse(self.path)
match url.path:
case '/updateLicense':
self.reply({'error': 1, 'errorMessage': 'OK'})
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=' ')
if input() in ('y', 'yes'):
signed = sign(xml, key=self.server.user_key, cert=self.server.user_cert)
self.reply({
'error': 1,
'errorMessage': '',
'filename': '',
'result': signed,
'signatures': [],
'timestamps': []
})
else:
self.reply({'error': -1, 'errorMessage': 'aborted'})
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', '--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')
args = parser.parse_args()
tls_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
tls_context.check_hostname = False
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.socket = tls_context.wrap_socket(httpd.socket, server_side=True)
httpd.serve_forever()

2
requirements.txt Normal file
View file

@ -0,0 +1,2 @@
lxml
signxml