• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from fontTools.misc.textTools import bytesjoin, strjoin, tobytes, tostr, safeEval
2from fontTools.misc import sstruct
3from . import DefaultTable
4import base64
5
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"""
27
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#
39
40
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]
66
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)
87
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()
103
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)
115
116
117pem_spam = lambda l, spam={
118    "-----BEGIN PKCS7-----": True,
119    "-----END PKCS7-----": True,
120    "": True,
121}: not spam.get(l.strip())
122
123
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)
133
134
135class SignatureRecord(object):
136    def __repr__(self):
137        return "<%s: %s>" % (self.__class__.__name__, self.__dict__)
138
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__)
146
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))))
152