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