Compare commits
25 Commits
ef47e2c1fd
...
main
Author | SHA1 | Date | |
---|---|---|---|
083bf20be7
|
|||
6626b33c6a
|
|||
8a3c44c517
|
|||
c7a6283e00
|
|||
0e7d01f0fd
|
|||
39b9f7455c
|
|||
97a2385167
|
|||
77aaf97d7b
|
|||
dd8c30787a
|
|||
8c5950b37f | |||
34857ae4b0
|
|||
df2a8d554f | |||
89cb05fce1
|
|||
3716c3ce78
|
|||
c8042a6f84
|
|||
9f085484f7
|
|||
958c96193e
|
|||
7c84464d13
|
|||
536115b105 | |||
2e6d3794bd | |||
c1a7957b64
|
|||
22c5191f43
|
|||
300b6f46f1
|
|||
ed81eb749c
|
|||
41ba98ee91
|
35
.drone.yml
35
.drone.yml
@@ -1,35 +0,0 @@
|
|||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: build-images
|
|
||||||
|
|
||||||
image_pull_secrets:
|
|
||||||
- dockerconfig
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
event:
|
|
||||||
- cron
|
|
||||||
- push
|
|
||||||
cron:
|
|
||||||
- build-image
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: build-base-image
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
registry: h3m6q87t.gra7.container-registry.ovh.net
|
|
||||||
repo: h3m6q87t.gra7.container-registry.ovh.net/sign-pdf-worker/worker
|
|
||||||
tag:
|
|
||||||
- latest
|
|
||||||
pull_image: true
|
|
||||||
context: ./pythonProject
|
|
||||||
dockerfile: ./pythonProject/Dockerfile
|
|
||||||
---
|
|
||||||
kind: signature
|
|
||||||
hmac: b90a8b716ded924055143fc3892ab7235297e8317e467ea9bf1b1848b590d8f9
|
|
||||||
|
|
||||||
...
|
|
37
.gitea/workflows/build-image.yaml
Normal file
37
.gitea/workflows/build-image.yaml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
name: Build image and push it to registry
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
schedule:
|
||||||
|
# every three of the month, at 04:05
|
||||||
|
- cron: '5 4 3 * *'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# Build job
|
||||||
|
build:
|
||||||
|
runs-on: cth-ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out repository
|
||||||
|
uses: https://github.com/actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: https://github.com/docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to registry
|
||||||
|
uses: https://github.com/docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
registry: 'h3m6q87t.gra7.container-registry.ovh.net'
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
uses: https://github.com/docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: ./pythonProject
|
||||||
|
file: ./pythonProject/Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: 'h3m6q87t.gra7.container-registry.ovh.net/sign-pdf-worker/worker:latest'
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
local.env
|
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -3,5 +3,5 @@
|
|||||||
<component name="Black">
|
<component name="Black">
|
||||||
<option name="sdkName" value="Python 3.10 (pythonProject)" />
|
<option name="sdkName" value="Python 3.10 (pythonProject)" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (pythonProject)" project-jdk-type="Python SDK" />
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 virtualenv at ~/dev/chill/sign-pdf-worker/pythonProject/.venv" project-jdk-type="Python SDK" />
|
||||||
</project>
|
</project>
|
2
.idea/sign-pdf-worker.iml
generated
2
.idea/sign-pdf-worker.iml
generated
@@ -4,7 +4,7 @@
|
|||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<excludeFolder url="file://$MODULE_DIR$/pythonProject/.venv" />
|
<excludeFolder url="file://$MODULE_DIR$/pythonProject/.venv" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="jdk" jdkName="Python 3.10 virtualenv at ~/dev/chill/sign-pdf-worker/pythonProject/.venv" jdkType="Python SDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
10
local.env.dist
Normal file
10
local.env.dist
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
AMQP_URL=amqp://guest:guest@localhost:32772/%2f/to_python_sign
|
||||||
|
LOG_LEVEL=DEBUG
|
||||||
|
PKCS12_PATH=./assets/dummy.p12
|
||||||
|
TIMESTAMP_URL=http://freetsa.org/tsr
|
||||||
|
QUEUE_IN=to_python_sign
|
||||||
|
EXCHANGE_OUT=signed_docs
|
||||||
|
OUT_ROUTING_KEY=signed_doc
|
||||||
|
TSA_CONFIG_PATH=/home/julien/dev/chill/sign-pdf-worker/ts-authority/rootca.conf
|
||||||
|
TSA_CERT_CHAIN=/home/julien/dev/chill/sign-pdf-worker/ts-authority/ca/tsa-chain.pem
|
||||||
|
TSA_KEY_PASSWORD=5678
|
@@ -4,6 +4,12 @@ FROM python:3.10-alpine
|
|||||||
# Set working directory
|
# Set working directory
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
# add required clis
|
||||||
|
RUN apk add --no-cache openssl tzdata
|
||||||
|
|
||||||
|
# set timezone
|
||||||
|
RUN ln -s /usr/share/zoneinfo/Europe/Paris /etc/localtime
|
||||||
|
|
||||||
# Copy requirements.txt to the Docker container
|
# Copy requirements.txt to the Docker container
|
||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
|
|
||||||
|
@@ -1,20 +1,22 @@
|
|||||||
asn1crypto==1.5.1
|
asn1crypto==1.5.1
|
||||||
certifi==2024.6.2
|
certifi==2025.4.26
|
||||||
cffi==1.16.0
|
cffi==1.17.1
|
||||||
charset-normalizer==3.3.2
|
charset-normalizer==3.4.2
|
||||||
click==8.1.7
|
click==8.2.1
|
||||||
cryptography==42.0.8
|
cryptography==45.0.3
|
||||||
idna==3.7
|
idna==3.10
|
||||||
|
lxml==5.4.0
|
||||||
oscrypto==1.3.0
|
oscrypto==1.3.0
|
||||||
pika==1.3.2
|
pika==1.3.2
|
||||||
|
pika-stubs==0.1.3
|
||||||
pycparser==2.22
|
pycparser==2.22
|
||||||
pyHanko==0.25.0
|
pyHanko==0.29.0
|
||||||
pyhanko-certvalidator==0.26.3
|
pyhanko-certvalidator==0.27.0
|
||||||
pypng==0.20220715.0
|
pypng==0.20220715.0
|
||||||
PyYAML==6.0.1
|
PyYAML==6.0.2
|
||||||
qrcode==7.4.2
|
qrcode==8.2
|
||||||
requests==2.32.3
|
requests==2.32.3
|
||||||
typing_extensions==4.12.2
|
typing_extensions==4.14.0
|
||||||
tzlocal==5.2
|
tzlocal==5.3.1
|
||||||
uritools==4.0.3
|
uritools==5.0.0
|
||||||
urllib3==2.2.2
|
urllib3==2.4.0
|
||||||
|
@@ -1,17 +1,21 @@
|
|||||||
import io
|
import io
|
||||||
|
from random import randint
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from pyhanko import stamp
|
from pyhanko import stamp
|
||||||
from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter
|
from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter
|
||||||
from pyhanko.sign import signers, timestamps, fields
|
from pyhanko.sign import signers, fields
|
||||||
from pyhanko_certvalidator import ValidationContext
|
|
||||||
from typing_extensions import Buffer
|
from typing_extensions import Buffer
|
||||||
|
|
||||||
|
from timestamp import LocalOpensslTimestamp
|
||||||
|
|
||||||
|
|
||||||
class SignOrchestrator:
|
class SignOrchestrator:
|
||||||
"""Orchestrate the signature on document"""
|
"""Orchestrate the signature on document"""
|
||||||
|
|
||||||
def __init__(self, pkcs12_path: str, timestamp_url: str, pkcs12_password: Optional[bytes] = None):
|
def __init__(self, pkcs12_path: str,
|
||||||
|
tsa_config_path: str, tsa_password: str, tsa_cert_chain: str,
|
||||||
|
pkcs12_password: Optional[bytes] = None):
|
||||||
# Load signer key material from PKCS#12 file
|
# Load signer key material from PKCS#12 file
|
||||||
# This assumes that any relevant intermediate certs are also included
|
# This assumes that any relevant intermediate certs are also included
|
||||||
# in the PKCS#12 file.
|
# in the PKCS#12 file.
|
||||||
@@ -20,9 +24,7 @@ class SignOrchestrator:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Set up a timestamping client to fetch timestamps tokens
|
# Set up a timestamping client to fetch timestamps tokens
|
||||||
self.timestamper = timestamps.HTTPTimeStamper(
|
self.timestamper = LocalOpensslTimestamp(tsa_config_path, tsa_password, tsa_cert_chain)
|
||||||
url=timestamp_url,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.stamp_style = stamp.TextStampStyle(
|
self.stamp_style = stamp.TextStampStyle(
|
||||||
stamp_text="Signé par:\n%(signer_text)s\nLe %(ts)s",
|
stamp_text="Signé par:\n%(signer_text)s\nLe %(ts)s",
|
||||||
@@ -47,8 +49,8 @@ class SignOrchestrator:
|
|||||||
reason=reason,
|
reason=reason,
|
||||||
)
|
)
|
||||||
|
|
||||||
def sign(self, reason: str, signature_index: int, input_content: Buffer, on_page: int, box_place: (int, int, int, int), signer_text: str) -> io.BytesIO:
|
def sign(self, reason: str, signature_index: int|None, input_content: Buffer, on_page: int, box_place: (int, int, int, int), signer_text: str) -> io.BytesIO:
|
||||||
field_name = 'Signature' + str(signature_index)
|
field_name = 'Signature' + str(signature_index) if signature_index is not None else 'Signature'+ str(randint(1000, 99999999999))
|
||||||
signature_meta = self._make_signature_metadata(reason, field_name)
|
signature_meta = self._make_signature_metadata(reason, field_name)
|
||||||
|
|
||||||
pdf_signer = signers.PdfSigner(
|
pdf_signer = signers.PdfSigner(
|
||||||
|
@@ -1,19 +1,33 @@
|
|||||||
from sign import SignOrchestrator
|
from sign import SignOrchestrator
|
||||||
|
|
||||||
orchestrator = SignOrchestrator('./assets/dummy.p12','http://freetsa.org/tsr', pkcs12_password=None)
|
"""
|
||||||
|
This is a script to sign a file with the dummy assets
|
||||||
|
|
||||||
|
It is created mainly for testing purpose
|
||||||
|
"""
|
||||||
|
|
||||||
|
orchestrator = SignOrchestrator('./assets/dummy.p12',
|
||||||
|
'/home/julien/dev/chill/sign-pdf-worker/ts-authority/rootca.conf',
|
||||||
|
'5678',
|
||||||
|
'/home/julien/dev/chill/sign-pdf-worker/ts-authority/ca/tsa-chain.pem',
|
||||||
|
pkcs12_password=None)
|
||||||
|
|
||||||
with open('./assets/test.pdf', 'rb') as input:
|
with open('./assets/test.pdf', 'rb') as input:
|
||||||
signed_content = orchestrator.sign(reason="first signer", signature_index=0,
|
signed_content = orchestrator.sign(reason="first\nsigner\nreturn\ntext", signature_index=None,
|
||||||
input_content=input.read(), box_place=(300, 600, 500, 660), on_page=0,
|
input_content=input.read(), box_place=(300, 600, 500, 660), on_page=0,
|
||||||
signer_text="Mme Caroline Diallo")
|
signer_text="""Mme Caroline Diallo
|
||||||
|
with
|
||||||
|
return
|
||||||
|
text
|
||||||
|
""")
|
||||||
|
|
||||||
with open('./assets/test_signed_0.pdf', 'wb') as output:
|
with open('./assets/test_signed_0.pdf', 'wb') as output:
|
||||||
output.write(signed_content.read())
|
output.write(signed_content.read())
|
||||||
|
|
||||||
with open('./assets/test_signed_0.pdf', 'rb') as input:
|
with open('./assets/test_signed_0.pdf', 'rb') as input:
|
||||||
signed_content = orchestrator.sign(reason="second signer", signature_index=1,
|
signed_content = orchestrator.sign(reason="second signer", signature_index=None,
|
||||||
input_content=input.read(), box_place=(100, 600, 300, 660), on_page=0,
|
input_content=input.read(), box_place=(100, 600, 300, 660), on_page=0,
|
||||||
signer_text="M. Bah Mamadou")
|
signer_text="M. Bah Mamadou")
|
||||||
|
|
||||||
with open('./assets/test_signed_1.pdf', 'wb') as output:
|
with open('./assets/test_signed_1.pdf', 'wb') as output:
|
||||||
output.write(signed_content.read())
|
output.write(signed_content.read())
|
||||||
|
29
pythonProject/sign_individual_protected_CA.py
Normal file
29
pythonProject/sign_individual_protected_CA.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
from sign import SignOrchestrator
|
||||||
|
|
||||||
|
"""
|
||||||
|
This is a script to sign a file with the dummy assets
|
||||||
|
|
||||||
|
It is created mainly for testing purpose
|
||||||
|
"""
|
||||||
|
|
||||||
|
orchestrator = SignOrchestrator('/run/user/1000/ca/cachet.p12',
|
||||||
|
'/home/julien/dev/chill/sign-pdf-worker/ts-authority/vendee-tsa.conf',
|
||||||
|
'xxxxxxxxxxxxxxxxxxx',
|
||||||
|
'/run/user/1000/ca/tsa-chain.pem',
|
||||||
|
pkcs12_password=b"xxxxxxxxxxxxxxxx")
|
||||||
|
|
||||||
|
with open('./assets/test.pdf', 'rb') as input:
|
||||||
|
signed_content = orchestrator.sign(reason="first signer", signature_index=0,
|
||||||
|
input_content=input.read(), box_place=(300, 600, 500, 660), on_page=0,
|
||||||
|
signer_text="Mme Caroline Diallo")
|
||||||
|
|
||||||
|
with open('./assets/test_signed_0.pdf', 'wb') as output:
|
||||||
|
output.write(signed_content.read())
|
||||||
|
|
||||||
|
with open('./assets/test_signed_0.pdf', 'rb') as input:
|
||||||
|
signed_content = orchestrator.sign(reason="second signer", signature_index=1,
|
||||||
|
input_content=input.read(), box_place=(100, 600, 300, 660), on_page=0,
|
||||||
|
signer_text="M. Bah Mamadou")
|
||||||
|
|
||||||
|
with open('./assets/test_signed_1.pdf', 'wb') as output:
|
||||||
|
output.write(signed_content.read())
|
64
pythonProject/timestamp.py
Normal file
64
pythonProject/timestamp.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from asn1crypto import tsp
|
||||||
|
from asn1crypto.tsp import TimeStampResp
|
||||||
|
from pyhanko.sign.timestamps import TimeStamper
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
LOGGER.setLevel(os.environ.get('LOG_LEVEL', logging.DEBUG))
|
||||||
|
|
||||||
|
|
||||||
|
class LocalOpensslTimestamp(TimeStamper):
|
||||||
|
"""
|
||||||
|
Apply a timestamp using a local openssl cli.
|
||||||
|
|
||||||
|
The class provides methods to request a timestamp response from a local OpenSSL TSA cli.
|
||||||
|
"""
|
||||||
|
def __init__(self, path_config: str, password: str, path_chain: str):
|
||||||
|
super().__init__()
|
||||||
|
self.path_conf = path_config
|
||||||
|
self.password = password
|
||||||
|
self.path_chain = path_chain
|
||||||
|
|
||||||
|
def request_tsa_response(self, req: tsp.TimeStampReq) -> tsp.TimeStampResp:
|
||||||
|
with open('/tmp/request.tsq', 'wb') as request:
|
||||||
|
request.write(req.dump())
|
||||||
|
|
||||||
|
cmd = [
|
||||||
|
'openssl', 'ts', '-reply',
|
||||||
|
'-config', self.path_conf,
|
||||||
|
'-queryfile', '/tmp/request.tsq',
|
||||||
|
'-chain', self.path_chain,
|
||||||
|
'-out', '/tmp/response.tsr',
|
||||||
|
'-passin', f'pass:{self.password}'
|
||||||
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
subprocess.run(cmd, capture_output=True, check=True, timeout=10)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
LOGGER.error("Could not generate a timestamp")
|
||||||
|
LOGGER.error(f"response code: {e.returncode}")
|
||||||
|
LOGGER.error(f"stderr: {e.stderr}")
|
||||||
|
LOGGER.error(f"stdout: {e.stdout}")
|
||||||
|
LOGGER.error(f"error: {e.output}")
|
||||||
|
|
||||||
|
raise Exception("could not generate a timestamp")
|
||||||
|
except subprocess.TimeoutExpired as e:
|
||||||
|
LOGGER.error("timeout expired")
|
||||||
|
LOGGER.error(f"stderr: {e.stderr}")
|
||||||
|
LOGGER.error(f"stdout: {e.stdout}")
|
||||||
|
LOGGER.error(f"error: {e.output}")
|
||||||
|
|
||||||
|
raise e
|
||||||
|
|
||||||
|
with open('/tmp/response.tsr', mode='rb') as f:
|
||||||
|
tsp = TimeStampResp.load(f.read())
|
||||||
|
|
||||||
|
os.unlink('/tmp/response.tsr')
|
||||||
|
|
||||||
|
return tsp
|
||||||
|
|
||||||
|
async def async_request_tsa_response(self, req: tsp.TimeStampReq) -> tsp.TimeStampResp:
|
||||||
|
return self.request_tsa_response(req)
|
@@ -1,5 +1,4 @@
|
|||||||
import base64
|
import base64
|
||||||
import io
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
@@ -13,20 +12,21 @@ logging.basicConfig(level=logging.INFO, format=LOG_FORMAT)
|
|||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
LOGGER.setLevel(os.environ.get('LOG_LEVEL', logging.INFO))
|
LOGGER.setLevel(os.environ.get('LOG_LEVEL', logging.INFO))
|
||||||
|
|
||||||
for v in ['AMQP_URL', 'PKCS12_PATH', 'TIMESTAMP_URL', 'QUEUE_IN', 'EXCHANGE_OUT', 'OUT_ROUTING_KEY']:
|
for v in ['AMQP_URL', 'PKCS12_PATH', 'QUEUE_IN', 'EXCHANGE_OUT', 'OUT_ROUTING_KEY', 'TSA_CONFIG_PATH', 'TSA_CERT_CHAIN', 'TSA_KEY_PASSWORD']:
|
||||||
if v not in os.environ:
|
if v not in os.environ:
|
||||||
LOGGER.error('Missing environment variable: %s', v)
|
LOGGER.error('Missing environment variable: %s', v)
|
||||||
raise ValueError('Missing environment variable: ' + v)
|
raise ValueError('Missing environment variable: ' + v)
|
||||||
|
|
||||||
DSN = os.environ.get('AMQP_URL')
|
DSN = os.environ.get('AMQP_URL')
|
||||||
PKCS12_PATH = os.environ.get('PKCS12_PATH')
|
PKCS12_PATH = os.environ.get('PKCS12_PATH')
|
||||||
TIMESTAMP_URL = os.environ.get('TIMESTAMP_URL')
|
|
||||||
QUEUE_IN = os.environ.get('QUEUE_IN')
|
QUEUE_IN = os.environ.get('QUEUE_IN')
|
||||||
EXCHANGE_OUT = os.environ.get('EXCHANGE_OUT')
|
EXCHANGE_OUT = os.environ.get('EXCHANGE_OUT')
|
||||||
OUT_ROUTING_KEY = os.environ.get('OUT_ROUTING_KEY')
|
OUT_ROUTING_KEY = os.environ.get('OUT_ROUTING_KEY')
|
||||||
|
TSA_CONFIG_PATH = os.environ.get('TSA_CONFIG_PATH')
|
||||||
|
TSA_CERT_CHAIN = os.environ.get('TSA_CERT_CHAIN')
|
||||||
|
TSA_KEY_PASSWORD = os.environ.get('TSA_KEY_PASSWORD')
|
||||||
|
|
||||||
|
orchestrator = sign.SignOrchestrator(PKCS12_PATH, TSA_CONFIG_PATH, TSA_KEY_PASSWORD, TSA_CERT_CHAIN, pkcs12_password=None if os.environ.get('PKCS12_PASSWORD', None) is None else os.environ.get('PKCS12_PASSWORD').encode())
|
||||||
orchestrator = sign.SignOrchestrator(PKCS12_PATH, TIMESTAMP_URL, pkcs12_password=os.environ.get('PKCS12_PASSWORD', None))
|
|
||||||
|
|
||||||
parameters = pika.URLParameters(DSN)
|
parameters = pika.URLParameters(DSN)
|
||||||
connection = pika.BlockingConnection(parameters)
|
connection = pika.BlockingConnection(parameters)
|
||||||
@@ -42,7 +42,7 @@ def on_message(channel, method_frame, header_frame, body):
|
|||||||
try:
|
try:
|
||||||
box_place = (body_content['signatureZone']['x'], body_content['signatureZone']['y'],
|
box_place = (body_content['signatureZone']['x'], body_content['signatureZone']['y'],
|
||||||
body_content['signatureZone']['x'] + body_content['signatureZone']['width'],
|
body_content['signatureZone']['x'] + body_content['signatureZone']['width'],
|
||||||
body_content['signatureZone']['y'] + body_content['signatureZone']['height'])
|
body_content['signatureZone']['y'] - body_content['signatureZone']['height'])
|
||||||
LOGGER.debug("will try signature")
|
LOGGER.debug("will try signature")
|
||||||
signed = orchestrator.sign(reason=body_content['reason'], signature_index=body_content['signatureZoneIndex'],
|
signed = orchestrator.sign(reason=body_content['reason'], signature_index=body_content['signatureZoneIndex'],
|
||||||
box_place=box_place, on_page=body_content['signatureZone']['PDFPage']['index'],
|
box_place=box_place, on_page=body_content['signatureZone']['PDFPage']['index'],
|
||||||
@@ -59,6 +59,7 @@ def on_message(channel, method_frame, header_frame, body):
|
|||||||
|
|
||||||
channel.basic_publish(exchange=EXCHANGE_OUT,
|
channel.basic_publish(exchange=EXCHANGE_OUT,
|
||||||
body=json.dumps({'signatureId': body_content['signatureId'],
|
body=json.dumps({'signatureId': body_content['signatureId'],
|
||||||
|
'signatureZoneIndex': body_content['signatureZoneIndex'],
|
||||||
'content': base64.b64encode(signed.read()).decode('utf-8')}),
|
'content': base64.b64encode(signed.read()).decode('utf-8')}),
|
||||||
properties=pika.BasicProperties(content_type='application/json',
|
properties=pika.BasicProperties(content_type='application/json',
|
||||||
delivery_mode=pika.DeliveryMode.Transient),
|
delivery_mode=pika.DeliveryMode.Transient),
|
||||||
@@ -75,6 +76,7 @@ def on_message(channel, method_frame, header_frame, body):
|
|||||||
else:
|
else:
|
||||||
LOGGER.warning(f"first try failed, signatureId: {body_content['signatureId']}")
|
LOGGER.warning(f"first try failed, signatureId: {body_content['signatureId']}")
|
||||||
channel.basic_ack(delivery_tag=method_frame.delivery_tag)
|
channel.basic_ack(delivery_tag=method_frame.delivery_tag)
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
4
ts-authority/.gitignore
vendored
Normal file
4
ts-authority/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
ca/*
|
||||||
|
!ca/certs/.gitkeep
|
||||||
|
!ca/db/.gitkeep
|
||||||
|
!ca/private/.gitkeep
|
167
ts-authority/README.md
Normal file
167
ts-authority/README.md
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
# Set up CA
|
||||||
|
|
||||||
|
## Sources:
|
||||||
|
|
||||||
|
- https://www.jimby.name/techbits/recent/openssl_tsa/
|
||||||
|
|
||||||
|
## Create directory
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# mkdir certs db private ## should already be created
|
||||||
|
# chmod 700 private ## should already be set
|
||||||
|
touch db/index
|
||||||
|
openssl rand -hex 16 > db/serial
|
||||||
|
echo ‘1001’ > db/crlnumber
|
||||||
|
echo 01 > tsa_serial
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create files for certificate authority
|
||||||
|
|
||||||
|
Create a new private key and root CA certificate request in one step:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openssl req -new -newkey rsa:2048 -config ./rootca.conf -out ca/root-ca.csr -keyout ca/private/root-ca.key
|
||||||
|
```
|
||||||
|
|
||||||
|
Don’t forget the password – you’ll need it again and again below.
|
||||||
|
|
||||||
|
Now self-sign the certificate request.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openssl ca -selfsign -config ./rootca.conf -in ca/root-ca.csr -out ca/root-ca.crt -extensions ca_ext
|
||||||
|
```
|
||||||
|
|
||||||
|
Check the certificate:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openssl x509 -text -in ca/root-ca.crt -noout
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create certificate for timestamp
|
||||||
|
|
||||||
|
To proceed, we first make a key and a certificate request for a non-CA certificate. We use the -subj option so we don’t
|
||||||
|
have to use a configuration file for this step. The Country (C=US) and Organization (O=Example Inc.) elements must
|
||||||
|
match the root certificate.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openssl req -new \
|
||||||
|
-newkey rsa:2048 \
|
||||||
|
-subj "/C=US/O=Example Inc./OU=Engineering/CN=Example Inc. TSA Responder" \
|
||||||
|
-keyout ca/private/tsa.key \
|
||||||
|
-out ca/tsa.csr
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
You should use a different password for the tsa.key private key.
|
||||||
|
|
||||||
|
Then we generate a non-CA certificate using the -extension tsa_ext command line option which points to the required
|
||||||
|
extendedKeyUsage in the configuration file.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openssl ca -config ./rootca.conf -in ca/tsa.csr -out ca/tsa.crt -extensions tsa_ext -days 365
|
||||||
|
```
|
||||||
|
|
||||||
|
Sign with the root-ca.key private key password, and commit to the database.
|
||||||
|
|
||||||
|
Examine the new TSA certificate as follows:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openssl x509 -in ca/tsa.crt -text -noout
|
||||||
|
```
|
||||||
|
|
||||||
|
Ensure that it has CA: false , keyUsage nonRepudiation, and extendedKeyUsage timeStamping.
|
||||||
|
|
||||||
|
Requests to the time stamp service usually require that the reply include the certificate chain of the service. We now create the certificate chain as follows:
|
||||||
|
|
||||||
|
First, extract just the PEM form of the x509 certificates for root-ca.crt and tsa.crt :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openssl x509 -in ca/root-ca.crt -outform PEM -out ca/root-ca.pem
|
||||||
|
openssl x509 -in ca/tsa.crt -outform PEM -out ca/tsa.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, concatenate the two bare certificates ensuring that the root certificate is last in the file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat ca/tsa.pem ca/root-ca.pem > ca/tsa-chain.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
You can verify this chain by just viewing the file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat ca/tsa-chain.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
## Generate a timestamp request
|
||||||
|
|
||||||
|
Ok! We are now ready to create a time stamp request. First, we prepare a query:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openssl ts -query -config ./rootca.conf -cert -data /etc/hosts -out /tmp/request.tsq
|
||||||
|
```
|
||||||
|
|
||||||
|
View the request with
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openssl ts -query -in /tmp/request.tsq -text
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that since we did not request certificate checking (using the -cert option in the request command above), the text
|
||||||
|
output of this command shows “Certificate required: no”. Also, note that we did not specify our own configuration
|
||||||
|
file in the above example.
|
||||||
|
|
||||||
|
If you want to use a stronger digest algorithm, specify it on the command line (sha512 requested here):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openssl ts -query -config ./rootca.conf -data /etc/hosts -out /tmp/request.tsr -sha512
|
||||||
|
```
|
||||||
|
|
||||||
|
## Generating a reply
|
||||||
|
|
||||||
|
We can now process a reply to the the request. Note that the openssl ts -reply sub-command does require a configuration
|
||||||
|
file, including the all the tsa sections. In particular, it uses the tsa_policy1(2,3) options we added at the top of the file.
|
||||||
|
|
||||||
|
Here (and everywhere you utilize the services of the tsa.crt certificate), you must enter the password for the tsa
|
||||||
|
certificate private key.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openssl ts -reply -config ./rootca.conf -queryfile /tmp/request.tsq -chain ca/tsa-chain.pem -out /tmp/response.tsr
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openssl ts -reply -config ./rootca.conf -in /tmp/response.tsr -text
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Openssl can also verify the received timestamp ensuring that the data file or data digest the query was based on still
|
||||||
|
applies to the current version of the file.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openssl ts -verify -queryfile /tmp/request.tsq -in /tmp/response.tsr -CAfile ca/root-ca.pem -untrusted ca/tsa.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
The OK response ensures that the original signed timestamp is correctly authorized by the root and tsa certificates
|
||||||
|
(in PEM format).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openssl ts -verify -data /etc/hosts -in /tmp/response.tsr -CAfile ca/root-ca.pem -untrusted ca/tsa.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
# Préparation à partir d'un certificat d'horodatage au format pkcs12
|
||||||
|
|
||||||
|
## Extraire les infos
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openssl pkcs12 -info -in horodatage.p12 -legacy
|
||||||
|
```
|
||||||
|
|
||||||
|
Ca demandera un mot de passe pour déchiffrer, et un autre mot de passe pour chiffrer la clé qui apparaitra.
|
||||||
|
|
||||||
|
- on recopie la clé et on fait un copier-coller dans /run/user/1000/ca/private/tsa.key
|
||||||
|
- on recopie tous les certificats, on supprime les interligne, et on colle ça dans /run/user/1000/ca/tsa-chain.pem
|
||||||
|
- on recopie le premier certificat, pour céer /run/user/1000/ca/tsa.crt
|
168
ts-authority/rootca.conf
Normal file
168
ts-authority/rootca.conf
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
#
|
||||||
|
# rootca.conf
|
||||||
|
#
|
||||||
|
# See Ristic OpenSSL Cookbook URL above.
|
||||||
|
|
||||||
|
oid_section = new_oids
|
||||||
|
|
||||||
|
[ new_oids ]
|
||||||
|
tsa_policy1 = 1.2.3.4.1
|
||||||
|
tsa_policy2 = 1.2.3.4.5.6
|
||||||
|
tsa_policy3 = 1.2.3.4.5.7
|
||||||
|
|
||||||
|
###### First Part ########
|
||||||
|
|
||||||
|
[default]
|
||||||
|
name = root-ca
|
||||||
|
domain_suffix = example.com
|
||||||
|
aia_url = http://$name.$domain_suffix/$name.crt
|
||||||
|
crl_url = http://$name.$domain_suffix/$name.crl
|
||||||
|
ocsp_url = http://ocsp.$name.$domain_suffix:9080
|
||||||
|
default_ca = ca_default
|
||||||
|
name_opt = utf8,esc_ctrl,multiline,lname,align
|
||||||
|
|
||||||
|
[ca_dn]
|
||||||
|
countryName = "US"
|
||||||
|
organizationName = "Example Inc."
|
||||||
|
commonName = "Root CA"
|
||||||
|
|
||||||
|
###### Second Part #######
|
||||||
|
|
||||||
|
[ca_default]
|
||||||
|
home = .
|
||||||
|
database = $home/ca/db/index
|
||||||
|
serial = $home/ca/db/serial
|
||||||
|
crlnumber = $home/ca/db/crlnumber
|
||||||
|
certificate = $home/ca/$name.crt
|
||||||
|
private_key = $home/ca/private/$name.key
|
||||||
|
RANDFILE = $home/ca/private/random
|
||||||
|
new_certs_dir = $home/ca/certs
|
||||||
|
unique_subject = no
|
||||||
|
copy_extensions = none
|
||||||
|
default_days = 3650
|
||||||
|
default_crl_days = 30
|
||||||
|
default_md = sha256
|
||||||
|
policy = policy_c_o_match
|
||||||
|
name = foo@example.com
|
||||||
|
|
||||||
|
|
||||||
|
[policy_c_o_match]
|
||||||
|
countryName = match
|
||||||
|
stateOrProvinceName = optional
|
||||||
|
organizationName = match
|
||||||
|
organizationalUnitName = optional
|
||||||
|
commonName = supplied
|
||||||
|
emailAddress = optional
|
||||||
|
|
||||||
|
|
||||||
|
##### Third Part #######
|
||||||
|
|
||||||
|
[req]
|
||||||
|
default_bits = 4096
|
||||||
|
encrypt_key = yes
|
||||||
|
default_md = sha256
|
||||||
|
utf8 = yes
|
||||||
|
string_mask = utf8only
|
||||||
|
prompt = no
|
||||||
|
distinguished_name = ca_dn
|
||||||
|
req_extensions = ca_ext
|
||||||
|
|
||||||
|
|
||||||
|
[ca_ext]
|
||||||
|
basicConstraints = critical,CA:true
|
||||||
|
keyUsage = critical,keyCertSign,cRLSign
|
||||||
|
subjectKeyIdentifier = hash
|
||||||
|
|
||||||
|
|
||||||
|
####### Fourth Part - Extensions ########
|
||||||
|
#
|
||||||
|
# Value Meaning - see x509v3.cnf(5)
|
||||||
|
# -------- ------------------------------
|
||||||
|
# serverAuth SSL/TLS web server authentication
|
||||||
|
# clientAuth SSL/TLS web client authentication
|
||||||
|
# codeSigning code signing
|
||||||
|
# emailProtection email protection (S/MIME)
|
||||||
|
# timeStamping trusted doc hash timestamping
|
||||||
|
# OCSPSigning OCSP Signing
|
||||||
|
# ipsecIKE IPsec internet key exchange
|
||||||
|
# msCodeInd Microsoft individual code signing (authenticode)
|
||||||
|
# msCodeCom Microsoft commercial code signing (authenticode)
|
||||||
|
# msCTLSign Microsoft trust list signing
|
||||||
|
# msEFS Microsoft encrypted file system (EFS)
|
||||||
|
|
||||||
|
|
||||||
|
[sub_ca_ext]
|
||||||
|
authorityInfoAccess = @issuer_info
|
||||||
|
authorityKeyIdentifier = keyid:always
|
||||||
|
basicConstraints = critical,CA:true,pathlen:0
|
||||||
|
crlDistributionPoints = @crl_info
|
||||||
|
keyUsage = critical,keyCertSign,cRLSign
|
||||||
|
extendedKeyUsage = clientAuth,serverAuth
|
||||||
|
nameConstraints = @name_constraints
|
||||||
|
subjectKeyIdentifier = hash
|
||||||
|
|
||||||
|
|
||||||
|
[crl_info]
|
||||||
|
URI.0 = $crl_url
|
||||||
|
|
||||||
|
[issuer_info]
|
||||||
|
caIssuers;URI.0 = $aia_url
|
||||||
|
OCSP;URI.0 = $ocsp_url
|
||||||
|
|
||||||
|
[name_constraints]
|
||||||
|
permitted;DNS.0=example.com
|
||||||
|
permitted;DNS.1=example.org
|
||||||
|
excluded;IP.0=0.0.0.0/0.0.0.0
|
||||||
|
excluded;IP.1=0:0:0:0:0:0:0:0/0:0:0:0:0:0:0:0
|
||||||
|
|
||||||
|
|
||||||
|
####### Fifth Part ==========
|
||||||
|
|
||||||
|
|
||||||
|
[ocsp_ext]
|
||||||
|
authorityKeyIdentifier = keyid:always
|
||||||
|
basicConstraints = critical,CA:false
|
||||||
|
extendedKeyUsage = OCSPSigning
|
||||||
|
keyUsage = critical,digitalSignature
|
||||||
|
subjectKeyIdentifier = hash
|
||||||
|
|
||||||
|
|
||||||
|
########### TSA extension ##############
|
||||||
|
#
|
||||||
|
# Copied from the OpenSSL CAtsa.cnf test configuration and modified for use as a TSA extension.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
[ tsa ]
|
||||||
|
|
||||||
|
default_tsa = tsa_config1
|
||||||
|
|
||||||
|
[ tsa_config1 ]
|
||||||
|
dir = /home/julien/dev/chill/sign-pdf-worker/ts-authority # TSA root directory, same as root-ca
|
||||||
|
serial = $dir/ca/tsa_serial # current serial number (mandatory)
|
||||||
|
signer_cert = $dir/ca/tsa.crt # signing certificate (optional)
|
||||||
|
certs = $dir/ca/tsa-chain.pem # certification chain (optional)
|
||||||
|
signer_key = $dir/ca/private/tsa.key # tsa private key (optional)
|
||||||
|
default_policy = tsa_policy1
|
||||||
|
signer_digest = sha256 # digest to use for signing (optional)
|
||||||
|
other_policies = tsa_policy2,tsa_policy3 # other policies (optional)
|
||||||
|
digests = sha256,sha384,sha512 # acceptable digests (mandatory)
|
||||||
|
accuracy = secs:1,millisecs:500,microsecs:100 # accuracy optional
|
||||||
|
ordering = yes # is ordering defined? (optional, default: no)
|
||||||
|
tsa_name = yes # must tsa name be included in reply? (opt., default: no)
|
||||||
|
ess_cert_id_chain = yes # must ess cert id change be incl? (opt., default: no)
|
||||||
|
ess_cert_id_alg = sha256 # alg to compute cert. id (optional, default: sha1)
|
||||||
|
|
||||||
|
# added, was missing in the blog post
|
||||||
|
crypto_device = builtin
|
||||||
|
|
||||||
|
# The tsa_ext extension is
|
||||||
|
# used to create the tsa cert tsa.crt
|
||||||
|
|
||||||
|
[ tsa_ext ]
|
||||||
|
|
||||||
|
authorityKeyIdentifier = keyid:always
|
||||||
|
basicConstraints = critical,CA:false
|
||||||
|
extendedKeyUsage = critical,timeStamping
|
||||||
|
keyUsage = critical,nonRepudiation
|
||||||
|
subjectKeyIdentifier = hash
|
46
ts-authority/vendee-tsa.conf
Normal file
46
ts-authority/vendee-tsa.conf
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#
|
||||||
|
# rootca.conf
|
||||||
|
#
|
||||||
|
# See Ristic OpenSSL Cookbook URL above.
|
||||||
|
|
||||||
|
oid_section = new_oids
|
||||||
|
|
||||||
|
[ new_oids ]
|
||||||
|
tsa_policy1 = 1.2.3.4.1
|
||||||
|
tsa_policy2 = 1.2.3.4.5.6
|
||||||
|
tsa_policy3 = 1.2.3.4.5.7
|
||||||
|
|
||||||
|
|
||||||
|
[ tsa ]
|
||||||
|
|
||||||
|
default_tsa = tsa_config1
|
||||||
|
|
||||||
|
[ tsa_config1 ]
|
||||||
|
dir = /run/user/1000/ca # TSA root directory, same as root-ca
|
||||||
|
serial = $dir/tsa_serial # current serial number (mandatory)
|
||||||
|
signer_cert = $dir/tsa.crt # signing certificate (optional)
|
||||||
|
certs = $dir/tsa-chain.pem # certification chain (optional)
|
||||||
|
signer_key = $dir/private/tsa.key # tsa private key (optional)
|
||||||
|
default_policy = tsa_policy1
|
||||||
|
signer_digest = sha256 # digest to use for signing (optional)
|
||||||
|
other_policies = tsa_policy2,tsa_policy3 # other policies (optional)
|
||||||
|
digests = sha256,sha384,sha512 # acceptable digests (mandatory)
|
||||||
|
accuracy = secs:1,millisecs:500,microsecs:100 # accuracy optional
|
||||||
|
ordering = yes # is ordering defined? (optional, default: no)
|
||||||
|
tsa_name = yes # must tsa name be included in reply? (opt., default: no)
|
||||||
|
ess_cert_id_chain = yes # must ess cert id change be incl? (opt., default: no)
|
||||||
|
ess_cert_id_alg = sha256 # alg to compute cert. id (optional, default: sha1)
|
||||||
|
|
||||||
|
# added, was missing in the blog post
|
||||||
|
crypto_device = builtin
|
||||||
|
|
||||||
|
# The tsa_ext extension is
|
||||||
|
# used to create the tsa cert tsa.crt
|
||||||
|
|
||||||
|
[ tsa_ext ]
|
||||||
|
|
||||||
|
authorityKeyIdentifier = keyid:always
|
||||||
|
basicConstraints = critical,CA:false
|
||||||
|
extendedKeyUsage = critical,timeStamping
|
||||||
|
keyUsage = critical,nonRepudiation
|
||||||
|
subjectKeyIdentifier = hash
|
Reference in New Issue
Block a user