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