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