Beispiel #1
0
class PdfSigner(object):
    def __init__(self, pdfStream, x509PemStream, rsaPvkPemStream):
        self._pdfStream = pdfStream
        self._x509PemStream = x509PemStream
        self._rsaPvkPemStream = rsaPvkPemStream
        self._outputpdf = StringIO()
        self._newobjectscount = 0
        self._startxref = 0
        self._xref_location = 0
        self._newobjects_collections = {}
        self._newxref = {}
        self._xrefcount = {}
        self._reader = None
        self._trailer = None
        self._catalog = None
        self._objectscount = None
        self._PageParentRef = 0
        self._VObjIdnum = 0
        self._SigObjIdnum = 0
        self._SigDictObj = DictionaryObject()
        self._ByteRange1 = 0
        self._ByteRange2 = 0
        self._ByteRange3 = 0
        self.PKCS7_SIZE = 4098

        self._rebuildPDF()

    def _getObjectsCount(self):
        retval = self._trailer["/Size"]
        return retval

    def _isSigned(self):
        _bIsSigned = False
        if self._catalog.has_key("/AcroForm"):
            acroform = self._catalog["/AcroForm"]
            if acroform.has_key("/SigFlags"):
                n = acroform["/SigFlags"]
                if n == 3:
                    _bIsSigned = True
        return _bIsSigned

    def _rebuildPDF(self):
        self._reader = PdfFileReader(self._pdfStream)
        self._trailer = self._reader.trailer
        self._catalog = self._trailer["/Root"]

        _bIsSigned = self._isSigned()

        if not _bIsSigned:
            output = PdfFileWriter()
            for page in self._reader.pages:
                output.addPage(page)
            output.write(self._outputpdf)
        else:
            self._reader.stream.seek(0, 0)
            self._outputpdf.write(self._reader.stream.read())

        self._reader = PdfFileReader(self._outputpdf)
        self._trailer = self._reader.trailer
        self._catalog = self._trailer["/Root"]
        self._objectscount = self._trailer["/Size"]

    def _addPageAnnots(self, nPageNumber):
        logging.debug("AddPageAnnots")

        # Get Page Parent
        page = self._reader.getPage(nPageNumber)
        pageparent = page["/Parent"]
        kids = pageparent["/Kids"]
        pageIndirect = kids[nPageNumber]
        self._PageParentRef = pageIndirect.idnum

        if page.has_key("/Annots"):
            annots = page["/Annots"]
            annots.append(IndirectObject(self._SigObjIdnum, 0, self))
        else:
            annots = ArrayObject()
            annots.append(IndirectObject(self._SigObjIdnum, 0, self))
            page[NameObject("/Annots")] = annots

        self._newobjects_collections[self._PageParentRef] = page

    def _addAcroFormRef(self):
        # found AcroForm
        if self._catalog.has_key("/AcroForm"):
            acroform = self._catalog["/AcroForm"]
            acroform[NameObject("/SigFlags")] = NumberObject(3)
            acrofields = acroform["/Fields"]
            self._newobjectscount += 0
            self._SigObjIdnum = self._objectscount + self._newobjectscount
            acrofields.append(IndirectObject(self._SigObjIdnum, 0, self))
            #acroform.update({NameObject("/Fields"): acrofields})
            acroformRef = self._catalog.raw_get("/AcroForm")

            # check if AcroForm is a standalone object
            if isinstance(acroformRef, IndirectObject):
                self._newobjects_collections[acroformRef.idnum] = acroform
            else:
                catalogobj = self._trailer.raw_get("/Root")
                self._newobjects_collections[catalogobj.idnum] = self._catalog

        else:  # no AcroForm
            self._newobjectscount += 0
            catalogobj = self._trailer.raw_get("/Root")
            self._catalog.update({
                NameObject("/AcroForm"):
                IndirectObject(self._objectscount + self._newobjectscount, 0,
                               self)
            })
            self._newobjects_collections[catalogobj.idnum] = self._catalog
            self._addNewAcroFormObj()

    def _addNewAcroFormObj(self):
        logging.debug("AddNewAcroFormObj")

        fieldsArray = ArrayObject()
        objcount = self._newobjectscount
        self._newobjectscount += 1
        self._SigObjIdnum = self._objectscount + self._newobjectscount
        fieldsArray.append(IndirectObject(self._SigObjIdnum, 0, self))

        acroform = DictionaryObject()
        acroform[NameObject("/Fields")] = fieldsArray
        acroform[NameObject("/SigFlags")] = NumberObject(3)

        self._newobjects_collections[self._objectscount + objcount] = acroform

    def _addSigFieldObj(self):
        logging.debug("AddDigSigFieldObj")

        self._newobjectscount += 1

        SigFieldObj = DictionaryObject()
        SigFieldObj[NameObject("/Type")] = NameObject("/Annot")
        SigFieldObj[NameObject("/Subtype")] = NameObject("/Widget")
        SigFieldObj[NameObject("/FT")] = NameObject("/Sig")
        SigFieldObj[NameObject("/Ff")] = NumberObject(1)
        SigFieldObj[NameObject("/F")] = NumberObject(132)
        SigName = u"Signature" + str(self._objectscount)
        SigFieldObj[NameObject("/T")] = createStringObject(SigName)
        SigFieldObj[NameObject("/V")] = IndirectObject(
            self._objectscount + self._newobjectscount, 0, self)
        SigFieldObj[NameObject("/P")] = IndirectObject(self._PageParentRef, 0,
                                                       self)

        rect = ArrayObject()
        rect.append(NumberObject(0))
        rect.append(NumberObject(0))
        rect.append(NumberObject(0))
        rect.append(NumberObject(0))

        SigFieldObj[NameObject("/Rect")] = rect

        self._newobjects_collections[self._SigObjIdnum] = SigFieldObj

    def _addSigDictObj(self):
        logging.debug("AddDigSigDictObj")

        brArray = ArrayObject()
        brArray.append(NumberObject(0))
        brArray.append(NumberObject(1000))
        brArray.append(NumberObject(1000))
        brArray.append(NumberObject(1000))

        #sByteRange = ' ' * 30
        #sByteRange += "/ByteRange"
        self._SigDictObj[NameObject("/ByteRange")] = brArray
        self._SigDictObj[NameObject("/Type")] = NameObject("/Sig")
        self._SigDictObj[NameObject("/Name")] = createStringObject(u"asaf")
        self._SigDictObj[NameObject("/Filter")] = NameObject("/Asaf.Doron")
        self._SigDictObj[NameObject("/SubFilter")] = NameObject(
            "/adbe.pkcs7.sha1")
        # get the date
        now = datetime.utcnow()
        strnow = now.strftime('D:%Y%m%d%H%M%S')
        self._SigDictObj[NameObject("/M")] = createStringObject(strnow)

        sContents = '0' * 4096
        self._SigDictObj[NameObject("/Contents")] = NameObject("<" +
                                                               sContents + ">")

        self._VObjIdnum = self._objectscount + self._newobjectscount
        self._newobjects_collections[self._VObjIdnum] = self._SigDictObj

    #def _addSigAPObj(self):
    #logging.debug("AddSigAPObj")

    #SigAPObj = DictionaryObject()

    def _buildNewTrailer(self):
        logging.debug("BuildNewTrailer")

        self._trailer.update({
            NameObject("/Size"):
            NumberObject(self._objectscount + self._newobjectscount + 1),
            NameObject("/Prev"):
            NumberObject(self._startxref)
        })

    def _getStartXref(self):
        logging.debug("GetStartXref")

        stream = self._outputpdf
        # start at the end:
        stream.seek(-1, 2)
        line = ''
        while not line:
            line = self._reader.readNextEndLine(stream)

        # find startxref entry - the location of the xref table
        line = self._reader.readNextEndLine(stream)
        self._startxref = int(line)

    def _writeXref(self):
        logging.debug("WriteXref")

        stream = self._outputpdf
        self._xref_location = stream.tell()
        stream.write("xref\n")

        keyfirst = self._xrefcount.keys()[0]
        val = self._xrefcount[keyfirst]

        if keyfirst == 1:
            stream.write("0 %s\n" % (val + 1))
        else:
            stream.write("0 1\n")

        stream.write("%010d %05d f \n" % (0, 65535))

        for key in sorted(self._xrefcount.iterkeys()):
            val = self._xrefcount[key]

            if key != 1:
                stream.write("%s %s\n" % (key, val))

            for i in range(0, val):
                offset = self._newxref[key + i]
                stream.write("%010d %05d n \n" % (offset, 0))

    def _preSign(self):
        logging.debug("PreSign")

        self._addAcroFormRef()
        #self.AddAcroFormObj()
        self._addPageAnnots(0)
        self._addSigFieldObj()
        self._addSigDictObj()

        self._getStartXref()

        stream = self._reader.stream
        stream.seek(0, 2)

        colkeys = sorted(self._newobjects_collections.keys())
        keytmp = colkeys[0]
        keyprev = colkeys[0]
        nxref = 1
        idx = 0
        # write the new objects
        for key in sorted(self._newobjects_collections.iterkeys()):
            #...do whatever with dict[key]...
            objdict = self._newobjects_collections[key]
            self._newxref[key] = stream.tell()
            stream.write(str(key) + " 0 obj\n")
            objdict.writeToStream(stream, None)
            if key == self._VObjIdnum:
                #scomment = '%' * 30
                scomment = ' ' * 30
                scomment += "\n"
                stream.write(scomment)
            stream.write("\nendobj\n")

            #build xref counter
            if idx > 0:
                if key == keyprev + 1:
                    nxref += 1
                else:
                    self._xrefcount[keytmp] = nxref
                    nxref = 1
                    keytmp = key

            idx += 1
            keyprev = key

        if nxref > 1:
            self._xrefcount[keytmp] = nxref

        # write xref
        self._writeXref()

        # write trailer
        self._buildNewTrailer()
        stream.write("trailer\n")
        self._trailer.writeToStream(stream, None)

        # eof
        stream.write("\nstartxref\n%s\n%%%%EOF\n" % (self._xref_location))

    def _calcByteRange(self):
        logging.debug("CalcByteRange")

        stream = self._outputpdf
        eof = stream.tell()

        VObjXref = self._newxref[self._VObjIdnum]
        stream.seek(VObjXref, 0)

        i = 0  # count of '<'
        j = 0  # how many chars we read

        while True:
            tok = stream.read(1)
            j += 1
            if tok == '<':
                i += 1
            if i == 3:
                break

        self._ByteRange1 = VObjXref + j - 1
        self._ByteRange2 = self._ByteRange1 + self.PKCS7_SIZE
        self._ByteRange3 = eof - self._ByteRange2

        brArray = ArrayObject()
        brArray.append(NumberObject(0))
        brArray.append(NumberObject(self._ByteRange1))
        brArray.append(NumberObject(self._ByteRange2))
        brArray.append(NumberObject(self._ByteRange3))

        len1 = len("1000 1000 1000")
        len2 = len(
            str(self._ByteRange1) + str(self._ByteRange2) +
            str(self._ByteRange3)) + 2

        self._SigDictObj.update({NameObject("/ByteRange"): brArray})

        stream.seek(VObjXref, 0)
        stream.write(str(self._VObjIdnum) + " 0 obj\n")
        self._SigDictObj.writeToStream(stream, None)

        len3 = 30 - len2 + len1
        scomment = '%' * len3
        scomment += "\n"
        stream.write(scomment)

    def _calcSha1(self, block_size):
        logging.debug("calc_sha1")

        stream = StringIO()
        stream1 = self._outputpdf
        stream1.seek(0, 0)
        stream.write(stream1.read(self._ByteRange1))
        stream1.seek(self._ByteRange2, 0)
        stream.write(stream1.read(self._ByteRange3 + 1))
        stream.seek(0, 0)

        sha1 = hashlib.sha1()
        while True:
            data = stream.read(block_size)
            if not data:
                break
            sha1.update(data)

        digest = sha1.digest()

        return digest

    #def _closePDFFile(self):
    #self._outputpdf.close()

    def _insertPks7(self, digest):
        logging.debug("InsertPks7")

        p7 = CMS()
        strp7 = p7.createPKCS7(digest, self._x509PemStream,
                               self._rsaPvkPemStream)
        self._outputpdf.seek(self._ByteRange1 + 1, 0)
        self._outputpdf.write(strp7)

    def Sign(self):
        logging.debug("Sign")

        self._preSign()
        self._calcByteRange()
        digest = self._calcSha1(8192)
        self._insertPks7(digest)

        logging.debug("Sign END")

        return self._outputpdf