D_S_I_G_.py revision 816df48e03957e937ff389acdbccd4889d840491
1from __future__ import print_function, division
2from fontTools.misc.py23 import *
3from fontTools.misc.textTools import safeEval
4from fontTools.misc import sstruct
5from . import DefaultTable
6import base64
7
8DSIG_HeaderFormat = """
9	> # big endian
10	ulVersion:      L
11	usNumSigs:      H
12	usFlag:         H
13"""
14# followed by an array of usNumSigs DSIG_Signature records
15DSIG_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
23DSIG_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
42class 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		return bytesjoin(headers+data)
76
77	def toXML(self, xmlWriter, ttFont):
78		xmlWriter.comment("note that the Digital Signature will be invalid after recompilation!")
79		xmlWriter.newline()
80		xmlWriter.simpletag("tableHeader", version=self.ulVersion, numSigs=self.usNumSigs, flag="0x%X" % self.usFlag)
81		for sigrec in self.signatureRecords:
82			xmlWriter.newline()
83			sigrec.toXML(xmlWriter, ttFont)
84		xmlWriter.newline()
85
86	def fromXML(self, name, attrs, content, ttFont):
87		if name == "tableHeader":
88			self.signatureRecords = []
89			self.ulVersion = safeEval(attrs["version"])
90			self.usNumSigs = safeEval(attrs["numSigs"])
91			self.usFlag = safeEval(attrs["flag"])
92			return
93		if name == "SignatureRecord":
94			sigrec = SignatureRecord()
95			sigrec.fromXML(name, attrs, content, ttFont)
96			self.signatureRecords.append(sigrec)
97
98pem_spam = lambda l, spam = {
99	"-----BEGIN PKCS7-----": True, "-----END PKCS7-----": True, "": True
100}: not spam.get(l.strip())
101
102def b64encode(b):
103	s = base64.b64encode(b)
104	# Line-break at 76 chars.
105	items = []
106	while s:
107		items.append(tostr(s[:76]))
108		items.append('\n')
109		s = s[76:]
110	return strjoin(items)
111
112class SignatureRecord:
113	def __repr__(self):
114		return "<%s: %s>" % (self.__class__.__name__, self.__dict__)
115
116	def toXML(self, writer, ttFont):
117		writer.begintag(self.__class__.__name__, format=self.ulFormat)
118		writer.newline()
119		writer.write_noindent("-----BEGIN PKCS7-----\n")
120		writer.write_noindent(b64encode(self.pkcs7))
121		writer.write_noindent("-----END PKCS7-----\n")
122		writer.endtag(self.__class__.__name__)
123
124	def fromXML(self, name, attrs, content, ttFont):
125		self.ulFormat = safeEval(attrs["format"])
126		self.usReserved1 = safeEval(attrs.get("reserved1", "0"))
127		self.usReserved2 = safeEval(attrs.get("reserved2", "0"))
128		self.pkcs7 = base64.b64decode(tobytes(strjoin(filter(pem_spam, content))))
129