| from fontTools.misc.textTools import bytesjoin, strjoin, tobytes, tostr, safeEval |
| from fontTools.misc import sstruct |
| from . import DefaultTable |
| import base64 |
| |
| DSIG_HeaderFormat = """ |
| > # big endian |
| ulVersion: L |
| usNumSigs: H |
| usFlag: H |
| """ |
| # followed by an array of usNumSigs DSIG_Signature records |
| DSIG_SignatureFormat = """ |
| > # big endian |
| ulFormat: L |
| ulLength: L # length includes DSIG_SignatureBlock header |
| ulOffset: L |
| """ |
| # followed by an array of usNumSigs DSIG_SignatureBlock records, |
| # each followed immediately by the pkcs7 bytes |
| DSIG_SignatureBlockFormat = """ |
| > # big endian |
| usReserved1: H |
| usReserved2: H |
| cbSignature: l # length of following raw pkcs7 data |
| """ |
| |
| # |
| # NOTE |
| # the DSIG table format allows for SignatureBlocks residing |
| # anywhere in the table and possibly in a different order as |
| # listed in the array after the first table header |
| # |
| # this implementation does not keep track of any gaps and/or data |
| # before or after the actual signature blocks while decompiling, |
| # and puts them in the same physical order as listed in the header |
| # on compilation with no padding whatsoever. |
| # |
| |
| class table_D_S_I_G_(DefaultTable.DefaultTable): |
| |
| def decompile(self, data, ttFont): |
| dummy, newData = sstruct.unpack2(DSIG_HeaderFormat, data, self) |
| assert self.ulVersion == 1, "DSIG ulVersion must be 1" |
| assert self.usFlag & ~1 == 0, "DSIG usFlag must be 0x1 or 0x0" |
| self.signatureRecords = sigrecs = [] |
| for n in range(self.usNumSigs): |
| sigrec, newData = sstruct.unpack2(DSIG_SignatureFormat, newData, SignatureRecord()) |
| assert sigrec.ulFormat == 1, "DSIG signature record #%d ulFormat must be 1" % n |
| sigrecs.append(sigrec) |
| for sigrec in sigrecs: |
| dummy, newData = sstruct.unpack2(DSIG_SignatureBlockFormat, data[sigrec.ulOffset:], sigrec) |
| assert sigrec.usReserved1 == 0, "DSIG signature record #%d usReserverd1 must be 0" % n |
| assert sigrec.usReserved2 == 0, "DSIG signature record #%d usReserverd2 must be 0" % n |
| sigrec.pkcs7 = newData[:sigrec.cbSignature] |
| |
| def compile(self, ttFont): |
| packed = sstruct.pack(DSIG_HeaderFormat, self) |
| headers = [packed] |
| offset = len(packed) + self.usNumSigs * sstruct.calcsize(DSIG_SignatureFormat) |
| data = [] |
| for sigrec in self.signatureRecords: |
| # first pack signature block |
| sigrec.cbSignature = len(sigrec.pkcs7) |
| packed = sstruct.pack(DSIG_SignatureBlockFormat, sigrec) + sigrec.pkcs7 |
| data.append(packed) |
| # update redundant length field |
| sigrec.ulLength = len(packed) |
| # update running table offset |
| sigrec.ulOffset = offset |
| headers.append(sstruct.pack(DSIG_SignatureFormat, sigrec)) |
| offset += sigrec.ulLength |
| if offset % 2: |
| # Pad to even bytes |
| data.append(b'\0') |
| return bytesjoin(headers+data) |
| |
| def toXML(self, xmlWriter, ttFont): |
| xmlWriter.comment("note that the Digital Signature will be invalid after recompilation!") |
| xmlWriter.newline() |
| xmlWriter.simpletag("tableHeader", version=self.ulVersion, numSigs=self.usNumSigs, flag="0x%X" % self.usFlag) |
| for sigrec in self.signatureRecords: |
| xmlWriter.newline() |
| sigrec.toXML(xmlWriter, ttFont) |
| xmlWriter.newline() |
| |
| def fromXML(self, name, attrs, content, ttFont): |
| if name == "tableHeader": |
| self.signatureRecords = [] |
| self.ulVersion = safeEval(attrs["version"]) |
| self.usNumSigs = safeEval(attrs["numSigs"]) |
| self.usFlag = safeEval(attrs["flag"]) |
| return |
| if name == "SignatureRecord": |
| sigrec = SignatureRecord() |
| sigrec.fromXML(name, attrs, content, ttFont) |
| self.signatureRecords.append(sigrec) |
| |
| pem_spam = lambda l, spam = { |
| "-----BEGIN PKCS7-----": True, "-----END PKCS7-----": True, "": True |
| }: not spam.get(l.strip()) |
| |
| def b64encode(b): |
| s = base64.b64encode(b) |
| # Line-break at 76 chars. |
| items = [] |
| while s: |
| items.append(tostr(s[:76])) |
| items.append('\n') |
| s = s[76:] |
| return strjoin(items) |
| |
| class SignatureRecord(object): |
| def __repr__(self): |
| return "<%s: %s>" % (self.__class__.__name__, self.__dict__) |
| |
| def toXML(self, writer, ttFont): |
| writer.begintag(self.__class__.__name__, format=self.ulFormat) |
| writer.newline() |
| writer.write_noindent("-----BEGIN PKCS7-----\n") |
| writer.write_noindent(b64encode(self.pkcs7)) |
| writer.write_noindent("-----END PKCS7-----\n") |
| writer.endtag(self.__class__.__name__) |
| |
| def fromXML(self, name, attrs, content, ttFont): |
| self.ulFormat = safeEval(attrs["format"]) |
| self.usReserved1 = safeEval(attrs.get("reserved1", "0")) |
| self.usReserved2 = safeEval(attrs.get("reserved2", "0")) |
| self.pkcs7 = base64.b64decode(tobytes(strjoin(filter(pem_spam, content)))) |