def sign(self, datau, udct, key, cert, othercerts, algomd, hsm, timestampurl): startdata = len(datau) fi = io.BytesIO(datau) # read end decrypt prev = pdf.PdfFileReader(fi) if prev.isEncrypted: rc = prev.decrypt(udct["password"]) else: rc = 0 # digest method must remain unchanged from prevoius signatures obj = prev.trailer for k in ("/Root", "/Perms", "/DocMDP", "/Reference"): if k in obj: obj = obj[k] if isinstance(obj, po.ArrayObject): obj = obj[0] obj = obj.getObject() else: obj = None break if obj is not None: algomd = obj["/DigestMethod"][1:].lower() # produce smaller signatures, but must be signed twice aligned = udct.get("aligned", 0) if aligned: zeros = b"00" * aligned else: md = getattr(hashlib, algomd)().digest() contents = signer.sign(None, key, cert, othercerts, algomd, True, md, hsm, False, timestampurl) zeros = contents.hex().encode("utf-8") self.makepdf(prev, udct, algomd, zeros) # if document was encrypted, encrypt this version too if prev.isEncrypted: self.encrypt(prev, udct["password"], rc) else: self._encrypt_key = None # ID[0] is used in password protection, must be unchanged ID = prev.trailer.get("/ID", None) if ID is None: ID = hashlib.md5(repr(time.time()).encode()).digest() else: ID = ID[0] if isinstance(ID, str): ID = ID.encode() self._ID = po.ArrayObject([ po.ByteStringObject(ID), po.ByteStringObject( hashlib.md5(repr(random.random()).encode()).digest()), ]) fo = io.BytesIO() self.write(fo, prev, startdata) datas = fo.getvalue() br = [0, 0, 0, 0] bfrom = (b"[ " + b" ".join([WNumberObject.Format] * 4) + b" ]") % tuple(br) pdfbr1 = datas.find(zeros) pdfbr2 = pdfbr1 + len(zeros) br = [ 0, startdata + pdfbr1 - 1, startdata + pdfbr2 + 1, len(datas) - pdfbr2 - 1, ] bto = b"[%d %d %d %d]" % tuple(br) bto += b" " * (len(bfrom) - len(bto)) assert len(bfrom) == len(bto) datas = datas.replace(bfrom, bto, 1) md = getattr(hashlib, algomd)() md.update(datau) b1 = datas[:br[1] - startdata] b2 = datas[br[2] - startdata:] md.update(b1) md.update(b2) md = md.digest() contents = signer.sign(None, key, cert, othercerts, algomd, True, md, hsm, False, timestampurl) contents = contents.hex().encode("utf-8") if aligned: nb = len(zeros) - len(contents) contents += b"0" * nb assert len(zeros) == len(contents) datas = datas.replace(zeros, contents, 1) return datas
def main(self, fname, password): with open(fname, "rb") as fi: datau = fi.read() startdata = len(datau) fi = io.BytesIO(datau) prev = pdf.PdfFileReader(fi) if prev.isEncrypted: rc = prev.decrypt(password) else: rc = 0 algomd = "sha1" aligned = False obj = prev.trailer for k in ("/Root", "/Perms", "/DocMDP", "/Reference"): if k in obj: obj = obj[k] if isinstance(obj, po.ArrayObject): obj = obj[0] obj = obj.getObject() else: obj = None break if obj is not None: algomd = obj["/DigestMethod"][1:].lower() if aligned: zeros = b"0" * 37888 else: md = getattr(hashlib, algomd)().digest() contents = self.sign(md, algomd) zeros = contents.hex().encode("utf-8") self.makepdf(prev, algomd, zeros) if prev.isEncrypted: self.encrypt(prev, password, rc) else: self._encrypt_key = None ID = prev.trailer.get("/ID", None) if ID is None: ID = po.ByteStringObject( hashlib.md5(repr(time.time()).encode()).digest()) else: ID = ID[0] self._ID = po.ArrayObject([ ID, po.ByteStringObject( hashlib.md5(repr(random.random()).encode()).digest()), ]) fo = io.BytesIO() self.write(fo, prev, startdata) datas = fo.getvalue() br = [0, 0, 0, 0] bfrom = (b"[ " + b" ".join([WNumberObject.Format] * 4) + b" ]") % tuple(br) pdfbr1 = datas.find(zeros) pdfbr2 = pdfbr1 + len(zeros) br = [ 0, startdata + pdfbr1 - 1, startdata + pdfbr2 + 1, len(datas) - pdfbr2 - 1, ] bto = b"[%d %d %d %d]" % tuple(br) bto += b" " * (len(bfrom) - len(bto)) assert len(bfrom) == len(bto) datas = datas.replace(bfrom, bto, 1) md = getattr(hashlib, algomd)() md.update(datau) b1 = datas[:br[1] - startdata] b2 = datas[br[2] - startdata:] md.update(b1) md.update(b2) md = md.digest() contents = self.sign(md, algomd) contents = contents.hex().encode("utf-8") if aligned: nb = len(zeros) - len(contents) contents += b"0" * nb datas = datas.replace(zeros, contents, 1) fname = fname.replace(".pdf", "-signed-pypdf.pdf") with open(fname, "wb") as fp: fp.write(datau) fp.write(datas)