aboutsummaryrefslogtreecommitdiff
path: root/Lib/fontTools/ttLib/tables/D_S_I_G_.py
blob: d902a29080aff5a275f530c7658d3c9eb4498034 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
from fontTools.misc.textTools import bytesjoin, strjoin, tobytes, tostr, safeEval
from fontTools.misc import sstruct
from . import DefaultTable
import base64

DSIG_HeaderFormat = """
	> # big endian
	ulVersion:      L
	usNumSigs:      H
	usFlag:         H
"""
# followed by an array of usNumSigs DSIG_Signature records
DSIG_SignatureFormat = """
	> # big endian
	ulFormat:       L
	ulLength:       L # length includes DSIG_SignatureBlock header
	ulOffset:       L
"""
# followed by an array of usNumSigs DSIG_SignatureBlock records,
# each followed immediately by the pkcs7 bytes
DSIG_SignatureBlockFormat = """
	> # big endian
	usReserved1:    H
	usReserved2:    H
	cbSignature:    l # length of following raw pkcs7 data
"""

#
# NOTE
# the DSIG table format allows for SignatureBlocks residing
# anywhere in the table and possibly in a different order as
# listed in the array after the first table header
#
# this implementation does not keep track of any gaps and/or data
# before or after the actual signature blocks while decompiling,
# and puts them in the same physical order as listed in the header
# on compilation with no padding whatsoever.
#


class table_D_S_I_G_(DefaultTable.DefaultTable):
    def decompile(self, data, ttFont):
        dummy, newData = sstruct.unpack2(DSIG_HeaderFormat, data, self)
        assert self.ulVersion == 1, "DSIG ulVersion must be 1"
        assert self.usFlag & ~1 == 0, "DSIG usFlag must be 0x1 or 0x0"
        self.signatureRecords = sigrecs = []
        for n in range(self.usNumSigs):
            sigrec, newData = sstruct.unpack2(
                DSIG_SignatureFormat, newData, SignatureRecord()
            )
            assert sigrec.ulFormat == 1, (
                "DSIG signature record #%d ulFormat must be 1" % n
            )
            sigrecs.append(sigrec)
        for sigrec in sigrecs:
            dummy, newData = sstruct.unpack2(
                DSIG_SignatureBlockFormat, data[sigrec.ulOffset :], sigrec
            )
            assert sigrec.usReserved1 == 0, (
                "DSIG signature record #%d usReserverd1 must be 0" % n
            )
            assert sigrec.usReserved2 == 0, (
                "DSIG signature record #%d usReserverd2 must be 0" % n
            )
            sigrec.pkcs7 = newData[: sigrec.cbSignature]

    def compile(self, ttFont):
        packed = sstruct.pack(DSIG_HeaderFormat, self)
        headers = [packed]
        offset = len(packed) + self.usNumSigs * sstruct.calcsize(DSIG_SignatureFormat)
        data = []
        for sigrec in self.signatureRecords:
            # first pack signature block
            sigrec.cbSignature = len(sigrec.pkcs7)
            packed = sstruct.pack(DSIG_SignatureBlockFormat, sigrec) + sigrec.pkcs7
            data.append(packed)
            # update redundant length field
            sigrec.ulLength = len(packed)
            # update running table offset
            sigrec.ulOffset = offset
            headers.append(sstruct.pack(DSIG_SignatureFormat, sigrec))
            offset += sigrec.ulLength
        if offset % 2:
            # Pad to even bytes
            data.append(b"\0")
        return bytesjoin(headers + data)

    def toXML(self, xmlWriter, ttFont):
        xmlWriter.comment(
            "note that the Digital Signature will be invalid after recompilation!"
        )
        xmlWriter.newline()
        xmlWriter.simpletag(
            "tableHeader",
            version=self.ulVersion,
            numSigs=self.usNumSigs,
            flag="0x%X" % self.usFlag,
        )
        for sigrec in self.signatureRecords:
            xmlWriter.newline()
            sigrec.toXML(xmlWriter, ttFont)
        xmlWriter.newline()

    def fromXML(self, name, attrs, content, ttFont):
        if name == "tableHeader":
            self.signatureRecords = []
            self.ulVersion = safeEval(attrs["version"])
            self.usNumSigs = safeEval(attrs["numSigs"])
            self.usFlag = safeEval(attrs["flag"])
            return
        if name == "SignatureRecord":
            sigrec = SignatureRecord()
            sigrec.fromXML(name, attrs, content, ttFont)
            self.signatureRecords.append(sigrec)


pem_spam = lambda l, spam={
    "-----BEGIN PKCS7-----": True,
    "-----END PKCS7-----": True,
    "": True,
}: not spam.get(l.strip())


def b64encode(b):
    s = base64.b64encode(b)
    # Line-break at 76 chars.
    items = []
    while s:
        items.append(tostr(s[:76]))
        items.append("\n")
        s = s[76:]
    return strjoin(items)


class SignatureRecord(object):
    def __repr__(self):
        return "<%s: %s>" % (self.__class__.__name__, self.__dict__)

    def toXML(self, writer, ttFont):
        writer.begintag(self.__class__.__name__, format=self.ulFormat)
        writer.newline()
        writer.write_noindent("-----BEGIN PKCS7-----\n")
        writer.write_noindent(b64encode(self.pkcs7))
        writer.write_noindent("-----END PKCS7-----\n")
        writer.endtag(self.__class__.__name__)

    def fromXML(self, name, attrs, content, ttFont):
        self.ulFormat = safeEval(attrs["format"])
        self.usReserved1 = safeEval(attrs.get("reserved1", "0"))
        self.usReserved2 = safeEval(attrs.get("reserved2", "0"))
        self.pkcs7 = base64.b64decode(tobytes(strjoin(filter(pem_spam, content))))