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