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