forked from x4nth055/pythoncode-tutorials
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsign_pdf.py
225 lines (208 loc) · 9.45 KB
/
sign_pdf.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# Import Libraries
import OpenSSL
import os
import time
import argparse
from PDFNetPython3.PDFNetPython import *
from typing import Tuple
def createKeyPair(type, bits):
"""
Create a public/private key pair
Arguments: Type - Key Type, must be one of TYPE_RSA and TYPE_DSA
bits - Number of bits to use in the key (1024 or 2048 or 4096)
Returns: The public/private key pair in a PKey object
"""
pkey = OpenSSL.crypto.PKey()
pkey.generate_key(type, bits)
return pkey
def create_self_signed_cert(pKey):
"""Create a self signed certificate. This certificate will not require to be signed by a Certificate Authority."""
# Create a self signed certificate
cert = OpenSSL.crypto.X509()
# Common Name (e.g. server FQDN or Your Name)
cert.get_subject().CN = "BASSEM MARJI"
# Serial Number
cert.set_serial_number(int(time.time() * 10))
# Not Before
cert.gmtime_adj_notBefore(0) # Not before
# Not After (Expire after 10 years)
cert.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60)
# Identify issue
cert.set_issuer((cert.get_subject()))
cert.set_pubkey(pKey)
cert.sign(pKey, 'md5') # or cert.sign(pKey, 'sha256')
return cert
def load():
"""Generate the certificate"""
summary = {}
summary['OpenSSL Version'] = OpenSSL.__version__
# Generating a Private Key...
key = createKeyPair(OpenSSL.crypto.TYPE_RSA, 1024)
# PEM encoded
with open('.\static\private_key.pem', 'wb') as pk:
pk_str = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key)
pk.write(pk_str)
summary['Private Key'] = pk_str
# Done - Generating a private key...
# Generating a self-signed client certification...
cert = create_self_signed_cert(pKey=key)
with open('.\static\certificate.cer', 'wb') as cer:
cer_str = OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, cert)
cer.write(cer_str)
summary['Self Signed Certificate'] = cer_str
# Done - Generating a self-signed client certification...
# Generating the public key...
with open('.\static\public_key.pem', 'wb') as pub_key:
pub_key_str = OpenSSL.crypto.dump_publickey(
OpenSSL.crypto.FILETYPE_PEM, cert.get_pubkey())
#print("Public key = ",pub_key_str)
pub_key.write(pub_key_str)
summary['Public Key'] = pub_key_str
# Done - Generating the public key...
# Take a private key and a certificate and combine them into a PKCS12 file.
# Generating a container file of the private key and the certificate...
p12 = OpenSSL.crypto.PKCS12()
p12.set_privatekey(key)
p12.set_certificate(cert)
open('.\static\container.pfx', 'wb').write(p12.export())
# You may convert a PKSC12 file (.pfx) to a PEM format
# Done - Generating a container file of the private key and the certificate...
# To Display A Summary
print("## Initialization Summary ##################################################")
print("\n".join("{}:{}".format(i, j) for i, j in summary.items()))
print("############################################################################")
return True
def sign_file(input_file: str, signatureID: str, x_coordinate: int,
y_coordinate: int, pages: Tuple = None, output_file: str = None
):
"""Sign a PDF file"""
# An output file is automatically generated with the word signed added at its end
if not output_file:
output_file = (os.path.splitext(input_file)[0]) + "_signed.pdf"
# Initialize the library
PDFNet.Initialize()
doc = PDFDoc(input_file)
# Create a signature field
sigField = SignatureWidget.Create(doc, Rect(
x_coordinate, y_coordinate, x_coordinate+100, y_coordinate+50), signatureID)
# Iterate throughout document pages
for page in range(1, (doc.GetPageCount() + 1)):
# If required for specific pages
if pages:
if str(page) not in pages:
continue
pg = doc.GetPage(page)
# Create a signature text field and push it on the page
pg.AnnotPushBack(sigField)
# Signature image
sign_filename = os.path.dirname(
os.path.abspath(__file__)) + "\static\signature.jpg"
# Self signed certificate
pk_filename = os.path.dirname(
os.path.abspath(__file__)) + "\static\container.pfx"
# Retrieve the signature field.
approval_field = doc.GetField(signatureID)
approval_signature_digsig_field = DigitalSignatureField(approval_field)
# Add appearance to the signature field.
img = Image.Create(doc.GetSDFDoc(), sign_filename)
found_approval_signature_widget = SignatureWidget(
approval_field.GetSDFObj())
found_approval_signature_widget.CreateSignatureAppearance(img)
# Prepare the signature and signature handler for signing.
approval_signature_digsig_field.SignOnNextSave(pk_filename, '')
# The signing will be done during the following incremental save operation.
doc.Save(output_file, SDFDoc.e_incremental)
# Develop a Process Summary
summary = {
"Input File": input_file, "Signature ID": signatureID,
"Output File": output_file, "Signature File": sign_filename,
"Certificate File": pk_filename
}
# Printing Summary
print("## Summary ########################################################")
print("\n".join("{}:{}".format(i, j) for i, j in summary.items()))
print("###################################################################")
return True
def sign_folder(**kwargs):
"""Sign all PDF Files within a specified path"""
input_folder = kwargs.get('input_folder')
signatureID = kwargs.get('signatureID')
pages = kwargs.get('pages')
x_coordinate = int(kwargs.get('x_coordinate'))
y_coordinate = int(kwargs.get('y_coordinate'))
# Run in recursive mode
recursive = kwargs.get('recursive')
# Loop though the files within the input folder.
for foldername, dirs, filenames in os.walk(input_folder):
for filename in filenames:
# Check if pdf file
if not filename.endswith('.pdf'):
continue
# PDF File found
inp_pdf_file = os.path.join(foldername, filename)
print("Processing file =", inp_pdf_file)
# Compress Existing file
sign_file(input_file=inp_pdf_file, signatureID=signatureID, x_coordinate=x_coordinate,
y_coordinate=y_coordinate, pages=pages, output_file=None)
if not recursive:
break
def is_valid_path(path):
"""Validates the path inputted and checks whether it is a file path or a folder path"""
if not path:
raise ValueError(f"Invalid Path")
if os.path.isfile(path):
return path
elif os.path.isdir(path):
return path
else:
raise ValueError(f"Invalid Path {path}")
def parse_args():
"""Get user command line parameters"""
parser = argparse.ArgumentParser(description="Available Options")
parser.add_argument('-l', '--load', dest='load', action="store_true",
help="Load the required configurations and create the certificate")
parser.add_argument('-i', '--input_path', dest='input_path', type=is_valid_path,
help="Enter the path of the file or the folder to process")
parser.add_argument('-s', '--signatureID', dest='signatureID',
type=str, help="Enter the ID of the signature")
parser.add_argument('-p', '--pages', dest='pages', type=tuple,
help="Enter the pages to consider e.g.: [1,3]")
parser.add_argument('-x', '--x_coordinate', dest='x_coordinate',
type=int, help="Enter the x coordinate.")
parser.add_argument('-y', '--y_coordinate', dest='y_coordinate',
type=int, help="Enter the y coordinate.")
path = parser.parse_known_args()[0].input_path
if path and os.path.isfile(path):
parser.add_argument('-o', '--output_file', dest='output_file',
type=str, help="Enter a valid output file")
if path and os.path.isdir(path):
parser.add_argument('-r', '--recursive', dest='recursive', default=False, type=lambda x: (
str(x).lower() in ['true', '1', 'yes']), help="Process Recursively or Non-Recursively")
args = vars(parser.parse_args())
# To Display The Command Line Arguments
print("## Command Arguments #################################################")
print("\n".join("{}:{}".format(i, j) for i, j in args.items()))
print("######################################################################")
return args
if __name__ == '__main__':
# Parsing command line arguments entered by user
args = parse_args()
if args['load'] == True:
load()
else:
# If File Path
if os.path.isfile(args['input_path']):
sign_file(
input_file=args['input_path'], signatureID=args['signatureID'],
x_coordinate=int(args['x_coordinate']), y_coordinate=int(args['y_coordinate']),
pages=args['pages'], output_file=args['output_file']
)
# If Folder Path
elif os.path.isdir(args['input_path']):
# Process a folder
sign_folder(
input_folder=args['input_path'], signatureID=args['signatureID'],
x_coordinate=int(args['x_coordinate']), y_coordinate=int(args['y_coordinate']),
pages=args['pages'], recursive=args['recursive']
)