D_S_I_G_.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. from fontTools.misc.textTools import bytesjoin, strjoin, tobytes, tostr, safeEval
  2. from fontTools.misc import sstruct
  3. from . import DefaultTable
  4. import base64
  5. DSIG_HeaderFormat = """
  6. > # big endian
  7. ulVersion: L
  8. usNumSigs: H
  9. usFlag: H
  10. """
  11. # followed by an array of usNumSigs DSIG_Signature records
  12. DSIG_SignatureFormat = """
  13. > # big endian
  14. ulFormat: L
  15. ulLength: L # length includes DSIG_SignatureBlock header
  16. ulOffset: L
  17. """
  18. # followed by an array of usNumSigs DSIG_SignatureBlock records,
  19. # each followed immediately by the pkcs7 bytes
  20. DSIG_SignatureBlockFormat = """
  21. > # big endian
  22. usReserved1: H
  23. usReserved2: H
  24. cbSignature: l # length of following raw pkcs7 data
  25. """
  26. #
  27. # NOTE
  28. # the DSIG table format allows for SignatureBlocks residing
  29. # anywhere in the table and possibly in a different order as
  30. # listed in the array after the first table header
  31. #
  32. # this implementation does not keep track of any gaps and/or data
  33. # before or after the actual signature blocks while decompiling,
  34. # and puts them in the same physical order as listed in the header
  35. # on compilation with no padding whatsoever.
  36. #
  37. class table_D_S_I_G_(DefaultTable.DefaultTable):
  38. def decompile(self, data, ttFont):
  39. dummy, newData = sstruct.unpack2(DSIG_HeaderFormat, data, self)
  40. assert self.ulVersion == 1, "DSIG ulVersion must be 1"
  41. assert self.usFlag & ~1 == 0, "DSIG usFlag must be 0x1 or 0x0"
  42. self.signatureRecords = sigrecs = []
  43. for n in range(self.usNumSigs):
  44. sigrec, newData = sstruct.unpack2(
  45. DSIG_SignatureFormat, newData, SignatureRecord()
  46. )
  47. assert sigrec.ulFormat == 1, (
  48. "DSIG signature record #%d ulFormat must be 1" % n
  49. )
  50. sigrecs.append(sigrec)
  51. for sigrec in sigrecs:
  52. dummy, newData = sstruct.unpack2(
  53. DSIG_SignatureBlockFormat, data[sigrec.ulOffset :], sigrec
  54. )
  55. assert sigrec.usReserved1 == 0, (
  56. "DSIG signature record #%d usReserverd1 must be 0" % n
  57. )
  58. assert sigrec.usReserved2 == 0, (
  59. "DSIG signature record #%d usReserverd2 must be 0" % n
  60. )
  61. sigrec.pkcs7 = newData[: sigrec.cbSignature]
  62. def compile(self, ttFont):
  63. packed = sstruct.pack(DSIG_HeaderFormat, self)
  64. headers = [packed]
  65. offset = len(packed) + self.usNumSigs * sstruct.calcsize(DSIG_SignatureFormat)
  66. data = []
  67. for sigrec in self.signatureRecords:
  68. # first pack signature block
  69. sigrec.cbSignature = len(sigrec.pkcs7)
  70. packed = sstruct.pack(DSIG_SignatureBlockFormat, sigrec) + sigrec.pkcs7
  71. data.append(packed)
  72. # update redundant length field
  73. sigrec.ulLength = len(packed)
  74. # update running table offset
  75. sigrec.ulOffset = offset
  76. headers.append(sstruct.pack(DSIG_SignatureFormat, sigrec))
  77. offset += sigrec.ulLength
  78. if offset % 2:
  79. # Pad to even bytes
  80. data.append(b"\0")
  81. return bytesjoin(headers + data)
  82. def toXML(self, xmlWriter, ttFont):
  83. xmlWriter.comment(
  84. "note that the Digital Signature will be invalid after recompilation!"
  85. )
  86. xmlWriter.newline()
  87. xmlWriter.simpletag(
  88. "tableHeader",
  89. version=self.ulVersion,
  90. numSigs=self.usNumSigs,
  91. flag="0x%X" % self.usFlag,
  92. )
  93. for sigrec in self.signatureRecords:
  94. xmlWriter.newline()
  95. sigrec.toXML(xmlWriter, ttFont)
  96. xmlWriter.newline()
  97. def fromXML(self, name, attrs, content, ttFont):
  98. if name == "tableHeader":
  99. self.signatureRecords = []
  100. self.ulVersion = safeEval(attrs["version"])
  101. self.usNumSigs = safeEval(attrs["numSigs"])
  102. self.usFlag = safeEval(attrs["flag"])
  103. return
  104. if name == "SignatureRecord":
  105. sigrec = SignatureRecord()
  106. sigrec.fromXML(name, attrs, content, ttFont)
  107. self.signatureRecords.append(sigrec)
  108. pem_spam = lambda l, spam={
  109. "-----BEGIN PKCS7-----": True,
  110. "-----END PKCS7-----": True,
  111. "": True,
  112. }: not spam.get(l.strip())
  113. def b64encode(b):
  114. s = base64.b64encode(b)
  115. # Line-break at 76 chars.
  116. items = []
  117. while s:
  118. items.append(tostr(s[:76]))
  119. items.append("\n")
  120. s = s[76:]
  121. return strjoin(items)
  122. class SignatureRecord(object):
  123. def __repr__(self):
  124. return "<%s: %s>" % (self.__class__.__name__, self.__dict__)
  125. def toXML(self, writer, ttFont):
  126. writer.begintag(self.__class__.__name__, format=self.ulFormat)
  127. writer.newline()
  128. writer.write_noindent("-----BEGIN PKCS7-----\n")
  129. writer.write_noindent(b64encode(self.pkcs7))
  130. writer.write_noindent("-----END PKCS7-----\n")
  131. writer.endtag(self.__class__.__name__)
  132. def fromXML(self, name, attrs, content, ttFont):
  133. self.ulFormat = safeEval(attrs["format"])
  134. self.usReserved1 = safeEval(attrs.get("reserved1", "0"))
  135. self.usReserved2 = safeEval(attrs.get("reserved2", "0"))
  136. self.pkcs7 = base64.b64decode(tobytes(strjoin(filter(pem_spam, content))))