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