PageRenderTime 36ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/tools/insertkeys.py

https://gitlab.com/drgroovestarr/system_sepolicy
Python | 267 lines | 239 code | 14 blank | 14 comment | 3 complexity | bb048f87e468068d1174de03558ea18a MD5 | raw file
  1. #!/usr/bin/env python
  2. from xml.sax import saxutils, handler, make_parser
  3. from optparse import OptionParser
  4. import ConfigParser
  5. import logging
  6. import base64
  7. import sys
  8. import os
  9. __VERSION = (0, 1)
  10. '''
  11. This tool reads a mac_permissions.xml and replaces keywords in the signature
  12. clause with keys provided by pem files.
  13. '''
  14. class GenerateKeys(object):
  15. def __init__(self, path):
  16. '''
  17. Generates an object with Base16 and Base64 encoded versions of the keys
  18. found in the supplied pem file argument. PEM files can contain multiple
  19. certs, however this seems to be unused in Android as pkg manager grabs
  20. the first cert in the APK. This will however support multiple certs in
  21. the resulting generation with index[0] being the first cert in the pem
  22. file.
  23. '''
  24. self._base64Key = list()
  25. self._base16Key = list()
  26. if not os.path.isfile(path):
  27. sys.exit("Path " + path + " does not exist or is not a file!")
  28. pkFile = open(path, 'rb').readlines()
  29. base64Key = ""
  30. lineNo = 1
  31. certNo = 1
  32. inCert = False
  33. for line in pkFile:
  34. line = line.strip()
  35. # Are we starting the certificate?
  36. if line == "-----BEGIN CERTIFICATE-----":
  37. if inCert:
  38. sys.exit("Encountered another BEGIN CERTIFICATE without END CERTIFICATE on " +
  39. "line: " + str(lineNo))
  40. inCert = True
  41. # Are we ending the ceritifcate?
  42. elif line == "-----END CERTIFICATE-----":
  43. if not inCert:
  44. sys.exit("Encountered END CERTIFICATE before BEGIN CERTIFICATE on line: "
  45. + str(lineNo))
  46. # If we ended the certificate trip the flag
  47. inCert = False
  48. # Sanity check the input
  49. if len(base64Key) == 0:
  50. sys.exit("Empty certficate , certificate "+ str(certNo) + " found in file: "
  51. + path)
  52. # ... and append the certificate to the list
  53. # Base 64 includes uppercase. DO NOT tolower()
  54. self._base64Key.append(base64Key)
  55. try:
  56. # Pkgmanager and setool see hex strings with lowercase, lets be consistent
  57. self._base16Key.append(base64.b16encode(base64.b64decode(base64Key)).lower())
  58. except TypeError:
  59. sys.exit("Invalid certificate, certificate "+ str(certNo) + " found in file: "
  60. + path)
  61. # After adding the key, reset the accumulator as pem files may have subsequent keys
  62. base64Key=""
  63. # And increment your cert number
  64. certNo = certNo + 1
  65. # If we haven't started the certificate, then we should not encounter any data
  66. elif not inCert:
  67. if line is not "":
  68. sys.exit("Detected erroneous line \""+ line + "\" on " + str(lineNo)
  69. + " in pem file: " + path)
  70. # else we have started the certicate and need to append the data
  71. elif inCert:
  72. base64Key += line
  73. else:
  74. # We should never hit this assert, if we do then an unaccounted for state
  75. # was entered that was NOT addressed by the if/elif statements above
  76. assert(False == True)
  77. # The last thing to do before looping up is to increment line number
  78. lineNo = lineNo + 1
  79. def __len__(self):
  80. return len(self._base16Key)
  81. def __str__(self):
  82. return str(self.getBase16Keys())
  83. def getBase16Keys(self):
  84. return self._base16Key
  85. def getBase64Keys(self):
  86. return self._base64Key
  87. class ParseConfig(ConfigParser.ConfigParser):
  88. # This must be lowercase
  89. OPTION_WILDCARD_TAG = "all"
  90. def generateKeyMap(self, target_build_variant, key_directory):
  91. keyMap = dict()
  92. for tag in self.sections():
  93. options = self.options(tag)
  94. for option in options:
  95. # Only generate the key map for debug or release,
  96. # not both!
  97. if option != target_build_variant and \
  98. option != ParseConfig.OPTION_WILDCARD_TAG:
  99. logging.info("Skipping " + tag + " : " + option +
  100. " because target build variant is set to " +
  101. str(target_build_variant))
  102. continue
  103. if tag in keyMap:
  104. sys.exit("Duplicate tag detected " + tag)
  105. tag_path = os.path.expandvars(self.get(tag, option))
  106. path = os.path.join(key_directory, tag_path)
  107. keyMap[tag] = GenerateKeys(path)
  108. # Multiple certificates may exist in
  109. # the pem file. GenerateKeys supports
  110. # this however, the mac_permissions.xml
  111. # as well as PMS do not.
  112. assert len(keyMap[tag]) == 1
  113. return keyMap
  114. class ReplaceTags(handler.ContentHandler):
  115. DEFAULT_TAG = "default"
  116. PACKAGE_TAG = "package"
  117. POLICY_TAG = "policy"
  118. SIGNER_TAG = "signer"
  119. SIGNATURE_TAG = "signature"
  120. TAGS_WITH_CHILDREN = [ DEFAULT_TAG, PACKAGE_TAG, POLICY_TAG, SIGNER_TAG ]
  121. XML_ENCODING_TAG = '<?xml version="1.0" encoding="iso-8859-1"?>'
  122. def __init__(self, keyMap, out=sys.stdout):
  123. handler.ContentHandler.__init__(self)
  124. self._keyMap = keyMap
  125. self._out = out
  126. self._out.write(ReplaceTags.XML_ENCODING_TAG)
  127. self._out.write("<!-- AUTOGENERATED FILE DO NOT MODIFY -->")
  128. self._out.write("<policy>")
  129. def __del__(self):
  130. self._out.write("</policy>")
  131. def startElement(self, tag, attrs):
  132. if tag == ReplaceTags.POLICY_TAG:
  133. return
  134. self._out.write('<' + tag)
  135. for (name, value) in attrs.items():
  136. if name == ReplaceTags.SIGNATURE_TAG and value in self._keyMap:
  137. for key in self._keyMap[value].getBase16Keys():
  138. logging.info("Replacing " + name + " " + value + " with " + key)
  139. self._out.write(' %s="%s"' % (name, saxutils.escape(key)))
  140. else:
  141. self._out.write(' %s="%s"' % (name, saxutils.escape(value)))
  142. if tag in ReplaceTags.TAGS_WITH_CHILDREN:
  143. self._out.write('>')
  144. else:
  145. self._out.write('/>')
  146. def endElement(self, tag):
  147. if tag == ReplaceTags.POLICY_TAG:
  148. return
  149. if tag in ReplaceTags.TAGS_WITH_CHILDREN:
  150. self._out.write('</%s>' % tag)
  151. def characters(self, content):
  152. if not content.isspace():
  153. self._out.write(saxutils.escape(content))
  154. def ignorableWhitespace(self, content):
  155. pass
  156. def processingInstruction(self, target, data):
  157. self._out.write('<?%s %s?>' % (target, data))
  158. if __name__ == "__main__":
  159. # Intentional double space to line up equls signs and opening " for
  160. # readability.
  161. usage = "usage: %prog [options] CONFIG_FILE MAC_PERMISSIONS_FILE [MAC_PERMISSIONS_FILE...]\n"
  162. usage += "This tool allows one to configure an automatic inclusion\n"
  163. usage += "of signing keys into the mac_permision.xml file(s) from the\n"
  164. usage += "pem files. If mulitple mac_permision.xml files are included\n"
  165. usage += "then they are unioned to produce a final version."
  166. version = "%prog " + str(__VERSION)
  167. parser = OptionParser(usage=usage, version=version)
  168. parser.add_option("-v", "--verbose",
  169. action="store_true", dest="verbose", default=False,
  170. help="Print internal operations to stdout")
  171. parser.add_option("-o", "--output", default="stdout", dest="output_file",
  172. metavar="FILE", help="Specify an output file, default is stdout")
  173. parser.add_option("-c", "--cwd", default=os.getcwd(), dest="root",
  174. metavar="DIR", help="Specify a root (CWD) directory to run this from, it" \
  175. "chdirs' AFTER loading the config file")
  176. parser.add_option("-t", "--target-build-variant", default="eng", dest="target_build_variant",
  177. help="Specify the TARGET_BUILD_VARIANT, defaults to eng")
  178. parser.add_option("-d", "--key-directory", default="", dest="key_directory",
  179. help="Specify a parent directory for keys")
  180. (options, args) = parser.parse_args()
  181. if len(args) < 2:
  182. parser.error("Must specify a config file (keys.conf) AND mac_permissions.xml file(s)!")
  183. logging.basicConfig(level=logging.INFO if options.verbose == True else logging.WARN)
  184. # Read the config file
  185. config = ParseConfig()
  186. config.read(args[0])
  187. os.chdir(options.root)
  188. output_file = sys.stdout if options.output_file == "stdout" else open(options.output_file, "w")
  189. logging.info("Setting output file to: " + options.output_file)
  190. # Generate the key list
  191. key_map = config.generateKeyMap(options.target_build_variant.lower(), options.key_directory)
  192. logging.info("Generate key map:")
  193. for k in key_map:
  194. logging.info(k + " : " + str(key_map[k]))
  195. # Generate the XML file with markup replaced with keys
  196. parser = make_parser()
  197. parser.setContentHandler(ReplaceTags(key_map, output_file))
  198. for f in args[1:]:
  199. parser.parse(f)