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